summaryrefslogtreecommitdiff
path: root/src/audio/backend/jittered_audio_backend.cc
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-07 14:00:23 +0100
committerskal <pascal.massimino@gmail.com>2026-02-07 14:00:23 +0100
commita6a7bf0440dbabdc6c994c0fb21a8ac31c27be07 (patch)
tree26663d3d65b110fca618d6fa33c83f7a8d1e362a /src/audio/backend/jittered_audio_backend.cc
parentda1d4e10731789191d8a23e60c3dd35217e6bdb0 (diff)
feat(audio): Add SilentBackend, fix peak measurement, reorganize backends
## Critical Fixes **Peak Measurement Timing:** - Fixed 400ms audio-visual desync by measuring peak at playback time - Added get_realtime_peak() to AudioBackend interface - Implemented real-time measurement in MiniaudioBackend audio callback - Updated main.cc and test_demo.cc to use audio_get_realtime_peak() **Peak Decay Rate:** - Fixed slow decay (0.95 → 0.7 per callback) - Old: 5.76 seconds to fade to 10% (constant flashing in test_demo) - New: 1.15 seconds to fade to 10% (proper visual sync) ## New Features **SilentBackend:** - Test-only backend for testing audio.cc without hardware - Controllable peak for testing edge cases - Tracks frames rendered and voice triggers - Added 7 comprehensive tests covering: - Lifecycle (init/start/shutdown) - Peak control and tracking - Playback time and buffer management - Integration with AudioEngine ## Refactoring **Backend Organization:** - Created src/audio/backend/ directory - Moved all backend implementations to subdirectory - Updated include paths and CMakeLists.txt - Cleaner codebase structure **Code Cleanup:** - Removed unused register_spec_asset() function - Added deprecation note to synth_get_output_peak() ## Testing - All 28 tests passing (100%) - New test: test_silent_backend - Improved audio.cc test coverage significantly ## Documentation - Created PEAK_FIX_SUMMARY.md with technical details - Created TASKS_SUMMARY.md with complete task report Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/audio/backend/jittered_audio_backend.cc')
-rw-r--r--src/audio/backend/jittered_audio_backend.cc118
1 files changed, 118 insertions, 0 deletions
diff --git a/src/audio/backend/jittered_audio_backend.cc b/src/audio/backend/jittered_audio_backend.cc
new file mode 100644
index 0000000..0c1c4a6
--- /dev/null
+++ b/src/audio/backend/jittered_audio_backend.cc
@@ -0,0 +1,118 @@
+// This file is part of the 64k demo project.
+// It implements a test backend that simulates jittered audio consumption.
+
+#if !defined(STRIP_ALL)
+
+#include "jittered_audio_backend.h"
+#include "../audio.h"
+#include "../ring_buffer.h"
+#include <chrono>
+#include <cstdlib>
+#include <cstring>
+#include <random>
+#include <thread>
+
+JitteredAudioBackend::JitteredAudioBackend()
+ : running_(false), should_stop_(false), jitter_ms_(5.0f),
+ base_interval_ms_(10.0f), min_chunk_frames_(256), max_chunk_frames_(1024),
+ total_frames_consumed_(0), underrun_count_(0) {
+}
+
+JitteredAudioBackend::~JitteredAudioBackend() {
+ shutdown();
+}
+
+void JitteredAudioBackend::init() {
+ // Nothing to initialize
+}
+
+void JitteredAudioBackend::start() {
+ if (running_.load())
+ return;
+
+ should_stop_.store(false);
+ running_.store(true);
+
+ // Start audio thread
+ audio_thread_ = std::thread(&JitteredAudioBackend::audio_thread_loop, this);
+}
+
+void JitteredAudioBackend::shutdown() {
+ if (!running_.load())
+ return;
+
+ should_stop_.store(true);
+
+ if (audio_thread_.joinable()) {
+ audio_thread_.join();
+ }
+
+ running_.store(false);
+}
+
+void JitteredAudioBackend::set_jitter_amount(float jitter_ms) {
+ jitter_ms_ = jitter_ms;
+}
+
+void JitteredAudioBackend::set_base_interval(float interval_ms) {
+ base_interval_ms_ = interval_ms;
+}
+
+void JitteredAudioBackend::set_chunk_size_range(int min_frames,
+ int max_frames) {
+ min_chunk_frames_ = min_frames;
+ max_chunk_frames_ = max_frames;
+}
+
+void JitteredAudioBackend::audio_thread_loop() {
+ AudioRingBuffer* ring_buffer = audio_get_ring_buffer();
+ if (ring_buffer == nullptr)
+ return;
+
+ // Random number generator for jitter
+ std::random_device rd;
+ std::mt19937 gen(rd());
+ std::uniform_real_distribution<float> jitter_dist(-jitter_ms_, jitter_ms_);
+ std::uniform_int_distribution<int> chunk_dist(min_chunk_frames_,
+ max_chunk_frames_);
+
+ while (!should_stop_.load()) {
+ // Calculate jittered wait time
+ const float jitter = jitter_dist(gen);
+ const float wait_ms = base_interval_ms_ + jitter;
+ const int wait_us = (int)(wait_ms * 1000.0f);
+
+ if (wait_us > 0) {
+ std::this_thread::sleep_for(std::chrono::microseconds(wait_us));
+ }
+
+ // Random chunk size
+ const int chunk_frames = chunk_dist(gen);
+ const int chunk_samples = chunk_frames * 2; // Stereo
+
+ // Read from ring buffer
+ float* temp_buffer = new float[chunk_samples];
+ const int read_samples = ring_buffer->read(temp_buffer, chunk_samples);
+
+ // Check for underrun
+ if (read_samples < chunk_samples) {
+ underrun_count_.fetch_add(1);
+ }
+
+ // Track consumption
+ total_frames_consumed_.fetch_add(read_samples / 2);
+
+ // Notify of frames rendered (for tracking)
+ on_frames_rendered(read_samples / 2);
+
+ delete[] temp_buffer;
+ }
+}
+
+float JitteredAudioBackend::get_realtime_peak() {
+ // Jittered backend: No real-time playback, return 0
+ // This backend simulates timing jitter but doesn't play audio
+ return 0.0f;
+}
+
+#endif /* !defined(STRIP_ALL) */