use crate::{buffer::*, file::*};
use crc32fast::Hasher as Crc32Hasher;
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use std::{fs::File, hash::Hash, io, path::Path};
#[derive(Debug)]
pub enum SampleError {
FormatError,
FileError,
InvalidFormat,
InvalidChannelCount,
EmptySample,
}
impl From<hound::Error> for SampleError {
fn from(value: hound::Error) -> Self {
match value {
hound::Error::FormatError(_) => SampleError::FormatError,
hound::Error::IoError(_) => SampleError::FileError,
hound::Error::Unsupported => SampleError::InvalidFormat,
_ => todo!(),
}
}
}
impl From<std::io::Error> for SampleError {
fn from(value: std::io::Error) -> Self {
match value.kind() {
std::io::ErrorKind::NotFound => SampleError::FileError,
_ => todo!(),
}
}
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct SampleId(uuid::Uuid);
pub trait PoolHolding {
fn sample(&self, id: SampleId) -> Option<SharedAudioBuffer>;
fn sample_count(&self) -> usize;
fn live_memory(&self) -> usize;
}
#[derive(Default)]
pub struct SamplePool {
samples: HashMap<SampleId, SharedAudioBuffer, core::hash::BuildHasherDefault<Crc32Hasher>>,
files: HashMap<SampleId, std::path::PathBuf, core::hash::BuildHasherDefault<Crc32Hasher>>,
}
impl PoolHolding for SamplePool {
fn sample(&self, id: SampleId) -> Option<SharedAudioBuffer> {
self.samples.get(&id).cloned()
}
fn sample_count(&self) -> usize {
self.samples.len()
}
fn live_memory(&self) -> usize {
self.samples.values().map(|b| b.size()).sum()
}
}
impl SamplePool {
pub fn from_manifest(manifest: PoolManifest) -> Result<Self, SampleError> {
manifest
.entries
.iter()
.try_fold(Self::default(), |mut pool, entry| {
pool.add_sample(&entry.path)?;
Ok(pool)
})
}
pub fn build_manifest(&self) -> Result<PoolManifest, io::Error> {
let mut entries = Vec::<PoolManifestEntry>::with_capacity(self.files.len());
let mut buffer = Vec::with_capacity(4096);
for (id, path) in &self.files {
entries.push(PoolManifestEntry {
path: path.clone(),
size: self.samples[id].size(),
hash: hash_file_contents(path, &mut buffer)?,
name: path
.file_stem()
.and_then(|stem| stem.to_str())
.unwrap_or_default()
.to_string(),
});
}
Ok(PoolManifest::new(entries))
}
pub fn from_dir(path: impl AsRef<Path>) -> Result<Self, SampleError> {
let mut pool = Self::default();
pool.add_samples(path)?;
Ok(pool)
}
pub fn add_samples(&mut self, dir: impl AsRef<Path>) -> Result<Vec<SampleId>, SampleError> {
let mut ids = Vec::new();
walk_dir(dir.as_ref(), &mut |path| {
if path.extension().map_or(false, |ext| ext == "wav") {
if let Ok(id) = self.add_sample(path) {
ids.push(id);
}
}
})?;
Ok(ids)
}
pub fn add_sample(&mut self, file: impl AsRef<Path>) -> Result<SampleId, SampleError> {
let reader = hound::WavReader::open(file.as_ref())?;
let spec = reader.spec();
let samples = match (spec.sample_format, spec.bits_per_sample) {
(hound::SampleFormat::Float, 32) => load_f32_wav(reader),
(hound::SampleFormat::Int, 16) => load_i16_wav(reader),
(hound::SampleFormat::Int, 24) => load_i24_wav(reader),
_ => return Err(SampleError::InvalidFormat),
};
if spec.sample_rate != 48000 {
log::warn!("expected sample rate 48kHz, got {}Hz", spec.sample_rate)
}
let buffer = match spec.channels {
1 => SharedAudioBuffer::from_mono(samples),
2 => SharedAudioBuffer::from_stereo_interleaved(samples),
_ => return Err(SampleError::InvalidChannelCount),
};
self.insert_sample(buffer, file)
}
pub fn remove_sample(&mut self, id: SampleId) {
match self.samples.get(&id) {
Some(sample) => {
if !sample.is_unique() {
todo!("synchronous sample dropping");
}
}
None => return,
}
self.samples.remove(&id);
self.files.remove(&id);
}
pub fn samples(&self) -> impl Iterator<Item = (SampleId, SharedAudioBuffer)> + '_ {
self.samples
.iter()
.map(|(id, buffer)| (*id, buffer.clone()))
}
fn insert_sample(
&mut self,
buffer: SharedAudioBuffer,
path: impl AsRef<Path>,
) -> Result<SampleId, SampleError> {
if buffer.is_empty() {
return Err(SampleError::EmptySample);
}
let id = SampleId(uuid::Uuid::new_v4());
self.samples.insert(id, buffer);
self.files.insert(id, path.as_ref().to_owned());
Ok(id)
}
}
#[inline]
fn load_f32_wav(reader: hound::WavReader<io::BufReader<File>>) -> SharedBuffer {
let num_samples = reader.len() as usize;
reader
.into_samples::<f32>()
.filter_map(Result::ok)
.fold(Vec::with_capacity(num_samples), |mut output, sample| {
output.push(sample);
output
})
.into()
}
#[inline]
fn load_i16_wav(reader: hound::WavReader<io::BufReader<File>>) -> SharedBuffer {
const I16_TO_FLOAT: f32 = 1.0 / i16::MAX as f32;
let num_samples = reader.len() as usize;
reader
.into_samples::<i16>()
.filter_map(Result::ok)
.fold(Vec::with_capacity(num_samples), |mut output, sample| {
output.push(sample as f32 * I16_TO_FLOAT);
output
})
.into()
}
#[inline]
fn load_i24_wav(reader: hound::WavReader<io::BufReader<File>>) -> SharedBuffer {
const I24_MAX: i32 = (1 << 23) - 1;
const I24_TO_FLOAT: f32 = 1.0 / I24_MAX as f32;
let num_samples = reader.len() as usize;
reader
.into_samples::<i32>()
.filter_map(Result::ok)
.fold(Vec::with_capacity(num_samples), |mut output, sample| {
output.push(sample as f32 * I24_TO_FLOAT);
output
})
.into()
}
#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Debug)]
pub struct PoolManifestEntry {
pub path: std::path::PathBuf,
pub size: usize,
pub name: String,
pub hash: u32,
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub struct PoolManifest {
pub hash: u32,
pub entries: Vec<PoolManifestEntry>,
}
impl PoolManifest {
pub fn new(entries: Vec<PoolManifestEntry>) -> Self {
let mut hasher = Crc32Hasher::new();
entries.iter().for_each(|e| e.hash(&mut hasher));
let hash = hasher.finalize();
Self { hash, entries }
}
pub fn from_file(path: impl AsRef<Path>) -> Result<Self, io::Error> {
let file = File::open(path)?;
let manifest: Self = serde_json::from_reader(file)?;
Ok(manifest)
}
pub fn save(&self, path: impl AsRef<Path>) -> Result<(), io::Error> {
let file = File::create(path)?;
serde_json::to_writer_pretty(file, self)?;
Ok(())
}
}