finish support for most MIDI messages
This commit is contained in:
parent
2c6dbbf952
commit
0675e7f26e
2 changed files with 115 additions and 74 deletions
24
src/midi.rs
24
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,
|
||||
}
|
||||
|
|
165
src/parser.rs
165
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)]
|
||||
|
|
Loading…
Reference in a new issue