1use std::error::Error;
4use std::path::Path;
5
6pub fn save_wav_mono_i16(
15 path: &Path,
16 samples: &[f32],
17 sample_rate: u32,
18) -> Result<(), Box<dyn Error>> {
19 let spec = hound::WavSpec {
20 channels: 1,
21 sample_rate,
22 bits_per_sample: 16,
23 sample_format: hound::SampleFormat::Int,
24 };
25 let mut wr = hound::WavWriter::create(path, spec)?;
26 for &s in samples {
27 let v = (s.clamp(-1.0, 1.0) * (i16::MAX as f32)).round() as i16;
28 wr.write_sample(v)?;
29 }
30 wr.finalize()?;
31 Ok(())
32}
33
34pub fn load_wav_mono_f32(path: &Path) -> Result<(Vec<f32>, u32), Box<dyn Error>> {
41 let mut rd = hound::WavReader::open(path)?;
42 let spec = rd.spec();
43 if spec.channels != 1 {
44 return Err("only mono WAV is supported".into());
45 }
46 let sr = spec.sample_rate;
47 let samples = match (spec.sample_format, spec.bits_per_sample) {
48 (hound::SampleFormat::Int, 16) => rd
49 .samples::<i16>()
50 .map(|r| r.map(|v| v as f32 / i16::MAX as f32))
51 .collect::<Result<Vec<_>, _>>()?,
52 _ => return Err("unsupported WAV format (expected 16-bit PCM mono)".into()),
53 };
54 Ok((samples, sr))
55}
56
57#[cfg(test)]
58mod tests {
59 use super::{load_wav_mono_f32, save_wav_mono_i16};
60 use std::path::PathBuf;
61 use std::time::{SystemTime, UNIX_EPOCH};
62
63 fn tmp_wav_path() -> PathBuf {
70 let ts = SystemTime::now()
71 .duration_since(UNIX_EPOCH)
72 .expect("clock drift")
73 .as_nanos();
74 std::env::temp_dir().join(format!("acoustic_ofdm_wav_test_{ts}.wav"))
75 }
76
77 #[test]
78 fn wav_roundtrip_mono_i16() {
80 let path = tmp_wav_path();
81 let src = vec![0.0f32, 0.25, -0.25, 0.9, -0.9, 0.1, -0.1];
82 save_wav_mono_i16(&path, &src, 48_000).expect("write wav");
83 let (dst, sr) = load_wav_mono_f32(&path).expect("read wav");
84 assert_eq!(sr, 48_000);
85 assert_eq!(dst.len(), src.len());
86 for (a, b) in src.iter().zip(dst.iter()) {
87 assert!((a - b).abs() < 2.0 / i16::MAX as f32);
88 }
89 let _ = std::fs::remove_file(path);
90 }
91}
92
93