use crate::audio::{AudioInput, WHISPER_SAMPLE_RATE};
use crate::error::{Result, UserError};
use std::sync::Arc;
pub const DEFAULT_DICTATION_MAX_SECS: f64 = 60.0;
#[derive(Debug, Clone)]
pub struct PcmBuffer {
samples: Vec<f32>,
max_samples: usize,
rolling: bool,
}
impl PcmBuffer {
pub fn with_max_secs(max_secs: f64) -> Self {
let max_samples = (max_secs.max(0.0) * f64::from(WHISPER_SAMPLE_RATE)).round() as usize;
Self {
samples: Vec::with_capacity(max_samples.clamp(1, 16_000)),
max_samples: max_samples.max(1),
rolling: true,
}
}
pub fn dictation() -> Self {
Self::with_max_secs(DEFAULT_DICTATION_MAX_SECS)
}
pub fn bounded(max_secs: f64) -> Self {
let mut b = Self::with_max_secs(max_secs);
b.rolling = false;
b
}
pub fn clear(&mut self) {
self.samples.clear();
}
pub fn len(&self) -> usize {
self.samples.len()
}
pub fn is_empty(&self) -> bool {
self.samples.is_empty()
}
pub fn max_samples(&self) -> usize {
self.max_samples
}
pub fn duration_secs(&self) -> f64 {
self.samples.len() as f64 / f64::from(WHISPER_SAMPLE_RATE)
}
pub fn samples(&self) -> &[f32] {
&self.samples
}
pub fn push(&mut self, chunk: &[f32]) -> Result<()> {
if chunk.is_empty() {
return Ok(());
}
if self.rolling {
self.samples.extend_from_slice(chunk);
if self.samples.len() > self.max_samples {
let overflow = self.samples.len() - self.max_samples;
self.samples.drain(0..overflow);
}
Ok(())
} else if self.samples.len().saturating_add(chunk.len()) > self.max_samples {
Err(UserError::AudioTooLong {
duration_secs: (self.samples.len() + chunk.len()) as f64
/ f64::from(WHISPER_SAMPLE_RATE),
max_secs: self.max_samples as f64 / f64::from(WHISPER_SAMPLE_RATE),
}
.into())
} else {
self.samples.extend_from_slice(chunk);
Ok(())
}
}
pub fn rms(&self) -> f32 {
if self.samples.is_empty() {
return 0.0;
}
let sum: f32 = self.samples.iter().map(|s| s * s).sum();
(sum / self.samples.len() as f32).sqrt()
}
pub fn to_audio_input(&self) -> Result<AudioInput> {
if self.samples.is_empty() {
return Err(UserError::InvalidAudio {
reason: "PCM buffer is empty".into(),
}
.into());
}
let samples: Arc<[f32]> = self.samples.clone().into();
AudioInput::from_pcm(samples, WHISPER_SAMPLE_RATE)
}
pub fn take_audio_input(&mut self) -> Result<AudioInput> {
if self.samples.is_empty() {
return Err(UserError::InvalidAudio {
reason: "PCM buffer is empty".into(),
}
.into());
}
let samples: Arc<[f32]> = std::mem::take(&mut self.samples).into();
AudioInput::from_pcm(samples, WHISPER_SAMPLE_RATE)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rolling_drops_oldest() {
let mut buf = PcmBuffer::with_max_secs(0.01); assert_eq!(buf.max_samples(), 160);
buf.push(&vec![1.0; 100]).unwrap();
buf.push(&vec![2.0; 100]).unwrap();
assert_eq!(buf.len(), 160);
assert!(buf.samples().iter().all(|&s| s == 1.0 || s == 2.0));
assert!(buf.samples()[0] == 2.0 || buf.samples().contains(&2.0));
assert_eq!(buf.samples()[0], 1.0);
assert_eq!(buf.samples()[59], 1.0);
assert_eq!(buf.samples()[60], 2.0);
}
#[test]
fn bounded_errors() {
let mut buf = PcmBuffer::bounded(0.01);
buf.push(&vec![0.1; 160]).unwrap();
let err = buf.push(&[0.1]);
assert!(err.is_err());
}
#[test]
fn to_audio_input() {
let mut buf = PcmBuffer::dictation();
buf.push(&vec![0.0; 1600]).unwrap();
let audio = buf.to_audio_input().unwrap();
assert_eq!(audio.sample_rate, 16_000);
assert_eq!(audio.len(), 1600);
assert!(!buf.is_empty());
let taken = buf.take_audio_input().unwrap();
assert_eq!(taken.len(), 1600);
assert!(buf.is_empty());
}
}