From 0675e7f26e0081fb31349b517e1b9859b06d6b08 Mon Sep 17 00:00:00 2001 From: Nicole Tietz-Sokolskaya <me@ntietz.com> Date: Thu, 5 Dec 2024 10:55:06 -0500 Subject: [PATCH] finish support for most MIDI messages --- src/midi.rs | 24 +++++++- src/parser.rs | 165 ++++++++++++++++++++++++++++---------------------- 2 files changed, 115 insertions(+), 74 deletions(-) diff --git a/src/midi.rs b/src/midi.rs index 078d5ca..fe4d0b9 100644 --- a/src/midi.rs +++ b/src/midi.rs @@ -1,7 +1,8 @@ #[derive(PartialEq, Eq, Debug, Clone)] pub enum Message { Voice(VoiceMessage), - System(SystemMessage), + System(SystemCommon), + Realtime(SystemRealtime), } #[derive(PartialEq, Eq, Debug, Clone)] @@ -28,4 +29,23 @@ pub enum VoiceCategory { } #[derive(PartialEq, Eq, Debug, Clone)] -pub enum SystemMessage {} +pub enum SystemCommon { + SystemExclusive { data: Vec<u8> }, + MidiTimeCode { time_code: u8 }, + SongPositionPointer { value: u16 }, + SongSelect { song_number: u8 }, + TuneRequest, + Unknown, +} + +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum SystemRealtime { + Clock, + Tick, + Start, + Stop, + Continue, + ActiveSense, + Reset, + Unknown, +} diff --git a/src/parser.rs b/src/parser.rs index a04d9f8..43b7c20 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,25 +1,65 @@ -use nom::{bytes::complete::take, IResult}; +use nom::{ + bytes::complete::{tag, take, take_till}, + combinator::opt, + IResult, +}; -use crate::midi::{Message, SystemMessage, VoiceCategory, VoiceMessage}; +use crate::midi::{Message, SystemCommon, SystemRealtime, VoiceCategory, VoiceMessage}; pub fn parse_message(bytes: &[u8]) -> IResult<&[u8], Message> { let (bytes, status_byte) = take(1usize)(bytes)?; let status_byte = status_byte[0]; + // TODO: implement running status; see [1]. + // [1]: http://midi.teragonaudio.com/tech/midispec/run.htm + if status_byte < 0xF0 { let (bytes, vm) = parse_voice_message(status_byte, bytes)?; Ok((bytes, Message::Voice(vm))) + } else if status_byte < 0xf8 { + let (bytes, sc) = parse_system_common(status_byte, bytes)?; + Ok((bytes, Message::System(sc))) } else { - let (bytes, sm) = parse_status_message(status_byte, bytes)?; - Ok((bytes, Message::System(sm))) + let sr = parse_system_realtime(status_byte); + Ok((bytes, Message::Realtime(sr))) } } -fn parse_status_message(_status_byte: u8, bytes: &[u8]) -> IResult<&[u8], SystemMessage> { - Err(nom::Err::Error(nom::error::Error { - input: bytes, - code: nom::error::ErrorKind::Fail, - })) +/// The highest order bit is only ever set for status bytes, so we can test +/// for to tell whether or not we've found a status byte. This is needed for +/// finding the end of dynamic-length messages, such as SysEx, which is +/// considered aborted if another status byte comes before 0xf7. +fn is_status_byte(b: u8) -> bool { + (b & 0x80) != 0 +} + +fn parse_system_common(status_byte: u8, bytes: &[u8]) -> IResult<&[u8], SystemCommon> { + match status_byte { + 0xf0 => parse_system_exclusive(bytes), + 0xf1 => one_byte_message(bytes, |time_code| SystemCommon::MidiTimeCode { time_code }), + 0xf2 => parse_song_position_pointer(bytes), + 0xf3 => one_byte_message(bytes, |song_number| SystemCommon::SongSelect { + song_number, + }), + 0xf6 => Ok((bytes, SystemCommon::TuneRequest)), + _ => Err(nom::Err::Error(nom::error::Error { + input: bytes, + code: nom::error::ErrorKind::Fail, + })), + } +} + +pub fn parse_system_realtime(status_byte: u8) -> SystemRealtime { + match status_byte { + 0xf8 => SystemRealtime::Clock, + 0xf9 => SystemRealtime::Tick, + 0xfa => SystemRealtime::Start, + 0xfb => SystemRealtime::Continue, + 0xfc => SystemRealtime::Stop, + 0xfe => SystemRealtime::ActiveSense, + 0xff => SystemRealtime::Reset, + _ => SystemRealtime::Unknown, + } } pub fn parse_voice_message(status_byte: u8, remainder: &[u8]) -> IResult<&[u8], VoiceMessage> { @@ -30,10 +70,17 @@ pub fn parse_voice_message(status_byte: u8, remainder: &[u8]) -> IResult<&[u8], let (remainder, category) = match category_nibble { 0x80 => parse_voice_note(remainder, true)?, 0x90 => parse_voice_note(remainder, false)?, - 0xa0 => parse_aftertouch(remainder)?, - 0xb0 => parse_control_change(remainder)?, - 0xc0 => parse_program_change(remainder)?, - 0xd0 => parse_channel_pressure(remainder)?, + 0xa0 => two_byte_message(remainder, |note, pressure| VoiceCategory::AfterTouch { + note, + pressure, + })?, + 0xb0 => two_byte_message(remainder, |controller, value| { + VoiceCategory::ControlChange { controller, value } + })?, + 0xc0 => one_byte_message(remainder, |value| VoiceCategory::ProgramChange { value })?, + 0xd0 => one_byte_message(remainder, |pressure| VoiceCategory::ChannelPressure { + pressure, + })?, 0xe0 => parse_pitch_wheel(remainder)?, _ => { return Err(nom::Err::Error(nom::error::Error { @@ -47,81 +94,55 @@ pub fn parse_voice_message(status_byte: u8, remainder: &[u8]) -> IResult<&[u8], } pub fn parse_voice_note(bytes: &[u8], off: bool) -> IResult<&[u8], VoiceCategory> { - let (remainder, data) = take(2usize)(bytes)?; - - let note = data[0]; - let velocity = data[1]; - - let category = if velocity == 0 || off { - VoiceCategory::NoteOff { note, velocity } - } else { - VoiceCategory::NoteOn { note, velocity } - }; - - Ok((remainder, category)) -} - -pub fn generic_error(bytes: &[u8]) -> nom::Err<nom::error::Error<&[u8]>> { - nom::Err::Error(nom::error::Error { - input: bytes, - code: nom::error::ErrorKind::Fail, + two_byte_message(bytes, |note, velocity| { + if velocity == 0 || off { + VoiceCategory::NoteOff { note, velocity } + } else { + VoiceCategory::NoteOn { note, velocity } + } }) } pub fn parse_pitch_wheel(bytes: &[u8]) -> IResult<&[u8], VoiceCategory> { - if bytes.len() < 2 { - return Err(generic_error(bytes)); - } - - let (db1, db2) = (bytes[0], bytes[1]); - let value = ((db1 as u16) << 7) | db2 as u16; - - Ok((&bytes[2..], VoiceCategory::PitchWheel { value })) + let (bytes, value) = take_14_bit_value(bytes)?; + Ok((bytes, VoiceCategory::PitchWheel { value })) } -pub fn parse_control_change(bytes: &[u8]) -> IResult<&[u8], VoiceCategory> { - if bytes.len() < 2 { - return Err(generic_error(bytes)); - } +pub fn parse_system_exclusive(bytes: &[u8]) -> IResult<&[u8], SystemCommon> { + let (remainder, data) = take_till(is_status_byte)(bytes)?; + let (remainder, _) = opt(tag([0xf7]))(remainder)?; - let controller = bytes[0]; - let value = bytes[1]; + let data: Vec<u8> = data.into(); - Ok(( - &bytes[2..], - VoiceCategory::ControlChange { controller, value }, - )) + Ok((remainder, SystemCommon::SystemExclusive { data })) } -pub fn parse_program_change(bytes: &[u8]) -> IResult<&[u8], VoiceCategory> { - if bytes.is_empty() { - return Err(generic_error(bytes)); - } - - let value = bytes[0]; - - Ok((&bytes[1..], VoiceCategory::ProgramChange { value })) +pub fn parse_song_position_pointer(bytes: &[u8]) -> IResult<&[u8], SystemCommon> { + let (remainder, value) = take_14_bit_value(bytes)?; + Ok((remainder, SystemCommon::SongPositionPointer { value })) } -pub fn parse_aftertouch(bytes: &[u8]) -> IResult<&[u8], VoiceCategory> { - if bytes.len() < 2 { - return Err(generic_error(bytes)); - } - - let note = bytes[0]; - let pressure = bytes[1]; - - Ok((&bytes[2..], VoiceCategory::AfterTouch { note, pressure })) +pub fn one_byte_message<T, F>(bytes: &[u8], f: F) -> IResult<&[u8], T> +where + F: Fn(u8) -> T, +{ + let (bytes, b) = take(1usize)(bytes)?; + Ok((bytes, f(b[0]))) } -pub fn parse_channel_pressure(bytes: &[u8]) -> IResult<&[u8], VoiceCategory> { - if bytes.is_empty() { - return Err(generic_error(bytes)); - } +pub fn two_byte_message<T, F>(bytes: &[u8], f: F) -> IResult<&[u8], T> +where + F: Fn(u8, u8) -> T, +{ + let (bytes, b) = take(2usize)(bytes)?; + Ok((bytes, f(b[0], b[1]))) +} - let pressure = bytes[0]; +pub fn take_14_bit_value(bytes: &[u8]) -> IResult<&[u8], u16> { + let (bytes, db) = take(2usize)(bytes)?; + let value = ((db[0] as u16) << 7) | db[1] as u16; - Ok((&bytes[1..], VoiceCategory::ChannelPressure { pressure })) + Ok((bytes, value)) } #[cfg(test)]