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
|
@@ -4,9 +4,62 @@ require_relative '../core-ext/with'
|
|
|
4
4
|
|
|
5
5
|
module Musa
|
|
6
6
|
module Sequencer
|
|
7
|
+
# High-level DSL wrapper for BaseSequencer.
|
|
8
|
+
#
|
|
9
|
+
# Provides user-friendly interface with block context management via
|
|
10
|
+
# `with` method. Wraps BaseSequencer methods to automatically evaluate
|
|
11
|
+
# blocks in DSL context, enabling clean musical composition code.
|
|
12
|
+
#
|
|
13
|
+
# ## DSL Context
|
|
14
|
+
#
|
|
15
|
+
# Blocks passed to scheduling methods (now, at, wait, play, every, move)
|
|
16
|
+
# are evaluated in DSL context via `with`, providing access to sequencer
|
|
17
|
+
# methods and allowing for cleaner composition syntax.
|
|
18
|
+
#
|
|
19
|
+
# ## Delegation
|
|
20
|
+
#
|
|
21
|
+
# Delegates most methods to either BaseSequencer or DSLContext:
|
|
22
|
+
#
|
|
23
|
+
# - Timing: beats_per_bar, ticks_per_beat, position, tick, reset
|
|
24
|
+
# - Scheduling: now, at, wait, play, play_timed, every, move
|
|
25
|
+
# - Events: launch, on
|
|
26
|
+
# - Inspection: size, empty?, everying, playing, moving
|
|
27
|
+
#
|
|
28
|
+
# ## Musical Applications
|
|
29
|
+
#
|
|
30
|
+
# Provides composer-friendly API for:
|
|
31
|
+
#
|
|
32
|
+
# - Musical composition scripts
|
|
33
|
+
# - Interactive sequencing
|
|
34
|
+
# - Live coding
|
|
35
|
+
# - Algorithmic composition
|
|
36
|
+
#
|
|
37
|
+
# @example Basic DSL usage
|
|
38
|
+
# sequencer = Musa::Sequencer::Sequencer.new(4, 96) do
|
|
39
|
+
# at(1r) { puts "Bar 1" }
|
|
40
|
+
# at(2r) { puts "Bar 2" }
|
|
41
|
+
#
|
|
42
|
+
# every(1r, duration: 4r) do
|
|
43
|
+
# puts "Every beat"
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# sequencer.run
|
|
48
|
+
#
|
|
49
|
+
# @example DSL context access
|
|
50
|
+
# sequencer = Musa::Sequencer::Sequencer.new(4, 96) do
|
|
51
|
+
# at(1r) do
|
|
52
|
+
# puts "Position: #{position}" # DSL context method
|
|
53
|
+
#
|
|
54
|
+
# wait(1r) { puts "One bar later" } # Nested scheduling
|
|
55
|
+
# end
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
# @api public
|
|
7
59
|
class Sequencer
|
|
8
60
|
extend Forwardable
|
|
9
61
|
|
|
62
|
+
# Delegates to BaseSequencer
|
|
10
63
|
def_delegators :@sequencer,
|
|
11
64
|
:beats_per_bar, :ticks_per_beat, :ticks_per_bar, :tick_duration,
|
|
12
65
|
:offset,
|
|
@@ -18,12 +71,38 @@ module Musa
|
|
|
18
71
|
:position=,
|
|
19
72
|
:event_handler
|
|
20
73
|
|
|
74
|
+
# Delegates to DSLContext
|
|
21
75
|
def_delegators :@dsl, :position, :quantize_position, :logger, :debug
|
|
22
76
|
def_delegators :@dsl, :now, :at, :wait, :play, :play_timed, :every, :move
|
|
23
77
|
def_delegators :@dsl, :everying, :playing, :moving
|
|
24
78
|
def_delegators :@dsl, :launch, :on
|
|
25
79
|
def_delegators :@dsl, :run
|
|
26
80
|
|
|
81
|
+
# Creates sequencer with optional initialization block.
|
|
82
|
+
#
|
|
83
|
+
# @param beats_per_bar [Integer, nil] beats per bar (tick-based mode)
|
|
84
|
+
# @param ticks_per_beat [Integer, nil] ticks per beat (tick-based mode)
|
|
85
|
+
# @param offset [Rational, nil] starting position offset
|
|
86
|
+
# @param sequencer [BaseSequencer, nil] use existing sequencer
|
|
87
|
+
# @param logger [Logger, nil] logger instance
|
|
88
|
+
# @param do_log [Boolean, nil] enable logging
|
|
89
|
+
# @param do_error_log [Boolean, nil] enable error logging
|
|
90
|
+
# @param log_position_format [Symbol, nil] position format for logs
|
|
91
|
+
# @param dsl_context_class [Class, nil] custom DSL context class
|
|
92
|
+
# @param keep_block_context [Boolean, nil] preserve block's original binding
|
|
93
|
+
# @yield initialization block evaluated in DSL context
|
|
94
|
+
#
|
|
95
|
+
# @example Tick-based sequencer
|
|
96
|
+
# seq = Sequencer.new(4, 96) do
|
|
97
|
+
# at(1r) { puts "Start" }
|
|
98
|
+
# end
|
|
99
|
+
#
|
|
100
|
+
# @example Tickless sequencer
|
|
101
|
+
# seq = Sequencer.new do
|
|
102
|
+
# at(1r) { puts "Start" }
|
|
103
|
+
# end
|
|
104
|
+
#
|
|
105
|
+
# @api public
|
|
27
106
|
def initialize(beats_per_bar = nil,
|
|
28
107
|
ticks_per_beat = nil,
|
|
29
108
|
offset: nil,
|
|
@@ -49,16 +128,89 @@ module Musa
|
|
|
49
128
|
@dsl.with &block if block_given?
|
|
50
129
|
end
|
|
51
130
|
|
|
131
|
+
# Evaluates block in DSL context.
|
|
132
|
+
#
|
|
133
|
+
# Provides `with` method for evaluating blocks with DSL context access.
|
|
134
|
+
# The block is executed in the DSL context, giving it direct access to
|
|
135
|
+
# sequencer methods like at, wait, play, every, move without needing to
|
|
136
|
+
# reference the sequencer object.
|
|
137
|
+
#
|
|
138
|
+
# @param value_parameters [Array] positional parameters
|
|
139
|
+
# @param key_parameters [Hash] keyword parameters
|
|
140
|
+
# @yield block to evaluate in DSL context
|
|
141
|
+
#
|
|
142
|
+
# @return [Object] block return value
|
|
143
|
+
#
|
|
144
|
+
# @example Evaluating blocks in DSL context
|
|
145
|
+
# seq = Musa::Sequencer::Sequencer.new(4, 96)
|
|
146
|
+
#
|
|
147
|
+
# executed = []
|
|
148
|
+
#
|
|
149
|
+
# # Use 'with' to evaluate block in DSL context
|
|
150
|
+
# seq.with do
|
|
151
|
+
# # Inside this block, we have direct access to DSL methods
|
|
152
|
+
# at(1) { executed << "bar 1" }
|
|
153
|
+
# at(2) { executed << "bar 2" }
|
|
154
|
+
#
|
|
155
|
+
# every(1, duration: 4) do
|
|
156
|
+
# executed << "beat at #{position}"
|
|
157
|
+
# end
|
|
158
|
+
# end
|
|
159
|
+
#
|
|
160
|
+
# seq.run
|
|
161
|
+
#
|
|
162
|
+
# # executed contains ["bar 1", "beat at 1", "bar 2", "beat at 2", ...]
|
|
163
|
+
#
|
|
164
|
+
# @example Passing parameters to with block
|
|
165
|
+
# seq = Musa::Sequencer::Sequencer.new(4, 96)
|
|
166
|
+
#
|
|
167
|
+
# notes = []
|
|
168
|
+
#
|
|
169
|
+
# seq.with(60, 64, 67) do |c, e, g|
|
|
170
|
+
# at(1) { notes << c } # Uses parameter c = 60
|
|
171
|
+
# at(2) { notes << e } # Uses parameter e = 64
|
|
172
|
+
# at(3) { notes << g } # Uses parameter g = 67
|
|
173
|
+
# end
|
|
174
|
+
#
|
|
175
|
+
# seq.run
|
|
176
|
+
#
|
|
177
|
+
# # notes contains [60, 64, 67]
|
|
178
|
+
#
|
|
179
|
+
# @example Comparison: with DSL context vs external context
|
|
180
|
+
# seq = Musa::Sequencer::Sequencer.new(4, 96)
|
|
181
|
+
#
|
|
182
|
+
# # Without 'with': need to reference seq explicitly
|
|
183
|
+
# seq.at(1) { seq.at(2) { puts "nested" } }
|
|
184
|
+
#
|
|
185
|
+
# # With 'with': DSL methods available directly
|
|
186
|
+
# seq.with do
|
|
187
|
+
# at(1) { at(2) { puts "nested" } } # Cleaner syntax
|
|
188
|
+
# end
|
|
189
|
+
#
|
|
190
|
+
# @api public
|
|
52
191
|
def with(*value_parameters, **key_parameters, &block)
|
|
53
192
|
@dsl.with(*value_parameters, **key_parameters, &block)
|
|
54
193
|
end
|
|
55
194
|
|
|
195
|
+
# DSL context providing block evaluation with sequencer method access.
|
|
196
|
+
#
|
|
197
|
+
# Wraps BaseSequencer methods to evaluate user blocks in controlled
|
|
198
|
+
# context via `with`. Enables clean DSL syntax by providing direct
|
|
199
|
+
# access to sequencer methods within block scopes.
|
|
200
|
+
#
|
|
201
|
+
# ## Block Context Modes
|
|
202
|
+
#
|
|
203
|
+
# - **keep_block_context: false** (default): Evaluate blocks in DSL context
|
|
204
|
+
# - **keep_block_context: true**: Preserve block's original binding
|
|
205
|
+
#
|
|
56
206
|
class DSLContext
|
|
57
207
|
extend Forwardable
|
|
58
208
|
include Musa::Extension::With
|
|
59
209
|
|
|
210
|
+
# @return [BaseSequencer] underlying sequencer
|
|
60
211
|
attr_reader :sequencer
|
|
61
212
|
|
|
213
|
+
# Delegates to BaseSequencer
|
|
62
214
|
def_delegators :@sequencer,
|
|
63
215
|
:launch, :on,
|
|
64
216
|
:position, :quantize_position,
|
|
@@ -67,11 +219,20 @@ module Musa
|
|
|
67
219
|
:ticks_per_bar, :logger, :debug, :inspect,
|
|
68
220
|
:run
|
|
69
221
|
|
|
222
|
+
# @api private
|
|
70
223
|
def initialize(sequencer, keep_block_context:)
|
|
71
224
|
@sequencer = sequencer
|
|
72
225
|
@keep_block_context_on_with = keep_block_context
|
|
73
226
|
end
|
|
74
227
|
|
|
228
|
+
# Schedules block at current position via DSL context.
|
|
229
|
+
#
|
|
230
|
+
# Wraps BaseSequencer#now, evaluating block in DSL context.
|
|
231
|
+
#
|
|
232
|
+
# @param value_parameters [Array] parameters to pass to block
|
|
233
|
+
# @param key_parameters [Hash] keyword parameters
|
|
234
|
+
# @yield block to execute at current position
|
|
235
|
+
# @return [void]
|
|
75
236
|
def now(*value_parameters, **key_parameters, &block)
|
|
76
237
|
block ||= proc {}
|
|
77
238
|
|
|
@@ -80,6 +241,14 @@ module Musa
|
|
|
80
241
|
end
|
|
81
242
|
end
|
|
82
243
|
|
|
244
|
+
# Schedules block at absolute position via DSL context.
|
|
245
|
+
#
|
|
246
|
+
# Wraps BaseSequencer#at, evaluating block in DSL context.
|
|
247
|
+
#
|
|
248
|
+
# @param value_parameters [Array] parameters (first is position)
|
|
249
|
+
# @param key_parameters [Hash] keyword parameters
|
|
250
|
+
# @yield block to execute at position
|
|
251
|
+
# @return [void]
|
|
83
252
|
def at(*value_parameters, **key_parameters, &block)
|
|
84
253
|
block ||= proc {}
|
|
85
254
|
|
|
@@ -88,6 +257,14 @@ module Musa
|
|
|
88
257
|
end
|
|
89
258
|
end
|
|
90
259
|
|
|
260
|
+
# Schedules block after wait duration via DSL context.
|
|
261
|
+
#
|
|
262
|
+
# Wraps BaseSequencer#wait, evaluating block in DSL context.
|
|
263
|
+
#
|
|
264
|
+
# @param value_parameters [Array] parameters (first is duration)
|
|
265
|
+
# @param key_parameters [Hash] keyword parameters
|
|
266
|
+
# @yield block to execute after wait
|
|
267
|
+
# @return [void]
|
|
91
268
|
def wait(*value_parameters, **key_parameters, &block)
|
|
92
269
|
block ||= proc {}
|
|
93
270
|
@sequencer.wait *value_parameters, **key_parameters do |*values, **key_values|
|
|
@@ -95,6 +272,14 @@ module Musa
|
|
|
95
272
|
end
|
|
96
273
|
end
|
|
97
274
|
|
|
275
|
+
# Plays series via DSL context.
|
|
276
|
+
#
|
|
277
|
+
# Wraps BaseSequencer#play, evaluating block in DSL context.
|
|
278
|
+
#
|
|
279
|
+
# @param value_parameters [Array] parameters (series, etc.)
|
|
280
|
+
# @param key_parameters [Hash] keyword parameters
|
|
281
|
+
# @yield block to execute for each element
|
|
282
|
+
# @return [PlayControl] control object
|
|
98
283
|
def play(*value_parameters, **key_parameters, &block)
|
|
99
284
|
block ||= proc {}
|
|
100
285
|
|
|
@@ -103,6 +288,14 @@ module Musa
|
|
|
103
288
|
end
|
|
104
289
|
end
|
|
105
290
|
|
|
291
|
+
# Plays timed series via DSL context.
|
|
292
|
+
#
|
|
293
|
+
# Wraps BaseSequencer#play_timed, evaluating block in DSL context.
|
|
294
|
+
#
|
|
295
|
+
# @param value_parameters [Array] parameters (timed series, etc.)
|
|
296
|
+
# @param key_parameters [Hash] keyword parameters
|
|
297
|
+
# @yield block to execute for each element
|
|
298
|
+
# @return [PlayTimedControl] control object
|
|
106
299
|
def play_timed(*value_parameters, **key_parameters, &block)
|
|
107
300
|
block ||= proc {}
|
|
108
301
|
|
|
@@ -111,6 +304,15 @@ module Musa
|
|
|
111
304
|
end
|
|
112
305
|
end
|
|
113
306
|
|
|
307
|
+
# Repeats block at intervals via DSL context.
|
|
308
|
+
#
|
|
309
|
+
# Wraps BaseSequencer#every, evaluating block in DSL context.
|
|
310
|
+
# Uses SmartProcBinder to apply parameters before with.
|
|
311
|
+
#
|
|
312
|
+
# @param value_parameters [Array] parameters (interval, etc.)
|
|
313
|
+
# @param key_parameters [Hash] keyword parameters
|
|
314
|
+
# @yield block to execute each iteration
|
|
315
|
+
# @return [EveryControl] control object
|
|
114
316
|
def every(*value_parameters, **key_parameters, &block)
|
|
115
317
|
block ||= proc {}
|
|
116
318
|
|
|
@@ -120,6 +322,14 @@ module Musa
|
|
|
120
322
|
end
|
|
121
323
|
end
|
|
122
324
|
|
|
325
|
+
# Animates values over time via DSL context.
|
|
326
|
+
#
|
|
327
|
+
# Wraps BaseSequencer#move, evaluating block in DSL context.
|
|
328
|
+
#
|
|
329
|
+
# @param value_parameters [Array] parameters (from, to, etc.)
|
|
330
|
+
# @param key_parameters [Hash] keyword parameters
|
|
331
|
+
# @yield block to execute each iteration with current value
|
|
332
|
+
# @return [MoveControl] control object
|
|
123
333
|
def move(*value_parameters, **key_parameters, &block)
|
|
124
334
|
block ||= proc {}
|
|
125
335
|
|
|
@@ -3,24 +3,101 @@ require 'set'
|
|
|
3
3
|
module Musa
|
|
4
4
|
module Sequencer
|
|
5
5
|
class BaseSequencer
|
|
6
|
+
# Sorted time-indexed event storage for sequencer.
|
|
7
|
+
#
|
|
8
|
+
# Timeslots extends Hash to maintain scheduled events indexed by time,
|
|
9
|
+
# with efficient sorted access for sequential playback. Each timeslot
|
|
10
|
+
# (key) maps to an array of events scheduled at that time.
|
|
11
|
+
#
|
|
12
|
+
# ## Implementation
|
|
13
|
+
#
|
|
14
|
+
# Uses a SortedSet to track keys in temporal order, enabling fast
|
|
15
|
+
# lookup of next scheduled event without full hash traversal.
|
|
16
|
+
#
|
|
17
|
+
# ## Usage
|
|
18
|
+
#
|
|
19
|
+
# - **Store**: `timeslots[time] = events` schedules events at time
|
|
20
|
+
# - **Retrieve**: `timeslots[time]` gets events at specific time
|
|
21
|
+
# - **Next**: `first_after(pos)` finds next scheduled time >= position
|
|
22
|
+
# - **Delete**: `delete(time)` removes timeslot and updates sort index
|
|
23
|
+
#
|
|
24
|
+
# ## Time Representation
|
|
25
|
+
#
|
|
26
|
+
# Times are Rational numbers representing musical beats/ticks.
|
|
27
|
+
# Granularity depends on sequencer configuration (tick-based or tickless).
|
|
28
|
+
#
|
|
29
|
+
# ## Internal Usage Examples
|
|
30
|
+
#
|
|
31
|
+
# Note: These are illustrative examples for internal implementation.
|
|
32
|
+
# Timeslots is private and cannot be instantiated from public API.
|
|
33
|
+
#
|
|
34
|
+
# # Within BaseSequencer context:
|
|
35
|
+
# timeslots = Timeslots.new
|
|
36
|
+
# timeslots[0r] = [event1, event2] # Events at time 0
|
|
37
|
+
# timeslots[1/2r] = [event3] # Event at beat 0.5
|
|
38
|
+
# timeslots[1r] = [event4, event5] # Events at beat 1
|
|
39
|
+
#
|
|
40
|
+
# next_time = timeslots.first_after(0.5) # => 1r
|
|
41
|
+
# events = timeslots[next_time] # => [event4, event5]
|
|
42
|
+
#
|
|
43
|
+
# @api private
|
|
6
44
|
class Timeslots < Hash
|
|
7
45
|
|
|
46
|
+
# Creates empty timeslots storage.
|
|
47
|
+
#
|
|
48
|
+
# @param several_variants [Array] optional Hash initialization parameters
|
|
49
|
+
# @api private
|
|
8
50
|
def initialize(*several_variants)
|
|
9
51
|
super
|
|
10
52
|
@sorted_keys = SortedSet.new
|
|
11
53
|
end
|
|
12
54
|
|
|
55
|
+
# Stores events at time, maintaining sort index.
|
|
56
|
+
#
|
|
57
|
+
# @param key [Rational] time position
|
|
58
|
+
# @param value [Array, Object] events at this time
|
|
59
|
+
#
|
|
60
|
+
# @return [Array, Object] stored value
|
|
61
|
+
# @api private
|
|
13
62
|
def []=(key, value)
|
|
14
63
|
super
|
|
15
64
|
@sorted_keys << key
|
|
16
65
|
end
|
|
17
66
|
|
|
67
|
+
# Removes timeslot, updating sort index.
|
|
68
|
+
#
|
|
69
|
+
# @param key [Rational] time position to remove
|
|
70
|
+
#
|
|
71
|
+
# @return [Array, Object, nil] removed value
|
|
72
|
+
# @api private
|
|
18
73
|
def delete(key)
|
|
19
74
|
super
|
|
20
75
|
@sorted_keys.delete key
|
|
21
76
|
|
|
22
77
|
end
|
|
23
78
|
|
|
79
|
+
# Finds first scheduled time at or after position.
|
|
80
|
+
#
|
|
81
|
+
# Used by sequencer to find next event to execute during playback.
|
|
82
|
+
#
|
|
83
|
+
# Internal behavior (illustrative, not executable from public API):
|
|
84
|
+
#
|
|
85
|
+
# timeslots[1r] = [:event_a]
|
|
86
|
+
# timeslots[2r] = [:event_b]
|
|
87
|
+
# timeslots[3r] = [:event_c]
|
|
88
|
+
#
|
|
89
|
+
# timeslots.first_after(nil) # => 1r
|
|
90
|
+
# timeslots.first_after(0r) # => 1r
|
|
91
|
+
# timeslots.first_after(1r) # => 1r
|
|
92
|
+
# timeslots.first_after(1.5r) # => 2r
|
|
93
|
+
# timeslots.first_after(3r) # => 3r
|
|
94
|
+
# timeslots.first_after(4r) # => nil
|
|
95
|
+
#
|
|
96
|
+
# @param position [Rational, nil] search position (nil for first overall)
|
|
97
|
+
#
|
|
98
|
+
# @return [Rational, nil] next scheduled time, or nil if none
|
|
99
|
+
#
|
|
100
|
+
# @api private
|
|
24
101
|
def first_after(position)
|
|
25
102
|
if position.nil?
|
|
26
103
|
@sorted_keys.first
|
|
@@ -29,6 +106,8 @@ module Musa
|
|
|
29
106
|
end
|
|
30
107
|
end
|
|
31
108
|
end
|
|
109
|
+
|
|
110
|
+
private_constant :Timeslots
|
|
32
111
|
end
|
|
33
112
|
end
|
|
34
113
|
end
|
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
require_relative '../series'
|
|
2
2
|
|
|
3
|
-
# TODO: esto sería un refinement, no?
|
|
4
3
|
|
|
5
4
|
class Array
|
|
5
|
+
# TODO: esto sería un refinement, no?
|
|
6
|
+
|
|
7
|
+
# Converts array to Serie.
|
|
8
|
+
#
|
|
9
|
+
# Three conversion modes:
|
|
10
|
+
#
|
|
11
|
+
# - **Basic**: Direct conversion to serie
|
|
12
|
+
# - **of_series**: Each element of the array becomes a new serie (for array of arrays)
|
|
13
|
+
# - **recursive**: Recursive conversion of nested arrays
|
|
14
|
+
#
|
|
15
|
+
# @param of_series [Boolean, nil] convert each element to a serie (default: false)
|
|
16
|
+
# @param recursive [Boolean, nil] recursively convert nested arrays (default: false)
|
|
17
|
+
#
|
|
18
|
+
# @return [Serie] converted serie
|
|
19
|
+
#
|
|
20
|
+
# @raise [ArgumentError] if both of_series and recursive are true
|
|
21
|
+
#
|
|
22
|
+
# @example Basic conversion
|
|
23
|
+
# [60, 64, 67].to_serie.i.to_a # => [60, 64, 67]
|
|
24
|
+
#
|
|
25
|
+
# @example Serie of series
|
|
26
|
+
# [[1, 2], [3, 4]].to_serie(of_series: true)
|
|
27
|
+
# # Each [1,2], [3,4] becomes S(1,2), S(3,4)
|
|
28
|
+
#
|
|
29
|
+
# @example Recursive conversion
|
|
30
|
+
# [[1, [2, 3]], [4, 5]].to_serie(recursive: true)
|
|
31
|
+
# # Nested arrays become nested series
|
|
32
|
+
#
|
|
33
|
+
# @api public
|
|
6
34
|
def to_serie(of_series: nil, recursive: nil)
|
|
7
35
|
of_series ||= false
|
|
8
36
|
recursive ||= false
|
|
@@ -18,5 +46,13 @@ class Array
|
|
|
18
46
|
end
|
|
19
47
|
end
|
|
20
48
|
|
|
49
|
+
# Short alias for {#to_serie}.
|
|
50
|
+
#
|
|
51
|
+
# @return [Serie] converted serie
|
|
52
|
+
#
|
|
53
|
+
# @example Short form
|
|
54
|
+
# [1, 2, 3].s # => S(1, 2, 3)
|
|
55
|
+
#
|
|
56
|
+
# @api public
|
|
21
57
|
alias_method :s, :to_serie
|
|
22
58
|
end
|