diff --git a/Cargo.toml b/Cargo.toml index ad7c209..3db2212 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,4 @@ edition = "2018" [dependencies] embedded-hal = "0.2.2" nb = "0.1.2" -usb-device = {version = "0.2.3", path = "../usb-device" } +usb-device = {version = "0.2.3"} diff --git a/src/data/midi/code_index_number.rs b/src/data/midi/code_index_number.rs new file mode 100644 index 0000000..a57e2c3 --- /dev/null +++ b/src/data/midi/code_index_number.rs @@ -0,0 +1,63 @@ +use crate::util::try_from::{TryFrom}; + +/// The Code Index Number(CIN) indicates the classification +/// of the bytes in the MIDI_x fields +pub struct CodeIndexNumber(u8); + +impl TryFrom for CodeIndexNumber { + + fn try_from(value:u8) -> Option { + if value > 0xF { + None + } else { + Some(CodeIndexNumber(value)) + } + } +} + +impl Into for CodeIndexNumber { + fn into(self) -> u8 { + self.0 + } +} + + +impl CodeIndexNumber { + + /// Miscellaneous function codes. Reserved for future extensions + pub const MISC_FUNCTION : CodeIndexNumber = CodeIndexNumber(0x00); + /// Cable events. Reserved for future expansion. + pub const CABLE_EVENTS : CodeIndexNumber = CodeIndexNumber(0x1); + /// Two-byte System Common messages like MTC, SongSelect, etc. + pub const SYSTEM_COMMON_LEN2 :CodeIndexNumber = CodeIndexNumber(0x2); + /// Three-byte System Common messages like SPP, etc. + pub const SYSTEM_COMMON_LEN3 :CodeIndexNumber = CodeIndexNumber(0x3); + /// SysEx starts or continues + pub const SYSEX_STARTS : CodeIndexNumber = CodeIndexNumber(0x4); + pub const SYSEX_CONTINUES : CodeIndexNumber = CodeIndexNumber::SYSEX_STARTS; + /// Single-byte System Common Message or SysEx ends with following single byte. + pub const SYSTEM_COMMON_LEN1 : CodeIndexNumber= CodeIndexNumber(0x5); + /// SysEx ends with the following byte + pub const SYSEX_ENDS_NEXT1 :CodeIndexNumber = CodeIndexNumber::SYSTEM_COMMON_LEN1; + /// SysEx ends with following two bytes + pub const SYSEX_ENDS_NEXT2 : CodeIndexNumber = CodeIndexNumber(0x6); + /// SysEx ends with following three bytes + pub const SYSEX_ENDS_NEXT3 : CodeIndexNumber = CodeIndexNumber(0x7); + /// Note - Off + pub const NOTE_OFF : CodeIndexNumber = CodeIndexNumber(0x8); + /// Note - On + pub const NOTE_ON : CodeIndexNumber = CodeIndexNumber(0x9); + /// Poly-KeyPress + pub const POLY_KEYPRESS : CodeIndexNumber = CodeIndexNumber(0xA); + /// Control Change + pub const CONTROL_CHANGE : CodeIndexNumber = CodeIndexNumber(0xB); + /// Program Change + pub const PROGRAM_CHANGE : CodeIndexNumber = CodeIndexNumber(0xC); + /// Channel Pressure + pub const CHANNEL_PRESSURE : CodeIndexNumber = CodeIndexNumber(0xD); + /// Pitch Bend Change + pub const PITCHBEND_CHANGE : CodeIndexNumber = CodeIndexNumber(0xE); + /// Single Byte + pub const SINGLE_BYTE : CodeIndexNumber= CodeIndexNumber(0xF); + +} diff --git a/src/data/midi/midi_channel.rs b/src/data/midi/midi_channel.rs new file mode 100644 index 0000000..84ab8bb --- /dev/null +++ b/src/data/midi/midi_channel.rs @@ -0,0 +1,25 @@ +use crate::util::try_from::{TryFrom}; + +/// The MidiChannel is a value ranging from 0x0 to 0xF +/// This is a standard midi concept +pub struct MidiChannel(u8); + + +impl TryFrom for MidiChannel { + + fn try_from(value:u8) -> Option { + if value > 0xF { + None + } else { + Some(MidiChannel(value)) + } + } +} + +impl Into for MidiChannel { + fn into(self) -> u8 { + self.0 + } +} + + diff --git a/src/data/midi/midi_message.rs b/src/data/midi/midi_message.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/data/midi/mod.rs b/src/data/midi/mod.rs new file mode 100644 index 0000000..98a2383 --- /dev/null +++ b/src/data/midi/mod.rs @@ -0,0 +1,3 @@ +pub mod notes; +pub mod code_index_number; +pub mod midi_channel; diff --git a/src/notes.rs b/src/data/midi/notes.rs similarity index 76% rename from src/notes.rs rename to src/data/midi/notes.rs index eb4e849..4884196 100644 --- a/src/notes.rs +++ b/src/data/midi/notes.rs @@ -1,121 +1,35 @@ -//A simple enum type that represents all the midi 'notes' -//note the flat versions are associated constants -//but can be referenced like Note::Bb3 +/// A simple enum type that represents all the midi 'notes' +/// note the flat versions are associated constants +/// but can be referenced like Note::Bb3 +/// C1m is the C-1 #[derive(Debug)] #[repr(u8)] pub enum Note { - A0 = 21, - As0, - B0, - C1, - Cs1, - D1, - Ds1, - E1, - F1, - Fs1, - G1, - Gs1, - A1, - As1, - B1, - C2, - Cs2, - D2, - Ds2, - E2, - F2, - Fs2, - G2, - Gs2, - A2, - As2, - B2, - C3, - Cs3, - D3, - Ds3, - E3, - F3, - Fs3, - G3, - Gs3, - A3, - As3, - B3, - C4, - Cs4, - D4, - Ds4, - E4, - F4, - Fs4, - G4, - Gs4, - A4, - As4, - B4, - C5, - Cs5, - D5, - Ds5, - E5, - F5, - Fs5, - G5, - Gs5, - A5, - As5, - B5, - C6, - Cs6, - D6, - Ds6, - E6, - F6, - Fs6, - G6, - Gs6, - A6, - As6, - B6, - C7, - Cs7, - D7, - Ds7, - E7, - F7, - Fs7, - G7, - Gs7, - A7, - As7, - B7, - C8, - Cs8, - D8, - Ds8, - E8, - F8, - Fs8, - G8, - Gs8, - A8, - As8, - B8, - C9, - Cs9, - D9, - Ds9, - E9, - F9, - Fs9, - G9, - Gs9 + C1m, Cs1m, D1m, Ds1m, E1m, F1m, Fs1m, G1m, Gs1m, A1m, As1m, B1m, + C0 , Cs0 , D0 , Ds0 , E0 , F0 , Fs0 , G0 , Gs0 , A0 , As0 , B0 , + C1 , Cs1 , D1 , Ds1 , E1 , F1 , Fs1 , G1 , Gs1 , A1 , As1 , B1 , + C2 , Cs2 , D2 , Ds2 , E2 , F2 , Fs2 , G2 , Gs2 , A2 , As2 , B2 , + C3 , Cs3 , D3 , Ds3 , E3 , F3 , Fs3 , G3 , Gs3 , A3 , As3 , B3 , + C4 , Cs4 , D4 , Ds4 , E4 , F4 , Fs4 , G4 , Gs4 , A4 , As4 , B4 , + C5 , Cs5 , D5 , Ds5 , E5 , F5 , Fs5 , G5 , Gs5 , A5 , As5 , B5 , + C6 , Cs6 , D6 , Ds6 , E6 , F6 , Fs6 , G6 , Gs6 , A6 , As6 , B6 , + C7 , Cs7 , D7 , Ds7 , E7 , F7 , Fs7 , G7 , Gs7 , A7 , As7 , B7 , + C8 , Cs8 , D8 , Ds8 , E8 , F8 , Fs8 , G8 , Gs8 , A8 , As8 , B8 , + C9 , Cs9 , D9 , Ds9 , E9 , F9 , Fs9 , G9 , Gs9 } impl Note { + #[allow(non_upper_case_globals)] pub const Db1m : Note = Note::Cs1m; + #[allow(non_upper_case_globals)] pub const Eb1m : Note = Note::Ds1m; + #[allow(non_upper_case_globals)] pub const Gb1m : Note = Note::Fs1m; + #[allow(non_upper_case_globals)] pub const Ab1m : Note = Note::Gs1m; + #[allow(non_upper_case_globals)] pub const Bb1m : Note = Note::As1m; + #[allow(non_upper_case_globals)] pub const Db0 : Note = Note::Cs0; + #[allow(non_upper_case_globals)] pub const Eb0 : Note = Note::Ds0; + #[allow(non_upper_case_globals)] pub const Gb0 : Note = Note::Fs0; + #[allow(non_upper_case_globals)] pub const Ab0 : Note = Note::Gs0; + #[allow(non_upper_case_globals)] pub const Bb0 : Note = Note::As0; #[allow(non_upper_case_globals)] pub const Db1 : Note = Note::Cs1; #[allow(non_upper_case_globals)] pub const Eb1 : Note = Note::Ds1; #[allow(non_upper_case_globals)] pub const Gb1 : Note = Note::Fs1; @@ -182,6 +96,40 @@ mod tests { //These test mainly prove we are generating all the sharps/flats //(as the same number) correctly. note_test! { + note_c1m: (Note::C1m,0), + note_cs1m: (Note::Cs1m,1), + note_db1m: (Note::Db1m,1), + note_d1m: (Note::D1m,2), + note_ds1m: (Note::Ds1m,3), + note_eb1m: (Note::Eb1m,3), + note_e1m: (Note::E1m,4), + note_f1m: (Note::F1m,5), + note_fs1m: (Note::Fs1m,6), + note_gb1m: (Note::Gb1m,6), + note_g1m: (Note::G1m,7), + note_gs1m: (Note::Gs1m,8), + note_ab1m: (Note::Ab1m,8), + note_a1m: (Note::A1m,9), + note_as1m: (Note::As1m,10), + note_bb1m: (Note::Bb1m,10), + note_b1m: (Note::B1m,11), + note_c0: (Note::C0,12), + note_cs0: (Note::Cs0,13), + note_db0: (Note::Db0,13), + note_d0: (Note::D0,14), + note_ds0: (Note::Ds0,15), + note_eb0: (Note::Eb0,15), + note_e0: (Note::E0,16), + note_f0: (Note::F0,17), + note_fs0: (Note::Fs0,18), + note_gb0: (Note::Gb0,18), + note_g0: (Note::G0,19), + note_gs0: (Note::Gs0,20), + note_ab0: (Note::Ab0,20), + note_a0: (Note::A0,21), + note_as0: (Note::As0,22), + note_bb0: (Note::Bb0,22), + note_b0: (Note::B0,23), note_c1: (Note::C1,24), note_cs1: (Note::Cs1,25), note_db1: (Note::Db1,25), diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..b55381c --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1,3 @@ +pub mod midi; +pub mod usb; +pub mod usb_midi; diff --git a/src/usb_constants.rs b/src/data/usb/constants.rs similarity index 100% rename from src/usb_constants.rs rename to src/data/usb/constants.rs diff --git a/src/data/usb/mod.rs b/src/data/usb/mod.rs new file mode 100644 index 0000000..987b09f --- /dev/null +++ b/src/data/usb/mod.rs @@ -0,0 +1 @@ +pub mod constants; \ No newline at end of file diff --git a/src/data/usb_midi/cable_number.rs b/src/data/usb_midi/cable_number.rs new file mode 100644 index 0000000..14f439d --- /dev/null +++ b/src/data/usb_midi/cable_number.rs @@ -0,0 +1,23 @@ +use crate::util::try_from::{TryFrom}; + +/// The Cable Number (CN) is a value ranging from 0x0 to 0xF +/// indicating the number assignment of the Embedded MIDI Jack associated +/// with the endpoint that is transferring the data +pub struct CableNumber(u8); + +impl TryFrom for CableNumber { + + fn try_from(value:u8) -> Option { + if value > 0xF { + None + } else { + Some(CableNumber(value)) + } + } +} + +impl Into for CableNumber { + fn into(self) -> u8 { + self.0 + } +} diff --git a/src/data/usb_midi/mod.rs b/src/data/usb_midi/mod.rs new file mode 100644 index 0000000..82ed118 --- /dev/null +++ b/src/data/usb_midi/mod.rs @@ -0,0 +1,2 @@ +pub mod usb_midi_event_packet; +pub mod cable_number; \ No newline at end of file diff --git a/src/data/usb_midi/usb_midi_event_packet.rs b/src/data/usb_midi/usb_midi_event_packet.rs new file mode 100644 index 0000000..0882fbf --- /dev/null +++ b/src/data/usb_midi/usb_midi_event_packet.rs @@ -0,0 +1,57 @@ +use crate::data::usb_midi::cable_number::CableNumber; +use crate::data::midi::code_index_number::CodeIndexNumber; +use crate::data::midi::notes::Note; +use crate::data::midi::midi_channel::MidiChannel; + +/// A packet that communicates with the host +/// Note that the payload seems fairly 'open' +/// It's contents can depend on the cable/code index number +/// but may not!? +pub struct UsbMidiEventPacket { + cable_number : CableNumber, + code_index_number: CodeIndexNumber, + payload: [u8;3] +} + +/// Combines two nibbles. +/// Note that the upper will overflow if greater than 0xF +/// The lower will be clamped to the range 0-0xF +fn combine_nibble(upper:u8,lower:u8) -> u8 { + let upper = upper.overflowing_shl(8).0; + let lower = lower & 0xF; + upper | lower +} + +/// Constructs a note-on midi message given the cable, note and velocity +pub fn note_on( cable:CableNumber, + channel: MidiChannel, + note:Note, + velocity: u8) -> UsbMidiEventPacket { + + let code :u8 = CodeIndexNumber::NOTE_ON.into(); + let channel : u8 = channel.into(); + + UsbMidiEventPacket{ + cable_number : cable, + code_index_number : CodeIndexNumber::NOTE_ON, + payload : [ code & channel, + note as u8, + velocity + ] + } +} +impl Into<[u8;4]> for UsbMidiEventPacket { + /// Converts the midi packet into a byte array + /// suitable for transfer via usb + fn into(self) -> [u8;4] { + let cable_number : u8 = self.cable_number.into(); + let index_number : u8 = self.code_index_number.into(); + let header = combine_nibble(cable_number,index_number); + [ header, + self.payload[0], + self.payload[1], + self.payload[2] + ] + } +} + diff --git a/src/lib.rs b/src/lib.rs index 581007b..b168479 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,6 @@ #![no_std] -mod usb_constants; -mod midi_device; -mod notes; -pub use usb_device::{Result,UsbError}; -pub use crate::usb_constants::USB_CLASS_NONE; -pub use crate::midi_device::*; -pub use crate::notes::Note; \ No newline at end of file +mod util; +pub mod data; +pub mod midi_device; + diff --git a/src/midi_device.rs b/src/midi_device.rs index dcf43d6..44d8431 100644 --- a/src/midi_device.rs +++ b/src/midi_device.rs @@ -1,7 +1,8 @@ use usb_device::class_prelude::*; use usb_device::Result; -use crate::notes::Note; -use crate::usb_constants::*; +use crate::data::midi::notes::Note; +use crate::data::usb::constants::*; +use crate::data::usb_midi::usb_midi_event_packet::UsbMidiEventPacket; //const MIDI_IN_SIZE: u8 = 0x06; const MIDI_OUT_SIZE: u8 = 0x09; @@ -28,6 +29,11 @@ impl MidiClass<'_, B> { } } + pub fn send_message(&mut self, usb_midi:UsbMidiEventPacket) -> Result { + let bytes : [u8;4] = usb_midi.into(); + self.standard_bulkin.write(&bytes) + } + pub fn note_on(&mut self, chan: u8, note: Note, vel : u8) -> Result { let note = note as u8; self.standard_bulkin.write( diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..e5737c0 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1 @@ +pub mod try_from; \ No newline at end of file diff --git a/src/util/try_from.rs b/src/util/try_from.rs new file mode 100644 index 0000000..ed2af70 --- /dev/null +++ b/src/util/try_from.rs @@ -0,0 +1,6 @@ + +/// A version of try from that works on options +/// useful for the bounded ints +pub trait TryFrom : Sized{ + fn try_from(value:T) -> Option; +}