Skip to main content

ff_format/frame/audio/
samples.rs

1//! Utility and PCM conversion methods for [`AudioFrame`].
2
3use crate::SampleFormat;
4
5use super::AudioFrame;
6
7impl AudioFrame {
8    // ==========================================================================
9    // Utility Methods
10    // ==========================================================================
11
12    /// Returns the total size in bytes of all sample data.
13    ///
14    /// # Examples
15    ///
16    /// ```
17    /// use ff_format::{AudioFrame, SampleFormat};
18    ///
19    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
20    /// assert_eq!(frame.total_size(), 1024 * 2 * 4);
21    /// ```
22    #[must_use]
23    pub fn total_size(&self) -> usize {
24        self.planes.iter().map(Vec::len).sum()
25    }
26
27    /// Returns the size in bytes of a single sample (one channel).
28    ///
29    /// # Examples
30    ///
31    /// ```
32    /// use ff_format::{AudioFrame, SampleFormat};
33    ///
34    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
35    /// assert_eq!(frame.bytes_per_sample(), 4);
36    ///
37    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16).unwrap();
38    /// assert_eq!(frame.bytes_per_sample(), 2);
39    /// ```
40    #[must_use]
41    #[inline]
42    pub fn bytes_per_sample(&self) -> usize {
43        self.format.bytes_per_sample()
44    }
45
46    /// Returns the total number of samples across all channels (`samples * channels`).
47    ///
48    /// # Examples
49    ///
50    /// ```
51    /// use ff_format::{AudioFrame, SampleFormat};
52    ///
53    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
54    /// assert_eq!(frame.sample_count(), 2048);
55    /// ```
56    #[must_use]
57    #[inline]
58    pub fn sample_count(&self) -> usize {
59        self.samples * self.channels as usize
60    }
61
62    // ==========================================================================
63    // PCM Conversion
64    // ==========================================================================
65
66    /// Converts the audio frame to interleaved 32-bit float PCM.
67    ///
68    /// All [`SampleFormat`] variants are supported. Planar formats are transposed
69    /// to interleaved layout (L0 R0 L1 R1 ...). Returns an empty `Vec` for
70    /// [`SampleFormat::Other`].
71    ///
72    /// # Scaling
73    ///
74    /// | Source format | Normalization |
75    /// |---|---|
76    /// | U8  | `(sample − 128) / 128.0` → `[−1.0, 1.0]` |
77    /// | I16 | `sample / 32767.0` → `[−1.0, 1.0]` |
78    /// | I32 | `sample / 2147483647.0` → `[−1.0, 1.0]` |
79    /// | F32 | identity |
80    /// | F64 | narrowed to f32 (`as f32`) |
81    ///
82    /// # Examples
83    ///
84    /// ```
85    /// use ff_format::{AudioFrame, SampleFormat};
86    ///
87    /// let frame = AudioFrame::empty(4, 2, 48000, SampleFormat::F32p).unwrap();
88    /// let pcm = frame.to_f32_interleaved();
89    /// assert_eq!(pcm.len(), 8); // 4 samples × 2 channels
90    /// ```
91    #[must_use]
92    #[allow(
93        clippy::cast_possible_truncation,
94        clippy::cast_precision_loss,
95        clippy::too_many_lines
96    )]
97    pub fn to_f32_interleaved(&self) -> Vec<f32> {
98        let total = self.sample_count();
99        if total == 0 {
100            return Vec::new();
101        }
102
103        match self.format {
104            SampleFormat::F32 => self.as_f32().map(<[f32]>::to_vec).unwrap_or_default(),
105            SampleFormat::F32p => {
106                let mut out = vec![0f32; total];
107                let ch_count = self.channels as usize;
108                for ch in 0..ch_count {
109                    if let Some(plane) = self.channel_as_f32(ch) {
110                        for (i, &s) in plane.iter().enumerate() {
111                            out[i * ch_count + ch] = s;
112                        }
113                    }
114                }
115                out
116            }
117            SampleFormat::F64 => {
118                let bytes = self.data();
119                bytes
120                    .chunks_exact(8)
121                    .map(|b| {
122                        f64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]) as f32
123                    })
124                    .collect()
125            }
126            SampleFormat::F64p => {
127                let mut out = vec![0f32; total];
128                let ch_count = self.channels as usize;
129                for ch in 0..ch_count {
130                    if let Some(bytes) = self.channel(ch) {
131                        for (i, b) in bytes.chunks_exact(8).enumerate() {
132                            out[i * ch_count + ch] = f64::from_le_bytes([
133                                b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
134                            ]) as f32;
135                        }
136                    }
137                }
138                out
139            }
140            SampleFormat::I16 => {
141                let bytes = self.data();
142                bytes
143                    .chunks_exact(2)
144                    .map(|b| f32::from(i16::from_le_bytes([b[0], b[1]])) / f32::from(i16::MAX))
145                    .collect()
146            }
147            SampleFormat::I16p => {
148                let mut out = vec![0f32; total];
149                let ch_count = self.channels as usize;
150                for ch in 0..ch_count {
151                    if let Some(bytes) = self.channel(ch) {
152                        for (i, b) in bytes.chunks_exact(2).enumerate() {
153                            out[i * ch_count + ch] =
154                                f32::from(i16::from_le_bytes([b[0], b[1]])) / f32::from(i16::MAX);
155                        }
156                    }
157                }
158                out
159            }
160            SampleFormat::I32 => {
161                let bytes = self.data();
162                bytes
163                    .chunks_exact(4)
164                    .map(|b| i32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f32 / i32::MAX as f32)
165                    .collect()
166            }
167            SampleFormat::I32p => {
168                let mut out = vec![0f32; total];
169                let ch_count = self.channels as usize;
170                for ch in 0..ch_count {
171                    if let Some(bytes) = self.channel(ch) {
172                        for (i, b) in bytes.chunks_exact(4).enumerate() {
173                            out[i * ch_count + ch] = i32::from_le_bytes([b[0], b[1], b[2], b[3]])
174                                as f32
175                                / i32::MAX as f32;
176                        }
177                    }
178                }
179                out
180            }
181            SampleFormat::U8 => {
182                let bytes = self.data();
183                bytes
184                    .iter()
185                    .map(|&b| (f32::from(b) - 128.0) / 128.0)
186                    .collect()
187            }
188            SampleFormat::U8p => {
189                let mut out = vec![0f32; total];
190                let ch_count = self.channels as usize;
191                for ch in 0..ch_count {
192                    if let Some(bytes) = self.channel(ch) {
193                        for (i, &b) in bytes.iter().enumerate() {
194                            out[i * ch_count + ch] = (f32::from(b) - 128.0) / 128.0;
195                        }
196                    }
197                }
198                out
199            }
200            SampleFormat::Other(_) => Vec::new(),
201        }
202    }
203
204    /// Converts the audio frame to interleaved 16-bit signed integer PCM.
205    ///
206    /// Suitable for use with `rodio::buffer::SamplesBuffer<i16>`. All
207    /// [`SampleFormat`] variants are supported. Returns an empty `Vec` for
208    /// [`SampleFormat::Other`].
209    ///
210    /// # Scaling
211    ///
212    /// | Source format | Conversion |
213    /// |---|---|
214    /// | I16 | identity |
215    /// | I32 | `sample >> 16` (high 16 bits) |
216    /// | U8  | `(sample − 128) << 8` |
217    /// | F32/F64 | `clamp(−1, 1) × 32767`, truncated |
218    ///
219    /// # Examples
220    ///
221    /// ```
222    /// use ff_format::{AudioFrame, SampleFormat};
223    ///
224    /// let frame = AudioFrame::empty(4, 2, 48000, SampleFormat::I16p).unwrap();
225    /// let pcm = frame.to_i16_interleaved();
226    /// assert_eq!(pcm.len(), 8);
227    /// ```
228    #[must_use]
229    #[allow(clippy::cast_possible_truncation, clippy::too_many_lines)] // float→i16 and i32→i16 are intentional truncations
230    pub fn to_i16_interleaved(&self) -> Vec<i16> {
231        let total = self.sample_count();
232        if total == 0 {
233            return Vec::new();
234        }
235
236        match self.format {
237            SampleFormat::I16 => self.as_i16().map(<[i16]>::to_vec).unwrap_or_default(),
238            SampleFormat::I16p => {
239                let mut out = vec![0i16; total];
240                let ch_count = self.channels as usize;
241                for ch in 0..ch_count {
242                    if let Some(plane) = self.channel_as_i16(ch) {
243                        for (i, &s) in plane.iter().enumerate() {
244                            out[i * ch_count + ch] = s;
245                        }
246                    }
247                }
248                out
249            }
250            SampleFormat::F32 => {
251                let bytes = self.data();
252                bytes
253                    .chunks_exact(4)
254                    .map(|b| {
255                        let s = f32::from_le_bytes([b[0], b[1], b[2], b[3]]);
256                        (s.clamp(-1.0, 1.0) * f32::from(i16::MAX)) as i16
257                    })
258                    .collect()
259            }
260            SampleFormat::F32p => {
261                let mut out = vec![0i16; total];
262                let ch_count = self.channels as usize;
263                for ch in 0..ch_count {
264                    if let Some(bytes) = self.channel(ch) {
265                        for (i, b) in bytes.chunks_exact(4).enumerate() {
266                            let s = f32::from_le_bytes([b[0], b[1], b[2], b[3]]);
267                            out[i * ch_count + ch] =
268                                (s.clamp(-1.0, 1.0) * f32::from(i16::MAX)) as i16;
269                        }
270                    }
271                }
272                out
273            }
274            SampleFormat::F64 => {
275                let bytes = self.data();
276                bytes
277                    .chunks_exact(8)
278                    .map(|b| {
279                        let s =
280                            f64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]);
281                        (s.clamp(-1.0, 1.0) * f64::from(i16::MAX)) as i16
282                    })
283                    .collect()
284            }
285            SampleFormat::F64p => {
286                let mut out = vec![0i16; total];
287                let ch_count = self.channels as usize;
288                for ch in 0..ch_count {
289                    if let Some(bytes) = self.channel(ch) {
290                        for (i, b) in bytes.chunks_exact(8).enumerate() {
291                            let s = f64::from_le_bytes([
292                                b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
293                            ]);
294                            out[i * ch_count + ch] =
295                                (s.clamp(-1.0, 1.0) * f64::from(i16::MAX)) as i16;
296                        }
297                    }
298                }
299                out
300            }
301            SampleFormat::I32 => {
302                let bytes = self.data();
303                bytes
304                    .chunks_exact(4)
305                    .map(|b| (i32::from_le_bytes([b[0], b[1], b[2], b[3]]) >> 16) as i16)
306                    .collect()
307            }
308            SampleFormat::I32p => {
309                let mut out = vec![0i16; total];
310                let ch_count = self.channels as usize;
311                for ch in 0..ch_count {
312                    if let Some(bytes) = self.channel(ch) {
313                        for (i, b) in bytes.chunks_exact(4).enumerate() {
314                            out[i * ch_count + ch] =
315                                (i32::from_le_bytes([b[0], b[1], b[2], b[3]]) >> 16) as i16;
316                        }
317                    }
318                }
319                out
320            }
321            SampleFormat::U8 => {
322                let bytes = self.data();
323                bytes.iter().map(|&b| (i16::from(b) - 128) << 8).collect()
324            }
325            SampleFormat::U8p => {
326                let mut out = vec![0i16; total];
327                let ch_count = self.channels as usize;
328                for ch in 0..ch_count {
329                    if let Some(bytes) = self.channel(ch) {
330                        for (i, &b) in bytes.iter().enumerate() {
331                            out[i * ch_count + ch] = (i16::from(b) - 128) << 8;
332                        }
333                    }
334                }
335                out
336            }
337            SampleFormat::Other(_) => Vec::new(),
338        }
339    }
340}
341
342#[cfg(test)]
343#[allow(clippy::unwrap_used)]
344mod tests {
345    use super::*;
346    use crate::{Rational, Timestamp};
347
348    // ==========================================================================
349    // Utility Tests
350    // ==========================================================================
351
352    #[test]
353    fn total_size_packed_should_equal_samples_times_channels_times_bytes() {
354        // Packed stereo F32: 1024 samples * 2 channels * 4 bytes
355        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
356        assert_eq!(frame.total_size(), 1024 * 2 * 4);
357
358        // Planar stereo F32p: 2 planes * 1024 samples * 4 bytes
359        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
360        assert_eq!(frame.total_size(), 1024 * 4 * 2);
361    }
362
363    #[test]
364    fn bytes_per_sample_should_match_format_width() {
365        assert_eq!(
366            AudioFrame::empty(1024, 2, 48000, SampleFormat::U8)
367                .unwrap()
368                .bytes_per_sample(),
369            1
370        );
371        assert_eq!(
372            AudioFrame::empty(1024, 2, 48000, SampleFormat::I16)
373                .unwrap()
374                .bytes_per_sample(),
375            2
376        );
377        assert_eq!(
378            AudioFrame::empty(1024, 2, 48000, SampleFormat::F32)
379                .unwrap()
380                .bytes_per_sample(),
381            4
382        );
383        assert_eq!(
384            AudioFrame::empty(1024, 2, 48000, SampleFormat::F64)
385                .unwrap()
386                .bytes_per_sample(),
387            8
388        );
389    }
390
391    #[test]
392    fn sample_count_should_return_samples_times_channels() {
393        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
394        assert_eq!(frame.sample_count(), 2048);
395
396        let mono = AudioFrame::empty(512, 1, 44100, SampleFormat::I16).unwrap();
397        assert_eq!(mono.sample_count(), 512);
398    }
399
400    // ==========================================================================
401    // PCM Conversion Tests
402    // ==========================================================================
403
404    #[test]
405    fn to_f32_interleaved_f32p_should_transpose_to_interleaved() {
406        // Stereo F32p: L=[1.0, 2.0], R=[3.0, 4.0]
407        // Expected interleaved: [1.0, 3.0, 2.0, 4.0]
408        let left: Vec<u8> = [1.0f32, 2.0f32]
409            .iter()
410            .flat_map(|f| f.to_le_bytes())
411            .collect();
412        let right: Vec<u8> = [3.0f32, 4.0f32]
413            .iter()
414            .flat_map(|f| f.to_le_bytes())
415            .collect();
416
417        let frame = AudioFrame::new(
418            vec![left, right],
419            2,
420            2,
421            48000,
422            SampleFormat::F32p,
423            Timestamp::default(),
424        )
425        .unwrap();
426
427        let pcm = frame.to_f32_interleaved();
428        assert_eq!(pcm.len(), 4);
429        assert!((pcm[0] - 1.0).abs() < f32::EPSILON); // L0
430        assert!((pcm[1] - 3.0).abs() < f32::EPSILON); // R0
431        assert!((pcm[2] - 2.0).abs() < f32::EPSILON); // L1
432        assert!((pcm[3] - 4.0).abs() < f32::EPSILON); // R1
433    }
434
435    #[test]
436    fn to_f32_interleaved_i16p_should_scale_to_minus_one_to_one() {
437        // i16::MAX → ~1.0,  i16::MIN → ~-1.0,  0 → 0.0
438        let make_i16_bytes = |v: i16| v.to_le_bytes().to_vec();
439
440        let left: Vec<u8> = [i16::MAX, 0i16]
441            .iter()
442            .flat_map(|&v| make_i16_bytes(v))
443            .collect();
444        let right: Vec<u8> = [i16::MIN, 0i16]
445            .iter()
446            .flat_map(|&v| make_i16_bytes(v))
447            .collect();
448
449        let frame = AudioFrame::new(
450            vec![left, right],
451            2,
452            2,
453            48000,
454            SampleFormat::I16p,
455            Timestamp::default(),
456        )
457        .unwrap();
458
459        let pcm = frame.to_f32_interleaved();
460        // i16 is asymmetric: MIN=-32768, MAX=32767, so MIN/MAX ≈ -1.00003
461        // Values should be very close to [-1.0, 1.0]
462        for &s in &pcm {
463            assert!(s >= -1.001 && s <= 1.001, "out of range: {s}");
464        }
465        assert!((pcm[0] - 1.0).abs() < 0.0001); // i16::MAX → ~1.0
466        assert!((pcm[1] - (-1.0)).abs() < 0.001); // i16::MIN → ~-1.00003
467    }
468
469    #[test]
470    fn to_f32_interleaved_unknown_should_return_empty() {
471        // AudioFrame::new() (unlike empty()) accepts Other(_): Other is treated
472        // as packed so expected_planes = 1.
473        let frame = AudioFrame::new(
474            vec![vec![0u8; 16]],
475            4,
476            1,
477            48000,
478            SampleFormat::Other(999),
479            Timestamp::default(),
480        )
481        .unwrap();
482        assert_eq!(frame.to_f32_interleaved(), Vec::<f32>::new());
483    }
484
485    #[test]
486    fn to_i16_interleaved_i16p_should_transpose_to_interleaved() {
487        let left: Vec<u8> = [100i16, 200i16]
488            .iter()
489            .flat_map(|v| v.to_le_bytes())
490            .collect();
491        let right: Vec<u8> = [300i16, 400i16]
492            .iter()
493            .flat_map(|v| v.to_le_bytes())
494            .collect();
495
496        let frame = AudioFrame::new(
497            vec![left, right],
498            2,
499            2,
500            48000,
501            SampleFormat::I16p,
502            Timestamp::default(),
503        )
504        .unwrap();
505
506        let pcm = frame.to_i16_interleaved();
507        assert_eq!(pcm, vec![100, 300, 200, 400]);
508    }
509
510    #[test]
511    fn to_i16_interleaved_f32_should_scale_and_clamp() {
512        // 1.0 → i16::MAX, -1.0 → -i16::MAX, 2.0 → clamped to i16::MAX
513        let samples: &[f32] = &[1.0, -1.0, 2.0, -2.0];
514        let bytes: Vec<u8> = samples.iter().flat_map(|f| f.to_le_bytes()).collect();
515
516        let frame = AudioFrame::new(
517            vec![bytes],
518            4,
519            1,
520            48000,
521            SampleFormat::F32,
522            Timestamp::default(),
523        )
524        .unwrap();
525
526        let pcm = frame.to_i16_interleaved();
527        assert_eq!(pcm.len(), 4);
528        assert_eq!(pcm[0], i16::MAX);
529        assert_eq!(pcm[1], -i16::MAX);
530        // Clamped values
531        assert_eq!(pcm[2], i16::MAX);
532        assert_eq!(pcm[3], -i16::MAX);
533    }
534
535    #[test]
536    fn audio_frame_clone_should_have_identical_data() {
537        let samples = 512;
538        let channels = 2u32;
539        let bytes_per_sample = 4; // F32
540        let plane_data = vec![7u8; samples * bytes_per_sample];
541        let ts = Timestamp::new(500, Rational::new(1, 1000));
542
543        let original = AudioFrame::new(
544            vec![plane_data.clone()],
545            samples,
546            channels,
547            44100,
548            SampleFormat::F32,
549            ts,
550        )
551        .unwrap();
552
553        let clone = original.clone();
554
555        assert_eq!(clone.samples(), original.samples());
556        assert_eq!(clone.channels(), original.channels());
557        assert_eq!(clone.sample_rate(), original.sample_rate());
558        assert_eq!(clone.format(), original.format());
559        assert_eq!(clone.timestamp(), original.timestamp());
560        assert_eq!(clone.num_planes(), original.num_planes());
561        assert_eq!(clone.plane(0), original.plane(0));
562    }
563}