summaryrefslogtreecommitdiff
path: root/src/audio
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-07 10:46:43 +0100
committerskal <pascal.massimino@gmail.com>2026-02-07 10:46:43 +0100
commit0f79b532c886f338ab80d506d4b06048e1784056 (patch)
tree28639cc355c91e294ced1dd1d3491b822c3e45b8 /src/audio
parent858f9d5e765bfc8f8f13b38fa0fab790139a0740 (diff)
refactor(audio): Remove tempo logic from WavDumpBackend
Fixed design flaw where WavDumpBackend had hardcoded tempo curves duplicating logic from main.cc. Backend should be passive and just write audio data, not implement simulation logic. Changes: - WavDumpBackend.start() is now non-blocking (was blocking simulation loop) - Added write_audio() method for passive audio writing - Removed all tempo scaling logic from backend (lines 62-97) - Removed tracker_update() and audio_render_ahead() calls from backend - Removed set_duration() (no longer needed, frontend controls duration) Frontend (main.cc): - Added WAV dump mode loop that drives simulation with its own tempo logic - Reads from ring buffer and calls wav_backend.write_audio() - Tempo logic stays in one place (no duplication) - Added ring_buffer.h include for AudioRingBuffer access Test (test_wav_dump.cc): - Updated to use frontend-driven approach - Test manually drives simulation loop - Calls write_audio() after each frame - Verifies passive backend behavior Design: - Backend: Passive file writer (init/start/write_audio/shutdown) - Frontend: Active simulation driver (tempo, tracker, rendering) - Zero duplication of tempo/simulation logic - Clean separation of concerns All 27 tests pass. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/audio')
-rw-r--r--src/audio/wav_dump_backend.cc111
-rw-r--r--src/audio/wav_dump_backend.h5
2 files changed, 26 insertions, 90 deletions
diff --git a/src/audio/wav_dump_backend.cc b/src/audio/wav_dump_backend.cc
index 9fa13f5..eda835c 100644
--- a/src/audio/wav_dump_backend.cc
+++ b/src/audio/wav_dump_backend.cc
@@ -5,10 +5,6 @@
#if !defined(STRIP_ALL)
-#include "audio.h"
-#include "ring_buffer.h"
-#include "synth.h"
-#include "tracker.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
@@ -16,7 +12,7 @@
WavDumpBackend::WavDumpBackend()
: wav_file_(nullptr), samples_written_(0),
output_filename_("audio_dump.wav"), is_active_(false),
- duration_sec_(60.0f) {
+ duration_sec_(0.0f) {
sample_buffer_.resize(kBufferSize);
}
@@ -28,10 +24,6 @@ void WavDumpBackend::set_output_file(const char* filename) {
output_filename_ = filename;
}
-void WavDumpBackend::set_duration(float seconds) {
- duration_sec_ = seconds;
-}
-
void WavDumpBackend::init() {
// Open WAV file for writing
wav_file_ = fopen(output_filename_, "wb");
@@ -49,90 +41,29 @@ void WavDumpBackend::init() {
void WavDumpBackend::start() {
is_active_ = true;
- printf("WAV dump started, rendering audio...\n");
-
- // Render audio in small chunks with tracker updates
- // This matches the seek logic in main.cc
- const float update_dt = 1.0f / 60.0f; // 60Hz update rate (matches main loop)
- const int frames_per_update = (int)(kSampleRate * update_dt); // ~533 frames
- const int samples_per_update =
- frames_per_update * 2; // Stereo: 2 samples per frame
- const int total_updates = (int)(duration_sec_ / update_dt);
-
- // Music time tracking
- float music_time = 0.0f;
- float tempo_scale = 1.0f;
- float physical_time = 0.0f;
-
- // Get ring buffer for reading
- AudioRingBuffer* ring_buffer = audio_get_ring_buffer();
-
- // Temporary buffer for each update chunk (stereo)
- std::vector<float> chunk_buffer(samples_per_update);
-
- for (int update_count = 0; update_count < total_updates; ++update_count) {
- // Update tempo scaling (matches main.cc phases)
- if (physical_time < 10.0f) {
- tempo_scale = 1.0f;
- } else if (physical_time < 15.0f) {
- const float progress = (physical_time - 10.0f) / 5.0f;
- tempo_scale = 1.0f + progress * 1.0f; // 1.0 → 2.0
- } else if (physical_time < 20.0f) {
- tempo_scale = 1.0f;
- } else if (physical_time < 25.0f) {
- const float progress = (physical_time - 20.0f) / 5.0f;
- tempo_scale = 1.0f - progress * 0.5f; // 1.0 → 0.5
- } else {
- tempo_scale = 1.0f;
- }
-
- // Advance music time
- music_time += update_dt * tempo_scale;
- physical_time += update_dt;
-
- // Update tracker (triggers patterns)
- tracker_update(music_time);
-
- // Fill ring buffer with upcoming audio
- audio_render_ahead(music_time, update_dt);
-
- // Read from ring buffer (same as audio callback would do)
- if (ring_buffer != nullptr) {
- ring_buffer->read(chunk_buffer.data(), samples_per_update);
- }
-
- // Convert float to int16 and write to WAV (stereo interleaved)
- for (int i = 0; i < samples_per_update; ++i) {
- float sample = chunk_buffer[i];
- if (sample > 1.0f)
- sample = 1.0f;
- if (sample < -1.0f)
- sample = -1.0f;
+ printf("WAV dump started (passive mode - frontend drives rendering)\n");
+}
- const int16_t sample_i16 = (int16_t)(sample * 32767.0f);
- fwrite(&sample_i16, sizeof(int16_t), 1, wav_file_);
- }
+void WavDumpBackend::write_audio(const float* samples, int num_samples) {
+ if (!is_active_ || wav_file_ == nullptr) {
+ return;
+ }
- samples_written_ += samples_per_update;
+ // Convert float samples to int16 and write to WAV
+ for (int i = 0; i < num_samples; ++i) {
+ float sample = samples[i];
- // Progress indicator
- if (update_count % 60 == 0) {
- printf(" Rendering: %.1fs / %.1fs (music: %.1fs, tempo: %.2fx)\r",
- physical_time, duration_sec_, music_time, tempo_scale);
- fflush(stdout);
- }
+ // Clamp to [-1.0, 1.0]
+ if (sample > 1.0f)
+ sample = 1.0f;
+ if (sample < -1.0f)
+ sample = -1.0f;
- // Call frame rendering hook (pass frames, not samples)
- on_frames_rendered(frames_per_update);
+ const int16_t sample_i16 = (int16_t)(sample * 32767.0f);
+ fwrite(&sample_i16, sizeof(int16_t), 1, wav_file_);
}
- printf(
- "\nWAV dump complete: %zu samples (%.2f seconds stereo, %.2f music "
- "time)\n",
- samples_written_, (float)samples_written_ / (kSampleRate * 2),
- music_time);
-
- is_active_ = false;
+ samples_written_ += num_samples;
}
void WavDumpBackend::shutdown() {
@@ -142,8 +73,12 @@ void WavDumpBackend::shutdown() {
fclose(wav_file_);
wav_file_ = nullptr;
- printf("WAV file written: %s\n", output_filename_);
+ const float duration_sec = (float)samples_written_ / (kSampleRate * 2);
+ printf("WAV file written: %s (%.2f seconds, %zu samples)\n",
+ output_filename_, duration_sec, samples_written_);
}
+
+ is_active_ = false;
}
void WavDumpBackend::write_wav_header(FILE* file, uint32_t num_samples) {
diff --git a/src/audio/wav_dump_backend.h b/src/audio/wav_dump_backend.h
index eb6e011..6482ef3 100644
--- a/src/audio/wav_dump_backend.h
+++ b/src/audio/wav_dump_backend.h
@@ -26,8 +26,9 @@ class WavDumpBackend : public AudioBackend {
// Set output filename (call before init())
void set_output_file(const char* filename);
- // Set duration in seconds (default: 60s, call before start())
- void set_duration(float seconds);
+ // Write audio data to WAV file (stereo interleaved float samples)
+ // num_samples: Total number of samples (2x num_frames for stereo)
+ void write_audio(const float* samples, int num_samples);
// Get total samples written
size_t get_samples_written() const {