rescale_core/wav/
writer.rs1use 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 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 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 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 writer.write_all(b"RIFF")?;
113 writer.write_all(&riff_chunk_size.to_le_bytes())?;
114 writer.write_all(b"WAVE")?;
115
116 writer.write_all(b"fmt ")?;
118 writer.write_all(&16u32.to_le_bytes())?; 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 writer.write_all(b"data")?;
128 writer.write_all(&data_chunk_size.to_le_bytes())?;
129
130 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 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}