wav-codec 0.1.0

WAV (RIFF/WAVE) reader and writer over std I/O. PCM and IEEE-float WAV only. Compressed WAV (ADPCM, mu-law, A-law) not decoded.
Documentation
use crate::{Error, Result, SampleFormat};

mod private {
    use crate::{Result, SampleFormat};

    /// Resolved on-disk encoding: format class, valid bits, and container width.
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub struct Codec {
        pub format: SampleFormat,
        /// Valid bits per sample.
        pub bits: u16,
        /// Bytes per sample on disk.
        pub container: u16,
        /// `32 - bits`, the shift that sign-extends the valid bits of a PCM value.
        pub shift: u32,
    }

    impl Codec {
        pub fn new(format: SampleFormat, bits: u16, container: u16) -> Codec {
            let shift = 32u32.saturating_sub(u32::from(bits));
            Codec {
                format,
                bits,
                container,
                shift,
            }
        }

        pub fn width(&self) -> usize {
            usize::from(self.container)
        }
    }

    /// Encode and decode against [`Codec`]. Public [`super::Sample`] is a
    /// sealed marker. This trait and `Codec` stay crate-private.
    pub trait SampleOps: Copy {
        fn accepts_read(codec: &Codec) -> bool;
        fn accepts_write(codec: &Codec) -> bool;
        fn decode(bytes: &[u8], codec: &Codec) -> Self;
        fn encode(self, codec: &Codec, out: &mut Vec<u8>) -> Result<()>;
    }
}

pub(crate) use private::{Codec, SampleOps};

/// A sample type the reader can decode into and the writer can encode from.
///
/// Implemented for `i8`, `i16`, `i32`, `f32`, and `f64`. The trait is
/// sealed.
pub trait Sample: Copy + private::SampleOps {}

#[inline(always)]
fn decode_int(bytes: &[u8], codec: &Codec) -> i32 {
    match *bytes {
        [a] => i32::from((a ^ 0x80) as i8),
        [a, b] => ((u32::from(u16::from_le_bytes([a, b])) << codec.shift) as i32) >> codec.shift,
        [a, b, c] => ((u32::from_le_bytes([a, b, c, 0]) << codec.shift) as i32) >> codec.shift,
        [a, b, c, d] => ((u32::from_le_bytes([a, b, c, d]) << codec.shift) as i32) >> codec.shift,
        _ => unreachable!("PCM container width is 1, 2, 3, or 4 after validate"),
    }
}

/// Append `value` as a PCM container of `codec.container` bytes, checking the
/// range when the source type is wider than the file's valid bits.
#[inline(always)]
fn encode_int(value: i32, source_bits: u16, codec: &Codec, out: &mut Vec<u8>) -> Result<()> {
    if source_bits > codec.bits {
        let half = 1i64 << (codec.bits - 1);
        let v = i64::from(value);
        if v < -half || v >= half {
            return Err(Error::TooWide);
        }
    }
    match codec.container {
        1 => out.push((value as u8) ^ 0x80),
        w => out.extend_from_slice(&value.to_le_bytes()[..usize::from(w)]),
    }
    Ok(())
}

macro_rules! int_sample {
    ($t:ty, $bits:expr) => {
        impl SampleOps for $t {
            #[inline]
            fn accepts_read(codec: &Codec) -> bool {
                codec.format == SampleFormat::Int && codec.bits <= $bits
            }
            #[inline]
            fn accepts_write(codec: &Codec) -> bool {
                codec.format == SampleFormat::Int
            }
            #[inline(always)]
            fn decode(bytes: &[u8], codec: &Codec) -> Self {
                decode_int(bytes, codec) as $t
            }
            #[inline(always)]
            fn encode(self, codec: &Codec, out: &mut Vec<u8>) -> Result<()> {
                encode_int(i32::from(self), $bits, codec, out)
            }
        }
        impl Sample for $t {}
    };
}

int_sample!(i8, 8);
int_sample!(i16, 16);
int_sample!(i32, 32);

impl SampleOps for f32 {
    #[inline]
    fn accepts_read(codec: &Codec) -> bool {
        codec.format == SampleFormat::Float && codec.bits == 32
    }
    #[inline]
    fn accepts_write(codec: &Codec) -> bool {
        codec.format == SampleFormat::Float
    }
    #[inline(always)]
    fn decode(bytes: &[u8], _codec: &Codec) -> Self {
        match *bytes {
            [a, b, c, d] => f32::from_le_bytes([a, b, c, d]),
            _ => unreachable!("f32 files use 4-byte containers"),
        }
    }
    #[inline(always)]
    fn encode(self, codec: &Codec, out: &mut Vec<u8>) -> Result<()> {
        match codec.container {
            4 => out.extend_from_slice(&self.to_le_bytes()),
            _ => out.extend_from_slice(&f64::from(self).to_le_bytes()),
        }
        Ok(())
    }
}
impl Sample for f32 {}

impl SampleOps for f64 {
    #[inline]
    fn accepts_read(codec: &Codec) -> bool {
        codec.format == SampleFormat::Float && (codec.bits == 32 || codec.bits == 64)
    }
    #[inline]
    fn accepts_write(codec: &Codec) -> bool {
        codec.format == SampleFormat::Float && codec.bits == 64
    }
    #[inline(always)]
    fn decode(bytes: &[u8], _codec: &Codec) -> Self {
        match *bytes {
            [a, b, c, d] => f64::from(f32::from_le_bytes([a, b, c, d])),
            [a, b, c, d, e, f, g, h] => f64::from_le_bytes([a, b, c, d, e, f, g, h]),
            _ => unreachable!("float files use 4-byte or 8-byte containers"),
        }
    }
    #[inline(always)]
    fn encode(self, _codec: &Codec, out: &mut Vec<u8>) -> Result<()> {
        out.extend_from_slice(&self.to_le_bytes());
        Ok(())
    }
}
impl Sample for f64 {}

/// Decode `dst.len()` samples from `src`, which holds exactly
/// `dst.len() * codec.container` bytes.
#[inline]
pub(crate) fn decode_block<S: SampleOps>(codec: &Codec, src: &[u8], dst: &mut [S]) {
    match codec.container {
        1 => decode_fixed::<S, 1>(codec, src, dst),
        2 => decode_fixed::<S, 2>(codec, src, dst),
        3 => decode_fixed::<S, 3>(codec, src, dst),
        4 => decode_fixed::<S, 4>(codec, src, dst),
        8 => decode_fixed::<S, 8>(codec, src, dst),
        _ => unreachable!("container width is 1, 2, 3, 4, or 8 after validate"),
    }
}

#[inline(always)]
fn decode_fixed<S: SampleOps, const W: usize>(codec: &Codec, src: &[u8], dst: &mut [S]) {
    for (d, c) in dst.iter_mut().zip(src.chunks_exact(W)) {
        *d = S::decode(c, codec);
    }
}