#include "tracker.h" #include "audio.h" #include "audio/synth.h" #include "util/asset_manager.h" #include #include static uint32_t g_last_trigger_idx = 0; struct ManagedSpectrogram { int synth_id; float* data; bool active; }; // Simple pool for dynamic spectrograms static ManagedSpectrogram g_spec_pool[MAX_SPECTROGRAMS]; static int g_next_pool_slot = 0; // Round-robin allocation void tracker_init() { g_last_trigger_idx = 0; g_next_pool_slot = 0; 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; } } 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) // This automatically handles cleanup of old patterns const int slot = g_next_pool_slot; g_next_pool_slot = (g_next_pool_slot + 1) % MAX_SPECTROGRAMS; return slot; } void tracker_update(float time_sec) { while (g_last_trigger_idx < g_tracker_score.num_triggers) { const TrackerPatternTrigger& trigger = g_tracker_score.triggers[g_last_trigger_idx]; if (trigger.time_sec > time_sec) break; const TrackerPattern& pattern = g_tracker_patterns[trigger.pattern_id]; int dest_num_frames = 0; std::vector pattern_data; float beat_to_sec = 60.0f / g_tracker_score.bpm; for (uint32_t i = 0; i < pattern.num_events; ++i) { const TrackerEvent& event = pattern.events[i]; std::vector note_data; int note_frames = 0; AssetId aid = g_tracker_sample_assets[event.sample_id]; if (aid != AssetId::ASSET_LAST_ID) { size_t size; const uint8_t* data = GetAsset(aid, &size); if (data && size >= sizeof(SpecHeader)) { const SpecHeader* header = (const SpecHeader*)data; note_frames = header->num_frames; const float* src_spectral_data = (const float*)(data + sizeof(SpecHeader)); note_data.assign(src_spectral_data, src_spectral_data + (size_t)note_frames * DCT_SIZE); } } else { const NoteParams& params = g_tracker_samples[event.sample_id]; note_data = generate_note_spectrogram(params, ¬e_frames); } if (note_frames > 0) { int frame_offset = (int)(event.beat * beat_to_sec * 32000.0f / DCT_SIZE); paste_spectrogram(pattern_data, &dest_num_frames, note_data, note_frames, frame_offset); } } if (dest_num_frames > 0) { const int slot = get_free_pool_slot(); // Clean up old data in this slot if reusing if (g_spec_pool[slot].synth_id != -1) { synth_unregister_spectrogram(g_spec_pool[slot].synth_id); g_spec_pool[slot].synth_id = -1; } if (g_spec_pool[slot].data != nullptr) { delete[] g_spec_pool[slot].data; g_spec_pool[slot].data = nullptr; } // Allocate and register new pattern g_spec_pool[slot].data = new float[pattern_data.size()]; memcpy(g_spec_pool[slot].data, pattern_data.data(), pattern_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 = dest_num_frames; g_spec_pool[slot].synth_id = synth_register_spectrogram(&spec); g_spec_pool[slot].active = true; synth_trigger_voice(g_spec_pool[slot].synth_id, 1.0f, 0.0f); } g_last_trigger_idx++; } }