use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
use std::{
cell::RefCell,
error::Error,
fmt::Display,
io::{Cursor, Read},
sync::Arc,
};
pub const RIFF_TAG: u32 = 0x52_49_46_46;
pub const DATA_TAG: u32 = 0x64_61_74_61;
pub const WAVE_TAG: u32 = 0x57_41_56_45;
pub const FMT_TAG: u32 = 0x66_6d_74_20;
pub const SUPPORTED_BITS_PER_SAMPLE: [u16; 4] = [64, 32, 24, 16];
pub const SUPPORTED_AUDIO_FORMATS: [AudioFormat; 2] = [AudioFormat::IEEEFLOAT, AudioFormat::PCM];
#[derive(Clone, Debug)]
pub struct Wav {
path: Option<String>,
pub samples: Arc<RefCell<Vec<Vec<f64>>>>, pub fmt_subchunk: FmtSubchunk,
}
#[derive(Default, Debug, Clone, Copy)]
#[repr(C)]
pub struct FmtSubchunk {
pub subchunk1_size: u32,
pub audio_format: u16,
pub channels: u16,
pub sample_rate: u32,
pub byte_rate: u32,
pub block_align: u16,
pub bits_per_sample: u16,
}
#[derive(Debug)]
struct WavError(String);
impl Display for WavError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "A WAV related error has occured: {}", self.0)
}
}
impl Error for WavError {}
#[derive(PartialEq, Debug)]
pub enum AudioFormat {
Unsupported,
PCM,
IEEEFLOAT,
}
impl From<u16> for AudioFormat {
fn from(value: u16) -> Self {
match value {
1 => AudioFormat::PCM,
3 => AudioFormat::IEEEFLOAT,
_ => AudioFormat::Unsupported,
}
}
}
impl Wav {
pub fn new(fmt_subchunk: FmtSubchunk) -> Self {
Self {
path: None,
samples: Arc::new(RefCell::new(Vec::new())),
fmt_subchunk,
}
}
pub fn path(&self) -> Option<String> {
self.path.clone()
}
pub fn read(path: &str) -> Result<Self, Box<dyn Error>> {
let bytes = std::fs::read(path)?;
let mut cursor = Cursor::new(&bytes);
if cursor.read_u32::<BigEndian>()? != RIFF_TAG {
return Err(Box::new(WavError(String::from("No RIFF tag"))));
}
let _ = cursor.read_u32::<LittleEndian>()? as u64;
if cursor.read_u32::<BigEndian>()? != WAVE_TAG {
return Err(Box::new(WavError(String::from("No WAVE tag"))));
}
if cursor.read_u32::<BigEndian>()? != FMT_TAG {
return Err(Box::new(WavError(String::from("No fmt tag"))));
}
let mut fmt_subchunk = [0u8; std::mem::size_of::<FmtSubchunk>()];
cursor.read_exact(&mut fmt_subchunk)?;
let fmt_subchunk = unsafe {
std::mem::transmute::<[u8; std::mem::size_of::<FmtSubchunk>()], FmtSubchunk>(
fmt_subchunk,
)
};
if !SUPPORTED_BITS_PER_SAMPLE.contains(&fmt_subchunk.bits_per_sample) {
return Err(Box::new(WavError(format!(
"Unsupported bits per sample (given: {}, expected one of: {:?})",
fmt_subchunk.bits_per_sample, SUPPORTED_BITS_PER_SAMPLE
))));
}
if !SUPPORTED_AUDIO_FORMATS.contains(&fmt_subchunk.audio_format.into()) {
return Err(Box::new(WavError(format!(
"Unsupported audio format (given: {:?}, expected one of: {:?})",
<u16 as Into<AudioFormat>>::into(fmt_subchunk.audio_format),
SUPPORTED_AUDIO_FORMATS
))));
}
if <u16 as Into<AudioFormat>>::into(fmt_subchunk.audio_format) == AudioFormat::IEEEFLOAT
&& !SUPPORTED_BITS_PER_SAMPLE[..2].contains(&fmt_subchunk.bits_per_sample)
{
return Err(Box::new(WavError(format!(
"Unsupported bits per sample for IEEEFLOAT format (given: {:?}, expected one of: {:?})",
fmt_subchunk.bits_per_sample,
&SUPPORTED_BITS_PER_SAMPLE[..2]
))));
}
while cursor.read_u32::<BigEndian>()? != DATA_TAG {
let chunk_size = cursor.read_u32::<LittleEndian>()?;
let mut buf = vec![0u8; chunk_size as usize];
cursor.read_exact(&mut buf)?; }
let bytes_per_sample = fmt_subchunk.bits_per_sample as usize / 8;
let data_size = cursor.read_u32::<LittleEndian>()? as usize;
let mut samples = Vec::new();
let data_end = cursor.position() + data_size as u64;
while cursor.position() < data_end {
let mut samples_ = Vec::new();
for _ in 0..fmt_subchunk.channels {
let sample = match &fmt_subchunk.audio_format.into() {
AudioFormat::PCM => {
let (raw_sample, max) = match &bytes_per_sample {
2 => (cursor.read_i16::<LittleEndian>()? as f64, i16::MAX as f64),
3 => (
cursor.read_i24::<LittleEndian>()? as f64,
2_f64.powi(24) / 2.0,
),
4 => (cursor.read_i32::<LittleEndian>()? as f64, i32::MAX as f64),
8 => (cursor.read_i64::<LittleEndian>()? as f64, i64::MAX as f64),
_ => unreachable!(),
};
raw_sample / max
}
AudioFormat::IEEEFLOAT => match &bytes_per_sample {
4 => cursor.read_f32::<LittleEndian>()? as f64,
8 => cursor.read_f64::<LittleEndian>()?,
_ => unreachable!(),
},
_ => unreachable!(),
};
samples_.push(sample);
}
samples.push(samples_);
}
let calculated_data_size =
samples.len() * bytes_per_sample * fmt_subchunk.channels as usize;
if calculated_data_size != data_size {
return Err(Box::new(WavError(format!(
"Data size does not match! ({} != {})",
calculated_data_size, data_size
))));
}
Ok(Self {
path: Some(path.to_string()),
samples: Arc::new(RefCell::new(samples)),
fmt_subchunk,
})
}
pub fn as_bytes(self) -> Result<Vec<u8>, Box<dyn Error>> {
let mut bytes = Vec::new();
let data_size = self.samples.borrow().len() as u32
* (self.fmt_subchunk.bits_per_sample as u32 / 8)
* self.fmt_subchunk.channels as u32;
let bytes_per_sample = self.fmt_subchunk.bits_per_sample / 8;
bytes.write_u32::<BigEndian>(RIFF_TAG)?;
bytes.write_u32::<LittleEndian>(
4 + 4 + std::mem::size_of::<FmtSubchunk>() as u32
+ 4 + 4 + data_size,
)?;
bytes.write_u32::<BigEndian>(WAVE_TAG)?;
bytes.write_u32::<BigEndian>(FMT_TAG)?;
unsafe {
bytes.extend_from_slice(&std::mem::transmute::<
FmtSubchunk,
[u8; std::mem::size_of::<FmtSubchunk>()],
>(self.fmt_subchunk))
};
bytes.write_u32::<BigEndian>(DATA_TAG)?;
bytes.write_u32::<LittleEndian>(data_size)?;
for samples in self.samples.borrow().iter() {
for &sample in samples {
match self.fmt_subchunk.audio_format.into() {
AudioFormat::PCM => {
match bytes_per_sample {
2 => bytes
.write_i16::<LittleEndian>((sample * i16::MAX as f64) as i16)?,
3 => {
bytes.write_i24::<LittleEndian>((sample * 2f64.powi(24)) as i32)?
}
4 => bytes
.write_i32::<LittleEndian>((sample * u32::MAX as f64) as i32)?,
8 => bytes
.write_i64::<LittleEndian>((sample * u64::MAX as f64) as i64)?,
_ => unreachable!(),
};
}
AudioFormat::IEEEFLOAT => match bytes_per_sample {
4 => bytes.write_f32::<LittleEndian>(sample as f32)?,
8 => bytes.write_f64::<LittleEndian>(sample)?,
_ => unreachable!(),
},
_ => unreachable!(),
}
}
}
Ok(bytes)
}
pub fn add_samples(&mut self, offset: usize, new_samples: &[Vec<f64>]) {
let range = offset..offset + new_samples.len();
if range.end > self.samples.borrow().len() {
self.samples
.borrow_mut()
.resize(range.end, vec![0.0; self.fmt_subchunk.channels as usize]);
}
let borrowed_samples = &mut *self.samples.borrow_mut();
for (i, samples) in new_samples.iter().enumerate() {
for (j, sample) in samples.iter().enumerate() {
borrowed_samples[range.start + i][j] += sample;
borrowed_samples[range.start + i][j] =
borrowed_samples[range.start + i][j].clamp(-1.0, 1.0);
}
}
}
}
unsafe impl Send for Wav {}
unsafe impl Sync for Wav {}