Token-protected Bun.serve server (bun run align [--host] [--port] [--url-host] [--root]) with the layout-A page: recording and warped-MIDI lanes on one zoomable timeline, draggable anchors (recording end / MIDI end), stretch tempo labels with warnings, fit-flagged windows, Web Audio playback with mix choice, auto-align, save, reset, undo. API per the task brief; transcode/peaks cache under reference/<id>/. Adds bun run align auto <id>, writeSmf (tests), encodeWav, recordingOnsets/fitFromOnsets, fitSummary and stretch tempo helpers. Co-Authored-By: Claude Opus 5.5 <[email protected]>
26 lines
1.3 KiB
TypeScript
26 lines
1.3 KiB
TypeScript
import { test, expect } from "bun:test";
|
||
import { avgBpm, stretchInfo, stretchLabel } from "../../src/align/tempo.ts";
|
||
import type { Stretch } from "../../src/align/warp.ts";
|
||
|
||
const map = [{ sec: 0, bpm: 120 }, { sec: 10, bpm: 60 }];
|
||
|
||
test("avgBpm: beats over seconds across tempo changes", () => {
|
||
expect(avgBpm(map, 0, 10)).toBeCloseTo(120, 9);
|
||
expect(avgBpm(map, 10, 20)).toBeCloseTo(60, 9);
|
||
expect(avgBpm(map, 5, 15)).toBeCloseTo((10 + 5) / 10 * 60, 9); // 10 beats + 5 beats in 10 s = 90 bpm
|
||
expect(avgBpm([], 0, 4)).toBeCloseTo(120, 9); // implicit 120
|
||
expect(avgBpm(map, 3, 3)).toBeCloseTo(120, 9); // empty span: tempo at that point
|
||
});
|
||
|
||
test("stretchInfo: effective bpm = MIDI bpm / ratio, signed percent", () => {
|
||
const s: Stretch[] = [{ fromMidi: 0, toMidi: 10, ratio: 120 / 121.68, warn: false }, { fromMidi: 10, toMidi: 20, ratio: 60 / 55, warn: true }];
|
||
const [a, b] = stretchInfo(s, map);
|
||
expect(a!.midiBpm).toBeCloseTo(120, 9);
|
||
expect(a!.bpm).toBeCloseTo(121.68, 6);
|
||
expect(a!.pct).toBeCloseTo(1.4, 6);
|
||
expect(b!.bpm).toBeCloseTo(55, 6);
|
||
expect(stretchLabel(a!)).toBe("120.0 → 121.7 bpm (+1.4%)");
|
||
expect(stretchLabel(b!)).toBe("60.0 → 55.0 bpm (−8.3%) ⚠");
|
||
expect(stretchLabel({ ...a!, bpm: 120, pct: 0.01 })).toBe("120.0 → 120.0 bpm");
|
||
});
|