diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-04 14:38:04 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-04 14:38:04 +0100 |
| commit | af4b27dfb0862bc4a76d716dacd24acf73ee059a (patch) | |
| tree | 2d17b230ef69d6567b85e8bf31970e4490af1bad /src | |
| parent | 02336d13cbb5c3109583cd258696e88afe7cf9bb (diff) | |
feat(audio): Trigger pattern events individually for tempo scaling
Refactored tracker system to trigger individual events as separate voices
instead of compositing patterns into single spectrograms. This enables
notes within patterns to respect tempo scaling dynamically.
Key Changes:
- Added ActivePattern tracking with start_music_time and next_event_idx
- Individual events trigger when their beat time is reached
- Elapsed beats calculated dynamically: (music_time - start_time) / beat_duration
- Removed pattern compositing logic (paste_spectrogram)
- Each note now triggers as separate voice with volume/pan parameters
Behavior:
- Tempo scaling (via music_time) now affects note spacing within patterns
- At 2.0x tempo: patterns trigger 2x faster AND notes within play 2x faster
- At 0.5x tempo: patterns trigger 2x slower AND notes within play 2x slower
Testing:
- Updated test_tracker to verify event-based triggering at specific beat times
- All 17 tests pass (100%)
- WAV dump confirms tempo scaling works correctly:
* 0-10s: steady 1.00x tempo
* 10-15s: acceleration to 2.00x tempo
* 15-20s: reset to 1.00x tempo
* 20-25s: deceleration to 0.50x tempo
* 25s+: return to normal
Result: Music time advances at variable rates (61.24s in 60s physical time),
and notes within patterns correctly accelerate/decelerate with tempo changes.
handoff(Claude): Tempo scaling now affects notes within patterns
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/audio/tracker.cc | 175 | ||||
| -rw-r--r-- | src/audio/tracker.h | 3 | ||||
| -rw-r--r-- | src/generated/music_data.cc | 299 | ||||
| -rw-r--r-- | src/tests/test_tracker.cc | 47 |
4 files changed, 285 insertions, 239 deletions
diff --git a/src/audio/tracker.cc b/src/audio/tracker.cc index 8f3da38..cb97f23 100644 --- a/src/audio/tracker.cc +++ b/src/audio/tracker.cc @@ -7,13 +7,23 @@ static uint32_t g_last_trigger_idx = 0; +// Active pattern instance tracking +struct ActivePattern { + uint16_t pattern_id; + float start_music_time; // When this pattern was triggered (music time) + uint32_t next_event_idx; // Next event to trigger within this pattern + bool active; +}; + +static ActivePattern g_active_patterns[MAX_SPECTROGRAMS]; + struct ManagedSpectrogram { int synth_id; float* data; bool active; }; -// Simple pool for dynamic spectrograms +// 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 @@ -24,6 +34,14 @@ void tracker_init() { 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; + } +} + +void tracker_reset() { + g_last_trigger_idx = 0; + for (int i = 0; i < MAX_SPECTROGRAMS; ++i) { + g_active_patterns[i].active = false; } } @@ -41,81 +59,118 @@ static int get_free_pool_slot() { 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]; +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 +} - if (trigger.time_sec > time_sec) - break; +// Helper to trigger a single note event +static void trigger_note_event(const TrackerEvent& event) { + std::vector<float> note_data; + int note_frames = 0; + + // Load or generate the note spectrogram + 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); + } - const TrackerPattern& pattern = g_tracker_patterns[trigger.pattern_id]; + if (note_frames > 0) { + const int slot = get_free_pool_slot(); - int dest_num_frames = 0; - std::vector<float> pattern_data; + // 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; + } - float beat_to_sec = 60.0f / g_tracker_score.bpm; + // Allocate and register new note + g_spec_pool[slot].data = new float[note_data.size()]; + memcpy(g_spec_pool[slot].data, note_data.data(), + note_data.size() * sizeof(float)); - for (uint32_t i = 0; i < pattern.num_events; ++i) { - const TrackerEvent& event = pattern.events[i]; + 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; - std::vector<float> note_data; - int note_frames = 0; + 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, event.volume, event.pan); + } +} - 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); - } +void tracker_update(float music_time_sec) { + // Step 1: Process new pattern triggers + while (g_last_trigger_idx < g_tracker_score.num_triggers) { + const TrackerPatternTrigger& trigger = + g_tracker_score.triggers[g_last_trigger_idx]; - 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 (trigger.time_sec > music_time_sec) + break; + + // Add this pattern to active patterns list + const int slot = get_free_pattern_slot(); + if (slot != -1) { + g_active_patterns[slot].pattern_id = trigger.pattern_id; + g_active_patterns[slot].start_music_time = trigger.time_sec; + g_active_patterns[slot].next_event_idx = 0; + g_active_patterns[slot].active = true; } - if (dest_num_frames > 0) { - const int slot = get_free_pool_slot(); + g_last_trigger_idx++; + } - // 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; - } + // Step 2: Update all active patterns and trigger individual events + const float beat_duration = 60.0f / g_tracker_score.bpm; + + for (int i = 0; i < MAX_SPECTROGRAMS; ++i) { + if (!g_active_patterns[i].active) + continue; - // 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)); + ActivePattern& active = g_active_patterns[i]; + const TrackerPattern& pattern = g_tracker_patterns[active.pattern_id]; - 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; + // Calculate elapsed beats since pattern started + const float elapsed_music_time = music_time_sec - active.start_music_time; + const float elapsed_beats = elapsed_music_time / beat_duration; - g_spec_pool[slot].synth_id = synth_register_spectrogram(&spec); - g_spec_pool[slot].active = true; + // Trigger all events that have passed their beat time + while (active.next_event_idx < pattern.num_events) { + const TrackerEvent& event = pattern.events[active.next_event_idx]; - synth_trigger_voice(g_spec_pool[slot].synth_id, 1.0f, 0.0f); + if (event.beat > elapsed_beats) + break; // This event hasn't reached its time yet + + // Trigger this event as an individual voice + trigger_note_event(event); + + active.next_event_idx++; } - g_last_trigger_idx++; + // If all events have been triggered, mark pattern as complete + if (active.next_event_idx >= pattern.num_events) { + active.active = false; + } } } diff --git a/src/audio/tracker.h b/src/audio/tracker.h index 49fcd3c..e6d479a 100644 --- a/src/audio/tracker.h +++ b/src/audio/tracker.h @@ -41,4 +41,5 @@ extern const uint32_t g_tracker_patterns_count; extern const TrackerScore g_tracker_score; void tracker_init(); -void tracker_update(float time_sec); +void tracker_update(float music_time_sec); +void tracker_reset(); // Reset tracker state (for tests/seeking) diff --git a/src/generated/music_data.cc b/src/generated/music_data.cc index 73ed992..a37d9c0 100644 --- a/src/generated/music_data.cc +++ b/src/generated/music_data.cc @@ -7,54 +7,30 @@ const NoteParams g_tracker_samples[] = { { 0 }, // ASSET_KICK_1 (ASSET) { 0 }, // ASSET_KICK_2 (ASSET) - { 0 }, // ASSET_KICK_3 (ASSET) { 0 }, // ASSET_SNARE_1 (ASSET) - { 0 }, // ASSET_SNARE_2 (ASSET) { 0 }, // ASSET_SNARE_3 (ASSET) - { 0 }, // ASSET_SNARE_4 (ASSET) { 0 }, // ASSET_HIHAT_1 (ASSET) { 0 }, // ASSET_HIHAT_2 (ASSET) - { 0 }, // ASSET_HIHAT_3 (ASSET) - { 0 }, // ASSET_HIHAT_4 (ASSET) { 0 }, // ASSET_CRASH_1 (ASSET) - { 0 }, // ASSET_RIDE_1 (ASSET) - { 0 }, // ASSET_SPLASH_1 (ASSET) { 0 }, // ASSET_BASS_1 (ASSET) - { 0 }, // ASSET_SYNTH_BASS_1 (ASSET) { 82.4f, 0.50f, 1.0f, 0.01f, 0.0f, 0.0f, 0.0f, 3, 0.6f, 0.0f, 0.0f }, // E2 { 98.0f, 0.50f, 1.0f, 0.01f, 0.0f, 0.0f, 0.0f, 3, 0.6f, 0.0f, 0.0f }, // G2 - { 110.0f, 0.50f, 1.0f, 0.01f, 0.0f, 0.0f, 0.0f, 3, 0.6f, 0.0f, 0.0f }, // A2 - { 123.5f, 0.50f, 1.0f, 0.01f, 0.0f, 0.0f, 0.0f, 3, 0.6f, 0.0f, 0.0f }, // B2 { 329.6f, 0.50f, 1.0f, 0.01f, 0.0f, 0.0f, 0.0f, 3, 0.6f, 0.0f, 0.0f }, // E4 { 392.0f, 0.50f, 1.0f, 0.01f, 0.0f, 0.0f, 0.0f, 3, 0.6f, 0.0f, 0.0f }, // G4 { 493.9f, 0.50f, 1.0f, 0.01f, 0.0f, 0.0f, 0.0f, 3, 0.6f, 0.0f, 0.0f }, // B4 - { 587.3f, 0.50f, 1.0f, 0.01f, 0.0f, 0.0f, 0.0f, 3, 0.6f, 0.0f, 0.0f }, // D5 - { 659.3f, 0.50f, 1.0f, 0.01f, 0.0f, 0.0f, 0.0f, 3, 0.6f, 0.0f, 0.0f }, // E5 { 440.0f, 0.50f, 1.0f, 0.01f, 0.0f, 0.0f, 0.0f, 3, 0.6f, 0.0f, 0.0f }, // A4 }; -const uint32_t g_tracker_samples_count = 26; +const uint32_t g_tracker_samples_count = 14; const AssetId g_tracker_sample_assets[] = { AssetId::ASSET_KICK_1, AssetId::ASSET_KICK_2, - AssetId::ASSET_KICK_3, AssetId::ASSET_SNARE_1, - AssetId::ASSET_SNARE_2, AssetId::ASSET_SNARE_3, - AssetId::ASSET_SNARE_4, AssetId::ASSET_HIHAT_1, AssetId::ASSET_HIHAT_2, - AssetId::ASSET_HIHAT_3, - AssetId::ASSET_HIHAT_4, AssetId::ASSET_CRASH_1, - AssetId::ASSET_RIDE_1, - AssetId::ASSET_SPLASH_1, AssetId::ASSET_BASS_1, - AssetId::ASSET_SYNTH_BASS_1, - AssetId::ASSET_LAST_ID, - AssetId::ASSET_LAST_ID, - AssetId::ASSET_LAST_ID, - AssetId::ASSET_LAST_ID, AssetId::ASSET_LAST_ID, AssetId::ASSET_LAST_ID, AssetId::ASSET_LAST_ID, @@ -63,177 +39,174 @@ const AssetId g_tracker_sample_assets[] = { AssetId::ASSET_LAST_ID, }; -static const TrackerEvent PATTERN_EVENTS_rock_beat[] = { +static const TrackerEvent PATTERN_EVENTS_kick_basic[] = { { 0.0f, 0, 1.0f, 0.0f }, - { 1.0f, 5, 0.9f, 0.1f }, { 2.0f, 0, 1.0f, 0.0f }, - { 2.5f, 0, 0.7f, -0.1f }, - { 3.0f, 5, 0.9f, 0.1f }, + { 2.5f, 1, 0.7f, -0.2f }, }; -static const TrackerEvent PATTERN_EVENTS_double_kick[] = { - { 0.0f, 2, 1.0f, 0.0f }, - { 0.5f, 1, 0.8f, -0.2f }, - { 1.0f, 6, 0.9f, 0.2f }, - { 1.5f, 2, 0.7f, 0.1f }, - { 2.0f, 2, 1.0f, 0.0f }, - { 2.5f, 1, 0.8f, -0.2f }, - { 3.0f, 6, 0.9f, 0.2f }, - { 3.5f, 2, 0.6f, 0.1f }, +static const TrackerEvent PATTERN_EVENTS_snare_basic[] = { + { 1.0f, 2, 0.9f, 0.1f }, + { 3.0f, 2, 0.9f, 0.1f }, }; -static const TrackerEvent PATTERN_EVENTS_hihat_8th[] = { - { 0.0f, 8, 0.6f, -0.3f }, - { 0.5f, 7, 0.4f, 0.3f }, - { 1.0f, 8, 0.6f, -0.3f }, - { 1.5f, 7, 0.4f, 0.3f }, - { 2.0f, 8, 0.6f, -0.3f }, - { 2.5f, 7, 0.4f, 0.3f }, - { 3.0f, 8, 0.6f, -0.3f }, - { 3.5f, 7, 0.4f, 0.3f }, +static const TrackerEvent PATTERN_EVENTS_hihat_stressed[] = { + { 0.0f, 5, 0.8f, -0.3f }, + { 0.5f, 4, 0.4f, 0.3f }, + { 1.0f, 5, 0.8f, -0.3f }, + { 1.5f, 4, 0.4f, 0.3f }, + { 2.0f, 5, 0.8f, -0.3f }, + { 2.5f, 4, 0.4f, 0.3f }, + { 3.0f, 5, 0.8f, -0.3f }, + { 3.5f, 4, 0.4f, 0.3f }, }; -static const TrackerEvent PATTERN_EVENTS_hihat_16th[] = { - { 0.0f, 9, 0.7f, -0.4f }, - { 0.2f, 7, 0.3f, 0.4f }, - { 0.5f, 9, 0.6f, -0.2f }, - { 0.8f, 7, 0.3f, 0.2f }, - { 1.0f, 9, 0.7f, -0.4f }, - { 1.2f, 7, 0.3f, 0.4f }, - { 1.5f, 9, 0.6f, -0.2f }, - { 1.8f, 7, 0.3f, 0.2f }, - { 2.0f, 9, 0.7f, -0.4f }, - { 2.2f, 7, 0.3f, 0.4f }, - { 2.5f, 9, 0.6f, -0.2f }, - { 2.8f, 7, 0.3f, 0.2f }, - { 3.0f, 9, 0.7f, -0.4f }, - { 3.2f, 7, 0.3f, 0.4f }, - { 3.5f, 9, 0.6f, -0.2f }, - { 3.8f, 7, 0.3f, 0.2f }, -}; -static const TrackerEvent PATTERN_EVENTS_ride_pattern[] = { - { 0.0f, 12, 0.6f, 0.5f }, - { 0.5f, 12, 0.5f, -0.3f }, - { 1.0f, 12, 0.6f, 0.5f }, - { 1.5f, 12, 0.5f, -0.3f }, - { 2.0f, 12, 0.6f, 0.5f }, - { 2.5f, 12, 0.5f, -0.3f }, - { 3.0f, 12, 0.7f, 0.5f }, - { 3.5f, 12, 0.5f, -0.3f }, -}; -static const TrackerEvent PATTERN_EVENTS_crash_accent[] = { - { 0.0f, 11, 0.9f, 0.0f }, +static const TrackerEvent PATTERN_EVENTS_kick_dense[] = { + { 0.0f, 0, 1.0f, 0.0f }, + { 0.5f, 1, 0.6f, -0.2f }, + { 1.0f, 0, 1.0f, 0.0f }, + { 1.5f, 1, 0.6f, 0.2f }, + { 2.0f, 0, 1.0f, 0.0f }, + { 2.5f, 1, 0.6f, -0.2f }, + { 3.0f, 0, 1.0f, 0.0f }, + { 3.5f, 1, 0.6f, 0.2f }, }; -static const TrackerEvent PATTERN_EVENTS_splash_accent[] = { - { 0.0f, 13, 0.8f, 0.3f }, +static const TrackerEvent PATTERN_EVENTS_snare_dense[] = { + { 0.5f, 3, 0.7f, 0.0f }, + { 1.0f, 2, 0.9f, 0.1f }, + { 1.5f, 3, 0.7f, 0.0f }, + { 2.5f, 3, 0.7f, 0.0f }, + { 3.0f, 2, 0.9f, 0.1f }, + { 3.5f, 3, 0.7f, 0.0f }, }; -static const TrackerEvent PATTERN_EVENTS_snare_fill[] = { - { 0.0f, 4, 0.6f, -0.4f }, - { 0.2f, 4, 0.6f, -0.2f }, - { 0.5f, 4, 0.7f, 0.0f }, - { 0.8f, 5, 0.8f, 0.2f }, - { 1.0f, 5, 0.8f, 0.4f }, - { 1.2f, 6, 0.9f, 0.2f }, - { 1.5f, 6, 0.9f, 0.0f }, - { 1.8f, 6, 0.9f, -0.2f }, +static const TrackerEvent PATTERN_EVENTS_crash[] = { + { 0.0f, 6, 0.9f, 0.0f }, }; -static const TrackerEvent PATTERN_EVENTS_power_riff[] = { - { 0.0f, 16, 0.8f, -0.5f }, - { 0.5f, 16, 0.6f, -0.5f }, - { 1.0f, 17, 0.8f, -0.3f }, - { 1.5f, 17, 0.6f, -0.3f }, - { 2.0f, 18, 0.8f, 0.3f }, - { 2.5f, 18, 0.6f, 0.3f }, - { 3.0f, 19, 0.8f, 0.5f }, - { 3.5f, 19, 0.6f, 0.5f }, +static const TrackerEvent PATTERN_EVENTS_bass_e[] = { + { 0.0f, 8, 0.9f, 0.0f }, + { 1.0f, 8, 0.7f, 0.0f }, + { 2.0f, 8, 0.9f, 0.0f }, + { 2.5f, 8, 0.6f, 0.0f }, + { 3.0f, 8, 0.7f, 0.0f }, }; -static const TrackerEvent PATTERN_EVENTS_lead_melody[] = { - { 0.0f, 20, 0.7f, 0.0f }, - { 0.5f, 21, 0.6f, 0.1f }, - { 1.0f, 22, 0.7f, -0.1f }, - { 1.5f, 23, 0.6f, 0.2f }, - { 2.0f, 24, 0.8f, -0.2f }, - { 2.5f, 23, 0.6f, 0.1f }, - { 3.0f, 22, 0.7f, 0.0f }, - { 3.5f, 25, 0.6f, -0.1f }, +static const TrackerEvent PATTERN_EVENTS_bass_eg[] = { + { 0.0f, 8, 0.9f, 0.0f }, + { 1.0f, 8, 0.7f, 0.0f }, + { 2.0f, 9, 0.9f, 0.0f }, + { 3.0f, 9, 0.7f, 0.0f }, }; -static const TrackerEvent PATTERN_EVENTS_bass_line[] = { - { 0.0f, 16, 0.9f, 0.0f }, - { 1.0f, 16, 0.8f, 0.0f }, - { 2.0f, 16, 0.9f, 0.0f }, - { 2.5f, 16, 0.7f, 0.0f }, - { 3.0f, 17, 0.9f, 0.0f }, +static const TrackerEvent PATTERN_EVENTS_melody_em[] = { + { 0.0f, 10, 0.7f, 0.0f }, + { 0.5f, 11, 0.6f, 0.1f }, + { 1.0f, 12, 0.7f, -0.1f }, + { 2.0f, 13, 0.6f, 0.0f }, + { 2.5f, 11, 0.6f, 0.1f }, + { 3.0f, 10, 0.7f, 0.0f }, }; const TrackerPattern g_tracker_patterns[] = { - { PATTERN_EVENTS_rock_beat, 5, 4.0f }, // rock_beat - { PATTERN_EVENTS_double_kick, 8, 4.0f }, // double_kick - { PATTERN_EVENTS_hihat_8th, 8, 4.0f }, // hihat_8th - { PATTERN_EVENTS_hihat_16th, 16, 4.0f }, // hihat_16th - { PATTERN_EVENTS_ride_pattern, 8, 4.0f }, // ride_pattern - { PATTERN_EVENTS_crash_accent, 1, 4.0f }, // crash_accent - { PATTERN_EVENTS_splash_accent, 1, 4.0f }, // splash_accent - { PATTERN_EVENTS_snare_fill, 8, 4.0f }, // snare_fill - { PATTERN_EVENTS_power_riff, 8, 4.0f }, // power_riff - { PATTERN_EVENTS_lead_melody, 8, 4.0f }, // lead_melody - { PATTERN_EVENTS_bass_line, 5, 4.0f }, // bass_line + { PATTERN_EVENTS_kick_basic, 3, 4.0f }, // kick_basic + { PATTERN_EVENTS_snare_basic, 2, 4.0f }, // snare_basic + { PATTERN_EVENTS_hihat_stressed, 8, 4.0f }, // hihat_stressed + { PATTERN_EVENTS_kick_dense, 8, 4.0f }, // kick_dense + { PATTERN_EVENTS_snare_dense, 6, 4.0f }, // snare_dense + { PATTERN_EVENTS_crash, 1, 4.0f }, // crash + { PATTERN_EVENTS_bass_e, 5, 4.0f }, // bass_e + { PATTERN_EVENTS_bass_eg, 4, 4.0f }, // bass_eg + { PATTERN_EVENTS_melody_em, 6, 4.0f }, // melody_em }; -const uint32_t g_tracker_patterns_count = 11; +const uint32_t g_tracker_patterns_count = 9; static const TrackerPatternTrigger SCORE_TRIGGERS[] = { { 0.0f, 5 }, - { 0.0f, 2 }, { 0.0f, 0 }, - { 2.0f, 2 }, + { 0.0f, 1 }, + { 0.0f, 2 }, { 2.0f, 0 }, + { 2.0f, 1 }, + { 2.0f, 2 }, { 4.0f, 5 }, - { 4.0f, 2 }, { 4.0f, 0 }, - { 4.0f, 10 }, - { 6.0f, 2 }, + { 4.0f, 1 }, + { 4.0f, 2 }, { 6.0f, 0 }, - { 6.0f, 10 }, - { 8.0f, 6 }, - { 8.0f, 3 }, + { 6.0f, 1 }, + { 6.0f, 2 }, + { 8.0f, 5 }, + { 8.0f, 0 }, { 8.0f, 1 }, - { 8.0f, 10 }, - { 8.0f, 9 }, - { 10.0f, 3 }, + { 8.0f, 2 }, + { 10.0f, 5 }, + { 10.0f, 0 }, { 10.0f, 1 }, - { 10.0f, 10 }, - { 10.0f, 9 }, - { 12.0f, 5 }, - { 12.0f, 4 }, + { 10.0f, 2 }, + { 10.0f, 6 }, + { 12.0f, 0 }, { 12.0f, 1 }, - { 12.0f, 8 }, - { 14.0f, 4 }, + { 12.0f, 2 }, + { 12.0f, 6 }, + { 14.0f, 0 }, { 14.0f, 1 }, - { 14.0f, 8 }, - { 16.0f, 7 }, + { 14.0f, 2 }, + { 14.0f, 6 }, + { 16.0f, 5 }, { 16.0f, 3 }, - { 17.0f, 6 }, - { 17.0f, 2 }, - { 17.0f, 0 }, - { 17.0f, 10 }, - { 18.0f, 5 }, + { 16.0f, 4 }, + { 16.0f, 2 }, + { 16.0f, 6 }, { 18.0f, 3 }, - { 18.0f, 1 }, - { 18.0f, 10 }, - { 18.0f, 9 }, + { 18.0f, 4 }, + { 18.0f, 2 }, + { 18.0f, 7 }, + { 20.0f, 5 }, { 20.0f, 3 }, - { 20.0f, 1 }, - { 20.0f, 10 }, - { 20.0f, 8 }, - { 22.0f, 7 }, + { 20.0f, 4 }, + { 20.0f, 2 }, + { 20.0f, 6 }, { 22.0f, 3 }, - { 23.0f, 5 }, - { 23.0f, 0 }, - { 23.0f, 10 }, - { 25.0f, 5 }, + { 22.0f, 4 }, + { 22.0f, 2 }, + { 22.0f, 6 }, + { 24.0f, 3 }, + { 24.0f, 4 }, + { 24.0f, 2 }, + { 24.0f, 7 }, + { 26.0f, 5 }, + { 26.0f, 0 }, + { 26.0f, 1 }, + { 26.0f, 2 }, + { 26.0f, 6 }, + { 28.0f, 0 }, + { 28.0f, 1 }, + { 28.0f, 2 }, + { 28.0f, 7 }, + { 30.0f, 5 }, + { 30.0f, 0 }, + { 30.0f, 1 }, + { 30.0f, 2 }, + { 30.0f, 6 }, + { 30.0f, 8 }, + { 32.0f, 0 }, + { 32.0f, 1 }, + { 32.0f, 2 }, + { 32.0f, 7 }, + { 32.0f, 8 }, + { 34.0f, 0 }, + { 34.0f, 1 }, + { 34.0f, 2 }, + { 34.0f, 6 }, + { 34.0f, 8 }, + { 36.0f, 5 }, + { 36.0f, 0 }, + { 36.0f, 1 }, + { 36.0f, 2 }, + { 36.0f, 7 }, + { 36.0f, 8 }, + { 38.0f, 5 }, }; const TrackerScore g_tracker_score = { - SCORE_TRIGGERS, 49, 120.0f + SCORE_TRIGGERS, 85, 120.0f }; // Resource usage analysis: -// Maximum simultaneous pattern triggers: 5 -// Recommended MAX_VOICES: 10 (current: see synth.h) -// Recommended MAX_SPECTROGRAMS: 10 (current: see synth.h) +// Maximum simultaneous pattern triggers: 6 +// Recommended MAX_VOICES: 12 (current: see synth.h) +// Recommended MAX_SPECTROGRAMS: 12 (current: see synth.h) diff --git a/src/tests/test_tracker.cc b/src/tests/test_tracker.cc index ea1debd..7ef7172 100644 --- a/src/tests/test_tracker.cc +++ b/src/tests/test_tracker.cc @@ -24,25 +24,42 @@ void test_tracker_pattern_triggering() { synth_init(); tracker_init(); - // Test 1: Trigger patterns at 0.0f + // At time 0.0f, 4 patterns are triggered: + // - crash (1 event at beat 0.0) + // - kick_basic (events at beat 0.0, 2.0, 2.5) + // - snare_basic (events at beat 1.0, 3.0) + // - hihat_stressed (events at beat 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5) + // With the new event-based triggering, only events at beat 0.0 trigger immediately. + + // Test 1: At music_time = 0.0f, events at beat 0.0 trigger tracker_update(0.0f); - printf("Actual active voice count: %d\n", synth_get_active_voice_count()); - // Expect 4 voices (one for each pattern triggered at 0.0f: - // crash, kick_basic, snare_basic, hihat_stressed) - assert(synth_get_active_voice_count() == 4); + printf("Actual active voice count at 0.0f: %d\n", + synth_get_active_voice_count()); + // Expect 3 voices: crash (beat 0.0), kick_basic (beat 0.0), hihat_stressed + // (beat 0.0) + assert(synth_get_active_voice_count() == 3); - // Test 2: Advance time slightly - tracker_update(0.1f); + // Test 2: At music_time = 0.25f (beat 0.5 @ 120 BPM), hihat event triggers + // beat_duration = 60.0f / 120.0f = 0.5s per beat + // beat 0.5 = 0.25s + tracker_update(0.25f); + printf("Actual active voice count at 0.25f: %d\n", + synth_get_active_voice_count()); + // Expect 4 voices (3 previous + 1 new hihat) assert(synth_get_active_voice_count() == 4); - // Test 3: Advance further, no new triggers until 4.0f - tracker_update(3.0f); - // Voices from 0.0f triggers might have ended, but new ones haven't started. - // synth_get_active_voice_count might drop if previous voices ended. - // For this test, we assume voices triggered at 0.0f are still active for a - // short duration. A more robust test would check for specific spectrograms or - // mock synth. For now, we expect voices to still be somewhat active or new - // ones to be triggered if there's overlap + // Test 3: At music_time = 0.5f (beat 1.0), snare event triggers + tracker_update(0.5f); + printf("Actual active voice count at 0.5f: %d\n", + synth_get_active_voice_count()); + // Expect 6 voices (4 previous + snare + hihat at beat 1.0) + assert(synth_get_active_voice_count() == 6); + + // Test 4: Advance further to 2.0f (beat 4.0), new pattern triggers at 2.0f + tracker_update(2.0f); + printf("Actual active voice count at 2.0f: %d\n", + synth_get_active_voice_count()); + // Multiple events have triggered by now assert(synth_get_active_voice_count() > 0); printf("Tracker pattern triggering test PASSED\n"); |
