Skip to main content

sim_lib_stream_file/
wav.rs

1use std::path::Path;
2
3use sim_kernel::{Error, Result};
4use sim_lib_stream_audio::{MemoryPcmSink, MemoryPcmSource, PcmBuffer, PcmPumpSummary, PcmSpec};
5use sim_lib_stream_core::{StreamMetadata, StreamValue};
6
7use crate::effect_io::{read_file_with_effect, write_file_with_effect};
8
9/// A PCM stream decoded from a WAV file together with its sample format.
10pub struct WavStream {
11    spec: PcmSpec,
12    stream: StreamValue,
13}
14
15impl WavStream {
16    /// Returns the PCM sample format (channels and sample rate) of the WAV.
17    pub fn spec(&self) -> PcmSpec {
18        self.spec
19    }
20
21    /// Borrows the decoded PCM stream.
22    pub fn stream(&self) -> &StreamValue {
23        &self.stream
24    }
25
26    /// Consumes the wrapper and returns the decoded PCM stream.
27    pub fn into_stream(self) -> StreamValue {
28        self.stream
29    }
30}
31
32/// Reads a canonical PCM16 WAV file from `path` and opens it as a PCM stream.
33///
34/// The read is gated by the filesystem read capability and recorded as a
35/// KERNEL 6 filesystem effect. Packets carry up to `frames_per_packet` frames.
36pub fn read_wav_stream(
37    cx: &mut sim_kernel::Cx,
38    path: impl AsRef<Path>,
39    frames_per_packet: usize,
40    metadata: StreamMetadata,
41) -> Result<WavStream> {
42    let bytes = read_file_with_effect(cx, path)?;
43    wav_bytes_to_stream(&bytes, frames_per_packet, metadata)
44}
45
46/// Decodes canonical PCM16 WAV bytes and opens them as a PCM stream.
47///
48/// Only little-endian PCM16 RIFF/WAVE input is supported; other formats fail.
49pub fn wav_bytes_to_stream(
50    bytes: &[u8],
51    frames_per_packet: usize,
52    metadata: StreamMetadata,
53) -> Result<WavStream> {
54    let (spec, buffers) = wav_bytes_to_buffers(bytes, frames_per_packet)?;
55    let mut source = MemoryPcmSource::new(spec, buffers)?;
56    let stream = sim_lib_stream_audio::pcm_source_to_stream(&mut source, metadata)?;
57    Ok(WavStream { spec, stream })
58}
59
60/// Drains a PCM stream and writes it to `path` as a canonical PCM16 WAV file.
61///
62/// The write is gated by the filesystem write capability and recorded as a
63/// KERNEL 6 filesystem effect. Returns a summary of the pumped PCM.
64pub fn write_wav_stream(
65    cx: &mut sim_kernel::Cx,
66    path: impl AsRef<Path>,
67    stream: &StreamValue,
68    spec: PcmSpec,
69) -> Result<PcmPumpSummary> {
70    let mut sink = MemoryPcmSink::new(spec);
71    let summary = sim_lib_stream_audio::stream_to_pcm_sink(stream, &mut sink)?;
72    let bytes = pcm_buffers_to_wav_bytes(spec, sink.buffers())?;
73    write_file_with_effect(cx, path, bytes)?;
74    Ok(summary)
75}
76
77/// Drains a PCM stream and encodes it as canonical PCM16 WAV bytes.
78///
79/// Returns the encoded bytes together with a summary of the pumped PCM.
80pub fn stream_to_wav_bytes(
81    stream: &StreamValue,
82    spec: PcmSpec,
83) -> Result<(Vec<u8>, PcmPumpSummary)> {
84    let mut sink = MemoryPcmSink::new(spec);
85    let summary = sim_lib_stream_audio::stream_to_pcm_sink(stream, &mut sink)?;
86    Ok((pcm_buffers_to_wav_bytes(spec, sink.buffers())?, summary))
87}
88
89/// Encodes PCM buffers into canonical PCM16 WAV bytes.
90///
91/// Every buffer must share `spec`; a mismatched buffer spec is an error.
92///
93/// # Examples
94///
95/// ```
96/// use sim_lib_stream_audio::PcmSpec;
97/// use sim_lib_stream_file::pcm_buffers_to_wav_bytes;
98///
99/// let spec = PcmSpec::i16(2, 48_000).unwrap();
100/// let bytes = pcm_buffers_to_wav_bytes(spec, &[]).unwrap();
101/// assert_eq!(&bytes[0..4], b"RIFF");
102/// assert_eq!(&bytes[8..12], b"WAVE");
103/// ```
104pub fn pcm_buffers_to_wav_bytes(spec: PcmSpec, buffers: &[PcmBuffer]) -> Result<Vec<u8>> {
105    let mut samples = Vec::new();
106    for buffer in buffers {
107        if buffer.spec() != spec {
108            return Err(Error::Eval(
109                "WAV writer received a PCM buffer with a mismatched spec".to_owned(),
110            ));
111        }
112        samples.extend_from_slice(buffer.samples_i16());
113    }
114    encode_wav_i16(spec, &samples)
115}
116
117fn wav_bytes_to_buffers(
118    bytes: &[u8],
119    frames_per_packet: usize,
120) -> Result<(PcmSpec, Vec<PcmBuffer>)> {
121    if frames_per_packet == 0 {
122        return Err(Error::Eval(
123            "WAV frames-per-packet must be greater than zero".to_owned(),
124        ));
125    }
126    let parsed = parse_wav_i16(bytes)?;
127    let channels = parsed.spec.channels();
128    let samples_per_packet = channels
129        .checked_mul(frames_per_packet)
130        .ok_or_else(|| Error::Eval("WAV packet sample count overflow".to_owned()))?;
131    let mut buffers = Vec::new();
132    for chunk in parsed.samples.chunks(samples_per_packet) {
133        if chunk.is_empty() {
134            continue;
135        }
136        if !chunk.len().is_multiple_of(channels) {
137            return Err(Error::Eval(
138                "malformed WAV file: PCM data ends mid-frame".to_owned(),
139            ));
140        }
141        buffers.push(PcmBuffer::i16(
142            parsed.spec,
143            chunk.len() / channels,
144            chunk.to_vec(),
145        )?);
146    }
147    Ok((parsed.spec, buffers))
148}
149
150struct ParsedWav {
151    spec: PcmSpec,
152    samples: Vec<i16>,
153}
154
155fn parse_wav_i16(bytes: &[u8]) -> Result<ParsedWav> {
156    if bytes.len() < 12 || &bytes[0..4] != b"RIFF" || &bytes[8..12] != b"WAVE" {
157        return Err(Error::Eval(
158            "malformed WAV file: missing RIFF/WAVE header".to_owned(),
159        ));
160    }
161    let mut pos = 12usize;
162    let mut fmt: Option<(usize, u32, u16)> = None;
163    let mut data: Option<&[u8]> = None;
164    while pos + 8 <= bytes.len() {
165        let chunk_id = &bytes[pos..pos + 4];
166        let len = read_u32_le(bytes, pos + 4)? as usize;
167        let start = pos + 8;
168        let end = start
169            .checked_add(len)
170            .ok_or_else(|| Error::Eval("malformed WAV file: chunk length overflow".to_owned()))?;
171        if end > bytes.len() {
172            return Err(Error::Eval(
173                "malformed WAV file: chunk extends past end of file".to_owned(),
174            ));
175        }
176        match chunk_id {
177            b"fmt " => fmt = Some(parse_fmt_chunk(&bytes[start..end])?),
178            b"data" => data = Some(&bytes[start..end]),
179            _ => {}
180        }
181        pos = end + usize::from(!len.is_multiple_of(2));
182    }
183    let (channels, sample_rate, bits_per_sample) =
184        fmt.ok_or_else(|| Error::Eval("malformed WAV file: missing fmt chunk".to_owned()))?;
185    if bits_per_sample != 16 {
186        return Err(Error::Eval(format!(
187            "malformed WAV file: unsupported PCM bit depth {bits_per_sample}"
188        )));
189    }
190    let data =
191        data.ok_or_else(|| Error::Eval("malformed WAV file: missing data chunk".to_owned()))?;
192    if !data.len().is_multiple_of(2) {
193        return Err(Error::Eval(
194            "malformed WAV file: PCM16 data has odd byte length".to_owned(),
195        ));
196    }
197    let spec = PcmSpec::i16(channels, sample_rate)?;
198    let samples = data
199        .chunks_exact(2)
200        .map(|bytes| i16::from_le_bytes([bytes[0], bytes[1]]))
201        .collect::<Vec<_>>();
202    if !samples.len().is_multiple_of(channels) {
203        return Err(Error::Eval(
204            "malformed WAV file: PCM data ends mid-frame".to_owned(),
205        ));
206    }
207    Ok(ParsedWav { spec, samples })
208}
209
210fn parse_fmt_chunk(bytes: &[u8]) -> Result<(usize, u32, u16)> {
211    if bytes.len() < 16 {
212        return Err(Error::Eval(
213            "malformed WAV file: fmt chunk is too short".to_owned(),
214        ));
215    }
216    let audio_format = read_u16_le(bytes, 0)?;
217    if audio_format != 1 {
218        return Err(Error::Eval(format!(
219            "malformed WAV file: unsupported audio format {audio_format}"
220        )));
221    }
222    let channels = usize::from(read_u16_le(bytes, 2)?);
223    let sample_rate = read_u32_le(bytes, 4)?;
224    let block_align = usize::from(read_u16_le(bytes, 12)?);
225    let bits_per_sample = read_u16_le(bytes, 14)?;
226    if block_align != channels.saturating_mul(2) {
227        return Err(Error::Eval(
228            "malformed WAV file: PCM16 block alignment mismatch".to_owned(),
229        ));
230    }
231    Ok((channels, sample_rate, bits_per_sample))
232}
233
234fn encode_wav_i16(spec: PcmSpec, samples: &[i16]) -> Result<Vec<u8>> {
235    if !samples.len().is_multiple_of(spec.channels()) {
236        return Err(Error::Eval(
237            "WAV writer received samples that end mid-frame".to_owned(),
238        ));
239    }
240    let channels = u16::try_from(spec.channels())
241        .map_err(|_| Error::Eval("WAV channel count exceeds u16".to_owned()))?;
242    let data_len = samples
243        .len()
244        .checked_mul(2)
245        .ok_or_else(|| Error::Eval("WAV data length overflow".to_owned()))?;
246    let data_len_u32 =
247        u32::try_from(data_len).map_err(|_| Error::Eval("WAV data exceeds u32".to_owned()))?;
248    let riff_len = 36u32
249        .checked_add(data_len_u32)
250        .ok_or_else(|| Error::Eval("WAV RIFF length overflow".to_owned()))?;
251    let block_align = channels
252        .checked_mul(2)
253        .ok_or_else(|| Error::Eval("WAV block alignment overflow".to_owned()))?;
254    let byte_rate = spec
255        .sample_rate_hz()
256        .checked_mul(u32::from(block_align))
257        .ok_or_else(|| Error::Eval("WAV byte rate overflow".to_owned()))?;
258
259    let mut out = Vec::with_capacity(44 + data_len);
260    out.extend_from_slice(b"RIFF");
261    out.extend_from_slice(&riff_len.to_le_bytes());
262    out.extend_from_slice(b"WAVEfmt ");
263    out.extend_from_slice(&16u32.to_le_bytes());
264    out.extend_from_slice(&1u16.to_le_bytes());
265    out.extend_from_slice(&channels.to_le_bytes());
266    out.extend_from_slice(&spec.sample_rate_hz().to_le_bytes());
267    out.extend_from_slice(&byte_rate.to_le_bytes());
268    out.extend_from_slice(&block_align.to_le_bytes());
269    out.extend_from_slice(&16u16.to_le_bytes());
270    out.extend_from_slice(b"data");
271    out.extend_from_slice(&data_len_u32.to_le_bytes());
272    for sample in samples {
273        out.extend_from_slice(&sample.to_le_bytes());
274    }
275    Ok(out)
276}
277
278fn read_u16_le(bytes: &[u8], offset: usize) -> Result<u16> {
279    let end = offset + 2;
280    let slice = bytes.get(offset..end).ok_or_else(|| {
281        Error::Eval(format!(
282            "malformed WAV file: unexpected end at byte {offset}"
283        ))
284    })?;
285    Ok(u16::from_le_bytes([slice[0], slice[1]]))
286}
287
288fn read_u32_le(bytes: &[u8], offset: usize) -> Result<u32> {
289    let end = offset + 4;
290    let slice = bytes.get(offset..end).ok_or_else(|| {
291        Error::Eval(format!(
292            "malformed WAV file: unexpected end at byte {offset}"
293        ))
294    })?;
295    Ok(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
296}