Skip to main content

midi_io/midi/
decode.rs

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}