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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +70 -117
  3. data/docs/.nojekyll +0 -0
  4. data/docs/assets/playground-worker.js +373 -0
  5. data/docs/assets/playground.css +440 -0
  6. data/docs/assets/playground.js +652 -0
  7. data/docs/assets/site.css +744 -0
  8. data/docs/assets/vizcore-demo.gif +0 -0
  9. data/docs/assets/vizcore-poster.png +0 -0
  10. data/docs/assets/vj-tunnel.js +159 -0
  11. data/docs/index.html +225 -0
  12. data/docs/playground.html +81 -0
  13. data/docs/shape_dsl.md +269 -0
  14. data/examples/README.md +59 -0
  15. data/examples/assets/README.md +19 -0
  16. data/examples/audio_inspector.rb +34 -0
  17. data/examples/club_intro_drop.rb +78 -0
  18. data/examples/kansai_rubykaigi_visual.rb +70 -0
  19. data/examples/live_coding_minimal.rb +22 -0
  20. data/examples/midi_controller_show.rb +78 -0
  21. data/examples/midi_scene_switch.rb +3 -1
  22. data/examples/parser_visualizer.rb +48 -0
  23. data/examples/readme_demo.rb +17 -0
  24. data/examples/rhythm_geometry.rb +34 -0
  25. data/examples/ruby_crystal_show.rb +35 -0
  26. data/examples/shader_playground.rb +18 -0
  27. data/examples/unyo_liquid.rb +59 -0
  28. data/examples/vj_ambient_chill_room.rb +124 -0
  29. data/examples/vj_dnb_jungle.rb +170 -0
  30. data/examples/vj_festival_mainstage.rb +245 -0
  31. data/examples/vj_festival_mainstage.yml +17 -0
  32. data/examples/vj_glitch_industrial.rb +164 -0
  33. data/examples/vj_hiphop_cipher.rb +167 -0
  34. data/examples/vj_jpop_idol_live.rb +210 -0
  35. data/examples/vj_synthwave_retro.rb +173 -0
  36. data/examples/vj_techno_warehouse.rb +195 -0
  37. data/frontend/index.html +494 -2
  38. data/frontend/src/audio-inspector.js +40 -0
  39. data/frontend/src/custom-shape-param-controls.js +106 -0
  40. data/frontend/src/live-controls.js +131 -0
  41. data/frontend/src/main.js +1060 -16
  42. data/frontend/src/mapping-target-selector.js +109 -0
  43. data/frontend/src/midi-learn.js +194 -0
  44. data/frontend/src/performance-monitor.js +183 -0
  45. data/frontend/src/plugin-runtime.js +130 -0
  46. data/frontend/src/projector-mode.js +56 -0
  47. data/frontend/src/renderer/engine.js +157 -3
  48. data/frontend/src/renderer/layer-manager.js +442 -30
  49. data/frontend/src/renderer/shader-manager.js +26 -0
  50. data/frontend/src/runtime-control-preset.js +11 -0
  51. data/frontend/src/shader-error-overlay.js +29 -0
  52. data/frontend/src/shader-param-controls.js +93 -0
  53. data/frontend/src/shaders/builtins.js +380 -2
  54. data/frontend/src/shaders/post-effects.js +52 -0
  55. data/frontend/src/shape-editor-controls.js +157 -0
  56. data/frontend/src/visual-regression.js +67 -0
  57. data/frontend/src/visual-settings-preset.js +103 -0
  58. data/frontend/src/visuals/geometry.js +666 -0
  59. data/frontend/src/visuals/image-renderer.js +291 -0
  60. data/frontend/src/visuals/particle-system.js +56 -10
  61. data/frontend/src/visuals/shape-renderer.js +475 -0
  62. data/frontend/src/visuals/spectrogram-renderer.js +226 -0
  63. data/frontend/src/visuals/svg-arc.js +104 -0
  64. data/frontend/src/visuals/text-renderer.js +112 -11
  65. data/frontend/src/websocket-client.js +12 -1
  66. data/lib/vizcore/analysis/adaptive_normalizer.rb +70 -0
  67. data/lib/vizcore/analysis/beat_detector.rb +4 -2
  68. data/lib/vizcore/analysis/bpm_estimator.rb +8 -0
  69. data/lib/vizcore/analysis/feature_recorder.rb +159 -0
  70. data/lib/vizcore/analysis/feature_replay.rb +84 -0
  71. data/lib/vizcore/analysis/pipeline.rb +235 -11
  72. data/lib/vizcore/analysis/tap_tempo.rb +74 -0
  73. data/lib/vizcore/analysis.rb +4 -0
  74. data/lib/vizcore/audio/dummy_sine_input.rb +1 -1
  75. data/lib/vizcore/audio/fixture_input.rb +65 -0
  76. data/lib/vizcore/audio/input_manager.rb +4 -2
  77. data/lib/vizcore/audio/mic_input.rb +24 -8
  78. data/lib/vizcore/audio/portaudio_ffi.rb +106 -1
  79. data/lib/vizcore/audio.rb +1 -0
  80. data/lib/vizcore/cli/doctor.rb +159 -0
  81. data/lib/vizcore/cli/dsl_reference.rb +99 -0
  82. data/lib/vizcore/cli/layer_docs.rb +46 -0
  83. data/lib/vizcore/cli/scene_diagnostics.rb +23 -0
  84. data/lib/vizcore/cli/scene_inspector.rb +136 -0
  85. data/lib/vizcore/cli/scene_validator.rb +337 -0
  86. data/lib/vizcore/cli/shader_template.rb +68 -0
  87. data/lib/vizcore/cli/shader_uniform_docs.rb +54 -0
  88. data/lib/vizcore/cli.rb +689 -18
  89. data/lib/vizcore/config.rb +103 -2
  90. data/lib/vizcore/control_preset.rb +68 -0
  91. data/lib/vizcore/dsl/engine.rb +277 -5
  92. data/lib/vizcore/dsl/layer_builder.rb +1280 -23
  93. data/lib/vizcore/dsl/layer_group_builder.rb +112 -0
  94. data/lib/vizcore/dsl/mapping_resolver.rb +290 -7
  95. data/lib/vizcore/dsl/mapping_transform_builder.rb +71 -0
  96. data/lib/vizcore/dsl/reaction_builder.rb +44 -0
  97. data/lib/vizcore/dsl/scene_builder.rb +61 -5
  98. data/lib/vizcore/dsl/shader_source_resolver.rb +67 -6
  99. data/lib/vizcore/dsl/style_builder.rb +68 -0
  100. data/lib/vizcore/dsl/timeline_builder.rb +138 -0
  101. data/lib/vizcore/dsl/transition_controller.rb +77 -0
  102. data/lib/vizcore/dsl.rb +5 -1
  103. data/lib/vizcore/layer_catalog.rb +275 -0
  104. data/lib/vizcore/project_manifest.rb +152 -0
  105. data/lib/vizcore/renderer/png_writer.rb +57 -0
  106. data/lib/vizcore/renderer/render_sequence.rb +153 -0
  107. data/lib/vizcore/renderer/scene_frame_source.rb +132 -0
  108. data/lib/vizcore/renderer/scene_serializer.rb +36 -3
  109. data/lib/vizcore/renderer/snapshot.rb +38 -0
  110. data/lib/vizcore/renderer/snapshot_renderer.rb +938 -0
  111. data/lib/vizcore/renderer.rb +5 -0
  112. data/lib/vizcore/server/frame_broadcaster.rb +143 -8
  113. data/lib/vizcore/server/gallery_app.rb +155 -0
  114. data/lib/vizcore/server/gallery_page.rb +100 -0
  115. data/lib/vizcore/server/gallery_runner.rb +48 -0
  116. data/lib/vizcore/server/rack_app.rb +203 -4
  117. data/lib/vizcore/server/runner.rb +391 -22
  118. data/lib/vizcore/server/scene_dependency_watcher.rb +79 -0
  119. data/lib/vizcore/server/websocket_handler.rb +60 -10
  120. data/lib/vizcore/server.rb +4 -0
  121. data/lib/vizcore/shape.rb +719 -0
  122. data/lib/vizcore/sync/osc_message.rb +103 -0
  123. data/lib/vizcore/sync/osc_receiver.rb +68 -0
  124. data/lib/vizcore/sync.rb +4 -0
  125. data/lib/vizcore/templates/midi_control_scene.rb +3 -1
  126. data/lib/vizcore/templates/plugin_layer.rb +20 -0
  127. data/lib/vizcore/templates/plugin_readme.md +23 -0
  128. data/lib/vizcore/templates/plugin_renderer.js +43 -0
  129. data/lib/vizcore/templates/plugin_scene.rb +14 -0
  130. data/lib/vizcore/templates/project_readme.md +7 -23
  131. data/lib/vizcore/templates/rubykaigi_scene.rb +30 -0
  132. data/lib/vizcore/version.rb +1 -1
  133. data/lib/vizcore.rb +28 -0
  134. data/scripts/browser_capture.mjs +75 -0
  135. data/sig/vizcore.rbs +461 -0
  136. metadata +94 -3
  137. 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">&gt;</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) =&gt; :rotation_speed
118
+ map frequency_band(:high) =&gt; :effect_intensity
119
+ end
120
+
121
+ layer :storm do
122
+ type :particle_field
123
+ count 9000
124
+ blend :add
125
+ map amplitude =&gt; :speed
126
+ map beat? =&gt; :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? =&gt; :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>