summaryrefslogtreecommitdiff
path: root/doc/TRACKER.md
blob: 5cb59deb5f5a4a5feba5a9b68d16a29c5676e41b (plain)
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
# 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

SAMPLE <name> [OFFSET <sec>] [VOL <volume>]  # Define sample with optional offset/volume

PATTERN <name> LENGTH <duration>   # Define pattern with unit-less duration
  <unit_time>, <sample>, <volume>, <pan>   # Pattern events

HUMANIZE SEED <int> TIMING <pct> VOLUME <pct>  # Optional humanization params

SCORE                    # Score section (pattern triggers)
  <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

Samples can specify an intrinsic offset (time-shift left):
```
SAMPLE ASSET_KICK_1 OFFSET 0.05 VOL 1.2
```

- **OFFSET**: Seconds to shift trigger earlier (preserves beat sync)
- **VOL**: Default volume multiplier for this sample

### Humanization

Add per-note timing/volume variation for realistic playback:
```
HUMANIZE SEED 42 TIMING 2.0 VOLUME 5.0
```

- **SEED**: Random seed for reproducibility
- **TIMING**: Timing variation (% of beat duration)
- **VOLUME**: Volume variation (% of event volume)

Applied per-note, baked into 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()`