musa-dsl 0.42.2 → 0.42.3
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/docs/subsystems/sequencer.md +45 -1
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +8 -6
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +42 -14
- data/lib/musa-dsl/sequencer/base-sequencer.rb +29 -7
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +6 -6
- data/lib/musa-dsl/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5b00aaa2474967b2283adc187b2bd8696b96a83ba98b5868c8479f69e1efa195
|
|
4
|
+
data.tar.gz: bf3e363b1160ae7787a9a64d1bd96cd8c2735d80a39449268f01bff1af57d8e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 19349d6b67aac7e4e00f95d1da0c7b59b1f3d8c4bbb7ea6dd9d92d335f416b4ac0ceebc4647389d8fa901a763e3af2e6443281f5dfa7268f155be1503b2fec2a
|
|
7
|
+
data.tar.gz: e90b0fac158af0990e6fccffbb683001b5f7d641775a5678fe879d16ba905002c5817e56b089977e28632090a54f69be95ddf578f780db3c93d2caf05931dd40
|
|
@@ -102,7 +102,11 @@ every 1/4r do ... end
|
|
|
102
102
|
at 0.5 do ... end
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
## Control
|
|
105
|
+
## Control Objects and `.stop`
|
|
106
|
+
|
|
107
|
+
All scheduling methods (`at`, `wait`, `now`, `play`, `play_timed`, `every`, `move`) return a control object that supports `.stop` to cancel execution. Calling `.stop` on the control prevents the associated block from running at its scheduled position, or stops further iterations for series/recurring operations.
|
|
108
|
+
|
|
109
|
+
### `on_stop` vs `after` callbacks
|
|
106
110
|
|
|
107
111
|
The control objects returned by `every`, `play`, `play_timed`, and `move` support two types of callbacks with different semantics:
|
|
108
112
|
|
|
@@ -153,6 +157,46 @@ ctrl.on_stop { puts "Fade ended" } # Any reason
|
|
|
153
157
|
ctrl.after { launch :next_section } # Only if fade completes
|
|
154
158
|
```
|
|
155
159
|
|
|
160
|
+
### Stopping `at`, `wait`, `now` and `play_timed`
|
|
161
|
+
|
|
162
|
+
All scheduling methods return a control object that supports `.stop`:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
# Stop a scheduled at/wait/now before it executes
|
|
166
|
+
h = at 5 do
|
|
167
|
+
puts "This won't execute if stopped before bar 5"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
at 3 do
|
|
171
|
+
h.stop # Cancels the block scheduled at bar 5
|
|
172
|
+
end
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
# Stop a series-based at/wait
|
|
177
|
+
h = at [1, 2, 3, 4, 5] do
|
|
178
|
+
puts "Repeating at positions from series"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
at 3.5 do
|
|
182
|
+
h.stop # No more executions from the series after this point
|
|
183
|
+
end
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
# Stop play_timed — same on_stop/after semantics as play/every/move
|
|
188
|
+
ctrl = play_timed(timed_serie) do |values, time:, started_ago:, control:|
|
|
189
|
+
# process values
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
ctrl.on_stop { puts "Stopped (any reason)" }
|
|
193
|
+
ctrl.after { launch :next_section } # Only on natural end
|
|
194
|
+
|
|
195
|
+
at 10 do
|
|
196
|
+
ctrl.stop # on_stop fires, after does NOT
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
156
200
|
### Parameter form
|
|
157
201
|
|
|
158
202
|
`on_stop` and `after` can also be passed as parameters:
|
|
@@ -79,7 +79,7 @@ module Musa::Sequencer
|
|
|
79
79
|
last_positions,
|
|
80
80
|
binder, control)
|
|
81
81
|
|
|
82
|
-
source_next_value = timed_serie.next_value
|
|
82
|
+
source_next_value = control.stopped? ? nil : timed_serie.next_value
|
|
83
83
|
|
|
84
84
|
if source_next_value
|
|
85
85
|
affected_components = component_ids.select { |_| !source_next_value[:value][_].nil? }
|
|
@@ -109,11 +109,13 @@ module Musa::Sequencer
|
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
_numeric_at _quantize_position(start_position + time, warn: true), control do
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
unless control.stopped?
|
|
113
|
+
binder.call(values,
|
|
114
|
+
**extra_attributes,
|
|
115
|
+
time: start_position + time,
|
|
116
|
+
started_ago: started_ago,
|
|
117
|
+
control: control)
|
|
118
|
+
end
|
|
117
119
|
|
|
118
120
|
_play_timed_step(hash_mode,
|
|
119
121
|
component_ids, extra_attribute_names,
|
|
@@ -19,12 +19,21 @@ module Musa::Sequencer
|
|
|
19
19
|
#
|
|
20
20
|
# - Shifts command from queue
|
|
21
21
|
# - Deletes timeslot if queue becomes empty
|
|
22
|
-
# - Pushes parent_control to event handler stack if present
|
|
23
|
-
# -
|
|
22
|
+
# - Pushes parent_control to event handler stack if present and not stopped
|
|
23
|
+
# - If command has skip_if_stopped and control is stopped, skips block execution
|
|
24
|
+
# - Otherwise executes command block with parameters (mutex-protected)
|
|
24
25
|
# - Pops parent_control from stack
|
|
25
26
|
#
|
|
26
27
|
# 4. Yields to other threads
|
|
27
28
|
#
|
|
29
|
+
# ## skip_if_stopped
|
|
30
|
+
#
|
|
31
|
+
# Commands scheduled with `skip_if_stopped: true` (via `_numeric_at`) are
|
|
32
|
+
# silently skipped when their control is stopped. Used by `at`, `wait`, `now`
|
|
33
|
+
# and `_serie_at` which have no cleanup logic. Not used by `play`, `every`,
|
|
34
|
+
# `move` or `play_timed` whose blocks mix user callbacks with cleanup/recursion
|
|
35
|
+
# that must execute even when stopped.
|
|
36
|
+
#
|
|
28
37
|
# @param position_to_run [Rational] position to execute events at
|
|
29
38
|
#
|
|
30
39
|
# @return [void]
|
|
@@ -43,8 +52,10 @@ module Musa::Sequencer
|
|
|
43
52
|
|
|
44
53
|
@event_handlers.push(command[:parent_control]) if push_parent
|
|
45
54
|
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
unless command[:skip_if_stopped] && command[:parent_control]&.stopped?
|
|
56
|
+
@tick_mutex.synchronize do
|
|
57
|
+
command[:block]&.call *command[:value_parameters], **command[:key_parameters]
|
|
58
|
+
end
|
|
48
59
|
end
|
|
49
60
|
|
|
50
61
|
@event_handlers.pop if push_parent
|
|
@@ -65,7 +76,7 @@ module Musa::Sequencer
|
|
|
65
76
|
# @param force_first [Boolean] if true, insert at front of queue
|
|
66
77
|
# @yield block to execute at position
|
|
67
78
|
#
|
|
68
|
-
# @return [
|
|
79
|
+
# @return [void]
|
|
69
80
|
#
|
|
70
81
|
# @example Force execution order
|
|
71
82
|
# _raw_numeric_at(1r) { puts "second" }
|
|
@@ -115,9 +126,23 @@ module Musa::Sequencer
|
|
|
115
126
|
# - **Past position**: Warns and ignores
|
|
116
127
|
# - **nil position** (tickless before first event): Allows scheduling
|
|
117
128
|
#
|
|
129
|
+
# ## skip_if_stopped
|
|
130
|
+
#
|
|
131
|
+
# When `skip_if_stopped: true`, the block is silently skipped if `control`
|
|
132
|
+
# is stopped at execution time. This applies both for immediate execution
|
|
133
|
+
# (current position) and for future execution (checked in `_tick`).
|
|
134
|
+
# Used by `at`, `wait`, `now` and `_serie_at` for clean cancellation
|
|
135
|
+
# without wrapper procs or extra SmartProcBinder overhead.
|
|
136
|
+
#
|
|
137
|
+
# Not suitable for `play`/`every`/`move`/`play_timed` whose scheduled
|
|
138
|
+
# blocks contain cleanup logic (do_on_stop, do_after) that must run
|
|
139
|
+
# even when stopped.
|
|
140
|
+
#
|
|
118
141
|
# @param at_position [Rational] position to schedule at
|
|
119
142
|
# @param control [EventHandler] parent control for hierarchy
|
|
120
143
|
# @param debug [Boolean, nil] enable debug callbacks
|
|
144
|
+
# @param skip_if_stopped [Boolean, nil] when true, skip block execution
|
|
145
|
+
# if control is stopped. Used by at/wait/now/_serie_at.
|
|
121
146
|
# @yield block to execute at position (may accept control:)
|
|
122
147
|
#
|
|
123
148
|
# @return [nil]
|
|
@@ -125,7 +150,7 @@ module Musa::Sequencer
|
|
|
125
150
|
# @raise [ArgumentError] if at_position is nil or block not given
|
|
126
151
|
#
|
|
127
152
|
# @api private
|
|
128
|
-
private def _numeric_at(at_position, control, debug: nil, &block)
|
|
153
|
+
private def _numeric_at(at_position, control, debug: nil, skip_if_stopped: nil, &block)
|
|
129
154
|
raise ArgumentError, "'at_position' parameter cannot be nil" if at_position.nil?
|
|
130
155
|
raise ArgumentError, 'Yield block is mandatory' unless block
|
|
131
156
|
|
|
@@ -140,11 +165,13 @@ module Musa::Sequencer
|
|
|
140
165
|
if at_position == @position
|
|
141
166
|
@on_debug_at.each(&:call) if @logger.sev_threshold >= ::Logger::Severity::DEBUG
|
|
142
167
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
168
|
+
unless skip_if_stopped && control.stopped?
|
|
169
|
+
begin
|
|
170
|
+
locked = @tick_mutex.try_lock
|
|
171
|
+
block_key_parameters_binder._call(nil, key_parameters)
|
|
172
|
+
ensure
|
|
173
|
+
@tick_mutex.unlock if locked
|
|
174
|
+
end
|
|
148
175
|
end
|
|
149
176
|
|
|
150
177
|
elsif @position.nil? || at_position > @position
|
|
@@ -159,7 +186,8 @@ module Musa::Sequencer
|
|
|
159
186
|
|
|
160
187
|
@timeslots[at_position] << { parent_control: control,
|
|
161
188
|
block: block_key_parameters_binder,
|
|
162
|
-
key_parameters: key_parameters
|
|
189
|
+
key_parameters: key_parameters,
|
|
190
|
+
skip_if_stopped: skip_if_stopped }
|
|
163
191
|
else
|
|
164
192
|
@logger.warn('BaseSequencer') { "._numeric_at: ignoring past 'at' command for #{at_position}" }
|
|
165
193
|
end
|
|
@@ -201,9 +229,9 @@ module Musa::Sequencer
|
|
|
201
229
|
bar_position = position_or_serie.next_value
|
|
202
230
|
|
|
203
231
|
if bar_position
|
|
204
|
-
_numeric_at bar_position, control, debug: debug, &block
|
|
232
|
+
_numeric_at bar_position, control, debug: debug, skip_if_stopped: true, &block
|
|
205
233
|
|
|
206
|
-
_numeric_at bar_position, control, debug: false do
|
|
234
|
+
_numeric_at bar_position, control, debug: false, skip_if_stopped: true do
|
|
207
235
|
_serie_at position_or_serie, control, debug: debug, &block
|
|
208
236
|
end
|
|
209
237
|
else
|
|
@@ -522,13 +522,22 @@ module Musa
|
|
|
522
522
|
|
|
523
523
|
# Schedules block relative to current position.
|
|
524
524
|
#
|
|
525
|
+
# Returns a control object whose `.stop` method cancels execution:
|
|
526
|
+
# the block will not run if the control is stopped before its scheduled
|
|
527
|
+
# position. For series-based delays, `.stop` also prevents further
|
|
528
|
+
# elements from being scheduled.
|
|
529
|
+
#
|
|
525
530
|
# @param bars_delay [Numeric, Series, Array] delay from current position
|
|
526
531
|
# @param debug [Boolean] enable debug logging
|
|
527
532
|
# @yield block to execute at position + delay
|
|
528
|
-
# @return [EventHandler] control object
|
|
533
|
+
# @return [EventHandler] control object (supports .stop to cancel)
|
|
529
534
|
#
|
|
530
|
-
# @example
|
|
535
|
+
# @example Basic wait
|
|
531
536
|
# seq.wait(2) { puts "2 beats later" }
|
|
537
|
+
#
|
|
538
|
+
# @example Cancelling a scheduled wait
|
|
539
|
+
# h = seq.wait(4) { puts "won't run" }
|
|
540
|
+
# h.stop
|
|
532
541
|
def wait(bars_delay, debug: nil, &block)
|
|
533
542
|
debug ||= false
|
|
534
543
|
|
|
@@ -536,7 +545,7 @@ module Musa
|
|
|
536
545
|
@event_handlers.push control
|
|
537
546
|
|
|
538
547
|
if bars_delay.is_a? Numeric
|
|
539
|
-
_numeric_at position + bars_delay.rationalize, control, debug: debug, &block
|
|
548
|
+
_numeric_at position + bars_delay.rationalize, control, debug: debug, skip_if_stopped: true, &block
|
|
540
549
|
else
|
|
541
550
|
bars_delay = Series::S(*bars_delay) if bars_delay.is_a?(Array)
|
|
542
551
|
bars_delay = bars_delay.instance if bars_delay
|
|
@@ -551,8 +560,12 @@ module Musa
|
|
|
551
560
|
|
|
552
561
|
# Schedules block at current position (immediate execution on next tick).
|
|
553
562
|
#
|
|
563
|
+
# Returns a control object whose `.stop` method cancels execution
|
|
564
|
+
# if the block hasn't run yet (e.g., scheduled at current position
|
|
565
|
+
# but not yet reached by the tick loop).
|
|
566
|
+
#
|
|
554
567
|
# @yield block to execute at current position
|
|
555
|
-
# @return [EventHandler] control object
|
|
568
|
+
# @return [EventHandler] control object (supports .stop to cancel)
|
|
556
569
|
#
|
|
557
570
|
# @example
|
|
558
571
|
# seq.now { puts "Executes now" }
|
|
@@ -560,7 +573,7 @@ module Musa
|
|
|
560
573
|
control = EventHandler.new @event_handlers.last
|
|
561
574
|
@event_handlers.push control
|
|
562
575
|
|
|
563
|
-
_numeric_at position, control, &block
|
|
576
|
+
_numeric_at position, control, skip_if_stopped: true, &block
|
|
564
577
|
|
|
565
578
|
@event_handlers.pop
|
|
566
579
|
|
|
@@ -582,16 +595,25 @@ module Musa
|
|
|
582
595
|
|
|
583
596
|
# Schedules block at absolute position.
|
|
584
597
|
#
|
|
598
|
+
# Returns a control object whose `.stop` method cancels execution:
|
|
599
|
+
# the block will not run if the control is stopped before the scheduled
|
|
600
|
+
# position. For series-based positions, `.stop` also prevents further
|
|
601
|
+
# elements from being scheduled.
|
|
602
|
+
#
|
|
585
603
|
# @param bar_position [Numeric, Series, Array] absolute position(s)
|
|
586
604
|
# @param debug [Boolean] enable debug logging
|
|
587
605
|
# @yield block to execute at position
|
|
588
|
-
# @return [EventHandler] control object
|
|
606
|
+
# @return [EventHandler] control object (supports .stop to cancel)
|
|
589
607
|
#
|
|
590
608
|
# @example Single position
|
|
591
609
|
# seq.at(4) { puts "At beat 4" }
|
|
592
610
|
#
|
|
593
611
|
# @example Series of positions
|
|
594
612
|
# seq.at([1, 2, 3.5, 4]) { |pos| puts "At #{pos}" }
|
|
613
|
+
#
|
|
614
|
+
# @example Cancelling a scheduled at
|
|
615
|
+
# h = seq.at(8) { puts "won't run" }
|
|
616
|
+
# h.stop
|
|
595
617
|
def at(bar_position, debug: nil, &block)
|
|
596
618
|
debug ||= false
|
|
597
619
|
|
|
@@ -599,7 +621,7 @@ module Musa
|
|
|
599
621
|
@event_handlers.push control
|
|
600
622
|
|
|
601
623
|
if bar_position.is_a? Numeric
|
|
602
|
-
_numeric_at bar_position.rationalize, control, debug: debug, &block
|
|
624
|
+
_numeric_at bar_position.rationalize, control, debug: debug, skip_if_stopped: true, &block
|
|
603
625
|
else
|
|
604
626
|
bar_position = Series::S(*bar_position) if bar_position.is_a? Array
|
|
605
627
|
bar_position = bar_position.instance if bar_position
|
|
@@ -222,7 +222,7 @@ module Musa
|
|
|
222
222
|
# @param value_parameters [Array] parameters to pass to block
|
|
223
223
|
# @param key_parameters [Hash] keyword parameters
|
|
224
224
|
# @yield block to execute now
|
|
225
|
-
# @return [
|
|
225
|
+
# @return [EventHandler] control object
|
|
226
226
|
|
|
227
227
|
# @!method at(position, *value_parameters, **key_parameters, &block)
|
|
228
228
|
# Schedules block to execute at specified position.
|
|
@@ -233,7 +233,7 @@ module Musa
|
|
|
233
233
|
# @param value_parameters [Array] parameters to pass to block
|
|
234
234
|
# @param key_parameters [Hash] keyword parameters
|
|
235
235
|
# @yield block to execute at position
|
|
236
|
-
# @return [
|
|
236
|
+
# @return [EventHandler] control object
|
|
237
237
|
|
|
238
238
|
# @!method wait(duration, *value_parameters, **key_parameters, &block)
|
|
239
239
|
# Schedules block after waiting specified duration.
|
|
@@ -244,7 +244,7 @@ module Musa
|
|
|
244
244
|
# @param value_parameters [Array] parameters to pass to block
|
|
245
245
|
# @param key_parameters [Hash] keyword parameters
|
|
246
246
|
# @yield block to execute after wait
|
|
247
|
-
# @return [
|
|
247
|
+
# @return [EventHandler] control object
|
|
248
248
|
|
|
249
249
|
# @!method play(serie, decoder: nil, mode: nil, **options, &block)
|
|
250
250
|
# Plays a series using the decoder.
|
|
@@ -596,7 +596,7 @@ module Musa
|
|
|
596
596
|
# @param value_parameters [Array] parameters to pass to block
|
|
597
597
|
# @param key_parameters [Hash] keyword parameters
|
|
598
598
|
# @yield block to execute at current position
|
|
599
|
-
# @return [
|
|
599
|
+
# @return [EventHandler] control object
|
|
600
600
|
def now(*value_parameters, **key_parameters, &block)
|
|
601
601
|
block ||= proc {}
|
|
602
602
|
|
|
@@ -612,7 +612,7 @@ module Musa
|
|
|
612
612
|
# @param value_parameters [Array] parameters (first is position)
|
|
613
613
|
# @param key_parameters [Hash] keyword parameters
|
|
614
614
|
# @yield block to execute at position
|
|
615
|
-
# @return [
|
|
615
|
+
# @return [EventHandler] control object
|
|
616
616
|
def at(*value_parameters, **key_parameters, &block)
|
|
617
617
|
block ||= proc {}
|
|
618
618
|
|
|
@@ -628,7 +628,7 @@ module Musa
|
|
|
628
628
|
# @param value_parameters [Array] parameters (first is duration)
|
|
629
629
|
# @param key_parameters [Hash] keyword parameters
|
|
630
630
|
# @yield block to execute after wait
|
|
631
|
-
# @return [
|
|
631
|
+
# @return [EventHandler] control object
|
|
632
632
|
def wait(*value_parameters, **key_parameters, &block)
|
|
633
633
|
block ||= proc {}
|
|
634
634
|
@sequencer.wait *value_parameters, **key_parameters do |*values, **key_values|
|
data/lib/musa-dsl/version.rb
CHANGED