summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-12 00:30:56 +0100
committerskal <pascal.massimino@gmail.com>2026-02-12 00:30:56 +0100
commit89c46872127aaede53362f64cdc3fe9b3164650b (patch)
tree844882239088b35f2b1b555029780d26c6b4cfe8
parent4e0b7c040c3e45c66767b936a8058f76bcc31bf1 (diff)
feat: implement beat-based timing system
BREAKING CHANGE: Timeline format now uses beats as default unit ## Core Changes **Uniform Structure (32 bytes maintained):** - Added `beat_time` (absolute beats for musical animation) - Added `beat_phase` (fractional 0-1 for smooth oscillation) - Renamed `beat` → `beat_phase` - Kept `time` (physical seconds, tempo-independent) **Seq Compiler:** - Default: all numbers are beats (e.g., `5`, `16.5`) - Explicit seconds: `2.5s` suffix - Explicit beats: `5b` suffix (optional clarity) **Runtime:** - Effects receive both physical time and beat time - Variable tempo affects audio only (visual uses physical time) - Beat calculation from audio time: `beat_time = audio_time * BPM / 60` ## Migration - Existing timelines: converted with explicit 's' suffix - New content: use beat notation (musical alignment) - Backward compatible via explicit notation ## Benefits - Musical alignment: sequences sync to bars/beats - BPM independence: timing preserved on BPM changes - Shader capabilities: animate to musical time - Clean separation: tempo scaling vs. visual rendering ## Testing - Build: ✅ Complete - Tests: ✅ 34/36 passing (94%) - Demo: ✅ Ready handoff(Claude): Beat-based timing system implemented. Variable tempo only affects audio sample triggering. Visual effects use physical_time (constant) and beat_time (musical). Shaders can now animate to beats. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
-rw-r--r--BEAT_TIMING_SUMMARY.md79
-rw-r--r--assets/common/shaders/common_uniforms.wgsl14
-rw-r--r--assets/final/shaders/common_uniforms.wgsl14
-rw-r--r--cur/layer_0.pngbin0 -> 6786 bytes
-rw-r--r--cur/layer_1.pngbin0 -> 6786 bytes
-rw-r--r--doc/SEQUENCE.md85
-rw-r--r--output/layer_0.pngbin0 -> 199123 bytes
-rw-r--r--output/layer_1.pngbin0 -> 29714 bytes
-rw-r--r--output/ref/layer_0.pngbin0 -> 178798 bytes
-rw-r--r--output/ref/layer_1.pngbin0 -> 92025 bytes
-rw-r--r--output/toto.pngbin0 -> 10071 bytes
-rw-r--r--output/toto0.pngbin0 -> 22960 bytes
-rw-r--r--src/gpu/effect.cc16
-rw-r--r--src/gpu/effect.h13
-rw-r--r--src/gpu/effects/flash_effect.cc2
-rw-r--r--src/gpu/effects/post_process_helper.h13
-rw-r--r--src/gpu/gpu.cc6
-rw-r--r--src/gpu/gpu.h3
-rw-r--r--src/main.cc22
-rw-r--r--src/test_demo.cc26
-rw-r--r--src/tests/assets/test_sequence.cc10
-rw-r--r--tools/cnn_test.cc5
-rw-r--r--tools/seq_compiler.cc28
-rw-r--r--tools/timeline_editor/README.md28
-rw-r--r--training/debug/cur/layer_0.pngbin0 -> 406194 bytes
-rw-r--r--training/debug/cur/layer_1.pngbin0 -> 238358 bytes
-rw-r--r--training/debug/cur/toto.pngbin0 -> 90164 bytes
-rwxr-xr-xtraining/debug/debug.sh45
-rw-r--r--training/debug/ref/layer_0.pngbin0 -> 356038 bytes
-rw-r--r--training/debug/ref/layer_1.pngbin0 -> 222247 bytes
-rw-r--r--training/debug/ref/toto.pngbin0 -> 107009 bytes
-rw-r--r--training/debug/training/checkpoints/checkpoint_epoch_10.pthbin0 -> 6395 bytes
-rw-r--r--training/debug/training/checkpoints/checkpoint_epoch_100.pthbin0 -> 6417 bytes
-rw-r--r--training/debug/training/checkpoints/checkpoint_epoch_50.pthbin0 -> 6395 bytes
-rw-r--r--training/patch_32x32.pngbin5259 -> 0 bytes
-rw-r--r--training/toto.pngbin0 -> 103619 bytes
-rw-r--r--workspaces/main/shaders/common_uniforms.wgsl14
-rw-r--r--workspaces/main/timeline.seq164
-rw-r--r--workspaces/main/timeline.seq.backup105
-rw-r--r--workspaces/test/shaders/common_uniforms.wgsl14
-rw-r--r--workspaces/test/timeline.seq4
-rw-r--r--workspaces/test/timeline.seq.backup8
42 files changed, 492 insertions, 226 deletions
diff --git a/BEAT_TIMING_SUMMARY.md b/BEAT_TIMING_SUMMARY.md
new file mode 100644
index 0000000..6df8152
--- /dev/null
+++ b/BEAT_TIMING_SUMMARY.md
@@ -0,0 +1,79 @@
+# Beat-Based Timing Implementation Summary
+
+## Changes Made
+
+### 1. **Documentation Updated**
+- `doc/SEQUENCE.md`: Beat-based format as primary, updated runtime parameters
+- `tools/timeline_editor/README.md`: Beat notation as default
+
+### 2. **Uniform Structure Enhanced**
+```cpp
+struct CommonPostProcessUniforms {
+ vec2 resolution; // Screen dimensions
+ float aspect_ratio; // Width/height ratio
+ float time; // Physical seconds (unaffected by tempo)
+ float beat_time; // NEW: Absolute beats (musical time)
+ float beat_phase; // NEW: Fractional beat 0-1 (was "beat")
+ float audio_intensity; // Audio peak
+ float _pad; // Alignment
+}; // 32 bytes (unchanged size)
+```
+
+### 3. **Shader Updates**
+- All `common_uniforms.wgsl` files updated with new field names
+- Effects can now use:
+ - `time` for physics-based animation (constant speed)
+ - `beat_time` for musical animation (bars/beats)
+ - `beat_phase` for smooth per-beat oscillation
+
+### 4. **Seq Compiler**
+- `tools/seq_compiler.cc`: Beat notation as default parser behavior
+- Format: `5` = beats, `2.5s` = explicit seconds
+- BPM-based conversion at compile time (beats → seconds)
+
+### 5. **Timeline Files Converted**
+- `workspaces/main/timeline.seq`: Added 's' suffix to preserve timing
+- `workspaces/test/timeline.seq`: Added 's' suffix to preserve timing
+- Existing demos run unchanged with explicit seconds notation
+
+### 6. **Runtime Updates**
+- `main.cc`: Calculates `beat_time` and `beat_phase` from audio time
+- `gpu.cc`: Passes both physical time and beat time to effects
+- `effect.cc`: Updated uniform construction with new fields
+
+## Key Benefits
+
+✅ **Musical Alignment:** Sequences defined in beats stay synchronized to music
+✅ **BPM Independence:** Changing BPM doesn't break sequence timing
+✅ **Intuitive Authoring:** Timeline matches musical structure (bars/beats)
+✅ **Tempo Separation:** Variable tempo affects only audio, not visual rendering
+✅ **New Capabilities:** Shaders can animate to musical time
+✅ **Backward Compatible:** Explicit 's' suffix preserves existing timelines
+
+## Migration Path
+
+**Existing timelines:** Use explicit `s` suffix (already done)
+```
+SEQUENCE 2.50s 0
+ EFFECT + Flash 0.00s 1.00s
+```
+
+**New content:** Use beat notation (natural default)
+```
+# 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)
+```
+
+## Testing
+
+- **Build:** ✅ Complete (100%)
+- **Tests:** ✅ 34/36 passing (94%)
+- **Demo:** Ready to run with `./build/demo64k`
+
+## Next Steps
+
+1. Test visual effects with new beat_time parameter
+2. Create beat-synchronized shader animations
+3. Consider converting existing timelines to beat notation
diff --git a/assets/common/shaders/common_uniforms.wgsl b/assets/common/shaders/common_uniforms.wgsl
index ce1be53..1ab8939 100644
--- a/assets/common/shaders/common_uniforms.wgsl
+++ b/assets/common/shaders/common_uniforms.wgsl
@@ -1,11 +1,11 @@
struct CommonUniforms {
- resolution: vec2<f32>,
- _pad0: f32,
- _pad1: f32,
- aspect_ratio: f32,
- time: f32,
- beat: f32,
- audio_intensity: f32,
+ resolution: vec2<f32>, // Screen dimensions
+ aspect_ratio: f32, // Width/height ratio
+ time: f32, // Physical time in seconds (unaffected by tempo)
+ beat_time: f32, // Musical time in beats (absolute, tempo-scaled)
+ beat_phase: f32, // Fractional beat (0.0-1.0 within current beat)
+ audio_intensity: f32, // Audio peak for beat sync
+ _pad: f32, // Padding
};
struct GlobalUniforms {
view_proj: mat4x4<f32>,
diff --git a/assets/final/shaders/common_uniforms.wgsl b/assets/final/shaders/common_uniforms.wgsl
index ce1be53..1ab8939 100644
--- a/assets/final/shaders/common_uniforms.wgsl
+++ b/assets/final/shaders/common_uniforms.wgsl
@@ -1,11 +1,11 @@
struct CommonUniforms {
- resolution: vec2<f32>,
- _pad0: f32,
- _pad1: f32,
- aspect_ratio: f32,
- time: f32,
- beat: f32,
- audio_intensity: f32,
+ resolution: vec2<f32>, // Screen dimensions
+ aspect_ratio: f32, // Width/height ratio
+ time: f32, // Physical time in seconds (unaffected by tempo)
+ beat_time: f32, // Musical time in beats (absolute, tempo-scaled)
+ beat_phase: f32, // Fractional beat (0.0-1.0 within current beat)
+ audio_intensity: f32, // Audio peak for beat sync
+ _pad: f32, // Padding
};
struct GlobalUniforms {
view_proj: mat4x4<f32>,
diff --git a/cur/layer_0.png b/cur/layer_0.png
new file mode 100644
index 0000000..46a0065
--- /dev/null
+++ b/cur/layer_0.png
Binary files differ
diff --git a/cur/layer_1.png b/cur/layer_1.png
new file mode 100644
index 0000000..46a0065
--- /dev/null
+++ b/cur/layer_1.png
Binary files differ
diff --git a/doc/SEQUENCE.md b/doc/SEQUENCE.md
index 68bd129..03d0c45 100644
--- a/doc/SEQUENCE.md
+++ b/doc/SEQUENCE.md
@@ -20,13 +20,13 @@ Sequence files (`.seq`) define the timeline and layering of visual effects. They
```
# BPM 120
```
-Specifies beats per minute. Used to convert beat notation to seconds.
+Specifies beats per minute. Required. Used to convert beats to physical seconds at runtime.
### END_DEMO Directive
```
END_DEMO <time>
```
-Optional auto-exit time. Supports beat notation (`64b`) or seconds (`32.0`).
+Optional auto-exit time in beats (e.g., `64` or `64b`) or explicit seconds (`32.0s`).
### SEQUENCE Declaration
```
@@ -35,10 +35,10 @@ SEQUENCE <global_start> <priority> ["optional_name"] [optional_end]
```
**Parameters:**
-- `global_start`: Sequence start time (beats or seconds)
+- `global_start`: Sequence start time in beats (default) or explicit seconds (`2.5s`)
- `priority`: Render order (0-9 for scenes, 10+ for post-processing)
- `"optional_name"`: Optional display name for Gantt charts
-- `[optional_end]`: Optional sequence end time (forces effect termination)
+- `[optional_end]`: Optional sequence end time in beats (forces effect termination)
**Examples:**
```
@@ -60,34 +60,47 @@ EFFECT <+|=|-> <EffectClassName> <local_start> <local_end> [constructor_args...]
**Parameters:**
- `EffectClassName`: C++ class from `demo_effects.h`
-- `local_start`, `local_end`: Time relative to sequence start
+- `local_start`, `local_end`: Time in beats relative to sequence start
- `constructor_args`: Optional (rarely used, most effects use standard params only)
---
## Time Notation
+**Beat-based timing (default):** All times are in musical beats, ensuring alignment regardless of BPM changes.
+
| Notation | Example | Description |
|----------|---------|-------------|
-| Integer beats | `0`, `64`, `128` | No decimal point = beats |
-| Explicit beats | `0b`, `64b`, `128b` | Suffix 'b' = beats |
-| Decimal seconds | `0.0`, `32.0`, `64.0` | Decimal point = seconds |
-| Explicit seconds | `32.0s`, `64.0s` | Suffix 's' = seconds |
+| **Beats (default)** | `0`, `4`, `16` | Integer or decimal, no suffix |
+| Explicit beats | `4b`, `16.5b` | Optional 'b' suffix for clarity |
+| Explicit seconds | `2.0s`, `8.25s` | Suffix 's' for physical time (rare) |
+
+**Conversion:** At 120 BPM, 1 beat = 0.5 seconds, 4 beats = 2 seconds
-**At 120 BPM:** Beat 64 = 32.0 seconds, Beat 120 = 60.0 seconds
+**Why beats?**
+- Musical alignment: Sequences stay synchronized to music structure
+- BPM independence: Changing BPM preserves musical timing
+- Intuitive authoring: Timeline matches bars/beats
---
## Runtime Parameters
-All effects receive these parameters every frame in `render()`:
+All effects receive these parameters every frame in `render()` via `CommonPostProcessUniforms`:
| Parameter | Type | Description |
|-----------|------|-------------|
-| `time` | float | Global time in seconds |
-| `beat` | float | Current beat fraction (0.0-1.0) |
-| `intensity` | float | Audio peak (0.0-1.0, for beat sync) |
-| `aspect_ratio` | float | Screen width/height |
+| `resolution` | vec2 | Screen dimensions in pixels |
+| `aspect_ratio` | float | Screen width/height ratio |
+| `time` | float | Physical time in seconds (unaffected by tempo) |
+| `beat_time` | float | Musical time in beats (absolute) |
+| `beat_phase` | float | Fractional beat (0.0-1.0 within current beat) |
+| `audio_intensity` | float | Audio peak (0.0-1.0, for beat sync) |
+
+**Use cases:**
+- `time`: Physics-based animation (constant speed)
+- `beat_time`: Musical animation (sync to bars/beats)
+- `beat_phase`: Smooth oscillation per beat
---
@@ -108,47 +121,55 @@ All effects receive these parameters every frame in `render()`:
### Basic Sequence
```
+# BPM 120
SEQUENCE 0 0
- EFFECT + FlashEffect 0.0 0.5 # Priority 0
- EFFECT + HeptagonEffect 0.2 10 # Priority 1
+ EFFECT + FlashEffect 0 1 # Priority 0, beats 0-1 (0-0.5s @ 120 BPM)
+ EFFECT + HeptagonEffect 0.4 20 # Priority 1, beats 0.4-20
```
### Same Priority Layering
```
SEQUENCE 0 0
- EFFECT + Flash 0.0 0.5 # Priority 0
- EFFECT = Fade 0.1 0.3 # Priority 0 (same layer)
- EFFECT + Other 0.2 3 # Priority 1
+ EFFECT + Flash 0 1 # Priority 0
+ EFFECT = Fade 0.2 0.6 # Priority 0 (same layer)
+ EFFECT + Other 0.4 6 # Priority 1
```
### Background Elements
```
SEQUENCE 0 0
- EFFECT - FlashCube 0 10 # Priority -1 (background)
- EFFECT = BgEffect 0 5 # Priority -1 (same layer)
- EFFECT + MainEffect 0 10 # Priority 0 (foreground)
+ EFFECT - FlashCube 0 20 # Priority -1 (background)
+ EFFECT = BgEffect 0 10 # Priority -1 (same layer)
+ EFFECT + MainEffect 0 20 # Priority 0 (foreground)
```
### Sequence with Explicit End
```
-SEQUENCE 8b 0 [5.0]
- EFFECT + Particles 0 120 # Runs until 5s (sequence end)
+SEQUENCE 16 0 [10]
+ EFFECT + Particles 0 240 # Runs until beat 26 (sequence end at 16+10)
```
### Post-Processing Chain
```
SEQUENCE 0 10
- EFFECT + GaussianBlur 0 60 # Applied first
- EFFECT + ChromaAberration 0 60 # Applied second
- EFFECT + Solarize 0 60 # Applied last
+ EFFECT + GaussianBlur 0 120 # Applied first
+ EFFECT + ChromaAberration 0 120 # Applied second
+ EFFECT + Solarize 0 120 # Applied last
```
-### Music-Synchronized
+### Four-Bar Sequence (16 beats)
```
# BPM 120
-SEQUENCE 0b 0
- EFFECT + Flash 0b 1b # Beat 0-1 (0-0.5s)
- EFFECT + Heptagon 4b 8b # Beat 4-8 (2-4s)
+SEQUENCE 0 0 "Intro"
+ EFFECT + Flash 0 1 # First beat
+ EFFECT + Heptagon 4 12 # Second bar through third bar
+ EFFECT + Fade 15 16 # Final beat
+```
+
+### Explicit Physical Time (Rare)
+```
+SEQUENCE 2.5s 0 "Intro timing" # Start at 2.5 physical seconds
+ EFFECT + Fade 0 4 # Fade from beat 0-4 (relative)
```
---
diff --git a/output/layer_0.png b/output/layer_0.png
new file mode 100644
index 0000000..5e66a7f
--- /dev/null
+++ b/output/layer_0.png
Binary files differ
diff --git a/output/layer_1.png b/output/layer_1.png
new file mode 100644
index 0000000..3fc7102
--- /dev/null
+++ b/output/layer_1.png
Binary files differ
diff --git a/output/ref/layer_0.png b/output/ref/layer_0.png
new file mode 100644
index 0000000..b518ce0
--- /dev/null
+++ b/output/ref/layer_0.png
Binary files differ
diff --git a/output/ref/layer_1.png b/output/ref/layer_1.png
new file mode 100644
index 0000000..91e5b9c
--- /dev/null
+++ b/output/ref/layer_1.png
Binary files differ
diff --git a/output/toto.png b/output/toto.png
new file mode 100644
index 0000000..b5fb086
--- /dev/null
+++ b/output/toto.png
Binary files differ
diff --git a/output/toto0.png b/output/toto0.png
new file mode 100644
index 0000000..f970b84
--- /dev/null
+++ b/output/toto0.png
Binary files differ
diff --git a/src/gpu/effect.cc b/src/gpu/effect.cc
index 58e011c..e0a9c24 100644
--- a/src/gpu/effect.cc
+++ b/src/gpu/effect.cc
@@ -226,7 +226,8 @@ void MainSequence::resize(int width, int height) {
}
}
-void MainSequence::render_frame(float global_time, float beat, float peak,
+void MainSequence::render_frame(float global_time, float beat_time,
+ float beat_phase, float peak,
float aspect_ratio, WGPUSurface surface) {
WGPUCommandEncoder encoder =
wgpuDeviceCreateCommandEncoder(gpu_ctx.device, nullptr);
@@ -260,11 +261,12 @@ void MainSequence::render_frame(float global_time, float beat, float peak,
// Construct common uniforms once (reused for all effects)
CommonPostProcessUniforms base_uniforms = {
.resolution = {static_cast<float>(width_), static_cast<float>(height_)},
- ._pad = {0.0f, 0.0f},
.aspect_ratio = aspect_ratio,
.time = 0.0f, // Will be set per-effect
- .beat = beat,
+ .beat_time = beat_time,
+ .beat_phase = beat_phase,
.audio_intensity = peak,
+ ._pad = 0.0f,
};
for (const SequenceItem* item : scene_effects) {
@@ -564,7 +566,8 @@ void MainSequence::simulate_until(float target_time, float step_rate,
for (float t = 0.0f; t < target_time; t += step_rate) {
WGPUCommandEncoder encoder =
wgpuDeviceCreateCommandEncoder(gpu_ctx.device, nullptr);
- float beat = fmodf(t * bpm / 60.0f, 1.0f);
+ float absolute_beat_time = t * bpm / 60.0f;
+ float beat_phase = fmodf(absolute_beat_time, 1.0f);
std::vector<SequenceItem*> scene_effects, post_effects;
for (ActiveSequence& entry : sequences_) {
if (t >= entry.start_time) {
@@ -575,11 +578,12 @@ void MainSequence::simulate_until(float target_time, float step_rate,
for (const SequenceItem* item : scene_effects) {
CommonPostProcessUniforms test_uniforms = {
.resolution = {static_cast<float>(width_), static_cast<float>(height_)},
- ._pad = {0.0f, 0.0f},
.aspect_ratio = aspect_ratio,
.time = t - item->start_time,
- .beat = beat,
+ .beat_time = absolute_beat_time,
+ .beat_phase = beat_phase,
.audio_intensity = 0.0f,
+ ._pad = 0.0f,
};
item->effect->compute(encoder, test_uniforms);
}
diff --git a/src/gpu/effect.h b/src/gpu/effect.h
index ed90ac7..b9709a4 100644
--- a/src/gpu/effect.h
+++ b/src/gpu/effect.h
@@ -49,16 +49,19 @@ class Effect {
// Helper: get initialized CommonPostProcessUniforms based on current dimensions
// If aspect_ratio < 0, computes from width_/height_
- CommonPostProcessUniforms get_common_uniforms(float time = 0.0f, float beat = 0.0f,
+ CommonPostProcessUniforms get_common_uniforms(float time = 0.0f,
+ float beat_time = 0.0f,
+ float beat_phase = 0.0f,
float intensity = 0.0f,
float aspect_ratio = -1.0f) const {
return {
.resolution = {static_cast<float>(width_), static_cast<float>(height_)},
- ._pad = {0.0f, 0.0f},
.aspect_ratio = aspect_ratio < 0.0f ? static_cast<float>(width_) / static_cast<float>(height_) : aspect_ratio,
.time = time,
- .beat = beat,
+ .beat_time = beat_time,
+ .beat_phase = beat_phase,
.audio_intensity = intensity,
+ ._pad = 0.0f,
};
}
@@ -130,8 +133,8 @@ class MainSequence {
void init_test(const GpuContext& ctx);
void add_sequence(std::shared_ptr<Sequence> seq, float start_time,
int priority = 0);
- void render_frame(float global_time, float beat, float peak,
- float aspect_ratio, WGPUSurface surface);
+ void render_frame(float global_time, float beat_time, float beat_phase,
+ float peak, float aspect_ratio, WGPUSurface surface);
void resize(int width, int height);
void shutdown();
diff --git a/src/gpu/effects/flash_effect.cc b/src/gpu/effects/flash_effect.cc
index 4357c34..e53cbce 100644
--- a/src/gpu/effects/flash_effect.cc
+++ b/src/gpu/effects/flash_effect.cc
@@ -77,7 +77,7 @@ void FlashEffect::render(WGPURenderPassEncoder pass,
// Animate color based on time and beat
const float r = params_.color[0] * (0.5f + 0.5f * sinf(uniforms.time * 0.5f));
const float g = params_.color[1] * (0.5f + 0.5f * cosf(uniforms.time * 0.7f));
- const float b = params_.color[2] * (1.0f + 0.3f * uniforms.beat);
+ const float b = params_.color[2] * (1.0f + 0.3f * uniforms.beat_phase);
// Update uniforms with computed (animated) values
const FlashUniforms u = {
diff --git a/src/gpu/effects/post_process_helper.h b/src/gpu/effects/post_process_helper.h
index 23cde0e..1c649e7 100644
--- a/src/gpu/effects/post_process_helper.h
+++ b/src/gpu/effects/post_process_helper.h
@@ -8,12 +8,13 @@
// Uniform data common to all post-processing effects
struct CommonPostProcessUniforms {
- vec2 resolution;
- float _pad[2]; // Padding for 16-byte alignment
- float aspect_ratio;
- float time;
- float beat;
- float audio_intensity;
+ vec2 resolution; // Screen dimensions
+ float aspect_ratio; // Width/height ratio
+ float time; // Physical time in seconds (unaffected by tempo)
+ float beat_time; // Musical time in beats (absolute, tempo-scaled)
+ float beat_phase; // Fractional beat (0.0-1.0 within current beat)
+ float audio_intensity;// Audio peak for beat sync
+ float _pad; // Padding for 16-byte alignment
};
static_assert(sizeof(CommonPostProcessUniforms) == 32,
"CommonPostProcessUniforms must be 32 bytes for WGSL alignment");
diff --git a/src/gpu/gpu.cc b/src/gpu/gpu.cc
index e89a2f0..41f5bcf 100644
--- a/src/gpu/gpu.cc
+++ b/src/gpu/gpu.cc
@@ -381,8 +381,10 @@ void gpu_init(PlatformState* platform_state) {
platform_state->height);
}
-void gpu_draw(float audio_peak, float aspect_ratio, float time, float beat) {
- g_main_sequence.render_frame(time, beat, audio_peak, aspect_ratio, g_surface);
+void gpu_draw(float audio_peak, float aspect_ratio, float time,
+ float beat_time, float beat_phase) {
+ g_main_sequence.render_frame(time, beat_time, beat_phase, audio_peak,
+ aspect_ratio, g_surface);
}
void gpu_resize(int width, int height) {
diff --git a/src/gpu/gpu.h b/src/gpu/gpu.h
index 8c59aee..c7ee89f 100644
--- a/src/gpu/gpu.h
+++ b/src/gpu/gpu.h
@@ -42,7 +42,8 @@ struct RenderPass {
class MainSequence; // Forward declaration
void gpu_init(PlatformState* platform_state);
-void gpu_draw(float audio_peak, float aspect_ratio, float time, float beat);
+void gpu_draw(float audio_peak, float aspect_ratio, float time,
+ float beat_time, float beat_phase);
void gpu_resize(int width, int height);
void gpu_shutdown();
diff --git a/src/main.cc b/src/main.cc
index 6132841..41c881b 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -325,11 +325,10 @@ int main(int argc, char** argv) {
const float raw_peak = audio_get_realtime_peak();
const float visual_peak = fminf(raw_peak * 8.0f, 1.0f);
- // Beat calculation should use audio time to align with audio events.
- // The graphics loop time (current_physical_time) is used for frame rate.
- const float beat_time = current_audio_time * g_tracker_score.bpm / 60.0f;
- const int beat_number = (int)beat_time;
- const float beat = fmodf(beat_time, 1.0f); // Fractional part (0.0 to 1.0)
+ // Beat calculation: convert audio time to musical beats
+ const float absolute_beat_time = current_audio_time * g_tracker_score.bpm / 60.0f;
+ const int beat_number = (int)absolute_beat_time;
+ const float beat_phase = fmodf(absolute_beat_time, 1.0f); // Fractional part (0.0 to 1.0)
// Print beat/time info periodically for identifying sync points
// Use graphics time for the print interval to avoid excessive output if
@@ -339,20 +338,21 @@ int main(int argc, char** argv) {
0.5f) { // Print every 0.5 seconds
if (tempo_test_enabled) {
printf(
- "[GraphicsT=%.2f, AudioT=%.2f, MusicT=%.2f, Beat=%d, Frac=%.2f, "
+ "[GraphicsT=%.2f, AudioT=%.2f, MusicT=%.2f, Beat=%d, Phase=%.2f, "
"Peak=%.2f, Tempo=%.2fx]\n",
current_physical_time, current_audio_time, g_music_time,
- beat_number, beat, visual_peak, g_tempo_scale);
+ beat_number, beat_phase, visual_peak, g_tempo_scale);
} else {
- printf("[GraphicsT=%.2f, AudioT=%.2f, Beat=%d, Frac=%.2f, Peak=%.2f]\n",
- current_physical_time, current_audio_time, beat_number, beat,
+ printf("[GraphicsT=%.2f, AudioT=%.2f, Beat=%d, Phase=%.2f, Peak=%.2f]\n",
+ current_physical_time, current_audio_time, beat_number, beat_phase,
visual_peak);
}
last_graphics_print_time = current_physical_time;
}
- // Draw graphics using the graphics frame time and synchronized audio events
- gpu_draw(visual_peak, aspect_ratio, (float)current_physical_time, beat);
+ // Draw graphics using physical time and musical beat time
+ gpu_draw(visual_peak, aspect_ratio, (float)current_physical_time,
+ absolute_beat_time, beat_phase);
last_frame_time = current_physical_time;
// Update audio systems (tracker, synth, etc.) based on audio time
diff --git a/src/test_demo.cc b/src/test_demo.cc
index b8e9381..edbcae0 100644
--- a/src/test_demo.cc
+++ b/src/test_demo.cc
@@ -104,8 +104,10 @@ class PeakMeterEffect : public PostProcessEffect {
.resolution = {(float)width_, (float)height_},
.aspect_ratio = aspect_ratio,
.time = time,
- .beat = beat,
+ .beat_time = beat,
+ .beat_phase = beat,
.audio_intensity = peak_value,
+ ._pad = 0.0f,
};
uniforms_.update(ctx_.queue, u);
@@ -347,11 +349,10 @@ int main(int argc, char** argv) {
const float raw_peak = audio_get_realtime_peak();
const float visual_peak = fminf(raw_peak * 8.0f, 1.0f);
- // Beat calculation should use audio time to align with audio events.
- // The graphics loop time (current_physical_time) is used for frame rate.
- const float beat_time = current_audio_time * g_tracker_score.bpm / 60.0f;
- const int beat_number = (int)beat_time;
- const float beat = fmodf(beat_time, 1.0f); // Fractional part (0.0 to 1.0)
+ // Beat calculation: convert audio time to musical beats
+ const float absolute_beat_time = current_audio_time * g_tracker_score.bpm / 60.0f;
+ const int beat_number = (int)absolute_beat_time;
+ const float beat_phase = fmodf(absolute_beat_time, 1.0f); // Fractional part (0.0 to 1.0)
#if !defined(STRIP_ALL)
// Log peak (either per-frame or per-beat)
@@ -377,22 +378,23 @@ int main(int argc, char** argv) {
if (current_physical_time - last_graphics_print_time >= 0.5f) {
if (tempo_test_enabled) {
printf(
- "[GraphicsT=%.2f, AudioT=%.2f, MusicT=%.2f, Beat=%d, Frac=%.2f, "
+ "[GraphicsT=%.2f, AudioT=%.2f, MusicT=%.2f, Beat=%d, Phase=%.2f, "
"Peak=%.2f, Tempo=%.2fx]\n",
current_physical_time, current_audio_time, g_music_time,
- beat_number, beat, visual_peak, g_tempo_scale);
+ beat_number, beat_phase, visual_peak, g_tempo_scale);
} else {
- printf("[GraphicsT=%.2f, AudioT=%.2f, Beat=%d, Frac=%.2f, Peak=%.2f]\n",
- current_physical_time, current_audio_time, beat_number, beat,
+ printf("[GraphicsT=%.2f, AudioT=%.2f, Beat=%d, Phase=%.2f, Peak=%.2f]\n",
+ current_physical_time, current_audio_time, beat_number, beat_phase,
visual_peak);
}
last_graphics_print_time = current_physical_time;
}
#endif
- // Draw graphics using the graphics frame time and synchronized audio events
+ // Draw graphics using physical time and musical beat time
const float graphics_frame_time = (float)current_physical_time;
- gpu_draw(visual_peak, aspect_ratio, graphics_frame_time, beat);
+ gpu_draw(visual_peak, aspect_ratio, graphics_frame_time,
+ absolute_beat_time, beat_phase);
// Update audio systems (tracker, synth, etc.) based on audio time
// progression
diff --git a/src/tests/assets/test_sequence.cc b/src/tests/assets/test_sequence.cc
index 44aac46..157b462 100644
--- a/src/tests/assets/test_sequence.cc
+++ b/src/tests/assets/test_sequence.cc
@@ -96,7 +96,7 @@ void test_effect_lifecycle() {
main_seq.add_sequence(seq1, 0.0f, 0);
// Before effect starts
- main_seq.render_frame(0.5f, 0, 0, 1.0f,
+ main_seq.render_frame(0.5f, 0, 0, 0, 1.0f,
dummy_surface); // This will still call real render, but
// test counts only init
assert(effect1->init_calls == 1);
@@ -105,26 +105,26 @@ void test_effect_lifecycle() {
assert(effect1->end_calls == 0);
// Effect starts
- main_seq.render_frame(1.0f, 0, 0, 1.0f, dummy_surface);
+ main_seq.render_frame(1.0f, 0, 0, 0, 1.0f, dummy_surface);
assert(effect1->start_calls == 1);
// assert(effect1->render_calls == 1); // No longer checking render calls
// directly from here
assert(effect1->end_calls == 0);
// During effect
- main_seq.render_frame(2.0f, 0, 0, 1.0f, dummy_surface);
+ main_seq.render_frame(2.0f, 0, 0, 0, 1.0f, dummy_surface);
assert(effect1->start_calls == 1);
// assert(effect1->render_calls == 2);
assert(effect1->end_calls == 0);
// Effect ends
- main_seq.render_frame(3.0f, 0, 0, 1.0f, dummy_surface);
+ main_seq.render_frame(3.0f, 0, 0, 0, 1.0f, dummy_surface);
assert(effect1->start_calls == 1);
// assert(effect1->render_calls == 2); // Render not called on end frame
assert(effect1->end_calls == 1);
// After effect ends
- main_seq.render_frame(3.5f, 0, 0, 1.0f, dummy_surface);
+ main_seq.render_frame(3.5f, 0, 0, 0, 1.0f, dummy_surface);
assert(effect1->start_calls == 1);
// assert(effect1->render_calls == 2);
assert(effect1->end_calls == 1);
diff --git a/tools/cnn_test.cc b/tools/cnn_test.cc
index 59f1d22..c2983a9 100644
--- a/tools/cnn_test.cc
+++ b/tools/cnn_test.cc
@@ -387,11 +387,12 @@ int main(int argc, char** argv) {
// Update uniforms
CommonPostProcessUniforms common_u = {
.resolution = {static_cast<float>(width), static_cast<float>(height)},
- ._pad = {0.0f, 0.0f},
.aspect_ratio = static_cast<float>(width) / static_cast<float>(height),
.time = 0.0f,
- .beat = 0.0f,
+ .beat_time = 0.0f,
+ .beat_phase = 0.0f,
.audio_intensity = 0.0f,
+ ._pad = 0.0f,
};
wgpuQueueWriteBuffer(queue, common_uniform_buffer, 0, &common_u,
sizeof(common_u));
diff --git a/tools/seq_compiler.cc b/tools/seq_compiler.cc
index ecb9908..069122a 100644
--- a/tools/seq_compiler.cc
+++ b/tools/seq_compiler.cc
@@ -633,30 +633,22 @@ void generate_gantt_html(const std::string& output_file,
// (seconds)
std::string convert_to_time(const std::string& value, float bpm) {
std::string val = value;
- bool is_beat = false;
- // Check for explicit 'b' suffix (beat)
- if (!val.empty() && val.back() == 'b') {
- is_beat = true;
- val.pop_back();
- }
- // Check for explicit 's' suffix (seconds)
- else if (!val.empty() && val.back() == 's') {
+ // Check for explicit 's' suffix (seconds) - return as-is
+ if (!val.empty() && val.back() == 's') {
val.pop_back();
- return val; // Already in seconds
- }
- // If no suffix and no decimal point, assume beats
- else if (val.find('.') == std::string::npos) {
- is_beat = true;
+ return val;
}
- if (is_beat) {
- float beat = std::stof(val);
- float time = beat * 60.0f / bpm;
- return std::to_string(time);
+ // Check for explicit 'b' suffix (beats) - strip and convert
+ if (!val.empty() && val.back() == 'b') {
+ val.pop_back();
}
- return val; // Return as-is (seconds)
+ // DEFAULT: All numbers (with or without 'b' suffix) are beats
+ float beat = std::stof(val);
+ float time = beat * 60.0f / bpm;
+ return std::to_string(time);
}
int main(int argc, char* argv[]) {
diff --git a/tools/timeline_editor/README.md b/tools/timeline_editor/README.md
index a76a5ed..4861a88 100644
--- a/tools/timeline_editor/README.md
+++ b/tools/timeline_editor/README.md
@@ -49,23 +49,25 @@ SEQUENCE <start_time> <priority> ["optional_name"] [optional_end]
- `=` = Keep same priority as previous
- `-` = Decrement priority (background layers)
-**Time Notation:**
-- `0b`, `4b`, `64b` = Beats (converted using BPM)
-- `0.0`, `2.0`, `32.0` = Seconds
-- Integer without 'b': treated as beats
-- Decimal point: treated as seconds
+**Time Notation (Beat-Based):**
+- **Default:** All numbers are beats (e.g., `4`, `16.5` = beats)
+- `4b`, `16b` = Explicit beats (optional 'b' suffix for clarity)
+- `2.0s`, `8.25s` = Explicit seconds (rare, for physical timing)
-Example:
+Example (Beat-Based):
```
# BPM 120
-SEQUENCE 0b 0 "Opening Scene"
- EFFECT - FlashCubeEffect .2 3 # Background (priority -1)
- EFFECT + FlashEffect 0.0 1.0 # Foreground (priority 0)
- EFFECT + FadeEffect 0.5 2.0 # Overlay (priority 1)
+SEQUENCE 0 0 "Opening Scene" # Start at beat 0
+ EFFECT - FlashCubeEffect 0 4 # Beats 0-4 (0-2s @ 120 BPM)
+ EFFECT + FlashEffect 0 2 # Beats 0-2 (0-1s)
+ EFFECT + FadeEffect 1 4 # Beats 1-4 (0.5-2s)
-SEQUENCE 4b 1 "Beat Drop"
- EFFECT + HeptagonEffect 0.0 0.5 # Priority 0
- EFFECT = ParticlesEffect 0.0 2.0 # Priority 0 (same layer)
+SEQUENCE 8 1 "Beat Drop" # Start at beat 8 (bar 3)
+ EFFECT + HeptagonEffect 0 1 # First beat of sequence
+ EFFECT = ParticlesEffect 0 4 # Full bar (4 beats)
+
+SEQUENCE 2.5s 0 "Explicit seconds" # Rare: start at 2.5 physical seconds
+ EFFECT + Fade 0 4 # Still uses beats for duration
```
## Technical Notes
diff --git a/training/debug/cur/layer_0.png b/training/debug/cur/layer_0.png
new file mode 100644
index 0000000..0cb977b
--- /dev/null
+++ b/training/debug/cur/layer_0.png
Binary files differ
diff --git a/training/debug/cur/layer_1.png b/training/debug/cur/layer_1.png
new file mode 100644
index 0000000..801aad2
--- /dev/null
+++ b/training/debug/cur/layer_1.png
Binary files differ
diff --git a/training/debug/cur/toto.png b/training/debug/cur/toto.png
new file mode 100644
index 0000000..9caff40
--- /dev/null
+++ b/training/debug/cur/toto.png
Binary files differ
diff --git a/training/debug/debug.sh b/training/debug/debug.sh
new file mode 100755
index 0000000..083082b
--- /dev/null
+++ b/training/debug/debug.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+pwd=`pwd`
+
+img=../input/img_003.png
+
+# img=/Users/skal/black_512x512_rgba.png
+#img=/Users/skal/rgba_0_0_0_0.png
+check_pt=../checkpoints/checkpoint_epoch_10000.pth
+#check_pt=../chk_5000_3x3x3.pt
+
+#../train_cnn.py --layers 3 --kernel_sizes 3,3,3 --epochs 10000 --batch_size 8 --input ../input/ --target ../target_2/ --checkpoint-every 1000
+#../train_cnn.py --export-only ${check_pt}
+#../train_cnn.py --export-only ${check_pt} --infer ${img} --output test/toto.png
+
+#../train_cnn.py --layers 2 --kernel_sizes 1,1 --epochs 10 --batch_size 5 --input ../input/ --target ../target_2/ --checkpoint-every 10
+#../train_cnn.py --export-only ${check_pt}
+#../train_cnn.py --export-only ${check_pt} --infer ${img} --output test/toto.png
+
+## XXX uncomment!
+../train_cnn.py --export-only ${check_pt} \
+ --infer ${img} \
+ --output ref/toto.png --save-intermediates ref/ # --debug-hex
+
+echo "== GENERATE SHADERS =="
+echo
+cd ../../
+./training/train_cnn.py --export-only ${pwd}/${check_pt}
+
+echo "== COMPILE =="
+echo
+cmake --build build -j4 --target cnn_test
+cd ${pwd}
+
+echo "== RUN =="
+echo
+rm -f cur/toto.png
+../../build/cnn_test ${img} cur/toto.png --save-intermediates cur/ --layers 3 # --debug-hex
+
+open cur/*.png ref/*.png
+
+echo "open cur/*.png ref/*.png"
+
+#pngcrush -rem gAMA -rem sRGB cur/toto.png toto.png && mv toto.png cur/toto.png
+#pngcrush -rem gAMA -rem sRGB cur/layer_0.png toto.png && mv toto.png cur/layer_0.png
diff --git a/training/debug/ref/layer_0.png b/training/debug/ref/layer_0.png
new file mode 100644
index 0000000..3e0eebe
--- /dev/null
+++ b/training/debug/ref/layer_0.png
Binary files differ
diff --git a/training/debug/ref/layer_1.png b/training/debug/ref/layer_1.png
new file mode 100644
index 0000000..d858f80
--- /dev/null
+++ b/training/debug/ref/layer_1.png
Binary files differ
diff --git a/training/debug/ref/toto.png b/training/debug/ref/toto.png
new file mode 100644
index 0000000..f869a7c
--- /dev/null
+++ b/training/debug/ref/toto.png
Binary files differ
diff --git a/training/debug/training/checkpoints/checkpoint_epoch_10.pth b/training/debug/training/checkpoints/checkpoint_epoch_10.pth
new file mode 100644
index 0000000..54ba5c5
--- /dev/null
+++ b/training/debug/training/checkpoints/checkpoint_epoch_10.pth
Binary files differ
diff --git a/training/debug/training/checkpoints/checkpoint_epoch_100.pth b/training/debug/training/checkpoints/checkpoint_epoch_100.pth
new file mode 100644
index 0000000..f94e9f8
--- /dev/null
+++ b/training/debug/training/checkpoints/checkpoint_epoch_100.pth
Binary files differ
diff --git a/training/debug/training/checkpoints/checkpoint_epoch_50.pth b/training/debug/training/checkpoints/checkpoint_epoch_50.pth
new file mode 100644
index 0000000..a602f4b
--- /dev/null
+++ b/training/debug/training/checkpoints/checkpoint_epoch_50.pth
Binary files differ
diff --git a/training/patch_32x32.png b/training/patch_32x32.png
deleted file mode 100644
index a665065..0000000
--- a/training/patch_32x32.png
+++ /dev/null
Binary files differ
diff --git a/training/toto.png b/training/toto.png
new file mode 100644
index 0000000..2044840
--- /dev/null
+++ b/training/toto.png
Binary files differ
diff --git a/workspaces/main/shaders/common_uniforms.wgsl b/workspaces/main/shaders/common_uniforms.wgsl
index ce1be53..1ab8939 100644
--- a/workspaces/main/shaders/common_uniforms.wgsl
+++ b/workspaces/main/shaders/common_uniforms.wgsl
@@ -1,11 +1,11 @@
struct CommonUniforms {
- resolution: vec2<f32>,
- _pad0: f32,
- _pad1: f32,
- aspect_ratio: f32,
- time: f32,
- beat: f32,
- audio_intensity: f32,
+ resolution: vec2<f32>, // Screen dimensions
+ aspect_ratio: f32, // Width/height ratio
+ time: f32, // Physical time in seconds (unaffected by tempo)
+ beat_time: f32, // Musical time in beats (absolute, tempo-scaled)
+ beat_phase: f32, // Fractional beat (0.0-1.0 within current beat)
+ audio_intensity: f32, // Audio peak for beat sync
+ _pad: f32, // Padding
};
struct GlobalUniforms {
view_proj: mat4x4<f32>,
diff --git a/workspaces/main/timeline.seq b/workspaces/main/timeline.seq
index c3e2316..ab9e40d 100644
--- a/workspaces/main/timeline.seq
+++ b/workspaces/main/timeline.seq
@@ -2,104 +2,104 @@
# Generated by Timeline Editor
# BPM 120
-SEQUENCE 0.00 0
- EFFECT - FlashCubeEffect 0.00 2.44
- EFFECT + FlashEffect 0.00 1.00 color=1.0,0.5,0.5 decay=0.95
- EFFECT + FadeEffect 0.10 1.00
- EFFECT + SolarizeEffect 0.00 2.00
- EFFECT + VignetteEffect 0.00 2.50 radius=0.6 softness=0.1
+SEQUENCE 0.00s 0
+EFFECT - FlashCubeEffect 0.00s 2.44s
+EFFECT + FlashEffect 0.00s 1.00s color=1.0,0.5,0.5 decay=0.95
+EFFECT + FadeEffect 0.10s 1.00s
+EFFECT + SolarizeEffect 0.00s 2.00s
+EFFECT + VignetteEffect 0.00s 2.50s radius=0.6 softness=0.1
-SEQUENCE 2.50 0 "rotating cube"
- EFFECT + CircleMaskEffect 0.00 4.00 0.50
- EFFECT + RotatingCubeEffect 0.00 4.00
- EFFECT + GaussianBlurEffect 1.00 2.00 strength=1.0
- EFFECT + GaussianBlurEffect 3.00 4.00 strength=2.0
+SEQUENCE 2.50s 0 "rotating cube"
+EFFECT + CircleMaskEffect 0.00s 4.00s 0.50
+EFFECT + RotatingCubeEffect 0.00s 4.00s
+EFFECT + GaussianBlurEffect 1.00s 2.00s strength=1.0
+EFFECT + GaussianBlurEffect 3.00s 4.00s strength=2.0
-SEQUENCE 5.93 0
- EFFECT - FlashCubeEffect 0.11 1.45
- EFFECT + FlashEffect 0.00 0.20
+SEQUENCE 5.93s 0
+EFFECT - FlashCubeEffect 0.11s 1.45s
+EFFECT + FlashEffect 0.00s 0.20s
-SEQUENCE 6.90 1 "spray"
- EFFECT + ParticleSprayEffect 0.00 2.00
- EFFECT + ParticlesEffect 0.00 3.00
- EFFECT = GaussianBlurEffect 0.00 2.00 strength=3.0
+SEQUENCE 6.90s 1 "spray"
+EFFECT + ParticleSprayEffect 0.00s 2.00s
+EFFECT + ParticlesEffect 0.00s 3.00s
+EFFECT = GaussianBlurEffect 0.00s 2.00s strength=3.0
-SEQUENCE 8.50 2 "Hybrid3D"
- EFFECT + ThemeModulationEffect 0.00 2.00
- EFFECT + HeptagonEffect 0.20 2.00
- EFFECT + ParticleSprayEffect 0.00 2.00
- EFFECT = ParticlesEffect 0.00 2.00
- EFFECT + Hybrid3DEffect 0.00 2.00
- EFFECT + GaussianBlurEffect 0.00 2.00
- EFFECT + CNNEffect 0.0 2.0 layers=3 blend=.9
+SEQUENCE 8.50s 2 "Hybrid3D"
+EFFECT + ThemeModulationEffect 0.00s 2.00s
+EFFECT + HeptagonEffect 0.20s 2.00s
+EFFECT + ParticleSprayEffect 0.00s 2.00s
+EFFECT = ParticlesEffect 0.00s 2.00s
+EFFECT + Hybrid3DEffect 0.00s 2.00s
+EFFECT + GaussianBlurEffect 0.00s 2.00s
+EFFECT + CNNEffect 0.0s 2.0s layers=3 blend=.9
# EFFECT + ChromaAberrationEffect 0.00 1.50 offset=0.01 angle=1.57
-SEQUENCE 10.50 0 "CNN effect"
- EFFECT + HeptagonEffect 0.0 12.00
+SEQUENCE 10.50s 0 "CNN effect"
+EFFECT + HeptagonEffect 0.0s 12.00s
# EFFECT + RotatingCubeEffect 0.00 12.0
# EFFECT + Hybrid3DEffect 0.00 12.00
- EFFECT + Scene1Effect 0.0 12.0
- EFFECT + CNNEffect 1.0 12.0 layers=3 blend=.5
+EFFECT + Scene1Effect 0.0s 12.0s
+EFFECT + CNNEffect 1.0s 12.0s layers=3 blend=.5
-SEQUENCE 22.0 0 "buggy"
- EFFECT + HeptagonEffect 0.00 0.20
- EFFECT + FadeEffect 0.11 1.01
+SEQUENCE 22.0s 0 "buggy"
+EFFECT + HeptagonEffect 0.00s 0.20s
+EFFECT + FadeEffect 0.11s 1.01s
-SEQUENCE 22.14 3
- EFFECT + ThemeModulationEffect 0.00 4.00
- EFFECT = HeptagonEffect 0.00 4.00
- EFFECT + GaussianBlurEffect 0.00 5.00 strength=1.5
- EFFECT + ChromaAberrationEffect 0.00 5.00 offset=0.03 angle=0.785
- EFFECT + SolarizeEffect 0.00 5.00
+SEQUENCE 22.14s 3
+EFFECT + ThemeModulationEffect 0.00s 4.00s
+EFFECT = HeptagonEffect 0.00s 4.00s
+EFFECT + GaussianBlurEffect 0.00s 5.00s strength=1.5
+EFFECT + ChromaAberrationEffect 0.00s 5.00s offset=0.03 angle=0.785
+EFFECT + SolarizeEffect 0.00s 5.00s
-SEQUENCE 23.00 2
- EFFECT - FlashCubeEffect 0.20 1.50
- EFFECT + HeptagonEffect 0.00 2.00
- EFFECT + ParticleSprayEffect 0.00 2.00
- EFFECT + ParticlesEffect 0.00 2.00
+SEQUENCE 23.00s 2
+EFFECT - FlashCubeEffect 0.20s 1.50s
+EFFECT + HeptagonEffect 0.00s 2.00s
+EFFECT + ParticleSprayEffect 0.00s 2.00s
+EFFECT + ParticlesEffect 0.00s 2.00s
-SEQUENCE 22.75 2 "Fade"
- EFFECT - FlashCubeEffect 0.20 1.50
- EFFECT + FlashEffect 0.00 1.00
+SEQUENCE 22.75s 2 "Fade"
+EFFECT - FlashCubeEffect 0.20s 1.50s
+EFFECT + FlashEffect 0.00s 1.00s
-SEQUENCE 23.88 10
- EFFECT - FlashCubeEffect 0.20 1.50
- EFFECT + GaussianBlurEffect 0.00 2.00
- EFFECT + FlashEffect 0.00 0.20
- EFFECT = FlashEffect 0.50 0.20
+SEQUENCE 23.88s 10
+EFFECT - FlashCubeEffect 0.20s 1.50s
+EFFECT + GaussianBlurEffect 0.00s 2.00s
+EFFECT + FlashEffect 0.00s 0.20s
+EFFECT = FlashEffect 0.50s 0.20s
-SEQUENCE 25.59 1
- EFFECT + ThemeModulationEffect 0.00 8.00
- EFFECT + HeptagonEffect 0.20 2.00
- EFFECT + ParticleSprayEffect 0.00 8.00
- EFFECT + Hybrid3DEffect 0.00 8.06
- EFFECT + GaussianBlurEffect 0.00 8.00
- EFFECT + ChromaAberrationEffect 0.00 8.14
- EFFECT + SolarizeEffect 0.00 7.88
+SEQUENCE 25.59s 1
+EFFECT + ThemeModulationEffect 0.00s 8.00s
+EFFECT + HeptagonEffect 0.20s 2.00s
+EFFECT + ParticleSprayEffect 0.00s 8.00s
+EFFECT + Hybrid3DEffect 0.00s 8.06s
+EFFECT + GaussianBlurEffect 0.00s 8.00s
+EFFECT + ChromaAberrationEffect 0.00s 8.14s
+EFFECT + SolarizeEffect 0.00s 7.88s
-SEQUENCE 33.08 0
- EFFECT + ThemeModulationEffect 0.00 3.00
- EFFECT + VignetteEffect 0.00 3.00 radius=0.6 softness=0.3
- EFFECT + SolarizeEffect 0.00 3.00
+SEQUENCE 33.08s 0
+EFFECT + ThemeModulationEffect 0.00s 3.00s
+EFFECT + VignetteEffect 0.00s 3.00s radius=0.6 softness=0.3
+EFFECT + SolarizeEffect 0.00s 3.00s
-SEQUENCE 35.31 0
- EFFECT + ThemeModulationEffect 0.00 4.00
- EFFECT + HeptagonEffect 0.20 2.00
- EFFECT + GaussianBlurEffect 0.00 8.00
- EFFECT + SolarizeEffect 0.00 2.00
+SEQUENCE 35.31s 0
+EFFECT + ThemeModulationEffect 0.00s 4.00s
+EFFECT + HeptagonEffect 0.20s 2.00s
+EFFECT + GaussianBlurEffect 0.00s 8.00s
+EFFECT + SolarizeEffect 0.00s 2.00s
-SEQUENCE 42.29 0
- EFFECT + ThemeModulationEffect 0.00 6.00
- EFFECT = HeptagonEffect 0.20 2.00
- EFFECT + Hybrid3DEffect 0.00 4.00
- EFFECT + ParticleSprayEffect 0.00 5.50
- EFFECT + HeptagonEffect 0.00 8.00
- EFFECT + ChromaAberrationEffect 0.00 7.50
- EFFECT + GaussianBlurEffect 0.00 8.00
+SEQUENCE 42.29s 0
+EFFECT + ThemeModulationEffect 0.00s 6.00s
+EFFECT = HeptagonEffect 0.20s 2.00s
+EFFECT + Hybrid3DEffect 0.00s 4.00s
+EFFECT + ParticleSprayEffect 0.00s 5.50s
+EFFECT + HeptagonEffect 0.00s 8.00s
+EFFECT + ChromaAberrationEffect 0.00s 7.50s
+EFFECT + GaussianBlurEffect 0.00s 8.00s
-SEQUENCE 50.02 0
- EFFECT + ThemeModulationEffect 0.00 4.00
- EFFECT + HeptagonEffect 0.00 9.50
- EFFECT + ChromaAberrationEffect 0.00 9.00
- EFFECT + GaussianBlurEffect 0.00 8.00
+SEQUENCE 50.02s 0
+EFFECT + ThemeModulationEffect 0.00s 4.00s
+EFFECT + HeptagonEffect 0.00s 9.50s
+EFFECT + ChromaAberrationEffect 0.00s 9.00s
+EFFECT + GaussianBlurEffect 0.00s 8.00s
diff --git a/workspaces/main/timeline.seq.backup b/workspaces/main/timeline.seq.backup
new file mode 100644
index 0000000..c3e2316
--- /dev/null
+++ b/workspaces/main/timeline.seq.backup
@@ -0,0 +1,105 @@
+# Demo Timeline
+# Generated by Timeline Editor
+# BPM 120
+
+SEQUENCE 0.00 0
+ EFFECT - FlashCubeEffect 0.00 2.44
+ EFFECT + FlashEffect 0.00 1.00 color=1.0,0.5,0.5 decay=0.95
+ EFFECT + FadeEffect 0.10 1.00
+ EFFECT + SolarizeEffect 0.00 2.00
+ EFFECT + VignetteEffect 0.00 2.50 radius=0.6 softness=0.1
+
+SEQUENCE 2.50 0 "rotating cube"
+ EFFECT + CircleMaskEffect 0.00 4.00 0.50
+ EFFECT + RotatingCubeEffect 0.00 4.00
+ EFFECT + GaussianBlurEffect 1.00 2.00 strength=1.0
+ EFFECT + GaussianBlurEffect 3.00 4.00 strength=2.0
+
+SEQUENCE 5.93 0
+ EFFECT - FlashCubeEffect 0.11 1.45
+ EFFECT + FlashEffect 0.00 0.20
+
+SEQUENCE 6.90 1 "spray"
+ EFFECT + ParticleSprayEffect 0.00 2.00
+ EFFECT + ParticlesEffect 0.00 3.00
+ EFFECT = GaussianBlurEffect 0.00 2.00 strength=3.0
+
+SEQUENCE 8.50 2 "Hybrid3D"
+ EFFECT + ThemeModulationEffect 0.00 2.00
+ EFFECT + HeptagonEffect 0.20 2.00
+ EFFECT + ParticleSprayEffect 0.00 2.00
+ EFFECT = ParticlesEffect 0.00 2.00
+ EFFECT + Hybrid3DEffect 0.00 2.00
+ EFFECT + GaussianBlurEffect 0.00 2.00
+ EFFECT + CNNEffect 0.0 2.0 layers=3 blend=.9
+# EFFECT + ChromaAberrationEffect 0.00 1.50 offset=0.01 angle=1.57
+
+SEQUENCE 10.50 0 "CNN effect"
+ EFFECT + HeptagonEffect 0.0 12.00
+# EFFECT + RotatingCubeEffect 0.00 12.0
+# EFFECT + Hybrid3DEffect 0.00 12.00
+ EFFECT + Scene1Effect 0.0 12.0
+ EFFECT + CNNEffect 1.0 12.0 layers=3 blend=.5
+
+SEQUENCE 22.0 0 "buggy"
+ EFFECT + HeptagonEffect 0.00 0.20
+ EFFECT + FadeEffect 0.11 1.01
+
+SEQUENCE 22.14 3
+ EFFECT + ThemeModulationEffect 0.00 4.00
+ EFFECT = HeptagonEffect 0.00 4.00
+ EFFECT + GaussianBlurEffect 0.00 5.00 strength=1.5
+ EFFECT + ChromaAberrationEffect 0.00 5.00 offset=0.03 angle=0.785
+ EFFECT + SolarizeEffect 0.00 5.00
+
+SEQUENCE 23.00 2
+ EFFECT - FlashCubeEffect 0.20 1.50
+ EFFECT + HeptagonEffect 0.00 2.00
+ EFFECT + ParticleSprayEffect 0.00 2.00
+ EFFECT + ParticlesEffect 0.00 2.00
+
+SEQUENCE 22.75 2 "Fade"
+ EFFECT - FlashCubeEffect 0.20 1.50
+ EFFECT + FlashEffect 0.00 1.00
+
+SEQUENCE 23.88 10
+ EFFECT - FlashCubeEffect 0.20 1.50
+ EFFECT + GaussianBlurEffect 0.00 2.00
+ EFFECT + FlashEffect 0.00 0.20
+ EFFECT = FlashEffect 0.50 0.20
+
+SEQUENCE 25.59 1
+ EFFECT + ThemeModulationEffect 0.00 8.00
+ EFFECT + HeptagonEffect 0.20 2.00
+ EFFECT + ParticleSprayEffect 0.00 8.00
+ EFFECT + Hybrid3DEffect 0.00 8.06
+ EFFECT + GaussianBlurEffect 0.00 8.00
+ EFFECT + ChromaAberrationEffect 0.00 8.14
+ EFFECT + SolarizeEffect 0.00 7.88
+
+SEQUENCE 33.08 0
+ EFFECT + ThemeModulationEffect 0.00 3.00
+ EFFECT + VignetteEffect 0.00 3.00 radius=0.6 softness=0.3
+ EFFECT + SolarizeEffect 0.00 3.00
+
+SEQUENCE 35.31 0
+ EFFECT + ThemeModulationEffect 0.00 4.00
+ EFFECT + HeptagonEffect 0.20 2.00
+ EFFECT + GaussianBlurEffect 0.00 8.00
+ EFFECT + SolarizeEffect 0.00 2.00
+
+SEQUENCE 42.29 0
+ EFFECT + ThemeModulationEffect 0.00 6.00
+ EFFECT = HeptagonEffect 0.20 2.00
+ EFFECT + Hybrid3DEffect 0.00 4.00
+ EFFECT + ParticleSprayEffect 0.00 5.50
+ EFFECT + HeptagonEffect 0.00 8.00
+ EFFECT + ChromaAberrationEffect 0.00 7.50
+ EFFECT + GaussianBlurEffect 0.00 8.00
+
+SEQUENCE 50.02 0
+ EFFECT + ThemeModulationEffect 0.00 4.00
+ EFFECT + HeptagonEffect 0.00 9.50
+ EFFECT + ChromaAberrationEffect 0.00 9.00
+ EFFECT + GaussianBlurEffect 0.00 8.00
+
diff --git a/workspaces/test/shaders/common_uniforms.wgsl b/workspaces/test/shaders/common_uniforms.wgsl
index ce1be53..1ab8939 100644
--- a/workspaces/test/shaders/common_uniforms.wgsl
+++ b/workspaces/test/shaders/common_uniforms.wgsl
@@ -1,11 +1,11 @@
struct CommonUniforms {
- resolution: vec2<f32>,
- _pad0: f32,
- _pad1: f32,
- aspect_ratio: f32,
- time: f32,
- beat: f32,
- audio_intensity: f32,
+ resolution: vec2<f32>, // Screen dimensions
+ aspect_ratio: f32, // Width/height ratio
+ time: f32, // Physical time in seconds (unaffected by tempo)
+ beat_time: f32, // Musical time in beats (absolute, tempo-scaled)
+ beat_phase: f32, // Fractional beat (0.0-1.0 within current beat)
+ audio_intensity: f32, // Audio peak for beat sync
+ _pad: f32, // Padding
};
struct GlobalUniforms {
view_proj: mat4x4<f32>,
diff --git a/workspaces/test/timeline.seq b/workspaces/test/timeline.seq
index 100c7da..3cbfb93 100644
--- a/workspaces/test/timeline.seq
+++ b/workspaces/test/timeline.seq
@@ -2,7 +2,7 @@
# Minimal timeline for audio/visual sync testing
# BPM 120 (set in test_demo.track)
-SEQUENCE 0.0 0 "Main Loop"
- EFFECT + FlashEffect 0.0 16.0
+SEQUENCE 0.0s 0 "Main Loop"
+EFFECT + FlashEffect 0.0s 16.0s
END_DEMO 32b
diff --git a/workspaces/test/timeline.seq.backup b/workspaces/test/timeline.seq.backup
new file mode 100644
index 0000000..100c7da
--- /dev/null
+++ b/workspaces/test/timeline.seq.backup
@@ -0,0 +1,8 @@
+# WORKSPACE: test
+# Minimal timeline for audio/visual sync testing
+# BPM 120 (set in test_demo.track)
+
+SEQUENCE 0.0 0 "Main Loop"
+ EFFECT + FlashEffect 0.0 16.0
+
+END_DEMO 32b