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)
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 248 249 250 251 252 253 254 255 256 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 208 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).
150 151 152 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 150 def @beats_per_bar end |
#everying ⇒ Array<EveryControl> (readonly)
Returns active every loops.
162 163 164 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 162 def @everying end |
#logger ⇒ Musa::Logger::Logger (readonly)
Returns sequencer logger.
171 172 173 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 171 def logger @logger end |
#moving ⇒ Array<MoveControl> (readonly)
Returns active move operations.
168 169 170 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 168 def moving @moving end |
#offset ⇒ Rational (readonly)
Returns time offset for position calculations.
156 157 158 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 156 def offset @offset end |
#playing ⇒ Array<PlayControl, PlayTimedControl> (readonly)
Returns active play operations.
165 166 167 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 165 def @playing end |
#running_position ⇒ Rational (readonly)
Returns current running position.
159 160 161 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 159 def running_position @running_position end |
#ticks_per_beat ⇒ Rational? (readonly)
Returns ticks per beat (tick-based mode only).
153 154 155 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 153 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.
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 678 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.
493 494 495 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 493 def before_tick(&block) @before_tick << Musa::Extension::SmartProcBinder::SmartProcBinder.new(block) end |
#continuation_play(parameters) ⇒ Object
788 789 790 791 792 793 794 795 796 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 788 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
1195 1196 1197 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 1195 def debug(msg = nil) @logger.debug { msg || '...' } end |
#empty? ⇒ Boolean
Checks if sequencer has no scheduled events.
308 309 310 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 308 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.
342 343 344 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 342 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
983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 983 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.
580 581 582 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 580 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
1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 1166 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).
633 634 635 636 637 638 639 640 641 642 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 633 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.
564 565 566 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 564 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.
374 375 376 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 374 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.
408 409 410 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 408 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.
449 450 451 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 449 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.
760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 760 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.
892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 892 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).
318 319 320 321 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 318 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).
651 652 653 654 655 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 651 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.
287 288 289 290 291 292 293 294 295 296 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 287 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.
334 335 336 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 334 def run tick until empty? end |
#size ⇒ Integer
Counts total scheduled events.
301 302 303 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 301 def size @timeslots.values.sum(&:size) end |
#to_s ⇒ Object
1199 1200 1201 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 1199 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.
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 |
# File 'lib/musa-dsl/sequencer/base-sequencer.rb', line 602 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 |