diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-07 21:43:30 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-07 21:43:30 +0100 |
| commit | 9d91a1b6f5fa26605fb8567e61603d07d2f6fd9e (patch) | |
| tree | f580a255e1672075b8a3c7f8ea055e4c4b8c10ee /src/audio | |
| parent | 727177329f833f76683b53570f3268c39b463e86 (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.cc | 27 |
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++; } |
