Skip to main content

rescale_core/wav/
writer.rs

1//! Binary Writer untuk menulis file RIFF WAV dari raw f32 buffer.
2
3use super::header::{WavHeader, WavSampleFormat};
4use std::fs::File;
5use std::io::{self, BufWriter, Write};
6use std::path::Path;
7
8pub struct WavWriter;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum DitherMode {
12    None,
13    Tpdf,
14}
15
16#[derive(Debug, Clone, Copy)]
17pub struct WavWriteOptions {
18    pub format: WavSampleFormat,
19    pub dither: DitherMode,
20}
21
22impl WavWriteOptions {
23    pub const fn new(format: WavSampleFormat, dither: DitherMode) -> Self {
24        Self { format, dither }
25    }
26}
27
28impl WavWriter {
29    /// Menulis audio channel data (f32 normalized [-1.0, 1.0]) ke file WAV.
30    /// Default dither: TPDF (hanya berlaku pada format PCM; float 32-bit ditulis langsung).
31    pub fn write_to_file<P: AsRef<Path>>(
32        path: P,
33        channels: &[Vec<f32>],
34        sample_rate: u32,
35        format: WavSampleFormat,
36    ) -> io::Result<()> {
37        Self::write_to_file_with_options(
38            path,
39            channels,
40            sample_rate,
41            WavWriteOptions::new(format, DitherMode::Tpdf),
42        )
43    }
44
45    pub fn write_to_file_with_options<P: AsRef<Path>>(
46        path: P,
47        channels: &[Vec<f32>],
48        sample_rate: u32,
49        options: WavWriteOptions,
50    ) -> io::Result<()> {
51        let file = File::create(path)?;
52        let mut writer = BufWriter::new(file);
53        Self::write_to_stream_with_options(&mut writer, channels, sample_rate, options)
54    }
55
56    /// Menulis audio ke writer stream generic.
57    /// Default dither: TPDF (hanya berlaku pada format PCM; float 32-bit ditulis langsung).
58    pub fn write_to_stream<W: Write>(
59        writer: &mut W,
60        channels: &[Vec<f32>],
61        sample_rate: u32,
62        format: WavSampleFormat,
63    ) -> io::Result<()> {
64        Self::write_to_stream_with_options(
65            writer,
66            channels,
67            sample_rate,
68            WavWriteOptions::new(format, DitherMode::Tpdf),
69        )
70    }
71
72    pub fn write_to_stream_with_options<W: Write>(
73        writer: &mut W,
74        channels: &[Vec<f32>],
75        sample_rate: u32,
76        options: WavWriteOptions,
77    ) -> io::Result<()> {
78        if channels.is_empty() {
79            return Err(io::Error::new(
80                io::ErrorKind::InvalidInput,
81                "Channels cannot be empty",
82            ));
83        }
84
85        let num_channels = channels.len() as u16;
86        let num_samples_per_channel = channels[0].len();
87
88        // Validasi panjang tiap channel sama
89        for ch in channels {
90            if ch.len() != num_samples_per_channel {
91                return Err(io::Error::new(
92                    io::ErrorKind::InvalidInput,
93                    "All channels must have the same length",
94                ));
95            }
96        }
97
98        let header = WavHeader::new(
99            num_channels,
100            sample_rate,
101            options.format,
102            num_samples_per_channel,
103        );
104        let bits_per_sample = header.bits_per_sample();
105        let _bytes_per_sample = (bits_per_sample / 8) as u32;
106        let block_align = num_channels * (bits_per_sample / 8);
107        let byte_rate = sample_rate * (block_align as u32);
108        let data_chunk_size = (num_samples_per_channel as u32) * (block_align as u32);
109        let riff_chunk_size = 36 + data_chunk_size;
110
111        // 1. Deskriptor Chunk RIFF
112        writer.write_all(b"RIFF")?;
113        writer.write_all(&riff_chunk_size.to_le_bytes())?;
114        writer.write_all(b"WAVE")?;
115
116        // 2. Subchunk "fmt "
117        writer.write_all(b"fmt ")?;
118        writer.write_all(&16u32.to_le_bytes())?; // Subchunk1Size = 16 untuk PCM
119        writer.write_all(&header.audio_format_tag().to_le_bytes())?;
120        writer.write_all(&num_channels.to_le_bytes())?;
121        writer.write_all(&sample_rate.to_le_bytes())?;
122        writer.write_all(&byte_rate.to_le_bytes())?;
123        writer.write_all(&block_align.to_le_bytes())?;
124        writer.write_all(&bits_per_sample.to_le_bytes())?;
125
126        // 3. Subchunk "data"
127        writer.write_all(b"data")?;
128        writer.write_all(&data_chunk_size.to_le_bytes())?;
129
130        // Interleaving sample: L0, R0, L1, R1, ...
131        let mut dither = TpdfDither::new();
132        for i in 0..num_samples_per_channel {
133            for channel in channels.iter() {
134                let sample = channel[i].clamp(-1.0, 1.0);
135                match options.format {
136                    WavSampleFormat::Pcm16Bit => {
137                        let sample = dither.apply(sample, options.dither, 32768.0);
138                        let val: i16 = if sample < 0.0 {
139                            (sample * 32768.0).round() as i16
140                        } else {
141                            (sample * 32767.0).round() as i16
142                        };
143                        writer.write_all(&val.to_le_bytes())?;
144                    }
145                    WavSampleFormat::Pcm24Bit => {
146                        let sample = dither.apply(sample, options.dither, 8388608.0);
147                        let val: i32 = if sample < 0.0 {
148                            (sample * 8388608.0).round() as i32
149                        } else {
150                            (sample * 8388607.0).round() as i32
151                        };
152                        let bytes = val.to_le_bytes();
153                        // Tulis 3 byte (little endian)
154                        writer.write_all(&bytes[0..3])?;
155                    }
156                    WavSampleFormat::Pcm32BitFloat => {
157                        writer.write_all(&sample.to_le_bytes())?;
158                    }
159                }
160            }
161        }
162
163        writer.flush()?;
164        Ok(())
165    }
166}
167
168struct TpdfDither(u32);
169
170impl TpdfDither {
171    fn new() -> Self {
172        Self(0xA5A5_1F3D)
173    }
174
175    fn uniform(&mut self) -> f32 {
176        self.0 = self.0.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
177        self.0 as f32 / u32::MAX as f32
178    }
179
180    fn apply(&mut self, sample: f32, mode: DitherMode, scale: f32) -> f32 {
181        if mode == DitherMode::Tpdf {
182            (sample + (self.uniform() - self.uniform()) / scale).clamp(-1.0, 1.0)
183        } else {
184            sample
185        }
186    }
187}