feat: stem rendering, fx and mixing

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Sonnet 5.5 committed 2026-10-30 02:48:59 +01:00
1 parent cd82e05d85
commit a1e9ce86a0
6 files changed
+448

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
import type { Audio } from "../core/types.ts";
const PEAK_TARGET = Math.pow(10, -1 / 20);
/** Sum stems (with per-stem gain in dB), then peak-normalise to -1 dBFS. */
export function mixStems(stems: { audio: Audio; gainDb: number }[]): Audio {
if (stems.length === 0) throw new Error("mixStems: no stems");
const sr = stems[0]!.audio.sampleRate;
const nch = Math.max(...stems.map((s) => s.audio.channels.length));
const n = Math.max(...stems.map((s) => s.audio.channels[0]?.length ?? 0));
const out = Array.from({ length: nch }, () => new Float32Array(n));
for (const { audio, gainDb } of stems) {
if (audio.sampleRate !== sr) throw new Error("mixStems: sample rate mismatch");
const g = Math.pow(10, gainDb / 20);
for (let c = 0; c < nch; c++) {
const src = audio.channels[c] ?? audio.channels[0]!;
const dst = out[c]!;
for (let i = 0; i < src.length; i++) dst[i]! += src[i]! * g;
}
}
let peak = 0;
for (const ch of out) for (let i = 0; i < n; i++) peak = Math.max(peak, Math.abs(ch[i]!));
if (peak > 0) {
const k = PEAK_TARGET / peak;
for (const ch of out) for (let i = 0; i < n; i++) ch[i]! *= k;
}
return { sampleRate: sr, channels: out };
}