import { expect, test } from "bun:test"; import { scoreSong, type PipelineOutput } from "../../src/eval/score.ts"; import { secondsToTicks } from "../../src/core/time.ts"; import type { TimedNote, ScoreNote, TruthTrack } from "../../src/core/types.ts"; const flags = { oracleStems: false, oracleTempo: false, oracleNotes: false }; const bpm = 120; // 0.5 s per beat const n = (onset: number, pitch: number, string?: number): TimedNote => ({ onset, offset: onset + 0.5, pitch, string }); const sc = (t: TimedNote[]): ScoreNote[] => t.map((x) => ({ tick: Math.round(secondsToTicks(x.onset, bpm)), dur: Math.round(secondsToTicks(x.offset - x.onset, bpm)), pitch: x.pitch, string: x.string, })); const grid = { bpm, firstDownbeat: 0, beatsPerBar: 4 }; test("two ref guitars, one merged out track", () => { const g1 = [n(0, 64, 1), n(0.5, 62, 2)]; const g2 = [n(0, 40, 6), n(0.5, 45, 5)]; const truth = { bpm, timeSig: [4, 4] as [number, number], tracks: [ { name: "g1", role: "guitar", notes: g1 }, { name: "g2", role: "guitar", notes: g2 } ] as TruthTrack[] }; const merged = [...g1, ...g2]; const out: PipelineOutput = { grid, tracks: [{ name: "o", role: "guitar", notes: merged, scoreNotes: sc(merged) }] }; const s = scoreSong(truth, out, flags); expect(s.guitarMerged.f1).toBe(1); expect(s.guitarMerged.pass).toBe(true); expect(s.partSplit).not.toBeNull(); expect(s.partSplit!.outTracks).toBe(1); expect(s.partSplit!.refTracks).toBe(2); expect(s.partSplit!.f1).toBeCloseTo(0.5, 6); expect(s.rhythm).toEqual({ factor: 1, beatOffset: 0, onBeat: 1, durationOk: 1 }); expect(s.stringAgreement).toBe(1); expect(s.spuriousRate).toBe(0); expect(s.perRole.vocals!.f1).toBeNull(); expect(s.drumsPerClass).toBeNull(); }); test("partSplit null with one ref guitar; rhythm null without guitar", () => { const truth = { bpm, timeSig: [4, 4] as [number, number], tracks: [{ name: "g", role: "guitar", notes: [n(0, 60)] }] as TruthTrack[] }; const s = scoreSong(truth, { grid, tracks: [] }, flags); expect(s.partSplit).toBeNull(); expect(s.guitarMerged.recall).toBe(0); expect(s.guitarMerged.pass).toBe(false); expect(s.rhythm).toBeNull(); expect(s.spuriousRate).toBeNull(); }); test("drums per class", () => { const truth = { bpm, timeSig: [4, 4] as [number, number], tracks: [{ name: "d", role: "drums", notes: [n(0, 36), n(0.5, 38)] }] as TruthTrack[] }; const est = [n(0, 35), n(0.5, 42)]; const s = scoreSong(truth, { grid, tracks: [{ name: "d", role: "drums", notes: est, scoreNotes: [] }] }, flags); expect(s.drumsPerClass!.kick.f1).toBe(1); expect(s.drumsPerClass!.snare.recall).toBe(0); expect(s.drumsPerClass!.hihat.precision).toBe(0); expect(s.drumsPerClass!.tom.f1).toBeNull(); });