vizcore 0.1.0 → 1.1.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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +70 -117
  3. data/docs/.nojekyll +0 -0
  4. data/docs/assets/playground-worker.js +373 -0
  5. data/docs/assets/playground.css +440 -0
  6. data/docs/assets/playground.js +652 -0
  7. data/docs/assets/site.css +744 -0
  8. data/docs/assets/vizcore-demo.gif +0 -0
  9. data/docs/assets/vizcore-poster.png +0 -0
  10. data/docs/assets/vj-tunnel.js +159 -0
  11. data/docs/index.html +225 -0
  12. data/docs/playground.html +81 -0
  13. data/docs/shape_dsl.md +269 -0
  14. data/examples/README.md +59 -0
  15. data/examples/assets/README.md +19 -0
  16. data/examples/audio_inspector.rb +34 -0
  17. data/examples/club_intro_drop.rb +78 -0
  18. data/examples/kansai_rubykaigi_visual.rb +70 -0
  19. data/examples/live_coding_minimal.rb +22 -0
  20. data/examples/midi_controller_show.rb +78 -0
  21. data/examples/midi_scene_switch.rb +3 -1
  22. data/examples/parser_visualizer.rb +48 -0
  23. data/examples/readme_demo.rb +17 -0
  24. data/examples/rhythm_geometry.rb +34 -0
  25. data/examples/ruby_crystal_show.rb +35 -0
  26. data/examples/shader_playground.rb +18 -0
  27. data/examples/unyo_liquid.rb +59 -0
  28. data/examples/vj_ambient_chill_room.rb +124 -0
  29. data/examples/vj_dnb_jungle.rb +170 -0
  30. data/examples/vj_festival_mainstage.rb +245 -0
  31. data/examples/vj_festival_mainstage.yml +17 -0
  32. data/examples/vj_glitch_industrial.rb +164 -0
  33. data/examples/vj_hiphop_cipher.rb +167 -0
  34. data/examples/vj_jpop_idol_live.rb +210 -0
  35. data/examples/vj_synthwave_retro.rb +173 -0
  36. data/examples/vj_techno_warehouse.rb +195 -0
  37. data/frontend/index.html +494 -2
  38. data/frontend/src/audio-inspector.js +40 -0
  39. data/frontend/src/custom-shape-param-controls.js +106 -0
  40. data/frontend/src/live-controls.js +131 -0
  41. data/frontend/src/main.js +1060 -16
  42. data/frontend/src/mapping-target-selector.js +109 -0
  43. data/frontend/src/midi-learn.js +194 -0
  44. data/frontend/src/performance-monitor.js +183 -0
  45. data/frontend/src/plugin-runtime.js +130 -0
  46. data/frontend/src/projector-mode.js +56 -0
  47. data/frontend/src/renderer/engine.js +157 -3
  48. data/frontend/src/renderer/layer-manager.js +442 -30
  49. data/frontend/src/renderer/shader-manager.js +26 -0
  50. data/frontend/src/runtime-control-preset.js +11 -0
  51. data/frontend/src/shader-error-overlay.js +29 -0
  52. data/frontend/src/shader-param-controls.js +93 -0
  53. data/frontend/src/shaders/builtins.js +380 -2
  54. data/frontend/src/shaders/post-effects.js +52 -0
  55. data/frontend/src/shape-editor-controls.js +157 -0
  56. data/frontend/src/visual-regression.js +67 -0
  57. data/frontend/src/visual-settings-preset.js +103 -0
  58. data/frontend/src/visuals/geometry.js +666 -0
  59. data/frontend/src/visuals/image-renderer.js +291 -0
  60. data/frontend/src/visuals/particle-system.js +56 -10
  61. data/frontend/src/visuals/shape-renderer.js +475 -0
  62. data/frontend/src/visuals/spectrogram-renderer.js +226 -0
  63. data/frontend/src/visuals/svg-arc.js +104 -0
  64. data/frontend/src/visuals/text-renderer.js +112 -11
  65. data/frontend/src/websocket-client.js +12 -1
  66. data/lib/vizcore/analysis/adaptive_normalizer.rb +70 -0
  67. data/lib/vizcore/analysis/beat_detector.rb +4 -2
  68. data/lib/vizcore/analysis/bpm_estimator.rb +8 -0
  69. data/lib/vizcore/analysis/feature_recorder.rb +159 -0
  70. data/lib/vizcore/analysis/feature_replay.rb +84 -0
  71. data/lib/vizcore/analysis/pipeline.rb +235 -11
  72. data/lib/vizcore/analysis/tap_tempo.rb +74 -0
  73. data/lib/vizcore/analysis.rb +4 -0
  74. data/lib/vizcore/audio/dummy_sine_input.rb +1 -1
  75. data/lib/vizcore/audio/fixture_input.rb +65 -0
  76. data/lib/vizcore/audio/input_manager.rb +4 -2
  77. data/lib/vizcore/audio/mic_input.rb +24 -8
  78. data/lib/vizcore/audio/portaudio_ffi.rb +106 -1
  79. data/lib/vizcore/audio.rb +1 -0
  80. data/lib/vizcore/cli/doctor.rb +159 -0
  81. data/lib/vizcore/cli/dsl_reference.rb +99 -0
  82. data/lib/vizcore/cli/layer_docs.rb +46 -0
  83. data/lib/vizcore/cli/scene_diagnostics.rb +23 -0
  84. data/lib/vizcore/cli/scene_inspector.rb +136 -0
  85. data/lib/vizcore/cli/scene_validator.rb +337 -0
  86. data/lib/vizcore/cli/shader_template.rb +68 -0
  87. data/lib/vizcore/cli/shader_uniform_docs.rb +54 -0
  88. data/lib/vizcore/cli.rb +689 -18
  89. data/lib/vizcore/config.rb +103 -2
  90. data/lib/vizcore/control_preset.rb +68 -0
  91. data/lib/vizcore/dsl/engine.rb +277 -5
  92. data/lib/vizcore/dsl/layer_builder.rb +1280 -23
  93. data/lib/vizcore/dsl/layer_group_builder.rb +112 -0
  94. data/lib/vizcore/dsl/mapping_resolver.rb +290 -7
  95. data/lib/vizcore/dsl/mapping_transform_builder.rb +71 -0
  96. data/lib/vizcore/dsl/reaction_builder.rb +44 -0
  97. data/lib/vizcore/dsl/scene_builder.rb +61 -5
  98. data/lib/vizcore/dsl/shader_source_resolver.rb +67 -6
  99. data/lib/vizcore/dsl/style_builder.rb +68 -0
  100. data/lib/vizcore/dsl/timeline_builder.rb +138 -0
  101. data/lib/vizcore/dsl/transition_controller.rb +77 -0
  102. data/lib/vizcore/dsl.rb +5 -1
  103. data/lib/vizcore/layer_catalog.rb +275 -0
  104. data/lib/vizcore/project_manifest.rb +152 -0
  105. data/lib/vizcore/renderer/png_writer.rb +57 -0
  106. data/lib/vizcore/renderer/render_sequence.rb +153 -0
  107. data/lib/vizcore/renderer/scene_frame_source.rb +132 -0
  108. data/lib/vizcore/renderer/scene_serializer.rb +36 -3
  109. data/lib/vizcore/renderer/snapshot.rb +38 -0
  110. data/lib/vizcore/renderer/snapshot_renderer.rb +938 -0
  111. data/lib/vizcore/renderer.rb +5 -0
  112. data/lib/vizcore/server/frame_broadcaster.rb +143 -8
  113. data/lib/vizcore/server/gallery_app.rb +155 -0
  114. data/lib/vizcore/server/gallery_page.rb +100 -0
  115. data/lib/vizcore/server/gallery_runner.rb +48 -0
  116. data/lib/vizcore/server/rack_app.rb +203 -4
  117. data/lib/vizcore/server/runner.rb +391 -22
  118. data/lib/vizcore/server/scene_dependency_watcher.rb +79 -0
  119. data/lib/vizcore/server/websocket_handler.rb +60 -10
  120. data/lib/vizcore/server.rb +4 -0
  121. data/lib/vizcore/shape.rb +719 -0
  122. data/lib/vizcore/sync/osc_message.rb +103 -0
  123. data/lib/vizcore/sync/osc_receiver.rb +68 -0
  124. data/lib/vizcore/sync.rb +4 -0
  125. data/lib/vizcore/templates/midi_control_scene.rb +3 -1
  126. data/lib/vizcore/templates/plugin_layer.rb +20 -0
  127. data/lib/vizcore/templates/plugin_readme.md +23 -0
  128. data/lib/vizcore/templates/plugin_renderer.js +43 -0
  129. data/lib/vizcore/templates/plugin_scene.rb +14 -0
  130. data/lib/vizcore/templates/project_readme.md +7 -23
  131. data/lib/vizcore/templates/rubykaigi_scene.rb +30 -0
  132. data/lib/vizcore/version.rb +1 -1
  133. data/lib/vizcore.rb +28 -0
  134. data/scripts/browser_capture.mjs +75 -0
  135. data/sig/vizcore.rbs +461 -0
  136. metadata +94 -3
  137. data/docs/GETTING_STARTED.md +0 -105
@@ -1,5 +1,7 @@
1
1
  import { LayerManager } from "./layer-manager.js";
2
2
  import { ShaderManager } from "./shader-manager.js";
3
+ import { applyShaderParamOverrides } from "../shader-param-controls.js";
4
+ import { applyShapeEditorOverrides } from "../shape-editor-controls.js";
3
5
 
4
6
  export class Engine {
5
7
  constructor(canvas) {
@@ -12,12 +14,32 @@ export class Engine {
12
14
  this.currentRotationSpeed = 0.5;
13
15
  this.mediaElement = null;
14
16
  this.lastMediaTime = null;
17
+ this.visualSettings = {
18
+ visualGain: 1,
19
+ bassBoost: 1,
20
+ smoothing: 0,
21
+ beatHoldMs: 180,
22
+ wobbleAmount: 1,
23
+ };
24
+ this.visualAudioState = null;
25
+ this.liveControls = {
26
+ blackout: false,
27
+ freeze: false,
28
+ };
29
+ this.runtimeGlobals = {};
30
+ this.shaderParamOverrides = {};
31
+ this.shapeEditorOverrides = {};
32
+ this.beatHoldUntil = 0;
15
33
  this.frame = {
16
34
  audio: {
17
35
  amplitude: 0,
18
36
  bands: { sub: 0, low: 0, mid: 0, high: 0 },
19
37
  fft: [],
38
+ onset: 0,
39
+ onsets: { sub: 0, low: 0, mid: 0, high: 0 },
40
+ drums: { kick: 0, snare: 0, hihat: 0 },
20
41
  beat: false,
42
+ beat_pulse: 0,
21
43
  beat_count: 0,
22
44
  bpm: 0
23
45
  },
@@ -56,6 +78,32 @@ export class Engine {
56
78
  this.lastMediaTime = null;
57
79
  }
58
80
 
81
+ setVisualSettings(settings = {}) {
82
+ this.visualSettings = {
83
+ ...this.visualSettings,
84
+ ...settings,
85
+ };
86
+ }
87
+
88
+ setLiveControls(controls = {}) {
89
+ this.liveControls = {
90
+ blackout: !!controls?.blackout,
91
+ freeze: !!controls?.freeze,
92
+ };
93
+ }
94
+
95
+ setRuntimeGlobals(globals = {}) {
96
+ this.runtimeGlobals = globals && typeof globals === "object" ? { ...globals } : {};
97
+ }
98
+
99
+ setShaderParamOverrides(overrides = {}) {
100
+ this.shaderParamOverrides = overrides && typeof overrides === "object" ? overrides : {};
101
+ }
102
+
103
+ setShapeEditorOverrides(overrides = {}) {
104
+ this.shapeEditorOverrides = overrides && typeof overrides === "object" ? overrides : {};
105
+ }
106
+
59
107
  start() {
60
108
  this.lastTime = performance.now();
61
109
  requestAnimationFrame((time) => this.render(time));
@@ -94,8 +142,30 @@ export class Engine {
94
142
  this.lastMediaTime = null;
95
143
  }
96
144
 
97
- const audio = this.frame?.audio || {};
98
- const layers = Array.isArray(this.frame?.scene?.layers) ? this.frame.scene.layers : [];
145
+ if (this.liveControls.blackout) {
146
+ this.gl.clearColor(0, 0, 0, 1);
147
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
148
+ requestAnimationFrame((nextTime) => this.render(nextTime));
149
+ return;
150
+ }
151
+
152
+ if (this.liveControls.freeze) {
153
+ requestAnimationFrame((nextTime) => this.render(nextTime));
154
+ return;
155
+ }
156
+
157
+ const rawAudio = this.frame?.audio || {};
158
+ const audio = applyVisualSettings({
159
+ audio: rawAudio,
160
+ settings: this.visualSettings,
161
+ previous: this.visualAudioState,
162
+ });
163
+ this.visualAudioState = audio;
164
+ const rawLayers = Array.isArray(this.frame?.scene?.layers) ? this.frame.scene.layers : [];
165
+ const layers = applyShapeEditorOverrides(
166
+ applyShaderParamOverrides(rawLayers, this.shaderParamOverrides),
167
+ this.shapeEditorOverrides
168
+ );
99
169
  const amplitude = clamp(Number(audio.amplitude || 0), 0, 1);
100
170
  const rotationSpeed = resolveRotationSpeed(layers, amplitude);
101
171
  this.currentRotationSpeed += (rotationSpeed - this.currentRotationSpeed) * 0.1;
@@ -114,7 +184,9 @@ export class Engine {
114
184
  audio,
115
185
  time: visualTimeSeconds,
116
186
  rotation: this.rotation,
117
- resolution: [this.canvas.width, this.canvas.height]
187
+ resolution: [this.canvas.width, this.canvas.height],
188
+ globals: this.runtimeGlobals,
189
+ visualSettings: this.visualSettings
118
190
  });
119
191
 
120
192
  requestAnimationFrame((nextTime) => this.render(nextTime));
@@ -132,4 +204,86 @@ const resolveRotationSpeed = (layers, amplitude) => {
132
204
  return 0.7 + amplitude * 2.4;
133
205
  };
134
206
 
207
+ export const applyVisualSettings = ({ audio, settings, previous }) => {
208
+ const visualGain = clamp(Number(settings?.visualGain ?? 1), 0.1, 16);
209
+ const bassBoost = clamp(Number(settings?.bassBoost ?? 1), 0, 8);
210
+ const smoothing = clamp(Number(settings?.smoothing ?? 0), 0, 0.95);
211
+ const wobbleAmount = clamp(Number(settings?.wobbleAmount ?? 1), 0, 8);
212
+
213
+ const rawBands = audio?.bands || {};
214
+ const rawOnsets = audio?.onsets || {};
215
+ const rawDrums = audio?.drums || {};
216
+ const next = {
217
+ ...audio,
218
+ amplitude: clamp(Number(audio?.amplitude || 0) * visualGain, 0, 1),
219
+ bands: {
220
+ ...rawBands,
221
+ sub: clamp(Number(rawBands.sub || 0) * bassBoost, 0, 1),
222
+ low: clamp(Number(rawBands.low || 0) * bassBoost, 0, 1),
223
+ mid: clamp(Number(rawBands.mid || 0) * visualGain, 0, 1),
224
+ high: clamp(Number(rawBands.high || 0) * visualGain, 0, 1),
225
+ },
226
+ onset: clamp(Number(audio?.onset || 0) * visualGain, 0, 1),
227
+ onsets: {
228
+ ...rawOnsets,
229
+ sub: clamp(Number(rawOnsets.sub || 0) * bassBoost, 0, 1),
230
+ low: clamp(Number(rawOnsets.low || 0) * bassBoost, 0, 1),
231
+ mid: clamp(Number(rawOnsets.mid || 0) * visualGain, 0, 1),
232
+ high: clamp(Number(rawOnsets.high || 0) * visualGain, 0, 1),
233
+ },
234
+ drums: {
235
+ ...rawDrums,
236
+ kick: clamp(Number(rawDrums.kick || 0) * bassBoost, 0, 1),
237
+ snare: clamp(Number(rawDrums.snare || 0) * visualGain, 0, 1),
238
+ hihat: clamp(Number(rawDrums.hihat || 0) * visualGain, 0, 1),
239
+ },
240
+ visual_gain: visualGain,
241
+ bass_boost: bassBoost,
242
+ wobble_amount: wobbleAmount,
243
+ };
244
+
245
+ if (isSilentAudio(next)) {
246
+ return next;
247
+ }
248
+
249
+ if (!previous || smoothing <= 0) {
250
+ return next;
251
+ }
252
+
253
+ const previousBands = previous.bands || {};
254
+ const alpha = 1 - smoothing;
255
+ return {
256
+ ...next,
257
+ amplitude: previous.amplitude + (next.amplitude - previous.amplitude) * alpha,
258
+ bands: {
259
+ ...next.bands,
260
+ sub: Number(previousBands.sub || 0) + (next.bands.sub - Number(previousBands.sub || 0)) * alpha,
261
+ low: Number(previousBands.low || 0) + (next.bands.low - Number(previousBands.low || 0)) * alpha,
262
+ mid: Number(previousBands.mid || 0) + (next.bands.mid - Number(previousBands.mid || 0)) * alpha,
263
+ high: Number(previousBands.high || 0) + (next.bands.high - Number(previousBands.high || 0)) * alpha,
264
+ },
265
+ };
266
+ };
267
+
268
+ const isSilentAudio = (audio) => {
269
+ const bands = audio?.bands || {};
270
+ const onsets = audio?.onsets || {};
271
+ const drums = audio?.drums || {};
272
+ return Number(audio?.amplitude || 0) <= 0
273
+ && Number(audio?.onset || 0) <= 0
274
+ && Number(audio?.beat_pulse || 0) <= 0
275
+ && !audio?.beat
276
+ && Number(bands.sub || 0) <= 0
277
+ && Number(bands.low || 0) <= 0
278
+ && Number(bands.mid || 0) <= 0
279
+ && Number(bands.high || 0) <= 0
280
+ && Number(onsets.sub || 0) <= 0
281
+ && Number(onsets.low || 0) <= 0
282
+ && Number(onsets.mid || 0) <= 0
283
+ && Number(onsets.high || 0) <= 0
284
+ && Number(drums.kick || 0) <= 0
285
+ && Number(drums.snare || 0) <= 0
286
+ && Number(drums.hihat || 0) <= 0;
287
+ };
288
+
135
289
  const clamp = (value, min, max) => Math.min(Math.max(value, min), max);