Class: Musa::Clock::ExternalTickClock

Inherits:
Clock show all
Defined in:
lib/musa-dsl/transport/external-tick-clock.rb

Overview

Clock driven by external tick() calls for integration and testing.

ExternalTickClock doesn't generate its own ticks. Instead, ticks are triggered manually by calling the #tick method.

Activation Model

IMPORTANT: ExternalTickClock requires manual tick generation. After calling transport.start (which returns immediately, doesn't block), you must call clock.tick() repeatedly from your external system to generate ticks.

This activation model is appropriate for:

  • Testing: Precise control over timing for step-by-step debugging
  • Game engine integration: Game loop controls tick timing
  • Frame-based systems: One tick per frame or custom logic
  • Offline rendering: Generate ticks as fast as needed

Operation

  1. Call #run to initialize (doesn't block, returns immediately)
  2. Call #tick repeatedly to generate ticks manually
  3. Call #terminate when done

Differences from Other Clocks

Unlike TimerClock and InputMidiClock, ExternalTickClock's run method does not block - it returns immediately. This allows your external system to control the timing loop.

Examples:

Manual stepping for testing

clock = ExternalTickClock.new
transport = Transport.new(clock)

# Schedule some events. Note the parentheses: `at 1 { ... }` is a syntax
# error in Ruby, since the braces would bind to the argument.
transport.sequencer.at(1) { puts "Bar 1" }
transport.sequencer.at(2) { puts "Bar 2" }

# Start in background (non-blocking for ExternalTickClock)
thread = Thread.new { transport.start }
sleep 0.1  # Let transport initialize

# Generate ticks manually. The sequencer starts one tick before bar 1,
# so the very first tick is bar 1 -- and bar 2 is 96 ticks further on.
clock.tick  # => "Bar 1"
95.times { clock.tick }
clock.tick  # => "Bar 2"

transport.stop
thread.join

Integration with game loop

clock = ExternalTickClock.new
transport = Transport.new(clock)
thread = Thread.new { transport.start }
sleep 0.1

# In game update loop:
def update(delta_time)
  if should_tick?(delta_time)
    clock.tick  # Advance sequencer by one tick
  end
end

See Also:

Instance Method Summary collapse

Constructor Details

#initialize(do_log: nil) ⇒ ExternalTickClock

Creates a new externally-controlled clock.

Parameters:

  • do_log (Boolean, nil) (defaults to: nil)

    enable logging



77
78
79
80
81
82
83
# File 'lib/musa-dsl/transport/external-tick-clock.rb', line 77

def initialize(do_log: nil)
  do_log ||= false

  super()

  @do_log = do_log
end

Instance Method Details

#run { ... } ⇒ void

Note:

This method does NOT block

This method returns an undefined value.

Initializes the clock (non-blocking).

Unlike other clocks, this method doesn't block. It stores the block and calls on_start callbacks, then returns immediately. Ticks are generated by calling #tick.

Yields:

  • Called for each tick triggered by #tick



95
96
97
98
99
100
# File 'lib/musa-dsl/transport/external-tick-clock.rb', line 95

def run(&block)
  @stopped = false
  @on_start.each(&:call)
  @run = true
  @block = block
end

#stopvoid

This method returns an undefined value.

Stops the clock and fires on_stop callbacks.



120
121
122
123
# File 'lib/musa-dsl/transport/external-tick-clock.rb', line 120

def stop
  @run = false
  super
end

#terminatevoid

This method returns an undefined value.

Terminates the clock.

Delegates to #stop which fires on_stop callbacks and sets @run to false.



130
131
132
# File 'lib/musa-dsl/transport/external-tick-clock.rb', line 130

def terminate
  stop
end

#tickvoid

Note:

Only works if #run has been called

Note:

Thread-safe for integration with external event loops

This method returns an undefined value.

Generates one tick manually.

If the clock is running, calls the registered block (typically sequencer.tick). Has no effect if clock is not running.



111
112
113
114
115
# File 'lib/musa-dsl/transport/external-tick-clock.rb', line 111

def tick
  if @run
    @block.call if @block
  end
end