diff --git a/usb/src/dev_handler.rs b/usb/src/dev_handler.rs index b39ea2b..bc17e0c 100644 --- a/usb/src/dev_handler.rs +++ b/usb/src/dev_handler.rs @@ -10,7 +10,10 @@ use tokio::sync::mpsc; use pod_core::midi_io::{MidiIn, MidiOut}; use crate::devices::UsbDevice; use crate::endpoint::{Endpoint, find_endpoint}; +use crate::framer::{BoxedInFramer, BoxedOutFramer, InFramer}; use crate::line6::line6_read_serial; +use crate::midi_framer::new_usb_midi_framer; +use crate::podxt_framer::new_pod_xt_framer; use crate::usb::{DeviceHandle, SubmittedTransfer, Transfer, TransferCommand}; use crate::util::usb_address_string; @@ -36,6 +39,7 @@ pub struct DeviceInner { closed: Arc, read: SubmittedTransfer, kernel_state: DevOpenState, + out_framer: BoxedOutFramer } pub struct DeviceInput { @@ -141,10 +145,7 @@ impl DeviceInner { } let closed = Arc::new(AtomicBool::new(false)); - - const LEN: usize = 1024; - let mut read_buffer = [0u8; LEN]; - let mut read_offset = 0; + let (mut framer, out_framer) = Self::init_framers(&handle, &read_ep); let mut read_transfer = Transfer::new_bulk(&handle, read_ep.address, 1024); read_transfer.set_timeout(READ_DURATION); @@ -159,48 +160,18 @@ impl DeviceInner { // read timed out, continue return TransferCommand::Resubmit } - - // add received data to the read buffer at current read offset - let mut read_ptr = &mut read_buffer[read_offset .. read_offset + buf.len()]; - read_ptr.copy_from_slice(buf); - trace!("<< {:02x?} len={}", &read_ptr, read_ptr.len()); - - // go through the whole receive buffer from offset 0, check for - // for messages as send them to the MIDI thread - let process_len = read_offset + read_ptr.len(); - let mut process_buf = read_buffer[..process_len].as_mut(); - let mut process_offset = 0; - loop { - let process_buf = process_buf[process_offset .. process_len].as_mut(); - let buf = Self::find_message(process_buf); - if buf.len() > 0 { - // message found - trace!("<< msg {:02x?} len={}", &buf, buf.len()); - match tx.send(buf.to_vec()) { - Ok(_) => {} - Err(e) => { - error!("USB read thread tx failed: {}", e); - } - }; - } - process_offset += buf.len(); - if buf.len() == 0 || process_offset == process_len { break } - } - if process_offset > 0 { - // at least one message consumed - if process_buf.len() - process_offset > 0 { - // data left in the buffer, move it to the beginning of the read buffer - read_buffer.copy_within(process_offset .. process_len, 0); - read_offset = process_len - process_offset; - } else { - // all data consumed - read_offset = 0; - } - } else { - // unfinished message, adjust read offset - read_offset = process_len; - } - + trace!("<< {:02x?} len={}", &buf, buf.len()); + + framer.decode_incoming(buf).into_iter().for_each(|msg| { + // message found + trace!("<< msg {:02x?} len={}", &msg, msg.len()); + match tx.send(msg) { + Ok(_) => {} + Err(e) => { + error!("USB read thread tx failed: {}", e); + } + }; + }); TransferCommand::Resubmit }); let read = read_transfer.submit() @@ -215,7 +186,8 @@ impl DeviceInner { closed, write_ep, read, - kernel_state + kernel_state, + out_framer }) } @@ -224,19 +196,24 @@ impl DeviceInner { bail!("Device already closed"); } - let mut transfer = Transfer::new_bulk_with_data(&self.handle, self.write_ep.address, bytes); - transfer.set_timeout(WRITE_DURATION); - transfer.set_callback(|buf| { - if let Some(buf) = buf { - trace!(">> {:02x?} len={}", buf, buf.len()); - } else { - trace!(">> failed or cancelled"); - } - TransferCommand::Drop - }); - transfer.submit() - .map(|_| ()) - .map_err(|e| anyhow!("USB write transfer failed: {}", e)) + trace!(">> msg {:02x?} len={}", &bytes, bytes.len()); + + self.out_framer.encode_outgoing(bytes).into_iter().map(|msg| { + let mut transfer = Transfer::new_bulk_with_data(&self.handle, self.write_ep.address, &msg); + transfer.set_timeout(WRITE_DURATION); + transfer.set_callback(|buf| { + if let Some(buf) = buf { + trace!(">> {:02x?} len={}", buf, buf.len()); + } else { + trace!(">> failed or cancelled"); + } + TransferCommand::Drop + }); + transfer.submit() + .map(|_| ()) + .map_err(|e| anyhow!("USB write transfer failed: {}", e)) + }).collect::, _>>()?; + Ok(()) } fn close(&mut self) { @@ -303,24 +280,32 @@ impl DeviceInner { } } - fn find_message(read_ptr: &mut [u8]) -> &[u8] { - // correct PODxt lower nibble 0010 in command byte, see - // https://github.com/torvalds/linux/blob/8508fa2e7472f673edbeedf1b1d2b7a6bb898ecc/sound/usb/line6/midibuf.c#L148 - if read_ptr[0] == 0xb2 || read_ptr[0] == 0xc2 || read_ptr[0] == 0xf2 { - read_ptr[0] = read_ptr[0] & 0xf0; - } - - let sysex = read_ptr[0] == 0xf0; - if sysex { - for i in 0 .. read_ptr.len() { - if read_ptr[i] == 0xf7 { - return &read_ptr[..i + 1]; + fn init_framers(handle: &DeviceHandle, endpoint: &Endpoint) -> (BoxedInFramer, BoxedOutFramer) { + let dev = handle.device(); + let desc = dev.active_config_descriptor().ok(); + + let mut class_code = 0; + let mut sub_class_code = 0; + if let Some(desc) = desc { + for id in desc.interfaces().flat_map(|i| i.descriptors()) { + if id.interface_number() == endpoint.iface && id.setting_number() == endpoint.setting { + class_code = id.class_code(); + sub_class_code = id.sub_class_code(); } + } - return &[]; + } - } else { - return read_ptr; + match (class_code, sub_class_code) { + // class = 1 (audio), sub-class = 3 (MIDI streaming) + (1, 3) => { + info!("Using USB-MIDI framer"); + new_usb_midi_framer() + } + _ => { + info!("Using PodXT USB framer"); + new_pod_xt_framer() + } } } } diff --git a/usb/src/framer.rs b/usb/src/framer.rs new file mode 100644 index 0000000..d085c7f --- /dev/null +++ b/usb/src/framer.rs @@ -0,0 +1,10 @@ +pub trait InFramer { + fn decode_incoming(&mut self, bytes: &[u8]) -> Vec>; +} + +pub trait OutFramer { + fn encode_outgoing(&self, bytes: &[u8]) -> Vec>; +} + +pub type BoxedInFramer = Box; +pub type BoxedOutFramer = Box; diff --git a/usb/src/lib.rs b/usb/src/lib.rs index d3fba98..565e05b 100644 --- a/usb/src/lib.rs +++ b/usb/src/lib.rs @@ -5,6 +5,9 @@ mod dev_handler; mod endpoint; mod util; mod usb; +mod midi_framer; +mod podxt_framer; +mod framer; use log::{debug, error, info, trace}; use anyhow::*; diff --git a/usb/src/midi_framer.rs b/usb/src/midi_framer.rs new file mode 100644 index 0000000..984b525 --- /dev/null +++ b/usb/src/midi_framer.rs @@ -0,0 +1,105 @@ +/// A framer for USB-MIDI protocol, converting MIDI data to USB-MIDI +/// Event Packets and back according to the spec: https://www.usb.org/sites/default/files/midi10.pdf +/// It assumes CN=0 as it does in PocketPOD. In case of new devices +/// where this is different, proper CN handling may need to be implemented +/// later + +use log::{error, warn}; +use crate::framer::*; + +/// A framer for incoming messages using the USB-MIDI protocol, converting +/// USB-MIDI Event Packet stream to MIDI messages +pub struct UsbMidiInFramer { + sysex_buffer: Vec, + sysex_offset: usize +} + +impl UsbMidiInFramer { + fn new() -> Self { + let sysex_buffer = vec![0u8; 1024]; + + Self { sysex_buffer, sysex_offset: 0 } + } +} + +impl InFramer for UsbMidiInFramer { + fn decode_incoming(&mut self, bytes: &[u8]) -> Vec> { + if bytes.len() % 4 != 0 { + warn!("Incoming bytes slice size {} is not a multiple of 4", bytes.len()) + } + + let mut ret = vec![]; + bytes.chunks_exact(4).for_each(|b| { + let mut push_sysex = false; + let mut sysex_ptr = &mut self.sysex_buffer[self.sysex_offset .. self.sysex_offset + 3]; + match b[0] { + 0x0b => ret.push( b[1 .. 4].to_vec() ), + 0x0c => ret.push( b[1 .. 3].to_vec() ), + 0x04 => { + sysex_ptr.copy_from_slice(&b[1 .. b.len()]); + self.sysex_offset += 3; + } + 0x05 ..= 0x07 => { + sysex_ptr.copy_from_slice(&b[1 .. b.len()]); + self.sysex_offset += match b[0] { + 0x05 => 1, + 0x06 => 2, + 0x07 => 3, + _ => unreachable!() + }; + push_sysex = true; + }, + 0x00 => {} // silently drop 0x00-packets + _ => warn!("Unsupported event packet: {:02x?}", b) + } + if push_sysex { + ret.push( + self.sysex_buffer[..self.sysex_offset].to_vec() + ); + self.sysex_offset = 0; + } + }); + + ret + } +} + +/// A framer for outgoing messages using the USB-MIDI protocol, converting +/// MIDI messages to USB-MIDI Event Packets +pub struct UsbMidiOutFramer; + +impl OutFramer for UsbMidiOutFramer { + fn encode_outgoing(&self, bytes: &[u8]) -> Vec> { + match bytes[0] { + 0xb0 => vec![ + [ &[0x0b], bytes ].concat() + ], + 0xc0 => vec![ + [ &[0x0c], bytes, &[0x00] ].concat() + ], + 0xf0 => { + bytes.chunks(3).map(|b| { + if b.last() == Some(&0xf7) { + // sysex finishing + match b.len() { + 3 => [ &[0x07], b ].concat(), + 2 => [ &[0x06], b, &[0x00] ].concat(), + 1 => [ &[0x05], b, &[0x00, 0x00] ].concat(), + _ => unreachable!() + } + } else { + [ &[0x04], b ].concat() + } + }).collect::>() + } + _ => { + error!("Unsupported midi message {:?}", bytes); + vec![] + } + } + } +} + +pub fn new_usb_midi_framer() -> (BoxedInFramer, BoxedOutFramer) { + (Box::new(UsbMidiInFramer::new()), Box::new(UsbMidiOutFramer)) +} diff --git a/usb/src/podxt_framer.rs b/usb/src/podxt_framer.rs new file mode 100644 index 0000000..404b293 --- /dev/null +++ b/usb/src/podxt_framer.rs @@ -0,0 +1,99 @@ +/// A framer for PODxt USB MIDI, which is essentially MIDI messages without +/// any framing, but with a few quirks (0xb2/0xc2/0xf2) + +use crate::framer::*; + +/// A framer for incoming messages using the PODxt USB MIDI protocol, +/// which buffers the message data, splitting SysEx messages from the +///incoming data (may be many SysEx messages in one USB transfer) into +/// separate MIDI messages. +/// TODO: check to make sure that 0xb0 and 0xc0 messages are not bundled +/// up into one USB transfer like the SysEx +pub struct PodXtInFramer { + read_buffer: Vec, + read_offset: usize +} + +impl PodXtInFramer { + pub fn new() -> Self { + let read_buffer = vec![0u8; 1024]; + + Self { read_buffer, read_offset: 0 } + } + + fn find_message(read_ptr: &mut [u8]) -> &[u8] { + // correct PODxt lower nibble 0010 in command byte, see + // https://github.com/torvalds/linux/blob/8508fa2e7472f673edbeedf1b1d2b7a6bb898ecc/sound/usb/line6/midibuf.c#L148 + if read_ptr[0] == 0xb2 || read_ptr[0] == 0xc2 || read_ptr[0] == 0xf2 { + read_ptr[0] = read_ptr[0] & 0xf0; + } + + let sysex = read_ptr[0] == 0xf0; + if sysex { + for i in 0 .. read_ptr.len() { + if read_ptr[i] == 0xf7 { + return &read_ptr[..i + 1]; + } + } + return &[]; + + } else { + return read_ptr; + } + } +} + +impl InFramer for PodXtInFramer { + fn decode_incoming(&mut self, bytes: &[u8]) -> Vec> { + // add received data to the read buffer at current read offset + let mut read_ptr = &mut self.read_buffer[self.read_offset .. self.read_offset + bytes.len()]; + read_ptr.copy_from_slice(bytes); + + // go through the whole receive buffer from offset 0, check for + // for messages as send them to the MIDI thread + let process_len = self.read_offset + read_ptr.len(); + let mut process_buf = self.read_buffer[..process_len].as_mut(); + let mut process_offset = 0; + let mut ret = vec![]; + loop { + let process_buf = process_buf[process_offset .. process_len].as_mut(); + let buf = Self::find_message(process_buf); + if buf.len() > 0 { + // message found + ret.push(buf.to_vec()); + } + process_offset += buf.len(); + if buf.len() == 0 || process_offset == process_len { break } + } + if process_offset > 0 { + // at least one message consumed + if process_buf.len() - process_offset > 0 { + // data left in the buffer, move it to the beginning of the read buffer + self.read_buffer.copy_within(process_offset .. process_len, 0); + self.read_offset = process_len - process_offset; + } else { + // all data consumed + self.read_offset = 0; + } + } else { + // unfinished message, adjust read offset + self.read_offset = process_len; + } + + ret + } +} + +/// A framer for outgoing messages using the PODxt USB MIDI protocol, +/// which essentially just sends the messages as-is +pub struct PodXtOutFramer; + +impl OutFramer for PodXtOutFramer { + fn encode_outgoing(&self, bytes: &[u8]) -> Vec> { + vec![ bytes.to_vec() ] + } +} + +pub fn new_pod_xt_framer() -> (BoxedInFramer, BoxedOutFramer) { + (Box::new(PodXtInFramer::new()), Box::new(PodXtOutFramer)) +} \ No newline at end of file