From af4b27dfb0862bc4a76d716dacd24acf73ee059a Mon Sep 17 00:00:00 2001 From: skal Date: Wed, 4 Feb 2026 14:38:04 +0100 Subject: 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 --- src/generated/music_data.cc | 317 ++++++++++++++++++++------------------------ 1 file changed, 145 insertions(+), 172 deletions(-) (limited to 'src/generated/music_data.cc') 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 }, -}; -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_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_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_splash_accent[] = { - { 0.0f, 13, 0.8f, 0.3f }, -}; -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_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_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_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 }, + { 2.5f, 1, 0.7f, -0.2f }, +}; +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_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_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_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_crash[] = { + { 0.0f, 6, 0.9f, 0.0f }, +}; +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_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_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 -}; -const uint32_t g_tracker_patterns_count = 11; + { 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 = 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) -- cgit v1.2.3