1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
# Minimal Audio Tracker
System for assembling audio samples (assets or procedural) into patterns and loops.
## .track File Format
### Timing Convention
**Unit-less timing:**
- **1 unit = 4 beats**
- Conversion: `seconds = units * (4 / BPM) * 60`
- At 120 BPM: 1 unit = 2 seconds
Patterns are BPM-independent. Changing BPM only affects playback speed.
### File Structure
```
# Comments start with #
BPM <tempo> # Optional, defaults to 120 BPM
HUMANIZE SEED <int> TIMING <pct> VOLUME <pct> # Optional humanization
# Generated samples:
SAMPLE <name>, <freq>, <dur>, <amp>, <attack>, <harmonics>, <decay> [OFFSET <sec>]
# Asset samples:
SAMPLE <asset_id> [OFFSET <sec>] # ASSET_* from assets.txt
# Auto-generated notes (no SAMPLE declaration needed):
# NOTE_C4, NOTE_A#3, NOTE_Eb2, etc.
PATTERN <name> [LENGTH <duration>] # Defaults to LENGTH 1.0
<unit_time>, <sample>, <volume>, <pan>
SCORE
<unit_time>, <pattern_name>
```
### Examples
#### Simple 4-beat pattern (1 unit):
```
PATTERN kick_snare LENGTH 1.0
0.00, ASSET_KICK_1, 1.0, 0.0 # Start of pattern (beat 0)
0.25, ASSET_SNARE_1, 0.9, 0.0 # 1/4 through (beat 1)
0.50, ASSET_KICK_1, 1.0, 0.0 # 1/2 through (beat 2)
0.75, ASSET_SNARE_1, 0.9, 0.0 # 3/4 through (beat 3)
```
#### Score triggers:
```
SCORE
0.0, kick_snare # Trigger at 0 seconds (120 BPM)
1.0, kick_snare # Trigger at 2 seconds (1 unit = 2s at 120 BPM)
2.0, kick_snare # Trigger at 4 seconds
```
#### Generated note:
```
SAMPLE NOTE_C4 # Automatically generates C4 note (261.63 Hz)
PATTERN melody LENGTH 1.0
0.00, NOTE_C4, 0.8, 0.0
0.25, NOTE_E4, 0.7, 0.0
0.50, NOTE_G4, 0.8, 0.0
```
### Pattern Length
- `LENGTH` parameter defaults to 1.0 if omitted
- Can be any value (0.5 for half-length, 2.0 for double-length)
- Events must be within `[0.0, LENGTH]`
Example half-length pattern:
```
PATTERN short_fill LENGTH 0.5 # 2 beats = 1 second at 120 BPM
0.00, ASSET_HIHAT, 0.7, 0.0
0.50, ASSET_HIHAT, 0.6, 0.0 # 0.50 * 0.5 = 1 beat into the pattern
```
### Sample Offset
Intrinsic timing offset per sample (compile-time):
```
SAMPLE ASSET_KICK_1 OFFSET 0.01 # Trigger 10ms earlier (attack compensation)
SAMPLE bass, 80, 0.5, 1.0, 0.02, 3, 0.6 OFFSET 0.005
```
- Shifts sample trigger earlier while preserving beat sync
- Applied at compile-time (zero runtime cost)
- Use for attack-heavy samples to align perceived beat
### Humanization
Per-note timing/volume variation (runtime, deterministic):
```
HUMANIZE SEED 42 TIMING 5.0 VOLUME 10.0
```
- **SEED**: Deterministic RNG seed (reproducible across runs)
- **TIMING**: ±% jitter of beat duration
- **VOLUME**: ±% variation of event volume
- Identical in real-time playback and WAV export
---
## AudioEngine API
**Production usage:**
```cpp
#include "audio/audio_engine.h"
audio_init();
static AudioEngine g_audio_engine;
g_audio_engine.init();
// Main loop
g_audio_engine.update(music_time);
g_audio_engine.shutdown();
audio_shutdown();
```
**Methods:**
- `init()`: Initialize synth + tracker
- `update(music_time)`: Update music state, trigger patterns
- `shutdown()`: Cleanup
- `seek(time)`: Jump to timestamp (debug builds only)
**Testing:**
```cpp
AudioEngine engine;
engine.init();
engine.update(1.0f);
engine.shutdown();
```
**Direct synth APIs** (performance-critical code only):
- `synth_register_spectrogram()`
- `synth_trigger_voice()`
- `synth_get_output_peak()`
- `synth_render()`
|