* feat(gp_autosync): piecewise time-warp helpers + refine_sync onset pass
auto_sync computes per-bar sync points but consumers only ever applied the
scalar bar-1 audio_offset, so any tempo drift between the recording and the
tab's authored tempo accumulated over the song. Add the librosa-free helpers
needed to apply the full piecewise mapping:
- bar_start_times(gp_path): per-bar score times sharing auto_sync's axis
(GPIF bar-resolution map, GP3/4/5 per-tick integration)
- build_warp_anchors(points, bar_starts): monotonic (score, audio) anchors
- warp_time(t, anchors): piecewise-linear map with edge-slope extrapolation
- warp_song_times(song, warp): retime a lib.song.Song in place (notes,
sustains, chords, beats, sections, anchors, handshapes, phrase levels,
tone changes, tempo overrides)
- gp_has_expandable_repeats(gp_path): detects GP3/4/5 repeat/volta/direction
markup whose playback expansion auto_sync's as-written points cannot map
Also implement refine_sync() — the editor's refine-sync endpoint has imported
it since the snapshot but it never existed in lib, so the Refine button 500'd.
It densifies the DTW points to every Nth bar and re-times each with a local
onset phase sweep (radius clamped under half a beat to avoid one-beat locks,
short scoring grid + median residual snap against the first beats). Synthetic
click-track validation: ~13ms mean / ~40ms max error from ±180ms coarse input
across 117-123 BPM recordings of a 120 BPM tab.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* review: Copilot round 1 — normalize bar_start_times GP3/4/5 parse failures to ValueError, document ImportError
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>