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
@@ -1,5 +1,6 @@
1
1
  import { LayerManager } from "./layer-manager.js";
2
2
  import { ShaderManager } from "./shader-manager.js";
3
+ import { applyShaderParamOverrides } from "../shader-param-controls.js";
3
4
 
4
5
  export class Engine {
5
6
  constructor(canvas) {
@@ -12,12 +13,31 @@ export class Engine {
12
13
  this.currentRotationSpeed = 0.5;
13
14
  this.mediaElement = null;
14
15
  this.lastMediaTime = null;
16
+ this.visualSettings = {
17
+ visualGain: 1,
18
+ bassBoost: 1,
19
+ smoothing: 0,
20
+ beatHoldMs: 180,
21
+ wobbleAmount: 1,
22
+ };
23
+ this.visualAudioState = null;
24
+ this.liveControls = {
25
+ blackout: false,
26
+ freeze: false,
27
+ };
28
+ this.runtimeGlobals = {};
29
+ this.shaderParamOverrides = {};
30
+ this.beatHoldUntil = 0;
15
31
  this.frame = {
16
32
  audio: {
17
33
  amplitude: 0,
18
34
  bands: { sub: 0, low: 0, mid: 0, high: 0 },
19
35
  fft: [],
36
+ onset: 0,
37
+ onsets: { sub: 0, low: 0, mid: 0, high: 0 },
38
+ drums: { kick: 0, snare: 0, hihat: 0 },
20
39
  beat: false,
40
+ beat_pulse: 0,
21
41
  beat_count: 0,
22
42
  bpm: 0
23
43
  },
@@ -56,6 +76,28 @@ export class Engine {
56
76
  this.lastMediaTime = null;
57
77
  }
58
78
 
79
+ setVisualSettings(settings = {}) {
80
+ this.visualSettings = {
81
+ ...this.visualSettings,
82
+ ...settings,
83
+ };
84
+ }
85
+
86
+ setLiveControls(controls = {}) {
87
+ this.liveControls = {
88
+ blackout: !!controls?.blackout,
89
+ freeze: !!controls?.freeze,
90
+ };
91
+ }
92
+
93
+ setRuntimeGlobals(globals = {}) {
94
+ this.runtimeGlobals = globals && typeof globals === "object" ? { ...globals } : {};
95
+ }
96
+
97
+ setShaderParamOverrides(overrides = {}) {
98
+ this.shaderParamOverrides = overrides && typeof overrides === "object" ? overrides : {};
99
+ }
100
+
59
101
  start() {
60
102
  this.lastTime = performance.now();
61
103
  requestAnimationFrame((time) => this.render(time));
@@ -94,8 +136,27 @@ export class Engine {
94
136
  this.lastMediaTime = null;
95
137
  }
96
138
 
97
- const audio = this.frame?.audio || {};
98
- const layers = Array.isArray(this.frame?.scene?.layers) ? this.frame.scene.layers : [];
139
+ if (this.liveControls.blackout) {
140
+ this.gl.clearColor(0, 0, 0, 1);
141
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
142
+ requestAnimationFrame((nextTime) => this.render(nextTime));
143
+ return;
144
+ }
145
+
146
+ if (this.liveControls.freeze) {
147
+ requestAnimationFrame((nextTime) => this.render(nextTime));
148
+ return;
149
+ }
150
+
151
+ const rawAudio = this.frame?.audio || {};
152
+ const audio = applyVisualSettings({
153
+ audio: rawAudio,
154
+ settings: this.visualSettings,
155
+ previous: this.visualAudioState,
156
+ });
157
+ this.visualAudioState = audio;
158
+ const rawLayers = Array.isArray(this.frame?.scene?.layers) ? this.frame.scene.layers : [];
159
+ const layers = applyShaderParamOverrides(rawLayers, this.shaderParamOverrides);
99
160
  const amplitude = clamp(Number(audio.amplitude || 0), 0, 1);
100
161
  const rotationSpeed = resolveRotationSpeed(layers, amplitude);
101
162
  this.currentRotationSpeed += (rotationSpeed - this.currentRotationSpeed) * 0.1;
@@ -114,7 +175,9 @@ export class Engine {
114
175
  audio,
115
176
  time: visualTimeSeconds,
116
177
  rotation: this.rotation,
117
- resolution: [this.canvas.width, this.canvas.height]
178
+ resolution: [this.canvas.width, this.canvas.height],
179
+ globals: this.runtimeGlobals,
180
+ visualSettings: this.visualSettings
118
181
  });
119
182
 
120
183
  requestAnimationFrame((nextTime) => this.render(nextTime));
@@ -132,4 +195,86 @@ const resolveRotationSpeed = (layers, amplitude) => {
132
195
  return 0.7 + amplitude * 2.4;
133
196
  };
134
197
 
198
+ export const applyVisualSettings = ({ audio, settings, previous }) => {
199
+ const visualGain = clamp(Number(settings?.visualGain ?? 1), 0.1, 16);
200
+ const bassBoost = clamp(Number(settings?.bassBoost ?? 1), 0, 8);
201
+ const smoothing = clamp(Number(settings?.smoothing ?? 0), 0, 0.95);
202
+ const wobbleAmount = clamp(Number(settings?.wobbleAmount ?? 1), 0, 8);
203
+
204
+ const rawBands = audio?.bands || {};
205
+ const rawOnsets = audio?.onsets || {};
206
+ const rawDrums = audio?.drums || {};
207
+ const next = {
208
+ ...audio,
209
+ amplitude: clamp(Number(audio?.amplitude || 0) * visualGain, 0, 1),
210
+ bands: {
211
+ ...rawBands,
212
+ sub: clamp(Number(rawBands.sub || 0) * bassBoost, 0, 1),
213
+ low: clamp(Number(rawBands.low || 0) * bassBoost, 0, 1),
214
+ mid: clamp(Number(rawBands.mid || 0) * visualGain, 0, 1),
215
+ high: clamp(Number(rawBands.high || 0) * visualGain, 0, 1),
216
+ },
217
+ onset: clamp(Number(audio?.onset || 0) * visualGain, 0, 1),
218
+ onsets: {
219
+ ...rawOnsets,
220
+ sub: clamp(Number(rawOnsets.sub || 0) * bassBoost, 0, 1),
221
+ low: clamp(Number(rawOnsets.low || 0) * bassBoost, 0, 1),
222
+ mid: clamp(Number(rawOnsets.mid || 0) * visualGain, 0, 1),
223
+ high: clamp(Number(rawOnsets.high || 0) * visualGain, 0, 1),
224
+ },
225
+ drums: {
226
+ ...rawDrums,
227
+ kick: clamp(Number(rawDrums.kick || 0) * bassBoost, 0, 1),
228
+ snare: clamp(Number(rawDrums.snare || 0) * visualGain, 0, 1),
229
+ hihat: clamp(Number(rawDrums.hihat || 0) * visualGain, 0, 1),
230
+ },
231
+ visual_gain: visualGain,
232
+ bass_boost: bassBoost,
233
+ wobble_amount: wobbleAmount,
234
+ };
235
+
236
+ if (isSilentAudio(next)) {
237
+ return next;
238
+ }
239
+
240
+ if (!previous || smoothing <= 0) {
241
+ return next;
242
+ }
243
+
244
+ const previousBands = previous.bands || {};
245
+ const alpha = 1 - smoothing;
246
+ return {
247
+ ...next,
248
+ amplitude: previous.amplitude + (next.amplitude - previous.amplitude) * alpha,
249
+ bands: {
250
+ ...next.bands,
251
+ sub: Number(previousBands.sub || 0) + (next.bands.sub - Number(previousBands.sub || 0)) * alpha,
252
+ low: Number(previousBands.low || 0) + (next.bands.low - Number(previousBands.low || 0)) * alpha,
253
+ mid: Number(previousBands.mid || 0) + (next.bands.mid - Number(previousBands.mid || 0)) * alpha,
254
+ high: Number(previousBands.high || 0) + (next.bands.high - Number(previousBands.high || 0)) * alpha,
255
+ },
256
+ };
257
+ };
258
+
259
+ const isSilentAudio = (audio) => {
260
+ const bands = audio?.bands || {};
261
+ const onsets = audio?.onsets || {};
262
+ const drums = audio?.drums || {};
263
+ return Number(audio?.amplitude || 0) <= 0
264
+ && Number(audio?.onset || 0) <= 0
265
+ && Number(audio?.beat_pulse || 0) <= 0
266
+ && !audio?.beat
267
+ && Number(bands.sub || 0) <= 0
268
+ && Number(bands.low || 0) <= 0
269
+ && Number(bands.mid || 0) <= 0
270
+ && Number(bands.high || 0) <= 0
271
+ && Number(onsets.sub || 0) <= 0
272
+ && Number(onsets.low || 0) <= 0
273
+ && Number(onsets.mid || 0) <= 0
274
+ && Number(onsets.high || 0) <= 0
275
+ && Number(drums.kick || 0) <= 0
276
+ && Number(drums.snare || 0) <= 0
277
+ && Number(drums.hihat || 0) <= 0;
278
+ };
279
+
135
280
  const clamp = (value, min, max) => Math.min(Math.max(value, min), max);