summaryrefslogtreecommitdiff
path: root/tools/timeline_editor
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-15 14:45:15 +0100
committerskal <pascal.massimino@gmail.com>2026-02-15 14:45:15 +0100
commitb464969cd1f5dd4dceb996ad8410e2695ab477c4 (patch)
treee763d7e782241ec26b8a32eb4ece350421d08ca8 /tools/timeline_editor
parent5b0d020241c8268c45ec0e197d60bfcfc2b7966b (diff)
docs: document audio WAV drift bug investigation
Root cause: audio_render_ahead() over-renders by 366ms per 10s, causing progressive timing drift in WAV files. Events appear early in viewer. Findings: - Renders 11,733 extra frames over 40s (331,533 vs 319,800 expected) - Ring buffer accumulates excess audio (~19 frames/iteration) - WAV dump reads exact 533 frames but renders ~552 frames per call - Results in -180ms drift at 60 beats visible in timeline viewer Debug changes: - Added render tracking to audio.cc to measure actual vs expected - Added drift printf to tracker.cc for kick/snare timing analysis - Added WAV sample rate detection to timeline viewer See doc/AUDIO_WAV_DRIFT_BUG.md for complete analysis and proposed fixes. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'tools/timeline_editor')
-rw-r--r--tools/timeline_editor/timeline-playback.js21
1 files changed, 20 insertions, 1 deletions
diff --git a/tools/timeline_editor/timeline-playback.js b/tools/timeline_editor/timeline-playback.js
index 1bcdcd0..bfeb75a 100644
--- a/tools/timeline_editor/timeline-playback.js
+++ b/tools/timeline_editor/timeline-playback.js
@@ -67,17 +67,36 @@ export class PlaybackController {
async loadAudioFile(file) {
try {
const arrayBuffer = await file.arrayBuffer();
+
+ // Detect original WAV sample rate before decoding
+ const dataView = new DataView(arrayBuffer);
+ let originalSampleRate = 32000; // Default assumption
+
+ // Parse WAV header to get original sample rate
+ // "RIFF" at 0, "WAVE" at 8, "fmt " at 12, sample rate at 24
+ if (dataView.getUint32(0, false) === 0x52494646 && // "RIFF"
+ dataView.getUint32(8, false) === 0x57415645) { // "WAVE"
+ originalSampleRate = dataView.getUint32(24, true); // Little-endian
+ console.log(`Detected WAV sample rate: ${originalSampleRate}Hz`);
+ }
+
if (!this.state.audioContext) {
this.state.audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
+
this.state.audioBuffer = await this.state.audioContext.decodeAudioData(arrayBuffer);
this.state.audioDuration = this.state.audioBuffer.duration;
+ this.state.originalSampleRate = originalSampleRate;
+ this.state.resampleRatio = this.state.audioContext.sampleRate / originalSampleRate;
+
+ console.log(`AudioContext rate: ${this.state.audioContext.sampleRate}Hz, resample ratio: ${this.state.resampleRatio.toFixed(3)}x`);
+
this.renderWaveform();
this.dom.playbackControls.style.display = 'flex';
this.dom.playbackIndicator.style.display = 'block';
this.dom.clearAudioBtn.disabled = false;
this.dom.replayBtn.disabled = false;
- this.showMessage(`Audio loaded: ${this.state.audioDuration.toFixed(2)}s`, 'success');
+ this.showMessage(`Audio loaded: ${this.state.audioDuration.toFixed(2)}s @ ${originalSampleRate}Hz`, 'success');
this.renderCallback('audioLoaded');
} catch (err) {
this.showMessage(`Error loading audio: ${err.message}`, 'error');