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.
- checksums.yaml +4 -4
- data/README.md +70 -117
- data/docs/.nojekyll +0 -0
- data/docs/assets/playground-worker.js +373 -0
- data/docs/assets/playground.css +440 -0
- data/docs/assets/playground.js +652 -0
- data/docs/assets/site.css +744 -0
- data/docs/assets/vizcore-demo.gif +0 -0
- data/docs/assets/vizcore-poster.png +0 -0
- data/docs/assets/vj-tunnel.js +159 -0
- data/docs/index.html +225 -0
- data/docs/playground.html +81 -0
- data/docs/shape_dsl.md +269 -0
- data/examples/README.md +59 -0
- data/examples/assets/README.md +19 -0
- data/examples/audio_inspector.rb +34 -0
- data/examples/club_intro_drop.rb +78 -0
- data/examples/kansai_rubykaigi_visual.rb +70 -0
- data/examples/live_coding_minimal.rb +22 -0
- data/examples/midi_controller_show.rb +78 -0
- data/examples/midi_scene_switch.rb +3 -1
- data/examples/parser_visualizer.rb +48 -0
- data/examples/readme_demo.rb +17 -0
- data/examples/rhythm_geometry.rb +34 -0
- data/examples/ruby_crystal_show.rb +35 -0
- data/examples/shader_playground.rb +18 -0
- data/examples/unyo_liquid.rb +59 -0
- data/examples/vj_ambient_chill_room.rb +124 -0
- data/examples/vj_dnb_jungle.rb +170 -0
- data/examples/vj_festival_mainstage.rb +245 -0
- data/examples/vj_festival_mainstage.yml +17 -0
- data/examples/vj_glitch_industrial.rb +164 -0
- data/examples/vj_hiphop_cipher.rb +167 -0
- data/examples/vj_jpop_idol_live.rb +210 -0
- data/examples/vj_synthwave_retro.rb +173 -0
- data/examples/vj_techno_warehouse.rb +195 -0
- data/frontend/index.html +494 -2
- data/frontend/src/audio-inspector.js +40 -0
- data/frontend/src/custom-shape-param-controls.js +106 -0
- data/frontend/src/live-controls.js +131 -0
- data/frontend/src/main.js +1060 -16
- data/frontend/src/mapping-target-selector.js +109 -0
- data/frontend/src/midi-learn.js +194 -0
- data/frontend/src/performance-monitor.js +183 -0
- data/frontend/src/plugin-runtime.js +130 -0
- data/frontend/src/projector-mode.js +56 -0
- data/frontend/src/renderer/engine.js +157 -3
- data/frontend/src/renderer/layer-manager.js +442 -30
- data/frontend/src/renderer/shader-manager.js +26 -0
- data/frontend/src/runtime-control-preset.js +11 -0
- data/frontend/src/shader-error-overlay.js +29 -0
- data/frontend/src/shader-param-controls.js +93 -0
- data/frontend/src/shaders/builtins.js +380 -2
- data/frontend/src/shaders/post-effects.js +52 -0
- data/frontend/src/shape-editor-controls.js +157 -0
- data/frontend/src/visual-regression.js +67 -0
- data/frontend/src/visual-settings-preset.js +103 -0
- data/frontend/src/visuals/geometry.js +666 -0
- data/frontend/src/visuals/image-renderer.js +291 -0
- data/frontend/src/visuals/particle-system.js +56 -10
- data/frontend/src/visuals/shape-renderer.js +475 -0
- data/frontend/src/visuals/spectrogram-renderer.js +226 -0
- data/frontend/src/visuals/svg-arc.js +104 -0
- data/frontend/src/visuals/text-renderer.js +112 -11
- data/frontend/src/websocket-client.js +12 -1
- data/lib/vizcore/analysis/adaptive_normalizer.rb +70 -0
- data/lib/vizcore/analysis/beat_detector.rb +4 -2
- data/lib/vizcore/analysis/bpm_estimator.rb +8 -0
- data/lib/vizcore/analysis/feature_recorder.rb +159 -0
- data/lib/vizcore/analysis/feature_replay.rb +84 -0
- data/lib/vizcore/analysis/pipeline.rb +235 -11
- data/lib/vizcore/analysis/tap_tempo.rb +74 -0
- data/lib/vizcore/analysis.rb +4 -0
- data/lib/vizcore/audio/dummy_sine_input.rb +1 -1
- data/lib/vizcore/audio/fixture_input.rb +65 -0
- data/lib/vizcore/audio/input_manager.rb +4 -2
- data/lib/vizcore/audio/mic_input.rb +24 -8
- data/lib/vizcore/audio/portaudio_ffi.rb +106 -1
- data/lib/vizcore/audio.rb +1 -0
- data/lib/vizcore/cli/doctor.rb +159 -0
- data/lib/vizcore/cli/dsl_reference.rb +99 -0
- data/lib/vizcore/cli/layer_docs.rb +46 -0
- data/lib/vizcore/cli/scene_diagnostics.rb +23 -0
- data/lib/vizcore/cli/scene_inspector.rb +136 -0
- data/lib/vizcore/cli/scene_validator.rb +337 -0
- data/lib/vizcore/cli/shader_template.rb +68 -0
- data/lib/vizcore/cli/shader_uniform_docs.rb +54 -0
- data/lib/vizcore/cli.rb +689 -18
- data/lib/vizcore/config.rb +103 -2
- data/lib/vizcore/control_preset.rb +68 -0
- data/lib/vizcore/dsl/engine.rb +277 -5
- data/lib/vizcore/dsl/layer_builder.rb +1280 -23
- data/lib/vizcore/dsl/layer_group_builder.rb +112 -0
- data/lib/vizcore/dsl/mapping_resolver.rb +290 -7
- data/lib/vizcore/dsl/mapping_transform_builder.rb +71 -0
- data/lib/vizcore/dsl/reaction_builder.rb +44 -0
- data/lib/vizcore/dsl/scene_builder.rb +61 -5
- data/lib/vizcore/dsl/shader_source_resolver.rb +67 -6
- data/lib/vizcore/dsl/style_builder.rb +68 -0
- data/lib/vizcore/dsl/timeline_builder.rb +138 -0
- data/lib/vizcore/dsl/transition_controller.rb +77 -0
- data/lib/vizcore/dsl.rb +5 -1
- data/lib/vizcore/layer_catalog.rb +275 -0
- data/lib/vizcore/project_manifest.rb +152 -0
- data/lib/vizcore/renderer/png_writer.rb +57 -0
- data/lib/vizcore/renderer/render_sequence.rb +153 -0
- data/lib/vizcore/renderer/scene_frame_source.rb +132 -0
- data/lib/vizcore/renderer/scene_serializer.rb +36 -3
- data/lib/vizcore/renderer/snapshot.rb +38 -0
- data/lib/vizcore/renderer/snapshot_renderer.rb +938 -0
- data/lib/vizcore/renderer.rb +5 -0
- data/lib/vizcore/server/frame_broadcaster.rb +143 -8
- data/lib/vizcore/server/gallery_app.rb +155 -0
- data/lib/vizcore/server/gallery_page.rb +100 -0
- data/lib/vizcore/server/gallery_runner.rb +48 -0
- data/lib/vizcore/server/rack_app.rb +203 -4
- data/lib/vizcore/server/runner.rb +391 -22
- data/lib/vizcore/server/scene_dependency_watcher.rb +79 -0
- data/lib/vizcore/server/websocket_handler.rb +60 -10
- data/lib/vizcore/server.rb +4 -0
- data/lib/vizcore/shape.rb +719 -0
- data/lib/vizcore/sync/osc_message.rb +103 -0
- data/lib/vizcore/sync/osc_receiver.rb +68 -0
- data/lib/vizcore/sync.rb +4 -0
- data/lib/vizcore/templates/midi_control_scene.rb +3 -1
- data/lib/vizcore/templates/plugin_layer.rb +20 -0
- data/lib/vizcore/templates/plugin_readme.md +23 -0
- data/lib/vizcore/templates/plugin_renderer.js +43 -0
- data/lib/vizcore/templates/plugin_scene.rb +14 -0
- data/lib/vizcore/templates/project_readme.md +7 -23
- data/lib/vizcore/templates/rubykaigi_scene.rb +30 -0
- data/lib/vizcore/version.rb +1 -1
- data/lib/vizcore.rb +28 -0
- data/scripts/browser_capture.mjs +75 -0
- data/sig/vizcore.rbs +461 -0
- metadata +94 -3
- 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
|
-
|
|
98
|
-
|
|
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);
|