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