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
@@ -1,7 +1,42 @@
1
1
  module Musa
2
2
  module Sequencer
3
3
  class BaseSequencer
4
+ # Play evaluation modes for interpreting series elements.
5
+ #
6
+ # PlayEval and its subclasses implement different strategies for interpreting
7
+ # series elements during play operations. Each mode determines:
8
+ # - What operation to perform (call block, launch event, nested play, etc.)
9
+ # - When to continue (now, at position, after wait, on event)
10
+ #
11
+ # ## Available Modes
12
+ #
13
+ # - **:at**: Elements specify absolute positions via :at key
14
+ # - **:wait**: Elements with duration specify wait time
15
+ # - **:neumalang**: Full Neumalang DSL with variables, commands, series, etc.
16
+ #
17
+ # ## Operation Hash Format
18
+ #
19
+ # run_operation returns hash with:
20
+ #
21
+ # - current_operation: :none, :block, :event, :play, :parallel_play, :no_eval_play
22
+ # - current_parameter: data for current operation
23
+ # - continue_operation: :now, :at, :wait, :on
24
+ # - continue_parameter: data for continue operation
25
+ #
26
+ # @api private
4
27
  class PlayEval
28
+ # Factory method creating appropriate evaluator for mode.
29
+ #
30
+ # @param mode [Symbol, nil] evaluation mode
31
+ # @param block_procedure_binder [SmartProcBinder] user block binder
32
+ # @param decoder [Object, nil] decoder for GDVD elements
33
+ # @param nl_context [Object, nil] Neumalang execution context
34
+ #
35
+ # @return [PlayEval] evaluator instance
36
+ #
37
+ # @raise [ArgumentError] if mode unknown
38
+ #
39
+ # @api private
5
40
  def self.create(mode, block_procedure_binder, decoder, nl_context)
6
41
  case mode
7
42
  when :at
@@ -15,16 +50,44 @@ module Musa
15
50
  end
16
51
  end
17
52
 
53
+ # @return [SmartProcBinder] user block binder
18
54
  attr_reader :block_procedure_binder
19
55
 
56
+ # Creates subcontext for nested plays.
57
+ #
58
+ # Default returns self. Neumalang mode creates new isolated context.
59
+ #
60
+ # @return [PlayEval] subcontext evaluator
61
+ #
62
+ # @api private
20
63
  def subcontext
21
64
  self
22
65
  end
23
66
 
67
+ # Evaluates element (mode-dependent).
68
+ #
69
+ # @param _element [Object] element to evaluate
70
+ #
71
+ # @return [Object] evaluated result
72
+ #
73
+ # @raise [NotImplementedError] must be implemented by subclass
74
+ #
75
+ # @api private
24
76
  def eval_element(_element)
25
77
  raise NotImplementedError
26
78
  end
27
79
 
80
+ # Determines operations from element.
81
+ #
82
+ # Returns hash specifying current and continue operations.
83
+ #
84
+ # @param _element [Object] element to process
85
+ #
86
+ # @return [Hash] operation specification
87
+ #
88
+ # @raise [NotImplementedError] must be implemented by subclass
89
+ #
90
+ # @api private
28
91
  def run_operation(_element)
29
92
  raise NotImplementedError
30
93
  end
@@ -32,12 +95,52 @@ module Musa
32
95
 
33
96
  private_constant :PlayEval
34
97
 
98
+ # At-mode evaluator for absolute position scheduling.
99
+ #
100
+ # Interprets elements as data with optional :at key specifying absolute
101
+ # position. If :at present, schedules at that position. Otherwise uses
102
+ # current position.
103
+ #
104
+ # @example At-mode usage
105
+ # seq = Musa::Sequencer::BaseSequencer.new(4, 24)
106
+ #
107
+ # series = Musa::Series::S(
108
+ # { pitch: 60, at: 0r },
109
+ # { pitch: 62, at: 1r },
110
+ # { pitch: 64, at: 2r }
111
+ # )
112
+ #
113
+ # played_notes = []
114
+ #
115
+ # seq.play(series, mode: :at) do |element|
116
+ # played_notes << { pitch: element[:pitch], position: seq.position }
117
+ # end
118
+ #
119
+ # seq.run
120
+ # # Result: played_notes contains [{pitch: 60, position: 0r}, {pitch: 62, position: 1r}, ...]
121
+ #
122
+ # @api private
35
123
  class AtModePlayEval < PlayEval
124
+ # Creates at-mode evaluator.
125
+ #
126
+ # @param block_procedure_binder [SmartProcBinder] user block binder
127
+ #
128
+ # @api private
36
129
  def initialize(block_procedure_binder)
37
130
  @block_procedure_binder = block_procedure_binder
38
131
  super()
39
132
  end
40
133
 
134
+ # Determines operation from element.
135
+ #
136
+ # Hash elements with :at key schedule at absolute position.
137
+ # Other elements use current position.
138
+ #
139
+ # @param element [Hash, Object] element to process
140
+ #
141
+ # @return [Hash] operation hash
142
+ #
143
+ # @api private
41
144
  def run_operation(element)
42
145
  value = nil
43
146
 
@@ -64,14 +167,59 @@ module Musa
64
167
 
65
168
  private_constant :AtModePlayEval
66
169
 
170
+ # Wait-mode evaluator for duration-based scheduling.
171
+ #
172
+ # Interprets elements as data with duration (AbsD compatible) specifying
173
+ # wait time before next element. Supports :wait_event for event-driven
174
+ # continuation.
175
+ #
176
+ # @example Wait-mode with duration
177
+ # seq = Musa::Sequencer::BaseSequencer.new(4, 24)
178
+ #
179
+ # series = Musa::Series::S(
180
+ # { pitch: 60, duration: 1r },
181
+ # { pitch: 62, duration: 0.5r },
182
+ # { pitch: 64, duration: 1.5r }
183
+ # )
184
+ #
185
+ # played_notes = []
186
+ #
187
+ # seq.play(series, mode: :wait) do |element|
188
+ # played_notes << { pitch: element[:pitch], duration: element[:duration], position: seq.position }
189
+ # end
190
+ #
191
+ # seq.run
192
+ # # Result: played_notes contains [{pitch: 60, duration: 1r, position: 0}, ...]
193
+ #
194
+ # @example Wait-mode with event
195
+ # # Elements can also use :wait_event for event-driven continuation
196
+ # # Example: { pitch: 60, wait_event: :next }
197
+ #
198
+ # @api private
67
199
  class WaitModePlayEval < PlayEval
68
200
  include Musa::Datasets
69
201
 
202
+ # Creates wait-mode evaluator.
203
+ #
204
+ # @param block_procedure_binder [SmartProcBinder] user block binder
205
+ #
206
+ # @api private
70
207
  def initialize(block_procedure_binder)
71
208
  @block_procedure_binder = block_procedure_binder
72
209
  super()
73
210
  end
74
211
 
212
+ # Determines operation from element.
213
+ #
214
+ # AbsD-compatible elements wait by duration.
215
+ # Elements with :wait_event continue on event.
216
+ # Other elements continue immediately.
217
+ #
218
+ # @param element [Hash, Object] element to process
219
+ #
220
+ # @return [Hash] operation hash
221
+ #
222
+ # @api private
75
223
  def run_operation(element)
76
224
  value = nil
77
225
 
@@ -112,16 +260,84 @@ module Musa
112
260
 
113
261
  private_constant :WaitModePlayEval
114
262
 
263
+ # Neumalang-mode evaluator for full DSL support.
264
+ #
265
+ # Implements complete Neumalang DSL evaluation including:
266
+ #
267
+ # - Variables (assign, use)
268
+ # - Commands (code blocks with parameters)
269
+ # - Series (nested plays)
270
+ # - Parallel execution
271
+ # - GDVD decoding
272
+ # - P (pattern) sequences
273
+ # - Event launching
274
+ # - Method chaining
275
+ #
276
+ # ## Neumalang Elements
277
+ #
278
+ # Elements have :kind specifying type:
279
+ #
280
+ # - :value - Simple value
281
+ # - :gdvd - Generative Diatonic Value/Duration
282
+ # - :p - Pattern sequence
283
+ # - :serie - Nested series
284
+ # - :parallel - Parallel series
285
+ # - :assign_to - Variable assignment
286
+ # - :use_variable - Variable reference
287
+ # - :command - Code block execution
288
+ # - :event - Event launch
289
+ # - :call_methods - Method chain
290
+ #
291
+ # ## Context Isolation
292
+ #
293
+ # Each nested play gets isolated subcontext with:
294
+ #
295
+ # - Shared neumalang context (variables persist)
296
+ # - Fresh decoder subcontext
297
+ # - Hierarchical ID for debugging
298
+ #
299
+ # @example Neumalang mode
300
+ # seq = Musa::Sequencer::BaseSequencer.new(4, 24)
301
+ #
302
+ # scale = Musa::Scales::Scales.et12[440.0].major[60]
303
+ # decoder = Musa::Neumas::Decoders::NeumaDecoder.new(scale, base_duration: 1/4r)
304
+ #
305
+ # using Musa::Extension::Neumas
306
+ # neumalang_series = "0 +2 +2 -1 0".to_neumas
307
+ #
308
+ # played_notes = []
309
+ #
310
+ # seq.play(neumalang_series, mode: :neumalang, decoder: decoder) do |gdv|
311
+ # played_notes << { pitch: gdv[:pitch], duration: gdv[:duration], velocity: gdv[:velocity] }
312
+ # end
313
+ #
314
+ # seq.run
315
+ # # Result: played_notes contains decoded GDVD values with pitch, duration, velocity
316
+ #
317
+ # @api private
115
318
  class NeumalangModePlayEval < PlayEval
116
319
  include Musa::Datasets
117
320
 
321
+ # Marker module for parallel series.
322
+ #
323
+ # @api private
118
324
  module Parallel end
119
325
 
120
326
  @@id = 0
121
327
 
328
+ # @return [Object] Neumalang execution context
329
+ # @return [SmartProcBinder] user block binder
122
330
  attr_reader :neumalang_context,
123
331
  :block_procedure_binder
124
332
 
333
+ # Creates Neumalang-mode evaluator.
334
+ #
335
+ # @param block_procedure_binder [SmartProcBinder] user block binder
336
+ # @param decoder [Object] GDVD decoder
337
+ # @param nl_context [Object, nil] Neumalang context (creates if nil)
338
+ # @param parent [NeumalangModePlayEval, nil] parent evaluator
339
+ #
340
+ # @api private
125
341
  def initialize(block_procedure_binder, decoder, nl_context, parent: nil)
126
342
  @id = @@id += 1
127
343
  @parent = parent
@@ -135,10 +351,29 @@ module Musa
135
351
  super()
136
352
  end
137
353
 
354
+ # Creates isolated subcontext for nested plays.
355
+ #
356
+ # Shares neumalang context but creates fresh decoder subcontext.
357
+ #
358
+ # @return [NeumalangModePlayEval] subcontext evaluator
359
+ #
360
+ # @api private
138
361
  def subcontext
139
362
  NeumalangModePlayEval.new @block_procedure_binder, @decoder.subcontext, @nl_context, parent: self
140
363
  end
141
364
 
365
+ # Evaluates Neumalang element by kind.
366
+ #
367
+ # Dispatches to appropriate eval_* method based on element :kind.
368
+ # AbsD-compatible elements converted directly.
369
+ #
370
+ # @param element [Hash, Object] element to evaluate
371
+ #
372
+ # @return [Object] evaluated result
373
+ #
374
+ # @raise [ArgumentError] if element kind unknown
375
+ #
376
+ # @api private
142
377
  def eval_element(element)
143
378
  if AbsD.is_compatible?(element)
144
379
  AbsD.to_AbsD(element)
@@ -161,28 +396,55 @@ module Musa
161
396
  end
162
397
  end
163
398
 
399
+ # @api private
164
400
  def eval_value(value)
165
401
  value
166
402
  end
167
403
 
404
+ # Decodes GDVD (Generative Diatonic Value/Duration) element.
405
+ #
406
+ # @param gdvd [Object] GDVD element
407
+ # @return [Object] decoded value
408
+ # @api private
168
409
  def eval_gdvd(gdvd)
169
410
  @decoder.decode(gdvd)
170
411
  end
171
412
 
413
+ # Converts P (pattern) to series.
414
+ #
415
+ # @param p [Object] pattern object
416
+ # @return [Series] pattern series instance
417
+ # @api private
172
418
  def eval_p(p)
173
419
  p.to_ps_serie(base_duration: @decoder.base_duration).instance
174
420
  end
175
421
 
422
+ # Evaluates series with subcontext.
423
+ #
424
+ # @param serie [Object] series definition
425
+ # @return [Series] evaluated series instance
426
+ # @api private
176
427
  def eval_serie(serie)
177
428
  context = subcontext
178
429
  serie.instance.eval(on_restart: proc { context = subcontext }) { |e| context.eval_element e }
179
430
  end
180
431
 
432
+ # Evaluates parallel series.
433
+ #
434
+ # @param series [Array] array of series definitions
435
+ # @return [Array] array of series instances (with Parallel marker)
436
+ # @api private
181
437
  def eval_parallel(series)
182
438
  context = subcontext
183
439
  series.collect { |s| context.eval_serie s[:serie] }.extend Parallel
184
440
  end
185
441
 
442
+ # Assigns value to variable(s) in neumalang context.
443
+ #
444
+ # @param variable_names [Array<Symbol>] variable names
445
+ # @param value [Object] value to assign
446
+ # @return [Object] assigned value
447
+ # @api private
186
448
  def eval_assign_to(variable_names, value)
187
449
  _value = nil
188
450
 
@@ -193,6 +455,12 @@ module Musa
193
455
  _value
194
456
  end
195
457
 
458
+ # Retrieves variable value from neumalang context.
459
+ #
460
+ # @param variable_name [Symbol] variable name
461
+ # @return [Object] variable value
462
+ # @raise [NameError] if variable not defined
463
+ # @api private
196
464
  def eval_use_variable(variable_name)
197
465
  if @nl_context.instance_variable_defined?(variable_name)
198
466
  @nl_context.instance_variable_get(variable_name)
@@ -201,6 +469,15 @@ module Musa
201
469
  end
202
470
  end
203
471
 
472
+ # Executes command block in neumalang context.
473
+ #
474
+ # Evaluates parameters then executes block via instance_exec.
475
+ #
476
+ # @param block [Proc] command block
477
+ # @param value_parameters [Array, nil] positional parameters
478
+ # @param key_parameters [Hash, nil] keyword parameters
479
+ # @return [Object] command result
480
+ # @api private
204
481
  def eval_command(block, value_parameters, key_parameters)
205
482
  _value_parameters = value_parameters&.collect { |e| subcontext.eval_element(e) } || []
206
483
  _key_parameters = key_parameters&.transform_values { |e| subcontext.eval_element(e) } || {}
@@ -211,6 +488,7 @@ module Musa
211
488
  @nl_context.instance_exec *_value_parameters, **_key_parameters, &block
212
489
  end
213
490
 
491
+ # @api private
214
492
  def eval_call_methods(on, call_methods)
215
493
  play_eval = subcontext
216
494
 
@@ -223,6 +501,7 @@ module Musa
223
501
  end
224
502
  end
225
503
 
504
+ # @api private
226
505
  def eval_methods(play_eval, value, methods)
227
506
  methods.each do |methd|
228
507
  value_parameters = methd[:value_parameters]&.collect { |e| play_eval.subcontext.eval_element(e) } || []
@@ -235,10 +514,12 @@ module Musa
235
514
  value
236
515
  end
237
516
 
517
+ # @api private
238
518
  def eval_command_reference(element)
239
519
  element[:command]
240
520
  end
241
521
 
522
+ # @api private
242
523
  def eval_proc_parameter(element)
243
524
  case element
244
525
  when Proc
@@ -257,6 +538,21 @@ module Musa
257
538
  end
258
539
  end
259
540
 
541
+ # Determines operation from Neumalang element.
542
+ #
543
+ # Dispatches based on element type and :kind, returning operation hash
544
+ # specifying current and continue operations.
545
+ #
546
+ # Handles all Neumalang element types including values, series, parallel
547
+ # plays, variables, commands, and events.
548
+ #
549
+ # @param element [Object] element to process
550
+ #
551
+ # @return [Hash] operation hash
552
+ #
553
+ # @raise [ArgumentError] if element kind unknown
554
+ #
555
+ # @api private
260
556
  def run_operation(element)
261
557
  case element
262
558
  when nil
@@ -4,6 +4,20 @@ module Musa::Sequencer
4
4
  class BaseSequencer
5
5
  using Musa::Extension::InspectNice
6
6
 
7
+ # Initializes timed series playback.
8
+ #
9
+ # Peeks first element to determine mode (hash/array), extract component
10
+ # IDs and extra attribute names. Then delegates to _play_timed_step for
11
+ # recursive processing.
12
+ #
13
+ # @param timed_serie [Series] timed series with :time and :value
14
+ # @param start_position [Rational] position offset for all times
15
+ # @param control [PlayTimedControl] control object
16
+ # @yield block to call for each element with values and metadata
17
+ #
18
+ # @return [void]
19
+ #
20
+ # @api private
7
21
  private def _play_timed(timed_serie, start_position, control, &block)
8
22
 
9
23
  if first_value_sample = timed_serie.peek_next_value
@@ -28,6 +42,38 @@ module Musa::Sequencer
28
42
  start_position, last_positions, binder, control)
29
43
  end
30
44
 
45
+ # Recursively processes timed series elements.
46
+ #
47
+ # Gets next element, extracts values for affected components, calculates
48
+ # started_ago for unchanged components, schedules user block at element's
49
+ # time, and recursively calls itself for next element.
50
+ #
51
+ # ## Component Processing
52
+ #
53
+ # Only processes components with non-nil values in current element.
54
+ # Tracks last update position per component to calculate started_ago.
55
+ #
56
+ # ## Block Parameters
57
+ #
58
+ # Calls user block with:
59
+ # - values (hash or array of current values)
60
+ # - extra_attributes (hash per attribute name, or array)
61
+ # - time: absolute position (start_position + element time)
62
+ # - started_ago: hash/array of deltas since last update per component
63
+ # - control: control object
64
+ #
65
+ # @param hash_mode [Boolean] true if hash mode, false if array mode
66
+ # @param component_ids [Array] component identifiers (keys or indices)
67
+ # @param extra_attribute_names [Set] names of extra attributes
68
+ # @param timed_serie [Series] series being played
69
+ # @param start_position [Rational] position offset
70
+ # @param last_positions [Hash, Array] last update positions per component
71
+ # @param binder [SmartProcBinder] user block binder
72
+ # @param control [PlayTimedControl] control object
73
+ #
74
+ # @return [void]
75
+ #
76
+ # @api private
31
77
  private def _play_timed_step(hash_mode,
32
78
  component_ids, extra_attribute_names,
33
79
  timed_serie,
@@ -87,9 +133,31 @@ module Musa::Sequencer
87
133
  end
88
134
  end
89
135
 
136
+ # Control object for play_timed operations.
137
+ #
138
+ # Manages lifecycle of timed series playback with callbacks.
139
+ # Simpler than PlayControl - no pause/continue support.
140
+ #
141
+ # @example Basic play_timed control
142
+ # control = sequencer.play_timed(timed_series) { |values| ... }
143
+ # control.on_stop { puts "Playback finished!" }
144
+ # control.after(2r) { puts "2 bars after end" }
145
+ #
146
+ # @api private
90
147
  class PlayTimedControl < EventHandler
91
- attr_reader :do_on_stop, :do_after
92
-
148
+ # @return [Array<Proc>] stop callbacks
149
+ attr_reader :do_on_stop
150
+ # @return [Array<Hash>] after callbacks with delays
151
+ attr_reader :do_after
152
+
153
+ # Creates play_timed control with callbacks.
154
+ #
155
+ # @param parent [EventHandler] parent event handler
156
+ # @param on_stop [Proc, nil] stop callback
157
+ # @param after_bars [Rational, nil] delay for after callback
158
+ # @param after [Proc, nil] after callback block
159
+ #
160
+ # @api private
93
161
  def initialize(parent, on_stop: nil, after_bars: nil, after: nil)
94
162
  super parent
95
163
  @do_on_stop = []
@@ -99,10 +167,28 @@ module Musa::Sequencer
99
167
  self.after after_bars, &after if after
100
168
  end
101
169
 
170
+ # Registers callback for when playback stops.
171
+ #
172
+ # @yield stop callback block
173
+ #
174
+ # @return [void]
175
+ #
176
+ # @api private
102
177
  def on_stop(&block)
103
178
  @do_on_stop << block
104
179
  end
105
180
 
181
+ # Registers callback to execute after playback completes.
182
+ #
183
+ # @param bars [Numeric, nil] delay in bars after completion (default: 0)
184
+ # @yield after callback block
185
+ #
186
+ # @return [void]
187
+ #
188
+ # @example Delayed callback
189
+ # control.after(4r) { puts "4 bars after playback ends" }
190
+ #
191
+ # @api private
106
192
  def after(bars = nil, &block)
107
193
  bars ||= 0
108
194
  @do_after << { bars: bars.rationalize, block: block }