diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-14 18:06:24 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-14 18:06:24 +0100 |
| commit | e9dde3cea39e69d6188a7f49034f6d95e4c8b6b4 (patch) | |
| tree | b3b383eff5f0d1d18bdbb781c331e6a31fcb25a7 /src/audio | |
| parent | b4c901700de0d9e867b9fd0c0c6a6586578f8480 (diff) | |
feat(tracker): add sample offset and humanization
Implements two tracker realism features:
1. Sample Offset (compile-time):
- Add offset_sec field to NoteParams and Sample structs
- Parse OFFSET parameter in SAMPLE directive
- Apply timing shift during compilation (zero runtime cost)
- Use for attack-heavy samples to align perceived beat
2. Humanization (runtime, deterministic):
- Add humanize_seed, timing_variation_pct, volume_variation_pct to TrackerScore
- Parse HUMANIZE directive with SEED/TIMING/VOLUME params
- Apply per-event RNG jitter using std::minstd_rand
- Deterministic: same seed produces identical output
Both features work in real-time playback and WAV export.
Test file: data/test_humanize.track
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/audio')
| -rw-r--r-- | src/audio/gen.h | 1 | ||||
| -rw-r--r-- | src/audio/tracker.cc | 33 | ||||
| -rw-r--r-- | src/audio/tracker.h | 3 |
3 files changed, 34 insertions, 3 deletions
diff --git a/src/audio/gen.h b/src/audio/gen.h index 94f4ee3..370971b 100644 --- a/src/audio/gen.h +++ b/src/audio/gen.h @@ -17,6 +17,7 @@ struct NoteParams { float harmonic_decay; float pitch_randomness; float amp_randomness; + float offset_sec = 0.0f; }; // Generates a single note into a new spectrogram buffer diff --git a/src/audio/tracker.cc b/src/audio/tracker.cc index 67c197f..1c0a9b2 100644 --- a/src/audio/tracker.cc +++ b/src/audio/tracker.cc @@ -5,6 +5,7 @@ #include "util/debug.h" #include "util/fatal_error.h" #include <cstring> +#include <random> #include <vector> static uint32_t g_last_trigger_idx = 0; @@ -190,8 +191,9 @@ static int get_free_pattern_slot() { // 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) +// volume_mult: Additional volume multiplier (for humanization) static void trigger_note_event(const TrackerEvent& event, - int start_offset_samples) { + int start_offset_samples, float volume_mult = 1.0f) { #if defined(DEBUG_LOG_TRACKER) // VALIDATION: Check sample_id bounds if (event.sample_id >= g_tracker_samples_count) { @@ -227,7 +229,7 @@ static void trigger_note_event(const TrackerEvent& event, } // Trigger voice with sample-accurate offset - synth_trigger_voice(cached_synth_id, event.volume, event.pan, + synth_trigger_voice(cached_synth_id, event.volume * volume_mult, event.pan, start_offset_samples); } @@ -287,7 +289,32 @@ void tracker_update(float music_time_sec, float dt_music_sec) { (int)((event_music_time - music_time_sec) / tempo_scale * 32000.0f); } - trigger_note_event(event, sample_offset); + // Apply humanization if enabled + float volume_mult = 1.0f; + if (g_tracker_score.humanize_seed != 0) { + // Deterministic per-event RNG: hash seed with pattern/event indices + uint32_t event_hash = g_tracker_score.humanize_seed ^ + (active.pattern_id << 16) ^ active.next_event_idx; + std::minstd_rand rng(event_hash); + std::uniform_real_distribution<float> dist(-1.0f, 1.0f); + + // Timing variation: jitter by % of beat duration + if (g_tracker_score.timing_variation_pct > 0.0f) { + float beat_sec = 60.0f / g_tracker_score.bpm; + float jitter = dist(rng) * + (g_tracker_score.timing_variation_pct / 100.0f) * + beat_sec; + sample_offset += (int)(jitter / tempo_scale * 32000.0f); + } + + // Volume variation: vary by % + if (g_tracker_score.volume_variation_pct > 0.0f) { + volume_mult += + dist(rng) * (g_tracker_score.volume_variation_pct / 100.0f); + } + } + + trigger_note_event(event, sample_offset, volume_mult); active.next_event_idx++; } diff --git a/src/audio/tracker.h b/src/audio/tracker.h index 8e7a63f..8e89a4e 100644 --- a/src/audio/tracker.h +++ b/src/audio/tracker.h @@ -31,6 +31,9 @@ struct TrackerScore { const TrackerPatternTrigger* triggers; uint32_t num_triggers; float bpm; // BPM is used only for playback scaling (1 unit = 4 beats) + uint32_t humanize_seed = 0; + float timing_variation_pct = 0.0f; + float volume_variation_pct = 0.0f; }; // Global music data generated by tracker_compiler |
