musa-dsl 0.30.2 → 0.40.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/.gitignore +3 -1
- data/.version +6 -0
- data/.yardopts +7 -0
- data/README.md +227 -6
- data/docs/README.md +83 -0
- data/docs/api-reference.md +86 -0
- data/docs/getting-started/quick-start.md +93 -0
- data/docs/getting-started/tutorial.md +58 -0
- data/docs/subsystems/core-extensions.md +316 -0
- data/docs/subsystems/datasets.md +465 -0
- data/docs/subsystems/generative.md +290 -0
- data/docs/subsystems/matrix.md +63 -0
- data/docs/subsystems/midi.md +123 -0
- data/docs/subsystems/music.md +233 -0
- data/docs/subsystems/musicxml-builder.md +264 -0
- data/docs/subsystems/neumas.md +71 -0
- data/docs/subsystems/repl.md +135 -0
- data/docs/subsystems/sequencer.md +98 -0
- data/docs/subsystems/series.md +302 -0
- data/docs/subsystems/transcription.md +152 -0
- data/docs/subsystems/transport.md +177 -0
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +68 -0
- data/lib/musa-dsl/core-ext/arrayfy.rb +110 -0
- data/lib/musa-dsl/core-ext/attribute-builder.rb +91 -30
- data/lib/musa-dsl/core-ext/deep-copy.rb +125 -2
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +78 -0
- data/lib/musa-dsl/core-ext/extension.rb +53 -0
- data/lib/musa-dsl/core-ext/hashify.rb +162 -1
- data/lib/musa-dsl/core-ext/inspect-nice.rb +154 -0
- data/lib/musa-dsl/core-ext/smart-proc-binder.rb +117 -0
- data/lib/musa-dsl/core-ext/with.rb +114 -0
- data/lib/musa-dsl/datasets/dataset.rb +109 -0
- data/lib/musa-dsl/datasets/delta-d.rb +78 -0
- data/lib/musa-dsl/datasets/e.rb +186 -2
- data/lib/musa-dsl/datasets/gdv.rb +279 -2
- data/lib/musa-dsl/datasets/gdvd.rb +201 -0
- data/lib/musa-dsl/datasets/helper.rb +75 -0
- data/lib/musa-dsl/datasets/p.rb +177 -2
- data/lib/musa-dsl/datasets/packed-v.rb +91 -0
- data/lib/musa-dsl/datasets/pdv.rb +136 -1
- data/lib/musa-dsl/datasets/ps.rb +134 -4
- data/lib/musa-dsl/datasets/score/queriable.rb +143 -1
- data/lib/musa-dsl/datasets/score/render.rb +105 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +138 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +111 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +200 -1
- data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +145 -1
- data/lib/musa-dsl/datasets/score.rb +279 -0
- data/lib/musa-dsl/datasets/v.rb +88 -0
- data/lib/musa-dsl/generative/darwin.rb +180 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +359 -0
- data/lib/musa-dsl/generative/markov.rb +133 -3
- data/lib/musa-dsl/generative/rules.rb +258 -4
- data/lib/musa-dsl/generative/variatio.rb +217 -2
- data/lib/musa-dsl/logger/logger.rb +267 -2
- data/lib/musa-dsl/matrix/matrix.rb +256 -10
- data/lib/musa-dsl/midi/midi-recorder.rb +108 -1
- data/lib/musa-dsl/midi/midi-voices.rb +265 -4
- data/lib/musa-dsl/music/chord-definition.rb +233 -1
- data/lib/musa-dsl/music/chord-definitions.rb +33 -6
- data/lib/musa-dsl/music/chords.rb +308 -2
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +315 -0
- data/lib/musa-dsl/music/scales.rb +957 -40
- data/lib/musa-dsl/musicxml/builder/attributes.rb +483 -3
- data/lib/musa-dsl/musicxml/builder/backup-forward.rb +166 -1
- data/lib/musa-dsl/musicxml/builder/direction.rb +243 -0
- data/lib/musa-dsl/musicxml/builder/helper.rb +240 -0
- data/lib/musa-dsl/musicxml/builder/measure.rb +284 -0
- data/lib/musa-dsl/musicxml/builder/note-complexities.rb +324 -8
- data/lib/musa-dsl/musicxml/builder/note.rb +285 -0
- data/lib/musa-dsl/musicxml/builder/part-group.rb +108 -1
- data/lib/musa-dsl/musicxml/builder/part.rb +139 -0
- data/lib/musa-dsl/musicxml/builder/pitched-note.rb +124 -0
- data/lib/musa-dsl/musicxml/builder/rest.rb +93 -0
- data/lib/musa-dsl/musicxml/builder/score-partwise.rb +276 -0
- data/lib/musa-dsl/musicxml/builder/typed-text.rb +62 -1
- data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +83 -0
- data/lib/musa-dsl/neumalang/neumalang.rb +675 -0
- data/lib/musa-dsl/neumas/array-to-neumas.rb +149 -0
- data/lib/musa-dsl/neumas/neuma-decoder.rb +253 -0
- data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +142 -2
- data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +82 -0
- data/lib/musa-dsl/neumas/neumas.rb +67 -0
- data/lib/musa-dsl/neumas/string-to-neumas.rb +233 -1
- data/lib/musa-dsl/repl/repl.rb +550 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +118 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +149 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +296 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +88 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +161 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +263 -0
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +173 -1
- data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +177 -0
- data/lib/musa-dsl/sequencer/base-sequencer.rb +710 -10
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +210 -0
- data/lib/musa-dsl/sequencer/timeslots.rb +79 -0
- data/lib/musa-dsl/series/array-to-serie.rb +37 -1
- data/lib/musa-dsl/series/base-series.rb +843 -5
- data/lib/musa-dsl/series/buffer-serie.rb +48 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +41 -0
- data/lib/musa-dsl/series/main-serie-constructors.rb +398 -2
- data/lib/musa-dsl/series/main-serie-operations.rb +538 -16
- data/lib/musa-dsl/series/proxy-serie.rb +67 -0
- data/lib/musa-dsl/series/quantizer-serie.rb +45 -7
- data/lib/musa-dsl/series/queue-serie.rb +65 -0
- data/lib/musa-dsl/series/series-composer.rb +701 -0
- data/lib/musa-dsl/series/timed-serie.rb +473 -28
- data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +404 -1
- data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +118 -0
- data/lib/musa-dsl/transcription/from-gdv.rb +84 -1
- data/lib/musa-dsl/transcription/transcription.rb +265 -0
- data/lib/musa-dsl/transport/clock.rb +125 -0
- data/lib/musa-dsl/transport/dummy-clock.rb +89 -2
- data/lib/musa-dsl/transport/external-tick-clock.rb +91 -0
- data/lib/musa-dsl/transport/input-midi-clock.rb +133 -1
- data/lib/musa-dsl/transport/timer-clock.rb +183 -1
- data/lib/musa-dsl/transport/timer.rb +83 -0
- data/lib/musa-dsl/transport/transport.rb +318 -0
- data/lib/musa-dsl/version.rb +1 -1
- data/lib/musa-dsl.rb +132 -25
- data/musa-dsl.gemspec +12 -10
- metadata +87 -8
|
@@ -5,6 +5,79 @@ using Musa::Extension::InspectNice
|
|
|
5
5
|
|
|
6
6
|
module Musa::Sequencer
|
|
7
7
|
class BaseSequencer
|
|
8
|
+
# Play implementation for series-based event scheduling.
|
|
9
|
+
#
|
|
10
|
+
# Implements the `play` method that consumes a Musa::Series and schedules
|
|
11
|
+
# events based on series elements. Supports multiple evaluation modes for
|
|
12
|
+
# interpreting series elements (e.g., as timing deltas, absolute positions,
|
|
13
|
+
# or complex data structures).
|
|
14
|
+
#
|
|
15
|
+
# ## Execution Model
|
|
16
|
+
#
|
|
17
|
+
# Play iterates through series elements:
|
|
18
|
+
# 1. Gets next element from series
|
|
19
|
+
# 2. PlayEval evaluates element to determine operations
|
|
20
|
+
# 3. Executes current operation (call block, launch event, nested play, etc.)
|
|
21
|
+
# 4. Schedules continuation based on continue operation
|
|
22
|
+
# 5. Recursively processes next element
|
|
23
|
+
#
|
|
24
|
+
# ## PlayEval System
|
|
25
|
+
#
|
|
26
|
+
# PlayEval.create builds appropriate evaluator based on mode parameter.
|
|
27
|
+
# Evaluator's run_operation method returns hash with:
|
|
28
|
+
#
|
|
29
|
+
# - current_operation: what to do now (:block, :event, :play, etc.)
|
|
30
|
+
# - current_parameter: data for current operation
|
|
31
|
+
# - continue_operation: when to continue (:now, :at, :wait, :on)
|
|
32
|
+
# - continue_parameter: data for continue operation
|
|
33
|
+
#
|
|
34
|
+
# ## Operations
|
|
35
|
+
#
|
|
36
|
+
# Current operations (what to do now):
|
|
37
|
+
#
|
|
38
|
+
# - **:none**: Skip element
|
|
39
|
+
# - **:block**: Call user block with element
|
|
40
|
+
# - **:event**: Launch named event
|
|
41
|
+
# - **:play**: Nested sequential play
|
|
42
|
+
# - **:no_eval_play**: Nested play without evaluation
|
|
43
|
+
# - **:parallel_play**: Multiple parallel plays
|
|
44
|
+
#
|
|
45
|
+
# Continue operations (when to continue):
|
|
46
|
+
#
|
|
47
|
+
# - **:now**: Immediately
|
|
48
|
+
# - **:at**: At absolute position
|
|
49
|
+
# - **:wait**: After time delta
|
|
50
|
+
# - **:on**: When event fires
|
|
51
|
+
#
|
|
52
|
+
# ## Running Modes
|
|
53
|
+
#
|
|
54
|
+
# Different modes interpret series elements differently:
|
|
55
|
+
#
|
|
56
|
+
# - **:at**: Elements specify absolute positions via :at key
|
|
57
|
+
# - **:wait**: Elements with duration specify wait time
|
|
58
|
+
# - **:neumalang**: Full Neumalang DSL with variables, commands, series, etc.
|
|
59
|
+
#
|
|
60
|
+
# @param serie [Series] series to play
|
|
61
|
+
# @param control [PlayControl] control object for lifecycle
|
|
62
|
+
# @param neumalang_context [Object, nil] context for neumalang evaluation
|
|
63
|
+
# @param mode [Symbol, nil] running mode
|
|
64
|
+
# @param decoder [Object, nil] custom decoder
|
|
65
|
+
# @param __play_eval [PlayEval, nil] evaluator (internal, created if nil)
|
|
66
|
+
# @param mode_args [Hash] additional mode-specific arguments
|
|
67
|
+
# @yield block to call for each element (mode-dependent)
|
|
68
|
+
#
|
|
69
|
+
# @return [nil]
|
|
70
|
+
#
|
|
71
|
+
#
|
|
72
|
+
# @api private
|
|
73
|
+
|
|
74
|
+
# Plays series by iterating elements and scheduling events.
|
|
75
|
+
#
|
|
76
|
+
# Recursively consumes series,
|
|
77
|
+
#
|
|
78
|
+
#
|
|
79
|
+
#
|
|
80
|
+
# @api private
|
|
8
81
|
private def _play(serie,
|
|
9
82
|
control,
|
|
10
83
|
neumalang_context = nil,
|
|
@@ -140,9 +213,55 @@ module Musa::Sequencer
|
|
|
140
213
|
nil
|
|
141
214
|
end
|
|
142
215
|
|
|
216
|
+
# Control object for play operations.
|
|
217
|
+
#
|
|
218
|
+
# Manages play lifecycle including pause/continue and after callbacks.
|
|
219
|
+
# Extends EventHandler to support custom events and hierarchical control.
|
|
220
|
+
#
|
|
221
|
+
# ## Pause/Continue
|
|
222
|
+
#
|
|
223
|
+
# When paused:
|
|
224
|
+
# 1. Stores continuation parameters (series state, evaluator, etc.)
|
|
225
|
+
# 2. Stops processing series
|
|
226
|
+
# 3. Awaits continue call
|
|
227
|
+
#
|
|
228
|
+
# When continued:
|
|
229
|
+
# 1. Restores continuation parameters
|
|
230
|
+
# 2. Resumes play from stored position
|
|
231
|
+
#
|
|
232
|
+
# ## After Callbacks
|
|
233
|
+
#
|
|
234
|
+
# Executed after play completes, with optional delay in bars.
|
|
235
|
+
#
|
|
236
|
+
# @example Basic play control
|
|
237
|
+
# seq = Musa::Sequencer::BaseSequencer.new(4, 24)
|
|
238
|
+
#
|
|
239
|
+
# series = Musa::Series::S(60, 62, 64, 65, 67)
|
|
240
|
+
# played_notes = []
|
|
241
|
+
# after_executed = []
|
|
242
|
+
#
|
|
243
|
+
# control = seq.play(series) do |note|
|
|
244
|
+
# played_notes << { pitch: note, position: seq.position }
|
|
245
|
+
# end
|
|
246
|
+
#
|
|
247
|
+
# control.after(2r) { after_executed << seq.position }
|
|
248
|
+
#
|
|
249
|
+
# seq.run
|
|
250
|
+
# # Result: played_notes contains all 5 notes
|
|
251
|
+
# # Result: after_executed contains position 2 bars after play completes
|
|
252
|
+
#
|
|
253
|
+
# @api private
|
|
143
254
|
class PlayControl < EventHandler
|
|
255
|
+
# @return [Array<Hash>] after callbacks with delays
|
|
144
256
|
attr_reader :do_after
|
|
145
257
|
|
|
258
|
+
# Creates play control with optional after callback.
|
|
259
|
+
#
|
|
260
|
+
# @param parent [EventHandler] parent event handler
|
|
261
|
+
# @param after_bars [Rational, nil] delay for after callback
|
|
262
|
+
# @param after [Proc, nil] after callback block
|
|
263
|
+
#
|
|
264
|
+
# @api private
|
|
146
265
|
def initialize(parent, after_bars: nil, after: nil)
|
|
147
266
|
super parent
|
|
148
267
|
|
|
@@ -151,10 +270,34 @@ module Musa::Sequencer
|
|
|
151
270
|
after(after_bars, &after) if after
|
|
152
271
|
end
|
|
153
272
|
|
|
273
|
+
# Pauses play and stores continuation state.
|
|
274
|
+
#
|
|
275
|
+
# Sets paused flag. Continuation must be stored separately via
|
|
276
|
+
# store_continuation.
|
|
277
|
+
#
|
|
278
|
+
# @return [void]
|
|
279
|
+
#
|
|
280
|
+
# @api private
|
|
154
281
|
def pause
|
|
155
282
|
@paused = true
|
|
156
283
|
end
|
|
157
284
|
|
|
285
|
+
# Stores state for continue operation.
|
|
286
|
+
#
|
|
287
|
+
# Saves all parameters needed to resume play from current position.
|
|
288
|
+
# Called automatically by _play when paused.
|
|
289
|
+
#
|
|
290
|
+
# @param sequencer [BaseSequencer] sequencer instance
|
|
291
|
+
# @param serie [Series] series being played
|
|
292
|
+
# @param neumalang_context [Object, nil] neumalang context
|
|
293
|
+
# @param mode [Symbol, nil] evaluation mode
|
|
294
|
+
# @param decoder [Object, nil] decoder
|
|
295
|
+
# @param play_eval [PlayEval] evaluator
|
|
296
|
+
# @param mode_args [Hash] mode arguments
|
|
297
|
+
#
|
|
298
|
+
# @return [void]
|
|
299
|
+
#
|
|
300
|
+
# @api private
|
|
158
301
|
def store_continuation(sequencer:, serie:, neumalang_context:, mode:, decoder:, play_eval:, mode_args:)
|
|
159
302
|
@continuation_sequencer = sequencer
|
|
160
303
|
@continuation_parameters = {
|
|
@@ -167,11 +310,29 @@ module Musa::Sequencer
|
|
|
167
310
|
mode_args: mode_args }
|
|
168
311
|
end
|
|
169
312
|
|
|
313
|
+
# Continues from pause.
|
|
314
|
+
#
|
|
315
|
+
# Restores paused state and resumes play using stored continuation.
|
|
316
|
+
#
|
|
317
|
+
# @return [void]
|
|
318
|
+
#
|
|
319
|
+
# @api private
|
|
170
320
|
def continue
|
|
171
321
|
super
|
|
172
322
|
@continuation_sequencer&.continuation_play(@continuation_parameters)
|
|
173
323
|
end
|
|
174
324
|
|
|
325
|
+
# Registers callback to execute after play completes.
|
|
326
|
+
#
|
|
327
|
+
# @param bars [Numeric, nil] delay in bars after completion (default: 0)
|
|
328
|
+
# @yield after callback block
|
|
329
|
+
#
|
|
330
|
+
# @return [void]
|
|
331
|
+
#
|
|
332
|
+
# @example Delayed callback
|
|
333
|
+
# control.after(4r) { puts "4 bars after play ends" }
|
|
334
|
+
#
|
|
335
|
+
# @api private
|
|
175
336
|
def after(bars = nil, &block)
|
|
176
337
|
bars ||= 0
|
|
177
338
|
@do_after << { bars: bars.rationalize, block: block }
|
|
@@ -5,6 +5,31 @@ module Musa::Sequencer
|
|
|
5
5
|
using Musa::Extension::InspectNice
|
|
6
6
|
|
|
7
7
|
class BaseSequencer
|
|
8
|
+
# Executes all events scheduled at position.
|
|
9
|
+
#
|
|
10
|
+
# Processes the event queue at the given position, executing each command's
|
|
11
|
+
# block in sequence. Handles parent control hierarchy for event handlers,
|
|
12
|
+
# thread safety with mutexes, and cleanup of empty timeslots.
|
|
13
|
+
#
|
|
14
|
+
# ## Execution Flow
|
|
15
|
+
#
|
|
16
|
+
# 1. Calls all before_tick callbacks with position
|
|
17
|
+
# 2. Gets event queue at position from timeslots
|
|
18
|
+
# 3. For each command in queue:
|
|
19
|
+
#
|
|
20
|
+
# - Shifts command from queue
|
|
21
|
+
# - Deletes timeslot if queue becomes empty
|
|
22
|
+
# - Pushes parent_control to event handler stack if present
|
|
23
|
+
# - Executes command block with parameters (mutex-protected)
|
|
24
|
+
# - Pops parent_control from stack
|
|
25
|
+
#
|
|
26
|
+
# 4. Yields to other threads
|
|
27
|
+
#
|
|
28
|
+
# @param position_to_run [Rational] position to execute events at
|
|
29
|
+
#
|
|
30
|
+
# @return [void]
|
|
31
|
+
#
|
|
32
|
+
# @api private
|
|
8
33
|
private def _tick(position_to_run)
|
|
9
34
|
@before_tick.each { |block| block.call position_to_run }
|
|
10
35
|
queue = @timeslots[position_to_run]
|
|
@@ -33,6 +58,24 @@ module Musa::Sequencer
|
|
|
33
58
|
Thread.pass
|
|
34
59
|
end
|
|
35
60
|
|
|
61
|
+
# Low-level event scheduling without control or quantization.
|
|
62
|
+
#
|
|
63
|
+
# Schedules a block at a position with minimal overhead. Used internally
|
|
64
|
+
# for basic scheduling where control hierarchy and quantization are not
|
|
65
|
+
# needed. Executes immediately if at current position, schedules for future
|
|
66
|
+
# if ahead, warns if attempting to schedule in past.
|
|
67
|
+
#
|
|
68
|
+
# @param at_position [Rational] position to schedule at
|
|
69
|
+
# @param force_first [Boolean] if true, insert at front of queue
|
|
70
|
+
# @yield block to execute at position
|
|
71
|
+
#
|
|
72
|
+
# @return [nil]
|
|
73
|
+
#
|
|
74
|
+
# @example Force execution order
|
|
75
|
+
# _raw_numeric_at(1r) { puts "second" }
|
|
76
|
+
# _raw_numeric_at(1r, force_first: true) { puts "first" }
|
|
77
|
+
#
|
|
78
|
+
# @api private
|
|
36
79
|
private def _raw_numeric_at(at_position, force_first: nil, &block)
|
|
37
80
|
force_first ||= false
|
|
38
81
|
|
|
@@ -59,6 +102,33 @@ module Musa::Sequencer
|
|
|
59
102
|
nil
|
|
60
103
|
end
|
|
61
104
|
|
|
105
|
+
# Schedules event with control hierarchy and quantization.
|
|
106
|
+
#
|
|
107
|
+
# Full-featured event scheduling that:
|
|
108
|
+
# - Quantizes position to timing grid (tick-based) or passes through (tickless)
|
|
109
|
+
# - Wraps block in SmartProcBinder for parameter binding and error handling
|
|
110
|
+
# - Passes control parameter to block if it accepts it
|
|
111
|
+
# - Adds debug callbacks if logging enabled
|
|
112
|
+
# - Handles parent_control hierarchy for event handlers
|
|
113
|
+
# - Thread-safe execution
|
|
114
|
+
#
|
|
115
|
+
# ## Position Handling
|
|
116
|
+
#
|
|
117
|
+
# - **Current position**: Executes immediately with try_lock
|
|
118
|
+
# - **Future position**: Adds to timeslots queue
|
|
119
|
+
# - **Past position**: Warns and ignores
|
|
120
|
+
# - **nil position** (tickless before first event): Allows scheduling
|
|
121
|
+
#
|
|
122
|
+
# @param at_position [Rational] position to schedule at
|
|
123
|
+
# @param control [EventHandler] parent control for hierarchy
|
|
124
|
+
# @param debug [Boolean, nil] enable debug callbacks
|
|
125
|
+
# @yield block to execute at position (may accept control:)
|
|
126
|
+
#
|
|
127
|
+
# @return [nil]
|
|
128
|
+
#
|
|
129
|
+
# @raise [ArgumentError] if at_position is nil or block not given
|
|
130
|
+
#
|
|
131
|
+
# @api private
|
|
62
132
|
private def _numeric_at(at_position, control, debug: nil, &block)
|
|
63
133
|
raise ArgumentError, "'at_position' parameter cannot be nil" if at_position.nil?
|
|
64
134
|
raise ArgumentError, 'Yield block is mandatory' unless block
|
|
@@ -101,6 +171,36 @@ module Musa::Sequencer
|
|
|
101
171
|
nil
|
|
102
172
|
end
|
|
103
173
|
|
|
174
|
+
# Recursively schedules events from a series of positions.
|
|
175
|
+
#
|
|
176
|
+
# Implements series-based scheduling by:
|
|
177
|
+
# 1. Getting next position from series
|
|
178
|
+
# 2. Scheduling user block at that position
|
|
179
|
+
# 3. Scheduling recursive call to continue series
|
|
180
|
+
#
|
|
181
|
+
# This enables scheduling blocks at positions generated by a Musa::Series,
|
|
182
|
+
# creating patterns like "every 4 beats" or "at positions from fibonacci
|
|
183
|
+
# sequence". Recursion continues until series is exhausted.
|
|
184
|
+
#
|
|
185
|
+
# ## Recursive Structure
|
|
186
|
+
#
|
|
187
|
+
# Each iteration schedules two events at the same position:
|
|
188
|
+
# - User's block (with debug enabled)
|
|
189
|
+
# - Recursive call to _serie_at (debug disabled to avoid duplication)
|
|
190
|
+
#
|
|
191
|
+
# @param position_or_serie [Series] series yielding positions
|
|
192
|
+
# @param control [EventHandler] parent control for hierarchy
|
|
193
|
+
# @param debug [Boolean, nil] enable debug callbacks
|
|
194
|
+
# @yield block to execute at each position from series
|
|
195
|
+
#
|
|
196
|
+
# @return [nil]
|
|
197
|
+
#
|
|
198
|
+
# @example Series scheduling
|
|
199
|
+
# positions = Musa::Series.from_array([1r, 1.5r, 2r, 3r])
|
|
200
|
+
# _serie_at(positions, control) { puts "event" }
|
|
201
|
+
# # Schedules events at 1r, 1.5r, 2r, 3r
|
|
202
|
+
#
|
|
203
|
+
# @api private
|
|
104
204
|
private def _serie_at(position_or_serie, control, debug: nil, &block)
|
|
105
205
|
bar_position = position_or_serie.next_value
|
|
106
206
|
|
|
@@ -117,6 +217,17 @@ module Musa::Sequencer
|
|
|
117
217
|
nil
|
|
118
218
|
end
|
|
119
219
|
|
|
220
|
+
# Handles errors during event execution.
|
|
221
|
+
#
|
|
222
|
+
# Logs error message and full backtrace, then calls all registered
|
|
223
|
+
# on_error callbacks with the exception. Used by SmartProcBinder and
|
|
224
|
+
# direct rescue blocks to centralize error handling.
|
|
225
|
+
#
|
|
226
|
+
# @param e [Exception] exception that occurred
|
|
227
|
+
#
|
|
228
|
+
# @return [void]
|
|
229
|
+
#
|
|
230
|
+
# @api private
|
|
120
231
|
def _rescue_error(e)
|
|
121
232
|
@logger.error('BaseSequencer') { e.to_s }
|
|
122
233
|
@logger.error('BaseSequencer') { e.full_message(highlight: true, order: :top) }
|
|
@@ -126,11 +237,66 @@ module Musa::Sequencer
|
|
|
126
237
|
end
|
|
127
238
|
end
|
|
128
239
|
|
|
240
|
+
# Hierarchical event handler with parent delegation.
|
|
241
|
+
#
|
|
242
|
+
# EventHandler implements a pub/sub event system with hierarchical event
|
|
243
|
+
# propagation. Handlers can be registered for named events, and events
|
|
244
|
+
# bubble up to parent handlers if not handled locally. Used as control
|
|
245
|
+
# objects for play, every, and move operations.
|
|
246
|
+
#
|
|
247
|
+
# ## Event Hierarchy
|
|
248
|
+
#
|
|
249
|
+
# Events are first checked locally. If no handler is registered, the event
|
|
250
|
+
# propagates to the parent handler. This enables:
|
|
251
|
+
# - Control-specific event handling (e.g., :stop for this play)
|
|
252
|
+
# - Global event handling (e.g., :stop for entire sequencer)
|
|
253
|
+
# - Override parent behavior by registering local handler
|
|
254
|
+
#
|
|
255
|
+
# ## Lifecycle
|
|
256
|
+
#
|
|
257
|
+
# - **stop**: Marks handler as stopped (events no longer execute)
|
|
258
|
+
# - **stopped?**: Checks if stopped
|
|
259
|
+
# - **pause/continue**: Not fully implemented
|
|
260
|
+
#
|
|
261
|
+
# ## Event Registration
|
|
262
|
+
#
|
|
263
|
+
# Use `on(event, &block)` to register handlers. Handlers receive parameters
|
|
264
|
+
# via SmartProcBinder, enabling flexible parameter signatures.
|
|
265
|
+
#
|
|
266
|
+
# @example Basic event handling
|
|
267
|
+
# control = EventHandler.new
|
|
268
|
+
# control.on(:finished) { puts "Done!" }
|
|
269
|
+
# control.launch(:finished) # Prints "Done!"
|
|
270
|
+
#
|
|
271
|
+
# @example Parent delegation
|
|
272
|
+
# parent = EventHandler.new
|
|
273
|
+
# parent.on(:stop) { puts "Parent stops" }
|
|
274
|
+
#
|
|
275
|
+
# child = EventHandler.new(parent)
|
|
276
|
+
# child.launch(:stop) # Prints "Parent stops" (delegates to parent)
|
|
277
|
+
#
|
|
278
|
+
# child.on(:stop) { puts "Child stops" }
|
|
279
|
+
# child.launch(:stop) # Prints "Child stops" (local handler, no delegation)
|
|
280
|
+
#
|
|
281
|
+
# @example One-time handler
|
|
282
|
+
# control.on(:init, only_once: true) { puts "Initialize" }
|
|
283
|
+
# control.launch(:init) # Prints "Initialize"
|
|
284
|
+
# control.launch(:init) # Does nothing (handler removed)
|
|
285
|
+
#
|
|
286
|
+
# @api private
|
|
129
287
|
class EventHandler
|
|
288
|
+
# Parameters for continue operation (not fully implemented).
|
|
289
|
+
#
|
|
290
|
+
# @return [Hash, nil] continue parameters
|
|
130
291
|
attr_accessor :continue_parameters
|
|
131
292
|
|
|
132
293
|
@@counter = 0
|
|
133
294
|
|
|
295
|
+
# Creates event handler with optional parent.
|
|
296
|
+
#
|
|
297
|
+
# @param parent [EventHandler, nil] parent for event delegation
|
|
298
|
+
#
|
|
299
|
+
# @api private
|
|
134
300
|
def initialize(parent = nil)
|
|
135
301
|
@id = (@@counter += 1)
|
|
136
302
|
|
|
@@ -140,26 +306,74 @@ module Musa::Sequencer
|
|
|
140
306
|
@stop = false
|
|
141
307
|
end
|
|
142
308
|
|
|
309
|
+
# Stops this event handler.
|
|
310
|
+
#
|
|
311
|
+
# Marks handler as stopped, preventing future event execution. Used by
|
|
312
|
+
# control objects to halt play/every/move operations.
|
|
313
|
+
#
|
|
314
|
+
# @return [void]
|
|
315
|
+
#
|
|
316
|
+
# @api private
|
|
143
317
|
def stop
|
|
144
318
|
@stop = true
|
|
145
319
|
end
|
|
146
320
|
|
|
321
|
+
# Checks if handler is stopped.
|
|
322
|
+
#
|
|
323
|
+
# @return [Boolean] true if stopped
|
|
324
|
+
#
|
|
325
|
+
# @api private
|
|
147
326
|
def stopped?
|
|
148
327
|
@stop
|
|
149
328
|
end
|
|
150
329
|
|
|
330
|
+
# Pauses handler (not implemented).
|
|
331
|
+
#
|
|
332
|
+
# @raise [NotImplementedError] pause not yet implemented
|
|
333
|
+
#
|
|
334
|
+
# @api private
|
|
151
335
|
def pause
|
|
152
336
|
raise NotImplementedError
|
|
153
337
|
end
|
|
154
338
|
|
|
339
|
+
# Continues from pause (not fully implemented).
|
|
340
|
+
#
|
|
341
|
+
# @return [void]
|
|
342
|
+
#
|
|
343
|
+
# @api private
|
|
155
344
|
def continue
|
|
156
345
|
@paused = false
|
|
157
346
|
end
|
|
158
347
|
|
|
348
|
+
# Checks if handler is paused.
|
|
349
|
+
#
|
|
350
|
+
# @return [Boolean] true if paused
|
|
351
|
+
#
|
|
352
|
+
# @api private
|
|
159
353
|
def paused?
|
|
160
354
|
@paused
|
|
161
355
|
end
|
|
162
356
|
|
|
357
|
+
# Registers event handler.
|
|
358
|
+
#
|
|
359
|
+
# Registers a block to be called when event is launched. Handlers are
|
|
360
|
+
# identified by event name and optional handler name. If only_once is
|
|
361
|
+
# true, handler is removed after first invocation.
|
|
362
|
+
#
|
|
363
|
+
# Block is wrapped in SmartProcBinder for flexible parameter binding.
|
|
364
|
+
#
|
|
365
|
+
# @param event [Symbol] event name to handle
|
|
366
|
+
# @param name [Symbol, nil] optional handler name (for removal)
|
|
367
|
+
# @param only_once [Boolean] remove after first call (default: false)
|
|
368
|
+
# @yield handler block with flexible parameters
|
|
369
|
+
#
|
|
370
|
+
# @return [void]
|
|
371
|
+
#
|
|
372
|
+
# @example Register handler
|
|
373
|
+
# control.on(:finished) { puts "Done" }
|
|
374
|
+
# control.on(:progress, name: :logger) { |pct| puts "#{pct}%" }
|
|
375
|
+
#
|
|
376
|
+
# @api private
|
|
163
377
|
def on(event, name: nil, only_once: nil, &block)
|
|
164
378
|
only_once ||= false
|
|
165
379
|
|
|
@@ -169,10 +383,47 @@ module Musa::Sequencer
|
|
|
169
383
|
@handlers[event][name] = { block: Musa::Extension::SmartProcBinder::SmartProcBinder.new(block), only_once: only_once }
|
|
170
384
|
end
|
|
171
385
|
|
|
386
|
+
# Launches event with parameters.
|
|
387
|
+
#
|
|
388
|
+
# Triggers all registered handlers for the event, passing parameters.
|
|
389
|
+
# If no local handlers exist, delegates to parent handler (bubbling).
|
|
390
|
+
# Supports value parameters (*args), keyword parameters (**kwargs),
|
|
391
|
+
# and block parameter (&block).
|
|
392
|
+
#
|
|
393
|
+
# @param event [Symbol] event name to launch
|
|
394
|
+
# @param value_parameters [Array] positional arguments for handlers
|
|
395
|
+
# @param key_parameters [Hash] keyword arguments for handlers
|
|
396
|
+
# @param proc_parameter [Proc, nil] block parameter for handlers
|
|
397
|
+
#
|
|
398
|
+
# @return [void]
|
|
399
|
+
#
|
|
400
|
+
# @example Launch with parameters
|
|
401
|
+
# control.on(:progress) { |percent| puts "#{percent}%" }
|
|
402
|
+
# control.launch(:progress, 50) # Prints "50%"
|
|
403
|
+
#
|
|
404
|
+
# @example Launch with keyword parameters
|
|
405
|
+
# control.on(:update) { |position:, value:| puts "#{position}: #{value}" }
|
|
406
|
+
# control.launch(:update, position: 1r, value: 60)
|
|
407
|
+
#
|
|
408
|
+
# @api private
|
|
172
409
|
def launch(event, *value_parameters, **key_parameters, &proc_parameter)
|
|
173
410
|
_launch event, value_parameters, key_parameters, proc_parameter
|
|
174
411
|
end
|
|
175
412
|
|
|
413
|
+
# Internal launch implementation with delegation.
|
|
414
|
+
#
|
|
415
|
+
# Processes handlers locally, then delegates to parent if no local
|
|
416
|
+
# handlers processed the event. Removes only_once handlers after
|
|
417
|
+
# first invocation.
|
|
418
|
+
#
|
|
419
|
+
# @param event [Symbol] event name
|
|
420
|
+
# @param value_parameters [Array] positional args
|
|
421
|
+
# @param key_parameters [Hash] keyword args
|
|
422
|
+
# @param proc_parameter [Proc, nil] block arg
|
|
423
|
+
#
|
|
424
|
+
# @return [void]
|
|
425
|
+
#
|
|
426
|
+
# @api private
|
|
176
427
|
def _launch(event, value_parameters = nil, key_parameters = nil, proc_parameter = nil)
|
|
177
428
|
value_parameters ||= []
|
|
178
429
|
key_parameters ||= {}
|
|
@@ -189,10 +440,22 @@ module Musa::Sequencer
|
|
|
189
440
|
@parent._launch event, value_parameters, key_parameters, proc_parameter if @parent && !processed
|
|
190
441
|
end
|
|
191
442
|
|
|
443
|
+
# Returns string representation.
|
|
444
|
+
#
|
|
445
|
+
# @return [String] "EventHandler <id>"
|
|
446
|
+
#
|
|
447
|
+
# @api private
|
|
192
448
|
def inspect
|
|
193
449
|
"EventHandler #{id}"
|
|
194
450
|
end
|
|
195
451
|
|
|
452
|
+
# Returns hierarchical identifier.
|
|
453
|
+
#
|
|
454
|
+
# Builds identifier showing parent chain and instance ID.
|
|
455
|
+
#
|
|
456
|
+
# @return [String] hierarchical ID like "EventHandler-1.PlayControl-5"
|
|
457
|
+
#
|
|
458
|
+
# @api private
|
|
196
459
|
def id
|
|
197
460
|
if @parent
|
|
198
461
|
"#{@parent.id}.#{self.class.name.split('::').last}-#{@id}"
|