use midi_types::{Channel, Control, MidiMessage, Note};
#[derive(Debug, Clone, PartialEq)]
pub struct MidiParser {
state: MidiParserState,
}
#[derive(Debug, Clone, PartialEq)]
enum MidiParserState {
Idle,
NoteOnRecvd(Channel),
NoteOnNoteRecvd(Channel, Note),
NoteOffRecvd(Channel),
NoteOffNoteRecvd(Channel, Note),
KeyPressureRecvd(Channel),
KeyPressureNoteRecvd(Channel, Note),
ControlChangeRecvd(Channel),
ControlChangeControlRecvd(Channel, Control),
ProgramChangeRecvd(Channel),
ChannelPressureRecvd(Channel),
PitchBendRecvd(Channel),
PitchBendFirstByteRecvd(Channel, u8),
QuarterFrameRecvd,
SongPositionRecvd,
SongPositionLsbRecvd(u8),
SongSelectRecvd,
}
fn is_status_byte(byte: u8) -> bool {
byte & 0x80 == 0x80
}
fn is_system_message(byte: u8) -> bool {
byte & 0xf0 == 0xf0
}
fn split_message_and_channel(byte: u8) -> (u8, Channel) {
(byte & 0xf0u8, (byte & 0x0fu8).into())
}
impl MidiParser {
pub fn new() -> Self {
MidiParser {
state: MidiParserState::Idle,
}
}
pub fn parse_byte(&mut self, byte: u8) -> Option<MidiMessage> {
if is_status_byte(byte) {
if is_system_message(byte) {
match byte {
0xf0 => {
self.state = MidiParserState::Idle;
None
}
0xf1 => {
self.state = MidiParserState::QuarterFrameRecvd;
None
}
0xf2 => {
self.state = MidiParserState::SongPositionRecvd;
None
}
0xf3 => {
self.state = MidiParserState::SongSelectRecvd;
None
}
0xf6 => {
self.state = MidiParserState::Idle;
Some(MidiMessage::TuneRequest)
}
0xf7 => {
self.state = MidiParserState::Idle;
None
}
0xf8 => Some(MidiMessage::TimingClock),
0xf9 => None,
0xfa => Some(MidiMessage::Start),
0xfb => Some(MidiMessage::Continue),
0xfc => Some(MidiMessage::Stop),
0xfd => None,
0xfe => Some(MidiMessage::ActiveSensing),
0xff => Some(MidiMessage::Reset),
_ => {
self.state = MidiParserState::Idle;
None
}
}
} else {
let (message, channel) = split_message_and_channel(byte);
match message {
0x80 => {
self.state = MidiParserState::NoteOffRecvd(channel);
None
}
0x90 => {
self.state = MidiParserState::NoteOnRecvd(channel);
None
}
0xA0 => {
self.state = MidiParserState::KeyPressureRecvd(channel);
None
}
0xB0 => {
self.state = MidiParserState::ControlChangeRecvd(channel);
None
}
0xC0 => {
self.state = MidiParserState::ProgramChangeRecvd(channel);
None
}
0xD0 => {
self.state = MidiParserState::ChannelPressureRecvd(channel);
None
}
0xE0 => {
self.state = MidiParserState::PitchBendRecvd(channel);
None
}
_ => None,
}
}
} else {
match self.state {
MidiParserState::NoteOffRecvd(channel) => {
self.state = MidiParserState::NoteOffNoteRecvd(channel, byte.into());
None
}
MidiParserState::NoteOffNoteRecvd(channel, note) => {
self.state = MidiParserState::NoteOffRecvd(channel);
Some(MidiMessage::NoteOff(channel, note, byte.into()))
}
MidiParserState::NoteOnRecvd(channel) => {
self.state = MidiParserState::NoteOnNoteRecvd(channel, byte.into());
None
}
MidiParserState::NoteOnNoteRecvd(channel, note) => {
self.state = MidiParserState::NoteOnRecvd(channel);
Some(MidiMessage::NoteOn(channel, note, byte.into()))
}
MidiParserState::KeyPressureRecvd(channel) => {
self.state = MidiParserState::KeyPressureNoteRecvd(channel, byte.into());
None
}
MidiParserState::KeyPressureNoteRecvd(channel, note) => {
self.state = MidiParserState::KeyPressureRecvd(channel);
Some(MidiMessage::KeyPressure(channel, note, byte.into()))
}
MidiParserState::ControlChangeRecvd(channel) => {
self.state = MidiParserState::ControlChangeControlRecvd(channel, byte.into());
None
}
MidiParserState::ControlChangeControlRecvd(channel, control) => {
self.state = MidiParserState::ControlChangeRecvd(channel);
Some(MidiMessage::ControlChange(channel, control, byte.into()))
}
MidiParserState::ProgramChangeRecvd(channel) => {
Some(MidiMessage::ProgramChange(channel, byte.into()))
}
MidiParserState::ChannelPressureRecvd(channel) => {
Some(MidiMessage::ChannelPressure(channel, byte.into()))
}
MidiParserState::PitchBendRecvd(channel) => {
self.state = MidiParserState::PitchBendFirstByteRecvd(channel, byte);
None
}
MidiParserState::PitchBendFirstByteRecvd(channel, byte1) => {
self.state = MidiParserState::PitchBendRecvd(channel);
Some(MidiMessage::PitchBendChange(channel, (byte1, byte).into()))
}
MidiParserState::QuarterFrameRecvd => Some(MidiMessage::QuarterFrame(byte.into())),
MidiParserState::SongPositionRecvd => {
self.state = MidiParserState::SongPositionLsbRecvd(byte);
None
}
MidiParserState::SongPositionLsbRecvd(lsb) => {
self.state = MidiParserState::SongPositionRecvd;
Some(MidiMessage::SongPositionPointer((lsb, byte).into()))
}
MidiParserState::SongSelectRecvd => Some(MidiMessage::SongSelect(byte.into())),
_ => None,
}
}
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use std::vec::Vec;
#[test]
fn should_parse_status_byte() {
assert!(is_status_byte(0x80u8));
assert!(is_status_byte(0x94u8));
assert!(!is_status_byte(0x00u8));
assert!(!is_status_byte(0x78u8));
}
#[test]
fn should_parse_system_message() {
assert!(is_system_message(0xf0));
assert!(is_system_message(0xf4));
assert!(!is_system_message(0x0f));
assert!(!is_system_message(0x77));
}
#[test]
fn should_split_message_and_channel() {
let (message, channel) = split_message_and_channel(0x91u8);
assert_eq!(message, 0x90u8);
assert_eq!(channel, 1.into());
}
#[test]
fn should_parse_note_off() {
MidiParser::new().assert_result(
&[0x82, 0x76, 0x34],
&[MidiMessage::NoteOff(2.into(), 0x76.into(), 0x34.into())],
);
}
#[test]
fn should_handle_note_off_running_state() {
MidiParser::new().assert_result(
&[
0x82, 0x76, 0x34,
0x33, 0x65,
],
&[
MidiMessage::NoteOff(2.into(), 0x76.into(), 0x34.into()),
MidiMessage::NoteOff(2.into(), 0x33.into(), 0x65.into()),
],
);
}
#[test]
fn should_parse_note_on() {
MidiParser::new().assert_result(
&[0x91, 0x04, 0x34],
&[MidiMessage::NoteOn(1.into(), 4.into(), 0x34.into())],
);
}
#[test]
fn should_handle_note_on_running_state() {
MidiParser::new().assert_result(
&[
0x92, 0x76, 0x34,
0x33, 0x65,
],
&[
MidiMessage::NoteOn(2.into(), 0x76.into(), 0x34.into()),
MidiMessage::NoteOn(2.into(), 0x33.into(), 0x65.into()),
],
);
}
#[test]
fn should_parse_keypressure() {
MidiParser::new().assert_result(
&[0xAA, 0x13, 0x34],
&[MidiMessage::KeyPressure(
10.into(),
0x13.into(),
0x34.into(),
)],
);
}
#[test]
fn should_handle_keypressure_running_state() {
MidiParser::new().assert_result(
&[
0xA8, 0x77, 0x03,
0x14, 0x56,
],
&[
MidiMessage::KeyPressure(8.into(), 0x77.into(), 0x03.into()),
MidiMessage::KeyPressure(8.into(), 0x14.into(), 0x56.into()),
],
);
}
#[test]
fn should_parse_control_change() {
MidiParser::new().assert_result(
&[0xB2, 0x76, 0x34],
&[MidiMessage::ControlChange(
2.into(),
0x76.into(),
0x34.into(),
)],
);
}
#[test]
fn should_parse_control_change_running_state() {
MidiParser::new().assert_result(
&[
0xb3, 0x3C, 0x18,
0x43, 0x01,
],
&[
MidiMessage::ControlChange(3.into(), 0x3c.into(), 0x18.into()),
MidiMessage::ControlChange(3.into(), 0x43.into(), 0x01.into()),
],
);
}
#[test]
fn should_parse_program_change() {
MidiParser::new().assert_result(
&[0xC9, 0x15],
&[MidiMessage::ProgramChange(9.into(), 0x15.into())],
);
}
#[test]
fn should_parse_program_change_running_state() {
MidiParser::new().assert_result(
&[
0xC3, 0x67,
0x01,
],
&[
MidiMessage::ProgramChange(3.into(), 0x67.into()),
MidiMessage::ProgramChange(3.into(), 0x01.into()),
],
);
}
#[test]
fn should_parse_channel_pressure() {
MidiParser::new().assert_result(
&[0xDD, 0x37],
&[MidiMessage::ChannelPressure(13.into(), 0x37.into())],
);
}
#[test]
fn should_parse_channel_pressure_running_state() {
MidiParser::new().assert_result(
&[
0xD6, 0x77,
0x43,
],
&[
MidiMessage::ChannelPressure(6.into(), 0x77.into()),
MidiMessage::ChannelPressure(6.into(), 0x43.into()),
],
);
}
#[test]
fn should_parse_pitchbend() {
MidiParser::new().assert_result(
&[0xE8, 0x14, 0x56],
&[MidiMessage::PitchBendChange(8.into(), (0x14, 0x56).into())],
);
}
#[test]
fn should_parse_pitchbend_running_state() {
MidiParser::new().assert_result(
&[
0xE3, 0x3C, 0x18,
0x43, 0x01,
],
&[
MidiMessage::PitchBendChange(3.into(), (0x3c, 0x18).into()),
MidiMessage::PitchBendChange(3.into(), (0x43, 0x01).into()),
],
);
}
#[test]
fn should_parse_quarter_frame() {
MidiParser::new().assert_result(&[0xf1, 0x7f], &[MidiMessage::QuarterFrame(0x7f.into())]);
}
#[test]
fn should_handle_quarter_frame_running_state() {
MidiParser::new().assert_result(
&[
0xf1, 0x7f,
0x56,
],
&[
MidiMessage::QuarterFrame(0x7f.into()),
MidiMessage::QuarterFrame(0x56.into()),
],
);
}
#[test]
fn should_parse_song_position_pointer() {
MidiParser::new().assert_result(
&[0xf2, 0x7f, 0x68],
&[MidiMessage::SongPositionPointer((0x7f, 0x68).into())],
);
}
#[test]
fn should_handle_song_position_pointer_running_state() {
MidiParser::new().assert_result(
&[
0xf2, 0x7f, 0x68,
0x23, 0x7b,
],
&[
MidiMessage::SongPositionPointer((0x7f, 0x68).into()),
MidiMessage::SongPositionPointer((0x23, 0x7b).into()),
],
);
}
#[test]
fn should_parse_song_select() {
MidiParser::new().assert_result(&[0xf3, 0x3f], &[MidiMessage::SongSelect(0x3f.into())]);
}
#[test]
fn should_handle_song_select_running_state() {
MidiParser::new().assert_result(
&[
0xf3, 0x3f,
0x00,
],
&[
MidiMessage::SongSelect(0x3f.into()),
MidiMessage::SongSelect(0x00.into()),
],
);
}
#[test]
fn should_parse_tune_request() {
MidiParser::new().assert_result(&[0xf6], &[MidiMessage::TuneRequest]);
}
#[test]
fn should_interrupt_parsing_for_tune_request() {
MidiParser::new().assert_result(
&[
0x92, 0x76,
0xf6,
0x34,
],
&[MidiMessage::TuneRequest],
);
}
#[test]
fn should_interrupt_parsing_for_undefined_message() {
MidiParser::new().assert_result(
&[
0x92, 0x76,
0xf5,
0x34,
],
&[],
);
}
#[test]
fn should_parse_timingclock_message() {
MidiParser::new().assert_result(&[0xf8], &[MidiMessage::TimingClock]);
}
#[test]
fn should_parse_timingclock_message_as_realtime() {
MidiParser::new().assert_result(
&[
0xD6,
0xf8,
0x77,
],
&[
MidiMessage::TimingClock,
MidiMessage::ChannelPressure(6.into(), 0x77.into()),
],
);
}
#[test]
fn should_parse_start_message() {
MidiParser::new().assert_result(&[0xfa], &[MidiMessage::Start]);
}
#[test]
fn should_parse_start_message_as_realtime() {
MidiParser::new().assert_result(
&[
0xD6,
0xfa,
0x77,
],
&[
MidiMessage::Start,
MidiMessage::ChannelPressure(6.into(), 0x77.into()),
],
);
}
#[test]
fn should_parse_continue_message() {
MidiParser::new().assert_result(&[0xfb], &[MidiMessage::Continue]);
}
#[test]
fn should_parse_continue_message_as_realtime() {
MidiParser::new().assert_result(
&[
0xD6,
0xfb,
0x77,
],
&[
MidiMessage::Continue,
MidiMessage::ChannelPressure(6.into(), 0x77.into()),
],
);
}
#[test]
fn should_parse_stop_message() {
MidiParser::new().assert_result(&[0xfc], &[MidiMessage::Stop]);
}
#[test]
fn should_parse_stop_message_as_realtime() {
MidiParser::new().assert_result(
&[
0xD6,
0xfc,
0x77,
],
&[
MidiMessage::Stop,
MidiMessage::ChannelPressure(6.into(), 0x77.into()),
],
);
}
#[test]
fn should_parse_activesensing_message() {
MidiParser::new().assert_result(&[0xfe], &[MidiMessage::ActiveSensing]);
}
#[test]
fn should_parse_activesensing_message_as_realtime() {
MidiParser::new().assert_result(
&[
0xD6,
0xfe,
0x77,
],
&[
MidiMessage::ActiveSensing,
MidiMessage::ChannelPressure(6.into(), 0x77.into()),
],
);
}
#[test]
fn should_parse_reset_message() {
MidiParser::new().assert_result(&[0xff], &[MidiMessage::Reset]);
}
#[test]
fn should_parse_reset_message_as_realtime() {
MidiParser::new().assert_result(
&[
0xD6,
0xff,
0x77,
],
&[
MidiMessage::Reset,
MidiMessage::ChannelPressure(6.into(), 0x77.into()),
],
);
}
#[test]
fn should_ignore_incomplete_messages() {
MidiParser::new().assert_result(
&[
0x92, 0x1b,
0x82, 0x76, 0x34,
],
&[MidiMessage::NoteOff(2.into(), 0x76.into(), 0x34.into())],
);
}
impl MidiParser {
fn assert_result(&mut self, bytes: &[u8], expected_events: &[MidiMessage]) {
let events: Vec<MidiMessage> = bytes
.into_iter()
.filter_map(|byte| self.parse_byte(*byte))
.collect();
assert_eq!(expected_events, events.as_slice());
}
}
}