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
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const canvas = document.querySelector("#vj-field");
|
|
2
|
+
|
|
3
|
+
if (canvas) {
|
|
4
|
+
const context = canvas.getContext("2d", { alpha: true });
|
|
5
|
+
const reducedMotionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
6
|
+
const state = {
|
|
7
|
+
width: 0,
|
|
8
|
+
height: 0,
|
|
9
|
+
ratio: 1,
|
|
10
|
+
pointerX: 0,
|
|
11
|
+
pointerY: 0,
|
|
12
|
+
reduced: reducedMotionQuery.matches,
|
|
13
|
+
animationFrame: 0
|
|
14
|
+
};
|
|
15
|
+
const palette = ["#22c55e", "#38bdf8", "#a3e635", "#facc15", "#fb7185", "#8b5cf6"];
|
|
16
|
+
|
|
17
|
+
const resize = () => {
|
|
18
|
+
state.ratio = Math.min(window.devicePixelRatio || 1, 2);
|
|
19
|
+
state.width = Math.max(1, canvas.clientWidth);
|
|
20
|
+
state.height = Math.max(1, canvas.clientHeight);
|
|
21
|
+
canvas.width = Math.floor(state.width * state.ratio);
|
|
22
|
+
canvas.height = Math.floor(state.height * state.ratio);
|
|
23
|
+
context.setTransform(state.ratio, 0, 0, state.ratio, 0, 0);
|
|
24
|
+
draw(performance.now());
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const color = (index, alpha) => {
|
|
28
|
+
const hex = palette[index % palette.length];
|
|
29
|
+
const red = Number.parseInt(hex.slice(1, 3), 16);
|
|
30
|
+
const green = Number.parseInt(hex.slice(3, 5), 16);
|
|
31
|
+
const blue = Number.parseInt(hex.slice(5, 7), 16);
|
|
32
|
+
return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const drawTunnel = (time) => {
|
|
36
|
+
const centerX = state.width * (0.62 + state.pointerX * 0.08);
|
|
37
|
+
const centerY = state.height * (0.48 + state.pointerY * 0.08);
|
|
38
|
+
const radiusMax = Math.hypot(state.width, state.height) * 0.72;
|
|
39
|
+
|
|
40
|
+
context.save();
|
|
41
|
+
context.globalCompositeOperation = "lighter";
|
|
42
|
+
for (let ring = 1; ring <= 24; ring += 1) {
|
|
43
|
+
const progress = ring / 24;
|
|
44
|
+
const radius = progress * radiusMax;
|
|
45
|
+
const pulse = Math.sin(time * 0.0024 + ring * 0.72) * 14;
|
|
46
|
+
context.beginPath();
|
|
47
|
+
for (let point = 0; point <= 96; point += 1) {
|
|
48
|
+
const theta = (point / 96) * Math.PI * 2;
|
|
49
|
+
const wobble = Math.sin(theta * 5 + time * 0.0018 + ring) * 18 * progress;
|
|
50
|
+
const x = centerX + Math.cos(theta + time * 0.0004) * (radius + pulse + wobble);
|
|
51
|
+
const y = centerY + Math.sin(theta - time * 0.0003) * (radius * 0.62 + pulse + wobble);
|
|
52
|
+
if (point === 0) {
|
|
53
|
+
context.moveTo(x, y);
|
|
54
|
+
} else {
|
|
55
|
+
context.lineTo(x, y);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
context.closePath();
|
|
59
|
+
context.lineWidth = Math.max(1.4, 6 - progress * 4);
|
|
60
|
+
context.strokeStyle = color(ring, 0.32 + progress * 0.42);
|
|
61
|
+
context.stroke();
|
|
62
|
+
}
|
|
63
|
+
context.restore();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const drawLasers = (time) => {
|
|
67
|
+
const centerX = state.width * (0.62 + state.pointerX * 0.06);
|
|
68
|
+
const centerY = state.height * (0.5 + state.pointerY * 0.06);
|
|
69
|
+
const length = Math.hypot(state.width, state.height);
|
|
70
|
+
|
|
71
|
+
context.save();
|
|
72
|
+
context.globalCompositeOperation = "lighter";
|
|
73
|
+
for (let index = 0; index < 42; index += 1) {
|
|
74
|
+
const angle = index * 0.42 + time * 0.00065;
|
|
75
|
+
const start = Math.sin(time * 0.001 + index) * 34;
|
|
76
|
+
context.beginPath();
|
|
77
|
+
context.moveTo(centerX + Math.cos(angle) * start, centerY + Math.sin(angle) * start);
|
|
78
|
+
context.lineTo(centerX + Math.cos(angle) * length, centerY + Math.sin(angle) * length);
|
|
79
|
+
context.lineWidth = index % 7 === 0 ? 3 : 1.2;
|
|
80
|
+
context.strokeStyle = color(index + 2, index % 7 === 0 ? 0.62 : 0.28);
|
|
81
|
+
context.stroke();
|
|
82
|
+
}
|
|
83
|
+
context.restore();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const drawSpectrum = (time) => {
|
|
87
|
+
const bars = 46;
|
|
88
|
+
const baseline = state.height * 0.86;
|
|
89
|
+
const barWidth = state.width / bars;
|
|
90
|
+
|
|
91
|
+
context.save();
|
|
92
|
+
context.globalCompositeOperation = "lighter";
|
|
93
|
+
for (let index = 0; index < bars; index += 1) {
|
|
94
|
+
const wave = Math.sin(time * 0.004 + index * 0.55);
|
|
95
|
+
const kick = Math.sin(time * 0.0016 + index * 0.17);
|
|
96
|
+
const height = 22 + Math.abs(wave * kick) * state.height * 0.24;
|
|
97
|
+
const x = index * barWidth;
|
|
98
|
+
context.fillStyle = color(index, 0.16 + Math.abs(wave) * 0.32);
|
|
99
|
+
context.fillRect(x, baseline - height, Math.max(2, barWidth - 4), height);
|
|
100
|
+
}
|
|
101
|
+
context.restore();
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const drawNoise = (time) => {
|
|
105
|
+
context.save();
|
|
106
|
+
context.globalAlpha = 0.2;
|
|
107
|
+
context.fillStyle = "#ffffff";
|
|
108
|
+
for (let index = 0; index < 120; index += 1) {
|
|
109
|
+
const x = (Math.sin(index * 43.12 + time * 0.0017) * 0.5 + 0.5) * state.width;
|
|
110
|
+
const y = (Math.cos(index * 27.33 + time * 0.0011) * 0.5 + 0.5) * state.height;
|
|
111
|
+
context.fillRect(x, y, 1.2, 1.2);
|
|
112
|
+
}
|
|
113
|
+
context.restore();
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const draw = (time) => {
|
|
117
|
+
context.clearRect(0, 0, state.width, state.height);
|
|
118
|
+
context.fillStyle = "rgba(8, 10, 15, 0.76)";
|
|
119
|
+
context.fillRect(0, 0, state.width, state.height);
|
|
120
|
+
|
|
121
|
+
const gradient = context.createLinearGradient(0, 0, state.width, state.height);
|
|
122
|
+
gradient.addColorStop(0, "rgba(34, 197, 94, 0.24)");
|
|
123
|
+
gradient.addColorStop(0.45, "rgba(56, 189, 248, 0.14)");
|
|
124
|
+
gradient.addColorStop(1, "rgba(139, 92, 246, 0.18)");
|
|
125
|
+
context.fillStyle = gradient;
|
|
126
|
+
context.fillRect(0, 0, state.width, state.height);
|
|
127
|
+
|
|
128
|
+
drawLasers(time);
|
|
129
|
+
drawTunnel(time);
|
|
130
|
+
drawSpectrum(time);
|
|
131
|
+
drawNoise(time);
|
|
132
|
+
|
|
133
|
+
if (!state.reduced) {
|
|
134
|
+
state.animationFrame = window.requestAnimationFrame(draw);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const start = () => {
|
|
139
|
+
window.cancelAnimationFrame(state.animationFrame);
|
|
140
|
+
draw(performance.now());
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
window.addEventListener("resize", resize, { passive: true });
|
|
144
|
+
window.addEventListener(
|
|
145
|
+
"pointermove",
|
|
146
|
+
(event) => {
|
|
147
|
+
state.pointerX = event.clientX / Math.max(1, state.width) - 0.5;
|
|
148
|
+
state.pointerY = event.clientY / Math.max(1, state.height) - 0.5;
|
|
149
|
+
},
|
|
150
|
+
{ passive: true }
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
reducedMotionQuery.addEventListener("change", (event) => {
|
|
154
|
+
state.reduced = event.matches;
|
|
155
|
+
start();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
resize();
|
|
159
|
+
}
|
data/docs/index.html
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="description" content="Vizcore is a Ruby gem for audio-reactive VJ visuals, scene DSL, beat analysis, MIDI control, and browser rendering." />
|
|
7
|
+
<meta name="theme-color" content="#080a0f" />
|
|
8
|
+
<link rel="canonical" href="https://ydah.github.io/vizcore/" />
|
|
9
|
+
<meta property="og:type" content="website" />
|
|
10
|
+
<meta property="og:title" content="Vizcore - Ruby DSL for Audio-Reactive VJ Visuals" />
|
|
11
|
+
<meta property="og:description" content="Ruby DSL, audio analysis, MIDI control, and browser-rendered VJ visuals." />
|
|
12
|
+
<meta property="og:url" content="https://ydah.github.io/vizcore/" />
|
|
13
|
+
<meta property="og:image" content="https://ydah.github.io/vizcore/assets/vizcore-poster.png" />
|
|
14
|
+
<title>Vizcore - Ruby DSL for Audio-Reactive VJ Visuals</title>
|
|
15
|
+
<link rel="stylesheet" href="assets/site.css" />
|
|
16
|
+
<script src="assets/vj-tunnel.js" defer></script>
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
<a class="skip-link" href="#main">Skip to content</a>
|
|
20
|
+
|
|
21
|
+
<header class="site-header" aria-label="Site header">
|
|
22
|
+
<a class="brand" href="#top" aria-label="Vizcore home">Vizcore</a>
|
|
23
|
+
<nav class="site-nav" aria-label="Primary navigation">
|
|
24
|
+
<a href="#signal">Signal</a>
|
|
25
|
+
<a href="#dsl">DSL</a>
|
|
26
|
+
<a href="#examples">Examples</a>
|
|
27
|
+
<a href="#install">Install</a>
|
|
28
|
+
<a href="playground.html">Playground</a>
|
|
29
|
+
</nav>
|
|
30
|
+
<a class="nav-cta" href="https://github.com/ydah/vizcore">GitHub</a>
|
|
31
|
+
</header>
|
|
32
|
+
|
|
33
|
+
<section class="hero" id="top" aria-labelledby="hero-title">
|
|
34
|
+
<canvas id="vj-field" aria-hidden="true"></canvas>
|
|
35
|
+
<div class="hero-grid" aria-hidden="true"></div>
|
|
36
|
+
<div class="hero-copy">
|
|
37
|
+
<p class="kicker">Ruby DSL / Audio Analysis / Browser Renderer</p>
|
|
38
|
+
<h1 id="hero-title">Vizcore</h1>
|
|
39
|
+
<p class="hero-title-sub">Ruby DSL for audio-reactive VJ visuals.</p>
|
|
40
|
+
<p class="hero-lede">
|
|
41
|
+
Vizcore connects scene definitions, FFT analysis, beat detection, MIDI control,
|
|
42
|
+
and WebSocket streaming into one Ruby-first workflow for live visuals.
|
|
43
|
+
</p>
|
|
44
|
+
<div class="hero-actions" aria-label="Primary actions">
|
|
45
|
+
<a class="button is-primary" href="playground.html">Open playground</a>
|
|
46
|
+
<a class="button is-secondary" href="https://github.com/ydah/vizcore">View source</a>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="hero-meta" aria-label="Vizcore highlights">
|
|
49
|
+
<span><strong>Scene DSL</strong> Ruby-first show files</span>
|
|
50
|
+
<span><strong>Realtime</strong> FFT, beats, MIDI</span>
|
|
51
|
+
<span><strong>Browser</strong> GLSL and canvas output</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="terminal-strip" aria-label="Quick start commands">
|
|
54
|
+
<span class="terminal-line"><span class="prompt">$</span> gem install vizcore</span>
|
|
55
|
+
<span class="terminal-line"><span class="prompt">$</span> vizcore start examples/basic.rb</span>
|
|
56
|
+
<span class="terminal-line"><span class="prompt">></span> open http://127.0.0.1:4567</span>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<a class="scroll-cue" href="#signal">Signal chain</a>
|
|
60
|
+
</section>
|
|
61
|
+
|
|
62
|
+
<main id="main">
|
|
63
|
+
<section class="section" id="signal" aria-labelledby="signal-title">
|
|
64
|
+
<div class="section-inner signal-layout">
|
|
65
|
+
<div class="section-copy">
|
|
66
|
+
<p class="section-kicker">Signal Chain</p>
|
|
67
|
+
<h2 id="signal-title">Turn sound into frames, then frames into light.</h2>
|
|
68
|
+
<p>
|
|
69
|
+
Analyze microphones, WAV, MP3, FLAC, dummy sources, and MIDI events,
|
|
70
|
+
then stream amplitude, frequency bands, BPM, beats, and scene data into
|
|
71
|
+
the browser renderer.
|
|
72
|
+
</p>
|
|
73
|
+
<div class="signal-steps" aria-label="Vizcore signal flow">
|
|
74
|
+
<div class="signal-step">
|
|
75
|
+
<strong>Input</strong>
|
|
76
|
+
<span>mic / file / dummy / MIDI</span>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="signal-step">
|
|
79
|
+
<strong>Analyze</strong>
|
|
80
|
+
<span>amplitude / FFT / bands / beat</span>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="signal-step">
|
|
83
|
+
<strong>Render</strong>
|
|
84
|
+
<span>GLSL / video / particles / spectrogram</span>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<figure class="poster">
|
|
89
|
+
<img src="assets/vizcore-demo.gif" width="640" height="360" loading="lazy" alt="Animated Vizcore demo where detected beats expand concentric rings" />
|
|
90
|
+
<figcaption>readme_demo.rb: beat_pulse -> ring radius</figcaption>
|
|
91
|
+
</figure>
|
|
92
|
+
</div>
|
|
93
|
+
</section>
|
|
94
|
+
|
|
95
|
+
<section class="section section-dark" id="dsl" aria-labelledby="dsl-title">
|
|
96
|
+
<div class="section-inner split-layout">
|
|
97
|
+
<div class="section-copy">
|
|
98
|
+
<p class="section-kicker">Scene DSL</p>
|
|
99
|
+
<h2 id="dsl-title">Write the show structure in Ruby.</h2>
|
|
100
|
+
<p>
|
|
101
|
+
Define layers, shaders, transitions, and audio mappings with a Ruby DSL.
|
|
102
|
+
Connect peaks, low-end pressure, high-frequency motion, and beat counts
|
|
103
|
+
directly to visual parameters.
|
|
104
|
+
</p>
|
|
105
|
+
<ul class="feature-list">
|
|
106
|
+
<li>Use scenes and transitions to script the flow of a live set.</li>
|
|
107
|
+
<li>Map frequency_band, amplitude, and beat? into visual parameters.</li>
|
|
108
|
+
<li>Switch scenes and tune intensity with MIDI notes and CC values.</li>
|
|
109
|
+
</ul>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="code-stage" aria-label="Ruby scene DSL example">
|
|
112
|
+
<pre><code>Vizcore.define do
|
|
113
|
+
scene :drop do
|
|
114
|
+
layer :bg do
|
|
115
|
+
shader :kaleidoscope
|
|
116
|
+
effect :glitch
|
|
117
|
+
map frequency_band(:low) => :rotation_speed
|
|
118
|
+
map frequency_band(:high) => :effect_intensity
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
layer :storm do
|
|
122
|
+
type :particle_field
|
|
123
|
+
count 9000
|
|
124
|
+
blend :add
|
|
125
|
+
map amplitude => :speed
|
|
126
|
+
map beat? => :flash
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
layer :scope do
|
|
130
|
+
type :waveform
|
|
131
|
+
source :audio
|
|
132
|
+
style :ribbon
|
|
133
|
+
map amplitude, to: :height
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
layer :waterfall do
|
|
137
|
+
type :spectrogram
|
|
138
|
+
scroll :vertical
|
|
139
|
+
map amplitude, to: :gain
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
layer :rings do
|
|
143
|
+
circle count: 8 do
|
|
144
|
+
radius 100
|
|
145
|
+
map bass, to: :radius
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
layer :footage do
|
|
150
|
+
type :video
|
|
151
|
+
file "assets/loop.mp4"
|
|
152
|
+
map beat? => :invert
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end</code></pre>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</section>
|
|
159
|
+
|
|
160
|
+
<section class="section" id="examples" aria-labelledby="examples-title">
|
|
161
|
+
<div class="section-inner">
|
|
162
|
+
<div class="section-head">
|
|
163
|
+
<p class="section-kicker">Show Files</p>
|
|
164
|
+
<h2 id="examples-title">Scenes you can launch now.</h2>
|
|
165
|
+
<p>
|
|
166
|
+
Start with a wireframe cube, beat-synced drops, file playback, MIDI
|
|
167
|
+
switching, and custom GLSL scenes built for pre-show testing.
|
|
168
|
+
</p>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="example-grid">
|
|
171
|
+
<article class="example-item">
|
|
172
|
+
<h3>readme_demo.rb</h3>
|
|
173
|
+
<p>One beat mapping expands concentric rings.</p>
|
|
174
|
+
<code>vizcore start examples/readme_demo.rb --audio-source file</code>
|
|
175
|
+
</article>
|
|
176
|
+
<article class="example-item">
|
|
177
|
+
<h3>intro_drop.rb</h3>
|
|
178
|
+
<p>A beat-counted transition from intro into drop.</p>
|
|
179
|
+
<code>vizcore start examples/intro_drop.rb</code>
|
|
180
|
+
</article>
|
|
181
|
+
<article class="example-item">
|
|
182
|
+
<h3>complex_audio_showcase.rb</h3>
|
|
183
|
+
<p>Layered rings, grid, particles, wireframe, and glitch energy.</p>
|
|
184
|
+
<code>vizcore start examples/complex_audio_showcase.rb --audio-source file</code>
|
|
185
|
+
</article>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</section>
|
|
189
|
+
|
|
190
|
+
<section class="section section-dark" id="install" aria-labelledby="install-title">
|
|
191
|
+
<div class="section-inner install-panel">
|
|
192
|
+
<div class="section-copy">
|
|
193
|
+
<p class="section-kicker">Install</p>
|
|
194
|
+
<h2 id="install-title">Boot it with Ruby 3.2+.</h2>
|
|
195
|
+
<p>
|
|
196
|
+
Install PortAudio and ffmpeg on macOS, or libportaudio and ffmpeg on
|
|
197
|
+
Linux, then add the gem. fftw3 is optional and speeds up FFT analysis
|
|
198
|
+
when it is available.
|
|
199
|
+
</p>
|
|
200
|
+
</div>
|
|
201
|
+
<div class="install-commands" aria-label="Install commands">
|
|
202
|
+
<pre><code>gem install vizcore
|
|
203
|
+
vizcore start examples/basic.rb
|
|
204
|
+
|
|
205
|
+
# macOS
|
|
206
|
+
brew install portaudio ffmpeg
|
|
207
|
+
brew install fftw
|
|
208
|
+
|
|
209
|
+
# Ubuntu / Debian
|
|
210
|
+
sudo apt install -y libportaudio2 libportaudio-dev ffmpeg
|
|
211
|
+
sudo apt install -y libfftw3-dev</code></pre>
|
|
212
|
+
</div>
|
|
213
|
+
<div class="install-actions">
|
|
214
|
+
<a class="button is-primary" href="https://rubygems.org/gems/vizcore">RubyGems</a>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
</section>
|
|
218
|
+
</main>
|
|
219
|
+
|
|
220
|
+
<footer class="site-footer">
|
|
221
|
+
<span>Vizcore</span>
|
|
222
|
+
<a href="https://github.com/ydah/vizcore">github.com/ydah/vizcore</a>
|
|
223
|
+
</footer>
|
|
224
|
+
</body>
|
|
225
|
+
</html>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="description" content="Vizcore Ruby wasm playground for editing Ruby scene DSL and previewing audio-reactive visuals in the browser." />
|
|
7
|
+
<meta name="theme-color" content="#08110f" />
|
|
8
|
+
<link rel="canonical" href="https://ydah.github.io/vizcore/playground.html" />
|
|
9
|
+
<meta property="og:type" content="website" />
|
|
10
|
+
<meta property="og:title" content="Vizcore Playground" />
|
|
11
|
+
<meta property="og:description" content="Edit Vizcore Ruby DSL and preview audio-reactive scenes in the browser." />
|
|
12
|
+
<meta property="og:url" content="https://ydah.github.io/vizcore/playground.html" />
|
|
13
|
+
<meta property="og:image" content="https://ydah.github.io/vizcore/assets/vizcore-poster.png" />
|
|
14
|
+
<title>Vizcore Playground</title>
|
|
15
|
+
<link rel="stylesheet" href="assets/playground.css" />
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<a class="skip-link" href="#editor">Skip to editor</a>
|
|
19
|
+
|
|
20
|
+
<header class="topbar" aria-label="Playground header">
|
|
21
|
+
<a class="brand" href="index.html">Vizcore</a>
|
|
22
|
+
<nav class="topbar-nav" aria-label="Playground navigation">
|
|
23
|
+
<a href="index.html">Home</a>
|
|
24
|
+
<a href="https://github.com/ydah/vizcore">GitHub</a>
|
|
25
|
+
</nav>
|
|
26
|
+
</header>
|
|
27
|
+
|
|
28
|
+
<main class="playground-shell">
|
|
29
|
+
<section class="editor-panel" aria-labelledby="editor-title">
|
|
30
|
+
<div class="panel-header">
|
|
31
|
+
<div>
|
|
32
|
+
<p class="eyebrow">Ruby wasm</p>
|
|
33
|
+
<h1 id="editor-title">Playground</h1>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="toolbar">
|
|
36
|
+
<label class="preset-label" for="preset-select">Preset</label>
|
|
37
|
+
<select id="preset-select" class="preset-select"></select>
|
|
38
|
+
<button id="run-button" class="button primary" type="button">Run</button>
|
|
39
|
+
<button id="reset-button" class="button secondary" type="button">Reset</button>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<textarea id="editor" class="code-editor" spellcheck="false" autocomplete="off" autocapitalize="off"></textarea>
|
|
44
|
+
|
|
45
|
+
<div class="output-row">
|
|
46
|
+
<div class="status-line" aria-live="polite">
|
|
47
|
+
<span id="ruby-status">Ruby wasm idle</span>
|
|
48
|
+
<span id="compile-status">Waiting</span>
|
|
49
|
+
</div>
|
|
50
|
+
<details class="json-output">
|
|
51
|
+
<summary>Scene JSON</summary>
|
|
52
|
+
<pre id="json-output">{}</pre>
|
|
53
|
+
</details>
|
|
54
|
+
</div>
|
|
55
|
+
</section>
|
|
56
|
+
|
|
57
|
+
<section class="preview-panel" aria-labelledby="preview-title">
|
|
58
|
+
<div class="preview-header">
|
|
59
|
+
<div>
|
|
60
|
+
<p class="eyebrow">Canvas preview</p>
|
|
61
|
+
<h2 id="preview-title">Output</h2>
|
|
62
|
+
</div>
|
|
63
|
+
<div id="scene-tabs" class="scene-tabs" aria-label="Scenes"></div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="canvas-wrap">
|
|
67
|
+
<canvas id="preview-canvas" width="1280" height="720" aria-label="Vizcore visual preview"></canvas>
|
|
68
|
+
<div class="preview-meter" aria-live="polite">
|
|
69
|
+
<span id="scene-stat">Scene: --</span>
|
|
70
|
+
<span id="audio-stat">Amplitude: --</span>
|
|
71
|
+
<span id="beat-stat">Beat: --</span>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div id="error-output" class="error-output" role="alert" hidden></div>
|
|
76
|
+
</section>
|
|
77
|
+
</main>
|
|
78
|
+
|
|
79
|
+
<script type="module" src="assets/playground.js"></script>
|
|
80
|
+
</body>
|
|
81
|
+
</html>
|