diff --git a/Cargo.toml b/Cargo.toml index ba8516a..604be9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "usbd-midi" version = "0.2.0" -authors = ["beau trepp "] +authors = ["beau trepp ", "Florian Jung "] edition = "2018" description = "A usb-midi implementation for usb-device" homepage = "https://github.com/btrepp/usbd-midi" diff --git a/README.md b/README.md index df9a38b..c6bdc79 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ fn main() -> ! { let usb_bus = UsbBus::new(usb); - let mut midi = MidiClass::new(&usb_bus); + let mut midi = MidiClass::new(&usb_bus, 1, 1); let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x5e4)) .product("MIDI Test") @@ -68,4 +68,20 @@ fn main() -> ! { } } } -``` \ No newline at end of file +``` + +## Using more than one MIDI port + +Calling `MidiClass::new(&usb_bus, N, M);` with `N, M >= 1` to provide more +than one input or output port requires the `control-buffer-256` feature of +the usb-device crate: + +Cargo.toml: +``` +usb-device = { version = ">=0.2.1", features = ["control-buffer-256"] } +``` + +Up to 5 in/out pairs can be used this way until we again run out of buffer +space. Note that exceeding the available buffer space will silently fail +to send the descriptors correctly, no obvious `panic!` will hint the +actual problem. diff --git a/src/data/usb/constants.rs b/src/data/usb/constants.rs index 6da6ee6..293bbe6 100644 --- a/src/data/usb/constants.rs +++ b/src/data/usb/constants.rs @@ -7,6 +7,7 @@ pub const USB_MIDISTREAMING_SUBCLASS: u8 =0x03; pub const MIDI_IN_JACK_SUBTYPE : u8 = 0x02; pub const MIDI_OUT_JACK_SUBTYPE : u8 = 0x03; pub const EMBEDDED : u8 = 0x01; +pub const EXTERNAL : u8 = 0x02; pub const CS_INTERFACE: u8 = 0x24; pub const CS_ENDPOINT: u8 = 0x25; pub const HEADER_SUBTYPE: u8 = 0x01; diff --git a/src/midi_device.rs b/src/midi_device.rs index 44d7536..60a9761 100644 --- a/src/midi_device.rs +++ b/src/midi_device.rs @@ -16,7 +16,9 @@ pub struct MidiClass<'a,B: UsbBus> { standard_ac: InterfaceNumber, standard_mc: InterfaceNumber, standard_bulkout: EndpointOut<'a, B>, - standard_bulkin: EndpointIn<'a,B> + standard_bulkin: EndpointIn<'a,B>, + n_in_jacks: u8, + n_out_jacks: u8, } pub enum MidiReadError { @@ -24,18 +26,31 @@ pub enum MidiReadError { UsbError(UsbError) } +#[derive(Debug)] +pub struct InvalidArguments; + impl MidiClass<'_, B> { - /// Creates a new MidiClass with the provided UsbBus - pub fn new(alloc: &UsbBusAllocator) -> MidiClass<'_, B> { - MidiClass { + /// Creates a new MidiClass with the provided UsbBus and `n_in/out_jacks` embedded input/output jacks (or "cables", + /// depending on the terminology). + /// Note that a maximum of 16 in and 16 out jacks are supported. + pub fn new(alloc: &UsbBusAllocator, n_in_jacks: u8, n_out_jacks: u8) -> core::result::Result, InvalidArguments> { + if n_in_jacks > 16 || n_out_jacks > 16 { + return Err(InvalidArguments); + } + Ok(MidiClass { standard_ac: alloc.interface(), standard_mc: alloc.interface(), standard_bulkout : alloc.bulk(MAX_PACKET_SIZE as u16), - standard_bulkin: alloc.bulk(MAX_PACKET_SIZE as u16) - } + standard_bulkin: alloc.bulk(MAX_PACKET_SIZE as u16), + n_in_jacks, + n_out_jacks + }) } - pub fn send_message(&mut self, usb_midi:UsbMidiEventPacket) -> Result { + pub fn send_bytes(&mut self, buffer: [u8; 4]) -> Result { + self.standard_bulkin.write(&buffer) + } + pub fn send_message(&mut self, usb_midi: UsbMidiEventPacket) -> Result { let bytes : [u8;MIDI_PACKET_SIZE] = usb_midi.into(); self.standard_bulkin.write(&bytes) } @@ -43,14 +58,34 @@ impl MidiClass<'_, B> { pub fn read(&mut self, buffer: &mut [u8]) -> Result { self.standard_bulkout.read(buffer) } + + /// calculates the index'th external midi in jack id + fn in_jack_id_ext(&self, index: u8) -> u8 { + debug_assert!(index < self.n_in_jacks); + return 2 * index + 1; + } + /// calculates the index'th embedded midi out jack id + fn out_jack_id_emb(&self, index: u8) -> u8 { + debug_assert!(index < self.n_in_jacks); + return 2 * index + 2; + } + + /// calculates the index'th external midi out jack id + fn out_jack_id_ext(&self, index: u8) -> u8 { + debug_assert!(index < self.n_out_jacks); + return 2 * self.n_in_jacks + 2 * index + 1; + } + /// calculates the index'th embedded midi in jack id + fn in_jack_id_emb(&self, index: u8) -> u8 { + debug_assert!(index < self.n_out_jacks); + return 2 * self.n_in_jacks + 2 * index + 2; + } } impl UsbClass for MidiClass<'_, B> { fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> { - //AUDIO CONTROL STANDARD - writer.interface( self.standard_ac, USB_AUDIO_CLASS, @@ -79,64 +114,107 @@ impl UsbClass for MidiClass<'_, B> { 0, //no protocol )?; //Num endpoints? - //Streaming extra info + let midi_streaming_start_byte = writer.position(); + let midi_streaming_total_length = + 7 + (self.n_in_jacks + self.n_out_jacks) as usize * (MIDI_IN_SIZE + MIDI_OUT_SIZE) as usize + + 7 + (4+self.n_out_jacks as usize) + 7 + (4+self.n_in_jacks as usize); - writer.write( + //Streaming extra info + writer.write( // len = 7 CS_INTERFACE, &[ MS_HEADER_SUBTYPE, 0x00,0x01, //REVISION - (0x07 + MIDI_OUT_SIZE),0x00 //Total size of class specific descriptors? (little endian?) + (midi_streaming_total_length & 0xFF) as u8, ((midi_streaming_total_length >> 8) & 0xFF) as u8 ] )?; - + //JACKS + for i in 0..self.n_in_jacks { + writer.write( // len = 6 = MIDI_IN_SIZE + CS_INTERFACE, + &[ + MIDI_IN_JACK_SUBTYPE, + EXTERNAL, + self.in_jack_id_ext(i), // id + 0x00 + ] + )?; + } - writer.write( - CS_INTERFACE, - &[ - MIDI_IN_JACK_SUBTYPE, - EMBEDDED, - 0x01, // id - 0x00 - ] - )?; + for i in 0..self.n_out_jacks { + writer.write( // len = 6 = MIDI_IN_SIZE + CS_INTERFACE, + &[ + MIDI_IN_JACK_SUBTYPE, + EMBEDDED, + self.in_jack_id_emb(i), // id + 0x00 + ] + )?; + } - writer.write ( - CS_INTERFACE, - &[ - MIDI_OUT_JACK_SUBTYPE, - EMBEDDED, - 0x01,//id - 0x01, // 1 pin - 0x01, // pin 1 - 0x01, //sorta vague source pin? - 0x00 - ] - )?; + for i in 0..self.n_out_jacks { + writer.write ( // len = 9 = MIDI_OUT_SIZE + CS_INTERFACE, + &[ + MIDI_OUT_JACK_SUBTYPE, + EXTERNAL, + self.out_jack_id_ext(i), //id + 0x01, // 1 pin + self.in_jack_id_emb(i), // pin is connected to this entity... + 0x01, // ...to the first pin + 0x00 + ] + )?; + } - writer.endpoint(&self.standard_bulkout)?; + for i in 0..self.n_in_jacks { + writer.write ( // len = 9 = MIDI_OUT_SIZE + CS_INTERFACE, + &[ + MIDI_OUT_JACK_SUBTYPE, + EMBEDDED, + self.out_jack_id_emb(i), //id + 0x01, // 1 pin + self.in_jack_id_ext(i), // pin is connected to this entity... + 0x01, // ...to the first pin + 0x00 + ] + )?; + } - writer.write( + let mut endpoint_data = [ + MS_GENERAL, + 0, // number of jacks. must be filled in! + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 // jack mappings. must be filled in and cropped. + ]; + + writer.endpoint(&self.standard_bulkout)?; // len = 7 + + endpoint_data[1] = self.n_out_jacks; + for i in 0..self.n_out_jacks { + endpoint_data[2 + i as usize] = self.in_jack_id_emb(i); + } + writer.write( // len = 4 + self.n_out_jacks CS_ENDPOINT, - &[ - MS_GENERAL, - 0x01, - 0x01 - ] + &endpoint_data[0..2+self.n_out_jacks as usize] )?; - writer.endpoint(&self.standard_bulkin)?; - - writer.write( + writer.endpoint(&self.standard_bulkin)?; // len = 7 + endpoint_data[1] = self.n_in_jacks; + for i in 0..self.n_in_jacks { + endpoint_data[2 + i as usize] = self.out_jack_id_emb(i); + } + writer.write( // len = 4 + self.n_in_jacks CS_ENDPOINT, - &[ - MS_GENERAL, - 0x01, - 0x01 - ] + &endpoint_data[0..2+self.n_in_jacks as usize] )?; + + let midi_streaming_end_byte = writer.position(); + assert!(midi_streaming_end_byte - midi_streaming_start_byte == midi_streaming_total_length); + Ok(()) } -} \ No newline at end of file +}