use crate::{prelude::*, sample::WavSample};
use std::path::Path;
pub type WavFileReader = hound::WavReader<std::io::BufReader<std::fs::File>>;
#[derive(Debug)]
pub enum Error {
ChannelMismatch {
expected_mono: bool,
},
Hound(hound::Error),
}
impl From<hound::Error> for Error {
fn from(value: hound::Error) -> Self {
Error::Hound(value)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
&Self::ChannelMismatch { expected_mono } => {
if expected_mono {
write!(f, "expected mono audio, got stereo audio")
} else {
write!(f, "expected stereo audio, got mono audio")
}
}
Self::Hound(err) => write!(f, "{err}"),
}
}
}
impl std::error::Error for Error {}
fn init_reader(path: &Path, expected_mono: bool) -> Result<WavFileReader, Error> {
let reader = hound::WavReader::open(path)?;
if (reader.spec().channels == 1) != expected_mono {
return Err(Error::ChannelMismatch { expected_mono });
}
Ok(reader)
}
impl buf::Dyn<smp::Mono> {
fn get_ptr(length: usize) -> *mut smp::Mono {
if length == 0 {
return std::ptr::null_mut();
}
let layout =
std::alloc::Layout::array::<smp::Mono>(length).expect("could not allocate buffer");
#[allow(clippy::cast_ptr_alignment)]
let ptr = unsafe { std::alloc::alloc(layout) }.cast::<smp::Mono>();
if ptr.is_null() {
std::alloc::handle_alloc_error(layout)
} else {
ptr
}
}
unsafe fn write_ptr_gen<S: WavSample>(
reader: WavFileReader,
ptr: *mut smp::Mono,
) -> hound::Result<()> {
let length = reader.len() as usize;
for (index, sample) in reader.into_samples::<S>().enumerate() {
debug_assert!(index < length);
*ptr.add(index) = sample?.into_mono();
}
Ok(())
}
unsafe fn write_ptr(reader: WavFileReader, ptr: *mut smp::Mono) -> hound::Result<()> {
match reader.spec().sample_format {
hound::SampleFormat::Float => Self::write_ptr_gen::<f32>(reader, ptr),
hound::SampleFormat::Int => match reader.spec().bits_per_sample {
8 => Self::write_ptr_gen::<i8>(reader, ptr),
16 => Self::write_ptr_gen::<i16>(reader, ptr),
24 | 32 => Self::write_ptr_gen::<i32>(reader, ptr),
_ => Err(hound::Error::Unsupported),
},
}
}
unsafe fn from_ptr(length: usize, ptr: *mut smp::Mono) -> Self {
if ptr.is_null() {
buf::Dyn::empty()
} else {
debug_assert_ne!(length, 0);
buf::Dyn::from_data(unsafe { Vec::from_raw_parts(ptr, length, length) })
}
}
pub fn from_wav_gen<P: AsRef<Path>, S: WavSample>(path: P) -> Result<Self, Error> {
let reader = init_reader(path.as_ref(), true)?;
let length = reader.len() as usize;
let ptr = Self::get_ptr(length);
unsafe {
Self::write_ptr_gen::<S>(reader, ptr)?;
Ok(Self::from_ptr(length, ptr))
}
}
pub fn from_wav<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let reader = init_reader(path.as_ref(), true)?;
let length = reader.len() as usize;
let ptr = Self::get_ptr(length);
unsafe {
Self::write_ptr(reader, ptr)?;
Ok(Self::from_ptr(length, ptr))
}
}
}
impl buf::Dyn<smp::Stereo> {
fn from_ptr(length: usize, ptr: *mut smp::Mono) -> Self {
if ptr.is_null() {
buf::Dyn::empty()
} else {
debug_assert_eq!(length % 2, 0);
debug_assert_ne!(length, 0);
buf::Dyn::from_data(unsafe {
Vec::from_raw_parts(ptr.cast::<smp::Stereo>(), length / 2, length / 2)
})
}
}
pub fn from_wav_gen<P: AsRef<Path>, S: WavSample>(path: P) -> Result<Self, Error> {
let reader = init_reader(path.as_ref(), false)?;
let length = reader.len() as usize;
let ptr = buf::Dyn::get_ptr(length);
unsafe {
buf::Dyn::write_ptr_gen::<S>(reader, ptr)?;
Ok(Self::from_ptr(length, ptr))
}
}
pub fn from_wav<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let reader = init_reader(path.as_ref(), false)?;
let length = reader.len() as usize;
let ptr = buf::Dyn::get_ptr(length);
unsafe {
buf::Dyn::write_ptr(reader, ptr)?;
Ok(Self::from_ptr(length, ptr))
}
}
}