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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
|
# Beat-Based Timing System
## Overview
The demo uses **beat-based timing** for visual effect sequences, ensuring musical synchronization regardless of BPM changes. All timeline sequences are authored in beats (musical time) and converted to physical seconds at runtime.
**Key Principle:** Variable tempo only affects audio sample triggering. Visual effects run at constant physical time with optional beat-synchronized animation.
---
## Quick Start
### Timeline Authoring
```seq
# BPM 120
SEQUENCE 0 0 "Intro" # Beat 0 (bar 1)
EFFECT + Flash 0 2 # Beats 0-2 (half bar)
EFFECT + Fade 4 8 # Beats 4-8 (full bar)
SEQUENCE 16 1 "Drop" # Beat 16 (bar 5)
EFFECT + Heptagon 0 16 # 4 bars
```
**Conversion:** At 120 BPM, 1 beat = 0.5 seconds, 4 beats = 2 seconds
### Shader Animation
```wgsl
@group(0) @binding(2) var<uniform> uniforms: CommonUniforms;
// Use beat_time for musical animation
let bar_cycle = uniforms.beat_time / 4.0; // Bars
let pulse = sin(bar_cycle * TAU);
// Use beat_phase for smooth per-beat effects
let wave = sin(uniforms.beat_phase * TAU);
// Use time for constant-speed physics
let rotation = uniforms.time * TAU;
```
---
## Uniform Structure
All effects receive `CommonPostProcessUniforms` with timing data:
```cpp
struct CommonPostProcessUniforms {
vec2 resolution; // Screen dimensions (pixels)
float aspect_ratio; // Width/height ratio
float time; // Physical seconds (constant, unaffected by tempo)
float beat_time; // Absolute beats (musical time from audio clock)
float beat_phase; // Fractional beat 0.0-1.0 (smooth oscillation)
float audio_intensity; // Audio peak for beat sync
float _pad; // Alignment padding
}; // 32 bytes
```
**Use Cases:**
- `time`: Physics animation, constant-speed rotation/movement
- `beat_time`: Bar-based patterns, musical synchronization
- `beat_phase`: Smooth per-beat pulse/wave effects
---
## Timeline Format
### Time Notation
**Default:** Beats (no suffix)
```seq
SEQUENCE 0 0 # Beat 0
EFFECT + Flash 0 4 # Beats 0-4
```
**Explicit Seconds:** Use `s` suffix (rare)
```seq
SEQUENCE 2.5s 0 # 2.5 physical seconds
EFFECT + Flash 0 4 # Still uses beats for duration
```
**Explicit Beats:** Use `b` suffix (optional clarity)
```seq
SEQUENCE 8b 0 # Same as "8"
EFFECT + Flash 0b 4b # Same as "0 4"
```
### BPM Declaration
**Required** in all timeline files:
```seq
# BPM 120
```
Specifies beats per minute for runtime conversion to seconds.
---
## Architecture
### Timing Flow
```
Platform Clock (physical seconds)
│
├──► Physical Time ────────┐
│ (constant) │
│ │
└──► Audio Time ────┐ │
(playback) │ │
▼ │
Beat Calculation │
(BPM * 60) │
│ │
▼ ▼
Visual Effects Rendering
(time + beat_time + beat_phase)
```
### Key Insight
**Variable tempo changes `music_time`** (used for audio event triggering), but **visual effects receive `time` (physical)** and **`beat_time` (from audio playback clock)**, not from tempo-scaled music time.
This separation ensures:
- ✅ Visual effects run at constant frame rate
- ✅ Beat-synced animations track actual audio playback
- ✅ Tempo changes don't cause visual stuttering
---
## Implementation
### Beat Calculation (Runtime)
```cpp
// main.cc - Calculate from audio playback time
const float absolute_beat_time = current_audio_time * g_tracker_score.bpm / 60.0f;
const float beat_phase = fmodf(absolute_beat_time, 1.0f);
// Pass to GPU rendering
gpu_draw(visual_peak, aspect_ratio, physical_time, absolute_beat_time, beat_phase);
```
### Timeline Compilation
```cpp
// seq_compiler.cc - Convert beats to seconds at compile time
std::string convert_to_time(const std::string& value, float bpm) {
if (value.back() == 's') return explicit_seconds; // Pass through
// Default: treat as beats
float beat = std::stof(value);
float time = beat * 60.0f / bpm;
return time;
}
```
**Result:** Generated `timeline.cc` contains physical seconds for effect activation.
---
## Migration
### Existing Timelines
Already migrated with explicit `s` suffix to preserve timing:
```seq
SEQUENCE 2.50s 0 # Physical seconds preserved
EFFECT + Flash 0.00s 1.00s
```
### New Content
Use beat notation (recommended):
```seq
# BPM 140
SEQUENCE 0 0 "Intro"
EFFECT + Flash 0 4 # 4 beats = 1.71s @ 140 BPM
EFFECT + Fade 4 8 # 4 beats = 1.71s
```
**Benefits:**
- Natural musical alignment (bars/beats)
- BPM changes don't break timing
- Easier to author to music
---
## Examples
### Four-Bar Pattern
```seq
# BPM 120
SEQUENCE 0 0 "Verse 1"
EFFECT - Background 0 16 # 4 bars background
EFFECT + Flash 0 1 # First beat flash
EFFECT + Pulse 4 5 # Second bar pulse
EFFECT + Fade 15 16 # Final beat fade
```
### Multi-Bar Sequence
```seq
SEQUENCE 16 0 "Chorus" # Bar 5
EFFECT + Heptagon 0 32 # 8 bars (full chorus)
EFFECT + Particles 8 24 # Bars 7-11 (middle)
```
### Beat-Synced Shader
```wgsl
fn fragment_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
// Pulse every bar (4 beats)
let bar_phase = fract(uniforms.beat_time / 4.0);
let bar_pulse = smoothstep(0.0, 0.1, bar_phase) *
(1.0 - smoothstep(0.9, 1.0, bar_phase));
// Smooth per-beat wave
let beat_wave = sin(uniforms.beat_phase * TAU);
// Combine
let intensity = bar_pulse * 0.5 + beat_wave * 0.3;
return vec4<f32>(color * intensity, 1.0);
}
```
---
## Troubleshooting
### Shader Compilation Error: "invalid accessor 'beat'"
**Cause:** Old shader using `uniforms.beat` (deprecated field)
**Fix:** Use `uniforms.beat_phase` or `uniforms.beat_time`
```wgsl
// OLD (error):
let x = uniforms.beat;
// NEW:
let x = uniforms.beat_phase; // For 0-1 fractional
let y = uniforms.beat_time; // For absolute beats
```
### Timeline Parse Error
**Cause:** Missing BPM declaration
**Fix:** Add BPM at top of file
```seq
# BPM 120 ← Required
SEQUENCE 0 0
```
### Effects Start at Wrong Time
**Cause:** Mixing beats and seconds without explicit suffixes
**Fix:** Be explicit
```seq
SEQUENCE 8 0 # 8 beats (not 8 seconds)
SEQUENCE 8s 0 # 8 seconds (explicit)
SEQUENCE 8b 0 # 8 beats (explicit, same as first)
```
---
## See Also
- **Format Reference:** `doc/SEQUENCE.md` - Complete .seq syntax
- **Implementation:** `BEAT_TIMING_SUMMARY.md` - Technical details
- **Effect Creation:** `doc/EFFECT_WORKFLOW.md` - Adding new effects
- **Timeline Editor:** `tools/timeline_editor/README.md` - Visual editing
|