1use std::path::Path;
2
3use crate::error::PiperError;
4
5pub fn audio_float_to_int16(audio: &[f32]) -> Vec<i16> {
8 if audio.is_empty() {
9 return Vec::new();
10 }
11
12 let max_val = audio.iter().map(|x| x.abs()).fold(0.01f32, f32::max);
14
15 let scale = 32767.0 / max_val;
16
17 let mut result = Vec::with_capacity(audio.len());
18 for &x in audio {
19 result.push((x * scale).clamp(-32768.0, 32767.0) as i16);
20 }
21 result
22}
23
24pub fn write_wav(path: &Path, sample_rate: u32, audio: &[i16]) -> Result<(), PiperError> {
26 let spec = hound::WavSpec {
27 channels: 1,
28 sample_rate,
29 bits_per_sample: 16,
30 sample_format: hound::SampleFormat::Int,
31 };
32
33 let mut writer =
34 hound::WavWriter::create(path, spec).map_err(|e| PiperError::WavWrite(e.to_string()))?;
35
36 for &sample in audio {
37 writer
38 .write_sample(sample)
39 .map_err(|e| PiperError::WavWrite(e.to_string()))?;
40 }
41
42 writer
43 .finalize()
44 .map_err(|e| PiperError::WavWrite(e.to_string()))?;
45
46 Ok(())
47}
48
49pub fn write_wav_to_stdout(sample_rate: u32, audio: &[i16]) -> Result<(), PiperError> {
51 use std::io::Write;
52
53 let mut stdout = std::io::stdout().lock();
54
55 let data_size = (audio.len() * 2) as u32;
57 let file_size = data_size + 36;
58
59 stdout.write_all(b"RIFF")?;
61 stdout.write_all(&file_size.to_le_bytes())?;
62 stdout.write_all(b"WAVE")?;
63
64 stdout.write_all(b"fmt ")?;
66 stdout.write_all(&16u32.to_le_bytes())?; stdout.write_all(&1u16.to_le_bytes())?; stdout.write_all(&1u16.to_le_bytes())?; stdout.write_all(&sample_rate.to_le_bytes())?;
70 stdout.write_all(&(sample_rate * 2).to_le_bytes())?; stdout.write_all(&2u16.to_le_bytes())?; stdout.write_all(&16u16.to_le_bytes())?; stdout.write_all(b"data")?;
76 stdout.write_all(&data_size.to_le_bytes())?;
77 let mut buf = Vec::with_capacity(audio.len() * 2);
78 for &sample in audio {
79 buf.extend_from_slice(&sample.to_le_bytes());
80 }
81 stdout.write_all(&buf)?;
82
83 stdout.flush()?;
84 Ok(())
85}
86
87pub fn write_raw_pcm(writer: &mut impl std::io::Write, samples: &[i16]) -> Result<(), PiperError> {
91 let mut buf = Vec::with_capacity(samples.len() * 2);
92 for &sample in samples {
93 buf.extend_from_slice(&sample.to_le_bytes());
94 }
95 writer.write_all(&buf)?;
96 writer.flush()?;
97 Ok(())
98}
99
100pub fn write_raw_to_stdout(audio: &[i16]) -> Result<(), PiperError> {
102 let mut stdout = std::io::stdout().lock();
103 write_raw_pcm(&mut stdout, audio)
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_audio_float_to_int16_silence() {
112 let audio = vec![0.0f32; 100];
113 let result = audio_float_to_int16(&audio);
114 assert_eq!(result.len(), 100);
115 assert!(result.iter().all(|&x| x == 0));
116 }
117
118 #[test]
119 fn test_audio_float_to_int16_peak_normalization() {
120 let audio = vec![0.5f32, -0.5, 1.0, -1.0];
121 let result = audio_float_to_int16(&audio);
122 assert_eq!(result.len(), 4);
123 assert_eq!(result[2], 32767);
125 assert_eq!(result[3], -32767);
126 }
127
128 #[test]
129 fn test_audio_float_to_int16_empty() {
130 let result = audio_float_to_int16(&[]);
131 assert!(result.is_empty());
132 }
133
134 #[test]
135 fn test_audio_float_to_int16_clipping() {
136 let audio = vec![2.0f32, -2.0]; let result = audio_float_to_int16(&audio);
138 assert_eq!(result[0], 32767);
140 assert_eq!(result[1], -32767);
141 }
142
143 #[test]
144 fn test_write_raw_pcm_little_endian() {
145 let samples = vec![0x0100i16, -1i16, 0i16];
146 let mut buf = Vec::new();
147 write_raw_pcm(&mut buf, &samples).unwrap();
148 assert_eq!(buf, vec![0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00]);
152 }
153
154 #[test]
155 fn test_write_raw_pcm_empty() {
156 let mut buf = Vec::new();
157 write_raw_pcm(&mut buf, &[]).unwrap();
158 assert!(buf.is_empty());
159 }
160}