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
@@ -3,12 +3,110 @@ require_relative '../core-ext/inspect-nice'
3
3
  require_relative '../sequencer'
4
4
 
5
5
  module Musa
6
+ # Transport system connecting clocks to sequencers.
7
+ #
8
+ # The Transport module provides the infrastructure for managing musical playback,
9
+ # connecting timing sources (clocks) to the sequencer that executes musical events.
10
+ #
11
+ # @see Transport Main transport class
12
+ # @see Clock Clock implementations
13
+ # @see Sequencer Event scheduling
6
14
  module Transport
15
+ # Main transport class connecting clocks to sequencers with lifecycle management.
16
+ #
17
+ # Transport acts as the bridge between a clock (timing source) and a sequencer
18
+ # (event scheduler). It manages the playback lifecycle, including initialization,
19
+ # start/stop, and position changes, with support for callbacks at each stage.
20
+ #
21
+ # ## Architecture
22
+ #
23
+ # Clock --ticks--> Transport --tick()--> Sequencer --events--> Music
24
+ #
25
+ # ## Lifecycle Phases
26
+ #
27
+ # 1. **before_begin**: Run once before first start (initialization)
28
+ # 2. **on_start**: Run each time transport starts
29
+ # 3. **Running**: Clock generates ticks → sequencer processes events
30
+ # 4. **on_position_change**: Run when position jumps/seeks
31
+ # 5. **after_stop**: Run when transport stops
32
+ #
33
+ # ## Position Management
34
+ #
35
+ # Transport handles three position formats:
36
+ #
37
+ # - **bars**: Musical position in bars (Rational)
38
+ # - **beats**: Position in beats
39
+ # - **midi_beats**: Position in MIDI beats (for MIDI Clock sync)
40
+ #
41
+ # ## Use Cases
42
+ #
43
+ # - Standalone compositions with internal timing
44
+ # - DAW-synchronized playback via MIDI Clock
45
+ # - Testing with dummy/external clocks
46
+ # - Live coding with dynamic tempo changes
47
+ #
48
+ # @example Basic setup with TimerClock
49
+ # clock = Musa::Clock::TimerClock.new(bpm: 120)
50
+ # transport = Musa::Transport::Transport.new(
51
+ # clock,
52
+ # beats_per_bar: 4,
53
+ # ticks_per_beat: 24
54
+ # )
55
+ #
56
+ # # Schedule events
57
+ # transport.sequencer.at 0 { puts "Start!" }
58
+ # transport.sequencer.at 4 { puts "Bar 4" }
59
+ #
60
+ # transport.start
61
+ #
62
+ # @example With lifecycle callbacks
63
+ # transport = Transport.new(clock) do |t|
64
+ # t.before_begin { puts "Initializing..." }
65
+ # t.on_start { puts "Started!" }
66
+ # t.after_stop { puts "Stopped, cleaning up..." }
67
+ # end
68
+ #
69
+ # @example MIDI Clock synchronization
70
+ # midi_input = MIDICommunications::Input.all.first
71
+ # clock = Musa::Clock::InputMidiClock.new(midi_input)
72
+ # transport = Transport.new(clock)
73
+ # transport.start # Waits for MIDI Clock Start
74
+ #
75
+ # @see Clock::TimerClock Internal timer-based clock
76
+ # @see Clock::InputMidiClock MIDI Clock synchronized
77
+ # @see Sequencer Event scheduling
7
78
  class Transport
8
79
  using Musa::Extension::InspectNice
9
80
 
81
+ # The sequencer instance managing event scheduling.
82
+ #
83
+ # @return [Sequencer::Sequencer] the sequencer
10
84
  attr_reader :sequencer
11
85
 
86
+ # Creates a new transport connecting a clock to a sequencer.
87
+ #
88
+ # @param clock [Clock] timing source (TimerClock, InputMidiClock, etc.)
89
+ # @param beats_per_bar [Integer, nil] time signature numerator (default: 4)
90
+ # @param ticks_per_beat [Integer, nil] timing resolution (default: 24)
91
+ # @param offset [Rational, nil] time offset in bars (default: 0)
92
+ # @param sequencer [Sequencer, nil] existing sequencer (creates new if nil)
93
+ # @param before_begin [Proc, nil] callback run once before first start
94
+ # @param on_start [Proc, nil] callback run each time transport starts
95
+ # @param after_stop [Proc, nil] callback run when transport stops
96
+ # @param on_position_change [Proc, nil] callback for position changes
97
+ # @param logger [Logger, nil] logger instance
98
+ # @param do_log [Boolean, nil] enable logging
99
+ #
100
+ # @example With callback parameters
101
+ # Transport.new(clock, 4, 24,
102
+ # on_start: -> (seq) { puts "Started at #{seq.position}" }
103
+ # )
104
+ #
105
+ # @example With callback methods
106
+ # transport = Transport.new(clock, 4, 24)
107
+ # transport.before_begin { setup_instruments }
108
+ # transport.on_start { start_recording }
109
+ # transport.after_stop { save_recording }
12
110
  def initialize(clock,
13
111
  beats_per_bar = nil,
14
112
  ticks_per_beat = nil,
@@ -57,24 +155,161 @@ module Musa
57
155
  @clock.on_change_position do |bars: nil, beats: nil, midi_beats: nil|
58
156
  change_position_to bars: bars, beats: beats, midi_beats: midi_beats
59
157
  end
158
+
159
+ # TODO: Consider adding block/DSL support for cleaner initialization syntax.
160
+ #
161
+ # Future enhancement could yield a DSL context that provides direct access to
162
+ # transport methods without requiring the transport variable. This would enable:
163
+ #
164
+ # Transport.new(clock, 4, 24) do
165
+ # before_begin { setup_instruments }
166
+ # on_start { start_recording }
167
+ # after_stop { save_recording }
168
+ # end
169
+ #
170
+ # Instead of current approach:
171
+ #
172
+ # transport = Transport.new(clock, 4, 24)
173
+ # transport.before_begin { setup_instruments }
174
+ # transport.on_start { start_recording }
175
+ # transport.after_stop { save_recording }
176
+ #
177
+ # Implementation would require a DSL context object that delegates methods to
178
+ # the transport instance, similar to the sequencer DSL pattern.
60
179
  end
61
180
 
181
+ # Registers a callback to run once before the first start.
182
+ #
183
+ # before_begin callbacks are run only once, before the very first start.
184
+ # They're ideal for one-time setup like loading samples or initializing state.
185
+ #
186
+ # After a stop, before_begin runs again before the next start.
187
+ #
188
+ # @yield [sequencer] Called before first start
189
+ # @yieldparam sequencer [Sequencer] the sequencer instance
190
+ # @return [void]
191
+ #
192
+ # @example
193
+ # transport.before_begin do |seq|
194
+ # puts "Initializing at position #{seq.position}"
195
+ # load_samples
196
+ # end
62
197
  def before_begin(&block)
63
198
  @before_begin << Musa::Extension::SmartProcBinder::SmartProcBinder.new(block)
64
199
  end
65
200
 
201
+ # Registers a callback to run each time the transport starts.
202
+ #
203
+ # on_start callbacks run every time {#start} is called, after before_begin.
204
+ #
205
+ # @yield [sequencer] Called on each start
206
+ # @yieldparam sequencer [Sequencer] the sequencer instance
207
+ # @return [void]
208
+ #
209
+ # @example
210
+ # transport.on_start do |seq|
211
+ # puts "Starting playback at #{seq.position}"
212
+ # end
66
213
  def on_start(&block)
67
214
  @on_start << Musa::Extension::SmartProcBinder::SmartProcBinder.new(block)
68
215
  end
69
216
 
217
+ # Registers a callback to run when the transport stops.
218
+ #
219
+ # after_stop callbacks run when the clock stops, before the sequencer is reset.
220
+ #
221
+ # @yield [sequencer] Called when stopping
222
+ # @yieldparam sequencer [Sequencer] the sequencer instance
223
+ # @return [void]
224
+ #
225
+ # @example
226
+ # transport.after_stop do |seq|
227
+ # puts "Stopped at position #{seq.position}"
228
+ # cleanup_resources
229
+ # end
70
230
  def after_stop(&block)
71
231
  @after_stop << Musa::Extension::SmartProcBinder::SmartProcBinder.new(block)
72
232
  end
73
233
 
234
+ # Registers a callback for position changes.
235
+ #
236
+ # Called when playback position changes non-linearly (seek/jump), typically
237
+ # from MIDI Song Position Pointer or manual position changes.
238
+ #
239
+ # @yield [sequencer] Called on position change
240
+ # @yieldparam sequencer [Sequencer] the sequencer instance
241
+ # @return [void]
242
+ #
243
+ # @example
244
+ # transport.on_change_position do |seq|
245
+ # puts "Position jumped to #{seq.position}"
246
+ # resync_external_devices
247
+ # end
74
248
  def on_change_position(&block)
75
249
  @on_change_position << Musa::Extension::SmartProcBinder::SmartProcBinder.new(block)
76
250
  end
77
251
 
252
+ # Starts the transport and begins playback.
253
+ #
254
+ # Runs before_begin (if first start or after stop), then starts the clock's
255
+ # run loop. **Behavior depends on the clock type**:
256
+ #
257
+ # ## Clock Activation by Type
258
+ #
259
+ # **DummyClock** (automatic activation):
260
+ # - Starts generating ticks immediately
261
+ # - Blocks until tick count/condition completes
262
+ # - No external activation needed
263
+ #
264
+ # **TimerClock** (external activation required):
265
+ # - Blocks but remains paused until `clock.start` is called
266
+ # - Must call `clock.start()` from another thread to begin ticks
267
+ # - Typical pattern: `Thread.new { transport.start }` then `clock.start`
268
+ #
269
+ # **InputMidiClock** (MIDI activation required):
270
+ # - Blocks waiting for MIDI "Start" (0xFA) message
271
+ # - External DAW/device controls when ticks begin
272
+ # - Automatically starts when MIDI Start received
273
+ #
274
+ # **ExternalTickClock** (manual tick control):
275
+ # - Returns immediately (doesn't block)
276
+ # - Call `clock.tick()` manually to generate each tick
277
+ # - Complete control over timing from external system
278
+ #
279
+ # @return [void]
280
+ #
281
+ # @note Blocking behavior varies by clock type (see above)
282
+ # @note For TimerClock, must call clock.start from separate thread
283
+ #
284
+ # @example With DummyClock (automatic)
285
+ # clock = DummyClock.new(100)
286
+ # transport = Transport.new(clock, 4, 24)
287
+ # transport.start # Runs 100 ticks automatically, then returns
288
+ #
289
+ # @example With TimerClock (external activation)
290
+ # clock = TimerClock.new(bpm: 120)
291
+ # transport = Transport.new(clock, 4, 24)
292
+ #
293
+ # thread = Thread.new { transport.start } # Blocks waiting
294
+ # sleep 0.1
295
+ # clock.start # Activate from external control
296
+ # thread.join
297
+ #
298
+ # @example With InputMidiClock (MIDI activation)
299
+ # input = MIDICommunications::Input.all.first
300
+ # clock = InputMidiClock.new(input)
301
+ # transport = Transport.new(clock, 4, 24)
302
+ # transport.start # Blocks until MIDI Start received from DAW
303
+ #
304
+ # @example With ExternalTickClock (manual control)
305
+ # clock = ExternalTickClock.new
306
+ # transport = Transport.new(clock, 4, 24)
307
+ #
308
+ # thread = Thread.new { transport.start } # Returns immediately
309
+ # sleep 0.1
310
+ # 100.times { clock.tick } # Generate ticks manually
311
+ # transport.stop
312
+ # thread.join
78
313
  def start
79
314
  do_before_begin unless @before_begin_already_done
80
315
 
@@ -84,16 +319,73 @@ module Musa
84
319
  end
85
320
  end
86
321
 
322
+ # Changes the playback position (seek/jump).
323
+ #
324
+ # Handles position changes from various sources, converting between formats.
325
+ # **IMPORTANT**: Position changes trigger fast-forward, which executes all
326
+ # intermediate events between current and target positions.
327
+ #
328
+ # ## Fast-Forward Behavior
329
+ #
330
+ # When changing position forward:
331
+ # 1. Calls `sequencer.on_fast_forward` callbacks with `true` (entering fast-forward)
332
+ # 2. Ticks through all intermediate positions, **executing all scheduled events**
333
+ # 3. Calls `sequencer.on_fast_forward` callbacks with `false` (exiting fast-forward)
334
+ # 4. Calls `on_change_position` callbacks at the new position
335
+ #
336
+ # When changing position backward (requires stop):
337
+ # 1. Stops the transport (calls `after_stop` callbacks)
338
+ # 2. Resets the sequencer to initial state
339
+ # 3. Sets position to target
340
+ # 4. Restarts (calls `on_start` callbacks)
341
+ #
342
+ # ## Handling Intermediate Events
343
+ #
344
+ # During fast-forward, all scheduled events execute. To prevent unwanted sound
345
+ # output (e.g., MIDI notes), use `on_fast_forward` callbacks:
346
+ #
347
+ # - **MIDIVoices integration**: Set `midi_voices.fast_forward = true` during
348
+ # fast-forward to register note state internally without emitting MIDI messages
349
+ # - **Custom handlers**: Check fast-forward state in event handlers to skip
350
+ # audio/visual output during position jumps
351
+ #
352
+ # @param bars [Numeric, nil] target position in bars
353
+ # @param beats [Numeric, nil] offset in beats to add
354
+ # @param midi_beats [Integer, nil] offset in MIDI beats (for MIDI Clock)
355
+ #
356
+ # @return [void]
357
+ #
358
+ # @raise [ArgumentError] if no valid position specified
359
+ #
360
+ # @note Backward seeks trigger stop/restart cycle
361
+ # @note Calls on_change_position callbacks at new position
362
+ # @note Calls sequencer.on_fast_forward callbacks during forward seek
363
+ #
364
+ # @example Jump to bar 8 (fast-forwards through bars 1-7)
365
+ # transport.change_position_to(bars: 8)
366
+ #
367
+ # @example MIDI Song Position Pointer
368
+ # transport.change_position_to(midi_beats: 96) # Bar 4 in 4/4
369
+ #
370
+ # @example Preventing sound during fast-forward with MIDIVoices
371
+ # transport.sequencer.on_fast_forward do |is_starting|
372
+ # midi_voices.fast_forward = is_starting
373
+ # end
374
+ #
375
+ # # Now position changes won't produce audible MIDI output
376
+ # transport.change_position_to(bars: 10)
87
377
  def change_position_to(bars: nil, beats: nil, midi_beats: nil)
88
378
  logger.debug('Transport') do
89
379
  "asked to change position to #{"#{bars} bars " if bars}#{"#{beats} beats " if beats}" \
90
380
  "#{"#{midi_beats} midi beats " if midi_beats}"
91
381
  end
92
382
 
383
+ # Calculate position from provided parameters
93
384
  position = bars&.rationalize || 1r
94
385
  position += Rational(midi_beats, 4 * @sequencer.beats_per_bar) if midi_beats
95
386
  position += Rational(beats, @sequencer.beats_per_bar) if beats
96
387
 
388
+ # Adjust for sequencer offset and tick duration
97
389
  position += @sequencer.offset
98
390
  position -= @sequencer.tick_duration
99
391
 
@@ -103,6 +395,7 @@ module Musa
103
395
 
104
396
  start_again_later = false
105
397
 
398
+ # Backward seek requires stop/restart to reinitialize state
106
399
  if @sequencer.position > position
107
400
  do_stop
108
401
  start_again_later = true
@@ -110,6 +403,7 @@ module Musa
110
403
 
111
404
  logger.debug('Transport') { "setting sequencer position #{position.inspect}" }
112
405
 
406
+ # Schedule position change callback at new position
113
407
  @sequencer.raw_at position, force_first: true do
114
408
  @on_change_position.each { |block| block.call @sequencer }
115
409
  end
@@ -119,28 +413,51 @@ module Musa
119
413
  do_on_start if start_again_later
120
414
  end
121
415
 
416
+ # Stops the transport.
417
+ #
418
+ # Terminates the clock, which triggers the stop sequence (after_stop callbacks,
419
+ # sequencer reset, etc.)
420
+ #
421
+ # @return [void]
122
422
  def stop
123
423
  @clock.terminate
124
424
  end
125
425
 
426
+ # Returns the transport's logger.
427
+ #
428
+ # Delegates to sequencer's logger.
429
+ #
430
+ # @return [Logger] the logger instance
126
431
  def logger
127
432
  @sequencer.logger
128
433
  end
129
434
 
130
435
  private
131
436
 
437
+ # Executes before_begin callbacks.
438
+ #
439
+ # @api private
132
440
  def do_before_begin
133
441
  logger.debug('Transport') { 'doing before_begin initialization...' } unless @before_begin.empty?
134
442
  @before_begin.each { |block| block.call @sequencer }
135
443
  logger.debug('Transport') { 'doing before_begin initialization... done' } unless @before_begin.empty?
136
444
  end
137
445
 
446
+ # Executes on_start callbacks.
447
+ #
448
+ # @api private
138
449
  def do_on_start
139
450
  logger.debug('Transport') { 'starting...' } unless @on_start.empty?
140
451
  @on_start.each { |block| block.call @sequencer }
141
452
  logger.debug('Transport') { 'starting... done' } unless @on_start.empty?
142
453
  end
143
454
 
455
+ # Executes the stop sequence.
456
+ #
457
+ # Runs after_stop callbacks, resets the sequencer, then runs before_begin
458
+ # callbacks (preparing for next start).
459
+ #
460
+ # @api private
144
461
  def do_stop
145
462
  logger.debug('Transport') { 'stopping...' } unless @after_stop.empty?
146
463
  @after_stop.each { |block| block.call @sequencer }
@@ -150,6 +467,7 @@ module Musa
150
467
  @sequencer.reset
151
468
  logger.debug('Transport') { 'resetting sequencer... done' }
152
469
 
470
+ # Prepare for next start
153
471
  do_before_begin
154
472
  @before_begin_already_done = true
155
473
  end
@@ -1,3 +1,3 @@
1
1
  module Musa
2
- VERSION = '0.30.2'.freeze
2
+ VERSION = '0.40.0'.freeze
3
3
  end
data/lib/musa-dsl.rb CHANGED
@@ -24,41 +24,148 @@ require_relative 'musa-dsl/music'
24
24
 
25
25
  require_relative 'musa-dsl/generative'
26
26
 
27
- module Musa::All
28
- # Core
27
+ # Musa-DSL: A Ruby framework and DSL for algorithmic sound and musical thinking and composition.
28
+ #
29
+ # Musa-DSL is a programming language DSL (Domain-Specific Language) based on Ruby designed for
30
+ # sonic and musical composition. It emphasizes the creation of complex temporal structures
31
+ # independently of the audio rendering engine, providing composers and developers with powerful
32
+ # tools for algorithmic composition, generative music, and musical notation.
33
+ #
34
+ # ## Who is it for?
35
+ #
36
+ # - Composers exploring algorithmic composition
37
+ # - Musicians interested in generative music systems
38
+ # - Developers building music applications
39
+ # - Researchers in computational musicology
40
+ # - Live coders and interactive music performers
41
+ #
42
+ # ## Key Features
43
+ #
44
+ # - **Advanced Sequencer** - Precise temporal control for complex polyrhythmic and polytemporal structures
45
+ # - **Transport & Timing** - Multiple clock sources (internal, MIDI, external) with microsecond precision
46
+ # - **Audio Engine Independent** - Works with any MIDI-capable, OSC-capable or any other output hardware or software system
47
+ # - **Series-Based Composition** - Flexible sequence generators for pitches, rhythms, dynamics, and any musical parameter
48
+ # - **Generative Tools** - Markov chains, combinatorial variations (Variatio), rule-based production systems (Rules), formal grammars (GenerativeGrammar), and genetic algorithms (Darwin)
49
+ # - **Matrix Operations** - Mathematical transformations for musical structures
50
+ # - **Scale System** - Comprehensive support for scales, tuning systems, and chord structures
51
+ # - **Neumalang Notation** - Intuitive text-based and customizable musical (or sound) notation
52
+ # - **Transcription System** - Convert musical gestures to MIDI and MusicXML with ornament transcription expansion
53
+ #
54
+ # ## Documentation
55
+ #
56
+ # For complete user manual, tutorials, examples, and detailed subsystem documentation,
57
+ # please visit the Musa-DSL project on GitHub:
58
+ #
59
+ # https://github.com/javier-sy/musa-dsl
60
+ #
61
+ # The README.md file contains comprehensive documentation including:
62
+ #
63
+ # - Quick Start guide
64
+ # - Complete tutorial
65
+ # - System architecture overview
66
+ # - Detailed documentation for all subsystems
67
+ # - Installation instructions
68
+ # - Usage examples
69
+ #
70
+ module Musa
71
+ # Convenience module that includes all Musa DSL components in a single namespace.
29
72
  #
30
- include Musa::Logger
73
+ # This module provides a convenient way to include all Musa DSL functionality
74
+ # into your code with a single `include Musa::All` statement, rather than
75
+ # including each component individually.
76
+ #
77
+ # ## Included Components
78
+ #
79
+ # ### Core Functionality
80
+ #
81
+ # - {Musa::Logger} - Logging utilities
82
+ # - {Musa::Clock} - Timing and clock management
83
+ # - {Musa::Transport} - Transport control (play, stop, tempo)
84
+ # - {Musa::Sequencer} - Event sequencing and scheduling
85
+ # - {Musa::Series} - Series operations and transformations
86
+ # - {Musa::Datasets} - Musical dataset management
87
+ # - {Musa::REPL} - Read-Eval-Print Loop for live coding
88
+ #
89
+ # ### Notation and Language
90
+ #
91
+ # - {Musa::Neumalang} - Neuma language parser
92
+ # - {Musa::Neumas} - Neuma notation system
93
+ #
94
+ # ### Musical Theory
95
+ #
96
+ # - {Musa::Scales} - Scale definitions and operations
97
+ # - {Musa::Chords} - Chord construction and manipulation
98
+ #
99
+ # ### Data Structures
100
+ #
101
+ # - {Musa::Matrix} - Matrix operations for musical data
102
+ #
103
+ # ### Generative Algorithms
104
+ #
105
+ # - {Musa::Darwin} - Evolutionary/genetic algorithms
106
+ # - {Musa::Markov} - Markov chain generation
107
+ # - {Musa::Rules} - Rule-based generative system
108
+ # - {Musa::Variatio} - Combinatorial variation generator
109
+ #
110
+ # ### Input/Output
111
+ #
112
+ # - {Musa::MIDIRecorder} - MIDI event recording
113
+ # - {Musa::MIDIVoices} - MIDI voice management
114
+ # - {Musa::MusicXML} - MusicXML score generation
115
+ # - {Musa::Transcription} - Event transcription system
116
+ # - {Musa::Transcriptors} - Transcriptor implementations
117
+ #
118
+ # ## Usage
119
+ #
120
+ # @example Include all Musa DSL components
121
+ # require 'musa-dsl'
122
+ # include Musa::All
123
+ #
124
+ # # Now you have access to all Musa DSL methods and classes
125
+ # score = S.with(pitches: [60, 62, 64, 65])
126
+ # sequencer = Sequencer.new
127
+ #
128
+ # @example Selective inclusion (alternative)
129
+ # # Instead of Musa::All, you can include only what you need:
130
+ # include Musa::Series
131
+ # include Musa::Sequencer
132
+ module All
133
+ # Core
134
+ #
135
+ include Musa::Logger
31
136
 
32
- include Musa::Clock
33
- include Musa::Transport
34
- include Musa::Sequencer
137
+ include Musa::Clock
138
+ include Musa::Transport
139
+ include Musa::Sequencer
35
140
 
36
- include Musa::Series
37
- include Musa::Datasets
141
+ include Musa::Series
142
+ include Musa::Datasets
38
143
 
39
- include Musa::Neumalang
40
- include Musa::Neumas
144
+ include Musa::Neumalang
145
+ include Musa::Neumas
41
146
 
42
- include Musa::Transcription
147
+ include Musa::Transcription
43
148
 
44
- include Musa::REPL
149
+ include Musa::REPL
45
150
 
46
- # Extensions: ojo, el nombre extensions ya se usa para algunos paquetes de core-ext que funcionan con Refinements
47
- #
48
- include Musa::Scales
49
- include Musa::Chords
151
+ # Extensions: ojo, el nombre extensions ya se usa para algunos paquetes de core-ext que funcionan con Refinements
152
+ #
153
+ include Musa::Scales
154
+ include Musa::Chords
50
155
 
51
- include Musa::Matrix
156
+ # Note: Musa::Extension::Matrix is a refinement and cannot be included
157
+ # Use: using Musa::Extension::Matrix
52
158
 
53
- include Musa::Darwin
54
- include Musa::Markov
55
- include Musa::Rules
56
- include Musa::Variatio
159
+ include Musa::Darwin
160
+ include Musa::Markov
161
+ include Musa::Rules
162
+ include Musa::Variatio
57
163
 
58
- include Musa::MIDIRecorder
59
- include Musa::MIDIVoices
164
+ include Musa::MIDIRecorder
165
+ include Musa::MIDIVoices
60
166
 
61
- include Musa::MusicXML
167
+ include Musa::MusicXML
62
168
 
63
- include Musa::Transcriptors
169
+ include Musa::Transcriptors
170
+ end
64
171
  end
data/musa-dsl.gemspec CHANGED
@@ -12,15 +12,13 @@ Gem::Specification.new do |s|
12
12
  s.homepage = 'https://github.com/javier-sy/musa-dsl'
13
13
  s.license = 'LGPL-3.0-or-later'
14
14
 
15
- s.required_ruby_version = '~> 3.4'
15
+ s.required_ruby_version = '>= 3.4.7'
16
16
 
17
- # TODO para sistema de paquetes de MusaDSL
18
- #s.metadata = {
19
- # "source_code_uri" => "https://",
20
- # "homepage_uri" => "",
21
- # "documentation_uri" => "",
22
- # "changelog_uri" => ""
23
- #}
17
+ s.metadata = {
18
+ 'homepage_uri' => s.homepage,
19
+ 'source_code_uri' => s.homepage,
20
+ 'documentation_uri' => 'https://www.rubydoc.info/gems/musa-dsl'
21
+ }
24
22
 
25
23
  s.add_runtime_dependency 'prime', '~> 0.1'
26
24
  s.add_runtime_dependency 'matrix', '~> 0.4'
@@ -30,9 +28,13 @@ Gem::Specification.new do |s|
30
28
 
31
29
  s.add_runtime_dependency 'citrus', '~> 3.0'
32
30
 
33
- s.add_runtime_dependency 'midi-events', '~> 0.6'
34
- s.add_runtime_dependency 'midi-parser', '~> 0.4'
31
+ s.add_runtime_dependency 'midi-events', '~> 0.7'
32
+ s.add_runtime_dependency 'midi-parser', '~> 0.5'
35
33
 
36
34
  s.add_development_dependency 'descriptive-statistics', '~> 2.2'
37
35
  s.add_development_dependency 'rspec', '~> 3'
36
+ s.add_development_dependency 'yard', '~> 0.9'
37
+ s.add_development_dependency 'redcarpet', '~> 3.6'
38
+ s.add_development_dependency 'webrick', '~> 1.8'
39
+ s.add_development_dependency 'rack', '~> 2.2'
38
40
  end