1use midi_types::{Channel, Control, MidiMessage, Note};
3
4#[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
40fn is_status_byte(byte: u8) -> bool {
42 byte & 0x80 == 0x80
43}
44
45fn is_system_message(byte: u8) -> bool {
47 byte & 0xf0 == 0xf0
48}
49
50fn split_message_and_channel(byte: u8) -> (u8, Channel) {
52 (byte & 0xf0u8, (byte & 0x0fu8).into())
53}
54
55impl MidiParser {
58 pub fn new() -> Self {
60 MidiParser {
61 state: MidiParserState::Idle,
62 }
63 }
64
65 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 0xf0 => {
74 self.state = MidiParserState::Idle;
76 None
77 }
78 0xf1 => {
79 self.state = MidiParserState::QuarterFrameRecvd;
81 None
82 }
83 0xf2 => {
84 self.state = MidiParserState::SongPositionRecvd;
86 None
87 }
88 0xf3 => {
89 self.state = MidiParserState::SongSelectRecvd;
91 None
92 }
93 0xf6 => {
94 self.state = MidiParserState::Idle;
96 Some(MidiMessage::TuneRequest)
97 }
98 0xf7 => {
99 self.state = MidiParserState::Idle;
101 None
102 }
104
105 0xf8 => Some(MidiMessage::TimingClock),
107 0xf9 => None, 0xfa => Some(MidiMessage::Start),
109 0xfb => Some(MidiMessage::Continue),
110 0xfc => Some(MidiMessage::Stop),
111 0xfd => None, 0xfe => Some(MidiMessage::ActiveSensing),
113 0xff => Some(MidiMessage::Reset),
114
115 _ => {
116 self.state = MidiParserState::Idle;
118 None
119 }
120 }
121 } else {
122 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, 0x33, 0x65, ],
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, 0x33, 0x65, ],
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, 0x14, 0x56, ],
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, 0x43, 0x01, ],
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, 0x01, ],
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, 0x43, ],
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, 0x43, 0x01, ],
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, 0x56, ],
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, 0x23, 0x7b, ],
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, 0x00, ],
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, 0xf6, 0x34, ],
492 &[MidiMessage::TuneRequest],
493 );
494 }
495
496 #[test]
514 fn should_interrupt_parsing_for_undefined_message() {
515 MidiParser::new().assert_result(
516 &[
517 0x92, 0x76, 0xf5, 0x34, ],
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, 0xf8, 0x77, ],
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, 0xfa, 0x77, ],
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, 0xfb, 0x77, ],
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, 0xfc, 0x77, ],
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, 0xfe, 0x77, ],
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, 0xff, 0x77, ],
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, 0x82, 0x76, 0x34, ],
652 &[MidiMessage::NoteOff(2.into(), 0x76.into(), 0x34.into())],
653 );
654 }
655
656 impl MidiParser {
657 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}