Skip to main content

piper_plus/
audio.rs

1use std::path::Path;
2
3use crate::error::PiperError;
4
5/// float32 音声データを int16 に変換 (ピーク正規化)
6/// C++ 版 piper.cpp と同じアルゴリズム
7pub fn audio_float_to_int16(audio: &[f32]) -> Vec<i16> {
8    if audio.is_empty() {
9        return Vec::new();
10    }
11
12    // ピーク値を検出 (最小値 0.01 でゼロ除算防止)
13    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
24/// WAV ファイルを書き出す
25pub 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
49/// WAV データを stdout にバイナリで書き出す (パイプ用)
50pub 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    // WAV ヘッダ (44 bytes)
56    let data_size = (audio.len() * 2) as u32;
57    let file_size = data_size + 36;
58
59    // RIFF header
60    stdout.write_all(b"RIFF")?;
61    stdout.write_all(&file_size.to_le_bytes())?;
62    stdout.write_all(b"WAVE")?;
63
64    // fmt chunk
65    stdout.write_all(b"fmt ")?;
66    stdout.write_all(&16u32.to_le_bytes())?; // chunk size
67    stdout.write_all(&1u16.to_le_bytes())?; // PCM format
68    stdout.write_all(&1u16.to_le_bytes())?; // mono
69    stdout.write_all(&sample_rate.to_le_bytes())?;
70    stdout.write_all(&(sample_rate * 2).to_le_bytes())?; // byte rate
71    stdout.write_all(&2u16.to_le_bytes())?; // block align
72    stdout.write_all(&16u16.to_le_bytes())?; // bits per sample
73
74    // data chunk
75    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
87/// Write raw PCM int16 samples to any writer (little-endian).
88///
89/// This is the testable core of `write_raw_to_stdout`.
90pub 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
100/// raw PCM int16 データを stdout にバイナリで書き出す (WAV ヘッダなし)
101pub 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        // 最大値 1.0 → 32767 にスケール
124        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]; // 範囲外の値
137        let result = audio_float_to_int16(&audio);
138        // ピーク正規化で 2.0 → 32767, -2.0 → -32767
139        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        // 0x0100 in LE = [0x00, 0x01]
149        // -1 in LE = [0xFF, 0xFF]
150        // 0 in LE = [0x00, 0x00]
151        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}