use std::collections::HashMap;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::mem::size_of;
use std::path::Path;
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub mod read;
pub mod write;
const FILE_ID: &[u8] = &[137_u8, b'k', b'H', b's'];
const FORMAT_VERSION: &[u8] = "Bank0001".as_bytes();
pub const BACKGROUND_FILE_STEM: &str = "background";
const CORRUPTION_CHECK_BYTES: &[u8] = &[0x0d, 0x0a, 0x1a, 0x0a];
pub const PATH_SEPARATOR: char = '/';
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum ItemKind {
Background,
Metadata,
Sample,
MultipassPreset,
PhasePlantPreset,
SnapHeapPreset,
ThreeBandEq,
Bitcrush,
CarveEq,
Chorus,
CombFilter,
Compressor,
Convolver,
Delay,
Disperser,
Distortion,
Dynamics,
Ensemble,
Faturator,
Filter,
Flanger,
FormatFilter,
FrequencyShifter,
Gain,
Gate,
Haas,
LadderFilter,
Limiter,
NonlinearFilter,
PhaseDistortion,
Phaser,
PitchShifter,
Resonator,
Reverb,
Reverser,
RingMod,
SliceEq,
Stereo,
TapeStop,
TranceGate,
TransientShaper,
}
impl ItemKind {
#[must_use]
pub fn directory(&self) -> Option<&'static str> {
match self {
ItemKind::Background | ItemKind::Metadata => None,
ItemKind::Sample => Some("samples"),
kind => kind.extensions().first().copied(),
}
}
#[must_use]
pub fn extensions(&self) -> Vec<&'static str> {
match self {
Self::Background => vec!["jpg", "png"],
Self::Metadata => vec!["json"],
Self::Sample => vec!["flac", "mp3", "wav"],
Self::MultipassPreset => vec!["multipass"],
Self::PhasePlantPreset => vec!["phaseplant"],
Self::SnapHeapPreset => vec!["snapheap"],
Self::ThreeBandEq => vec!["ksqe"],
Self::Bitcrush => vec!["ksbc"],
Self::CarveEq => vec!["ksge"],
Self::Chorus => vec!["ksch"],
Self::CombFilter => vec!["kscf"],
Self::Compressor => vec!["kscp"],
Self::Convolver => vec!["ksco"],
Self::Delay => vec!["ksdl"],
Self::Disperser => vec!["kdsp"],
Self::Distortion => vec!["ksdt"],
Self::Dynamics => vec!["ksot"],
Self::Ensemble => vec!["ksun"],
Self::Faturator => vec!["kfat"],
Self::Filter => vec!["ksfi"],
Self::Flanger => vec!["ksfl"],
Self::FormatFilter => vec!["ksvf"],
Self::FrequencyShifter => vec!["ksfs"],
Self::Gain => vec!["ksgn"],
Self::Gate => vec!["ksgt"],
Self::Haas => vec!["ksha"],
Self::LadderFilter => vec!["ksla"],
Self::Limiter => vec!["kslt"],
Self::NonlinearFilter => vec!["ksdf"],
Self::PhaseDistortion => vec!["kspd"],
Self::Phaser => vec!["ksph"],
Self::PitchShifter => vec!["ksps"],
Self::Resonator => vec!["ksre"],
Self::Reverb => vec!["ksrv"],
Self::Reverser => vec!["ksrr"],
Self::RingMod => vec!["ksrm"],
Self::SliceEq => vec!["kpeq"],
Self::Stereo => vec!["ksst"],
Self::TapeStop => vec!["ksts"],
Self::TranceGate => vec!["kstg"],
Self::TransientShaper => vec!["kstr"],
}
}
#[must_use]
pub fn has_extension(&self, extension: &OsStr) -> bool {
let extension = extension.to_string_lossy();
self.extensions()
.iter()
.any(|ext| ext.eq_ignore_ascii_case(&extension))
}
#[must_use]
pub const fn all() -> [ItemKind; 41] {
[
Self::Background,
Self::Metadata,
Self::MultipassPreset,
Self::PhasePlantPreset,
Self::SnapHeapPreset,
Self::Sample,
Self::ThreeBandEq,
Self::Bitcrush,
Self::CarveEq,
Self::Chorus,
Self::CombFilter,
Self::Compressor,
Self::Convolver,
Self::Delay,
Self::Disperser,
Self::Distortion,
Self::Dynamics,
Self::Ensemble,
Self::Faturator,
Self::Filter,
Self::Flanger,
Self::FormatFilter,
Self::FrequencyShifter,
Self::Gain,
Self::Gate,
Self::Haas,
Self::LadderFilter,
Self::Limiter,
Self::NonlinearFilter,
Self::PhaseDistortion,
Self::Phaser,
Self::PitchShifter,
Self::Resonator,
Self::Reverb,
Self::Reverser,
Self::RingMod,
Self::SliceEq,
Self::Stereo,
Self::TapeStop,
Self::TranceGate,
Self::TransientShaper,
]
}
#[must_use]
pub fn from<P: AsRef<Path>>(path: P) -> Option<ItemKind> {
let file_name = path.as_ref().file_name();
if file_name
.unwrap_or_default()
.eq_ignore_ascii_case(Metadata::FILE_NAME)
{
return Some(ItemKind::Metadata);
} else if file_name
.unwrap_or_default()
.eq_ignore_ascii_case(BACKGROUND_FILE_STEM)
{
return Some(ItemKind::Background);
}
path.as_ref().extension().and_then(|extension| {
ItemKind::all().into_iter().find(|kind| {
kind.extensions()
.iter()
.any(|ext| ext.eq_ignore_ascii_case(&extension.to_string_lossy()))
})
})
}
}
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct Metadata {
pub version: Option<u32>,
pub id: String,
pub name: String,
pub author: String,
pub description: String,
pub hash: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl Metadata {
pub const FILE_NAME: &'static str = "index.json";
#[must_use]
pub fn sanitize_id(str: &str) -> String {
str.chars()
.filter_map(|c| {
if c.is_alphanumeric() || c == '.' {
Some(c.to_ascii_lowercase())
} else {
None
}
})
.collect::<String>()
}
}
#[derive(Clone, Debug)]
struct Location {
file_name_offset: u64,
data_offset: u64,
data_size: u64,
}
impl Location {
const BLOCK_SIZE: usize = size_of::<u64>() * 3;
pub fn data_end(&self) -> u64 {
self.data_offset + self.data_size
}
}