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_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.
-
#midi(name, **options) ⇒ void
Register a MIDI input definition.
-
#midi_map(note: nil, cc: nil, pc: nil) { ... } ⇒ 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) { ... } ⇒ void
Define a beat-counted song section as a scene and auto-transition to the following section.
-
#set(key, value) ⇒ Object
Set a mutable global value shared with scene/runtime logic.
-
#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.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/vizcore/dsl/engine.rb', line 71 def initialize @audio_inputs = [] @midi_inputs = [] @scenes = [] @transitions = [] @midi_mappings = [] @key_mappings = [] @global_params = {} @analysis_settings = {} @section_tail = nil @timelines = [] @styles = {} @themes = {} @scene_registry = {} end |
Class Method Details
.current ⇒ Vizcore::DSL::Engine?
Returns current thread-local DSL engine.
56 57 58 |
# File 'lib/vizcore/dsl/engine.rb', line 56 def current Thread.current[THREAD_KEY] end |
.define { ... } ⇒ Hash
Evaluate a DSL block using the current thread-local engine, or a new engine.
21 22 23 24 |
# File 'lib/vizcore/dsl/engine.rb', line 21 def define(&block) engine = current || new engine.evaluate(&block) end |
.load_file(path) ⇒ Hash
Load and evaluate a scene file.
31 32 33 34 35 36 37 38 |
# File 'lib/vizcore/dsl/engine.rb', line 31 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.
48 49 50 51 52 53 |
# File 'lib/vizcore/dsl/engine.rb', line 48 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.
101 102 103 |
# File 'lib/vizcore/dsl/engine.rb', line 101 def audio(name, **) @audio_inputs << { name: name.to_sym, options: symbolize_keys() } end |
#audio_normalize(mode: :adaptive, **options) ⇒ Hash
Configure analysis-level audio feature normalization.
141 142 143 144 |
# File 'lib/vizcore/dsl/engine.rb', line 141 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.
150 151 152 |
# File 'lib/vizcore/dsl/engine.rb', line 150 def bpm(value) @analysis_settings[:bpm] = positive_float(value, "bpm") end |
#bpm_lock(value = true) ⇒ Boolean
Enable or disable fixed BPM output.
158 159 160 |
# File 'lib/vizcore/dsl/engine.rb', line 158 def bpm_lock(value = true) @analysis_settings[:bpm_lock] = !!value end |
#evaluate { ... } ⇒ Hash
Evaluate DSL methods on this engine instance.
91 92 93 94 |
# File 'lib/vizcore/dsl/engine.rb', line 91 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.
261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/vizcore/dsl/engine.rb', line 261 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 |
#midi(name, **options) ⇒ void
This method returns an undefined value.
Register a MIDI input definition.
132 133 134 |
# File 'lib/vizcore/dsl/engine.rb', line 132 def midi(name, **) @midi_inputs << { name: name.to_sym, options: symbolize_keys() } end |
#midi_map(note: nil, cc: nil, pc: nil) { ... } ⇒ void
This method returns an undefined value.
Register a MIDI trigger/action mapping.
242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/vizcore/dsl/engine.rb', line 242 def midi_map(note: nil, cc: nil, pc: nil, &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? @midi_mappings << { trigger: trigger, action: block } end |
#result ⇒ Hash
Returns deep-copied definition payload for renderer/runtime.
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/vizcore/dsl/engine.rb', line 284 def result 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) }, 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[: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.
179 180 181 182 183 184 185 |
# File 'lib/vizcore/dsl/engine.rb', line 179 def scene(name, extends: nil, &block) builder = SceneBuilder.new(name: name, styles: @styles, themes: @themes, layers: inherited_layers(extends)) 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) { ... } ⇒ void
This method returns an undefined value.
Define a beat-counted song section as a scene and auto-transition to the following section.
195 196 197 198 199 200 201 202 |
# File 'lib/vizcore/dsl/engine.rb', line 195 def section(name, bars:, beats_per_bar: 4, &block) section_name = name.to_sym section_beats = positive_integer(, "section bars") * positive_integer(, "beats_per_bar") scene(section_name, &block) add_section_transition(to: section_name) if @section_tail @section_tail = { name: section_name, beats: section_beats } end |
#set(key, value) ⇒ Object
Set a mutable global value shared with scene/runtime logic.
279 280 281 |
# File 'lib/vizcore/dsl/engine.rb', line 279 def set(key, value) @global_params[key.to_sym] = value end |
#style(name) { ... } ⇒ void
This method returns an undefined value.
Register a reusable layer parameter style.
110 111 112 113 114 |
# File 'lib/vizcore/dsl/engine.rb', line 110 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.
166 167 168 169 170 171 |
# File 'lib/vizcore/dsl/engine.rb', line 166 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.
121 122 123 124 125 |
# File 'lib/vizcore/dsl/engine.rb', line 121 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.
209 210 211 212 213 214 215 216 |
# File 'lib/vizcore/dsl/engine.rb', line 209 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: ).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.
224 225 226 227 228 229 230 231 232 |
# File 'lib/vizcore/dsl/engine.rb', line 224 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 |