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
|
@@ -28,7 +28,8 @@ module Vizcore
|
|
|
28
28
|
# @param audio_device [String, Integer, nil] input device index/name for `:mic`
|
|
29
29
|
def initialize(source: :mic, sample_rate: DEFAULT_SAMPLE_RATE, frame_size: DEFAULT_FRAME_SIZE, ring_buffer_size: DEFAULT_RING_BUFFER_SIZE, file_path: nil, audio_device: nil)
|
|
30
30
|
@source_name = source.to_sym
|
|
31
|
-
@
|
|
31
|
+
@requested_sample_rate = Integer(sample_rate)
|
|
32
|
+
@sample_rate = @requested_sample_rate
|
|
32
33
|
@frame_size = Integer(frame_size)
|
|
33
34
|
@ring_buffer = RingBuffer.new(ring_buffer_size)
|
|
34
35
|
@audio_device = audio_device
|
|
@@ -69,6 +70,32 @@ module Vizcore
|
|
|
69
70
|
ring_buffer.latest(count)
|
|
70
71
|
end
|
|
71
72
|
|
|
73
|
+
# @return [Hash] current input health values for runtime status endpoints.
|
|
74
|
+
def status
|
|
75
|
+
{
|
|
76
|
+
source: source_name.to_s,
|
|
77
|
+
sample_rate: sample_rate,
|
|
78
|
+
frame_size: frame_size,
|
|
79
|
+
ring_buffer: ring_buffer.respond_to?(:metrics) ? ring_buffer.metrics : {},
|
|
80
|
+
requested_sample_rate: @requested_sample_rate,
|
|
81
|
+
sample_rate_mismatch: sample_rate_mismatch?
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [Float, nil] current transport position seconds for file sources
|
|
86
|
+
def transport_position_seconds
|
|
87
|
+
return @input.transport_position_seconds if @input.respond_to?(:transport_position_seconds)
|
|
88
|
+
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @return [Float, nil] input track duration seconds for file sources
|
|
93
|
+
def track_duration_seconds
|
|
94
|
+
return @input.track_duration_seconds if @input.respond_to?(:track_duration_seconds)
|
|
95
|
+
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
|
|
72
99
|
# @param frame_rate [Numeric]
|
|
73
100
|
# @return [Integer] approximate real-time sample count to ingest per render tick
|
|
74
101
|
def realtime_capture_size(frame_rate)
|
|
@@ -130,6 +157,14 @@ module Vizcore
|
|
|
130
157
|
rescue StandardError
|
|
131
158
|
fallback
|
|
132
159
|
end
|
|
160
|
+
|
|
161
|
+
def sample_rate_mismatch?
|
|
162
|
+
return false unless @input.respond_to?(:stream_sample_rate)
|
|
163
|
+
|
|
164
|
+
Integer(@input.stream_sample_rate) != Integer(@requested_sample_rate)
|
|
165
|
+
rescue StandardError
|
|
166
|
+
false
|
|
167
|
+
end
|
|
133
168
|
end
|
|
134
169
|
end
|
|
135
170
|
end
|
|
@@ -16,6 +16,9 @@ module Vizcore
|
|
|
16
16
|
@buffer = Array.new(@capacity, 0.0)
|
|
17
17
|
@write_index = 0
|
|
18
18
|
@size = 0
|
|
19
|
+
@write_count = 0
|
|
20
|
+
@overrun_count = 0
|
|
21
|
+
@underrun_count = 0
|
|
19
22
|
@mutex = Mutex.new
|
|
20
23
|
end
|
|
21
24
|
|
|
@@ -27,8 +30,10 @@ module Vizcore
|
|
|
27
30
|
|
|
28
31
|
@mutex.synchronize do
|
|
29
32
|
normalized.each do |sample|
|
|
33
|
+
@overrun_count += 1 if @size == @capacity
|
|
30
34
|
@buffer[@write_index] = sample
|
|
31
35
|
@write_index = (@write_index + 1) % @capacity
|
|
36
|
+
@write_count += 1
|
|
32
37
|
@size += 1 if @size < @capacity
|
|
33
38
|
end
|
|
34
39
|
end
|
|
@@ -49,6 +54,7 @@ module Vizcore
|
|
|
49
54
|
requested = count ? Integer(count) : @size
|
|
50
55
|
return [] if requested <= 0
|
|
51
56
|
|
|
57
|
+
@underrun_count += requested - @size if requested > @size
|
|
52
58
|
length = [requested, @size].min
|
|
53
59
|
start = (@write_index - length) % @capacity
|
|
54
60
|
|
|
@@ -61,12 +67,28 @@ module Vizcore
|
|
|
61
67
|
@mutex.synchronize { @size }
|
|
62
68
|
end
|
|
63
69
|
|
|
70
|
+
# @return [Hash] buffer health counters for runtime diagnostics.
|
|
71
|
+
def metrics
|
|
72
|
+
@mutex.synchronize do
|
|
73
|
+
{
|
|
74
|
+
capacity: @capacity,
|
|
75
|
+
size: @size,
|
|
76
|
+
write_count: @write_count,
|
|
77
|
+
overrun_count: @overrun_count,
|
|
78
|
+
underrun_count: @underrun_count
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
64
83
|
# @return [void]
|
|
65
84
|
def clear
|
|
66
85
|
@mutex.synchronize do
|
|
67
86
|
@buffer.fill(0.0)
|
|
68
87
|
@write_index = 0
|
|
69
88
|
@size = 0
|
|
89
|
+
@write_count = 0
|
|
90
|
+
@overrun_count = 0
|
|
91
|
+
@underrun_count = 0
|
|
70
92
|
end
|
|
71
93
|
end
|
|
72
94
|
|
data/lib/vizcore/audio.rb
CHANGED
|
@@ -12,20 +12,23 @@ module Vizcore
|
|
|
12
12
|
Entry.new(syntax: "audio :mic, **options", description: "Register an audio input definition."),
|
|
13
13
|
Entry.new(syntax: "midi :controller, **options", description: "Register a MIDI input definition."),
|
|
14
14
|
Entry.new(syntax: "audio_normalize mode: :adaptive", description: "Configure analysis-level normalization."),
|
|
15
|
+
Entry.new(syntax: "audio_analysis onset_sensitivity: 1.4, fft_bins: 64", description: "Tune analysis feature extraction."),
|
|
15
16
|
Entry.new(syntax: "bpm 128 / bpm_lock true", description: "Set and optionally lock the analysis BPM."),
|
|
16
17
|
Entry.new(syntax: "tap_tempo key: :space", description: "Enable browser tap tempo events."),
|
|
17
18
|
Entry.new(syntax: "set :global_intensity, 0.8", description: "Set a runtime global exposed to shaders."),
|
|
18
19
|
Entry.new(syntax: "style :name { ... } / theme :name { ... }", description: "Define reusable layer params or scene defaults."),
|
|
20
|
+
Entry.new(syntax: "mapping :name { ... }", description: "Define reusable layer mapping groups."),
|
|
19
21
|
Entry.new(syntax: "scene :name, extends: :base { ... }", description: "Define a scene and optional inherited layers."),
|
|
20
|
-
Entry.new(syntax: "section :intro, bars: 8
|
|
21
|
-
Entry.new(syntax: "timeline { at beats(0), scene: :intro }", description: "Define ordered scene markers."),
|
|
22
|
+
Entry.new(syntax: "section :intro, bars: 8, loop: true, hold: 2, outro: false", description: "Define beat-counted scenes with optional auto-loop/outro/hold behavior."),
|
|
23
|
+
Entry.new(syntax: "timeline { at beats(0), scene: :intro, cue: :prelude }", description: "Define ordered scene markers with optional cue metadata."),
|
|
22
24
|
Entry.new(syntax: "transition from: :intro, to: :drop { ... }", description: "Define explicit scene transitions."),
|
|
23
|
-
Entry.new(syntax: "midi_map
|
|
25
|
+
Entry.new(syntax: "midi_map cc: 1, channel: 1, deadband: 2, smooth: 0.25, allow_multiple: true { ... }", description: "Map MIDI events to runtime actions."),
|
|
24
26
|
Entry.new(syntax: "key \"d\" { switch_scene :drop }", description: "Map browser keyboard shortcuts to runtime actions.")
|
|
25
27
|
].freeze
|
|
26
28
|
|
|
27
29
|
SCENE = [
|
|
28
30
|
Entry.new(syntax: "use_theme :name", description: "Apply scene-wide layer defaults."),
|
|
31
|
+
Entry.new(syntax: "use_mapping :name", description: "Apply a reusable mapping group to a layer."),
|
|
29
32
|
Entry.new(syntax: "group :foreground { layer :name { ... } }", description: "Apply shared params to a related layer group."),
|
|
30
33
|
Entry.new(syntax: "layer :name { ... }", description: "Append a render layer to the scene.")
|
|
31
34
|
].freeze
|
|
@@ -43,19 +46,72 @@ module Vizcore
|
|
|
43
46
|
Entry.new(syntax: "circle count: 8 { radius 100 } / rect width: 320, height: 160 / custom_shape :flower", description: "Render declarative and Ruby-generated 2D shape primitives."),
|
|
44
47
|
Entry.new(syntax: "font \"Inter\" / letter_spacing 4", description: "Set text presentation params."),
|
|
45
48
|
Entry.new(syntax: "palette \"#ff0055\", \"#00ffff\"", description: "Set ordered colors for supported layer renderers."),
|
|
49
|
+
Entry.new(syntax: "hsl(240, 100, 50) / hsv(0, 100, 100)", description: "Generate CSS hex color values from HSL / HSV."),
|
|
50
|
+
Entry.new(syntax: "gradient(type: :radial, colors: [\"#f00\", \"#0ff\"])", description: "Build reusable gradient descriptors for color fields."),
|
|
51
|
+
Entry.new(syntax: "grid(count: 12, columns: 4, spacing: 20)", description: "Generate point layouts for shape geometry."),
|
|
52
|
+
Entry.new(syntax: "radial(count: 16, radius: 120, start_angle: -90)", description: "Generate polar layout points."),
|
|
53
|
+
Entry.new(syntax: "spiral(count: 24, radius: 150, turns: 4)", description: "Generate expanding spiral points."),
|
|
54
|
+
Entry.new(syntax: "circle_pack(count: 20, radius: 140)", description: "Generate packed points in concentric circles."),
|
|
55
|
+
Entry.new(syntax: "scatter(count: 12, width: 320, height: 180, seed: 7)", description: "Generate deterministic scattered point layouts."),
|
|
46
56
|
Entry.new(syntax: "blend :add / effect :bloom / vj_effect :mirror", description: "Set compositing and browser effects."),
|
|
57
|
+
Entry.new(syntax: "post :bloom / post :motion_blur / post :chromatic", description: "Build a post effect chain for a layer."),
|
|
47
58
|
Entry.new(syntax: "param :wobble, default: 0.3, range: 0.0..2.0", description: "Declare numeric shader parameter metadata."),
|
|
48
59
|
Entry.new(syntax: "map amplitude, to: :speed, range: 0.0..2.0", description: "Map audio features to layer params."),
|
|
49
60
|
Entry.new(syntax: "react_to bass { change :size }", description: "Group mappings by source.")
|
|
50
61
|
].freeze
|
|
51
62
|
|
|
52
|
-
SOURCES =
|
|
53
|
-
amplitude
|
|
54
|
-
|
|
63
|
+
SOURCES = [
|
|
64
|
+
"amplitude",
|
|
65
|
+
"frequency_band(:low)",
|
|
66
|
+
"frequency_band_peak(:low)",
|
|
67
|
+
"sub",
|
|
68
|
+
"low",
|
|
69
|
+
"bass",
|
|
70
|
+
"bass_peak",
|
|
71
|
+
"mid",
|
|
72
|
+
"mid_peak",
|
|
73
|
+
"high",
|
|
74
|
+
"high_peak",
|
|
75
|
+
"treble",
|
|
76
|
+
"fft_spectrum",
|
|
77
|
+
"onset",
|
|
78
|
+
"onset(:high)",
|
|
79
|
+
"kick",
|
|
80
|
+
"snare",
|
|
81
|
+
"hihat",
|
|
82
|
+
"beat?",
|
|
83
|
+
"beat",
|
|
84
|
+
"beat_confidence",
|
|
85
|
+
"beat_pulse",
|
|
86
|
+
"beat_count",
|
|
87
|
+
"adsr(:kick, attack: 0.02, decay: 0.08, sustain: 0.7, release: 0.16, threshold: 0.0, peak: 1.0)",
|
|
88
|
+
"envelope(:kick, attack: 0.02)",
|
|
89
|
+
"beat_phase",
|
|
90
|
+
"beat_2",
|
|
91
|
+
"beat_4",
|
|
92
|
+
"beat_8",
|
|
93
|
+
"triplet",
|
|
94
|
+
"bar_phase",
|
|
95
|
+
"bar_count",
|
|
96
|
+
"phrase_count",
|
|
97
|
+
"bpm"
|
|
55
98
|
].freeze
|
|
56
99
|
|
|
57
|
-
TRANSFORMS =
|
|
58
|
-
|
|
100
|
+
TRANSFORMS = [
|
|
101
|
+
"as(:trigger)",
|
|
102
|
+
"gain",
|
|
103
|
+
"range",
|
|
104
|
+
"min",
|
|
105
|
+
"max",
|
|
106
|
+
"curve",
|
|
107
|
+
"deadzone",
|
|
108
|
+
"threshold",
|
|
109
|
+
"hysteresis",
|
|
110
|
+
"hold",
|
|
111
|
+
"decay",
|
|
112
|
+
"cooldown",
|
|
113
|
+
"one_shot",
|
|
114
|
+
"smooth(attack:,release:)"
|
|
59
115
|
].freeze
|
|
60
116
|
|
|
61
117
|
# @return [Array<String>]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require_relative "../plugin_asset_policy"
|
|
5
|
+
|
|
6
|
+
module Vizcore
|
|
7
|
+
module CLISupport
|
|
8
|
+
# Performs local smoke checks for a Vizcore plugin scaffold.
|
|
9
|
+
class PluginChecker
|
|
10
|
+
Check = Struct.new(:name, :status, :message, keyword_init: true) do
|
|
11
|
+
def failure?
|
|
12
|
+
status == :fail
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Report = Struct.new(:checks, keyword_init: true) do
|
|
17
|
+
def failure?
|
|
18
|
+
checks.any?(&:failure?)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param root [String, Pathname]
|
|
23
|
+
def initialize(root)
|
|
24
|
+
@root = Pathname.new(root).expand_path
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Report]
|
|
28
|
+
def call
|
|
29
|
+
return Report.new(checks: [fail_check("Plugin root", "directory not found: #{@root}")]) unless @root.directory?
|
|
30
|
+
|
|
31
|
+
Report.new(
|
|
32
|
+
checks: [
|
|
33
|
+
ruby_layer_check,
|
|
34
|
+
frontend_asset_check,
|
|
35
|
+
example_scene_check
|
|
36
|
+
]
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def ruby_layer_check
|
|
43
|
+
files = @root.join("lib").glob("*.rb")
|
|
44
|
+
return fail_check("Ruby layer", "missing lib/*.rb") if files.empty?
|
|
45
|
+
|
|
46
|
+
syntax_check("Ruby layer", files.first)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def frontend_asset_check
|
|
50
|
+
files = @root.join("frontend").glob("*.{js,mjs}")
|
|
51
|
+
return fail_check("Frontend renderer", "missing frontend/*.js or frontend/*.mjs") if files.empty?
|
|
52
|
+
|
|
53
|
+
Vizcore::PluginAssetPolicy.validate!(files.first, root: @root)
|
|
54
|
+
body = files.first.read
|
|
55
|
+
return warn_check("Frontend renderer", "#{files.first} does not register globalThis.VizcorePlugins") unless body.include?("VizcorePlugins")
|
|
56
|
+
|
|
57
|
+
ok("Frontend renderer", "#{files.first.relative_path_from(@root)} is loadable")
|
|
58
|
+
rescue ArgumentError => e
|
|
59
|
+
fail_check("Frontend renderer", e.message)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def example_scene_check
|
|
63
|
+
files = @root.join("examples").glob("*.rb")
|
|
64
|
+
return fail_check("Example scene", "missing examples/*.rb") if files.empty?
|
|
65
|
+
|
|
66
|
+
syntax_check("Example scene", files.first)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def syntax_check(name, path)
|
|
70
|
+
if defined?(RubyVM::InstructionSequence)
|
|
71
|
+
RubyVM::InstructionSequence.compile_file(path.to_s)
|
|
72
|
+
else
|
|
73
|
+
return warn_check(name, "syntax check skipped on this Ruby engine")
|
|
74
|
+
end
|
|
75
|
+
ok(name, "#{path.relative_path_from(@root)} has valid Ruby syntax")
|
|
76
|
+
rescue SyntaxError => e
|
|
77
|
+
fail_check(name, "#{path.relative_path_from(@root)} has invalid Ruby syntax: #{e.message.lines.first&.strip}")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def ok(name, message)
|
|
81
|
+
Check.new(name: name, status: :ok, message: message)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def warn_check(name, message)
|
|
85
|
+
Check.new(name: name, status: :warn, message: message)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def fail_check(name, message)
|
|
89
|
+
Check.new(name: name, status: :fail, message: message)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -7,8 +7,8 @@ module Vizcore
|
|
|
7
7
|
module CLISupport
|
|
8
8
|
# Facade used by Thor commands for scene validation and inspection.
|
|
9
9
|
class SceneDiagnostics
|
|
10
|
-
def initialize(scene_file:)
|
|
11
|
-
@validator = SceneValidator.new(scene_file: scene_file)
|
|
10
|
+
def initialize(scene_file:, strict: false)
|
|
11
|
+
@validator = SceneValidator.new(scene_file: scene_file, strict: strict)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def validate
|
|
@@ -19,8 +19,27 @@ module Vizcore
|
|
|
19
19
|
output
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def to_h
|
|
23
|
+
sanitize(@definition)
|
|
24
|
+
end
|
|
25
|
+
|
|
22
26
|
private
|
|
23
27
|
|
|
28
|
+
def sanitize(value)
|
|
29
|
+
case value
|
|
30
|
+
when Hash
|
|
31
|
+
value.each_with_object({}) do |(key, entry), output|
|
|
32
|
+
output[key.to_s] = sanitize(entry)
|
|
33
|
+
end
|
|
34
|
+
when Array
|
|
35
|
+
value.map { |entry| sanitize(entry) }
|
|
36
|
+
when Symbol
|
|
37
|
+
value.to_s
|
|
38
|
+
else
|
|
39
|
+
value.respond_to?(:call) ? true : value
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
24
43
|
def append_inputs(output, label, values)
|
|
25
44
|
return if values.empty?
|
|
26
45
|
|
|
@@ -95,10 +114,21 @@ module Vizcore
|
|
|
95
114
|
return "unknown" unless values[:kind]
|
|
96
115
|
return "frequency_band(#{values[:band]})" if values[:kind].to_sym == :frequency_band
|
|
97
116
|
return "onset(#{values[:band]})" if values[:kind].to_sym == :onset && values[:band]
|
|
117
|
+
return "global(#{values[:name]})" if values[:kind].to_sym == :global && values[:name]
|
|
118
|
+
return format_lfo_source(values) if values[:kind].to_sym == :lfo
|
|
98
119
|
|
|
99
120
|
values[:kind].to_s
|
|
100
121
|
end
|
|
101
122
|
|
|
123
|
+
def format_lfo_source(values)
|
|
124
|
+
wave = values[:wave] || :sine
|
|
125
|
+
parts = []
|
|
126
|
+
parts << "rate=#{values[:rate]}" if values.key?(:rate)
|
|
127
|
+
parts << "phase=#{values[:phase]}" if values.key?(:phase)
|
|
128
|
+
suffix = parts.empty? ? "" : ", #{parts.join(', ')}"
|
|
129
|
+
"lfo(#{wave}#{suffix})"
|
|
130
|
+
end
|
|
131
|
+
|
|
102
132
|
def format_transform(transform)
|
|
103
133
|
values = Hash(transform || {})
|
|
104
134
|
return "" if values.empty?
|
|
@@ -121,7 +151,11 @@ module Vizcore
|
|
|
121
151
|
when :switch_scene
|
|
122
152
|
"switch_scene #{values[:scene]}"
|
|
123
153
|
when :live_control
|
|
124
|
-
values
|
|
154
|
+
options = values.slice(:value, :fade, :release)
|
|
155
|
+
control = values[:control].to_s
|
|
156
|
+
return control if options.empty?
|
|
157
|
+
|
|
158
|
+
"#{control}#{format_options(options)}"
|
|
125
159
|
else
|
|
126
160
|
"unknown"
|
|
127
161
|
end
|