1use std::borrow::Cow;
2
3#[derive(Debug, Clone)]
28pub struct AudioFrame<'a> {
29 samples: Cow<'a, [f32]>,
30 sample_rate: u32,
31}
32
33impl<'a> AudioFrame<'a> {
34 pub fn new(samples: impl IntoSamples<'a>, sample_rate: u32) -> Self {
38 Self {
39 samples: samples.into_samples(),
40 sample_rate,
41 }
42 }
43
44 pub fn samples(&self) -> &[f32] {
46 &self.samples
47 }
48
49 pub fn sample_rate(&self) -> u32 {
51 self.sample_rate
52 }
53
54 pub fn len(&self) -> usize {
56 self.samples.len()
57 }
58
59 pub fn is_empty(&self) -> bool {
61 self.samples.is_empty()
62 }
63
64 pub fn duration_secs(&self) -> f64 {
66 self.samples.len() as f64 / self.sample_rate as f64
67 }
68
69 pub fn into_owned(self) -> AudioFrame<'static> {
71 AudioFrame {
72 samples: Cow::Owned(self.samples.into_owned()),
73 sample_rate: self.sample_rate,
74 }
75 }
76}
77
78pub trait IntoSamples<'a> {
82 fn into_samples(self) -> Cow<'a, [f32]>;
84}
85
86impl<'a> IntoSamples<'a> for &'a [f32] {
87 #[inline]
88 fn into_samples(self) -> Cow<'a, [f32]> {
89 Cow::Borrowed(self)
90 }
91}
92
93impl<'a> IntoSamples<'a> for &'a Vec<f32> {
94 #[inline]
95 fn into_samples(self) -> Cow<'a, [f32]> {
96 Cow::Borrowed(self.as_slice())
97 }
98}
99
100impl<'a, const N: usize> IntoSamples<'a> for &'a [f32; N] {
101 #[inline]
102 fn into_samples(self) -> Cow<'a, [f32]> {
103 Cow::Borrowed(self.as_slice())
104 }
105}
106
107impl<'a> IntoSamples<'a> for &'a [i16] {
108 #[inline]
109 fn into_samples(self) -> Cow<'a, [f32]> {
110 Cow::Owned(self.iter().map(|&s| s as f32 / 32768.0).collect())
111 }
112}
113
114impl<'a> IntoSamples<'a> for &'a Vec<i16> {
115 #[inline]
116 fn into_samples(self) -> Cow<'a, [f32]> {
117 Cow::Owned(self.iter().map(|&s| s as f32 / 32768.0).collect())
118 }
119}
120
121impl<'a, const N: usize> IntoSamples<'a> for &'a [i16; N] {
122 #[inline]
123 fn into_samples(self) -> Cow<'a, [f32]> {
124 Cow::Owned(self.iter().map(|&s| s as f32 / 32768.0).collect())
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn f32_is_zero_copy() {
134 let samples = vec![0.1f32, -0.2, 0.3];
135 let frame = AudioFrame::new(samples.as_slice(), 16000);
136 assert!(matches!(frame.samples, Cow::Borrowed(_)));
138 assert_eq!(frame.samples(), &[0.1, -0.2, 0.3]);
139 }
140
141 #[test]
142 fn i16_normalizes_to_f32() {
143 let samples: Vec<i16> = vec![0, 16384, -16384, i16::MAX, i16::MIN];
144 let frame = AudioFrame::new(samples.as_slice(), 16000);
145 assert!(matches!(frame.samples, Cow::Owned(_)));
146
147 let s = frame.samples();
148 assert!((s[0] - 0.0).abs() < f32::EPSILON);
149 assert!((s[1] - 0.5).abs() < 0.001);
150 assert!((s[2] - -0.5).abs() < 0.001);
151 assert!((s[3] - (i16::MAX as f32 / 32768.0)).abs() < f32::EPSILON);
152 assert!((s[4] - -1.0).abs() < f32::EPSILON);
153 }
154
155 #[test]
156 fn metadata() {
157 let samples = vec![0.0f32; 160];
158 let frame = AudioFrame::new(samples.as_slice(), 16000);
159 assert_eq!(frame.sample_rate(), 16000);
160 assert_eq!(frame.len(), 160);
161 assert!(!frame.is_empty());
162 assert!((frame.duration_secs() - 0.01).abs() < 1e-9);
163 }
164
165 #[test]
166 fn empty_frame() {
167 let samples: &[f32] = &[];
168 let frame = AudioFrame::new(samples, 16000);
169 assert!(frame.is_empty());
170 assert_eq!(frame.len(), 0);
171 }
172
173 #[test]
174 fn into_owned() {
175 let samples = vec![0.5f32, -0.5];
176 let frame = AudioFrame::new(samples.as_slice(), 16000);
177 let owned: AudioFrame<'static> = frame.into_owned();
178 assert_eq!(owned.samples(), &[0.5, -0.5]);
179 assert_eq!(owned.sample_rate(), 16000);
180 }
181}