use std::fmt;
use std::convert::TryFrom;
use bit::BitIndex;
use num_enum::TryFromPrimitive;
use crate::{
Ranged,
SystemExclusiveData,
Checksum,
ParseError,
MIDIChannel,
MIDINote,
};
use crate::k4::{
Level,
PatchNumber,
EffectNumber,
Transpose
};
pub const SECTION_COUNT: usize = 8;
#[derive(Clone)]
pub struct MultiPatch {
pub name: String,
pub volume: Level,
pub effect: EffectNumber,
pub sections: [Section; SECTION_COUNT],
}
impl MultiPatch {
fn collect_data(&self) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
buf.extend(self.name.as_bytes());
buf.push(self.volume.value().try_into().unwrap());
buf.push((self.effect.value() - 1).try_into().unwrap());
for s in self.sections {
buf.extend(s.to_bytes());
}
buf
}
}
impl Default for MultiPatch {
fn default() -> Self {
MultiPatch {
name: "NewMulti ".to_string(),
volume: Level::new(100),
effect: EffectNumber::new(1),
sections: [Default::default(); SECTION_COUNT],
}
}
}
impl fmt::Display for MultiPatch {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} volume={} effect={}",
self.name, self.volume.value(), self.effect.value())
}
}
impl SystemExclusiveData for MultiPatch {
fn from_bytes(data: &[u8]) -> Result<Self, ParseError> {
let mut offset: usize = 0;
let start: usize = 0;
let end = start + crate::k4::NAME_LENGTH;
let name = String::from_utf8(data[start..end].to_vec()).expect("Found invalid UTF-8");
let name = str::replace(&name, char::from(0), " ").to_string();
offset += crate::k4::NAME_LENGTH + 2;
let mut sections: [Section; SECTION_COUNT] = [Default::default(); SECTION_COUNT];
for i in 0..SECTION_COUNT {
sections[i] = Section::from_bytes(&data[offset .. offset + 8])?;
offset += 8;
}
Ok(MultiPatch {
name,
volume: Level::new(data[10].into()),
effect: EffectNumber::new(data[11].into()),
sections,
})
}
fn to_bytes(&self) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
let data = self.collect_data();
buf.extend(data);
buf.push(self.checksum());
buf
}
fn data_size() -> usize { 77 }
}
impl Checksum for MultiPatch {
fn checksum(&self) -> u8 {
let data = self.collect_data();
let mut total = data.iter().fold(0, |acc, x| acc + ((*x as u32) & 0xFF));
total += 0xA5;
(total & 0x7F) as u8
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct Section {
pub single_number: PatchNumber,
pub zone: Zone,
pub velocity_switch: VelocitySwitch,
pub receive_channel: MIDIChannel,
pub is_muted: bool,
pub out_select: u8,
pub play_mode: PlayMode,
pub level: Level,
pub transpose: Transpose,
pub tune: i8, }
impl Section {
pub fn new() -> Section {
Section {
single_number: PatchNumber::new(0),
zone: Zone {
low_key: Key { note: MIDINote::new(0) },
high_key: Key { note: MIDINote::new(127) }
},
velocity_switch: VelocitySwitch::All,
receive_channel: MIDIChannel::new(1), is_muted: false,
out_select: 0,
play_mode: PlayMode::Keyboard,
level: Level::new(100),
transpose: Transpose::new(0),
tune: 0,
}
}
}
impl Default for Section {
fn default() -> Self {
Section::new()
}
}
impl SystemExclusiveData for Section {
fn from_bytes(data: &[u8]) -> Result<Self, ParseError> {
let transpose = data[6] as i32 - 24;
Ok(Section {
single_number: PatchNumber::new(data[0].into()),
zone: Zone::from_bytes(&[data[1], data[2]])?,
velocity_switch: VelocitySwitch::try_from((data[3] >> 4) & 0b0000_0011).unwrap(),
receive_channel: MIDIChannel::new(((data[3] & 0b0000_1111) + 1).into()), is_muted: data[3] >> 6 == 1,
out_select: data[4] & 0b0000_0111,
play_mode: PlayMode::try_from((data[4] >> 3) & 0b0000_0011).unwrap(),
level: Level::new(data[5].into()),
transpose: Transpose::new(transpose),
tune: (data[7] as i8) - 50,
})
}
fn to_bytes(&self) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
buf.push(self.single_number.value().try_into().unwrap());
buf.push(self.zone.low_key.note.value() as u8);
buf.push(self.zone.high_key.note.value() as u8);
let mut m15 = (self.receive_channel.value() as u8) | ((self.velocity_switch as u8) << 4);
m15.set_bit(6, self.is_muted);
buf.push(m15);
let m16 = self.out_select | ((self.play_mode as u8) << 3);
buf.push(m16);
buf.push(self.level.value().try_into().unwrap());
buf.push((self.transpose.value() + 24).try_into().unwrap());
buf.push((self.tune + 50) as u8);
buf
}
fn data_size() -> usize { 8 }
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct Key {
pub note: MIDINote,
}
impl Key {
pub fn note_name(&self) -> String {
self.note.name()
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct Zone {
pub low_key: Key,
pub high_key: Key,
}
impl fmt::Display for Zone {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ... {}",
self.low_key.note.name(),
self.high_key.note.name())
}
}
impl SystemExclusiveData for Zone {
fn from_bytes(data: &[u8]) -> Result<Self, ParseError> {
Ok(
Zone {
low_key: Key { note: MIDINote::new(data[0].into()) },
high_key: Key { note: MIDINote::new(data[1].into()) }
}
)
}
fn to_bytes(&self) -> Vec<u8> {
vec![
self.low_key.note.value().try_into().unwrap(),
self.high_key.note.value().try_into().unwrap()
]
}
fn data_size() -> usize { 2 }
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, TryFromPrimitive)]
#[repr(u8)]
pub enum VelocitySwitch {
All,
Soft,
Loud,
}
impl fmt::Display for VelocitySwitch {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match self {
VelocitySwitch::All => "All",
VelocitySwitch::Soft => "Soft",
VelocitySwitch::Loud => "Loud",
})
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, TryFromPrimitive)]
#[repr(u8)]
pub enum PlayMode {
Keyboard,
Midi,
Mix,
}
impl fmt::Display for PlayMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match self {
PlayMode::Keyboard => "Keyboard",
PlayMode::Midi => "MIDI",
PlayMode::Mix => "Mix",
})
}
}
#[cfg(test)]
mod tests {
use super::{*};
use crate::Ranged;
use crate::k4::{
bank,
sysex::Header,
single::SinglePatch,
multi::MultiPatch,
};
use super::{*};
static DATA: &'static [u8] = include_bytes!("A401.SYX");
#[test]
fn test_multi_patch_from_bytes() {
let start: usize = dbg!(
2 +
Header::data_size() +
bank::SINGLE_PATCH_COUNT * SinglePatch::data_size());
let patch = MultiPatch::from_bytes(&DATA[start..]);
assert_eq!(patch.as_ref().unwrap().name, "Fatt!Anna5");
assert_eq!(patch.as_ref().unwrap().volume.value(), 0x50);
}
}