class Topaz::MIDIClockInput

Trigger an event based on received midi clock messages

Attributes

clock[R]
listening[R]
listening?[R]
running[R]
running?[R]

Public Class Methods

new(input, options = {}) click to toggle source

@param [UniMIDI::Input] input @param [Hash] options @option options [Clock::Event] :event @option options [Boolean] :midi_transport Whether to respect start/stop MIDI commands from a MIDI input

# File lib/topaz/midi_clock_input.rb, line 16
def initialize(input, options = {})
  @event = options[:event]
  @use_transport = !!options[:midi_transport]
  @tick_counter = 0
  @pause = false
  @listening = false
  @running = false
  @tempo_calculator = TempoCalculator.new
  @tick_threshold = interval_to_ticks(options.fetch(:interval, 4))

  initialize_listener(input)
end

Public Instance Methods

interval() click to toggle source

Return the interval at which the tick event is fired @return [Fixnum]

# File lib/topaz/midi_clock_input.rb, line 85
def interval
  ticks_to_interval(@tick_threshold)
end
interval=(interval) click to toggle source

Change the clock interval Defaults to 4, which means click once every 24 ticks or one quarter note (per MIDI spec). Therefore, to fire the on_tick event twice as often, pass 8

 1 = whole note
 2 = half note
 4 = quarter note
 6 = dotted quarter
 8 = eighth note
16 = sixteenth note
etc

@param [Fixnum] interval @return [Fixnum]

# File lib/topaz/midi_clock_input.rb, line 79
def interval=(interval)
  @tick_threshold = interval_to_ticks(interval)
end
join() click to toggle source

Join the listener thread @return [MIDIInputClock] self

# File lib/topaz/midi_clock_input.rb, line 60
def join
  @listener.join
  self
end
start(options = {}) click to toggle source

Start the listener @param [Hash] options @option options [Boolean] :background Whether to run the listener in a background process @option options [Boolean] :focus (or :blocking) Whether to run the listener in a foreground process @return [MIDIInputClock] self

# File lib/topaz/midi_clock_input.rb, line 40
def start(options = {})
  @listening = true
  blocking = options[:focus] || options[:blocking]
  background = !blocking unless blocking.nil?
  background = options[:background] if background.nil?
  background = false if background.nil?
  @listener.start(:background => background)
  self
end
stop(*a) click to toggle source

Stop the listener @return [MIDIInputClock] self

# File lib/topaz/midi_clock_input.rb, line 52
def stop(*a)
  @listening = false
  @listener.stop
  self
end
tempo() click to toggle source

This will return a calculated tempo @return [Fixnum]

# File lib/topaz/midi_clock_input.rb, line 31
def tempo
  @tempo_calculator.calculate
end

Private Instance Methods

advance() click to toggle source

Advance the tick counter @return [Fixnum]

# File lib/topaz/midi_clock_input.rb, line 152
def advance
  @tick_counter += 1
end
handle_clock_message(message) click to toggle source

Handle a received clock message @param [Hash] message @return [Fixnum] The current counter

# File lib/topaz/midi_clock_input.rb, line 141
def handle_clock_message(message)
  if @running || !@use_transport
    @running ||= true
    thru
    log(message)
    tick? ? tick : advance
  end
end
handle_start_message() click to toggle source

Handle a received start message @return [Boolean]

# File lib/topaz/midi_clock_input.rb, line 120
def handle_start_message
  @running = true
  if !@event.nil?
    @event.do_start
    true
  end
end
handle_stop_message() click to toggle source

Handle a received stop message @return [Boolean]

# File lib/topaz/midi_clock_input.rb, line 130
def handle_stop_message
  @running = false
  if !@event.nil?
    @event.do_stop
    true
  end
end
initialize_listener(input) click to toggle source

Initialize the MIDI input listener @param [UniMIDI::Input] input @return [MIDIEye::Listener]

# File lib/topaz/midi_clock_input.rb, line 110
def initialize_listener(input)
  @listener = MIDIEye::Listener.new(input)
  @listener.listen_for(:name => "Clock") { |message| handle_clock_message(message) }
  @listener.listen_for(:name => "Start") { handle_start_message }
  @listener.listen_for(:name => "Stop") { handle_stop_message }
  @listener
end
interval_to_ticks(interval) click to toggle source

Convert a note interval to number of ticks @param [Fixnum] interval @param [Fixnum]

# File lib/topaz/midi_clock_input.rb, line 94
def interval_to_ticks(interval)
  per_qn = interval / 4
  24 / per_qn
end
log(message) click to toggle source

Log the timestamp of a message for tempo calculation @param [Hash] message @return [Array<Fixnum>]

# File lib/topaz/midi_clock_input.rb, line 159
def log(message)
  time = message[:timestamp] / 1000.0
  @tempo_calculator.timestamps << time
end
thru() click to toggle source

Fire the clock event (this results in MIDI output sending clock, thus thru) @return [Boolean]

# File lib/topaz/midi_clock_input.rb, line 167
def thru
  if !@event.nil?
    @event.do_clock
    true
  end
end
tick() click to toggle source

Fire the tick event @return [Boolean]

# File lib/topaz/midi_clock_input.rb, line 176
def tick
  @tick_counter = 0
  if !@event.nil? && !@pause
    @event.do_tick
    true
  end
end
tick?() click to toggle source

Should the tick event be fired given the current state? @return [Boolean]

# File lib/topaz/midi_clock_input.rb, line 186
def tick?
  @tick_counter >= @tick_threshold - 1
end
ticks_to_interval(ticks) click to toggle source

Convert a number of ticks to a note interval @param [Fixnum] ticks @param [Fixnum]

# File lib/topaz/midi_clock_input.rb, line 102
def ticks_to_interval(ticks)
  note_value = 24 / ticks
  4 * note_value
end