module SurfaceMaster module Orbit # Low-level interface to Numark Orbit wireless MIDI control surface. class Device < SurfaceMaster::Device include MIDICodes def initialize(opts = nil) @name = "Numark ORBIT" super(opts) reset! end def reset! # Skip Sysex begin, vendor header, command code, aaaaand sysex end -- # this will let us compare command vs. response payloads to determine # if the state of the device is what we want. Of course, sometimes it # lies, but we can't do much about that. expected_state = MAPPINGS[6..-2] sysex!(MAPPINGS) sleep 0.1 sysex!(READ_STATE) current_state = nil started_at = Time.now.to_f attempts = 1 loop do # TODO: It appears that accessing `buffer` is HIGHLY unsafe! We may # TODO: be OK if everyone's waiting on us to come back from this # TODO: method before they begin clamoring for input, but that's just # TODO: a guess right now. if @input.buffer.length == 0 elapsed = Time.now.to_f - started_at if elapsed > 4.0 logger.warn { "Timeout fetching state of Numark Orbit!" } break elsif elapsed > (1.0 * attempts) logger.warn { "Asking for current state of Numark Orbit again!" } attempts += 1 @output.puts(READ_STATE) next end sleep 0.01 next end raw = @input.gets current_state = raw.find { |ii| ii[:data][0] == 0xF0 } break unless current_state.nil? end return unless current_state current_state = current_state[:data][6..-2].dup logger.debug { "Got state info from Numark Orbit!" } if expected_state != current_state logger.error { "UH OH! Numark Orbit state didn't match what we sent!" } logger.error { "Expected: #{expected_state.inspect}" } logger.error { "Got: #{current_state.inspect}" } else logger.debug { "Your Numark Orbit should be in the right state now." } end end def read super .map { |input| decode_input(input) } .compact end protected MAPPINGS = [0x03, 0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x08, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x09, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x08, 0x00, 0x01, 0x0C, 0x00, 0x01, 0x01, 0x00, 0x01, 0x05, 0x00, 0x01, 0x09, 0x00, 0x01, 0x0D, 0x00, 0x01, 0x02, 0x00, 0x01, 0x06, 0x00, 0x01, 0x0A, 0x00, 0x01, 0x0E, 0x00, 0x01, 0x03, 0x00, 0x01, 0x07, 0x00, 0x01, 0x0B, 0x00, 0x01, 0x0F, 0x00, 0x02, 0x00, 0x00, 0x02, 0x04, 0x00, 0x02, 0x08, 0x00, 0x02, 0x0C, 0x00, 0x02, 0x01, 0x00, 0x02, 0x05, 0x00, 0x02, 0x09, 0x00, 0x02, 0x0D, 0x00, 0x02, 0x02, 0x00, 0x02, 0x06, 0x00, 0x02, 0x0A, 0x00, 0x02, 0x0E, 0x00, 0x02, 0x03, 0x00, 0x02, 0x07, 0x00, 0x02, 0x0B, 0x00, 0x02, 0x0F, 0x00, 0x03, 0x00, 0x00, 0x03, 0x04, 0x00, 0x03, 0x08, 0x00, 0x03, 0x0C, 0x00, 0x03, 0x01, 0x00, 0x03, 0x05, 0x00, 0x03, 0x09, 0x00, 0x03, 0x0D, 0x00, 0x03, 0x02, 0x00, 0x03, 0x06, 0x00, 0x03, 0x0A, 0x00, 0x03, 0x0E, 0x00, 0x03, 0x03, 0x00, 0x03, 0x07, 0x00, 0x03, 0x0B, 0x00, 0x03, 0x0F, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x01, 0x03, 0x01, 0x00, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, 0x02, 0x00, 0x03, 0x01, 0x03, 0x02, 0x03, 0x03, 0x03, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0D, 0x00] READ_STATE = [0x01, 0x00, 0x00] def sysex_prefix; @sysex_prefix ||= super + [0x00, 0x01, 0x3F, 0x2B]; end def decode_shoulder(decoded, note, _velocity) decoded[:control] = decoded[:control].merge(SurfaceMaster::Orbit::Device::SHOULDERS[note]) decoded end def decode_pad(decoded, note, _velocity) decoded[:control] = decoded[:control].merge(button: note + 1) decoded end def decode_knob(decoded, note, velocity) decoded[:control] = decoded[:control].merge(bank: note + 1) decoded[:value] = velocity decoded end def decode_control(decoded, note, velocity) decoded = decoded.merge(SurfaceMaster::Orbit::Device::SELECTORS[note]) decoded[:control] = { button: velocity } decoded end def decode_accelerometer(decoded, _note, velocity) decoded[:value] = velocity decoded end def enrich_decoded_message(decoded, note, velocity, timestamp) case decoded[:type] when :shoulder then decoded = decode_shoulder(decoded, note, velocity) when :pad then decoded = decode_pad(decoded, note, velocity) when :vknob then decoded = decode_knob(decoded, note, velocity) when :accelerometer then decoded = decode_accelerometer(decoded, note, velocity) else decoded = decode_control(decoded, note, velocity) end decoded[:timestamp] = timestamp decoded end def decode_input(input) note = input[:note] velocity = input[:velocity] code_high = input[:code] & 0xF0 code_low = input[:code] & 0x0F raw = SurfaceMaster::Orbit::Device::CONTROLS[code_high] raw = raw[code_low] if raw raw = enrich_decoded_message(raw.dup, note, velocity, input[:timestamp]) if raw raw end end end end