mirror of
https://github.com/italicsjenga/usbd-midi.git
synced 2024-12-23 12:21:30 +11:00
commit
f0a03436a7
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
Cargo.lock
|
||||
target
|
||||
.idea
|
|
@ -11,4 +11,8 @@ license = "MIT"
|
|||
[dependencies]
|
||||
embedded-hal = "0.2.2"
|
||||
nb = "0.1.2"
|
||||
usb-device = "0.2.3"
|
||||
usb-device = "0.2.3"
|
||||
|
||||
[dependencies.num_enum]
|
||||
version = "0.5.1"
|
||||
default-features = false
|
||||
|
|
62
README.md
62
README.md
|
@ -4,8 +4,68 @@ usbd-midi
|
|||
A simple usb midi device class for [usb-device](https://crates.io/crates/usb-device).
|
||||
|
||||
Currently this aims to be a very simple implementation, that allows the micro
|
||||
controller to send midi information to the PC.
|
||||
controller to send MIDI information to the PC and also receive MIDI information.
|
||||
|
||||
This crate requires the use of a hardware driver, that implements the
|
||||
usb-device traits.
|
||||
|
||||
## Example
|
||||
|
||||
### Receive MIDI
|
||||
Turn on the integrated LED of a STM32 BluePill board as long as C2 is pressed
|
||||
```rust
|
||||
fn main() -> ! {
|
||||
let dp = pac::Peripherals::take().unwrap();
|
||||
|
||||
let mut rcc = dp.RCC.constrain();
|
||||
|
||||
let mut gpioa = dp.GPIOA.split(&mut rcc.apb2);
|
||||
let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
|
||||
|
||||
let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
|
||||
led.set_high().unwrap();
|
||||
|
||||
let mut usb_dp = gpioa.pa12.into_push_pull_output(&mut gpioa.crh);
|
||||
|
||||
let usb = Peripheral {
|
||||
usb: dp.USB,
|
||||
pin_dm: gpioa.pa11,
|
||||
pin_dp: usb_dp.into_floating_input(&mut gpioa.crh),
|
||||
};
|
||||
|
||||
let usb_bus = UsbBus::new(usb);
|
||||
|
||||
let mut midi = MidiClass::new(&usb_bus);
|
||||
|
||||
let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x5e4))
|
||||
.product("MIDI Test")
|
||||
.device_class(USB_AUDIO_CLASS)
|
||||
.device_sub_class(USB_MIDISTREAMING_SUBCLASS)
|
||||
.build();
|
||||
|
||||
loop {
|
||||
if !usb_dev.poll(&mut [&mut midi]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut buffer = [0; 64];
|
||||
|
||||
if let Ok(size) = midi.read(&mut buffer) {
|
||||
let buffer_reader = MidiPacketBufferReader::new(&buffer, size);
|
||||
for packet in buffer_reader.into_iter() {
|
||||
if let Ok(packet) = packet {
|
||||
match packet.message {
|
||||
Message::NoteOn(Channel1, Note::C2, ..) => {
|
||||
led.set_low().unwrap();
|
||||
},
|
||||
Message::NoteOff(Channel1, Note::C2, ..) => {
|
||||
led.set_high().unwrap();
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -2,7 +2,8 @@ use core::convert::TryFrom;
|
|||
use crate::data::byte::from_traits::{FromOverFlow,FromClamped};
|
||||
|
||||
/// A primitive value that can be from 0-0x7F
|
||||
pub struct U7(u8);
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct U7(pub(crate)u8);
|
||||
|
||||
/// Error representing that this value is not a valid u7
|
||||
pub struct InvalidU7(u8);
|
||||
|
|
|
@ -3,7 +3,7 @@ use core::convert::TryFrom;
|
|||
/// The Channel is a value ranging from 0x0 to 0xF
|
||||
/// This is a standard midi concept
|
||||
/// Note Channel1 = 0 on the wire
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
#[derive(Debug,Copy,Clone, Eq, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum Channel {
|
||||
Channel1 = 0x0, Channel2 = 0x1, Channel3 = 0x2, Channel4 = 0x3,
|
||||
|
|
137
src/data/midi/message/control_function.rs
Normal file
137
src/data/midi/message/control_function.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
use crate::data::byte::u7::U7;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct ControlFunction(pub U7);
|
||||
|
||||
/// Control Functions as defined in the MIDI 1.0 Specification
|
||||
/// Source: https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2
|
||||
impl ControlFunction {
|
||||
pub const BANK_SELECT_0: Self = ControlFunction(U7(0));
|
||||
pub const MOD_WHEEL_1: Self = ControlFunction(U7(1));
|
||||
pub const BREATH_CONTROLLER_2: Self = ControlFunction(U7(2));
|
||||
pub const UNDEFINED_3: Self = ControlFunction(U7(3));
|
||||
pub const FOOT_CONTROLLER_4: Self = ControlFunction(U7(4));
|
||||
pub const PORTAMENTO_TIME_5: Self = ControlFunction(U7(5));
|
||||
pub const DATA_ENTRY_MSB_6: Self = ControlFunction(U7(6));
|
||||
pub const CHANNEL_VOLUME_7: Self = ControlFunction(U7(7));
|
||||
pub const BALANCE_8: Self = ControlFunction(U7(8));
|
||||
pub const UNDEFINED_9: Self = ControlFunction(U7(9));
|
||||
pub const PAN_10: Self = ControlFunction(U7(10));
|
||||
pub const EXPRESSION_CONTROLLER_11: Self = ControlFunction(U7(11));
|
||||
pub const EFFECT_CONTROL_1_12: Self = ControlFunction(U7(12));
|
||||
pub const EFFECT_CONTROL_2_13: Self = ControlFunction(U7(13));
|
||||
pub const UNDEFINED_14: Self = ControlFunction(U7(14));
|
||||
pub const UNDEFINED_15: Self = ControlFunction(U7(15));
|
||||
pub const GENERAL_PURPOSE_CONTROLLER_1_16: Self = ControlFunction(U7(16));
|
||||
pub const GENERAL_PURPOSE_CONTROLLER_2_17: Self = ControlFunction(U7(17));
|
||||
pub const GENERAL_PURPOSE_CONTROLLER_3_18: Self = ControlFunction(U7(18));
|
||||
pub const GENERAL_PURPOSE_CONTROLLER_4_19: Self = ControlFunction(U7(19));
|
||||
pub const UNDEFINED_20: Self = ControlFunction(U7(20));
|
||||
pub const UNDEFINED_21: Self = ControlFunction(U7(21));
|
||||
pub const UNDEFINED_22: Self = ControlFunction(U7(22));
|
||||
pub const UNDEFINED_23: Self = ControlFunction(U7(23));
|
||||
pub const UNDEFINED_24: Self = ControlFunction(U7(24));
|
||||
pub const UNDEFINED_25: Self = ControlFunction(U7(25));
|
||||
pub const UNDEFINED_26: Self = ControlFunction(U7(26));
|
||||
pub const UNDEFINED_27: Self = ControlFunction(U7(27));
|
||||
pub const UNDEFINED_28: Self = ControlFunction(U7(28));
|
||||
pub const UNDEFINED_29: Self = ControlFunction(U7(29));
|
||||
pub const UNDEFINED_30: Self = ControlFunction(U7(30));
|
||||
pub const UNDEFINED_31: Self = ControlFunction(U7(31));
|
||||
pub const LSB_FOR_BANK_SELECT_32: Self = ControlFunction(U7(32));
|
||||
pub const LSB_FOR_MOD_WHEEL_33: Self = ControlFunction(U7(33));
|
||||
pub const LSB_FOR_BREATH_CONTROLLER_34: Self = ControlFunction(U7(34));
|
||||
pub const LSB_FOR_UNDEFINED_35: Self = ControlFunction(U7(35));
|
||||
pub const LSB_FOR_FOOT_CONTROLLER_36: Self = ControlFunction(U7(36));
|
||||
pub const LSB_FOR_PORTAMENTO_TIME_37: Self = ControlFunction(U7(37));
|
||||
pub const LSB_FOR_DATA_ENTRY_MSB_38: Self = ControlFunction(U7(38));
|
||||
pub const LSB_FOR_CHANNEL_VOLUME_39: Self = ControlFunction(U7(39));
|
||||
pub const LSB_FOR_BALANCE_40: Self = ControlFunction(U7(40));
|
||||
pub const LSB_FOR_UNDEFINED_41: Self = ControlFunction(U7(41));
|
||||
pub const LSB_FOR_PAN_42: Self = ControlFunction(U7(42));
|
||||
pub const LSB_FOR_EXPRESSION_CONTROLLER_43: Self = ControlFunction(U7(43));
|
||||
pub const LSB_FOR_EFFECT_CONTROL_1_44: Self = ControlFunction(U7(44));
|
||||
pub const LSB_FOR_EFFECT_CONTROL_2_45: Self = ControlFunction(U7(45));
|
||||
pub const LSB_FOR_UNDEFINED_14_46: Self = ControlFunction(U7(46));
|
||||
pub const LSB_FOR_UNDEFINED_15_47: Self = ControlFunction(U7(47));
|
||||
pub const LSB_FOR_GENERAL_PURPOSE_CONTROLLER_1_48: Self = ControlFunction(U7(48));
|
||||
pub const LSB_FOR_GENERAL_PURPOSE_CONTROLLER_2_49: Self = ControlFunction(U7(49));
|
||||
pub const LSB_FOR_GENERAL_PURPOSE_CONTROLLER_3_50: Self = ControlFunction(U7(50));
|
||||
pub const LSB_FOR_GENERAL_PURPOSE_CONTROLLER_4_51: Self = ControlFunction(U7(51));
|
||||
pub const LSB_FOR_UNDEFINED_20_52: Self = ControlFunction(U7(52));
|
||||
pub const LSB_FOR_UNDEFINED_21_53: Self = ControlFunction(U7(53));
|
||||
pub const LSB_FOR_UNDEFINED_22_54: Self = ControlFunction(U7(54));
|
||||
pub const LSB_FOR_UNDEFINED_23_55: Self = ControlFunction(U7(55));
|
||||
pub const LSB_FOR_UNDEFINED_24_56: Self = ControlFunction(U7(56));
|
||||
pub const LSB_FOR_UNDEFINED_25_57: Self = ControlFunction(U7(57));
|
||||
pub const LSB_FOR_UNDEFINED_26_58: Self = ControlFunction(U7(58));
|
||||
pub const LSB_FOR_UNDEFINED_27_59: Self = ControlFunction(U7(59));
|
||||
pub const LSB_FOR_UNDEFINED_28_60: Self = ControlFunction(U7(60));
|
||||
pub const LSB_FOR_UNDEFINED_29_61: Self = ControlFunction(U7(61));
|
||||
pub const LSB_FOR_UNDEFINED_30_62: Self = ControlFunction(U7(62));
|
||||
pub const LSB_FOR_UNDEFINED_31_63: Self = ControlFunction(U7(63));
|
||||
pub const DAMPER_PEDAL_ON_OFF_64: Self = ControlFunction(U7(64));
|
||||
pub const PORTAMENTO_ON_OFF_65: Self = ControlFunction(U7(65));
|
||||
pub const SOSTENUTO_ON_OFF_66: Self = ControlFunction(U7(66));
|
||||
pub const SOFT_PEDAL_ON_OFF_67: Self = ControlFunction(U7(67));
|
||||
pub const LEGATO_FOOTSWITCH_68: Self = ControlFunction(U7(68));
|
||||
pub const HOLD_2_69: Self = ControlFunction(U7(69));
|
||||
pub const SOUND_CONTROLLER_1_70: Self = ControlFunction(U7(70));
|
||||
pub const SOUND_CONTROLLER_2_71: Self = ControlFunction(U7(71));
|
||||
pub const SOUND_CONTROLLER_3_72: Self = ControlFunction(U7(72));
|
||||
pub const SOUND_CONTROLLER_4_73: Self = ControlFunction(U7(73));
|
||||
pub const SOUND_CONTROLLER_5_74: Self = ControlFunction(U7(74));
|
||||
pub const SOUND_CONTROLLER_6_75: Self = ControlFunction(U7(75));
|
||||
pub const SOUND_CONTROLLER_7_76: Self = ControlFunction(U7(76));
|
||||
pub const SOUND_CONTROLLER_8_77: Self = ControlFunction(U7(77));
|
||||
pub const SOUND_CONTROLLER_9_78: Self = ControlFunction(U7(78));
|
||||
pub const SOUND_CONTROLLER_10_79: Self = ControlFunction(U7(79));
|
||||
pub const GENERAL_PURPOSE_CONTROLLER_5_80: Self = ControlFunction(U7(80));
|
||||
pub const GENERAL_PURPOSE_CONTROLLER_6_81: Self = ControlFunction(U7(81));
|
||||
pub const GENERAL_PURPOSE_CONTROLLER_7_82: Self = ControlFunction(U7(82));
|
||||
pub const GENERAL_PURPOSE_CONTROLLER_8_83: Self = ControlFunction(U7(83));
|
||||
pub const PORTAMENTO_CONTROL_84: Self = ControlFunction(U7(84));
|
||||
pub const UNDEFINED_85: Self = ControlFunction(U7(85));
|
||||
pub const UNDEFINED_86: Self = ControlFunction(U7(86));
|
||||
pub const UNDEFINED_87: Self = ControlFunction(U7(87));
|
||||
pub const HIGH_RESOLUTION_VELOCITY_PREFIX_88: Self = ControlFunction(U7(88));
|
||||
pub const UNDEFINED_89: Self = ControlFunction(U7(89));
|
||||
pub const UNDEFINED_90: Self = ControlFunction(U7(90));
|
||||
pub const EFFECTS_1_DEPTH_91: Self = ControlFunction(U7(91));
|
||||
pub const EFFECTS_2_DEPTH_92: Self = ControlFunction(U7(92));
|
||||
pub const EFFECTS_3_DEPTH_93: Self = ControlFunction(U7(93));
|
||||
pub const EFFECTS_4_DEPTH_94: Self = ControlFunction(U7(94));
|
||||
pub const EFFECTS_5_DEPTH_95: Self = ControlFunction(U7(95));
|
||||
pub const DATA_INCREMENT_96: Self = ControlFunction(U7(96));
|
||||
pub const DATA_DECREMENT_97: Self = ControlFunction(U7(97));
|
||||
pub const NPRN_LSB_98: Self = ControlFunction(U7(98));
|
||||
pub const NPRN_MSB_99: Self = ControlFunction(U7(99));
|
||||
pub const RPN_LSB_100: Self = ControlFunction(U7(100));
|
||||
pub const UNDEFINED_101: Self = ControlFunction(U7(101));
|
||||
pub const UNDEFINED_102: Self = ControlFunction(U7(102));
|
||||
pub const UNDEFINED_103: Self = ControlFunction(U7(103));
|
||||
pub const UNDEFINED_104: Self = ControlFunction(U7(104));
|
||||
pub const UNDEFINED_105: Self = ControlFunction(U7(105));
|
||||
pub const UNDEFINED_106: Self = ControlFunction(U7(106));
|
||||
pub const UNDEFINED_107: Self = ControlFunction(U7(107));
|
||||
pub const UNDEFINED_108: Self = ControlFunction(U7(108));
|
||||
pub const UNDEFINED_109: Self = ControlFunction(U7(109));
|
||||
pub const UNDEFINED_110: Self = ControlFunction(U7(110));
|
||||
pub const UNDEFINED_111: Self = ControlFunction(U7(111));
|
||||
pub const UNDEFINED_112: Self = ControlFunction(U7(112));
|
||||
pub const UNDEFINED_113: Self = ControlFunction(U7(113));
|
||||
pub const UNDEFINED_114: Self = ControlFunction(U7(114));
|
||||
pub const UNDEFINED_115: Self = ControlFunction(U7(115));
|
||||
pub const UNDEFINED_116: Self = ControlFunction(U7(116));
|
||||
pub const UNDEFINED_117: Self = ControlFunction(U7(117));
|
||||
pub const UNDEFINED_118: Self = ControlFunction(U7(118));
|
||||
pub const UNDEFINED_119: Self = ControlFunction(U7(119));
|
||||
pub const ALL_SOUND_OFF_120: Self = ControlFunction(U7(120));
|
||||
pub const RESET_ALL_CONTROLLERS_121: Self = ControlFunction(U7(121));
|
||||
pub const LOCAL_CONTROL_OFF_122: Self = ControlFunction(U7(122));
|
||||
pub const ALL_NOTES_OFF_123: Self = ControlFunction(U7(123));
|
||||
pub const OMNI_MODE_OFF_124: Self = ControlFunction(U7(124));
|
||||
pub const OMNI_MODE_ON_125: Self = ControlFunction(U7(125));
|
||||
pub const MONO_MODE_ON_126: Self = ControlFunction(U7(126));
|
||||
pub const POLY_MODE_ON_127: Self = ControlFunction(U7(127));
|
||||
}
|
|
@ -3,6 +3,10 @@ use crate::data::midi::channel::Channel;
|
|||
use crate::data::midi::notes::Note;
|
||||
use crate::data::byte::u7::U7;
|
||||
use crate::data::midi::message::raw::{Raw,Payload};
|
||||
use crate::data::byte::from_traits::FromClamped;
|
||||
use core::convert::TryFrom;
|
||||
use crate::data::usb_midi::usb_midi_event_packet::MidiPacketParsingError;
|
||||
use crate::data::midi::message::control_function::ControlFunction;
|
||||
|
||||
type Velocity = U7;
|
||||
|
||||
|
@ -10,6 +14,7 @@ type Velocity = U7;
|
|||
/// Note: not current exhaustive and SysEx messages end up
|
||||
/// being a confusing case. So are currently note implemented
|
||||
/// they are sort-of unbounded
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum Message {
|
||||
NoteOff(Channel,Note,Velocity),
|
||||
NoteOn(Channel,Note,Velocity),
|
||||
|
@ -17,14 +22,16 @@ pub enum Message {
|
|||
ProgramChange(Channel,U7),
|
||||
ChannelAftertouch(Channel,U7),
|
||||
PitchWheelChange(Channel,U7,U7),
|
||||
ControlChange(Channel,ControlFunction,U7)
|
||||
}
|
||||
|
||||
const NOTE_OFF_MASK :u8 = 0b1000_0000;
|
||||
const NOTE_ON_MASK :u8 = 0b1001_0000;
|
||||
const POLYPHONIC_MASK :u8 = 0b1010_0000;
|
||||
const PROGRAM_MASK :u8 = 0b1100_0000;
|
||||
const CHANNEL_AFTERTOUCH_MASK :u8 = 0b1101_0000;
|
||||
const PITCH_BEND_MASK :u8 = 0b1110_0000;
|
||||
const NOTE_OFF_MASK :u8 = 0b1000_0000;
|
||||
const NOTE_ON_MASK :u8 = 0b1001_0000;
|
||||
const POLYPHONIC_MASK :u8 = 0b1010_0000;
|
||||
const PROGRAM_MASK :u8 = 0b1100_0000;
|
||||
const CHANNEL_AFTERTOUCH_MASK :u8 = 0b1101_0000;
|
||||
const PITCH_BEND_MASK :u8 = 0b1110_0000;
|
||||
const CONTROL_CHANGE_MASK :u8 = 0b1011_0000;
|
||||
|
||||
impl From<Message> for Raw {
|
||||
fn from(value:Message) -> Raw {
|
||||
|
@ -58,8 +65,58 @@ impl From<Message> for Raw {
|
|||
let payload = Payload::DoubleByte(lsb,msb);
|
||||
let status = PITCH_BEND_MASK | u8::from(chan);
|
||||
Raw {status , payload}
|
||||
},
|
||||
Message::ControlChange(chan, control_function, value) => {
|
||||
let payload = Payload::DoubleByte(control_function.0, value);
|
||||
let status = CONTROL_CHANGE_MASK | u8::from(chan);
|
||||
Raw {status, payload}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a [u8]> for Message {
|
||||
type Error = MidiPacketParsingError;
|
||||
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
|
||||
let status_byte = match data.get(0) {
|
||||
Some(byte) => byte,
|
||||
None => return Err(MidiPacketParsingError::MissingDataPacket)
|
||||
};
|
||||
|
||||
let event_type = status_byte & 0b1111_0000;
|
||||
let channel_bytes = (status_byte) & 0b0000_1111;
|
||||
|
||||
let channel = Channel::try_from(channel_bytes).ok().unwrap();
|
||||
|
||||
match event_type {
|
||||
NOTE_ON_MASK => Ok(Message::NoteOn(channel, get_note(data)?, get_u7_at(data, 2)?)),
|
||||
NOTE_OFF_MASK => Ok(Message::NoteOff(channel, get_note(data)?, get_u7_at(data, 2)?)),
|
||||
POLYPHONIC_MASK => Ok(Message::PolyphonicAftertouch(channel, get_note(data)?, get_u7_at(data, 2)?)),
|
||||
PROGRAM_MASK => Ok(Message::ProgramChange(channel, get_u7_at(data, 1)?)),
|
||||
CHANNEL_AFTERTOUCH_MASK => Ok(Message::ChannelAftertouch(channel, get_u7_at(data, 1)?)),
|
||||
PITCH_BEND_MASK => Ok(Message::PitchWheelChange(channel, get_u7_at(data, 1)?, get_u7_at(data, 2)?)),
|
||||
CONTROL_CHANGE_MASK => Ok(Message::ControlChange(channel, ControlFunction(get_u7_at(data, 1)?), get_u7_at(data, 2)?)),
|
||||
_ => Err(MidiPacketParsingError::InvalidEventType(event_type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_note(data: &[u8]) -> Result<Note, MidiPacketParsingError> {
|
||||
let note_byte = get_byte_at_position(data, 1)?;
|
||||
match Note::try_from(note_byte) {
|
||||
Ok(note) => Ok(note),
|
||||
Err(_) => Err(MidiPacketParsingError::InvalidNote(note_byte))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_u7_at(data: &[u8], index: usize) -> Result<U7, MidiPacketParsingError> {
|
||||
let data_byte = get_byte_at_position(data, index)?;
|
||||
Ok(U7::from_clamped(data_byte))
|
||||
}
|
||||
|
||||
fn get_byte_at_position(data: &[u8], index: usize) -> Result<u8, MidiPacketParsingError> {
|
||||
match data.get(index) {
|
||||
Some(byte) => Ok(*byte),
|
||||
None => Err(MidiPacketParsingError::MissingDataPacket)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod message;
|
||||
pub mod raw;
|
||||
pub use crate::data::midi::message::message::{Message};
|
||||
pub use crate::data::midi::message::message::{Message};
|
||||
pub mod control_function;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use crate::data::byte::u7::U7;
|
||||
use crate::data::byte::from_traits::FromOverFlow;
|
||||
use num_enum::TryFromPrimitive;
|
||||
/// 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,Copy,Clone)]
|
||||
#[derive(Debug,Copy,Clone,TryFromPrimitive,Eq,PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum Note {
|
||||
C1m, Cs1m, D1m, Ds1m, E1m, F1m, Fs1m, G1m, Gs1m, A1m, As1m, B1m,
|
||||
|
|
|
@ -4,7 +4,7 @@ pub const USB_CLASS_NONE : u8 = 0x00;
|
|||
pub const USB_AUDIO_CLASS: u8 = 0x01;
|
||||
pub const USB_AUDIOCONTROL_SUBCLASS: u8 = 0x01;
|
||||
pub const USB_MIDISTREAMING_SUBCLASS: u8 =0x03;
|
||||
//pub const MIDI_IN_JACK_SUBTYPE : u8 = 0x02;
|
||||
pub const MIDI_IN_JACK_SUBTYPE : u8 = 0x02;
|
||||
pub const MIDI_OUT_JACK_SUBTYPE : u8 = 0x03;
|
||||
pub const EMBEDDED : u8 = 0x01;
|
||||
pub const CS_INTERFACE: u8 = 0x24;
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::data::byte::u4::U4;
|
|||
/// 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
|
||||
#[derive(Debug,Clone,Copy)]
|
||||
#[derive(Debug,Clone,Copy,Eq, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum CableNumber {
|
||||
Cable0 = 0x0, Cable1 = 0x1, Cable2 = 0x2, Cable3 = 0x3,
|
||||
|
|
|
@ -71,7 +71,8 @@ impl CodeIndexNumber {
|
|||
Message::ChannelAftertouch(_,_) => CodeIndexNumber::CHANNEL_PRESSURE,
|
||||
Message::PitchWheelChange(_,_,_) => CodeIndexNumber::PITCHBEND_CHANGE,
|
||||
Message::PolyphonicAftertouch(_,_,_) => CodeIndexNumber::POLY_KEYPRESS,
|
||||
Message::ProgramChange(_,_) => CodeIndexNumber::PROGRAM_CHANGE
|
||||
Message::ProgramChange(_,_) => CodeIndexNumber::PROGRAM_CHANGE,
|
||||
Message::ControlChange(_,_,_) => CodeIndexNumber::CONTROL_CHANGE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
36
src/data/usb_midi/midi_packet_reader.rs
Normal file
36
src/data/usb_midi/midi_packet_reader.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use core::convert::TryFrom;
|
||||
use crate::data::usb_midi::usb_midi_event_packet::{UsbMidiEventPacket, MidiPacketParsingError};
|
||||
use crate::midi_device::{MAX_PACKET_SIZE, MIDI_PACKET_SIZE};
|
||||
|
||||
pub struct MidiPacketBufferReader<'a> {
|
||||
buffer: &'a [u8; MAX_PACKET_SIZE],
|
||||
position: usize,
|
||||
raw_bytes_received: usize
|
||||
}
|
||||
|
||||
impl<'a> MidiPacketBufferReader<'a> {
|
||||
pub fn new(buffer: &'a [u8; MAX_PACKET_SIZE], raw_bytes_received: usize) -> Self {
|
||||
MidiPacketBufferReader {
|
||||
buffer,
|
||||
position: 0,
|
||||
raw_bytes_received
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for MidiPacketBufferReader<'a> {
|
||||
type Item = Result<UsbMidiEventPacket, MidiPacketParsingError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.position <= MAX_PACKET_SIZE && self.position < self.raw_bytes_received {
|
||||
let packet = match self.buffer.get(self.position .. (self.position + MIDI_PACKET_SIZE)) {
|
||||
Some(packet) => Some(UsbMidiEventPacket::try_from(packet)),
|
||||
None => None
|
||||
};
|
||||
|
||||
self.position += MIDI_PACKET_SIZE;
|
||||
return packet;
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod usb_midi_event_packet;
|
||||
pub mod cable_number;
|
||||
pub mod code_index_number;
|
||||
pub mod code_index_number;
|
||||
pub mod midi_packet_reader;
|
|
@ -3,11 +3,13 @@ use crate::data::usb_midi::code_index_number::CodeIndexNumber;
|
|||
use crate::data::midi::message::Message;
|
||||
use crate::data::byte::u4::U4;
|
||||
use crate::data::midi::message::raw::{Payload,Raw};
|
||||
use core::convert::TryFrom;
|
||||
|
||||
|
||||
/// A packet that communicates with the host
|
||||
/// Currently supported is sending the specified normal midi
|
||||
/// message over the supplied cable number
|
||||
#[derive(Debug,Eq, PartialEq)]
|
||||
pub struct UsbMidiEventPacket {
|
||||
pub cable_number : CableNumber,
|
||||
pub message: Message
|
||||
|
@ -37,6 +39,42 @@ impl From<UsbMidiEventPacket> for [u8;4] {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MidiPacketParsingError {
|
||||
InvalidNote(u8),
|
||||
InvalidCableNumber(u8),
|
||||
InvalidEventType(u8),
|
||||
MissingDataPacket
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for UsbMidiEventPacket {
|
||||
type Error = MidiPacketParsingError;
|
||||
|
||||
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||
let raw_cable_number= match value.get(0) {
|
||||
Some(byte) => *byte >> 4,
|
||||
None => return Err(MidiPacketParsingError::MissingDataPacket)
|
||||
};
|
||||
|
||||
let cable_number = match CableNumber::try_from(u8::from(raw_cable_number)) {
|
||||
Ok(val) => val,
|
||||
_ => return Err(MidiPacketParsingError::InvalidCableNumber(raw_cable_number))
|
||||
};
|
||||
|
||||
let message_body = match value.get(1..) {
|
||||
Some(bytes) => bytes,
|
||||
None => return Err(MidiPacketParsingError::MissingDataPacket)
|
||||
};
|
||||
|
||||
let message = Message::try_from(message_body)?;
|
||||
|
||||
Ok(UsbMidiEventPacket {
|
||||
cable_number,
|
||||
message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl UsbMidiEventPacket{
|
||||
|
||||
pub fn from_midi(cable:CableNumber, midi:Message)
|
||||
|
@ -46,4 +84,60 @@ impl UsbMidiEventPacket{
|
|||
message : midi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::convert::TryFrom;
|
||||
use crate::data::usb_midi::usb_midi_event_packet::UsbMidiEventPacket;
|
||||
use crate::data::midi::channel::Channel::{Channel1, Channel2};
|
||||
use crate::data::midi::notes::Note;
|
||||
use crate::data::byte::u7::U7;
|
||||
use crate::data::midi::message::Message;
|
||||
use crate::data::usb_midi::cable_number::CableNumber::{Cable0,Cable1};
|
||||
use crate::data::midi::message::control_function::ControlFunction;
|
||||
|
||||
macro_rules! decode_message_test {
|
||||
($($id:ident:$value:expr,)*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $id() {
|
||||
let (usb_midi_data_packet,expected) = $value;
|
||||
let message = UsbMidiEventPacket::try_from(&usb_midi_data_packet[..]).unwrap();
|
||||
assert_eq!(expected, message);
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
decode_message_test! {
|
||||
note_on: ([9, 144, 36, 127], UsbMidiEventPacket {
|
||||
cable_number: Cable0,
|
||||
message: Message::NoteOn(Channel1, Note::C2, U7(127))
|
||||
}),
|
||||
note_off: ([8, 128, 36, 0], UsbMidiEventPacket {
|
||||
cable_number: Cable0,
|
||||
message: Message::NoteOff(Channel1, Note::C2, U7(0))
|
||||
}),
|
||||
polyphonic_aftertouch: ([10, 160, 36, 64], UsbMidiEventPacket {
|
||||
cable_number: Cable0,
|
||||
message: Message::PolyphonicAftertouch(Channel1, Note::C2, U7(64))
|
||||
}),
|
||||
program_change: ([28, 192, 127, 0], UsbMidiEventPacket {
|
||||
cable_number: Cable1,
|
||||
message: Message::ProgramChange(Channel1, U7(127))
|
||||
}),
|
||||
channel_aftertouch: ([13, 208, 127, 0], UsbMidiEventPacket {
|
||||
cable_number: Cable0,
|
||||
message: Message::ChannelAftertouch(Channel1, U7(127))
|
||||
}),
|
||||
pitch_wheel: ([14, 224, 64, 32], UsbMidiEventPacket {
|
||||
cable_number: Cable0,
|
||||
message: Message::PitchWheelChange(Channel1, U7(64), U7(32))
|
||||
}),
|
||||
control_change: ([11, 177, 1, 32], UsbMidiEventPacket {
|
||||
cable_number: Cable0,
|
||||
message: Message::ControlChange(Channel2, ControlFunction::MOD_WHEEL_1, U7(32))
|
||||
}),
|
||||
}
|
||||
}
|
|
@ -1,21 +1,28 @@
|
|||
use usb_device::class_prelude::*;
|
||||
use usb_device::Result;
|
||||
use crate::data::usb::constants::*;
|
||||
use crate::data::usb_midi::usb_midi_event_packet::UsbMidiEventPacket;
|
||||
use crate::data::usb_midi::usb_midi_event_packet::{UsbMidiEventPacket, MidiPacketParsingError};
|
||||
|
||||
//const MIDI_IN_SIZE: u8 = 0x06;
|
||||
const MIDI_IN_SIZE: u8 = 0x06;
|
||||
const MIDI_OUT_SIZE: u8 = 0x09;
|
||||
|
||||
pub const MIDI_PACKET_SIZE: usize = 4;
|
||||
pub const MAX_PACKET_SIZE: usize = 64;
|
||||
|
||||
///Note we are using MidiIn here to refer to the fact that
|
||||
///The Host sees it as a midi in device
|
||||
///This class allows you to send data in
|
||||
pub struct MidiClass<'a,B: UsbBus> {
|
||||
standard_ac: InterfaceNumber,
|
||||
standard_mc: InterfaceNumber,
|
||||
//standard_bulkout: EndpointOut<'a, B>,
|
||||
standard_bulkout: EndpointOut<'a, B>,
|
||||
standard_bulkin: EndpointIn<'a,B>
|
||||
}
|
||||
|
||||
pub enum MidiReadError {
|
||||
ParsingFailed(MidiPacketParsingError),
|
||||
UsbError(UsbError)
|
||||
}
|
||||
|
||||
impl<B: UsbBus> MidiClass<'_, B> {
|
||||
/// Creates a new MidiClass with the provided UsbBus
|
||||
|
@ -23,16 +30,19 @@ impl<B: UsbBus> MidiClass<'_, B> {
|
|||
MidiClass {
|
||||
standard_ac: alloc.interface(),
|
||||
standard_mc: alloc.interface(),
|
||||
//standard_bulkout : alloc.bulk(64),
|
||||
standard_bulkin: alloc.bulk(64)
|
||||
standard_bulkout : alloc.bulk(MAX_PACKET_SIZE as u16),
|
||||
standard_bulkin: alloc.bulk(MAX_PACKET_SIZE as u16)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_message(&mut self, usb_midi:UsbMidiEventPacket) -> Result<usize> {
|
||||
let bytes : [u8;4] = usb_midi.into();
|
||||
let bytes : [u8;MIDI_PACKET_SIZE] = usb_midi.into();
|
||||
self.standard_bulkin.write(&bytes)
|
||||
}
|
||||
|
||||
pub fn read(&mut self, buffer: &mut [u8]) -> Result<usize> {
|
||||
self.standard_bulkout.read(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: UsbBus> UsbClass<B> for MidiClass<'_, B> {
|
||||
|
@ -82,7 +92,7 @@ impl<B: UsbBus> UsbClass<B> for MidiClass<'_, B> {
|
|||
|
||||
//JACKS
|
||||
|
||||
/* writer.write(
|
||||
writer.write(
|
||||
CS_INTERFACE,
|
||||
&[
|
||||
MIDI_IN_JACK_SUBTYPE,
|
||||
|
@ -90,7 +100,7 @@ impl<B: UsbBus> UsbClass<B> for MidiClass<'_, B> {
|
|||
0x01, // id
|
||||
0x00
|
||||
]
|
||||
)?; */
|
||||
)?;
|
||||
|
||||
writer.write (
|
||||
CS_INTERFACE,
|
||||
|
@ -105,7 +115,7 @@ impl<B: UsbBus> UsbClass<B> for MidiClass<'_, B> {
|
|||
]
|
||||
)?;
|
||||
|
||||
/* writer.endpoint(&self.standard_bulkout)?;
|
||||
writer.endpoint(&self.standard_bulkout)?;
|
||||
|
||||
writer.write(
|
||||
CS_ENDPOINT,
|
||||
|
@ -114,7 +124,7 @@ impl<B: UsbBus> UsbClass<B> for MidiClass<'_, B> {
|
|||
0x01,
|
||||
0x01
|
||||
]
|
||||
)?; */
|
||||
)?;
|
||||
|
||||
writer.endpoint(&self.standard_bulkin)?;
|
||||
|
||||
|
|
Loading…
Reference in a new issue