musa-dsl 0.21.3 → 0.22.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) 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/repl/repl.rb +4 -8
  15. data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +87 -0
  16. data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +439 -0
  17. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +3 -3
  18. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +210 -0
  19. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +178 -0
  20. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +150 -595
  21. data/lib/musa-dsl/sequencer/base-sequencer-public.rb +58 -5
  22. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +5 -9
  23. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +1 -5
  24. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +11 -2
  25. data/lib/musa-dsl/series/base-series.rb +43 -78
  26. data/lib/musa-dsl/series/flattener-timed-serie.rb +61 -0
  27. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +95 -0
  28. data/lib/musa-dsl/series/holder-serie.rb +1 -1
  29. data/lib/musa-dsl/series/main-serie-constructors.rb +29 -83
  30. data/lib/musa-dsl/series/main-serie-operations.rb +60 -215
  31. data/lib/musa-dsl/series/proxy-serie.rb +1 -1
  32. data/lib/musa-dsl/series/quantizer-serie.rb +558 -0
  33. data/lib/musa-dsl/series/queue-serie.rb +1 -1
  34. data/lib/musa-dsl/series/series.rb +7 -2
  35. data/lib/musa-dsl/transport/input-midi-clock.rb +19 -12
  36. data/lib/musa-dsl/transport/transport.rb +6 -6
  37. data/musa-dsl.gemspec +2 -2
  38. metadata +10 -4
  39. data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +0 -216
  40. 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,558 @@
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 Musa::Datasets::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] } }
148
+ .extend(Musa::Datasets::AbsTimed)
149
+ .extend(Musa::Datasets::AbsD)
150
+ else
151
+ i += 1
152
+ end
153
+ end
154
+
155
+ return nil
156
+
157
+ else
158
+ i = 2
159
+ # puts "\n\n"
160
+ loop do
161
+ while @segments.size < i && process2; end
162
+
163
+ first = @segments[0]
164
+ last = @segments[i - 1]
165
+
166
+ # puts "_next_value: first #{first || 'nil'} last #{last || 'nil'}"
167
+
168
+ break if first.nil?
169
+
170
+ if last.nil? || first[:value] != last[:value]
171
+
172
+ # puts "_next_value: found segments:"
173
+
174
+ durations_to_sum = @segments.shift(i-1)
175
+
176
+ # durations_to_sum.each { |i| puts i.inspect }
177
+ #
178
+ # puts "_next_value: result #{{ time: first[:time],
179
+ # @value_attribute => first[:value],
180
+ # duration: durations_to_sum.sum { |_| _[:duration] } }}"
181
+
182
+ return { time: first[:time],
183
+ @value_attribute => first[:value],
184
+ duration: durations_to_sum.sum { |_| _[:duration] } }
185
+ .extend(Musa::Datasets::AbsTimed)
186
+ .extend(Musa::Datasets::AbsD)
187
+ else
188
+ i += 1
189
+ end
190
+ end
191
+
192
+ return nil
193
+ end
194
+ end
195
+
196
+ private def process2
197
+ while (ready_count = count_ready_points) <= 2 &&
198
+ process(*get_time_value(@source.next_value), !@source.peek_next_value)
199
+ end
200
+
201
+ if ready_count >= 2
202
+ point = @points.shift
203
+
204
+ from_time = point[:time]
205
+ from_value = point[:value]
206
+
207
+ next_point = @points.first
208
+
209
+ to_time = next_point[:time]
210
+ to_value = next_point[:value]
211
+ to_point_is_last = next_point[:last]
212
+
213
+ sign = to_value <=> from_value # to_value > from_value => +1
214
+
215
+ # 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}"
216
+
217
+ if sign == 0
218
+ if @segments.last && @segments.last[:time] == from_time
219
+
220
+ @segments.last[:duration] = to_time - from_time
221
+ @segments.last[:info] += "; edited on a as start"
222
+
223
+ else
224
+ @segments << { time: from_time,
225
+ value: from_value,
226
+ duration: to_time - from_time,
227
+ info: "added on a as start" }
228
+
229
+ end
230
+
231
+ if !to_point_is_last
232
+ @segments << { time: to_time,
233
+ value: from_value,
234
+ duration: 0,
235
+ stop: true,
236
+ info: "added on a as end stop" }
237
+ end
238
+ else
239
+ time_increment = to_time - from_time
240
+
241
+ step_value_increment = @step_size * sign
242
+
243
+ extra_steps = 0
244
+
245
+ if @right_open
246
+ loop_to_value = to_value - step_value_increment
247
+ else
248
+ loop_to_value = to_value
249
+ extra_steps += 1
250
+ end
251
+
252
+ if @left_open
253
+ loop_from_value = from_value + step_value_increment
254
+ extra_steps -= 1
255
+ else
256
+ loop_from_value = from_value
257
+ end
258
+
259
+ step_time_increment = time_increment / ((to_value - from_value).abs + extra_steps)
260
+
261
+ intermediate_point_time = from_time
262
+
263
+ # 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}"
264
+
265
+ loop_from_value.step(loop_to_value, step_value_increment) do |value|
266
+ if @segments.last &&
267
+ @segments.last[:time] == intermediate_point_time &&
268
+ @segments.last[:value] == value
269
+
270
+ @segments.last[:duration] = step_time_increment
271
+ @segments.last[:info] += "; edited on b"
272
+
273
+ # puts "process2: editing #{@segments.last}"
274
+
275
+ else
276
+ @segments << v = { time: intermediate_point_time,
277
+ value: value,
278
+ duration: step_time_increment,
279
+ info: "added on b" }
280
+
281
+ # puts "process2: adding #{v.inspect}"
282
+ end
283
+
284
+ intermediate_point_time += step_time_increment
285
+ end
286
+ end
287
+
288
+ true
289
+ else
290
+ false
291
+ end
292
+ end
293
+
294
+ private def count_ready_points
295
+ @points.select { |_| _[:ready] }.size
296
+ end
297
+
298
+ private def process(time, value, last_time_value)
299
+ if time && value
300
+ raise RuntimeError, "time only can go forward" if @last_processed_time && time <= @last_processed_time
301
+
302
+ q_value = round_quantize(value)
303
+
304
+ # A continuation point time will be changed if new points of equal value arrive.
305
+ # For this reason this point is not ready to consume.
306
+ # A ready point is a well determined point that can be consumed.
307
+ # When we arrive to a well determined point all the previous points become also determined (ready).
308
+
309
+ if q_value == @last_processed_q_value && !last_time_value
310
+ if @points.size == 1 || @points.last[:ready]
311
+ # If @points.last is the first point of a segment the new point is a continuation point.
312
+ # The continuation point is used as a stop point.
313
+ @points << { time: time, value: q_value }
314
+ else
315
+ # @points.last is NOT the first point of a segment but a continuation point.
316
+ @points.last[:time] = time
317
+ end
318
+ else
319
+ @points.reverse_each do |point|
320
+ break if point[:ready]
321
+ point[:ready] = true
322
+ end
323
+
324
+ @points << { time: time, value: q_value, ready: true, last: last_time_value }
325
+ end
326
+
327
+ @last_processed_q_value = q_value
328
+ @last_processed_time = time
329
+
330
+ true
331
+ else
332
+ false
333
+ end
334
+ end
335
+
336
+ private def round_quantize(value)
337
+ round((value - @reference) / @step_size) * @step_size + @reference
338
+ end
339
+
340
+ private def round(value)
341
+ i = value.floor
342
+ value > (i + 1/2r) ? i + 1r : i.rationalize
343
+ end
344
+
345
+ def infinite?
346
+ !!@source.infinite?
347
+ end
348
+ end
349
+
350
+ private_constant :RawQuantizer
351
+
352
+ class PredictiveQuantizer
353
+ include Serie
354
+ include QuantizerTools
355
+
356
+ attr_reader :source
357
+
358
+ def initialize(reference, step, source, value_attribute, include_stops)
359
+ @reference = reference
360
+ @step_size = step
361
+
362
+ @source = source
363
+ @value_attribute = value_attribute
364
+
365
+ @include_stops = include_stops
366
+
367
+ @halfway_offset = step / 2r
368
+ @crossing_reference = reference - @halfway_offset
369
+
370
+ _restart false
371
+
372
+ mark_regarding! source
373
+ end
374
+
375
+ def _restart(restart_sources = true)
376
+ @source.restart if restart_sources
377
+
378
+ @last_time = nil
379
+ @crossings = []
380
+
381
+ @first = true
382
+ end
383
+
384
+ def infinite?
385
+ !!@source.infinite?
386
+ end
387
+
388
+ def _next_value
389
+ result = nil
390
+
391
+ first_time, first_value = get_time_value(@source.peek_next_value) if @first
392
+
393
+ while @crossings.size <= 2 && process_more; end
394
+
395
+ @crossings.delete_if { |c| c[:stop] && c[:stops].nil? }
396
+
397
+ if @crossings[0]
398
+ time = @crossings[0][:time]
399
+ value = @crossings[0][@value_attribute]
400
+
401
+ if @first
402
+ @first = false
403
+
404
+ if time > first_time
405
+ result = { time: first_time,
406
+ @value_attribute => round_to_nearest_quantize(first_value, value),
407
+ duration: time - first_time }
408
+ .extend(Musa::Datasets::AbsD)
409
+ .extend(Musa::Datasets::AbsTimed)
410
+ else
411
+ result = _next_value
412
+ end
413
+ else
414
+ if @crossings[1]
415
+ next_time = @crossings[1][:time]
416
+ result = { time: time,
417
+ @value_attribute => value,
418
+ duration: next_time - time }
419
+ .extend(Musa::Datasets::AbsD)
420
+ .extend(Musa::Datasets::AbsTimed)
421
+
422
+ @crossings.shift
423
+
424
+ else
425
+ if @last_time && @last_time > @crossings[0][:time]
426
+ result = { time: @crossings[0][:time],
427
+ @value_attribute => @crossings[0][@value_attribute],
428
+ duration: @last_time - @crossings[0][:time] }
429
+ .extend(Musa::Datasets::AbsD)
430
+ .extend(Musa::Datasets::AbsTimed)
431
+
432
+ @last_time = nil
433
+ end
434
+ end
435
+ end
436
+ else
437
+ if @first && @last_time && @last_time > first_time
438
+ result = { time: first_time,
439
+ value: round_to_nearest_quantize(first_value),
440
+ duration: @last_time - first_time }
441
+ .extend(Musa::Datasets::AbsD)
442
+ .extend(Musa::Datasets::AbsTimed)
443
+
444
+ @first = false
445
+ @last_time = false
446
+ end
447
+ end
448
+
449
+ return result
450
+ end
451
+
452
+ private def process_more
453
+ while (@crossings.size <= 2 || @crossings[-1][:stop]) && new_crossings = next_crossings
454
+ new_crossings.each do |c|
455
+ if @last_time.nil? || c[:time] > @last_time
456
+
457
+ if c[:stop] &&
458
+ @crossings.dig(-1, :stop) &&
459
+ @crossings.dig(-1, @value_attribute) == c[@value_attribute]
460
+
461
+ c[:stops] = (@crossings[-1][:stops] || 0) + 1
462
+
463
+ @crossings[-1] = c
464
+ else
465
+ @crossings << c
466
+ end
467
+ end
468
+ end
469
+ end
470
+
471
+ !!new_crossings
472
+ end
473
+
474
+ private def next_crossings
475
+ from_time, from_value = get_time_value(@source.next_value)
476
+
477
+ if from_time && from_value
478
+ raise RuntimeError, "time only can go forward" if @last_time && from_time <= @last_time
479
+
480
+ @last_time = from_time
481
+
482
+ to_time, to_value = get_time_value(@source.peek_next_value)
483
+
484
+ if to_time && to_value
485
+ crossings(from_time, from_value, to_time, to_value)
486
+ end
487
+ end
488
+ end
489
+
490
+ private def crossings(from_time, from_value, to_time, to_value)
491
+ sign = to_value >= from_value ? 1r : -1r
492
+
493
+ if sign == 1
494
+ from_step = ((from_value - @crossing_reference) / @step_size).ceil
495
+ last_step = ((to_value - @crossing_reference) / @step_size).floor
496
+ else
497
+ from_step = ((from_value - @crossing_reference) / @step_size).floor
498
+ last_step = ((to_value - @crossing_reference) / @step_size).ceil
499
+ end
500
+
501
+ delta_value = to_value - from_value
502
+ delta_time = to_time - from_time
503
+
504
+ crossings =
505
+ from_step.step(last_step, sign).collect do |i|
506
+ value = @crossing_reference + i * @step_size
507
+
508
+ { time: from_time + (delta_time / delta_value) * (value - from_value),
509
+ @value_attribute => value + sign * @halfway_offset }
510
+ end
511
+
512
+ if @include_stops
513
+ first_crossing_time = crossings.dig(0, :time)
514
+ last_crossing_time = crossings.dig(-1, :time)
515
+
516
+ if first_crossing_time.nil? || from_time < first_crossing_time
517
+ stop_before = [ { time: from_time,
518
+ @value_attribute =>
519
+ round_to_nearest_quantize(from_value,
520
+ crossings.dig(0, @value_attribute)),
521
+ stop: true } ]
522
+ else
523
+ stop_before = []
524
+ end
525
+
526
+ if last_crossing_time.nil? || to_time > last_crossing_time
527
+ stop_after = [ { time: to_time,
528
+ @value_attribute =>
529
+ round_to_nearest_quantize(to_value,
530
+ crossings.dig(-1, @value_attribute)),
531
+ stop: true } ]
532
+ else
533
+ stop_after = []
534
+ end
535
+
536
+ stop_before + crossings + stop_after
537
+ else
538
+ crossings
539
+ end
540
+ end
541
+
542
+ private def round_to_nearest_quantize(value, nearest = nil)
543
+ v = (value - @reference) / @step_size
544
+
545
+ if nearest
546
+ a = v.floor * @step_size + @reference
547
+ b = v.ceil * @step_size + @reference
548
+
549
+ (nearest - a).abs < (nearest - b).abs ? a : b
550
+ else
551
+ v.round * @step_size + @reference
552
+ end
553
+ end
554
+ end
555
+
556
+ private_constant :PredictiveQuantizer
557
+ end
558
+ end