summaryrefslogtreecommitdiff
path: root/src/audio/tracker.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio/tracker.cc')
-rw-r--r--src/audio/tracker.cc241
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, &note_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)