import { test, expect } from "bun:test"; import { fitQuality } from "../../src/align/fit.ts"; test("perfect fit: match rate 1, residual 0, one window per 30 s", () => { const on = [1, 2, 3, 31, 32, 61]; const r = fitQuality(on, on, 75); expect(r.matchRate).toBe(1); expect(r.medianResidualMs).toBe(0); expect(r.windows.map((w) => w.startSec)).toEqual([0, 30, 60]); expect(r.windows.every((w) => !w.flagged && w.matchRate === 1)).toBe(true); }); test("match within ±70 ms only; residual is median |audio − midi| of matched onsets", () => { const midi = [1, 2, 3, 4]; const audio = [1.01, 2.03, 3.069, 4.2]; // last is 200 ms away: unmatched const r = fitQuality(audio, midi, 10); expect(r.matchRate).toBe(0.75); expect(r.medianResidualMs!).toBeCloseTo(30, 6); }); test("nearest recording onset is used and may be shared by several MIDI onsets", () => { const r = fitQuality([1.0], [0.98, 1.0, 1.02], 5); expect(r.matchRate).toBe(1); expect(r.medianResidualMs!).toBeCloseTo(20, 6); }); test("empty window gives nulls and is not flagged", () => { const r = fitQuality([1, 2, 65], [1, 2, 65], 90); expect(r.windows).toHaveLength(3); const w = r.windows[1]!; expect(w).toEqual({ startSec: 30, matchRate: null, medianResidualMs: null, flagged: false }); }); test("no MIDI onsets at all gives overall nulls", () => { const r = fitQuality([1, 2], [], 10); expect(r.matchRate).toBeNull(); expect(r.medianResidualMs).toBeNull(); expect(r.windows).toEqual([{ startSec: 0, matchRate: null, medianResidualMs: null, flagged: false }]); }); test("window flagged when match rate < 0.5", () => { const midi = [1, 2, 3, 4, 31, 32]; const audio = [1, 31, 32]; // window 0: 1/4 matched const r = fitQuality(audio, midi, 60); expect(r.windows[0]!.matchRate).toBe(0.25); expect(r.windows[0]!.flagged).toBe(true); expect(r.windows[1]!.flagged).toBe(false); expect(r.matchRate).toBe(0.5); }); test("window flagged when median residual > 60 ms even with full match", () => { const midi = [1, 2, 3]; const r = fitQuality(midi.map((t) => t + 0.065), midi, 10); expect(r.matchRate).toBe(1); expect(r.windows[0]!.medianResidualMs!).toBeCloseTo(65, 6); expect(r.windows[0]!.flagged).toBe(true); }); test("unsorted and duplicate MIDI onsets: duplicates count once", () => { const r = fitQuality([2, 1], [2, 1, 1, 1], 5); expect(r.matchRate).toBe(1); const s = fitQuality([1], [1, 1, 1, 3], 5); expect(s.matchRate).toBe(0.5); // unique onsets {1, 3} });