musa-dsl 0.21.1 → 0.22.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/musa-dsl.rb +1 -1
  3. data/lib/musa-dsl/core-ext.rb +1 -0
  4. data/lib/musa-dsl/core-ext/arrayfy.rb +9 -9
  5. data/lib/musa-dsl/core-ext/hashify.rb +42 -0
  6. data/lib/musa-dsl/core-ext/inspect-nice.rb +6 -1
  7. data/lib/musa-dsl/datasets/e.rb +22 -5
  8. data/lib/musa-dsl/datasets/gdv.rb +0 -1
  9. data/lib/musa-dsl/datasets/p.rb +28 -37
  10. data/lib/musa-dsl/datasets/pdv.rb +0 -1
  11. data/lib/musa-dsl/datasets/ps.rb +10 -78
  12. data/lib/musa-dsl/logger/logger.rb +4 -3
  13. data/lib/musa-dsl/matrix/matrix.rb +0 -57
  14. data/lib/musa-dsl/midi/midi-voices.rb +4 -0
  15. data/lib/musa-dsl/repl/repl.rb +30 -11
  16. data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +87 -0
  17. data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +439 -0
  18. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +3 -3
  19. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +210 -0
  20. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +178 -0
  21. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +150 -595
  22. data/lib/musa-dsl/sequencer/base-sequencer-public.rb +58 -5
  23. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +5 -9
  24. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +1 -5
  25. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +8 -0
  26. data/lib/musa-dsl/series/base-series.rb +43 -78
  27. data/lib/musa-dsl/series/flattener-timed-serie.rb +61 -0
  28. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +95 -0
  29. data/lib/musa-dsl/series/holder-serie.rb +1 -1
  30. data/lib/musa-dsl/series/main-serie-constructors.rb +29 -83
  31. data/lib/musa-dsl/series/main-serie-operations.rb +60 -215
  32. data/lib/musa-dsl/series/proxy-serie.rb +1 -1
  33. data/lib/musa-dsl/series/quantizer-serie.rb +546 -0
  34. data/lib/musa-dsl/series/queue-serie.rb +1 -1
  35. data/lib/musa-dsl/series/series.rb +7 -2
  36. data/lib/musa-dsl/transport/input-midi-clock.rb +19 -12
  37. data/lib/musa-dsl/transport/transport.rb +25 -12
  38. data/musa-dsl.gemspec +2 -2
  39. metadata +10 -4
  40. data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +0 -216
  41. 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