vizcore 1.1.0 → 1.2.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/frontend/index.html +24 -2
- data/frontend/src/audio-inspector.js +9 -0
- data/frontend/src/live-controls.js +219 -7
- data/frontend/src/main.js +447 -57
- data/frontend/src/midi-learn.js +22 -2
- data/frontend/src/performance-monitor.js +137 -1
- data/frontend/src/renderer/engine.js +391 -10
- data/frontend/src/renderer/layer-manager.js +472 -71
- data/frontend/src/runtime-control-preset.js +44 -0
- data/frontend/src/scene-patches.js +159 -0
- data/frontend/src/shader-error-overlay.js +1 -0
- data/frontend/src/visuals/image-renderer.js +19 -0
- data/frontend/src/visuals/particle-system.js +10 -0
- data/frontend/src/visuals/shape-renderer.js +13 -0
- data/frontend/src/visuals/spectrogram-renderer.js +14 -0
- data/frontend/src/visuals/text-renderer.js +13 -0
- data/frontend/src/websocket-client.js +6 -0
- data/lib/vizcore/analysis/adaptive_normalizer.rb +20 -2
- data/lib/vizcore/analysis/bpm_estimator.rb +18 -8
- data/lib/vizcore/analysis/feature_recorder.rb +117 -7
- data/lib/vizcore/analysis/feature_replay.rb +48 -9
- data/lib/vizcore/analysis/pipeline.rb +258 -9
- data/lib/vizcore/analysis/tap_tempo.rb +17 -2
- data/lib/vizcore/audio/calibration.rb +156 -0
- data/lib/vizcore/audio/file_input.rb +28 -0
- data/lib/vizcore/audio/input_manager.rb +36 -1
- data/lib/vizcore/audio/midi_input.rb +5 -0
- data/lib/vizcore/audio/ring_buffer.rb +22 -0
- data/lib/vizcore/audio.rb +1 -0
- data/lib/vizcore/cli/dsl_reference.rb +64 -8
- data/lib/vizcore/cli/plugin_checker.rb +93 -0
- data/lib/vizcore/cli/scene_diagnostics.rb +2 -2
- data/lib/vizcore/cli/scene_inspector.rb +35 -1
- data/lib/vizcore/cli/scene_validator.rb +487 -39
- data/lib/vizcore/cli/shader_template.rb +7 -2
- data/lib/vizcore/cli/shader_uniform_docs.rb +11 -0
- data/lib/vizcore/cli.rb +268 -15
- data/lib/vizcore/config.rb +40 -3
- data/lib/vizcore/control_preset.rb +29 -0
- data/lib/vizcore/deep_copy.rb +21 -0
- data/lib/vizcore/dsl/color_helpers.rb +155 -0
- data/lib/vizcore/dsl/engine.rb +219 -23
- data/lib/vizcore/dsl/layer_builder.rb +278 -15
- data/lib/vizcore/dsl/layer_group_builder.rb +10 -12
- data/lib/vizcore/dsl/layout_helpers.rb +290 -0
- data/lib/vizcore/dsl/mapping_preset_builder.rb +41 -0
- data/lib/vizcore/dsl/mapping_resolver.rb +404 -22
- data/lib/vizcore/dsl/mapping_transform_builder.rb +50 -0
- data/lib/vizcore/dsl/midi_map_executor.rb +219 -23
- data/lib/vizcore/dsl/reaction_builder.rb +1 -0
- data/lib/vizcore/dsl/scene_builder.rb +83 -13
- data/lib/vizcore/dsl/shader_source_resolver.rb +1 -10
- data/lib/vizcore/dsl/style_builder.rb +3 -0
- data/lib/vizcore/dsl/timeline_builder.rb +91 -8
- data/lib/vizcore/dsl/transition_controller.rb +157 -18
- data/lib/vizcore/dsl.rb +2 -0
- data/lib/vizcore/layer_catalog.rb +1 -0
- data/lib/vizcore/plugin_asset_policy.rb +55 -0
- data/lib/vizcore/project_manifest.rb +12 -2
- data/lib/vizcore/renderer/render_sequence.rb +104 -13
- data/lib/vizcore/renderer/scene_frame_source.rb +179 -14
- data/lib/vizcore/renderer/scene_serializer.rb +38 -0
- data/lib/vizcore/renderer/snapshot.rb +4 -3
- data/lib/vizcore/renderer/snapshot_renderer.rb +134 -8
- data/lib/vizcore/scene_trust.rb +31 -0
- data/lib/vizcore/server/frame_broadcaster.rb +469 -23
- data/lib/vizcore/server/rack_app.rb +151 -4
- data/lib/vizcore/server/runner.rb +676 -82
- data/lib/vizcore/server/websocket_handler.rb +236 -14
- data/lib/vizcore/server.rb +21 -0
- data/lib/vizcore/shape.rb +39 -16
- data/lib/vizcore/sync/osc_message.rb +66 -9
- data/lib/vizcore/version.rb +1 -1
- data/lib/vizcore.rb +33 -0
- data/scripts/browser_capture.mjs +31 -2
- data/sig/vizcore.rbs +55 -4
- metadata +18 -3
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require "pathname"
|
|
5
5
|
require "rack"
|
|
6
6
|
require_relative "../control_preset"
|
|
7
|
+
require_relative "../plugin_asset_policy"
|
|
7
8
|
require_relative "websocket_handler"
|
|
8
9
|
|
|
9
10
|
module Vizcore
|
|
@@ -29,6 +30,7 @@ module Vizcore
|
|
|
29
30
|
# @param control_preset_path [String, Pathname, nil]
|
|
30
31
|
# @param plugin_assets [Array<String, Pathname>, nil]
|
|
31
32
|
# @param projector_mode [Boolean]
|
|
33
|
+
# @param runtime_status_provider [#call, nil]
|
|
32
34
|
def initialize(
|
|
33
35
|
frontend_root:,
|
|
34
36
|
websocket_path: "/ws",
|
|
@@ -41,7 +43,8 @@ module Vizcore
|
|
|
41
43
|
control_preset: nil,
|
|
42
44
|
control_preset_path: nil,
|
|
43
45
|
plugin_assets: nil,
|
|
44
|
-
projector_mode: false
|
|
46
|
+
projector_mode: false,
|
|
47
|
+
runtime_status_provider: nil
|
|
45
48
|
)
|
|
46
49
|
@frontend_root = frontend_root.expand_path
|
|
47
50
|
@websocket_path = websocket_path
|
|
@@ -55,6 +58,7 @@ module Vizcore
|
|
|
55
58
|
@control_preset_path = control_preset_path ? Pathname.new(control_preset_path).expand_path : nil
|
|
56
59
|
@plugin_assets = normalize_plugin_assets(plugin_assets)
|
|
57
60
|
@projector_mode = !!projector_mode
|
|
61
|
+
@runtime_status_provider = runtime_status_provider
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
# @param env [Hash]
|
|
@@ -96,7 +100,11 @@ module Vizcore
|
|
|
96
100
|
control_preset_writable: !!@control_preset_path,
|
|
97
101
|
control_preset_url: @control_preset_path ? CONTROL_PRESET_PATH : nil,
|
|
98
102
|
plugin_assets: @plugin_assets.map { |asset| asset.fetch(:url) },
|
|
99
|
-
projector_mode: @projector_mode
|
|
103
|
+
projector_mode: @projector_mode,
|
|
104
|
+
websocket_clients: WebSocketHandler.connection_count,
|
|
105
|
+
dropped_frames: WebSocketHandler.dropped_frame_count,
|
|
106
|
+
websocket_backpressure: WebSocketHandler.backpressure_status,
|
|
107
|
+
runtime: runtime_status
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
if audio_file_available?
|
|
@@ -187,6 +195,35 @@ module Vizcore
|
|
|
187
195
|
@projector_mode ? "projector" : "auto"
|
|
188
196
|
end
|
|
189
197
|
|
|
198
|
+
def runtime_status
|
|
199
|
+
return {} unless @runtime_status_provider.respond_to?(:call)
|
|
200
|
+
|
|
201
|
+
normalize_runtime_status(@runtime_status_provider.call)
|
|
202
|
+
rescue StandardError => e
|
|
203
|
+
{ "last_error" => e.message }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def normalize_runtime_status(values)
|
|
207
|
+
Hash(values || {}).each_with_object({}) do |(key, value), output|
|
|
208
|
+
output[key.to_s] = normalize_runtime_status_value(value)
|
|
209
|
+
end
|
|
210
|
+
rescue StandardError
|
|
211
|
+
{}
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def normalize_runtime_status_value(value)
|
|
215
|
+
case value
|
|
216
|
+
when Hash
|
|
217
|
+
normalize_runtime_status(value)
|
|
218
|
+
when Array
|
|
219
|
+
value.map { |entry| normalize_runtime_status_value(entry) }
|
|
220
|
+
when Symbol
|
|
221
|
+
value.to_s
|
|
222
|
+
else
|
|
223
|
+
value
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
190
227
|
def static_response(body, content_type:)
|
|
191
228
|
headers = {
|
|
192
229
|
"content-type" => content_type,
|
|
@@ -280,7 +317,23 @@ module Vizcore
|
|
|
280
317
|
control = (action[:control] || action["control"]).to_s.strip
|
|
281
318
|
return nil unless %w[blackout freeze].include?(control)
|
|
282
319
|
|
|
283
|
-
{ type: "live_control", control: control }
|
|
320
|
+
live_control = { type: "live_control", control: control }
|
|
321
|
+
if action.key?(:value) || action.key?("value")
|
|
322
|
+
live_control[:value] = action[:value]
|
|
323
|
+
live_control[:value] = action["value"] if action.key?("value")
|
|
324
|
+
end
|
|
325
|
+
fade = action.key?(:fade) ? action[:fade] : action["fade"]
|
|
326
|
+
release = action.key?(:release) ? action[:release] : action["release"]
|
|
327
|
+
color = action.key?(:color) ? action[:color] : action["color"]
|
|
328
|
+
normalized_fade = finite_float(fade)
|
|
329
|
+
normalized_release = finite_float(release)
|
|
330
|
+
normalized_color = normalize_control_color(color)
|
|
331
|
+
return live_control if normalized_fade.nil? && normalized_release.nil? && !live_control.key?(:value) && normalized_color.nil?
|
|
332
|
+
|
|
333
|
+
live_control[:fade] = normalized_fade if normalized_fade
|
|
334
|
+
live_control[:release] = normalized_release if normalized_release
|
|
335
|
+
live_control[:color] = normalized_color if normalized_color
|
|
336
|
+
live_control
|
|
284
337
|
end
|
|
285
338
|
end
|
|
286
339
|
|
|
@@ -299,21 +352,115 @@ module Vizcore
|
|
|
299
352
|
preset = values.is_a?(Hash) ? values : {}
|
|
300
353
|
visual_settings = preset[:visual_settings] || preset["visual_settings"] || preset[:visualSettings] || preset["visualSettings"]
|
|
301
354
|
midi_learn_bindings = preset[:midi_learn_bindings] || preset["midi_learn_bindings"] || preset[:midiLearnBindings] || preset["midiLearnBindings"]
|
|
355
|
+
scene_overrides = preset[:scene_overrides] || preset["scene_overrides"] || preset[:sceneOverrides] || preset["sceneOverrides"]
|
|
302
356
|
|
|
303
357
|
{}.tap do |payload|
|
|
304
358
|
payload["visual_settings"] = visual_settings if visual_settings.is_a?(Hash)
|
|
305
359
|
payload["midi_learn_bindings"] = midi_learn_bindings if midi_learn_bindings.is_a?(Hash)
|
|
360
|
+
normalized_scene_overrides = normalize_scene_overrides(scene_overrides)
|
|
361
|
+
payload["scene_overrides"] = normalized_scene_overrides if normalized_scene_overrides
|
|
362
|
+
end
|
|
363
|
+
rescue StandardError
|
|
364
|
+
{}
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def normalize_scene_overrides(values)
|
|
368
|
+
return nil unless values
|
|
369
|
+
|
|
370
|
+
raw_overrides = values.is_a?(Hash) ? values : {}
|
|
371
|
+
normalized = {}
|
|
372
|
+
|
|
373
|
+
raw_overrides.each do |raw_scene, raw_override|
|
|
374
|
+
scene_name = raw_scene.to_s.strip
|
|
375
|
+
next if scene_name.empty?
|
|
376
|
+
|
|
377
|
+
scene_override = normalize_scene_override(raw_override)
|
|
378
|
+
next if scene_override.empty?
|
|
379
|
+
|
|
380
|
+
normalized[scene_name] = scene_override
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
normalized.empty? ? nil : normalized
|
|
384
|
+
rescue StandardError
|
|
385
|
+
nil
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def normalize_scene_override(value)
|
|
389
|
+
input = value.is_a?(Hash) ? value : {}
|
|
390
|
+
|
|
391
|
+
{
|
|
392
|
+
visual_settings: input[:visual_settings] || input["visual_settings"] || input[:visualSettings] || input["visualSettings"],
|
|
393
|
+
midi_learn_bindings: input[:midi_learn_bindings] || input["midi_learn_bindings"] || input[:midiLearnBindings] || input["midiLearnBindings"] || input[:midi] || input["midi"]
|
|
394
|
+
}.each_with_object({}) do |(key, value), output|
|
|
395
|
+
output[key.to_s] = value if value.is_a?(Hash)
|
|
306
396
|
end
|
|
307
397
|
rescue StandardError
|
|
308
398
|
{}
|
|
309
399
|
end
|
|
310
400
|
|
|
401
|
+
def finite_float(value)
|
|
402
|
+
numeric = Float(value)
|
|
403
|
+
return numeric if numeric.finite?
|
|
404
|
+
|
|
405
|
+
nil
|
|
406
|
+
rescue ArgumentError, TypeError
|
|
407
|
+
nil
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def normalize_control_color(value)
|
|
411
|
+
return nil if value.nil?
|
|
412
|
+
if value.is_a?(Array)
|
|
413
|
+
return nil unless (3..4).cover?(value.length)
|
|
414
|
+
|
|
415
|
+
channels = Array(value).map { |entry| Float(entry, exception: false) }
|
|
416
|
+
return nil if channels.include?(nil)
|
|
417
|
+
|
|
418
|
+
rgb = channels.take(3)
|
|
419
|
+
alpha = channels[3]
|
|
420
|
+
normalized_rgb = if rgb.all? { |channel| channel.between?(0.0, 1.0) }
|
|
421
|
+
rgb
|
|
422
|
+
else
|
|
423
|
+
rgb.map { |channel| channel / 255.0 }
|
|
424
|
+
end
|
|
425
|
+
normalized = normalized_rgb.map { |channel| [0.0, [1.0, channel].min].max }
|
|
426
|
+
return alpha.nil? ? normalized : normalized + [normalize_control_alpha(alpha)]
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
raw = value.to_s.strip
|
|
430
|
+
match = raw.match(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/)
|
|
431
|
+
return nil unless match
|
|
432
|
+
|
|
433
|
+
raw_hex = match[1]
|
|
434
|
+
hex = raw_hex.length == 3 || raw_hex.length == 4 ? raw_hex.chars.map { |entry| "#{entry}#{entry}" }.join("") : raw_hex
|
|
435
|
+
|
|
436
|
+
[
|
|
437
|
+
Integer("0x#{hex[0, 2]}", 16),
|
|
438
|
+
Integer("0x#{hex[2, 2]}", 16),
|
|
439
|
+
Integer("0x#{hex[4, 2]}", 16),
|
|
440
|
+
Integer("0x#{hex[6, 2]}", 16)
|
|
441
|
+
].take(raw_hex.length > 4 ? 4 : 3).map { |channel| [0.0, [1.0, channel / 255.0].min].max }
|
|
442
|
+
rescue ArgumentError, TypeError
|
|
443
|
+
nil
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def normalize_control_alpha(value)
|
|
447
|
+
return nil if value.nil?
|
|
448
|
+
|
|
449
|
+
alpha = Float(value, exception: false)
|
|
450
|
+
return nil if alpha.nil?
|
|
451
|
+
|
|
452
|
+
return [0.0, [1.0, alpha].min].max if alpha.between?(0.0, 1.0)
|
|
453
|
+
|
|
454
|
+
[0.0, [1.0, alpha / 255.0].min].max
|
|
455
|
+
end
|
|
456
|
+
|
|
311
457
|
def normalize_plugin_assets(values)
|
|
312
458
|
Array(values).each_with_index.filter_map do |value, index|
|
|
313
459
|
raw_value = value.to_s.strip
|
|
314
460
|
next if raw_value.empty?
|
|
315
461
|
|
|
316
|
-
|
|
462
|
+
raw_path = value.is_a?(Pathname) ? value.expand_path : Pathname.new(raw_value).expand_path
|
|
463
|
+
path = Vizcore::PluginAssetPolicy.validate!(raw_path)
|
|
317
464
|
{
|
|
318
465
|
path: path,
|
|
319
466
|
url: "#{PLUGIN_ASSET_PREFIX}#{index}/#{rack_escape_path(path.basename.to_s)}"
|