summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app/main.cc47
-rw-r--r--src/app/test_demo.cc7
-rw-r--r--src/audio/audio.cc29
-rw-r--r--src/audio/audio.h6
-rw-r--r--src/audio/tracker.cc10
-rw-r--r--src/tests/audio/test_jittered_audio.cc5
-rw-r--r--src/tests/audio/test_silent_backend.cc8
-rw-r--r--src/tests/audio/test_wav_dump.cc2
8 files changed, 92 insertions, 22 deletions
diff --git a/src/app/main.cc b/src/app/main.cc
index 90e3015..537da74 100644
--- a/src/app/main.cc
+++ b/src/app/main.cc
@@ -45,6 +45,8 @@ int main(int argc, char** argv) {
int width = 1280;
int height = 720;
bool dump_wav = false;
+ float dump_wav_start = -1.0f;
+ float dump_wav_duration = -1.0f;
bool tempo_test_enabled = false;
bool headless_mode = false;
float headless_duration = 30.0f;
@@ -73,6 +75,12 @@ int main(int argc, char** argv) {
if (i + 1 < argc && argv[i + 1][0] != '-') {
wav_output_file = argv[++i];
}
+ } else if (strcmp(argv[i], "--dump-wav-start") == 0 && i + 1 < argc) {
+ dump_wav_start = atof(argv[i + 1]);
+ ++i;
+ } else if (strcmp(argv[i], "--dump-wav-duration") == 0 && i + 1 < argc) {
+ dump_wav_duration = atof(argv[i + 1]);
+ ++i;
} else if (strcmp(argv[i], "--tempo") == 0) {
tempo_test_enabled = true;
#if defined(DEMO_HEADLESS)
@@ -198,8 +206,8 @@ int main(int argc, char** argv) {
}
#endif /* !defined(STRIP_ALL) */
- // Pre-fill using same pattern as main loop (100ms)
- fill_audio_buffer(0.1f, 0.0);
+ // Pre-fill ring buffer to target lookahead (prevents startup delay)
+ fill_audio_buffer(audio_get_required_prefill_time(), 0.0);
audio_start();
g_last_audio_time = audio_get_playback_time(); // Initialize after start
@@ -247,10 +255,29 @@ int main(int argc, char** argv) {
#if !defined(STRIP_ALL)
// In WAV dump mode, run headless simulation and write audio to file
if (dump_wav) {
- printf("Running WAV dump simulation...\n");
-
+ // Determine start and end times
+ const float start_time = (dump_wav_start >= 0.0f) ? dump_wav_start : 0.0f;
const float demo_duration = GetDemoDuration();
- const float max_duration = (demo_duration > 0.0f) ? demo_duration : 60.0f;
+ float end_time;
+ if (dump_wav_duration >= 0.0f) {
+ end_time = start_time + dump_wav_duration;
+ } else {
+ end_time = (demo_duration > 0.0f) ? demo_duration : 60.0f;
+ }
+
+ printf("Running WAV dump simulation (%.1fs - %.1fs)...\n", start_time,
+ end_time);
+
+ // Seek to start time if needed
+ if (start_time > 0.0f) {
+ const double step = 1.0 / 60.0;
+ for (double t = 0.0; t < start_time; t += step) {
+ fill_audio_buffer(step, t);
+ audio_render_silent((float)step);
+ }
+ printf("Seeked to %.1fs\n", start_time);
+ }
+
const float update_dt = 1.0f / 60.0f; // 60Hz update rate
const int frames_per_update = (int)(32000 * update_dt); // ~533 frames
const int samples_per_update = frames_per_update * 2; // Stereo
@@ -258,8 +285,8 @@ int main(int argc, char** argv) {
AudioRingBuffer* ring_buffer = audio_get_ring_buffer();
std::vector<float> chunk_buffer(samples_per_update);
- double physical_time = 0.0;
- while (physical_time < max_duration) {
+ double physical_time = start_time;
+ while (physical_time < end_time) {
// Update music time and tracker (using tempo logic from
// fill_audio_buffer)
fill_audio_buffer(update_dt, physical_time);
@@ -278,13 +305,13 @@ int main(int argc, char** argv) {
if ((int)physical_time % 1 == 0 &&
physical_time - update_dt < (int)physical_time) {
printf(" Rendering: %.1fs / %.1fs (music: %.1fs, tempo: %.2fx)\r",
- physical_time, max_duration, g_music_time, g_tempo_scale);
+ physical_time, end_time, g_music_time, g_tempo_scale);
fflush(stdout);
}
}
- printf("\nWAV dump complete: %.2fs physical, %.2fs music time\n",
- physical_time, g_music_time);
+ printf("\nWAV dump complete: %.2fs (%.2fs - %.2fs), music: %.2fs\n",
+ physical_time - start_time, start_time, physical_time, g_music_time);
#if defined(DEMO_HEADLESS)
g_wav_backend_ptr = nullptr;
diff --git a/src/app/test_demo.cc b/src/app/test_demo.cc
index 39dbcba..5775e74 100644
--- a/src/app/test_demo.cc
+++ b/src/app/test_demo.cc
@@ -280,14 +280,13 @@ int main(int argc, char** argv) {
g_tempo_scale = 1.0f; // No tempo variation
}
- g_music_time += audio_dt * g_tempo_scale;
-
g_audio_engine.update(g_music_time, audio_dt * g_tempo_scale);
audio_render_ahead(g_music_time, audio_dt * g_tempo_scale);
+ g_music_time += audio_dt * g_tempo_scale;
};
- // Pre-fill using same pattern as main loop (100ms)
- fill_audio_buffer(0.1f, 0.0);
+ // Pre-fill ring buffer to target lookahead (prevents startup delay)
+ fill_audio_buffer(audio_get_required_prefill_time(), 0.0);
audio_start();
g_last_audio_time = audio_get_playback_time();
diff --git a/src/audio/audio.cc b/src/audio/audio.cc
index d044b00..ba76a28 100644
--- a/src/audio/audio.cc
+++ b/src/audio/audio.cc
@@ -57,11 +57,35 @@ void audio_init() {
g_audio_backend->init();
}
+float audio_get_required_prefill_time() {
+ return (float)RING_BUFFER_LOOKAHEAD_MS / 1000.0f;
+}
+
+bool audio_is_prefilled() {
+ const int buffered = g_ring_buffer.available_read();
+ const float buffered_time =
+ (float)buffered / (RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS);
+ const float required = audio_get_required_prefill_time();
+ return buffered_time >= (required - 0.001f); // 1ms tolerance
+}
+
void audio_start() {
if (g_audio_backend == nullptr) {
printf("Cannot start: audio not initialized.\n");
return;
}
+
+#if !defined(STRIP_ALL)
+ if (!audio_is_prefilled()) {
+ const int buffered = g_ring_buffer.available_read();
+ const float buffered_ms =
+ (float)buffered / (RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS) *
+ 1000.0f;
+ printf("WARNING: Audio buffer not pre-filled (%.1fms < %.1fms)\n",
+ buffered_ms, audio_get_required_prefill_time() * 1000.0f);
+ }
+#endif
+
g_audio_backend->start();
}
@@ -73,6 +97,11 @@ void audio_render_ahead(float music_time, float dt, float target_fill) {
// Render in small chunks to keep synth time synchronized with tracker
// Chunk size: one frame's worth of audio (~16.6ms @ 60fps)
+ // TODO(timing): CRITICAL BUG - Truncation here may cause 180ms drift over 63 beats
+ // (int) cast loses fractional samples: 0.333 samples/frame * 2560 frames = 853 samples = 27ms
+ // But observed drift is 180ms, so this is not the only source (27ms < 180ms)
+ // NOTE: This is NOT a float vs double precision issue - floats handle <500s times fine
+ // See also: tracker.cc BPM timing calculation
const int chunk_frames = (int)(dt * RING_BUFFER_SAMPLE_RATE);
const int chunk_samples = chunk_frames * RING_BUFFER_CHANNELS;
diff --git a/src/audio/audio.h b/src/audio/audio.h
index 9d521e6..beb994f 100644
--- a/src/audio/audio.h
+++ b/src/audio/audio.h
@@ -23,6 +23,12 @@ struct SpecHeader {
void audio_init();
void audio_start(); // Starts the audio device callback
+// Get required pre-fill time (matches ring buffer lookahead)
+float audio_get_required_prefill_time();
+
+// Check if buffer is sufficiently pre-filled
+bool audio_is_prefilled();
+
// Ring buffer audio rendering (main thread fills buffer)
// target_fill: Target buffer fill time in seconds (default:
// RING_BUFFER_LOOKAHEAD_MS/1000)
diff --git a/src/audio/tracker.cc b/src/audio/tracker.cc
index 1c0a9b2..37f0683 100644
--- a/src/audio/tracker.cc
+++ b/src/audio/tracker.cc
@@ -234,6 +234,16 @@ static void trigger_note_event(const TrackerEvent& event,
}
void tracker_update(float music_time_sec, float dt_music_sec) {
+ // TODO(timing): CRITICAL BUG - Events trigger ~180ms early over 63 beats @ BPM=90
+ // Observed: Beat 63 snare at 41.82s in WAV, should be at 42.00s (180ms drift)
+ // NOTE: This is NOT a float vs double precision issue - floats handle <500s times fine
+ // Root cause unknown - suspects:
+ // 1. Systematic bias in time calculation (not random accumulation)
+ // 2. Truncation in audio.cc:103 chunk_frames = (int)(dt * sample_rate)
+ // 3. BPM calculation precision below (unit_duration_sec)
+ // 4. Mismatch between tracker time and actual sample rendering
+ // See also: audio.cc sample rendering truncation
+
// Unit-less timing: 1 unit = 4 beats (by convention)
const float BEATS_PER_UNIT = 4.0f;
const float unit_duration_sec =
diff --git a/src/tests/audio/test_jittered_audio.cc b/src/tests/audio/test_jittered_audio.cc
index d8260ec..d7c6a7d 100644
--- a/src/tests/audio/test_jittered_audio.cc
+++ b/src/tests/audio/test_jittered_audio.cc
@@ -42,11 +42,10 @@ void test_jittered_audio_basic() {
float music_time = 0.0f;
for (float t = 0.0f; t < total_time; t += dt) {
- music_time += dt; // Normal tempo
-
// Update tracker and fill buffer
tracker_update(music_time, dt);
audio_render_ahead(music_time, dt);
+ music_time += dt;
// Sleep minimal time to let audio thread run
std::this_thread::sleep_for(std::chrono::milliseconds(1));
@@ -114,7 +113,7 @@ void test_jittered_audio_with_acceleration() {
// Update tracker and fill buffer
tracker_update(music_time, dt * tempo_scale);
- audio_render_ahead(music_time, dt);
+ (void)audio_render_ahead(music_time, dt);
// Sleep minimal time to let audio thread run
std::this_thread::sleep_for(std::chrono::milliseconds(1));
diff --git a/src/tests/audio/test_silent_backend.cc b/src/tests/audio/test_silent_backend.cc
index 3dc1cd4..cecf72c 100644
--- a/src/tests/audio/test_silent_backend.cc
+++ b/src/tests/audio/test_silent_backend.cc
@@ -97,7 +97,7 @@ void test_silent_backend_tracking() {
assert(backend.get_voice_trigger_count() == 1);
// Render audio (calls on_frames_rendered)
- audio_render_ahead(0.0f, 0.1f); // Render ~0.1 seconds
+ (void)audio_render_ahead(0.0f, 0.1f); // Render ~0.1 seconds
assert(backend.get_frames_rendered() > 0);
// Reset stats
@@ -123,7 +123,7 @@ void test_audio_playback_time() {
assert(t0 == 0.0f);
// Render some audio
- audio_render_ahead(0.5f, 0.1f); // Advance music time to 0.5s
+ (void)audio_render_ahead(0.5f, 0.1f); // Advance music time to 0.5s
// Playback time should advance based on frames rendered
// Note: audio_get_playback_time() tracks cumulative frames consumed
@@ -131,7 +131,7 @@ void test_audio_playback_time() {
assert(t1 >= 0.0f); // Should have advanced
// Render more
- audio_render_ahead(1.0f, 0.5f);
+ (void)audio_render_ahead(1.0f, 0.5f);
float t2 = audio_get_playback_time();
assert(t2 >= t1); // Should continue advancing
@@ -152,7 +152,7 @@ void test_audio_buffer_partial_writes() {
// Note: With SilentBackend, frames_rendered won't increase because
// there's no audio callback consuming from the ring buffer
for (int i = 0; i < 10; ++i) {
- audio_render_ahead((float)i * 0.1f, 0.1f);
+ (void)audio_render_ahead((float)i * 0.1f, 0.1f);
}
// Buffer should have handled multiple writes correctly (no crash)
diff --git a/src/tests/audio/test_wav_dump.cc b/src/tests/audio/test_wav_dump.cc
index a0f2a4a..ce161a4 100644
--- a/src/tests/audio/test_wav_dump.cc
+++ b/src/tests/audio/test_wav_dump.cc
@@ -60,10 +60,10 @@ void test_wav_format_matches_live_audio() {
for (float t = 0.0f; t < duration; t += update_dt) {
// Update audio engine (triggers patterns)
fixture.engine().update(music_time, update_dt);
- music_time += update_dt;
// Render audio ahead
audio_render_ahead(music_time, update_dt);
+ music_time += update_dt;
// Read from ring buffer
if (ring_buffer != nullptr) {