musa-dsl 0.21.4 → 0.22.3
Sign up to get free protection for your applications and to get access to all the features.
- 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/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 +2 -2
- 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 -597
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +6 -8
- data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +1 -5
- data/lib/musa-dsl/sequencer/{base-sequencer-public.rb → base-sequencer.rb} +59 -5
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +11 -2
- data/lib/musa-dsl/sequencer/sequencer.rb +1 -1
- 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 +143 -0
- data/lib/musa-dsl/series/holder-serie.rb +1 -1
- data/lib/musa-dsl/series/main-serie-constructors.rb +32 -92
- 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 +8 -2
- data/lib/musa-dsl/series/union-timed-series.rb +109 -0
- data/musa-dsl.gemspec +2 -2
- metadata +12 -5
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +0 -216
- data/lib/musa-dsl/series/hash-serie-splitter.rb +0 -196
@@ -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
|