Skip to main content

pito_codecs/media/
wav.rs

1use super::{AudioBuffer, Error, Result};
2
3fn invalid(what: impl std::fmt::Display) -> Error {
4    Error::Invalid(format!("WAV: {what}"))
5}
6
7fn le16(b: &[u8], at: usize) -> Result<u16> {
8    b.get(at..at + 2)
9        .map(|s| u16::from_le_bytes([s[0], s[1]]))
10        .ok_or_else(|| invalid("the fmt chunk is too short"))
11}
12
13fn le32(b: &[u8], at: usize) -> Result<u32> {
14    b.get(at..at + 4)
15        .map(|s| u32::from_le_bytes([s[0], s[1], s[2], s[3]]))
16        .ok_or_else(|| invalid("the file ends inside a chunk header"))
17}
18
19#[derive(Clone, Copy, PartialEq, Eq)]
20enum Sample {
21    Int,
22    Float,
23}
24
25pub fn decode(data: &[u8]) -> Result<AudioBuffer> {
26    if data.get(..4) != Some(b"RIFF") || data.get(8..12) != Some(b"WAVE") {
27        return Err(invalid("not a RIFF WAVE file"));
28    }
29    let mut format = None;
30    let mut samples = None;
31    let mut at = 12usize;
32    while at + 8 <= data.len() {
33        let len = le32(data, at + 4)? as usize;
34        let start = at + 8;
35        let end = start.saturating_add(len).min(data.len());
36        let body = &data[start..end];
37        match &data[at..at + 4] {
38            b"fmt " => {
39                let tag = le16(body, 0)?;
40                let channels = le16(body, 2)?;
41                let rate = le32(body, 4).map_err(|_| invalid("the fmt chunk is too short"))?;
42                let align = le16(body, 12)?;
43                let bits = le16(body, 14)?;
44                let tag = if tag == 0xfffe { le16(body, 24)? } else { tag };
45                let kind = match (tag, bits) {
46                    (1, 8 | 16 | 24 | 32) => Sample::Int,
47                    (3, 32 | 64) => Sample::Float,
48                    _ => {
49                        return Err(Error::Unsupported(format!(
50                            "WAV format {tag} with {bits}-bit samples; PCM 8, 16, 24 or 32-bit and float 32 or 64-bit are read"
51                        )));
52                    }
53                };
54                if channels == 0 || rate == 0 {
55                    return Err(invalid(format!("{channels} channels at {rate} Hz")));
56                }
57                if usize::from(align) != usize::from(channels) * usize::from(bits / 8) {
58                    return Err(invalid(format!(
59                        "a block of {align} bytes for {channels} channels of {bits} bits"
60                    )));
61                }
62                format = Some((kind, channels, rate, bits));
63            }
64            b"data" => {
65                samples = Some(body);
66                if format.is_some() {
67                    break;
68                }
69            }
70            _ => {}
71        }
72        at = start.saturating_add(len).saturating_add(len & 1);
73    }
74    let (kind, channels, rate, bits) = format.ok_or_else(|| invalid("no fmt chunk"))?;
75    let body = samples.ok_or_else(|| invalid("no data chunk"))?;
76    let width = usize::from(bits / 8);
77    let frame = width * usize::from(channels);
78    let body = &body[..body.len() - body.len() % frame];
79    let out: Vec<f32> = match (kind, bits) {
80        (Sample::Int, 8) => body
81            .iter()
82            .map(|&b| (f32::from(b) - 128.0) / 128.0)
83            .collect(),
84        (Sample::Int, 16) => body
85            .chunks_exact(2)
86            .map(|s| f32::from(i16::from_le_bytes([s[0], s[1]])) / 32768.0)
87            .collect(),
88        (Sample::Int, 24) => body
89            .chunks_exact(3)
90            .map(|s| (i32::from_le_bytes([0, s[0], s[1], s[2]]) >> 8) as f32 / 8_388_608.0)
91            .collect(),
92        (Sample::Int, _) => body
93            .chunks_exact(4)
94            .map(|s| i32::from_le_bytes([s[0], s[1], s[2], s[3]]) as f32 / 2_147_483_648.0)
95            .collect(),
96        (Sample::Float, 32) => body
97            .chunks_exact(4)
98            .map(|s| clean(f32::from_le_bytes([s[0], s[1], s[2], s[3]])))
99            .collect(),
100        (Sample::Float, _) => body
101            .chunks_exact(8)
102            .map(|s| {
103                let mut b = [0u8; 8];
104                b.copy_from_slice(s);
105                clean(f64::from_le_bytes(b) as f32)
106            })
107            .collect(),
108    };
109    Ok(AudioBuffer {
110        samples: out,
111        rate,
112        channels,
113    })
114}
115
116fn clean(v: f32) -> f32 {
117    if v.is_finite() { v } else { 0.0 }
118}