musa-dsl 0.21.3 → 0.22.2
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/lib/musa-dsl.rb +1 -1
- data/lib/musa-dsl/core-ext.rb +1 -0
- data/lib/musa-dsl/core-ext/arrayfy.rb +9 -9
- data/lib/musa-dsl/core-ext/hashify.rb +42 -0
- data/lib/musa-dsl/core-ext/inspect-nice.rb +6 -1
- data/lib/musa-dsl/datasets/e.rb +22 -5
- data/lib/musa-dsl/datasets/gdv.rb +0 -1
- data/lib/musa-dsl/datasets/p.rb +28 -37
- data/lib/musa-dsl/datasets/pdv.rb +0 -1
- data/lib/musa-dsl/datasets/ps.rb +10 -78
- data/lib/musa-dsl/logger/logger.rb +4 -3
- data/lib/musa-dsl/matrix/matrix.rb +0 -57
- data/lib/musa-dsl/repl/repl.rb +4 -8
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +87 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +439 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +3 -3
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +210 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +178 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +150 -595
- data/lib/musa-dsl/sequencer/base-sequencer-public.rb +58 -5
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +5 -9
- data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +1 -5
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +11 -2
- data/lib/musa-dsl/series/base-series.rb +43 -78
- data/lib/musa-dsl/series/flattener-timed-serie.rb +61 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +95 -0
- data/lib/musa-dsl/series/holder-serie.rb +1 -1
- data/lib/musa-dsl/series/main-serie-constructors.rb +29 -83
- data/lib/musa-dsl/series/main-serie-operations.rb +60 -215
- data/lib/musa-dsl/series/proxy-serie.rb +1 -1
- data/lib/musa-dsl/series/quantizer-serie.rb +558 -0
- data/lib/musa-dsl/series/queue-serie.rb +1 -1
- data/lib/musa-dsl/series/series.rb +7 -2
- data/lib/musa-dsl/transport/input-midi-clock.rb +19 -12
- data/lib/musa-dsl/transport/transport.rb +6 -6
- data/musa-dsl.gemspec +2 -2
- metadata +10 -4
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +0 -216
- data/lib/musa-dsl/series/hash-serie-splitter.rb +0 -196
data/lib/musa-dsl/repl/repl.rb
CHANGED
@@ -39,6 +39,7 @@ module Musa
|
|
39
39
|
case line
|
40
40
|
when '#begin'
|
41
41
|
buffer = StringIO.new
|
42
|
+
|
42
43
|
when '#end'
|
43
44
|
@@repl_mutex.synchronize do
|
44
45
|
@block_source = buffer.string
|
@@ -48,8 +49,8 @@ module Musa
|
|
48
49
|
binder.eval @block_source, "(repl)", 1
|
49
50
|
|
50
51
|
rescue StandardError, ScriptError => e
|
51
|
-
@logger.
|
52
|
-
@logger.
|
52
|
+
@logger.warn('REPL') { 'code execution error' }
|
53
|
+
@logger.warn('REPL') { e.full_message(highlight: true, order: :top) }
|
53
54
|
|
54
55
|
send_exception e, output: @connection
|
55
56
|
else
|
@@ -65,12 +66,6 @@ module Musa
|
|
65
66
|
@logger.warn('REPL') { 'lost connection' }
|
66
67
|
@logger.warn('REPL') { e.full_message(highlight: true, order: :top) }
|
67
68
|
|
68
|
-
rescue Exception, SystemExit, Interrupt
|
69
|
-
puts "EXCEPTION / SYSTEM EXIT / INTERUPT"
|
70
|
-
$stdout.flush
|
71
|
-
@logger.warn('REPL') { 'received interruption; will close connection...' }
|
72
|
-
raise
|
73
|
-
|
74
69
|
ensure
|
75
70
|
@logger.debug("REPL") { "closing connection (running #{@run})" }
|
76
71
|
@connection.close
|
@@ -92,6 +87,7 @@ module Musa
|
|
92
87
|
|
93
88
|
@main_thread.terminate
|
94
89
|
Thread.pass
|
90
|
+
|
95
91
|
@main_thread = nil
|
96
92
|
|
97
93
|
@client_threads.each { |t| t.terminate; Thread.pass }
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Musa; module Sequencer
|
2
|
+
class BaseSequencer
|
3
|
+
private def _every(interval, control, block_procedure_binder: nil, &block)
|
4
|
+
block ||= proc {}
|
5
|
+
|
6
|
+
block_procedure_binder ||= SmartProcBinder.new block, on_rescue: proc { |e| _rescue_error(e) }
|
7
|
+
|
8
|
+
_numeric_at position, control do
|
9
|
+
control._start_position ||= position
|
10
|
+
control._execution_counter ||= 0
|
11
|
+
|
12
|
+
duration_exceeded =
|
13
|
+
(control._start_position + control.duration_value - interval) <= position if interval && control.duration_value
|
14
|
+
|
15
|
+
till_exceeded = control.till_value - interval <= position if interval && control.till_value
|
16
|
+
|
17
|
+
condition_failed = !control.condition_block.call if control.condition_block
|
18
|
+
|
19
|
+
unless control.stopped? || condition_failed || till_exceeded
|
20
|
+
block_procedure_binder.call(control: control)
|
21
|
+
control._execution_counter += 1
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
unless control.stopped? || duration_exceeded || till_exceeded || condition_failed || interval.nil?
|
26
|
+
_numeric_at control._start_position + control._execution_counter * interval, control do
|
27
|
+
_every interval, control, block_procedure_binder: block_procedure_binder
|
28
|
+
end
|
29
|
+
|
30
|
+
else
|
31
|
+
control.do_on_stop.each(&:call)
|
32
|
+
|
33
|
+
control.do_after.each do |do_after|
|
34
|
+
_numeric_at position + (interval || 0) + do_after[:bars], control, &do_after[:block]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
class EveryControl < EventHandler
|
43
|
+
attr_reader :duration_value, :till_value, :condition_block, :do_on_stop, :do_after
|
44
|
+
|
45
|
+
attr_accessor :_start_position
|
46
|
+
attr_accessor :_execution_counter
|
47
|
+
|
48
|
+
def initialize(parent, duration: nil, till: nil, condition: nil, on_stop: nil, after_bars: nil, after: nil)
|
49
|
+
super parent
|
50
|
+
|
51
|
+
@duration_value = duration
|
52
|
+
@till_value = till
|
53
|
+
@condition_block = condition
|
54
|
+
|
55
|
+
@do_on_stop = []
|
56
|
+
@do_after = []
|
57
|
+
|
58
|
+
@do_on_stop << on_stop if on_stop
|
59
|
+
|
60
|
+
self.after after_bars, &after if after
|
61
|
+
end
|
62
|
+
|
63
|
+
def duration(value)
|
64
|
+
@duration_value = value.rationalize
|
65
|
+
end
|
66
|
+
|
67
|
+
def till(value)
|
68
|
+
@till_value = value.rationalize
|
69
|
+
end
|
70
|
+
|
71
|
+
def condition(&block)
|
72
|
+
@condition_block = block
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_stop(&block)
|
76
|
+
@do_on_stop << block
|
77
|
+
end
|
78
|
+
|
79
|
+
def after(bars = nil, &block)
|
80
|
+
bars ||= 0
|
81
|
+
@do_after << { bars: bars.rationalize, block: block }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private_constant :EveryControl
|
86
|
+
end
|
87
|
+
end; end
|
@@ -0,0 +1,439 @@
|
|
1
|
+
using Musa::Extension::Hashify
|
2
|
+
using Musa::Extension::Arrayfy
|
3
|
+
|
4
|
+
using Musa::Extension::InspectNice
|
5
|
+
|
6
|
+
module Musa; module Sequencer
|
7
|
+
class BaseSequencer
|
8
|
+
private def _move(every: nil,
|
9
|
+
from:, to: nil,
|
10
|
+
step: nil,
|
11
|
+
duration: nil, till: nil,
|
12
|
+
function: nil,
|
13
|
+
right_open: nil,
|
14
|
+
on_stop: nil,
|
15
|
+
after_bars: nil, after: nil,
|
16
|
+
&block)
|
17
|
+
|
18
|
+
#
|
19
|
+
# Main calling parameters error check
|
20
|
+
#
|
21
|
+
raise ArgumentError,
|
22
|
+
"Cannot use duration: #{duration} and till: #{till} parameters at the same time. " \
|
23
|
+
"Use only one of them." if till && duration
|
24
|
+
|
25
|
+
raise ArgumentError,
|
26
|
+
"Invalid use: 'function:' parameter is incompatible with 'step:' parameter" if function && step
|
27
|
+
raise ArgumentError,
|
28
|
+
"Invalid use: 'function:' parameter needs 'to:' parameter to be not nil" if function && !to
|
29
|
+
|
30
|
+
#
|
31
|
+
# Homogenize mode parameters
|
32
|
+
#
|
33
|
+
array_mode = from.is_a?(Array)
|
34
|
+
hash_mode = from.is_a?(Hash)
|
35
|
+
|
36
|
+
if array_mode
|
37
|
+
from = from.arrayfy
|
38
|
+
size = from.size
|
39
|
+
|
40
|
+
elsif hash_mode
|
41
|
+
hash_keys = from.keys
|
42
|
+
from = from.values
|
43
|
+
size = from.size
|
44
|
+
|
45
|
+
if every.is_a?(Hash)
|
46
|
+
every = hash_keys.collect { |k| every[k] }
|
47
|
+
raise ArgumentError,
|
48
|
+
"Invalid use: 'every:' parameter should contain the same keys as 'from:' Hash" \
|
49
|
+
unless every.all? { |_| _ }
|
50
|
+
end
|
51
|
+
|
52
|
+
if to.is_a?(Hash)
|
53
|
+
to = hash_keys.collect { |k| to[k] }
|
54
|
+
raise ArgumentError,
|
55
|
+
"Invalid use: 'to:' parameter should contain the same keys as 'from:' Hash" unless to.all? { |_| _ }
|
56
|
+
end
|
57
|
+
|
58
|
+
if step.is_a?(Hash)
|
59
|
+
step = hash_keys.collect { |k| step[k] }
|
60
|
+
end
|
61
|
+
|
62
|
+
if right_open.is_a?(Hash)
|
63
|
+
right_open = hash_keys.collect { |k| right_open[k] }
|
64
|
+
end
|
65
|
+
|
66
|
+
else
|
67
|
+
from = from.arrayfy
|
68
|
+
size = from.size
|
69
|
+
end
|
70
|
+
|
71
|
+
every = every.arrayfy(size: size)
|
72
|
+
to = to.arrayfy(size: size)
|
73
|
+
step = step.arrayfy(size: size)
|
74
|
+
right_open = right_open.arrayfy(size: size)
|
75
|
+
|
76
|
+
# from, to, step, every
|
77
|
+
# from, to, step, (duration | till)
|
78
|
+
# from, to, every, (duration | till)
|
79
|
+
# from, step, every, (duration | till)
|
80
|
+
|
81
|
+
block ||= proc {}
|
82
|
+
|
83
|
+
step.map!.with_index do |s, i|
|
84
|
+
(s && to[i] && ((s > 0 && to[i] < from[i]) || (s < 0 && from[i] < to[i]))) ? -s : s
|
85
|
+
end
|
86
|
+
|
87
|
+
right_open.map! { |v| v || false }
|
88
|
+
|
89
|
+
function ||= proc { |ratio| ratio }
|
90
|
+
function = function.arrayfy(size: size)
|
91
|
+
|
92
|
+
function_range = 1r.arrayfy(size: size)
|
93
|
+
function_offset = 0r.arrayfy(size: size)
|
94
|
+
|
95
|
+
#
|
96
|
+
# Prepare intervals, steps & transformation functions
|
97
|
+
#
|
98
|
+
start_position = position
|
99
|
+
|
100
|
+
if duration || till
|
101
|
+
effective_duration = duration || till - start_position
|
102
|
+
|
103
|
+
# Add 1 tick to arrive to final value in duration time (no need to add an extra tick)
|
104
|
+
right_open_offset = right_open.collect { |ro| ro ? 0 : 1 }
|
105
|
+
|
106
|
+
size.times do |i|
|
107
|
+
if to[i] && step[i] && !every[i]
|
108
|
+
|
109
|
+
steps = (to[i] - from[i]) / step[i]
|
110
|
+
|
111
|
+
# When to == from don't need to do any iteration with every
|
112
|
+
if steps + right_open_offset[i] > 0
|
113
|
+
every[i] = Rational(effective_duration, steps + right_open_offset[i])
|
114
|
+
else
|
115
|
+
every[i] = nil
|
116
|
+
end
|
117
|
+
|
118
|
+
elsif to[i] && !step[i] && !every[i]
|
119
|
+
|
120
|
+
if tick_duration > 0
|
121
|
+
function_range[i] = to[i] - from[i]
|
122
|
+
function_offset[i] = from[i]
|
123
|
+
|
124
|
+
from[i] = 0r
|
125
|
+
to[i] = 1r
|
126
|
+
|
127
|
+
step[i] = 1r / (effective_duration * ticks_per_bar - right_open_offset[i])
|
128
|
+
every[i] = tick_duration
|
129
|
+
else
|
130
|
+
raise ArgumentError, "Cannot use sequencer tickless mode without 'step' or 'every' parameter values"
|
131
|
+
end
|
132
|
+
|
133
|
+
elsif to[i] && !step[i] && every[i]
|
134
|
+
function_range[i] = to[i] - from[i]
|
135
|
+
function_offset[i] = from[i]
|
136
|
+
|
137
|
+
from[i] = 0r
|
138
|
+
to[i] = 1r
|
139
|
+
|
140
|
+
steps = effective_duration / every[i]
|
141
|
+
step[i] = 1r / (steps - right_open_offset[i])
|
142
|
+
|
143
|
+
elsif !to[i] && step[i] && every[i]
|
144
|
+
# ok
|
145
|
+
elsif !to[i] && !step[i] && every[i]
|
146
|
+
step[i] = 1r
|
147
|
+
|
148
|
+
else
|
149
|
+
raise ArgumentError, 'Cannot use this parameters combination (with \'duration\' or \'till\')'
|
150
|
+
end
|
151
|
+
end
|
152
|
+
else
|
153
|
+
size.times do |i|
|
154
|
+
if to[i] && step[i] && every[i]
|
155
|
+
# ok
|
156
|
+
elsif to[i] && !step[i] && every[i]
|
157
|
+
size.times do |i|
|
158
|
+
step[i] = (to[i] <=> from[i]).to_r
|
159
|
+
end
|
160
|
+
else
|
161
|
+
raise ArgumentError, 'Cannot use this parameters combination'
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
#
|
167
|
+
# Prepare yield block, parameters to yield block and coincident moving interval groups
|
168
|
+
#
|
169
|
+
binder = SmartProcBinder.new(block)
|
170
|
+
|
171
|
+
every_groups = {}
|
172
|
+
group_counter = {}
|
173
|
+
|
174
|
+
positions = Array.new(size)
|
175
|
+
q_durations = Array.new(size)
|
176
|
+
position_jitters = Array.new(size)
|
177
|
+
duration_jitters = Array.new(size)
|
178
|
+
|
179
|
+
size.times.each do |i|
|
180
|
+
every_groups[every[i]] ||= []
|
181
|
+
every_groups[every[i]] << i
|
182
|
+
group_counter[every[i]] = 0
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# Initialize external control object
|
187
|
+
#
|
188
|
+
control = MoveControl.new(@event_handlers.last,
|
189
|
+
duration: duration, till: till,
|
190
|
+
on_stop: on_stop, after_bars: after_bars, after: after)
|
191
|
+
|
192
|
+
control.on_stop do
|
193
|
+
control.do_after.each do |do_after|
|
194
|
+
_numeric_at position + do_after[:bars], control, &do_after[:block]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
@event_handlers.push control
|
199
|
+
|
200
|
+
#
|
201
|
+
# Let's go with the loop!
|
202
|
+
#
|
203
|
+
_numeric_at start_position, control do
|
204
|
+
next_values = from.dup
|
205
|
+
|
206
|
+
values = Array.new(size)
|
207
|
+
stop = Array.new(size, false)
|
208
|
+
last_position = Array.new(size)
|
209
|
+
|
210
|
+
#
|
211
|
+
# Ok, the loop is here...
|
212
|
+
#
|
213
|
+
_every _common_interval(every_groups.keys), control.every_control do
|
214
|
+
process_indexes = []
|
215
|
+
|
216
|
+
every_groups.each_pair do |group_interval, affected_indexes|
|
217
|
+
group_position = start_position + ((group_interval || 0) * group_counter[group_interval])
|
218
|
+
|
219
|
+
# We consider a position to be on current tick position when it is inside the interval of one tick
|
220
|
+
# centered on the current tick (current tick +- 1/2 tick duration).
|
221
|
+
# This allow to round the irregularly timed positions due to every intervals not integer
|
222
|
+
# multiples of the tick_duration.
|
223
|
+
#
|
224
|
+
if tick_duration == 0 && group_position == position ||
|
225
|
+
group_position >= position - tick_duration && group_position < position + tick_duration
|
226
|
+
|
227
|
+
process_indexes << affected_indexes
|
228
|
+
|
229
|
+
group_counter[group_interval] += 1
|
230
|
+
|
231
|
+
next_group_position = start_position +
|
232
|
+
if group_interval
|
233
|
+
(group_interval * group_counter[group_interval])
|
234
|
+
else
|
235
|
+
effective_duration
|
236
|
+
end
|
237
|
+
|
238
|
+
next_group_q_position = _quantize_position(next_group_position)
|
239
|
+
|
240
|
+
affected_indexes.each do |i|
|
241
|
+
positions[i] = group_position
|
242
|
+
q_durations[i] = next_group_q_position - position
|
243
|
+
|
244
|
+
position_jitters[i] = group_position - position
|
245
|
+
duration_jitters[i] = next_group_position - next_group_q_position
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
process_indexes.flatten!
|
251
|
+
|
252
|
+
#
|
253
|
+
# Calculate values and next_values for yield block
|
254
|
+
#
|
255
|
+
if process_indexes.any?
|
256
|
+
process_indexes.each do |i|
|
257
|
+
unless stop[i]
|
258
|
+
values[i] = next_values[i]
|
259
|
+
next_values[i] += step[i]
|
260
|
+
|
261
|
+
if to[i]
|
262
|
+
stop[i] = if right_open[i]
|
263
|
+
step[i].positive? ? next_values[i] >= to[i] : next_values[i] <= to[i]
|
264
|
+
else
|
265
|
+
step[i].positive? ? next_values[i] > to[i] : next_values[i] < to[i]
|
266
|
+
end
|
267
|
+
|
268
|
+
if stop[i]
|
269
|
+
if right_open[i]
|
270
|
+
next_values[i] = nil if values[i] == to[i]
|
271
|
+
else
|
272
|
+
next_values[i] = nil
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
#
|
280
|
+
# Do we need stop?
|
281
|
+
#
|
282
|
+
control.stop if stop.all?
|
283
|
+
|
284
|
+
#
|
285
|
+
# Calculate effective values and next_values applying the parameter function
|
286
|
+
#
|
287
|
+
effective_values = from.clone(freeze: false).map!.with_index do |_, i|
|
288
|
+
function[i].call(values[i]) * function_range[i] + function_offset[i] unless values[i].nil?
|
289
|
+
end
|
290
|
+
|
291
|
+
effective_next_values = from.clone(freeze: false).map!.with_index do |_, i|
|
292
|
+
function[i].call(next_values[i]) * function_range[i] +
|
293
|
+
function_offset[i] unless next_values[i].nil?
|
294
|
+
end
|
295
|
+
|
296
|
+
# TODO add to 'values' and 'next_values' elements the modules of the original from and/or to objects (i.e. GDV).
|
297
|
+
|
298
|
+
#
|
299
|
+
# Adapt values to array/hash/value mode
|
300
|
+
#
|
301
|
+
value_parameters, key_parameters =
|
302
|
+
if array_mode
|
303
|
+
binder.apply(effective_values, effective_next_values,
|
304
|
+
control: control,
|
305
|
+
duration: _durations(every_groups, effective_duration),
|
306
|
+
quantized_duration: q_durations.dup,
|
307
|
+
started_ago: _started_ago(last_position, position, process_indexes),
|
308
|
+
position_jitter: position_jitters.dup,
|
309
|
+
duration_jitter: duration_jitters.dup,
|
310
|
+
right_open: right_open.dup)
|
311
|
+
elsif hash_mode
|
312
|
+
binder.apply(_hash_from_keys_and_values(hash_keys, effective_values),
|
313
|
+
_hash_from_keys_and_values(hash_keys, effective_next_values),
|
314
|
+
control: control,
|
315
|
+
duration: _hash_from_keys_and_values(
|
316
|
+
hash_keys,
|
317
|
+
_durations(every_groups, effective_duration)),
|
318
|
+
quantized_duration: _hash_from_keys_and_values(
|
319
|
+
hash_keys,
|
320
|
+
q_durations),
|
321
|
+
started_ago: _hash_from_keys_and_values(
|
322
|
+
hash_keys,
|
323
|
+
_started_ago(last_position, position, process_indexes)),
|
324
|
+
position_jitter: _hash_from_keys_and_values(
|
325
|
+
hash_keys,
|
326
|
+
position_jitters),
|
327
|
+
duration_jitter: _hash_from_keys_and_values(
|
328
|
+
hash_keys,
|
329
|
+
duration_jitters),
|
330
|
+
right_open: _hash_from_keys_and_values(hash_keys, right_open))
|
331
|
+
else
|
332
|
+
binder.apply(effective_values.first,
|
333
|
+
effective_next_values.first,
|
334
|
+
control: control,
|
335
|
+
duration: _durations(every_groups, effective_duration).first,
|
336
|
+
quantized_duration: q_durations.first,
|
337
|
+
position_jitter: position_jitters.first,
|
338
|
+
duration_jitter: duration_jitters.first,
|
339
|
+
started_ago: nil,
|
340
|
+
right_open: right_open.first)
|
341
|
+
end
|
342
|
+
|
343
|
+
#
|
344
|
+
# Do the REAL thing
|
345
|
+
#
|
346
|
+
yield *value_parameters, **key_parameters
|
347
|
+
|
348
|
+
process_indexes.each { |i| last_position[i] = position }
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
@event_handlers.pop
|
354
|
+
|
355
|
+
control
|
356
|
+
end
|
357
|
+
|
358
|
+
private def _started_ago(last_positions, position, affected_indexes)
|
359
|
+
Array.new(last_positions.size).tap do |a|
|
360
|
+
last_positions.each_index do |i|
|
361
|
+
if last_positions[i] && !affected_indexes.include?(i)
|
362
|
+
a[i] = position - last_positions[i]
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
private def _durations(every_groups, largest_duration)
|
369
|
+
[].tap do |a|
|
370
|
+
if every_groups.any?
|
371
|
+
every_groups.each_pair do |every_group, affected_indexes|
|
372
|
+
affected_indexes.each do |i|
|
373
|
+
a[i] = every_group || largest_duration
|
374
|
+
end
|
375
|
+
end
|
376
|
+
else
|
377
|
+
a << largest_duration
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
private def _hash_from_keys_and_values(keys, values)
|
383
|
+
{}.tap { |h| keys.each_index { |i| h[keys[i]] = values[i] } }
|
384
|
+
end
|
385
|
+
|
386
|
+
private def _common_interval(intervals)
|
387
|
+
intervals = intervals.compact
|
388
|
+
return nil if intervals.empty?
|
389
|
+
|
390
|
+
lcm_denominators = intervals.collect(&:denominator).reduce(1, :lcm)
|
391
|
+
numerators = intervals.collect { |i| i.numerator * lcm_denominators / i.denominator }
|
392
|
+
gcd_numerators = numerators.reduce(numerators.first, :gcd)
|
393
|
+
|
394
|
+
#intervals.reduce(1r, :*)
|
395
|
+
|
396
|
+
Rational(gcd_numerators, lcm_denominators)
|
397
|
+
end
|
398
|
+
|
399
|
+
class MoveControl < EventHandler
|
400
|
+
attr_reader :every_control, :do_on_stop, :do_after
|
401
|
+
|
402
|
+
def initialize(parent, duration: nil, till: nil, on_stop: nil, after_bars: nil, after: nil)
|
403
|
+
super parent
|
404
|
+
|
405
|
+
@every_control = EveryControl.new(self, duration: duration, till: till)
|
406
|
+
|
407
|
+
@do_on_stop = []
|
408
|
+
@do_after = []
|
409
|
+
|
410
|
+
@do_on_stop << on_stop if on_stop
|
411
|
+
self.after after_bars, &after if after
|
412
|
+
|
413
|
+
@every_control.on_stop do
|
414
|
+
@stop = true
|
415
|
+
@do_on_stop.each(&:call)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def on_stop(&block)
|
420
|
+
@do_on_stop << block
|
421
|
+
end
|
422
|
+
|
423
|
+
def after(bars = nil, &block)
|
424
|
+
bars ||= 0
|
425
|
+
@do_after << { bars: bars.rationalize, block: block }
|
426
|
+
end
|
427
|
+
|
428
|
+
def stop
|
429
|
+
@every_control.stop
|
430
|
+
end
|
431
|
+
|
432
|
+
def stopped?
|
433
|
+
@stop
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
private_constant :MoveControl
|
438
|
+
end
|
439
|
+
end; end
|