Skip to main content

rescale_core/wav/
stream.rs

1//! Streaming I/O untuk file RIFF WAV berbasis block (Zero-Allocation on process).
2
3use super::header::{WavHeader, WavSampleFormat};
4use super::writer::{DitherMode, WavWriteOptions};
5use crate::engine::{AudioBlock, AudioSpec, EngineError};
6use std::fs::File;
7use std::io::{self, BufReader, BufWriter, Read, Seek, SeekFrom, Write};
8use std::path::Path;
9
10/// Reader streaming berbasis blok untuk file RIFF WAV.
11pub struct WavStreamReader<R> {
12    reader: R,
13    header: WavHeader,
14    samples_read_per_channel: usize,
15    data_chunk_offset: u64,
16}
17
18impl WavStreamReader<BufReader<File>> {
19    /// Membuka file WAV dari path filesystem dalam mode streaming reader.
20    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
21        let file = File::open(path)?;
22        let reader = BufReader::new(file);
23        Self::new(reader)
24    }
25}
26
27impl<R: Read + Seek> WavStreamReader<R> {
28    /// Membuat streaming reader dari reader yang mengimplementasikan `Read + Seek`.
29    pub fn new(mut reader: R) -> io::Result<Self> {
30        let mut fourcc = [0u8; 4];
31
32        // 1. Validasi header RIFF
33        reader.read_exact(&mut fourcc)?;
34        if &fourcc != b"RIFF" {
35            return Err(io::Error::new(
36                io::ErrorKind::InvalidData,
37                "Not a valid RIFF file",
38            ));
39        }
40
41        let mut riff_size_bytes = [0u8; 4];
42        reader.read_exact(&mut riff_size_bytes)?;
43
44        reader.read_exact(&mut fourcc)?;
45        if &fourcc != b"WAVE" {
46            return Err(io::Error::new(
47                io::ErrorKind::InvalidData,
48                "Not a WAVE format",
49            ));
50        }
51
52        let mut audio_format: u16 = 0;
53        let mut num_channels: u16 = 0;
54        let mut sample_rate: u32 = 0;
55        let mut bits_per_sample: u16 = 0;
56        let mut data_chunk_size: u32 = 0;
57        let mut fmt_found = false;
58
59        // 2. Scan chunks sampai menemukan 'fmt ' dan 'data'
60        loop {
61            if reader.read_exact(&mut fourcc).is_err() {
62                break;
63            }
64
65            let mut chunk_size_bytes = [0u8; 4];
66            reader.read_exact(&mut chunk_size_bytes)?;
67            let chunk_size = u32::from_le_bytes(chunk_size_bytes);
68
69            match &fourcc {
70                b"fmt " => {
71                    let mut format_tag_bytes = [0u8; 2];
72                    let mut channels_bytes = [0u8; 2];
73                    let mut samplerate_bytes = [0u8; 4];
74                    let mut byterate_bytes = [0u8; 4];
75                    let mut blockalign_bytes = [0u8; 2];
76                    let mut bitspersample_bytes = [0u8; 2];
77
78                    reader.read_exact(&mut format_tag_bytes)?;
79                    reader.read_exact(&mut channels_bytes)?;
80                    reader.read_exact(&mut samplerate_bytes)?;
81                    reader.read_exact(&mut byterate_bytes)?;
82                    reader.read_exact(&mut blockalign_bytes)?;
83                    reader.read_exact(&mut bitspersample_bytes)?;
84
85                    audio_format = u16::from_le_bytes(format_tag_bytes);
86                    num_channels = u16::from_le_bytes(channels_bytes);
87                    sample_rate = u32::from_le_bytes(samplerate_bytes);
88                    bits_per_sample = u16::from_le_bytes(bitspersample_bytes);
89
90                    if chunk_size > 16 {
91                        reader.seek(SeekFrom::Current((chunk_size - 16) as i64))?;
92                    }
93                    fmt_found = true;
94                }
95                b"data" => {
96                    data_chunk_size = chunk_size;
97                    break;
98                }
99                _ => {
100                    reader.seek(SeekFrom::Current(chunk_size as i64))?;
101                }
102            }
103        }
104
105        if !fmt_found {
106            return Err(io::Error::new(
107                io::ErrorKind::InvalidData,
108                "Missing 'fmt ' chunk in WAV file",
109            ));
110        }
111
112        let sample_format = match (audio_format, bits_per_sample) {
113            (1, 16) => WavSampleFormat::Pcm16Bit,
114            (1, 24) => WavSampleFormat::Pcm24Bit,
115            (3, 32) => WavSampleFormat::Pcm32BitFloat,
116            _ => {
117                return Err(io::Error::new(
118                    io::ErrorKind::Unsupported,
119                    format!("Unsupported WAV format: tag={audio_format}, bits={bits_per_sample}"),
120                ));
121            }
122        };
123
124        let bytes_per_sample = (bits_per_sample / 8) as usize;
125        let total_samples = (data_chunk_size as usize) / bytes_per_sample;
126        let samples_per_channel = if num_channels > 0 {
127            total_samples / (num_channels as usize)
128        } else {
129            0
130        };
131
132        let data_chunk_offset = reader.stream_position()?;
133        let header = WavHeader::new(
134            num_channels,
135            sample_rate,
136            sample_format,
137            samples_per_channel,
138        );
139
140        Ok(Self {
141            reader,
142            header,
143            samples_read_per_channel: 0,
144            data_chunk_offset,
145        })
146    }
147
148    pub fn header(&self) -> &WavHeader {
149        &self.header
150    }
151
152    pub fn spec(&self) -> Result<AudioSpec, EngineError> {
153        AudioSpec::new(
154            self.header.sample_rate as f32,
155            self.header.num_channels as usize,
156        )
157    }
158
159    pub fn total_frames(&self) -> usize {
160        self.header.num_samples_per_channel
161    }
162
163    pub fn frames_read(&self) -> usize {
164        self.samples_read_per_channel
165    }
166
167    pub fn frames_remaining(&self) -> usize {
168        self.total_frames()
169            .saturating_sub(self.samples_read_per_channel)
170    }
171
172    pub fn is_finished(&self) -> bool {
173        self.frames_remaining() == 0
174    }
175
176    /// Membaca blok planar langsung ke dalam `AudioBlock`.
177    /// Mengembalikan jumlah frame yang berhasil dibaca (0 jika mencapai EOF).
178    pub fn read_block(&mut self, block: &mut AudioBlock<'_>) -> io::Result<usize> {
179        if block.channels() != self.header.num_channels as usize {
180            return Err(io::Error::new(
181                io::ErrorKind::InvalidInput,
182                format!(
183                    "AudioBlock channels ({}) does not match WAV channels ({})",
184                    block.channels(),
185                    self.header.num_channels
186                ),
187            ));
188        }
189
190        let frames_to_read = block.frames().min(self.frames_remaining());
191        if frames_to_read == 0 {
192            return Ok(0);
193        }
194
195        let num_channels = self.header.num_channels as usize;
196        for frame in 0..frames_to_read {
197            for ch in 0..num_channels {
198                let sample_val = match self.header.format {
199                    WavSampleFormat::Pcm16Bit => {
200                        let mut b = [0u8; 2];
201                        self.reader.read_exact(&mut b)?;
202                        let i = i16::from_le_bytes(b);
203                        if i < 0 {
204                            i as f32 / 32768.0
205                        } else {
206                            i as f32 / 32767.0
207                        }
208                    }
209                    WavSampleFormat::Pcm24Bit => {
210                        let mut b = [0u8; 3];
211                        self.reader.read_exact(&mut b)?;
212                        let sign = if b[2] & 0x80 != 0 { 0xFF } else { 0x00 };
213                        let i = i32::from_le_bytes([b[0], b[1], b[2], sign]);
214                        if i < 0 {
215                            i as f32 / 8388608.0
216                        } else {
217                            i as f32 / 8388607.0
218                        }
219                    }
220                    WavSampleFormat::Pcm32BitFloat => {
221                        let mut b = [0u8; 4];
222                        self.reader.read_exact(&mut b)?;
223                        f32::from_le_bytes(b)
224                    }
225                };
226
227                let channel_slice = block.channel_mut(ch).expect("validated channel count");
228                channel_slice[frame] = sample_val;
229            }
230        }
231
232        self.samples_read_per_channel += frames_to_read;
233        Ok(frames_to_read)
234    }
235
236    /// Reset posisi pembacaan kembali ke awal chunk data.
237    pub fn rewind(&mut self) -> io::Result<()> {
238        self.reader.seek(SeekFrom::Start(self.data_chunk_offset))?;
239        self.samples_read_per_channel = 0;
240        Ok(())
241    }
242}
243
244/// Writer streaming berbasis blok untuk menulis file RIFF WAV secara bertahap.
245pub struct WavStreamWriter<W> {
246    writer: W,
247    options: WavWriteOptions,
248    num_channels: u16,
249    sample_rate: u32,
250    frames_written: usize,
251    dither_state: u32,
252}
253
254impl WavStreamWriter<BufWriter<File>> {
255    /// Membuat file WAV baru untuk streaming write.
256    pub fn create<P: AsRef<Path>>(
257        path: P,
258        num_channels: u16,
259        sample_rate: u32,
260        options: WavWriteOptions,
261    ) -> io::Result<Self> {
262        let file = File::create(path)?;
263        let writer = BufWriter::new(file);
264        Self::new(writer, num_channels, sample_rate, options)
265    }
266}
267
268impl<W: Write + Seek> WavStreamWriter<W> {
269    /// Inisialisasi streaming writer dan menulis placeholder header 44-byte.
270    pub fn new(
271        mut writer: W,
272        num_channels: u16,
273        sample_rate: u32,
274        options: WavWriteOptions,
275    ) -> io::Result<Self> {
276        if num_channels == 0 {
277            return Err(io::Error::new(
278                io::ErrorKind::InvalidInput,
279                "Channels must be at least 1",
280            ));
281        }
282
283        // Tulis placeholder header
284        let header = WavHeader::new(num_channels, sample_rate, options.format, 0);
285        let bits_per_sample = header.bits_per_sample();
286        let block_align = num_channels * (bits_per_sample / 8);
287        let byte_rate = sample_rate * (block_align as u32);
288
289        // 1. RIFF descriptor placeholder
290        writer.write_all(b"RIFF")?;
291        writer.write_all(&0u32.to_le_bytes())?; // Placeholder ukuran RIFF
292        writer.write_all(b"WAVE")?;
293
294        // 2. fmt chunk
295        writer.write_all(b"fmt ")?;
296        writer.write_all(&16u32.to_le_bytes())?;
297        writer.write_all(&header.audio_format_tag().to_le_bytes())?;
298        writer.write_all(&num_channels.to_le_bytes())?;
299        writer.write_all(&sample_rate.to_le_bytes())?;
300        writer.write_all(&byte_rate.to_le_bytes())?;
301        writer.write_all(&block_align.to_le_bytes())?;
302        writer.write_all(&bits_per_sample.to_le_bytes())?;
303
304        // 3. data chunk placeholder
305        writer.write_all(b"data")?;
306        writer.write_all(&0u32.to_le_bytes())?; // Placeholder ukuran data
307
308        Ok(Self {
309            writer,
310            options,
311            num_channels,
312            sample_rate,
313            frames_written: 0,
314            dither_state: 0xA5A5_1F3D,
315        })
316    }
317
318    pub fn frames_written(&self) -> usize {
319        self.frames_written
320    }
321
322    fn uniform_random(&mut self) -> f32 {
323        self.dither_state = self
324            .dither_state
325            .wrapping_mul(1_664_525)
326            .wrapping_add(1_013_904_223);
327        self.dither_state as f32 / u32::MAX as f32
328    }
329
330    fn apply_dither(&mut self, sample: f32, scale: f32) -> f32 {
331        if self.options.dither == DitherMode::Tpdf {
332            (sample + (self.uniform_random() - self.uniform_random()) / scale).clamp(-1.0, 1.0)
333        } else {
334            sample
335        }
336    }
337
338    /// Menulis blok audio planar ke stream file.
339    pub fn write_block(&mut self, block: &AudioBlock<'_>) -> io::Result<()> {
340        self.write_block_frames(block, block.frames())
341    }
342
343    /// Menulis sejumlah `frame_count` dari `AudioBlock` ke stream file.
344    pub fn write_block_frames(
345        &mut self,
346        block: &AudioBlock<'_>,
347        frame_count: usize,
348    ) -> io::Result<()> {
349        if block.channels() != self.num_channels as usize {
350            return Err(io::Error::new(
351                io::ErrorKind::InvalidInput,
352                format!(
353                    "AudioBlock channels ({}) does not match writer channels ({})",
354                    block.channels(),
355                    self.num_channels
356                ),
357            ));
358        }
359
360        let frames = frame_count.min(block.frames());
361        let num_ch = self.num_channels as usize;
362
363        for frame in 0..frames {
364            for ch in 0..num_ch {
365                // AudioBlock::channel_mut butuh &mut, jadi dipakai indeks slice planar
366                // Borrow aman karena tiap channel berbeda
367                let sample_val =
368                    block.channel(ch).expect("validated channel count")[frame].clamp(-1.0, 1.0);
369
370                match self.options.format {
371                    WavSampleFormat::Pcm16Bit => {
372                        let dithered = self.apply_dither(sample_val, 32768.0);
373                        let val: i16 = if dithered < 0.0 {
374                            (dithered * 32768.0).round() as i16
375                        } else {
376                            (dithered * 32767.0).round() as i16
377                        };
378                        self.writer.write_all(&val.to_le_bytes())?;
379                    }
380                    WavSampleFormat::Pcm24Bit => {
381                        let dithered = self.apply_dither(sample_val, 8388608.0);
382                        let val: i32 = if dithered < 0.0 {
383                            (dithered * 8388608.0).round() as i32
384                        } else {
385                            (dithered * 8388607.0).round() as i32
386                        };
387                        let bytes = val.to_le_bytes();
388                        self.writer.write_all(&bytes[0..3])?;
389                    }
390                    WavSampleFormat::Pcm32BitFloat => {
391                        self.writer.write_all(&sample_val.to_le_bytes())?;
392                    }
393                }
394            }
395        }
396
397        self.frames_written += frames;
398        Ok(())
399    }
400
401    /// Menulis planar slices langsung.
402    pub fn write_planar(&mut self, channels: &[&[f32]]) -> io::Result<()> {
403        if channels.len() != self.num_channels as usize {
404            return Err(io::Error::new(
405                io::ErrorKind::InvalidInput,
406                "Channel count mismatch",
407            ));
408        }
409        if channels.is_empty() {
410            return Ok(());
411        }
412
413        let frames = channels[0].len();
414        for ch in channels {
415            if ch.len() != frames {
416                return Err(io::Error::new(
417                    io::ErrorKind::InvalidInput,
418                    "All channel slices must have the same length",
419                ));
420            }
421        }
422
423        for frame in 0..frames {
424            for channel in channels.iter() {
425                let sample_val = channel[frame].clamp(-1.0, 1.0);
426                match self.options.format {
427                    WavSampleFormat::Pcm16Bit => {
428                        let dithered = self.apply_dither(sample_val, 32768.0);
429                        let val: i16 = if dithered < 0.0 {
430                            (dithered * 32768.0).round() as i16
431                        } else {
432                            (dithered * 32767.0).round() as i16
433                        };
434                        self.writer.write_all(&val.to_le_bytes())?;
435                    }
436                    WavSampleFormat::Pcm24Bit => {
437                        let dithered = self.apply_dither(sample_val, 8388608.0);
438                        let val: i32 = if dithered < 0.0 {
439                            (dithered * 8388608.0).round() as i32
440                        } else {
441                            (dithered * 8388607.0).round() as i32
442                        };
443                        let bytes = val.to_le_bytes();
444                        self.writer.write_all(&bytes[0..3])?;
445                    }
446                    WavSampleFormat::Pcm32BitFloat => {
447                        self.writer.write_all(&sample_val.to_le_bytes())?;
448                    }
449                }
450            }
451        }
452
453        self.frames_written += frames;
454        Ok(())
455    }
456
457    /// Menutup stream, menulis ukuran final pada header RIFF dan data chunk, dan melakukan flush.
458    pub fn finalize(mut self) -> io::Result<WavHeader> {
459        let header = WavHeader::new(
460            self.num_channels,
461            self.sample_rate,
462            self.options.format,
463            self.frames_written,
464        );
465
466        let bits_per_sample = header.bits_per_sample();
467        let block_align = self.num_channels as u32 * (bits_per_sample as u32 / 8);
468        let data_chunk_size = (self.frames_written as u32) * block_align;
469        let riff_chunk_size = 36 + data_chunk_size;
470
471        // 1. Update ukuran RIFF (offset 4)
472        self.writer.seek(SeekFrom::Start(4))?;
473        self.writer.write_all(&riff_chunk_size.to_le_bytes())?;
474
475        // 2. Update ukuran data chunk (offset 40)
476        self.writer.seek(SeekFrom::Start(40))?;
477        self.writer.write_all(&data_chunk_size.to_le_bytes())?;
478
479        // 3. Posisikan ke akhir dan flush
480        self.writer.seek(SeekFrom::End(0))?;
481        self.writer.flush()?;
482
483        Ok(header)
484    }
485}