Class: Musa::Sequencer::BaseSequencer::PlayControl

Inherits:
EventHandler show all
Defined in:
lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb

Overview

Control object for play operations.

Manages play lifecycle including pause/continue and after callbacks. Extends EventHandler to support custom events and hierarchical control.

Pause/Continue

When paused:

  1. Stores continuation parameters (series state, evaluator, etc.)
  2. Stops processing series
  3. Awaits continue call

When continued:

  1. Restores continuation parameters
  2. Resumes play from stored position

After Callbacks

Executed after play completes, with optional delay in bars.

This is what #play RETURNS, so stop, after, on_stop, pause and continue are the caller's handle on a running serie. Only its construction is internal.

Examples:

Basic play control

seq = Musa::Sequencer::BaseSequencer.new(4, 24)

# play consumes datasets, not bare values: each element is a hash, and
# its :duration is how far the player advances before the next one.
series = Musa::Series::Constructors.S({ pitch: 60, duration: 1r },
                                      { pitch: 62, duration: 1r })
played_notes = []
after_executed = []

control = seq.play(series) do |pitch:, duration:|
  played_notes << { pitch: pitch, position: seq.position }
end

control.after(2r) { after_executed << seq.position }

seq.run
played_notes.collect { |n| n[:pitch] }  # => [60, 62]
# after_executed holds the position 2 bars after play completes

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent, on_stop: nil, after_bars: nil, after: nil) ⇒ PlayControl

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.

Creates play control with optional callbacks.

Parameters:

  • parent (EventHandler)

    parent event handler

  • on_stop (Proc, nil) (defaults to: nil)

    stop callback (fires on any termination)

  • after_bars (Rational, nil) (defaults to: nil)

    delay for after callback

  • after (Proc, nil) (defaults to: nil)

    after callback block (fires only on natural termination)



301
302
303
304
305
306
307
308
309
# File 'lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb', line 301

def initialize(parent, on_stop: nil, after_bars: nil, after: nil)
  super parent

  @do_on_stop = []
  @do_after = []

  @do_on_stop << on_stop if on_stop
  after(after_bars, &after) if after
end

Instance Attribute Details

#do_afterArray<Hash> (readonly)

Returns after callbacks with delays (only on natural termination).

Returns:

  • (Array<Hash>)

    after callbacks with delays (only on natural termination)



291
292
293
# File 'lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb', line 291

def do_after
  @do_after
end

#do_on_stopArray<Proc> (readonly)

Returns callbacks when play stops (any reason, including manual stop).

Returns:

  • (Array<Proc>)

    callbacks when play stops (any reason, including manual stop)



289
290
291
# File 'lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb', line 289

def do_on_stop
  @do_on_stop
end

Instance Method Details

#_finished!(sequencer, position) ⇒ 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.

Records that the play has ended, and where.

Only the termination branch of _play calls this. It exists because the control can reach the caller already dead -- see #after.

Parameters:

  • sequencer (BaseSequencer)

    the sequencer that was playing

  • position (Rational)

    where the play ended



406
407
408
409
# File 'lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb', line 406

def _finished!(sequencer, position)
  @finished_sequencer = sequencer
  @finished_at = position
end

#after(bars = nil) { ... } ⇒ 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.

Registers callback to execute after play completes naturally (series exhausted). Not called on manual stop.

Examples:

Delayed callback

control = seq.play(series) { |pitch:, duration:| }
control.after(4r) { puts "4 bars after play ends" }

Parameters:

  • bars (Numeric, nil) (defaults to: nil)

    delay in bars after completion (default: 0)

Yields:

  • after callback block



424
425
426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb', line 424

def after(bars = nil, &block)
  bars ||= 0

  # Already over: `after(2)` means "two bars after it ends", and it ended
  # at @finished_at, so that is where this goes. Registering it in the
  # list would be registering it after the only moment anything reads the
  # list, which is what used to happen to every play that resolved within
  # one instant -- a chord written as a serie of `forward_duration: 0`
  # elements, a lone event with no duration -- and the callback simply
  # never ran (issue #84).
  return @finished_sequencer.at(@finished_at + bars.rationalize, &block) if @finished_at

  @do_after << { bars: bars.rationalize, block: block }
end

#continuevoid

This method returns an undefined value.

Resumes a paused serie from where it stopped.

See Also:



375
376
377
378
# File 'lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb', line 375

def continue
  super
  @continuation_sequencer&.continuation_play(@continuation_parameters)
end

#on_stop { ... } ⇒ 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.

Registers callback for when play stops (any reason, including manual stop).

Yields:

  • stop callback block



387
388
389
390
391
392
393
# File 'lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb', line 387

def on_stop(&block)
  # Already over: on_stop means "when it terminates", and it has. The
  # normal path calls these directly too, rather than scheduling them.
  return block.call if @finished_at

  @do_on_stop << block
end

#pausevoid

This method returns an undefined value.

Suspends the serie where it is, keeping its place.

Unlike EventHandler#stop, nothing is torn down: the continuation is kept, so #continue resumes from the next element rather than from the start. Neither on_stop nor after fires -- a pause is not a termination.

Examples:

seq = Musa::Sequencer::BaseSequencer.new(4, 24)
played = []

control = seq.play(S({ v: 1, duration: 1r }, { v: 2, duration: 1r },
                     { v: 3, duration: 1r }, { v: 4, duration: 1r },
                     { v: 5, duration: 1r })) { |v:| played << v }

seq.at(3) { control.pause }
seq.run
played  # => [1, 2, 3]

control.continue
seq.run
played  # => [1, 2, 3, 4, 5]


336
337
338
# File 'lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb', line 336

def pause
  @paused = true
end

#store_continuation(sequencer:, serie:, neumalang_context:, mode:, decoder:, play_eval:, mode_args:) ⇒ 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.

Stores state for continue operation.

Saves all parameters needed to resume play from current position. Called automatically by _play when paused.

Parameters:

  • sequencer (BaseSequencer)

    sequencer instance

  • serie (Series)

    series being played

  • neumalang_context (Object, nil)

    neumalang context

  • mode (Symbol, nil)

    evaluation mode

  • decoder (Object, nil)

    decoder

  • play_eval (PlayEval)

    evaluator

  • mode_args (Hash)

    mode arguments



356
357
358
359
360
361
362
363
364
365
366
# File 'lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb', line 356

def store_continuation(sequencer:, serie:, neumalang_context:, mode:, decoder:, play_eval:, mode_args:)
  @continuation_sequencer = sequencer
  @continuation_parameters = {
    serie: serie,
    control: self,
    neumalang_context: neumalang_context,
    mode: mode,
    decoder: decoder,
    play_eval: play_eval,
    mode_args: mode_args }
end