Class: Musa::Clock::Timer

Inherits:
Object show all
Defined in:
lib/musa-dsl/transport/timer.rb

Overview

High-precision timer for generating regular ticks.

Timer uses Ruby's monotonic clock (Process::CLOCK_MONOTONIC) for drift-free timing. It compensates for processing delays and reports when the system cannot keep up with the requested tick rate.

Precision Features

  • Monotonic clock: Immune to system time changes
  • Drift compensation: Calculates exact next tick time
  • Overload detection: Reports delayed ticks when processing is slow
  • Correction parameter: Fine-tune timing for specific systems

Usage Pattern

Timer is typically used internally by TimerClock, not directly. It runs in a loop, yielding for each tick and managing precise sleep intervals.

Timing Algorithm

  1. Record next expected tick time
  2. Yield (caller processes tick)
  3. Add period to next_moment
  4. Calculate sleep time = (next_moment + correction) - current_time
  5. Sleep if positive, warn if negative (delayed)

Examples:

Internal use by TimerClock

timer = Timer.new(0.02083, logger: logger)  # ~48 ticks/second
timer.run { sequencer.tick }

See Also:

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tick_period_in_seconds, correction: nil, stop: nil, delayed_ticks_error: nil, logger: nil, do_log: nil) ⇒ Timer

Creates a new precision timer.

Examples:

120 BPM, 24 ticks per beat

period = 60.0 / (120 * 24)  # 0.02083 seconds
timer = Timer.new(period, logger: logger)


52
53
54
55
56
57
58
59
60
# File 'lib/musa-dsl/transport/timer.rb', line 52

def initialize(tick_period_in_seconds, correction: nil, stop: nil, delayed_ticks_error: nil, logger: nil, do_log: nil)
  @period = tick_period_in_seconds.rationalize
  @correction = (correction || 0r).rationalize
  @stop = stop || false

  @delayed_ticks_error = delayed_ticks_error || 1.0
  @logger = logger
  @do_log = do_log
end

Instance Attribute Details

#periodRational

The period between ticks in seconds.



38
39
40
# File 'lib/musa-dsl/transport/timer.rb', line 38

def period
  @period
end

Instance Method Details

#continuevoid

Note:

Resets timing baseline to prevent tick accumulation

This method returns an undefined value.

Resumes the timer after being stopped.

Resets the next tick moment to avoid a burst of catchup ticks, then wakes the timer thread.

See Also:



131
132
133
134
135
# File 'lib/musa-dsl/transport/timer.rb', line 131

def continue
  @stop = false
  @next_moment = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  @thread.run
end

#run { ... } ⇒ void

Note:

This method blocks the current thread

Note:

Uses monotonic clock for drift-free timing

This method returns an undefined value.

Runs the timer loop, yielding for each tick.

This method blocks and runs indefinitely until stopped. For each tick:

  1. Yields to caller if not stopped
  2. Calculates next tick time
  3. Sleeps precisely until next tick
  4. Logs warnings if timing cannot be maintained

When stopped (@stop = true), the thread sleeps until #continue is called.

Yields:

  • Called once per tick for processing



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/musa-dsl/transport/timer.rb', line 77

def run
  @thread = Thread.current

  @next_moment = Process.clock_gettime(Process::CLOCK_MONOTONIC)

  loop do
    unless @stop
      # Process the tick
      yield

      # Calculate next tick moment (compensates for processing time)
      @next_moment += @period
      to_sleep = (@next_moment + @correction) - Process.clock_gettime(Process::CLOCK_MONOTONIC)

      # Log timing issues if enabled
      if @do_log && to_sleep.negative? & @logger
        tick_errors = -to_sleep / @period
        if tick_errors >= @delayed_ticks_error
          @logger.error "Timer delayed #{tick_errors.round(2)} ticks (#{-to_sleep.round(3)}s)"
        else
          @logger.warn "Timer delayed #{tick_errors.round(2)} ticks (#{-to_sleep.round(3)}s)"
        end
      end

      # Sleep precisely until next tick (if not already late)
      sleep to_sleep if to_sleep > 0.0
    end

    # When stopped, sleep thread until continue is called
    sleep if @stop
  end
end

#stopvoid

This method returns an undefined value.

Pauses the timer without terminating the loop.

The timer thread sleeps until #continue is called. Ticks are not generated while stopped.

See Also:



118
119
120
# File 'lib/musa-dsl/transport/timer.rb', line 118

def stop
  @stop = true
end