musa-dsl 0.21.0 → 0.22.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -1
  3. data/lib/musa-dsl.rb +1 -1
  4. data/lib/musa-dsl/core-ext.rb +1 -0
  5. data/lib/musa-dsl/core-ext/arrayfy.rb +9 -9
  6. data/lib/musa-dsl/core-ext/hashify.rb +42 -0
  7. data/lib/musa-dsl/core-ext/inspect-nice.rb +6 -1
  8. data/lib/musa-dsl/datasets/e.rb +22 -5
  9. data/lib/musa-dsl/datasets/gdv.rb +0 -1
  10. data/lib/musa-dsl/datasets/p.rb +28 -37
  11. data/lib/musa-dsl/datasets/pdv.rb +0 -1
  12. data/lib/musa-dsl/datasets/ps.rb +10 -78
  13. data/lib/musa-dsl/generative/markov.rb +1 -1
  14. data/lib/musa-dsl/logger/logger.rb +4 -3
  15. data/lib/musa-dsl/matrix/matrix.rb +0 -57
  16. data/lib/musa-dsl/midi/midi-voices.rb +4 -0
  17. data/lib/musa-dsl/neumas/string-to-neumas.rb +1 -0
  18. data/lib/musa-dsl/repl/repl.rb +30 -11
  19. data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +87 -0
  20. data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +439 -0
  21. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +3 -3
  22. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +210 -0
  23. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +178 -0
  24. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +150 -595
  25. data/lib/musa-dsl/sequencer/base-sequencer-public.rb +58 -5
  26. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +5 -9
  27. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +1 -5
  28. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +8 -0
  29. data/lib/musa-dsl/series/base-series.rb +43 -78
  30. data/lib/musa-dsl/series/flattener-timed-serie.rb +61 -0
  31. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +95 -0
  32. data/lib/musa-dsl/series/holder-serie.rb +1 -1
  33. data/lib/musa-dsl/series/main-serie-constructors.rb +29 -83
  34. data/lib/musa-dsl/series/main-serie-operations.rb +60 -215
  35. data/lib/musa-dsl/series/proxy-serie.rb +1 -1
  36. data/lib/musa-dsl/series/quantizer-serie.rb +546 -0
  37. data/lib/musa-dsl/series/queue-serie.rb +1 -1
  38. data/lib/musa-dsl/series/series.rb +7 -2
  39. data/lib/musa-dsl/transport/input-midi-clock.rb +19 -12
  40. data/lib/musa-dsl/transport/transport.rb +25 -12
  41. data/musa-dsl.gemspec +2 -2
  42. metadata +11 -5
  43. data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +0 -216
  44. data/lib/musa-dsl/series/hash-serie-splitter.rb +0 -196
@@ -20,7 +20,7 @@ module Musa
20
20
  @target = target.instance
21
21
  end
22
22
 
23
- def _prototype
23
+ def _prototype!
24
24
  raise PrototypingSerieError, 'Cannot get prototype of a proxy serie'
25
25
  end
26
26
 
@@ -0,0 +1,546 @@
1
+ require_relative '../datasets/e'
2
+ require_relative '../core-ext/inspect-nice'
3
+
4
+ # TODO remove debugging puts, intermediate hash comments on :info and InspectNice
5
+ using Musa::Extension::InspectNice
6
+
7
+ module Musa
8
+ module Series
9
+ module SerieOperations
10
+ def quantize(reference: nil, step: nil,
11
+ value_attribute: nil,
12
+ stops: nil,
13
+ predictive: nil,
14
+ left_open: nil,
15
+ right_open: nil)
16
+
17
+ Series.QUANTIZE(self,
18
+ reference: reference,
19
+ step: step,
20
+ value_attribute: value_attribute,
21
+ stops: stops,
22
+ predictive: predictive,
23
+ left_open: left_open,
24
+ right_open: right_open)
25
+ end
26
+ end
27
+ end
28
+
29
+ module Series
30
+ extend self
31
+
32
+ def QUANTIZE(time_value_serie,
33
+ reference: nil, step: nil,
34
+ value_attribute: nil,
35
+ stops: nil,
36
+ predictive: nil,
37
+ left_open: nil,
38
+ right_open: nil)
39
+
40
+ reference ||= 0r
41
+ step ||= 1r
42
+ value_attribute ||= :value
43
+ stops ||= false
44
+ predictive ||= false
45
+
46
+ if predictive
47
+ raise ArgumentError, "Predictive quantization doesn't allow parameters 'left_open' or 'right_open'" if left_open || right_open
48
+
49
+ PredictiveQuantizer.new(reference, step, time_value_serie, value_attribute, stops)
50
+ else
51
+ # By default: left closed and right_open
52
+ # By default 2:
53
+ # if right_open is true and left_open is nil, left_open will be false
54
+ # if left_open is true and right_open is nil, right_open will be false
55
+
56
+ right_open = right_open.nil? ? !left_open : right_open
57
+ left_open = left_open.nil? ? !right_open : left_open
58
+
59
+ RawQuantizer.new(reference, step, time_value_serie, value_attribute, stops, left_open, right_open)
60
+ end
61
+ end
62
+
63
+ module QuantizerTools
64
+ private def get_time_value(n)
65
+ case n
66
+ when nil
67
+ time = value = nil
68
+ when AbsTimed
69
+ time = n[:time].rationalize
70
+ value = n[@value_attribute].rationalize
71
+ when Array
72
+ time = n[0].rationalize
73
+ value = n[1].rationalize
74
+ else
75
+ raise RuntimeError, "Don't know how to process #{n}"
76
+ end
77
+
78
+ return time, value
79
+ end
80
+ end
81
+
82
+ private_constant :QuantizerTools
83
+
84
+ class RawQuantizer
85
+ include Serie
86
+ include QuantizerTools
87
+
88
+ attr_reader :source
89
+
90
+ attr_reader :points_history
91
+
92
+ def initialize(reference, step, source, value_attribute, stops, left_open, right_open)
93
+ @reference = reference
94
+ @step_size = step.abs
95
+
96
+ @source = source
97
+ @value_attribute = value_attribute
98
+
99
+ @stops = stops
100
+ @left_open = left_open
101
+ @right_open = right_open
102
+
103
+ _restart false
104
+
105
+ mark_regarding! source
106
+ end
107
+
108
+ def _restart(restart_sources = true)
109
+ @last_processed_q_value = nil
110
+ @last_processed_time = nil
111
+
112
+ @before_last_processed_q_value = nil
113
+
114
+ @points = []
115
+ @segments = []
116
+
117
+ @source.restart if restart_sources
118
+ end
119
+
120
+ def _next_value
121
+ if @stops
122
+ i = 2
123
+
124
+ loop do
125
+ while @segments.size < i && process2; end
126
+
127
+ first = @segments[0]
128
+ last = @segments[i - 1]
129
+
130
+ # puts "_next_value: first #{first || 'nil'} last #{last || 'nil'}"
131
+
132
+ break if first.nil?
133
+
134
+ if last.nil? || last[:stop] || first[:value] != last[:value]
135
+ # puts "_next_value: found segments:"
136
+
137
+ durations_to_sum = @segments.shift(i-1)
138
+
139
+ # durations_to_sum.each { |i| puts i.inspect }
140
+ #
141
+ # puts "_next_value: result #{{ time: first[:time],
142
+ # @value_attribute => first[:value],
143
+ # duration: durations_to_sum.sum { |_| _[:duration] } }}"
144
+
145
+ return { time: first[:time],
146
+ @value_attribute => first[:value],
147
+ duration: durations_to_sum.sum { |_| _[:duration] } }.extend(AbsTimed).extend(AbsD)
148
+ else
149
+ i += 1
150
+ end
151
+ end
152
+
153
+ return nil
154
+
155
+ else
156
+ i = 2
157
+ # puts "\n\n"
158
+ loop do
159
+ while @segments.size < i && process2; end
160
+
161
+ first = @segments[0]
162
+ last = @segments[i - 1]
163
+
164
+ # puts "_next_value: first #{first || 'nil'} last #{last || 'nil'}"
165
+
166
+ break if first.nil?
167
+
168
+ if last.nil? || first[:value] != last[:value]
169
+
170
+ # puts "_next_value: found segments:"
171
+
172
+ durations_to_sum = @segments.shift(i-1)
173
+
174
+ # durations_to_sum.each { |i| puts i.inspect }
175
+ #
176
+ # puts "_next_value: result #{{ time: first[:time],
177
+ # @value_attribute => first[:value],
178
+ # duration: durations_to_sum.sum { |_| _[:duration] } }}"
179
+
180
+ return { time: first[:time],
181
+ @value_attribute => first[:value],
182
+ duration: durations_to_sum.sum { |_| _[:duration] } }.extend(AbsTimed).extend(AbsD)
183
+ else
184
+ i += 1
185
+ end
186
+ end
187
+
188
+ return nil
189
+ end
190
+ end
191
+
192
+ private def process2
193
+ while (ready_count = count_ready_points) <= 2 &&
194
+ process(*get_time_value(@source.next_value), !@source.peek_next_value)
195
+ end
196
+
197
+ if ready_count >= 2
198
+ point = @points.shift
199
+
200
+ from_time = point[:time]
201
+ from_value = point[:value]
202
+
203
+ next_point = @points.first
204
+
205
+ to_time = next_point[:time]
206
+ to_value = next_point[:value]
207
+ to_point_is_last = next_point[:last]
208
+
209
+ sign = to_value <=> from_value # to_value > from_value => +1
210
+
211
+ # puts "process2: from_time #{from_time} from_value #{from_value} to_time #{to_time} to_value #{to_value} to_last #{to_point_is_last || 'nil'} sign #{sign}"
212
+
213
+ if sign == 0
214
+ if @segments.last && @segments.last[:time] == from_time
215
+
216
+ @segments.last[:duration] = to_time - from_time
217
+ @segments.last[:info] += "; edited on a as start"
218
+
219
+ else
220
+ @segments << { time: from_time,
221
+ value: from_value,
222
+ duration: to_time - from_time,
223
+ info: "added on a as start" }
224
+
225
+ end
226
+
227
+ if !to_point_is_last
228
+ @segments << { time: to_time,
229
+ value: from_value,
230
+ duration: 0,
231
+ stop: true,
232
+ info: "added on a as end stop" }
233
+ end
234
+ else
235
+ time_increment = to_time - from_time
236
+
237
+ step_value_increment = @step_size * sign
238
+
239
+ extra_steps = 0
240
+
241
+ if @right_open
242
+ loop_to_value = to_value - step_value_increment
243
+ else
244
+ loop_to_value = to_value
245
+ extra_steps += 1
246
+ end
247
+
248
+ if @left_open
249
+ loop_from_value = from_value + step_value_increment
250
+ extra_steps -= 1
251
+ else
252
+ loop_from_value = from_value
253
+ end
254
+
255
+ step_time_increment = time_increment / ((to_value - from_value).abs + extra_steps)
256
+
257
+ intermediate_point_time = from_time
258
+
259
+ # puts "process2: loop_from_value #{loop_from_value} loop_to_value #{loop_to_value} step_value_increment #{step_value_increment} step_time_increment #{step_time_increment}"
260
+
261
+ loop_from_value.step(loop_to_value, step_value_increment) do |value|
262
+ if @segments.last &&
263
+ @segments.last[:time] == intermediate_point_time &&
264
+ @segments.last[:value] == value
265
+
266
+ @segments.last[:duration] = step_time_increment
267
+ @segments.last[:info] += "; edited on b"
268
+
269
+ # puts "process2: editing #{@segments.last}"
270
+
271
+ else
272
+ @segments << v = { time: intermediate_point_time,
273
+ value: value,
274
+ duration: step_time_increment,
275
+ info: "added on b" }
276
+
277
+ # puts "process2: adding #{v.inspect}"
278
+ end
279
+
280
+ intermediate_point_time += step_time_increment
281
+ end
282
+ end
283
+
284
+ true
285
+ else
286
+ false
287
+ end
288
+ end
289
+
290
+ private def count_ready_points
291
+ @points.select { |_| _[:ready] }.size
292
+ end
293
+
294
+ private def process(time, value, last_time_value)
295
+ if time && value
296
+ raise RuntimeError, "time only can go forward" if @last_processed_time && time <= @last_processed_time
297
+
298
+ q_value = round_quantize(value)
299
+
300
+ # A continuation point time will be changed if new points of equal value arrive.
301
+ # For this reason this point is not ready to consume.
302
+ # A ready point is a well determined point that can be consumed.
303
+ # When we arrive to a well determined point all the previous points become also determined (ready).
304
+
305
+ if q_value == @last_processed_q_value && !last_time_value
306
+ if @points.size == 1 || @points.last[:ready]
307
+ # If @points.last is the first point of a segment the new point is a continuation point.
308
+ # The continuation point is used as a stop point.
309
+ @points << { time: time, value: q_value }
310
+ else
311
+ # @points.last is NOT the first point of a segment but a continuation point.
312
+ @points.last[:time] = time
313
+ end
314
+ else
315
+ @points.reverse_each do |point|
316
+ break if point[:ready]
317
+ point[:ready] = true
318
+ end
319
+
320
+ @points << { time: time, value: q_value, ready: true, last: last_time_value }
321
+ end
322
+
323
+ @last_processed_q_value = q_value
324
+ @last_processed_time = time
325
+
326
+ true
327
+ else
328
+ false
329
+ end
330
+ end
331
+
332
+ private def round_quantize(value)
333
+ round((value - @reference) / @step_size) * @step_size + @reference
334
+ end
335
+
336
+ private def round(value)
337
+ i = value.floor
338
+ value > (i + 1/2r) ? i + 1r : i.rationalize
339
+ end
340
+
341
+ def infinite?
342
+ !!@source.infinite?
343
+ end
344
+ end
345
+
346
+ private_constant :RawQuantizer
347
+
348
+ class PredictiveQuantizer
349
+ include Serie
350
+ include QuantizerTools
351
+
352
+ attr_reader :source
353
+
354
+ def initialize(reference, step, source, value_attribute, include_stops)
355
+ @reference = reference
356
+ @step_size = step
357
+
358
+ @source = source
359
+ @value_attribute = value_attribute
360
+
361
+ @include_stops = include_stops
362
+
363
+ @halfway_offset = step / 2r
364
+ @crossing_reference = reference - @halfway_offset
365
+
366
+ _restart false
367
+
368
+ mark_regarding! source
369
+ end
370
+
371
+ def _restart(restart_sources = true)
372
+ @source.restart if restart_sources
373
+
374
+ @last_time = nil
375
+ @crossings = []
376
+
377
+ @first = true
378
+ end
379
+
380
+ def infinite?
381
+ !!@source.infinite?
382
+ end
383
+
384
+ def _next_value
385
+ result = nil
386
+
387
+ first_time, first_value = get_time_value(@source.peek_next_value) if @first
388
+
389
+ while @crossings.size <= 2 && process_more; end
390
+
391
+ @crossings.delete_if { |c| c[:stop] && c[:stops].nil? }
392
+
393
+ if @crossings[0]
394
+ time = @crossings[0][:time]
395
+ value = @crossings[0][@value_attribute]
396
+
397
+ if @first
398
+ @first = false
399
+
400
+ if time > first_time
401
+ result = { time: first_time,
402
+ @value_attribute => round_to_nearest_quantize(first_value, value),
403
+ duration: time - first_time }.extend(AbsD).extend(AbsTimed)
404
+ else
405
+ result = _next_value
406
+ end
407
+ else
408
+ if @crossings[1]
409
+ next_time = @crossings[1][:time]
410
+ result = { time: time,
411
+ @value_attribute => value,
412
+ duration: next_time - time }.extend(AbsD).extend(AbsTimed)
413
+
414
+ @crossings.shift
415
+
416
+ else
417
+ if @last_time && @last_time > @crossings[0][:time]
418
+ result = { time: @crossings[0][:time],
419
+ @value_attribute => @crossings[0][@value_attribute],
420
+ duration: @last_time - @crossings[0][:time] }.extend(AbsD).extend(AbsTimed)
421
+
422
+ @last_time = nil
423
+ end
424
+ end
425
+ end
426
+ else
427
+ if @first && @last_time && @last_time > first_time
428
+ result = { time: first_time,
429
+ value: round_to_nearest_quantize(first_value),
430
+ duration: @last_time - first_time }.extend(AbsD).extend(AbsTimed)
431
+
432
+ @first = false
433
+ @last_time = false
434
+ end
435
+ end
436
+
437
+ return result
438
+ end
439
+
440
+ private def process_more
441
+ while (@crossings.size <= 2 || @crossings[-1][:stop]) && new_crossings = next_crossings
442
+ new_crossings.each do |c|
443
+ if @last_time.nil? || c[:time] > @last_time
444
+
445
+ if c[:stop] &&
446
+ @crossings.dig(-1, :stop) &&
447
+ @crossings.dig(-1, @value_attribute) == c[@value_attribute]
448
+
449
+ c[:stops] = (@crossings[-1][:stops] || 0) + 1
450
+
451
+ @crossings[-1] = c
452
+ else
453
+ @crossings << c
454
+ end
455
+ end
456
+ end
457
+ end
458
+
459
+ !!new_crossings
460
+ end
461
+
462
+ private def next_crossings
463
+ from_time, from_value = get_time_value(@source.next_value)
464
+
465
+ if from_time && from_value
466
+ raise RuntimeError, "time only can go forward" if @last_time && from_time <= @last_time
467
+
468
+ @last_time = from_time
469
+
470
+ to_time, to_value = get_time_value(@source.peek_next_value)
471
+
472
+ if to_time && to_value
473
+ crossings(from_time, from_value, to_time, to_value)
474
+ end
475
+ end
476
+ end
477
+
478
+ private def crossings(from_time, from_value, to_time, to_value)
479
+ sign = to_value >= from_value ? 1r : -1r
480
+
481
+ if sign == 1
482
+ from_step = ((from_value - @crossing_reference) / @step_size).ceil
483
+ last_step = ((to_value - @crossing_reference) / @step_size).floor
484
+ else
485
+ from_step = ((from_value - @crossing_reference) / @step_size).floor
486
+ last_step = ((to_value - @crossing_reference) / @step_size).ceil
487
+ end
488
+
489
+ delta_value = to_value - from_value
490
+ delta_time = to_time - from_time
491
+
492
+ crossings =
493
+ from_step.step(last_step, sign).collect do |i|
494
+ value = @crossing_reference + i * @step_size
495
+
496
+ { time: from_time + (delta_time / delta_value) * (value - from_value),
497
+ @value_attribute => value + sign * @halfway_offset }
498
+ end
499
+
500
+ if @include_stops
501
+ first_crossing_time = crossings.dig(0, :time)
502
+ last_crossing_time = crossings.dig(-1, :time)
503
+
504
+ if first_crossing_time.nil? || from_time < first_crossing_time
505
+ stop_before = [ { time: from_time,
506
+ @value_attribute =>
507
+ round_to_nearest_quantize(from_value,
508
+ crossings.dig(0, @value_attribute)),
509
+ stop: true } ]
510
+ else
511
+ stop_before = []
512
+ end
513
+
514
+ if last_crossing_time.nil? || to_time > last_crossing_time
515
+ stop_after = [ { time: to_time,
516
+ @value_attribute =>
517
+ round_to_nearest_quantize(to_value,
518
+ crossings.dig(-1, @value_attribute)),
519
+ stop: true } ]
520
+ else
521
+ stop_after = []
522
+ end
523
+
524
+ stop_before + crossings + stop_after
525
+ else
526
+ crossings
527
+ end
528
+ end
529
+
530
+ private def round_to_nearest_quantize(value, nearest = nil)
531
+ v = (value - @reference) / @step_size
532
+
533
+ if nearest
534
+ a = v.floor * @step_size + @reference
535
+ b = v.ceil * @step_size + @reference
536
+
537
+ (nearest - a).abs < (nearest - b).abs ? a : b
538
+ else
539
+ v.round * @step_size + @reference
540
+ end
541
+ end
542
+ end
543
+
544
+ private_constant :PredictiveQuantizer
545
+ end
546
+ end