Skip to main content

shuyi_sound/
wav.rs

1#![cfg(feature = "std")]
2//! WAV files, over the `hound` crate: integer samples of 8, 16, 24 or 32 bits
3//! and float samples of 32. Playdate's IMA ADPCM WAV isn't one of them.
4
5use std::io::{Cursor, Read};
6
7use hound::{SampleFormat, WavReader};
8
9use crate::error::Error;
10use crate::guard::guarded;
11use crate::pcm::{self, Limits, PcmFormat};
12use crate::source::{Source, room};
13
14const FORMAT: &str = "WAV";
15
16/// How a file's samples become 16 bits.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18enum Kind {
19    Int8,
20    Int16,
21    Int24,
22    Int32,
23    Float32,
24}
25
26/// A WAV file's samples, read from its bytes as they are asked for. It borrows them
27/// or owns them, as `B` does: a worker that outlives its caller holds an `Arc<[u8]>`.
28pub struct WavSource<B> {
29    reader: WavReader<Cursor<B>>,
30    format: PcmFormat,
31    kind: Kind,
32    frames: u64,
33    /// How many frames are still to come.
34    left: u64,
35}
36
37fn malformed(error: &hound::Error) -> Error {
38    Error::Malformed {
39        format: FORMAT,
40        reason: error.to_string(),
41    }
42}
43
44impl<B: AsRef<[u8]>> WavSource<B> {
45    /// A source of `bytes`, whose header says what it holds. What that is must
46    /// be within `limits`, and the bytes must be there for it.
47    pub fn new(bytes: B, limits: &Limits) -> Result<Self, Error> {
48        guarded(FORMAT, || Self::open(bytes, limits))
49    }
50
51    fn open(bytes: B, limits: &Limits) -> Result<Self, Error> {
52        // The reader stops at the first sample, and says where only when given up.
53        let (start, available) = {
54            let borrowed = Cursor::new(bytes.as_ref());
55            let header = WavReader::new(borrowed).map_err(|e| malformed(&e))?;
56            (header.into_inner().position(), bytes.as_ref().len() as u64)
57        };
58        let reader = WavReader::new(Cursor::new(bytes)).map_err(|e| malformed(&e))?;
59        let spec = reader.spec();
60        let format = PcmFormat::new(spec.sample_rate, u32::from(spec.channels), limits)?;
61        let kind = match (spec.sample_format, spec.bits_per_sample) {
62            (SampleFormat::Int, 8) => Kind::Int8,
63            (SampleFormat::Int, 16) => Kind::Int16,
64            (SampleFormat::Int, 24) => Kind::Int24,
65            (SampleFormat::Int, 32) => Kind::Int32,
66            (SampleFormat::Float, 32) => Kind::Float32,
67            (SampleFormat::Int, bits) => {
68                return Err(Error::SampleKind {
69                    kind: "integer",
70                    bits,
71                });
72            }
73            (SampleFormat::Float, bits) => {
74                return Err(Error::SampleKind {
75                    kind: "float",
76                    bits,
77                });
78            }
79        };
80        let samples = u64::from(reader.len());
81        let channels = u64::from(format.channels.count());
82        if !samples.is_multiple_of(channels) {
83            return Err(Error::PartialFrame {
84                samples,
85                channels: format.channels.count(),
86            });
87        }
88        let frames = samples / channels;
89        if frames > limits.max_frames {
90            return Err(Error::TooLong {
91                frames,
92                limit: limits.max_frames,
93            });
94        }
95        // What the header promises, against what is there: a corrupt length
96        // shows now, and not as the file runs out while it plays.
97        let needed = samples * u64::from(spec.bits_per_sample / 8);
98        if needed > available.saturating_sub(start) {
99            return Err(Error::Malformed {
100                format: FORMAT,
101                reason: format!("it says it holds {needed} bytes of samples, and has less"),
102            });
103        }
104        Ok(Self {
105            reader,
106            format,
107            kind,
108            frames,
109            left: frames,
110        })
111    }
112}
113
114/// Fills `out` with `reader`'s next samples of `S`, each through `convert`.
115fn fill<S: hound::Sample, R: Read>(
116    reader: &mut WavReader<R>,
117    out: &mut [i16],
118    convert: impl Fn(S) -> i16,
119) -> Result<(), Error> {
120    let mut samples = reader.samples::<S>();
121    for slot in out {
122        match samples.next() {
123            Some(Ok(sample)) => *slot = convert(sample),
124            Some(Err(e)) => return Err(malformed(&e)),
125            None => {
126                return Err(Error::Malformed {
127                    format: FORMAT,
128                    reason: "it ends early".to_owned(),
129                });
130            }
131        }
132    }
133    Ok(())
134}
135
136/// A float sample, where 1 is full scale, as 16 bits, clamped. The one float in
137/// the crate: this is where it stops being one.
138#[allow(clippy::float_arithmetic)]
139fn from_float(sample: f32) -> i16 {
140    // Clamped to within `i16` first, so the cast never truncates; NaN casts to 0.
141    #[allow(clippy::cast_possible_truncation)]
142    let narrow = (sample * 32_767.0).round().clamp(-32_768.0, 32_767.0) as i16;
143    narrow
144}
145
146impl<B: AsRef<[u8]>> Source for WavSource<B> {
147    fn format(&self) -> PcmFormat {
148        self.format
149    }
150
151    fn total_frames(&self) -> Option<u64> {
152        Some(self.frames)
153    }
154
155    fn read(&mut self, out: &mut [i16]) -> Result<usize, Error> {
156        let room = room(self.format, out)?;
157        // At most `room`, which is a `usize`.
158        let frames = usize::try_from(self.left).map_or(room, |left| left.min(room));
159        let count = frames * usize::from(self.format.channels.count());
160        let (kind, reader) = (self.kind, &mut self.reader);
161        let out = &mut out[..count];
162        guarded(FORMAT, || match kind {
163            Kind::Int8 => fill(reader, out, pcm::from_i8),
164            Kind::Int16 => fill(reader, out, |sample: i16| sample),
165            Kind::Int24 => fill(reader, out, pcm::from_i24),
166            Kind::Int32 => fill(reader, out, pcm::from_i32),
167            Kind::Float32 => fill(reader, out, from_float),
168        })?;
169        self.left -= frames as u64;
170        Ok(frames)
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn a_float_sample_rounds_and_clamps() {
180        assert_eq!(from_float(0.0), 0);
181        assert_eq!(from_float(1.0), 32_767);
182        assert_eq!(from_float(-1.0), -32_767);
183        assert_eq!(from_float(0.5), 16_384);
184        assert_eq!(from_float(4.0), 32_767);
185        assert_eq!(from_float(-4.0), -32_768);
186        assert_eq!(from_float(f32::NAN), 0);
187    }
188}