diff options
Diffstat (limited to 'src/audio')
| -rw-r--r-- | src/audio/audio_engine.cc | 7 | ||||
| -rw-r--r-- | src/audio/spectrogram_resource_manager.cc | 89 | ||||
| -rw-r--r-- | src/audio/spectrogram_resource_manager.h | 3 | ||||
| -rw-r--r-- | src/audio/tracker.cc | 241 | ||||
| -rw-r--r-- | src/audio/tracker.h | 6 |
5 files changed, 129 insertions, 217 deletions
diff --git a/src/audio/audio_engine.cc b/src/audio/audio_engine.cc index c184324..1d6659d 100644 --- a/src/audio/audio_engine.cc +++ b/src/audio/audio_engine.cc @@ -13,7 +13,7 @@ void AudioEngine::init() { // Initialize in correct order (synth first, then tracker) synth_init(); resource_mgr_.init(); - tracker_init(); + tracker_init(&resource_mgr_); // Initialize sample-to-synth-id mapping for (int i = 0; i < MAX_SPECTROGRAM_RESOURCES; ++i) { @@ -48,8 +48,8 @@ void AudioEngine::reset() { } synth_init(); // Re-init synth (clears all state) - ::tracker_init(); // Re-register all spectrograms (synth slots now clean) resource_mgr_.reset(); + ::tracker_init(&resource_mgr_); // Re-register all spectrograms // Clear sample-to-synth mapping for (int i = 0; i < MAX_SPECTROGRAM_RESOURCES; ++i) { @@ -171,7 +171,8 @@ void AudioEngine::seek(float target_time) { synth_init(); // 2. Re-init tracker: re-registers all spectrograms with now-clean synth slots - ::tracker_init(); + resource_mgr_.reset(); + ::tracker_init(&resource_mgr_); // 3. Clear sample-to-synth mapping (will be re-registered on demand) for (int i = 0; i < MAX_SPECTROGRAM_RESOURCES; ++i) { diff --git a/src/audio/spectrogram_resource_manager.cc b/src/audio/spectrogram_resource_manager.cc index c0fb0a3..f182869 100644 --- a/src/audio/spectrogram_resource_manager.cc +++ b/src/audio/spectrogram_resource_manager.cc @@ -3,11 +3,17 @@ #include "spectrogram_resource_manager.h" #include "audio/audio.h" +#include "audio/dct.h" #include "procedural/generator.h" #include "util/debug.h" #include <cstring> #include <vector> +#if !defined(STRIP_ALL) +#include "audio/mp3_sample.h" +#include "audio/window.h" +#endif + void SpectrogramResourceManager::init() { for (int i = 0; i < MAX_SPECTROGRAM_RESOURCES; ++i) { resources_[i].owned_data = nullptr; @@ -32,10 +38,13 @@ void SpectrogramResourceManager::shutdown() { } void SpectrogramResourceManager::reset() { - // Clear state but keep registrations (useful for seeking) - // Don't free memory, just mark as unloaded + // Clear state and free owned memory, keep registrations for (int i = 0; i < MAX_SPECTROGRAM_RESOURCES; ++i) { if (resources_[i].state == LOADED) { + if (resources_[i].owned_data) { + delete[] resources_[i].owned_data; + resources_[i].owned_data = nullptr; + } resources_[i].state = REGISTERED; } } @@ -50,7 +59,9 @@ void SpectrogramResourceManager::register_asset(int sample_id, Resource& resource = resources_[sample_id]; resource.asset_id = asset_id; - resource.state = REGISTERED; + if (resource.state == UNREGISTERED) { + resource.state = REGISTERED; + } #if defined(DEBUG_LOG_ASSETS) DEBUG_ASSETS("[ResourceMgr] Registered asset sample_id=%d, asset_id=%d\n", @@ -67,7 +78,9 @@ void SpectrogramResourceManager::register_procedural(int sample_id, Resource& resource = resources_[sample_id]; resource.asset_id = AssetId::ASSET_LAST_ID; // Mark as procedural resource.proc_params = params; - resource.state = REGISTERED; + if (resource.state == UNREGISTERED) { + resource.state = REGISTERED; + } #if defined(DEBUG_LOG_ASSETS) DEBUG_ASSETS("[ResourceMgr] Registered procedural sample_id=%d, freq=%.2f\n", @@ -184,12 +197,19 @@ void SpectrogramResourceManager::try_evict_lru(float current_time) { void SpectrogramResourceManager::load_asset(Resource* resource) { size_t size; const uint8_t* data = GetAsset(resource->asset_id, &size); + if (data == nullptr || size == 0) { + return; + } - if (data == nullptr || size < sizeof(SpecHeader)) { -#if defined(DEBUG_LOG_ASSETS) - DEBUG_ASSETS("[ResourceMgr ERROR] Failed to load asset %d\n", - (int)resource->asset_id); +#if !defined(STRIP_ALL) + // MP3 assets: decode to spectrogram via OLA analysis + if (GetAssetType(resource->asset_id) == AssetType::MP3) { + load_mp3(resource, data, size); + return; + } #endif + + if (size < sizeof(SpecHeader)) { return; } @@ -201,12 +221,57 @@ void SpectrogramResourceManager::load_asset(Resource* resource) { resource->spec.num_frames = header->num_frames; resource->spec.version = header->version; resource->owned_data = nullptr; // Asset data is not owned +} -#if defined(DEBUG_LOG_ASSETS) - DEBUG_ASSETS("[ResourceMgr] Loaded asset %d: %d frames\n", - (int)resource->asset_id, header->num_frames); -#endif +#if !defined(STRIP_ALL) +void SpectrogramResourceManager::load_mp3(Resource* resource, + const uint8_t* data, size_t size) { + Mp3Decoder* dec = mp3_open(data, size); + if (!dec) + return; + + float window[DCT_SIZE]; + hann_window_512(window); + + std::vector<float> spec_data; + float pcm_buf[DCT_SIZE]; + float pcm_chunk[DCT_SIZE]; + float dct_chunk[DCT_SIZE]; + + memset(pcm_buf, 0, sizeof(pcm_buf)); + + for (;;) { + memmove(pcm_buf, pcm_buf + OLA_HOP_SIZE, OLA_HOP_SIZE * sizeof(float)); + const int decoded = mp3_decode(dec, OLA_HOP_SIZE, pcm_buf + OLA_HOP_SIZE); + if (decoded < OLA_HOP_SIZE) { + memset(pcm_buf + OLA_HOP_SIZE + decoded, 0, + (OLA_HOP_SIZE - decoded) * sizeof(float)); + } + + for (int i = 0; i < DCT_SIZE; ++i) + pcm_chunk[i] = pcm_buf[i] * window[i]; + fdct_512(pcm_chunk, dct_chunk); + spec_data.insert(spec_data.end(), dct_chunk, dct_chunk + DCT_SIZE); + + if (decoded == 0) + break; + } + + mp3_close(dec); + if (spec_data.empty()) + return; + + const int num_frames = (int)(spec_data.size() / DCT_SIZE); + resource->owned_data = new float[spec_data.size()]; + memcpy(resource->owned_data, spec_data.data(), + spec_data.size() * sizeof(float)); + + resource->spec.spectral_data_a = resource->owned_data; + resource->spec.spectral_data_b = resource->owned_data; + resource->spec.num_frames = num_frames; + resource->spec.version = SPEC_VERSION_V2_OLA; } +#endif void SpectrogramResourceManager::load_procedural(Resource* resource) { int note_frames = 0; diff --git a/src/audio/spectrogram_resource_manager.h b/src/audio/spectrogram_resource_manager.h index 45faa37..687f91e 100644 --- a/src/audio/spectrogram_resource_manager.h +++ b/src/audio/spectrogram_resource_manager.h @@ -68,6 +68,9 @@ class SpectrogramResourceManager { // Load implementation void load_asset(Resource* resource); void load_procedural(Resource* resource); +#if !defined(STRIP_ALL) + void load_mp3(Resource* resource, const uint8_t* data, size_t size); +#endif Resource resources_[MAX_SPECTROGRAM_RESOURCES]; int loaded_count_ = 0; diff --git a/src/audio/tracker.cc b/src/audio/tracker.cc index a511a62..e634333 100644 --- a/src/audio/tracker.cc +++ b/src/audio/tracker.cc @@ -1,5 +1,6 @@ #include "tracker.h" #include "audio.h" +#include "audio/spectrogram_resource_manager.h" #include "audio/synth.h" #include "ring_buffer.h" #include "util/asset_manager.h" @@ -7,13 +8,6 @@ #include "util/fatal_error.h" #include <cstring> #include <random> -#include <vector> - -#if !defined(STRIP_ALL) -#include "audio/dct.h" -#include "audio/mp3_sample.h" -#include "audio/window.h" -#endif // !defined(STRIP_ALL) static uint32_t g_last_trigger_idx = 0; @@ -27,201 +21,76 @@ struct ActivePattern { static ActivePattern g_active_patterns[MAX_SPECTROGRAMS]; -struct ManagedSpectrogram { - int synth_id; - float* data; - bool active; -}; - -// Simple pool for dynamic spectrograms (now for individual notes) -static ManagedSpectrogram g_spec_pool[MAX_SPECTROGRAMS]; -static int g_next_pool_slot = 0; // Round-robin allocation - // CACHE: Pre-registered synth_ids for all samples (indexed by sample_id) -// This eliminates redundant spectrogram generation and registration -static int g_sample_synth_cache[256]; // Max 256 unique samples -static bool g_cache_initialized = false; - -// Forward declarations -static int get_free_pool_slot(); - -#if !defined(STRIP_ALL) -// Decode an in-memory MP3 blob to a heap-allocated spectrogram (caller owns). -// Uses OLA analysis: 512-sample Hann window, OLA_HOP_SIZE advance per frame. -// Returns nullptr on error. Sets *out_num_frames to frame count. -static float* convert_mp3_to_spectrogram(const uint8_t* data, size_t size, - int* out_num_frames) { - *out_num_frames = 0; - Mp3Decoder* dec = mp3_open(data, size); - if (!dec) - return nullptr; +static int g_sample_synth_cache[MAX_SPECTROGRAM_RESOURCES]; - float window[DCT_SIZE]; - hann_window_512(window); - - std::vector<float> spec_data; - float pcm_buf[DCT_SIZE]; - float pcm_chunk[DCT_SIZE]; - float dct_chunk[DCT_SIZE]; - - // Sliding-window OLA analysis: advance OLA_HOP_SIZE samples per frame. - // First iteration: pcm_buf is zero-initialized (silence before signal start). - memset(pcm_buf, 0, sizeof(pcm_buf)); - - for (;;) { - // Slide left by OLA_HOP_SIZE; fill right half with new samples. - memmove(pcm_buf, pcm_buf + OLA_HOP_SIZE, OLA_HOP_SIZE * sizeof(float)); - const int decoded = mp3_decode(dec, OLA_HOP_SIZE, pcm_buf + OLA_HOP_SIZE); - if (decoded < OLA_HOP_SIZE) { - memset(pcm_buf + OLA_HOP_SIZE + decoded, 0, - (OLA_HOP_SIZE - decoded) * sizeof(float)); - } - - // Window + DCT the current 512-sample frame. - for (int i = 0; i < DCT_SIZE; ++i) - pcm_chunk[i] = pcm_buf[i] * window[i]; - fdct_512(pcm_chunk, dct_chunk); - spec_data.insert(spec_data.end(), dct_chunk, dct_chunk + DCT_SIZE); - - if (decoded == 0) - break; +static int get_free_pattern_slot() { + for (int i = 0; i < MAX_SPECTROGRAMS; ++i) { + if (!g_active_patterns[i].active) + return i; } - - mp3_close(dec); - if (spec_data.empty()) - return nullptr; - - const int num_frames = (int)(spec_data.size() / DCT_SIZE); - float* result = new float[spec_data.size()]; - memcpy(result, spec_data.data(), spec_data.size() * sizeof(float)); - *out_num_frames = num_frames; - return result; + return -1; // No free slots } -#endif // !defined(STRIP_ALL) -void tracker_init() { +void tracker_init(SpectrogramResourceManager* resource_mgr) { g_last_trigger_idx = 0; - g_next_pool_slot = 0; - - // Free any previously allocated generated note data to prevent leaks. - // Must run before the pool is zeroed (relies on .active/.data still set). - if (g_cache_initialized) { - for (int i = 0; i < MAX_SPECTROGRAMS; ++i) { - if (g_spec_pool[i].data != nullptr && g_spec_pool[i].active) { - delete[] g_spec_pool[i].data; - } - } - } for (int i = 0; i < MAX_SPECTROGRAMS; ++i) { - g_spec_pool[i].synth_id = -1; - g_spec_pool[i].data = nullptr; - g_spec_pool[i].active = false; g_active_patterns[i].active = false; } - // Initialize sample cache - { - for (int i = 0; i < 256; ++i) { - g_sample_synth_cache[i] = -1; - } + for (int i = 0; i < MAX_SPECTROGRAM_RESOURCES; ++i) { + g_sample_synth_cache[i] = -1; + } - // Pre-register all unique samples (assets + generated notes) - for (uint32_t sid = 0; sid < g_tracker_samples_count; ++sid) { - AssetId aid = g_tracker_sample_assets[sid]; + // Pre-register all unique samples (assets + generated notes) + for (uint32_t sid = 0; sid < g_tracker_samples_count; ++sid) { + AssetId aid = g_tracker_sample_assets[sid]; + if (resource_mgr) { + // Unified path: register metadata, load eagerly, register with synth + if (aid != AssetId::ASSET_LAST_ID) { + resource_mgr->register_asset(sid, aid); + } else { + resource_mgr->register_procedural(sid, g_tracker_samples[sid]); + } + const Spectrogram* spec = resource_mgr->get_or_load(sid); + if (spec) { + g_sample_synth_cache[sid] = synth_register_spectrogram(spec); + } + } else { + // Legacy path (no resource manager — standalone tests) if (aid != AssetId::ASSET_LAST_ID) { - // ASSET sample: Load once and cache size_t size; const uint8_t* data = GetAsset(aid, &size); #if !defined(STRIP_ALL) if (data && size > 0 && GetAssetType(aid) == AssetType::MP3) { - int num_frames = 0; - float* spec_data = - convert_mp3_to_spectrogram(data, size, &num_frames); - if (spec_data && num_frames > 0) { - const int slot = get_free_pool_slot(); - g_spec_pool[slot].data = spec_data; - g_spec_pool[slot].active = true; - Spectrogram spec; - spec.spectral_data_a = spec_data; - spec.spectral_data_b = spec_data; - spec.num_frames = num_frames; - spec.version = SPEC_VERSION_V2_OLA; - g_sample_synth_cache[sid] = synth_register_spectrogram(&spec); - g_spec_pool[slot].synth_id = g_sample_synth_cache[sid]; - } - } else + continue; // MP3 requires resource manager + } #else if (data != nullptr && GetAssetType(aid) == AssetType::MP3) { - continue; // MP3 decoding not available in STRIP_ALL builds + continue; } #endif - if (data && size >= sizeof(SpecHeader)) { + if (data && size >= sizeof(SpecHeader)) { const SpecHeader* header = (const SpecHeader*)data; - const int note_frames = header->num_frames; - const float* spectral_data = - (const float*)(data + sizeof(SpecHeader)); - Spectrogram spec; - spec.spectral_data_a = spectral_data; - spec.spectral_data_b = spectral_data; - spec.num_frames = note_frames; + spec.spectral_data_a = (const float*)(data + sizeof(SpecHeader)); + spec.spectral_data_b = spec.spectral_data_a; + spec.num_frames = header->num_frames; spec.version = header->version; - - g_sample_synth_cache[sid] = synth_register_spectrogram(&spec); - -#if defined(DEBUG_LOG_TRACKER) - if (g_sample_synth_cache[sid] == -1) { - DEBUG_TRACKER( - "[TRACKER INIT] Failed to cache asset sample_id=%d (aid=%d)\n", - sid, (int)aid); - } -#endif /* defined(DEBUG_LOG_TRACKER) */ - } - } else { - // GENERATED note: Generate once and cache - const NoteParams& params = g_tracker_samples[sid]; - int note_frames = 0; - std::vector<float> note_data = - generate_note_spectrogram(params, ¬e_frames); - - if (note_frames > 0) { - // Allocate persistent storage for this note - const int slot = get_free_pool_slot(); - g_spec_pool[slot].data = new float[note_data.size()]; - memcpy(g_spec_pool[slot].data, note_data.data(), - note_data.size() * sizeof(float)); - - Spectrogram spec; - spec.spectral_data_a = g_spec_pool[slot].data; - spec.spectral_data_b = g_spec_pool[slot].data; - spec.num_frames = note_frames; - spec.version = SPEC_VERSION_V1; - g_sample_synth_cache[sid] = synth_register_spectrogram(&spec); - g_spec_pool[slot].synth_id = g_sample_synth_cache[sid]; - g_spec_pool[slot].active = true; // Mark as permanently allocated - -#if defined(DEBUG_LOG_TRACKER) - if (g_sample_synth_cache[sid] == -1) { - DEBUG_TRACKER( - "[TRACKER INIT] Failed to cache generated sample_id=%d " - "(freq=%.2f)\n", - sid, params.base_freq); - } -#endif /* defined(DEBUG_LOG_TRACKER) */ } } + // Note: procedural notes without resource_mgr are not supported + // (requires std::vector allocation — use resource_mgr path) } - - g_cache_initialized = true; + } #if defined(DEBUG_LOG_TRACKER) - DEBUG_TRACKER("[TRACKER INIT] Cached %d unique samples\n", - g_tracker_samples_count); -#endif /* defined(DEBUG_LOG_TRACKER) */ - } + DEBUG_TRACKER("[TRACKER INIT] Cached %d unique samples\n", + g_tracker_samples_count); +#endif // Validate that all pattern events are sorted by unit_time // (required for early-exit optimization in tracker_update) @@ -247,36 +116,6 @@ void tracker_reset() { } } -static int get_free_pool_slot() { - // Try to find an inactive slot first (unused slots) - for (int i = 0; i < MAX_SPECTROGRAMS; ++i) { - if (!g_spec_pool[i].active) - return i; - } - - // If all slots are active, reuse the oldest one (round-robin) - // Clean up the old slot before reuse - const int slot = g_next_pool_slot; - g_next_pool_slot = (g_next_pool_slot + 1) % MAX_SPECTROGRAMS; - - if (g_spec_pool[slot].synth_id >= 0) { - synth_unregister_spectrogram(g_spec_pool[slot].synth_id); - } - delete[] g_spec_pool[slot].data; - g_spec_pool[slot].data = nullptr; - g_spec_pool[slot].synth_id = -1; - g_spec_pool[slot].active = false; - return slot; -} - -static int get_free_pattern_slot() { - for (int i = 0; i < MAX_SPECTROGRAMS; ++i) { - if (!g_active_patterns[i].active) - return i; - } - return -1; // No free slots -} - // Helper to trigger a single note event (OPTIMIZED with caching) // start_offset_samples: How many samples into the future to trigger (for // sample-accurate timing) diff --git a/src/audio/tracker.h b/src/audio/tracker.h index f471bd8..a96a801 100644 --- a/src/audio/tracker.h +++ b/src/audio/tracker.h @@ -7,6 +7,8 @@ #include "generated/assets.h" #include <cstdint> +class SpectrogramResourceManager; + struct TrackerEvent { float unit_time; // Unit-less time within pattern (0.0 to pattern.unit_length) uint16_t sample_id; @@ -44,6 +46,8 @@ extern const TrackerPattern g_tracker_patterns[]; extern const uint32_t g_tracker_patterns_count; extern const TrackerScore g_tracker_score; -void tracker_init(); +// Initialize tracker. If resource_mgr is provided, spectrograms are loaded +// through it (unified ownership). If nullptr, tracker manages its own storage. +void tracker_init(SpectrogramResourceManager* resource_mgr = nullptr); void tracker_update(double music_time_sec, double dt_music_sec); void tracker_reset(); // Reset tracker state (for tests/seeking) |
