use std::convert::TryFrom;
use std::fmt;
use num_enum::TryFromPrimitive;
use crate::{
SystemExclusiveData,
ParseError,
MIDIChannel,
Ranged
};
const GROUP: u8 = 0x00; const MACHINE_ID: u8 = 0x04;
#[derive(Debug, Eq, PartialEq, Copy, Clone, TryFromPrimitive)]
#[repr(u8)]
pub enum Function {
OnePatchDumpRequest = 0x00,
BlockPatchDumpRequest = 0x01,
AllPatchDumpRequest = 0x02,
ParameterSend = 0x10,
OnePatchDataDump = 0x20,
BlockPatchDataDump = 0x21,
AllPatchDataDump = 0x22,
EditBufferDump = 0x23,
ProgramChange = 0x30,
WriteComplete = 0x40,
WriteError = 0x41,
WriteErrorProtect = 0x42,
WriteErrorNoCard = 0x43,
}
impl fmt::Display for Function {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match self {
Function::OnePatchDumpRequest => String::from("One Patch Dump Request"),
Function::BlockPatchDumpRequest => String::from("Block Patch Dump Request"),
Function::AllPatchDumpRequest => String::from("All Patch Dump Request"),
Function::ParameterSend => String::from("Parameter Send"),
Function::OnePatchDataDump => String::from("One Patch Data Dump"),
Function::BlockPatchDataDump => String::from("Block Patch Data Dump"),
Function::AllPatchDataDump => String::from("All Patch Data Dump"),
Function::EditBufferDump => String::from("Edit Buffer Dump"),
Function::ProgramChange => String::from("Program Change"),
Function::WriteComplete => String::from("Write Complete"),
Function::WriteError => String::from("Write Error"),
Function::WriteErrorProtect => String::from("Write Error (Protect)"),
Function::WriteErrorNoCard => String::from("Write Error (No Card)"),
})
}
}
pub struct Header {
pub channel: MIDIChannel,
pub function: Function,
pub substatus1: u8,
pub substatus2: u8,
}
impl fmt::Display for Header {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Ch: {} Fn: {}, Sub1: {}, Sub2: {}",
self.channel.value(),
self.function,
self.substatus1,
self.substatus2)
}
}
impl SystemExclusiveData for Header {
fn from_bytes(data: &[u8]) -> Result<Self, ParseError> {
Ok(Header {
channel: MIDIChannel::new(data[0] as i32 + 1),
function: Function::try_from(data[1]).unwrap(),
substatus1: data[4],
substatus2: data[5],
})
}
fn to_bytes(&self) -> Vec<u8> {
let ch = self.channel.value() - 1; vec![
ch as u8,
self.function as u8,
GROUP,
MACHINE_ID,
self.substatus1,
self.substatus2,
]
}
fn data_size() -> usize { 6 }
}
pub enum Locality {
Internal,
External,
}
impl fmt::Display for Locality {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match self {
Locality::Internal => String::from("INT"),
Locality::External => String::from("EXT"),
})
}
}
pub struct Dump {
pub kind: Kind,
pub locality: Locality,
pub payload: Vec<u8>,
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum Kind {
All,
OneSingle(u8),
OneMulti(u8),
Drum,
OneEffect(u8),
BlockSingle,
BlockMulti,
BlockEffect,
}
impl Dump {
pub fn identify(payload: Vec<u8>) -> Result<Dump, ParseError> {
let header_data = &payload[0..Header::data_size() as usize];
let header = Header::from_bytes(header_data);
let raw_data = &payload[Header::data_size() as usize..];
match (header.as_ref().unwrap().function, header.as_ref().unwrap().substatus1, header.as_ref().unwrap().substatus2) {
(Function::OnePatchDataDump, 0x00, number) if (0..=63).contains(&number) =>
Ok(Dump { kind: Kind::OneSingle(number), locality: Locality::Internal, payload: raw_data.to_vec() }),
(Function::OnePatchDataDump, 0x00, number) if (64..=127).contains(&number) =>
Ok(Dump { kind: Kind::OneMulti(number), locality: Locality::Internal, payload: raw_data.to_vec() }),
(Function::OnePatchDataDump, 0x02, number) if (0..=63).contains(&number) =>
Ok(Dump { kind: Kind::OneSingle(number), locality: Locality::External, payload: raw_data.to_vec() }),
(Function::OnePatchDataDump, 0x02, number) if (64..=127).contains(&number) =>
Ok(Dump { kind: Kind::OneMulti(number), locality: Locality::External, payload: raw_data.to_vec() }),
(Function::OnePatchDataDump, 0x01, number) if (0..=31).contains(&number) =>
Ok(Dump { kind: Kind::OneEffect(number), locality: Locality::Internal, payload: raw_data.to_vec() }),
(Function::OnePatchDataDump, 0x03, number) if (0..=31).contains(&number) =>
Ok(Dump { kind: Kind::OneEffect(number), locality: Locality::External, payload: raw_data.to_vec() }),
(Function::OnePatchDataDump, 0x01, 32) =>
Ok(Dump { kind: Kind::Drum, locality: Locality::Internal, payload: raw_data.to_vec() }),
(Function::OnePatchDataDump, 0x03, 32) =>
Ok(Dump { kind: Kind::Drum, locality: Locality::External, payload: raw_data.to_vec() }),
(Function::BlockPatchDataDump, 0x00, 0x00) =>
Ok(Dump { kind: Kind::BlockSingle, locality: Locality::Internal, payload: raw_data.to_vec() }),
(Function::BlockPatchDataDump, 0x00, 0x40) =>
Ok(Dump { kind: Kind::BlockMulti, locality: Locality::Internal, payload: raw_data.to_vec() }),
(Function::BlockPatchDataDump, 0x02, 0x00) =>
Ok(Dump { kind: Kind::BlockSingle, locality: Locality::External, payload: raw_data.to_vec() }),
(Function::BlockPatchDataDump, 0x02, 0x40) =>
Ok(Dump { kind: Kind::BlockMulti, locality: Locality::External, payload: raw_data.to_vec() }),
(Function::BlockPatchDataDump, 0x01, 0x00) =>
Ok(Dump { kind: Kind::BlockEffect, locality: Locality::Internal, payload: raw_data.to_vec() }),
(Function::BlockPatchDataDump, 0x03, 0x00) =>
Ok(Dump { kind: Kind::BlockEffect, locality: Locality::External, payload: raw_data.to_vec() }),
(Function::AllPatchDataDump, 0x00, 0x00) =>
Ok(Dump { kind: Kind::All, locality: Locality::Internal, payload: raw_data.to_vec() }),
(Function::AllPatchDataDump, 0x02, 0x00) =>
Ok(Dump { kind: Kind::All, locality: Locality::External, payload: raw_data.to_vec() }),
_ => Err(ParseError::Unidentified),
}
}
}
#[cfg(test)]
mod tests {
use super::{*};
use syxpack::Message;
static DATA: &'static [u8] = include_bytes!("A401.SYX");
#[test]
fn test_dump_identify_all() {
match Message::from_bytes(&DATA.to_vec()) {
Ok(Message::ManufacturerSpecific { manufacturer: _, payload }) => {
match Dump::identify(payload) {
Ok(dump) => {
assert_eq!(dump.kind, Kind::All);
},
Err(e) => {
panic!("{:?}", e);
}
}
},
Ok(Message::Universal { kind: _, target: _, sub_id1: _, sub_id2: _, payload: _ }) => {
panic!("Universal message, not manufacturer-specific");
}
Err(e) => {
panic!("{:?}", e);
}
}
}
#[test]
fn test_dump_identify_single() {
let data: [u8; 137] = include!("intsingle.in");
match Dump::identify(data.to_vec()) {
Ok(dump) => {
assert_eq!(dump.kind, Kind::OneSingle(0));
},
Err(e) => {
panic!("{:?}", e);
}
}
}
}