Skip to main content

riff_wave_core/
mux.rs

1//! RIFF/WAVE mux (`fmt ` + `data` chunks).
2
3#![forbid(unsafe_code)]
4
5use crate::types::WaveFormat;
6
7/// Builds a single RIFF/WAVE file from pushed PCM samples.
8///
9/// Unlike `iso-bmff`'s incrementally-flushable fragmented output, RIFF's `RIFF` and
10/// `data` chunk sizes must be known before the header can be written — there is no
11/// fragmented/streamable RIFF profile in scope here. Samples are buffered internally
12/// until [`Muxer::finish`] is called.
13#[derive(Debug, Clone)]
14pub struct Muxer {
15    format: WaveFormat,
16    samples: Vec<u8>,
17}
18
19impl Muxer {
20    /// Start a new mux session for `format`.
21    #[must_use]
22    pub const fn new(format: WaveFormat) -> Self {
23        Self {
24            format,
25            samples: Vec::new(),
26        }
27    }
28
29    /// Append raw interleaved PCM bytes (already encoded per `format`).
30    pub fn push_samples(&mut self, pcm: &[u8]) {
31        self.samples.extend_from_slice(pcm);
32    }
33
34    /// Finalize and return the complete RIFF/WAVE byte stream.
35    #[must_use]
36    pub fn finish(self) -> Vec<u8> {
37        let data_len = u32::try_from(self.samples.len()).unwrap_or(u32::MAX);
38        let riff_len = 4 + (8 + 16) + (8 + data_len);
39
40        let mut out = Vec::with_capacity(12 + 24 + 8 + self.samples.len());
41        out.extend_from_slice(b"RIFF");
42        out.extend_from_slice(&riff_len.to_le_bytes());
43        out.extend_from_slice(b"WAVE");
44
45        out.extend_from_slice(b"fmt ");
46        out.extend_from_slice(&16u32.to_le_bytes());
47        out.extend_from_slice(&self.format.sample_format.tag().to_le_bytes());
48        out.extend_from_slice(&self.format.channels.to_le_bytes());
49        out.extend_from_slice(&self.format.sample_rate.to_le_bytes());
50        out.extend_from_slice(&self.format.byte_rate().to_le_bytes());
51        out.extend_from_slice(&self.format.block_align().to_le_bytes());
52        out.extend_from_slice(&self.format.bits_per_sample.to_le_bytes());
53
54        out.extend_from_slice(b"data");
55        out.extend_from_slice(&data_len.to_le_bytes());
56        out.extend_from_slice(&self.samples);
57        if data_len % 2 == 1 {
58            out.push(0); // RIFF chunks are word-aligned; pad odd-sized data chunks.
59        }
60        out
61    }
62}
63
64#[cfg(test)]
65#[path = "mux_tests.rs"]
66mod tests;