// This file is part of the 64k demo project. // AudioEngine implementation. #include "audio_engine.h" #include "util/debug.h" #include #include 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) */