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.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.version +6 -0
  4. data/.yardopts +7 -0
  5. data/README.md +227 -6
  6. data/docs/README.md +83 -0
  7. data/docs/api-reference.md +86 -0
  8. data/docs/getting-started/quick-start.md +93 -0
  9. data/docs/getting-started/tutorial.md +58 -0
  10. data/docs/subsystems/core-extensions.md +316 -0
  11. data/docs/subsystems/datasets.md +465 -0
  12. data/docs/subsystems/generative.md +290 -0
  13. data/docs/subsystems/matrix.md +63 -0
  14. data/docs/subsystems/midi.md +123 -0
  15. data/docs/subsystems/music.md +233 -0
  16. data/docs/subsystems/musicxml-builder.md +264 -0
  17. data/docs/subsystems/neumas.md +71 -0
  18. data/docs/subsystems/repl.md +135 -0
  19. data/docs/subsystems/sequencer.md +98 -0
  20. data/docs/subsystems/series.md +302 -0
  21. data/docs/subsystems/transcription.md +152 -0
  22. data/docs/subsystems/transport.md +177 -0
  23. data/lib/musa-dsl/core-ext/array-explode-ranges.rb +68 -0
  24. data/lib/musa-dsl/core-ext/arrayfy.rb +110 -0
  25. data/lib/musa-dsl/core-ext/attribute-builder.rb +91 -30
  26. data/lib/musa-dsl/core-ext/deep-copy.rb +125 -2
  27. data/lib/musa-dsl/core-ext/dynamic-proxy.rb +78 -0
  28. data/lib/musa-dsl/core-ext/extension.rb +53 -0
  29. data/lib/musa-dsl/core-ext/hashify.rb +162 -1
  30. data/lib/musa-dsl/core-ext/inspect-nice.rb +154 -0
  31. data/lib/musa-dsl/core-ext/smart-proc-binder.rb +117 -0
  32. data/lib/musa-dsl/core-ext/with.rb +114 -0
  33. data/lib/musa-dsl/datasets/dataset.rb +109 -0
  34. data/lib/musa-dsl/datasets/delta-d.rb +78 -0
  35. data/lib/musa-dsl/datasets/e.rb +186 -2
  36. data/lib/musa-dsl/datasets/gdv.rb +279 -2
  37. data/lib/musa-dsl/datasets/gdvd.rb +201 -0
  38. data/lib/musa-dsl/datasets/helper.rb +75 -0
  39. data/lib/musa-dsl/datasets/p.rb +177 -2
  40. data/lib/musa-dsl/datasets/packed-v.rb +91 -0
  41. data/lib/musa-dsl/datasets/pdv.rb +136 -1
  42. data/lib/musa-dsl/datasets/ps.rb +134 -4
  43. data/lib/musa-dsl/datasets/score/queriable.rb +143 -1
  44. data/lib/musa-dsl/datasets/score/render.rb +105 -1
  45. data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +138 -1
  46. data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +111 -0
  47. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +200 -1
  48. data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +145 -1
  49. data/lib/musa-dsl/datasets/score.rb +279 -0
  50. data/lib/musa-dsl/datasets/v.rb +88 -0
  51. data/lib/musa-dsl/generative/darwin.rb +180 -1
  52. data/lib/musa-dsl/generative/generative-grammar.rb +359 -0
  53. data/lib/musa-dsl/generative/markov.rb +133 -3
  54. data/lib/musa-dsl/generative/rules.rb +258 -4
  55. data/lib/musa-dsl/generative/variatio.rb +217 -2
  56. data/lib/musa-dsl/logger/logger.rb +267 -2
  57. data/lib/musa-dsl/matrix/matrix.rb +256 -10
  58. data/lib/musa-dsl/midi/midi-recorder.rb +108 -1
  59. data/lib/musa-dsl/midi/midi-voices.rb +265 -4
  60. data/lib/musa-dsl/music/chord-definition.rb +233 -1
  61. data/lib/musa-dsl/music/chord-definitions.rb +33 -6
  62. data/lib/musa-dsl/music/chords.rb +308 -2
  63. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +315 -0
  64. data/lib/musa-dsl/music/scales.rb +957 -40
  65. data/lib/musa-dsl/musicxml/builder/attributes.rb +483 -3
  66. data/lib/musa-dsl/musicxml/builder/backup-forward.rb +166 -1
  67. data/lib/musa-dsl/musicxml/builder/direction.rb +243 -0
  68. data/lib/musa-dsl/musicxml/builder/helper.rb +240 -0
  69. data/lib/musa-dsl/musicxml/builder/measure.rb +284 -0
  70. data/lib/musa-dsl/musicxml/builder/note-complexities.rb +324 -8
  71. data/lib/musa-dsl/musicxml/builder/note.rb +285 -0
  72. data/lib/musa-dsl/musicxml/builder/part-group.rb +108 -1
  73. data/lib/musa-dsl/musicxml/builder/part.rb +139 -0
  74. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +124 -0
  75. data/lib/musa-dsl/musicxml/builder/rest.rb +93 -0
  76. data/lib/musa-dsl/musicxml/builder/score-partwise.rb +276 -0
  77. data/lib/musa-dsl/musicxml/builder/typed-text.rb +62 -1
  78. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +83 -0
  79. data/lib/musa-dsl/neumalang/neumalang.rb +675 -0
  80. data/lib/musa-dsl/neumas/array-to-neumas.rb +149 -0
  81. data/lib/musa-dsl/neumas/neuma-decoder.rb +253 -0
  82. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +142 -2
  83. data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +82 -0
  84. data/lib/musa-dsl/neumas/neumas.rb +67 -0
  85. data/lib/musa-dsl/neumas/string-to-neumas.rb +233 -1
  86. data/lib/musa-dsl/repl/repl.rb +550 -0
  87. data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +118 -2
  88. data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +149 -2
  89. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +296 -0
  90. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +88 -2
  91. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +161 -0
  92. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +263 -0
  93. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +173 -1
  94. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +177 -0
  95. data/lib/musa-dsl/sequencer/base-sequencer.rb +710 -10
  96. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +210 -0
  97. data/lib/musa-dsl/sequencer/timeslots.rb +79 -0
  98. data/lib/musa-dsl/series/array-to-serie.rb +37 -1
  99. data/lib/musa-dsl/series/base-series.rb +843 -5
  100. data/lib/musa-dsl/series/buffer-serie.rb +48 -0
  101. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +41 -0
  102. data/lib/musa-dsl/series/main-serie-constructors.rb +398 -2
  103. data/lib/musa-dsl/series/main-serie-operations.rb +538 -16
  104. data/lib/musa-dsl/series/proxy-serie.rb +67 -0
  105. data/lib/musa-dsl/series/quantizer-serie.rb +45 -7
  106. data/lib/musa-dsl/series/queue-serie.rb +65 -0
  107. data/lib/musa-dsl/series/series-composer.rb +701 -0
  108. data/lib/musa-dsl/series/timed-serie.rb +473 -28
  109. data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +404 -1
  110. data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +118 -0
  111. data/lib/musa-dsl/transcription/from-gdv.rb +84 -1
  112. data/lib/musa-dsl/transcription/transcription.rb +265 -0
  113. data/lib/musa-dsl/transport/clock.rb +125 -0
  114. data/lib/musa-dsl/transport/dummy-clock.rb +89 -2
  115. data/lib/musa-dsl/transport/external-tick-clock.rb +91 -0
  116. data/lib/musa-dsl/transport/input-midi-clock.rb +133 -1
  117. data/lib/musa-dsl/transport/timer-clock.rb +183 -1
  118. data/lib/musa-dsl/transport/timer.rb +83 -0
  119. data/lib/musa-dsl/transport/transport.rb +318 -0
  120. data/lib/musa-dsl/version.rb +1 -1
  121. data/lib/musa-dsl.rb +132 -25
  122. data/musa-dsl.gemspec +12 -10
  123. 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}"