Skip to main content

ff_format/
channel.rs

1//! Audio channel layout definitions.
2//!
3//! This module provides the [`ChannelLayout`] enum for representing
4//! common audio channel configurations.
5//!
6//! # Examples
7//!
8//! ```
9//! use ff_format::channel::ChannelLayout;
10//!
11//! let stereo = ChannelLayout::Stereo;
12//! assert_eq!(stereo.channels(), 2);
13//! assert!(stereo.is_stereo());
14//!
15//! let surround = ChannelLayout::Surround5_1;
16//! assert_eq!(surround.channels(), 6);
17//! assert!(surround.is_surround());
18//! ```
19
20use std::fmt;
21
22/// Audio channel layout representing the speaker configuration.
23///
24/// This enum covers common channel layouts used in audio/video files.
25/// For uncommon layouts, use `Other` with the channel count.
26///
27/// # Common Layouts
28///
29/// - **Mono**: Single channel (1.0)
30/// - **Stereo**: Left + Right (2.0)
31/// - **Surround 5.1**: FL + FR + FC + LFE + BL + BR (standard home theater)
32/// - **Surround 7.1**: 5.1 + SL + SR (extended surround)
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34#[non_exhaustive]
35pub enum ChannelLayout {
36    /// Mono (1 channel)
37    Mono,
38    /// Stereo (2 channels: Left, Right)
39    Stereo,
40    /// 2.1 (3 channels: Left, Right, LFE)
41    Stereo2_1,
42    /// 3.0 (3 channels: Left, Right, Center)
43    Surround3_0,
44    /// 4.0 Quad (4 channels: FL, FR, BL, BR)
45    Quad,
46    /// 5.0 (5 channels: FL, FR, FC, BL, BR)
47    Surround5_0,
48    /// 5.1 (6 channels: FL, FR, FC, LFE, BL, BR)
49    Surround5_1,
50    /// 6.1 (7 channels: FL, FR, FC, LFE, BC, SL, SR)
51    Surround6_1,
52    /// 7.1 (8 channels: FL, FR, FC, LFE, BL, BR, SL, SR)
53    Surround7_1,
54    /// Other layout with specified channel count
55    Other(u32),
56}
57
58impl ChannelLayout {
59    /// Returns the number of audio channels in this layout.
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use ff_format::channel::ChannelLayout;
65    ///
66    /// assert_eq!(ChannelLayout::Mono.channels(), 1);
67    /// assert_eq!(ChannelLayout::Stereo.channels(), 2);
68    /// assert_eq!(ChannelLayout::Surround5_1.channels(), 6);
69    /// assert_eq!(ChannelLayout::Surround7_1.channels(), 8);
70    /// assert_eq!(ChannelLayout::Other(10).channels(), 10);
71    /// ```
72    #[must_use]
73    pub const fn channels(&self) -> u32 {
74        match self {
75            Self::Mono => 1,
76            Self::Stereo => 2,
77            Self::Stereo2_1 | Self::Surround3_0 => 3,
78            Self::Quad => 4,
79            Self::Surround5_0 => 5,
80            Self::Surround5_1 => 6,
81            Self::Surround6_1 => 7,
82            Self::Surround7_1 => 8,
83            Self::Other(n) => *n,
84        }
85    }
86
87    /// Returns the layout name as a human-readable string.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// use ff_format::channel::ChannelLayout;
93    ///
94    /// assert_eq!(ChannelLayout::Mono.name(), "mono");
95    /// assert_eq!(ChannelLayout::Stereo.name(), "stereo");
96    /// assert_eq!(ChannelLayout::Surround5_1.name(), "5.1");
97    /// ```
98    #[must_use]
99    pub const fn name(&self) -> &'static str {
100        match self {
101            Self::Mono => "mono",
102            Self::Stereo => "stereo",
103            Self::Stereo2_1 => "2.1",
104            Self::Surround3_0 => "3.0",
105            Self::Quad => "quad",
106            Self::Surround5_0 => "5.0",
107            Self::Surround5_1 => "5.1",
108            Self::Surround6_1 => "6.1",
109            Self::Surround7_1 => "7.1",
110            Self::Other(_) => "custom",
111        }
112    }
113
114    /// Returns `true` if this is a mono layout.
115    ///
116    /// # Examples
117    ///
118    /// ```
119    /// use ff_format::channel::ChannelLayout;
120    ///
121    /// assert!(ChannelLayout::Mono.is_mono());
122    /// assert!(!ChannelLayout::Stereo.is_mono());
123    /// ```
124    #[must_use]
125    pub const fn is_mono(&self) -> bool {
126        matches!(self, Self::Mono)
127    }
128
129    /// Returns `true` if this is a stereo layout.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// use ff_format::channel::ChannelLayout;
135    ///
136    /// assert!(ChannelLayout::Stereo.is_stereo());
137    /// assert!(!ChannelLayout::Mono.is_stereo());
138    /// ```
139    #[must_use]
140    pub const fn is_stereo(&self) -> bool {
141        matches!(self, Self::Stereo)
142    }
143
144    /// Returns `true` if this is a surround sound layout (more than 2 channels).
145    ///
146    /// # Examples
147    ///
148    /// ```
149    /// use ff_format::channel::ChannelLayout;
150    ///
151    /// assert!(ChannelLayout::Surround5_1.is_surround());
152    /// assert!(ChannelLayout::Surround7_1.is_surround());
153    /// assert!(!ChannelLayout::Stereo.is_surround());
154    /// ```
155    #[must_use]
156    pub const fn is_surround(&self) -> bool {
157        matches!(
158            self,
159            Self::Stereo2_1
160                | Self::Surround3_0
161                | Self::Quad
162                | Self::Surround5_0
163                | Self::Surround5_1
164                | Self::Surround6_1
165                | Self::Surround7_1
166        ) || matches!(self, Self::Other(n) if *n > 2)
167    }
168
169    /// Returns `true` if this layout includes an LFE (subwoofer) channel.
170    ///
171    /// # Examples
172    ///
173    /// ```
174    /// use ff_format::channel::ChannelLayout;
175    ///
176    /// assert!(ChannelLayout::Stereo2_1.has_lfe());
177    /// assert!(ChannelLayout::Surround5_1.has_lfe());
178    /// assert!(!ChannelLayout::Surround5_0.has_lfe());
179    /// ```
180    #[must_use]
181    pub const fn has_lfe(&self) -> bool {
182        matches!(
183            self,
184            Self::Stereo2_1 | Self::Surround5_1 | Self::Surround6_1 | Self::Surround7_1
185        )
186    }
187
188    /// Creates a `ChannelLayout` from a channel count.
189    ///
190    /// This tries to match common layouts, falling back to `Other` for
191    /// uncommon channel counts.
192    ///
193    /// # Examples
194    ///
195    /// ```
196    /// use ff_format::channel::ChannelLayout;
197    ///
198    /// assert_eq!(ChannelLayout::from_channels(1), ChannelLayout::Mono);
199    /// assert_eq!(ChannelLayout::from_channels(2), ChannelLayout::Stereo);
200    /// assert_eq!(ChannelLayout::from_channels(6), ChannelLayout::Surround5_1);
201    /// assert_eq!(ChannelLayout::from_channels(10), ChannelLayout::Other(10));
202    /// ```
203    #[must_use]
204    pub const fn from_channels(channels: u32) -> Self {
205        match channels {
206            1 => Self::Mono,
207            2 => Self::Stereo,
208            // 3 channels could be either 2.1 or 3.0, default to stereo + LFE
209            3 => Self::Stereo2_1,
210            4 => Self::Quad,
211            5 => Self::Surround5_0,
212            6 => Self::Surround5_1,
213            7 => Self::Surround6_1,
214            8 => Self::Surround7_1,
215            n => Self::Other(n),
216        }
217    }
218}
219
220impl Default for ChannelLayout {
221    /// Returns the default channel layout (Stereo).
222    fn default() -> Self {
223        Self::Stereo
224    }
225}
226
227impl fmt::Display for ChannelLayout {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        write!(f, "{}", self.name())
230    }
231}
232
233impl From<u32> for ChannelLayout {
234    fn from(channels: u32) -> Self {
235        Self::from_channels(channels)
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn test_channel_count() {
245        assert_eq!(ChannelLayout::Mono.channels(), 1);
246        assert_eq!(ChannelLayout::Stereo.channels(), 2);
247        assert_eq!(ChannelLayout::Stereo2_1.channels(), 3);
248        assert_eq!(ChannelLayout::Surround3_0.channels(), 3);
249        assert_eq!(ChannelLayout::Quad.channels(), 4);
250        assert_eq!(ChannelLayout::Surround5_0.channels(), 5);
251        assert_eq!(ChannelLayout::Surround5_1.channels(), 6);
252        assert_eq!(ChannelLayout::Surround6_1.channels(), 7);
253        assert_eq!(ChannelLayout::Surround7_1.channels(), 8);
254        assert_eq!(ChannelLayout::Other(16).channels(), 16);
255    }
256
257    #[test]
258    fn test_names() {
259        assert_eq!(ChannelLayout::Mono.name(), "mono");
260        assert_eq!(ChannelLayout::Stereo.name(), "stereo");
261        assert_eq!(ChannelLayout::Stereo2_1.name(), "2.1");
262        assert_eq!(ChannelLayout::Surround3_0.name(), "3.0");
263        assert_eq!(ChannelLayout::Quad.name(), "quad");
264        assert_eq!(ChannelLayout::Surround5_0.name(), "5.0");
265        assert_eq!(ChannelLayout::Surround5_1.name(), "5.1");
266        assert_eq!(ChannelLayout::Surround6_1.name(), "6.1");
267        assert_eq!(ChannelLayout::Surround7_1.name(), "7.1");
268        assert_eq!(ChannelLayout::Other(10).name(), "custom");
269    }
270
271    #[test]
272    fn test_display() {
273        assert_eq!(format!("{}", ChannelLayout::Mono), "mono");
274        assert_eq!(format!("{}", ChannelLayout::Surround5_1), "5.1");
275        assert_eq!(format!("{}", ChannelLayout::Other(10)), "custom");
276    }
277
278    #[test]
279    fn test_default() {
280        assert_eq!(ChannelLayout::default(), ChannelLayout::Stereo);
281    }
282
283    #[test]
284    fn test_is_mono_stereo() {
285        assert!(ChannelLayout::Mono.is_mono());
286        assert!(!ChannelLayout::Stereo.is_mono());
287        assert!(!ChannelLayout::Surround5_1.is_mono());
288
289        assert!(ChannelLayout::Stereo.is_stereo());
290        assert!(!ChannelLayout::Mono.is_stereo());
291        assert!(!ChannelLayout::Surround5_1.is_stereo());
292    }
293
294    #[test]
295    fn test_is_surround() {
296        assert!(!ChannelLayout::Mono.is_surround());
297        assert!(!ChannelLayout::Stereo.is_surround());
298        assert!(ChannelLayout::Stereo2_1.is_surround());
299        assert!(ChannelLayout::Surround3_0.is_surround());
300        assert!(ChannelLayout::Quad.is_surround());
301        assert!(ChannelLayout::Surround5_0.is_surround());
302        assert!(ChannelLayout::Surround5_1.is_surround());
303        assert!(ChannelLayout::Surround6_1.is_surround());
304        assert!(ChannelLayout::Surround7_1.is_surround());
305
306        // Other with > 2 channels is surround
307        assert!(ChannelLayout::Other(4).is_surround());
308        // Other with <= 2 channels is not surround
309        assert!(!ChannelLayout::Other(2).is_surround());
310    }
311
312    #[test]
313    fn test_has_lfe() {
314        assert!(!ChannelLayout::Mono.has_lfe());
315        assert!(!ChannelLayout::Stereo.has_lfe());
316        assert!(ChannelLayout::Stereo2_1.has_lfe());
317        assert!(!ChannelLayout::Surround3_0.has_lfe());
318        assert!(!ChannelLayout::Quad.has_lfe());
319        assert!(!ChannelLayout::Surround5_0.has_lfe());
320        assert!(ChannelLayout::Surround5_1.has_lfe());
321        assert!(ChannelLayout::Surround6_1.has_lfe());
322        assert!(ChannelLayout::Surround7_1.has_lfe());
323    }
324
325    #[test]
326    fn test_from_channels() {
327        assert_eq!(ChannelLayout::from_channels(1), ChannelLayout::Mono);
328        assert_eq!(ChannelLayout::from_channels(2), ChannelLayout::Stereo);
329        assert_eq!(ChannelLayout::from_channels(3), ChannelLayout::Stereo2_1);
330        assert_eq!(ChannelLayout::from_channels(4), ChannelLayout::Quad);
331        assert_eq!(ChannelLayout::from_channels(5), ChannelLayout::Surround5_0);
332        assert_eq!(ChannelLayout::from_channels(6), ChannelLayout::Surround5_1);
333        assert_eq!(ChannelLayout::from_channels(7), ChannelLayout::Surround6_1);
334        assert_eq!(ChannelLayout::from_channels(8), ChannelLayout::Surround7_1);
335        assert_eq!(ChannelLayout::from_channels(10), ChannelLayout::Other(10));
336    }
337
338    #[test]
339    fn test_from_u32() {
340        let layout: ChannelLayout = 2u32.into();
341        assert_eq!(layout, ChannelLayout::Stereo);
342
343        let layout: ChannelLayout = 6u32.into();
344        assert_eq!(layout, ChannelLayout::Surround5_1);
345    }
346
347    #[test]
348    fn test_debug() {
349        assert_eq!(format!("{:?}", ChannelLayout::Mono), "Mono");
350        assert_eq!(format!("{:?}", ChannelLayout::Surround5_1), "Surround5_1");
351        assert_eq!(format!("{:?}", ChannelLayout::Other(10)), "Other(10)");
352    }
353
354    #[test]
355    fn test_equality_and_hash() {
356        use std::collections::HashSet;
357
358        assert_eq!(ChannelLayout::Stereo, ChannelLayout::Stereo);
359        assert_ne!(ChannelLayout::Stereo, ChannelLayout::Mono);
360        assert_eq!(ChannelLayout::Other(4), ChannelLayout::Other(4));
361        assert_ne!(ChannelLayout::Other(4), ChannelLayout::Other(5));
362
363        let mut set = HashSet::new();
364        set.insert(ChannelLayout::Stereo);
365        set.insert(ChannelLayout::Surround5_1);
366        assert!(set.contains(&ChannelLayout::Stereo));
367        assert!(!set.contains(&ChannelLayout::Mono));
368    }
369
370    #[test]
371    fn test_copy() {
372        let layout = ChannelLayout::Surround5_1;
373        let copied = layout;
374        assert_eq!(layout, copied);
375        assert_eq!(layout.channels(), copied.channels());
376    }
377}