summaryrefslogtreecommitdiff
path: root/src/audio
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-07 21:43:30 +0100
committerskal <pascal.massimino@gmail.com>2026-02-07 21:43:30 +0100
commit9d91a1b6f5fa26605fb8567e61603d07d2f6fd9e (patch)
treef580a255e1672075b8a3c7f8ea055e4c4b8c10ee /src/audio
parent727177329f833f76683b53570f3268c39b463e86 (diff)
fix(audio): Remove sample offsets - incompatible with tempo scaling
This fixes the irregular timing caused by mixing music time and physical time. ROOT CAUSE (THE REAL BUG): Sample offset calculation was mixing two incompatible time domains: 1. event_trigger_time: in MUSIC TIME (tempo-scaled, can be 2x faster) 2. current_render_time: in PHYSICAL TIME (1:1 with real time, not scaled) When tempo != 1.0, these diverge dramatically: Example at 2.0x tempo: - Music time: 10.0s (advanced 2x faster) - Physical render time: 5.0s (real time elapsed) - Calculated offset: (10.0 - 5.0) * 32000 = 160000 samples = 5 SECONDS! - Result: Event triggers 5 seconds late This caused irregular timing because: - At tempo 1.0x: offsets were roughly correct (domains aligned) - At tempo != 1.0x: offsets were wildly wrong (domains diverged) - Result: Random jitter as tempo changed WHY WAV DUMP WORKED: WAV dump doesn't use tempo scaling (g_tempo_scale = 1.0), so music_time ≈ physical_time and the domains stayed aligned by accident. THE SOLUTION: Remove sample offsets entirely. Trigger events immediately when music_time passes their trigger time. Accept ~16ms quantization (one frame at 60fps). TRADE-OFFS: - Before: Attempted sample-accurate timing (but broken with tempo scaling) - After: ~16ms quantization (acceptable for rhythmic events) - Benefit: Consistent timing across all tempo values - Benefit: Same behavior in WAV dump and miniaudio playback CHANGES: - tracker.cc: Remove offset calculation, always pass offset=0 - Simplify event triggering logic - Add comment explaining why offsets don't work with tempo scaling Previous commits (9cae6f1, 7271773) attempted to fix this with render_time tracking, but missed the fundamental issue: you can't calculate sample offsets when event times and render times are in different time domains. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/audio')
-rw-r--r--src/audio/tracker.cc27
1 files changed, 7 insertions, 20 deletions
diff --git a/src/audio/tracker.cc b/src/audio/tracker.cc
index 1cccc57..fd25e84 100644
--- a/src/audio/tracker.cc
+++ b/src/audio/tracker.cc
@@ -239,10 +239,10 @@ void tracker_update(float music_time_sec) {
}
// Step 2: Update all active patterns and trigger individual events
- // Get current audio RENDER position (write position) for sample-accurate timing
- // This is where we're currently writing to the ring buffer (~400ms ahead of playback)
- const float current_render_time = audio_get_render_time();
- const float SAMPLE_RATE = 32000.0f; // Audio sample rate
+ // NOTE: We trigger events immediately when their time passes (no sample offsets)
+ // This gives ~16ms quantization (60fps) which is acceptable
+ // Sample offsets don't work with tempo scaling because music_time and render_time
+ // are in different time domains (tempo-scaled vs physical)
for (int i = 0; i < MAX_SPECTROGRAMS; ++i) {
if (!g_active_patterns[i].active)
@@ -262,22 +262,9 @@ void tracker_update(float music_time_sec) {
if (event.unit_time > elapsed_units)
break; // This event hasn't reached its time yet
- // Calculate exact trigger time for this event
- const float event_trigger_time = active.start_music_time +
- (event.unit_time * unit_duration_sec);
-
- // Calculate sample-accurate offset from current RENDER position (write pos)
- // This is where we're currently writing to the buffer, not where playback is
- const float time_delta = event_trigger_time - current_render_time;
- int sample_offset = (int)(time_delta * SAMPLE_RATE);
-
- // Clamp to 0 if negative (event is late, play immediately)
- if (sample_offset < 0) {
- sample_offset = 0;
- }
-
- // Trigger this event as an individual voice with sample-accurate timing
- trigger_note_event(event, sample_offset);
+ // Trigger this event immediately (no sample offset)
+ // Timing quantization: ~16ms at 60fps, acceptable for rhythm
+ trigger_note_event(event, 0);
active.next_event_idx++;
}