Skip to main content

g2g_core/
channels.rs

1//! Speaker channel positions and layouts for multichannel PCM. A
2//! [`ChannelLayout`] is a bitmask in the WAV / ffmpeg bit order, and that bit
3//! order (ascending) is also the interleave order of the samples, so a layout
4//! fully describes which speaker each interleaved channel index feeds.
5//!
6//! [`Caps::Audio`](crate::Caps::Audio) carries only a channel count; the layout
7//! for a count follows the ffmpeg default-layout convention
8//! ([`ChannelLayout::default_for`]), which is what the decode path emits.
9
10/// One speaker position. The discriminant is the WAV / ffmpeg mask bit.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[repr(u8)]
13pub enum ChannelPosition {
14    /// Front left.
15    Fl = 0,
16    /// Front right.
17    Fr = 1,
18    /// Front center.
19    Fc = 2,
20    /// Low-frequency effects (subwoofer).
21    Lfe = 3,
22    /// Back left.
23    Bl = 4,
24    /// Back right.
25    Br = 5,
26    /// Front left-of-center.
27    Flc = 6,
28    /// Front right-of-center.
29    Frc = 7,
30    /// Back center.
31    Bc = 8,
32    /// Side left.
33    Sl = 9,
34    /// Side right.
35    Sr = 10,
36}
37
38impl ChannelPosition {
39    /// Every position, in mask-bit (interleave) order.
40    pub const ALL: [ChannelPosition; 11] = [
41        ChannelPosition::Fl,
42        ChannelPosition::Fr,
43        ChannelPosition::Fc,
44        ChannelPosition::Lfe,
45        ChannelPosition::Bl,
46        ChannelPosition::Br,
47        ChannelPosition::Flc,
48        ChannelPosition::Frc,
49        ChannelPosition::Bc,
50        ChannelPosition::Sl,
51        ChannelPosition::Sr,
52    ];
53
54    const fn bit(self) -> u16 {
55        1 << (self as u16)
56    }
57}
58
59/// A set of speaker positions; ascending bit order is the interleave order.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct ChannelLayout(u16);
62
63impl ChannelLayout {
64    /// Mono: front center only.
65    pub const MONO: ChannelLayout = ChannelLayout::of(&[ChannelPosition::Fc]);
66    /// Stereo: front left + right.
67    pub const STEREO: ChannelLayout =
68        ChannelLayout::of(&[ChannelPosition::Fl, ChannelPosition::Fr]);
69    /// 5.1 (back): FL FR FC LFE BL BR.
70    pub const SURROUND_5_1: ChannelLayout = ChannelLayout::of(&[
71        ChannelPosition::Fl,
72        ChannelPosition::Fr,
73        ChannelPosition::Fc,
74        ChannelPosition::Lfe,
75        ChannelPosition::Bl,
76        ChannelPosition::Br,
77    ]);
78    /// 7.1: FL FR FC LFE BL BR SL SR.
79    pub const SURROUND_7_1: ChannelLayout = ChannelLayout::of(&[
80        ChannelPosition::Fl,
81        ChannelPosition::Fr,
82        ChannelPosition::Fc,
83        ChannelPosition::Lfe,
84        ChannelPosition::Bl,
85        ChannelPosition::Br,
86        ChannelPosition::Sl,
87        ChannelPosition::Sr,
88    ]);
89
90    /// Layout from a set of positions.
91    pub const fn of(positions: &[ChannelPosition]) -> Self {
92        let mut mask = 0u16;
93        let mut i = 0;
94        while i < positions.len() {
95            mask |= positions[i].bit();
96            i += 1;
97        }
98        ChannelLayout(mask)
99    }
100
101    /// The conventional layout for a channel count (the ffmpeg default-layout
102    /// table, which the decode path emits): mono, stereo, 2.1, 4.0, 5.0, 5.1,
103    /// 6.1, 7.1. `None` for 0 or > 8 channels.
104    pub const fn default_for(channels: u8) -> Option<Self> {
105        use ChannelPosition::*;
106        Some(match channels {
107            1 => Self::MONO,
108            2 => Self::STEREO,
109            3 => Self::of(&[Fl, Fr, Lfe]),
110            4 => Self::of(&[Fl, Fr, Fc, Bc]),
111            5 => Self::of(&[Fl, Fr, Fc, Bl, Br]),
112            6 => Self::SURROUND_5_1,
113            7 => Self::of(&[Fl, Fr, Fc, Lfe, Bc, Sl, Sr]),
114            8 => Self::SURROUND_7_1,
115            _ => return None,
116        })
117    }
118
119    /// Number of channels in the layout.
120    pub const fn channels(self) -> u8 {
121        self.0.count_ones() as u8
122    }
123
124    pub const fn contains(self, position: ChannelPosition) -> bool {
125        self.0 & position.bit() != 0
126    }
127
128    /// The interleaved channel index of `position` (its rank among the set
129    /// bits), or `None` if the layout lacks it.
130    pub const fn index_of(self, position: ChannelPosition) -> Option<usize> {
131        if !self.contains(position) {
132            return None;
133        }
134        Some((self.0 & (position.bit() - 1)).count_ones() as usize)
135    }
136
137    /// The positions in interleave order.
138    pub fn positions(self) -> impl Iterator<Item = ChannelPosition> {
139        ChannelPosition::ALL
140            .into_iter()
141            .filter(move |p| self.contains(*p))
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn default_layouts_match_their_counts() {
151        for n in 1..=8 {
152            let layout = ChannelLayout::default_for(n).expect("default exists");
153            assert_eq!(layout.channels(), n, "count {n}");
154        }
155        assert_eq!(ChannelLayout::default_for(0), None);
156        assert_eq!(ChannelLayout::default_for(9), None);
157    }
158
159    #[test]
160    fn index_follows_interleave_order() {
161        // 5.1 interleaves FL FR FC LFE BL BR.
162        let l = ChannelLayout::SURROUND_5_1;
163        assert_eq!(l.index_of(ChannelPosition::Fl), Some(0));
164        assert_eq!(l.index_of(ChannelPosition::Fc), Some(2));
165        assert_eq!(l.index_of(ChannelPosition::Lfe), Some(3));
166        assert_eq!(l.index_of(ChannelPosition::Br), Some(5));
167        assert_eq!(l.index_of(ChannelPosition::Sl), None);
168        let expected = [
169            ChannelPosition::Fl,
170            ChannelPosition::Fr,
171            ChannelPosition::Fc,
172            ChannelPosition::Lfe,
173            ChannelPosition::Bl,
174            ChannelPosition::Br,
175        ];
176        assert!(l.positions().eq(expected));
177    }
178}