vizcore 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +544 -9
  3. data/docs/.nojekyll +0 -0
  4. data/docs/assets/site.css +744 -0
  5. data/docs/assets/vizcore-demo.gif +0 -0
  6. data/docs/assets/vizcore-poster.png +0 -0
  7. data/docs/assets/vj-tunnel.js +159 -0
  8. data/docs/index.html +224 -0
  9. data/examples/README.md +59 -0
  10. data/examples/assets/README.md +19 -0
  11. data/examples/audio_inspector.rb +34 -0
  12. data/examples/club_intro_drop.rb +78 -0
  13. data/examples/kansai_rubykaigi_visual.rb +70 -0
  14. data/examples/live_coding_minimal.rb +22 -0
  15. data/examples/midi_controller_show.rb +78 -0
  16. data/examples/midi_scene_switch.rb +3 -1
  17. data/examples/parser_visualizer.rb +48 -0
  18. data/examples/readme_demo.rb +17 -0
  19. data/examples/rhythm_geometry.rb +34 -0
  20. data/examples/ruby_crystal_show.rb +35 -0
  21. data/examples/shader_playground.rb +18 -0
  22. data/examples/unyo_liquid.rb +59 -0
  23. data/examples/vj_ambient_chill_room.rb +124 -0
  24. data/examples/vj_dnb_jungle.rb +170 -0
  25. data/examples/vj_festival_mainstage.rb +245 -0
  26. data/examples/vj_festival_mainstage.yml +17 -0
  27. data/examples/vj_glitch_industrial.rb +164 -0
  28. data/examples/vj_hiphop_cipher.rb +167 -0
  29. data/examples/vj_jpop_idol_live.rb +210 -0
  30. data/examples/vj_synthwave_retro.rb +173 -0
  31. data/examples/vj_techno_warehouse.rb +195 -0
  32. data/frontend/index.html +468 -2
  33. data/frontend/src/audio-inspector.js +40 -0
  34. data/frontend/src/live-controls.js +131 -0
  35. data/frontend/src/main.js +792 -16
  36. data/frontend/src/midi-learn.js +194 -0
  37. data/frontend/src/performance-monitor.js +183 -0
  38. data/frontend/src/plugin-runtime.js +130 -0
  39. data/frontend/src/projector-mode.js +56 -0
  40. data/frontend/src/renderer/engine.js +148 -3
  41. data/frontend/src/renderer/layer-manager.js +428 -30
  42. data/frontend/src/renderer/shader-manager.js +26 -0
  43. data/frontend/src/runtime-control-preset.js +11 -0
  44. data/frontend/src/shader-error-overlay.js +29 -0
  45. data/frontend/src/shader-param-controls.js +93 -0
  46. data/frontend/src/shaders/builtins.js +380 -2
  47. data/frontend/src/shaders/post-effects.js +52 -0
  48. data/frontend/src/visual-regression.js +67 -0
  49. data/frontend/src/visual-settings-preset.js +103 -0
  50. data/frontend/src/visuals/geometry.js +268 -0
  51. data/frontend/src/visuals/image-renderer.js +291 -0
  52. data/frontend/src/visuals/particle-system.js +56 -10
  53. data/frontend/src/visuals/spectrogram-renderer.js +226 -0
  54. data/frontend/src/visuals/text-renderer.js +112 -11
  55. data/frontend/src/websocket-client.js +12 -1
  56. data/lib/vizcore/analysis/adaptive_normalizer.rb +70 -0
  57. data/lib/vizcore/analysis/beat_detector.rb +4 -2
  58. data/lib/vizcore/analysis/bpm_estimator.rb +8 -0
  59. data/lib/vizcore/analysis/feature_recorder.rb +159 -0
  60. data/lib/vizcore/analysis/feature_replay.rb +84 -0
  61. data/lib/vizcore/analysis/pipeline.rb +235 -11
  62. data/lib/vizcore/analysis/tap_tempo.rb +74 -0
  63. data/lib/vizcore/analysis.rb +4 -0
  64. data/lib/vizcore/audio/dummy_sine_input.rb +1 -1
  65. data/lib/vizcore/audio/fixture_input.rb +65 -0
  66. data/lib/vizcore/audio/input_manager.rb +4 -2
  67. data/lib/vizcore/audio/mic_input.rb +24 -8
  68. data/lib/vizcore/audio/portaudio_ffi.rb +106 -1
  69. data/lib/vizcore/audio.rb +1 -0
  70. data/lib/vizcore/cli/doctor.rb +159 -0
  71. data/lib/vizcore/cli/dsl_reference.rb +99 -0
  72. data/lib/vizcore/cli/layer_docs.rb +46 -0
  73. data/lib/vizcore/cli/scene_diagnostics.rb +23 -0
  74. data/lib/vizcore/cli/scene_inspector.rb +136 -0
  75. data/lib/vizcore/cli/scene_validator.rb +245 -0
  76. data/lib/vizcore/cli/shader_template.rb +68 -0
  77. data/lib/vizcore/cli/shader_uniform_docs.rb +54 -0
  78. data/lib/vizcore/cli.rb +689 -18
  79. data/lib/vizcore/config.rb +103 -2
  80. data/lib/vizcore/control_preset.rb +68 -0
  81. data/lib/vizcore/dsl/engine.rb +277 -5
  82. data/lib/vizcore/dsl/layer_builder.rb +491 -22
  83. data/lib/vizcore/dsl/layer_group_builder.rb +112 -0
  84. data/lib/vizcore/dsl/mapping_resolver.rb +132 -3
  85. data/lib/vizcore/dsl/mapping_transform_builder.rb +71 -0
  86. data/lib/vizcore/dsl/reaction_builder.rb +44 -0
  87. data/lib/vizcore/dsl/scene_builder.rb +61 -5
  88. data/lib/vizcore/dsl/shader_source_resolver.rb +67 -6
  89. data/lib/vizcore/dsl/style_builder.rb +68 -0
  90. data/lib/vizcore/dsl/timeline_builder.rb +138 -0
  91. data/lib/vizcore/dsl/transition_controller.rb +77 -0
  92. data/lib/vizcore/dsl.rb +5 -1
  93. data/lib/vizcore/layer_catalog.rb +273 -0
  94. data/lib/vizcore/project_manifest.rb +152 -0
  95. data/lib/vizcore/renderer/png_writer.rb +57 -0
  96. data/lib/vizcore/renderer/render_sequence.rb +153 -0
  97. data/lib/vizcore/renderer/scene_frame_source.rb +119 -0
  98. data/lib/vizcore/renderer/scene_serializer.rb +36 -3
  99. data/lib/vizcore/renderer/snapshot.rb +38 -0
  100. data/lib/vizcore/renderer/snapshot_renderer.rb +446 -0
  101. data/lib/vizcore/renderer.rb +5 -0
  102. data/lib/vizcore/server/frame_broadcaster.rb +91 -5
  103. data/lib/vizcore/server/gallery_app.rb +155 -0
  104. data/lib/vizcore/server/gallery_page.rb +100 -0
  105. data/lib/vizcore/server/gallery_runner.rb +48 -0
  106. data/lib/vizcore/server/rack_app.rb +203 -4
  107. data/lib/vizcore/server/runner.rb +370 -22
  108. data/lib/vizcore/server/scene_dependency_watcher.rb +79 -0
  109. data/lib/vizcore/server/websocket_handler.rb +60 -10
  110. data/lib/vizcore/server.rb +4 -0
  111. data/lib/vizcore/sync/osc_message.rb +103 -0
  112. data/lib/vizcore/sync/osc_receiver.rb +68 -0
  113. data/lib/vizcore/sync.rb +4 -0
  114. data/lib/vizcore/templates/midi_control_scene.rb +3 -1
  115. data/lib/vizcore/templates/plugin_layer.rb +20 -0
  116. data/lib/vizcore/templates/plugin_readme.md +23 -0
  117. data/lib/vizcore/templates/plugin_renderer.js +43 -0
  118. data/lib/vizcore/templates/plugin_scene.rb +14 -0
  119. data/lib/vizcore/templates/project_readme.md +7 -23
  120. data/lib/vizcore/templates/rubykaigi_scene.rb +30 -0
  121. data/lib/vizcore/version.rb +1 -1
  122. data/lib/vizcore.rb +27 -0
  123. data/scripts/browser_capture.mjs +75 -0
  124. data/sig/vizcore.rbs +362 -0
  125. metadata +83 -3
  126. data/docs/GETTING_STARTED.md +0 -105
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ # vj_festival_mainstage.rb
4
+ #
5
+ # Genre : EDM / Festival mainstage
6
+ # BPM : 124-132
7
+ # Duration: opener, warmup, build, drop, outro
8
+ # Audio : --audio-source mic
9
+ # --audio-source file --audio-file examples/assets/complex_demo_loop.wav
10
+ # Keys : 1..5 scenes, B blackout, F freeze, Space tap-tempo
11
+ # MIDI : note 36..40 = scenes, CC 1 = global intensity
12
+ #
13
+ # Manifest: examples/vj_festival_mainstage.yml shows a reusable file-audio setup.
14
+ Vizcore.define do
15
+ set :global_intensity, 0.92
16
+ audio_normalize mode: :adaptive, window: 3.0, target: 0.88, floor: 0.04
17
+ bpm 128
18
+ bpm_lock true
19
+ tap_tempo key: :space
20
+
21
+ theme :mainstage do
22
+ palette "#ffffff", "#00f5ff", "#ff2f92", "#fffb00"
23
+ background "#020617"
24
+ end
25
+
26
+ style :stage_glow do
27
+ blend :screen
28
+ opacity 0.82
29
+ end
30
+
31
+ scene :opener do
32
+ use_theme :mainstage
33
+
34
+ layer :opener_logo_slot do
35
+ type :text
36
+ content "MAIN STAGE"
37
+ font "Inter"
38
+ font_size 78
39
+ align :center
40
+ color "#ffffff"
41
+ stroke width: 2, color: "#020617"
42
+ shadow color: "#00f5ff", blur: 20
43
+ glow_strength 0.24
44
+ use_style :stage_glow
45
+ map amplitude, to: :glow_strength, gain: 1.4, range: 0.18..0.88, curve: :sqrt
46
+ map beat_pulse, to: :font_size, gain: 20.0, min: 72.0, max: 106.0
47
+ map fft_spectrum => :deform
48
+ end
49
+
50
+ layer :countdown_rings do
51
+ type :shape
52
+ blend :add
53
+ opacity 0.52
54
+ circle x: 640, y: 360, radius: 130, count: 6 do
55
+ stroke 3
56
+ map low, to: :radius, gain: 120.0, min: 110.0, max: 230.0
57
+ map beat_pulse, to: :stroke, gain: 5.0, min: 2.0, max: 8.0
58
+ end
59
+ end
60
+ end
61
+
62
+ scene :warmup do
63
+ use_theme :mainstage
64
+
65
+ layer :warmup_grid do
66
+ shader :neon_grid
67
+ use_style :stage_glow
68
+ effect :bloom
69
+ effect_intensity 0.16
70
+ map amplitude, to: :effect_intensity, gain: 1.1, range: 0.12..0.52, curve: :sqrt
71
+ map beat_pulse, to: :pulse, range: 0.12..0.62
72
+ map fft_spectrum => :deform
73
+ end
74
+
75
+ layer :warmup_cube do
76
+ type :wireframe_cube
77
+ opacity 0.64
78
+ blend :add
79
+ map bass, to: :rotation_speed, gain: 2.0, range: 0.18..3.2
80
+ map kick, to: :scale, gain: 0.55, range: 0.82..1.4
81
+ map fft_spectrum => :deform
82
+ end
83
+ end
84
+
85
+ scene :build do
86
+ use_theme :mainstage
87
+
88
+ layer :riser_tunnel do
89
+ shader :bass_tunnel
90
+ use_style :stage_glow
91
+ effect :motion_blur
92
+ effect_intensity 0.18
93
+ map amplitude, to: :warp, gain: 2.2, range: 0.18..1.8, curve: :sqrt
94
+ map beat_pulse, to: :effect_intensity, range: 0.12..0.55, attack: 1.0, release: 0.1
95
+ map fft_spectrum => :deform
96
+ end
97
+
98
+ layer :riser_particles do
99
+ type :particle_field
100
+ count 3600 # tune: 2400 on laptops, 7000 on mainstage GPUs
101
+ force_field :fountain
102
+ speed 1.6
103
+ size 2.2
104
+ blend :add
105
+ map high, to: :speed, gain: 3.8, range: 0.8..7.2, curve: :sqrt
106
+ map kick, to: :bass_explosion, gain: 1.2, range: 0.2..1.5, attack: 1.0, release: 0.08
107
+ map fft_spectrum => :deform
108
+ end
109
+ end
110
+
111
+ scene :drop, extends: :build do
112
+ use_theme :mainstage
113
+
114
+ group :foreground do
115
+ blend :add
116
+ opacity 0.84
117
+
118
+ layer :drop_crystal do
119
+ shader :ruby_crystal
120
+ facets 10.0
121
+ refraction 0.68
122
+ effect :chromatic
123
+ effect_intensity 0.2
124
+ map low, to: :refraction, gain: 1.2, range: 0.42..1.0, curve: :sqrt
125
+ map beat_pulse, to: :effect_intensity, range: 0.16..0.86, attack: 1.0, release: 0.08
126
+ map fft_spectrum => :deform
127
+ end
128
+
129
+ layer :drop_title do
130
+ type :text
131
+ content "DROP"
132
+ font_size 132
133
+ align :center
134
+ color "#ffffff"
135
+ glow_strength 0.42
136
+ shadow color: "#ff2f92", blur: 26
137
+ map amplitude, to: :glow_strength, gain: 1.4, range: 0.3..1.0
138
+ map kick, to: :font_size, gain: 36.0, min: 120.0, max: 172.0
139
+ map fft_spectrum => :deform
140
+ end
141
+ end
142
+ end
143
+
144
+ scene :outro do
145
+ use_theme :mainstage
146
+
147
+ layer :afterglow do
148
+ shader :waveform_ribbon
149
+ blend :screen
150
+ opacity 0.56
151
+ effect :feedback
152
+ effect_intensity 0.1
153
+ map amplitude, to: :opacity, gain: 0.7, range: 0.3..0.72
154
+ map beat_pulse, to: :effect_intensity, range: 0.06..0.24
155
+ map fft_spectrum => :deform
156
+ end
157
+
158
+ layer :thanks_text do
159
+ type :text
160
+ content "THANK YOU"
161
+ font_size 70
162
+ align :center
163
+ color "#ffffff"
164
+ glow_strength 0.18
165
+ blend :screen
166
+ map mid, to: :glow_strength, gain: 1.4, range: 0.12..0.58
167
+ map beat_pulse, to: :letter_spacing, gain: 12.0, min: 0.0, max: 18.0
168
+ map fft_spectrum => :deform
169
+ end
170
+ end
171
+
172
+ transition from: :opener, to: :warmup do
173
+ on_bar 8
174
+ effect :crossfade, duration: 0.8
175
+ end
176
+
177
+ transition from: :warmup, to: :build do
178
+ on_bar 16
179
+ effect :crossfade, duration: 0.75
180
+ end
181
+
182
+ transition from: :build, to: :drop do
183
+ trigger { beat_count >= 32 || frame_count >= 480 }
184
+ effect :flash, duration: 0.3
185
+ end
186
+
187
+ transition from: :drop, to: :outro do
188
+ on_bar 32
189
+ effect :crossfade, duration: 1.0
190
+ end
191
+
192
+ midi :controller, device: :default
193
+
194
+ midi_map note: 36 do
195
+ switch_scene :opener
196
+ end
197
+
198
+ midi_map note: 37 do
199
+ switch_scene :warmup
200
+ end
201
+
202
+ midi_map note: 38 do
203
+ switch_scene :build
204
+ end
205
+
206
+ midi_map note: 39 do
207
+ switch_scene :drop
208
+ end
209
+
210
+ midi_map note: 40 do
211
+ switch_scene :outro
212
+ end
213
+
214
+ midi_map cc: 1 do |value|
215
+ set :global_intensity, value / 127.0
216
+ end
217
+
218
+ key "1" do
219
+ switch_scene :opener
220
+ end
221
+
222
+ key "2" do
223
+ switch_scene :warmup
224
+ end
225
+
226
+ key "3" do
227
+ switch_scene :build
228
+ end
229
+
230
+ key "4" do
231
+ switch_scene :drop
232
+ end
233
+
234
+ key "5" do
235
+ switch_scene :outro
236
+ end
237
+
238
+ key "b" do
239
+ blackout
240
+ end
241
+
242
+ key "f" do
243
+ freeze
244
+ end
245
+ end
@@ -0,0 +1,17 @@
1
+ # Example manifest for the festival mainstage set.
2
+ #
3
+ # Start with:
4
+ # vizcore start examples/vj_festival_mainstage.yml
5
+ #
6
+ # A writable control preset can be attached when you want HUD reactivity and
7
+ # MIDI Learn bindings to round-trip with "Save Project":
8
+ #
9
+ # control_preset: assets/vj_festival_mainstage_controls.json
10
+ scene: vj_festival_mainstage.rb
11
+ audio_source: file
12
+ audio_file: assets/complex_demo_loop.wav
13
+
14
+ profiles:
15
+ mic:
16
+ audio_source: mic
17
+ audio_file:
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ # vj_glitch_industrial.rb
4
+ #
5
+ # Genre : Glitch / Industrial / Noise
6
+ # BPM : n/a
7
+ # Duration: scan, tear, white_out
8
+ # Audio : --audio-source mic
9
+ # --audio-source file --audio-file examples/assets/complex_demo_loop.wav
10
+ # Keys : 1 scan, 2 tear, X white_out, B blackout, F freeze
11
+ # MIDI : note 36..38 = scenes, note 41 = white_out, CC 1 = global intensity
12
+ Vizcore.define do
13
+ set :global_intensity, 0.84
14
+ audio_normalize mode: :adaptive, window: 2.0, target: 0.88, floor: 0.02
15
+
16
+ theme :factory_noise do
17
+ palette "#f8fafc", "#71717a", "#ef4444", "#111827"
18
+ background "#000000"
19
+ end
20
+
21
+ style :abrasive do
22
+ blend :difference
23
+ opacity 0.74
24
+ end
25
+
26
+ scene :scan do
27
+ use_theme :factory_noise
28
+
29
+ layer :scanner_waterfall do
30
+ type :spectrogram
31
+ scroll :vertical
32
+ bins 128
33
+ history 90
34
+ gain 0.9
35
+ use_style :abrasive
36
+ effect :glitch
37
+ effect_intensity 0.18
38
+ map amplitude, to: :opacity, gain: 0.9, range: 0.36..0.92, curve: :sqrt
39
+ map onset(:high), to: :effect_intensity, gain: 2.4, range: 0.12..0.94, attack: 1.0, release: 0.04
40
+ map fft_spectrum => :deform
41
+ end
42
+
43
+ layer :scanline_text do
44
+ type :text
45
+ content "SCAN"
46
+ font_size 76
47
+ color "#f8fafc"
48
+ blend :multiply
49
+ shadow color: "#ef4444", blur: 10
50
+ glow_strength 0.08
51
+ map high, to: :glow_strength, gain: 2.6, range: 0.06..0.7
52
+ map beat_pulse, to: :letter_spacing, gain: 18.0, min: 0.0, max: 22.0
53
+ map fft_spectrum => :deform
54
+ end
55
+ end
56
+
57
+ scene :tear do
58
+ use_theme :factory_noise
59
+
60
+ layer :feedback_tear do
61
+ shader :liquid_wobble
62
+ opacity 0.86
63
+ blend :difference
64
+ effect :feedback
65
+ effect_intensity 0.28
66
+ wobble 0.54
67
+ warp 1.1
68
+ map amplitude, to: :wobble, gain: 3.2, range: 0.28..1.8, curve: :sqrt
69
+ map onset(:high), to: :effect_intensity, gain: 2.2, range: 0.18..1.0, attack: 1.0, release: 0.05
70
+ map fft_spectrum => :deform
71
+ end
72
+
73
+ layer :tear_mesh do
74
+ type :mesh
75
+ geometry :octahedron
76
+ material :wireframe
77
+ scale 0.9
78
+ deform 0.36
79
+ blend :multiply
80
+ map low, to: :scale, gain: 0.8, range: 0.75..1.42
81
+ map beat_confidence, to: :deform, gain: 1.3, range: 0.22..1.0
82
+ map fft_spectrum => :color_shift
83
+ end
84
+ end
85
+
86
+ scene :white_out do
87
+ use_theme :factory_noise
88
+
89
+ layer :white_flash do
90
+ shader :glitch_flash
91
+ opacity 0.94
92
+ blend :add
93
+ effect :crt
94
+ effect_intensity 0.42
95
+ map amplitude, to: :opacity, gain: 1.2, range: 0.52..1.0, curve: :sqrt
96
+ map onset(:high), to: :effect_intensity, gain: 3.0, range: 0.22..1.0, attack: 1.0, release: 0.03
97
+ map fft_spectrum => :deform
98
+ end
99
+
100
+ layer :warning_text do
101
+ type :text
102
+ content "WHITE OUT"
103
+ font_size 96
104
+ color "#000000"
105
+ blend :difference
106
+ glow_strength 0.48
107
+ map mid, to: :glow_strength, gain: 2.5, range: 0.28..1.0
108
+ map beat_pulse, to: :font_size, gain: 26.0, min: 90.0, max: 132.0
109
+ map fft_spectrum => :deform
110
+ end
111
+ end
112
+
113
+ transition from: :scan, to: :tear do
114
+ trigger { seconds >= 36 || beat_count >= 32 }
115
+ effect :crossfade, duration: 0.35
116
+ end
117
+
118
+ transition from: :tear, to: :scan do
119
+ trigger { seconds >= 48 || beat_count >= 48 }
120
+ effect :flash, duration: 0.18
121
+ end
122
+
123
+ midi :controller, device: :default
124
+
125
+ midi_map note: 36 do
126
+ switch_scene :scan
127
+ end
128
+
129
+ midi_map note: 37 do
130
+ switch_scene :tear
131
+ end
132
+
133
+ midi_map note: 38 do
134
+ switch_scene :white_out
135
+ end
136
+
137
+ midi_map note: 41 do
138
+ switch_scene :white_out
139
+ end
140
+
141
+ midi_map cc: 1 do |value|
142
+ set :global_intensity, value / 127.0
143
+ end
144
+
145
+ key "1" do
146
+ switch_scene :scan
147
+ end
148
+
149
+ key "2" do
150
+ switch_scene :tear
151
+ end
152
+
153
+ key "x" do
154
+ switch_scene :white_out
155
+ end
156
+
157
+ key "b" do
158
+ blackout
159
+ end
160
+
161
+ key "f" do
162
+ freeze
163
+ end
164
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ # vj_hiphop_cipher.rb
4
+ #
5
+ # Genre : HipHop / Cipher
6
+ # BPM : 85-100
7
+ # Duration: performer-friendly intro, verse, hook
8
+ # Audio : --audio-source mic
9
+ # --audio-source file --audio-file examples/assets/complex_demo_loop.wav
10
+ # Keys : 1 intro, V verse, H hook, B blackout, F freeze, Space tap-tempo
11
+ # MIDI : note 36..38 = scenes, CC 1 = global intensity
12
+ Vizcore.define do
13
+ set :global_intensity, 0.78
14
+ audio_normalize mode: :adaptive, window: 3.5, target: 0.78, floor: 0.05
15
+ bpm 92
16
+ tap_tempo key: :space
17
+
18
+ theme :cipher_blacktop do
19
+ palette "#0f172a", "#f8fafc", "#facc15", "#ef4444"
20
+ background "#020617"
21
+ end
22
+
23
+ style :stage_text do
24
+ blend :screen
25
+ opacity 0.88
26
+ end
27
+
28
+ scene :intro do
29
+ use_theme :cipher_blacktop
30
+
31
+ layer :mic_check_title do
32
+ type :text
33
+ content "MIC CHECK"
34
+ font "Inter"
35
+ font_size 92
36
+ align :center
37
+ color "#f8fafc"
38
+ stroke width: 2, color: "#020617"
39
+ shadow color: "#facc15", blur: 18
40
+ glow_strength 0.18
41
+ use_style :stage_text
42
+ map amplitude, to: :glow_strength, gain: 1.3, range: 0.14..0.72, curve: :sqrt
43
+ map beat_pulse, to: :letter_spacing, gain: 10.0, min: 0.0, max: 14.0
44
+ map fft_spectrum => :deform
45
+ end
46
+
47
+ layer :intro_pulse do
48
+ type :radial_blob
49
+ opacity 0.34
50
+ blend :add
51
+ radius 0.34
52
+ wobble 0.12
53
+ map low, to: :radius, gain: 0.4, range: 0.3..0.68
54
+ map kick, to: :wobble, gain: 0.8, range: 0.08..0.52, attack: 1.0, release: 0.12
55
+ map fft_spectrum => :deform
56
+ end
57
+ end
58
+
59
+ scene :verse do
60
+ use_theme :cipher_blacktop
61
+
62
+ layer :quiet_backline do
63
+ type :waveform
64
+ source :audio
65
+ style :ribbon
66
+ height 0.12
67
+ opacity 0.36
68
+ blend :screen
69
+ map amplitude, to: :height, gain: 0.8, range: 0.08..0.28, curve: :sqrt, release: 0.34
70
+ map beat_pulse, to: :opacity, range: 0.28..0.52, attack: 1.0, release: 0.18
71
+ map fft_spectrum => :deform
72
+ end
73
+
74
+ layer :cipher_dots do
75
+ type :shape
76
+ opacity 0.42
77
+ blend :add
78
+ circle x: 240, y: 520, radius: 22, count: 3 do
79
+ stroke 2
80
+ map mid, to: :radius, gain: 36.0, min: 18.0, max: 56.0
81
+ map beat_pulse, to: :stroke, gain: 4.0, min: 1.0, max: 6.0
82
+ end
83
+ circle x: 1040, y: 180, radius: 18, count: 2 do
84
+ stroke 2
85
+ map fft_spectrum => :radius
86
+ end
87
+ end
88
+ end
89
+
90
+ scene :hook do
91
+ use_theme :cipher_blacktop
92
+
93
+ layer :hook_color do
94
+ shader :gradient_pulse
95
+ blend :screen
96
+ opacity 0.86
97
+ effect :bloom
98
+ effect_intensity 0.24
99
+ map amplitude, to: :effect_intensity, gain: 1.2, range: 0.18..0.72, curve: :sqrt
100
+ map beat_pulse, to: :pulse, range: 0.1..1.0, attack: 1.0, release: 0.14
101
+ map fft_spectrum => :deform
102
+ end
103
+
104
+ layer :hook_text do
105
+ type :text
106
+ content "HOOK"
107
+ font "Inter"
108
+ font_size 116
109
+ align :center
110
+ color "#facc15"
111
+ shadow color: "#ef4444", blur: 24
112
+ glow_strength 0.32
113
+ blend :add
114
+ map high, to: :glow_strength, gain: 2.0, range: 0.24..1.0
115
+ map beat_pulse, to: :font_size, gain: 32.0, min: 104.0, max: 140.0
116
+ map fft_spectrum => :deform
117
+ end
118
+ end
119
+
120
+ transition from: :intro, to: :verse do
121
+ trigger { beat_count >= 16 || frame_count >= 240 }
122
+ effect :crossfade, duration: 0.7
123
+ end
124
+
125
+ transition from: :verse, to: :hook do
126
+ trigger { beat_count >= 48 || frame_count >= 720 }
127
+ effect :flash, duration: 0.2
128
+ end
129
+
130
+ midi :controller, device: :default
131
+
132
+ midi_map note: 36 do
133
+ switch_scene :intro
134
+ end
135
+
136
+ midi_map note: 37 do
137
+ switch_scene :verse
138
+ end
139
+
140
+ midi_map note: 38 do
141
+ switch_scene :hook
142
+ end
143
+
144
+ midi_map cc: 1 do |value|
145
+ set :global_intensity, value / 127.0
146
+ end
147
+
148
+ key "1" do
149
+ switch_scene :intro
150
+ end
151
+
152
+ key "v" do
153
+ switch_scene :verse
154
+ end
155
+
156
+ key "h" do
157
+ switch_scene :hook
158
+ end
159
+
160
+ key "b" do
161
+ blackout
162
+ end
163
+
164
+ key "f" do
165
+ freeze
166
+ end
167
+ end