oxihuman_export/
wav_export.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9pub struct WavConfig {
10 pub sample_rate: u32,
11 pub channels: u16,
12 pub bits_per_sample: u16,
13}
14
15impl Default for WavConfig {
16 fn default() -> Self {
17 Self {
18 sample_rate: 44100,
19 channels: 1,
20 bits_per_sample: 16,
21 }
22 }
23}
24
25#[allow(dead_code)]
27pub struct WavFile {
28 pub config: WavConfig,
29 pub samples: Vec<i16>,
30}
31
32#[allow(dead_code)]
34pub fn build_wav_header(config: &WavConfig, data_bytes: u32) -> Vec<u8> {
35 let byte_rate =
36 config.sample_rate * config.channels as u32 * (config.bits_per_sample as u32 / 8);
37 let block_align = config.channels * (config.bits_per_sample / 8);
38 let file_size = 36 + data_bytes;
39 let mut hdr = Vec::with_capacity(44);
40 hdr.extend_from_slice(b"RIFF");
41 hdr.extend_from_slice(&file_size.to_le_bytes());
42 hdr.extend_from_slice(b"WAVE");
43 hdr.extend_from_slice(b"fmt ");
44 hdr.extend_from_slice(&16u32.to_le_bytes());
45 hdr.extend_from_slice(&1u16.to_le_bytes());
46 hdr.extend_from_slice(&config.channels.to_le_bytes());
47 hdr.extend_from_slice(&config.sample_rate.to_le_bytes());
48 hdr.extend_from_slice(&byte_rate.to_le_bytes());
49 hdr.extend_from_slice(&block_align.to_le_bytes());
50 hdr.extend_from_slice(&config.bits_per_sample.to_le_bytes());
51 hdr.extend_from_slice(b"data");
52 hdr.extend_from_slice(&data_bytes.to_le_bytes());
53 hdr
54}
55
56#[allow(dead_code)]
58pub fn encode_samples_i16(samples: &[i16]) -> Vec<u8> {
59 samples.iter().flat_map(|s| s.to_le_bytes()).collect()
60}
61
62#[allow(dead_code)]
64pub fn export_wav(wav: &WavFile) -> Vec<u8> {
65 let data = encode_samples_i16(&wav.samples);
66 let mut out = build_wav_header(&wav.config, data.len() as u32);
67 out.extend_from_slice(&data);
68 out
69}
70
71#[allow(dead_code)]
73pub fn silent_wav(config: WavConfig, duration_secs: f32) -> WavFile {
74 let n = (config.sample_rate as f32 * duration_secs * config.channels as f32) as usize;
75 WavFile {
76 config,
77 samples: vec![0i16; n],
78 }
79}
80
81#[allow(dead_code)]
83pub fn sine_wav(config: WavConfig, freq_hz: f32, duration_secs: f32, amplitude: f32) -> WavFile {
84 let n = (config.sample_rate as f32 * duration_secs) as usize;
85 let sr = config.sample_rate as f32;
86 let samples: Vec<i16> = (0..n)
87 .map(|i| {
88 let t = i as f32 / sr;
89 let v = amplitude * (2.0 * std::f32::consts::PI * freq_hz * t).sin();
90 (v * i16::MAX as f32) as i16
91 })
92 .collect();
93 WavFile { config, samples }
94}
95
96#[allow(dead_code)]
98pub fn wav_duration(wav: &WavFile) -> f32 {
99 if wav.config.sample_rate == 0 || wav.config.channels == 0 {
100 return 0.0;
101 }
102 wav.samples.len() as f32 / (wav.config.sample_rate as f32 * wav.config.channels as f32)
103}
104
105#[allow(dead_code)]
107pub fn wav_peak_amplitude(wav: &WavFile) -> i16 {
108 wav.samples.iter().map(|s| s.abs()).max().unwrap_or(0)
109}
110
111#[allow(dead_code)]
113pub fn wav_export_size(wav: &WavFile) -> usize {
114 44 + wav.samples.len() * 2
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn header_is_44_bytes() {
123 let config = WavConfig::default();
124 let hdr = build_wav_header(&config, 0);
125 assert_eq!(hdr.len(), 44);
126 }
127
128 #[test]
129 fn header_starts_with_riff() {
130 let config = WavConfig::default();
131 let hdr = build_wav_header(&config, 0);
132 assert_eq!(&hdr[0..4], b"RIFF");
133 }
134
135 #[test]
136 fn encode_samples_byte_length() {
137 let samples = vec![0i16, 100, -100, i16::MAX];
138 let bytes = encode_samples_i16(&samples);
139 assert_eq!(bytes.len(), 8);
140 }
141
142 #[test]
143 fn export_wav_total_size() {
144 let wav = silent_wav(WavConfig::default(), 0.01);
145 let out = export_wav(&wav);
146 assert_eq!(out.len(), wav_export_size(&wav));
147 }
148
149 #[test]
150 fn silent_wav_all_zeros() {
151 let wav = silent_wav(WavConfig::default(), 0.01);
152 assert!(wav.samples.iter().all(|&s| s == 0));
153 }
154
155 #[test]
156 fn sine_wav_nonzero() {
157 let wav = sine_wav(WavConfig::default(), 440.0, 0.01, 0.5);
158 assert!(wav_peak_amplitude(&wav) > 0);
159 }
160
161 #[test]
162 fn wav_duration_correct() {
163 let config = WavConfig::default();
164 let dur = 0.5;
165 let wav = silent_wav(config, dur);
166 assert!((wav_duration(&wav) - dur).abs() < 0.001);
167 }
168
169 #[test]
170 fn wav_export_size_formula() {
171 let wav = silent_wav(WavConfig::default(), 0.01);
172 assert_eq!(wav_export_size(&wav), 44 + wav.samples.len() * 2);
173 }
174
175 #[test]
176 fn wave_contains_wave_marker() {
177 let config = WavConfig::default();
178 let hdr = build_wav_header(&config, 0);
179 assert_eq!(&hdr[8..12], b"WAVE");
180 }
181
182 #[test]
183 fn default_config_correct() {
184 let c = WavConfig::default();
185 assert_eq!(c.sample_rate, 44100);
186 assert_eq!(c.channels, 1);
187 assert_eq!(c.bits_per_sample, 16);
188 }
189}