embedded_midi/
parser.rs

1//! Parse midi messages
2use midi_types::{Channel, Control, MidiMessage, Note};
3
4/// Keeps state for parsing Midi messages
5#[derive(Debug, Clone, PartialEq)]
6pub struct MidiParser {
7    state: MidiParserState,
8}
9
10#[derive(Debug, Clone, PartialEq)]
11enum MidiParserState {
12    Idle,
13    NoteOnRecvd(Channel),
14    NoteOnNoteRecvd(Channel, Note),
15
16    NoteOffRecvd(Channel),
17    NoteOffNoteRecvd(Channel, Note),
18
19    KeyPressureRecvd(Channel),
20    KeyPressureNoteRecvd(Channel, Note),
21
22    ControlChangeRecvd(Channel),
23    ControlChangeControlRecvd(Channel, Control),
24
25    ProgramChangeRecvd(Channel),
26
27    ChannelPressureRecvd(Channel),
28
29    PitchBendRecvd(Channel),
30    PitchBendFirstByteRecvd(Channel, u8),
31
32    QuarterFrameRecvd,
33
34    SongPositionRecvd,
35    SongPositionLsbRecvd(u8),
36
37    SongSelectRecvd,
38}
39
40/// Check if most significant bit is set which signifies a Midi status byte
41fn is_status_byte(byte: u8) -> bool {
42    byte & 0x80 == 0x80
43}
44
45/// Check if a byte corresponds to 0x1111xxxx which signifies either a system common or realtime message
46fn is_system_message(byte: u8) -> bool {
47    byte & 0xf0 == 0xf0
48}
49
50/// Split the message and channel part of a channel voice message
51fn split_message_and_channel(byte: u8) -> (u8, Channel) {
52    (byte & 0xf0u8, (byte & 0x0fu8).into())
53}
54
55/// State machine for parsing Midi data, can be fed bytes one-by-one, and returns parsed Midi
56/// messages whenever one is completed.
57impl MidiParser {
58    /// Initialize midiparser state
59    pub fn new() -> Self {
60        MidiParser {
61            state: MidiParserState::Idle,
62        }
63    }
64
65    /// Parse midi event byte by byte. Call this whenever a byte is received. When a midi-event is
66    /// completed it is returned, otherwise this method updates the internal midiparser state and
67    /// and returns none.
68    pub fn parse_byte(&mut self, byte: u8) -> Option<MidiMessage> {
69        if is_status_byte(byte) {
70            if is_system_message(byte) {
71                match byte {
72                    // System common messages, these should reset parsing other messages
73                    0xf0 => {
74                        // System exclusive
75                        self.state = MidiParserState::Idle;
76                        None
77                    }
78                    0xf1 => {
79                        // Midi time code quarter frame
80                        self.state = MidiParserState::QuarterFrameRecvd;
81                        None
82                    }
83                    0xf2 => {
84                        // Song position pointer
85                        self.state = MidiParserState::SongPositionRecvd;
86                        None
87                    }
88                    0xf3 => {
89                        // Song select
90                        self.state = MidiParserState::SongSelectRecvd;
91                        None
92                    }
93                    0xf6 => {
94                        // Tune request
95                        self.state = MidiParserState::Idle;
96                        Some(MidiMessage::TuneRequest)
97                    }
98                    0xf7 => {
99                        // End of exclusive
100                        self.state = MidiParserState::Idle;
101                        None
102                        // Some(MidiMessage::EndOfExclusive)
103                    }
104
105                    // System realtime messages
106                    0xf8 => Some(MidiMessage::TimingClock),
107                    0xf9 => None, // Reserved
108                    0xfa => Some(MidiMessage::Start),
109                    0xfb => Some(MidiMessage::Continue),
110                    0xfc => Some(MidiMessage::Stop),
111                    0xfd => None, // Reserved
112                    0xfe => Some(MidiMessage::ActiveSensing),
113                    0xff => Some(MidiMessage::Reset),
114
115                    _ => {
116                        // Undefined messages like 0xf4 and should end up here
117                        self.state = MidiParserState::Idle;
118                        None
119                    }
120                }
121            } else {
122                // Channel voice message
123
124                let (message, channel) = split_message_and_channel(byte);
125
126                match message {
127                    0x80 => {
128                        self.state = MidiParserState::NoteOffRecvd(channel);
129                        None
130                    }
131                    0x90 => {
132                        self.state = MidiParserState::NoteOnRecvd(channel);
133                        None
134                    }
135                    0xA0 => {
136                        self.state = MidiParserState::KeyPressureRecvd(channel);
137                        None
138                    }
139                    0xB0 => {
140                        self.state = MidiParserState::ControlChangeRecvd(channel);
141                        None
142                    }
143                    0xC0 => {
144                        self.state = MidiParserState::ProgramChangeRecvd(channel);
145                        None
146                    }
147                    0xD0 => {
148                        self.state = MidiParserState::ChannelPressureRecvd(channel);
149                        None
150                    }
151                    0xE0 => {
152                        self.state = MidiParserState::PitchBendRecvd(channel);
153                        None
154                    }
155                    _ => None,
156                }
157            }
158        } else {
159            match self.state {
160                MidiParserState::NoteOffRecvd(channel) => {
161                    self.state = MidiParserState::NoteOffNoteRecvd(channel, byte.into());
162                    None
163                }
164                MidiParserState::NoteOffNoteRecvd(channel, note) => {
165                    self.state = MidiParserState::NoteOffRecvd(channel);
166                    Some(MidiMessage::NoteOff(channel, note, byte.into()))
167                }
168
169                MidiParserState::NoteOnRecvd(channel) => {
170                    self.state = MidiParserState::NoteOnNoteRecvd(channel, byte.into());
171                    None
172                }
173                MidiParserState::NoteOnNoteRecvd(channel, note) => {
174                    self.state = MidiParserState::NoteOnRecvd(channel);
175                    Some(MidiMessage::NoteOn(channel, note, byte.into()))
176                }
177
178                MidiParserState::KeyPressureRecvd(channel) => {
179                    self.state = MidiParserState::KeyPressureNoteRecvd(channel, byte.into());
180                    None
181                }
182                MidiParserState::KeyPressureNoteRecvd(channel, note) => {
183                    self.state = MidiParserState::KeyPressureRecvd(channel);
184                    Some(MidiMessage::KeyPressure(channel, note, byte.into()))
185                }
186
187                MidiParserState::ControlChangeRecvd(channel) => {
188                    self.state = MidiParserState::ControlChangeControlRecvd(channel, byte.into());
189                    None
190                }
191                MidiParserState::ControlChangeControlRecvd(channel, control) => {
192                    self.state = MidiParserState::ControlChangeRecvd(channel);
193                    Some(MidiMessage::ControlChange(channel, control, byte.into()))
194                }
195
196                MidiParserState::ProgramChangeRecvd(channel) => {
197                    Some(MidiMessage::ProgramChange(channel, byte.into()))
198                }
199
200                MidiParserState::ChannelPressureRecvd(channel) => {
201                    Some(MidiMessage::ChannelPressure(channel, byte.into()))
202                }
203
204                MidiParserState::PitchBendRecvd(channel) => {
205                    self.state = MidiParserState::PitchBendFirstByteRecvd(channel, byte);
206                    None
207                }
208                MidiParserState::PitchBendFirstByteRecvd(channel, byte1) => {
209                    self.state = MidiParserState::PitchBendRecvd(channel);
210                    Some(MidiMessage::PitchBendChange(channel, (byte1, byte).into()))
211                }
212                MidiParserState::QuarterFrameRecvd => Some(MidiMessage::QuarterFrame(byte.into())),
213                MidiParserState::SongPositionRecvd => {
214                    self.state = MidiParserState::SongPositionLsbRecvd(byte);
215                    None
216                }
217                MidiParserState::SongPositionLsbRecvd(lsb) => {
218                    self.state = MidiParserState::SongPositionRecvd;
219                    Some(MidiMessage::SongPositionPointer((lsb, byte).into()))
220                }
221                MidiParserState::SongSelectRecvd => Some(MidiMessage::SongSelect(byte.into())),
222                _ => None,
223            }
224        }
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    extern crate std;
231    use super::*;
232    use std::vec::Vec;
233
234    #[test]
235    fn should_parse_status_byte() {
236        assert!(is_status_byte(0x80u8));
237        assert!(is_status_byte(0x94u8));
238        assert!(!is_status_byte(0x00u8));
239        assert!(!is_status_byte(0x78u8));
240    }
241
242    #[test]
243    fn should_parse_system_message() {
244        assert!(is_system_message(0xf0));
245        assert!(is_system_message(0xf4));
246        assert!(!is_system_message(0x0f));
247        assert!(!is_system_message(0x77));
248    }
249
250    #[test]
251    fn should_split_message_and_channel() {
252        let (message, channel) = split_message_and_channel(0x91u8);
253        assert_eq!(message, 0x90u8);
254        assert_eq!(channel, 1.into());
255    }
256
257    #[test]
258    fn should_parse_note_off() {
259        MidiParser::new().assert_result(
260            &[0x82, 0x76, 0x34],
261            &[MidiMessage::NoteOff(2.into(), 0x76.into(), 0x34.into())],
262        );
263    }
264
265    #[test]
266    fn should_handle_note_off_running_state() {
267        MidiParser::new().assert_result(
268            &[
269                0x82, 0x76, 0x34, // First note_off
270                0x33, 0x65, // Second note_off without status byte
271            ],
272            &[
273                MidiMessage::NoteOff(2.into(), 0x76.into(), 0x34.into()),
274                MidiMessage::NoteOff(2.into(), 0x33.into(), 0x65.into()),
275            ],
276        );
277    }
278
279    #[test]
280    fn should_parse_note_on() {
281        MidiParser::new().assert_result(
282            &[0x91, 0x04, 0x34],
283            &[MidiMessage::NoteOn(1.into(), 4.into(), 0x34.into())],
284        );
285    }
286
287    #[test]
288    fn should_handle_note_on_running_state() {
289        MidiParser::new().assert_result(
290            &[
291                0x92, 0x76, 0x34, // First note_on
292                0x33, 0x65, // Second note on without status byte
293            ],
294            &[
295                MidiMessage::NoteOn(2.into(), 0x76.into(), 0x34.into()),
296                MidiMessage::NoteOn(2.into(), 0x33.into(), 0x65.into()),
297            ],
298        );
299    }
300
301    #[test]
302    fn should_parse_keypressure() {
303        MidiParser::new().assert_result(
304            &[0xAA, 0x13, 0x34],
305            &[MidiMessage::KeyPressure(
306                10.into(),
307                0x13.into(),
308                0x34.into(),
309            )],
310        );
311    }
312
313    #[test]
314    fn should_handle_keypressure_running_state() {
315        MidiParser::new().assert_result(
316            &[
317                0xA8, 0x77, 0x03, // First key_pressure
318                0x14, 0x56, // Second key_pressure without status byte
319            ],
320            &[
321                MidiMessage::KeyPressure(8.into(), 0x77.into(), 0x03.into()),
322                MidiMessage::KeyPressure(8.into(), 0x14.into(), 0x56.into()),
323            ],
324        );
325    }
326
327    #[test]
328    fn should_parse_control_change() {
329        MidiParser::new().assert_result(
330            &[0xB2, 0x76, 0x34],
331            &[MidiMessage::ControlChange(
332                2.into(),
333                0x76.into(),
334                0x34.into(),
335            )],
336        );
337    }
338
339    #[test]
340    fn should_parse_control_change_running_state() {
341        MidiParser::new().assert_result(
342            &[
343                0xb3, 0x3C, 0x18, // First control change
344                0x43, 0x01, // Second control change without status byte
345            ],
346            &[
347                MidiMessage::ControlChange(3.into(), 0x3c.into(), 0x18.into()),
348                MidiMessage::ControlChange(3.into(), 0x43.into(), 0x01.into()),
349            ],
350        );
351    }
352
353    #[test]
354    fn should_parse_program_change() {
355        MidiParser::new().assert_result(
356            &[0xC9, 0x15],
357            &[MidiMessage::ProgramChange(9.into(), 0x15.into())],
358        );
359    }
360
361    #[test]
362    fn should_parse_program_change_running_state() {
363        MidiParser::new().assert_result(
364            &[
365                0xC3, 0x67, // First program change
366                0x01, // Second program change without status byte
367            ],
368            &[
369                MidiMessage::ProgramChange(3.into(), 0x67.into()),
370                MidiMessage::ProgramChange(3.into(), 0x01.into()),
371            ],
372        );
373    }
374
375    #[test]
376    fn should_parse_channel_pressure() {
377        MidiParser::new().assert_result(
378            &[0xDD, 0x37],
379            &[MidiMessage::ChannelPressure(13.into(), 0x37.into())],
380        );
381    }
382
383    #[test]
384    fn should_parse_channel_pressure_running_state() {
385        MidiParser::new().assert_result(
386            &[
387                0xD6, 0x77, // First channel pressure
388                0x43, // Second channel pressure without status byte
389            ],
390            &[
391                MidiMessage::ChannelPressure(6.into(), 0x77.into()),
392                MidiMessage::ChannelPressure(6.into(), 0x43.into()),
393            ],
394        );
395    }
396
397    #[test]
398    fn should_parse_pitchbend() {
399        MidiParser::new().assert_result(
400            &[0xE8, 0x14, 0x56],
401            &[MidiMessage::PitchBendChange(8.into(), (0x14, 0x56).into())],
402        );
403    }
404
405    #[test]
406    fn should_parse_pitchbend_running_state() {
407        MidiParser::new().assert_result(
408            &[
409                0xE3, 0x3C, 0x18, // First pitchbend
410                0x43, 0x01, // Second pitchbend without status byte
411            ],
412            &[
413                MidiMessage::PitchBendChange(3.into(), (0x3c, 0x18).into()),
414                MidiMessage::PitchBendChange(3.into(), (0x43, 0x01).into()),
415            ],
416        );
417    }
418
419    #[test]
420    fn should_parse_quarter_frame() {
421        MidiParser::new().assert_result(&[0xf1, 0x7f], &[MidiMessage::QuarterFrame(0x7f.into())]);
422    }
423
424    #[test]
425    fn should_handle_quarter_frame_running_state() {
426        MidiParser::new().assert_result(
427            &[
428                0xf1, 0x7f, // Send quarter frame
429                0x56, // Only send data of next quarter frame
430            ],
431            &[
432                MidiMessage::QuarterFrame(0x7f.into()),
433                MidiMessage::QuarterFrame(0x56.into()),
434            ],
435        );
436    }
437
438    #[test]
439    fn should_parse_song_position_pointer() {
440        MidiParser::new().assert_result(
441            &[0xf2, 0x7f, 0x68],
442            &[MidiMessage::SongPositionPointer((0x7f, 0x68).into())],
443        );
444    }
445
446    #[test]
447    fn should_handle_song_position_pointer_running_state() {
448        MidiParser::new().assert_result(
449            &[
450                0xf2, 0x7f, 0x68, // Send song position pointer
451                0x23, 0x7b, // Only send data of next song position pointer
452            ],
453            &[
454                MidiMessage::SongPositionPointer((0x7f, 0x68).into()),
455                MidiMessage::SongPositionPointer((0x23, 0x7b).into()),
456            ],
457        );
458    }
459
460    #[test]
461    fn should_parse_song_select() {
462        MidiParser::new().assert_result(&[0xf3, 0x3f], &[MidiMessage::SongSelect(0x3f.into())]);
463    }
464
465    #[test]
466    fn should_handle_song_select_running_state() {
467        MidiParser::new().assert_result(
468            &[
469                0xf3, 0x3f, // Send song select
470                0x00, // Only send data for next song select
471            ],
472            &[
473                MidiMessage::SongSelect(0x3f.into()),
474                MidiMessage::SongSelect(0x00.into()),
475            ],
476        );
477    }
478
479    #[test]
480    fn should_parse_tune_request() {
481        MidiParser::new().assert_result(&[0xf6], &[MidiMessage::TuneRequest]);
482    }
483
484    #[test]
485    fn should_interrupt_parsing_for_tune_request() {
486        MidiParser::new().assert_result(
487            &[
488                0x92, 0x76, // start note_on message
489                0xf6, // interrupt with tune request
490                0x34, // finish note on, this should be ignored
491            ],
492            &[MidiMessage::TuneRequest],
493        );
494    }
495
496    // #[test]
497    // fn should_parse_end_exclusive() {
498    //     MidiParser::new().assert_result(&[0xf7], &[MidiMessage::EndOfExclusive]);
499    // }
500
501    // #[test]
502    // fn should_interrupt_parsing_for_end_of_exclusive() {
503    //     MidiParser::new().assert_result(
504    //         &[
505    //             0x92, 0x76, // start note_on message
506    //             0xf7, // interrupt with end of exclusive
507    //             0x34, // finish note on, this should be ignored
508    //         ],
509    //         &[MidiMessage::EndOfExclusive],
510    //     );
511    // }
512
513    #[test]
514    fn should_interrupt_parsing_for_undefined_message() {
515        MidiParser::new().assert_result(
516            &[
517                0x92, 0x76, // start note_on message
518                0xf5, // interrupt with undefined message
519                0x34, // finish note on, this should be ignored
520            ],
521            &[],
522        );
523    }
524
525    #[test]
526    fn should_parse_timingclock_message() {
527        MidiParser::new().assert_result(&[0xf8], &[MidiMessage::TimingClock]);
528    }
529
530    #[test]
531    fn should_parse_timingclock_message_as_realtime() {
532        MidiParser::new().assert_result(
533            &[
534                0xD6, // Start channel pressure event
535                0xf8, // interupt with midi timing clock
536                0x77, // Finish channel pressure
537            ],
538            &[
539                MidiMessage::TimingClock,
540                MidiMessage::ChannelPressure(6.into(), 0x77.into()),
541            ],
542        );
543    }
544
545    #[test]
546    fn should_parse_start_message() {
547        MidiParser::new().assert_result(&[0xfa], &[MidiMessage::Start]);
548    }
549
550    #[test]
551    fn should_parse_start_message_as_realtime() {
552        MidiParser::new().assert_result(
553            &[
554                0xD6, // Start channel pressure event
555                0xfa, // interupt with start
556                0x77, // Finish channel pressure
557            ],
558            &[
559                MidiMessage::Start,
560                MidiMessage::ChannelPressure(6.into(), 0x77.into()),
561            ],
562        );
563    }
564
565    #[test]
566    fn should_parse_continue_message() {
567        MidiParser::new().assert_result(&[0xfb], &[MidiMessage::Continue]);
568    }
569
570    #[test]
571    fn should_parse_continue_message_as_realtime() {
572        MidiParser::new().assert_result(
573            &[
574                0xD6, // Start channel pressure event
575                0xfb, // interupt with continue
576                0x77, // Finish channel pressure
577            ],
578            &[
579                MidiMessage::Continue,
580                MidiMessage::ChannelPressure(6.into(), 0x77.into()),
581            ],
582        );
583    }
584
585    #[test]
586    fn should_parse_stop_message() {
587        MidiParser::new().assert_result(&[0xfc], &[MidiMessage::Stop]);
588    }
589
590    #[test]
591    fn should_parse_stop_message_as_realtime() {
592        MidiParser::new().assert_result(
593            &[
594                0xD6, // Start channel pressure event
595                0xfc, // interupt with stop
596                0x77, // Finish channel pressure
597            ],
598            &[
599                MidiMessage::Stop,
600                MidiMessage::ChannelPressure(6.into(), 0x77.into()),
601            ],
602        );
603    }
604
605    #[test]
606    fn should_parse_activesensing_message() {
607        MidiParser::new().assert_result(&[0xfe], &[MidiMessage::ActiveSensing]);
608    }
609
610    #[test]
611    fn should_parse_activesensing_message_as_realtime() {
612        MidiParser::new().assert_result(
613            &[
614                0xD6, // Start channel pressure event
615                0xfe, // interupt with activesensing
616                0x77, // Finish channel pressure
617            ],
618            &[
619                MidiMessage::ActiveSensing,
620                MidiMessage::ChannelPressure(6.into(), 0x77.into()),
621            ],
622        );
623    }
624
625    #[test]
626    fn should_parse_reset_message() {
627        MidiParser::new().assert_result(&[0xff], &[MidiMessage::Reset]);
628    }
629
630    #[test]
631    fn should_parse_reset_message_as_realtime() {
632        MidiParser::new().assert_result(
633            &[
634                0xD6, // Start channel pressure event
635                0xff, // interupt with reset
636                0x77, // Finish channel pressure
637            ],
638            &[
639                MidiMessage::Reset,
640                MidiMessage::ChannelPressure(6.into(), 0x77.into()),
641            ],
642        );
643    }
644
645    #[test]
646    fn should_ignore_incomplete_messages() {
647        MidiParser::new().assert_result(
648            &[
649                0x92, 0x1b, // Start note off message
650                0x82, 0x76, 0x34, // continue with a complete note on message
651            ],
652            &[MidiMessage::NoteOff(2.into(), 0x76.into(), 0x34.into())],
653        );
654    }
655
656    impl MidiParser {
657        /// Test helper function, asserts if a slice of bytes parses to some set of midi events
658        fn assert_result(&mut self, bytes: &[u8], expected_events: &[MidiMessage]) {
659            let events: Vec<MidiMessage> = bytes
660                .into_iter()
661                .filter_map(|byte| self.parse_byte(*byte))
662                .collect();
663
664            assert_eq!(expected_events, events.as_slice());
665        }
666    }
667}