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
@@ -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">&gt;</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) =&gt; :rotation_speed
117
+ map frequency_band(:high) =&gt; :effect_intensity
118
+ end
119
+
120
+ layer :storm do
121
+ type :particle_field
122
+ count 9000
123
+ blend :add
124
+ map amplitude =&gt; :speed
125
+ map beat? =&gt; :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? =&gt; :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>
@@ -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