pub(crate) mod tar_archive;
pub mod batch_tar_file;
pub mod tar_file;
pub mod tar_gz_file;
pub use tar_archive::*;
pub use batch_tar_file::BatchTarFile;
pub use tar_file::TarFile;
pub use tar_gz_file::TarGzFile;
pub trait StorageBackend : Sized {
type Error: std::fmt::Debug;
fn append_file(&mut self, mfile: libatm::MIDIFile, mode: Option<u32>) -> Result<(), Self::Error>;
fn append_melody(&mut self, melody: libatm::MIDINoteVec, mode: Option<u32>) -> Result<(), Self::Error> {
let mfile = libatm::MIDIFile::new(melody, libatm::MIDIFormat::Format0, 1, 1);
self.append_file(mfile, mode)
}
fn finish(&mut self) -> Result<(), Self::Error>;
}
pub trait IntoInner : StorageBackend {
type Inner;
fn into_inner(self) -> Result<Self::Inner, <Self as StorageBackend>::Error>;
}
#[derive(Debug, thiserror::Error)]
pub enum PathGeneratorError {
#[error(transparent)]
PartitionPathGenerator(#[from] PartitionPathGeneratorError),
}
pub trait PathGenerator {
fn gen_path_for_file(&self, mfile: &libatm::MIDIFile) -> Result<String, PathGeneratorError>;
}
pub struct MIDIHashPathGenerator;
impl PathGenerator for MIDIHashPathGenerator {
fn gen_path_for_file(&self, mfile: &libatm::MIDIFile) -> Result<String, PathGeneratorError> {
Ok(format!("{}.mid", mfile.gen_hash()))
}
}
#[derive(Debug, thiserror::Error)]
pub enum PartitionPathGeneratorError {
#[error("Expected melody of length {expected}, found length {observed}")]
MelodyLengthMismatch { expected: u32, observed: u32, },
#[error("Partition depth must be less than the length of generated melodies \
({partition_depth} > {melody_length})")]
PartitionDepthLongerThanMelody { partition_depth: u32, melody_length: u32, },
#[error("Melodies of length {melody_length} cannot be partitioned with depth \
{partition_depth} and length {partition_length}")]
PartitionsLongerThanMelody { melody_length: u32, partition_depth: u32, partition_length: u32, },
}
pub struct PartitionPathGenerator {
melody_length: u32,
partition_depth: u32,
partition_length: u32,
}
impl PartitionPathGenerator {
fn gen_partition_length(
num_notes: u32,
num_melodies: u64,
melody_length: u32,
max_files: u32,
partition_depth: u32
) -> Result<u32, PartitionPathGeneratorError> {
let max_partitions = (num_melodies as f64) / max_files as f64;
let partition_length = max_partitions.log(num_notes.pow(partition_depth).into()).ceil() as u32;
if (melody_length as u32) < partition_depth * partition_length {
return Err(PartitionPathGeneratorError::PartitionsLongerThanMelody {
melody_length,
partition_depth,
partition_length,
});
}
Ok(partition_length)
}
pub fn new(
num_notes: u32,
melody_length: u32,
max_files: u32,
partition_depth: u32
) -> Result<Self, PartitionPathGeneratorError> {
if partition_depth > melody_length as u32 {
return Err(PartitionPathGeneratorError::PartitionDepthLongerThanMelody {
partition_depth,
melody_length,
});
}
let num_melodies = crate::utils::gen_num_melodies(num_notes, melody_length);
let mut calc_partition_depth = 1;
let mut calc_partition_length = 0;
if !(num_notes == 1 || num_melodies <= max_files.into()) {
calc_partition_depth = partition_depth;
calc_partition_length = Self::gen_partition_length(
num_notes,
num_melodies,
melody_length,
max_files,
partition_depth,
)?;
}
Ok(Self {
melody_length: melody_length as u32,
partition_depth: calc_partition_depth,
partition_length: calc_partition_length,
})
}
fn gen_basename_for_file(&self, mfile: &libatm::MIDIFile) -> Result<String, PathGeneratorError> {
let melody_length = mfile.sequence.len() as u32;
if melody_length != self.melody_length {
return Err(PathGeneratorError::PartitionPathGenerator(
PartitionPathGeneratorError::MelodyLengthMismatch {
expected: self.melody_length,
observed: melody_length
}
));
}
match self.partition_depth {
0 => Ok(String::new()),
_ => Ok((0..self.partition_depth)
.map(|p| {
&mfile.sequence[
( (self.partition_length * p) as usize )..( (self.partition_length * (p + 1)) as usize )
]
})
.map(|p| p.iter().map(|n| n.convert().to_string()).collect::<Vec<String>>().join(""))
.collect::<Vec<String>>()
.join(&std::path::MAIN_SEPARATOR.to_string()))
}
}
}
impl PathGenerator for PartitionPathGenerator {
fn gen_path_for_file(&self, mfile: &libatm::MIDIFile) -> Result<String, PathGeneratorError> {
let basename = self.gen_basename_for_file(mfile)?;
let filename = format!("{}.mid", mfile.gen_hash());
Ok(format!(
"{}",
std::path::Path::new(&basename)
.join(&filename)
.as_path()
.to_string_lossy(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn test_partition_depth_melody_length() {
let _ = PartitionPathGenerator::new(3, 3, 4096, 4).unwrap();
}
#[test]
#[should_panic]
fn test_melody_length_match() {
let path_generator = PartitionPathGenerator::new(4, 12, 4096, 2).unwrap();
let mfile = libatm::MIDIFile::new(
"C:4,D:5,G:7".parse::<libatm::MIDINoteVec>().unwrap(),
libatm::MIDIFormat::Format0,
1,
1,
);
path_generator.gen_path_for_file(&mfile).unwrap();
}
macro_rules! check_num_files_partition {
($test_name:ident, $note_set:expr, $melody_length:expr, $max_files:expr, $partition_depth:expr) => {
#[test]
fn $test_name() {
let notes = $note_set.parse::<libatm::MIDINoteSet>().unwrap();
let num_notes = notes.len() as f32;
let mut partition = String::new();
let mut num_files_in_partition = 0;
let path_generator = PartitionPathGenerator::new(
num_notes,
$melody_length,
$max_files,
$partition_depth,
).unwrap();
for melody in crate::utils::gen_sequences(
&Vec::from(¬es),
$melody_length,
) {
let melody_partition = path_generator.gen_basename_for_file(&libatm::MIDIFile::new(
melody.iter().map(|n| *n.clone()).collect::<Vec<libatm::MIDINote>>(),
libatm::MIDIFormat::Format0,
1,
1,
)).unwrap();
if melody_partition != partition {
assert!(
num_files_in_partition as f32 <= $max_files,
"{} files in partition, maximum specified was {}",
num_files_in_partition,
$max_files,
);
num_files_in_partition = 0;
partition = melody_partition;
}
}
}
}
}
}