Class: Vizcore::DSL::Engine
- Inherits:
-
Object
- Object
- Vizcore::DSL::Engine
- Defined in:
- lib/vizcore/dsl/engine.rb
Overview
Evaluates and stores scene definitions built with the Vizcore Ruby DSL.
Defined Under Namespace
Classes: KeyBindingBuilder, TransitionBuilder
Constant Summary collapse
- THREAD_KEY =
Thread-local key used when evaluating scene files.
:vizcore_current_dsl_engine
Class Method Summary collapse
-
.current ⇒ Vizcore::DSL::Engine?
Current thread-local DSL engine.
-
.define { ... } ⇒ Hash
Evaluate a DSL block using the current thread-local engine, or a new engine.
-
.load_file(path) ⇒ Hash
Load and evaluate a scene file.
-
.watch_file(path, poll_interval: FileWatcher::DEFAULT_POLL_INTERVAL, listener_factory: nil) {|definition, changed_path| ... } ⇒ Vizcore::DSL::FileWatcher
Build a file watcher that reloads and yields definitions on change.
Instance Method Summary collapse
-
#audio(name, **options) ⇒ void
Register an audio input definition.
-
#audio_analysis(**options) ⇒ Hash
Configure analysis feature extraction behavior.
-
#audio_normalize(mode: :adaptive, **options) ⇒ Hash
Configure analysis-level audio feature normalization.
-
#bpm(value) ⇒ Float
Set a fixed BPM value for analysis output.
-
#bpm_lock(value = true) ⇒ Boolean
Enable or disable fixed BPM output.
-
#evaluate { ... } ⇒ Hash
Evaluate DSL methods on this engine instance.
-
#initialize ⇒ Engine
constructor
A new instance of Engine.
-
#key(value) { ... } ⇒ void
Register a browser keyboard shortcut for runtime controls.
-
#mapping(name) { ... } ⇒ void
Register reusable mapping behavior for layer-level targets.
-
#midi(name, **options) ⇒ void
Register a MIDI input definition.
-
#midi_map(note: nil, cc: nil, pc: nil, channel: nil, relative: false, deadband: nil, smooth: nil, pickup: nil, allow_multiple: false) { ... } ⇒ void
Register a MIDI trigger/action mapping.
-
#result ⇒ Hash
Deep-copied definition payload for renderer/runtime.
-
#scene(name, extends: nil) { ... } ⇒ void
Define a scene and its layers.
-
#section(name, bars:, beats_per_bar: 4, loop: false, hold: 0, outro: false) { ... } ⇒ void
Define a beat-counted song section as a scene and auto-transition to the following section.
-
#seed(value) ⇒ Integer
Set a deterministic Ruby random seed for offline rendering.
-
#set(key, value) ⇒ Object
Set a mutable global value shared with scene/runtime logic.
-
#strict! ⇒ Boolean
Enable strict DSL validation while the file is evaluated.
-
#style(name) { ... } ⇒ void
Register a reusable layer parameter style.
-
#tap_tempo(key: :t) ⇒ Hash
Enable browser keyboard tap tempo.
-
#theme(name) { ... } ⇒ void
Register a reusable scene-wide layer parameter theme.
-
#timeline(beats_per_bar: TimelineBuilder::DEFAULT_BEATS_PER_BAR) { ... } ⇒ void
Define ordered scene markers and derive transitions between them.
-
#transition(from:, to:) { ... } ⇒ void
Define a transition between scenes.
Constructor Details
#initialize ⇒ Engine
Returns a new instance of Engine.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/vizcore/dsl/engine.rb', line 72 def initialize @audio_inputs = [] @midi_inputs = [] @scenes = [] @transitions = [] @midi_mappings = [] @key_mappings = [] @global_params = {} @mapping_presets = {} @analysis_settings = {} @section_tail = nil @timelines = [] @styles = {} @themes = {} @scene_registry = {} @strict = false @seed = nil end |
Class Method Details
.current ⇒ Vizcore::DSL::Engine?
Returns current thread-local DSL engine.
57 58 59 |
# File 'lib/vizcore/dsl/engine.rb', line 57 def current Thread.current[THREAD_KEY] end |
.define { ... } ⇒ Hash
Evaluate a DSL block using the current thread-local engine, or a new engine.
22 23 24 25 |
# File 'lib/vizcore/dsl/engine.rb', line 22 def define(&block) engine = current || new engine.evaluate(&block) end |
.load_file(path) ⇒ Hash
Load and evaluate a scene file.
32 33 34 35 36 37 38 39 |
# File 'lib/vizcore/dsl/engine.rb', line 32 def load_file(path) scene_path = Pathname.new(path.to_s). raise ArgumentError, "Scene file not found: #{scene_path}" unless scene_path.file? engine = new with_current(engine) { Kernel.load(scene_path.to_s) } engine.result end |
.watch_file(path, poll_interval: FileWatcher::DEFAULT_POLL_INTERVAL, listener_factory: nil) {|definition, changed_path| ... } ⇒ Vizcore::DSL::FileWatcher
Build a file watcher that reloads and yields definitions on change.
49 50 51 52 53 54 |
# File 'lib/vizcore/dsl/engine.rb', line 49 def watch_file(path, poll_interval: FileWatcher::DEFAULT_POLL_INTERVAL, listener_factory: nil, &on_change) FileWatcher.new(path: path, poll_interval: poll_interval, listener_factory: listener_factory) do |changed_path| definition = load_file(changed_path.to_s) on_change&.call(definition, changed_path) end end |
Instance Method Details
#audio(name, **options) ⇒ void
This method returns an undefined value.
Register an audio input definition.
105 106 107 |
# File 'lib/vizcore/dsl/engine.rb', line 105 def audio(name, **) @audio_inputs << { name: name.to_sym, options: symbolize_keys() } end |
#audio_analysis(**options) ⇒ Hash
Configure analysis feature extraction behavior.
182 183 184 185 |
# File 'lib/vizcore/dsl/engine.rb', line 182 def audio_analysis(**) settings = normalize_audio_analysis() @analysis_settings.merge!(settings) end |
#audio_normalize(mode: :adaptive, **options) ⇒ Hash
Configure analysis-level audio feature normalization.
173 174 175 176 |
# File 'lib/vizcore/dsl/engine.rb', line 173 def audio_normalize(mode: :adaptive, **) settings = normalize_audio_normalize(mode: mode, **) @analysis_settings[:audio_normalize] = settings end |
#bpm(value) ⇒ Float
Set a fixed BPM value for analysis output.
191 192 193 |
# File 'lib/vizcore/dsl/engine.rb', line 191 def bpm(value) @analysis_settings[:bpm] = positive_float(value, "bpm") end |
#bpm_lock(value = true) ⇒ Boolean
Enable or disable fixed BPM output.
199 200 201 |
# File 'lib/vizcore/dsl/engine.rb', line 199 def bpm_lock(value = true) @analysis_settings[:bpm_lock] = !!value end |
#evaluate { ... } ⇒ Hash
Evaluate DSL methods on this engine instance.
95 96 97 98 |
# File 'lib/vizcore/dsl/engine.rb', line 95 def evaluate(&block) instance_eval(&block) if block result end |
#key(value) { ... } ⇒ void
This method returns an undefined value.
Register a browser keyboard shortcut for runtime controls.
328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/vizcore/dsl/engine.rb', line 328 def key(value, &block) binding_key = normalize_keyboard_key(value) builder = KeyBindingBuilder.new builder.instance_eval(&block) if block action = builder.to_h raise ArgumentError, "key #{binding_key.inspect} requires an action" if action.empty? @key_mappings << { key: binding_key, action: action } end |
#mapping(name) { ... } ⇒ void
This method returns an undefined value.
Register reusable mapping behavior for layer-level targets.
136 137 138 139 140 |
# File 'lib/vizcore/dsl/engine.rb', line 136 def mapping(name, &block) builder = MappingPresetBuilder.new(name: name, strict: @strict) preset_definition = builder.evaluate(&block).to_h @mapping_presets[preset_definition[:name]] = deep_dup(preset_definition[:mappings]) end |
#midi(name, **options) ⇒ void
This method returns an undefined value.
Register a MIDI input definition.
147 148 149 |
# File 'lib/vizcore/dsl/engine.rb', line 147 def midi(name, **) @midi_inputs << { name: name.to_sym, options: symbolize_keys() } end |
#midi_map(note: nil, cc: nil, pc: nil, channel: nil, relative: false, deadband: nil, smooth: nil, pickup: nil, allow_multiple: false) { ... } ⇒ void
This method returns an undefined value.
Register a MIDI trigger/action mapping.
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/vizcore/dsl/engine.rb', line 303 def midi_map(note: nil, cc: nil, pc: nil, channel: nil, relative: false, deadband: nil, smooth: nil, pickup: nil, allow_multiple: false, &block) trigger = {} trigger[:note] = Integer(note) unless note.nil? trigger[:cc] = Integer(cc) unless cc.nil? trigger[:pc] = Integer(pc) unless pc.nil? raise ArgumentError, "midi_map requires note, cc or pc" if trigger.empty? trigger[:channel] = normalize_midi_channel(channel) unless channel.nil? trigger[:relative] = true if relative && trigger.key?(:cc) trigger[:deadband] = non_negative_float(deadband, "midi deadband") unless deadband.nil? trigger[:smooth] = normalize_midi_smooth(smooth) unless smooth.nil? || smooth == false trigger[:pickup] = pickup if trigger.key?(:cc) && trigger[:cc].between?(0, 127) && !!pickup trigger[:allow_multiple] = !!allow_multiple @midi_mappings << { trigger: trigger, action: block } end |
#result ⇒ Hash
Returns deep-copied definition payload for renderer/runtime.
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/vizcore/dsl/engine.rb', line 351 def result append_pending_section_transition definition = { audio: @audio_inputs.map { |item| deep_dup(item) }, midi: @midi_inputs.map { |item| deep_dup(item) }, scenes: @scenes.map { |scene| deep_dup(scene) }, transitions: @transitions.map { |transition| deep_dup(transition) }, midi_maps: @midi_mappings.map { |mapping| deep_dup(mapping) }, key_mappings: @key_mappings.map { |mapping| deep_dup(mapping) }, mapping_presets: @mapping_presets.map { |name, mappings| { name: name, mappings: deep_dup(mappings) } }, globals: deep_dup(@global_params), analysis: deep_dup(@analysis_settings), styles: @styles.map { |name, params| { name: name, params: deep_dup(params) } }, themes: @themes.map { |name, params| { name: name, params: deep_dup(params) } } } definition[:strict] = true if @strict definition[:seed] = @seed unless @seed.nil? definition[:timelines] = @timelines.map { |timeline| deep_dup(timeline) } unless @timelines.empty? definition end |
#scene(name, extends: nil) { ... } ⇒ void
This method returns an undefined value.
Define a scene and its layers.
220 221 222 223 224 225 226 |
# File 'lib/vizcore/dsl/engine.rb', line 220 def scene(name, extends: nil, &block) builder = SceneBuilder.new(name: name, styles: @styles, themes: @themes, mapping_presets: @mapping_presets, layers: inherited_layers(extends), strict: @strict) builder.evaluate(&block) scene_definition = builder.to_h @scenes << scene_definition @scene_registry[scene_definition[:name]] = deep_dup(scene_definition) end |
#section(name, bars:, beats_per_bar: 4, loop: false, hold: 0, outro: false) { ... } ⇒ void
This method returns an undefined value.
Define a beat-counted song section as a scene and auto-transition to the following section.
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/vizcore/dsl/engine.rb', line 239 def section(name, bars:, beats_per_bar: 4, loop: false, hold: 0, outro: false, &block) section_name = name.to_sym section_beats = positive_integer(, "section bars") * positive_integer(, "beats_per_bar") normalized_hold = non_negative_float(hold, "section hold") is_loop = !!loop is_outro = !!outro if is_loop && is_outro raise ArgumentError, "section cannot be both loop and outro" end scene(section_name, &block) add_section_transition(to: section_name) if @section_tail @section_tail = { name: section_name, beats: section_beats, hold: normalized_hold, loop: is_loop, outro: is_outro } end |
#seed(value) ⇒ Integer
Set a deterministic Ruby random seed for offline rendering.
162 163 164 165 166 |
# File 'lib/vizcore/dsl/engine.rb', line 162 def seed(value) @seed = Integer(value) rescue ArgumentError, TypeError raise ArgumentError, "seed must be an integer" end |
#set(key, value) ⇒ Object
Set a mutable global value shared with scene/runtime logic.
346 347 348 |
# File 'lib/vizcore/dsl/engine.rb', line 346 def set(key, value) @global_params[key.to_sym] = value end |
#strict! ⇒ Boolean
Enable strict DSL validation while the file is evaluated.
154 155 156 |
# File 'lib/vizcore/dsl/engine.rb', line 154 def strict! @strict = true end |
#style(name) { ... } ⇒ void
This method returns an undefined value.
Register a reusable layer parameter style.
114 115 116 117 118 |
# File 'lib/vizcore/dsl/engine.rb', line 114 def style(name, &block) builder = StyleBuilder.new(name: name) style_definition = builder.evaluate(&block).to_h @styles[style_definition[:name]] = deep_dup(style_definition[:params]) end |
#tap_tempo(key: :t) ⇒ Hash
Enable browser keyboard tap tempo.
207 208 209 210 211 212 |
# File 'lib/vizcore/dsl/engine.rb', line 207 def tap_tempo(key: :t) normalized_key = key.to_s.strip.downcase raise ArgumentError, "tap_tempo key must not be empty" if normalized_key.empty? @analysis_settings[:tap_tempo] = { key: normalized_key } end |
#theme(name) { ... } ⇒ void
This method returns an undefined value.
Register a reusable scene-wide layer parameter theme.
125 126 127 128 129 |
# File 'lib/vizcore/dsl/engine.rb', line 125 def theme(name, &block) builder = StyleBuilder.new(name: name, kind: "theme") theme_definition = builder.evaluate(&block).to_h @themes[theme_definition[:name]] = deep_dup(theme_definition[:params]) end |
#timeline(beats_per_bar: TimelineBuilder::DEFAULT_BEATS_PER_BAR) { ... } ⇒ void
This method returns an undefined value.
Define ordered scene markers and derive transitions between them.
265 266 267 268 269 270 271 272 |
# File 'lib/vizcore/dsl/engine.rb', line 265 def timeline(beats_per_bar: TimelineBuilder::DEFAULT_BEATS_PER_BAR, &block) raise ArgumentError, "timeline requires a block" unless block builder = TimelineBuilder.new(beats_per_bar: , bpm: @analysis_settings[:bpm]).evaluate(&block) entries = builder.to_h @timelines << entries unless entries.empty? @transitions.concat(builder.transitions) end |
#transition(from:, to:) { ... } ⇒ void
This method returns an undefined value.
Define a transition between scenes.
280 281 282 283 284 285 286 287 288 |
# File 'lib/vizcore/dsl/engine.rb', line 280 def transition(from:, to:, &block) definition = { from: from.to_sym, to: to.to_sym } builder = TransitionBuilder.new builder.instance_eval(&block) if block @transitions << definition.merge(builder.to_h) end |