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