gemini_adk_fluent_rs/voice/dsp/
mod.rs1#[cfg(feature = "dsp")]
45pub mod aec;
46#[cfg(feature = "dsp")]
47pub mod resample;
48pub mod stages;
49#[cfg(feature = "dsp")]
50pub mod stft;
51
52#[cfg(feature = "dsp")]
53pub use aec::{Aec, AecConfig, AecFarEnd};
54#[cfg(feature = "dsp")]
55pub use resample::SincResampler;
56pub use stages::{Agc, HighPass, Limiter};
57#[cfg(feature = "dsp")]
58pub use stft::{Identity, SpectralFloor, SpectralStage, Stft};
59
60use std::sync::Arc;
61use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
62
63use serde::Serialize;
64
65use gemini_adk_rs::live::InputAudioProcessor;
66
67pub struct AudioBus<'a> {
71 pub samples: &'a mut Vec<f32>,
73 pub sample_rate: u32,
75}
76
77pub trait DspStage: Send {
79 fn name(&self) -> &'static str;
81 fn process(&mut self, bus: &mut AudioBus);
83 fn latency_samples(&self) -> usize {
86 0
87 }
88}
89
90pub struct IntStage<P: InputAudioProcessor> {
95 inner: P,
96 name: &'static str,
97 scratch: Vec<i16>,
98 latency: usize,
99}
100
101impl<P: InputAudioProcessor> IntStage<P> {
102 pub fn named(inner: P, name: &'static str) -> Self {
104 Self {
105 inner,
106 name,
107 scratch: Vec::new(),
108 latency: 0,
109 }
110 }
111
112 pub fn with_latency(mut self, samples: usize) -> Self {
116 self.latency = samples;
117 self
118 }
119}
120
121impl<P: InputAudioProcessor> DspStage for IntStage<P> {
122 fn name(&self) -> &'static str {
123 self.name
124 }
125
126 fn latency_samples(&self) -> usize {
127 self.latency
128 }
129
130 fn process(&mut self, bus: &mut AudioBus) {
131 self.scratch.clear();
132 self.scratch.extend(
133 bus.samples
134 .iter()
135 .map(|&s| (s * 32768.0).round().clamp(-32768.0, 32767.0) as i16),
136 );
137 self.inner.process_frame(&mut self.scratch);
138 bus.samples.clear();
139 bus.samples
140 .extend(self.scratch.iter().map(|&s| f32::from(s) / 32768.0));
141 }
142}
143
144#[derive(Default)]
146struct StageMeter {
147 peak_out: AtomicU32,
149 rms_out: AtomicU32,
151 blocks: AtomicU64,
153}
154
155#[derive(Debug, Clone, Serialize)]
157pub struct StageSnapshot {
158 pub name: &'static str,
160 pub peak_out: f32,
162 pub rms_out: f32,
164 pub latency_samples: usize,
166}
167
168#[derive(Debug, Clone, Serialize)]
170pub struct ChainSnapshot {
171 pub stages: Vec<StageSnapshot>,
173 pub exit_clipped: u64,
175 pub total_latency_samples: usize,
177 pub blocks: u64,
179}
180
181#[derive(Clone)]
183pub struct ChainMetrics {
184 inner: Arc<MetricsInner>,
185}
186
187struct MetricsInner {
188 meters: Vec<StageMeter>,
189 names: Vec<&'static str>,
190 latencies: Vec<usize>,
191 exit_clipped: AtomicU64,
192 blocks: AtomicU64,
193}
194
195impl ChainMetrics {
196 pub fn snapshot(&self) -> ChainSnapshot {
198 let stages = self
199 .inner
200 .meters
201 .iter()
202 .zip(&self.inner.names)
203 .zip(&self.inner.latencies)
204 .map(|((meter, name), &latency)| StageSnapshot {
205 name,
206 peak_out: f32::from_bits(meter.peak_out.load(Ordering::Relaxed)),
207 rms_out: f32::from_bits(meter.rms_out.load(Ordering::Relaxed)),
208 latency_samples: latency,
209 })
210 .collect();
211 ChainSnapshot {
212 stages,
213 exit_clipped: self.inner.exit_clipped.load(Ordering::Relaxed),
214 total_latency_samples: self.inner.latencies.iter().sum(),
215 blocks: self.inner.blocks.load(Ordering::Relaxed),
216 }
217 }
218}
219
220pub struct DspChain {
223 stages: Vec<Box<dyn DspStage>>,
224 sample_rate: u32,
225 bus: Vec<f32>,
226 metrics: Option<ChainMetrics>,
227}
228
229impl DspChain {
230 pub fn new(sample_rate: u32) -> Self {
232 Self {
233 stages: Vec::new(),
234 sample_rate,
235 bus: Vec::new(),
236 metrics: None,
237 }
238 }
239
240 pub fn stage(mut self, stage: impl DspStage + 'static) -> Self {
242 self.stages.push(Box::new(stage));
243 self.metrics = None; self
245 }
246
247 pub fn metrics(&mut self) -> ChainMetrics {
249 self.ensure_metrics();
250 self.metrics.as_ref().expect("just built").clone()
251 }
252
253 pub fn total_latency_samples(&self) -> usize {
255 self.stages.iter().map(|s| s.latency_samples()).sum()
256 }
257
258 fn ensure_metrics(&mut self) {
259 if self.metrics.is_none() {
260 self.metrics = Some(ChainMetrics {
261 inner: Arc::new(MetricsInner {
262 meters: self.stages.iter().map(|_| StageMeter::default()).collect(),
263 names: self.stages.iter().map(|s| s.name()).collect(),
264 latencies: self.stages.iter().map(|s| s.latency_samples()).collect(),
265 exit_clipped: AtomicU64::new(0),
266 blocks: AtomicU64::new(0),
267 }),
268 });
269 }
270 }
271}
272
273impl InputAudioProcessor for DspChain {
274 fn process_frame(&mut self, frame: &mut Vec<i16>) {
275 self.ensure_metrics();
276 self.bus.clear();
278 self.bus
279 .extend(frame.iter().map(|&s| f32::from(s) / 32768.0));
280
281 let metrics = self.metrics.as_ref().expect("ensured").inner.clone();
282 let mut rate = self.sample_rate;
286 for (stage, meter) in self.stages.iter_mut().zip(&metrics.meters) {
287 let mut bus = AudioBus {
288 samples: &mut self.bus,
289 sample_rate: rate,
290 };
291 stage.process(&mut bus);
292 rate = bus.sample_rate;
293
294 let mut peak = 0.0f32;
296 let mut energy = 0.0f64;
297 for &s in self.bus.iter() {
298 peak = peak.max(s.abs());
299 energy += f64::from(s) * f64::from(s);
300 }
301 let rms = if self.bus.is_empty() {
302 0.0
303 } else {
304 (energy / self.bus.len() as f64).sqrt() as f32
305 };
306 meter.peak_out.fetch_max(peak.to_bits(), Ordering::Relaxed);
307 let prev = f32::from_bits(meter.rms_out.load(Ordering::Relaxed));
308 let ewma = if meter.blocks.load(Ordering::Relaxed) == 0 {
309 rms
310 } else {
311 prev * 0.9 + rms * 0.1
312 };
313 meter.rms_out.store(ewma.to_bits(), Ordering::Relaxed);
314 meter.blocks.fetch_add(1, Ordering::Relaxed);
315 }
316
317 let mut clipped = 0u64;
319 frame.clear();
320 frame.extend(self.bus.iter().map(|&s| {
321 let scaled = (s * 32768.0).round();
322 if !(-32768.0..=32767.0).contains(&scaled) {
323 clipped += 1;
324 }
325 scaled.clamp(-32768.0, 32767.0) as i16
326 }));
327 if clipped > 0 {
328 metrics.exit_clipped.fetch_add(clipped, Ordering::Relaxed);
329 }
330 metrics.blocks.fetch_add(1, Ordering::Relaxed);
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 struct Gain(f32);
339 impl DspStage for Gain {
340 fn name(&self) -> &'static str {
341 "gain"
342 }
343 fn process(&mut self, bus: &mut AudioBus) {
344 for s in bus.samples.iter_mut() {
345 *s *= self.0;
346 }
347 }
348 }
349
350 #[cfg(feature = "dsp")]
351 #[test]
352 fn rate_change_does_not_persist_across_frames() {
353 let mut chain = DspChain::new(48_000).stage(
359 crate::voice::dsp::resample::SincResampler::new(48_000, 16_000),
360 );
361 let mut frame = vec![1000i16; 960]; chain.process_frame(&mut frame);
363 let mut frame2 = vec![1000i16; 960];
364 chain.process_frame(&mut frame2); }
366
367 #[test]
368 fn empty_chain_is_bit_transparent() {
369 let mut chain = DspChain::new(16_000);
370 let original: Vec<i16> = (-40..40).map(|i| (i * 400) as i16).collect();
371 let mut frame = original.clone();
372 chain.process_frame(&mut frame);
373 assert_eq!(frame, original);
374 }
375
376 #[test]
377 fn exit_clipping_is_counted_not_silent() {
378 let mut chain = DspChain::new(16_000).stage(Gain(4.0));
379 let metrics = chain.metrics();
380 let mut frame = vec![20_000i16; 160];
381 chain.process_frame(&mut frame);
382 assert!(frame.iter().all(|&s| s == i16::MAX || s == i16::MIN));
383 let snap = metrics.snapshot();
384 assert_eq!(snap.exit_clipped, 160);
385 assert!(snap.stages[0].peak_out > 2.0);
386 }
387
388 #[test]
389 fn int_adapter_round_trips_a_passthrough() {
390 struct Nop;
391 impl InputAudioProcessor for Nop {
392 fn process_frame(&mut self, _frame: &mut Vec<i16>) {}
393 }
394 let mut chain = DspChain::new(16_000).stage(IntStage::named(Nop, "nop"));
395 let original: Vec<i16> = (0..160).map(|i| (i * 100 - 8000) as i16).collect();
396 let mut frame = original.clone();
397 chain.process_frame(&mut frame);
398 assert_eq!(frame, original);
400 }
401
402 #[test]
403 fn metrics_report_stage_names_and_latency() {
404 struct Delayed;
405 impl DspStage for Delayed {
406 fn name(&self) -> &'static str {
407 "delayed"
408 }
409 fn process(&mut self, _bus: &mut AudioBus) {}
410 fn latency_samples(&self) -> usize {
411 80
412 }
413 }
414 let mut chain = DspChain::new(16_000).stage(Delayed).stage(Gain(1.0));
415 let snap = chain.metrics().snapshot();
416 assert_eq!(snap.stages.len(), 2);
417 assert_eq!(snap.stages[0].name, "delayed");
418 assert_eq!(snap.total_latency_samples, 80);
419 assert_eq!(chain.total_latency_samples(), 80);
420 }
421}