summaryrefslogtreecommitdiff
path: root/src/audio/audio_engine.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio/audio_engine.cc')
-rw-r--r--src/audio/audio_engine.cc216
1 files changed, 216 insertions, 0 deletions
diff --git a/src/audio/audio_engine.cc b/src/audio/audio_engine.cc
new file mode 100644
index 0000000..184e4aa
--- /dev/null
+++ b/src/audio/audio_engine.cc
@@ -0,0 +1,216 @@
+// This file is part of the 64k demo project.
+// AudioEngine implementation.
+
+#include "audio_engine.h"
+#include "util/debug.h"
+#include <cstring>
+#include <algorithm>
+
+void AudioEngine::init() {
+ if (initialized_) {
+ return;
+ }
+
+ // Initialize in correct order (synth first, then tracker)
+ synth_init();
+ resource_mgr_.init();
+ tracker_init();
+
+ // Initialize sample-to-synth-id mapping
+ for (int i = 0; i < MAX_SPECTROGRAM_RESOURCES; ++i) {
+ sample_to_synth_id_[i] = -1;
+ }
+
+ current_time_ = 0.0f;
+ initialized_ = true;
+
+#if defined(DEBUG_LOG_AUDIO)
+ DEBUG_AUDIO("[AudioEngine] Initialized\n");
+#endif
+}
+
+void AudioEngine::shutdown() {
+ if (!initialized_) {
+ return;
+ }
+
+ resource_mgr_.shutdown();
+ synth_shutdown();
+ initialized_ = false;
+
+#if defined(DEBUG_LOG_AUDIO)
+ DEBUG_AUDIO("[AudioEngine] Shutdown\n");
+#endif
+}
+
+void AudioEngine::reset() {
+ if (!initialized_) {
+ return;
+ }
+
+ synth_init(); // Re-init synth (clears all state)
+ tracker_reset();
+ resource_mgr_.reset();
+
+ // Clear sample-to-synth mapping
+ for (int i = 0; i < MAX_SPECTROGRAM_RESOURCES; ++i) {
+ sample_to_synth_id_[i] = -1;
+ }
+
+ current_time_ = 0.0f;
+
+#if defined(DEBUG_LOG_AUDIO)
+ DEBUG_AUDIO("[AudioEngine] Reset\n");
+#endif
+}
+
+void AudioEngine::load_music_data(const TrackerScore* score,
+ 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) {
+ resource_mgr_.register_asset(i, sample_assets[i]);
+ } else {
+ resource_mgr_.register_procedural(i, samples[i]);
+ }
+ }
+
+#if defined(DEBUG_LOG_AUDIO)
+ DEBUG_AUDIO("[AudioEngine] Loaded music data: %u samples\n", sample_count);
+#endif
+}
+
+void AudioEngine::update(float music_time) {
+ 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);
+}
+
+void AudioEngine::render(float* output_buffer, int num_frames) {
+ synth_render(output_buffer, num_frames);
+}
+
+int AudioEngine::get_active_voice_count() const {
+ return synth_get_active_voice_count();
+}
+
+void AudioEngine::tracker_reset() {
+ ::tracker_reset();
+}
+
+void AudioEngine::trigger_sample(int sample_id, float volume, float pan) {
+ // Load resource on-demand if not cached
+ const Spectrogram* spec = resource_mgr_.get_or_load(sample_id);
+ if (spec == nullptr) {
+#if defined(DEBUG_LOG_AUDIO)
+ DEBUG_AUDIO("[AudioEngine ERROR] Failed to load sample_id=%d\n", sample_id);
+#endif
+ return;
+ }
+
+ // Register with synth (lazy registration)
+ 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);
+#endif
+ return;
+ }
+
+ // Trigger voice
+ synth_trigger_voice(synth_id, volume, pan);
+}
+
+int AudioEngine::get_or_register_synth_id(int sample_id) {
+ if (sample_id < 0 || sample_id >= MAX_SPECTROGRAM_RESOURCES) {
+ return -1;
+ }
+
+ // Already registered?
+ if (sample_to_synth_id_[sample_id] != -1) {
+ return sample_to_synth_id_[sample_id];
+ }
+
+ // Get resource (should already be loaded by trigger_sample)
+ const Spectrogram* spec = resource_mgr_.get_spectrogram(sample_id);
+ if (spec == nullptr) {
+ return -1;
+ }
+
+ // Register with synth
+ const int synth_id = synth_register_spectrogram(spec);
+ 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);
+#endif
+
+ return synth_id;
+}
+
+#if !defined(STRIP_ALL)
+void AudioEngine::seek(float target_time) {
+ if (!initialized_) {
+ return;
+ }
+
+#if defined(DEBUG_LOG_AUDIO)
+ DEBUG_AUDIO("[AudioEngine] Seeking to t=%.3fs (from t=%.3fs)\n", target_time, current_time_);
+#endif
+
+ // 1. Reset synth state (clear all active voices)
+ synth_init();
+
+ // 2. Reset tracker state
+ tracker_reset();
+
+ // 3. Clear sample-to-synth mapping (will be re-registered on demand)
+ for (int i = 0; i < MAX_SPECTROGRAM_RESOURCES; ++i) {
+ sample_to_synth_id_[i] = -1;
+ }
+
+ // 4. Pre-warm samples for target time range
+ const float prewarm_start = std::max(0.0f, target_time - 1.0f);
+ const float prewarm_end = target_time + 2.0f;
+ prewarm_for_time_range(prewarm_start, prewarm_end);
+
+ // 5. Simulate tracker up to target time (without audio)
+ const float dt = 0.1f;
+ for (float t = 0.0f; t < target_time; t += dt) {
+ update_silent(t);
+ }
+
+ // 6. Final update at exact target time
+ tracker_update(target_time);
+ current_time_ = target_time;
+
+#if defined(DEBUG_LOG_AUDIO)
+ DEBUG_AUDIO("[AudioEngine] Seek complete, loaded samples: %d\n",
+ resource_mgr_.get_loaded_count());
+#endif
+}
+
+void AudioEngine::prewarm_for_time_range(float start_time, float end_time) {
+ // TODO: Scan tracker score for patterns in this time range
+ // and pre-load their samples. For now, this is a placeholder.
+ // The proper implementation requires access to g_tracker_score
+ // 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);
+#endif
+}
+
+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);
+}
+#endif /* !defined(STRIP_ALL) */