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.
- checksums.yaml +4 -4
- data/README.md +544 -9
- data/docs/.nojekyll +0 -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 +224 -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 +468 -2
- data/frontend/src/audio-inspector.js +40 -0
- data/frontend/src/live-controls.js +131 -0
- data/frontend/src/main.js +792 -16
- 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 +148 -3
- data/frontend/src/renderer/layer-manager.js +428 -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/visual-regression.js +67 -0
- data/frontend/src/visual-settings-preset.js +103 -0
- data/frontend/src/visuals/geometry.js +268 -0
- data/frontend/src/visuals/image-renderer.js +291 -0
- data/frontend/src/visuals/particle-system.js +56 -10
- data/frontend/src/visuals/spectrogram-renderer.js +226 -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 +245 -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 +491 -22
- data/lib/vizcore/dsl/layer_group_builder.rb +112 -0
- data/lib/vizcore/dsl/mapping_resolver.rb +132 -3
- 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 +273 -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 +119 -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 +446 -0
- data/lib/vizcore/renderer.rb +5 -0
- data/lib/vizcore/server/frame_broadcaster.rb +91 -5
- 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 +370 -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/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 +27 -0
- data/scripts/browser_capture.mjs +75 -0
- data/sig/vizcore.rbs +362 -0
- metadata +83 -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,224 @@
|
|
|
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
|
+
</nav>
|
|
29
|
+
<a class="nav-cta" href="https://github.com/ydah/vizcore">GitHub</a>
|
|
30
|
+
</header>
|
|
31
|
+
|
|
32
|
+
<section class="hero" id="top" aria-labelledby="hero-title">
|
|
33
|
+
<canvas id="vj-field" aria-hidden="true"></canvas>
|
|
34
|
+
<div class="hero-grid" aria-hidden="true"></div>
|
|
35
|
+
<div class="hero-copy">
|
|
36
|
+
<p class="kicker">Ruby DSL / Audio Analysis / Browser Renderer</p>
|
|
37
|
+
<h1 id="hero-title">Vizcore</h1>
|
|
38
|
+
<p class="hero-title-sub">Ruby DSL for audio-reactive VJ visuals.</p>
|
|
39
|
+
<p class="hero-lede">
|
|
40
|
+
Vizcore connects scene definitions, FFT analysis, beat detection, MIDI control,
|
|
41
|
+
and WebSocket streaming into one Ruby-first workflow for live visuals.
|
|
42
|
+
</p>
|
|
43
|
+
<div class="hero-actions" aria-label="Primary actions">
|
|
44
|
+
<a class="button is-primary" href="#install">Start a scene</a>
|
|
45
|
+
<a class="button is-secondary" href="https://github.com/ydah/vizcore">View source</a>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="hero-meta" aria-label="Vizcore highlights">
|
|
48
|
+
<span><strong>Scene DSL</strong> Ruby-first show files</span>
|
|
49
|
+
<span><strong>Realtime</strong> FFT, beats, MIDI</span>
|
|
50
|
+
<span><strong>Browser</strong> GLSL and canvas output</span>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="terminal-strip" aria-label="Quick start commands">
|
|
53
|
+
<span class="terminal-line"><span class="prompt">$</span> gem install vizcore</span>
|
|
54
|
+
<span class="terminal-line"><span class="prompt">$</span> vizcore start examples/basic.rb</span>
|
|
55
|
+
<span class="terminal-line"><span class="prompt">></span> open http://127.0.0.1:4567</span>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<a class="scroll-cue" href="#signal">Signal chain</a>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
<main id="main">
|
|
62
|
+
<section class="section" id="signal" aria-labelledby="signal-title">
|
|
63
|
+
<div class="section-inner signal-layout">
|
|
64
|
+
<div class="section-copy">
|
|
65
|
+
<p class="section-kicker">Signal Chain</p>
|
|
66
|
+
<h2 id="signal-title">Turn sound into frames, then frames into light.</h2>
|
|
67
|
+
<p>
|
|
68
|
+
Analyze microphones, WAV, MP3, FLAC, dummy sources, and MIDI events,
|
|
69
|
+
then stream amplitude, frequency bands, BPM, beats, and scene data into
|
|
70
|
+
the browser renderer.
|
|
71
|
+
</p>
|
|
72
|
+
<div class="signal-steps" aria-label="Vizcore signal flow">
|
|
73
|
+
<div class="signal-step">
|
|
74
|
+
<strong>Input</strong>
|
|
75
|
+
<span>mic / file / dummy / MIDI</span>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="signal-step">
|
|
78
|
+
<strong>Analyze</strong>
|
|
79
|
+
<span>amplitude / FFT / bands / beat</span>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="signal-step">
|
|
82
|
+
<strong>Render</strong>
|
|
83
|
+
<span>GLSL / video / particles / spectrogram</span>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
<figure class="poster">
|
|
88
|
+
<img src="assets/vizcore-demo.gif" width="640" height="360" loading="lazy" alt="Animated Vizcore demo where detected beats expand concentric rings" />
|
|
89
|
+
<figcaption>readme_demo.rb: beat_pulse -> ring radius</figcaption>
|
|
90
|
+
</figure>
|
|
91
|
+
</div>
|
|
92
|
+
</section>
|
|
93
|
+
|
|
94
|
+
<section class="section section-dark" id="dsl" aria-labelledby="dsl-title">
|
|
95
|
+
<div class="section-inner split-layout">
|
|
96
|
+
<div class="section-copy">
|
|
97
|
+
<p class="section-kicker">Scene DSL</p>
|
|
98
|
+
<h2 id="dsl-title">Write the show structure in Ruby.</h2>
|
|
99
|
+
<p>
|
|
100
|
+
Define layers, shaders, transitions, and audio mappings with a Ruby DSL.
|
|
101
|
+
Connect peaks, low-end pressure, high-frequency motion, and beat counts
|
|
102
|
+
directly to visual parameters.
|
|
103
|
+
</p>
|
|
104
|
+
<ul class="feature-list">
|
|
105
|
+
<li>Use scenes and transitions to script the flow of a live set.</li>
|
|
106
|
+
<li>Map frequency_band, amplitude, and beat? into visual parameters.</li>
|
|
107
|
+
<li>Switch scenes and tune intensity with MIDI notes and CC values.</li>
|
|
108
|
+
</ul>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="code-stage" aria-label="Ruby scene DSL example">
|
|
111
|
+
<pre><code>Vizcore.define do
|
|
112
|
+
scene :drop do
|
|
113
|
+
layer :bg do
|
|
114
|
+
shader :kaleidoscope
|
|
115
|
+
effect :glitch
|
|
116
|
+
map frequency_band(:low) => :rotation_speed
|
|
117
|
+
map frequency_band(:high) => :effect_intensity
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
layer :storm do
|
|
121
|
+
type :particle_field
|
|
122
|
+
count 9000
|
|
123
|
+
blend :add
|
|
124
|
+
map amplitude => :speed
|
|
125
|
+
map beat? => :flash
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
layer :scope do
|
|
129
|
+
type :waveform
|
|
130
|
+
source :audio
|
|
131
|
+
style :ribbon
|
|
132
|
+
map amplitude, to: :height
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
layer :waterfall do
|
|
136
|
+
type :spectrogram
|
|
137
|
+
scroll :vertical
|
|
138
|
+
map amplitude, to: :gain
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
layer :rings do
|
|
142
|
+
circle count: 8 do
|
|
143
|
+
radius 100
|
|
144
|
+
map bass, to: :radius
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
layer :footage do
|
|
149
|
+
type :video
|
|
150
|
+
file "assets/loop.mp4"
|
|
151
|
+
map beat? => :invert
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end</code></pre>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</section>
|
|
158
|
+
|
|
159
|
+
<section class="section" id="examples" aria-labelledby="examples-title">
|
|
160
|
+
<div class="section-inner">
|
|
161
|
+
<div class="section-head">
|
|
162
|
+
<p class="section-kicker">Show Files</p>
|
|
163
|
+
<h2 id="examples-title">Scenes you can launch now.</h2>
|
|
164
|
+
<p>
|
|
165
|
+
Start with a wireframe cube, beat-synced drops, file playback, MIDI
|
|
166
|
+
switching, and custom GLSL scenes built for pre-show testing.
|
|
167
|
+
</p>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="example-grid">
|
|
170
|
+
<article class="example-item">
|
|
171
|
+
<h3>readme_demo.rb</h3>
|
|
172
|
+
<p>One beat mapping expands concentric rings.</p>
|
|
173
|
+
<code>vizcore start examples/readme_demo.rb --audio-source file</code>
|
|
174
|
+
</article>
|
|
175
|
+
<article class="example-item">
|
|
176
|
+
<h3>intro_drop.rb</h3>
|
|
177
|
+
<p>A beat-counted transition from intro into drop.</p>
|
|
178
|
+
<code>vizcore start examples/intro_drop.rb</code>
|
|
179
|
+
</article>
|
|
180
|
+
<article class="example-item">
|
|
181
|
+
<h3>complex_audio_showcase.rb</h3>
|
|
182
|
+
<p>Layered rings, grid, particles, wireframe, and glitch energy.</p>
|
|
183
|
+
<code>vizcore start examples/complex_audio_showcase.rb --audio-source file</code>
|
|
184
|
+
</article>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</section>
|
|
188
|
+
|
|
189
|
+
<section class="section section-dark" id="install" aria-labelledby="install-title">
|
|
190
|
+
<div class="section-inner install-panel">
|
|
191
|
+
<div class="section-copy">
|
|
192
|
+
<p class="section-kicker">Install</p>
|
|
193
|
+
<h2 id="install-title">Boot it with Ruby 3.2+.</h2>
|
|
194
|
+
<p>
|
|
195
|
+
Install PortAudio and ffmpeg on macOS, or libportaudio and ffmpeg on
|
|
196
|
+
Linux, then add the gem. fftw3 is optional and speeds up FFT analysis
|
|
197
|
+
when it is available.
|
|
198
|
+
</p>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="install-commands" aria-label="Install commands">
|
|
201
|
+
<pre><code>gem install vizcore
|
|
202
|
+
vizcore start examples/basic.rb
|
|
203
|
+
|
|
204
|
+
# macOS
|
|
205
|
+
brew install portaudio ffmpeg
|
|
206
|
+
brew install fftw
|
|
207
|
+
|
|
208
|
+
# Ubuntu / Debian
|
|
209
|
+
sudo apt install -y libportaudio2 libportaudio-dev ffmpeg
|
|
210
|
+
sudo apt install -y libfftw3-dev</code></pre>
|
|
211
|
+
</div>
|
|
212
|
+
<div class="install-actions">
|
|
213
|
+
<a class="button is-primary" href="https://rubygems.org/gems/vizcore">RubyGems</a>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</section>
|
|
217
|
+
</main>
|
|
218
|
+
|
|
219
|
+
<footer class="site-footer">
|
|
220
|
+
<span>Vizcore</span>
|
|
221
|
+
<a href="https://github.com/ydah/vizcore">github.com/ydah/vizcore</a>
|
|
222
|
+
</footer>
|
|
223
|
+
</body>
|
|
224
|
+
</html>
|
data/examples/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Vizcore Examples
|
|
2
|
+
|
|
3
|
+
Run the browser gallery for launch commands and scene metadata:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
vizcore gallery
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Most examples also work directly:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
vizcore start examples/basic.rb
|
|
13
|
+
vizcore start examples/audio_inspector.rb --audio-source mic
|
|
14
|
+
vizcore start examples/audio_inspector.rb --audio-source mic --noise-gate 0.001
|
|
15
|
+
vizcore start examples/vj_techno_warehouse.rb --audio-source file --audio-file examples/assets/complex_demo_loop.wav
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Core Examples
|
|
19
|
+
|
|
20
|
+
| File | Description |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `basic.rb` | Single wireframe cube layer |
|
|
23
|
+
| `intro_drop.rb` | Beat-triggered scene transition |
|
|
24
|
+
| `file_audio_demo.rb` | File audio source walkthrough |
|
|
25
|
+
| `complex_audio_showcase.rb` | Dense multi-layer showcase |
|
|
26
|
+
| `rhythm_geometry.rb` | Drum-reactive geometric pattern |
|
|
27
|
+
| `ruby_crystal_show.rb` | Ruby-themed crystal visual |
|
|
28
|
+
| `parser_visualizer.rb` | Parser-themed token and AST sketch |
|
|
29
|
+
| `live_coding_minimal.rb` | Tiny live-coding scene |
|
|
30
|
+
| `club_intro_drop.rb` | Intro, build, drop flow |
|
|
31
|
+
| `shader_playground.rb` | Focused shader params example |
|
|
32
|
+
| `audio_inspector.rb` | Audio feature visualization |
|
|
33
|
+
| `readme_demo.rb` | Minimal beat pulse to ring radius demo |
|
|
34
|
+
| `midi_scene_switch.rb` | MIDI scene switching |
|
|
35
|
+
| `midi_controller_show.rb` | MIDI pads and CC controls |
|
|
36
|
+
| `kansai_rubykaigi_visual.rb` | Event showcase visual |
|
|
37
|
+
| `custom_shader.rb` | Custom GLSL shader |
|
|
38
|
+
| `unyo_liquid.rb` | Liquid wobble and FFT blob |
|
|
39
|
+
|
|
40
|
+
## VJ Set Examples
|
|
41
|
+
|
|
42
|
+
Ready-to-run scenes grouped by genre / mood. All scenes accept live mic input
|
|
43
|
+
or any audio file. `B` toggles Blackout, `F` toggles Freeze, and scenes that
|
|
44
|
+
declare tap tempo use Space.
|
|
45
|
+
|
|
46
|
+
| File | Genre / Mood | BPM | Scenes | Notes |
|
|
47
|
+
|---|---|---|---|---|
|
|
48
|
+
| `vj_techno_warehouse.rb` | Techno / Warehouse | 128-140 | 4 | wireframe + particles |
|
|
49
|
+
| `vj_dnb_jungle.rb` | Drum & Bass / Jungle | 170-180 | 3 | kick/snare/hihat split |
|
|
50
|
+
| `vj_ambient_chill_room.rb` | Ambient / Chill | 60-90 | 2 | beatless, drone-friendly |
|
|
51
|
+
| `vj_hiphop_cipher.rb` | HipHop / Cipher | 85-100 | 3 | text-forward |
|
|
52
|
+
| `vj_jpop_idol_live.rb` | J-POP / Idol | 130-180 | 4 | color fields + tap tempo |
|
|
53
|
+
| `vj_synthwave_retro.rb` | Synthwave / Retro | 100-120 | 3 | circle and line primitives |
|
|
54
|
+
| `vj_glitch_industrial.rb` | Glitch / Industrial | n/a | 3 | feedback + difference blend |
|
|
55
|
+
| `vj_festival_mainstage.rb` | EDM / Mainstage | 124-132 | 5 | uses `extends:` and `group` |
|
|
56
|
+
|
|
57
|
+
Use `examples/vj_festival_mainstage.yml` as a manifest pattern for file-audio
|
|
58
|
+
rehearsal. Replace `audio_file` with your set recording or switch to
|
|
59
|
+
`audio_source: mic` for live input.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Example Assets
|
|
2
|
+
|
|
3
|
+
`complex_demo_loop.wav` is bundled for repeatable file-audio testing.
|
|
4
|
+
|
|
5
|
+
For logos or show-specific art, place your own files in this directory and
|
|
6
|
+
reference them from a scene:
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
layer :logo do
|
|
10
|
+
type :svg
|
|
11
|
+
file "assets/your_logo.svg"
|
|
12
|
+
fit :contain
|
|
13
|
+
map beat_pulse, to: :scale, range: 0.9..1.08
|
|
14
|
+
end
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
No copyrighted venue logos, artist marks, photos, or fonts are bundled with
|
|
18
|
+
Vizcore examples. Keep those assets local to your project unless you have the
|
|
19
|
+
right to redistribute them.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Visualizes the same audio features shown in the HUD inspector.
|
|
4
|
+
Vizcore.define do
|
|
5
|
+
audio_normalize mode: :adaptive, window: 2.0, target: 0.85, floor: 0.001
|
|
6
|
+
|
|
7
|
+
scene :audio_inspector do
|
|
8
|
+
layer :bars do
|
|
9
|
+
shader :audio_bars
|
|
10
|
+
param :bar_count, default: 32.0, range: 12.0..64.0, step: 1.0
|
|
11
|
+
param :floor_glow, default: 0.18, range: 0.0..1.0, step: 0.02
|
|
12
|
+
blend :screen
|
|
13
|
+
map amplitude, to: :floor_glow, gain: 1.6, range: 0.08..0.5
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
layer :peak_blob do
|
|
17
|
+
type :radial_blob
|
|
18
|
+
radius 0.28
|
|
19
|
+
wobble 0.35
|
|
20
|
+
blend :add
|
|
21
|
+
map amplitude, to: :radius, range: 0.22..0.58, curve: :sqrt
|
|
22
|
+
map fft_spectrum => :spectrum
|
|
23
|
+
map treble, to: :wobble, range: 0.2..1.2
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
layer :label do
|
|
27
|
+
type :text
|
|
28
|
+
content "AUDIO"
|
|
29
|
+
font_size 76
|
|
30
|
+
glow_strength 0.2
|
|
31
|
+
map beat_pulse, to: :glow_strength, range: 0.12..0.8
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# File-audio friendly intro -> build -> drop flow.
|
|
4
|
+
Vizcore.define do
|
|
5
|
+
scene :intro do
|
|
6
|
+
layer :grid do
|
|
7
|
+
shader :neon_grid
|
|
8
|
+
opacity 0.75
|
|
9
|
+
map mid, to: :effect_intensity, range: 0.08..0.28
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
layer :title do
|
|
13
|
+
type :text
|
|
14
|
+
content "INTRO"
|
|
15
|
+
font_size 84
|
|
16
|
+
map beat_pulse, to: :glow_strength, range: 0.12..0.65
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
scene :build do
|
|
21
|
+
layer :rings do
|
|
22
|
+
shader :spectrum_rings
|
|
23
|
+
blend :screen
|
|
24
|
+
map bass, to: :effect_intensity, gain: 1.4, range: 0.16..0.5
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
layer :particles do
|
|
28
|
+
type :particle_field
|
|
29
|
+
count 2200
|
|
30
|
+
blend :add
|
|
31
|
+
map amplitude, to: :speed, range: 0.4..3.2
|
|
32
|
+
map treble, to: :sparkle, range: 0.0..0.9
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
scene :drop do
|
|
37
|
+
layer :tunnel do
|
|
38
|
+
shader :bass_tunnel
|
|
39
|
+
blend :screen
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
layer :flash do
|
|
43
|
+
shader :glitch_flash
|
|
44
|
+
blend :add
|
|
45
|
+
map beat_pulse, to: :intensity, range: 0.2..1.0, attack: 1.0, release: 0.08
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
layer :drop_text do
|
|
49
|
+
type :text
|
|
50
|
+
content "DROP"
|
|
51
|
+
font_size 122
|
|
52
|
+
blend :screen
|
|
53
|
+
map beat_pulse, to: :glow_strength, range: 0.3..1.0
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
transition from: :intro, to: :build do
|
|
58
|
+
trigger { beat_count >= 32 || frame_count >= 240 }
|
|
59
|
+
effect :crossfade, duration: 1.0
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
transition from: :build, to: :drop do
|
|
63
|
+
trigger { beat_count >= 64 || frame_count >= 480 }
|
|
64
|
+
effect :flash, duration: 0.35
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
key "i" do
|
|
68
|
+
switch_scene :intro
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
key "u" do
|
|
72
|
+
switch_scene :build
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
key "d" do
|
|
76
|
+
switch_scene :drop
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Kansai RubyKaigi visual: ruby crystal, water ripple, geometric pattern, and title.
|
|
4
|
+
Vizcore.define do
|
|
5
|
+
scene :kansai_rubykaigi do
|
|
6
|
+
layer :lake_ripple do
|
|
7
|
+
shader :liquid_wobble
|
|
8
|
+
wobble 0.18
|
|
9
|
+
warp 0.32
|
|
10
|
+
distortion 0.16
|
|
11
|
+
opacity 0.86
|
|
12
|
+
blend :screen
|
|
13
|
+
effect :feedback
|
|
14
|
+
effect_intensity 0.08
|
|
15
|
+
|
|
16
|
+
map bass, to: :wobble, gain: 0.9, range: 0.12..0.58, curve: :sqrt
|
|
17
|
+
map mid, to: :warp, gain: 1.2, range: 0.22..1.45
|
|
18
|
+
map high, to: :effect_intensity, range: 0.04..0.22
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
layer :kyoto_pattern do
|
|
22
|
+
shader :kaleidoscope
|
|
23
|
+
opacity 0.34
|
|
24
|
+
blend :add
|
|
25
|
+
vj_effect :mirror
|
|
26
|
+
effect_intensity 0.18
|
|
27
|
+
|
|
28
|
+
map mid, to: :effect_intensity, gain: 1.2, range: 0.08..0.42
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
layer :ruby_glow do
|
|
32
|
+
shader :ruby_crystal
|
|
33
|
+
facets 8.0
|
|
34
|
+
refraction 0.52
|
|
35
|
+
opacity 0.82
|
|
36
|
+
blend :screen
|
|
37
|
+
effect :bloom
|
|
38
|
+
|
|
39
|
+
map bass, to: :refraction, gain: 0.9, range: 0.34..0.86, curve: :sqrt
|
|
40
|
+
map beat_pulse, to: :effect_intensity, range: 0.12..0.72
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
layer :spark_line do
|
|
44
|
+
type :particle_field
|
|
45
|
+
count 1800
|
|
46
|
+
force_field :vortex
|
|
47
|
+
turbulence 0.18
|
|
48
|
+
blend :add
|
|
49
|
+
|
|
50
|
+
map amplitude, to: :speed, gain: 2.2, range: 0.25..4.2, curve: :sqrt
|
|
51
|
+
map bass, to: :size, gain: 2.0, range: 1.4..5.4
|
|
52
|
+
map hihat, to: :sparkle, gain: 2.4, range: 0.0..1.0
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
layer :event_title do
|
|
56
|
+
type :text
|
|
57
|
+
content "KANSAI RUBYKAIGI"
|
|
58
|
+
font "IBM Plex Sans"
|
|
59
|
+
font_size 66
|
|
60
|
+
align :center
|
|
61
|
+
color "#fff4e6"
|
|
62
|
+
stroke width: 2, color: "#120617"
|
|
63
|
+
shadow color: "#ff335f", blur: 20
|
|
64
|
+
glow_strength 0.28
|
|
65
|
+
blend :screen
|
|
66
|
+
|
|
67
|
+
map beat_pulse, to: :glow_strength, range: 0.2..0.92
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Short scene intended for live-coding demos.
|
|
4
|
+
Vizcore.define do
|
|
5
|
+
scene :main do
|
|
6
|
+
layer :pulse do
|
|
7
|
+
type :radial_blob
|
|
8
|
+
radius 0.34
|
|
9
|
+
wobble 0.2
|
|
10
|
+
map amplitude, to: :radius, range: 0.22..0.7, curve: :sqrt
|
|
11
|
+
map fft_spectrum => :spectrum
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
layer :beat_label do
|
|
15
|
+
type :text
|
|
16
|
+
content "VIZCORE"
|
|
17
|
+
font_size 86
|
|
18
|
+
glow_strength 0.2
|
|
19
|
+
map beat_pulse, to: :glow_strength, range: 0.12..0.85
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|