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,11 +1,104 @@
1
1
  module Musa
2
2
  module Sequencer
3
3
  class BaseSequencer
4
+ # Tick-based timing implementation for BaseSequencer.
5
+ #
6
+ # TickBasedTiming provides quantized time progression where time advances
7
+ # in discrete tick increments. Musical time is divided into bars, beats,
8
+ # and ticks, with position rounded to the nearest tick boundary. This
9
+ # provides a traditional sequencer timing model similar to DAWs and MIDI
10
+ # sequencers.
11
+ #
12
+ # ## Tick-Based Time Model
13
+ #
14
+ # Time is quantized to a grid determined by:
15
+ #
16
+ # - **beats_per_bar**: Time signature numerator (e.g., 4 for 4/4)
17
+ # - **ticks_per_beat**: Subdivisions per beat (resolution)
18
+ # - **ticks_per_bar**: Total ticks = beats_per_bar × ticks_per_beat
19
+ # - **tick_duration**: 1 / ticks_per_bar (Rational)
20
+ #
21
+ # Position always aligns to tick boundaries. Non-aligned positions are
22
+ # rounded with a warning.
23
+ #
24
+ # ## Time Progression
25
+ #
26
+ # - **Forward only**: Position cannot decrease (enforced)
27
+ # - **Tick advancement**: {#tick} increments by tick_duration
28
+ # - **Fast-forward**: {#position=} jumps to future position
29
+ # - **Quantization**: All positions rounded to tick grid
30
+ #
31
+ # ## Fast-Forward Mechanism
32
+ #
33
+ # When jumping forward via `position=`:
34
+ # 1. Triggers on_fast_forward callbacks with `true`
35
+ # 2. Ticks forward until reaching target position
36
+ # 3. Triggers on_fast_forward callbacks with `false`
37
+ # 4. Allows event handlers to skip/optimize during jumps
38
+ #
39
+ # ## Musical Applications
40
+ #
41
+ # - Traditional MIDI sequencing with quantized timing
42
+ # - Grid-aligned rhythmic patterns
43
+ # - Time signature-based composition (4/4, 3/4, etc.)
44
+ # - Tick-precise event scheduling
45
+ #
46
+ # @example Creating tick-based sequencer (4/4, 96 ticks per beat)
47
+ # sequencer = BaseSequencer.new(4, 96) # 4 beats, 96 ticks/beat
48
+ # sequencer.ticks_per_bar # => 384r
49
+ # sequencer.tick_duration # => 1/384r
50
+ # sequencer.position # => 1r (start of bar 1)
51
+ #
52
+ # @example Advancing time with tick
53
+ # sequencer.tick # Advance one tick (1/384 of a bar)
54
+ # sequencer.position # => 385/384r
55
+ #
56
+ # @example Fast-forward to future position
57
+ # sequencer.position = 2r # Jump to bar 2
58
+ # # Triggers on_fast_forward(true), ticks forward, on_fast_forward(false)
59
+ #
60
+ # @example Quantization warning
61
+ # sequencer.at(1.5001r) { puts "event" }
62
+ # # WARN: rounding position 1.5001 to tick precision: 1.5
63
+ #
64
+ # @api private
4
65
  module TickBasedTiming
5
66
  using Musa::Extension::InspectNice
6
67
 
7
- attr_reader :position, :ticks_per_bar, :tick_duration
68
+ # Current playback position in bars (Rational).
69
+ #
70
+ # Always aligned to tick boundaries. Bar 1 starts at position 1r.
71
+ #
72
+ # @return [Rational] current position
73
+ attr_reader :position
8
74
 
75
+ # Total ticks per bar (beats_per_bar × ticks_per_beat).
76
+ #
77
+ # @return [Rational] ticks per bar
78
+ attr_reader :ticks_per_bar
79
+
80
+ # Duration of one tick in bars (1 / ticks_per_bar).
81
+ #
82
+ # @return [Rational] tick duration
83
+ attr_reader :tick_duration
84
+
85
+ # Advances sequencer by one tick.
86
+ #
87
+ # Increments position by tick_duration and executes all events
88
+ # scheduled at the new position. This is the primary time progression
89
+ # method for tick-based sequencers.
90
+ #
91
+ # If ticks are held (during fast-forward), accumulates ticks in buffer
92
+ # without executing events until released.
93
+ #
94
+ # @return [void]
95
+ #
96
+ # @example Normal tick progression
97
+ # sequencer.position # => 1r
98
+ # sequencer.tick
99
+ # sequencer.position # => 385/384r (1 + 1/384)
100
+ #
101
+ # @api public
9
102
  def tick
10
103
  if @hold_public_ticks
11
104
  @hold_ticks += 1
@@ -14,6 +107,41 @@ module Musa
14
107
  end
15
108
  end
16
109
 
110
+ # Fast-forwards sequencer to new position.
111
+ #
112
+ # Jumps to a future position by ticking forward until reaching the
113
+ # target. Executes all scheduled events between current and target
114
+ # positions. Triggers on_fast_forward callbacks to allow handlers
115
+ # to optimize processing during the jump.
116
+ #
117
+ # Position can only move forward - attempting to move backward raises
118
+ # ArgumentError. Position is automatically quantized to tick boundaries.
119
+ #
120
+ # ## Fast-Forward Process
121
+ #
122
+ # 1. Validates new_position >= current position
123
+ # 2. Calls on_fast_forward callbacks with `true` (entering fast-forward)
124
+ # 3. Holds public tick accumulation
125
+ # 4. Ticks forward, executing events at each tick
126
+ # 5. Releases accumulated ticks
127
+ # 6. Calls on_fast_forward callbacks with `false` (exiting fast-forward)
128
+ #
129
+ # @param new_position [Rational] target position (must be >= current)
130
+ #
131
+ # @return [void]
132
+ #
133
+ # @raise [ArgumentError] if new_position < current position
134
+ #
135
+ # @example Jump to future bar
136
+ # sequencer.position # => 1r
137
+ # sequencer.position = 5r # Fast-forward to bar 5
138
+ # # Executes all events from bar 1 to bar 5
139
+ #
140
+ # @example Cannot move backward
141
+ # sequencer.position = 0r
142
+ # # => ArgumentError: cannot move back
143
+ #
144
+ # @api public
17
145
  def position=(new_position)
18
146
  raise ArgumentError,
19
147
  "Sequencer #{self}: cannot move back. current position: #{@position} new position: #{new_position}" \
@@ -28,6 +156,12 @@ module Musa
28
156
  _release_public_ticks
29
157
  end
30
158
 
159
+ # Initializes tick-based timing parameters.
160
+ #
161
+ # Calculates derived timing values from beats_per_bar and ticks_per_beat.
162
+ #
163
+ # @return [void]
164
+ # @api private
31
165
  private def _init_timing
32
166
  @ticks_per_bar = Rational(beats_per_bar * ticks_per_beat)
33
167
  @tick_duration = Rational(1, @ticks_per_bar)
@@ -36,10 +170,34 @@ module Musa
36
170
  @hold_ticks = 0
37
171
  end
38
172
 
173
+ # Resets position to start of first bar.
174
+ #
175
+ # Sets position to 1r + offset - tick_duration, so first tick brings
176
+ # position to 1r + offset (start of bar 1).
177
+ #
178
+ # @return [void]
179
+ # @api private
39
180
  private def _reset_timing
40
181
  @position = @position_mutex.synchronize { 1r + @offset - @tick_duration }
41
182
  end
42
183
 
184
+ # Quantizes position to nearest tick boundary.
185
+ #
186
+ # Rounds non-aligned positions to the tick grid, optionally logging
187
+ # a warning when rounding occurs. Ensures all positions align with
188
+ # tick_duration increments for consistent timing.
189
+ #
190
+ # @param position [Rational] position to quantize
191
+ # @param warn [Boolean] log warning if rounding occurs (default: true)
192
+ #
193
+ # @return [Rational] quantized position aligned to tick grid
194
+ #
195
+ # @example Quantization to tick boundaries
196
+ # # With 384 ticks per bar, tick_duration = 1/384r
197
+ # _quantize_position(1.5001r) # => 1.5r (385/384 ticks)
198
+ # # Logs warning: "rounding position 1.5001 to tick precision: 1.5"
199
+ #
200
+ # @api private
43
201
  private def _quantize_position(position, warn: true)
44
202
  ticks_position = position / @tick_duration
45
203
 
@@ -59,10 +217,24 @@ module Musa
59
217
  position
60
218
  end
61
219
 
220
+ # Holds tick accumulation during fast-forward.
221
+ #
222
+ # Prevents immediate tick execution, buffering ticks for batch release.
223
+ # Used during position= to collect ticks that occur during the jump.
224
+ #
225
+ # @return [void]
226
+ # @api private
62
227
  private def _hold_public_ticks
63
228
  @hold_public_ticks = true
64
229
  end
65
230
 
231
+ # Releases accumulated ticks after fast-forward.
232
+ #
233
+ # Executes all buffered ticks collected during hold period. Restores
234
+ # normal tick processing.
235
+ #
236
+ # @return [void]
237
+ # @api private
66
238
  private def _release_public_ticks
67
239
  @hold_ticks.times { _tick(@position_mutex.synchronize { @position += @tick_duration }) }
68
240
  @hold_ticks = 0
@@ -1,14 +1,105 @@
1
1
  module Musa
2
2
  module Sequencer
3
3
  class BaseSequencer
4
+ # Tickless (continuous) timing implementation for BaseSequencer.
5
+ #
6
+ # TicklessBasedTiming provides continuous, non-quantized time progression
7
+ # where events can be scheduled at any Rational position without rounding
8
+ # to a tick grid. Time advances by jumping directly to the next scheduled
9
+ # event, enabling precise timing for complex musical structures and
10
+ # avoiding quantization artifacts.
11
+ #
12
+ # ## Tickless Time Model
13
+ #
14
+ # - **No quantization**: Positions are exact Rational values
15
+ # - **Event-driven**: Time jumps to next scheduled event
16
+ # - **Infinite resolution**: ticks_per_bar = Float::INFINITY
17
+ # - **Zero tick duration**: tick_duration = 0r
18
+ # - **Continuous time**: No discrete grid or rounding
19
+ #
20
+ # ## Time Progression
21
+ #
22
+ # Unlike tick-based mode that advances in fixed increments, tickless
23
+ # mode advances position directly to the next scheduled event. If events
24
+ # are at positions [1r, 1.25r, 1.5r, 2r], calling {#tick} jumps through
25
+ # these exact positions without intermediate steps.
26
+ #
27
+ # - **Forward only**: Position cannot decrease (enforced)
28
+ # - **Event jumping**: {#tick} moves to next scheduled position
29
+ # - **Fast-forward**: {#position=} executes events up to target
30
+ # - **No rounding**: Positions preserved exactly
31
+ #
32
+ # ## Comparison with Tick-Based Mode
33
+ #
34
+ # | Aspect | Tick-Based | Tickless |
35
+ # |--------|-----------|----------|
36
+ # | Time grid | Quantized to ticks | Continuous |
37
+ # | Resolution | ticks_per_beat | Infinite (Rational) |
38
+ # | Advancement | Fixed tick_duration | Jump to next event |
39
+ # | Rounding | Yes (with warning) | No |
40
+ # | Use case | Traditional sequencing | Complex timing, microtiming |
41
+ #
42
+ # ## Musical Applications
43
+ #
44
+ # - Complex polyrhythms without quantization loss
45
+ # - Precise microtiming and groove timing
46
+ # - Non-isochronous meters and irregular divisions
47
+ # - Algorithmic composition with exact ratios
48
+ # - Avoiding rounding errors in long sequences
49
+ #
50
+ # @example Creating tickless sequencer
51
+ # sequencer = BaseSequencer.new # No tick parameters
52
+ # sequencer.ticks_per_bar # => Float::INFINITY
53
+ # sequencer.tick_duration # => 0r
54
+ # sequencer.position # => nil (before first event)
55
+ #
56
+ # @example Precise timing without quantization
57
+ # sequencer.at(1r) { puts "Event 1" }
58
+ # sequencer.at(1 + 1/7r) { puts "Event 2" } # Exact 1/7 division
59
+ # sequencer.at(1 + 1/3r) { puts "Event 3" } # Exact 1/3 division
60
+ # sequencer.tick # Jumps to 1r
61
+ # sequencer.tick # Jumps to 8/7r (1 + 1/7)
62
+ # sequencer.tick # Jumps to 4/3r (1 + 1/3)
63
+ #
64
+ # @example Complex polyrhythm (5 against 7)
65
+ # require 'musa-dsl'
66
+ #
67
+ # sequencer = Musa::Sequencer::BaseSequencer.new # Tickless mode
68
+ #
69
+ # 7.times { |i| sequencer.at(1 + Rational(i, 7)) { puts "Note A at #{sequencer.position}" } }
70
+ # 5.times { |i| sequencer.at(1 + Rational(i, 5)) { puts "Note B at #{sequencer.position}" } }
71
+ #
72
+ # sequencer.run # Events at exact rational positions
73
+ #
74
+ # @api private
4
75
  module TicklessBasedTiming
5
76
 
77
+ # Current playback position in bars (Rational or nil).
78
+ #
79
+ # Position is nil before first event, then becomes the exact Rational
80
+ # position of the current event. No quantization or rounding occurs.
81
+ #
82
+ # @return [Rational, nil] current position or nil before first event
6
83
  attr_reader :position
7
84
 
85
+ # Returns infinite ticks per bar for tickless mode.
86
+ #
87
+ # Indicates unlimited timing resolution (no tick grid).
88
+ #
89
+ # @return [Float] Float::INFINITY
90
+ #
91
+ # @api public
8
92
  def ticks_per_bar
9
93
  Float::INFINITY
10
94
  end
11
95
 
96
+ # Returns zero tick duration for tickless mode.
97
+ #
98
+ # Indicates infinitesimal tick duration (continuous time).
99
+ #
100
+ # @return [Rational] 0r
101
+ #
102
+ # @api public
12
103
  def tick_duration
13
104
  0r
14
105
  end
@@ -19,6 +110,29 @@ module Musa
19
110
  # TODO tendría sentido sólo si también se usa con ticks temporizados, lo cual ocurriría si se reimplementa el modo tickbased
20
111
  # TODO a partir del modo tickless.
21
112
 
113
+ # Advances sequencer to next scheduled event.
114
+ #
115
+ # Jumps position directly to the next event in the timeslots, skipping
116
+ # any intermediate positions. Executes all handlers scheduled at that
117
+ # position. This is the primary time progression method for tickless
118
+ # sequencers.
119
+ #
120
+ # Unlike tick-based mode with fixed increments, tickless tick jumps to
121
+ # wherever the next event is scheduled, enabling precise event-driven
122
+ # timing without quantization.
123
+ #
124
+ # @return [void]
125
+ #
126
+ # @example Event-driven progression
127
+ # sequencer.at(1r) { puts "A" }
128
+ # sequencer.at(1.5r) { puts "B" }
129
+ # sequencer.at(2r) { puts "C" }
130
+ #
131
+ # sequencer.tick # position becomes 1r, prints "A"
132
+ # sequencer.tick # position becomes 1.5r, prints "B"
133
+ # sequencer.tick # position becomes 2r, prints "C"
134
+ #
135
+ # @api public
22
136
  def tick
23
137
  _tick @position_mutex.synchronize { @position = @timeslots.first_after(@position) }
24
138
  end
@@ -29,6 +143,44 @@ module Musa
29
143
  # TODO Por otro lado cuando se cuantiza en _at se redondea al más cercano, mientras que el modelo basado en avanzar redondea siempre hacia arriba,
30
144
  # TODO pero esto podría resolverse con una opción de cuantización opcional que en _at ajustara la posición a la más cercana.
31
145
 
146
+ # Fast-forwards sequencer to new position.
147
+ #
148
+ # Jumps to a future position by executing all scheduled events between
149
+ # current and target positions. Triggers on_fast_forward callbacks to
150
+ # allow handlers to optimize processing during the jump.
151
+ #
152
+ # Position can only move forward - attempting to move backward raises
153
+ # ArgumentError. Unlike tick-based mode, no quantization occurs.
154
+ #
155
+ # ## Fast-Forward Process
156
+ #
157
+ # 1. Validates new_position >= current position
158
+ # 2. Calls on_fast_forward callbacks with `true` (entering fast-forward)
159
+ # 3. Executes all events at positions <= new_position
160
+ # 4. Sets position to new_position (exact, no rounding)
161
+ # 5. Calls on_fast_forward callbacks with `false` (exiting fast-forward)
162
+ #
163
+ # @param new_position [Rational] target position (must be >= current)
164
+ #
165
+ # @return [void]
166
+ #
167
+ # @raise [ArgumentError] if new_position < current position
168
+ #
169
+ # @example Jump to future position
170
+ # sequencer.at(1.25r) { puts "Event 1" }
171
+ # sequencer.at(1.5r) { puts "Event 2" }
172
+ # sequencer.at(2.75r) { puts "Event 3" }
173
+ #
174
+ # sequencer.position = 2r
175
+ # # Executes events at 1.25r and 1.5r
176
+ # # Position becomes exactly 2r (not 2.75r)
177
+ #
178
+ # @example Cannot move backward
179
+ # sequencer.position = 1r
180
+ # sequencer.tick # position = 1.25r
181
+ # sequencer.position = 1r # => ArgumentError: cannot move back
182
+ #
183
+ # @api public
32
184
  def position=(new_position)
33
185
  raise ArgumentError, "Sequencer #{self}: cannot move back. current position: #{@position} new position: #{new_position}" if new_position < @position
34
186
 
@@ -53,13 +205,38 @@ module Musa
53
205
  @on_fast_forward.each { |block| block.call(false) }
54
206
  end
55
207
 
208
+ # Initializes tickless timing (no-op).
209
+ #
210
+ # Tickless mode requires no timing parameter calculation or setup.
211
+ #
212
+ # @return [void]
213
+ # @api private
56
214
  private def _init_timing
57
215
  end
58
216
 
217
+ # Resets position to nil (before first event).
218
+ #
219
+ # Sets position to nil, indicating sequencer has not yet reached any
220
+ # scheduled event. First tick will advance to first scheduled position.
221
+ #
222
+ # @return [void]
223
+ # @api private
59
224
  private def _reset_timing
60
225
  @position = nil
61
226
  end
62
227
 
228
+ # Returns position unchanged (no quantization).
229
+ #
230
+ # Tickless mode preserves exact Rational positions without rounding.
231
+ # The warn parameter is ignored (included for interface compatibility
232
+ # with tick-based mode).
233
+ #
234
+ # @param position [Rational] position to pass through
235
+ # @param warn [Boolean] ignored (for compatibility)
236
+ #
237
+ # @return [Rational] exact position unchanged
238
+ #
239
+ # @api private
63
240
  private def _quantize_position(position, warn: false)
64
241
  position
65
242
  end