use std::borrow::Cow;
#[derive(Debug, Clone)]
pub struct AudioFrame<'a> {
samples: Cow<'a, [f32]>,
sample_rate: u32,
}
impl<'a> AudioFrame<'a> {
pub fn new(samples: impl IntoSamples<'a>, sample_rate: u32) -> Self {
Self {
samples: samples.into_samples(),
sample_rate,
}
}
pub fn samples(&self) -> &[f32] {
&self.samples
}
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub fn len(&self) -> usize {
self.samples.len()
}
pub fn is_empty(&self) -> bool {
self.samples.is_empty()
}
pub fn duration_secs(&self) -> f64 {
self.samples.len() as f64 / self.sample_rate as f64
}
pub fn into_owned(self) -> AudioFrame<'static> {
AudioFrame {
samples: Cow::Owned(self.samples.into_owned()),
sample_rate: self.sample_rate,
}
}
}
impl AudioFrame<'static> {
pub fn from_vec(samples: Vec<f32>, sample_rate: u32) -> Self {
Self {
samples: Cow::Owned(samples),
sample_rate,
}
}
}
pub trait IntoSamples<'a> {
fn into_samples(self) -> Cow<'a, [f32]>;
}
impl<'a> IntoSamples<'a> for &'a [f32] {
#[inline]
fn into_samples(self) -> Cow<'a, [f32]> {
Cow::Borrowed(self)
}
}
impl<'a> IntoSamples<'a> for &'a Vec<f32> {
#[inline]
fn into_samples(self) -> Cow<'a, [f32]> {
Cow::Borrowed(self.as_slice())
}
}
impl<'a, const N: usize> IntoSamples<'a> for &'a [f32; N] {
#[inline]
fn into_samples(self) -> Cow<'a, [f32]> {
Cow::Borrowed(self.as_slice())
}
}
impl<'a> IntoSamples<'a> for &'a [i16] {
#[inline]
fn into_samples(self) -> Cow<'a, [f32]> {
Cow::Owned(self.iter().map(|&s| s as f32 / 32768.0).collect())
}
}
impl<'a> IntoSamples<'a> for &'a Vec<i16> {
#[inline]
fn into_samples(self) -> Cow<'a, [f32]> {
Cow::Owned(self.iter().map(|&s| s as f32 / 32768.0).collect())
}
}
impl<'a, const N: usize> IntoSamples<'a> for &'a [i16; N] {
#[inline]
fn into_samples(self) -> Cow<'a, [f32]> {
Cow::Owned(self.iter().map(|&s| s as f32 / 32768.0).collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn f32_is_zero_copy() {
let samples = vec![0.1f32, -0.2, 0.3];
let frame = AudioFrame::new(samples.as_slice(), 16000);
assert!(matches!(frame.samples, Cow::Borrowed(_)));
assert_eq!(frame.samples(), &[0.1, -0.2, 0.3]);
}
#[test]
fn i16_normalizes_to_f32() {
let samples: Vec<i16> = vec![0, 16384, -16384, i16::MAX, i16::MIN];
let frame = AudioFrame::new(samples.as_slice(), 16000);
assert!(matches!(frame.samples, Cow::Owned(_)));
let s = frame.samples();
assert!((s[0] - 0.0).abs() < f32::EPSILON);
assert!((s[1] - 0.5).abs() < 0.001);
assert!((s[2] - -0.5).abs() < 0.001);
assert!((s[3] - (i16::MAX as f32 / 32768.0)).abs() < f32::EPSILON);
assert!((s[4] - -1.0).abs() < f32::EPSILON);
}
#[test]
fn metadata() {
let samples = vec![0.0f32; 160];
let frame = AudioFrame::new(samples.as_slice(), 16000);
assert_eq!(frame.sample_rate(), 16000);
assert_eq!(frame.len(), 160);
assert!(!frame.is_empty());
assert!((frame.duration_secs() - 0.01).abs() < 1e-9);
}
#[test]
fn empty_frame() {
let samples: &[f32] = &[];
let frame = AudioFrame::new(samples, 16000);
assert!(frame.is_empty());
assert_eq!(frame.len(), 0);
}
#[test]
fn into_owned() {
let samples = vec![0.5f32, -0.5];
let frame = AudioFrame::new(samples.as_slice(), 16000);
let owned: AudioFrame<'static> = frame.into_owned();
assert_eq!(owned.samples(), &[0.5, -0.5]);
assert_eq!(owned.sample_rate(), 16000);
}
#[test]
fn from_vec_is_zero_copy() {
let samples = vec![0.5f32, -0.5];
let ptr = samples.as_ptr();
let frame = AudioFrame::from_vec(samples, 24000);
assert_eq!(frame.samples().as_ptr(), ptr);
assert_eq!(frame.sample_rate(), 24000);
}
}