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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce1f988b99c9aa0a48313adf5e0f67a1c38fedb82a1ec8f14f1b628b9a1658d0
4
- data.tar.gz: 3415ebc9842ee9fbe09f586206ac241efe0560a98e1170e5b9115247883a2c23
3
+ metadata.gz: 88eb7916fe255e3b5d72520d25d4057252c41f24926975659712668a43c0cc41
4
+ data.tar.gz: 7fbe17e6453b42f4729104e47392c7b4819436de0bf0e64fd28505f0e936e76f
5
5
  SHA512:
6
- metadata.gz: 3a886f0ad181adf8cc0fbb11da1677539a488a54f5b61502ef53c29b9a0ebd2483c79ea5a5031d22a04f1269f5c5979eb1fd6af80c21bd7e4fc937c58198ebeb
7
- data.tar.gz: efb5db224fc2bea5439c136d7981c342b98c587a7c2ee50865ffa0677d638d4065c9a1dfb1ea7570c267fec1a2b46bd7cdec1b9a763928fa37742696b94caeb4
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
- To cleanly terminate a transport using TimerClock:
92
+ `transport.stop` triggers the complete lifecycle shutdown sequence, consistently across all clock types:
93
93
 
94
- 1. Call `transport.stop` from within a scheduled event
95
- 2. This calls `clock.terminate` internally
96
- 3. The clock's run loop exits
97
- 4. `transport.start` returns
98
- 5. Your code continues after `transport.start` for cleanup
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
- **Note:** For `InputMidiClock`, stop/start cycles are controlled by the DAW
113
- and don't terminate the process. The `terminate` method can be used explicitly
114
- if you need the run loop to exit.
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. Implement `terminate` - Stop the clock
46
- # 3. Call registered callbacks at appropriate times
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
- # @on_stop.each(&:call)
62
+ # stop # Fires on_stop callbacks (idempotent)
61
63
  # end
62
64
  #
63
65
  # def terminate
64
- # @run = false
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 callbacks when stopping.
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
- # Subclasses must implement this method to cleanly stop the clock.
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 on_stop callbacks when done.
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
- @on_stop.each(&:call)
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
- @run = false
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
- # Terminates the clock and calls on_stop callbacks.
115
+ # Stops the clock and fires on_stop callbacks.
115
116
  #
116
117
  # @return [void]
117
- def terminate
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
- # Triggers @on_stop callbacks and marks clock as not started.
234
- # Has no effect if not currently started.
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
- # Causes {#run} to exit by setting the run flag to false and terminating
283
- # the internal timer. This is the clean shutdown mechanism.
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
@@ -1,3 +1,3 @@
1
1
  module Musa
2
- VERSION = '0.42.5'.freeze
2
+ VERSION = '0.42.6'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: musa-dsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.42.5
4
+ version: 0.42.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Javier Sánchez Yeste