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
78impl AudioFrame<'static> {
79 pub fn from_vec(samples: Vec<f32>, sample_rate: u32) -> Self {
95 Self {
96 samples: Cow::Owned(samples),
97 sample_rate,
98 }
99 }
100}
101
102pub trait IntoSamples<'a> {
106 fn into_samples(self) -> Cow<'a, [f32]>;
108}
109
110impl<'a> IntoSamples<'a> for &'a [f32] {
111 #[inline]
112 fn into_samples(self) -> Cow<'a, [f32]> {
113 Cow::Borrowed(self)
114 }
115}
116
117impl<'a> IntoSamples<'a> for &'a Vec<f32> {
118 #[inline]
119 fn into_samples(self) -> Cow<'a, [f32]> {
120 Cow::Borrowed(self.as_slice())
121 }
122}
123
124impl<'a, const N: usize> IntoSamples<'a> for &'a [f32; N] {
125 #[inline]
126 fn into_samples(self) -> Cow<'a, [f32]> {
127 Cow::Borrowed(self.as_slice())
128 }
129}
130
131impl<'a> IntoSamples<'a> for &'a [i16] {
132 #[inline]
133 fn into_samples(self) -> Cow<'a, [f32]> {
134 Cow::Owned(self.iter().map(|&s| s as f32 / 32768.0).collect())
135 }
136}
137
138impl<'a> IntoSamples<'a> for &'a Vec<i16> {
139 #[inline]
140 fn into_samples(self) -> Cow<'a, [f32]> {
141 Cow::Owned(self.iter().map(|&s| s as f32 / 32768.0).collect())
142 }
143}
144
145impl<'a, const N: usize> IntoSamples<'a> for &'a [i16; N] {
146 #[inline]
147 fn into_samples(self) -> Cow<'a, [f32]> {
148 Cow::Owned(self.iter().map(|&s| s as f32 / 32768.0).collect())
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn f32_is_zero_copy() {
158 let samples = vec![0.1f32, -0.2, 0.3];
159 let frame = AudioFrame::new(samples.as_slice(), 16000);
160 assert!(matches!(frame.samples, Cow::Borrowed(_)));
162 assert_eq!(frame.samples(), &[0.1, -0.2, 0.3]);
163 }
164
165 #[test]
166 fn i16_normalizes_to_f32() {
167 let samples: Vec<i16> = vec![0, 16384, -16384, i16::MAX, i16::MIN];
168 let frame = AudioFrame::new(samples.as_slice(), 16000);
169 assert!(matches!(frame.samples, Cow::Owned(_)));
170
171 let s = frame.samples();
172 assert!((s[0] - 0.0).abs() < f32::EPSILON);
173 assert!((s[1] - 0.5).abs() < 0.001);
174 assert!((s[2] - -0.5).abs() < 0.001);
175 assert!((s[3] - (i16::MAX as f32 / 32768.0)).abs() < f32::EPSILON);
176 assert!((s[4] - -1.0).abs() < f32::EPSILON);
177 }
178
179 #[test]
180 fn metadata() {
181 let samples = vec![0.0f32; 160];
182 let frame = AudioFrame::new(samples.as_slice(), 16000);
183 assert_eq!(frame.sample_rate(), 16000);
184 assert_eq!(frame.len(), 160);
185 assert!(!frame.is_empty());
186 assert!((frame.duration_secs() - 0.01).abs() < 1e-9);
187 }
188
189 #[test]
190 fn empty_frame() {
191 let samples: &[f32] = &[];
192 let frame = AudioFrame::new(samples, 16000);
193 assert!(frame.is_empty());
194 assert_eq!(frame.len(), 0);
195 }
196
197 #[test]
198 fn into_owned() {
199 let samples = vec![0.5f32, -0.5];
200 let frame = AudioFrame::new(samples.as_slice(), 16000);
201 let owned: AudioFrame<'static> = frame.into_owned();
202 assert_eq!(owned.samples(), &[0.5, -0.5]);
203 assert_eq!(owned.sample_rate(), 16000);
204 }
205
206 #[test]
207 fn from_vec_is_zero_copy() {
208 let samples = vec![0.5f32, -0.5];
209 let ptr = samples.as_ptr();
210 let frame = AudioFrame::from_vec(samples, 24000);
211 assert_eq!(frame.samples().as_ptr(), ptr);
212 assert_eq!(frame.sample_rate(), 24000);
213 }
214}