#[derive(Debug, PartialEq, Eq)]
pub enum SysExEvent {
Complete(Vec<u8>),
Realtime(u8),
}
pub struct SysExReassembler {
buffer: Vec<u8>,
in_sysex: bool,
}
impl SysExReassembler {
pub fn new() -> Self {
Self {
buffer: Vec::new(),
in_sysex: false,
}
}
pub fn feed(&mut self, bytes: &[u8]) -> Vec<SysExEvent> {
let mut events = Vec::new();
for &byte in bytes {
if !self.in_sysex {
if byte == 0xF0 {
self.buffer.clear();
self.buffer.push(0xF0);
self.in_sysex = true;
}
} else {
if byte == 0xF7 {
self.buffer.push(0xF7);
self.in_sysex = false;
events.push(SysExEvent::Complete(self.buffer.drain(..).collect()));
} else if byte >= 0xF8 {
events.push(SysExEvent::Realtime(byte));
} else if byte >= 0x80 {
self.buffer.clear();
self.in_sysex = false;
if byte == 0xF0 {
self.buffer.push(0xF0);
self.in_sysex = true;
}
} else {
self.buffer.push(byte);
}
}
}
events
}
pub fn in_sysex(&self) -> bool {
self.in_sysex
}
pub fn reset(&mut self) {
self.buffer.clear();
self.in_sysex = false;
}
}
impl Default for SysExReassembler {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn midi_message_len(status: u8) -> Option<usize> {
match status & 0xF0 {
0x80 | 0x90 | 0xA0 | 0xB0 | 0xE0 => Some(3), 0xC0 | 0xD0 => Some(2), 0xF0 => match status {
0xF1 | 0xF3 => Some(2), 0xF2 => Some(3), 0xF6 | 0xF8 | 0xFA | 0xFB | 0xFC | 0xFE | 0xFF => Some(1), _ => None, },
_ => None,
}
}
#[must_use]
pub fn is_realtime(byte: u8) -> bool {
byte >= 0xF8
}
#[must_use]
pub fn is_status(byte: u8) -> bool {
byte & 0x80 != 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_midi_message_len() {
assert_eq!(midi_message_len(0x90), Some(3)); assert_eq!(midi_message_len(0xC0), Some(2)); assert_eq!(midi_message_len(0xF8), Some(1)); assert_eq!(midi_message_len(0xF0), None); }
#[test]
fn test_sysex_reassembler_complete_in_one_chunk() {
let mut asm = SysExReassembler::new();
let events = asm.feed(&[0xF0, 0x41, 0x10, 0x42, 0xF7]);
assert_eq!(events.len(), 1);
if let SysExEvent::Complete(data) = &events[0] {
assert_eq!(data.as_slice(), &[0xF0, 0x41, 0x10, 0x42, 0xF7]);
} else {
panic!("expected Complete");
}
assert!(!asm.in_sysex());
}
#[test]
fn test_sysex_reassembler_split_across_chunks() {
let mut asm = SysExReassembler::new();
let e1 = asm.feed(&[0xF0, 0x41, 0x10]);
assert!(e1.is_empty());
assert!(asm.in_sysex());
let e2 = asm.feed(&[0x42, 0xF7]);
assert_eq!(e2.len(), 1);
if let SysExEvent::Complete(data) = &e2[0] {
assert_eq!(data.as_slice(), &[0xF0, 0x41, 0x10, 0x42, 0xF7]);
} else {
panic!("expected Complete");
}
assert!(!asm.in_sysex());
}
#[test]
fn test_sysex_reassembler_interleaved_realtime() {
let mut asm = SysExReassembler::new();
let e1 = asm.feed(&[0xF0, 0x41]);
assert!(e1.is_empty());
let e2 = asm.feed(&[0xF8]); assert_eq!(e2.len(), 1);
assert!(matches!(&e2[0], SysExEvent::Realtime(0xF8)));
assert!(asm.in_sysex()); let e3 = asm.feed(&[0x42, 0xF7]);
assert_eq!(e3.len(), 1);
if let SysExEvent::Complete(data) = &e3[0] {
assert_eq!(data.as_slice(), &[0xF0, 0x41, 0x42, 0xF7]);
} else {
panic!("expected Complete");
}
}
#[test]
fn test_sysex_reassembler_back_to_back() {
let mut asm = SysExReassembler::new();
let e1 = asm.feed(&[0xF0, 0x01, 0xF7]);
assert_eq!(e1.len(), 1);
let e2 = asm.feed(&[0xF0, 0x02, 0xF7]);
assert_eq!(e2.len(), 1);
}
#[test]
fn test_is_realtime() {
assert!(is_realtime(0xF8)); assert!(is_realtime(0xFE)); assert!(!is_realtime(0x90)); assert!(!is_realtime(0xF0)); }
#[test]
fn test_is_status() {
assert!(is_status(0x90));
assert!(is_status(0xF0));
assert!(is_status(0xFF));
assert!(!is_status(0x00));
assert!(!is_status(0x7F));
}
#[test]
fn test_sysex_reassembler_cancelled_by_status() {
let mut asm = SysExReassembler::new();
let e1 = asm.feed(&[0xF0, 0x41]); assert!(e1.is_empty());
let e2 = asm.feed(&[0x90]); assert!(e2.is_empty()); assert!(!asm.in_sysex());
}
#[test]
fn test_sysex_reassembler_reset() {
let mut asm = SysExReassembler::new();
let _ = asm.feed(&[0xF0, 0x41]); assert!(asm.in_sysex());
asm.reset();
assert!(!asm.in_sysex());
let events = asm.feed(&[0xF0, 0x42, 0xF7]);
assert_eq!(events.len(), 1);
assert!(matches!(&events[0], SysExEvent::Complete(data) if data[1] == 0x42));
}
#[test]
fn test_midi_message_len_all_channels() {
for ch in 0u8..16 {
assert_eq!(midi_message_len(0x80 | ch), Some(3)); assert_eq!(midi_message_len(0x90 | ch), Some(3)); assert_eq!(midi_message_len(0xA0 | ch), Some(3)); assert_eq!(midi_message_len(0xB0 | ch), Some(3)); assert_eq!(midi_message_len(0xC0 | ch), Some(2)); assert_eq!(midi_message_len(0xD0 | ch), Some(2)); assert_eq!(midi_message_len(0xE0 | ch), Some(3)); }
}
#[test]
fn test_midi_message_len_system_messages() {
assert_eq!(midi_message_len(0xF1), Some(2)); assert_eq!(midi_message_len(0xF2), Some(3)); assert_eq!(midi_message_len(0xF3), Some(2)); assert_eq!(midi_message_len(0xF6), Some(1)); assert_eq!(midi_message_len(0xF7), None); assert_eq!(midi_message_len(0xF8), Some(1)); assert_eq!(midi_message_len(0xFA), Some(1)); assert_eq!(midi_message_len(0xFB), Some(1)); assert_eq!(midi_message_len(0xFC), Some(1)); assert_eq!(midi_message_len(0xFE), Some(1)); assert_eq!(midi_message_len(0xFF), Some(1)); }
}