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 }; }