musa-dsl 0.30.2 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/.version +6 -0
- data/.yardopts +7 -0
- data/README.md +227 -6
- data/docs/README.md +83 -0
- data/docs/api-reference.md +86 -0
- data/docs/getting-started/quick-start.md +93 -0
- data/docs/getting-started/tutorial.md +58 -0
- data/docs/subsystems/core-extensions.md +316 -0
- data/docs/subsystems/datasets.md +465 -0
- data/docs/subsystems/generative.md +290 -0
- data/docs/subsystems/matrix.md +63 -0
- data/docs/subsystems/midi.md +123 -0
- data/docs/subsystems/music.md +233 -0
- data/docs/subsystems/musicxml-builder.md +264 -0
- data/docs/subsystems/neumas.md +71 -0
- data/docs/subsystems/repl.md +135 -0
- data/docs/subsystems/sequencer.md +98 -0
- data/docs/subsystems/series.md +302 -0
- data/docs/subsystems/transcription.md +152 -0
- data/docs/subsystems/transport.md +177 -0
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +68 -0
- data/lib/musa-dsl/core-ext/arrayfy.rb +110 -0
- data/lib/musa-dsl/core-ext/attribute-builder.rb +91 -30
- data/lib/musa-dsl/core-ext/deep-copy.rb +125 -2
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +78 -0
- data/lib/musa-dsl/core-ext/extension.rb +53 -0
- data/lib/musa-dsl/core-ext/hashify.rb +162 -1
- data/lib/musa-dsl/core-ext/inspect-nice.rb +154 -0
- data/lib/musa-dsl/core-ext/smart-proc-binder.rb +117 -0
- data/lib/musa-dsl/core-ext/with.rb +114 -0
- data/lib/musa-dsl/datasets/dataset.rb +109 -0
- data/lib/musa-dsl/datasets/delta-d.rb +78 -0
- data/lib/musa-dsl/datasets/e.rb +186 -2
- data/lib/musa-dsl/datasets/gdv.rb +279 -2
- data/lib/musa-dsl/datasets/gdvd.rb +201 -0
- data/lib/musa-dsl/datasets/helper.rb +75 -0
- data/lib/musa-dsl/datasets/p.rb +177 -2
- data/lib/musa-dsl/datasets/packed-v.rb +91 -0
- data/lib/musa-dsl/datasets/pdv.rb +136 -1
- data/lib/musa-dsl/datasets/ps.rb +134 -4
- data/lib/musa-dsl/datasets/score/queriable.rb +143 -1
- data/lib/musa-dsl/datasets/score/render.rb +105 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +138 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +111 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +200 -1
- data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +145 -1
- data/lib/musa-dsl/datasets/score.rb +279 -0
- data/lib/musa-dsl/datasets/v.rb +88 -0
- data/lib/musa-dsl/generative/darwin.rb +180 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +359 -0
- data/lib/musa-dsl/generative/markov.rb +133 -3
- data/lib/musa-dsl/generative/rules.rb +258 -4
- data/lib/musa-dsl/generative/variatio.rb +217 -2
- data/lib/musa-dsl/logger/logger.rb +267 -2
- data/lib/musa-dsl/matrix/matrix.rb +256 -10
- data/lib/musa-dsl/midi/midi-recorder.rb +108 -1
- data/lib/musa-dsl/midi/midi-voices.rb +265 -4
- data/lib/musa-dsl/music/chord-definition.rb +233 -1
- data/lib/musa-dsl/music/chord-definitions.rb +33 -6
- data/lib/musa-dsl/music/chords.rb +308 -2
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +315 -0
- data/lib/musa-dsl/music/scales.rb +957 -40
- data/lib/musa-dsl/musicxml/builder/attributes.rb +483 -3
- data/lib/musa-dsl/musicxml/builder/backup-forward.rb +166 -1
- data/lib/musa-dsl/musicxml/builder/direction.rb +243 -0
- data/lib/musa-dsl/musicxml/builder/helper.rb +240 -0
- data/lib/musa-dsl/musicxml/builder/measure.rb +284 -0
- data/lib/musa-dsl/musicxml/builder/note-complexities.rb +324 -8
- data/lib/musa-dsl/musicxml/builder/note.rb +285 -0
- data/lib/musa-dsl/musicxml/builder/part-group.rb +108 -1
- data/lib/musa-dsl/musicxml/builder/part.rb +139 -0
- data/lib/musa-dsl/musicxml/builder/pitched-note.rb +124 -0
- data/lib/musa-dsl/musicxml/builder/rest.rb +93 -0
- data/lib/musa-dsl/musicxml/builder/score-partwise.rb +276 -0
- data/lib/musa-dsl/musicxml/builder/typed-text.rb +62 -1
- data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +83 -0
- data/lib/musa-dsl/neumalang/neumalang.rb +675 -0
- data/lib/musa-dsl/neumas/array-to-neumas.rb +149 -0
- data/lib/musa-dsl/neumas/neuma-decoder.rb +253 -0
- data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +142 -2
- data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +82 -0
- data/lib/musa-dsl/neumas/neumas.rb +67 -0
- data/lib/musa-dsl/neumas/string-to-neumas.rb +233 -1
- data/lib/musa-dsl/repl/repl.rb +550 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +118 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +149 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +296 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +88 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +161 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +263 -0
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +173 -1
- data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +177 -0
- data/lib/musa-dsl/sequencer/base-sequencer.rb +710 -10
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +210 -0
- data/lib/musa-dsl/sequencer/timeslots.rb +79 -0
- data/lib/musa-dsl/series/array-to-serie.rb +37 -1
- data/lib/musa-dsl/series/base-series.rb +843 -5
- data/lib/musa-dsl/series/buffer-serie.rb +48 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +41 -0
- data/lib/musa-dsl/series/main-serie-constructors.rb +398 -2
- data/lib/musa-dsl/series/main-serie-operations.rb +538 -16
- data/lib/musa-dsl/series/proxy-serie.rb +67 -0
- data/lib/musa-dsl/series/quantizer-serie.rb +45 -7
- data/lib/musa-dsl/series/queue-serie.rb +65 -0
- data/lib/musa-dsl/series/series-composer.rb +701 -0
- data/lib/musa-dsl/series/timed-serie.rb +473 -28
- data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +404 -1
- data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +118 -0
- data/lib/musa-dsl/transcription/from-gdv.rb +84 -1
- data/lib/musa-dsl/transcription/transcription.rb +265 -0
- data/lib/musa-dsl/transport/clock.rb +125 -0
- data/lib/musa-dsl/transport/dummy-clock.rb +89 -2
- data/lib/musa-dsl/transport/external-tick-clock.rb +91 -0
- data/lib/musa-dsl/transport/input-midi-clock.rb +133 -1
- data/lib/musa-dsl/transport/timer-clock.rb +183 -1
- data/lib/musa-dsl/transport/timer.rb +83 -0
- data/lib/musa-dsl/transport/transport.rb +318 -0
- data/lib/musa-dsl/version.rb +1 -1
- data/lib/musa-dsl.rb +132 -25
- data/musa-dsl.gemspec +12 -10
- metadata +87 -8
|
@@ -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
|
-
|
|
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
|