summaryrefslogtreecommitdiff
path: root/src/audio
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-08 11:13:53 +0100
committerskal <pascal.massimino@gmail.com>2026-02-08 11:13:53 +0100
commit392d03c0c05f24be3210a04d9a50cd9714d1e265 (patch)
tree3c4985879bdf614536020f2061c299ecf442677f /src/audio
parentd2d20763ac61f59187d261bb7d6dedcab525bc54 (diff)
refactor(audio): Finalize audio sync, update docs, and clean up test artifacts
- Implemented sample-accurate audio-visual synchronization by using the hardware audio clock as the master time source. - Ensured tracker updates and visual rendering are slaved to the stable audio clock. - Corrected to accept and use delta time for sample-accurate event scheduling. - Updated all relevant tests (, , , , ) to use the new delta time parameter. - Added function. - Marked Task #71 as completed in . - Updated to reflect the audio system's current status. - Created a handoff document: . - Removed temporary peak log files (, ).
Diffstat (limited to 'src/audio')
-rw-r--r--src/audio/audio_engine.cc8
-rw-r--r--src/audio/audio_engine.h2
-rw-r--r--src/audio/synth.cc4
-rw-r--r--src/audio/synth.h1
-rw-r--r--src/audio/tracker.cc36
-rw-r--r--src/audio/tracker.h2
6 files changed, 31 insertions, 22 deletions
diff --git a/src/audio/audio_engine.cc b/src/audio/audio_engine.cc
index 6d2ee92..d11303c 100644
--- a/src/audio/audio_engine.cc
+++ b/src/audio/audio_engine.cc
@@ -82,14 +82,14 @@ void AudioEngine::load_music_data(const TrackerScore* score,
#endif
}
-void AudioEngine::update(float music_time) {
+void AudioEngine::update(float music_time, float dt) {
current_time_ = music_time;
// Pre-warm samples needed in next 2 seconds (lazy loading strategy)
// TODO: Implement pre-warming based on upcoming pattern triggers
// Update tracker (triggers events)
- tracker_update(music_time);
+ tracker_update(music_time, dt);
}
void AudioEngine::render(float* output_buffer, int num_frames) {
@@ -191,7 +191,7 @@ void AudioEngine::seek(float target_time) {
}
// 6. Final update at exact target time
- tracker_update(target_time);
+ tracker_update(target_time, 0.0f);
current_time_ = target_time;
#if defined(DEBUG_LOG_AUDIO)
@@ -216,6 +216,6 @@ void AudioEngine::update_silent(float music_time) {
// Update tracker without triggering audio (for fast-forward/seeking)
// This is a placeholder - proper implementation requires tracker support
// for silent updates. For now, we just update normally.
- tracker_update(music_time);
+ tracker_update(music_time, 0.0f);
}
#endif /* !defined(STRIP_ALL) */
diff --git a/src/audio/audio_engine.h b/src/audio/audio_engine.h
index 95761ad..699213d 100644
--- a/src/audio/audio_engine.h
+++ b/src/audio/audio_engine.h
@@ -21,7 +21,7 @@ class AudioEngine {
const AssetId* sample_assets, uint32_t sample_count);
// Update loop
- void update(float music_time);
+ void update(float music_time, float dt);
#if !defined(STRIP_ALL)
// Timeline seeking (debugging only)
diff --git a/src/audio/synth.cc b/src/audio/synth.cc
index e790c12..5fadf3c 100644
--- a/src/audio/synth.cc
+++ b/src/audio/synth.cc
@@ -67,6 +67,10 @@ void synth_set_tempo_scale(float tempo_scale) {
g_tempo_scale = tempo_scale;
}
+float synth_get_tempo_scale() {
+ return g_tempo_scale;
+}
+
int synth_register_spectrogram(const Spectrogram* spec) {
#if defined(DEBUG_LOG_SYNTH)
// VALIDATION: Check spectrogram pointer and data
diff --git a/src/audio/synth.h b/src/audio/synth.h
index b2625b3..3a42a61 100644
--- a/src/audio/synth.h
+++ b/src/audio/synth.h
@@ -43,6 +43,7 @@ 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)
+float synth_get_tempo_scale();
int synth_get_active_voice_count();
diff --git a/src/audio/tracker.cc b/src/audio/tracker.cc
index 2bb4159..42e074d 100644
--- a/src/audio/tracker.cc
+++ b/src/audio/tracker.cc
@@ -215,19 +215,22 @@ static void trigger_note_event(const TrackerEvent& event,
start_offset_samples);
}
-void tracker_update(float music_time_sec) {
+void tracker_update(float music_time_sec, float dt_music_sec) {
// Unit-less timing: 1 unit = 4 beats (by convention)
const float BEATS_PER_UNIT = 4.0f;
const float unit_duration_sec =
(BEATS_PER_UNIT / g_tracker_score.bpm) * 60.0f;
+ const float end_music_time = music_time_sec + dt_music_sec;
+ const float tempo_scale = synth_get_tempo_scale();
+
// Step 1: Process new pattern triggers
while (g_last_trigger_idx < g_tracker_score.num_triggers) {
const TrackerPatternTrigger& trigger =
g_tracker_score.triggers[g_last_trigger_idx];
const float trigger_time_sec = trigger.unit_time * unit_duration_sec;
- if (trigger_time_sec > music_time_sec)
+ if (trigger_time_sec > end_music_time)
break;
// Add this pattern to active patterns list
@@ -243,11 +246,7 @@ void tracker_update(float music_time_sec) {
}
// Step 2: Update all active patterns and trigger individual events
- // NOTE: We trigger events immediately when their time passes (no sample
- // offsets) This gives ~16ms quantization (60fps) which is acceptable Sample
- // offsets don't work with tempo scaling because music_time and render_time
- // are in different time domains (tempo-scaled vs physical)
-
+ // Sample-accurate timing: Calculate offset relative to music_time_sec
for (int i = 0; i < MAX_SPECTROGRAMS; ++i) {
if (!g_active_patterns[i].active)
continue;
@@ -255,26 +254,31 @@ void tracker_update(float music_time_sec) {
ActivePattern& active = g_active_patterns[i];
const TrackerPattern& pattern = g_tracker_patterns[active.pattern_id];
- // Calculate elapsed unit-less time since pattern started
- const float elapsed_music_time = music_time_sec - active.start_music_time;
- const float elapsed_units = elapsed_music_time / unit_duration_sec;
-
// Trigger all events that have passed their unit time
while (active.next_event_idx < pattern.num_events) {
const TrackerEvent& event = pattern.events[active.next_event_idx];
+ const float event_music_time =
+ active.start_music_time + event.unit_time * unit_duration_sec;
- if (event.unit_time > elapsed_units)
+ if (event_music_time > end_music_time)
break; // This event hasn't reached its time yet
- // Trigger this event immediately (no sample offset)
- // Timing quantization: ~16ms at 60fps, acceptable for rhythm
- trigger_note_event(event, 0);
+ // Sample-accurate timing:
+ // Offset = (music_time_delta / tempo_scale) * sample_rate
+ int sample_offset = 0;
+ if (event_music_time > music_time_sec) {
+ sample_offset = (int)((event_music_time - music_time_sec) /
+ tempo_scale * 32000.0f);
+ }
+ trigger_note_event(event, sample_offset);
active.next_event_idx++;
}
// Pattern remains active until full duration elapses
- if (elapsed_units >= pattern.unit_length) {
+ const float pattern_end_time =
+ active.start_music_time + pattern.unit_length * unit_duration_sec;
+ if (pattern_end_time <= end_music_time) {
active.active = false;
}
}
diff --git a/src/audio/tracker.h b/src/audio/tracker.h
index 3ef06a1..8e7a63f 100644
--- a/src/audio/tracker.h
+++ b/src/audio/tracker.h
@@ -42,5 +42,5 @@ extern const uint32_t g_tracker_patterns_count;
extern const TrackerScore g_tracker_score;
void tracker_init();
-void tracker_update(float music_time_sec);
+void tracker_update(float music_time_sec, float dt_music_sec);
void tracker_reset(); // Reset tracker state (for tests/seeking)