Skip to main content

wav_codec/
sample.rs

1use crate::{Error, Result, SampleFormat};
2
3mod private {
4    use crate::{Result, SampleFormat};
5
6    /// Resolved on-disk encoding: format class, valid bits, and container width.
7    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
8    pub struct Codec {
9        pub format: SampleFormat,
10        /// Valid bits per sample.
11        pub bits: u16,
12        /// Bytes per sample on disk.
13        pub container: u16,
14        /// `32 - bits`, the shift that sign-extends the valid bits of a PCM value.
15        pub shift: u32,
16    }
17
18    impl Codec {
19        pub fn new(format: SampleFormat, bits: u16, container: u16) -> Codec {
20            let shift = 32u32.saturating_sub(u32::from(bits));
21            Codec {
22                format,
23                bits,
24                container,
25                shift,
26            }
27        }
28
29        pub fn width(&self) -> usize {
30            usize::from(self.container)
31        }
32    }
33
34    /// Encode and decode against [`Codec`]. Public [`super::Sample`] is a
35    /// sealed marker. This trait and `Codec` stay crate-private.
36    pub trait SampleOps: Copy {
37        fn accepts_read(codec: &Codec) -> bool;
38        fn accepts_write(codec: &Codec) -> bool;
39        fn decode(bytes: &[u8], codec: &Codec) -> Self;
40        fn encode(self, codec: &Codec, out: &mut Vec<u8>) -> Result<()>;
41    }
42}
43
44pub(crate) use private::{Codec, SampleOps};
45
46/// A sample type the reader can decode into and the writer can encode from.
47///
48/// Implemented for `i8`, `i16`, `i32`, `f32`, and `f64`. The trait is
49/// sealed.
50pub trait Sample: Copy + private::SampleOps {}
51
52#[inline(always)]
53fn decode_int(bytes: &[u8], codec: &Codec) -> i32 {
54    match *bytes {
55        [a] => i32::from((a ^ 0x80) as i8),
56        [a, b] => ((u32::from(u16::from_le_bytes([a, b])) << codec.shift) as i32) >> codec.shift,
57        [a, b, c] => ((u32::from_le_bytes([a, b, c, 0]) << codec.shift) as i32) >> codec.shift,
58        [a, b, c, d] => ((u32::from_le_bytes([a, b, c, d]) << codec.shift) as i32) >> codec.shift,
59        _ => unreachable!("PCM container width is 1, 2, 3, or 4 after validate"),
60    }
61}
62
63/// Append `value` as a PCM container of `codec.container` bytes, checking the
64/// range when the source type is wider than the file's valid bits.
65#[inline(always)]
66fn encode_int(value: i32, source_bits: u16, codec: &Codec, out: &mut Vec<u8>) -> Result<()> {
67    if source_bits > codec.bits {
68        let half = 1i64 << (codec.bits - 1);
69        let v = i64::from(value);
70        if v < -half || v >= half {
71            return Err(Error::TooWide);
72        }
73    }
74    match codec.container {
75        1 => out.push((value as u8) ^ 0x80),
76        w => out.extend_from_slice(&value.to_le_bytes()[..usize::from(w)]),
77    }
78    Ok(())
79}
80
81macro_rules! int_sample {
82    ($t:ty, $bits:expr) => {
83        impl SampleOps for $t {
84            #[inline]
85            fn accepts_read(codec: &Codec) -> bool {
86                codec.format == SampleFormat::Int && codec.bits <= $bits
87            }
88            #[inline]
89            fn accepts_write(codec: &Codec) -> bool {
90                codec.format == SampleFormat::Int
91            }
92            #[inline(always)]
93            fn decode(bytes: &[u8], codec: &Codec) -> Self {
94                decode_int(bytes, codec) as $t
95            }
96            #[inline(always)]
97            fn encode(self, codec: &Codec, out: &mut Vec<u8>) -> Result<()> {
98                encode_int(i32::from(self), $bits, codec, out)
99            }
100        }
101        impl Sample for $t {}
102    };
103}
104
105int_sample!(i8, 8);
106int_sample!(i16, 16);
107int_sample!(i32, 32);
108
109impl SampleOps for f32 {
110    #[inline]
111    fn accepts_read(codec: &Codec) -> bool {
112        codec.format == SampleFormat::Float && codec.bits == 32
113    }
114    #[inline]
115    fn accepts_write(codec: &Codec) -> bool {
116        codec.format == SampleFormat::Float
117    }
118    #[inline(always)]
119    fn decode(bytes: &[u8], _codec: &Codec) -> Self {
120        match *bytes {
121            [a, b, c, d] => f32::from_le_bytes([a, b, c, d]),
122            _ => unreachable!("f32 files use 4-byte containers"),
123        }
124    }
125    #[inline(always)]
126    fn encode(self, codec: &Codec, out: &mut Vec<u8>) -> Result<()> {
127        match codec.container {
128            4 => out.extend_from_slice(&self.to_le_bytes()),
129            _ => out.extend_from_slice(&f64::from(self).to_le_bytes()),
130        }
131        Ok(())
132    }
133}
134impl Sample for f32 {}
135
136impl SampleOps for f64 {
137    #[inline]
138    fn accepts_read(codec: &Codec) -> bool {
139        codec.format == SampleFormat::Float && (codec.bits == 32 || codec.bits == 64)
140    }
141    #[inline]
142    fn accepts_write(codec: &Codec) -> bool {
143        codec.format == SampleFormat::Float && codec.bits == 64
144    }
145    #[inline(always)]
146    fn decode(bytes: &[u8], _codec: &Codec) -> Self {
147        match *bytes {
148            [a, b, c, d] => f64::from(f32::from_le_bytes([a, b, c, d])),
149            [a, b, c, d, e, f, g, h] => f64::from_le_bytes([a, b, c, d, e, f, g, h]),
150            _ => unreachable!("float files use 4-byte or 8-byte containers"),
151        }
152    }
153    #[inline(always)]
154    fn encode(self, _codec: &Codec, out: &mut Vec<u8>) -> Result<()> {
155        out.extend_from_slice(&self.to_le_bytes());
156        Ok(())
157    }
158}
159impl Sample for f64 {}
160
161/// Decode `dst.len()` samples from `src`, which holds exactly
162/// `dst.len() * codec.container` bytes.
163#[inline]
164pub(crate) fn decode_block<S: SampleOps>(codec: &Codec, src: &[u8], dst: &mut [S]) {
165    match codec.container {
166        1 => decode_fixed::<S, 1>(codec, src, dst),
167        2 => decode_fixed::<S, 2>(codec, src, dst),
168        3 => decode_fixed::<S, 3>(codec, src, dst),
169        4 => decode_fixed::<S, 4>(codec, src, dst),
170        8 => decode_fixed::<S, 8>(codec, src, dst),
171        _ => unreachable!("container width is 1, 2, 3, 4, or 8 after validate"),
172    }
173}
174
175#[inline(always)]
176fn decode_fixed<S: SampleOps, const W: usize>(codec: &Codec, src: &[u8], dst: &mut [S]) {
177    for (d, c) in dst.iter_mut().zip(src.chunks_exact(W)) {
178        *d = S::decode(c, codec);
179    }
180}