import { test, expect, beforeAll } from "bun:test"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { buildSong, loadSongDir } from "../../src/bench/dataset.ts"; import { estimateGrid, gridFromTruth } from "../../src/pipeline/tempo.ts"; import type { TierId } from "../../src/gen/tiers.ts"; type Row = { id: string; trueBpm: number; estBpm: number; factor: number; beatErrMs: number; barErrMs: number }; const rows: Row[] = []; let root = ""; function circ(x: number, period: number): number { const m = ((x % period) + period) % period; return Math.min(m, period - m); } beforeAll(async () => { root = await mkdtemp(join(tmpdir(), "stsheets-tempo-")); for (const tier of ["t1b", "t1c"] as TierId[]) { for (let seed = 1; seed <= 5; seed++) { const dir = await buildSong(tier, seed, root); const { song, mix } = await loadSongDir(dir); const g = estimateGrid(mix); const beat = 60 / song.bpm; const f = [0.5, 1, 2].find((c) => Math.abs(g.bpm / song.bpm - c) / c < 0.02) ?? 0; // Compare against the estimated beat period so a 2x/0.5x grid is judged on its own beat. const estBeat = 60 / g.bpm; rows.push({ id: `${tier}-${seed}`, trueBpm: song.bpm, estBpm: g.bpm, factor: f, beatErrMs: circ(g.firstDownbeat, estBeat) * 1000, barErrMs: circ(g.firstDownbeat, estBeat * g.beatsPerBar) * 1000, }); void beat; } } console.log(rows.map((r) => `${r.id} true=${r.trueBpm} est=${r.estBpm.toFixed(2)} f=${r.factor} beatErr=${r.beatErrMs.toFixed(1)}ms barErr=${r.barErrMs.toFixed(1)}ms`).join("\n")); }, 600_000); test("gridFromTruth is the oracle grid", () => { expect(gridFromTruth(80, 4)).toEqual({ bpm: 80, firstDownbeat: 0, beatsPerBar: 4 }); }); test("tempo within 2% (allowing 0.5x/1x/2x) in >= 8 of 10 songs", () => { expect(rows.filter((r) => r.factor !== 0).length).toBeGreaterThanOrEqual(8); }); test("beat phase within 50 ms of 0 in >= 8 of 10 songs", () => { expect(rows.filter((r) => r.beatErrMs <= 50).length).toBeGreaterThanOrEqual(8); }); test("report exact 1x and downbeat-phase hit counts", () => { const exact = rows.filter((r) => r.factor === 1).length; const bar = rows.filter((r) => r.factor === 1 && r.barErrMs <= 50).length; console.log(`exact 1x: ${exact}/10, bar-phase right: ${bar}/10`); expect(exact).toBeGreaterThanOrEqual(0); }); import { afterAll } from "bun:test"; afterAll(async () => { if (root) await rm(root, { recursive: true, force: true }); });