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,10 +1,143 @@
|
|
|
1
1
|
module Musa
|
|
2
2
|
module Series
|
|
3
|
+
# Series transformation operations for composing and modifying series.
|
|
4
|
+
#
|
|
5
|
+
# Provides methods for transforming, combining, and controlling series flow.
|
|
6
|
+
# All operations return new series (functional/immutable style).
|
|
7
|
+
#
|
|
8
|
+
# ## Categories
|
|
9
|
+
#
|
|
10
|
+
# ### Mapping & Transformation
|
|
11
|
+
#
|
|
12
|
+
# - **map** - Transform values via block
|
|
13
|
+
# - **with** - Combine multiple series for mapping
|
|
14
|
+
# - **process_with** - Generic processor with parameters
|
|
15
|
+
# - **hashify** - Convert array values to hash
|
|
16
|
+
# - **shift** - Shift values by offset
|
|
17
|
+
#
|
|
18
|
+
# ### Filtering & Selection
|
|
19
|
+
#
|
|
20
|
+
# - **select** - Keep values matching condition
|
|
21
|
+
# - **remove** - Remove values matching condition
|
|
22
|
+
# - **skip** - Skip first N values
|
|
23
|
+
# - **max_size** - Limit to N values
|
|
24
|
+
# - **cut** - Cut into chunks
|
|
25
|
+
#
|
|
26
|
+
# ### Flow Control
|
|
27
|
+
#
|
|
28
|
+
# - **repeat** - Repeat series N times or conditionally
|
|
29
|
+
# - **autorestart** - Auto-restart when exhausted
|
|
30
|
+
# - **flatten** - Flatten nested series
|
|
31
|
+
# - **merge** - Merge serie of series
|
|
32
|
+
# - **after** / **+** - Append series sequentially
|
|
33
|
+
#
|
|
34
|
+
# ### Switching & Multiplexing
|
|
35
|
+
#
|
|
36
|
+
# - **switch** - Switch between series based on selector
|
|
37
|
+
# - **multiplex** - Multiplex series based on selector
|
|
38
|
+
# - **switch_serie** - Switch to different series entirely
|
|
39
|
+
#
|
|
40
|
+
# ### Structural Operations
|
|
41
|
+
#
|
|
42
|
+
# - **reverse** - Reverse values
|
|
43
|
+
# - **randomize** - Shuffle values randomly
|
|
44
|
+
# - **lock** - Lock serie (prevent changes)
|
|
45
|
+
# - **flatten** - Flatten nested series
|
|
46
|
+
#
|
|
47
|
+
# ### Timing Operations
|
|
48
|
+
#
|
|
49
|
+
# - **anticipate** - Evaluate block one step ahead
|
|
50
|
+
# - **lazy** - Delay evaluation to next step
|
|
51
|
+
#
|
|
52
|
+
# ## Usage Patterns
|
|
53
|
+
#
|
|
54
|
+
# ### Mapping
|
|
55
|
+
#
|
|
56
|
+
# ```ruby
|
|
57
|
+
# notes = S(60, 64, 67).map { |n| n + 12 } # Transpose octave
|
|
58
|
+
# notes.i.to_a # => [72, 76, 79]
|
|
59
|
+
# ```
|
|
60
|
+
#
|
|
61
|
+
# ### Filtering
|
|
62
|
+
#
|
|
63
|
+
# ```ruby
|
|
64
|
+
# evens = S(1, 2, 3, 4, 5, 6).select { |n| n.even? }
|
|
65
|
+
# evens.i.to_a # => [2, 4, 6]
|
|
66
|
+
# ```
|
|
67
|
+
#
|
|
68
|
+
# ### Combining
|
|
69
|
+
#
|
|
70
|
+
# ```ruby
|
|
71
|
+
# pitches = S(60, 64, 67)
|
|
72
|
+
# velocities = S(96, 80, 64)
|
|
73
|
+
# notes = pitches.with(velocities) { |p, v| {pitch: p, velocity: v} }
|
|
74
|
+
# ```
|
|
75
|
+
#
|
|
76
|
+
# ### Repeating
|
|
77
|
+
#
|
|
78
|
+
# ```ruby
|
|
79
|
+
# pattern = S(1, 2, 3).repeat(3)
|
|
80
|
+
# pattern.i.to_a # => [1, 2, 3, 1, 2, 3, 1, 2, 3]
|
|
81
|
+
# ```
|
|
82
|
+
#
|
|
83
|
+
# ### Chaining Operations
|
|
84
|
+
#
|
|
85
|
+
# ```ruby
|
|
86
|
+
# result = S(1, 2, 3, 4, 5)
|
|
87
|
+
# .select { |n| n.even? }
|
|
88
|
+
# .map { |n| n * 10 }
|
|
89
|
+
# .repeat(2)
|
|
90
|
+
# result.i.to_a # => [20, 40, 20, 40]
|
|
91
|
+
# ```
|
|
92
|
+
#
|
|
93
|
+
# @see Musa::Series::Constructors Serie creation methods
|
|
94
|
+
#
|
|
95
|
+
# @api public
|
|
3
96
|
module Operations
|
|
97
|
+
# Auto-restarts serie when exhausted.
|
|
98
|
+
#
|
|
99
|
+
# Creates an infinite serie from an original serie that automatically restarts from beginning
|
|
100
|
+
# when it reaches the end.
|
|
101
|
+
#
|
|
102
|
+
# @return [Autorestart] auto-restarting serie
|
|
103
|
+
#
|
|
104
|
+
# @example Infinite loop
|
|
105
|
+
# pattern = S(1, 2, 3).autorestart
|
|
106
|
+
# pattern.infinite? # => true
|
|
107
|
+
# inst = pattern.i
|
|
108
|
+
# inst.max_size(7).to_a # => [1, 2, 3, 1, 2, 3, 1]
|
|
109
|
+
#
|
|
110
|
+
# @api public
|
|
4
111
|
def autorestart
|
|
5
112
|
Autorestart.new self
|
|
6
113
|
end
|
|
7
114
|
|
|
115
|
+
# Repeats serie multiple times or conditionally.
|
|
116
|
+
#
|
|
117
|
+
# Three modes:
|
|
118
|
+
# - **times**: Repeat exact number of times
|
|
119
|
+
# - **condition**: Repeat while condition true
|
|
120
|
+
# - **neither**: Infinite repetition
|
|
121
|
+
#
|
|
122
|
+
# @param times [Integer, nil] number of repetitions
|
|
123
|
+
# @param condition [Proc, nil] condition block
|
|
124
|
+
# @yield optional condition block
|
|
125
|
+
#
|
|
126
|
+
# @return [Repeater, InfiniteRepeater] repeating serie
|
|
127
|
+
#
|
|
128
|
+
# @example Fixed repetitions
|
|
129
|
+
# s = S(1, 2, 3).repeat(3)
|
|
130
|
+
# s.i.to_a # => [1, 2, 3, 1, 2, 3, 1, 2, 3]
|
|
131
|
+
#
|
|
132
|
+
# @example Conditional repeat
|
|
133
|
+
# count = 0
|
|
134
|
+
# s = S(1, 2, 3).repeat { count += 1; count < 3 }
|
|
135
|
+
#
|
|
136
|
+
# @example Infinite repeat
|
|
137
|
+
# s = S(1, 2, 3).repeat
|
|
138
|
+
# s.infinite? # => true
|
|
139
|
+
#
|
|
140
|
+
# @api public
|
|
8
141
|
def repeat(times = nil, condition: nil, &condition_block)
|
|
9
142
|
condition ||= condition_block
|
|
10
143
|
|
|
@@ -15,92 +148,370 @@ module Musa
|
|
|
15
148
|
end
|
|
16
149
|
end
|
|
17
150
|
|
|
151
|
+
# Limits serie to maximum number of values.
|
|
152
|
+
#
|
|
153
|
+
# Stops after N values regardless of source length.
|
|
154
|
+
#
|
|
155
|
+
# @param length [Integer] maximum number of values
|
|
156
|
+
#
|
|
157
|
+
# @return [LengthLimiter] length-limited serie
|
|
158
|
+
#
|
|
159
|
+
# @example Limit to 5
|
|
160
|
+
# s = FOR(from: 0, step: 1).max_size(5)
|
|
161
|
+
# s.i.to_a # => [0, 1, 2, 3, 4]
|
|
162
|
+
#
|
|
163
|
+
# @api public
|
|
18
164
|
def max_size(length)
|
|
19
165
|
LengthLimiter.new self, length
|
|
20
166
|
end
|
|
21
167
|
|
|
168
|
+
# Skips first N values.
|
|
169
|
+
#
|
|
170
|
+
# Discards first `length` values, returns rest.
|
|
171
|
+
#
|
|
172
|
+
# @param length [Integer] number of values to skip
|
|
173
|
+
#
|
|
174
|
+
# @return [Skipper] serie with skipped values
|
|
175
|
+
#
|
|
176
|
+
# @example Skip first 2
|
|
177
|
+
# s = S(1, 2, 3, 4, 5).skip(2)
|
|
178
|
+
# s.i.to_a # => [3, 4, 5]
|
|
179
|
+
#
|
|
180
|
+
# @api public
|
|
22
181
|
def skip(length)
|
|
23
182
|
Skipper.new self, length
|
|
24
183
|
end
|
|
25
184
|
|
|
185
|
+
# Flattens nested series into single level.
|
|
186
|
+
#
|
|
187
|
+
# Recursively consumes series elements that are themselves series.
|
|
188
|
+
#
|
|
189
|
+
# @return [Flattener] flattened serie
|
|
190
|
+
#
|
|
191
|
+
# @example Flatten nested
|
|
192
|
+
# s = S(S(1, 2), S(3, 4), 5).flatten
|
|
193
|
+
# s.i.to_a # => [1, 2, 3, 4, 5]
|
|
194
|
+
#
|
|
195
|
+
# @api public
|
|
26
196
|
def flatten
|
|
27
197
|
Flattener.new self
|
|
28
198
|
end
|
|
29
199
|
|
|
200
|
+
# Processes values with parameterized block.
|
|
201
|
+
#
|
|
202
|
+
# Generic processor passing values and parameters to block.
|
|
203
|
+
#
|
|
204
|
+
# @param parameters [Hash] parameters passed to processor block
|
|
205
|
+
# @yield processor block
|
|
206
|
+
# @yieldparam value [Object] current value
|
|
207
|
+
# @yieldparam parameters [Hash] processor parameters
|
|
208
|
+
#
|
|
209
|
+
# @return [Processor] processed serie
|
|
210
|
+
#
|
|
211
|
+
# @api public
|
|
30
212
|
def process_with(**parameters, &processor)
|
|
31
213
|
Processor.new self, parameters, &processor
|
|
32
214
|
end
|
|
33
215
|
|
|
34
|
-
#
|
|
216
|
+
# Converts array values to hash with specified keys.
|
|
217
|
+
#
|
|
218
|
+
# Takes array-valued serie and converts to hash using provided keys.
|
|
219
|
+
#
|
|
220
|
+
# @param keys [Array] hash keys for array elements
|
|
221
|
+
#
|
|
222
|
+
# @return [HashFromSeriesArray] hashified serie
|
|
223
|
+
#
|
|
224
|
+
# @example Array to hash
|
|
225
|
+
# s = S([60, 96], [64, 80]).hashify(:pitch, :velocity)
|
|
226
|
+
# s.i.next_value # => {pitch: 60, velocity: 96}
|
|
227
|
+
#
|
|
228
|
+
# @api public
|
|
35
229
|
def hashify(*keys)
|
|
36
230
|
HashFromSeriesArray.new self, keys
|
|
37
231
|
end
|
|
38
232
|
|
|
39
|
-
#
|
|
233
|
+
# Rotates serie elements circularly.
|
|
234
|
+
#
|
|
235
|
+
# Performs circular rotation of elements:
|
|
236
|
+
# - Negative values rotate left (first elements move to end)
|
|
237
|
+
# - Positive values rotate right (last elements move to beginning)
|
|
238
|
+
# - Zero performs no rotation
|
|
239
|
+
#
|
|
240
|
+
# Note: Right rotation (positive values) requires finite series as the
|
|
241
|
+
# entire serie must be loaded into memory for rotation.
|
|
242
|
+
#
|
|
243
|
+
# @param shift [Integer] rotation amount
|
|
244
|
+
# - Negative: rotate left by N positions
|
|
245
|
+
# - Positive: rotate right by N positions
|
|
246
|
+
# - Zero: no rotation
|
|
247
|
+
#
|
|
248
|
+
# @return [Shifter] rotated serie
|
|
249
|
+
#
|
|
250
|
+
# @example Rotate left (negative shift)
|
|
251
|
+
# s = S(60, 64, 67).shift(-1) # First element moves to end
|
|
252
|
+
# s.i.to_a # => [64, 67, 60]
|
|
253
|
+
#
|
|
254
|
+
# @example Rotate right (positive shift)
|
|
255
|
+
# s = S(1, 2, 3, 4, 5).shift(2) # Last 2 elements move to beginning
|
|
256
|
+
# s.i.to_a # => [4, 5, 1, 2, 3]
|
|
257
|
+
#
|
|
258
|
+
# @example No rotation
|
|
259
|
+
# s = S(1, 2, 3).shift(0)
|
|
260
|
+
# s.i.to_a # => [1, 2, 3]
|
|
261
|
+
#
|
|
262
|
+
# @api public
|
|
40
263
|
def shift(shift)
|
|
41
264
|
Shifter.new self, shift
|
|
42
265
|
end
|
|
43
266
|
|
|
44
|
-
#
|
|
267
|
+
# Locks serie preventing further modifications.
|
|
268
|
+
#
|
|
269
|
+
# Returns locked copy that cannot be transformed further.
|
|
270
|
+
#
|
|
271
|
+
# @return [Locker] locked serie
|
|
272
|
+
#
|
|
273
|
+
# @api public
|
|
45
274
|
def lock
|
|
46
275
|
Locker.new self
|
|
47
276
|
end
|
|
48
277
|
|
|
49
|
-
#
|
|
278
|
+
# Reverses order of values.
|
|
279
|
+
#
|
|
280
|
+
# Consumes entire serie and returns values in reverse order.
|
|
281
|
+
# Requires finite serie.
|
|
282
|
+
#
|
|
283
|
+
# @return [Reverser] reversed serie
|
|
284
|
+
#
|
|
285
|
+
# @example Retrograde
|
|
286
|
+
# s = S(1, 2, 3, 4).reverse
|
|
287
|
+
# s.i.to_a # => [4, 3, 2, 1]
|
|
288
|
+
#
|
|
289
|
+
# @api public
|
|
50
290
|
def reverse
|
|
51
291
|
Reverser.new self
|
|
52
292
|
end
|
|
53
293
|
|
|
54
|
-
#
|
|
294
|
+
# Randomizes order of values.
|
|
295
|
+
#
|
|
296
|
+
# Shuffles values randomly. Requires finite serie.
|
|
297
|
+
#
|
|
298
|
+
# @param random [Random, nil] Random instance (default: new Random)
|
|
299
|
+
#
|
|
300
|
+
# @return [Randomizer] randomized serie
|
|
301
|
+
#
|
|
302
|
+
# @example Shuffle
|
|
303
|
+
# s = S(1, 2, 3, 4, 5).randomize
|
|
304
|
+
# s.i.to_a # => random permutation
|
|
305
|
+
#
|
|
306
|
+
# @api public
|
|
55
307
|
def randomize(random: nil)
|
|
56
308
|
random ||= Random.new
|
|
57
309
|
Randomizer.new self, random
|
|
58
310
|
end
|
|
59
311
|
|
|
312
|
+
# Removes values matching condition.
|
|
313
|
+
#
|
|
314
|
+
# Filters out values where block returns true.
|
|
315
|
+
#
|
|
316
|
+
# @param block [Proc, nil] filter block
|
|
317
|
+
# @yield filter condition
|
|
318
|
+
# @yieldparam value [Object] current value
|
|
319
|
+
# @yieldreturn [Boolean] true to remove value
|
|
320
|
+
#
|
|
321
|
+
# @return [Remover] filtered serie
|
|
322
|
+
#
|
|
323
|
+
# @example Remove odds
|
|
324
|
+
# s = S(1, 2, 3, 4, 5).remove { |n| n.odd? }
|
|
325
|
+
# s.i.to_a # => [2, 4]
|
|
326
|
+
#
|
|
327
|
+
# @api public
|
|
60
328
|
def remove(block = nil, &yield_block)
|
|
61
|
-
# TODO make history an optional block parameter (via keyparametersprocedurebinder)
|
|
62
329
|
block ||= yield_block
|
|
63
330
|
Remover.new self, &block
|
|
64
331
|
end
|
|
65
332
|
|
|
333
|
+
# Selects values matching condition.
|
|
334
|
+
#
|
|
335
|
+
# Keeps only values where block returns true.
|
|
336
|
+
#
|
|
337
|
+
# @param block [Proc, nil] filter block
|
|
338
|
+
# @yield filter condition
|
|
339
|
+
# @yieldparam value [Object] current value
|
|
340
|
+
# @yieldreturn [Boolean] true to keep value
|
|
341
|
+
#
|
|
342
|
+
# @return [Selector] filtered serie
|
|
343
|
+
#
|
|
344
|
+
# @example Select evens
|
|
345
|
+
# s = S(1, 2, 3, 4, 5).select { |n| n.even? }
|
|
346
|
+
# s.i.to_a # => [2, 4]
|
|
347
|
+
#
|
|
348
|
+
# @api public
|
|
66
349
|
def select(block = nil, &yield_block)
|
|
67
|
-
# TODO add optional history (via keyparametersprocedurebinder)
|
|
68
350
|
block ||= yield_block
|
|
69
351
|
Selector.new self, &block
|
|
70
352
|
end
|
|
71
353
|
|
|
72
|
-
#
|
|
354
|
+
# Switches between multiple series based on selector values.
|
|
355
|
+
#
|
|
356
|
+
# Uses selector serie to choose which source serie to read from.
|
|
357
|
+
# Selector values can be indices (Integer) or keys (Symbol).
|
|
358
|
+
#
|
|
359
|
+
# @param indexed_series [Array] series indexed by integer
|
|
360
|
+
# @param hash_series [Hash] series indexed by symbol key
|
|
361
|
+
#
|
|
362
|
+
# @return [Switcher] switching serie
|
|
363
|
+
#
|
|
364
|
+
# @example Index switching
|
|
365
|
+
# s1 = S(1, 2, 3)
|
|
366
|
+
# s2 = S(10, 20, 30)
|
|
367
|
+
# selector = S(0, 1, 0, 1)
|
|
368
|
+
# result = selector.switch(s1, s2)
|
|
369
|
+
# result.i.to_a # => [1, 10, 2, 20]
|
|
370
|
+
#
|
|
371
|
+
# @api public
|
|
73
372
|
def switch(*indexed_series, **hash_series)
|
|
74
373
|
Switcher.new self, indexed_series, hash_series
|
|
75
374
|
end
|
|
76
375
|
|
|
376
|
+
# Multiplexes values from multiple series based on selector.
|
|
377
|
+
#
|
|
378
|
+
# Like switch but returns composite values instead of switching.
|
|
379
|
+
#
|
|
380
|
+
# @param indexed_series [Array] series to multiplex
|
|
381
|
+
# @param hash_series [Hash] series to multiplex by key
|
|
382
|
+
#
|
|
383
|
+
# @return [MultiplexSelector] multiplexed serie
|
|
384
|
+
#
|
|
385
|
+
# @api public
|
|
77
386
|
def multiplex(*indexed_series, **hash_series)
|
|
78
387
|
MultiplexSelector.new self, indexed_series, hash_series
|
|
79
388
|
end
|
|
80
389
|
|
|
81
|
-
#
|
|
390
|
+
# Switches to entirely different series based on selector.
|
|
391
|
+
#
|
|
392
|
+
# Changes which serie is being consumed entirely.
|
|
393
|
+
#
|
|
394
|
+
# @param indexed_series [Array] series to switch between
|
|
395
|
+
# @param hash_series [Hash] series to switch between by key
|
|
396
|
+
#
|
|
397
|
+
# @return [SwitchFullSerie] serie switcher
|
|
398
|
+
#
|
|
399
|
+
# @api public
|
|
82
400
|
def switch_serie(*indexed_series, **hash_series)
|
|
83
401
|
SwitchFullSerie.new self, indexed_series, hash_series
|
|
84
402
|
end
|
|
85
403
|
|
|
404
|
+
# Appends series sequentially.
|
|
405
|
+
#
|
|
406
|
+
# Alias for MERGE - plays this serie, then others in sequence.
|
|
407
|
+
#
|
|
408
|
+
# @param series [Array<Serie>] series to append
|
|
409
|
+
#
|
|
410
|
+
# @return [Sequence] sequential combination
|
|
411
|
+
#
|
|
412
|
+
# @example Append
|
|
413
|
+
# s = S(1, 2).after(S(3, 4), S(5, 6))
|
|
414
|
+
# s.i.to_a # => [1, 2, 3, 4, 5, 6]
|
|
415
|
+
#
|
|
416
|
+
# @api public
|
|
86
417
|
def after(*series)
|
|
87
418
|
Musa::Series::Constructors.MERGE self, *series
|
|
88
419
|
end
|
|
89
420
|
|
|
421
|
+
# Appends another serie (operator alias for after).
|
|
422
|
+
#
|
|
423
|
+
# @param other [Serie] serie to append
|
|
424
|
+
#
|
|
425
|
+
# @return [Sequence] sequential combination
|
|
426
|
+
#
|
|
427
|
+
# @example Concatenate
|
|
428
|
+
# s = S(1, 2) + S(3, 4)
|
|
429
|
+
# s.i.to_a # => [1, 2, 3, 4]
|
|
430
|
+
#
|
|
431
|
+
# @api public
|
|
90
432
|
def +(other)
|
|
91
433
|
Musa::Series::Constructors.MERGE self, other
|
|
92
434
|
end
|
|
93
435
|
|
|
436
|
+
# Cuts serie into chunks of specified length.
|
|
437
|
+
#
|
|
438
|
+
# Returns serie of arrays, each containing `length` values.
|
|
439
|
+
#
|
|
440
|
+
# @param length [Integer] chunk size
|
|
441
|
+
#
|
|
442
|
+
# @return [Cutter] chunked serie
|
|
443
|
+
#
|
|
444
|
+
# @example Cut into pairs
|
|
445
|
+
# s = S(1, 2, 3, 4, 5, 6).cut(2)
|
|
446
|
+
# s.i.to_a # => [[1, 2], [3, 4], [5, 6]]
|
|
447
|
+
#
|
|
448
|
+
# @api public
|
|
94
449
|
def cut(length)
|
|
95
450
|
Cutter.new self, length
|
|
96
451
|
end
|
|
97
452
|
|
|
453
|
+
# Merges serie of series into single serie.
|
|
454
|
+
#
|
|
455
|
+
# Flattens one level: consumes serie where each element is itself
|
|
456
|
+
# a serie, merging them sequentially.
|
|
457
|
+
#
|
|
458
|
+
# @return [MergeSerieOfSeries] merged serie
|
|
459
|
+
#
|
|
460
|
+
# @example Merge phrases
|
|
461
|
+
# phrases = S(S(1, 2, 3), S(4, 5, 6))
|
|
462
|
+
# merged = phrases.merge
|
|
463
|
+
# merged.i.to_a # => [1, 2, 3, 4, 5, 6]
|
|
464
|
+
#
|
|
465
|
+
# @api public
|
|
98
466
|
def merge
|
|
99
467
|
MergeSerieOfSeries.new self
|
|
100
468
|
end
|
|
101
469
|
|
|
102
|
-
#
|
|
470
|
+
# Combines multiple series for mapping.
|
|
471
|
+
#
|
|
472
|
+
# Synchronously iterates multiple series, passing all values to block.
|
|
473
|
+
# Enables multi-voice transformations and combinations.
|
|
474
|
+
#
|
|
475
|
+
# ## Parameters
|
|
103
476
|
#
|
|
477
|
+
# - **with_series**: Positional series (passed as array to block)
|
|
478
|
+
# - **with_key_series**: Named series (passed as keywords to block)
|
|
479
|
+
# - **on_restart**: Block called on restart
|
|
480
|
+
# - **isolate_values**: Clone values to prevent mutation
|
|
481
|
+
#
|
|
482
|
+
# ## Block Parameters
|
|
483
|
+
#
|
|
484
|
+
# Block receives:
|
|
485
|
+
# - Main serie value (first argument)
|
|
486
|
+
# - Positional with_series values (array)
|
|
487
|
+
# - Keyword with_key_series values (keywords)
|
|
488
|
+
#
|
|
489
|
+
# @param with_series [Array<Serie>] positional series to combine
|
|
490
|
+
# @param on_restart [Proc, nil] restart callback
|
|
491
|
+
# @param isolate_values [Boolean, nil] clone values to prevent mutation
|
|
492
|
+
# @param with_key_series [Hash] keyword series to combine
|
|
493
|
+
# @yield combination block
|
|
494
|
+
# @yieldparam main_value [Object] value from main serie
|
|
495
|
+
# @yieldparam with_values [Array] values from positional series
|
|
496
|
+
# @yieldparam with_key_values [Hash] values from keyword series
|
|
497
|
+
# @yieldreturn [Object] combined value
|
|
498
|
+
#
|
|
499
|
+
# @return [With] combined serie
|
|
500
|
+
#
|
|
501
|
+
# @example Combine pitches and velocities
|
|
502
|
+
# pitches = S(60, 64, 67)
|
|
503
|
+
# velocities = S(96, 80, 64)
|
|
504
|
+
# notes = pitches.with(velocities) { |p, v| {pitch: p, velocity: v} }
|
|
505
|
+
# notes.i.to_a # => [{pitch: 60, velocity: 96}, ...]
|
|
506
|
+
#
|
|
507
|
+
# @example Named series
|
|
508
|
+
# melody = S(60, 64, 67)
|
|
509
|
+
# rhythm = S(1r, 0.5r, 0.5r)
|
|
510
|
+
# combined = melody.with(duration: rhythm) { |pitch, duration:|
|
|
511
|
+
# {pitch: pitch, duration: duration}
|
|
512
|
+
# }
|
|
513
|
+
#
|
|
514
|
+
# @api public
|
|
104
515
|
def with(*with_series, on_restart: nil, isolate_values: nil, **with_key_series, &block)
|
|
105
516
|
if with_series.any? && with_key_series.any?
|
|
106
517
|
raise ArgumentError, 'Can\'t use extra parameters series and key named parameters series'
|
|
@@ -117,18 +528,80 @@ module Musa
|
|
|
117
528
|
ProcessWith.new self, extra_series, on_restart, isolate_values: isolate_values, &block
|
|
118
529
|
end
|
|
119
530
|
|
|
531
|
+
# Alias for {#with}.
|
|
532
|
+
#
|
|
533
|
+
# @api public
|
|
120
534
|
alias_method :eval, :with
|
|
121
535
|
|
|
536
|
+
# Maps values via transformation block.
|
|
537
|
+
#
|
|
538
|
+
# Simplest and most common transformation. Applies block to each value.
|
|
539
|
+
# Shorthand for `with` without additional series.
|
|
540
|
+
#
|
|
541
|
+
# @param isolate_values [Boolean, nil] clone values to prevent mutation (default: false)
|
|
542
|
+
# @yield transformation block
|
|
543
|
+
# @yieldparam value [Object] current value
|
|
544
|
+
# @yieldreturn [Object] transformed value
|
|
545
|
+
#
|
|
546
|
+
# @return [ProcessWith] mapped serie
|
|
547
|
+
#
|
|
548
|
+
# @example Transpose notes
|
|
549
|
+
# notes = S(60, 64, 67).map { |n| n + 12 }
|
|
550
|
+
# notes.i.to_a # => [72, 76, 79]
|
|
551
|
+
#
|
|
552
|
+
# @example Transform to hash
|
|
553
|
+
# s = S(1, 2, 3).map { |n| {value: n, squared: n**2} }
|
|
554
|
+
#
|
|
555
|
+
# @api public
|
|
122
556
|
def map(isolate_values: nil, &block)
|
|
123
557
|
isolate_values ||= isolate_values.nil? ? false : isolate_values
|
|
124
558
|
|
|
125
559
|
ProcessWith.new self, isolate_values: isolate_values, &block
|
|
126
560
|
end
|
|
127
561
|
|
|
562
|
+
# Evaluates block one step ahead (anticipate).
|
|
563
|
+
#
|
|
564
|
+
# Block receives current value and NEXT value (peeked).
|
|
565
|
+
# Enables look-ahead transformations and transitions.
|
|
566
|
+
#
|
|
567
|
+
# @yield anticipation block
|
|
568
|
+
# @yieldparam current [Object] current value
|
|
569
|
+
# @yieldparam next_value [Object, nil] next value (nil if last)
|
|
570
|
+
# @yieldreturn [Object] transformed value
|
|
571
|
+
#
|
|
572
|
+
# @return [Anticipate] anticipating serie
|
|
573
|
+
#
|
|
574
|
+
# @example Smooth transitions
|
|
575
|
+
# s = S(1, 5, 3, 8).anticipate { |current, next_val|
|
|
576
|
+
# next_val ? (current + next_val) / 2.0 : current
|
|
577
|
+
# }
|
|
578
|
+
#
|
|
579
|
+
# @example Add interval information
|
|
580
|
+
# notes = S(60, 64, 67, 72).anticipate { |pitch, next_pitch|
|
|
581
|
+
# interval = next_pitch ? next_pitch - pitch : nil
|
|
582
|
+
# {pitch: pitch, interval: interval}
|
|
583
|
+
# }
|
|
584
|
+
#
|
|
585
|
+
# @api public
|
|
128
586
|
def anticipate(&block)
|
|
129
587
|
Anticipate.new self, &block
|
|
130
588
|
end
|
|
131
589
|
|
|
590
|
+
# Delays evaluation to next step (lazy evaluation).
|
|
591
|
+
#
|
|
592
|
+
# Block receives previous value and evaluates for current step.
|
|
593
|
+
# Enables state-dependent transformations.
|
|
594
|
+
#
|
|
595
|
+
# @yield lazy evaluation block
|
|
596
|
+
# @yieldparam previous [Object, nil] previous value
|
|
597
|
+
# @yieldreturn [Object] current value
|
|
598
|
+
#
|
|
599
|
+
# @return [LazySerieEval] lazy-evaluated serie
|
|
600
|
+
#
|
|
601
|
+
# @example Cumulative sum
|
|
602
|
+
# s = S(1, 2, 3, 4).lazy { |prev| (prev || 0) + value }
|
|
603
|
+
#
|
|
604
|
+
# @api public
|
|
132
605
|
def lazy(&block)
|
|
133
606
|
LazySerieEval.new self, &block
|
|
134
607
|
end
|
|
@@ -661,6 +1134,29 @@ module Musa
|
|
|
661
1134
|
|
|
662
1135
|
private_constant :MergeSerieOfSeries
|
|
663
1136
|
|
|
1137
|
+
# Serie operation that processes/transforms values using a block.
|
|
1138
|
+
#
|
|
1139
|
+
# Applies transformation function to each value from source serie.
|
|
1140
|
+
# The block can return single values or arrays (which are flattened
|
|
1141
|
+
# into the output stream).
|
|
1142
|
+
#
|
|
1143
|
+
# Uses smart block binding for flexible parameter handling.
|
|
1144
|
+
#
|
|
1145
|
+
# @example Simple transformation
|
|
1146
|
+
# serie = FromArray.new([1, 2, 3])
|
|
1147
|
+
# processor = Processor.new(serie, {}) { |v| v * 2 }
|
|
1148
|
+
# processor.next_value # => 2
|
|
1149
|
+
# processor.next_value # => 4
|
|
1150
|
+
#
|
|
1151
|
+
# @example Transformation with parameters
|
|
1152
|
+
# processor = Processor.new(serie, multiplier: 3) { |v, multiplier:| v * multiplier }
|
|
1153
|
+
#
|
|
1154
|
+
# @example Returning arrays (flattened)
|
|
1155
|
+
# processor = Processor.new(serie, {}) { |v| [v, v + 1] }
|
|
1156
|
+
# processor.next_value # => 1
|
|
1157
|
+
# processor.next_value # => 2
|
|
1158
|
+
#
|
|
1159
|
+
# @api private
|
|
664
1160
|
class Processor
|
|
665
1161
|
include Serie.with(source: true, smart_block: true)
|
|
666
1162
|
|
|
@@ -931,8 +1427,8 @@ module Musa
|
|
|
931
1427
|
include Serie.with(source: true)
|
|
932
1428
|
|
|
933
1429
|
def initialize(serie, shift)
|
|
934
|
-
self.source = serie
|
|
935
1430
|
self.shift = shift
|
|
1431
|
+
self.source = serie
|
|
936
1432
|
|
|
937
1433
|
init
|
|
938
1434
|
end
|
|
@@ -948,13 +1444,14 @@ module Musa
|
|
|
948
1444
|
|
|
949
1445
|
def shift=(value)
|
|
950
1446
|
raise ArgumentError, "cannot shift to right an infinite serie" if value > 0 && @source&.infinite?
|
|
951
|
-
raise NotImplementedError, 'cannot shift to right: function not yet implemented' if value > 0
|
|
952
1447
|
|
|
953
1448
|
@shift = value
|
|
954
1449
|
end
|
|
955
1450
|
|
|
956
1451
|
private def _init
|
|
957
1452
|
@shifted = []
|
|
1453
|
+
@buffer = []
|
|
1454
|
+
@buffer_index = 0
|
|
958
1455
|
@first = true
|
|
959
1456
|
end
|
|
960
1457
|
|
|
@@ -964,14 +1461,39 @@ module Musa
|
|
|
964
1461
|
|
|
965
1462
|
private def _next_value
|
|
966
1463
|
if @first
|
|
967
|
-
|
|
1464
|
+
if @shift < 0
|
|
1465
|
+
# Shift left: guardar primeros N elementos para moverlos al final
|
|
1466
|
+
@shift.abs.times { @shifted << @source.next_value }
|
|
1467
|
+
elsif @shift > 0
|
|
1468
|
+
# Shift right: leer toda la serie y rotarla
|
|
1469
|
+
while (value = @source.next_value)
|
|
1470
|
+
@buffer << value
|
|
1471
|
+
end
|
|
1472
|
+
|
|
1473
|
+
# Rotar: tomar últimos N elementos y ponerlos al principio
|
|
1474
|
+
if @buffer.size >= @shift
|
|
1475
|
+
last_elements = @buffer.pop(@shift)
|
|
1476
|
+
@buffer = last_elements + @buffer
|
|
1477
|
+
end
|
|
1478
|
+
end
|
|
1479
|
+
|
|
968
1480
|
@first = false
|
|
969
1481
|
end
|
|
970
1482
|
|
|
971
|
-
|
|
972
|
-
|
|
1483
|
+
# Retornar valores según el tipo de shift
|
|
1484
|
+
if @shift > 0
|
|
1485
|
+
# Shift derecho: devolver del buffer pre-cargado
|
|
1486
|
+
return nil if @buffer_index >= @buffer.size
|
|
1487
|
+
value = @buffer[@buffer_index]
|
|
1488
|
+
@buffer_index += 1
|
|
1489
|
+
value
|
|
1490
|
+
else
|
|
1491
|
+
# Shift izquierdo o sin shift: lógica original
|
|
1492
|
+
value = @source.next_value
|
|
1493
|
+
return value unless value.nil?
|
|
973
1494
|
|
|
974
|
-
|
|
1495
|
+
@shifted.shift
|
|
1496
|
+
end
|
|
975
1497
|
end
|
|
976
1498
|
end
|
|
977
1499
|
|