mirror of
https://github.com/italicsjenga/usbd-midi.git
synced 2024-12-23 20:31:30 +11:00
commit
f0a03436a7
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
target
|
target
|
||||||
|
.idea
|
|
@ -12,3 +12,7 @@ license = "MIT"
|
||||||
embedded-hal = "0.2.2"
|
embedded-hal = "0.2.2"
|
||||||
nb = "0.1.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).
|
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
|
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
|
This crate requires the use of a hardware driver, that implements the
|
||||||
usb-device traits.
|
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};
|
use crate::data::byte::from_traits::{FromOverFlow,FromClamped};
|
||||||
|
|
||||||
/// A primitive value that can be from 0-0x7F
|
/// 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
|
/// Error representing that this value is not a valid u7
|
||||||
pub struct InvalidU7(u8);
|
pub struct InvalidU7(u8);
|
||||||
|
|
|
@ -3,7 +3,7 @@ use core::convert::TryFrom;
|
||||||
/// The Channel is a value ranging from 0x0 to 0xF
|
/// The Channel is a value ranging from 0x0 to 0xF
|
||||||
/// This is a standard midi concept
|
/// This is a standard midi concept
|
||||||
/// Note Channel1 = 0 on the wire
|
/// Note Channel1 = 0 on the wire
|
||||||
#[derive(Debug,Copy,Clone)]
|
#[derive(Debug,Copy,Clone, Eq, PartialEq)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Channel {
|
pub enum Channel {
|
||||||
Channel1 = 0x0, Channel2 = 0x1, Channel3 = 0x2, Channel4 = 0x3,
|
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::midi::notes::Note;
|
||||||
use crate::data::byte::u7::U7;
|
use crate::data::byte::u7::U7;
|
||||||
use crate::data::midi::message::raw::{Raw,Payload};
|
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;
|
type Velocity = U7;
|
||||||
|
|
||||||
|
@ -10,6 +14,7 @@ type Velocity = U7;
|
||||||
/// Note: not current exhaustive and SysEx messages end up
|
/// Note: not current exhaustive and SysEx messages end up
|
||||||
/// being a confusing case. So are currently note implemented
|
/// being a confusing case. So are currently note implemented
|
||||||
/// they are sort-of unbounded
|
/// they are sort-of unbounded
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
NoteOff(Channel,Note,Velocity),
|
NoteOff(Channel,Note,Velocity),
|
||||||
NoteOn(Channel,Note,Velocity),
|
NoteOn(Channel,Note,Velocity),
|
||||||
|
@ -17,14 +22,16 @@ pub enum Message {
|
||||||
ProgramChange(Channel,U7),
|
ProgramChange(Channel,U7),
|
||||||
ChannelAftertouch(Channel,U7),
|
ChannelAftertouch(Channel,U7),
|
||||||
PitchWheelChange(Channel,U7,U7),
|
PitchWheelChange(Channel,U7,U7),
|
||||||
|
ControlChange(Channel,ControlFunction,U7)
|
||||||
}
|
}
|
||||||
|
|
||||||
const NOTE_OFF_MASK :u8 = 0b1000_0000;
|
const NOTE_OFF_MASK :u8 = 0b1000_0000;
|
||||||
const NOTE_ON_MASK :u8 = 0b1001_0000;
|
const NOTE_ON_MASK :u8 = 0b1001_0000;
|
||||||
const POLYPHONIC_MASK :u8 = 0b1010_0000;
|
const POLYPHONIC_MASK :u8 = 0b1010_0000;
|
||||||
const PROGRAM_MASK :u8 = 0b1100_0000;
|
const PROGRAM_MASK :u8 = 0b1100_0000;
|
||||||
const CHANNEL_AFTERTOUCH_MASK :u8 = 0b1101_0000;
|
const CHANNEL_AFTERTOUCH_MASK :u8 = 0b1101_0000;
|
||||||
const PITCH_BEND_MASK :u8 = 0b1110_0000;
|
const PITCH_BEND_MASK :u8 = 0b1110_0000;
|
||||||
|
const CONTROL_CHANGE_MASK :u8 = 0b1011_0000;
|
||||||
|
|
||||||
impl From<Message> for Raw {
|
impl From<Message> for Raw {
|
||||||
fn from(value:Message) -> Raw {
|
fn from(value:Message) -> Raw {
|
||||||
|
@ -58,8 +65,58 @@ impl From<Message> for Raw {
|
||||||
let payload = Payload::DoubleByte(lsb,msb);
|
let payload = Payload::DoubleByte(lsb,msb);
|
||||||
let status = PITCH_BEND_MASK | u8::from(chan);
|
let status = PITCH_BEND_MASK | u8::from(chan);
|
||||||
Raw {status , payload}
|
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 message;
|
||||||
pub mod raw;
|
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::u7::U7;
|
||||||
use crate::data::byte::from_traits::FromOverFlow;
|
use crate::data::byte::from_traits::FromOverFlow;
|
||||||
|
use num_enum::TryFromPrimitive;
|
||||||
/// A simple enum type that represents all the midi 'notes'
|
/// A simple enum type that represents all the midi 'notes'
|
||||||
/// note the flat versions are associated constants
|
/// note the flat versions are associated constants
|
||||||
/// but can be referenced like Note::Bb3
|
/// but can be referenced like Note::Bb3
|
||||||
/// C1m is the C-1
|
/// C1m is the C-1
|
||||||
#[derive(Debug,Copy,Clone)]
|
#[derive(Debug,Copy,Clone,TryFromPrimitive,Eq,PartialEq)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Note {
|
pub enum Note {
|
||||||
C1m, Cs1m, D1m, Ds1m, E1m, F1m, Fs1m, G1m, Gs1m, A1m, As1m, B1m,
|
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_AUDIO_CLASS: u8 = 0x01;
|
||||||
pub const USB_AUDIOCONTROL_SUBCLASS: u8 = 0x01;
|
pub const USB_AUDIOCONTROL_SUBCLASS: u8 = 0x01;
|
||||||
pub const USB_MIDISTREAMING_SUBCLASS: u8 =0x03;
|
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 MIDI_OUT_JACK_SUBTYPE : u8 = 0x03;
|
||||||
pub const EMBEDDED : u8 = 0x01;
|
pub const EMBEDDED : u8 = 0x01;
|
||||||
pub const CS_INTERFACE: u8 = 0x24;
|
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
|
/// The Cable Number (CN) is a value ranging from 0x0 to 0xF
|
||||||
/// indicating the number assignment of the Embedded MIDI Jack associated
|
/// indicating the number assignment of the Embedded MIDI Jack associated
|
||||||
/// with the endpoint that is transferring the data
|
/// with the endpoint that is transferring the data
|
||||||
#[derive(Debug,Clone,Copy)]
|
#[derive(Debug,Clone,Copy,Eq, PartialEq)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum CableNumber {
|
pub enum CableNumber {
|
||||||
Cable0 = 0x0, Cable1 = 0x1, Cable2 = 0x2, Cable3 = 0x3,
|
Cable0 = 0x0, Cable1 = 0x1, Cable2 = 0x2, Cable3 = 0x3,
|
||||||
|
|
|
@ -71,7 +71,8 @@ impl CodeIndexNumber {
|
||||||
Message::ChannelAftertouch(_,_) => CodeIndexNumber::CHANNEL_PRESSURE,
|
Message::ChannelAftertouch(_,_) => CodeIndexNumber::CHANNEL_PRESSURE,
|
||||||
Message::PitchWheelChange(_,_,_) => CodeIndexNumber::PITCHBEND_CHANGE,
|
Message::PitchWheelChange(_,_,_) => CodeIndexNumber::PITCHBEND_CHANGE,
|
||||||
Message::PolyphonicAftertouch(_,_,_) => CodeIndexNumber::POLY_KEYPRESS,
|
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 usb_midi_event_packet;
|
||||||
pub mod cable_number;
|
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::midi::message::Message;
|
||||||
use crate::data::byte::u4::U4;
|
use crate::data::byte::u4::U4;
|
||||||
use crate::data::midi::message::raw::{Payload,Raw};
|
use crate::data::midi::message::raw::{Payload,Raw};
|
||||||
|
use core::convert::TryFrom;
|
||||||
|
|
||||||
|
|
||||||
/// A packet that communicates with the host
|
/// A packet that communicates with the host
|
||||||
/// Currently supported is sending the specified normal midi
|
/// Currently supported is sending the specified normal midi
|
||||||
/// message over the supplied cable number
|
/// message over the supplied cable number
|
||||||
|
#[derive(Debug,Eq, PartialEq)]
|
||||||
pub struct UsbMidiEventPacket {
|
pub struct UsbMidiEventPacket {
|
||||||
pub cable_number : CableNumber,
|
pub cable_number : CableNumber,
|
||||||
pub message: Message
|
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{
|
impl UsbMidiEventPacket{
|
||||||
|
|
||||||
pub fn from_midi(cable:CableNumber, midi:Message)
|
pub fn from_midi(cable:CableNumber, midi:Message)
|
||||||
|
@ -47,3 +85,59 @@ impl UsbMidiEventPacket{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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::class_prelude::*;
|
||||||
use usb_device::Result;
|
use usb_device::Result;
|
||||||
use crate::data::usb::constants::*;
|
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;
|
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
|
///Note we are using MidiIn here to refer to the fact that
|
||||||
///The Host sees it as a midi in device
|
///The Host sees it as a midi in device
|
||||||
///This class allows you to send data in
|
///This class allows you to send data in
|
||||||
pub struct MidiClass<'a,B: UsbBus> {
|
pub struct MidiClass<'a,B: UsbBus> {
|
||||||
standard_ac: InterfaceNumber,
|
standard_ac: InterfaceNumber,
|
||||||
standard_mc: InterfaceNumber,
|
standard_mc: InterfaceNumber,
|
||||||
//standard_bulkout: EndpointOut<'a, B>,
|
standard_bulkout: EndpointOut<'a, B>,
|
||||||
standard_bulkin: EndpointIn<'a,B>
|
standard_bulkin: EndpointIn<'a,B>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum MidiReadError {
|
||||||
|
ParsingFailed(MidiPacketParsingError),
|
||||||
|
UsbError(UsbError)
|
||||||
|
}
|
||||||
|
|
||||||
impl<B: UsbBus> MidiClass<'_, B> {
|
impl<B: UsbBus> MidiClass<'_, B> {
|
||||||
/// Creates a new MidiClass with the provided UsbBus
|
/// Creates a new MidiClass with the provided UsbBus
|
||||||
|
@ -23,16 +30,19 @@ impl<B: UsbBus> MidiClass<'_, B> {
|
||||||
MidiClass {
|
MidiClass {
|
||||||
standard_ac: alloc.interface(),
|
standard_ac: alloc.interface(),
|
||||||
standard_mc: alloc.interface(),
|
standard_mc: alloc.interface(),
|
||||||
//standard_bulkout : alloc.bulk(64),
|
standard_bulkout : alloc.bulk(MAX_PACKET_SIZE as u16),
|
||||||
standard_bulkin: alloc.bulk(64)
|
standard_bulkin: alloc.bulk(MAX_PACKET_SIZE as u16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_message(&mut self, usb_midi:UsbMidiEventPacket) -> Result<usize> {
|
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)
|
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> {
|
impl<B: UsbBus> UsbClass<B> for MidiClass<'_, B> {
|
||||||
|
@ -82,7 +92,7 @@ impl<B: UsbBus> UsbClass<B> for MidiClass<'_, B> {
|
||||||
|
|
||||||
//JACKS
|
//JACKS
|
||||||
|
|
||||||
/* writer.write(
|
writer.write(
|
||||||
CS_INTERFACE,
|
CS_INTERFACE,
|
||||||
&[
|
&[
|
||||||
MIDI_IN_JACK_SUBTYPE,
|
MIDI_IN_JACK_SUBTYPE,
|
||||||
|
@ -90,7 +100,7 @@ impl<B: UsbBus> UsbClass<B> for MidiClass<'_, B> {
|
||||||
0x01, // id
|
0x01, // id
|
||||||
0x00
|
0x00
|
||||||
]
|
]
|
||||||
)?; */
|
)?;
|
||||||
|
|
||||||
writer.write (
|
writer.write (
|
||||||
CS_INTERFACE,
|
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(
|
writer.write(
|
||||||
CS_ENDPOINT,
|
CS_ENDPOINT,
|
||||||
|
@ -114,7 +124,7 @@ impl<B: UsbBus> UsbClass<B> for MidiClass<'_, B> {
|
||||||
0x01,
|
0x01,
|
||||||
0x01
|
0x01
|
||||||
]
|
]
|
||||||
)?; */
|
)?;
|
||||||
|
|
||||||
writer.endpoint(&self.standard_bulkin)?;
|
writer.endpoint(&self.standard_bulkin)?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue