finish support for most MIDI messages

This commit is contained in:
Nicole Tietz-Sokolskaya 2024-12-05 10:55:06 -05:00
parent 2c6dbbf952
commit 0675e7f26e
2 changed files with 115 additions and 74 deletions

View file

@ -1,7 +1,8 @@
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
pub enum Message { pub enum Message {
Voice(VoiceMessage), Voice(VoiceMessage),
System(SystemMessage), System(SystemCommon),
Realtime(SystemRealtime),
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
@ -28,4 +29,23 @@ pub enum VoiceCategory {
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[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,
}

View file

@ -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> { pub fn parse_message(bytes: &[u8]) -> IResult<&[u8], Message> {
let (bytes, status_byte) = take(1usize)(bytes)?; let (bytes, status_byte) = take(1usize)(bytes)?;
let status_byte = status_byte[0]; 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 { if status_byte < 0xF0 {
let (bytes, vm) = parse_voice_message(status_byte, bytes)?; let (bytes, vm) = parse_voice_message(status_byte, bytes)?;
Ok((bytes, Message::Voice(vm))) 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 { } else {
let (bytes, sm) = parse_status_message(status_byte, bytes)?; let sr = parse_system_realtime(status_byte);
Ok((bytes, Message::System(sm))) Ok((bytes, Message::Realtime(sr)))
} }
} }
fn parse_status_message(_status_byte: u8, bytes: &[u8]) -> IResult<&[u8], SystemMessage> { /// The highest order bit is only ever set for status bytes, so we can test
Err(nom::Err::Error(nom::error::Error { /// for to tell whether or not we've found a status byte. This is needed for
input: bytes, /// finding the end of dynamic-length messages, such as SysEx, which is
code: nom::error::ErrorKind::Fail, /// 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> { 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 { let (remainder, category) = match category_nibble {
0x80 => parse_voice_note(remainder, true)?, 0x80 => parse_voice_note(remainder, true)?,
0x90 => parse_voice_note(remainder, false)?, 0x90 => parse_voice_note(remainder, false)?,
0xa0 => parse_aftertouch(remainder)?, 0xa0 => two_byte_message(remainder, |note, pressure| VoiceCategory::AfterTouch {
0xb0 => parse_control_change(remainder)?, note,
0xc0 => parse_program_change(remainder)?, pressure,
0xd0 => parse_channel_pressure(remainder)?, })?,
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)?, 0xe0 => parse_pitch_wheel(remainder)?,
_ => { _ => {
return Err(nom::Err::Error(nom::error::Error { 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> { pub fn parse_voice_note(bytes: &[u8], off: bool) -> IResult<&[u8], VoiceCategory> {
let (remainder, data) = take(2usize)(bytes)?; two_byte_message(bytes, |note, velocity| {
if velocity == 0 || off {
let note = data[0]; VoiceCategory::NoteOff { note, velocity }
let velocity = data[1]; } else {
VoiceCategory::NoteOn { note, velocity }
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,
}) })
} }
pub fn parse_pitch_wheel(bytes: &[u8]) -> IResult<&[u8], VoiceCategory> { pub fn parse_pitch_wheel(bytes: &[u8]) -> IResult<&[u8], VoiceCategory> {
if bytes.len() < 2 { let (bytes, value) = take_14_bit_value(bytes)?;
return Err(generic_error(bytes)); Ok((bytes, VoiceCategory::PitchWheel { value }))
}
let (db1, db2) = (bytes[0], bytes[1]);
let value = ((db1 as u16) << 7) | db2 as u16;
Ok((&bytes[2..], VoiceCategory::PitchWheel { value }))
} }
pub fn parse_control_change(bytes: &[u8]) -> IResult<&[u8], VoiceCategory> { pub fn parse_system_exclusive(bytes: &[u8]) -> IResult<&[u8], SystemCommon> {
if bytes.len() < 2 { let (remainder, data) = take_till(is_status_byte)(bytes)?;
return Err(generic_error(bytes)); let (remainder, _) = opt(tag([0xf7]))(remainder)?;
}
let controller = bytes[0]; let data: Vec<u8> = data.into();
let value = bytes[1];
Ok(( Ok((remainder, SystemCommon::SystemExclusive { data }))
&bytes[2..],
VoiceCategory::ControlChange { controller, value },
))
} }
pub fn parse_program_change(bytes: &[u8]) -> IResult<&[u8], VoiceCategory> { pub fn parse_song_position_pointer(bytes: &[u8]) -> IResult<&[u8], SystemCommon> {
if bytes.is_empty() { let (remainder, value) = take_14_bit_value(bytes)?;
return Err(generic_error(bytes)); Ok((remainder, SystemCommon::SongPositionPointer { value }))
}
let value = bytes[0];
Ok((&bytes[1..], VoiceCategory::ProgramChange { value }))
} }
pub fn parse_aftertouch(bytes: &[u8]) -> IResult<&[u8], VoiceCategory> { pub fn one_byte_message<T, F>(bytes: &[u8], f: F) -> IResult<&[u8], T>
if bytes.len() < 2 { where
return Err(generic_error(bytes)); F: Fn(u8) -> T,
} {
let (bytes, b) = take(1usize)(bytes)?;
let note = bytes[0]; Ok((bytes, f(b[0])))
let pressure = bytes[1];
Ok((&bytes[2..], VoiceCategory::AfterTouch { note, pressure }))
} }
pub fn parse_channel_pressure(bytes: &[u8]) -> IResult<&[u8], VoiceCategory> { pub fn two_byte_message<T, F>(bytes: &[u8], f: F) -> IResult<&[u8], T>
if bytes.is_empty() { where
return Err(generic_error(bytes)); 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)] #[cfg(test)]