musa-dsl 0.30.2 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.version +6 -0
  4. data/.yardopts +7 -0
  5. data/README.md +227 -6
  6. data/docs/README.md +83 -0
  7. data/docs/api-reference.md +86 -0
  8. data/docs/getting-started/quick-start.md +93 -0
  9. data/docs/getting-started/tutorial.md +58 -0
  10. data/docs/subsystems/core-extensions.md +316 -0
  11. data/docs/subsystems/datasets.md +465 -0
  12. data/docs/subsystems/generative.md +290 -0
  13. data/docs/subsystems/matrix.md +63 -0
  14. data/docs/subsystems/midi.md +123 -0
  15. data/docs/subsystems/music.md +233 -0
  16. data/docs/subsystems/musicxml-builder.md +264 -0
  17. data/docs/subsystems/neumas.md +71 -0
  18. data/docs/subsystems/repl.md +135 -0
  19. data/docs/subsystems/sequencer.md +98 -0
  20. data/docs/subsystems/series.md +302 -0
  21. data/docs/subsystems/transcription.md +152 -0
  22. data/docs/subsystems/transport.md +177 -0
  23. data/lib/musa-dsl/core-ext/array-explode-ranges.rb +68 -0
  24. data/lib/musa-dsl/core-ext/arrayfy.rb +110 -0
  25. data/lib/musa-dsl/core-ext/attribute-builder.rb +91 -30
  26. data/lib/musa-dsl/core-ext/deep-copy.rb +125 -2
  27. data/lib/musa-dsl/core-ext/dynamic-proxy.rb +78 -0
  28. data/lib/musa-dsl/core-ext/extension.rb +53 -0
  29. data/lib/musa-dsl/core-ext/hashify.rb +162 -1
  30. data/lib/musa-dsl/core-ext/inspect-nice.rb +154 -0
  31. data/lib/musa-dsl/core-ext/smart-proc-binder.rb +117 -0
  32. data/lib/musa-dsl/core-ext/with.rb +114 -0
  33. data/lib/musa-dsl/datasets/dataset.rb +109 -0
  34. data/lib/musa-dsl/datasets/delta-d.rb +78 -0
  35. data/lib/musa-dsl/datasets/e.rb +186 -2
  36. data/lib/musa-dsl/datasets/gdv.rb +279 -2
  37. data/lib/musa-dsl/datasets/gdvd.rb +201 -0
  38. data/lib/musa-dsl/datasets/helper.rb +75 -0
  39. data/lib/musa-dsl/datasets/p.rb +177 -2
  40. data/lib/musa-dsl/datasets/packed-v.rb +91 -0
  41. data/lib/musa-dsl/datasets/pdv.rb +136 -1
  42. data/lib/musa-dsl/datasets/ps.rb +134 -4
  43. data/lib/musa-dsl/datasets/score/queriable.rb +143 -1
  44. data/lib/musa-dsl/datasets/score/render.rb +105 -1
  45. data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +138 -1
  46. data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +111 -0
  47. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +200 -1
  48. data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +145 -1
  49. data/lib/musa-dsl/datasets/score.rb +279 -0
  50. data/lib/musa-dsl/datasets/v.rb +88 -0
  51. data/lib/musa-dsl/generative/darwin.rb +180 -1
  52. data/lib/musa-dsl/generative/generative-grammar.rb +359 -0
  53. data/lib/musa-dsl/generative/markov.rb +133 -3
  54. data/lib/musa-dsl/generative/rules.rb +258 -4
  55. data/lib/musa-dsl/generative/variatio.rb +217 -2
  56. data/lib/musa-dsl/logger/logger.rb +267 -2
  57. data/lib/musa-dsl/matrix/matrix.rb +256 -10
  58. data/lib/musa-dsl/midi/midi-recorder.rb +108 -1
  59. data/lib/musa-dsl/midi/midi-voices.rb +265 -4
  60. data/lib/musa-dsl/music/chord-definition.rb +233 -1
  61. data/lib/musa-dsl/music/chord-definitions.rb +33 -6
  62. data/lib/musa-dsl/music/chords.rb +308 -2
  63. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +315 -0
  64. data/lib/musa-dsl/music/scales.rb +957 -40
  65. data/lib/musa-dsl/musicxml/builder/attributes.rb +483 -3
  66. data/lib/musa-dsl/musicxml/builder/backup-forward.rb +166 -1
  67. data/lib/musa-dsl/musicxml/builder/direction.rb +243 -0
  68. data/lib/musa-dsl/musicxml/builder/helper.rb +240 -0
  69. data/lib/musa-dsl/musicxml/builder/measure.rb +284 -0
  70. data/lib/musa-dsl/musicxml/builder/note-complexities.rb +324 -8
  71. data/lib/musa-dsl/musicxml/builder/note.rb +285 -0
  72. data/lib/musa-dsl/musicxml/builder/part-group.rb +108 -1
  73. data/lib/musa-dsl/musicxml/builder/part.rb +139 -0
  74. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +124 -0
  75. data/lib/musa-dsl/musicxml/builder/rest.rb +93 -0
  76. data/lib/musa-dsl/musicxml/builder/score-partwise.rb +276 -0
  77. data/lib/musa-dsl/musicxml/builder/typed-text.rb +62 -1
  78. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +83 -0
  79. data/lib/musa-dsl/neumalang/neumalang.rb +675 -0
  80. data/lib/musa-dsl/neumas/array-to-neumas.rb +149 -0
  81. data/lib/musa-dsl/neumas/neuma-decoder.rb +253 -0
  82. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +142 -2
  83. data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +82 -0
  84. data/lib/musa-dsl/neumas/neumas.rb +67 -0
  85. data/lib/musa-dsl/neumas/string-to-neumas.rb +233 -1
  86. data/lib/musa-dsl/repl/repl.rb +550 -0
  87. data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +118 -2
  88. data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +149 -2
  89. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +296 -0
  90. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +88 -2
  91. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +161 -0
  92. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +263 -0
  93. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +173 -1
  94. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +177 -0
  95. data/lib/musa-dsl/sequencer/base-sequencer.rb +710 -10
  96. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +210 -0
  97. data/lib/musa-dsl/sequencer/timeslots.rb +79 -0
  98. data/lib/musa-dsl/series/array-to-serie.rb +37 -1
  99. data/lib/musa-dsl/series/base-series.rb +843 -5
  100. data/lib/musa-dsl/series/buffer-serie.rb +48 -0
  101. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +41 -0
  102. data/lib/musa-dsl/series/main-serie-constructors.rb +398 -2
  103. data/lib/musa-dsl/series/main-serie-operations.rb +538 -16
  104. data/lib/musa-dsl/series/proxy-serie.rb +67 -0
  105. data/lib/musa-dsl/series/quantizer-serie.rb +45 -7
  106. data/lib/musa-dsl/series/queue-serie.rb +65 -0
  107. data/lib/musa-dsl/series/series-composer.rb +701 -0
  108. data/lib/musa-dsl/series/timed-serie.rb +473 -28
  109. data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +404 -1
  110. data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +118 -0
  111. data/lib/musa-dsl/transcription/from-gdv.rb +84 -1
  112. data/lib/musa-dsl/transcription/transcription.rb +265 -0
  113. data/lib/musa-dsl/transport/clock.rb +125 -0
  114. data/lib/musa-dsl/transport/dummy-clock.rb +89 -2
  115. data/lib/musa-dsl/transport/external-tick-clock.rb +91 -0
  116. data/lib/musa-dsl/transport/input-midi-clock.rb +133 -1
  117. data/lib/musa-dsl/transport/timer-clock.rb +183 -1
  118. data/lib/musa-dsl/transport/timer.rb +83 -0
  119. data/lib/musa-dsl/transport/transport.rb +318 -0
  120. data/lib/musa-dsl/version.rb +1 -1
  121. data/lib/musa-dsl.rb +132 -25
  122. data/musa-dsl.gemspec +12 -10
  123. metadata +87 -8
@@ -1,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
- # TODO: test case
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
- # TODO: test case
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
- # TODO: test case
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
- # TODO: test case
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
- # TODO: test case
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
- # TODO: test case
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
- # TODO: test case
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
- # TODO on with and map methods implement parameter passing with cloning on restart as on E()
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
- @shift.abs.times { @shifted << @source.next_value } if @shift < 0
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
- value = @source.next_value
972
- return value unless value.nil?
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
- @shifted.shift
1495
+ @shifted.shift
1496
+ end
975
1497
  end
976
1498
  end
977
1499