usbd_midi/packet/
mod.rs

1//! USB MIDI event packet parser and related types.
2
3pub mod cable_number;
4pub mod code_index_number;
5pub mod reader;
6
7use crate::packet::cable_number::CableNumber;
8use crate::packet::code_index_number::CodeIndexNumber;
9
10/// A packet that communicates with the host.
11///
12/// Currently supported is sending the specified normal midi
13/// message over the supplied cable number
14#[derive(Debug, Clone, Eq, PartialEq)]
15pub struct UsbMidiEventPacket {
16    /// Raw packet data.
17    raw: [u8; 4],
18}
19
20impl From<UsbMidiEventPacket> for [u8; 4] {
21    fn from(value: UsbMidiEventPacket) -> [u8; 4] {
22        value.raw
23    }
24}
25
26/// Error variants for parsing the packet.
27#[derive(Debug, Clone, Eq, PartialEq)]
28pub enum UsbMidiEventPacketError {
29    /// Invalid packet.
30    InvalidPacket,
31    /// Invalid note.
32    InvalidNote(u8),
33    /// Invalid cable number.
34    InvalidCableNumber(u8),
35    /// Invalid code index number.
36    InvalidCodeIndexNumber(u8),
37    /// Invalid event type.
38    InvalidEventType(u8),
39    /// Missing data packet.
40    MissingDataPacket,
41    /// Empty payload.
42    EmptyPayload,
43    /// Invalid payload status.
44    InvalidPayloadStatus,
45    /// Invalid payload size.
46    InvalidPayloadSize,
47}
48
49impl TryFrom<&[u8]> for UsbMidiEventPacket {
50    type Error = UsbMidiEventPacketError;
51
52    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
53        let Ok(raw) = value.try_into() else {
54            return Err(UsbMidiEventPacketError::InvalidPacket);
55        };
56
57        Ok(Self { raw })
58    }
59}
60
61impl UsbMidiEventPacket {
62    /// Returns the cable number.
63    pub fn cable_number(&self) -> CableNumber {
64        let raw_cable_number = self.raw[0] >> 4;
65
66        // Unwrap can't fail because of limited `raw_cable_number` value range.
67        CableNumber::try_from(raw_cable_number).unwrap_or_default()
68    }
69
70    /// Returns the header byte.
71    pub fn header(&self) -> u8 {
72        self.raw[0]
73    }
74
75    /// Returns a slice to the event payload bytes. The length is dependent on the payload type.
76    pub fn payload_bytes(&self) -> &[u8] {
77        let raw_cin = self.raw[0] & 0x0F;
78
79        match CodeIndexNumber::try_from(raw_cin) {
80            Ok(cin) => {
81                let size = cin.payload_size();
82                &self.raw[1..1 + size]
83            }
84
85            // Can't happen because of limited `raw_cin` value range.
86            Err(_) => &[],
87        }
88    }
89
90    /// Returns a reference to the raw bytes.
91    pub fn as_raw_bytes(&self) -> &[u8] {
92        &self.raw
93    }
94
95    /// Returns the raw bytes as owned array.
96    pub fn to_raw_bytes(&self) -> [u8; 4] {
97        self.raw
98    }
99
100    /// Creates a packet from a slice of event payload bytes.
101    pub fn try_from_payload_bytes(
102        cable: CableNumber,
103        bytes: &[u8],
104    ) -> Result<Self, UsbMidiEventPacketError> {
105        let cin = CodeIndexNumber::try_from_payload(bytes)?;
106        let payload_size = cin.payload_size();
107
108        if bytes.len() < payload_size {
109            return Err(UsbMidiEventPacketError::InvalidPayloadSize);
110        }
111
112        let mut raw = [0; 4];
113        raw[0] = (cable as u8) << 4 | cin as u8;
114        raw[1..1 + payload_size].copy_from_slice(&bytes[..payload_size]);
115
116        Ok(Self { raw })
117    }
118
119    /// Returns if the packet payload is part of a SysEx message.
120    pub fn is_sysex(&self) -> bool {
121        let Ok(cin) = CodeIndexNumber::try_from(self.raw[0] & 0x0F) else {
122            return false;
123        };
124
125        match cin {
126            CodeIndexNumber::SysexStartsOrContinues
127            | CodeIndexNumber::SysexEnds2Bytes
128            | CodeIndexNumber::SysexEnds3Bytes => true,
129            CodeIndexNumber::SystemCommon1Byte => self.raw[1] == 0xF7,
130            CodeIndexNumber::SingleByte => self.raw[1] < 0x80,
131            _ => false,
132        }
133    }
134
135    /// Returns if the packet payload contains the start of a SysEx message.
136    pub fn is_sysex_start(&self) -> bool {
137        self.is_sysex() && self.raw[1] == 0xF0
138    }
139
140    /// Returns if the packet payload contains the end of a SysEx message.
141    pub fn is_sysex_end(&self) -> bool {
142        self.is_sysex() && (self.raw[1] == 0xF7 || self.raw[2] == 0xF7 || self.raw[3] == 0xF7)
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    mod decode {
151        use super::*;
152
153        macro_rules! decode_packet_test {
154            ($($id:ident: $value:expr,)*) => {
155                $(
156                    #[test]
157                    fn $id() {
158                        let (raw, expected) = $value;
159                        let expected = (
160                            expected.0,
161                            expected.1.as_slice(),
162                            expected.2, expected.3,
163                            expected.4
164                        );
165                        let packet = UsbMidiEventPacket::try_from(raw.as_slice()).unwrap();
166                        let decoded = (
167                            packet.cable_number(),
168                            packet.payload_bytes(),
169                            packet.is_sysex(),
170                            packet.is_sysex_start(),
171                            packet.is_sysex_end()
172                        );
173                        assert_eq!(decoded, expected);
174                    }
175                )*
176            }
177        }
178
179        decode_packet_test! {
180            note_off: ([0x18, 0x80, 60, 64], (CableNumber::Cable1, [0x80, 60, 64], false, false, false)),
181            note_on: ([0x49, 0x92, 48, 20], (CableNumber::Cable4, [0x92, 48, 20], false, false, false)),
182            poly_key_press: ([0x0A, 0xA0, 15, 74], (CableNumber::Cable0, [0xA0, 15, 74], false, false, false)),
183            control_change: ([0x0B, 0xB0, 64, 127], (CableNumber::Cable0, [0xB0, 64, 127], false, false, false)),
184            program_change: ([0x0C, 0xC0, 5, 0], (CableNumber::Cable0, [0xC0, 5], false, false, false)),
185            channel_pressure: ([0x0D, 0xD0, 85, 0], (CableNumber::Cable0, [0xD0, 85], false, false, false)),
186            pitch_bend: ([0x0E, 0xE0, 40, 120], (CableNumber::Cable0, [0xE0, 40, 120], false, false, false)),
187            mtc_quarter_frame: ([0x02, 0xF1, 27, 0], (CableNumber::Cable0, [0xF1, 27], false, false, false)),
188            song_position_pointer: ([0x03, 0xF2, 38, 17], (CableNumber::Cable0, [0xF2, 38, 17], false, false, false)),
189            song_select: ([0x02, 0xF3, 2, 0], (CableNumber::Cable0, [0xF3, 2], false, false, false)),
190            tune_request: ([0x05, 0xF6, 0, 0], (CableNumber::Cable0, [0xF6], false, false, false)),
191            timing_clock: ([0x0F, 0xF8, 0, 0], (CableNumber::Cable0, [0xF8], false, false, false)),
192            tick: ([0x0F, 0xF9, 0, 0], (CableNumber::Cable0, [0xF9], false, false, false)),
193            start: ([0x0F, 0xFA, 0, 0], (CableNumber::Cable0, [0xFA], false, false, false)),
194            continue_: ([0x0F, 0xFB, 0, 0], (CableNumber::Cable0, [0xFB], false, false, false)),
195            stop: ([0x0F, 0xFC, 0, 0], (CableNumber::Cable0, [0xFC], false, false, false)),
196            active_sensing: ([0x0F, 0xFE, 0, 0], (CableNumber::Cable0, [0xFE], false, false, false)),
197            system_reset: ([0x0F, 0xFF, 0, 0], (CableNumber::Cable0, [0xFF], false, false, false)),
198            sysex_starts: ([0x04, 0xF0, 1, 2], (CableNumber::Cable0, [0xF0, 1, 2], true, true, false)),
199            sysex_continues_1byte: ([0x0F, 1, 0, 0], (CableNumber::Cable0, [1], true, false, false)),
200            sysex_continues_3bytes: ([0x04, 1, 2, 3], (CableNumber::Cable0, [1, 2, 3], true, false, false)),
201            sysex_ends_1byte: ([0x05, 0xF7, 0, 0], (CableNumber::Cable0, [0xF7], true, false, true)),
202            sysex_ends_2bytes: ([0x06, 1, 0xF7, 0], (CableNumber::Cable0, [1, 0xF7], true, false, true)),
203            sysex_ends_3bytes: ([0x07, 1, 2, 0xF7], (CableNumber::Cable0, [1, 2, 0xF7], true, false, true)),
204            sysex_2bytes: ([0x06, 0xF0, 0xF7, 0], (CableNumber::Cable0, [0xF0, 0xF7], true, true, true)),
205            sysex_3bytes: ([0x07, 0xF0, 1, 0xF7], (CableNumber::Cable0, [0xF0, 1, 0xF7], true, true, true)),
206            undefined_f4: ([0x02, 0xF4, 1, 0], (CableNumber::Cable0, [0xF4, 1], false, false, false)),
207            undefined_f5: ([0x03, 0xF5, 1, 2], (CableNumber::Cable0, [0xF5, 1, 2], false, false, false)),
208            empty: ([0x00, 0, 0, 0], (CableNumber::Cable0, [0, 0, 0], false, false, false)),
209        }
210    }
211
212    mod encode {
213        use super::*;
214
215        macro_rules! encode_packet_test {
216            ($($id:ident: $value:expr,)*) => {
217                $(
218                    #[test]
219                    fn $id() {
220                        let ((cable, payload), expected) = $value;
221                        let payload = payload.as_slice();
222                        let encoded = UsbMidiEventPacket::try_from_payload_bytes(cable, payload);
223                        let expected: Result<[u8; 4], UsbMidiEventPacketError> = expected;
224                        assert_eq!(encoded, expected.map(
225                            |v| UsbMidiEventPacket::try_from(v.as_slice()).unwrap())
226                        );
227                    }
228                )*
229            }
230        }
231
232        encode_packet_test! {
233            note_off: ((CableNumber::Cable3, [0x80, 33, 75]), Ok([0x38, 0x80, 33, 75])),
234            note_on: ((CableNumber::Cable2, [0x96, 67, 14]), Ok([0x29, 0x96, 67, 14])),
235            poly_key_press: ((CableNumber::Cable0, [0xA0, 48, 72]), Ok([0x0A, 0xA0, 48, 72])),
236            control_change: ((CableNumber::Cable0, [0xB0, 10, 127]), Ok([0x0B, 0xB0, 10, 127])),
237            program_change: ((CableNumber::Cable0, [0xC0, 36]), Ok([0x0C, 0xC0, 36, 0])),
238            program_change_extra: ((CableNumber::Cable0, [0xC0, 36, 54]), Ok([0x0C, 0xC0, 36, 0])),
239            channel_pressure: ((CableNumber::Cable0, [0xD0, 115]), Ok([0x0D, 0xD0, 115, 0])),
240            channel_pressure_extra: ((CableNumber::Cable0, [0xD0, 115, 27]), Ok([0x0D, 0xD0, 115, 0])),
241            pitch_bend: ((CableNumber::Cable0, [0xE0, 93, 46]), Ok([0x0E, 0xE0, 93, 46])),
242            mtc_quarter_frame: ((CableNumber::Cable0, [0xF1, 102, 46]), Ok([0x02, 0xF1, 102, 0])),
243            mtc_quarter_frame_extra: ((CableNumber::Cable0, [0xF1, 102, 46, 7]), Ok([0x02, 0xF1, 102, 0])),
244            song_position_pointer: ((CableNumber::Cable0, [0xF2, 42, 74]), Ok([0x03, 0xF2, 42, 74])),
245            song_select: ((CableNumber::Cable0, [0xF3, 24]), Ok([0x02, 0xF3, 24, 0])),
246            song_select_extra: ((CableNumber::Cable0, [0xF3, 24, 96]), Ok([0x02, 0xF3, 24, 0])),
247            tune_request: ((CableNumber::Cable0, [0xF6]), Ok([0x05, 0xF6, 0, 0])),
248            tune_request_extra: ((CableNumber::Cable0, [0xF6, 67, 72]), Ok([0x05, 0xF6, 0, 0])),
249            timing_clock: ((CableNumber::Cable0, [0xF8]), Ok([0x0F, 0xF8, 0, 0])),
250            timing_clock_extra: ((CableNumber::Cable0, [0xF8, 38, 126]), Ok([0x0F, 0xF8, 0, 0])),
251            tick: ((CableNumber::Cable0, [0xF9]), Ok([0x0F, 0xF9, 0, 0])),
252            start: ((CableNumber::Cable0, [0xFA]), Ok([0x0F, 0xFA, 0, 0])),
253            continue_: ((CableNumber::Cable0, [0xFB]), Ok([0x0F, 0xFB, 0, 0])),
254            stop: ((CableNumber::Cable0, [0xFC]), Ok([0x0F, 0xFC, 0, 0])),
255            active_sensing: ((CableNumber::Cable0, [0xFE]), Ok([0x0F, 0xFE, 0, 0])),
256            system_reset: ((CableNumber::Cable0, [0xFF]), Ok([0x0F, 0xFF, 0, 0])),
257            sysex_starts: ((CableNumber::Cable0, [0xF0, 1, 2]), Ok([0x04, 0xF0, 1, 2])),
258            sysex_starts_1byte: ((CableNumber::Cable0, [0xF0]), Err(UsbMidiEventPacketError::InvalidPayloadSize)),
259            sysex_starts_2bytes: ((CableNumber::Cable0, [0xF0, 1]), Err(UsbMidiEventPacketError::InvalidPayloadSize)),
260            sysex_continues_1byte: ((CableNumber::Cable0, [1]), Ok([0x0F, 1, 0, 0])),
261            sysex_continues_2bytes: ((CableNumber::Cable0, [1, 2]), Err(UsbMidiEventPacketError::InvalidPayloadSize)),
262            sysex_continues_3bytes: ((CableNumber::Cable0, [1, 2, 3]), Ok([0x04, 1, 2, 3])),
263            sysex_ends_1byte: ((CableNumber::Cable0, [0xF7]), Ok([0x05, 0xF7, 0, 0])),
264            sysex_ends_2bytes: ((CableNumber::Cable0, [1, 0xF7]), Ok([0x06, 1, 0xF7, 0])),
265            sysex_ends_3bytes: ((CableNumber::Cable0, [1, 2, 0xF7]), Ok([0x07, 1, 2, 0xF7])),
266            sysex_2bytes: ((CableNumber::Cable0, [0xF0, 0xF7]), Ok([0x06, 0xF0, 0xF7, 0])),
267            sysex_3bytes: ((CableNumber::Cable0, [0xF0, 1, 0xF7]), Ok([0x07, 0xF0, 1, 0xF7])),
268            undefined_f4: ((CableNumber::Cable0, [0xF4]), Err(UsbMidiEventPacketError::InvalidPayloadStatus)),
269            undefined_f5: ((CableNumber::Cable0, [0xF5]), Err(UsbMidiEventPacketError::InvalidPayloadStatus)),
270            note_off_missing_1byte: ((CableNumber::Cable3, [0x80, 26]), Err(UsbMidiEventPacketError::InvalidPayloadSize)),
271            note_off_missing_2bytes: ((CableNumber::Cable3, [0x80]), Err(UsbMidiEventPacketError::InvalidPayloadSize)),
272            empty: ((CableNumber::Cable0, []), Err(UsbMidiEventPacketError::EmptyPayload)),
273        }
274    }
275}