diff options
| author | skal <pascal.massimino@gmail.com> | 2026-03-28 18:45:42 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-03-28 20:14:59 +0100 |
| commit | 8bee7577cba9f55be8bc404038d5df959595b989 (patch) | |
| tree | 4a228f02a3345d9e1275b9e0949d2fb5d6d1864a /src/audio | |
| parent | 37df61d1a0dbd5e253f9db778c17c4187e453b8d (diff) | |
fix(audio): fix early timing drift in tracker, use ola_decode_frame in synth
- Replaced chunk_frames truncation accumulation with accurate double-precision integration in audio_render_ahead.
- Updated tracker to use double-precision time representations for exact sample-accurate scheduling.
- Extracted ola_decode_frame to handle per-frame OLA-IDCT synthesis in synth.cc.
- Updated TODO.md for completed audio tasks.
handoff(Claude): Audio timing drift and OLA-IDCT enhancement resolved.
Diffstat (limited to 'src/audio')
| -rw-r--r-- | src/audio/audio_engine.cc | 4 | ||||
| -rw-r--r-- | src/audio/audio_engine.h | 2 | ||||
| -rw-r--r-- | src/audio/ola.cc | 16 | ||||
| -rw-r--r-- | src/audio/ola.h | 6 | ||||
| -rw-r--r-- | src/audio/synth.cc | 16 | ||||
| -rw-r--r-- | src/audio/tracker.cc | 38 | ||||
| -rw-r--r-- | src/audio/tracker.h | 2 |
7 files changed, 41 insertions, 43 deletions
diff --git a/src/audio/audio_engine.cc b/src/audio/audio_engine.cc index 8e47eac..b4c4863 100644 --- a/src/audio/audio_engine.cc +++ b/src/audio/audio_engine.cc @@ -81,8 +81,8 @@ void AudioEngine::load_music_data(const TrackerScore* score, #endif } -void AudioEngine::update(float music_time, float dt) { - current_time_ = music_time; +void AudioEngine::update(double music_time, double dt) { + current_time_ = (float)music_time; // Pre-warm samples needed in next 2 seconds (lazy loading strategy) // TODO: Implement pre-warming based on upcoming pattern triggers diff --git a/src/audio/audio_engine.h b/src/audio/audio_engine.h index cc4c80a..c6fe4db 100644 --- a/src/audio/audio_engine.h +++ b/src/audio/audio_engine.h @@ -21,7 +21,7 @@ class AudioEngine { const AssetId* sample_assets, uint32_t sample_count); // Update loop - void update(float music_time, float dt); + void update(double music_time, double dt); #if !defined(STRIP_ALL) // Timeline seeking (debugging only) diff --git a/src/audio/ola.cc b/src/audio/ola.cc index 738df85..fd5098e 100644 --- a/src/audio/ola.cc +++ b/src/audio/ola.cc @@ -21,14 +21,18 @@ void ola_encode(const float* pcm, int n_samples, float* spec, int num_frames) { } } +void ola_decode_frame(const float* spec_frame, float* overlap, float* out_hop) { + float tmp[DCT_SIZE]; + idct_512(spec_frame, tmp); + for (int j = 0; j < OLA_HOP_SIZE; ++j) + out_hop[j] = tmp[j] + overlap[j]; + for (int j = 0; j < OLA_OVERLAP; ++j) + overlap[j] = tmp[OLA_HOP_SIZE + j]; +} + void ola_decode(const float* spec, int num_frames, float* pcm) { float overlap[OLA_OVERLAP] = {}; - float tmp[DCT_SIZE]; for (int f = 0; f < num_frames; ++f) { - idct_512(spec + f * DCT_SIZE, tmp); - for (int j = 0; j < OLA_HOP_SIZE; ++j) - pcm[f * OLA_HOP_SIZE + j] = tmp[j] + overlap[j]; - for (int j = 0; j < OLA_OVERLAP; ++j) - overlap[j] = tmp[OLA_HOP_SIZE + j]; + ola_decode_frame(spec + f * DCT_SIZE, overlap, pcm + f * OLA_HOP_SIZE); } } diff --git a/src/audio/ola.h b/src/audio/ola.h index 2d6267e..1fb2a4a 100644 --- a/src/audio/ola.h +++ b/src/audio/ola.h @@ -18,3 +18,9 @@ void ola_encode(const float* pcm, int n_samples, float* spec, int num_frames); // Hann at 50% overlap satisfies w[n]+w[n+H]=1 → perfect reconstruction. // pcm must hold num_frames * OLA_HOP_SIZE floats. void ola_decode(const float* spec, int num_frames, float* pcm); + +// Single-frame OLA-IDCT decoder. +// spec_frame: single DCT_SIZE spectral frame. +// overlap: OLA_OVERLAP buffer (read/write). Must be zero-initialized for first frame. +// out_hop: OLA_HOP_SIZE buffer for the resulting time-domain samples. +void ola_decode_frame(const float* spec_frame, float* overlap, float* out_hop); diff --git a/src/audio/synth.cc b/src/audio/synth.cc index 45ced59..9b56069 100644 --- a/src/audio/synth.cc +++ b/src/audio/synth.cc @@ -4,6 +4,7 @@ #include "synth.h" #include "audio/dct.h" +#include "audio/ola.h" #include "util/debug.h" #include <math.h> #include <string.h> // For memset @@ -264,20 +265,7 @@ void synth_render(float* output_buffer, int num_frames) { (v.current_spectral_frame * DCT_SIZE); if (v.ola_mode) { - // OLA-IDCT synthesis (v2): no synthesis window. - // Analysis used Hann; at 50% overlap w[n]+w[n+H]=1 so - // rectangular synthesis gives perfect reconstruction. - float tmp[DCT_SIZE]; - idct_512(spectral_frame, tmp); - // Add saved overlap from previous frame - for (int j = 0; j < OLA_OVERLAP; ++j) - tmp[j] += v.overlap_buf[j]; - // Save new tail as overlap for next frame - for (int j = 0; j < OLA_OVERLAP; ++j) - v.overlap_buf[j] = tmp[OLA_HOP_SIZE + j]; - // Output buffer holds first OLA_HOP_SIZE samples - for (int j = 0; j < OLA_HOP_SIZE; ++j) - v.time_domain_buffer[j] = tmp[j]; + ola_decode_frame(spectral_frame, v.overlap_buf, v.time_domain_buffer); } else { // V1: IDCT directly, no windowing idct_512(spectral_frame, v.time_domain_buffer); diff --git a/src/audio/tracker.cc b/src/audio/tracker.cc index 651c4d2..95b8022 100644 --- a/src/audio/tracker.cc +++ b/src/audio/tracker.cc @@ -20,7 +20,7 @@ static uint32_t g_last_trigger_idx = 0; // Active pattern instance tracking struct ActivePattern { uint16_t pattern_id; - float start_music_time; // When this pattern was triggered (music time) + double start_music_time; // When this pattern was triggered (music time) uint32_t next_event_idx; // Next event to trigger within this pattern bool active; }; @@ -315,21 +315,21 @@ static void trigger_note_event(const TrackerEvent& event, start_offset_samples); } -void tracker_update(float music_time_sec, float dt_music_sec) { +void tracker_update(double music_time_sec, double dt_music_sec) { // Unit-less timing: 1 unit = 4 beats (by convention) - const float BEATS_PER_UNIT = 4.0f; - const float unit_duration_sec = - (BEATS_PER_UNIT / g_tracker_score.bpm) * 60.0f; + const double BEATS_PER_UNIT = 4.0; + const double unit_duration_sec = + (BEATS_PER_UNIT / (double)g_tracker_score.bpm) * 60.0; - const float end_music_time = music_time_sec + dt_music_sec; - const float tempo_scale = synth_get_tempo_scale(); + const double end_music_time = music_time_sec + dt_music_sec; + const double tempo_scale = (double)synth_get_tempo_scale(); // Step 1: Process new pattern triggers while (g_last_trigger_idx < g_tracker_score.num_triggers) { const TrackerPatternTrigger& trigger = g_tracker_score.triggers[g_last_trigger_idx]; - const float trigger_time_sec = trigger.unit_time * unit_duration_sec; + const double trigger_time_sec = (double)trigger.unit_time * unit_duration_sec; if (trigger_time_sec > end_music_time) break; @@ -357,8 +357,8 @@ void tracker_update(float music_time_sec, float dt_music_sec) { // Trigger all events that have passed their unit time while (active.next_event_idx < pattern.num_events) { const TrackerEvent& event = pattern.events[active.next_event_idx]; - const float event_music_time = - active.start_music_time + event.unit_time * unit_duration_sec; + const double event_music_time = + active.start_music_time + (double)event.unit_time * unit_duration_sec; if (event_music_time > end_music_time) break; // This event hasn't reached its time yet @@ -368,7 +368,7 @@ void tracker_update(float music_time_sec, float dt_music_sec) { int sample_offset = 0; if (event_music_time > music_time_sec) { sample_offset = (int)((event_music_time - music_time_sec) / tempo_scale * - RING_BUFFER_SAMPLE_RATE); + (double)RING_BUFFER_SAMPLE_RATE); } // Apply humanization if enabled @@ -378,22 +378,22 @@ void tracker_update(float music_time_sec, float dt_music_sec) { uint32_t event_hash = g_tracker_score.humanize_seed ^ (active.pattern_id << 16) ^ active.next_event_idx; std::minstd_rand rng(event_hash); - std::uniform_real_distribution<float> dist(-1.0f, 1.0f); + std::uniform_real_distribution<double> dist(-1.0, 1.0); // Timing variation: jitter by % of beat duration if (g_tracker_score.timing_variation_pct > 0.0f) { - float beat_sec = 60.0f / g_tracker_score.bpm; - float jitter = dist(rng) * - (g_tracker_score.timing_variation_pct / 100.0f) * + double beat_sec = 60.0 / (double)g_tracker_score.bpm; + double jitter = dist(rng) * + (double)(g_tracker_score.timing_variation_pct / 100.0f) * beat_sec; sample_offset += - (int)(jitter / tempo_scale * RING_BUFFER_SAMPLE_RATE); + (int)(jitter / tempo_scale * (double)RING_BUFFER_SAMPLE_RATE); } // Volume variation: vary by % if (g_tracker_score.volume_variation_pct > 0.0f) { volume_mult += - dist(rng) * (g_tracker_score.volume_variation_pct / 100.0f); + (float)(dist(rng) * (double)(g_tracker_score.volume_variation_pct / 100.0f)); } } @@ -402,8 +402,8 @@ void tracker_update(float music_time_sec, float dt_music_sec) { } // Pattern remains active until full duration elapses - const float pattern_end_time = - active.start_music_time + pattern.unit_length * unit_duration_sec; + const double pattern_end_time = + active.start_music_time + (double)pattern.unit_length * unit_duration_sec; if (pattern_end_time <= end_music_time) { active.active = false; } diff --git a/src/audio/tracker.h b/src/audio/tracker.h index 8e89a4e..f471bd8 100644 --- a/src/audio/tracker.h +++ b/src/audio/tracker.h @@ -45,5 +45,5 @@ extern const uint32_t g_tracker_patterns_count; extern const TrackerScore g_tracker_score; void tracker_init(); -void tracker_update(float music_time_sec, float dt_music_sec); +void tracker_update(double music_time_sec, double dt_music_sec); void tracker_reset(); // Reset tracker state (for tests/seeking) |
