1pub 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#[derive(Debug, Clone, Eq, PartialEq)]
15pub struct UsbMidiEventPacket {
16 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#[derive(Debug, Clone, Eq, PartialEq)]
28pub enum UsbMidiEventPacketError {
29 InvalidPacket,
31 InvalidNote(u8),
33 InvalidCableNumber(u8),
35 InvalidCodeIndexNumber(u8),
37 InvalidEventType(u8),
39 MissingDataPacket,
41 EmptyPayload,
43 InvalidPayloadStatus,
45 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 pub fn cable_number(&self) -> CableNumber {
64 let raw_cable_number = self.raw[0] >> 4;
65
66 CableNumber::try_from(raw_cable_number).unwrap_or_default()
68 }
69
70 pub fn header(&self) -> u8 {
72 self.raw[0]
73 }
74
75 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 Err(_) => &[],
87 }
88 }
89
90 pub fn as_raw_bytes(&self) -> &[u8] {
92 &self.raw
93 }
94
95 pub fn to_raw_bytes(&self) -> [u8; 4] {
97 self.raw
98 }
99
100 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 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 pub fn is_sysex_start(&self) -> bool {
137 self.is_sysex() && self.raw[1] == 0xF0
138 }
139
140 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}