181 lines
5.9 KiB
Rust
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
|
|
);
|
|
}
|
|
}
|
|
}
|