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 <noreply@anthropic.com>
This commit is contained in:
Lucas Winther
2026-10-30 03:19:40 +01:00
co-authored by Claude Opus 5.5
parent 86416f7637
commit b5e6f63881
+33 -2
View File
@@ -45,6 +45,23 @@ let out: Record<Stem, Audio>;
let secsPerMin = 0;
const sdrs: Record<string, number> = {};
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<Stem, Audio>): 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 () => {