diff options
Diffstat (limited to 'src/audio/tracker.cc')
| -rw-r--r-- | src/audio/tracker.cc | 241 |
1 files changed, 40 insertions, 201 deletions
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) |
