1use rubato::audioadapter_buffers::direct::InterleavedSlice;
10use rubato::{
11 Async, FixedAsync, Indexing, Resampler, SincInterpolationParameters, SincInterpolationType,
12 WindowFunction,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct PcmFormat {
18 pub sample_rate: u32,
20 pub channels: u16,
22}
23
24pub(crate) struct PcmConverter {
29 format: PcmFormat,
30 channels: usize,
31 resampler: Option<MonoResampler>,
33 remainder: Vec<f32>,
35 mono: Vec<f32>,
37}
38
39impl PcmConverter {
40 pub(crate) fn new(format: PcmFormat, target_rate: u32) -> Result<Self, String> {
45 let channels = usize::from(format.channels.max(1));
46 let resampler = if format.sample_rate == target_rate {
47 None
48 } else {
49 Some(MonoResampler::new(format.sample_rate, target_rate)?)
50 };
51 Ok(PcmConverter {
52 format,
53 channels,
54 resampler,
55 remainder: Vec::new(),
56 mono: Vec::new(),
57 })
58 }
59
60 pub(crate) fn matches(&self, format: PcmFormat) -> bool {
62 self.format == format
63 }
64
65 pub(crate) fn convert(
71 &mut self,
72 interleaved: &[f32],
73 out: &mut Vec<f32>,
74 ) -> Result<(), String> {
75 self.remainder.extend_from_slice(interleaved);
77 let frames = self.remainder.len() / self.channels;
78 let used = frames * self.channels;
79
80 self.mono.clear();
81 downmix_to_mono(&self.remainder[..used], self.channels, &mut self.mono);
82 self.remainder.drain(..used);
83
84 match &mut self.resampler {
85 None => out.extend_from_slice(&self.mono),
86 Some(rs) => rs.push(&self.mono, out)?,
87 }
88 Ok(())
89 }
90}
91
92fn downmix_to_mono(src: &[f32], channels: usize, out: &mut Vec<f32>) {
97 if channels <= 1 {
98 out.extend_from_slice(src);
99 return;
100 }
101 let inv = 1.0 / channels as f32;
102 for frame in src.chunks_exact(channels) {
103 let sum: f32 = frame.iter().sum();
104 out.push(sum * inv);
105 }
106}
107
108struct MonoResampler {
113 inner: Async<f32>,
114 chunk_in_frames: usize,
116 max_out_frames: usize,
118 in_accum: Vec<f32>,
120 out_scratch: Vec<f32>,
122}
123
124impl MonoResampler {
125 fn new(in_sr: u32, out_sr: u32) -> Result<Self, String> {
126 let ratio = out_sr as f64 / in_sr as f64;
127 let chunk_in_frames = (in_sr as usize / 50).max(64);
129
130 let params = SincInterpolationParameters {
131 sinc_len: 128,
132 f_cutoff: 0.95,
133 interpolation: SincInterpolationType::Linear,
134 oversampling_factor: 128,
135 window: WindowFunction::BlackmanHarris2,
136 };
137
138 let inner = Async::<f32>::new_sinc(
139 ratio,
140 1.0, ¶ms,
142 chunk_in_frames,
143 1, FixedAsync::Input,
145 )
146 .map_err(|e| format!("rubato sinc resampler construction failed: {e}"))?;
147
148 let max_out_frames = inner.output_frames_max();
149
150 Ok(MonoResampler {
151 inner,
152 chunk_in_frames,
153 max_out_frames,
154 in_accum: Vec::with_capacity(chunk_in_frames * 4),
155 out_scratch: vec![0.0; max_out_frames],
156 })
157 }
158
159 fn push(&mut self, mono: &[f32], out: &mut Vec<f32>) -> Result<(), String> {
162 self.in_accum.extend_from_slice(mono);
163 let step = self.chunk_in_frames; while self.in_accum.len() >= step {
166 let in_adapter = InterleavedSlice::new(&self.in_accum[..step], 1, self.chunk_in_frames)
167 .map_err(|e| format!("rubato interleaved input adapter failed: {e}"))?;
168 let mut out_adapter =
169 InterleavedSlice::new_mut(&mut self.out_scratch[..], 1, self.max_out_frames)
170 .map_err(|e| format!("rubato interleaved output adapter failed: {e}"))?;
171
172 let indexing = Indexing {
173 input_offset: 0,
174 output_offset: 0,
175 partial_len: None,
176 active_channels_mask: None,
177 };
178
179 let (_in_used, out_written) = self
180 .inner
181 .process_into_buffer(&in_adapter, &mut out_adapter, Some(&indexing))
182 .map_err(|e| format!("rubato process_into_buffer failed: {e}"))?;
183
184 out.extend_from_slice(&self.out_scratch[..out_written]); self.in_accum.drain(..step);
186 }
187 Ok(())
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use std::f32::consts::PI;
195
196 #[test]
197 fn downmix_stereo_is_lr_average() {
198 let src = [0.5, -0.5, 0.3, 0.3, 1.0, 0.0];
200 let mut out = Vec::new();
201 downmix_to_mono(&src, 2, &mut out);
202 assert_eq!(out, vec![0.0, 0.3, 0.5]);
203 }
204
205 #[test]
206 fn downmix_quad_is_channel_average() {
207 let src = [1.0, 2.0, 3.0, 4.0]; let mut out = Vec::new();
209 downmix_to_mono(&src, 4, &mut out);
210 assert_eq!(out, vec![2.5]);
211 }
212
213 #[test]
214 fn downmix_mono_is_copy() {
215 let src = [0.1, -0.2, 0.3];
216 let mut out = Vec::new();
217 downmix_to_mono(&src, 1, &mut out);
218 assert_eq!(out, src.to_vec());
219 }
220
221 #[test]
224 fn resample_48k_to_16k_preserves_tone() {
225 let mut conv = PcmConverter::new(
226 PcmFormat {
227 sample_rate: 48_000,
228 channels: 1,
229 },
230 16_000,
231 )
232 .unwrap();
233
234 let freq = 440.0_f32;
235 let amp = 0.5_f32;
236 let mut out = Vec::new();
237 let total = 48_000 * 2;
239 let mut i = 0usize;
240 while i < total {
241 let take = 441.min(total - i);
242 let block: Vec<f32> = (0..take)
243 .map(|k| (2.0 * PI * freq * ((i + k) as f32) / 48_000.0).sin() * amp)
244 .collect();
245 conv.convert(&block, &mut out).unwrap();
246 i += take;
247 }
248 assert!(out.len() >= 16_000, "1 秒以上の出力が必要: {}", out.len());
249
250 let mid = &out[4_000..4_000 + 16_000];
252
253 let crossings = zero_crossings(mid);
255 assert!(
256 (876..=884).contains(&crossings),
257 "16k リサンプル後の周波数がずれた: crossings={crossings}"
258 );
259
260 let got = rms(mid);
262 let expect = amp / std::f32::consts::SQRT_2;
263 assert!(
264 (got - expect).abs() < 0.02,
265 "16k リサンプル後の RMS がずれた: got={got} expect={expect}"
266 );
267 }
268
269 #[test]
272 fn same_rate_stereo_only_downmixes() {
273 let mut conv = PcmConverter::new(
274 PcmFormat {
275 sample_rate: 16_000,
276 channels: 2,
277 },
278 16_000,
279 )
280 .unwrap();
281 assert!(conv.resampler.is_none());
282 let mut out = Vec::new();
283 conv.convert(&[0.5, -0.5, 0.2, 0.2], &mut out).unwrap();
284 assert_eq!(out, vec![0.0, 0.2]);
285 }
286
287 #[test]
289 fn split_across_partial_frame_matches_bulk() {
290 let fmt = PcmFormat {
291 sample_rate: 16_000,
292 channels: 2,
293 };
294 let interleaved: Vec<f32> = (0..2000).map(|i| (i as f32) * 1e-3).collect();
295
296 let mut bulk = Vec::new();
297 PcmConverter::new(fmt, 16_000)
298 .unwrap()
299 .convert(&interleaved, &mut bulk)
300 .unwrap();
301
302 let mut split = Vec::new();
304 let mut conv = PcmConverter::new(fmt, 16_000).unwrap();
305 for chunk in interleaved.chunks(777) {
306 conv.convert(chunk, &mut split).unwrap();
307 }
308 assert_eq!(bulk, split);
309 }
310
311 fn rms(samples: &[f32]) -> f32 {
312 let sum_sq: f64 = samples.iter().map(|&x| (x as f64) * (x as f64)).sum();
313 (sum_sq / samples.len() as f64).sqrt() as f32
314 }
315
316 fn zero_crossings(samples: &[f32]) -> usize {
317 let mut n = 0;
318 for w in samples.windows(2) {
319 if (w[0] < 0.0 && w[1] >= 0.0) || (w[0] >= 0.0 && w[1] < 0.0) {
320 n += 1;
321 }
322 }
323 n
324 }
325}