Files
STSheets/tests/align/preview.test.ts
T
Lucas WintherandClaude Opus 5.5 3ce58e1e05 perf(align): render previews on worker threads in parallel chunks, send Opus
The MIDI preview render (~2 s for a 5 min song) and auto-align ran synchronously on the
server's event loop, and each preview was a 12.4 MB WAV. Renders now run as 4 parallel time
chunks (2 s pre-roll, 10 ms cross-fade) on worker threads, auto-align on its own worker, and
previews are mono Ogg Opus 48 kb/s via ffmpeg (1.5 MB, same timing as the WAV in Chrome).
?format=wav keeps the PCM output. Swimming: render 2.0 s -> 0.65 s + 0.6 s encode; event loop
blocked 2.0 s -> <0.1 s per render.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 22:04:46 +01:00

55 lines
2.9 KiB
TypeScript

import { test, expect } from "bun:test";
import { planChunks, stitchChunks, MIN_CHUNK_SEC, PREROLL_SEC } from "../../src/align/preview.ts";
import { renderMidi, normalizePeak } from "../../src/align/render.ts";
import { makeWarp } from "../../src/align/warp.ts";
import type { RefNote, RefTrack } from "../../src/align/smf.ts";
test("planChunks covers [0, dur) contiguously, with pre-roll after the first chunk", () => {
const c = planChunks(280, 4);
expect(c).toHaveLength(4);
expect(c[0]!.from).toBe(0);
expect(c[3]!.to).toBe(280);
for (let k = 1; k < 4; k++) {
expect(c[k]!.from).toBe(c[k - 1]!.to);
expect(c[k]!.renderStart).toBeCloseTo(c[k]!.from - PREROLL_SEC, 9);
}
expect(planChunks(MIN_CHUNK_SEC * 1.5, 4)).toHaveLength(1);
expect(planChunks(5, 4)).toEqual([{ renderStart: 0, renderDur: 5, from: 0, to: 5 }]);
});
test("stitchChunks reproduces a signal from overlapping slices of it", () => {
const sr = 1000, dur = 90, n = dur * sr;
const x = Float32Array.from({ length: n }, (_, i) => Math.sin(i / 37) * 0.5);
const chunks = planChunks(dur, 3);
const pcm = chunks.map((c) => x.slice(Math.round(c.renderStart * sr), Math.round((c.renderStart + c.renderDur) * sr)));
const y = stitchChunks(chunks, pcm, sr, dur);
expect(y.length).toBe(n);
let maxErr = 0;
for (let i = 0; i < n; i++) maxErr = Math.max(maxErr, Math.abs(y[i]! - x[i]!));
expect(maxErr).toBeLessThan(1e-6);
});
test("a chunked render matches the whole render (onsets in place, small seam error)", async () => {
const SF2 = new Uint8Array(await Bun.file("node_modules/@coderline/alphatab/dist/soundfont/sonivox.sf2").arrayBuffer());
const notes: RefNote[] = [];
for (let t = 0.25; t < 58; t += 0.5) notes.push({ onset: t, offset: t + 0.4, pitch: 52 + ((t * 2) % 12), velocity: 100, channel: 0 });
notes.push({ onset: 18, offset: 23, pitch: 40, velocity: 100, channel: 0 }); // sustains across the first seam (20 s)
const tracks: RefTrack[] = [{ index: 0, name: "g", program: 25, isDrums: false, notes }];
const warp = makeWarp([{ midiSec: 0, audioSec: 0.3, kind: "manual" }, { midiSec: 50, audioSec: 51, kind: "manual" }]);
const sr = 22050, dur = 60;
const whole = renderMidi(tracks, warp, SF2, { sampleRate: sr, mono: true, durationSec: dur }).channels[0]!;
const chunks = planChunks(dur, 3);
const pcm = chunks.map((c) => renderMidi(tracks, warp, SF2, { sampleRate: sr, mono: true, startSec: c.renderStart, durationSec: c.renderDur, normalize: false }).channels[0]!);
const y = stitchChunks(chunks, pcm, sr, dur);
normalizePeak([y]);
// energy per 50 ms frame agrees closely everywhere (identical away from seams)
const frame = Math.floor(sr / 20);
let worst = 0;
for (let f = 0; f + frame <= y.length; f += frame) {
let a = 0, b = 0;
for (let i = f; i < f + frame; i++) { a += whole[i]! ** 2; b += y[i]! ** 2; }
worst = Math.max(worst, Math.abs(Math.sqrt(a / frame) - Math.sqrt(b / frame)));
}
expect(worst).toBeLessThan(0.05);
}, 60_000);