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 Winther
2026-10-30 19:50:48 +01:00
co-authored by Claude Opus 5.5
parent 3b002059e9
commit 3db87c0317
19 changed files with 1016 additions and 252 deletions
@@ -93,7 +93,7 @@ aligned MIDI.
starting from the raw MIDI; a "reset to raw MIDI" action exists.
- **Fit quality:** median absolute onset residual (ms) between matched recording
onsets and warped MIDI onsets, overall and per 30 s window; shown on the page
and in benchmark output.
and in benchmark output. (Amended 2026-10-30: see "Fit quality — Amended" below.)
- **Playback of the warped MIDI:** the server re-renders the warped MIDI audio
when anchors change (alphaTab renders a 4-minute song in ~1–2 s); the page
requests it on Play. Waveforms are sent as precomputed peaks, not full audio.
@@ -118,6 +118,49 @@ aligned MIDI.
match rate = share of warped MIDI onsets with a recording onset within ±70 ms,
overall and per 30 s window. A window is flagged "don't trust" when its match
rate < 0.5 or median residual > 60 ms; the page highlights flagged windows.
*(Superseded by the amendment below.)*
- **Fit quality — Amended 2026-10-30 after real-song evidence (rulings A7, A8).**
On real full mixes the onset picker finds ~1.4 attacks/s while a scored MIDI
has ~13 unique onsets/s, so the match rate above caps near 40 % even when
perfectly aligned (every window was flagged on every song), and onset metrics
cannot see whole-beat shifts (they alias onto the grid).
- **Flag metric = precision:** share of recording onsets (onset flux, k = 4,
parabolic sub-frame peak times) with a warped scored-role MIDI onset within
±70 ms; per 30 s window by recording time. A window with < 8 recording
onsets is null and never flagged; otherwise flagged when precision < 0.6.
- **Chance-corrected lift:** precision minus the mean precision with the
warped MIDI shifted by +0.11, +0.23 and +0.37 s (off-grid); reported per
window and overall; a window is also flagged when lift < 0.25.
- **Beat/bar-shift check:** per window, mean chroma cosine between the
recording (features chroma, 10 Hz) and a symbolic MIDI chroma (piano roll of
the warped pitched scored-role notes, no render) at the current alignment
vs the MIDI moved by ±1, ±2 beats and ±1 bar along the MIDI's own
tempo/time-signature grid. Flagged "beat shift suspected: ±k beat(s)/bar"
when a shift beats the current alignment by > 0.05 (the margin keeps
repetitive harmony, where shifts tie, from flagging). "+1 beat" means the
MIDI content belongs one beat later in the recording.
- **Information only:** the old match rate, renamed "MIDI onsets audible as
attacks" (JSON `audibleRate`), and the median residual (recording onsets →
nearest warped MIDI onset).
- **Stretch warnings:** the fit summary (CLI, page, real report) includes the
number of warned stretches (> 5 % tempo change) and of stretches with
|tempo change| > 15 %.
- **Constant-offset baseline:** grid search −2..+2 s in 10 ms steps for the
offset maximising overall precision; precision peaks within 0.1 of the best
are told apart by the symbolic chroma cosine (beat aliasing), and within a
peak the offset with the smallest mean residual is taken. Reported with its
precision. Without manual anchors, `autoAlign` returns the baseline as one
auto anchor (`chosen: "baseline"`) unless the DTW alignment's precision
beats it by ≥ 0.02 (`chosen: "dtw"`); with manual anchors there is never a
substitution.
- **Aligner guards:** both DTW passes use slope-limited steps (Sakoe–Chiba
P = 1: (1,1), (1,2), (2,1)), so the path cannot jump; the first segment may
start late in the recording (open begin) and the final segment uses an open
end (the path may stop before the recording ends, or before the MIDI ends,
each unconsumed MIDI frame costing 1), so a length mismatch is not absorbed
as a tempo ramp. Per-bar offsets more than ¼ beat from the median of their
±3 neighbouring bars are rejected (iterated until stable), and no auto
anchor is placed in the MIDI's final partial bar.
- **Editing model:** dragging an anchor only re-fits its two neighbouring
stretches. Adding or moving a manual anchor deletes the auto anchors in the two
affected stretches (those stretches become plain uniform scaling) until the user