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