feat(pipeline): transcribe guitar from guitar+piano+other stems
Demucs-6s scatters guitar energy across its guitar, piano and other stems. Synthetic t1 median guitar F1 rises from 0.39-0.60 to 0.72-0.87; the real 'Last Night on Earth' reference goes 0.390 -> 0.403. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c24c7c979f
commit
0f14fbc574
+16
-2
@@ -40,6 +40,19 @@ export function copyFingering(timed: TimedNote[], assigned: ScoreNote[], grid: G
|
||||
});
|
||||
}
|
||||
|
||||
/** Sample-wise sum of equally shaped audio buffers. */
|
||||
export function sumAudio(...as: Audio[]): Audio {
|
||||
const first = as[0]!;
|
||||
return {
|
||||
sampleRate: first.sampleRate,
|
||||
channels: first.channels.map((c, k) => {
|
||||
const o = new Float32Array(c.length);
|
||||
for (const a of as) { const x = a.channels[k]!; for (let i = 0; i < o.length; i++) o[i]! += x[i]!; }
|
||||
return o;
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function createPipeline(
|
||||
models: { demucs: Uint8Array; basicPitch: Uint8Array },
|
||||
): Promise<(input: PipelineInput, flags: OracleFlags) => Promise<PipelineResult>> {
|
||||
@@ -63,9 +76,10 @@ export async function createPipeline(
|
||||
}
|
||||
} else {
|
||||
const s = await separate(input.mix);
|
||||
warnings.push('demucs "other" stem unused');
|
||||
const mk = (name: string, role: Role, audio: Audio): Job => ({ name, role, audio, truthNotes: null });
|
||||
jobs.push(mk("Guitar", "guitar", s.guitar), mk("Bass", "bass", s.bass), mk("Keys", "keys", s.piano),
|
||||
// Guitar is transcribed from guitar + piano + other: Demucs-6s scatters guitar energy across all three
|
||||
// (synthetic t1 median guitar F1 0.39–0.60 → 0.72–0.87; real "Last Night on Earth" 0.390 → 0.403).
|
||||
jobs.push(mk("Guitar", "guitar", sumAudio(s.guitar, s.piano, s.other)), mk("Bass", "bass", s.bass), mk("Keys", "keys", s.piano),
|
||||
mk("Vocals", "vocals", s.vocals), mk("Drums", "drums", s.drums));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { test, expect, beforeAll } from "bun:test";
|
||||
import { join } from "node:path";
|
||||
import { buildSong, loadSongDir } from "../../src/bench/dataset.ts";
|
||||
import { createPipeline, copyFingering } from "../../src/pipeline/run.ts";
|
||||
import { createPipeline, copyFingering, sumAudio } from "../../src/pipeline/run.ts";
|
||||
import { snapTicks, quantize } from "../../src/pipeline/quantize.ts";
|
||||
import type { TimedNote } from "../../src/core/types.ts";
|
||||
import { truthFromGp } from "../../src/score/gp.ts";
|
||||
@@ -47,9 +47,9 @@ test("t1c/1 full pipeline yields all roles", async () => {
|
||||
const roles = new Set(output.tracks.map((t) => t.role));
|
||||
for (const r of ["guitar", "bass", "keys", "vocals", "drums"]) expect(roles.has(r as never)).toBe(true);
|
||||
expect(gp.length).toBeGreaterThan(0);
|
||||
expect(warnings.some((w) => w.includes("other"))).toBe(true);
|
||||
expect(warnings.some((w) => w.includes("other"))).toBe(false);
|
||||
expect(Math.abs(output.grid.bpm - d.truth.bpm)).toBeLessThan(2);
|
||||
expect(s.guitarMerged.f1!).toBeGreaterThan(0.3);
|
||||
expect(s.guitarMerged.f1!).toBeGreaterThan(0.6); // guitar from guitar+piano+other stems (was 0.57 from the guitar stem alone)
|
||||
}, SLOW);
|
||||
|
||||
test("t1d/1 all oracle flags: two guitar tracks, partSplit and merged F1 === 1", async () => {
|
||||
@@ -90,3 +90,12 @@ test("copyFingering matches by snapped tick + pitch (merged cluster, adjacent re
|
||||
copyFingering(timed, assigned, grid);
|
||||
expect(timed.map((x) => [x.string, x.fret])).toEqual([[2, 0], [2, 0], [3, 240], [1, 0]]);
|
||||
});
|
||||
|
||||
test("sumAudio adds buffers sample-wise", () => {
|
||||
const a = { sampleRate: 10, channels: [new Float32Array([1, 2]), new Float32Array([0, 1])] };
|
||||
const b = { sampleRate: 10, channels: [new Float32Array([3, -2]), new Float32Array([1, 1])] };
|
||||
const s = sumAudio(a, b);
|
||||
expect([...s.channels[0]!]).toEqual([4, 0]);
|
||||
expect([...s.channels[1]!]).toEqual([1, 2]);
|
||||
expect(s.sampleRate).toBe(10);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user