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