summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore7
-rw-r--r--CLAUDE.md2
-rw-r--r--GEMINI.md55
-rw-r--r--PROJECT_CONTEXT.md4
-rw-r--r--TODO.md2
-rw-r--r--cmake/DemoCodegen.cmake150
-rw-r--r--cmake/DemoCommon.cmake31
-rw-r--r--cmake/DemoLibraries.cmake8
-rw-r--r--cmake/DemoSourceLists.cmake51
-rw-r--r--cmake/DemoTests.cmake80
-rw-r--r--cnn_v1/README.md64
-rw-r--r--cnn_v1/docs/CNN.md (renamed from doc/CNN.md)2
-rw-r--r--cnn_v1/docs/CNN_BIAS_FIX_2026-02.md (renamed from doc/CNN_BIAS_FIX_2026-02.md)0
-rw-r--r--cnn_v1/docs/CNN_DEBUG.md (renamed from doc/CNN_DEBUG.md)0
-rw-r--r--cnn_v1/docs/CNN_FLATTEN_ANALYSIS.md (renamed from doc/CNN_FLATTEN_ANALYSIS.md)6
-rw-r--r--cnn_v1/docs/CNN_RGBD_GRAYSCALE_SUMMARY.md (renamed from doc/CNN_RGBD_GRAYSCALE_SUMMARY.md)0
-rw-r--r--cnn_v1/docs/CNN_TEST_TOOL.md (renamed from doc/CNN_TEST_TOOL.md)0
-rw-r--r--cnn_v1/docs/CNN_V1_EFFECT.md (renamed from doc/CNN_EFFECT.md)0
-rw-r--r--cnn_v1/shaders/cnn_activation.wgsl (renamed from workspaces/main/shaders/cnn/cnn_activation.wgsl)0
-rw-r--r--cnn_v1/shaders/cnn_conv1x1.wgsl (renamed from workspaces/main/shaders/cnn/cnn_conv1x1.wgsl)0
-rw-r--r--cnn_v1/shaders/cnn_conv3x3.wgsl (renamed from workspaces/main/shaders/cnn/cnn_conv3x3.wgsl)0
-rw-r--r--cnn_v1/shaders/cnn_conv5x5.wgsl (renamed from workspaces/main/shaders/cnn/cnn_conv5x5.wgsl)0
-rw-r--r--cnn_v1/shaders/cnn_conv7x7.wgsl (renamed from workspaces/main/shaders/cnn/cnn_conv7x7.wgsl)0
-rw-r--r--cnn_v1/shaders/cnn_layer.wgsl (renamed from workspaces/main/shaders/cnn/cnn_layer.wgsl)0
-rw-r--r--cnn_v1/shaders/cnn_weights_generated.wgsl (renamed from workspaces/main/shaders/cnn/cnn_weights_generated.wgsl)0
-rw-r--r--cnn_v1/src/cnn_v1_effect.cc (renamed from src/effects/cnn_effect.cc)18
-rw-r--r--cnn_v1/src/cnn_v1_effect.h (renamed from src/effects/cnn_effect.h)14
-rwxr-xr-xcnn_v1/training/train_cnn.py (renamed from training/train_cnn.py)0
-rw-r--r--cnn_v2/README.md60
-rw-r--r--cnn_v2/docs/CNN_V2.md (renamed from doc/CNN_V2.md)0
-rw-r--r--cnn_v2/docs/CNN_V2_BINARY_FORMAT.md (renamed from doc/CNN_V2_BINARY_FORMAT.md)0
-rw-r--r--cnn_v2/docs/CNN_V2_DEBUG_TOOLS.md (renamed from doc/CNN_V2_DEBUG_TOOLS.md)0
-rw-r--r--cnn_v2/docs/CNN_V2_WEB_TOOL.md (renamed from doc/CNN_V2_WEB_TOOL.md)0
-rwxr-xr-xcnn_v2/scripts/train_cnn_v2_full.sh (renamed from scripts/train_cnn_v2_full.sh)10
-rw-r--r--cnn_v2/shaders/cnn_v2_compute.wgsl (renamed from workspaces/main/shaders/cnn_v2/cnn_v2_compute.wgsl)0
-rw-r--r--cnn_v2/shaders/cnn_v2_layer_0.wgsl (renamed from workspaces/main/shaders/cnn_v2/cnn_v2_layer_0.wgsl)0
-rw-r--r--cnn_v2/shaders/cnn_v2_layer_1.wgsl (renamed from workspaces/main/shaders/cnn_v2/cnn_v2_layer_1.wgsl)0
-rw-r--r--cnn_v2/shaders/cnn_v2_layer_2.wgsl (renamed from workspaces/main/shaders/cnn_v2/cnn_v2_layer_2.wgsl)0
-rw-r--r--cnn_v2/shaders/cnn_v2_layer_template.wgsl (renamed from workspaces/main/shaders/cnn_v2/cnn_v2_layer_template.wgsl)0
-rw-r--r--cnn_v2/shaders/cnn_v2_static.wgsl (renamed from workspaces/main/shaders/cnn_v2/cnn_v2_static.wgsl)0
-rw-r--r--cnn_v2/src/cnn_v2_effect.cc (renamed from src/effects/cnn_v2_effect.cc)2
-rw-r--r--cnn_v2/src/cnn_v2_effect.h (renamed from src/effects/cnn_v2_effect.h)0
-rw-r--r--cnn_v2/tools/cnn_v2_test/README.md (renamed from tools/cnn_v2_test/README.md)0
-rw-r--r--cnn_v2/tools/cnn_v2_test/index.html (renamed from tools/cnn_v2_test/index.html)39
-rwxr-xr-xcnn_v2/training/export_cnn_v2_shader.py (renamed from training/export_cnn_v2_shader.py)6
-rwxr-xr-xcnn_v2/training/export_cnn_v2_weights.py (renamed from training/export_cnn_v2_weights.py)8
-rwxr-xr-xcnn_v2/training/gen_identity_weights.py (renamed from training/gen_identity_weights.py)6
-rwxr-xr-xcnn_v2/training/train_cnn_v2.py (renamed from training/train_cnn_v2.py)0
-rw-r--r--cnn_v3/README.md36
-rw-r--r--cnn_v3/training/input/photo1.jpgbin0 -> 5813575 bytes
-rw-r--r--cnn_v3/training/input/photo2.jpgbin0 -> 3328126 bytes
-rw-r--r--cnn_v3/training/input/photo3.jpgbin0 -> 723541 bytes
-rw-r--r--cnn_v3/training/input/photo4.jpgbin0 -> 286046 bytes
-rw-r--r--cnn_v3/training/target_1/photo1_out.pngbin0 -> 4038843 bytes
-rw-r--r--cnn_v3/training/target_1/photo2_1_out.pngbin0 -> 2875939 bytes
-rw-r--r--cnn_v3/training/target_1/photo2_2_out.pngbin0 -> 2865857 bytes
-rw-r--r--cnn_v3/training/target_1/photo4_out.pngbin0 -> 718912 bytes
-rw-r--r--doc/AUDIO_WAV_DRIFT_BUG.md17
-rw-r--r--doc/AUXILIARY_TEXTURE_INIT.md2
-rw-r--r--doc/BUILD.md8
-rw-r--r--doc/CMAKE_MODULES.md22
-rw-r--r--doc/COMPLETED.md6
-rw-r--r--doc/HOWTO.md44
-rw-r--r--src/app/main.cc43
-rw-r--r--src/app/test_demo.cc4
-rw-r--r--src/audio/audio.cc34
-rw-r--r--src/audio/tracker.cc19
-rw-r--r--src/effects/flash_cube_effect.cc6
-rw-r--r--src/effects/gaussian_blur_effect.h4
-rw-r--r--src/effects/particle_spray_effect.h2
-rw-r--r--src/effects/particles_effect.h2
-rw-r--r--src/effects/sdf_test_effect.cc4
-rw-r--r--src/gpu/demo_effects.h8
-rw-r--r--src/gpu/gpu.cc1
-rw-r--r--src/tests/audio/test_audio_engine.cc8
-rw-r--r--src/tests/gpu/test_demo_effects.cc4
-rw-r--r--src/tests/gpu/test_shader_assets.cc4
-rw-r--r--tools/cnn_test.cc2
-rw-r--r--tools/common/style.css117
-rw-r--r--tools/shader_editor/index.html42
-rw-r--r--tools/spectral_editor/index.html1
-rw-r--r--tools/spectral_editor/style.css106
-rw-r--r--tools/timeline_editor/index.html528
-rw-r--r--tools/timeline_editor/timeline-playback.js2
-rw-r--r--tools/timeline_editor/timeline-viewport.js10
-rw-r--r--tools/track_visualizer/index.html21
-rw-r--r--training/README.md2
-rw-r--r--workspaces/main/assets.txt20
-rw-r--r--workspaces/main/timeline.seq137
89 files changed, 1278 insertions, 613 deletions
diff --git a/.gitignore b/.gitignore
index 41d0683..bce098f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -70,3 +70,10 @@ Testing/
training/checkpoints/
checkpoints/
validation_results/
+**/assets_filtered_*.txt
+**/assets_assets_*.txt
+
+# Timeline editor temp files
+test_*.seq
+*_save.seq
+tools/timeline_editor/timeline.seq
diff --git a/CLAUDE.md b/CLAUDE.md
index 2a60521..526e940 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -12,7 +12,7 @@
# TIER 3: DESIGN DOCS (Load On-Demand by Subsystem)
#
# Audio: SPECTRAL_BRUSH_EDITOR.md, TRACKER.md, BEAT_TIMING.md
-# CNN: CNN.md, CNN_EFFECT.md, CNN_V2*.md, CNN_TEST_TOOL.md, CNN_*ANALYSIS.md, CNN_BIAS_FIX*.md
+# CNN: cnn_v1/docs/CNN_V1_EFFECT.md, cnn_v2/docs/CNN_V2*.md
# 3D/Graphics: 3D.md, GPU_PROCEDURAL_PHASE4.md, MASKING_SYSTEM.md, SDF_EFFECT_GUIDE.md
# Scene: SCENE_FORMAT.md, SEQUENCE.md, WORKSPACE_SYSTEM.md
# Build: ASSET_SYSTEM.md, BUILD.md, CMAKE_MODULES.md, SIZE_MEASUREMENT.md
diff --git a/GEMINI.md b/GEMINI.md
index 787ba22..5c47b07 100644
--- a/GEMINI.md
+++ b/GEMINI.md
@@ -84,48 +84,19 @@ IMPORTANT:
- Keep PROJECT_CONTEXT.md focused on current status
- Keep TODO.md focused on active/next tasks only
-# ============================================
-# CURRENT STATE SNAPSHOT (Gemini-Specific)
-# ============================================
-<state_snapshot>
- <overall_goal>
- Produce a cross-platform (Windows, macOS, Linux) 64-kilobyte demoscene production. This is achieved through a C++ codebase utilizing WebGPU for graphics (with a hybrid SDF/rasterization pipeline) and a real-time procedural audio engine for sound, with a heavy focus on size-optimization at all stages.
- </overall_goal>
-
- <active_constraints>
- - All tests passing (36/36 - 100%).
- - Strict 64k final binary size target.
- - Adherence to project coding style and contribution guidelines is mandatory.
- </active_constraints>
-
- <key_knowledge>
- - **Workspace System:** The project is organized into self-contained workspaces (e.g., `workspaces/main`, `workspaces/test`) managed by a `workspace.cfg` file, separating demo-specific content from a `common/` directory that holds shared shaders and resources. Selection is done at build time with `-DDEMO_WORKSPACE=<name>`.
- - **Build & Asset Pipeline:** A modular CMake system orchestrates the build. It uses host-native tools (`asset_packer`, `seq_compiler`, `tracker_compiler`) to parse manifest files (`assets.txt`, `timeline.seq`, `music.track`) and compile assets directly into the binary as C++ data, including procedural asset generation.
- - **Audio Engine:** A real-time, sample-accurate audio engine with a tracker system for sequencing patterns from `.track` files. It features procedural synthesis from spectrograms (FFT-based IDCT), variable tempo that is decoupled from visual timing, and an abstracted backend for testing and offline rendering (`WavDumpBackend`).
- - **Graphics & Rendering:** The renderer uses WebGPU (wgpu-native) and WGSL shaders. It employs a hybrid technique, rasterizing proxy geometry (cubes) and then performing SDF raymarching within the fragment shader. The 3D system supports BVH acceleration, and there's a pipeline for importing OBJ models.
- - **Sequence & Timing:** Visuals are defined in `.seq` files using a beat-based timeline that is compiled into physical seconds. Shaders receive a `CommonPostProcessUniforms` buffer containing both physical time (`time`) for constant-speed animations and musical time (`beat_time`, `beat_phase`) for syncing with the audio playback clock.
- - **CNN Post-Processing:** The project features a sophisticated CNN post-processing pipeline (CNNv2) for visual stylization. This includes a full Python/PyTorch training toolchain, a binary weight format, and a WebGPU-based validation tool. The network uses 7D parametric static features (RGBD, UV, sin, bias) for rich, position-aware effects.
- - **Development Workflow:** There is a strong emphasis on tooling and process, including a visual timeline editor, audio analysis tools, a web-based CNN debugger, strict coding standards enforced by `clang-format`, and a comprehensive pre-commit script (`./scripts/check_all.sh`).
- </key_knowledge>
+# Role: Senior Systems Engineer (C++ Focus)
- <artifact_trail>
- - `GEMINI.md`: This file, synchronized with CLAUDE.md structure
- - `PROJECT_CONTEXT.md`: Current system status
- - `TODO.md`: Active priorities (Task #5 in progress)
- </artifact_trail>
+## Response Style
+- **Extreme Brevity:** Provide direct answers. No "Sure, I can help," "I hope this helps," or "Here is the code."
+- **Code-First:** Lead with the solution or the code block.
+- **Explain on Demand:** Do not explain *how* the code works unless explicitly asked with "Why?" or "Explain."
+- **No Markdown Fluff:** Avoid bolding every other word. Use standard technical formatting.
- <recent_actions>
- - **File Hierarchy Cleanup:** Major reorganization of the project structure, establishing the `workspaces/` and `common/` directories and eliminating ~100 redundant files (especially shaders).
- - **CNNv2 Training Pipeline:** Fixed critical checkpointing bugs and streamlined the output of the training scripts for faster iteration.
- - **Effect Render API Refactor:** Simplified the `Effect::render` API and fixed uniform initialization bugs across 19 effects.
- - **CNN Shader Testing Tool:** Implemented `tools/cnn_test` for offline GPU-accelerated validation of trained CNN models.
- - **Effect Source Hierarchy Cleanup**: Refactored the effects system by splitting `src/gpu/demo_effects.h` into individual header files for each effect, creating 10 new headers, moving class definitions, updating `.cc` files with new includes, fixing missing `#include` statements, creating a common `particle_defs.h`, and cleaning up `demo_effects.h`. Verified by passing all 34 tests. handoff(Gemini):
- </recent_actions>
+## Technical Preferences
+- **C++ Standards:** Default to C++20/C++23 unless specified otherwise.
+- **Style:** Prefer Modern C++ (RAII, templates, constexpr, STL) over C-style patterns.
+- **Nomenclature:** Use standard engineering terminology (e.g., "O(n) complexity," "pointer aliasing," "cache miss") without defining the terms.
- <task_state>
- 1. [IN PROGRESS] Task #5: Spectral Brush Editor (Priority 1)
- 2. [PENDING] Task #18: 3D System Enhancements (Priority 4)
- 3. [RECURRENT] Task #50: WGSL Modularization (Priority 4)
- 4. [PENDING] Tracker Humanization & Sample Offset (Priority 3)
- </task_state>
-</state_snapshot> \ No newline at end of file
+## Interaction Protocol
+- If a query is ambiguous, provide the most likely technical solution rather than asking for clarification.
+- Treat the user as a peer with expert-level knowledge.
diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md
index 63e6f8a..f59eb8b 100644
--- a/PROJECT_CONTEXT.md
+++ b/PROJECT_CONTEXT.md
@@ -57,9 +57,9 @@ See `TODO.md` for current priorities and active tasks.
- `doc/CONTRIBUTING.md` - Development protocols
**Technical Reference:**
-- Core: `ASSET_SYSTEM.md`, `SEQUENCE.md`, `TRACKER.md`, `3D.md`, `CNN_EFFECT.md`, `CNN_V2.md`
+- Core: `ASSET_SYSTEM.md`, `SEQUENCE.md`, `TRACKER.md`, `3D.md`, `cnn_v1/docs/CNN_V1_EFFECT.md`, `cnn_v2/docs/CNN_V2.md`
- Formats: `SCENE_FORMAT.md`, `MASKING_SYSTEM.md`
-- Tools: `BUILD.md`, `WORKSPACE_SYSTEM.md`, `SIZE_MEASUREMENT.md`, `CNN_TEST_TOOL.md`, `tools/timeline_editor/README.md`
+- Tools: `BUILD.md`, `WORKSPACE_SYSTEM.md`, `SIZE_MEASUREMENT.md`, `cnn_v1/docs/CNN_TEST_TOOL.md`, `tools/timeline_editor/README.md`
**History:**
- `doc/COMPLETED.md` - Completed tasks archive
diff --git a/TODO.md b/TODO.md
index 78e13ab..4e67b51 100644
--- a/TODO.md
+++ b/TODO.md
@@ -28,7 +28,7 @@ Self-contained workspaces for parallel demo development.
Enhanced CNN post-processing with multi-dimensional feature inputs.
-**Design:** `doc/CNN_V2.md`
+**Design:** `cnn_v2/docs/CNN_V2.md`
**Status:**
- ✅ Full implementation complete and validated
diff --git a/cmake/DemoCodegen.cmake b/cmake/DemoCodegen.cmake
index 272e424..d27e9eb 100644
--- a/cmake/DemoCodegen.cmake
+++ b/cmake/DemoCodegen.cmake
@@ -53,6 +53,48 @@ function(pack_assets NAME INPUT_TXT HEADER_VAR DATA_CC_VAR TARGET_NAME)
add_custom_target(${TARGET_NAME} DEPENDS ${OUT_H} ${OUT_CC})
endfunction()
+# Pack assets by category (filters by filename pattern)
+function(pack_assets_category NAME PATTERN INPUT_TXT HEADER_VAR DATA_CC_VAR TARGET_NAME)
+ set(OUT_H ${CMAKE_CURRENT_SOURCE_DIR}/src/generated/${NAME}.h)
+ set(OUT_CC ${CMAKE_CURRENT_SOURCE_DIR}/src/generated/${NAME}_data.cc)
+
+ # Get directory of INPUT_TXT for relative path resolution
+ get_filename_component(INPUT_DIR ${INPUT_TXT} DIRECTORY)
+ get_filename_component(INPUT_NAME ${INPUT_TXT} NAME_WE)
+ set(FILTERED_TXT ${INPUT_DIR}/${INPUT_NAME}_${NAME}.txt)
+
+ # Create filtered asset list
+ file(STRINGS ${INPUT_TXT} ALL_LINES)
+ set(FILTERED_LINES "")
+ foreach(LINE ${ALL_LINES})
+ string(STRIP "${LINE}" LINE)
+ if(LINE MATCHES "^#" OR LINE STREQUAL "")
+ list(APPEND FILTERED_LINES "${LINE}")
+ else()
+ string(REGEX REPLACE "^[^,]+,[^,]+,[ ]*([^,]+).*" "\\1" FILENAME "${LINE}")
+ if(FILENAME MATCHES "${PATTERN}")
+ list(APPEND FILTERED_LINES "${LINE}")
+ endif()
+ endif()
+ endforeach()
+ string(REPLACE ";" "\n" FILTERED_CONTENT "${FILTERED_LINES}")
+ file(WRITE ${FILTERED_TXT} "${FILTERED_CONTENT}\n")
+
+ # Parse filtered list for dependencies (now in same directory as INPUT_TXT)
+ parse_asset_list(${FILTERED_TXT} ASSET_FILE_DEPS)
+
+ add_custom_command(
+ OUTPUT ${OUT_H} ${OUT_CC}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_SOURCE_DIR}/src/generated
+ COMMAND ${ASSET_PACKER_CMD} ${FILTERED_TXT} ${OUT_H} ${OUT_CC}
+ DEPENDS ${ASSET_PACKER_DEPENDS} ${INPUT_TXT} ${ASSET_FILE_DEPS}
+ COMMENT "Generating ${NAME} assets..."
+ )
+ set(${HEADER_VAR} ${OUT_H} PARENT_SCOPE)
+ set(${DATA_CC_VAR} ${OUT_CC} PARENT_SCOPE)
+ add_custom_target(${TARGET_NAME} DEPENDS ${OUT_H} ${OUT_CC})
+endfunction()
+
# Pack test assets (output to build/src/generated_test/)
function(pack_test_assets NAME INPUT_TXT HEADER_VAR DATA_CC_VAR TARGET_NAME)
set(OUT_H ${CMAKE_CURRENT_BINARY_DIR}/src/generated_test/${NAME}.h)
@@ -102,8 +144,112 @@ add_custom_command(
)
add_custom_target(generate_tracker_music ALL DEPENDS ${GENERATED_MUSIC_DATA_CC})
-# Asset packing
-pack_assets(assets ${WORKSPACE_ASSETS} GEN_DEMO_H GEN_DEMO_CC generate_demo_assets)
+# Asset packing (split by category for granular rebuild tracking)
+# Generate filtered asset files per category
+get_filename_component(ASSETS_DIR ${WORKSPACE_ASSETS} DIRECTORY)
+set(FILTERED_SHADERS ${ASSETS_DIR}/assets_filtered_shaders.txt)
+set(FILTERED_AUDIO ${ASSETS_DIR}/assets_filtered_audio.txt)
+set(FILTERED_MODELS ${ASSETS_DIR}/assets_filtered_models.txt)
+set(FILTERED_DATA ${ASSETS_DIR}/assets_filtered_data.txt)
+
+# Filter assets by category at configure time
+file(STRINGS ${WORKSPACE_ASSETS} ALL_ASSET_LINES)
+set(SHADERS_LINES "")
+set(AUDIO_LINES "")
+set(MODELS_LINES "")
+set(DATA_LINES "")
+
+foreach(LINE ${ALL_ASSET_LINES})
+ string(STRIP "${LINE}" LINE)
+ if(LINE MATCHES "^#" OR LINE STREQUAL "")
+ list(APPEND SHADERS_LINES "${LINE}")
+ list(APPEND AUDIO_LINES "${LINE}")
+ list(APPEND MODELS_LINES "${LINE}")
+ list(APPEND DATA_LINES "${LINE}")
+ else()
+ string(REGEX REPLACE "^[^,]+,[^,]+,[ ]*([^,]+).*" "\\1" FILENAME "${LINE}")
+ if(FILENAME MATCHES "\\.wgsl$")
+ list(APPEND SHADERS_LINES "${LINE}")
+ elseif(FILENAME MATCHES "\\.(spec|track)$")
+ list(APPEND AUDIO_LINES "${LINE}")
+ elseif(FILENAME MATCHES "\\.obj$")
+ list(APPEND MODELS_LINES "${LINE}")
+ elseif(FILENAME MATCHES "\\.(bin|png)$" OR FILENAME MATCHES "PROC\\(")
+ list(APPEND DATA_LINES "${LINE}")
+ endif()
+ endif()
+endforeach()
+
+string(REPLACE ";" "\n" SHADERS_CONTENT "${SHADERS_LINES}")
+string(REPLACE ";" "\n" AUDIO_CONTENT "${AUDIO_LINES}")
+string(REPLACE ";" "\n" MODELS_CONTENT "${MODELS_LINES}")
+string(REPLACE ";" "\n" DATA_CONTENT "${DATA_LINES}")
+
+file(WRITE ${FILTERED_SHADERS} "${SHADERS_CONTENT}\n")
+file(WRITE ${FILTERED_AUDIO} "${AUDIO_CONTENT}\n")
+file(WRITE ${FILTERED_MODELS} "${MODELS_CONTENT}\n")
+file(WRITE ${FILTERED_DATA} "${DATA_CONTENT}\n")
+
+# Parse dependencies per category
+parse_asset_list(${FILTERED_SHADERS} SHADERS_DEPS)
+parse_asset_list(${FILTERED_AUDIO} AUDIO_DEPS)
+parse_asset_list(${FILTERED_MODELS} MODELS_DEPS)
+parse_asset_list(${FILTERED_DATA} DATA_DEPS)
+
+# Single unified output (avoids duplicate symbols)
+set(GEN_DEMO_H ${CMAKE_CURRENT_SOURCE_DIR}/src/generated/assets.h)
+set(GEN_DEMO_CC ${CMAKE_CURRENT_SOURCE_DIR}/src/generated/assets_data.cc)
+
+# Category-specific targets for granular rebuilds
+add_custom_command(
+ OUTPUT ${GEN_DEMO_H}.shaders_stamp
+ COMMAND ${CMAKE_COMMAND} -E touch ${GEN_DEMO_H}.shaders_stamp
+ DEPENDS ${ASSET_PACKER_DEPENDS} ${WORKSPACE_ASSETS} ${SHADERS_DEPS}
+ COMMENT "Checking shader assets..."
+)
+add_custom_target(generate_demo_shaders DEPENDS ${GEN_DEMO_H}.shaders_stamp)
+
+add_custom_command(
+ OUTPUT ${GEN_DEMO_H}.audio_stamp
+ COMMAND ${CMAKE_COMMAND} -E touch ${GEN_DEMO_H}.audio_stamp
+ DEPENDS ${ASSET_PACKER_DEPENDS} ${WORKSPACE_ASSETS} ${AUDIO_DEPS}
+ COMMENT "Checking audio assets..."
+)
+add_custom_target(generate_demo_audio DEPENDS ${GEN_DEMO_H}.audio_stamp)
+
+add_custom_command(
+ OUTPUT ${GEN_DEMO_H}.models_stamp
+ COMMAND ${CMAKE_COMMAND} -E touch ${GEN_DEMO_H}.models_stamp
+ DEPENDS ${ASSET_PACKER_DEPENDS} ${WORKSPACE_ASSETS} ${MODELS_DEPS}
+ COMMENT "Checking model assets..."
+)
+add_custom_target(generate_demo_models DEPENDS ${GEN_DEMO_H}.models_stamp)
+
+add_custom_command(
+ OUTPUT ${GEN_DEMO_H}.data_stamp
+ COMMAND ${CMAKE_COMMAND} -E touch ${GEN_DEMO_H}.data_stamp
+ DEPENDS ${ASSET_PACKER_DEPENDS} ${WORKSPACE_ASSETS} ${DATA_DEPS}
+ COMMENT "Checking data assets..."
+)
+add_custom_target(generate_demo_data DEPENDS ${GEN_DEMO_H}.data_stamp)
+
+# Unified asset generation (triggered when any category changes)
+add_custom_command(
+ OUTPUT ${GEN_DEMO_H} ${GEN_DEMO_CC}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_SOURCE_DIR}/src/generated
+ COMMAND ${ASSET_PACKER_CMD} ${WORKSPACE_ASSETS} ${GEN_DEMO_H} ${GEN_DEMO_CC}
+ DEPENDS ${ASSET_PACKER_DEPENDS} ${WORKSPACE_ASSETS}
+ ${GEN_DEMO_H}.shaders_stamp
+ ${GEN_DEMO_H}.audio_stamp
+ ${GEN_DEMO_H}.models_stamp
+ ${GEN_DEMO_H}.data_stamp
+ COMMENT "Generating unified assets..."
+)
+
+# Combined target for compatibility
+add_custom_target(generate_demo_assets DEPENDS ${GEN_DEMO_H} ${GEN_DEMO_CC})
+
+# Test assets (unchanged)
pack_test_assets(test_assets ${CMAKE_CURRENT_SOURCE_DIR}/workspaces/test/assets.txt GEN_TEST_H GEN_TEST_CC generate_test_assets)
# Mark generated files so CMake always checks if they need rebuilding
diff --git a/cmake/DemoCommon.cmake b/cmake/DemoCommon.cmake
index 1c63b26..401ea05 100644
--- a/cmake/DemoCommon.cmake
+++ b/cmake/DemoCommon.cmake
@@ -197,3 +197,34 @@ macro(add_demo_test NAME TEST_NAME LABEL)
add_test(NAME ${TEST_NAME} COMMAND ${NAME})
set_tests_properties(${TEST_NAME} PROPERTIES LABELS "${LABEL}")
endmacro()
+
+# =============================================================================
+# demo_add_asset_deps(TARGET CATEGORY)
+# =============================================================================
+# Adds asset category dependencies to a target
+#
+# Arguments:
+# TARGET - CMake target name
+# CATEGORY - Asset category (shaders, audio, models, data, all, test)
+#
+# Usage:
+# demo_add_asset_deps(test_synth audio)
+# demo_add_asset_deps(test_shader_compilation shaders)
+# demo_add_asset_deps(demo64k all)
+function(demo_add_asset_deps TARGET CATEGORY)
+ if(CATEGORY STREQUAL "all")
+ add_dependencies(${TARGET} generate_demo_shaders generate_demo_audio generate_demo_models generate_demo_data)
+ elseif(CATEGORY STREQUAL "test")
+ add_dependencies(${TARGET} generate_test_assets)
+ elseif(CATEGORY STREQUAL "shaders")
+ add_dependencies(${TARGET} generate_demo_shaders)
+ elseif(CATEGORY STREQUAL "audio")
+ add_dependencies(${TARGET} generate_demo_audio)
+ elseif(CATEGORY STREQUAL "models")
+ add_dependencies(${TARGET} generate_demo_models)
+ elseif(CATEGORY STREQUAL "data")
+ add_dependencies(${TARGET} generate_demo_data)
+ else()
+ message(FATAL_ERROR "Unknown asset category: ${CATEGORY}")
+ endif()
+endfunction()
diff --git a/cmake/DemoLibraries.cmake b/cmake/DemoLibraries.cmake
index 3a2207a..f1891fb 100644
--- a/cmake/DemoLibraries.cmake
+++ b/cmake/DemoLibraries.cmake
@@ -3,8 +3,8 @@
# Utility library
add_library(util STATIC ${UTIL_SOURCES})
-add_dependencies(util generate_demo_assets generate_test_assets)
target_include_directories(util PUBLIC ${CORE_INCLUDES})
+add_dependencies(util generate_demo_assets)
# Procedural generation library
add_library(procedural STATIC ${PROCEDURAL_SOURCES})
@@ -12,18 +12,18 @@ target_include_directories(procedural PUBLIC ${CORE_INCLUDES})
# Audio synthesis and processing library
add_library(audio STATIC ${AUDIO_SOURCES})
-add_dependencies(audio generate_demo_assets)
target_include_directories(audio PUBLIC ${CORE_INCLUDES})
+add_dependencies(audio generate_demo_assets)
# 3D rendering library
add_library(3d STATIC ${3D_SOURCES})
-add_dependencies(3d generate_demo_assets)
target_include_directories(3d PUBLIC ${CORE_INCLUDES})
+add_dependencies(3d generate_demo_assets)
# GPU effects library
add_library(gpu STATIC ${GPU_SOURCES})
-add_dependencies(gpu generate_demo_assets)
target_include_directories(gpu PUBLIC ${CORE_INCLUDES})
+add_dependencies(gpu generate_demo_assets)
# Note: Static libraries do not strictly need to link dependencies,
# but if they did, PRIVATE would propagate to the executable.
diff --git a/cmake/DemoSourceLists.cmake b/cmake/DemoSourceLists.cmake
index d4cdd21..2d6cf42 100644
--- a/cmake/DemoSourceLists.cmake
+++ b/cmake/DemoSourceLists.cmake
@@ -26,24 +26,67 @@ set(PROCEDURAL_SOURCES src/procedural/generator.cc)
# Utility sources (unconditional)
set(UTIL_SOURCES src/util/asset_manager.cc src/util/file_watcher.cc)
+# Common effect sources (shared between headless and normal modes)
+set(COMMON_GPU_EFFECTS
+ src/gpu/effect.cc
+ src/effects/heptagon_effect.cc
+ src/effects/particles_effect.cc
+ src/effects/passthrough_effect.cc
+ src/effects/moving_ellipse_effect.cc
+ src/effects/particle_spray_effect.cc
+ src/effects/gaussian_blur_effect.cc
+ src/effects/solarize_effect.cc
+ src/effects/scene1_effect.cc
+ src/effects/chroma_aberration_effect.cc
+ src/effects/vignette_effect.cc
+ cnn_v1/src/cnn_v1_effect.cc
+ cnn_v2/src/cnn_v2_effect.cc
+ src/gpu/post_process_helper.cc
+ src/gpu/shaders.cc
+ src/effects/hybrid_3d_effect.cc
+ src/effects/flash_cube_effect.cc
+ src/effects/theme_modulation_effect.cc
+ src/effects/fade_effect.cc
+ src/effects/flash_effect.cc
+ src/gpu/shader_composer.cc
+ src/effects/circle_mask_effect.cc
+ src/effects/rotating_cube_effect.cc
+ src/gpu/texture_manager.cc
+ src/gpu/texture_readback.cc
+ src/effects/sdf_test_effect.cc
+)
+
# GPU sources (conditional: HEADLESS / STRIP_EXTERNAL / NORMAL)
demo_set_conditional_sources(GPU_SOURCES
# Headless mode: Functional stubs (timeline/audio work)
- "src/gpu/headless_gpu.cc;src/gpu/demo_effects.cc;src/gpu/effect.cc;src/effects/heptagon_effect.cc;src/effects/particles_effect.cc;src/effects/passthrough_effect.cc;src/effects/moving_ellipse_effect.cc;src/effects/particle_spray_effect.cc;src/effects/gaussian_blur_effect.cc;src/effects/solarize_effect.cc;src/effects/scene1_effect.cc;src/effects/chroma_aberration_effect.cc;src/effects/vignette_effect.cc;src/effects/cnn_effect.cc;src/effects/cnn_v2_effect.cc;src/gpu/post_process_helper.cc;src/gpu/shaders.cc;src/effects/hybrid_3d_effect.cc;src/effects/flash_cube_effect.cc;src/effects/theme_modulation_effect.cc;src/effects/fade_effect.cc;src/effects/flash_effect.cc;src/gpu/shader_composer.cc;src/effects/circle_mask_effect.cc;src/effects/rotating_cube_effect.cc;src/gpu/texture_manager.cc;src/gpu/texture_readback.cc;src/effects/sdf_test_effect.cc"
+ "src/gpu/headless_gpu.cc;src/gpu/demo_effects.cc;${COMMON_GPU_EFFECTS}"
# Strip mode: Minimal GPU stubs only
"src/gpu/stub_gpu.cc"
# Normal mode: Full GPU implementation
- "src/gpu/gpu.cc;src/gpu/effect.cc;src/effects/heptagon_effect.cc;src/effects/particles_effect.cc;src/effects/passthrough_effect.cc;src/effects/moving_ellipse_effect.cc;src/effects/particle_spray_effect.cc;src/effects/gaussian_blur_effect.cc;src/effects/solarize_effect.cc;src/effects/scene1_effect.cc;src/effects/chroma_aberration_effect.cc;src/effects/vignette_effect.cc;src/effects/cnn_effect.cc;src/effects/cnn_v2_effect.cc;src/gpu/post_process_helper.cc;src/gpu/shaders.cc;src/effects/hybrid_3d_effect.cc;src/effects/flash_cube_effect.cc;src/effects/theme_modulation_effect.cc;src/effects/fade_effect.cc;src/effects/flash_effect.cc;src/gpu/shader_composer.cc;src/effects/circle_mask_effect.cc;src/effects/rotating_cube_effect.cc;src/gpu/texture_manager.cc;src/gpu/texture_readback.cc;src/effects/sdf_test_effect.cc"
+ "src/gpu/gpu.cc;${COMMON_GPU_EFFECTS}"
+)
+
+# Common 3D sources (shared between headless and normal modes)
+set(COMMON_3D_FILES
+ src/3d/renderer.cc
+ src/3d/renderer_draw.cc
+ src/3d/renderer_pipelines.cc
+ src/3d/renderer_resources.cc
+ src/3d/renderer_helpers.cc
+ src/3d/visual_debug.cc
+ src/3d/bvh.cc
+ src/3d/physics.cc
+ src/3d/scene_loader.cc
)
# 3D sources (conditional: HEADLESS / STRIP_EXTERNAL / NORMAL)
demo_set_conditional_sources(3D_SOURCES
# Headless mode: Full 3D (needed for Hybrid3DEffect)
- "src/3d/renderer.cc;src/3d/renderer_draw.cc;src/3d/renderer_pipelines.cc;src/3d/renderer_resources.cc;src/3d/renderer_helpers.cc;src/3d/visual_debug.cc;src/3d/bvh.cc;src/3d/physics.cc;src/3d/scene_loader.cc"
+ "${COMMON_3D_FILES}"
# Strip mode: Stub 3D (depends on WebGPU)
"src/3d/bvh.cc;src/3d/physics.cc;src/3d/scene_loader.cc"
# Normal mode: Full 3D implementation
- "src/3d/renderer.cc;src/3d/renderer_draw.cc;src/3d/renderer_pipelines.cc;src/3d/renderer_resources.cc;src/3d/renderer_helpers.cc;src/3d/visual_debug.cc;src/3d/bvh.cc;src/3d/physics.cc;src/3d/scene_loader.cc"
+ "${COMMON_3D_FILES}"
)
# Platform sources (conditional: HEADLESS / STRIP_EXTERNAL / NORMAL)
diff --git a/cmake/DemoTests.cmake b/cmake/DemoTests.cmake
index fa64284..b511620 100644
--- a/cmake/DemoTests.cmake
+++ b/cmake/DemoTests.cmake
@@ -3,7 +3,7 @@
add_demo_test(test_window HammingWindowTest audio src/tests/audio/test_window.cc ${GEN_DEMO_CC})
target_link_libraries(test_window PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_window generate_demo_assets)
+demo_add_asset_deps(test_window audio)
add_demo_test(test_maths MathUtilsTest util src/tests/util/test_maths.cc)
@@ -12,71 +12,78 @@ target_link_libraries(test_file_watcher PRIVATE util ${DEMO_LIBS})
add_demo_test(test_synth SynthEngineTest audio src/tests/audio/test_synth.cc ${GEN_DEMO_CC})
target_link_libraries(test_synth PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_synth generate_demo_assets)
+demo_add_asset_deps(test_synth audio)
add_demo_test(test_dct DctTest audio src/tests/audio/test_dct.cc ${GEN_DEMO_CC})
target_link_libraries(test_dct PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_dct generate_demo_assets)
+demo_add_asset_deps(test_dct audio)
add_demo_test(test_fft FftTest audio src/tests/audio/test_fft.cc ${GEN_DEMO_CC})
target_link_libraries(test_fft PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_fft generate_demo_assets)
+demo_add_asset_deps(test_fft audio)
add_demo_test(test_spectral_brush SpectralBrushTest audio src/tests/audio/test_spectral_brush.cc ${GEN_DEMO_CC})
target_link_libraries(test_spectral_brush PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_spectral_brush generate_demo_assets)
+demo_add_asset_deps(test_spectral_brush audio)
add_demo_test(test_audio_gen AudioGenTest audio src/tests/audio/test_audio_gen.cc ${GEN_DEMO_CC})
target_link_libraries(test_audio_gen PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_audio_gen generate_demo_assets)
+demo_add_asset_deps(test_audio_gen audio)
add_demo_test(test_audio_backend AudioBackendTest audio src/tests/audio/test_audio_backend.cc ${GEN_DEMO_CC})
target_link_libraries(test_audio_backend PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_audio_backend generate_demo_assets)
+demo_add_asset_deps(test_audio_backend audio)
add_demo_test(test_silent_backend SilentBackendTest audio src/tests/audio/test_silent_backend.cc src/tests/common/audio_test_fixture.cc ${GEN_DEMO_CC} ${GENERATED_MUSIC_DATA_CC})
target_link_libraries(test_silent_backend PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_silent_backend generate_demo_assets generate_tracker_music)
+demo_add_asset_deps(test_silent_backend audio)
+add_dependencies(test_silent_backend generate_tracker_music)
add_demo_test(test_mock_backend MockAudioBackendTest audio src/tests/audio/test_mock_backend.cc src/audio/backend/mock_audio_backend.cc ${GEN_DEMO_CC})
target_link_libraries(test_mock_backend PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_mock_backend generate_demo_assets)
+demo_add_asset_deps(test_mock_backend audio)
add_demo_test(test_wav_dump WavDumpBackendTest audio src/tests/audio/test_wav_dump.cc src/tests/common/audio_test_fixture.cc src/audio/backend/wav_dump_backend.cc ${GEN_DEMO_CC} ${GENERATED_MUSIC_DATA_CC})
target_link_libraries(test_wav_dump PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_wav_dump generate_demo_assets generate_tracker_music)
+demo_add_asset_deps(test_wav_dump audio)
+add_dependencies(test_wav_dump generate_tracker_music)
add_demo_test(test_jittered_audio JitteredAudioBackendTest audio src/tests/audio/test_jittered_audio.cc src/audio/backend/jittered_audio_backend.cc ${GEN_DEMO_CC} ${GENERATED_MUSIC_DATA_CC})
target_link_libraries(test_jittered_audio PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_jittered_audio generate_demo_assets generate_tracker_music)
+demo_add_asset_deps(test_jittered_audio audio)
+add_dependencies(test_jittered_audio generate_tracker_music)
add_demo_test(test_tracker_timing TrackerTimingTest audio src/tests/audio/test_tracker_timing.cc src/tests/common/audio_test_fixture.cc src/audio/backend/mock_audio_backend.cc ${GEN_DEMO_CC} ${GENERATED_MUSIC_DATA_CC})
target_link_libraries(test_tracker_timing PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_tracker_timing generate_demo_assets generate_tracker_music)
+demo_add_asset_deps(test_tracker_timing audio)
+add_dependencies(test_tracker_timing generate_tracker_music)
add_demo_test(test_variable_tempo VariableTempoTest audio src/tests/audio/test_variable_tempo.cc src/tests/common/audio_test_fixture.cc src/audio/backend/mock_audio_backend.cc ${GEN_DEMO_CC} ${GENERATED_MUSIC_DATA_CC})
target_link_libraries(test_variable_tempo PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_variable_tempo generate_demo_assets generate_tracker_music)
+demo_add_asset_deps(test_variable_tempo audio)
+add_dependencies(test_variable_tempo generate_tracker_music)
add_demo_test(test_tracker TrackerSystemTest audio src/tests/audio/test_tracker.cc src/tests/common/audio_test_fixture.cc ${GEN_DEMO_CC} ${GENERATED_TEST_DEMO_MUSIC_CC})
target_link_libraries(test_tracker PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_tracker generate_demo_assets generate_test_demo_music)
+demo_add_asset_deps(test_tracker audio)
+add_dependencies(test_tracker generate_test_demo_music)
add_demo_test(test_audio_engine AudioEngineTest audio src/tests/audio/test_audio_engine.cc src/tests/common/audio_test_fixture.cc ${GEN_DEMO_CC} ${GENERATED_MUSIC_DATA_CC})
target_link_libraries(test_audio_engine PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_audio_engine generate_demo_assets generate_tracker_music)
+demo_add_asset_deps(test_audio_engine audio)
+add_dependencies(test_audio_engine generate_tracker_music)
add_demo_test(test_shader_assets ShaderAssetValidation gpu src/tests/gpu/test_shader_assets.cc ${GEN_DEMO_CC})
target_link_libraries(test_shader_assets PRIVATE util procedural ${DEMO_LIBS})
-add_dependencies(test_shader_assets generate_demo_assets)
+demo_add_asset_deps(test_shader_assets shaders)
add_demo_test(test_shader_compilation ShaderCompilationTest gpu src/tests/gpu/test_shader_compilation.cc ${PLATFORM_SOURCES} ${GEN_DEMO_CC})
target_link_libraries(test_shader_compilation PRIVATE gpu util procedural ${DEMO_LIBS})
-add_dependencies(test_shader_compilation generate_demo_assets)
+demo_add_asset_deps(test_shader_compilation shaders)
add_demo_test(test_noise_functions NoiseFunctionsTest gpu src/tests/gpu/test_noise_functions.cc ${PLATFORM_SOURCES} ${GEN_DEMO_CC})
target_link_libraries(test_noise_functions PRIVATE gpu util procedural ${DEMO_LIBS})
-add_dependencies(test_noise_functions generate_demo_assets)
+demo_add_asset_deps(test_noise_functions shaders)
add_demo_test(test_uniform_helper UniformHelperTest gpu src/tests/gpu/test_uniform_helper.cc)
target_link_libraries(test_uniform_helper PRIVATE gpu util ${DEMO_LIBS})
@@ -84,18 +91,20 @@ target_link_libraries(test_uniform_helper PRIVATE gpu util ${DEMO_LIBS})
add_demo_executable(test_spectool src/tests/gpu/test_spectool.cc ${PLATFORM_SOURCES} ${GEN_DEMO_CC} ${GENERATED_MUSIC_DATA_CC})
target_compile_definitions(test_spectool PRIVATE DEMO_BUILD_TOOLS)
target_link_libraries(test_spectool PRIVATE audio util procedural ${DEMO_LIBS})
-add_dependencies(test_spectool generate_tracker_music generate_demo_assets)
+demo_add_asset_deps(test_spectool audio)
+add_dependencies(test_spectool generate_tracker_music)
add_demo_test(test_assets AssetManagerTest assets src/tests/assets/test_assets.cc ${GEN_TEST_CC})
target_include_directories(test_assets PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/src/generated_test)
target_compile_definitions(test_assets PRIVATE USE_TEST_ASSETS)
target_link_libraries(test_assets PRIVATE util procedural ${DEMO_LIBS})
-add_dependencies(test_assets generate_test_assets)
+demo_add_asset_deps(test_assets test)
set_source_files_properties(src/tests/assets/test_assets.cc PROPERTIES COMPILE_DEFINITIONS "USE_TEST_ASSETS")
add_demo_test(test_sequence SequenceSystemTest assets src/tests/assets/test_sequence.cc ${GEN_DEMO_CC} ${GENERATED_TIMELINE_CC} ${PLATFORM_SOURCES})
target_link_libraries(test_sequence PRIVATE 3d gpu util procedural ${DEMO_LIBS})
-add_dependencies(test_sequence generate_timeline generate_demo_assets)
+demo_add_asset_deps(test_sequence all)
+add_dependencies(test_sequence generate_timeline)
add_demo_test(test_procedural ProceduralGenTest util src/tests/util/test_procedural.cc)
target_link_libraries(test_procedural PRIVATE procedural ${DEMO_LIBS})
@@ -109,27 +118,30 @@ add_demo_test(test_shader_composer ShaderComposerTest gpu src/tests/gpu/test_sha
target_compile_definitions(test_shader_composer PRIVATE USE_TEST_ASSETS)
target_include_directories(test_shader_composer PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/src/generated_test ${CORE_INCLUDES})
target_link_libraries(test_shader_composer PRIVATE gpu util procedural ${DEMO_LIBS})
-add_dependencies(test_shader_composer generate_test_assets)
+demo_add_asset_deps(test_shader_composer test)
add_demo_executable(test_3d_render src/tests/3d/test_3d_render.cc src/tests/common/test_3d_helpers.cc ${PLATFORM_SOURCES} ${GENERATED_TIMELINE_CC} ${GEN_DEMO_CC} ${GENERATED_MUSIC_DATA_CC})
target_link_libraries(test_3d_render PRIVATE 3d gpu audio procedural util ${DEMO_LIBS})
-add_dependencies(test_3d_render generate_timeline generate_demo_assets generate_tracker_music)
+demo_add_asset_deps(test_3d_render all)
+add_dependencies(test_3d_render generate_timeline generate_tracker_music)
add_demo_executable(test_3d_physics src/tests/3d/test_3d_physics.cc src/tests/common/test_3d_helpers.cc ${PLATFORM_SOURCES} ${GENERATED_TIMELINE_CC} ${GEN_DEMO_CC} ${GENERATED_MUSIC_DATA_CC})
target_link_libraries(test_3d_physics PRIVATE 3d gpu audio procedural util ${DEMO_LIBS})
-add_dependencies(test_3d_physics generate_timeline generate_demo_assets generate_tracker_music)
+demo_add_asset_deps(test_3d_physics all)
+add_dependencies(test_3d_physics generate_timeline generate_tracker_music)
add_demo_executable(test_mesh src/tests/3d/test_mesh.cc src/tests/common/test_3d_helpers.cc ${PLATFORM_SOURCES} ${GENERATED_TIMELINE_CC} ${GEN_DEMO_CC} ${GENERATED_MUSIC_DATA_CC})
target_link_libraries(test_mesh PRIVATE 3d gpu audio procedural util ${DEMO_LIBS})
-add_dependencies(test_mesh generate_timeline generate_demo_assets generate_tracker_music)
+demo_add_asset_deps(test_mesh all)
+add_dependencies(test_mesh generate_timeline generate_tracker_music)
add_demo_executable(test_platform src/tests/util/test_platform.cc ${PLATFORM_SOURCES})
target_link_libraries(test_platform PRIVATE util ${DEMO_LIBS})
add_demo_executable(test_scene_loader src/tests/3d/test_scene_loader.cc ${PLATFORM_SOURCES} ${GEN_DEMO_CC})
target_link_libraries(test_scene_loader PRIVATE 3d util procedural ${DEMO_LIBS})
-add_dependencies(test_scene_loader generate_demo_assets)
+demo_add_asset_deps(test_scene_loader models)
add_test(NAME SceneLoaderTest COMMAND test_scene_loader)
set_tests_properties(SceneLoaderTest PROPERTIES LABELS "3d")
@@ -144,7 +156,8 @@ add_demo_test(test_effect_base EffectBaseTest gpu
${GENERATED_TIMELINE_CC}
${GENERATED_MUSIC_DATA_CC})
target_link_libraries(test_effect_base PRIVATE 3d gpu audio procedural util ${DEMO_LIBS})
-add_dependencies(test_effect_base generate_timeline generate_demo_assets generate_tracker_music)
+demo_add_asset_deps(test_effect_base shaders)
+add_dependencies(test_effect_base generate_timeline generate_tracker_music)
# GPU Effects Test Infrastructure (Phase 2.1: Effect Classes)
add_demo_test(test_demo_effects DemoEffectsTest gpu
@@ -157,7 +170,8 @@ add_demo_test(test_demo_effects DemoEffectsTest gpu
${GENERATED_TIMELINE_CC}
${GENERATED_MUSIC_DATA_CC})
target_link_libraries(test_demo_effects PRIVATE 3d gpu audio procedural util ${DEMO_LIBS})
-add_dependencies(test_demo_effects generate_timeline generate_demo_assets generate_tracker_music)
+demo_add_asset_deps(test_demo_effects shaders)
+add_dependencies(test_demo_effects generate_timeline generate_tracker_music)
# GPU Effects Test Infrastructure (Phase 2.2: Post-Process Utilities)
add_demo_test(test_post_process_helper PostProcessHelperTest gpu
@@ -167,7 +181,7 @@ add_demo_test(test_post_process_helper PostProcessHelperTest gpu
${PLATFORM_SOURCES}
${GEN_DEMO_CC})
target_link_libraries(test_post_process_helper PRIVATE 3d gpu audio procedural util ${DEMO_LIBS})
-add_dependencies(test_post_process_helper generate_demo_assets)
+demo_add_asset_deps(test_post_process_helper shaders)
# TextureManager tests
add_demo_test(test_texture_manager TextureManagerTest gpu
@@ -176,7 +190,7 @@ add_demo_test(test_texture_manager TextureManagerTest gpu
${PLATFORM_SOURCES}
${GEN_DEMO_CC})
target_link_libraries(test_texture_manager PRIVATE 3d gpu audio procedural util ${DEMO_LIBS})
-add_dependencies(test_texture_manager generate_demo_assets)
+demo_add_asset_deps(test_texture_manager shaders)
# GPU Procedural Texture Test
add_demo_test(test_gpu_procedural GpuProceduralTest gpu
@@ -184,7 +198,7 @@ add_demo_test(test_gpu_procedural GpuProceduralTest gpu
${PLATFORM_SOURCES}
${GEN_DEMO_CC})
target_link_libraries(test_gpu_procedural PRIVATE 3d gpu audio procedural util ${DEMO_LIBS})
-add_dependencies(test_gpu_procedural generate_demo_assets)
+demo_add_asset_deps(test_gpu_procedural shaders)
# CNN shader testing tool (only when STRIP_ALL is OFF)
if(NOT DEMO_STRIP_ALL)
@@ -204,7 +218,7 @@ if(NOT DEMO_STRIP_ALL)
target_link_libraries(cnn_test PRIVATE
gpu util procedural ${DEMO_LIBS})
- add_dependencies(cnn_test generate_demo_assets)
+ demo_add_asset_deps(cnn_test shaders)
# Define STB_IMAGE macros
target_compile_definitions(cnn_test PRIVATE
@@ -218,7 +232,7 @@ add_demo_test(test_gpu_composite GpuCompositeTest gpu
${PLATFORM_SOURCES}
${GEN_DEMO_CC})
target_link_libraries(test_gpu_composite PRIVATE 3d gpu audio procedural util ${DEMO_LIBS})
-add_dependencies(test_gpu_composite generate_demo_assets)
+demo_add_asset_deps(test_gpu_composite shaders)
# Subsystem test targets
diff --git a/cnn_v1/README.md b/cnn_v1/README.md
new file mode 100644
index 0000000..052f22a
--- /dev/null
+++ b/cnn_v1/README.md
@@ -0,0 +1,64 @@
+# CNN v1: Original Post-Processing Neural Network
+
+**Architecture:** 3-layer convolution, generated shader weights
+**Status:** Active (used in timeline), legacy architecture
+
+## Overview
+
+Original CNN implementation with per-layer WGSL shaders. Supports multiple kernel sizes (1×1, 3×3, 5×5, 7×7) with generated weight arrays.
+
+**For new work, use CNN v2** (`cnn_v2/`) which provides:
+- Storage buffer architecture (~3.2 KB vs generated WGSL)
+- 7D static features (RGBD + UV + sin + bias)
+- Sigmoid activation with stable training
+- Dynamic layer configuration
+
+## Quick Reference
+
+**Training:**
+```bash
+./cnn_v1/training/train_cnn.py --input training/input --target training/output \
+ --layers 3 --kernel_sizes 3,5,3 --epochs 5000
+```
+
+**Integration:**
+- **C++:** `cnn_v1/src/cnn_effect.{h,cc}`
+- **Assets:** `workspaces/main/assets.txt` (lines 40-46)
+- **Timeline:** `workspaces/main/timeline.seq` (CNNEffect)
+
+## Documentation
+
+- [CNN.md](docs/CNN.md) - Architecture overview
+- [CNN_V1_EFFECT.md](docs/CNN_V1_EFFECT.md) - Implementation details
+- [CNN_TEST_TOOL.md](docs/CNN_TEST_TOOL.md) - Testing guide
+- [CNN_DEBUG.md](docs/CNN_DEBUG.md) - Debugging notes
+
+## Directory Structure
+
+```
+cnn_v1/
+├── README.md # This file
+├── src/
+│ ├── cnn_effect.h # Effect header
+│ └── cnn_effect.cc # Effect implementation
+├── shaders/ # WGSL shaders (7 files)
+├── training/ # Python training script
+└── docs/ # Documentation (7 markdown files)
+```
+
+## Differences from CNN v2
+
+| Feature | CNN v1 | CNN v2 |
+|---------|--------|--------|
+| Architecture | Generated WGSL weights | Storage buffer weights |
+| Input Features | 4D (RGBA/prev layer) | 12D (4D + 8D static) |
+| Activation | ReLU | Sigmoid + ReLU |
+| Size | ~Variable (WGSL gen) | ~3.2 KB (binary) |
+| Training | Full-image | Patch-based (default) |
+| Layer Config | Compile-time | Runtime (dynamic) |
+
+## Migration Notes
+
+CNN v1 remains in the timeline for historical validation. For new effects or experiments, use CNN v2's enhanced feature set and compact binary format.
+
+See `cnn_v2/docs/CNN_V2.md` for CNN v2 architecture details.
diff --git a/doc/CNN.md b/cnn_v1/docs/CNN.md
index 2dc3362..5d9a667 100644
--- a/doc/CNN.md
+++ b/cnn_v1/docs/CNN.md
@@ -8,7 +8,7 @@ Have the input 3d scene be processed by a multi-layer CNN trained on the side.
Input: some rendered scene.
Output: 'stylized' scene with CNN post-processing.
-**See `doc/CNN_EFFECT.md` for implementation details, usage, and API reference.**
+**See `CNN_V1_EFFECT.md` for implementation details, usage, and API reference.**
## Shader implementation
diff --git a/doc/CNN_BIAS_FIX_2026-02.md b/cnn_v1/docs/CNN_BIAS_FIX_2026-02.md
index 26db8eb..26db8eb 100644
--- a/doc/CNN_BIAS_FIX_2026-02.md
+++ b/cnn_v1/docs/CNN_BIAS_FIX_2026-02.md
diff --git a/doc/CNN_DEBUG.md b/cnn_v1/docs/CNN_DEBUG.md
index ba220a0..ba220a0 100644
--- a/doc/CNN_DEBUG.md
+++ b/cnn_v1/docs/CNN_DEBUG.md
diff --git a/doc/CNN_FLATTEN_ANALYSIS.md b/cnn_v1/docs/CNN_FLATTEN_ANALYSIS.md
index bf63c5d..8664157 100644
--- a/doc/CNN_FLATTEN_ANALYSIS.md
+++ b/cnn_v1/docs/CNN_FLATTEN_ANALYSIS.md
@@ -183,7 +183,7 @@ These yield better size/performance than shader architecture changes.
## References
-- `doc/CNN_EFFECT.md` - CNN implementation details
-- `doc/CNN.md` - High-level CNN design
-- `src/effects/cnn_effect.cc` - Current implementation
+- `CNN_V1_EFFECT.md` - CNN implementation details
+- `CNN.md` - High-level CNN design
+- `../src/cnn_effect.cc` - Current implementation
- `workspaces/main/shaders/cnn_*.wgsl` - Shader snippets
diff --git a/doc/CNN_RGBD_GRAYSCALE_SUMMARY.md b/cnn_v1/docs/CNN_RGBD_GRAYSCALE_SUMMARY.md
index 3439f2c..3439f2c 100644
--- a/doc/CNN_RGBD_GRAYSCALE_SUMMARY.md
+++ b/cnn_v1/docs/CNN_RGBD_GRAYSCALE_SUMMARY.md
diff --git a/doc/CNN_TEST_TOOL.md b/cnn_v1/docs/CNN_TEST_TOOL.md
index 4307894..4307894 100644
--- a/doc/CNN_TEST_TOOL.md
+++ b/cnn_v1/docs/CNN_TEST_TOOL.md
diff --git a/doc/CNN_EFFECT.md b/cnn_v1/docs/CNN_V1_EFFECT.md
index 40f095e..40f095e 100644
--- a/doc/CNN_EFFECT.md
+++ b/cnn_v1/docs/CNN_V1_EFFECT.md
diff --git a/workspaces/main/shaders/cnn/cnn_activation.wgsl b/cnn_v1/shaders/cnn_activation.wgsl
index 4fe771e..4fe771e 100644
--- a/workspaces/main/shaders/cnn/cnn_activation.wgsl
+++ b/cnn_v1/shaders/cnn_activation.wgsl
diff --git a/workspaces/main/shaders/cnn/cnn_conv1x1.wgsl b/cnn_v1/shaders/cnn_conv1x1.wgsl
index f77cfa8..f77cfa8 100644
--- a/workspaces/main/shaders/cnn/cnn_conv1x1.wgsl
+++ b/cnn_v1/shaders/cnn_conv1x1.wgsl
diff --git a/workspaces/main/shaders/cnn/cnn_conv3x3.wgsl b/cnn_v1/shaders/cnn_conv3x3.wgsl
index f7d11b1..f7d11b1 100644
--- a/workspaces/main/shaders/cnn/cnn_conv3x3.wgsl
+++ b/cnn_v1/shaders/cnn_conv3x3.wgsl
diff --git a/workspaces/main/shaders/cnn/cnn_conv5x5.wgsl b/cnn_v1/shaders/cnn_conv5x5.wgsl
index 9328d75..9328d75 100644
--- a/workspaces/main/shaders/cnn/cnn_conv5x5.wgsl
+++ b/cnn_v1/shaders/cnn_conv5x5.wgsl
diff --git a/workspaces/main/shaders/cnn/cnn_conv7x7.wgsl b/cnn_v1/shaders/cnn_conv7x7.wgsl
index e68d644..e68d644 100644
--- a/workspaces/main/shaders/cnn/cnn_conv7x7.wgsl
+++ b/cnn_v1/shaders/cnn_conv7x7.wgsl
diff --git a/workspaces/main/shaders/cnn/cnn_layer.wgsl b/cnn_v1/shaders/cnn_layer.wgsl
index cbd1686..cbd1686 100644
--- a/workspaces/main/shaders/cnn/cnn_layer.wgsl
+++ b/cnn_v1/shaders/cnn_layer.wgsl
diff --git a/workspaces/main/shaders/cnn/cnn_weights_generated.wgsl b/cnn_v1/shaders/cnn_weights_generated.wgsl
index 510f86f..510f86f 100644
--- a/workspaces/main/shaders/cnn/cnn_weights_generated.wgsl
+++ b/cnn_v1/shaders/cnn_weights_generated.wgsl
diff --git a/src/effects/cnn_effect.cc b/cnn_v1/src/cnn_v1_effect.cc
index 49c5239..1f44619 100644
--- a/src/effects/cnn_effect.cc
+++ b/cnn_v1/src/cnn_v1_effect.cc
@@ -1,7 +1,7 @@
// CNN post-processing effect implementation
// Neural network-based stylization with modular WGSL
-#include "effects/cnn_effect.h"
+#include "cnn_v1_effect.h"
#include "gpu/bind_group_builder.h"
#include "gpu/effect.h"
#include "gpu/pipeline_builder.h"
@@ -33,7 +33,7 @@ static WGPURenderPipeline create_cnn_pipeline(WGPUDevice device,
return pipeline;
}
-CNNEffect::CNNEffect(const GpuContext& ctx)
+CNNv1Effect::CNNv1Effect(const GpuContext& ctx)
: PostProcessEffect(ctx), layer_index_(0), total_layers_(1),
blend_amount_(1.0f), input_view_(nullptr), original_view_(nullptr),
bind_group_(nullptr) {
@@ -41,7 +41,7 @@ CNNEffect::CNNEffect(const GpuContext& ctx)
create_cnn_pipeline(ctx_.device, ctx_.format, cnn_layer_shader_wgsl);
}
-CNNEffect::CNNEffect(const GpuContext& ctx, const CNNEffectParams& params)
+CNNv1Effect::CNNv1Effect(const GpuContext& ctx, const CNNv1EffectParams& params)
: PostProcessEffect(ctx), layer_index_(params.layer_index),
total_layers_(params.total_layers), blend_amount_(params.blend_amount),
input_view_(nullptr), original_view_(nullptr), bind_group_(nullptr) {
@@ -49,7 +49,7 @@ CNNEffect::CNNEffect(const GpuContext& ctx, const CNNEffectParams& params)
create_cnn_pipeline(ctx_.device, ctx_.format, cnn_layer_shader_wgsl);
}
-void CNNEffect::init(MainSequence* demo) {
+void CNNv1Effect::init(MainSequence* demo) {
PostProcessEffect::init(demo);
demo_ = demo;
params_buffer_.init(ctx_.device);
@@ -62,11 +62,11 @@ void CNNEffect::init(MainSequence* demo) {
// Initialize uniforms BEFORE any bind group creation
uniforms_.update(ctx_.queue, get_common_uniforms());
- CNNLayerParams params = {layer_index_, blend_amount_, {0.0f, 0.0f}};
+ CNNv1LayerParams params = {layer_index_, blend_amount_, {0.0f, 0.0f}};
params_buffer_.update(ctx_.queue, params);
}
-void CNNEffect::resize(int width, int height) {
+void CNNv1Effect::resize(int width, int height) {
if (width == width_ && height == height_)
return;
@@ -78,7 +78,7 @@ void CNNEffect::resize(int width, int height) {
}
}
-void CNNEffect::render(WGPURenderPassEncoder pass,
+void CNNv1Effect::render(WGPURenderPassEncoder pass,
const CommonPostProcessUniforms& uniforms) {
if (!bind_group_) {
fprintf(stderr, "CNN render: no bind_group\n");
@@ -90,7 +90,7 @@ void CNNEffect::render(WGPURenderPassEncoder pass,
effective_blend = blend_amount_ * uniforms.beat_phase * beat_scale_;
}
- CNNLayerParams params = {layer_index_, effective_blend, {0.0f, 0.0f}};
+ CNNv1LayerParams params = {layer_index_, effective_blend, {0.0f, 0.0f}};
params_buffer_.update(ctx_.queue, params);
wgpuRenderPassEncoderSetPipeline(pass, pipeline_);
@@ -98,7 +98,7 @@ void CNNEffect::render(WGPURenderPassEncoder pass,
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
}
-void CNNEffect::update_bind_group(WGPUTextureView input_view) {
+void CNNv1Effect::update_bind_group(WGPUTextureView input_view) {
input_view_ = input_view;
// Update common uniforms (CRITICAL for UV calculation!)
diff --git a/src/effects/cnn_effect.h b/cnn_v1/src/cnn_v1_effect.h
index cdcd656..e820275 100644
--- a/src/effects/cnn_effect.h
+++ b/cnn_v1/src/cnn_v1_effect.h
@@ -5,23 +5,23 @@
#include "gpu/effect.h"
#include "gpu/uniform_helper.h"
-struct CNNLayerParams {
+struct CNNv1LayerParams {
int layer_index;
float blend_amount; // Blend: mix(input, output, blend_amount)
float _pad[2];
};
-static_assert(sizeof(CNNLayerParams) == 16);
+static_assert(sizeof(CNNv1LayerParams) == 16);
-struct CNNEffectParams {
+struct CNNv1EffectParams {
int layer_index = 0; // Which layer to render (0-based)
int total_layers = 1; // Total number of layers in the CNN
float blend_amount = 1.0f; // Final blend with original input
};
-class CNNEffect : public PostProcessEffect {
+class CNNv1Effect : public PostProcessEffect {
public:
- explicit CNNEffect(const GpuContext& ctx);
- explicit CNNEffect(const GpuContext& ctx, const CNNEffectParams& params);
+ explicit CNNv1Effect(const GpuContext& ctx);
+ explicit CNNv1Effect(const GpuContext& ctx, const CNNv1EffectParams& params);
void init(MainSequence* demo) override;
void resize(int width, int height) override;
@@ -47,7 +47,7 @@ class CNNEffect : public PostProcessEffect {
float beat_scale_ = 1.0f;
WGPUTextureView input_view_;
WGPUTextureView original_view_;
- UniformBuffer<CNNLayerParams> params_buffer_;
+ UniformBuffer<CNNv1LayerParams> params_buffer_;
WGPUBindGroup bind_group_;
MainSequence* demo_ = nullptr;
};
diff --git a/training/train_cnn.py b/cnn_v1/training/train_cnn.py
index 4171dcb..4171dcb 100755
--- a/training/train_cnn.py
+++ b/cnn_v1/training/train_cnn.py
diff --git a/cnn_v2/README.md b/cnn_v2/README.md
new file mode 100644
index 0000000..ef0cf44
--- /dev/null
+++ b/cnn_v2/README.md
@@ -0,0 +1,60 @@
+# CNN v2: Parametric Post-Processing Neural Network
+
+**Architecture:** 3-layer compute, storage buffer (~3.2 KB)
+**Features:** 7D static (RGBD + UV + sin + bias), sigmoid activation
+
+## Quick Start
+
+```bash
+./cnn_v2/scripts/train_cnn_v2_full.sh
+```
+
+## Documentation
+
+- [CNN_V2.md](docs/CNN_V2.md) - Architecture and implementation details
+- [CNN_V2_BINARY_FORMAT.md](docs/CNN_V2_BINARY_FORMAT.md) - Weight format specification
+- [CNN_V2_WEB_TOOL.md](docs/CNN_V2_WEB_TOOL.md) - Validation tool documentation
+- [CNN_V2_DEBUG_TOOLS.md](docs/CNN_V2_DEBUG_TOOLS.md) - Debugging and analysis tools
+
+## Integration
+
+- **C++:** `cnn_v2/src/cnn_v2_effect.{h,cc}`
+- **Assets:** `workspaces/main/assets.txt` (lines 47-49)
+- **Test:** `src/tests/gpu/test_demo_effects.cc` (line 93)
+
+## Directory Structure
+
+```
+cnn_v2/
+├── README.md # This file
+├── src/
+│ ├── cnn_v2_effect.h # Effect header
+│ └── cnn_v2_effect.cc # Effect implementation
+├── shaders/ # WGSL shaders (6 files)
+├── weights/ # Binary weights (3 files)
+├── training/ # Python training scripts (4 files)
+├── scripts/ # Shell scripts (train_cnn_v2_full.sh)
+├── tools/ # Validation tools (HTML)
+└── docs/ # Documentation (4 markdown files)
+```
+
+## Training Pipeline
+
+1. **Train model:** `./cnn_v2/scripts/train_cnn_v2_full.sh`
+2. **Export weights:** Automatic (binary format, ~3.2 KB)
+3. **Validate:** HTML tool at `cnn_v2/tools/cnn_v2_test/index.html`
+
+For detailed training options: `./cnn_v2/scripts/train_cnn_v2_full.sh --help`
+
+## Key Features
+
+- **Parametric static features:** 7D input (RGBD + UV + sin encoding + bias)
+- **Storage buffer architecture:** Dynamic layer count, compact binary format
+- **Sigmoid activation:** Smooth gradients, prevents training collapse
+- **Patch-based training:** Sample-efficient, focuses on salient regions
+- **Sub-10KB target:** Achieved with 3-layer model (~3.2 KB)
+
+## Next Steps
+
+- **8-bit quantization:** 2× size reduction (~1.6 KB) via quantization-aware training (QAT)
+- **CNN v3:** U-Net architecture for enhanced quality (separate directory)
diff --git a/doc/CNN_V2.md b/cnn_v2/docs/CNN_V2.md
index b7fd6f8..b7fd6f8 100644
--- a/doc/CNN_V2.md
+++ b/cnn_v2/docs/CNN_V2.md
diff --git a/doc/CNN_V2_BINARY_FORMAT.md b/cnn_v2/docs/CNN_V2_BINARY_FORMAT.md
index 59c859d..59c859d 100644
--- a/doc/CNN_V2_BINARY_FORMAT.md
+++ b/cnn_v2/docs/CNN_V2_BINARY_FORMAT.md
diff --git a/doc/CNN_V2_DEBUG_TOOLS.md b/cnn_v2/docs/CNN_V2_DEBUG_TOOLS.md
index 8d1289a..8d1289a 100644
--- a/doc/CNN_V2_DEBUG_TOOLS.md
+++ b/cnn_v2/docs/CNN_V2_DEBUG_TOOLS.md
diff --git a/doc/CNN_V2_WEB_TOOL.md b/cnn_v2/docs/CNN_V2_WEB_TOOL.md
index b6f5b0b..b6f5b0b 100644
--- a/doc/CNN_V2_WEB_TOOL.md
+++ b/cnn_v2/docs/CNN_V2_WEB_TOOL.md
diff --git a/scripts/train_cnn_v2_full.sh b/cnn_v2/scripts/train_cnn_v2_full.sh
index 078ea28..a21c1ac 100755
--- a/scripts/train_cnn_v2_full.sh
+++ b/cnn_v2/scripts/train_cnn_v2_full.sh
@@ -53,7 +53,7 @@ cd "$PROJECT_ROOT"
# Helper functions
export_weights() {
- python3 training/export_cnn_v2_weights.py "$1" --output-weights "$2" --quiet
+ python3 "$SCRIPT_DIR/../training/export_cnn_v2_weights.py" "$1" --output-weights "$2" --quiet
}
find_latest_checkpoint() {
@@ -64,6 +64,10 @@ build_target() {
cmake --build build -j4 --target "$1" > /dev/null 2>&1
}
+# Path resolution for running from any directory
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
+
# Default configuration
INPUT_DIR="training/input"
TARGET_DIR="training/target_1"
@@ -82,7 +86,7 @@ MIP_LEVEL=0
GRAYSCALE_LOSS=false
FULL_IMAGE_MODE=false
IMAGE_SIZE=256
-OUTPUT_WEIGHTS="workspaces/main/weights/cnn_v2_weights.bin"
+OUTPUT_WEIGHTS="${PROJECT_ROOT}/workspaces/main/weights/cnn_v2_weights.bin"
# Parse arguments
VALIDATE_ONLY=false
@@ -306,7 +310,7 @@ if [ "$VALIDATE_ONLY" = false ]; then
# Step 1: Train model
echo "[1/4] Training CNN v2 model..."
-python3 training/train_cnn_v2.py \
+python3 "$SCRIPT_DIR/../training/train_cnn_v2.py" \
--input "$INPUT_DIR" \
--target "$TARGET_DIR" \
$TRAINING_MODE_ARGS \
diff --git a/workspaces/main/shaders/cnn_v2/cnn_v2_compute.wgsl b/cnn_v2/shaders/cnn_v2_compute.wgsl
index cdbfd74..cdbfd74 100644
--- a/workspaces/main/shaders/cnn_v2/cnn_v2_compute.wgsl
+++ b/cnn_v2/shaders/cnn_v2_compute.wgsl
diff --git a/workspaces/main/shaders/cnn_v2/cnn_v2_layer_0.wgsl b/cnn_v2/shaders/cnn_v2_layer_0.wgsl
index 8e14957..8e14957 100644
--- a/workspaces/main/shaders/cnn_v2/cnn_v2_layer_0.wgsl
+++ b/cnn_v2/shaders/cnn_v2_layer_0.wgsl
diff --git a/workspaces/main/shaders/cnn_v2/cnn_v2_layer_1.wgsl b/cnn_v2/shaders/cnn_v2_layer_1.wgsl
index f490d13..f490d13 100644
--- a/workspaces/main/shaders/cnn_v2/cnn_v2_layer_1.wgsl
+++ b/cnn_v2/shaders/cnn_v2_layer_1.wgsl
diff --git a/workspaces/main/shaders/cnn_v2/cnn_v2_layer_2.wgsl b/cnn_v2/shaders/cnn_v2_layer_2.wgsl
index 2f9836a..2f9836a 100644
--- a/workspaces/main/shaders/cnn_v2/cnn_v2_layer_2.wgsl
+++ b/cnn_v2/shaders/cnn_v2_layer_2.wgsl
diff --git a/workspaces/main/shaders/cnn_v2/cnn_v2_layer_template.wgsl b/cnn_v2/shaders/cnn_v2_layer_template.wgsl
index 1bf6819..1bf6819 100644
--- a/workspaces/main/shaders/cnn_v2/cnn_v2_layer_template.wgsl
+++ b/cnn_v2/shaders/cnn_v2_layer_template.wgsl
diff --git a/workspaces/main/shaders/cnn_v2/cnn_v2_static.wgsl b/cnn_v2/shaders/cnn_v2_static.wgsl
index 309e832..309e832 100644
--- a/workspaces/main/shaders/cnn_v2/cnn_v2_static.wgsl
+++ b/cnn_v2/shaders/cnn_v2_static.wgsl
diff --git a/src/effects/cnn_v2_effect.cc b/cnn_v2/src/cnn_v2_effect.cc
index 7127aae..60538d4 100644
--- a/src/effects/cnn_v2_effect.cc
+++ b/cnn_v2/src/cnn_v2_effect.cc
@@ -1,6 +1,6 @@
// CNN v2 Effect Implementation
-#include "effects/cnn_v2_effect.h"
+#include "cnn_v2_effect.h"
#if defined(USE_TEST_ASSETS)
#include "test_assets.h"
diff --git a/src/effects/cnn_v2_effect.h b/cnn_v2/src/cnn_v2_effect.h
index 7960b4f..7960b4f 100644
--- a/src/effects/cnn_v2_effect.h
+++ b/cnn_v2/src/cnn_v2_effect.h
diff --git a/tools/cnn_v2_test/README.md b/cnn_v2/tools/cnn_v2_test/README.md
index d41a00f..d41a00f 100644
--- a/tools/cnn_v2_test/README.md
+++ b/cnn_v2/tools/cnn_v2_test/README.md
diff --git a/tools/cnn_v2_test/index.html b/cnn_v2/tools/cnn_v2_test/index.html
index e226d0c..84702d5 100644
--- a/tools/cnn_v2_test/index.html
+++ b/cnn_v2/tools/cnn_v2_test/index.html
@@ -32,32 +32,21 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CNN v2 Testing Tool</title>
+ <link rel="stylesheet" href="../common/style.css">
<style>
- * { margin: 0; padding: 0; box-sizing: border-box; }
body {
- font-family: 'Courier New', monospace;
- background: #1a1a1a;
- color: #e0e0e0;
display: flex;
flex-direction: column;
height: 100vh;
- overflow: hidden;
}
.header {
- background: #2a2a2a;
padding: 16px;
border-bottom: 1px solid #404040;
- display: flex;
- align-items: center;
gap: 24px;
- flex-wrap: wrap;
}
h1 { font-size: 18px; }
.controls {
- display: flex;
gap: 16px;
- align-items: center;
- flex-wrap: wrap;
}
.control-group {
display: flex;
@@ -66,7 +55,7 @@
}
.control-group label { font-size: 12px; }
input[type="range"] { width: 120px; }
- input[type="number"] { width: 60px; background: #1a1a1a; color: #e0e0e0; border: 1px solid #404040; padding: 4px; }
+ input[type="number"] { width: 60px; padding: 4px; }
.drop-zone {
border: 3px dashed #606060;
padding: 20px;
@@ -80,18 +69,10 @@
color: #4a9eff;
}
button {
- background: #1a1a1a;
- border: 1px solid #404040;
- color: #e0e0e0;
padding: 6px 12px;
font-size: 12px;
- font-family: 'Courier New', monospace;
- cursor: pointer;
- transition: all 0.2s;
- border-radius: 4px;
}
button:hover { border-color: #606060; background: #252525; }
- button:disabled { opacity: 0.3; cursor: not-allowed; }
video { display: none; }
.drop-zone:hover { border-color: #4a9eff; background: #2a3545; }
.drop-zone.active { border-color: #4a9eff; background: #1a2a3a; }
@@ -120,7 +101,6 @@
padding: 24px;
overflow: auto;
position: relative;
- background: #1a1a1a;
}
.video-controls-float {
position: absolute;
@@ -185,7 +165,6 @@
padding: 16px;
}
.panel {
- border: 1px solid #404040;
border-radius: 4px;
overflow: hidden;
}
@@ -228,28 +207,14 @@
margin-bottom: 12px;
}
.layer-buttons button {
- background: #1a1a1a;
- border: 1px solid #404040;
- color: #e0e0e0;
padding: 6px 12px;
font-size: 10px;
- font-family: 'Courier New', monospace;
- cursor: pointer;
- transition: all 0.2s;
- }
- .layer-buttons button:hover {
- border-color: #606060;
- background: #252525;
}
.layer-buttons button.active {
background: #4a9eff;
border-color: #4a9eff;
color: #1a1a1a;
}
- .layer-buttons button:disabled {
- opacity: 0.3;
- cursor: not-allowed;
- }
.layer-buttons button:disabled:hover {
border-color: #404040;
background: #1a1a1a;
diff --git a/training/export_cnn_v2_shader.py b/cnn_v2/training/export_cnn_v2_shader.py
index 1c74ad0..8692a62 100755
--- a/training/export_cnn_v2_shader.py
+++ b/cnn_v2/training/export_cnn_v2_shader.py
@@ -13,6 +13,10 @@ import numpy as np
import torch
from pathlib import Path
+# Path resolution for running from any directory
+SCRIPT_DIR = Path(__file__).parent
+PROJECT_ROOT = SCRIPT_DIR.parent.parent
+
def export_layer_shader(layer_idx, weights, kernel_size, output_dir, mip_level=0, is_output_layer=False):
"""Generate WGSL compute shader for a single CNN layer.
@@ -203,7 +207,7 @@ def export_checkpoint(checkpoint_path, output_dir):
def main():
parser = argparse.ArgumentParser(description='Export CNN v2 checkpoint to WGSL shaders')
parser.add_argument('checkpoint', type=str, help='Path to checkpoint .pth file')
- parser.add_argument('--output-dir', type=str, default='workspaces/main/shaders',
+ parser.add_argument('--output-dir', type=str, default=str(PROJECT_ROOT / 'workspaces/main/shaders'),
help='Output directory for shaders')
args = parser.parse_args()
diff --git a/training/export_cnn_v2_weights.py b/cnn_v2/training/export_cnn_v2_weights.py
index f64bd8d..d66b980 100755
--- a/training/export_cnn_v2_weights.py
+++ b/cnn_v2/training/export_cnn_v2_weights.py
@@ -11,6 +11,10 @@ import torch
import struct
from pathlib import Path
+# Path resolution for running from any directory
+SCRIPT_DIR = Path(__file__).parent
+PROJECT_ROOT = SCRIPT_DIR.parent.parent
+
def export_weights_binary(checkpoint_path, output_path, quiet=False):
"""Export CNN v2 weights to binary format.
@@ -261,9 +265,9 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
def main():
parser = argparse.ArgumentParser(description='Export CNN v2 weights to binary format')
parser.add_argument('checkpoint', type=str, help='Path to checkpoint .pth file')
- parser.add_argument('--output-weights', type=str, default='workspaces/main/weights/cnn_v2_weights.bin',
+ parser.add_argument('--output-weights', type=str, default=str(PROJECT_ROOT / 'workspaces/main/weights/cnn_v2_weights.bin'),
help='Output binary weights file')
- parser.add_argument('--output-shader', type=str, default='workspaces/main/shaders',
+ parser.add_argument('--output-shader', type=str, default=str(PROJECT_ROOT / 'workspaces/main/shaders'),
help='Output directory for shader template')
parser.add_argument('--quiet', action='store_true',
help='Suppress detailed output')
diff --git a/training/gen_identity_weights.py b/cnn_v2/training/gen_identity_weights.py
index 7865d68..08eecc6 100755
--- a/training/gen_identity_weights.py
+++ b/cnn_v2/training/gen_identity_weights.py
@@ -21,6 +21,10 @@ import numpy as np
import struct
from pathlib import Path
+# Path resolution for running from any directory
+SCRIPT_DIR = Path(__file__).parent
+PROJECT_ROOT = SCRIPT_DIR.parent.parent
+
def generate_identity_weights(output_path, kernel_size=1, mip_level=0, mix=False, p47=False):
"""Generate identity weights: output = input (ignores static features).
@@ -149,7 +153,7 @@ def generate_identity_weights(output_path, kernel_size=1, mip_level=0, mix=False
def main():
parser = argparse.ArgumentParser(description='Generate identity CNN v2 weights')
parser.add_argument('output', type=str, nargs='?',
- default='workspaces/main/weights/cnn_v2_identity.bin',
+ default=str(PROJECT_ROOT / 'workspaces/main/weights/cnn_v2_identity.bin'),
help='Output .bin file path')
parser.add_argument('--kernel-size', type=int, default=1,
help='Kernel size (default: 1×1)')
diff --git a/training/train_cnn_v2.py b/cnn_v2/training/train_cnn_v2.py
index 9e5df2f..9e5df2f 100755
--- a/training/train_cnn_v2.py
+++ b/cnn_v2/training/train_cnn_v2.py
diff --git a/cnn_v3/README.md b/cnn_v3/README.md
new file mode 100644
index 0000000..fdbf648
--- /dev/null
+++ b/cnn_v3/README.md
@@ -0,0 +1,36 @@
+# CNN v3
+
+Enhanced CNN post-processing with next-generation features.
+
+## Directory Structure
+
+```
+cnn_v3/
+├── docs/ # Documentation and design notes
+├── scripts/ # Training and build automation scripts
+├── shaders/ # WGSL compute shaders
+├── src/ # C++ implementation
+├── tools/ # Testing and validation tools
+├── training/ # Training pipeline
+│ ├── input/ # Source images for training
+│ ├── target_1/ # Style 1 target images
+│ └── target_2/ # Style 2 target images
+└── weights/ # Trained model weights (binary format)
+```
+
+## Training Data
+
+Training images are tracked in the repository:
+- `training/input/` - Original input images
+- `training/target_1/` - First style transformation targets
+- `training/target_2/` - Second style transformation targets
+
+Multiple target directories allow training different stylistic transformations from the same input set.
+
+Add images directly to these directories and commit them.
+
+## Status
+
+**TODO:** Define CNN v3 architecture and feature set.
+
+See `cnn_v2/` for reference implementation.
diff --git a/cnn_v3/training/input/photo1.jpg b/cnn_v3/training/input/photo1.jpg
new file mode 100644
index 0000000..1c1c2b2
--- /dev/null
+++ b/cnn_v3/training/input/photo1.jpg
Binary files differ
diff --git a/cnn_v3/training/input/photo2.jpg b/cnn_v3/training/input/photo2.jpg
new file mode 100644
index 0000000..a662fa8
--- /dev/null
+++ b/cnn_v3/training/input/photo2.jpg
Binary files differ
diff --git a/cnn_v3/training/input/photo3.jpg b/cnn_v3/training/input/photo3.jpg
new file mode 100644
index 0000000..703645e
--- /dev/null
+++ b/cnn_v3/training/input/photo3.jpg
Binary files differ
diff --git a/cnn_v3/training/input/photo4.jpg b/cnn_v3/training/input/photo4.jpg
new file mode 100644
index 0000000..5035993
--- /dev/null
+++ b/cnn_v3/training/input/photo4.jpg
Binary files differ
diff --git a/cnn_v3/training/target_1/photo1_out.png b/cnn_v3/training/target_1/photo1_out.png
new file mode 100644
index 0000000..36a36a7
--- /dev/null
+++ b/cnn_v3/training/target_1/photo1_out.png
Binary files differ
diff --git a/cnn_v3/training/target_1/photo2_1_out.png b/cnn_v3/training/target_1/photo2_1_out.png
new file mode 100644
index 0000000..5398232
--- /dev/null
+++ b/cnn_v3/training/target_1/photo2_1_out.png
Binary files differ
diff --git a/cnn_v3/training/target_1/photo2_2_out.png b/cnn_v3/training/target_1/photo2_2_out.png
new file mode 100644
index 0000000..b0a76bd
--- /dev/null
+++ b/cnn_v3/training/target_1/photo2_2_out.png
Binary files differ
diff --git a/cnn_v3/training/target_1/photo4_out.png b/cnn_v3/training/target_1/photo4_out.png
new file mode 100644
index 0000000..56f6274
--- /dev/null
+++ b/cnn_v3/training/target_1/photo4_out.png
Binary files differ
diff --git a/doc/AUDIO_WAV_DRIFT_BUG.md b/doc/AUDIO_WAV_DRIFT_BUG.md
index e22f4fa..050dd49 100644
--- a/doc/AUDIO_WAV_DRIFT_BUG.md
+++ b/doc/AUDIO_WAV_DRIFT_BUG.md
@@ -1,7 +1,8 @@
# Audio WAV Drift Bug Investigation
**Date:** 2026-02-15
-**Status:** ROOT CAUSE IDENTIFIED
+**Status:** ACCEPTABLE (to be continued)
+**Current State:** -150ms drift at beat 64b, no glitches
## Problem Statement
@@ -163,8 +164,18 @@ Eliminates cumulative truncation error.
1. ✅ Measure WAV sample positions directly (Python script)
2. ✅ Add render tracking debug output
3. ✅ Confirm over-rendering (366ms per 10s)
-4. ⏳ Implement fix
-5. ⏳ Verify corrected WAV alignment in viewer
+4. ✅ Implement partial fix (bypass ring buffer, direct render)
+5. ⚠️ Current result: -150ms drift at beat 64b (acceptable, needs further work)
+
+## Current Implementation (main.cc:286-308)
+
+**WAV dump now bypasses ring buffer entirely:**
+1. **Frame accumulator**: Calculates exact frames per update (no truncation)
+2. **Direct render**: Calls `synth_render()` directly with exact frame count
+3. **No ring buffer**: Eliminates buffer management complexity
+4. **Result**: No glitches, but -150ms drift remains
+
+**Remaining issue:** Drift persists despite direct rendering. Likely related to tempo scaling or audio engine state management. Acceptable for now.
## Notes
diff --git a/doc/AUXILIARY_TEXTURE_INIT.md b/doc/AUXILIARY_TEXTURE_INIT.md
index 9cac70b..036cbf7 100644
--- a/doc/AUXILIARY_TEXTURE_INIT.md
+++ b/doc/AUXILIARY_TEXTURE_INIT.md
@@ -18,7 +18,7 @@ entry.seq->resize(width, height); // Too late - textures already created
**Affected:**
- CircleMaskEffect (circle_mask texture)
-- CNNEffect (captured_frame texture)
+- CNNv1Effect (captured_frame texture)
- RotatingCubeEffect (consumer, hardcoded resolution in uniforms)
---
diff --git a/doc/BUILD.md b/doc/BUILD.md
index d3434f4..fd0c3d9 100644
--- a/doc/BUILD.md
+++ b/doc/BUILD.md
@@ -95,9 +95,11 @@ Use Xcode Metal debugger for shader performance analysis.
## Build System Internals
**Asset Dependency Tracking:**
-- CMake tracks 42 demo + 17 test assets
-- Editing shaders/audio/sequences auto-triggers rebuild
-- Asset lists parsed to extract individual file dependencies
+- CMake tracks 42 demo + 17 test assets split into 4 categories
+- **Granular rebuilds:** Changing a shader only rebuilds shader-dependent targets
+- **Categories:** `shaders` (.wgsl), `audio` (.spec, .track), `models` (.obj), `data` (.bin, .png, PROC)
+- Asset lists parsed at configure time to extract category-specific file dependencies
+- Unified output (`assets_data.cc`) avoids duplicate symbols while preserving granular tracking
**Header Organization:**
- `asset_manager_dcl.h`: Forward declarations
diff --git a/doc/CMAKE_MODULES.md b/doc/CMAKE_MODULES.md
index 2ea7d00..9f71d91 100644
--- a/doc/CMAKE_MODULES.md
+++ b/doc/CMAKE_MODULES.md
@@ -90,6 +90,18 @@ Creates an executable for the demo (legacy macro).
### `add_demo_test(NAME TEST_NAME LABEL SOURCES...)`
Creates a test executable and registers it with CTest (legacy macro).
+### `demo_add_asset_deps(TARGET CATEGORY)`
+Adds asset category dependencies to a target for granular rebuilds.
+
+**Categories:** `shaders`, `audio`, `models`, `data`, `all`, `test`
+
+**Example:**
+```cmake
+demo_add_asset_deps(test_synth audio) # Only depends on audio assets
+demo_add_asset_deps(test_shader_compilation shaders) # Only depends on shaders
+demo_add_asset_deps(demo64k all) # Depends on all asset categories
+```
+
---
## Conditional Inclusion
@@ -107,12 +119,13 @@ This reduces parse time when building without tests/tools.
## Adding New Components
### New Effect
-- Add sources to `cmake/DemoSourceLists.cmake` (GPU_SOURCES list)
-- No other CMake changes needed
+- Add sources to `cmake/DemoSourceLists.cmake` (`COMMON_GPU_EFFECTS` list)
+- No other CMake changes needed (automatically included in headless and normal modes)
### New Test
-- Add to `cmake/DemoTests.cmake` using `demo_add_test_with_deps()`
-- Use LINK and DEPENDS parameters for libraries/assets
+- Add to `cmake/DemoTests.cmake` using `add_demo_test()`
+- Use `demo_add_asset_deps()` to specify asset category dependencies (e.g., `shaders`, `audio`)
+- This enables granular rebuilds—only changed asset categories trigger test recompilation
### New Library
- Add to `cmake/DemoLibraries.cmake` with appropriate dependencies
@@ -132,6 +145,7 @@ This reduces parse time when building without tests/tools.
4. **Reusability:** Shared macros—eliminate 200+ lines of repetition
5. **Clarity:** Top-level CMakeLists.txt is 54-line roadmap
6. **Scalability:** Easy to add new tests/tools/libraries without bloating main file
+7. **Granular Rebuilds:** Asset categories enable 3-5× faster incremental builds for typical changes
---
diff --git a/doc/COMPLETED.md b/doc/COMPLETED.md
index 55fac50..8d30cca 100644
--- a/doc/COMPLETED.md
+++ b/doc/COMPLETED.md
@@ -67,7 +67,7 @@ Use `read @doc/archive/FILENAME.md` to access archived documents.
- **Changes**:
- Added `get_common_uniforms()` helper to Effect base class
- Refactored all render()/compute() signatures from 5 parameters to single `CommonPostProcessUniforms&`
- - Fixed uninitialized uniforms in CircleMaskEffect and CNNEffect
+ - Fixed uninitialized uniforms in CircleMaskEffect and CNNv1Effect
- Updated 19 effect implementations + headers
- Fixed WGSL syntax error in FlashEffect (u.audio_intensity → audio_intensity)
- **Impact**:
@@ -93,7 +93,7 @@ Use `read @doc/archive/FILENAME.md` to access archived documents.
- All 36 tests pass (100%)
- Processes 64×64 test image successfully
- Ready for ground-truth validation vs Python training script
- - Documented in `doc/CNN_TEST_TOOL.md`
+ - Documented in `cnn_v1/docs/CNN_TEST_TOOL.md`
## Recently Completed (February 10, 2026)
@@ -103,7 +103,7 @@ Use `read @doc/archive/FILENAME.md` to access archived documents.
- Created `BindGroupLayoutBuilder` and `BindGroupBuilder` for declarative bind group creation
- Created `RenderPipelineBuilder` to simplify pipeline setup with ShaderComposer integration
- Created `SamplerCache` singleton to deduplicate sampler instances
- - Refactored `post_process_helper.cc`, `cnn_effect.cc`, `rotating_cube_effect.cc`
+ - Refactored `post_process_helper.cc`, `cnn_v1_effect.cc`, `rotating_cube_effect.cc`
- **Result**:
- Bind group creation: 19 instances reduced from 14→4 lines each
- Pipeline creation: 30-50 lines reduced to 8 lines
diff --git a/doc/HOWTO.md b/doc/HOWTO.md
index 0dc9ec7..4cafaa2 100644
--- a/doc/HOWTO.md
+++ b/doc/HOWTO.md
@@ -100,7 +100,7 @@ make run_util_tests # Utility tests
Extracts patches at salient points, trains on center pixels only (matches WGSL sliding window):
```bash
# Train with 32×32 patches at detected corners/edges
-./training/train_cnn.py \
+./cnn_v1/training/train_cnn.py \
--input training/input/ --target training/output/ \
--patch-size 32 --patches-per-image 64 --detector harris \
--layers 3 --kernel_sizes 3,5,3 --epochs 5000 --batch_size 16 \
@@ -117,7 +117,7 @@ Extracts patches at salient points, trains on center pixels only (matches WGSL s
### Full-Image
Processes entire image with sliding window (matches WGSL):
```bash
-./training/train_cnn.py \
+./cnn_v1/training/train_cnn.py \
--input training/input/ --target training/output/ \
--layers 3 --kernel_sizes 3,5,3 --epochs 10000 --batch_size 8 \
--checkpoint-every 1000
@@ -126,10 +126,10 @@ Processes entire image with sliding window (matches WGSL):
### Export & Validation
```bash
# Generate shaders from checkpoint
-./training/train_cnn.py --export-only checkpoints/checkpoint_epoch_5000.pth
+./cnn_v1/training/train_cnn.py --export-only checkpoints/checkpoint_epoch_5000.pth
# Generate ground truth (sliding window, no tiling)
-./training/train_cnn.py --infer input.png \
+./cnn_v1/training/train_cnn.py --infer input.png \
--export-only checkpoints/checkpoint_epoch_5000.pth \
--output ground_truth.png
```
@@ -145,31 +145,31 @@ Enhanced CNN with parametric static features (7D input: RGBD + UV + sin encoding
**Complete Pipeline** (recommended):
```bash
# Train → Export → Build → Validate (default config)
-./scripts/train_cnn_v2_full.sh
+./cnn_v2/scripts/train_cnn_v2_full.sh
# Rapid debug (1 layer, 3×3, 5 epochs)
-./scripts/train_cnn_v2_full.sh --num-layers 1 --kernel-sizes 3 --epochs 5 --output-weights test.bin
+./cnn_v2/scripts/train_cnn_v2_full.sh --num-layers 1 --kernel-sizes 3 --epochs 5 --output-weights test.bin
# Custom training parameters
-./scripts/train_cnn_v2_full.sh --epochs 500 --batch-size 32 --checkpoint-every 100
+./cnn_v2/scripts/train_cnn_v2_full.sh --epochs 500 --batch-size 32 --checkpoint-every 100
# Custom architecture
-./scripts/train_cnn_v2_full.sh --kernel-sizes 3,5,3 --num-layers 3 --mip-level 1
+./cnn_v2/scripts/train_cnn_v2_full.sh --kernel-sizes 3,5,3 --num-layers 3 --mip-level 1
# Custom output path
-./scripts/train_cnn_v2_full.sh --output-weights workspaces/test/cnn_weights.bin
+./cnn_v2/scripts/train_cnn_v2_full.sh --output-weights workspaces/test/cnn_weights.bin
# Grayscale loss (compute loss on luminance instead of RGBA)
-./scripts/train_cnn_v2_full.sh --grayscale-loss
+./cnn_v2/scripts/train_cnn_v2_full.sh --grayscale-loss
# Custom directories
-./scripts/train_cnn_v2_full.sh --input training/input --target training/target_2
+./cnn_v2/scripts/train_cnn_v2_full.sh --input training/input --target training/target_2
# Full-image mode (instead of patch-based)
-./scripts/train_cnn_v2_full.sh --full-image --image-size 256
+./cnn_v2/scripts/train_cnn_v2_full.sh --full-image --image-size 256
# See all options
-./scripts/train_cnn_v2_full.sh --help
+./cnn_v2/scripts/train_cnn_v2_full.sh --help
```
**Defaults:** 200 epochs, 3×3 kernels, 8→4→4 channels, batch-size 16, patch-based (8×8, harris detector).
@@ -184,33 +184,33 @@ Enhanced CNN with parametric static features (7D input: RGBD + UV + sin encoding
**Validation Only** (skip training):
```bash
# Use latest checkpoint
-./scripts/train_cnn_v2_full.sh --validate
+./cnn_v2/scripts/train_cnn_v2_full.sh --validate
# Use specific checkpoint
-./scripts/train_cnn_v2_full.sh --validate checkpoints/checkpoint_epoch_50.pth
+./cnn_v2/scripts/train_cnn_v2_full.sh --validate checkpoints/checkpoint_epoch_50.pth
```
**Manual Training:**
```bash
# Default config
-./training/train_cnn_v2.py \
+./cnn_v2/training/train_cnn_v2.py \
--input training/input/ --target training/target_2/ \
--epochs 100 --batch-size 16 --checkpoint-every 5
# Custom architecture (per-layer kernel sizes)
-./training/train_cnn_v2.py \
+./cnn_v2/training/train_cnn_v2.py \
--input training/input/ --target training/target_2/ \
--kernel-sizes 1,3,5 \
--epochs 5000 --batch-size 16
# Mip-level for p0-p3 features (0=original, 1=half, 2=quarter, 3=eighth)
-./training/train_cnn_v2.py \
+./cnn_v2/training/train_cnn_v2.py \
--input training/input/ --target training/target_2/ \
--mip-level 1 \
--epochs 100 --batch-size 16
# Grayscale loss (compute loss on luminance Y = 0.299*R + 0.587*G + 0.114*B)
-./training/train_cnn_v2.py \
+./cnn_v2/training/train_cnn_v2.py \
--input training/input/ --target training/target_2/ \
--grayscale-loss \
--epochs 100 --batch-size 16
@@ -236,7 +236,7 @@ Use `--quiet` for streamlined output in scripts (used automatically by train_cnn
```
-**Validation:** Use HTML tool (`tools/cnn_v2_test/index.html`) for CNN v2 validation. See `doc/CNN_V2_WEB_TOOL.md`.
+**Validation:** Use HTML tool (`cnn_v2/tools/cnn_v2_test/index.html`) for CNN v2 validation. See `cnn_v2/docs/CNN_V2_WEB_TOOL.md`.
---
@@ -323,11 +323,11 @@ See `doc/ASSET_SYSTEM.md` and `doc/WORKSPACE_SYSTEM.md`.
**Status:**
- **CNN v2:** ✅ Fully functional, matches CNNv2Effect
-- **CNN v1:** ⚠️ Produces incorrect output, use CNNEffect in demo for validation
+- **CNN v1:** ⚠️ Produces incorrect output, use CNNv1Effect in demo for validation
**Note:** `--weights` loads layer count and kernel sizes from the binary file, overriding `--layers` and forcing CNN v2.
-See `doc/CNN_TEST_TOOL.md` for full documentation.
+See `cnn_v1/docs/CNN_TEST_TOOL.md` for full documentation.
---
diff --git a/src/app/main.cc b/src/app/main.cc
index 537da74..3c80520 100644
--- a/src/app/main.cc
+++ b/src/app/main.cc
@@ -207,7 +207,10 @@ int main(int argc, char** argv) {
#endif /* !defined(STRIP_ALL) */
// Pre-fill ring buffer to target lookahead (prevents startup delay)
- fill_audio_buffer(audio_get_required_prefill_time(), 0.0);
+ // Skip pre-fill in WAV dump mode (direct render, no ring buffer)
+ if (!dump_wav) {
+ fill_audio_buffer(audio_get_required_prefill_time(), 0.0);
+ }
audio_start();
g_last_audio_time = audio_get_playback_time(); // Initialize after start
@@ -268,37 +271,41 @@ int main(int argc, char** argv) {
printf("Running WAV dump simulation (%.1fs - %.1fs)...\n", start_time,
end_time);
- // Seek to start time if needed
+ // Seek to start time if needed (advance state without rendering)
if (start_time > 0.0f) {
const double step = 1.0 / 60.0;
for (double t = 0.0; t < start_time; t += step) {
- fill_audio_buffer(step, t);
- audio_render_silent((float)step);
+ g_audio_engine.update(g_music_time, (float)step * g_tempo_scale);
+ g_music_time += (float)step * g_tempo_scale;
}
printf("Seeked to %.1fs\n", start_time);
}
- const float update_dt = 1.0f / 60.0f; // 60Hz update rate
- const int frames_per_update = (int)(32000 * update_dt); // ~533 frames
- const int samples_per_update = frames_per_update * 2; // Stereo
+ const float update_dt = 1.0f / 60.0f; // 60Hz update rate
+ const int sample_rate = 32000;
- AudioRingBuffer* ring_buffer = audio_get_ring_buffer();
- std::vector<float> chunk_buffer(samples_per_update);
+ std::vector<float> chunk_buffer(2048); // Max samples for one update
double physical_time = start_time;
+ double frame_accumulator = 0.0;
while (physical_time < end_time) {
- // Update music time and tracker (using tempo logic from
- // fill_audio_buffer)
- fill_audio_buffer(update_dt, physical_time);
+ // Calculate exact frames for this update
+ frame_accumulator += sample_rate * update_dt;
+ const int frames_this_update = (int)frame_accumulator;
+ frame_accumulator -= frames_this_update;
+ const int samples_this_update = frames_this_update * 2;
- // Read rendered audio from ring buffer
- if (ring_buffer != nullptr) {
- ring_buffer->read(chunk_buffer.data(), samples_per_update);
- }
+ // Update tracker/audio state
+ g_audio_engine.update(g_music_time, update_dt * g_tempo_scale);
- // Write to WAV file
- wav_backend.write_audio(chunk_buffer.data(), samples_per_update);
+ // Render directly to buffer (bypass ring buffer)
+ if (frames_this_update > 0) {
+ synth_render(chunk_buffer.data(), frames_this_update);
+ wav_backend.write_audio(chunk_buffer.data(), samples_this_update);
+ }
+ // Advance music time
+ g_music_time += update_dt * g_tempo_scale;
physical_time += update_dt;
// Progress indicator every second
diff --git a/src/app/test_demo.cc b/src/app/test_demo.cc
index 5775e74..ff2c105 100644
--- a/src/app/test_demo.cc
+++ b/src/app/test_demo.cc
@@ -20,8 +20,8 @@ extern float GetDemoDuration();
extern void LoadTimeline(MainSequence& main_seq, const GpuContext& ctx);
// Inline peak meter effect for debugging audio-visual sync
-#include "effects/cnn_effect.h"
-#include "effects/cnn_v2_effect.h"
+#include "../../cnn_v1/src/cnn_v1_effect.h"
+#include "../../cnn_v2/src/cnn_v2_effect.h"
#include "gpu/post_process_helper.h"
#include "gpu/shader_composer.h"
diff --git a/src/audio/audio.cc b/src/audio/audio.cc
index f5bc4ab..a220fbb 100644
--- a/src/audio/audio.cc
+++ b/src/audio/audio.cc
@@ -78,9 +78,9 @@ void audio_start() {
#if !defined(STRIP_ALL)
if (!audio_is_prefilled()) {
const int buffered = g_ring_buffer.available_read();
- const float buffered_ms =
- (float)buffered / (RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS) *
- 1000.0f;
+ const float buffered_ms = (float)buffered /
+ (RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS) *
+ 1000.0f;
printf("WARNING: Audio buffer not pre-filled (%.1fms < %.1fms)\n",
buffered_ms, audio_get_required_prefill_time() * 1000.0f);
}
@@ -97,21 +97,18 @@ void audio_render_ahead(float music_time, float dt, float target_fill) {
// Render in small chunks to keep synth time synchronized with tracker
// Chunk size: one frame's worth of audio (~16.6ms @ 60fps)
- // TODO(timing): CRITICAL BUG - Truncation here may cause 180ms drift over 63 beats
- // (int) cast loses fractional samples: 0.333 samples/frame * 2560 frames = 853 samples = 27ms
- // But observed drift is 180ms, so this is not the only source (27ms < 180ms)
- // NOTE: This is NOT a float vs double precision issue - floats handle <500s times fine
- // See also: tracker.cc BPM timing calculation
+ // TODO(timing): CRITICAL BUG - Truncation here may cause 180ms drift over 63
+ // beats (int) cast loses fractional samples: 0.333 samples/frame * 2560
+ // frames = 853 samples = 27ms But observed drift is 180ms, so this is not the
+ // only source (27ms < 180ms) NOTE: This is NOT a float vs double precision
+ // issue - floats handle <500s times fine See also: tracker.cc BPM timing
+ // calculation
const int chunk_frames = (int)(dt * RING_BUFFER_SAMPLE_RATE);
const int chunk_samples = chunk_frames * RING_BUFFER_CHANNELS;
if (chunk_frames <= 0)
return;
- static int64_t g_total_render_calls = 0;
- static int64_t g_total_frames_rendered = 0;
- const int64_t frames_before = g_ring_buffer.get_total_written() / RING_BUFFER_CHANNELS;
-
// Keep rendering small chunks until buffer is full enough
while (true) {
// First, try to flush any pending samples from previous partial writes
@@ -228,19 +225,6 @@ void audio_render_ahead(float music_time, float dt, float target_fill) {
}
}
}
-
- // DEBUG: Track actual frames rendered vs expected
- const int64_t frames_after = g_ring_buffer.get_total_written() / RING_BUFFER_CHANNELS;
- const int64_t actual_rendered = frames_after - frames_before;
- g_total_render_calls++;
- g_total_frames_rendered += actual_rendered;
-
- if (g_total_render_calls % 600 == 0) { // Every 10 seconds at 60fps
- const float expected_frames = g_total_render_calls * (float)(chunk_frames);
- const float drift_ms = (expected_frames - g_total_frames_rendered) / RING_BUFFER_SAMPLE_RATE * 1000.0f;
- printf("[RENDER_DRIFT] calls=%lld expect=%.1f actual=%lld drift=%.2fms\n",
- g_total_render_calls, expected_frames, g_total_frames_rendered, drift_ms);
- }
}
float audio_get_playback_time() {
diff --git a/src/audio/tracker.cc b/src/audio/tracker.cc
index 00c31e9..38c814d 100644
--- a/src/audio/tracker.cc
+++ b/src/audio/tracker.cc
@@ -193,7 +193,8 @@ static int get_free_pattern_slot() {
// sample-accurate timing)
// volume_mult: Additional volume multiplier (for humanization)
static void trigger_note_event(const TrackerEvent& event,
- int start_offset_samples, float volume_mult = 1.0f) {
+ 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) {
@@ -234,10 +235,10 @@ static void trigger_note_event(const TrackerEvent& event,
}
void tracker_update(float music_time_sec, float dt_music_sec) {
- // TODO(timing): CRITICAL BUG - Events trigger ~180ms early over 63 beats @ BPM=90
- // Observed: Beat 63 snare at 41.82s in WAV, should be at 42.00s (180ms drift)
- // NOTE: This is NOT a float vs double precision issue - floats handle <500s times fine
- // Root cause unknown - suspects:
+ // TODO(timing): CRITICAL BUG - Events trigger ~180ms early over 63 beats @
+ // BPM=90 Observed: Beat 63 snare at 41.82s in WAV, should be at 42.00s (180ms
+ // drift) NOTE: This is NOT a float vs double precision issue - floats handle
+ // <500s times fine Root cause unknown - suspects:
// 1. Systematic bias in time calculation (not random accumulation)
// 2. Truncation in audio.cc:103 chunk_frames = (int)(dt * sample_rate)
// 3. BPM calculation precision below (unit_duration_sec)
@@ -324,14 +325,6 @@ void tracker_update(float music_time_sec, float dt_music_sec) {
}
}
- // DEBUG: Track kick/snare timing for drift investigation
- if (event.sample_id == 0 || event.sample_id == 1) { // Assuming kick=0, snare=1
- const char* name = (event.sample_id == 0) ? "KICK " : "SNARE";
- const float delta_ms = (event_music_time - music_time_sec) * 1000.0f;
- printf("[DRIFT] %s: music=%.4f expect=%.4f delta=%.2fms offset=%d\n",
- name, music_time_sec, event_music_time, delta_ms, sample_offset);
- }
-
trigger_note_event(event, sample_offset, volume_mult);
active.next_event_idx++;
}
diff --git a/src/effects/flash_cube_effect.cc b/src/effects/flash_cube_effect.cc
index 29e9897..383e66a 100644
--- a/src/effects/flash_cube_effect.cc
+++ b/src/effects/flash_cube_effect.cc
@@ -60,12 +60,12 @@ void FlashCubeEffect::render(WGPURenderPassEncoder pass,
// Detect beat changes for flash trigger (using intensity as proxy for beat
// hits) Intensity spikes on beats, so we can use it to trigger flashes
if (uniforms.audio_intensity > 0.5f &&
- flash_intensity_ < 0.3f) { // High intensity + flash cooled down
+ flash_intensity_ < 0.2f) { // High intensity + flash cooled down
flash_intensity_ = 1.0f; // Trigger full flash
}
// Exponential decay of flash
- flash_intensity_ *= 0.90f; // Slower fade for more visible effect
+ flash_intensity_ *= 0.95f; // Slower fade for more visible effect
// Always have base brightness, add flash on top
float base_brightness = 0.2f;
@@ -80,7 +80,7 @@ void FlashCubeEffect::render(WGPURenderPassEncoder pass,
// Slowly rotate the cube for visual interest
scene_.objects[0].rotation =
- quat::from_axis(vec3(0.3f, 1, 0.2f), uniforms.time * 0.05f);
+ quat::from_axis(vec3(0.3f, 1, 0.2f), uniforms.time * 0.04f);
// Position camera OUTSIDE the cube looking at it from a distance
// This way we see the cube as a background element
diff --git a/src/effects/gaussian_blur_effect.h b/src/effects/gaussian_blur_effect.h
index 651c5c3..bf1062f 100644
--- a/src/effects/gaussian_blur_effect.h
+++ b/src/effects/gaussian_blur_effect.h
@@ -8,9 +8,9 @@
// Parameters for GaussianBlurEffect (set at construction time)
struct GaussianBlurParams {
- float strength = 1.0f; // Default
+ float strength = 1.0f; // Default
float strength_audio = 0.5f; // how much to pulse with audio
- float stretch = 1.f; // y/x axis ratio
+ float stretch = 1.f; // y/x axis ratio
float _pad = 0.;
};
static_assert(sizeof(GaussianBlurParams) == 16,
diff --git a/src/effects/particle_spray_effect.h b/src/effects/particle_spray_effect.h
index c83d691..216e13f 100644
--- a/src/effects/particle_spray_effect.h
+++ b/src/effects/particle_spray_effect.h
@@ -3,8 +3,8 @@
#pragma once
-#include "gpu/effect.h"
#include "effects/particle_defs.h"
+#include "gpu/effect.h"
class ParticleSprayEffect : public Effect {
public:
diff --git a/src/effects/particles_effect.h b/src/effects/particles_effect.h
index 6d46ea2..a69039f 100644
--- a/src/effects/particles_effect.h
+++ b/src/effects/particles_effect.h
@@ -3,8 +3,8 @@
#pragma once
-#include "gpu/effect.h"
#include "effects/particle_defs.h"
+#include "gpu/effect.h"
class ParticlesEffect : public Effect {
public:
diff --git a/src/effects/sdf_test_effect.cc b/src/effects/sdf_test_effect.cc
index 28b3513..264809f 100644
--- a/src/effects/sdf_test_effect.cc
+++ b/src/effects/sdf_test_effect.cc
@@ -9,8 +9,8 @@ SDFTestEffect::SDFTestEffect(const GpuContext& ctx) : SDFEffect(ctx) {
ResourceBinding bindings[] = {
{uniforms_.get(), WGPUBufferBindingType_Uniform},
{camera_params_.get(), WGPUBufferBindingType_Uniform}};
- pass_ = gpu_create_render_pass(ctx_.device, ctx_.format,
- sdf_test_shader_wgsl, bindings, 2);
+ pass_ = gpu_create_render_pass(ctx_.device, ctx_.format, sdf_test_shader_wgsl,
+ bindings, 2);
pass_.vertex_count = 3;
}
diff --git a/src/gpu/demo_effects.h b/src/gpu/demo_effects.h
index 85498ad..6b22f3f 100644
--- a/src/gpu/demo_effects.h
+++ b/src/gpu/demo_effects.h
@@ -18,8 +18,8 @@
// Individual Effect Headers
#include "effects/chroma_aberration_effect.h"
#include "effects/circle_mask_effect.h"
-#include "effects/cnn_effect.h"
-#include "effects/cnn_v2_effect.h"
+#include "../../cnn_v1/src/cnn_v1_effect.h"
+#include "../../cnn_v2/src/cnn_v2_effect.h"
#include "effects/distort_effect.h"
#include "effects/fade_effect.h"
#include "effects/flash_cube_effect.h"
@@ -40,12 +40,8 @@
#include <memory>
-
-
// Common particle definition is now in effects/particle_defs.h
-
-
// Auto-generated functions from sequence compiler
void LoadTimeline(MainSequence& main_seq, const GpuContext& ctx);
diff --git a/src/gpu/gpu.cc b/src/gpu/gpu.cc
index ce234fa..ff4def7 100644
--- a/src/gpu/gpu.cc
+++ b/src/gpu/gpu.cc
@@ -143,7 +143,6 @@ RenderPass gpu_create_render_pass(WGPUDevice device, WGPUTextureFormat format,
WGPUShaderModule shader_module =
wgpuDeviceCreateShaderModule(device, &shader_desc);
-
// Create Bind Group Layout & Bind Group
std::vector<WGPUBindGroupLayoutEntry> bgl_entries;
std::vector<WGPUBindGroupEntry> bg_entries;
diff --git a/src/tests/audio/test_audio_engine.cc b/src/tests/audio/test_audio_engine.cc
index 3f0ad4d..3d23a5c 100644
--- a/src/tests/audio/test_audio_engine.cc
+++ b/src/tests/audio/test_audio_engine.cc
@@ -65,20 +65,17 @@ void test_audio_engine_manual_resource_loading() {
// Manually preload first few samples
res_mgr->preload(0);
res_mgr->preload(1);
- res_mgr->preload(2);
const int after_preload = res_mgr->get_loaded_count();
printf(" Samples loaded after manual preload: %d\n", after_preload);
- assert(after_preload == 3); // Should have 3 samples loaded
+ assert(after_preload == 2); // Should have 2 samples loaded
// Verify samples are accessible
const Spectrogram* spec0 = res_mgr->get_spectrogram(0);
const Spectrogram* spec1 = res_mgr->get_spectrogram(1);
- const Spectrogram* spec2 = res_mgr->get_spectrogram(2);
assert(spec0 != nullptr);
assert(spec1 != nullptr);
- assert(spec2 != nullptr);
printf(" ✓ AudioEngine manual resource loading test passed\n");
}
@@ -97,10 +94,9 @@ void test_audio_engine_reset() {
// Manually load some samples
res_mgr->preload(0);
res_mgr->preload(1);
- res_mgr->preload(2);
const int loaded_before_reset = res_mgr->get_loaded_count();
- assert(loaded_before_reset == 3);
+ assert(loaded_before_reset == 2);
// Reset engine
fixture.engine().reset();
diff --git a/src/tests/gpu/test_demo_effects.cc b/src/tests/gpu/test_demo_effects.cc
index ec78c10..8726e55 100644
--- a/src/tests/gpu/test_demo_effects.cc
+++ b/src/tests/gpu/test_demo_effects.cc
@@ -12,7 +12,7 @@
#include "../common/effect_test_helpers.h"
#include "../common/webgpu_test_fixture.h"
-#include "effects/cnn_effect.h"
+#include "../../../cnn_v1/src/cnn_v1_effect.h"
#include "gpu/demo_effects.h"
#include "gpu/effect.h"
#include <cassert>
@@ -89,7 +89,7 @@ static void test_post_process_effects() {
{"ThemeModulationEffect",
std::make_shared<ThemeModulationEffect>(fixture.ctx())},
{"VignetteEffect", std::make_shared<VignetteEffect>(fixture.ctx())},
- {"CNNEffect", std::make_shared<CNNEffect>(fixture.ctx())},
+ {"CNNv1Effect", std::make_shared<CNNv1Effect>(fixture.ctx())},
{"CNNv2Effect", std::make_shared<CNNv2Effect>(fixture.ctx())},
};
diff --git a/src/tests/gpu/test_shader_assets.cc b/src/tests/gpu/test_shader_assets.cc
index 135c477..63f9b5d 100644
--- a/src/tests/gpu/test_shader_assets.cc
+++ b/src/tests/gpu/test_shader_assets.cc
@@ -42,8 +42,8 @@ int main() {
all_passed &=
validate_shader(AssetId::ASSET_SHADER_COMMON_UNIFORMS, "COMMON_UNIFORMS",
{"struct", "GlobalUniforms"});
- all_passed &= validate_shader(AssetId::ASSET_SHADER_SDF_SHAPES,
- "SDF_SHAPES", {"fn", "sd"});
+ all_passed &= validate_shader(AssetId::ASSET_SHADER_SDF_SHAPES, "SDF_SHAPES",
+ {"fn", "sd"});
all_passed &= validate_shader(AssetId::ASSET_SHADER_LIGHTING, "LIGHTING",
{"fn", "calc"});
all_passed &= validate_shader(AssetId::ASSET_SHADER_RAY_BOX, "RAY_BOX",
diff --git a/tools/cnn_test.cc b/tools/cnn_test.cc
index 7d060ae..137d235 100644
--- a/tools/cnn_test.cc
+++ b/tools/cnn_test.cc
@@ -5,7 +5,7 @@
#error "cnn_test requires STRIP_ALL=OFF (tool builds only)"
#endif
-#include "effects/cnn_effect.h"
+#include "../cnn_v1/src/cnn_v1_effect.h"
#include "generated/assets.h"
#include "gpu/bind_group_builder.h"
#include "gpu/gpu.h"
diff --git a/tools/common/style.css b/tools/common/style.css
new file mode 100644
index 0000000..1ba4bad
--- /dev/null
+++ b/tools/common/style.css
@@ -0,0 +1,117 @@
+:root {
+ --bg-dark: #1e1e1e;
+ --bg-medium: #252526;
+ --bg-light: #3c3c3c;
+ --text-primary: #d4d4d4;
+ --text-muted: #858585;
+ --accent-blue: #0e639c;
+ --accent-blue-hover: #1177bb;
+ --accent-green: #4ec9b0;
+ --accent-orange: #ce9178;
+ --accent-red: #f48771;
+ --border-color: #858585;
+ --gap: 10px;
+ --radius: 4px;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background: var(--bg-dark);
+ color: var(--text-primary);
+ overflow: hidden;
+}
+
+button, .btn, .file-label {
+ background: var(--accent-blue);
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ border-radius: var(--radius);
+ cursor: pointer;
+ font-size: 14px;
+ display: inline-block;
+ text-align: center;
+}
+
+button:hover, .btn:hover, .file-label:hover {
+ background: var(--accent-blue-hover);
+}
+
+button:disabled, .btn:disabled {
+ background: var(--bg-light);
+ cursor: not-allowed;
+}
+
+input[type="file"] {
+ display: none;
+}
+
+input, select {
+ background: var(--bg-light);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius);
+ color: var(--text-primary);
+ padding: 8px;
+ font-size: 14px;
+}
+
+.container {
+ width: 100%;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+
+.header {
+ background: var(--bg-medium);
+ padding: 15px;
+ border-bottom: 1px solid var(--border-color);
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ flex-wrap: wrap;
+}
+
+h1 {
+ color: var(--accent-green);
+ font-size: 18px;
+ white-space: nowrap;
+}
+
+.controls {
+ display: flex;
+ gap: var(--gap);
+ flex-wrap: wrap;
+ align-items: center;
+}
+
+.panel {
+ background: var(--bg-medium);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius);
+ padding: 15px;
+}
+
+.error-message {
+ background: #5a1d1d;
+ color: var(--accent-red);
+ padding: 10px;
+ border-radius: var(--radius);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
+ margin: 10px 0;
+}
+
+.success-message {
+ background: #1e5231;
+ color: #89d185;
+ padding: 10px;
+ border-radius: var(--radius);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
+ margin: 10px 0;
+}
diff --git a/tools/shader_editor/index.html b/tools/shader_editor/index.html
index bad0abb..d93a595 100644
--- a/tools/shader_editor/index.html
+++ b/tools/shader_editor/index.html
@@ -4,26 +4,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WGSL Shader Editor</title>
+ <link rel="stylesheet" href="../common/style.css">
<style>
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
-}
-
-body {
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", monospace;
- background: #1e1e1e;
- color: #d4d4d4;
- overflow: hidden;
- height: 100vh;
-}
-
-.container {
- display: flex;
- height: 100vh;
-}
-
.preview-pane {
flex: 0 0 57%;
background: #252526;
@@ -89,26 +71,13 @@ body {
}
.control-group button {
- background: #0e639c;
- color: #fff;
- border: none;
padding: 6px 12px;
- border-radius: 3px;
- cursor: pointer;
font-size: 13px;
}
-.control-group button:hover {
- background: #1177bb;
-}
-
.control-group input[type="number"],
.control-group select {
- background: #3c3c3c;
- color: #d4d4d4;
- border: 1px solid #3e3e42;
padding: 4px 8px;
- border-radius: 3px;
font-size: 13px;
}
@@ -153,19 +122,10 @@ body {
}
.editor-header button {
- background: #0e639c;
- color: #fff;
- border: none;
padding: 6px 12px;
- border-radius: 3px;
- cursor: pointer;
font-size: 13px;
}
-.editor-header button:hover {
- background: #1177bb;
-}
-
.editor-container {
flex: 1;
position: relative;
diff --git a/tools/spectral_editor/index.html b/tools/spectral_editor/index.html
index 75658ae..2d5f3e5 100644
--- a/tools/spectral_editor/index.html
+++ b/tools/spectral_editor/index.html
@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spectral Brush Editor</title>
+ <link rel="stylesheet" href="../common/style.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
diff --git a/tools/spectral_editor/style.css b/tools/spectral_editor/style.css
index 48f7463..87fb54e 100644
--- a/tools/spectral_editor/style.css
+++ b/tools/spectral_editor/style.css
@@ -1,18 +1,4 @@
-/* Spectral Brush Editor Styles */
-
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
-}
-
-body {
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- background: #1e1e1e;
- color: #d4d4d4;
- overflow: hidden;
- height: 100vh;
-}
+/* Spectral Brush Editor Specific Styles */
#app {
display: flex;
@@ -20,41 +6,12 @@ body {
height: 100vh;
}
-/* Header */
-header {
- background: #252526;
- padding: 12px 20px;
- border-bottom: 1px solid #3e3e42;
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-header h1 {
- font-size: 18px;
- font-weight: 600;
- color: #cccccc;
-}
-
-.header-controls {
- display: flex;
- align-items: center;
- gap: 15px;
-}
-
-.file-info {
- font-size: 13px;
- color: #858585;
-}
-
-/* Main content area */
.main-content {
display: flex;
flex: 1;
overflow: hidden;
}
-/* Canvas container (80% width) */
.canvas-container {
flex: 1;
position: relative;
@@ -89,7 +46,6 @@ header h1 {
display: none;
}
-/* Mini spectrum viewer (bottom-right overlay) */
.spectrum-viewer {
position: absolute;
bottom: 10px;
@@ -99,12 +55,8 @@ header h1 {
background: rgba(30, 30, 30, 0.9);
border: 1px solid #3e3e42;
border-radius: 3px;
- display: block; /* Always visible */
- pointer-events: none; /* Don't interfere with mouse events */
-}
-
-.spectrum-viewer.active {
- display: block; /* Keep for backward compatibility */
+ display: block;
+ pointer-events: none;
}
#spectrumCanvas {
@@ -123,7 +75,6 @@ header h1 {
color: #858585;
}
-/* Toolbar (20% width) */
.toolbar {
width: 250px;
background: #252526;
@@ -155,16 +106,6 @@ header h1 {
transition: background 0.2s;
}
-.btn-toolbar:hover {
- background: #1177bb;
-}
-
-.btn-toolbar:disabled {
- background: #3e3e42;
- color: #858585;
- cursor: not-allowed;
-}
-
.btn-toolbar.btn-danger {
background: #a82d2d;
}
@@ -199,7 +140,6 @@ header h1 {
border-color: #0e639c;
}
-/* Point info panel */
.point-info {
margin-top: 10px;
padding: 10px;
@@ -224,7 +164,6 @@ header h1 {
font-family: monospace;
}
-/* Control panel (bottom) */
.control-panel {
background: #252526;
border-top: 1px solid #3e3e42;
@@ -314,16 +253,6 @@ header h1 {
transition: background 0.2s;
}
-.btn-playback:hover:not(:disabled) {
- background: #1177bb;
-}
-
-.btn-playback:disabled {
- background: #3e3e42;
- color: #858585;
- cursor: not-allowed;
-}
-
.btn-playback kbd {
background: rgba(255, 255, 255, 0.1);
padding: 2px 5px;
@@ -331,7 +260,6 @@ header h1 {
font-size: 11px;
}
-/* Action bar (bottom) */
.action-bar {
background: #2d2d30;
border-top: 1px solid #3e3e42;
@@ -365,11 +293,6 @@ header h1 {
border-color: #0e639c;
}
-.btn-action:disabled {
- color: #858585;
- cursor: not-allowed;
-}
-
.btn-primary {
padding: 6px 16px;
background: #0e639c;
@@ -381,17 +304,11 @@ header h1 {
transition: background 0.2s;
}
-.btn-primary:hover {
- background: #1177bb;
-}
-
-/* Icon styling */
.icon {
font-size: 14px;
line-height: 1;
}
-/* Modal */
.modal {
position: fixed;
z-index: 1000;
@@ -490,7 +407,6 @@ header h1 {
color: #cccccc;
}
-/* Scrollbar styling */
::-webkit-scrollbar {
width: 10px;
height: 10px;
@@ -509,7 +425,6 @@ header h1 {
background: #4e4e52;
}
-/* Waveform intensity viewer */
.waveform-container {
position: relative;
height: 120px;
@@ -570,24 +485,9 @@ header h1 {
transition: background 0.2s;
}
-.btn-copy:hover, .btn-snap:hover {
- background: #1177bb;
-}
-
-.btn-copy:active, .btn-snap:active {
- background: #0d5a8f;
-}
-
.spectrogram-wrapper {
flex: 1;
position: relative;
overflow: hidden;
z-index: 1;
}
-
-#spectrogramCanvas {
- width: 100%;
- height: 100%;
- display: block;
- cursor: crosshair;
-}
diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html
index d1c759d..c5e0264 100644
--- a/tools/timeline_editor/index.html
+++ b/tools/timeline_editor/index.html
@@ -4,107 +4,466 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Timeline Editor - timeline.seq</title>
+ <link rel="stylesheet" href="../common/style.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' fill='%231e1e1e'/><rect x='10' y='30' width='15' height='40' fill='%234ec9b0'/><rect x='30' y='20' width='15' height='60' fill='%234ec9b0'/><rect x='50' y='35' width='15' height='30' fill='%234ec9b0'/><rect x='70' y='15' width='15' height='70' fill='%234ec9b0'/></svg>">
<style>
- :root {
- --bg-dark: #1e1e1e;
- --bg-medium: #252526;
- --bg-light: #3c3c3c;
- --text-primary: #d4d4d4;
- --text-muted: #858585;
- --accent-blue: #0e639c;
- --accent-blue-hover: #1177bb;
- --accent-green: #4ec9b0;
- --accent-orange: #ce9178;
- --accent-red: #f48771;
- --border-color: #858585;
- --gap: 10px;
- --radius: 4px;
+ body {
+ padding: 20px;
+ min-height: 100vh;
}
- * { margin: 0; padding: 0; box-sizing: border-box; }
- body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: var(--bg-dark); color: var(--text-primary); padding: 20px; min-height: 100vh; }
- .container { max-width: 100%; width: 100%; margin: 0 auto; }
+ .container {
+ max-width: 100%;
+ width: 100%;
+ margin: 0 auto;
+ }
+
+ header {
+ background: var(--bg-medium);
+ padding: 20px;
+ border-radius: 8px;
+ margin-bottom: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 20px;
+ flex-wrap: wrap;
+ }
+
+ .zoom-controls {
+ display: flex;
+ gap: var(--gap);
+ flex-wrap: wrap;
+ align-items: center;
+ margin-bottom: var(--gap);
+ }
+
+ .checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+ user-select: none;
+ }
- header { background: var(--bg-medium); padding: 20px; border-radius: 8px; margin-bottom: 20px; display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
- h1 { color: var(--accent-green); white-space: nowrap; }
- .controls { display: flex; gap: var(--gap); flex-wrap: wrap; align-items: center; }
- .zoom-controls { display: flex; gap: var(--gap); flex-wrap: wrap; align-items: center; margin-bottom: var(--gap); }
+ .checkbox-label input[type="checkbox"] {
+ cursor: pointer;
+ }
- button, .file-label { background: var(--accent-blue); color: white; border: none; padding: 10px 20px; border-radius: var(--radius); cursor: pointer; font-size: 14px; display: inline-block; }
- button:hover, .file-label:hover { background: var(--accent-blue-hover); }
- button:disabled { background: var(--bg-light); cursor: not-allowed; }
- input[type="file"] { display: none; }
+ .timeline-container {
+ background: var(--bg-medium);
+ border-radius: 8px;
+ position: relative;
+ height: calc(100vh - 280px);
+ min-height: 500px;
+ display: flex;
+ flex-direction: column;
+ }
- .checkbox-label { display: flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; }
- .checkbox-label input[type="checkbox"] { cursor: pointer; }
+ .timeline-content {
+ flex: 1;
+ overflow: auto;
+ position: relative;
+ padding: 0 20px 20px 20px;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+ }
- .timeline-container { background: var(--bg-medium); border-radius: 8px; position: relative; height: calc(100vh - 280px); min-height: 500px; display: flex; flex-direction: column; }
- .timeline-content { flex: 1; overflow: auto; position: relative; padding: 0 20px 20px 20px; scrollbar-width: none; -ms-overflow-style: none; }
- .timeline-content::-webkit-scrollbar { display: none; }
- .timeline { position: relative; min-height: 100%; }
+ .timeline-content::-webkit-scrollbar {
+ display: none;
+ }
- .sticky-header { position: sticky; top: 0; background: var(--bg-medium); z-index: 100; padding: 20px 20px 10px 20px; border-bottom: 2px solid var(--bg-light); flex-shrink: 0; }
- .waveform-container { position: relative; height: 80px; overflow: hidden; background: rgba(0, 0, 0, 0.3); border-radius: var(--radius); cursor: crosshair; }
- #cpuLoadCanvas { position: absolute; left: 0; bottom: 0; height: 10px; display: block; z-index: 1; }
- #waveformCanvas { position: absolute; left: 0; top: 0; height: 80px; display: block; z-index: 2; }
- .waveform-cursor { position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(78, 201, 176, 0.6); pointer-events: none; z-index: 3; display: none; }
- .waveform-tooltip { position: absolute; background: rgba(30, 30, 30, 0.95); color: var(--text-primary); padding: 6px 10px; border-radius: 4px; font-size: 12px; pointer-events: none; z-index: 4; display: none; white-space: nowrap; border: 1px solid var(--border-color); box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
+ .timeline {
+ position: relative;
+ min-height: 100%;
+ }
- .playback-indicator { position: absolute; top: 0; bottom: 0; left: 20px; width: 2px; background: var(--accent-red); box-shadow: 0 0 4px rgba(244, 135, 113, 0.8); pointer-events: none; z-index: 110; display: none; }
+ .sticky-header {
+ position: sticky;
+ top: 0;
+ background: var(--bg-medium);
+ z-index: 100;
+ padding: 20px 20px 10px 20px;
+ border-bottom: 2px solid var(--bg-light);
+ flex-shrink: 0;
+ }
- .time-markers { position: relative; height: 30px; margin-top: var(--gap); border-bottom: 1px solid var(--bg-light); }
- .time-marker { position: absolute; top: 0; font-size: 12px; color: var(--text-muted); }
- .time-marker::before { content: ''; position: absolute; left: 0; top: 20px; width: 1px; height: 10px; background: var(--bg-light); }
- .time-marker::after { content: ''; position: absolute; left: 0; top: 30px; width: 1px; height: 10000px; background: rgba(60, 60, 60, 0.2); pointer-events: none; }
+ .waveform-container {
+ position: relative;
+ height: 80px;
+ overflow: hidden;
+ background: rgba(0, 0, 0, 0.5);
+ border-radius: var(--radius);
+ cursor: crosshair;
+ }
+
+ #cpuLoadCanvas {
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ height: 10px;
+ display: block;
+ z-index: 1;
+ }
+
+ #waveformCanvas {
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 80px;
+ display: block;
+ z-index: 2;
+ }
+
+ .waveform-cursor {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 1px;
+ background: rgba(78, 201, 176, 0.6);
+ pointer-events: none;
+ z-index: 3;
+ display: none;
+ }
+
+ .waveform-tooltip {
+ position: absolute;
+ background: rgba(30, 30, 30, 0.95);
+ color: var(--text-primary);
+ padding: 6px 10px;
+ border-radius: 4px;
+ font-size: 12px;
+ pointer-events: none;
+ z-index: 4;
+ display: none;
+ white-space: nowrap;
+ border: 1px solid var(--border-color);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
+ }
+
+ .playback-indicator {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 20px;
+ width: 2px;
+ background: var(--accent-red);
+ box-shadow: 0 0 4px rgba(244, 135, 113, 0.8);
+ pointer-events: none;
+ z-index: 110;
+ display: none;
+ }
+
+ .time-markers {
+ position: relative;
+ height: 30px;
+ margin-top: var(--gap);
+ border-bottom: 1px solid var(--bg-light);
+ }
+
+ .time-marker {
+ position: absolute;
+ top: 0;
+ font-size: 12px;
+ color: var(--text-muted);
+ }
+
+ .time-marker::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 20px;
+ width: 1px;
+ height: 10px;
+ background: var(--bg-light);
+ }
+
+ .time-marker::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 30px;
+ width: 1px;
+ height: 10000px;
+ background: rgba(100, 100, 60, 0.9);
+ pointer-events: none;
+ }
+
+ .sequence {
+ position: absolute;
+ background: #264f78;
+ border: 2px solid var(--accent-blue);
+ border-radius: var(--radius);
+ padding: 8px;
+ cursor: move;
+ min-height: 40px;
+ transition: box-shadow 0.2s;
+ }
+
+ .sequence:hover {
+ box-shadow: 0 0 10px rgba(14, 99, 156, 0.5);
+ }
+
+ .sequence.selected {
+ border-color: var(--accent-green);
+ box-shadow: 0 0 10px rgba(78, 201, 176, 0.5);
+ }
+
+ .sequence.collapsed {
+ overflow: hidden !important;
+ background: #1a3a4a !important;
+ }
+
+ .sequence.collapsed .sequence-name {
+ display: none !important;
+ }
+
+ .sequence.active-playing {
+ border-color: var(--accent-green);
+ background: #2a5f4a;
+ }
+
+ .sequence.active-flash {
+ animation: sequenceFlash 0.6s ease-out;
+ }
- .sequence { position: absolute; background: #264f78; border: 2px solid var(--accent-blue); border-radius: var(--radius); padding: 8px; cursor: move; min-height: 40px; transition: box-shadow 0.2s; }
- .sequence:hover { box-shadow: 0 0 10px rgba(14, 99, 156, 0.5); }
- .sequence.selected { border-color: var(--accent-green); box-shadow: 0 0 10px rgba(78, 201, 176, 0.5); }
- .sequence.collapsed { overflow: hidden !important; background: #1a3a4a !important; }
- .sequence.collapsed .sequence-name { display: none !important; }
- .sequence.active-playing { border-color: var(--accent-green); background: #2a5f4a; }
- .sequence.active-flash { animation: sequenceFlash 0.6s ease-out; }
@keyframes sequenceFlash {
- 0% { box-shadow: 0 0 20px rgba(78, 201, 176, 0.8); border-color: var(--accent-green); }
- 100% { box-shadow: 0 0 10px rgba(14, 99, 156, 0.5); border-color: var(--accent-blue); }
+ 0% {
+ box-shadow: 0 0 20px rgba(78, 201, 176, 0.8);
+ border-color: var(--accent-green);
+ }
+ 100% {
+ box-shadow: 0 0 10px rgba(14, 99, 156, 0.5);
+ border-color: var(--accent-blue);
+ }
}
- .sequence-header { position: absolute; top: 0; left: 0; right: 0; padding: 8px; z-index: 5; cursor: move; user-select: none; }
- .sequence-header-name { font-size: 14px; font-weight: bold; color: #ffffff; }
- .sequence:not(.collapsed) .sequence-header-name { display: none; }
- .sequence-name { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24px; font-weight: bold; color: #ffffff; text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.9), -1px -1px 4px rgba(0, 0, 0, 0.7); pointer-events: none; white-space: nowrap; opacity: 1; transition: opacity 0.3s ease; z-index: 10; }
- .sequence.hovered .sequence-name { opacity: 0; }
+ .sequence-header {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 8px;
+ z-index: 5;
+ cursor: move;
+ user-select: none;
+ }
- .effect { position: absolute; background: #3a3d41; border: 1px solid var(--border-color); border-radius: 3px; padding: 4px 8px; cursor: move; font-size: 11px; transition: box-shadow 0.2s; display: flex; align-items: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
- .effect:hover { box-shadow: 0 0 8px rgba(133, 133, 133, 0.5); background: #45484d; }
- .effect.selected { border-color: var(--accent-orange); box-shadow: 0 0 8px rgba(206, 145, 120, 0.5); }
- .effect.conflict { background: #4a1d1d; border-color: var(--accent-red); box-shadow: 0 0 8px rgba(244, 135, 113, 0.6); }
- .effect.conflict:hover { background: #5a2424; }
- .effect-handle { position: absolute; top: 0; width: 6px; height: 100%; background: rgba(78, 201, 176, 0.8); cursor: ew-resize; display: none; z-index: 10; }
- .effect.selected .effect-handle { display: block; }
- .effect-handle.left { left: 0; border-radius: 3px 0 0 3px; }
- .effect-handle.right { right: 0; border-radius: 0 3px 3px 0; }
- .effect-handle:hover { background: var(--accent-green); width: 8px; }
+ .sequence-header-name {
+ font-size: 14px;
+ font-weight: bold;
+ color: #ffffff;
+ }
- .properties-panel { position: fixed; bottom: 20px; left: 20px; width: 350px; max-height: 80vh; background: var(--bg-medium); padding: 15px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); z-index: 1000; overflow-y: auto; transition: transform 0.3s ease; }
- .properties-panel.collapsed { transform: translateY(calc(100% + 40px)); }
- .panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid var(--bg-light); }
- .panel-header h2 { margin: 0; color: var(--accent-green); font-size: 16px; }
- .panel-toggle { background: transparent; border: 1px solid var(--border-color); color: var(--text-primary); padding: 4px 8px; border-radius: 3px; cursor: pointer; font-size: 12px; }
- .panel-toggle:hover { background: var(--bg-light); }
- .panel-collapse-btn { position: fixed; bottom: 20px; left: 20px; background: var(--bg-medium); border: 1px solid var(--border-color); color: var(--text-primary); padding: 8px 12px; border-radius: var(--radius); cursor: pointer; z-index: 999; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); display: none; }
- .panel-collapse-btn:hover { background: var(--bg-light); }
- .panel-collapse-btn.visible { display: block; }
+ .sequence:not(.collapsed) .sequence-header-name {
+ display: none;
+ }
- .property-group { margin-bottom: 15px; }
- .property-group label { display: block; margin-bottom: 5px; color: var(--text-muted); font-size: 14px; }
- .property-group input, .property-group select { width: 100%; padding: 8px; background: var(--bg-light); border: 1px solid var(--border-color); border-radius: var(--radius); color: var(--text-primary); font-size: 14px; }
+ .sequence-name {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-size: 24px;
+ font-weight: bold;
+ color: #ffffff;
+ text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.9), -1px -1px 4px rgba(0, 0, 0, 0.7);
+ pointer-events: none;
+ white-space: nowrap;
+ opacity: 1;
+ transition: opacity 0.3s ease;
+ z-index: 10;
+ }
- .stats { background: var(--bg-dark); padding: 10px; border-radius: var(--radius); margin-top: 10px; font-size: 12px; color: var(--text-muted); }
- #messageArea { position: fixed; top: 80px; right: 20px; z-index: 2000; max-width: 400px; }
- .error { background: #5a1d1d; color: var(--accent-red); padding: 10px; border-radius: var(--radius); box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
- .success { background: #1e5231; color: #89d185; padding: 10px; border-radius: var(--radius); box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
+ .sequence.hovered .sequence-name {
+ opacity: 0;
+ }
+
+ .effect {
+ position: absolute;
+ background: #3a3d41;
+ border: 1px solid var(--border-color);
+ border-radius: 3px;
+ padding: 4px 8px;
+ cursor: move;
+ font-size: 11px;
+ transition: box-shadow 0.2s;
+ display: flex;
+ align-items: center;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .effect:hover {
+ box-shadow: 0 0 8px rgba(133, 133, 133, 0.5);
+ background: #45484d;
+ }
+
+ .effect.selected {
+ border-color: var(--accent-orange);
+ box-shadow: 0 0 8px rgba(206, 145, 120, 0.5);
+ }
+
+ .effect.conflict {
+ background: #4a1d1d;
+ border-color: var(--accent-red);
+ box-shadow: 0 0 8px rgba(244, 135, 113, 0.6);
+ }
+
+ .effect.conflict:hover {
+ background: #5a2424;
+ }
+
+ .effect-handle {
+ position: absolute;
+ top: 0;
+ width: 6px;
+ height: 100%;
+ background: rgba(78, 201, 176, 0.8);
+ cursor: ew-resize;
+ display: none;
+ z-index: 10;
+ }
+
+ .effect.selected .effect-handle {
+ display: block;
+ }
+
+ .effect-handle.left {
+ left: 0;
+ border-radius: 3px 0 0 3px;
+ }
+
+ .effect-handle.right {
+ right: 0;
+ border-radius: 0 3px 3px 0;
+ }
+
+ .effect-handle:hover {
+ background: var(--accent-green);
+ width: 8px;
+ }
+
+ .properties-panel {
+ position: fixed;
+ bottom: 20px;
+ left: 20px;
+ width: 350px;
+ max-height: 80vh;
+ background: var(--bg-medium);
+ padding: 15px;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
+ z-index: 1000;
+ overflow-y: auto;
+ transition: transform 0.3s ease;
+ }
+
+ .properties-panel.collapsed {
+ transform: translateY(calc(100% + 40px));
+ }
+
+ .panel-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid var(--bg-light);
+ }
+
+ .panel-header h2 {
+ margin: 0;
+ color: var(--accent-green);
+ font-size: 16px;
+ }
+
+ .panel-toggle {
+ background: transparent;
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ padding: 4px 8px;
+ border-radius: 3px;
+ cursor: pointer;
+ font-size: 12px;
+ }
+
+ .panel-toggle:hover {
+ background: var(--bg-light);
+ }
+
+ .panel-collapse-btn {
+ position: fixed;
+ bottom: 20px;
+ left: 20px;
+ background: var(--bg-medium);
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ padding: 8px 12px;
+ border-radius: var(--radius);
+ cursor: pointer;
+ z-index: 999;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
+ display: none;
+ }
+
+ .panel-collapse-btn:hover {
+ background: var(--bg-light);
+ }
+
+ .panel-collapse-btn.visible {
+ display: block;
+ }
+
+ .property-group {
+ margin-bottom: 15px;
+ }
+
+ .property-group label {
+ display: block;
+ margin-bottom: 5px;
+ color: var(--text-muted);
+ font-size: 14px;
+ }
+
+ .property-group input,
+ .property-group select {
+ width: 100%;
+ }
+
+ .stats {
+ background: var(--bg-dark);
+ padding: 10px;
+ border-radius: var(--radius);
+ margin-top: 10px;
+ font-size: 12px;
+ color: var(--text-muted);
+ }
+
+ #messageArea {
+ position: fixed;
+ top: 80px;
+ right: 20px;
+ z-index: 2000;
+ max-width: 400px;
+ }
+
+ .error {
+ background: #5a1d1d;
+ color: var(--accent-red);
+ padding: 10px;
+ border-radius: var(--radius);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
+ }
+
+ .success {
+ background: #1e5231;
+ color: #89d185;
+ padding: 10px;
+ border-radius: var(--radius);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
+ }
</style>
</head>
<body>
@@ -151,7 +510,7 @@
<div id="messageArea"></div>
- <div class="timeline-container">
+ <div class="timeline-container" id="timelineContainer">
<div class="playback-indicator" id="playbackIndicator"></div>
<div class="sticky-header">
<div class="waveform-container" id="waveformContainer">
@@ -220,6 +579,7 @@
// DOM
const dom = {
timeline: document.getElementById('timeline'),
+ timelineContainer: document.getElementById('timelineContainer'),
timelineContent: document.getElementById('timelineContent'),
fileInput: document.getElementById('fileInput'),
saveBtn: document.getElementById('saveBtn'),
@@ -503,7 +863,6 @@
seqDiv.addEventListener('mousedown', e => startDrag(e, 'sequence', seqIndex));
seqDiv.addEventListener('click', e => { e.stopPropagation(); selectItem('sequence', seqIndex); });
seqDiv.addEventListener('dblclick', e => { e.stopPropagation(); e.preventDefault(); seq._collapsed = !seq._collapsed; renderTimeline(); });
- seqDiv.addEventListener('wheel', e => viewportController.handleWheel(e), { passive: false });
dom.timeline.appendChild(seqDiv);
if (!seq._collapsed) {
const conflicts = detectConflicts(seq);
@@ -534,7 +893,6 @@
if (!e.target.classList.contains('effect-handle')) { e.stopPropagation(); startDrag(e, 'effect', seqIndex, effectIndex); }
});
effectDiv.addEventListener('click', e => { e.stopPropagation(); selectItem('effect', seqIndex, effectIndex); });
- effectDiv.addEventListener('wheel', e => viewportController.handleWheel(e), { passive: false });
dom.timeline.appendChild(effectDiv);
});
}
diff --git a/tools/timeline_editor/timeline-playback.js b/tools/timeline_editor/timeline-playback.js
index 8c84877..a1c50ab 100644
--- a/tools/timeline_editor/timeline-playback.js
+++ b/tools/timeline_editor/timeline-playback.js
@@ -168,7 +168,7 @@ export class PlaybackController {
ctx.stroke();
// Beat markers
- ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.50)';
ctx.lineWidth = 1;
for (let beat = 0; beat <= maxTimeBeats; beat++) {
const x = beat * this.state.pixelsPerBeat;
diff --git a/tools/timeline_editor/timeline-viewport.js b/tools/timeline_editor/timeline-viewport.js
index 196368e..dcedb45 100644
--- a/tools/timeline_editor/timeline-viewport.js
+++ b/tools/timeline_editor/timeline-viewport.js
@@ -22,17 +22,15 @@ export class ViewportController {
// Scroll sync
this.dom.timelineContent.addEventListener('scroll', () => this.handleScroll());
- // Wheel handling
+ // Wheel handling - capture at container level to override all child elements
const wheelHandler = e => this.handleWheel(e);
- this.dom.timelineContent.addEventListener('wheel', wheelHandler, { passive: false });
- this.dom.waveformContainer.addEventListener('wheel', wheelHandler, { passive: false });
+ this.dom.timelineContainer.addEventListener('wheel', wheelHandler, { passive: false, capture: true });
- // Prevent wheel bubbling from UI containers
+ // Prevent wheel bubbling from UI containers outside timeline
document.querySelector('header').addEventListener('wheel', e => e.stopPropagation());
this.dom.propertiesPanel.addEventListener('wheel', e => e.stopPropagation());
document.querySelector('.zoom-controls').addEventListener('wheel', e => e.stopPropagation());
document.querySelector('.stats').addEventListener('wheel', e => e.stopPropagation());
- document.getElementById('timeMarkers').addEventListener('wheel', e => e.stopPropagation());
// Waveform hover tracking
this.dom.waveformContainer.addEventListener('mouseenter', () => this.showWaveformCursor());
@@ -148,7 +146,7 @@ export class ViewportController {
const rect = this.dom.waveformContainer.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const scrollLeft = this.dom.timelineContent.scrollLeft;
- const timeBeats = (scrollLeft + mouseX - this.TIMELINE_LEFT_PADDING) / this.state.pixelsPerBeat;
+ const timeBeats = (scrollLeft + mouseX) / this.state.pixelsPerBeat;
const timeSeconds = timeBeats * this.state.secondsPerBeat;
// Position cursor
diff --git a/tools/track_visualizer/index.html b/tools/track_visualizer/index.html
index 4a613ec..d1e7480 100644
--- a/tools/track_visualizer/index.html
+++ b/tools/track_visualizer/index.html
@@ -4,18 +4,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Music Track Visualizer</title>
+ <link rel="stylesheet" href="../common/style.css">
<style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- body {
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- background: #1e1e1e;
- color: #d4d4d4;
- overflow: hidden;
- }
#controls {
padding: 15px;
background: #2d2d2d;
@@ -27,16 +17,8 @@
}
button, input[type="file"] {
padding: 8px 16px;
- background: #0e639c;
- color: white;
- border: none;
- border-radius: 4px;
- cursor: pointer;
font-size: 14px;
}
- button:hover {
- background: #1177bb;
- }
input[type="file"] {
padding: 6px 12px;
}
@@ -58,7 +40,6 @@
width: 100%;
height: calc(100vh - 70px);
overflow: auto;
- background: #1e1e1e;
}
#timeline-canvas {
display: block;
diff --git a/training/README.md b/training/README.md
index e78b471..bddf4d5 100644
--- a/training/README.md
+++ b/training/README.md
@@ -174,6 +174,6 @@ pip install torch torchvision pillow opencv-python numpy
## References
-- **CNN Effect:** `doc/CNN_EFFECT.md`
+- **CNN Effect:** `cnn_v1/docs/CNN_V1_EFFECT.md`
- **Timeline:** `doc/SEQUENCE.md`
- **HOWTO:** `doc/HOWTO.md`
diff --git a/workspaces/main/assets.txt b/workspaces/main/assets.txt
index 972cc8b..189e965 100644
--- a/workspaces/main/assets.txt
+++ b/workspaces/main/assets.txt
@@ -37,16 +37,16 @@ SHADER_PASSTHROUGH, NONE, ../../common/shaders/passthrough.wgsl, "Passthrough Sh
SHADER_ELLIPSE, NONE, shaders/ellipse.wgsl, "Ellipse Shader"
SHADER_PARTICLE_SPRAY_COMPUTE, NONE, shaders/particle_spray_compute.wgsl, "Particle Spray Compute"
SHADER_GAUSSIAN_BLUR, NONE, shaders/gaussian_blur.wgsl, "Gaussian Blur Shader"
-SHADER_CNN_ACTIVATION, NONE, shaders/cnn/cnn_activation.wgsl, "CNN Activation Functions"
-SHADER_CNN_CONV1X1, NONE, shaders/cnn/cnn_conv1x1.wgsl, "CNN 1x1 Convolution"
-SHADER_CNN_CONV3X3, NONE, shaders/cnn/cnn_conv3x3.wgsl, "CNN 3x3 Convolution"
-SHADER_CNN_CONV5X5, NONE, shaders/cnn/cnn_conv5x5.wgsl, "CNN 5x5 Convolution"
-SHADER_CNN_CONV7X7, NONE, shaders/cnn/cnn_conv7x7.wgsl, "CNN 7x7 Convolution"
-SHADER_CNN_WEIGHTS, NONE, shaders/cnn/cnn_weights_generated.wgsl, "CNN Weights (Generated)"
-SHADER_CNN_LAYER, NONE, shaders/cnn/cnn_layer.wgsl, "CNN Layer Shader"
-SHADER_CNN_V2_STATIC, NONE, shaders/cnn_v2/cnn_v2_static.wgsl, "CNN v2 Static Features"
-SHADER_CNN_V2_COMPUTE, NONE, shaders/cnn_v2/cnn_v2_compute.wgsl, "CNN v2 Compute (Storage Buffer)"
-WEIGHTS_CNN_V2, NONE, weights/cnn_v2_weights.bin, "CNN v2 Binary Weights"
+SHADER_CNN_ACTIVATION, NONE, ../../cnn_v1/shaders/cnn_activation.wgsl, "CNN Activation Functions"
+SHADER_CNN_CONV1X1, NONE, ../../cnn_v1/shaders/cnn_conv1x1.wgsl, "CNN 1x1 Convolution"
+SHADER_CNN_CONV3X3, NONE, ../../cnn_v1/shaders/cnn_conv3x3.wgsl, "CNN 3x3 Convolution"
+SHADER_CNN_CONV5X5, NONE, ../../cnn_v1/shaders/cnn_conv5x5.wgsl, "CNN 5x5 Convolution"
+SHADER_CNN_CONV7X7, NONE, ../../cnn_v1/shaders/cnn_conv7x7.wgsl, "CNN 7x7 Convolution"
+SHADER_CNN_WEIGHTS, NONE, ../../cnn_v1/shaders/cnn_weights_generated.wgsl, "CNN Weights (Generated)"
+SHADER_CNN_LAYER, NONE, ../../cnn_v1/shaders/cnn_layer.wgsl, "CNN Layer Shader"
+SHADER_CNN_V2_STATIC, NONE, ../../cnn_v2/shaders/cnn_v2_static.wgsl, "CNN v2 Static Features"
+SHADER_CNN_V2_COMPUTE, NONE, ../../cnn_v2/shaders/cnn_v2_compute.wgsl, "CNN v2 Compute (Storage Buffer)"
+WEIGHTS_CNN_V2, NONE, ../../cnn_v2/weights/cnn_v2_weights.bin, "CNN v2 Binary Weights"
SHADER_SOLARIZE, NONE, shaders/solarize.wgsl, "Solarize Shader"
SHADER_DISTORT, NONE, shaders/distort.wgsl, "Distort Shader"
SHADER_CHROMA_ABERRATION, NONE, shaders/chroma_aberration.wgsl, "Chroma Aberration Shader"
diff --git a/workspaces/main/timeline.seq b/workspaces/main/timeline.seq
index 3e9052b..b4663bb 100644
--- a/workspaces/main/timeline.seq
+++ b/workspaces/main/timeline.seq
@@ -3,100 +3,95 @@
# BPM 90
SEQUENCE 0.00 0
- EFFECT - FlashCubeEffect 0.00 4.88
- EFFECT + FlashEffect 0.00 2.00 color=1.0,0.5,0.5 decay=0.95
- EFFECT + FadeEffect 0.20 2.00
- EFFECT + SolarizeEffect 0.00 4.00
- EFFECT + VignetteEffect 0.00 5.00 radius=0.6 softness=0.1
+ EFFECT - FlashCubeEffect 0.00 4.00
+# EFFECT + FlashEffect 0.00 2.00 color=1.0,0.5,0.5 decay=0.95
+# EFFECT + FadeEffect 2.00 4.00
+# EFFECT + SolarizeEffect 0.00 4.00
+ EFFECT + VignetteEffect 0.00 4.00 radius=0.6 softness=0.1
-SEQUENCE 5.00 0 "rotating cube"
- EFFECT + CircleMaskEffect 0.00 8.00 0.50
- EFFECT + RotatingCubeEffect 0.00 8.00
- EFFECT + GaussianBlurEffect 2.00 4.00 strength=1.0
- EFFECT + GaussianBlurEffect 6.00 8.00 strength=2.0
+SEQUENCE 4.00 0 "rotating cube"
+ EFFECT + CircleMaskEffect 0.00 4.00 0.50
+ EFFECT + RotatingCubeEffect 0.00 4.00
+ EFFECT + GaussianBlurEffect 1.00 4.00 strength=1.0
-SEQUENCE 12.00 0
- EFFECT - FlashCubeEffect 0.22 2.90
+SEQUENCE 8.00 0 "Flash Cube"
+ EFFECT - FlashCubeEffect 0.00 4.02
EFFECT + FlashEffect 0.00 0.40
-SEQUENCE 14.00 1 "spray"
- EFFECT + ParticleSprayEffect 0.00 4.00
- EFFECT + ParticlesEffect 0.00 6.00
+SEQUENCE 12.00 1 "spray"
+ EFFECT + ParticleSprayEffect 0.00 2.00
+ EFFECT + ParticlesEffect 2.00 4.00
EFFECT = GaussianBlurEffect 0.00 4.00 strength=3.0
-SEQUENCE 17.00 2 "Hybrid3D"
+SEQUENCE 16.00 2 "Hybrid3D + CNN"
EFFECT + ThemeModulationEffect 0.00 4.00
- EFFECT + HeptagonEffect 0.40 4.00
- EFFECT + ParticleSprayEffect 0.00 4.00
- EFFECT = ParticlesEffect 0.00 4.00
+ EFFECT + HeptagonEffect 0.00 4.00
+ EFFECT + ParticleSprayEffect 0.00 2.00
+ EFFECT = ParticlesEffect 2.00 4.00
EFFECT + Hybrid3DEffect 0.00 4.00
- EFFECT + GaussianBlurEffect 0.00 4.00
- EFFECT + CNNEffect 0.00 4.00 layers=3 blend=.9
+ EFFECT + CNNv1Effect 0.00 4.00 layers=3 blend=.9
-SEQUENCE 21.00 0 "CNN effect"
- EFFECT + HeptagonEffect 0.00 22.00
- EFFECT + Scene1Effect 0.00 24.00
- EFFECT + CNNEffect 2.00 24.00 layers=3 blend=.5
+SEQUENCE 20.00 0 "CNN effect"
+ EFFECT + HeptagonEffect 0.00 8.00
+ EFFECT + Scene1Effect 0.00 8.00
+ EFFECT + CNNv1Effect 6.00 8.00 layers=3 blend=.5
-SEQUENCE 44.00 0 "buggy"
- EFFECT + HeptagonEffect 0.00 0.40
- EFFECT + FadeEffect 0.22 2.02
+SEQUENCE 28.00 0 "buggy"
+ EFFECT + HeptagonEffect 0.00 2.00
+ EFFECT + FadeEffect 0.00 2.00
-SEQUENCE 44.00 3 "Seq-8"
- EFFECT + ThemeModulationEffect 0.00 8.00
- EFFECT = HeptagonEffect 0.00 8.00
+SEQUENCE 30.00 3 "Seq-8"
+ EFFECT + ThemeModulationEffect 0.00 10.00
+ EFFECT = HeptagonEffect 0.00 10.00
EFFECT + GaussianBlurEffect 0.00 10.00 strength=1.5
EFFECT + ChromaAberrationEffect 0.00 10.00 offset=0.03 angle=0.785
EFFECT + SolarizeEffect 0.00 10.00
-SEQUENCE 46.00 2
- EFFECT - FlashCubeEffect 0.40 3.00
+SEQUENCE 40.00 2
+ EFFECT - FlashCubeEffect 0.00 4.00
EFFECT + HeptagonEffect 0.00 4.00
EFFECT + ParticleSprayEffect 0.00 4.00
- EFFECT + ParticlesEffect 0.00 4.00
-SEQUENCE 46.00 2 "Fade"
- EFFECT - FlashCubeEffect 0.40 3.00
- EFFECT + FlashEffect 0.00 2.00
+SEQUENCE 44.00 2 "Fade"
+ EFFECT - FlashCubeEffect 0.00 2.00
+ EFFECT + FlashEffect 1.00 2.00
-SEQUENCE 48.00 10
- EFFECT - FlashCubeEffect 0.40 3.00
- EFFECT + GaussianBlurEffect 0.00 4.00
- EFFECT + FlashEffect 0.00 0.40
- EFFECT = FlashEffect 1.00 0.40
+SEQUENCE 46.00 10
+ EFFECT - FlashCubeEffect 0.00 3.00
+ EFFECT + GaussianBlurEffect 0.00 3.00
+ EFFECT + FlashEffect 0.00 3.00
-SEQUENCE 51.00 1
- EFFECT + ThemeModulationEffect 0.00 16.00
- EFFECT + HeptagonEffect 0.40 4.00
- EFFECT + ParticleSprayEffect 0.00 16.00
- EFFECT + Hybrid3DEffect 0.00 16.12
- EFFECT + GaussianBlurEffect 0.00 16.00
- EFFECT + ChromaAberrationEffect 0.00 16.28
- EFFECT + SolarizeEffect 0.00 15.76
+SEQUENCE 49.00 1
+ EFFECT + ThemeModulationEffect 0.00 8.00
+ EFFECT + HeptagonEffect 0.00 8.00
+ EFFECT + ParticleSprayEffect 0.00 8.00
+ EFFECT + Hybrid3DEffect 0.00 8.00
+ EFFECT + GaussianBlurEffect 0.00 8.00
+ EFFECT + ChromaAberrationEffect 0.00 8.00
-SEQUENCE 66.00 0
- EFFECT + ThemeModulationEffect 0.00 6.00
- EFFECT + VignetteEffect 0.00 6.00 radius=0.6 softness=0.3
- EFFECT + SolarizeEffect 0.00 6.00
+SEQUENCE 57.00 0
+ EFFECT + ThemeModulationEffect 0.00 7.00
+ EFFECT + VignetteEffect 0.00 7.00 radius=0.6 softness=0.3
+ EFFECT + SolarizeEffect 0.00 7.00
-SEQUENCE 71.00 0
- EFFECT + ThemeModulationEffect 0.00 8.00
- EFFECT + HeptagonEffect 0.40 4.00
- EFFECT + GaussianBlurEffect 0.00 16.00
+SEQUENCE 64.00 0
+ EFFECT + ThemeModulationEffect 0.00 4.00
+ EFFECT + HeptagonEffect 0.00 4.00
+ EFFECT + GaussianBlurEffect 0.00 4.00
EFFECT + SolarizeEffect 0.00 4.00
-SEQUENCE 85.00 0 "double hepta!"
- EFFECT + ThemeModulationEffect 0.00 12.00
- EFFECT = HeptagonEffect 0.40 4.00
- EFFECT + Hybrid3DEffect 0.00 8.00
- EFFECT + ParticleSprayEffect 0.00 11.00
- EFFECT + HeptagonEffect 0.00 16.00
- EFFECT + ChromaAberrationEffect 0.00 15.00
- EFFECT + GaussianBlurEffect 0.00 16.00
+SEQUENCE 68.00 0 "double hepta!"
+ EFFECT + ThemeModulationEffect 0.00 4.00
+ EFFECT = HeptagonEffect 0.00 4.00
+ EFFECT + Hybrid3DEffect 0.00 4.00
+ EFFECT + ParticleSprayEffect 0.00 4.00
+ EFFECT + HeptagonEffect 0.00 4.00
+ EFFECT + ChromaAberrationEffect 0.00 4.00
+ EFFECT + GaussianBlurEffect 0.00 4.00
-SEQUENCE 100.00 0
- EFFECT + ThemeModulationEffect 0.00 8.00
- EFFECT + HeptagonEffect 0.00 19.00
- EFFECT + ChromaAberrationEffect 0.00 18.00
- EFFECT + GaussianBlurEffect 0.00 16.00
+SEQUENCE 72.00 0 "The End"
+ EFFECT + ThemeModulationEffect 0.00 7.00
+ EFFECT + HeptagonEffect 0.00 7.00
+ EFFECT + ChromaAberrationEffect 0.00 7.00
+ EFFECT + GaussianBlurEffect 0.00 7.00