From b5e6f638812abd6b3058bac8212071ee3d4010b9 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Fri, 30 Oct 2026 03:19:40 +0100 Subject: [PATCH] test: tighten demucs separation assertions (ruling R7) Bass absolute SDR floor, guitar/piano order pinned by stem energy, finite + non-silent checks on the mono and 3 s slice paths, and the test.failing comment cites ruling R7. Co-Authored-By: Claude Opus 5.5 --- tests/pipeline/separate.test.ts | 35 +++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/tests/pipeline/separate.test.ts b/tests/pipeline/separate.test.ts index 051ea09..fed24b8 100644 --- a/tests/pipeline/separate.test.ts +++ b/tests/pipeline/separate.test.ts @@ -45,6 +45,23 @@ let out: Record; let secsPerMin = 0; const sdrs: Record = {}; +function energy(a: Audio): number { + let e = 0; + for (const ch of a.channels) for (const v of ch) e += v * v; + return e; +} + +/** Every sample finite, and the stems together carry a real share of the input's energy (not silence). */ +function expectFiniteNonSilent(input: Audio, stems: Record): void { + let total = 0; + for (const s of STEMS) { + for (const ch of stems[s].channels) for (const v of ch) if (!Number.isFinite(v)) throw new Error(`${s}: non-finite sample ${v}`); + total += energy(stems[s]); + } + expect(energy(input)).toBeGreaterThan(0); + expect(total).toBeGreaterThan(0.1 * energy(input)); +} + function sum(...as: Audio[]): Audio { const len = as[0]!.channels[0]!.length; return { sampleRate: 44100, channels: [0, 1].map((c) => { @@ -64,6 +81,9 @@ beforeAll(async () => { sdrs[`${stem}_mixBaseline`] = sdr(oracle(role), data.mix); } sdrs.guitarPlusPiano = sdr(oracle("guitar"), sum(out.guitar, out.piano)); + sdrs.guitarRefVsPianoStem = sdr(oracle("guitar"), out.piano); + sdrs.guitarStemEnergy = energy(out.guitar); + sdrs.pianoStemEnergy = energy(out.piano); console.log(`separation t1b/1: ${secsPerMin.toFixed(1)} s per minute of audio`, sdrs); }, SLOW); @@ -82,12 +102,21 @@ test("separates t1b/1: drums > 3 dB, bass beats the mix, guitar(+piano) > 3 dB a expect(sdrs.drums!).toBeGreaterThan(3); expect(sdrs.drums!).toBeGreaterThan(sdrs.drums_mixBaseline!); expect(sdrs.bass!).toBeGreaterThan(sdrs.bass_mixBaseline!); + expect(sdrs.bass!).toBeGreaterThan(0.5); // absolute floor: a silent estimate scores 0 dB expect(sdrs.guitarPlusPiano!).toBeGreaterThan(3); expect(sdrs.guitarPlusPiano!).toBeGreaterThan(sdrs.guitar_mixBaseline!); }); -// Brief's literal criterion. Unmet by the model on this song (guitar 1.85 dB, bass 1.67 dB); kept as an -// expected failure so it flips loudly if separation improves. Needs a controller ruling. +test("separates t1b/1: guitar and piano stems are not swapped (guitar stem carries more energy)", () => { + // Pins the manifest stem order (… guitar, piano). Energy, not SDR: the piano stem's SDR against the oracle + // guitar (2.55 dB measured) exceeds the guitar stem's (1.85 dB) because both hold parts of the same guitar. + // Measured energies: guitar 19521, piano 12721. + expect(sdrs.guitarStemEnergy!).toBeGreaterThan(sdrs.pianoStemEnergy!); +}); + +// Brief's literal criterion, relaxed by controller ruling R7: HTDemucs-6s routes part of the SONiVOX GM guitar +// into its piano stem (synthetic-timbre gap), so guitar is 1.85 dB and bass 1.67 dB on t1b/1. Kept as an expected +// failure so it flips loudly if separation improves. test.failing("brief criterion: guitar and bass stems each > 3 dB and > mix baseline", () => { expect(sdrs.guitar!).toBeGreaterThan(3); expect(sdrs.guitar!).toBeGreaterThan(sdrs.guitar_mixBaseline!); @@ -100,6 +129,7 @@ test("a 3-second slice (shorter than one segment) returns 3 s stems", async () = const slice: Audio = { sampleRate: 44100, channels: data.mix.channels.map((c) => c.slice(44100, 44100 + n)) }; const out = await separate(slice); for (const s of STEMS) for (const ch of out[s].channels) expect(ch.length).toBe(n); + expectFiniteNonSilent(slice, out); }, SLOW); test("mono input is accepted and returns stereo stems of the same length", async () => { @@ -110,6 +140,7 @@ test("mono input is accepted and returns stereo stems of the same length", async expect(out[s].channels.length).toBe(2); expect(out[s].channels[0]!.length).toBe(n); } + expectFiniteNonSilent(mono, out); }, SLOW); test("rejects sample rates other than 44100", async () => {