feat(04-03): BeatGrid module + enhanced beat rendering + loading states

- Create beats.js with BeatGrid class: load(), getCalibratedBeats(), getCalibratedOnsets(), snap(), hasBeats getter
- BeatGrid manages calibrationOffset and snapEnabled state
- snap() uses 100ms threshold against calibrated beat positions
- Handles API field variants: beats/beat_times, tempo/tempo_bpm
- Refactor timeline.js: replace beatTimes/calibrationOffset/snapEnabled with this.beatGrid
- Add getters/setters for calibrationOffset and snapEnabled (backward compat with app.js)
- render() now shows DETECTING BEATS... loading indicator and error state
- render() draws beat marks via getCalibratedBeats(), onset ticks via getCalibratedOnsets()
- snapToBeat() delegates to beatGrid.snap()
- app.js chains BPM label update after loadBeats() resolves
This commit is contained in:
Claude
2026-04-06 23:48:27 +00:00
parent c37fa6d1f6
commit 287fe16a56
3 changed files with 115 additions and 24 deletions

View File

@@ -273,7 +273,13 @@ function initTimeline() {
const path = `/app/shows/${filename}`;
timeline.audioDuration = audio.duration;
timeline.loadWaveform(path);
timeline.loadBeats(path);
// Load beats and update BPM display when analysis completes
timeline.loadBeats(path).then(() => {
if (timeline.beatGrid.tempoBpm) {
const label = document.getElementById('beat-label');
if (label) label.textContent = `${Math.round(timeline.beatGrid.tempoBpm)} BPM`;
}
});
});
// Zoom slider