mod conversion;
mod core;
mod error;
mod header;
use std::fs;
use std::io::Write;
use std::path::Path;
pub use crate::conversion::{AudioSample, ConvertSlice, ConvertTo};
pub use crate::core::{wav_spec, ReadSeek, Samples, Wav, WavType};
pub use crate::error::{WaversError, WaversResult};
use crate::header::DATA;
pub use crate::header::{FmtChunk, WavHeader};
#[inline(always)]
pub fn read<T: AudioSample, P: AsRef<Path>>(path: P) -> WaversResult<(Samples<T>, i32)>
where
i16: ConvertTo<T>,
i24: ConvertTo<T>,
i32: ConvertTo<T>,
f32: ConvertTo<T>,
f64: ConvertTo<T>,
Box<[i16]>: ConvertSlice<T>,
Box<[i24]>: ConvertSlice<T>,
Box<[i32]>: ConvertSlice<T>,
Box<[f32]>: ConvertSlice<T>,
Box<[f64]>: ConvertSlice<T>,
{
let mut wav: Wav<T> = Wav::from_path(path)?;
let samples = wav.read()?;
Ok((samples, wav.sample_rate()))
}
#[inline(always)]
pub fn write<T: AudioSample, P: AsRef<Path>>(
fp: P,
samples: &[T],
sample_rate: i32,
n_channels: u16,
) -> WaversResult<()>
where
i16: ConvertTo<T>,
i24: ConvertTo<T>,
i32: ConvertTo<T>,
f32: ConvertTo<T>,
f64: ConvertTo<T>,
Box<[i16]>: ConvertSlice<T>,
Box<[i24]>: ConvertSlice<T>,
Box<[i32]>: ConvertSlice<T>,
Box<[f32]>: ConvertSlice<T>,
Box<[f64]>: ConvertSlice<T>,
{
let s = Samples::from(samples);
let samples_bytes = s.as_bytes();
let new_header = WavHeader::new_header::<T>(sample_rate, n_channels, s.len())?;
let header_bytes = new_header.as_bytes();
let mut f = fs::File::create(fp)?;
f.write_all(&header_bytes)?;
f.write_all(&DATA)?;
let data_size_bytes = samples_bytes.len() as u32; f.write_all(&data_size_bytes.to_ne_bytes())?; f.write_all(&samples_bytes)?; Ok(())
}
use std::{
ops::{Neg, Not},
str::FromStr,
};
use bytemuck::{Pod, Zeroable};
use num_traits::{Num, One, Zero};
#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct i24 {
pub data: [u8; 3],
}
unsafe impl Zeroable for i24 {}
unsafe impl Pod for i24 {}
impl One for i24 {
fn one() -> Self {
i24::from_i32(1)
}
}
impl Zero for i24 {
fn zero() -> Self {
i24::from_i32(0)
}
fn is_zero(&self) -> bool {
i24::from_i32(0) == *self
}
}
impl Num for i24 {
type FromStrRadixErr = std::num::ParseIntError;
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
let i32_result = i32::from_str_radix(str, radix)?;
Ok(i24::from_i32(i32_result))
}
}
impl std::fmt::Display for i24 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_i32())
}
}
impl FromStr for i24 {
type Err = std::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let i32_result = i32::from_str(s)?;
Ok(i24::from_i32(i32_result))
}
}
impl i24 {
pub const fn to_i32(self) -> i32 {
let [a, b, c] = self.data;
i32::from_ne_bytes([a, b, c, 0])
}
pub const fn from_i32(n: i32) -> Self {
let [a, b, c, _d] = i32::to_ne_bytes(n);
Self { data: [a, b, c] }
}
}
impl Neg for i24 {
type Output = Self;
fn neg(self) -> Self {
let i32_result = self.to_i32().wrapping_neg();
i24::from_i32(i32_result)
}
}
impl Not for i24 {
type Output = Self;
fn not(self) -> Self {
let i32_result = !self.to_i32();
i24::from_i32(i32_result)
}
}
macro_rules! implement_ops {
($($trait_path:path { $($function_name:ident),* }),*) => {
$(
impl $trait_path for i24 {
$(
type Output = Self;
fn $function_name(self, other: Self) -> Self {
let self_i32: i32 = self.to_i32();
let other_i32: i32 = other.to_i32();
let result = self_i32.$function_name(other_i32);
Self::from_i32(result)
}
)*
}
)*
};
}
macro_rules! implement_ops_assign {
($($trait_path:path { $($function_name:ident),* }),*) => {
$(
impl $trait_path for i24 {
$(
fn $function_name(&mut self, other: Self){
let mut self_i32: i32 = self.to_i32();
let other_i32: i32 = other.to_i32();
self_i32.$function_name(other_i32);
}
)*
}
)*
};
}
macro_rules! implement_ops_assign_ref {
($($trait_path:path { $($function_name:ident),* }),*) => {
$(
impl $trait_path for &i24 {
$(
fn $function_name(&mut self, other: Self){
let mut self_i32: i32 = self.to_i32();
let other_i32: i32 = other.to_i32();
self_i32.$function_name(other_i32);
}
)*
}
)*
};
}
implement_ops!(
std::ops::Add { add },
std::ops::Sub { sub },
std::ops::Mul { mul },
std::ops::Div { div },
std::ops::Rem { rem },
std::ops::BitAnd { bitand },
std::ops::BitOr { bitor },
std::ops::BitXor { bitxor },
std::ops::Shl { shl },
std::ops::Shr { shr }
);
implement_ops_assign!(
std::ops::AddAssign { add_assign },
std::ops::SubAssign { sub_assign },
std::ops::MulAssign { mul_assign },
std::ops::DivAssign { div_assign },
std::ops::RemAssign { rem_assign },
std::ops::BitAndAssign { bitand_assign },
std::ops::BitOrAssign { bitor_assign },
std::ops::BitXorAssign { bitxor_assign },
std::ops::ShlAssign { shl_assign },
std::ops::ShrAssign { shr_assign }
);
implement_ops_assign_ref!(
std::ops::AddAssign { add_assign },
std::ops::SubAssign { sub_assign },
std::ops::MulAssign { mul_assign },
std::ops::DivAssign { div_assign },
std::ops::RemAssign { rem_assign },
std::ops::BitAndAssign { bitand_assign },
std::ops::BitOrAssign { bitor_assign },
std::ops::BitXorAssign { bitxor_assign },
std::ops::ShlAssign { shl_assign },
std::ops::ShrAssign { shr_assign }
);
#[cfg(test)]
mod tests {
use approx_eq::assert_approx_eq;
use std::io::BufRead;
use std::{fs::File, path::Path, str::FromStr};
use super::{read, write, Samples, Wav};
const TEST_OUTPUT: &str = "./test_resources/tmp/";
#[test]
fn test_write() {
let expected_path = "./test_resources/one_channel_f32.txt";
let out_path = "./test_resources/tmp/one_channel_f32_tmp.wav";
let mut wav: Wav<f32> = Wav::from_path("./test_resources/one_channel_i16.wav")
.expect("Failed to open file wav file");
let samples = wav.read().expect("Failed to read data");
write(out_path, &samples, wav.sample_rate(), wav.n_channels())
.expect("Failed to write data");
let mut wav: Wav<f32> = Wav::from_path(out_path).expect("Failed to open file wav file");
let samples: Samples<f32> = wav.read().expect("Failed to read data");
let expected: Vec<f32> = read_text_to_vec(expected_path).expect("failed to load from txt");
for (exp, act) in expected.iter().zip(samples.as_ref()) {
assert_approx_eq!(*exp as f64, *act as f64, 1e-4);
}
std::fs::remove_file(Path::new(&out_path)).unwrap();
}
#[test]
fn test_read() {
let input_path = "./test_resources/one_channel_i16.wav";
let expected_path = "./test_resources/one_channel_i16.txt";
let expected_sr = 16000;
let mut wav: Wav<i16> = Wav::from_path(input_path).expect("Failed to open file wav file");
let samples: Samples<i16> = wav.read().expect("Failed to read data");
let actual_sr = wav.sample_rate();
let expected: Vec<i16> = read_text_to_vec(expected_path).expect("failed to load from txt");
assert_eq!(expected_sr, actual_sr, "Sample rates do not match");
for (exp, act) in expected.iter().zip(samples.as_ref()) {
assert_eq!(*exp, *act, "Samples do not match");
}
}
use std::stringify;
macro_rules! read_tests {
($($T:ident), *) => {
$(
paste::item! {
#[test]
fn [<read_$T>]() {
let t_string: &str = stringify!($T);
let wav_str = format!("./test_resources/one_channel_{}.wav", t_string);
let expected_str = format!("./test_resources/one_channel_{}.txt", t_string);
let (sample_data, _): (Samples<$T>, i32) = match read::<$T, _>(&wav_str) {
Ok((s, sr)) => (s, sr),
Err(e) => {eprintln!("{}\n{}", wav_str, e); panic!("Failed to read wav file")}
};
let expected_data: Vec<$T> = match read_text_to_vec(Path::new(&expected_str)) {
Ok(w) => w,
Err(e) => {eprintln!("{}\n{}", wav_str, e); panic!("Failed to read txt file")}
};
for (expected, actual) in expected_data.iter().zip(sample_data.iter()) {
assert_eq!(*expected, *actual, "{} != {}", expected, actual);
}
}
}
)*
}
}
read_tests!(i16, i32, f32, f64);
macro_rules! write_tests {
($($T:ident), *) => {
$(
paste::item! {
#[test]
fn [<write_$T>]() {
if !Path::new(TEST_OUTPUT).exists() {
std::fs::create_dir(TEST_OUTPUT).unwrap();
}
let t_string: &str = stringify!($T);
let wav_str = format!("./test_resources/one_channel_{}.wav", t_string);
let expected_str = format!("./test_resources/one_channel_{}.txt", t_string);
let mut wav: Wav<$T> =
Wav::from_path(wav_str).expect("Failed to create wav file");
let expected_samples: Samples<$T> = Samples::from(
read_text_to_vec(&Path::new(&expected_str)).expect("Failed to read to vec"),
);
let out = format!("{}_one_channel_{}.wav", TEST_OUTPUT, t_string);
let out_path = Path::new(&out);
wav.write::<$T, _>(out_path)
.expect("Failed to write file");
let mut new_wav: Wav<$T> = Wav::<$T>::from_path(out_path).unwrap();
for (expected, actual) in expected_samples
.iter()
.zip(new_wav.read().unwrap().iter())
{
assert_eq!(expected, actual, "{} != {}", expected, actual);
}
std::fs::remove_file(Path::new(&out_path)).unwrap();
}
}
)*
};
}
write_tests!(i16, i32, f32, f64);
use crate::ConvertSlice;
#[test]
fn write_sin_wav() {
let fp = "./wav.wav";
let sr: i32 = 16000;
let duration = 10;
let mut samples: Vec<f32> = (0..sr * duration).map(|x| (x as f32 / sr as f32)).collect();
for sample in samples.iter_mut() {
*sample *= 440.0 * 2.0 * std::f32::consts::PI;
*sample = sample.sin();
*sample *= i16::MAX as f32;
}
let samples: Samples<f32> = Samples::from(samples.into_boxed_slice().convert_slice());
write(fp, &samples, sr, 1).unwrap();
std::fs::remove_file(fp).unwrap();
}
fn read_lines<P>(filename: P) -> std::io::Result<std::io::Lines<std::io::BufReader<File>>>
where
P: AsRef<Path>,
{
let file = File::open(filename)?;
Ok(std::io::BufReader::new(file).lines())
}
fn read_text_to_vec<T: FromStr, P: AsRef<Path>>(
fp: P,
) -> Result<Vec<T>, Box<dyn std::error::Error>>
where
<T as FromStr>::Err: std::error::Error + 'static,
{
let mut data = Vec::new();
let lines = read_lines(fp)?;
for line in lines {
let line = line?;
for sample in line.split(" ") {
let parsed_sample: T = match sample.trim().parse::<T>() {
Ok(num) => num,
Err(err) => {
eprintln!("Failed to parse {}", sample);
panic!("{}", err)
}
};
data.push(parsed_sample);
}
}
Ok(data)
}
}
#[cfg(feature = "ndarray")]
pub use conversion::{AsNdarray, IntoNdarray};