summaryrefslogtreecommitdiff
path: root/src/audio
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-05 20:18:28 +0100
committerskal <pascal.massimino@gmail.com>2026-02-05 20:18:28 +0100
commit12816810855883472ecab454f9c0d08d66f0ae52 (patch)
tree37e294d82cfe7c6cb887ed774268e6243fae0c77 /src/audio
parent3ba0d20354a67b9fc62d29d13bc283c18130bbb9 (diff)
feat(audio): Complete Task #56 - Audio Lifecycle Refactor (All Phases)
SUMMARY ======= Successfully completed comprehensive 4-phase refactor of audio subsystem to eliminate fragile initialization order dependency between synth and tracker. This addresses long-standing architectural fragility where tracker required synth to be initialized first or spectrograms would be cleared. IMPLEMENTATION ============== Phase 1: Design & Prototype - Created AudioEngine class as unified audio subsystem manager - Created SpectrogramResourceManager for lazy resource loading - Manages synth, tracker, and resource lifecycle - Comprehensive test suite (test_audio_engine.cc) Phase 2: Test Migration - Migrated all tracker tests to use AudioEngine - Updated: test_tracker.cc, test_tracker_timing.cc, test_variable_tempo.cc, test_wav_dump.cc - Pattern: Replace synth_init() + tracker_init() with engine.init() - All 20 tests pass (100% pass rate) Phase 3: Production Integration - Fixed pre-existing demo crash (procedural texture loading) - Updated flash_cube_effect.cc and hybrid_3d_effect.cc - Migrated main.cc to use AudioEngine - Replaced tracker_update() calls with engine.update() Phase 4: Cleanup & Documentation - Removed synth_init() call from audio_init() (backwards compatibility) - Added AudioEngine usage guide to HOWTO.md - Added audio initialization protocols to CONTRIBUTING.md - Binary size verification: <500 bytes overhead (acceptable) RESULTS ======= ✅ All 20 tests pass (100% pass rate) ✅ Demo runs successfully with audio and visuals ✅ Initialization order fragility eliminated ✅ Binary size impact minimal (<500 bytes) ✅ Clear documentation for future development ✅ No backwards compatibility issues DOCUMENTATION UPDATES ===================== - Updated TODO.md: Moved Task #56 to "Recently Completed" - Updated PROJECT_CONTEXT.md: Added AudioEngine milestone - Updated HOWTO.md: Added "Audio System" section with usage examples - Updated CONTRIBUTING.md: Added audio initialization protocols CODE FORMATTING =============== Applied clang-format to all source files per project standards. FILES CREATED ============= - src/audio/audio_engine.h (new) - src/audio/audio_engine.cc (new) - src/audio/spectrogram_resource_manager.h (new) - src/audio/spectrogram_resource_manager.cc (new) - src/tests/test_audio_engine.cc (new) KEY FILES MODIFIED ================== - src/main.cc (migrated to AudioEngine) - src/audio/audio.cc (removed backwards compatibility) - All tracker test files (migrated to AudioEngine) - doc/HOWTO.md (added usage guide) - doc/CONTRIBUTING.md (added protocols) - TODO.md (marked complete) - PROJECT_CONTEXT.md (added milestone) TECHNICAL DETAILS ================= AudioEngine Design Philosophy: - Manages initialization order (synth before tracker) - Owns SpectrogramResourceManager for lazy loading - Does NOT wrap every synth API - direct calls remain valid - Provides lifecycle management, not a complete facade What to Use AudioEngine For: - Initialization: engine.init() instead of separate init calls - Updates: engine.update(music_time) instead of tracker_update() - Cleanup: engine.shutdown() for proper teardown - Seeking: engine.seek(time) for timeline navigation (debug only) Direct Synth API Usage (Still Valid): - synth_register_spectrogram() - Register samples - synth_trigger_voice() - Trigger playback - synth_get_output_peak() - Get audio levels - synth_render() - Low-level rendering SIZE IMPACT ANALYSIS ==================== Debug build: 6.2MB Size-optimized build: 5.0MB Stripped build: 5.0MB AudioEngine overhead: <500 bytes (0.01% of total) BACKWARD COMPATIBILITY ====================== No breaking changes. Tests that need low-level control can still call synth_init() directly. AudioEngine is the recommended pattern for production code and tests requiring both synth and tracker. handoff(Claude): Task #56 COMPLETE - All 4 phases finished. Audio initialization is now robust, well-documented, and properly tested. The fragile initialization order dependency has been eliminated. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/audio')
-rw-r--r--src/audio/audio.cc29
-rw-r--r--src/audio/audio_backend.h5
-rw-r--r--src/audio/audio_engine.cc23
-rw-r--r--src/audio/audio_engine.h18
-rw-r--r--src/audio/jittered_audio_backend.cc28
-rw-r--r--src/audio/jittered_audio_backend.h22
-rw-r--r--src/audio/miniaudio_backend.cc100
-rw-r--r--src/audio/miniaudio_backend.h4
-rw-r--r--src/audio/mock_audio_backend.cc2
-rw-r--r--src/audio/mock_audio_backend.h16
-rw-r--r--src/audio/ring_buffer.cc37
-rw-r--r--src/audio/ring_buffer.h22
-rw-r--r--src/audio/spectrogram_resource_manager.cc27
-rw-r--r--src/audio/spectrogram_resource_manager.h16
-rw-r--r--src/audio/synth.cc38
-rw-r--r--src/audio/synth.h8
-rw-r--r--src/audio/tracker.cc41
-rw-r--r--src/audio/tracker.h2
18 files changed, 264 insertions, 174 deletions
diff --git a/src/audio/audio.cc b/src/audio/audio.cc
index 0407fb3..b00d416 100644
--- a/src/audio/audio.cc
+++ b/src/audio/audio.cc
@@ -21,7 +21,7 @@ static AudioRingBuffer g_ring_buffer;
// Maximum size: one chunk (533 frames @ 60fps = 1066 samples stereo)
#define MAX_PENDING_SAMPLES 2048
static float g_pending_buffer[MAX_PENDING_SAMPLES];
-static int g_pending_samples = 0; // How many samples are waiting to be written
+static int g_pending_samples = 0; // How many samples are waiting to be written
// Global backend pointer for audio abstraction
static AudioBackend* g_audio_backend = nullptr;
@@ -59,7 +59,8 @@ int register_spec_asset(AssetId id) {
void audio_init() {
// Note: synth_init() must be called separately before using audio system.
- // In production code, use AudioEngine::init() which manages initialization order.
+ // In production code, use AudioEngine::init() which manages initialization
+ // order.
// Clear pending buffer
g_pending_samples = 0;
@@ -83,21 +84,22 @@ void audio_start() {
void audio_render_ahead(float music_time, float dt) {
// Target: maintain look-ahead buffer
- const float target_lookahead =
- (float)RING_BUFFER_LOOKAHEAD_MS / 1000.0f;
+ const float target_lookahead = (float)RING_BUFFER_LOOKAHEAD_MS / 1000.0f;
// Render in small chunks to keep synth time synchronized with tracker
// Chunk size: one frame's worth of audio (~16.6ms @ 60fps)
const int chunk_frames = (int)(dt * RING_BUFFER_SAMPLE_RATE);
const int chunk_samples = chunk_frames * RING_BUFFER_CHANNELS;
- if (chunk_frames <= 0) return;
+ if (chunk_frames <= 0)
+ return;
// Keep rendering small chunks until buffer is full enough
while (true) {
// First, try to flush any pending samples from previous partial writes
if (g_pending_samples > 0) {
- const int written = g_ring_buffer.write(g_pending_buffer, g_pending_samples);
+ const int written =
+ g_ring_buffer.write(g_pending_buffer, g_pending_samples);
if (written > 0) {
// Some or all samples were written
@@ -119,16 +121,19 @@ void audio_render_ahead(float music_time, float dt) {
}
// If still have pending samples, buffer is full - wait for consumption
- if (g_pending_samples > 0) break;
+ if (g_pending_samples > 0)
+ break;
}
// Check current buffer state
const int buffered_samples = g_ring_buffer.available_read();
const float buffered_time =
- (float)buffered_samples / (RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS);
+ (float)buffered_samples /
+ (RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS);
// Stop if buffer is full enough
- if (buffered_time >= target_lookahead) break;
+ if (buffered_time >= target_lookahead)
+ break;
// Check available space and render chunk that fits
const int available_space = g_ring_buffer.available_write();
@@ -139,7 +144,8 @@ void audio_render_ahead(float music_time, float dt) {
// Determine how much we can actually render
// Render the smaller of: desired chunk size OR available space
- const int actual_samples = (available_space < chunk_samples) ? available_space : chunk_samples;
+ const int actual_samples =
+ (available_space < chunk_samples) ? available_space : chunk_samples;
const int actual_frames = actual_samples / RING_BUFFER_CHANNELS;
// Allocate temporary buffer (stereo)
@@ -172,7 +178,8 @@ void audio_render_ahead(float music_time, float dt) {
delete[] temp_buffer;
// If we couldn't write everything, stop and retry next frame
- if (written < actual_samples) break;
+ if (written < actual_samples)
+ break;
}
}
diff --git a/src/audio/audio_backend.h b/src/audio/audio_backend.h
index 4a2b7fb..940e2b2 100644
--- a/src/audio/audio_backend.h
+++ b/src/audio/audio_backend.h
@@ -8,7 +8,8 @@
// Production uses MiniaudioBackend, tests use MockAudioBackend
class AudioBackend {
public:
- virtual ~AudioBackend() {}
+ virtual ~AudioBackend() {
+ }
// Initialize backend resources
virtual void init() = 0;
@@ -26,7 +27,7 @@ class AudioBackend {
// volume: Voice volume (0.0 - 1.0)
// pan: Pan position (-1.0 left, 0.0 center, 1.0 right)
virtual void on_voice_triggered(float timestamp, int spectrogram_id,
- float volume, float pan) {
+ float volume, float pan) {
// Default implementation does nothing (production path)
(void)timestamp;
(void)spectrogram_id;
diff --git a/src/audio/audio_engine.cc b/src/audio/audio_engine.cc
index 184e4aa..6d2ee92 100644
--- a/src/audio/audio_engine.cc
+++ b/src/audio/audio_engine.cc
@@ -3,8 +3,8 @@
#include "audio_engine.h"
#include "util/debug.h"
-#include <cstring>
#include <algorithm>
+#include <cstring>
void AudioEngine::init() {
if (initialized_) {
@@ -48,7 +48,7 @@ void AudioEngine::reset() {
return;
}
- synth_init(); // Re-init synth (clears all state)
+ synth_init(); // Re-init synth (clears all state)
tracker_reset();
resource_mgr_.reset();
@@ -65,9 +65,9 @@ void AudioEngine::reset() {
}
void AudioEngine::load_music_data(const TrackerScore* score,
- const NoteParams* samples,
- const AssetId* sample_assets,
- uint32_t sample_count) {
+ const NoteParams* samples,
+ const AssetId* sample_assets,
+ uint32_t sample_count) {
// Register sample metadata (lazy loading - don't load yet!)
for (uint32_t i = 0; i < sample_count; ++i) {
if (sample_assets[i] != AssetId::ASSET_LAST_ID) {
@@ -118,7 +118,9 @@ void AudioEngine::trigger_sample(int sample_id, float volume, float pan) {
const int synth_id = get_or_register_synth_id(sample_id);
if (synth_id == -1) {
#if defined(DEBUG_LOG_AUDIO)
- DEBUG_AUDIO("[AudioEngine ERROR] Failed to register sample_id=%d with synth\n", sample_id);
+ DEBUG_AUDIO(
+ "[AudioEngine ERROR] Failed to register sample_id=%d with synth\n",
+ sample_id);
#endif
return;
}
@@ -148,7 +150,8 @@ int AudioEngine::get_or_register_synth_id(int sample_id) {
sample_to_synth_id_[sample_id] = synth_id;
#if defined(DEBUG_LOG_AUDIO)
- DEBUG_AUDIO("[AudioEngine] Registered sample_id=%d → synth_id=%d\n", sample_id, synth_id);
+ DEBUG_AUDIO("[AudioEngine] Registered sample_id=%d → synth_id=%d\n",
+ sample_id, synth_id);
#endif
return synth_id;
@@ -161,7 +164,8 @@ void AudioEngine::seek(float target_time) {
}
#if defined(DEBUG_LOG_AUDIO)
- DEBUG_AUDIO("[AudioEngine] Seeking to t=%.3fs (from t=%.3fs)\n", target_time, current_time_);
+ DEBUG_AUDIO("[AudioEngine] Seeking to t=%.3fs (from t=%.3fs)\n", target_time,
+ current_time_);
#endif
// 1. Reset synth state (clear all active voices)
@@ -203,7 +207,8 @@ void AudioEngine::prewarm_for_time_range(float start_time, float end_time) {
// and pattern data to determine which samples will be needed.
#if defined(DEBUG_LOG_AUDIO)
- DEBUG_AUDIO("[AudioEngine] Pre-warming samples for t=%.2f-%.2f\n", start_time, end_time);
+ DEBUG_AUDIO("[AudioEngine] Pre-warming samples for t=%.2f-%.2f\n", start_time,
+ end_time);
#endif
}
diff --git a/src/audio/audio_engine.h b/src/audio/audio_engine.h
index ef3c163..95761ad 100644
--- a/src/audio/audio_engine.h
+++ b/src/audio/audio_engine.h
@@ -14,13 +14,11 @@ class AudioEngine {
// Lifecycle
void init();
void shutdown();
- void reset(); // Clear all state (for seeking/rewinding)
+ void reset(); // Clear all state (for seeking/rewinding)
// Music data loading
- void load_music_data(const TrackerScore* score,
- const NoteParams* samples,
- const AssetId* sample_assets,
- uint32_t sample_count);
+ void load_music_data(const TrackerScore* score, const NoteParams* samples,
+ const AssetId* sample_assets, uint32_t sample_count);
// Update loop
void update(float music_time);
@@ -28,7 +26,9 @@ class AudioEngine {
#if !defined(STRIP_ALL)
// Timeline seeking (debugging only)
void seek(float target_time);
- float get_time() const { return current_time_; }
+ float get_time() const {
+ return current_time_;
+ }
#endif /* !defined(STRIP_ALL) */
// Synth interface (delegates to internal synth)
@@ -39,7 +39,9 @@ class AudioEngine {
void tracker_reset();
// Resource access
- SpectrogramResourceManager* get_resource_manager() { return &resource_mgr_; }
+ SpectrogramResourceManager* get_resource_manager() {
+ return &resource_mgr_;
+ }
private:
// Trigger a sample (loads resource if needed, registers with synth)
@@ -51,7 +53,7 @@ class AudioEngine {
#if !defined(STRIP_ALL)
// Seeking support
void prewarm_for_time_range(float start_time, float end_time);
- void update_silent(float music_time); // Update without triggering audio
+ void update_silent(float music_time); // Update without triggering audio
#endif
SpectrogramResourceManager resource_mgr_;
diff --git a/src/audio/jittered_audio_backend.cc b/src/audio/jittered_audio_backend.cc
index 5561c11..8742aba 100644
--- a/src/audio/jittered_audio_backend.cc
+++ b/src/audio/jittered_audio_backend.cc
@@ -13,14 +13,9 @@
#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) {
+ : 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() {
@@ -32,7 +27,8 @@ void JitteredAudioBackend::init() {
}
void JitteredAudioBackend::start() {
- if (running_.load()) return;
+ if (running_.load())
+ return;
should_stop_.store(false);
running_.store(true);
@@ -42,7 +38,8 @@ void JitteredAudioBackend::start() {
}
void JitteredAudioBackend::shutdown() {
- if (!running_.load()) return;
+ if (!running_.load())
+ return;
should_stop_.store(true);
@@ -61,20 +58,23 @@ 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) {
+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;
+ 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_);
+ std::uniform_int_distribution<int> chunk_dist(min_chunk_frames_,
+ max_chunk_frames_);
while (!should_stop_.load()) {
// Calculate jittered wait time
@@ -88,7 +88,7 @@ void JitteredAudioBackend::audio_thread_loop() {
// Random chunk size
const int chunk_frames = chunk_dist(gen);
- const int chunk_samples = chunk_frames * 2; // Stereo
+ const int chunk_samples = chunk_frames * 2; // Stereo
// Read from ring buffer
float* temp_buffer = new float[chunk_samples];
diff --git a/src/audio/jittered_audio_backend.h b/src/audio/jittered_audio_backend.h
index e0ce9d7..c246c48 100644
--- a/src/audio/jittered_audio_backend.h
+++ b/src/audio/jittered_audio_backend.h
@@ -21,14 +21,22 @@ class JitteredAudioBackend : public AudioBackend {
void shutdown() override;
// Control simulation
- void set_jitter_amount(float jitter_ms); // Random jitter in ms (default: 5ms)
- void set_base_interval(float interval_ms); // Base interval between reads (default: 10ms)
- void set_chunk_size_range(int min_frames, int max_frames); // Variable chunk sizes
+ void set_jitter_amount(float jitter_ms); // Random jitter in ms (default: 5ms)
+ void set_base_interval(
+ float interval_ms); // Base interval between reads (default: 10ms)
+ void set_chunk_size_range(int min_frames,
+ int max_frames); // Variable chunk sizes
// Query state
- int get_total_frames_consumed() const { return total_frames_consumed_.load(); }
- int get_underrun_count() const { return underrun_count_.load(); }
- bool is_running() const { return running_.load(); }
+ int get_total_frames_consumed() const {
+ return total_frames_consumed_.load();
+ }
+ int get_underrun_count() const {
+ return underrun_count_.load();
+ }
+ bool is_running() const {
+ return running_.load();
+ }
private:
void audio_thread_loop();
@@ -45,7 +53,7 @@ class JitteredAudioBackend : public AudioBackend {
// Statistics
std::atomic<int> total_frames_consumed_;
- std::atomic<int> underrun_count_; // How many times buffer was empty
+ std::atomic<int> underrun_count_; // How many times buffer was empty
};
#endif /* !defined(STRIP_ALL) */
diff --git a/src/audio/miniaudio_backend.cc b/src/audio/miniaudio_backend.cc
index c2db268..baaf9bb 100644
--- a/src/audio/miniaudio_backend.cc
+++ b/src/audio/miniaudio_backend.cc
@@ -7,12 +7,12 @@
#include "ring_buffer.h"
#include "util/debug.h"
#include <stdio.h>
-#include <stdlib.h> // for abort()
+#include <stdlib.h> // for abort()
// Static callback for miniaudio (C API requirement)
void MiniaudioBackend::audio_callback(ma_device* pDevice, void* pOutput,
- const void* pInput,
- ma_uint32 frameCount) {
+ const void* pInput,
+ ma_uint32 frameCount) {
(void)pInput;
#if defined(DEBUG_LOG_AUDIO)
@@ -33,25 +33,30 @@ void MiniaudioBackend::audio_callback(ma_device* pDevice, void* pOutput,
double now = ts.tv_sec + ts.tv_nsec / 1000000000.0;
if (timing_initialized) {
- double delta = (now - last_time) * 1000.0; // ms
+ double delta = (now - last_time) * 1000.0; // ms
double expected = ((double)frameCount / pDevice->sampleRate) * 1000.0;
double jitter = delta - expected;
// Enhanced logging: Log first 20 callbacks in detail, then periodic summary
if (callback_number <= 20 || callback_number % 50 == 0) {
- const double elapsed_by_frames = (double)total_frames_requested / pDevice->sampleRate * 1000.0;
- const double elapsed_by_time = now * 1000.0; // Convert to ms
- DEBUG_AUDIO("[CB#%llu] frameCount=%u, Delta=%.2fms, Expected=%.2fms, Jitter=%.2fms, "
- "TotalFrames=%llu (%.1fms), TotalTime=%.1fms, Drift=%.2fms\n",
- callback_number, frameCount, delta, expected, jitter,
- total_frames_requested, elapsed_by_frames, elapsed_by_time,
- elapsed_by_time - elapsed_by_frames);
+ const double elapsed_by_frames =
+ (double)total_frames_requested / pDevice->sampleRate * 1000.0;
+ const double elapsed_by_time = now * 1000.0; // Convert to ms
+ DEBUG_AUDIO(
+ "[CB#%llu] frameCount=%u, Delta=%.2fms, Expected=%.2fms, "
+ "Jitter=%.2fms, "
+ "TotalFrames=%llu (%.1fms), TotalTime=%.1fms, Drift=%.2fms\n",
+ callback_number, frameCount, delta, expected, jitter,
+ total_frames_requested, elapsed_by_frames, elapsed_by_time,
+ elapsed_by_time - elapsed_by_frames);
}
// Detect large timing anomalies (>5ms off from expected)
if (fabs(jitter) > 5.0) {
- DEBUG_AUDIO("[TIMING ANOMALY] CB#%llu Delta=%.2fms, Expected=%.2fms, Jitter=%.2fms\n",
- callback_number, delta, expected, jitter);
+ DEBUG_AUDIO(
+ "[TIMING ANOMALY] CB#%llu Delta=%.2fms, Expected=%.2fms, "
+ "Jitter=%.2fms\n",
+ callback_number, delta, expected, jitter);
}
}
last_time = now;
@@ -81,8 +86,10 @@ void MiniaudioBackend::audio_callback(ma_device* pDevice, void* pOutput,
if (pDevice->sampleRate != 32000) {
static int rate_warning = 0;
if (rate_warning++ == 0) {
- DEBUG_AUDIO("WARNING: Device sample rate is %u, not 32000! Resampling may occur.\n",
- pDevice->sampleRate);
+ DEBUG_AUDIO(
+ "WARNING: Device sample rate is %u, not 32000! Resampling may "
+ "occur.\n",
+ pDevice->sampleRate);
}
}
#endif /* defined(DEBUG_LOG_AUDIO) */
@@ -91,14 +98,15 @@ void MiniaudioBackend::audio_callback(ma_device* pDevice, void* pOutput,
// BOUNDS CHECK: Sanity check on frameCount
if (frameCount > 8192 || frameCount == 0) {
- fprintf(stderr, "AUDIO CALLBACK ERROR: frameCount=%u (unreasonable!)\n", frameCount);
+ fprintf(stderr, "AUDIO CALLBACK ERROR: frameCount=%u (unreasonable!)\n",
+ frameCount);
abort();
}
// Read from ring buffer instead of calling synth directly
AudioRingBuffer* ring_buffer = audio_get_ring_buffer();
if (ring_buffer != nullptr) {
- const int samples_to_read = (int)frameCount * 2; // Stereo
+ const int samples_to_read = (int)frameCount * 2; // Stereo
#if defined(DEBUG_LOG_RING_BUFFER)
// Track buffer level and detect drops
@@ -108,20 +116,24 @@ void MiniaudioBackend::audio_callback(ma_device* pDevice, void* pOutput,
if (available < min_available) {
min_available = available;
DEBUG_RING_BUFFER("[BUFFER] CB#%llu NEW MIN: available=%d (%.1fms)\n",
- callback_number, available, (float)available / (32000.0f * 2.0f) * 1000.0f);
+ callback_number, available,
+ (float)available / (32000.0f * 2.0f) * 1000.0f);
}
// Log buffer state for first 20 callbacks and periodically
if (callback_number <= 20 || callback_number % 50 == 0) {
- DEBUG_RING_BUFFER("[BUFFER] CB#%llu requested=%d, available=%d (%.1fms), min=%d\n",
- callback_number, samples_to_read, available,
- (float)available / (32000.0f * 2.0f) * 1000.0f, min_available);
+ DEBUG_RING_BUFFER(
+ "[BUFFER] CB#%llu requested=%d, available=%d (%.1fms), min=%d\n",
+ callback_number, samples_to_read, available,
+ (float)available / (32000.0f * 2.0f) * 1000.0f, min_available);
}
// CRITICAL: Verify we have enough samples
if (available < samples_to_read) {
- DEBUG_RING_BUFFER("[BUFFER UNDERRUN] CB#%llu requested=%d, available=%d, SHORT=%d\n",
- callback_number, samples_to_read, available, samples_to_read - available);
+ DEBUG_RING_BUFFER(
+ "[BUFFER UNDERRUN] CB#%llu requested=%d, available=%d, SHORT=%d\n",
+ callback_number, samples_to_read, available,
+ samples_to_read - available);
}
#endif /* defined(DEBUG_LOG_RING_BUFFER) */
@@ -129,8 +141,11 @@ void MiniaudioBackend::audio_callback(ma_device* pDevice, void* pOutput,
#if defined(DEBUG_LOG_RING_BUFFER)
if (actually_read < samples_to_read) {
- DEBUG_RING_BUFFER("[PARTIAL READ] CB#%llu requested=%d, got=%d, padded=%d with silence\n",
- callback_number, samples_to_read, actually_read, samples_to_read - actually_read);
+ DEBUG_RING_BUFFER(
+ "[PARTIAL READ] CB#%llu requested=%d, got=%d, padded=%d with "
+ "silence\n",
+ callback_number, samples_to_read, actually_read,
+ samples_to_read - actually_read);
}
#endif /* defined(DEBUG_LOG_RING_BUFFER) */
}
@@ -171,8 +186,8 @@ void MiniaudioBackend::init() {
config.performanceProfile = ma_performance_profile_conservative;
// Let Core Audio choose the period size based on conservative profile
- config.periodSizeInFrames = 0; // 0 = let backend decide
- config.periods = 0; // 0 = let backend decide based on performance profile
+ config.periodSizeInFrames = 0; // 0 = let backend decide
+ config.periods = 0; // 0 = let backend decide based on performance profile
config.dataCallback = MiniaudioBackend::audio_callback;
config.pUserData = this;
@@ -187,23 +202,34 @@ void MiniaudioBackend::init() {
DEBUG_AUDIO("\n=== MINIAUDIO DEVICE CONFIGURATION ===\n");
DEBUG_AUDIO(" Sample rate: %u (requested: 32000)\n", device_.sampleRate);
DEBUG_AUDIO(" Channels: %u (requested: 2)\n", device_.playback.channels);
- DEBUG_AUDIO(" Format: %d (requested: %d, f32=%d)\n",
- device_.playback.format, config.playback.format, ma_format_f32);
+ DEBUG_AUDIO(" Format: %d (requested: %d, f32=%d)\n", device_.playback.format,
+ config.playback.format, ma_format_f32);
DEBUG_AUDIO(" Period size: %u frames (%.1fms at %uHz)\n",
device_.playback.internalPeriodSizeInFrames,
- (float)device_.playback.internalPeriodSizeInFrames / device_.sampleRate * 1000.0f,
+ (float)device_.playback.internalPeriodSizeInFrames /
+ device_.sampleRate * 1000.0f,
device_.sampleRate);
- DEBUG_AUDIO(" Periods: %u (buffer multiplier)\n", device_.playback.internalPeriods);
- DEBUG_AUDIO(" Backend: %s\n", ma_get_backend_name(device_.pContext->backend));
+ DEBUG_AUDIO(" Periods: %u (buffer multiplier)\n",
+ device_.playback.internalPeriods);
+ DEBUG_AUDIO(" Backend: %s\n",
+ ma_get_backend_name(device_.pContext->backend));
DEBUG_AUDIO(" Total buffer size: %u frames (%.2fms) [period * periods]\n",
- device_.playback.internalPeriodSizeInFrames * device_.playback.internalPeriods,
- (float)(device_.playback.internalPeriodSizeInFrames * device_.playback.internalPeriods) / device_.sampleRate * 1000.0f);
+ device_.playback.internalPeriodSizeInFrames *
+ device_.playback.internalPeriods,
+ (float)(device_.playback.internalPeriodSizeInFrames *
+ device_.playback.internalPeriods) /
+ device_.sampleRate * 1000.0f);
// Calculate expected callback interval
if (device_.playback.internalPeriodSizeInFrames > 0) {
- const float expected_callback_ms = (float)device_.playback.internalPeriodSizeInFrames / device_.sampleRate * 1000.0f;
- DEBUG_AUDIO(" Expected callback interval: %.2fms (based on period size)\n", expected_callback_ms);
- DEBUG_AUDIO(" WARNING: If actual callback interval differs, audio corruption may occur!\n");
+ const float expected_callback_ms =
+ (float)device_.playback.internalPeriodSizeInFrames /
+ device_.sampleRate * 1000.0f;
+ DEBUG_AUDIO(" Expected callback interval: %.2fms (based on period size)\n",
+ expected_callback_ms);
+ DEBUG_AUDIO(
+ " WARNING: If actual callback interval differs, audio corruption may "
+ "occur!\n");
}
DEBUG_AUDIO("======================================\n\n");
fflush(stderr);
diff --git a/src/audio/miniaudio_backend.h b/src/audio/miniaudio_backend.h
index d46a0c5..82c7b76 100644
--- a/src/audio/miniaudio_backend.h
+++ b/src/audio/miniaudio_backend.h
@@ -19,7 +19,9 @@ class MiniaudioBackend : public AudioBackend {
void shutdown() override;
// Get the underlying miniaudio device (for internal use)
- ma_device* get_device() { return &device_; }
+ ma_device* get_device() {
+ return &device_;
+ }
private:
ma_device device_;
diff --git a/src/audio/mock_audio_backend.cc b/src/audio/mock_audio_backend.cc
index 3f5a57a..33ed35a 100644
--- a/src/audio/mock_audio_backend.cc
+++ b/src/audio/mock_audio_backend.cc
@@ -25,7 +25,7 @@ void MockAudioBackend::shutdown() {
}
void MockAudioBackend::on_voice_triggered(float timestamp, int spectrogram_id,
- float volume, float pan) {
+ float volume, float pan) {
// Record the event with the timestamp provided by synth
VoiceTriggerEvent event;
event.timestamp_sec = timestamp;
diff --git a/src/audio/mock_audio_backend.h b/src/audio/mock_audio_backend.h
index 963d9cb..a4ee36d 100644
--- a/src/audio/mock_audio_backend.h
+++ b/src/audio/mock_audio_backend.h
@@ -38,12 +38,20 @@ class MockAudioBackend : public AudioBackend {
const std::vector<VoiceTriggerEvent>& get_events() const {
return recorded_events_;
}
- void clear_events() { recorded_events_.clear(); }
+ void clear_events() {
+ recorded_events_.clear();
+ }
// Manual time control for deterministic testing
- void advance_time(float delta_sec) { current_time_sec_ += delta_sec; }
- void set_time(float time_sec) { current_time_sec_ = time_sec; }
- float get_current_time() const { return current_time_sec_; }
+ void advance_time(float delta_sec) {
+ current_time_sec_ += delta_sec;
+ }
+ void set_time(float time_sec) {
+ current_time_sec_ = time_sec;
+ }
+ float get_current_time() const {
+ return current_time_sec_;
+ }
// Sample rate used for frame-to-time conversion
static const int kSampleRate = 32000;
diff --git a/src/audio/ring_buffer.cc b/src/audio/ring_buffer.cc
index ab51d6b..b30ebbb 100644
--- a/src/audio/ring_buffer.cc
+++ b/src/audio/ring_buffer.cc
@@ -4,14 +4,12 @@
#include "ring_buffer.h"
#include "util/debug.h"
#include <algorithm>
+#include <cstdio> // for fprintf()
+#include <cstdlib> // for abort()
#include <cstring>
-#include <cstdlib> // for abort()
-#include <cstdio> // for fprintf()
AudioRingBuffer::AudioRingBuffer()
- : capacity_(RING_BUFFER_CAPACITY_SAMPLES),
- write_pos_(0),
- read_pos_(0),
+ : capacity_(RING_BUFFER_CAPACITY_SAMPLES), write_pos_(0), read_pos_(0),
total_read_(0) {
memset(buffer_, 0, sizeof(buffer_));
}
@@ -25,7 +23,7 @@ int AudioRingBuffer::available_write() const {
const int read = read_pos_.load(std::memory_order_acquire);
if (write >= read) {
- return capacity_ - (write - read) - 1; // -1 to avoid full/empty ambiguity
+ return capacity_ - (write - read) - 1; // -1 to avoid full/empty ambiguity
} else {
return read - write - 1;
}
@@ -65,7 +63,8 @@ int AudioRingBuffer::write(const float* samples, int count) {
// Write in one chunk
// BOUNDS CHECK
if (write < 0 || write + to_write > capacity_) {
- fprintf(stderr, "BOUNDS ERROR in write(): write=%d, to_write=%d, capacity=%d\n",
+ fprintf(stderr,
+ "BOUNDS ERROR in write(): write=%d, to_write=%d, capacity=%d\n",
write, to_write, capacity_);
abort();
}
@@ -75,7 +74,9 @@ int AudioRingBuffer::write(const float* samples, int count) {
// Write in two chunks (wrap around)
// BOUNDS CHECK - first chunk
if (write < 0 || write + space_to_end > capacity_) {
- fprintf(stderr, "BOUNDS ERROR in write() chunk1: write=%d, space_to_end=%d, capacity=%d\n",
+ fprintf(stderr,
+ "BOUNDS ERROR in write() chunk1: write=%d, space_to_end=%d, "
+ "capacity=%d\n",
write, space_to_end, capacity_);
abort();
}
@@ -83,7 +84,8 @@ int AudioRingBuffer::write(const float* samples, int count) {
const int remainder = to_write - space_to_end;
// BOUNDS CHECK - second chunk
if (remainder < 0 || remainder > capacity_) {
- fprintf(stderr, "BOUNDS ERROR in write() chunk2: remainder=%d, capacity=%d\n",
+ fprintf(stderr,
+ "BOUNDS ERROR in write() chunk2: remainder=%d, capacity=%d\n",
remainder, capacity_);
abort();
}
@@ -114,7 +116,8 @@ int AudioRingBuffer::read(float* samples, int count) {
// Read in one chunk
// BOUNDS CHECK
if (read < 0 || read + to_read > capacity_) {
- fprintf(stderr, "BOUNDS ERROR in read(): read=%d, to_read=%d, capacity=%d\n",
+ fprintf(stderr,
+ "BOUNDS ERROR in read(): read=%d, to_read=%d, capacity=%d\n",
read, to_read, capacity_);
abort();
}
@@ -124,7 +127,9 @@ int AudioRingBuffer::read(float* samples, int count) {
// Read in two chunks (wrap around)
// BOUNDS CHECK - first chunk
if (read < 0 || read + space_to_end > capacity_) {
- fprintf(stderr, "BOUNDS ERROR in read() chunk1: read=%d, space_to_end=%d, capacity=%d\n",
+ fprintf(stderr,
+ "BOUNDS ERROR in read() chunk1: read=%d, space_to_end=%d, "
+ "capacity=%d\n",
read, space_to_end, capacity_);
abort();
}
@@ -132,7 +137,8 @@ int AudioRingBuffer::read(float* samples, int count) {
const int remainder = to_read - space_to_end;
// BOUNDS CHECK - second chunk
if (remainder < 0 || remainder > capacity_) {
- fprintf(stderr, "BOUNDS ERROR in read() chunk2: remainder=%d, capacity=%d\n",
+ fprintf(stderr,
+ "BOUNDS ERROR in read() chunk2: remainder=%d, capacity=%d\n",
remainder, capacity_);
abort();
}
@@ -148,9 +154,10 @@ int AudioRingBuffer::read(float* samples, int count) {
#if defined(DEBUG_LOG_RING_BUFFER)
// UNDERRUN DETECTED
static int underrun_count = 0;
- if (++underrun_count % 10 == 1) { // Log every 10th underrun
- DEBUG_RING_BUFFER("UNDERRUN #%d: requested=%d, available=%d, filling %d with silence\n",
- underrun_count, count, to_read, count - to_read);
+ if (++underrun_count % 10 == 1) { // Log every 10th underrun
+ DEBUG_RING_BUFFER(
+ "UNDERRUN #%d: requested=%d, available=%d, filling %d with silence\n",
+ underrun_count, count, to_read, count - to_read);
}
#endif /* defined(DEBUG_LOG_RING_BUFFER) */
memset(samples + to_read, 0, (count - to_read) * sizeof(float));
diff --git a/src/audio/ring_buffer.h b/src/audio/ring_buffer.h
index 6375161..b19c1ea 100644
--- a/src/audio/ring_buffer.h
+++ b/src/audio/ring_buffer.h
@@ -14,8 +14,10 @@
#define RING_BUFFER_LOOKAHEAD_MS 400
#define RING_BUFFER_SAMPLE_RATE 32000
#define RING_BUFFER_CHANNELS 2
-#define RING_BUFFER_CAPACITY_SAMPLES \
- ((RING_BUFFER_LOOKAHEAD_MS * RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS) / 1000)
+#define RING_BUFFER_CAPACITY_SAMPLES \
+ ((RING_BUFFER_LOOKAHEAD_MS * RING_BUFFER_SAMPLE_RATE * \
+ RING_BUFFER_CHANNELS) / \
+ 1000)
class AudioRingBuffer {
public:
@@ -32,20 +34,22 @@ class AudioRingBuffer {
int read(float* samples, int count);
// Query buffer state
- int available_write() const; // Samples that can be written
- int available_read() const; // Samples that can be read
+ int available_write() const; // Samples that can be written
+ int available_read() const; // Samples that can be read
// Get total samples consumed (for timing)
- int64_t get_total_read() const { return total_read_.load(std::memory_order_acquire); }
+ int64_t get_total_read() const {
+ return total_read_.load(std::memory_order_acquire);
+ }
// Clear buffer (for seeking)
void clear();
private:
float buffer_[RING_BUFFER_CAPACITY_SAMPLES];
- int capacity_; // Total capacity in samples
+ int capacity_; // Total capacity in samples
- std::atomic<int> write_pos_; // Write position (0 to capacity-1)
- std::atomic<int> read_pos_; // Read position (0 to capacity-1)
- std::atomic<int64_t> total_read_; // Total samples read (for playback time)
+ std::atomic<int> write_pos_; // Write position (0 to capacity-1)
+ std::atomic<int> read_pos_; // Read position (0 to capacity-1)
+ std::atomic<int64_t> total_read_; // Total samples read (for playback time)
};
diff --git a/src/audio/spectrogram_resource_manager.cc b/src/audio/spectrogram_resource_manager.cc
index dbed09e..30939e0 100644
--- a/src/audio/spectrogram_resource_manager.cc
+++ b/src/audio/spectrogram_resource_manager.cc
@@ -42,7 +42,8 @@ void SpectrogramResourceManager::reset() {
loaded_count_ = 0;
}
-void SpectrogramResourceManager::register_asset(int sample_id, AssetId asset_id) {
+void SpectrogramResourceManager::register_asset(int sample_id,
+ AssetId asset_id) {
if (sample_id < 0 || sample_id >= MAX_SPECTROGRAM_RESOURCES) {
return;
}
@@ -57,13 +58,14 @@ void SpectrogramResourceManager::register_asset(int sample_id, AssetId asset_id)
#endif
}
-void SpectrogramResourceManager::register_procedural(int sample_id, const NoteParams& params) {
+void SpectrogramResourceManager::register_procedural(int sample_id,
+ const NoteParams& params) {
if (sample_id < 0 || sample_id >= MAX_SPECTROGRAM_RESOURCES) {
return;
}
Resource& resource = resources_[sample_id];
- resource.asset_id = AssetId::ASSET_LAST_ID; // Mark as procedural
+ resource.asset_id = AssetId::ASSET_LAST_ID; // Mark as procedural
resource.proc_params = params;
resource.state = REGISTERED;
@@ -83,7 +85,7 @@ const Spectrogram* SpectrogramResourceManager::get_or_load(int sample_id) {
// Already loaded?
if (resource.state == LOADED) {
#if defined(DEMO_ENABLE_CACHE_EVICTION)
- resource.last_access_time = 0.0f; // TODO: Get actual time
+ resource.last_access_time = 0.0f; // TODO: Get actual time
#endif
return &resource.spec;
}
@@ -113,7 +115,8 @@ void SpectrogramResourceManager::preload_range(int start_id, int end_id) {
}
}
-const Spectrogram* SpectrogramResourceManager::get_spectrogram(int sample_id) const {
+const Spectrogram*
+SpectrogramResourceManager::get_spectrogram(int sample_id) const {
if (sample_id < 0 || sample_id >= MAX_SPECTROGRAM_RESOURCES) {
return nullptr;
}
@@ -164,7 +167,8 @@ void SpectrogramResourceManager::try_evict_lru(float current_time) {
float oldest_time = current_time;
for (int i = 0; i < MAX_SPECTROGRAM_RESOURCES; ++i) {
- if (resources_[i].state == LOADED && resources_[i].last_access_time < oldest_time) {
+ if (resources_[i].state == LOADED &&
+ resources_[i].last_access_time < oldest_time) {
oldest_time = resources_[i].last_access_time;
lru_id = i;
}
@@ -183,7 +187,8 @@ void SpectrogramResourceManager::load_asset(Resource* resource) {
if (data == nullptr || size < sizeof(SpecHeader)) {
#if defined(DEBUG_LOG_ASSETS)
- DEBUG_ASSETS("[ResourceMgr ERROR] Failed to load asset %d\n", (int)resource->asset_id);
+ DEBUG_ASSETS("[ResourceMgr ERROR] Failed to load asset %d\n",
+ (int)resource->asset_id);
#endif
return;
}
@@ -194,7 +199,7 @@ void SpectrogramResourceManager::load_asset(Resource* resource) {
resource->spec.spectral_data_a = spectral_data;
resource->spec.spectral_data_b = spectral_data;
resource->spec.num_frames = header->num_frames;
- resource->owned_data = nullptr; // Asset data is not owned
+ resource->owned_data = nullptr; // Asset data is not owned
#if defined(DEBUG_LOG_ASSETS)
DEBUG_ASSETS("[ResourceMgr] Loaded asset %d: %d frames\n",
@@ -204,7 +209,8 @@ void SpectrogramResourceManager::load_asset(Resource* resource) {
void SpectrogramResourceManager::load_procedural(Resource* resource) {
int note_frames = 0;
- std::vector<float> note_data = generate_note_spectrogram(resource->proc_params, &note_frames);
+ std::vector<float> note_data =
+ generate_note_spectrogram(resource->proc_params, &note_frames);
if (note_frames <= 0 || note_data.empty()) {
#if defined(DEBUG_LOG_ASSETS)
@@ -215,7 +221,8 @@ void SpectrogramResourceManager::load_procedural(Resource* resource) {
// Allocate persistent storage
resource->owned_data = new float[note_data.size()];
- memcpy(resource->owned_data, note_data.data(), note_data.size() * sizeof(float));
+ memcpy(resource->owned_data, note_data.data(),
+ note_data.size() * sizeof(float));
resource->spec.spectral_data_a = resource->owned_data;
resource->spec.spectral_data_b = resource->owned_data;
diff --git a/src/audio/spectrogram_resource_manager.h b/src/audio/spectrogram_resource_manager.h
index 97196d1..45faa37 100644
--- a/src/audio/spectrogram_resource_manager.h
+++ b/src/audio/spectrogram_resource_manager.h
@@ -17,8 +17,8 @@ class SpectrogramResourceManager {
public:
// Lifecycle
void init();
- void shutdown(); // Frees all owned memory
- void reset(); // Clear state but keep registrations
+ void shutdown(); // Frees all owned memory
+ void reset(); // Clear state but keep registrations
// Metadata registration (no loading yet, just bookkeeping)
void register_asset(int sample_id, AssetId asset_id);
@@ -45,18 +45,18 @@ class SpectrogramResourceManager {
private:
enum ResourceState {
- UNREGISTERED = 0, // No metadata registered
- REGISTERED, // Metadata registered, not loaded yet
- LOADED, // Fully loaded and ready
+ UNREGISTERED = 0, // No metadata registered
+ REGISTERED, // Metadata registered, not loaded yet
+ LOADED, // Fully loaded and ready
#if defined(DEMO_ENABLE_CACHE_EVICTION)
- EVICTED // Was loaded, now evicted
+ EVICTED // Was loaded, now evicted
#endif
};
struct Resource {
Spectrogram spec;
- float* owned_data; // nullptr if asset (not owned), allocated if procedural
- AssetId asset_id; // ASSET_LAST_ID if procedural
+ float* owned_data; // nullptr if asset (not owned), allocated if procedural
+ AssetId asset_id; // ASSET_LAST_ID if procedural
NoteParams proc_params;
ResourceState state;
diff --git a/src/audio/synth.cc b/src/audio/synth.cc
index 617ff2f..ada46fd 100644
--- a/src/audio/synth.cc
+++ b/src/audio/synth.cc
@@ -28,7 +28,7 @@ struct Voice {
float time_domain_buffer[DCT_SIZE];
int buffer_pos;
- float fractional_pos; // Fractional sample position for tempo scaling
+ float fractional_pos; // Fractional sample position for tempo scaling
const volatile float* active_spectral_data;
};
@@ -47,7 +47,7 @@ static float g_tempo_scale = 1.0f; // Playback speed multiplier
#if !defined(STRIP_ALL)
static float g_elapsed_time_sec = 0.0f; // Tracks elapsed time for event hooks
-#endif /* !defined(STRIP_ALL) */
+#endif /* !defined(STRIP_ALL) */
void synth_init() {
memset(&g_synth_data, 0, sizeof(g_synth_data));
@@ -72,22 +72,23 @@ int synth_register_spectrogram(const Spectrogram* spec) {
#if defined(DEBUG_LOG_SYNTH)
// VALIDATION: Check spectrogram pointer and data
if (spec == nullptr) {
- DEBUG_SYNTH( "[SYNTH ERROR] Null spectrogram pointer\n");
+ DEBUG_SYNTH("[SYNTH ERROR] Null spectrogram pointer\n");
return -1;
}
if (spec->spectral_data_a == nullptr || spec->spectral_data_b == nullptr) {
- DEBUG_SYNTH( "[SYNTH ERROR] Null spectral data pointers\n");
+ DEBUG_SYNTH("[SYNTH ERROR] Null spectral data pointers\n");
return -1;
}
if (spec->num_frames <= 0 || spec->num_frames > 10000) {
- DEBUG_SYNTH( "[SYNTH ERROR] Invalid num_frames=%d (must be 1-10000)\n",
- spec->num_frames);
+ DEBUG_SYNTH("[SYNTH ERROR] Invalid num_frames=%d (must be 1-10000)\n",
+ spec->num_frames);
return -1;
}
// VALIDATION: Check spectral data isn't all zeros (common corruption symptom)
bool all_zero = true;
const float* data = spec->spectral_data_a;
- const int samples_to_check = (spec->num_frames > 10) ? 10 * DCT_SIZE : spec->num_frames * DCT_SIZE;
+ const int samples_to_check =
+ (spec->num_frames > 10) ? 10 * DCT_SIZE : spec->num_frames * DCT_SIZE;
for (int j = 0; j < samples_to_check; ++j) {
if (data[j] != 0.0f) {
all_zero = false;
@@ -95,8 +96,9 @@ int synth_register_spectrogram(const Spectrogram* spec) {
}
}
if (all_zero) {
- DEBUG_SYNTH( "[SYNTH WARNING] Spectrogram appears to be all zeros (num_frames=%d)\n",
- spec->num_frames);
+ DEBUG_SYNTH(
+ "[SYNTH WARNING] Spectrogram appears to be all zeros (num_frames=%d)\n",
+ spec->num_frames);
}
#endif
@@ -157,8 +159,8 @@ void synth_trigger_voice(int spectrogram_id, float volume, float pan) {
if (spectrogram_id < 0 || spectrogram_id >= MAX_SPECTROGRAMS ||
!g_synth_data.spectrogram_registered[spectrogram_id]) {
#if defined(DEBUG_LOG_SYNTH)
- DEBUG_SYNTH( "[SYNTH ERROR] Invalid spectrogram_id=%d in trigger_voice\n",
- spectrogram_id);
+ DEBUG_SYNTH("[SYNTH ERROR] Invalid spectrogram_id=%d in trigger_voice\n",
+ spectrogram_id);
#endif
return;
}
@@ -166,12 +168,13 @@ void synth_trigger_voice(int spectrogram_id, float volume, float pan) {
#if defined(DEBUG_LOG_SYNTH)
// VALIDATION: Check volume and pan ranges
if (volume < 0.0f || volume > 2.0f) {
- DEBUG_SYNTH( "[SYNTH WARNING] Unusual volume=%.2f for spectrogram_id=%d\n",
- volume, spectrogram_id);
+ DEBUG_SYNTH("[SYNTH WARNING] Unusual volume=%.2f for spectrogram_id=%d\n",
+ volume, spectrogram_id);
}
if (pan < -1.0f || pan > 1.0f) {
- DEBUG_SYNTH( "[SYNTH WARNING] Invalid pan=%.2f (clamping) for spectrogram_id=%d\n",
- pan, spectrogram_id);
+ DEBUG_SYNTH(
+ "[SYNTH WARNING] Invalid pan=%.2f (clamping) for spectrogram_id=%d\n",
+ pan, spectrogram_id);
pan = (pan < -1.0f) ? -1.0f : 1.0f;
}
#endif
@@ -191,7 +194,8 @@ void synth_trigger_voice(int spectrogram_id, float volume, float pan) {
v.total_spectral_frames =
g_synth_data.spectrograms[spectrogram_id].num_frames;
v.buffer_pos = DCT_SIZE; // Force IDCT on first render
- v.fractional_pos = 0.0f; // Initialize fractional position for tempo scaling
+ v.fractional_pos =
+ 0.0f; // Initialize fractional position for tempo scaling
v.active_spectral_data =
g_synth_data.active_spectrogram_data[spectrogram_id];
@@ -200,7 +204,7 @@ void synth_trigger_voice(int spectrogram_id, float volume, float pan) {
AudioBackend* backend = audio_get_backend();
if (backend != nullptr) {
backend->on_voice_triggered(g_elapsed_time_sec, spectrogram_id, volume,
- pan);
+ pan);
}
#endif /* !defined(STRIP_ALL) */
diff --git a/src/audio/synth.h b/src/audio/synth.h
index 77b1878..cb0d1df 100644
--- a/src/audio/synth.h
+++ b/src/audio/synth.h
@@ -17,8 +17,9 @@
// - With caching: MAX_SPECTROGRAMS = 32 provides 2.3x headroom
//
// Memory cost: 32 slots × 48 bytes = 1.5KB (down from 12KB with 256 slots)
-#define MAX_VOICES 48 // Per tracker_compiler: required=24, recommended=48
-#define MAX_SPECTROGRAMS 32 // Current track: 14 unique, 32 provides comfortable headroom
+#define MAX_VOICES 48 // Per tracker_compiler: required=24, recommended=48
+#define MAX_SPECTROGRAMS \
+ 32 // Current track: 14 unique, 32 provides comfortable headroom
struct Spectrogram {
const float* spectral_data_a; // Front buffer
@@ -39,7 +40,8 @@ void synth_commit_update(int spectrogram_id);
void synth_trigger_voice(int spectrogram_id, float volume, float pan);
void synth_render(float* output_buffer, int num_frames);
-void synth_set_tempo_scale(float tempo_scale); // Set playback speed (1.0 = normal)
+void synth_set_tempo_scale(
+ float tempo_scale); // Set playback speed (1.0 = normal)
int synth_get_active_voice_count();
float synth_get_output_peak();
diff --git a/src/audio/tracker.cc b/src/audio/tracker.cc
index 5e30281..7ad5a67 100644
--- a/src/audio/tracker.cc
+++ b/src/audio/tracker.cc
@@ -30,7 +30,7 @@ static int g_next_pool_slot = 0; // Round-robin allocation
// CACHE: Pre-registered synth_ids for all samples (indexed by sample_id)
// This eliminates redundant spectrogram generation and registration
-static int g_sample_synth_cache[256]; // Max 256 unique samples
+static int g_sample_synth_cache[256]; // Max 256 unique samples
static bool g_cache_initialized = false;
// Forward declarations
@@ -80,7 +80,8 @@ void tracker_init() {
if (data && size >= sizeof(SpecHeader)) {
const SpecHeader* header = (const SpecHeader*)data;
const int note_frames = header->num_frames;
- const float* spectral_data = (const float*)(data + sizeof(SpecHeader));
+ const float* spectral_data =
+ (const float*)(data + sizeof(SpecHeader));
Spectrogram spec;
spec.spectral_data_a = spectral_data;
@@ -91,8 +92,9 @@ void tracker_init() {
#if defined(DEBUG_LOG_TRACKER)
if (g_sample_synth_cache[sid] == -1) {
- DEBUG_TRACKER( "[TRACKER INIT] Failed to cache asset sample_id=%d (aid=%d)\n",
- sid, (int)aid);
+ DEBUG_TRACKER(
+ "[TRACKER INIT] Failed to cache asset sample_id=%d (aid=%d)\n",
+ sid, (int)aid);
}
#endif /* defined(DEBUG_LOG_TRACKER) */
}
@@ -100,7 +102,8 @@ void tracker_init() {
// GENERATED note: Generate once and cache
const NoteParams& params = g_tracker_samples[sid];
int note_frames = 0;
- std::vector<float> note_data = generate_note_spectrogram(params, &note_frames);
+ std::vector<float> note_data =
+ generate_note_spectrogram(params, &note_frames);
if (note_frames > 0) {
// Allocate persistent storage for this note
@@ -116,12 +119,14 @@ void tracker_init() {
g_sample_synth_cache[sid] = synth_register_spectrogram(&spec);
g_spec_pool[slot].synth_id = g_sample_synth_cache[sid];
- g_spec_pool[slot].active = true; // Mark as permanently allocated
+ g_spec_pool[slot].active = true; // Mark as permanently allocated
#if defined(DEBUG_LOG_TRACKER)
if (g_sample_synth_cache[sid] == -1) {
- DEBUG_TRACKER( "[TRACKER INIT] Failed to cache generated sample_id=%d (freq=%.2f)\n",
- sid, params.base_freq);
+ DEBUG_TRACKER(
+ "[TRACKER INIT] Failed to cache generated sample_id=%d "
+ "(freq=%.2f)\n",
+ sid, params.base_freq);
}
#endif /* defined(DEBUG_LOG_TRACKER) */
}
@@ -131,7 +136,8 @@ void tracker_init() {
g_cache_initialized = true;
#if defined(DEBUG_LOG_TRACKER)
- DEBUG_TRACKER( "[TRACKER INIT] Cached %d unique samples\n", g_tracker_samples_count);
+ DEBUG_TRACKER("[TRACKER INIT] Cached %d unique samples\n",
+ g_tracker_samples_count);
#endif /* defined(DEBUG_LOG_TRACKER) */
}
}
@@ -170,18 +176,18 @@ static void trigger_note_event(const TrackerEvent& event) {
#if defined(DEBUG_LOG_TRACKER)
// VALIDATION: Check sample_id bounds
if (event.sample_id >= g_tracker_samples_count) {
- DEBUG_TRACKER( "[TRACKER ERROR] Invalid sample_id=%d (max=%d)\n",
- event.sample_id, g_tracker_samples_count - 1);
+ DEBUG_TRACKER("[TRACKER ERROR] Invalid sample_id=%d (max=%d)\n",
+ event.sample_id, g_tracker_samples_count - 1);
return;
}
// VALIDATION: Check volume and pan ranges
if (event.volume < 0.0f || event.volume > 2.0f) {
- DEBUG_TRACKER( "[TRACKER WARNING] Unusual volume=%.2f for sample_id=%d\n",
- event.volume, event.sample_id);
+ DEBUG_TRACKER("[TRACKER WARNING] Unusual volume=%.2f for sample_id=%d\n",
+ event.volume, event.sample_id);
}
if (event.pan < -1.0f || event.pan > 1.0f) {
- DEBUG_TRACKER( "[TRACKER WARNING] Invalid pan=%.2f for sample_id=%d\n",
- event.pan, event.sample_id);
+ DEBUG_TRACKER("[TRACKER WARNING] Invalid pan=%.2f for sample_id=%d\n",
+ event.pan, event.sample_id);
}
#endif /* defined(DEBUG_LOG_TRACKER) */
@@ -190,8 +196,9 @@ static void trigger_note_event(const TrackerEvent& event) {
#if defined(DEBUG_LOG_TRACKER)
if (cached_synth_id == -1) {
- DEBUG_TRACKER( "[TRACKER ERROR] No cached synth_id for sample_id=%d (init failed?)\n",
- event.sample_id);
+ DEBUG_TRACKER(
+ "[TRACKER ERROR] No cached synth_id for sample_id=%d (init failed?)\n",
+ event.sample_id);
return;
}
#endif /* defined(DEBUG_LOG_TRACKER) */
diff --git a/src/audio/tracker.h b/src/audio/tracker.h
index e6d479a..336f77f 100644
--- a/src/audio/tracker.h
+++ b/src/audio/tracker.h
@@ -42,4 +42,4 @@ extern const TrackerScore g_tracker_score;
void tracker_init();
void tracker_update(float music_time_sec);
-void tracker_reset(); // Reset tracker state (for tests/seeking)
+void tracker_reset(); // Reset tracker state (for tests/seeking)