gwav 0.1.0

Rudimentary WAV parsing library
Documentation
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>>>>, //Vec<Sample<Channel<f64>>> aka samples[sample][channel] (really inefficient way of storing this)
    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)?; //skip it, no useful information
        }

        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;

        //RIFF
        bytes.write_u32::<BigEndian>(RIFF_TAG)?;
        bytes.write_u32::<LittleEndian>(
            4 //WAVE tag
            + 4 //fmt tag
            + std::mem::size_of::<FmtSubchunk>() as u32
            + 4 //data tag
            + 4 //data_size
            + data_size,
        )?;

        bytes.write_u32::<BigEndian>(WAVE_TAG)?;

        //FMT
        bytes.write_u32::<BigEndian>(FMT_TAG)?;
        unsafe {
            bytes.extend_from_slice(&std::mem::transmute::<
                FmtSubchunk,
                [u8; std::mem::size_of::<FmtSubchunk>()],
            >(self.fmt_subchunk))
        };

        //Data
        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 {}