Class: Musa::Clock::InputMidiClock
- Defined in:
- lib/musa-dsl/transport/input-midi-clock.rb
Overview
Clock synchronized to external MIDI Clock messages.
InputMidiClock receives MIDI Clock, Start, Stop, Continue, and Song Position messages from an external source (typically a DAW or hardware sequencer) and generates ticks synchronized to that source.
Activation Model
IMPORTANT: InputMidiClock requires external MIDI activation. After calling
transport.start (which blocks), the clock waits for MIDI "Start" (0xFA)
message from the external source to begin generating ticks.
This activation model is appropriate for:
- DAW synchronization: DAW controls start/stop via MIDI Clock
- Hardware sequencer sync: External device controls timing
- Multi-device setups: One master device controls all slaves
MIDI Clock Protocol
- Clock (0xF8): Sent 24 times per quarter note (generates ticks when started)
- Start (0xFA): Begin playing from start (activates tick generation)
- Stop (0xFC): Stop playing (halts tick generation)
- Continue (0xFB): Resume from current position
- Song Position Pointer (0xF2): Jump to specific position
Features
- Automatic synchronization to external MIDI Clock
- Position changes via Song Position Pointer
- Start/Stop/Continue handling
- Performance monitoring (time_table for tick processing times)
- Graceful handling of missing input (waits until assigned)
Special Sequences
The clock handles the common sequence: Stop + Song Position + Continue as a position change while running, avoiding unnecessary stop/start cycles.
Performance Monitoring
The time_table tracks processing time per tick in milliseconds, useful for detecting performance issues.
Instance Attribute Summary collapse
-
#input ⇒ MIDICommunications::Input?
Current MIDI input port.
-
#time_table ⇒ Array<Integer>
readonly
Performance timing histogram.
Instance Method Summary collapse
-
#initialize(input = nil, logger: nil, do_log: nil) ⇒ InputMidiClock
constructor
Creates a new MIDI Clock synchronized clock.
-
#run { ... } ⇒ void
Runs the MIDI Clock processing loop.
-
#terminate ⇒ void
Terminates the MIDI Clock processing loop.
Constructor Details
#initialize(input = nil, logger: nil, do_log: nil) ⇒ InputMidiClock
Creates a new MIDI Clock synchronized clock.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/musa-dsl/transport/input-midi-clock.rb', line 82 def initialize(input = nil, logger: nil, do_log: nil) do_log ||= false super() @logger = logger self.input = input if logger @logger = logger else @logger = Musa::Logger::Logger.new @logger.debug! if do_log end @time_table = [] @midi_parser = MIDIParser.new end |
Instance Attribute Details
#input ⇒ MIDICommunications::Input?
Current MIDI input port.
105 106 107 |
# File 'lib/musa-dsl/transport/input-midi-clock.rb', line 105 def input @input end |
#time_table ⇒ Array<Integer> (readonly)
Performance timing histogram.
Maps processing time in milliseconds to tick count.
115 116 117 |
# File 'lib/musa-dsl/transport/input-midi-clock.rb', line 115 def time_table @time_table end |
Instance Method Details
#run { ... } ⇒ void
This method blocks until #terminate is called
Waits if no input assigned
This method returns an undefined value.
Runs the MIDI Clock processing loop.
This method blocks and processes incoming MIDI messages, generating ticks in response to MIDI Clock messages. If no input is assigned, it waits until one is assigned via #input=.
Message Handling
- Clock: Yields (generates tick) if started
- Start: Triggers on_start callbacks
- Stop: Triggers on_stop callbacks
- Continue: Resumes (typically after Stop)
- Song Position: Triggers on_change_position
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/musa-dsl/transport/input-midi-clock.rb', line 147 def run @run = true while @run if @input # Read raw MIDI messages from input port = @input.gets else # No input assigned yet - wait for assignment @logger.warn('InputMidiClock') { 'Waiting for clock input MIDI port' } @waiting_for_input = Thread.current sleep # Wait until input= wakes us @waiting_for_input = nil if @input @logger.info('InputMidiClock') { "Assigned clock input MIDI port '#{@input.name}'" } else @logger.warn('InputMidiClock') { 'Clock input MIDI port not found' } end end # Parse raw MIDI bytes into message objects = [] stop_index = nil &.each do || mm = @midi_parser.parse [:data] if mm if mm.is_a? Array mm.each do |m| stop_index = .size if m.name == 'Stop' && !stop_index << m end else stop_index = .size if mm.name == 'Stop' && !stop_index << mm end end end size = .size index = 0 while index < size if index == stop_index && size >= index + 3 && [index + 1].name == 'Song Position Pointer' && [index + 2].name == 'Continue' @logger.debug('InputMidiClock') { 'processing Stop + Song Position Pointer + Continue...' } process_start unless @started [index + 1] do yield if block_given? end index += 2 @logger.debug('InputMidiClock') { 'processing Stop + Song Position Pointer + Continue... done' } else [index] do yield if block_given? end end index += 1 end Thread.pass end end |
#terminate ⇒ void
This method returns an undefined value.
Terminates the MIDI Clock processing loop.
224 225 226 |
# File 'lib/musa-dsl/transport/input-midi-clock.rb', line 224 def terminate @run = false end |