From 5fc0517ffed67411ce5ca529742a2142acc2b5dc Mon Sep 17 00:00:00 2001 From: skal Date: Tue, 3 Feb 2026 07:39:54 +0100 Subject: feat: Finalize tracker asset-sample integration with unified pasting strategy --- src/audio/audio.cc | 18 +++++++++++ src/audio/audio.h | 11 +++++++ src/audio/tracker.cc | 75 ++++++++++++++++++++++++++-------------------- src/audio/tracker.h | 2 ++ src/main.cc | 43 -------------------------- src/tests/test_spectool.cc | 9 +----- src/tests/test_tracker.cc | 27 +++++------------ 7 files changed, 81 insertions(+), 104 deletions(-) (limited to 'src') diff --git a/src/audio/audio.cc b/src/audio/audio.cc index e4abbf8..ad324c0 100644 --- a/src/audio/audio.cc +++ b/src/audio/audio.cc @@ -3,6 +3,7 @@ // Implementation uses miniaudio for cross-platform support. #include "audio.h" +#include "util/asset_manager.h" #if !defined(DEMO_BUILD_TOOLS) #define MA_NO_FLAC @@ -16,6 +17,23 @@ #include +int register_spec_asset(AssetId id) { + size_t size; + const uint8_t* data = GetAsset(id, &size); + if (!data || size < sizeof(SpecHeader)) + return -1; + + const SpecHeader* header = (const SpecHeader*)data; + const float* spectral_data = (const float*)(data + sizeof(SpecHeader)); + + Spectrogram spec; + spec.spectral_data_a = spectral_data; + spec.spectral_data_b = spectral_data; // No double-buffer for static assets + spec.num_frames = header->num_frames; + + return synth_register_spectrogram(&spec); +} + static ma_device g_device; void audio_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, diff --git a/src/audio/audio.h b/src/audio/audio.h index 24db18f..a1ddb44 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -3,6 +3,15 @@ // Includes initialization, shutdown, and frame updates. #pragma once +#include "generated/assets.h" +#include + +struct SpecHeader { + char magic[4]; + int32_t version; + int32_t dct_size; + int32_t num_frames; +}; void audio_init(); void audio_start(); // Starts the audio device callback @@ -11,3 +20,5 @@ void audio_render_silent(float duration_sec); // Fast-forwards audio state #endif /* !defined(STRIP_ALL) */ void audio_update(); void audio_shutdown(); + +int register_spec_asset(AssetId id); diff --git a/src/audio/tracker.cc b/src/audio/tracker.cc index 9f9263d..5d99a45 100644 --- a/src/audio/tracker.cc +++ b/src/audio/tracker.cc @@ -1,8 +1,7 @@ -// This file is part of the 64k demo project. -// It implements the tracker runtime logic. - #include "tracker.h" +#include "audio.h" #include "audio/synth.h" +#include "util/asset_manager.h" #include #include @@ -31,9 +30,6 @@ static int get_free_pool_slot() { if (!g_spec_pool[i].active) return i; } - // If no free slot, find one where the synth voice is inactive - // (In a real implementation, we'd check if any voice is still using this) - // For now, just wrap around or return -1 return -1; } @@ -45,10 +41,8 @@ void tracker_update(float time_sec) { if (trigger.time_sec > time_sec) break; - // Trigger pattern! const TrackerPattern& pattern = g_tracker_patterns[trigger.pattern_id]; - // Generate spectrogram for the pattern int dest_num_frames = 0; std::vector pattern_data; @@ -56,39 +50,54 @@ void tracker_update(float time_sec) { for (uint32_t i = 0; i < pattern.num_events; ++i) { const TrackerEvent& event = pattern.events[i]; - const NoteParams& params = g_tracker_samples[event.sample_id]; - + + std::vector note_data; int note_frames = 0; - std::vector note_data = - generate_note_spectrogram(params, ¬e_frames); - 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); - } + 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); + } - // Register with synth - int slot = get_free_pool_slot(); - if (slot != -1) { - // Clean up old if needed - if (g_spec_pool[slot].synth_id != -1) { - synth_unregister_spectrogram(g_spec_pool[slot].synth_id); - delete[] g_spec_pool[slot].data; + 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) { + int slot = get_free_pool_slot(); + if (slot != -1) { + if (g_spec_pool[slot].synth_id != -1) { + synth_unregister_spectrogram(g_spec_pool[slot].synth_id); + delete[] g_spec_pool[slot].data; + } - g_spec_pool[slot].data = new float[pattern_data.size()]; - memcpy(g_spec_pool[slot].data, pattern_data.data(), - pattern_data.size() * sizeof(float)); + 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; + 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; + 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); + synth_trigger_voice(g_spec_pool[slot].synth_id, 1.0f, 0.0f); + } } g_last_trigger_idx++; diff --git a/src/audio/tracker.h b/src/audio/tracker.h index d97b483..49fcd3c 100644 --- a/src/audio/tracker.h +++ b/src/audio/tracker.h @@ -4,6 +4,7 @@ #pragma once #include "audio/gen.h" +#include "generated/assets.h" #include struct TrackerEvent { @@ -34,6 +35,7 @@ struct TrackerScore { // Global music data generated by tracker_compiler extern const NoteParams g_tracker_samples[]; extern const uint32_t g_tracker_samples_count; +extern const AssetId g_tracker_sample_assets[]; extern const TrackerPattern g_tracker_patterns[]; extern const uint32_t g_tracker_patterns_count; extern const TrackerScore g_tracker_score; diff --git a/src/main.cc b/src/main.cc index 97732da..7114460 100644 --- a/src/main.cc +++ b/src/main.cc @@ -19,30 +19,6 @@ #define SPEC_FRAMES 16 -struct SpecHeader { - char magic[4]; - int32_t version; - int32_t dct_size; - int32_t num_frames; -}; - -int register_spec_asset(AssetId id) { - size_t size; - const uint8_t* data = GetAsset(id, &size); - if (!data || size < sizeof(SpecHeader)) - return -1; - - const SpecHeader* header = (const SpecHeader*)data; - const float* spectral_data = (const float*)(data + sizeof(SpecHeader)); - - Spectrogram spec; - spec.spectral_data_a = spectral_data; - spec.spectral_data_b = spectral_data; // No double-buffer for static assets - spec.num_frames = header->num_frames; - - return synth_register_spectrogram(&spec); -} - static float* g_spec_buffer_a[SPEC_FRAMES * DCT_SIZE] = {0}; static float* g_spec_buffer_b[SPEC_FRAMES * DCT_SIZE] = {0}; @@ -107,10 +83,6 @@ int main(int argc, char** argv) { synth_init(); tracker_init(); - int kick_id = register_spec_asset(AssetId::ASSET_KICK_1); - int snare_id = register_spec_asset(AssetId::ASSET_SNARE_1); - int hihat_id = register_spec_asset(AssetId::ASSET_HIHAT_1); - // Still keep the dynamic tone for bass (can be integrated into tracker too) const float* g_spec_buffer_a = generate_tone(nullptr, 110.0f); // A2 const float* g_spec_buffer_b = generate_tone(nullptr, 110.0f); @@ -130,21 +102,6 @@ int main(int argc, char** argv) { const int step = beat_count % 16; - // Kick on 1, 9, 11, 14... - if (step == 0 || step == 8 || step == 10 || step == 13) { - synth_trigger_voice(kick_id, 0.6f, 0.0f); - } - - // Snare on 4, 12 - if (step == 4 || step == 12) { - synth_trigger_voice(snare_id, 0.48f, step & 8 ? -1.0f : 1.0f); - } - - // Hihat on every offbeat - if (step % 2 == 1) { - synth_trigger_voice(hihat_id, 0.3f, 0.3f); - } - // Bass pattern if (step % 4 == 0) { float* back_buffer = synth_begin_update(bass_id); diff --git a/src/tests/test_spectool.cc b/src/tests/test_spectool.cc index 37a74b7..984322a 100644 --- a/src/tests/test_spectool.cc +++ b/src/tests/test_spectool.cc @@ -11,14 +11,7 @@ #include "miniaudio.h" -// Redefine SpecHeader to avoid including spectool internals if possible, -// but for an E2E test we need to know the format. -struct SpecHeader { - char magic[4]; - int32_t version; - int32_t dct_size; - int32_t num_frames; -}; +// struct SpecHeader { ... } -> now in audio.h void generate_test_wav(const char* path, int duration_seconds) { ma_encoder_config config = diff --git a/src/tests/test_tracker.cc b/src/tests/test_tracker.cc index 95e746b..ad84163 100644 --- a/src/tests/test_tracker.cc +++ b/src/tests/test_tracker.cc @@ -25,28 +25,15 @@ void test_tracker_pattern_triggering() { synth_init(); tracker_init(); - // Need a minimal set of samples for generation - // These values should match what's expected by the music.track file - // For testing purposes, we define dummy data here. In a real scenario, - // we'd rely on the generated g_tracker_samples, g_tracker_patterns, etc. - // This test focuses on the logic of tracker_update, not the full audio generation pipeline. - - // Assuming g_tracker_score, g_tracker_patterns, and g_tracker_samples are available globally - // after tracker_compiler has run. - - // Test 1: No triggers initially, active voices should be 0 + // Test 1: Trigger patterns at 0.0f tracker_update(0.0f); - assert(synth_get_active_voice_count() == 2); // Expect 2 voices (one for each pattern triggered at 0.0f) - - // Test 2: Advance time to first trigger (0.0f in music.track for drum_loop and hihat_roll) - // In our dummy music.track, there are two patterns triggered at 0.0f - // Each pattern has multiple events which trigger voices. - tracker_update(0.1f); // Advance just past the 0.0f trigger point + printf("Actual active voice count: %d\n", synth_get_active_voice_count()); + // Expect 3 voices (one for each pattern triggered at 0.0f: drum_loop, hihat_roll, em_melody) + assert(synth_get_active_voice_count() == 3); - // The exact number of voices depends on the music.track content. - // For the given music.track, two patterns are triggered at 0.0f. - // Each pattern registers one spectrogram and triggers one voice. - assert(synth_get_active_voice_count() == 2); + // Test 2: Advance time slightly + tracker_update(0.1f); + assert(synth_get_active_voice_count() == 3); // Test 3: Advance further, no new triggers until 4.0f tracker_update(3.0f); -- cgit v1.2.3