From 5c7feffd3749ce4b355d0db6334cf39ca94d8d82 Mon Sep 17 00:00:00 2001 From: skal Date: Sun, 15 Feb 2026 23:56:43 +0100 Subject: perf(audio): smooth playback time and RMS-based peak at 60Hz Interpolates audio playback time between callbacks using CLOCK_MONOTONIC for smooth 60Hz updates instead of coarse 8-10Hz steps. Replaces artificial peak decay with true RMS calculation over 50ms window. Ring buffer computes RMS directly on internal buffer without copies for efficiency. All backends updated with get_callback_state() interface for time interpolation. Tests passing (34/34). Co-Authored-By: Claude Sonnet 4.5 --- src/audio/backend/miniaudio_backend.cc | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) (limited to 'src/audio/backend/miniaudio_backend.cc') diff --git a/src/audio/backend/miniaudio_backend.cc b/src/audio/backend/miniaudio_backend.cc index ffa0852..26194c9 100644 --- a/src/audio/backend/miniaudio_backend.cc +++ b/src/audio/backend/miniaudio_backend.cc @@ -13,6 +13,10 @@ // Updated in audio_callback when samples are read from ring buffer volatile float MiniaudioBackend::realtime_peak_ = 0.0f; +// Smooth playback time interpolation +volatile double MiniaudioBackend::last_callback_time_ = 0.0; +volatile int64_t MiniaudioBackend::last_callback_samples_ = 0; + // Static callback for miniaudio (C API requirement) void MiniaudioBackend::audio_callback(ma_device* pDevice, void* pOutput, const void* pInput, @@ -140,6 +144,12 @@ void MiniaudioBackend::audio_callback(ma_device* pDevice, void* pOutput, const int actually_read = ring_buffer->read(fOutput, samples_to_read); + // Update smooth playback time tracking (absolute time, no epoch needed) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + last_callback_time_ = ts.tv_sec + ts.tv_nsec / 1e9; + last_callback_samples_ = ring_buffer->get_total_read(); + #if defined(DEBUG_LOG_RING_BUFFER) if (actually_read < samples_to_read) { DEBUG_RING_BUFFER( @@ -150,22 +160,8 @@ void MiniaudioBackend::audio_callback(ma_device* pDevice, void* pOutput, } #endif /* defined(DEBUG_LOG_RING_BUFFER) */ - // Measure peak of samples being played RIGHT NOW (real-time sync) - // This ensures visual effects trigger at the same moment audio is heard - float frame_peak = 0.0f; - for (int i = 0; i < actually_read; ++i) { - frame_peak = fmaxf(frame_peak, fabsf(fOutput[i])); - } - - // Exponential averaging: instant attack, fast decay - // Decay rate of 0.5 gives ~500ms decay time for 120 BPM music - // (At 128ms callbacks: 0.5^3.9 ≈ 0.07 after ~500ms = 1 beat) - // TODO: Make decay rate configurable based on BPM from tracker/MainSequence - if (frame_peak > realtime_peak_) { - realtime_peak_ = frame_peak; // Attack: instant - } else { - realtime_peak_ *= 0.5f; // Decay: 50% per callback - } + // Peak calculation moved to audio_get_realtime_peak() for RMS-based measurement + // (uses ring buffer peek for accurate time-windowed RMS) } #if defined(DEBUG_LOG_AUDIO) @@ -285,3 +281,9 @@ void MiniaudioBackend::shutdown() { float MiniaudioBackend::get_realtime_peak() { return realtime_peak_; } + +void MiniaudioBackend::get_callback_state(double* out_time, + int64_t* out_samples) { + *out_time = last_callback_time_; + *out_samples = last_callback_samples_; +} -- cgit v1.2.3