1use crate::midi::channel::channel_from_nibble;
2use crate::midi::data_byte::DataByte;
3use crate::midi::message::MidiMessage;
4use crate::midi::pitch_bend::PitchBend;
5use crate::midi::song_position::SongPosition;
6use crate::midi::sys_ex::SysEx;
7use crate::CodecError;
8use crate::ParseError;
9use crate::RawMidiMessage;
10use crate::SysExError;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub enum Decoded {
14 Message(MidiMessage),
15 SysEx(SysEx),
16}
17
18impl Decoded {
19 pub fn to_wire_bytes(&self) -> Vec<u8> {
20 match self {
21 Decoded::Message(msg) => RawMidiMessage::from(msg).to_vec(),
22 Decoded::SysEx(sysex) => sysex.to_wire_bytes(),
23 }
24 }
25}
26
27pub(crate) fn expected_len(status: u8) -> Option<usize> {
28 match status {
29 0xF6 | 0xF8 | 0xFA | 0xFB | 0xFC | 0xFE | 0xFF => Some(1),
30 0xF1 | 0xF3 => Some(2),
31 0xF2 => Some(3),
32 0xF0 | 0xF4 | 0xF5 | 0xF7 | 0xF9 | 0xFD => None,
33 _ => match status & 0xF0 {
34 0x80 | 0x90 | 0xA0 | 0xB0 | 0xE0 => Some(3),
35 0xC0 | 0xD0 => Some(2),
36 _ => None,
37 },
38 }
39}
40
41pub(crate) fn sysex_decode_error(err: SysExError, bytes: Vec<u8>) -> CodecError {
42 let reason = match err {
43 SysExError::TooLong { len, max } => return CodecError::SysexTooLong { len, max },
44 SysExError::MissingStart => ParseError::UnrecognizedStatus,
45 SysExError::Unterminated => ParseError::UnterminatedSysex,
46 SysExError::EmptyBody => ParseError::EmptySysex,
47 SysExError::HighBit { .. } => ParseError::SysexDataOutOfRange,
48 };
49 CodecError::Parse { reason, bytes }
50}
51
52pub fn decode(bytes: &[u8]) -> Result<Decoded, CodecError> {
53 let Some(&status) = bytes.first() else {
54 return Err(CodecError::Parse {
55 reason: ParseError::Empty,
56 bytes: bytes.to_vec(),
57 });
58 };
59
60 if status == 0xF0 {
61 return SysEx::try_from(bytes)
62 .map(Decoded::SysEx)
63 .map_err(|e| sysex_decode_error(e, bytes.to_vec()));
64 }
65 if status == 0xF7 {
66 return Err(CodecError::Parse {
67 reason: ParseError::StandaloneSysexEnd,
68 bytes: bytes.to_vec(),
69 });
70 }
71
72 let Some(len) = expected_len(status) else {
73 return Err(CodecError::Parse {
74 reason: ParseError::UnrecognizedStatus,
75 bytes: bytes.to_vec(),
76 });
77 };
78 if bytes.len() < len {
79 return Err(CodecError::Parse {
80 reason: ParseError::Truncated,
81 bytes: bytes.to_vec(),
82 });
83 }
84 if bytes.len() > len {
85 return Err(CodecError::Parse {
86 reason: ParseError::TrailingData,
87 bytes: bytes.to_vec(),
88 });
89 }
90 let data = |i: usize| {
91 DataByte::try_from(bytes[i]).map_err(|_| CodecError::Parse {
92 reason: ParseError::DataByteOutOfRange,
93 bytes: bytes.to_vec(),
94 })
95 };
96
97 let message = match status {
98 0xF1 => MidiMessage::MtcQuarterFrame(data(1)?),
99 0xF2 => MidiMessage::SongPositionPointer(SongPosition::from_msb_lsb(data(2)?, data(1)?)),
100 0xF3 => MidiMessage::SongSelect(data(1)?),
101 0xF6 => MidiMessage::TuneRequest,
102 0xF8 => MidiMessage::TimingClock,
103 0xFA => MidiMessage::Start,
104 0xFB => MidiMessage::Continue,
105 0xFC => MidiMessage::Stop,
106 0xFE => MidiMessage::ActiveSensing,
107 0xFF => MidiMessage::Reset,
108 _ => {
109 let channel = channel_from_nibble(status);
110 match status & 0xF0 {
111 0x80 => MidiMessage::NoteOff {
112 channel,
113 key: data(1)?,
114 velocity: data(2)?,
115 },
116 0x90 => {
117 let key = data(1)?;
118 let velocity = data(2)?;
119 if velocity.get() == 0 {
120 MidiMessage::NoteOff {
121 channel,
122 key,
123 velocity,
124 }
125 } else {
126 MidiMessage::NoteOn {
127 channel,
128 key,
129 velocity,
130 }
131 }
132 }
133 0xA0 => MidiMessage::PolyKeyPressure {
134 channel,
135 key: data(1)?,
136 pressure: data(2)?,
137 },
138 0xB0 => MidiMessage::ControlChange {
139 channel,
140 controller: data(1)?,
141 value: data(2)?,
142 },
143 0xC0 => MidiMessage::ProgramChange {
144 channel,
145 program: data(1)?,
146 },
147 0xD0 => MidiMessage::ChannelPressure {
148 channel,
149 pressure: data(1)?,
150 },
151 0xE0 => MidiMessage::PitchBend {
152 channel,
153 value: PitchBend::from_msb_lsb(data(2)?, data(1)?),
154 },
155 _ => unreachable!("expected_len returns Some only for known statuses"),
156 }
157 }
158 };
159 Ok(Decoded::Message(message))
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crate::midi::channel::Channel;
166 use crate::midi::conformance;
167 use crate::midi::pitch_bend::PITCH_BEND_CENTER;
168 use crate::midi::sys_ex::MAX_SYSEX_BYTES;
169
170 fn sp(n: u16) -> SongPosition {
171 SongPosition::try_from(n).unwrap()
172 }
173
174 fn msg(bytes: &[u8]) -> MidiMessage {
175 match decode(bytes).unwrap() {
176 Decoded::Message(m) => m,
177 other => panic!("expected message, got {other:?}"),
178 }
179 }
180
181 #[test]
182 fn conformance_decode_matches_corpus() {
183 for (bytes, expected) in conformance::all() {
184 match decode(&bytes) {
185 Ok(Decoded::Message(m)) => assert_eq!(m, expected, "decode {bytes:02X?}"),
186 other => panic!("expected message for {bytes:02X?}, got {other:?}"),
187 }
188 }
189 }
190
191 #[test]
192 fn parse_note_on() {
193 assert_eq!(
194 msg(&[0x90, 60, 100]),
195 MidiMessage::NoteOn {
196 channel: Channel::Ch1,
197 key: DataByte::try_from(60).unwrap(),
198 velocity: DataByte::try_from(100).unwrap()
199 }
200 );
201 }
202
203 #[test]
204 fn parse_note_off() {
205 assert_eq!(
206 msg(&[0x80, 60, 0]),
207 MidiMessage::NoteOff {
208 channel: Channel::Ch1,
209 key: DataByte::try_from(60).unwrap(),
210 velocity: DataByte::try_from(0).unwrap()
211 }
212 );
213 }
214
215 #[test]
216 fn parse_note_on_channel() {
217 assert_eq!(
218 msg(&[0x93, 48, 80]),
219 MidiMessage::NoteOn {
220 channel: Channel::Ch4,
221 key: DataByte::try_from(48).unwrap(),
222 velocity: DataByte::try_from(80).unwrap()
223 }
224 );
225 }
226
227 #[test]
228 fn parse_control_change() {
229 assert_eq!(
230 msg(&[0xB0, 7, 127]),
231 MidiMessage::ControlChange {
232 channel: Channel::Ch1,
233 controller: DataByte::try_from(7).unwrap(),
234 value: DataByte::try_from(127).unwrap()
235 }
236 );
237 }
238
239 #[test]
240 fn parse_program_change() {
241 assert_eq!(
242 msg(&[0xC0, 42]),
243 MidiMessage::ProgramChange {
244 channel: Channel::Ch1,
245 program: DataByte::try_from(42).unwrap()
246 }
247 );
248 }
249
250 #[test]
251 fn parse_pitch_bend_roundtrip() {
252 use crate::RawMidiMessage;
253 for value in [-PITCH_BEND_CENTER, -1000, 0, 1000, PITCH_BEND_CENTER - 1] {
254 let original = MidiMessage::PitchBend {
255 channel: Channel::Ch1,
256 value: PitchBend::from_signed(value).unwrap(),
257 };
258 let raw = RawMidiMessage::from(&original);
259 assert_eq!(msg(&raw), original);
260 }
261 }
262
263 #[test]
264 fn parse_channel_pressure() {
265 assert_eq!(
266 msg(&[0xD1, 55]),
267 MidiMessage::ChannelPressure {
268 channel: Channel::Ch2,
269 pressure: DataByte::try_from(55).unwrap()
270 }
271 );
272 }
273
274 #[test]
275 fn parse_poly_key_pressure() {
276 assert_eq!(
277 msg(&[0xA2, 36, 90]),
278 MidiMessage::PolyKeyPressure {
279 channel: Channel::Ch3,
280 key: DataByte::try_from(36).unwrap(),
281 pressure: DataByte::try_from(90).unwrap()
282 }
283 );
284 }
285
286 #[test]
287 fn parse_sysex() {
288 assert_eq!(
289 decode(&[0xF0, 0x41, 0x10, 0xF7]).unwrap(),
290 Decoded::SysEx(SysEx::new(&[0x41, 0x10]).unwrap())
291 );
292 }
293
294 #[test]
295 fn parse_empty_sysex_is_err() {
296 let err = decode(&[0xF0, 0xF7]).unwrap_err();
297 let CodecError::Parse { reason, bytes } = err else {
298 panic!("expected Parse, got {err:?}");
299 };
300 assert_eq!(reason, ParseError::EmptySysex);
301 assert_eq!(bytes, vec![0xF0, 0xF7]);
302 }
303
304 #[test]
305 fn parse_sysex_high_bit_body_is_err() {
306 let bytes = [0xF0, 0x41, 0x80, 0x42, 0xF7];
307 let err = decode(&bytes).unwrap_err();
308 let CodecError::Parse {
309 reason,
310 bytes: returned,
311 } = err
312 else {
313 panic!("expected Parse, got {err:?}");
314 };
315 assert_eq!(reason, ParseError::SysexDataOutOfRange);
316 assert_eq!(returned, bytes.to_vec());
317 }
318
319 #[test]
320 fn decoded_to_wire_bytes_roundtrips() {
321 let message = Decoded::Message(MidiMessage::NoteOn {
322 channel: Channel::Ch4,
323 key: DataByte::try_from(60).unwrap(),
324 velocity: DataByte::try_from(100).unwrap(),
325 });
326 assert_eq!(decode(&message.to_wire_bytes()), Ok(message));
327
328 let sysex =
329 Decoded::SysEx(SysEx::try_from([0xF0u8, 0x41, 0x10, 0x20, 0xF7].as_slice()).unwrap());
330 assert_eq!(sysex.to_wire_bytes(), vec![0xF0, 0x41, 0x10, 0x20, 0xF7]);
331 assert_eq!(decode(&sysex.to_wire_bytes()), Ok(sysex));
332 }
333
334 #[test]
335 fn parse_timing_clock() {
336 assert_eq!(msg(&[0xF8]), MidiMessage::TimingClock);
337 }
338
339 #[test]
340 fn parse_undefined_realtime_f9_is_err() {
341 assert!(decode(&[0xF9]).is_err());
342 }
343
344 #[test]
345 fn parse_start() {
346 assert_eq!(msg(&[0xFA]), MidiMessage::Start);
347 }
348
349 #[test]
350 fn parse_truncated_note_on_error() {
351 assert!(decode(&[0x90, 60]).is_err());
352 }
353
354 #[test]
355 fn parse_empty_error() {
356 assert!(decode(&[]).is_err());
357 }
358
359 #[test]
360 fn parse_reset() {
361 assert_eq!(msg(&[0xFF]), MidiMessage::Reset);
362 }
363
364 #[test]
365 fn parse_sysex_unterminated_error() {
366 assert!(decode(&[0xF0, 0x41]).is_err());
367 }
368
369 #[test]
370 fn parse_sysex_multi_byte() {
371 assert_eq!(
372 decode(&[0xF0, 0x41, 0x10, 0x20, 0xF7]).unwrap(),
373 Decoded::SysEx(SysEx::new(&[0x41, 0x10, 0x20]).unwrap())
374 );
375 }
376
377 #[test]
378 fn parse_poly_key_pressure_truncated_error() {
379 assert!(decode(&[0xA3, 60]).is_err());
380 }
381
382 #[test]
383 fn parse_unrecognized_status_is_err() {
384 let err = decode(&[0xF4, 0x05]).unwrap_err();
385 assert!(matches!(
386 err,
387 CodecError::Parse {
388 reason: ParseError::UnrecognizedStatus,
389 ..
390 }
391 ));
392 }
393
394 #[test]
395 fn parse_data_byte_high_bit_is_err() {
396 let err = decode(&[0x90, 0x80, 0x40]).unwrap_err();
397 assert!(matches!(
398 err,
399 CodecError::Parse {
400 reason: ParseError::DataByteOutOfRange,
401 ..
402 }
403 ));
404 let CodecError::Parse { bytes, .. } = err else {
405 unreachable!()
406 };
407 assert_eq!(bytes, vec![0x90, 0x80, 0x40]);
408 }
409
410 #[test]
411 fn parse_rejects_trailing_bytes() {
412 let err = decode(&[0x90, 60, 100, 0x90]).unwrap_err();
413 let CodecError::Parse { reason, bytes } = err else {
414 panic!("expected Parse, got {err:?}");
415 };
416 assert_eq!(reason, ParseError::TrailingData);
417 assert_eq!(bytes, vec![0x90, 60, 100, 0x90]);
418 }
419
420 #[test]
421 fn parse_rejects_trailing_realtime_byte() {
422 let err = decode(&[0xF8, 0xF8]).unwrap_err();
423 assert!(matches!(
424 err,
425 CodecError::Parse {
426 reason: ParseError::TrailingData,
427 ..
428 }
429 ));
430 }
431
432 #[test]
433 fn parse_rejects_oversized_sysex() {
434 let mut bytes = vec![0xF0u8];
435 bytes.extend(std::iter::repeat(0x01).take(MAX_SYSEX_BYTES + 1));
436 bytes.push(0xF7);
437 assert!(matches!(
438 decode(&bytes),
439 Err(CodecError::SysexTooLong { .. })
440 ));
441 }
442
443 #[test]
444 fn parse_accepts_max_sized_sysex() {
445 let mut bytes = vec![0xF0u8];
446 bytes.extend(std::iter::repeat(0x01).take(MAX_SYSEX_BYTES));
447 bytes.push(0xF7);
448 assert!(matches!(decode(&bytes), Ok(Decoded::SysEx(_))));
449 }
450
451 #[test]
452 fn parse_mtc_quarter_frame() {
453 assert_eq!(
454 msg(&[0xF1, 0x35]),
455 MidiMessage::MtcQuarterFrame(DataByte::try_from(0x35).unwrap())
456 );
457 }
458
459 #[test]
460 fn parse_song_position_pointer() {
461 assert_eq!(
462 msg(&[0xF2, 0x00, 0x02]),
463 MidiMessage::SongPositionPointer(sp(256))
464 );
465 }
466
467 #[test]
468 fn parse_song_select() {
469 assert_eq!(
470 msg(&[0xF3, 42]),
471 MidiMessage::SongSelect(DataByte::try_from(42).unwrap())
472 );
473 }
474
475 #[test]
476 fn parse_tune_request() {
477 assert_eq!(msg(&[0xF6]), MidiMessage::TuneRequest);
478 }
479
480 #[test]
481 fn parse_song_position_pointer_roundtrip() {
482 use crate::RawMidiMessage;
483 for pos in [0u16, 1000, 16383] {
484 let original = MidiMessage::SongPositionPointer(sp(pos));
485 let raw = RawMidiMessage::from(&original);
486 assert_eq!(msg(&raw), original);
487 }
488 }
489}