fix(align): calibrated fit (precision, lift, beat-shift check, baseline) and aligner guards (A7, A8)

Fit flags on precision (<0.6) and chance-corrected lift (<0.25), null with <8
recording onsets; old match rate kept as audibleRate (info). Symbolic-chroma
beat/bar-shift check per window. Constant-offset baseline (-2..+2 s) replaces
DTW without manual anchors unless DTW precision is better by >=0.02. Stretch
warning counts in the fit summary. DTW: slope-limited steps, open begin/end
options; per-bar outlier rejection (1/4 beat, Theil-Sen-transported +-3 bar
median); no anchor in the final partial bar. bench real: saved/computed
source, baseline + method, corrupt alignment.json skipped. Parabolic onset
interpolation; warp extrapolation documented; spec amended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-10-30 19:50:48 +01:00
1 parent 3b002059e9
commit 3db87c0317
19 files changed
+1016 -252

No files matched your search

+13
View File
@@ -55,3 +55,16 @@ test("pickOnsets enforces 50 ms min gap and ignores flat input", () => {
expect(pickOnsets(o, 100)).toEqual([0.22, 0.6]);
expect(pickOnsets(new Float32Array(100), 100)).toEqual([]);
});
test("pickOnsets refines peaks to sub-frame times by parabolic interpolation", () => {
const o = new Float32Array(100);
o[30] = 4; o[31] = 8; o[32] = 6; // peak leans right: δ = ½(4 − 6)/(4 − 16 + 6) = 1/6 frame
const got = pickOnsets(o, 50);
expect(got).toHaveLength(1);
expect(got[0]!).toBeCloseTo((31 + 1 / 6) / 50, 9);
// min-gap competition still compares the peak heights, not the interpolated times
const p = new Float32Array(100);
p[40] = 5; p[41] = 3; p[42] = 7; p[43] = 1;
expect(pickOnsets(p, 100)).toHaveLength(1);
expect(pickOnsets(p, 100)[0]!).toBeCloseTo((42 + 0.5 * (3 - 1) / (3 - 14 + 1)) / 100, 9);
});