musa-dsl 0.42.5 → 0.42.6
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/transport.md +14 -9
- data/lib/musa-dsl/transport/clock.rb +29 -6
- data/lib/musa-dsl/transport/dummy-clock.rb +15 -3
- data/lib/musa-dsl/transport/external-tick-clock.rb +13 -3
- data/lib/musa-dsl/transport/input-midi-clock.rb +14 -4
- data/lib/musa-dsl/transport/timer-clock.rb +8 -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: 88eb7916fe255e3b5d72520d25d4057252c41f24926975659712668a43c0cc41
|
|
4
|
+
data.tar.gz: 7fbe17e6453b42f4729104e47392c7b4819436de0bf0e64fd28505f0e936e76f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 25514b23a820ff8ec8f2f99c6e607dcec8879db85d030e3d3e19398a26ea63f3c81752d7e839eefb27e6a640b355aa0b58fb0a43b6f076f79b59e2303bf00e8d
|
|
7
|
+
data.tar.gz: f89fbbf88a3f62f455dcf6cb342f47f324c1c4c93c225fcc45ba86f766e31a3300cacd7a3ff379698e97a201594a0e1610357cd20e843a4d797997c551b9c758
|
|
@@ -89,13 +89,15 @@ dummy_clock = Musa::Clock::DummyClock.new(100)
|
|
|
89
89
|
|
|
90
90
|
### Clean Shutdown
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
`transport.stop` triggers the complete lifecycle shutdown sequence, consistently across all clock types:
|
|
93
93
|
|
|
94
|
-
1.
|
|
95
|
-
2.
|
|
96
|
-
3.
|
|
97
|
-
4.
|
|
98
|
-
5.
|
|
94
|
+
1. `transport.stop` calls `clock.terminate`
|
|
95
|
+
2. `clock.terminate` calls `clock.stop` (fires `on_stop` callbacks)
|
|
96
|
+
3. Transport's `on_stop` handler executes `after_stop` callbacks
|
|
97
|
+
4. Sequencer is reset
|
|
98
|
+
5. `before_begin` callbacks run (preparing for potential restart)
|
|
99
|
+
6. Clock's run loop exits
|
|
100
|
+
7. `transport.start` returns
|
|
99
101
|
|
|
100
102
|
```ruby
|
|
101
103
|
# Example: Self-terminating composition
|
|
@@ -109,9 +111,12 @@ puts "Cleanup..." # Executes after stop
|
|
|
109
111
|
output.close
|
|
110
112
|
```
|
|
111
113
|
|
|
112
|
-
**
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
**Clock `stop` vs `terminate` contract:**
|
|
115
|
+
|
|
116
|
+
- **`stop`**: Fires `on_stop` callbacks. Idempotent (second call is a no-op). All clocks implement it.
|
|
117
|
+
- **`terminate`**: Calls `stop` first (guarantees callbacks), then exits the run loop. All clocks implement it.
|
|
118
|
+
|
|
119
|
+
**Note:** For `InputMidiClock`, MIDI Stop messages from the DAW also trigger `clock.stop` (and thus `on_stop` callbacks). To fully exit the run loop, call `clock.terminate` or `transport.stop`.
|
|
115
120
|
|
|
116
121
|
**Key methods:**
|
|
117
122
|
- `start` - Start playback (blocks while running)
|
|
@@ -42,13 +42,15 @@ module Musa
|
|
|
42
42
|
# Concrete clocks must:
|
|
43
43
|
#
|
|
44
44
|
# 1. Implement `run(&block)` - Start generating ticks, yield for each tick
|
|
45
|
-
# 2.
|
|
46
|
-
# 3.
|
|
45
|
+
# 2. Override `stop` if additional cleanup is needed (call super)
|
|
46
|
+
# 3. Override `terminate` to call `stop` then exit the run loop
|
|
47
47
|
# 4. Manage @run state properly
|
|
48
|
+
# 5. Reset `@stopped = false` at the start of `run` (and in `start` if applicable)
|
|
48
49
|
#
|
|
49
50
|
# @example Creating a simple clock subclass
|
|
50
51
|
# class SimpleClock < Clock
|
|
51
52
|
# def run
|
|
53
|
+
# @stopped = false
|
|
52
54
|
# @run = true
|
|
53
55
|
# @on_start.each(&:call)
|
|
54
56
|
#
|
|
@@ -57,11 +59,12 @@ module Musa
|
|
|
57
59
|
# sleep 0.1
|
|
58
60
|
# end
|
|
59
61
|
#
|
|
60
|
-
#
|
|
62
|
+
# stop # Fires on_stop callbacks (idempotent)
|
|
61
63
|
# end
|
|
62
64
|
#
|
|
63
65
|
# def terminate
|
|
64
|
-
#
|
|
66
|
+
# stop # Ensures on_stop callbacks fire
|
|
67
|
+
# @run = false # Exits the run loop
|
|
65
68
|
# end
|
|
66
69
|
# end
|
|
67
70
|
#
|
|
@@ -70,6 +73,7 @@ module Musa
|
|
|
70
73
|
# Initializes the clock with empty callback collections.
|
|
71
74
|
def initialize
|
|
72
75
|
@run = nil
|
|
76
|
+
@stopped = false
|
|
73
77
|
@on_start = []
|
|
74
78
|
@on_stop = []
|
|
75
79
|
@on_change_position = []
|
|
@@ -82,6 +86,20 @@ module Musa
|
|
|
82
86
|
@run
|
|
83
87
|
end
|
|
84
88
|
|
|
89
|
+
# Stops the clock and fires on_stop callbacks.
|
|
90
|
+
#
|
|
91
|
+
# Idempotent: calling stop multiple times only fires callbacks once.
|
|
92
|
+
# Subclasses that need additional stop logic (e.g., pausing a timer)
|
|
93
|
+
# should override and call super.
|
|
94
|
+
#
|
|
95
|
+
# @return [void]
|
|
96
|
+
def stop
|
|
97
|
+
return if @stopped
|
|
98
|
+
|
|
99
|
+
@stopped = true
|
|
100
|
+
@on_stop.each(&:call)
|
|
101
|
+
end
|
|
102
|
+
|
|
85
103
|
# Registers a callback to be called when the clock starts.
|
|
86
104
|
#
|
|
87
105
|
# Multiple callbacks can be registered and will be called in order.
|
|
@@ -132,6 +150,9 @@ module Musa
|
|
|
132
150
|
# This method should block and yield once per tick. Subclasses must
|
|
133
151
|
# implement this method.
|
|
134
152
|
#
|
|
153
|
+
# Subclasses should reset `@stopped = false` at the start of `run`
|
|
154
|
+
# to allow stop/start cycles.
|
|
155
|
+
#
|
|
135
156
|
# @yield Called once per tick to advance the sequencer.
|
|
136
157
|
# @return [void]
|
|
137
158
|
#
|
|
@@ -139,20 +160,22 @@ module Musa
|
|
|
139
160
|
#
|
|
140
161
|
# @note This method typically runs in a loop until {#terminate} is called.
|
|
141
162
|
# @note Subclasses should call @on_start callbacks when starting.
|
|
142
|
-
# @note Subclasses should call @on_stop
|
|
163
|
+
# @note Subclasses should call {#stop} (not @on_stop directly) when stopping.
|
|
143
164
|
def run
|
|
144
165
|
raise NotImplementedError
|
|
145
166
|
end
|
|
146
167
|
|
|
147
168
|
# Stops the clock and terminates the run loop.
|
|
148
169
|
#
|
|
149
|
-
#
|
|
170
|
+
# Calls {#stop} to ensure on_stop callbacks fire, then exits the run loop.
|
|
171
|
+
# Subclasses must implement this method.
|
|
150
172
|
#
|
|
151
173
|
# @return [void]
|
|
152
174
|
#
|
|
153
175
|
# @raise [NotImplementedError] if not overridden by subclass.
|
|
154
176
|
#
|
|
155
177
|
# @note After calling this, {#run} should exit.
|
|
178
|
+
# @note Must call {#stop} to guarantee on_stop callbacks fire.
|
|
156
179
|
def terminate
|
|
157
180
|
raise NotImplementedError
|
|
158
181
|
end
|
|
@@ -89,7 +89,7 @@ module Musa
|
|
|
89
89
|
#
|
|
90
90
|
# Calls on_start callbacks, then yields while the condition is true.
|
|
91
91
|
# Uses Thread.pass instead of sleep for fast operation.
|
|
92
|
-
# Calls
|
|
92
|
+
# Calls {#stop} when done (idempotent).
|
|
93
93
|
#
|
|
94
94
|
# @yield Called once per tick
|
|
95
95
|
# @return [void]
|
|
@@ -98,6 +98,7 @@ module Musa
|
|
|
98
98
|
def run
|
|
99
99
|
@on_start.each(&:call)
|
|
100
100
|
@run = true
|
|
101
|
+
@stopped = false
|
|
101
102
|
|
|
102
103
|
while @run && eval_condition
|
|
103
104
|
yield if block_given?
|
|
@@ -105,14 +106,25 @@ module Musa
|
|
|
105
106
|
Thread.pass # Cooperate with other threads
|
|
106
107
|
end
|
|
107
108
|
|
|
108
|
-
|
|
109
|
+
stop # Idempotent: if terminate already called stop, this is a no-op
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Stops the clock and fires on_stop callbacks.
|
|
113
|
+
#
|
|
114
|
+
# @return [void]
|
|
115
|
+
def stop
|
|
116
|
+
@run = false
|
|
117
|
+
super
|
|
109
118
|
end
|
|
110
119
|
|
|
111
120
|
# Terminates the clock loop.
|
|
112
121
|
#
|
|
122
|
+
# Calls {#stop} to ensure on_stop callbacks fire, then ensures the
|
|
123
|
+
# run loop exits.
|
|
124
|
+
#
|
|
113
125
|
# @return [void]
|
|
114
126
|
def terminate
|
|
115
|
-
|
|
127
|
+
stop
|
|
116
128
|
end
|
|
117
129
|
|
|
118
130
|
private
|
|
@@ -91,6 +91,7 @@ module Musa
|
|
|
91
91
|
#
|
|
92
92
|
# @note This method does NOT block
|
|
93
93
|
def run(&block)
|
|
94
|
+
@stopped = false
|
|
94
95
|
@on_start.each(&:call)
|
|
95
96
|
@run = true
|
|
96
97
|
@block = block
|
|
@@ -111,12 +112,21 @@ module Musa
|
|
|
111
112
|
end
|
|
112
113
|
end
|
|
113
114
|
|
|
114
|
-
#
|
|
115
|
+
# Stops the clock and fires on_stop callbacks.
|
|
115
116
|
#
|
|
116
117
|
# @return [void]
|
|
117
|
-
def
|
|
118
|
-
@on_stop.each(&:call)
|
|
118
|
+
def stop
|
|
119
119
|
@run = false
|
|
120
|
+
super
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Terminates the clock.
|
|
124
|
+
#
|
|
125
|
+
# Delegates to {#stop} which fires on_stop callbacks and sets @run to false.
|
|
126
|
+
#
|
|
127
|
+
# @return [void]
|
|
128
|
+
def terminate
|
|
129
|
+
stop
|
|
120
130
|
end
|
|
121
131
|
end
|
|
122
132
|
end
|
|
@@ -146,6 +146,7 @@ module Musa
|
|
|
146
146
|
# @note Waits if no input assigned
|
|
147
147
|
def run
|
|
148
148
|
@run = true
|
|
149
|
+
@stopped = false
|
|
149
150
|
|
|
150
151
|
while @run
|
|
151
152
|
if @input
|
|
@@ -218,10 +219,21 @@ module Musa
|
|
|
218
219
|
end
|
|
219
220
|
end
|
|
220
221
|
|
|
222
|
+
# Stops the clock and fires on_stop callbacks.
|
|
223
|
+
#
|
|
224
|
+
# @return [void]
|
|
225
|
+
def stop
|
|
226
|
+
@started = false
|
|
227
|
+
super
|
|
228
|
+
end
|
|
229
|
+
|
|
221
230
|
# Terminates the MIDI Clock processing loop.
|
|
222
231
|
#
|
|
232
|
+
# Calls {#stop} to ensure on_stop callbacks fire, then exits the run loop.
|
|
233
|
+
#
|
|
223
234
|
# @return [void]
|
|
224
235
|
def terminate
|
|
236
|
+
stop
|
|
225
237
|
@run = false
|
|
226
238
|
end
|
|
227
239
|
|
|
@@ -235,6 +247,7 @@ module Musa
|
|
|
235
247
|
def process_start
|
|
236
248
|
@logger.debug('InputMidiClock') { 'processing Start...' }
|
|
237
249
|
|
|
250
|
+
@stopped = false
|
|
238
251
|
@on_start.each(&:call)
|
|
239
252
|
@started = true
|
|
240
253
|
|
|
@@ -259,10 +272,7 @@ module Musa
|
|
|
259
272
|
|
|
260
273
|
when 'Stop'
|
|
261
274
|
@logger.debug('InputMidiClock') { 'processing Stop...' }
|
|
262
|
-
|
|
263
|
-
@on_stop.each(&:call)
|
|
264
|
-
@started = false
|
|
265
|
-
|
|
275
|
+
stop
|
|
266
276
|
@logger.debug('InputMidiClock') { 'processing Stop... done' }
|
|
267
277
|
|
|
268
278
|
when 'Continue'
|
|
@@ -195,6 +195,7 @@ module Musa
|
|
|
195
195
|
# @note Clock begins paused; call {#start} to begin ticking
|
|
196
196
|
def run
|
|
197
197
|
@run = true
|
|
198
|
+
@stopped = false
|
|
198
199
|
|
|
199
200
|
while @run
|
|
200
201
|
@timer = Timer.new(@period,
|
|
@@ -221,6 +222,7 @@ module Musa
|
|
|
221
222
|
# @note Calls registered on_start callbacks
|
|
222
223
|
def start
|
|
223
224
|
unless @started
|
|
225
|
+
@stopped = false
|
|
224
226
|
@on_start.each(&:call)
|
|
225
227
|
@started = true
|
|
226
228
|
@paused = false
|
|
@@ -230,20 +232,19 @@ module Musa
|
|
|
230
232
|
|
|
231
233
|
# Stops the clock and resets to initial state.
|
|
232
234
|
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
+
# Stops the internal timer, resets state flags, and fires on_stop
|
|
236
|
+
# callbacks via super (idempotent).
|
|
235
237
|
#
|
|
236
238
|
# @return [void]
|
|
237
239
|
#
|
|
238
|
-
# @note Calls registered on_stop callbacks
|
|
239
240
|
# @note Different from {#pause}: stop resets to initial state
|
|
240
241
|
def stop
|
|
241
242
|
if @started
|
|
242
243
|
@timer.stop
|
|
243
244
|
@started = false
|
|
244
245
|
@paused = false
|
|
245
|
-
@on_stop.each(&:call)
|
|
246
246
|
end
|
|
247
|
+
super
|
|
247
248
|
end
|
|
248
249
|
|
|
249
250
|
# Pauses the clock without stopping it.
|
|
@@ -279,13 +280,14 @@ module Musa
|
|
|
279
280
|
|
|
280
281
|
# Terminates the clock's run loop.
|
|
281
282
|
#
|
|
282
|
-
#
|
|
283
|
-
# the internal timer.
|
|
283
|
+
# Calls {#stop} to ensure on_stop callbacks fire, then exits the run loop
|
|
284
|
+
# by terminating the internal timer.
|
|
284
285
|
#
|
|
285
286
|
# @return [void]
|
|
286
287
|
#
|
|
287
288
|
# @note After calling this, {#run} will exit and {Musa::Transport::Transport#start} will return
|
|
288
289
|
def terminate
|
|
290
|
+
stop
|
|
289
291
|
@run = false
|
|
290
292
|
@timer&.terminate
|
|
291
293
|
end
|
data/lib/musa-dsl/version.rb
CHANGED