use nom::{ bytes::complete::{tag, take, take_till}, IResult, }; use crate::midi::{Message, ParsedMessage, SystemCommon, SystemRealtime, VoiceCategory, VoiceMessage}; pub fn parse_message(bytes: &[u8]) -> IResult<&[u8], Message> { let raw = Vec::from(bytes); 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::new(raw, ParsedMessage::Voice(vm)))) } else if status_byte < 0xf8 { let (bytes, sc) = parse_system_common(status_byte, bytes)?; Ok((bytes, Message::new(raw, ParsedMessage::System(sc)))) } else { let sr = parse_system_realtime(status_byte); Ok((bytes, Message::new(raw, ParsedMessage::Realtime(sr)))) } } /// 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)), _ => Ok((bytes, SystemCommon::Unknown)), } } 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> { let category_nibble = 0xf0 & status_byte; let channel = 0x0f & status_byte; let (remainder, category) = match category_nibble { 0x80 => parse_voice_note(remainder, true)?, 0x90 => parse_voice_note(remainder, false)?, 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)?, _ => (remainder, VoiceCategory::Unknown), }; Ok((remainder, VoiceMessage::new(category, channel))) } pub fn parse_voice_note(bytes: &[u8], off: bool) -> IResult<&[u8], VoiceCategory> { 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> { let (bytes, value) = take_14_bit_value(bytes)?; Ok((bytes, VoiceCategory::PitchWheel { value })) } pub fn parse_system_exclusive(bytes: &[u8]) -> IResult<&[u8], SystemCommon> { let (remainder, data) = take_till(is_status_byte)(bytes)?; let (remainder, _) = tag([0xf7])(remainder)?; let data: Vec = data.into(); Ok((remainder, SystemCommon::SystemExclusive { data })) } 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 one_byte_message(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 two_byte_message(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]))) } 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, value)) } #[cfg(test)] mod tests { use crate::{log::load_raw_log, midi::VoiceMessage}; use super::*; #[test] fn parses_note_on_message() { let msg = [0x90, 0x24, 0x51]; let (remainder, parsed) = parse_message(&msg).unwrap(); let note = 0x24; let velocity = 0x51; let expected = Message::Voice(VoiceMessage::new( VoiceCategory::NoteOn { note, velocity }, 0, )); assert_eq!(parsed, expected,); assert_eq!(remainder.len(), 0); } #[test] fn parse_log_from_windsynth() { // I played a few notes on my Roland AE-20 and saved it to a log file. // This test just reads that in and ensures that everything parses. It // doesn't check for *correct* parsing, but this is sufficient for much // of our needs. let filename = "assets/windsynth.log"; let entries = load_raw_log(filename).unwrap(); assert_eq!(1260, entries.len()); for entry in entries { let parsed = parse_message(&entry.message); assert!( parsed.is_ok(), "failed to parse message: {:?}; {:?}", &entry.message, parsed ); } } }