Class: Musa::Sequencer::BaseSequencer
- Defined in:
- lib/musa-dsl/sequencer/base-sequencer.rb,
lib/musa-dsl/sequencer/timeslots.rb,
lib/musa-dsl/sequencer/base-sequencer-tick-based.rb,
lib/musa-dsl/sequencer/base-sequencer-implementation.rb,
lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb,
lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb,
lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb,
lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb,
lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb,
lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb
Overview
Musical sequencer and scheduler system.
Sequencer provides precise timing and scheduling for musical events, supporting both tick-based (quantized) and tickless (continuous) timing modes. Events are scheduled with musical time units (bars, beats, ticks) and executed sequentially.
Core Concepts
-
Position: Current playback position in beats
-
Timeslots: Scheduled events indexed by time
-
Timing Modes:
-
Tick-based: Quantized to beats_per_bar × ticks_per_beat grid
-
Tickless: Continuous rational time (no quantization)
-
Scheduling Methods:
-
at: Schedule block at absolute position -
wait: Schedule relative to current position -
play: Play series over time -
every: Repeat at intervals -
move: Animate value over time -
Event Handlers: Hierarchical event pub/sub system
-
Controls: Objects returned by scheduling methods for lifecycle management
Block Parameter Flexibility (SmartProcBinder)
All scheduling methods (every, play, move, play_timed) pass parameters
to user blocks via SmartProcBinder. This means blocks can declare only the
parameters they need — undeclared parameters are silently ignored.
Keyword parameters (like control:) must be declared as keyword arguments
in the block signature (|control:|), not as positional arguments (|control|).
| Method | Positional params | Keyword params |
|---|---|---|
every |
(none) | control: |
play |
element (+ hash keys as keywords) | control: |
move |
value, next_value | control:, duration:, quantized_duration:, started_ago:, position_jitter:, duration_jitter:, right_open: |
play_timed |
values (+ extra attributes as keywords) | time:, started_ago:, control: |
Tick-based vs Tickless
Tick-based (beats_per_bar and ticks_per_beat specified):
- Positions quantized to tick grid
tickmethod advances by one tick- Suitable for MIDI-like discrete timing
- Example:
BaseSequencer.new(4, 24)→ 4/4 time, 24 ticks per beat
Tickless (no timing parameters):
- Continuous rational time
tickadvances to the next scheduled position (it takes no argument)- Suitable for score-like continuous timing
- Example:
BaseSequencer.new→ tickless mode
Musical Time Units
- Bar: Musical measure (defaults to 1.0 in value)
- Beat: Subdivision of bar (e.g., quarter note in 4/4)
- Tick: Smallest time quantum in tick-based mode
- All times are Rational for precision
The per-method examples further down are written against:
sequencer = BaseSequencer.new(4, 24)
Defined Under Namespace
Modules: TickBasedTiming, TicklessBasedTiming Classes: EventHandler, EveryControl, MoveControl, PlayControl, PlayTimedControl
Instance Attribute Summary collapse
-
#beats_per_bar ⇒ Rational?
readonly
Beats per bar (tick-based mode only).
-
#everying ⇒ Array<EveryControl>
readonly
Active every loops.
-
#logger ⇒ Musa::Logger::Logger
readonly
Sequencer logger.
-
#moving ⇒ Array<MoveControl>
readonly
Active move operations.
-
#offset ⇒ Rational
readonly
Time offset for position calculations.
-
#playing ⇒ Array<PlayControl, PlayTimedControl>
readonly
Active play operations.
-
#running_position ⇒ Rational
readonly
Current running position.
-
#ticks_per_beat ⇒ Rational?
readonly
Ticks per beat (tick-based mode only).
Instance Method Summary collapse
-
#_rescue_error(e) ⇒ void
private
Handles errors during event execution.
-
#at(bar_position, debug: nil) { ... } ⇒ EventHandler
Schedules block at absolute position.
-
#before_tick {|position| ... } ⇒ void
Registers callback executed before each tick.
- #continuation_play(parameters) ⇒ Object
- #debug(msg = nil) ⇒ Object
-
#empty? ⇒ Boolean
Checks if sequencer has no scheduled events.
-
#event_handler ⇒ EventHandler
private
Returns current event handler.
-
#every(interval, duration: nil, till: nil, condition: nil, on_stop: nil, after_bars: nil, after: nil) {|control| ... } ⇒ EveryControl
Executes block repeatedly at regular intervals.
-
#initialize(beats_per_bar = nil, ticks_per_beat = nil, offset: nil, logger: nil, do_log: nil, do_error_log: nil, log_position_format: nil) ⇒ BaseSequencer
constructor
Creates sequencer with timing configuration.
-
#launch(event, *value_parameters, **key_parameters) ⇒ void
Launches custom event.
-
#move(every: nil, from: nil, to: nil, step: nil, duration: nil, till: nil, function: nil, right_open: nil, on_stop: nil, after_bars: nil, after: nil) {|value, next_value, control, duration, quantized_duration, started_ago, position_jitter, duration_jitter, right_open| ... } ⇒ MoveControl
Animates value from start to end over time.
-
#now { ... } ⇒ EventHandler
Schedules block at current position (immediate execution on next tick).
-
#on(event) {|*args| ... } ⇒ void
Subscribes to custom event.
-
#on_debug_at { ... } ⇒ void
Registers debug callback for scheduled events.
-
#on_error {|error| ... } ⇒ void
Registers error callback.
-
#on_fast_forward {|is_starting| ... } ⇒ void
Registers fast-forward callback (when jumping over events).
-
#play(serie, mode: nil, parameter: nil, on_stop: nil, after_bars: nil, after: nil, context: nil, **mode_args) {|element, control| ... } ⇒ PlayControl
Plays series over time.
-
#play_timed(timed_serie, at: nil, on_stop: nil, after_bars: nil, after: nil) {|values, time, started_ago, control| ... } ⇒ PlayTimedControl
Plays timed series (series with embedded timing information).
-
#quantize_position(position, warn: nil) ⇒ Rational
Quantizes position to tick grid (tick-based mode only).
-
#raw_at(bar_position, force_first: nil) { ... } ⇒ nil
private
Schedules block at absolute position (low-level, no control object).
-
#reset ⇒ void
Resets sequencer to initial state.
-
#run ⇒ void
Executes all scheduled events until empty.
-
#size ⇒ Integer
Counts total scheduled events.
- #to_s ⇒ Object
-
#wait(bars_delay, debug: nil) { ... } ⇒ EventHandler
Schedules block relative to current position.
Constructor Details
#initialize(beats_per_bar = nil, ticks_per_beat = nil, offset: nil, logger: nil, do_log: nil, do_error_log: nil, log_position_format: nil) ⇒ BaseSequencer
Creates sequencer with timing configuration.
Timing Modes
Tick-based: Provide both beats_per_bar and ticks_per_beat
- Position quantized to tick grid
tickadvances by one tick
Tickless: Omit beats_per_bar and ticks_per_beat
- Continuous rational time
tickadvances to next scheduled position (without timing quantization)
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 199 def initialize( = nil, ticks_per_beat = nil, offset: nil, logger: nil, do_log: nil, do_error_log: nil, log_position_format: nil) unless && ticks_per_beat || .nil? && ticks_per_beat.nil? raise ArgumentError, "'beats_per_bar' and 'ticks_per_beat' parameters should be both nil or both have values" end if logger @logger = logger else @logger = Musa::Logger::Logger.new(sequencer: self, position_format: log_position_format) @logger.fatal! @logger.error! if do_error_log || do_error_log.nil? @logger.debug! if do_log end @offset = offset || 0r if && ticks_per_beat @beats_per_bar = Rational() @ticks_per_beat = Rational(ticks_per_beat) singleton_class.include TickBasedTiming else singleton_class.include TicklessBasedTiming end _init_timing @on_debug_at = [] @on_error = [] @before_tick = [] @on_fast_forward = [] @tick_mutex = Mutex.new @position_mutex = Mutex.new @timeslots = Timeslots.new @everying = [] @playing = [] @moving = [] reset end |
Instance Attribute Details
#beats_per_bar ⇒ Rational? (readonly)
Returns beats per bar (tick-based mode only).
141 142 143 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 141 def @beats_per_bar end |
#everying ⇒ Array<EveryControl> (readonly)
Returns active every loops.
153 154 155 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 153 def @everying end |
#logger ⇒ Musa::Logger::Logger (readonly)
Returns sequencer logger.
162 163 164 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 162 def logger @logger end |
#moving ⇒ Array<MoveControl> (readonly)
Returns active move operations.
159 160 161 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 159 def moving @moving end |
#offset ⇒ Rational (readonly)
Returns time offset for position calculations.
147 148 149 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 147 def offset @offset end |
#playing ⇒ Array<PlayControl, PlayTimedControl> (readonly)
Returns active play operations.
156 157 158 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 156 def @playing end |
#running_position ⇒ Rational (readonly)
Returns current running position.
150 151 152 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 150 def running_position @running_position end |
#ticks_per_beat ⇒ Rational? (readonly)
Returns ticks per beat (tick-based mode only).
144 145 146 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 144 def ticks_per_beat @ticks_per_beat end |
Instance Method Details
#_rescue_error(e) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Handles errors during event execution.
Logs error message and full backtrace, then calls all registered on_error callbacks with the exception. Used by SmartProcBinder and direct rescue blocks to centralize error handling.
287 288 289 290 291 292 293 294 |
# File 'lib/musa-dsl/sequencer/base-sequencer-implementation.rb', line 287 def _rescue_error(e) @logger.error('BaseSequencer') { e.to_s } @logger.error('BaseSequencer') { e.(highlight: true, order: :top) } @on_error.each do |block| block.call e end end |
#at(bar_position, debug: nil) { ... } ⇒ EventHandler
Schedules block at absolute position.
Returns a control object whose .stop method cancels execution:
the block will not run if the control is stopped before the scheduled
position. For series-based positions, .stop also prevents further
elements from being scheduled.
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 643 def at(, debug: nil, &block) debug ||= false control = EventHandler.new @event_handlers.last @event_handlers.push control if .is_a? Numeric _numeric_at .rationalize, control, debug: debug, skip_if_stopped: true, &block else = Series::S(*) if .is_a? Array = .instance if _serie_at , control, debug: debug, &block end @event_handlers.pop control end |
#before_tick {|position| ... } ⇒ void
This method returns an undefined value.
Registers callback executed before each tick.
Callback is invoked before processing events at each position. Useful for logging, metrics collection, or performing pre-tick setup. Receives the position about to be executed.
468 469 470 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 468 def before_tick(&block) @before_tick << Musa::Extension::SmartProcBinder::SmartProcBinder.new(block) end |
#continuation_play(parameters) ⇒ Object
753 754 755 756 757 758 759 760 761 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 753 def continuation_play(parameters) _play parameters[:serie], parameters[:control], parameters[:neumalang_context], mode: parameters[:mode], decoder: parameters[:decoder], __play_eval: parameters[:play_eval], **parameters[:mode_args] end |
#debug(msg = nil) ⇒ Object
1101 1102 1103 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 1101 def debug(msg = nil) @logger.debug { msg || '...' } end |
#empty? ⇒ Boolean
Checks if sequencer has no scheduled events.
296 297 298 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 296 def empty? @timeslots.empty? end |
#event_handler ⇒ EventHandler
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns current event handler.
330 331 332 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 330 def event_handler @event_handlers.last end |
#every(interval, duration: nil, till: nil, condition: nil, on_stop: nil, after_bars: nil, after: nil) {|control| ... } ⇒ EveryControl
Executes block repeatedly at regular intervals.
Execution Model
Every loop schedules itself recursively:
- Execute block at current position
- Check stopping conditions
- If not stopped, schedule next iteration at start + counter * interval
- If stopped, call on_stop and after callbacks
This ensures precise timing - iterations are scheduled relative to start position, not accumulated from previous iteration (avoiding drift).
Stopping Conditions
Loop stops when any of these conditions is met:
- manual stop:
control.stopcalled - duration: elapsed time >= duration (in bars)
- till: current position >= till position
- condition: condition block returns false
- nil interval: immediate stop after first execution
Block Parameters (via SmartProcBinder)
The block receives the following keyword parameter via SmartProcBinder. You can declare only the parameters you need — undeclared ones are silently ignored.
- control: [EveryControl] — the control object for the current loop
925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 925 def every(interval, duration: nil, till: nil, condition: nil, on_stop: nil, after_bars: nil, after: nil, &block) # nil interval means 'only once' interval = interval.rationalize unless interval.nil? control = EveryControl.new @event_handlers.last, duration: duration, till: till, condition: condition, on_stop: on_stop, after_bars: , after: after @event_handlers.push control _every interval, control, &block @event_handlers.pop @everying << control control.on_stop do @everying.delete control end control end |
#launch(event, *value_parameters, **key_parameters) ⇒ void
This method returns an undefined value.
Launches custom event.
Publishes a custom event to registered handlers. Events bubble up through the handler hierarchy if not handled locally. Supports both positional and keyword parameters.
545 546 547 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 545 def launch(event, *value_parameters, **key_parameters) @event_handlers.last.launch event, *value_parameters, **key_parameters end |
#move(every: nil, from: nil, to: nil, step: nil, duration: nil, till: nil, function: nil, right_open: nil, on_stop: nil, after_bars: nil, after: nil) {|value, next_value, control, duration, quantized_duration, started_ago, position_jitter, duration_jitter, right_open| ... } ⇒ MoveControl
Animates value from start to end over time. Supports single values, arrays, and hashes with flexible parameter combinations for controlling timing and interpolation.
Value Modes
- Single value:
from: 0, to: 100 - Array:
from: [60, 0.5], to: [72, 1.0]- multiple values - Hash:
from: {pitch: 60}, to: {pitch: 72}- named values
Parameter Combinations
Move requires enough information to calculate both step size and iteration interval. Valid combinations:
from, to, step, every- All explicitfrom, to, step, duration/till- Calculates every from steps neededfrom, to, every, duration/till- Calculates step from durationfrom, step, every, duration/till- Open-ended with time limit
Interpolation
- Linear (default):
function: proc { |ratio| ratio } - Ease-in:
function: proc { |ratio| ratio ** 2 } - Ease-out:
function: proc { |ratio| 1 - (1 - ratio) ** 2 } - Custom: Any proc mapping [0..1] to [0..1]
Applications
- Pitch bends and glissandi
- Volume fades and swells
- Filter sweeps and modulation
- Tempo changes and rubato
- Multi-parameter automation
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 1072 def move(every: nil, from: nil, to: nil, step: nil, duration: nil, till: nil, function: nil, right_open: nil, on_stop: nil, after_bars: nil, after: nil, &block) control = _move every: every, from: from, to: to, step: step, duration: duration, till: till, function: function, right_open: right_open, on_stop: on_stop, after_bars: , after: after, &block @moving << control control.on_stop do @moving.delete control end control end |
#now { ... } ⇒ EventHandler
Schedules block at current position (immediate execution on next tick).
Returns a control object whose .stop method cancels execution
if the block hasn't run yet (e.g., scheduled at current position
but not yet reached by the tick loop).
598 599 600 601 602 603 604 605 606 607 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 598 def now(&block) control = EventHandler.new @event_handlers.last @event_handlers.push control _numeric_at position, control, skip_if_stopped: true, &block @event_handlers.pop control end |
#on(event) {|*args| ... } ⇒ void
This method returns an undefined value.
Subscribes to custom event.
Registers a handler for custom events in the sequencer's pub/sub system. Events can be launched from scheduled blocks and handled at the sequencer level or at specific control levels. Supports hierarchical event delegation.
529 530 531 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 529 def on(event, &block) @event_handlers.last.on event, &block end |
#on_debug_at { ... } ⇒ void
This method returns an undefined value.
Registers debug callback for scheduled events.
Callback is invoked when debug logging is enabled (see do_log parameter in initialize). Called before executing each scheduled event, allowing inspection of sequencer state at event execution time.
358 359 360 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 358 def on_debug_at(&block) @on_debug_at << Musa::Extension::SmartProcBinder::SmartProcBinder.new(block) end |
#on_error {|error| ... } ⇒ void
This method returns an undefined value.
Registers error callback.
Callback is invoked when an error occurs during event execution. The error is logged and passed to all registered error handlers. Handlers receive the exception object and can process or report it.
389 390 391 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 389 def on_error(&block) @on_error << Musa::Extension::SmartProcBinder::SmartProcBinder.new(block) end |
#on_fast_forward {|is_starting| ... } ⇒ void
This method returns an undefined value.
Registers fast-forward callback (when jumping over events).
Callback is invoked when position is changed directly (via position=), causing the sequencer to skip ahead. Called twice: once with true when fast-forward begins, and once with false when it completes. Events between old and new positions are executed during fast-forward.
424 425 426 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 424 def on_fast_forward(&block) @on_fast_forward << Musa::Extension::SmartProcBinder::SmartProcBinder.new(block) end |
#play(serie, mode: nil, parameter: nil, on_stop: nil, after_bars: nil, after: nil, context: nil, **mode_args) {|element, control| ... } ⇒ PlayControl
Plays series over time.
Consumes series values sequentially, evaluating each element to determine operation and scheduling continuation. Supports pause/continue, nested plays, parallel plays, and event-driven continuation. Timing determined by mode.
Available Running Modes
- :wait (default): Elements with duration specify wait time before next element
- :at: Elements specify absolute positions via :at key
- :neumalang: Full Neumalang DSL with variables, commands, series, etc.
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 725 def play(serie, mode: nil, parameter: nil, on_stop: nil, after_bars: nil, after: nil, context: nil, **mode_args, &block) mode ||= :wait control = PlayControl.new @event_handlers.last, on_stop: on_stop, after_bars: , after: after @event_handlers.push control _play serie.instance, control, context, mode: mode, parameter: parameter, **mode_args, &block @event_handlers.pop @playing << control control.on_stop do @playing.delete control end control end |
#play_timed(timed_serie, at: nil, on_stop: nil, after_bars: nil, after: nil) {|values, time, started_ago, control| ... } ⇒ PlayTimedControl
Plays timed series (series with embedded timing information).
Similar to play but serie values include timing: each element specifies its
own timing via :time attribute. Unlike regular play which derives timing
from evaluation mode, play_timed uses explicit times from series data.
Timed Series Format
Each element must have:
- :time: Rational time offset from start
- :value: Actual value(s) - Hash or Array
- Optional extra attributes (passed to block)
Value Modes
- Hash mode:
{ time: 0r, value: {pitch: 60, velocity: 96} } - Array mode:
{ time: 0r, value: [60, 96] }
Mode is detected from first element and applied to entire series.
Component Tracking
Tracks last update time per component (hash key or array index) to
calculate started_ago - how long since each component changed.
845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 845 def play_timed(timed_serie, at: nil, on_stop: nil, after_bars: nil, after: nil, &block) at ||= position control = PlayTimedControl.new(@event_handlers.last, on_stop: on_stop, after_bars: , after: after) @event_handlers.push control _play_timed(timed_serie.instance, at, control, &block) @event_handlers.pop @playing << control control.on_stop do @playing.delete control end control end |
#quantize_position(position, warn: nil) ⇒ Rational
Quantizes position to tick grid (tick-based mode only).
306 307 308 309 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 306 def quantize_position(position, warn: nil) warn ||= false _quantize_position(position, warn: warn) end |
#raw_at(bar_position, force_first: nil) { ... } ⇒ nil
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Schedules block at absolute position (low-level, no control object).
616 617 618 619 620 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 616 def raw_at(, force_first: nil, &block) _raw_numeric_at .rationalize, force_first: force_first, &block nil end |
#reset ⇒ void
This method returns an undefined value.
Resets sequencer to initial state.
Clears all scheduled events, active operations, and event handlers. Resets timing to start position.
275 276 277 278 279 280 281 282 283 284 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 275 def reset @timeslots.clear @everying.clear @playing.clear @moving.clear @event_handlers = [EventHandler.new] _reset_timing end |
#run ⇒ void
This method returns an undefined value.
Executes all scheduled events until empty.
Advances time tick by tick (or position by position in tickless mode) until no events remain.
322 323 324 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 322 def run tick until empty? end |
#size ⇒ Integer
Counts total scheduled events.
289 290 291 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 289 def size @timeslots.values.sum(&:size) end |
#to_s ⇒ Object
1105 1106 1107 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 1105 def to_s super + ": position=#{position}" end |
#wait(bars_delay, debug: nil) { ... } ⇒ EventHandler
Schedules block relative to current position.
Returns a control object whose .stop method cancels execution:
the block will not run if the control is stopped before its scheduled
position. For series-based delays, .stop also prevents further
elements from being scheduled.
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 567 def wait(, debug: nil, &block) debug ||= false control = EventHandler.new @event_handlers.last @event_handlers.push control if .is_a? Numeric _numeric_at position + .rationalize, control, debug: debug, skip_if_stopped: true, &block else = Series::S(*) if .is_a?(Array) = .instance if _serie_at .with { |delay| position + delay }, control, debug: debug, &block end @event_handlers.pop control end |