gemini_adk_fluent_rs/voice/dsp/
resample.rs1use std::collections::VecDeque;
17
18use rubato::{
19 Async, FixedAsync, Resampler, SincInterpolationParameters, SincInterpolationType,
20 WindowFunction, audioadapter_buffers::direct::SequentialSliceOfVecs,
21};
22
23use super::{AudioBus, DspStage};
24
25pub struct SincResampler {
27 inner: Async<f32>,
28 from_hz: u32,
29 to_hz: u32,
30 chunk: usize,
31 input_fifo: VecDeque<f32>,
32 in_scratch: Vec<Vec<f32>>,
33 out_scratch: Vec<Vec<f32>>,
34 output_fifo: VecDeque<f32>,
35 latency_out: usize,
36 in_total: u64,
38 out_total: u64,
40}
41
42impl SincResampler {
43 pub fn new(from_hz: u32, to_hz: u32) -> Self {
46 let chunk = (from_hz / 100).max(1) as usize; let params = SincInterpolationParameters {
48 sinc_len: 128,
49 f_cutoff: Some(0.95),
50 interpolation: SincInterpolationType::Linear,
51 oversampling_factor: 128,
52 window: WindowFunction::BlackmanHarris2,
53 };
54 let inner = Async::<f32>::new_sinc(
57 f64::from(to_hz) / f64::from(from_hz),
58 1.0,
59 ¶ms,
60 chunk,
61 1,
62 FixedAsync::Input,
63 )
64 .expect("valid resampler parameters");
65 let latency_out = inner.output_delay();
66 let in_scratch = vec![vec![0.0f32; chunk]];
67 let out_scratch = vec![vec![0.0f32; inner.output_frames_max()]];
68 Self {
69 inner,
70 from_hz,
71 to_hz,
72 chunk,
73 input_fifo: VecDeque::with_capacity(chunk * 4),
74 in_scratch,
75 out_scratch,
76 output_fifo: VecDeque::with_capacity(chunk * 4),
77 latency_out,
78 in_total: 0,
79 out_total: 0,
80 }
81 }
82}
83
84impl DspStage for SincResampler {
85 fn name(&self) -> &'static str {
86 "resample"
87 }
88
89 fn process(&mut self, bus: &mut AudioBus) {
90 debug_assert_eq!(
91 bus.sample_rate, self.from_hz,
92 "SincResampler built for {} Hz fed {} Hz",
93 self.from_hz, bus.sample_rate
94 );
95 let in_len = bus.samples.len();
96 self.input_fifo.extend(bus.samples.iter().copied());
97
98 while self.input_fifo.len() >= self.chunk {
99 for slot in self.in_scratch[0].iter_mut() {
100 *slot = self.input_fifo.pop_front().unwrap_or(0.0);
101 }
102 let input = SequentialSliceOfVecs::new(&self.in_scratch, 1, self.chunk)
105 .expect("mono input scratch");
106 let output_capacity = self.out_scratch[0].len();
107 let mut output =
108 SequentialSliceOfVecs::new_mut(&mut self.out_scratch, 1, output_capacity)
109 .expect("mono output scratch");
110 let (consumed, out_len) = self
111 .inner
112 .process_into_buffer(&input, &mut output, None)
113 .expect("fixed-size chunk");
114 debug_assert_eq!(consumed, self.chunk);
115 self.output_fifo
116 .extend(self.out_scratch[0][..out_len].iter().copied());
117 }
118
119 self.in_total += in_len as u64;
126 let due = self.in_total * u64::from(self.to_hz) / u64::from(self.from_hz);
127 let want = due.saturating_sub(self.out_total) as usize;
128 bus.samples.clear();
129 let take = want.min(self.output_fifo.len());
130 for _ in 0..take {
131 bus.samples
132 .push(self.output_fifo.pop_front().unwrap_or(0.0));
133 }
134 self.out_total += take as u64;
135 bus.sample_rate = self.to_hz;
136 }
137
138 fn latency_samples(&self) -> usize {
139 self.latency_out
142 + (self.chunk as u64 * u64::from(self.to_hz) / u64::from(self.from_hz)) as usize
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 fn sine(hz: f32, rate: u32, seconds: f32, amp: f32) -> Vec<f32> {
151 let n = (rate as f32 * seconds) as usize;
152 (0..n)
153 .map(|i| amp * (2.0 * std::f32::consts::PI * hz * i as f32 / rate as f32).sin())
154 .collect()
155 }
156
157 fn run_in_chunks(stage: &mut SincResampler, input: &[f32], from_hz: u32) -> Vec<f32> {
158 let mut out = Vec::new();
159 let mut buf = Vec::new();
160 for chunk in input.chunks(173) {
161 buf.clear();
162 buf.extend_from_slice(chunk);
163 let mut bus = AudioBus {
164 samples: &mut buf,
165 sample_rate: from_hz,
166 };
167 stage.process(&mut bus);
168 out.extend_from_slice(&buf);
169 }
170 out
171 }
172
173 #[test]
174 fn preserves_tone_across_48k_to_16k() {
175 let mut stage = SincResampler::new(48_000, 16_000);
176 let input = sine(1_000.0, 48_000, 1.0, 0.5);
177 let out = run_in_chunks(&mut stage, &input, 48_000);
178 let skip = stage.latency_samples() * 2;
180 let tail = &out[skip..];
181 let rms = (tail.iter().map(|s| s * s).sum::<f32>() / tail.len() as f32).sqrt();
182 let expected = 0.5 / std::f32::consts::SQRT_2;
183 assert!(
184 (rms - expected).abs() / expected < 0.05,
185 "rms {rms} vs {expected}"
186 );
187 }
188
189 #[test]
190 fn rejects_content_above_target_nyquist() {
191 let mut stage = SincResampler::new(48_000, 16_000);
195 let input = sine(12_000.0, 48_000, 1.0, 0.5);
196 let out = run_in_chunks(&mut stage, &input, 48_000);
197 let skip = stage.latency_samples() * 2;
198 let tail = &out[skip..];
199 let rms = (tail.iter().map(|s| s * s).sum::<f32>() / tail.len() as f32).sqrt();
200 assert!(rms < 0.01, "alias energy leaked: rms {rms}");
201 }
202
203 #[test]
204 fn tiny_blocks_still_emit_the_full_rate_share() {
205 let mut stage = SincResampler::new(48_000, 16_000);
209 let mut emitted = 0usize;
210 let mut buf = Vec::with_capacity(1);
211 for _ in 0..48_000 {
212 buf.clear();
213 buf.push(0.25f32);
214 let mut bus = AudioBus {
215 samples: &mut buf,
216 sample_rate: 48_000,
217 };
218 stage.process(&mut bus);
219 emitted += buf.len();
220 }
221 let expected = 16_000usize;
222 assert!(
223 (emitted as i64 - expected as i64).unsigned_abs() as usize
224 <= stage.latency_samples() + 320,
225 "emitted {emitted} of ~{expected}"
226 );
227 }
228
229 #[test]
230 fn output_rate_and_length_track_ratio() {
231 let mut stage = SincResampler::new(48_000, 16_000);
232 let input = vec![0.0f32; 48_000];
233 let out = run_in_chunks(&mut stage, &input, 48_000);
234 let expected = 16_000usize;
235 assert!(
236 (out.len() as i64 - expected as i64).unsigned_abs() as usize
237 <= stage.latency_samples() + 320,
238 "len {} vs ~{expected}",
239 out.len()
240 );
241 }
242
243 fn run_partitioned(from: u32, to: u32, input: &[f32], sizes: &[usize]) -> Vec<f32> {
244 let mut stage = SincResampler::new(from, to);
245 let mut out = Vec::new();
246 let mut offset = 0;
247 let mut call = 0;
248 while offset < input.len() {
249 let end = (offset + sizes[call % sizes.len()]).min(input.len());
250 let mut samples = input[offset..end].to_vec();
251 let mut bus = AudioBus {
252 samples: &mut samples,
253 sample_rate: from,
254 };
255 stage.process(&mut bus);
256 assert_eq!(bus.sample_rate, to);
257 out.extend_from_slice(&samples);
258 offset = end;
259 call += 1;
260 assert_eq!(stage.in_total, offset as u64);
261 assert_eq!(stage.out_total, out.len() as u64);
262 assert!(stage.out_total <= stage.in_total * u64::from(to) / u64::from(from));
263 assert!(stage.input_fifo.len() < stage.chunk);
264 assert!(stage.output_fifo.len() <= stage.inner.output_frames_max());
265
266 let before = (stage.in_total, stage.out_total, stage.input_fifo.len());
268 let mut empty = Vec::new();
269 stage.process(&mut AudioBus {
270 samples: &mut empty,
271 sample_rate: from,
272 });
273 assert!(empty.is_empty());
274 assert_eq!(
275 before,
276 (stage.in_total, stage.out_total, stage.input_fifo.len())
277 );
278 }
279 let due = input.len() as u64 * u64::from(to) / u64::from(from);
280 assert!(due - stage.out_total <= stage.latency_samples() as u64);
281 out
282 }
283
284 #[test]
285 fn streaming_is_invariant_to_input_partitioning() {
286 for (from, to) in [
289 (48_000, 16_000),
290 (16_000, 48_000),
291 (44_100, 16_000),
292 (48_000, 44_100),
293 (16_000, 16_000),
294 ] {
295 let mut input = sine(997.0, from, 1.0, 0.5);
296 input.extend([0.2; 37]);
297 let reference = run_partitioned(from, to, &input, &[input.len()]);
298 for sizes in [&[1][..], &[173][..], &[7, 0, 479, 1, 2049, 13][..]] {
299 let actual = run_partitioned(from, to, &input, sizes);
300 assert_eq!(actual, reference, "partition {sizes:?}, {from} -> {to}");
301 }
302 }
303 }
304
305 #[test]
306 fn impulse_delay_and_filter_configuration_are_preserved() {
307 for (from, to) in [(48_000, 16_000), (16_000, 48_000), (44_100, 16_000)] {
308 let mut stage = SincResampler::new(from, to);
309 assert_eq!(stage.inner.input_frames_next(), stage.chunk);
310 let cutoff = 0.95 * (f64::from(to) / f64::from(from)).min(1.0) as f32;
311 assert!((stage.inner.cutoff().unwrap() - cutoff).abs() < 1e-6);
312 let delay = (64.0 * f64::from(to) / f64::from(from)) as usize;
313 assert_eq!(stage.latency_out, delay);
314 assert_eq!(
315 stage.latency_samples(),
316 delay + stage.chunk * to as usize / from as usize
317 );
318 let mut input = vec![0.0; from as usize / 10];
319 let impulse_at = stage.chunk * 2;
321 input[impulse_at] = 1.0;
322 let out = run_in_chunks(&mut stage, &input, from);
323 let peak = out
324 .iter()
325 .enumerate()
326 .max_by(|a, b| a.1.abs().total_cmp(&b.1.abs()))
327 .unwrap()
328 .0;
329 let expected = impulse_at * to as usize / from as usize + delay;
330 assert!(
331 peak.abs_diff(expected) <= 1,
332 "peak {peak}, expected {expected}"
333 );
334 }
335 }
336}