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)]
|
#[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,
|
||||||
|
}
|
||||||
|
|
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> {
|
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)]
|
||||||
|
|
Loading…
Reference in a new issue