use std::str;
const CHECKSUM_BYTE: usize = 0x08;
fn calculate_checksum(kit_file_vec: &Vec<u8>) -> usize {
return (kit_file_vec[CHECKSUM_BYTE] as usize + kit_file_vec[CHECKSUM_BYTE + 1] as usize) % 256;
}
fn verify_checksum(kit_file_vec: &Vec<u8>) -> Result<(), String> {
let checksum = kit_file_vec[CHECKSUM_BYTE as usize] as usize;
let calculated_checksum = calculate_checksum(kit_file_vec);
if checksum == calculated_checksum {
Ok(())
} else {
Err(format!(
"Kit file says checksum should be {checksum} but calculated {calculated_checksum}"
))
}
}
#[derive(Debug, PartialEq)]
pub struct Pad {
pub name: String,
pub midi_note: u8,
pub location: u8,
pub level: u8,
pub tune: u8,
pub pan: u8,
pub reverb: u8,
pub mode: u8,
pub sensitivity: u8,
pub mgrp: u8,
pub velocity_min: u8,
pub velocity_max: u8,
pub velocity_min_b: u8,
pub velocity_max_b: u8,
pub file_layer_a: String,
pub file_layer_b: String,
}
fn pad_file_order() -> [&'static str; 17] {
return [
"pad_01", "pad_02", "pad_03", "pad_04", "pad_05", "pad_06", "pad_07", "pad_08", "ext_1a",
"ext_1b", "ext_2", "kick", "hh_ope", "hh_mid", "hh_clo", "hh_chk", "hh_spl",
];
}
const HEADER_LENGTH: usize = 128;
const BLOCK_LENGTH: usize = 256;
fn get_pads(kit_file_vec: &Vec<u8>) -> Result<Vec<Pad>, String> {
let mut pads = vec![];
let pad_file_order = pad_file_order();
for (i, pad_name) in pad_file_order.iter().enumerate() {
let offset1 = HEADER_LENGTH + i * BLOCK_LENGTH;
let block1 = &kit_file_vec[offset1..offset1 + BLOCK_LENGTH];
let offset2 = HEADER_LENGTH + (pad_file_order.len() * BLOCK_LENGTH) + i * BLOCK_LENGTH;
let block2 = &kit_file_vec[offset2..offset2 + BLOCK_LENGTH];
pads.push(Pad {
name: pad_name.to_string(),
midi_note: block1[LOC_MIDI_NOTE],
location: block1[LOC_LOCATION],
level: block1[LOC_LEVEL],
tune: block1[LOC_TUNE],
pan: block1[LOC_PAN],
reverb: block1[LOC_REVERB],
mode: block1[LOC_MODE],
sensitivity: block1[LOC_SENSITIVITY],
mgrp: block1[LOC_MGRP],
velocity_min: block2[LOC_VELOCITY_MIN],
velocity_max: block2[LOC_VELOCITY_MAX],
velocity_min_b: block2[LOC_VELOCITY_MIN_B],
velocity_max_b: block2[LOC_VELOCITY_MAX_B],
file_layer_a: if block2[LOC_HAS_FILE_LAYER_A] != 0xaa {
"".to_string()
} else {
str::from_utf8(
&block2[LOC_FILE_NAME as usize
..(LOC_FILE_NAME + &(block2[LOC_FILE_NAME_LENGTH] as usize)) as usize],
)
.expect("Invalid UTF-8 sequence")
.to_string()
},
file_layer_b: if block2[LOC_HAS_FILE_LAYER_B] != 0xaa {
"".to_string()
} else {
str::from_utf8(
&block2[LOC_FILE_NAME_B as usize
..(LOC_FILE_NAME_B + &(block2[LOC_FILE_NAME_LENGTH_B] as usize)) as usize],
)
.expect("Invalid UTF-8 sequence")
.to_string()
},
});
}
Ok(pads)
}
#[derive(Debug, PartialEq)]
pub struct Kit {
pub name: String,
pub pads: Vec<Pad>,
}
fn get_kit_name(kit_vec: &Vec<u8>) -> String {
str::from_utf8(&kit_vec[0x48 as usize..(0x48 + &kit_vec[0x47]) as usize])
.expect("Invalid UTF-8 sequence")
.to_string()
}
const LOC_MIDI_NOTE: usize = 0x39;
const LOC_LOCATION: usize = 0x07;
const LOC_LEVEL: usize = 0x29;
const LOC_TUNE: usize = 0x2d;
const LOC_PAN: usize = 0x31;
const LOC_REVERB: usize = 0x35;
const LOC_MODE: usize = 0x3d;
const LOC_SENSITIVITY: usize = 0x41;
const LOC_MGRP: usize = 0x49;
const LOC_VELOCITY_MIN: usize = 0x82;
const LOC_VELOCITY_MAX: usize = 0x83;
const LOC_VELOCITY_MIN_B: usize = 0xa2;
const LOC_VELOCITY_MAX_B: usize = 0xa4;
const LOC_FILE_NAME_LENGTH: usize = 0x87;
const LOC_DISPLAY_NAME: usize = 0x88;
const LOC_FILE_NAME: usize = 0x90;
const LOC_HAS_FILE_LAYER_A: usize = 0x80;
const LOC_FILE_NAME_LENGTH_B: usize = 0xa7;
const LOC_DISPLAY_NAME_B: usize = 0xa8;
const LOC_FILE_NAME_B: usize = 0xb0;
const LOC_HAS_FILE_LAYER_B: usize = 0xa0;
const LOC_KIT_NAME_LENGTH: usize = 0x47;
const LOC_KIT_NAME: usize = 0x48;
pub fn serialize_kit(kit: Kit) -> Vec<u8> {
let mut header = vec![
0x4b, 0x49, 0x54, 0x48, 0x00, 0x80, 0x00, 0x00, 0xeb, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
header[LOC_KIT_NAME_LENGTH] = kit.name.len() as u8;
let kit_name = kit.name.as_bytes().to_owned();
header.splice(LOC_KIT_NAME..LOC_KIT_NAME + kit_name.len(), kit_name);
let mut blocks_one = vec![];
let mut blocks_two = vec![];
for pad in kit.pads.iter() {
let mut block1 = vec![
0x4b, 0x49, 0x54, 0x49, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x07, 0x32, 0x32, 0x41, 0x63, 0x42, 0x64, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x09, 0x00, 0x00, 0x00, 0x05,
0x00, 0x0a, 0x01, 0x00, 0x04, 0x08, 0x02, 0x00, 0x04, 0x08, 0x03, 0x00, 0x00, 0x0a,
0x08, 0x00, 0x00, 0x7f, 0x09, 0x01, 0x00, 0x05, 0x0c, 0x0c, 0x00, 0x08, 0x0d, 0x00,
0x00, 0x09, 0x0e, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x7f, 0x09, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
block1[LOC_MIDI_NOTE] = pad.midi_note;
block1[LOC_LOCATION] = pad.location;
block1[LOC_LEVEL] = pad.level;
block1[LOC_TUNE] = pad.tune;
block1[LOC_PAN] = pad.pan;
block1[LOC_REVERB] = pad.reverb;
block1[LOC_MODE] = pad.mode;
block1[LOC_SENSITIVITY] = pad.sensitivity;
block1[LOC_MGRP] = pad.mgrp;
blocks_one.append(&mut block1);
let mut block2 = vec![
0x4b, 0x49, 0x54, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x09, 0x00, 0x00, 0x00, 0x05,
0x00, 0x0a, 0x01, 0x00, 0x04, 0x08, 0x02, 0x00, 0x04, 0x08, 0x03, 0x00, 0x00, 0x0a,
0x08, 0x00, 0x00, 0x7f, 0x09, 0x01, 0x00, 0x05, 0x0c, 0x0c, 0x00, 0x08, 0x0d, 0x00,
0x00, 0x09, 0x0e, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xaa, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xaa, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
block2[LOC_MIDI_NOTE] = pad.midi_note;
block2[LOC_LOCATION] = pad.location;
block2[LOC_LEVEL] = pad.level;
block2[LOC_TUNE] = pad.tune;
block2[LOC_PAN] = pad.pan;
block2[LOC_REVERB] = pad.reverb;
block2[LOC_MODE] = pad.mode;
block2[LOC_SENSITIVITY] = pad.sensitivity;
block2[LOC_MGRP] = pad.mgrp;
block2[LOC_VELOCITY_MIN] = pad.velocity_min;
block2[LOC_VELOCITY_MAX] = pad.velocity_max;
block2[LOC_VELOCITY_MIN_B] = pad.velocity_min_b;
block2[LOC_VELOCITY_MAX_B] = pad.velocity_max_b;
block2[LOC_FILE_NAME_LENGTH] = pad.file_layer_a.len() as u8;
let mut display_name = pad.file_layer_a.to_uppercase().as_bytes().to_owned();
if display_name.len() < 9 {
display_name.resize(8, 0x20);
}
block2.splice(
LOC_DISPLAY_NAME..LOC_DISPLAY_NAME + display_name.len(),
display_name,
);
let mut file_name = pad.file_layer_a.as_bytes().to_owned();
if file_name.len() < 9 {
file_name.resize(8, 0x00);
}
block2.splice(LOC_FILE_NAME..LOC_FILE_NAME + file_name.len(), file_name);
block2[LOC_HAS_FILE_LAYER_A] = if pad.file_layer_a == "" { 0xff } else { 0xaa };
block2[LOC_FILE_NAME_LENGTH_B] = pad.file_layer_b.len() as u8;
let mut display_name = pad.file_layer_b.to_uppercase().as_bytes().to_owned();
if display_name.len() < 9 {
display_name.resize(8, 0x20);
}
block2.splice(
LOC_DISPLAY_NAME_B..LOC_DISPLAY_NAME_B + display_name.len(),
display_name,
);
let mut file_name = pad.file_layer_b.as_bytes().to_owned();
if file_name.len() < 9 {
file_name.resize(8, 0x00);
}
block2.splice(
LOC_FILE_NAME_B..LOC_FILE_NAME_B + file_name.len(),
file_name,
);
block2[LOC_HAS_FILE_LAYER_B] = if pad.file_layer_b == "" { 0xff } else { 0xaa };
blocks_two.append(&mut block2);
}
[header, blocks_one, blocks_two].concat()
}
pub fn deserialize_kit(kit_vec: &Vec<u8>) -> Result<Kit, String> {
verify_checksum(kit_vec)?;
Ok(Kit {
name: get_kit_name(&kit_vec),
pads: get_pads(&kit_vec)?,
})
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::fs;
fn get_test_kit() -> Vec<u8> {
return fs::read("./test_data/SMPLPDBS1/KITS/USER_001.KIT")
.expect("Couldn't read test kit file");
}
#[test]
fn test_checksum() {
assert_eq!(verify_checksum(&get_test_kit()), Ok(()));
}
#[test]
fn test_kit_name() {
assert_eq!("Acoustic", get_kit_name(&get_test_kit()));
}
#[test]
fn test_serde_round_trip() {
let kv = get_test_kit();
assert_eq!(
deserialize_kit(&kv),
deserialize_kit(&serialize_kit(
deserialize_kit(&kv).expect("Failed to deserialize kit")
))
);
}
#[test]
fn test_get_pads() {
assert_eq!(
get_pads(&get_test_kit()),
Ok(vec![
Pad {
name: "pad_01".to_string(),
midi_note: 49,
location: 1,
level: 8,
tune: 0,
pan: 0,
reverb: 3,
mode: 0,
sensitivity: 16,
mgrp: 0,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "G_Sub_Hit".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "pad_02".to_string(),
midi_note: 51,
location: 1,
level: 4,
tune: 0,
pan: 0,
reverb: 0,
mode: 1,
sensitivity: 16,
mgrp: 0,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "Y_Noise_Hit".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "pad_03".to_string(),
midi_note: 48,
location: 1,
level: 10,
tune: 0,
pan: 0,
reverb: 3,
mode: 0,
sensitivity: 16,
mgrp: 0,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "O_Kick_2".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "pad_04".to_string(),
midi_note: 45,
location: 1,
level: 8,
tune: 0,
pan: 0,
reverb: 0,
mode: 0,
sensitivity: 16,
mgrp: 0,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "R_Tom_Mute_H".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "pad_05".to_string(),
midi_note: 46,
location: 1,
level: 8,
tune: 0,
pan: 0,
reverb: 3,
mode: 1,
sensitivity: 16,
mgrp: 1,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "R_Tom_Mute_L".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "pad_06".to_string(),
midi_note: 36,
location: 1,
level: 10,
tune: 0,
pan: 0,
reverb: 2,
mode: 0,
sensitivity: 16,
mgrp: 0,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "R_Kick_2".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "pad_07".to_string(),
midi_note: 38,
location: 1,
level: 8,
tune: 0,
pan: 0,
reverb: 0,
mode: 1,
sensitivity: 16,
mgrp: 0,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "SNR_OFF_5".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "pad_08".to_string(),
midi_note: 42,
location: 1,
level: 8,
tune: 0,
pan: 0,
reverb: 0,
mode: 1,
sensitivity: 16,
mgrp: 0,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "R_Clap_Wet".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "ext_1a".to_string(),
midi_note: 38,
location: 0,
level: 8,
tune: 0,
pan: 0,
reverb: 3,
mode: 0,
sensitivity: 16,
mgrp: 0,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "ext_1b".to_string(),
midi_note: 40,
location: 0,
level: 8,
tune: 0,
pan: 0,
reverb: 3,
mode: 0,
sensitivity: 16,
mgrp: 0,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "ext_2".to_string(),
midi_note: 43,
location: 0,
level: 8,
tune: 0,
pan: 1,
reverb: 3,
mode: 0,
sensitivity: 16,
mgrp: 0,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "kick".to_string(),
midi_note: 36,
location: 0,
level: 8,
tune: 2,
pan: 0,
reverb: 2,
mode: 0,
sensitivity: 16,
mgrp: 0,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "hh_ope".to_string(),
midi_note: 46,
location: 0,
level: 8,
tune: 0,
pan: 253,
reverb: 3,
mode: 1,
sensitivity: 16,
mgrp: 1,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "hh_mid".to_string(),
midi_note: 23,
location: 0,
level: 8,
tune: 0,
pan: 253,
reverb: 3,
mode: 1,
sensitivity: 16,
mgrp: 1,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "hh_clo".to_string(),
midi_note: 42,
location: 0,
level: 8,
tune: 0,
pan: 253,
reverb: 3,
mode: 1,
sensitivity: 16,
mgrp: 1,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "hh_chk".to_string(),
midi_note: 44,
location: 0,
level: 8,
tune: 0,
pan: 253,
reverb: 3,
mode: 0,
sensitivity: 16,
mgrp: 1,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "".to_string(),
file_layer_b: "".to_string()
},
Pad {
name: "hh_spl".to_string(),
midi_note: 21,
location: 0,
level: 8,
tune: 0,
pan: 253,
reverb: 3,
mode: 0,
sensitivity: 16,
mgrp: 1,
velocity_min: 0,
velocity_max: 127,
velocity_min_b: 0,
velocity_max_b: 0,
file_layer_a: "".to_string(),
file_layer_b: "".to_string()
}
])
);
}
}