musa-dsl 0.42.1 → 0.42.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 613cf84af52f6eb5dcfd399b94417f1e175242f32f8dc7cb2ab6452ebb58e9d0
4
- data.tar.gz: 2a19dc6002ab89ae77f973d8a4405de380e73230bbd3f986d240071f654d505d
3
+ metadata.gz: 12fab469b50ab2d8c92ce166859645d8c30ad655e2ae339aa9b8c0254cc59253
4
+ data.tar.gz: 87aa5596a08c21f0b17ed7f6758b708a3473f9e8078020b7cf564b2149f6823a
5
5
  SHA512:
6
- metadata.gz: e61a579a8c116af3e420ec7c98756734a7b00252981a8b42a2fe8472fd18bea679793fa879ab2dc1e279558472b55ba55bcdd7d1cab5889bf39af204db3b461f
7
- data.tar.gz: b1678d001558905ed9939580849e48e8ef5a79c3d3131959e46f0d2c4505c77b1030c91bc6c156cc650b1e42bd1b7d32d978bc730fb14e576b8b29a0e0b552d5
6
+ metadata.gz: 38b45b016289a63870ada189d4ea91f9749754997ad4e250b5d24b1ea47c001741696e71eef8ce13dfeb4e6753caf213f970861fbbff2edbcee8f18504a9e03e
7
+ data.tar.gz: 6a14e6306e5f3945fa5e5febfa223fd1aece1cbff1d0ed8533a5a54231646bfddf96489a572d8aae1adbeb6b0dbbaee1f3aa1ef9effa8095c051378b145bde9a
@@ -102,6 +102,71 @@ every 1/4r do ... end
102
102
  at 0.5 do ... end
103
103
  ```
104
104
 
105
+ ## Control Callbacks: `on_stop` vs `after`
106
+
107
+ The control objects returned by `every`, `play`, `play_timed`, and `move` support two types of callbacks with different semantics:
108
+
109
+ - **`on_stop`**: Cleanup callback — fires **always** when the control terminates, whether naturally or via manual `.stop`. Use for resource cleanup, state updates, logging.
110
+ - **`after`**: Continuation callback — fires **only on natural termination** (duration reached, series exhausted, till exceeded, condition failed). **NOT** called when `.stop` is used. Use for chaining sections, scheduling follow-up events.
111
+
112
+ | Termination cause | `on_stop` fires? | `after` fires? |
113
+ |---|---|---|
114
+ | Manual `.stop` | Yes | **No** |
115
+ | Duration reached | Yes | Yes |
116
+ | Till position exceeded | Yes | Yes |
117
+ | Condition failed | Yes | Yes |
118
+ | Series exhausted (play) | Yes | Yes |
119
+ | Nil interval (every, one-shot) | Yes | Yes |
120
+
121
+ ### Examples
122
+
123
+ ```ruby
124
+ # Safe section chaining — .stop won't cause relaunch
125
+ ctrl = every 1r do
126
+ # ... play pattern ...
127
+ end
128
+
129
+ ctrl.on_stop { puts "Pattern stopped (any reason)" }
130
+ ctrl.after { launch :next_section } # Only on natural end
131
+
132
+ # Later: manual stop does NOT trigger :next_section
133
+ ctrl.stop
134
+ ```
135
+
136
+ ```ruby
137
+ # Play with on_stop for cleanup
138
+ ctrl = play melody do |note:, duration:|
139
+ voice.note(note, duration: duration)
140
+ end
141
+
142
+ ctrl.on_stop { voice.all_notes_off } # Always cleanup
143
+ ctrl.after { launch :next_phrase } # Only if melody finishes naturally
144
+ ```
145
+
146
+ ```ruby
147
+ # Move with after for continuation
148
+ ctrl = move from: 0, to: 127, duration: 4r, every: 1/4r do |v|
149
+ midi_cc(7, v.round)
150
+ end
151
+
152
+ ctrl.on_stop { puts "Fade ended" } # Any reason
153
+ ctrl.after { launch :next_section } # Only if fade completes
154
+ ```
155
+
156
+ ### Parameter form
157
+
158
+ `on_stop` and `after` can also be passed as parameters:
159
+
160
+ ```ruby
161
+ every 1r, on_stop: proc { cleanup }, after: proc { continue } do
162
+ # ...
163
+ end
164
+
165
+ play melody, on_stop: proc { cleanup } do |note:|
166
+ # ...
167
+ end
168
+ ```
169
+
105
170
  ## API Reference
106
171
 
107
172
  **Complete API documentation:**
@@ -48,9 +48,10 @@ module Musa::Sequencer
48
48
  if control.stopped? || duration_exceeded || till_exceeded || condition_failed || interval.nil?
49
49
  control.do_on_stop.each(&:call)
50
50
 
51
- control.do_after.each do |do_after|
52
- # _numeric_at position + (interval || 0) + do_after[:bars], control, &do_after[:block]
53
- _numeric_at position + do_after[:bars], control, &do_after[:block]
51
+ unless control.stopped?
52
+ control.do_after.each do |do_after|
53
+ _numeric_at position + do_after[:bars], control, &do_after[:block]
54
+ end
54
55
  end
55
56
  else
56
57
  _numeric_at control._start_position + control._execution_counter * interval, control do
@@ -77,8 +78,8 @@ module Musa::Sequencer
77
78
  #
78
79
  # ## Callbacks
79
80
  #
80
- # - **on_stop**: Called when loop stops (any reason)
81
- # - **after**: Called after loop stops, with optional delay in bars
81
+ # - **on_stop**: Called when loop stops (any reason, including manual stop)
82
+ # - **after**: Called only on natural termination (duration/till/condition/nil-interval), NOT on manual stop
82
83
  #
83
84
  # ## Execution Tracking
84
85
  #
@@ -169,7 +170,7 @@ module Musa::Sequencer
169
170
  @condition_block = block
170
171
  end
171
172
 
172
- # Registers callback for when loop stops.
173
+ # Registers callback for when loop stops (any reason, including manual stop).
173
174
  #
174
175
  # @yield stop callback block
175
176
  #
@@ -180,9 +181,10 @@ module Musa::Sequencer
180
181
  @do_on_stop << block
181
182
  end
182
183
 
183
- # Registers callback to execute after loop stops.
184
+ # Registers callback to execute after loop terminates naturally.
185
+ # NOT called on manual stop (.stop).
184
186
  #
185
- # @param bars [Numeric, nil] delay in bars after stop (default: 0)
187
+ # @param bars [Numeric, nil] delay in bars after natural termination (default: 0)
186
188
  # @yield after callback block
187
189
  #
188
190
  # @return [void]
@@ -235,8 +235,10 @@ module Musa::Sequencer
235
235
  on_stop: on_stop, after_bars: after_bars, after: after)
236
236
 
237
237
  control.on_stop do
238
- control.do_after.each do |do_after|
239
- _numeric_at position + do_after[:bars], control, &do_after[:block]
238
+ unless control.manually_stopped?
239
+ control.do_after.each do |do_after|
240
+ _numeric_at position + do_after[:bars], control, &do_after[:block]
241
+ end
240
242
  end
241
243
  end
242
244
 
@@ -324,7 +326,7 @@ module Musa::Sequencer
324
326
  #
325
327
  # Do we need stop?
326
328
  #
327
- control.stop if stop.all?
329
+ control.every_control.stop if stop.all?
328
330
 
329
331
  #
330
332
  # Calculate effective values and next_values applying the parameter function
@@ -527,6 +529,7 @@ module Musa::Sequencer
527
529
  super parent
528
530
 
529
531
  @every_control = EveryControl.new(self, duration: duration, till: till)
532
+ @manually_stopped = false
530
533
 
531
534
  @do_on_stop = []
532
535
  @do_after = []
@@ -540,7 +543,7 @@ module Musa::Sequencer
540
543
  end
541
544
  end
542
545
 
543
- # Registers callback for when movement stops.
546
+ # Registers callback for when movement stops (any reason, including manual stop).
544
547
  #
545
548
  # @yield stop callback block
546
549
  #
@@ -550,9 +553,10 @@ module Musa::Sequencer
550
553
  @do_on_stop << block
551
554
  end
552
555
 
553
- # Registers callback to execute after movement stops.
556
+ # Registers callback to execute after movement terminates naturally.
557
+ # NOT called on manual stop (.stop).
554
558
  #
555
- # @param bars [Numeric, nil] delay in bars after stop (default: 0)
559
+ # @param bars [Numeric, nil] delay in bars after natural termination (default: 0)
556
560
  # @yield after callback block
557
561
  #
558
562
  # @return [void]
@@ -565,14 +569,26 @@ module Musa::Sequencer
565
569
  @do_after << { bars: bars.rationalize, block: block }
566
570
  end
567
571
 
568
- # Stops movement.
572
+ # Stops movement manually.
573
+ #
574
+ # Sets manually_stopped flag before delegating to every_control.
575
+ # After callbacks will NOT fire on manual stop.
569
576
  #
570
577
  # @return [void]
571
578
  #
572
579
  def stop
580
+ @manually_stopped = true
573
581
  @every_control.stop
574
582
  end
575
583
 
584
+ # Checks if movement was stopped manually (via .stop).
585
+ #
586
+ # @return [Boolean] true if manually stopped
587
+ #
588
+ def manually_stopped?
589
+ @manually_stopped
590
+ end
591
+
576
592
  # Checks if movement is stopped.
577
593
  #
578
594
  # @return [Boolean] true if stopped
@@ -124,9 +124,12 @@ module Musa::Sequencer
124
124
  end
125
125
  end
126
126
  else
127
+ control.do_on_stop.each(&:call)
127
128
 
128
- control.do_after.each do |do_after|
129
- _numeric_at position + do_after[:bars], control, &do_after[:block]
129
+ unless control.stopped?
130
+ control.do_after.each do |do_after|
131
+ _numeric_at position + do_after[:bars], control, &do_after[:block]
132
+ end
130
133
  end
131
134
  end
132
135
  end
@@ -165,7 +168,7 @@ module Musa::Sequencer
165
168
  self.after after_bars, &after if after
166
169
  end
167
170
 
168
- # Registers callback for when playback stops.
171
+ # Registers callback for when playback stops (any reason, including manual stop).
169
172
  #
170
173
  # @yield stop callback block
171
174
  #
@@ -176,9 +179,10 @@ module Musa::Sequencer
176
179
  @do_on_stop << block
177
180
  end
178
181
 
179
- # Registers callback to execute after playback completes.
182
+ # Registers callback to execute after playback terminates naturally
183
+ # (series exhausted). NOT called on manual stop (.stop).
180
184
  #
181
- # @param bars [Numeric, nil] delay in bars after completion (default: 0)
185
+ # @param bars [Numeric, nil] delay in bars after natural termination (default: 0)
182
186
  # @yield after callback block
183
187
  #
184
188
  # @return [void]
@@ -203,10 +203,14 @@ module Musa::Sequencer
203
203
  end
204
204
  end
205
205
  else
206
- control2 = EventHandler.new control
206
+ control.do_on_stop.each(&:call)
207
207
 
208
- control.do_after.each do |do_after|
209
- _numeric_at position + do_after[:bars], control2, &do_after[:block]
208
+ unless control.stopped?
209
+ control2 = EventHandler.new control
210
+
211
+ control.do_after.each do |do_after|
212
+ _numeric_at position + do_after[:bars], control2, &do_after[:block]
213
+ end
210
214
  end
211
215
  end
212
216
 
@@ -252,21 +256,26 @@ module Musa::Sequencer
252
256
  #
253
257
  # @api private
254
258
  class PlayControl < EventHandler
255
- # @return [Array<Hash>] after callbacks with delays
259
+ # @return [Array<Proc>] callbacks when play stops (any reason, including manual stop)
260
+ attr_reader :do_on_stop
261
+ # @return [Array<Hash>] after callbacks with delays (only on natural termination)
256
262
  attr_reader :do_after
257
263
 
258
- # Creates play control with optional after callback.
264
+ # Creates play control with optional callbacks.
259
265
  #
260
266
  # @param parent [EventHandler] parent event handler
267
+ # @param on_stop [Proc, nil] stop callback (fires on any termination)
261
268
  # @param after_bars [Rational, nil] delay for after callback
262
- # @param after [Proc, nil] after callback block
269
+ # @param after [Proc, nil] after callback block (fires only on natural termination)
263
270
  #
264
271
  # @api private
265
- def initialize(parent, after_bars: nil, after: nil)
272
+ def initialize(parent, on_stop: nil, after_bars: nil, after: nil)
266
273
  super parent
267
274
 
275
+ @do_on_stop = []
268
276
  @do_after = []
269
277
 
278
+ @do_on_stop << on_stop if on_stop
270
279
  after(after_bars, &after) if after
271
280
  end
272
281
 
@@ -322,7 +331,19 @@ module Musa::Sequencer
322
331
  @continuation_sequencer&.continuation_play(@continuation_parameters)
323
332
  end
324
333
 
325
- # Registers callback to execute after play completes.
334
+ # Registers callback for when play stops (any reason, including manual stop).
335
+ #
336
+ # @yield stop callback block
337
+ #
338
+ # @return [void]
339
+ #
340
+ # @api private
341
+ def on_stop(&block)
342
+ @do_on_stop << block
343
+ end
344
+
345
+ # Registers callback to execute after play completes naturally
346
+ # (series exhausted). Not called on manual stop.
326
347
  #
327
348
  # @param bars [Numeric, nil] delay in bars after completion (default: 0)
328
349
  # @yield after callback block
@@ -39,19 +39,15 @@ module Musa::Sequencer
39
39
  command = queue.shift
40
40
  @timeslots.delete position_to_run if queue.empty?
41
41
 
42
- if command.key?(:parent_control) && !command[:parent_control].stopped?
43
- @event_handlers.push command[:parent_control]
44
-
45
- @tick_mutex.synchronize do
46
- command[:block]&.call *command[:value_parameters], **command[:key_parameters]
47
- end
48
-
49
- @event_handlers.pop
50
- else
51
- @tick_mutex.synchronize do
52
- command[:block]&.call *command[:value_parameters], **command[:key_parameters]
53
- end
42
+ push_parent = command.key?(:parent_control) && !command[:parent_control].stopped?
43
+
44
+ @event_handlers.push(command[:parent_control]) if push_parent
45
+
46
+ @tick_mutex.synchronize do
47
+ command[:block]&.call *command[:value_parameters], **command[:key_parameters]
54
48
  end
49
+
50
+ @event_handlers.pop if push_parent
55
51
  end
56
52
  end
57
53
 
@@ -622,8 +622,9 @@ module Musa
622
622
  # @param serie [Series] series to play
623
623
  # @param mode [Symbol] running mode (:at, :wait, :neumalang)
624
624
  # @param parameter [Symbol, nil] duration parameter name from serie values
625
- # @param after_bars [Numeric, nil] schedule block after play finishes
626
- # @param after [Proc, nil] block to execute after play finishes
625
+ # @param on_stop [Proc, nil] callback when play stops (any reason, including manual stop)
626
+ # @param after_bars [Numeric, nil] delay for after callback
627
+ # @param after [Proc, nil] callback after play completes naturally (NOT on manual stop)
627
628
  # @param context [Object, nil] context for neumalang processing
628
629
  # @param mode_args [Hash] additional mode-specific parameters
629
630
  # @yield [value] block executed for each serie value
@@ -666,6 +667,7 @@ module Musa
666
667
  def play(serie,
667
668
  mode: nil,
668
669
  parameter: nil,
670
+ on_stop: nil,
669
671
  after_bars: nil,
670
672
  after: nil,
671
673
  context: nil,
@@ -674,7 +676,7 @@ module Musa
674
676
 
675
677
  mode ||= :wait
676
678
 
677
- control = PlayControl.new @event_handlers.last, after_bars: after_bars, after: after
679
+ control = PlayControl.new @event_handlers.last, on_stop: on_stop, after_bars: after_bars, after: after
678
680
  @event_handlers.push control
679
681
 
680
682
  _play serie.instance, control, context, mode: mode, parameter: parameter, **mode_args, &block
@@ -683,10 +685,10 @@ module Musa
683
685
 
684
686
  @playing << control
685
687
 
686
- control.after do
688
+ control.on_stop do
687
689
  @playing.delete control
688
690
  end
689
-
691
+
690
692
  control
691
693
  end
692
694
 
@@ -780,12 +782,6 @@ module Musa
780
782
  control = PlayTimedControl.new(@event_handlers.last,
781
783
  on_stop: on_stop, after_bars: after_bars, after: after)
782
784
 
783
- control.on_stop do
784
- control.do_after.each do |do_after|
785
- _numeric_at position + do_after[:bars], control, &do_after[:block]
786
- end
787
- end
788
-
789
785
  @event_handlers.push control
790
786
 
791
787
  _play_timed(timed_serie.instance, at, control, &block)
@@ -794,7 +790,7 @@ module Musa
794
790
 
795
791
  @playing << control
796
792
 
797
- control.after do
793
+ control.on_stop do
798
794
  @playing.delete control
799
795
  end
800
796
 
@@ -876,7 +872,7 @@ module Musa
876
872
 
877
873
  @everying << control
878
874
 
879
- control.after do
875
+ control.on_stop do
880
876
  @everying.delete control
881
877
  end
882
878
 
@@ -1005,7 +1001,7 @@ module Musa
1005
1001
 
1006
1002
  @moving << control
1007
1003
 
1008
- control.after do
1004
+ control.on_stop do
1009
1005
  @moving.delete control
1010
1006
  end
1011
1007
 
@@ -641,9 +641,12 @@ module Musa
641
641
  # Wraps BaseSequencer#play, evaluating block in DSL context.
642
642
  #
643
643
  # @param value_parameters [Array] parameters (series, etc.)
644
- # @param key_parameters [Hash] keyword parameters
644
+ # @param key_parameters [Hash] keyword parameters (on_stop:, after:, after_bars:, mode:, etc.)
645
645
  # @yield block to execute for each element
646
646
  # @return [PlayControl] control object
647
+ #
648
+ # @see BaseSequencer#play for full parameter documentation
649
+ # @note on_stop: fires on any termination; after: fires only on natural termination (NOT on manual .stop)
647
650
  def play(*value_parameters, **key_parameters, &block)
648
651
  block ||= proc {}
649
652
 
@@ -657,9 +660,12 @@ module Musa
657
660
  # Wraps BaseSequencer#play_timed, evaluating block in DSL context.
658
661
  #
659
662
  # @param value_parameters [Array] parameters (timed series, etc.)
660
- # @param key_parameters [Hash] keyword parameters
663
+ # @param key_parameters [Hash] keyword parameters (on_stop:, after:, after_bars:, at:)
661
664
  # @yield block to execute for each element
662
665
  # @return [PlayTimedControl] control object
666
+ #
667
+ # @see BaseSequencer#play_timed for full parameter documentation
668
+ # @note on_stop: fires on any termination; after: fires only on natural termination (NOT on manual .stop)
663
669
  def play_timed(*value_parameters, **key_parameters, &block)
664
670
  block ||= proc {}
665
671
 
@@ -674,9 +680,12 @@ module Musa
674
680
  # Uses SmartProcBinder to apply parameters before with.
675
681
  #
676
682
  # @param value_parameters [Array] parameters (interval, etc.)
677
- # @param key_parameters [Hash] keyword parameters
683
+ # @param key_parameters [Hash] keyword parameters (duration:, till:, condition:, on_stop:, after:, after_bars:)
678
684
  # @yield block to execute each iteration
679
685
  # @return [EveryControl] control object
686
+ #
687
+ # @see BaseSequencer#every for full parameter documentation
688
+ # @note on_stop: fires on any termination; after: fires only on natural termination (NOT on manual .stop)
680
689
  def every(*value_parameters, **key_parameters, &block)
681
690
  block ||= proc {}
682
691
 
@@ -691,9 +700,12 @@ module Musa
691
700
  # Wraps BaseSequencer#move, evaluating block in DSL context.
692
701
  #
693
702
  # @param value_parameters [Array] parameters (from, to, etc.)
694
- # @param key_parameters [Hash] keyword parameters
703
+ # @param key_parameters [Hash] keyword parameters (every:, from:, to:, step:, duration:, till:, on_stop:, after:, after_bars:, etc.)
695
704
  # @yield block to execute each iteration with current value
696
705
  # @return [MoveControl] control object
706
+ #
707
+ # @see BaseSequencer#move for full parameter documentation
708
+ # @note on_stop: fires on any termination; after: fires only on natural termination (NOT on manual .stop)
697
709
  def move(*value_parameters, **key_parameters, &block)
698
710
  block ||= proc {}
699
711
 
@@ -1,3 +1,3 @@
1
1
  module Musa
2
- VERSION = '0.42.1'.freeze
2
+ VERSION = '0.42.2'.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.1
4
+ version: 0.42.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Javier Sánchez Yeste