Skip to main content

ff_format/
sample.rs

1//! Audio sample format definitions for audio processing.
2//!
3//! This module provides the [`SampleFormat`] enum which represents various
4//! audio sample formats used in audio processing. It supports both packed
5//! (interleaved) and planar formats commonly used in audio editing.
6//!
7//! # Examples
8//!
9//! ```
10//! use ff_format::SampleFormat;
11//!
12//! let format = SampleFormat::F32;
13//! assert!(!format.is_planar());
14//! assert!(format.is_float());
15//! assert_eq!(format.bytes_per_sample(), 4);
16//!
17//! let planar = SampleFormat::I16p;
18//! assert!(planar.is_planar());
19//! assert_eq!(planar.packed_equivalent(), SampleFormat::I16);
20//! ```
21
22use std::fmt;
23
24/// Audio sample format for audio frames.
25///
26/// This enum represents various sample formats used in audio processing.
27/// It is designed to cover the most common formats used in audio editing
28/// while remaining extensible via the `Other` variant.
29///
30/// # Format Categories
31///
32/// - **Packed (Interleaved)**: Samples from all channels are interleaved
33///   (U8, I16, I32, F32, F64)
34/// - **Planar**: Each channel stored in a separate buffer
35///   (U8p, I16p, I32p, F32p, F64p)
36///
37/// # Common Usage
38///
39/// - **I16**: CD quality audio (16-bit signed)
40/// - **F32**: Most common for audio editing (32-bit float)
41/// - **F32p**: Common in `FFmpeg` decoders for processing
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43#[non_exhaustive]
44pub enum SampleFormat {
45    // Packed (interleaved) formats
46    /// 8-bit unsigned integer (0-255)
47    U8,
48    /// 16-bit signed integer - CD quality audio
49    I16,
50    /// 32-bit signed integer
51    I32,
52    /// 32-bit floating point - most common for editing
53    F32,
54    /// 64-bit floating point - highest precision
55    F64,
56
57    // Planar formats
58    /// 8-bit unsigned integer, planar
59    U8p,
60    /// 16-bit signed integer, planar
61    I16p,
62    /// 32-bit signed integer, planar
63    I32p,
64    /// 32-bit floating point, planar - common in decoders
65    F32p,
66    /// 64-bit floating point, planar
67    F64p,
68
69    // Extensibility
70    /// Unknown or unsupported format with `FFmpeg`'s `AVSampleFormat` value
71    Other(u32),
72}
73
74impl SampleFormat {
75    /// Returns the format name as a human-readable string.
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// use ff_format::SampleFormat;
81    ///
82    /// assert_eq!(SampleFormat::I16.name(), "s16");
83    /// assert_eq!(SampleFormat::F32.name(), "flt");
84    /// assert_eq!(SampleFormat::F32p.name(), "fltp");
85    /// ```
86    #[must_use]
87    pub const fn name(&self) -> &'static str {
88        match self {
89            Self::U8 => "u8",
90            Self::I16 => "s16",
91            Self::I32 => "s32",
92            Self::F32 => "flt",
93            Self::F64 => "dbl",
94            Self::U8p => "u8p",
95            Self::I16p => "s16p",
96            Self::I32p => "s32p",
97            Self::F32p => "fltp",
98            Self::F64p => "dblp",
99            Self::Other(_) => "unknown",
100        }
101    }
102
103    /// Returns the number of bytes per sample.
104    ///
105    /// This is the size of a single sample value, regardless of whether
106    /// the format is planar or packed.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use ff_format::SampleFormat;
112    ///
113    /// assert_eq!(SampleFormat::U8.bytes_per_sample(), 1);
114    /// assert_eq!(SampleFormat::I16.bytes_per_sample(), 2);
115    /// assert_eq!(SampleFormat::I32.bytes_per_sample(), 4);
116    /// assert_eq!(SampleFormat::F32.bytes_per_sample(), 4);
117    /// assert_eq!(SampleFormat::F64.bytes_per_sample(), 8);
118    /// // Planar formats have the same bytes per sample
119    /// assert_eq!(SampleFormat::F32p.bytes_per_sample(), 4);
120    /// ```
121    #[must_use]
122    pub const fn bytes_per_sample(&self) -> usize {
123        match self {
124            Self::U8 | Self::U8p => 1,
125            Self::I16 | Self::I16p => 2,
126            Self::I32 | Self::I32p | Self::F32 | Self::F32p | Self::Other(_) => 4,
127            Self::F64 | Self::F64p => 8,
128        }
129    }
130
131    /// Returns `true` if this is a planar format.
132    ///
133    /// In planar formats, samples for each channel are stored in separate
134    /// contiguous buffers. This is more efficient for processing but
135    /// requires conversion for output.
136    ///
137    /// # Examples
138    ///
139    /// ```
140    /// use ff_format::SampleFormat;
141    ///
142    /// assert!(!SampleFormat::F32.is_planar());
143    /// assert!(SampleFormat::F32p.is_planar());
144    /// assert!(!SampleFormat::I16.is_planar());
145    /// assert!(SampleFormat::I16p.is_planar());
146    /// ```
147    #[must_use]
148    pub const fn is_planar(&self) -> bool {
149        matches!(
150            self,
151            Self::U8p | Self::I16p | Self::I32p | Self::F32p | Self::F64p
152        )
153    }
154
155    /// Returns `true` if this is a packed (interleaved) format.
156    ///
157    /// In packed formats, samples from all channels are interleaved
158    /// (e.g., L R L R L R for stereo). This is the format typically
159    /// used for audio output.
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// use ff_format::SampleFormat;
165    ///
166    /// assert!(SampleFormat::F32.is_packed());
167    /// assert!(!SampleFormat::F32p.is_packed());
168    /// assert!(SampleFormat::I16.is_packed());
169    /// assert!(!SampleFormat::I16p.is_packed());
170    /// ```
171    #[must_use]
172    pub const fn is_packed(&self) -> bool {
173        !self.is_planar()
174    }
175
176    /// Returns `true` if this is a floating-point format.
177    ///
178    /// Floating-point formats (F32, F64, F32p, F64p) offer higher
179    /// dynamic range and are preferred for audio processing to
180    /// avoid clipping.
181    ///
182    /// # Examples
183    ///
184    /// ```
185    /// use ff_format::SampleFormat;
186    ///
187    /// assert!(SampleFormat::F32.is_float());
188    /// assert!(SampleFormat::F64.is_float());
189    /// assert!(SampleFormat::F32p.is_float());
190    /// assert!(!SampleFormat::I16.is_float());
191    /// assert!(!SampleFormat::I32.is_float());
192    /// ```
193    #[must_use]
194    pub const fn is_float(&self) -> bool {
195        matches!(self, Self::F32 | Self::F64 | Self::F32p | Self::F64p)
196    }
197
198    /// Returns `true` if this is an integer format.
199    ///
200    /// Integer formats include both signed (I16, I32) and unsigned (U8) types.
201    ///
202    /// # Examples
203    ///
204    /// ```
205    /// use ff_format::SampleFormat;
206    ///
207    /// assert!(SampleFormat::I16.is_integer());
208    /// assert!(SampleFormat::U8.is_integer());
209    /// assert!(!SampleFormat::F32.is_integer());
210    /// ```
211    #[must_use]
212    pub const fn is_integer(&self) -> bool {
213        matches!(
214            self,
215            Self::U8 | Self::I16 | Self::I32 | Self::U8p | Self::I16p | Self::I32p
216        )
217    }
218
219    /// Returns `true` if this is a signed format.
220    ///
221    /// All formats except U8 and U8p are signed.
222    ///
223    /// # Examples
224    ///
225    /// ```
226    /// use ff_format::SampleFormat;
227    ///
228    /// assert!(SampleFormat::I16.is_signed());
229    /// assert!(SampleFormat::F32.is_signed());
230    /// assert!(!SampleFormat::U8.is_signed());
231    /// ```
232    #[must_use]
233    pub const fn is_signed(&self) -> bool {
234        !matches!(self, Self::U8 | Self::U8p | Self::Other(_))
235    }
236
237    /// Returns the packed (interleaved) equivalent of this format.
238    ///
239    /// If the format is already packed, returns itself.
240    ///
241    /// # Examples
242    ///
243    /// ```
244    /// use ff_format::SampleFormat;
245    ///
246    /// assert_eq!(SampleFormat::F32p.packed_equivalent(), SampleFormat::F32);
247    /// assert_eq!(SampleFormat::I16p.packed_equivalent(), SampleFormat::I16);
248    /// assert_eq!(SampleFormat::F32.packed_equivalent(), SampleFormat::F32);
249    /// ```
250    #[must_use]
251    pub const fn packed_equivalent(&self) -> Self {
252        match self {
253            Self::U8p => Self::U8,
254            Self::I16p => Self::I16,
255            Self::I32p => Self::I32,
256            Self::F32p => Self::F32,
257            Self::F64p => Self::F64,
258            // Already packed or unknown
259            other => *other,
260        }
261    }
262
263    /// Returns the planar equivalent of this format.
264    ///
265    /// If the format is already planar, returns itself.
266    ///
267    /// # Examples
268    ///
269    /// ```
270    /// use ff_format::SampleFormat;
271    ///
272    /// assert_eq!(SampleFormat::F32.planar_equivalent(), SampleFormat::F32p);
273    /// assert_eq!(SampleFormat::I16.planar_equivalent(), SampleFormat::I16p);
274    /// assert_eq!(SampleFormat::F32p.planar_equivalent(), SampleFormat::F32p);
275    /// ```
276    #[must_use]
277    pub const fn planar_equivalent(&self) -> Self {
278        match self {
279            Self::U8 => Self::U8p,
280            Self::I16 => Self::I16p,
281            Self::I32 => Self::I32p,
282            Self::F32 => Self::F32p,
283            Self::F64 => Self::F64p,
284            // Already planar or unknown
285            other => *other,
286        }
287    }
288
289    /// Returns the bit depth of this format.
290    ///
291    /// # Examples
292    ///
293    /// ```
294    /// use ff_format::SampleFormat;
295    ///
296    /// assert_eq!(SampleFormat::U8.bit_depth(), 8);
297    /// assert_eq!(SampleFormat::I16.bit_depth(), 16);
298    /// assert_eq!(SampleFormat::F32.bit_depth(), 32);
299    /// assert_eq!(SampleFormat::F64.bit_depth(), 64);
300    /// ```
301    #[must_use]
302    pub const fn bit_depth(&self) -> usize {
303        self.bytes_per_sample() * 8
304    }
305}
306
307impl fmt::Display for SampleFormat {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        write!(f, "{}", self.name())
310    }
311}
312
313impl Default for SampleFormat {
314    /// Returns the default sample format.
315    ///
316    /// The default is [`SampleFormat::F32`] as it's the most common
317    /// format used in audio editing and processing.
318    fn default() -> Self {
319        Self::F32
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326
327    #[test]
328    fn test_format_names() {
329        assert_eq!(SampleFormat::U8.name(), "u8");
330        assert_eq!(SampleFormat::I16.name(), "s16");
331        assert_eq!(SampleFormat::I32.name(), "s32");
332        assert_eq!(SampleFormat::F32.name(), "flt");
333        assert_eq!(SampleFormat::F64.name(), "dbl");
334        assert_eq!(SampleFormat::U8p.name(), "u8p");
335        assert_eq!(SampleFormat::I16p.name(), "s16p");
336        assert_eq!(SampleFormat::I32p.name(), "s32p");
337        assert_eq!(SampleFormat::F32p.name(), "fltp");
338        assert_eq!(SampleFormat::F64p.name(), "dblp");
339        assert_eq!(SampleFormat::Other(999).name(), "unknown");
340    }
341
342    #[test]
343    fn test_bytes_per_sample() {
344        // 1 byte formats
345        assert_eq!(SampleFormat::U8.bytes_per_sample(), 1);
346        assert_eq!(SampleFormat::U8p.bytes_per_sample(), 1);
347
348        // 2 byte formats
349        assert_eq!(SampleFormat::I16.bytes_per_sample(), 2);
350        assert_eq!(SampleFormat::I16p.bytes_per_sample(), 2);
351
352        // 4 byte formats
353        assert_eq!(SampleFormat::I32.bytes_per_sample(), 4);
354        assert_eq!(SampleFormat::I32p.bytes_per_sample(), 4);
355        assert_eq!(SampleFormat::F32.bytes_per_sample(), 4);
356        assert_eq!(SampleFormat::F32p.bytes_per_sample(), 4);
357
358        // 8 byte formats
359        assert_eq!(SampleFormat::F64.bytes_per_sample(), 8);
360        assert_eq!(SampleFormat::F64p.bytes_per_sample(), 8);
361
362        // Unknown defaults to 4
363        assert_eq!(SampleFormat::Other(123).bytes_per_sample(), 4);
364    }
365
366    #[test]
367    fn test_is_planar() {
368        // Packed formats
369        assert!(!SampleFormat::U8.is_planar());
370        assert!(!SampleFormat::I16.is_planar());
371        assert!(!SampleFormat::I32.is_planar());
372        assert!(!SampleFormat::F32.is_planar());
373        assert!(!SampleFormat::F64.is_planar());
374        assert!(!SampleFormat::Other(0).is_planar());
375
376        // Planar formats
377        assert!(SampleFormat::U8p.is_planar());
378        assert!(SampleFormat::I16p.is_planar());
379        assert!(SampleFormat::I32p.is_planar());
380        assert!(SampleFormat::F32p.is_planar());
381        assert!(SampleFormat::F64p.is_planar());
382    }
383
384    #[test]
385    fn test_is_packed() {
386        // Packed formats
387        assert!(SampleFormat::U8.is_packed());
388        assert!(SampleFormat::I16.is_packed());
389        assert!(SampleFormat::I32.is_packed());
390        assert!(SampleFormat::F32.is_packed());
391        assert!(SampleFormat::F64.is_packed());
392
393        // Planar formats
394        assert!(!SampleFormat::U8p.is_packed());
395        assert!(!SampleFormat::I16p.is_packed());
396        assert!(!SampleFormat::I32p.is_packed());
397        assert!(!SampleFormat::F32p.is_packed());
398        assert!(!SampleFormat::F64p.is_packed());
399    }
400
401    #[test]
402    fn test_is_float() {
403        // Float formats
404        assert!(SampleFormat::F32.is_float());
405        assert!(SampleFormat::F64.is_float());
406        assert!(SampleFormat::F32p.is_float());
407        assert!(SampleFormat::F64p.is_float());
408
409        // Integer formats
410        assert!(!SampleFormat::U8.is_float());
411        assert!(!SampleFormat::I16.is_float());
412        assert!(!SampleFormat::I32.is_float());
413        assert!(!SampleFormat::U8p.is_float());
414        assert!(!SampleFormat::I16p.is_float());
415        assert!(!SampleFormat::I32p.is_float());
416        assert!(!SampleFormat::Other(0).is_float());
417    }
418
419    #[test]
420    fn test_is_integer() {
421        // Integer formats
422        assert!(SampleFormat::U8.is_integer());
423        assert!(SampleFormat::I16.is_integer());
424        assert!(SampleFormat::I32.is_integer());
425        assert!(SampleFormat::U8p.is_integer());
426        assert!(SampleFormat::I16p.is_integer());
427        assert!(SampleFormat::I32p.is_integer());
428
429        // Float formats
430        assert!(!SampleFormat::F32.is_integer());
431        assert!(!SampleFormat::F64.is_integer());
432        assert!(!SampleFormat::F32p.is_integer());
433        assert!(!SampleFormat::F64p.is_integer());
434        assert!(!SampleFormat::Other(0).is_integer());
435    }
436
437    #[test]
438    fn test_is_signed() {
439        // Signed formats
440        assert!(SampleFormat::I16.is_signed());
441        assert!(SampleFormat::I32.is_signed());
442        assert!(SampleFormat::F32.is_signed());
443        assert!(SampleFormat::F64.is_signed());
444        assert!(SampleFormat::I16p.is_signed());
445        assert!(SampleFormat::I32p.is_signed());
446        assert!(SampleFormat::F32p.is_signed());
447        assert!(SampleFormat::F64p.is_signed());
448
449        // Unsigned formats
450        assert!(!SampleFormat::U8.is_signed());
451        assert!(!SampleFormat::U8p.is_signed());
452        assert!(!SampleFormat::Other(0).is_signed());
453    }
454
455    #[test]
456    fn test_packed_equivalent() {
457        // Planar to packed
458        assert_eq!(SampleFormat::U8p.packed_equivalent(), SampleFormat::U8);
459        assert_eq!(SampleFormat::I16p.packed_equivalent(), SampleFormat::I16);
460        assert_eq!(SampleFormat::I32p.packed_equivalent(), SampleFormat::I32);
461        assert_eq!(SampleFormat::F32p.packed_equivalent(), SampleFormat::F32);
462        assert_eq!(SampleFormat::F64p.packed_equivalent(), SampleFormat::F64);
463
464        // Already packed - returns itself
465        assert_eq!(SampleFormat::U8.packed_equivalent(), SampleFormat::U8);
466        assert_eq!(SampleFormat::I16.packed_equivalent(), SampleFormat::I16);
467        assert_eq!(SampleFormat::F32.packed_equivalent(), SampleFormat::F32);
468
469        // Unknown returns itself
470        assert_eq!(
471            SampleFormat::Other(42).packed_equivalent(),
472            SampleFormat::Other(42)
473        );
474    }
475
476    #[test]
477    fn test_planar_equivalent() {
478        // Packed to planar
479        assert_eq!(SampleFormat::U8.planar_equivalent(), SampleFormat::U8p);
480        assert_eq!(SampleFormat::I16.planar_equivalent(), SampleFormat::I16p);
481        assert_eq!(SampleFormat::I32.planar_equivalent(), SampleFormat::I32p);
482        assert_eq!(SampleFormat::F32.planar_equivalent(), SampleFormat::F32p);
483        assert_eq!(SampleFormat::F64.planar_equivalent(), SampleFormat::F64p);
484
485        // Already planar - returns itself
486        assert_eq!(SampleFormat::U8p.planar_equivalent(), SampleFormat::U8p);
487        assert_eq!(SampleFormat::I16p.planar_equivalent(), SampleFormat::I16p);
488        assert_eq!(SampleFormat::F32p.planar_equivalent(), SampleFormat::F32p);
489
490        // Unknown returns itself
491        assert_eq!(
492            SampleFormat::Other(42).planar_equivalent(),
493            SampleFormat::Other(42)
494        );
495    }
496
497    #[test]
498    fn test_bit_depth() {
499        assert_eq!(SampleFormat::U8.bit_depth(), 8);
500        assert_eq!(SampleFormat::U8p.bit_depth(), 8);
501        assert_eq!(SampleFormat::I16.bit_depth(), 16);
502        assert_eq!(SampleFormat::I16p.bit_depth(), 16);
503        assert_eq!(SampleFormat::I32.bit_depth(), 32);
504        assert_eq!(SampleFormat::F32.bit_depth(), 32);
505        assert_eq!(SampleFormat::F64.bit_depth(), 64);
506        assert_eq!(SampleFormat::F64p.bit_depth(), 64);
507    }
508
509    #[test]
510    fn test_display() {
511        assert_eq!(format!("{}", SampleFormat::F32), "flt");
512        assert_eq!(format!("{}", SampleFormat::I16), "s16");
513        assert_eq!(format!("{}", SampleFormat::F32p), "fltp");
514        assert_eq!(format!("{}", SampleFormat::Other(123)), "unknown");
515    }
516
517    #[test]
518    fn test_default() {
519        assert_eq!(SampleFormat::default(), SampleFormat::F32);
520    }
521
522    #[test]
523    fn test_debug() {
524        assert_eq!(format!("{:?}", SampleFormat::F32), "F32");
525        assert_eq!(format!("{:?}", SampleFormat::I16p), "I16p");
526        assert_eq!(format!("{:?}", SampleFormat::Other(42)), "Other(42)");
527    }
528
529    #[test]
530    fn test_equality_and_hash() {
531        use std::collections::HashSet;
532
533        assert_eq!(SampleFormat::F32, SampleFormat::F32);
534        assert_ne!(SampleFormat::F32, SampleFormat::F32p);
535        assert_eq!(SampleFormat::Other(1), SampleFormat::Other(1));
536        assert_ne!(SampleFormat::Other(1), SampleFormat::Other(2));
537
538        // Test Hash implementation
539        let mut set = HashSet::new();
540        set.insert(SampleFormat::F32);
541        set.insert(SampleFormat::I16);
542        assert!(set.contains(&SampleFormat::F32));
543        assert!(!set.contains(&SampleFormat::F64));
544    }
545
546    #[test]
547    fn test_copy() {
548        let format = SampleFormat::F32;
549        let copied = format;
550        // Both original and copy are still usable (Copy semantics)
551        assert_eq!(format, copied);
552        assert_eq!(format.name(), copied.name());
553    }
554
555    #[test]
556    fn test_round_trip_equivalents() {
557        // Packed -> planar -> packed should return original
558        let packed_formats = [
559            SampleFormat::U8,
560            SampleFormat::I16,
561            SampleFormat::I32,
562            SampleFormat::F32,
563            SampleFormat::F64,
564        ];
565        for format in packed_formats {
566            assert_eq!(format.planar_equivalent().packed_equivalent(), format);
567        }
568
569        // Planar -> packed -> planar should return original
570        let planar_formats = [
571            SampleFormat::U8p,
572            SampleFormat::I16p,
573            SampleFormat::I32p,
574            SampleFormat::F32p,
575            SampleFormat::F64p,
576        ];
577        for format in planar_formats {
578            assert_eq!(format.packed_equivalent().planar_equivalent(), format);
579        }
580    }
581}