pub use truce_utils::midi::*;
use crate::events::EventBody;
#[must_use]
pub fn decode_short_message(status: u8, data1: u8, data2: u8) -> Option<EventBody> {
let channel = status & 0x0F;
let d1 = data1 & 0x7F;
let d2 = data2 & 0x7F;
match status & 0xF0 {
0x90 if d2 > 0 => Some(EventBody::NoteOn {
group: 0,
channel,
note: d1,
velocity: d2,
}),
0x90 => Some(EventBody::NoteOff {
group: 0,
channel,
note: d1,
velocity: 0,
}),
0x80 => Some(EventBody::NoteOff {
group: 0,
channel,
note: d1,
velocity: d2,
}),
0xA0 => Some(EventBody::Aftertouch {
group: 0,
channel,
note: d1,
pressure: d2,
}),
0xB0 => Some(EventBody::ControlChange {
group: 0,
channel,
cc: d1,
value: d2,
}),
0xC0 => Some(EventBody::ProgramChange {
group: 0,
channel,
program: d1,
}),
0xD0 => Some(EventBody::ChannelPressure {
group: 0,
channel,
pressure: d1,
}),
0xE0 => Some(EventBody::PitchBend {
group: 0,
channel,
value: pitch_bend_from_bytes(d1, d2),
}),
_ => None,
}
}
#[must_use]
pub fn parse_midi1(group: u8, bytes: &[u8]) -> Option<EventBody> {
if bytes.is_empty() {
return None;
}
let status = bytes[0];
let (data1, data2) = match status & 0xF0 {
0xC0 | 0xD0 if bytes.len() >= 2 => (bytes[1], 0),
0x80..=0xB0 | 0xE0 if bytes.len() >= 3 => (bytes[1], bytes[2]),
_ => return None,
};
let mut event = decode_short_message(status, data1, data2)?;
rewrite_group(&mut event, group);
Some(event)
}
fn rewrite_group(event: &mut EventBody, new_group: u8) {
match event {
EventBody::NoteOn { group, .. }
| EventBody::NoteOff { group, .. }
| EventBody::Aftertouch { group, .. }
| EventBody::ChannelPressure { group, .. }
| EventBody::ControlChange { group, .. }
| EventBody::PitchBend { group, .. }
| EventBody::ProgramChange { group, .. } => *group = new_group,
_ => {}
}
}
#[must_use]
pub fn event_to_midi1(event: &EventBody) -> Option<(usize, [u8; 3])> {
match event {
EventBody::NoteOn {
channel,
note,
velocity,
..
} => Some((3, [0x90 | (channel & 0x0F), *note, *velocity])),
EventBody::NoteOff {
channel,
note,
velocity,
..
} => Some((3, [0x80 | (channel & 0x0F), *note, *velocity])),
EventBody::Aftertouch {
channel,
note,
pressure,
..
} => Some((3, [0xA0 | (channel & 0x0F), *note, *pressure])),
EventBody::ControlChange {
channel, cc, value, ..
} => Some((3, [0xB0 | (channel & 0x0F), *cc, *value])),
EventBody::PitchBend { channel, value, .. } => {
let (lsb, msb) = pitch_bend_to_bytes(*value);
Some((3, [0xE0 | (channel & 0x0F), lsb, msb]))
}
EventBody::ChannelPressure {
channel, pressure, ..
} => Some((2, [0xD0 | (channel & 0x0F), *pressure, 0])),
EventBody::ProgramChange {
channel, program, ..
} => Some((2, [0xC0 | (channel & 0x0F), *program, 0])),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_note_on() {
let bytes = [0x90, 60, 100];
let event = parse_midi1(0, &bytes).unwrap();
let (len, back) = event_to_midi1(&event).unwrap();
assert_eq!(len, 3);
assert_eq!(back, [0x90, 60, 100]);
}
#[test]
fn round_trip_pitch_bend_center() {
let bytes = [0xE0, 0x00, 0x40]; let event = parse_midi1(0, &bytes).unwrap();
if let EventBody::PitchBend { value, .. } = event {
assert_eq!(value, 8192);
} else {
panic!("expected PitchBend");
}
}
#[test]
fn note_on_zero_velocity_is_note_off() {
let bytes = [0x90, 60, 0];
let event = parse_midi1(0, &bytes).unwrap();
assert!(matches!(event, EventBody::NoteOff { .. }));
}
#[test]
fn channel_masked_on_encode() {
let event = EventBody::NoteOn {
group: 0,
channel: 64, note: 60,
velocity: 100,
};
let (_len, bytes) = event_to_midi1(&event).unwrap();
assert_eq!(bytes[0], 0x90);
}
#[test]
fn group_propagated_through_parse() {
let event = parse_midi1(7, &[0x90, 60, 100]).unwrap();
if let EventBody::NoteOn { group, .. } = event {
assert_eq!(group, 7);
} else {
panic!("expected NoteOn");
}
}
#[test]
fn decode_program_change() {
let event = decode_short_message(0xC3, 42, 0).unwrap();
if let EventBody::ProgramChange {
channel, program, ..
} = event
{
assert_eq!(channel, 3);
assert_eq!(program, 42);
} else {
panic!("expected ProgramChange, got {event:?}");
}
}
#[test]
fn decode_channel_pressure() {
let event = decode_short_message(0xD5, 96, 0).unwrap();
if let EventBody::ChannelPressure {
channel, pressure, ..
} = event
{
assert_eq!(channel, 5);
assert_eq!(pressure, 96);
} else {
panic!("expected ChannelPressure, got {event:?}");
}
}
#[test]
fn decode_short_message_unknown_status_returns_none() {
assert!(decode_short_message(0xF0, 0, 0).is_none());
assert!(decode_short_message(0xF8, 0, 0).is_none());
}
#[test]
fn decode_short_message_strips_data_high_bit() {
let event = decode_short_message(0xB0, 0xFF, 0xFF).unwrap();
if let EventBody::ControlChange { cc, value, .. } = event {
assert_eq!(cc, 0x7F);
assert_eq!(value, 0x7F);
} else {
panic!("expected ControlChange, got {event:?}");
}
}
}