midi-keys/src/parser.rs

181 lines
5.9 KiB
Rust

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<u8> = 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<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 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])))
}
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
);
}
}
}