bbx_dsp/
channel.rs

1//! Multi-channel audio configuration.
2//!
3//! This module provides types for describing channel layouts and how blocks
4//! should handle multi-channel audio.
5
6/// Describes the channel layout for audio processing.
7///
8/// Standard layouts include mono, stereo, surround formats, and ambisonics.
9/// Use `Custom(n)` for arbitrary channel counts.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
11pub enum ChannelLayout {
12    /// Single channel (mono).
13    Mono,
14
15    /// Two channels (left, right).
16    #[default]
17    Stereo,
18
19    /// 5.1 surround: L, R, C, LFE, Ls, Rs (6 channels).
20    Surround51,
21
22    /// 7.1 surround: L, R, C, LFE, Ls, Rs, Lrs, Rrs (8 channels).
23    Surround71,
24
25    /// First-order ambisonics (4 channels: W, Y, Z, X).
26    AmbisonicFoa,
27
28    /// Second-order ambisonics (9 channels).
29    AmbisonicSoa,
30
31    /// Third-order ambisonics (16 channels).
32    AmbisonicToa,
33
34    /// Custom channel count for non-standard configurations.
35    Custom(usize),
36}
37
38impl ChannelLayout {
39    /// Returns the number of channels for this layout.
40    #[inline]
41    pub const fn channel_count(&self) -> usize {
42        match self {
43            Self::Mono => 1,
44            Self::Stereo => 2,
45            Self::Surround51 => 6,
46            Self::Surround71 => 8,
47            Self::AmbisonicFoa => 4,
48            Self::AmbisonicSoa => 9,
49            Self::AmbisonicToa => 16,
50            Self::Custom(n) => *n,
51        }
52    }
53
54    /// Returns `true` if this is an ambisonic layout.
55    #[inline]
56    pub const fn is_ambisonic(&self) -> bool {
57        matches!(self, Self::AmbisonicFoa | Self::AmbisonicSoa | Self::AmbisonicToa)
58    }
59
60    /// Returns the ambisonic order if this is an ambisonic layout.
61    ///
62    /// Returns `None` for non-ambisonic layouts.
63    #[inline]
64    pub const fn ambisonic_order(&self) -> Option<usize> {
65        match self {
66            Self::AmbisonicFoa => Some(1),
67            Self::AmbisonicSoa => Some(2),
68            Self::AmbisonicToa => Some(3),
69            _ => None,
70        }
71    }
72
73    /// Creates an ambisonic layout from an order (1-3).
74    ///
75    /// Returns `None` if the order is out of range.
76    #[inline]
77    pub const fn from_ambisonic_order(order: usize) -> Option<Self> {
78        match order {
79            1 => Some(Self::AmbisonicFoa),
80            2 => Some(Self::AmbisonicSoa),
81            3 => Some(Self::AmbisonicToa),
82            _ => None,
83        }
84    }
85
86    /// Returns the number of channels for a given ambisonic order.
87    ///
88    /// Formula: (order + 1)^2
89    #[inline]
90    pub const fn ambisonic_channel_count(order: usize) -> usize {
91        (order + 1) * (order + 1)
92    }
93}
94
95/// Describes how a block handles multi-channel audio.
96///
97/// Most blocks process channels independently (Parallel), while some blocks
98/// like panners and mixers need explicit control over channel routing (Explicit).
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
100pub enum ChannelConfig {
101    /// Process each channel independently.
102    ///
103    /// The block receives the same number of inputs and outputs, and each
104    /// channel is processed through the same algorithm. This is the default
105    /// for most effect blocks (filters, gain, distortion).
106    #[default]
107    Parallel,
108
109    /// Block handles channel routing internally.
110    ///
111    /// The block may have different input and output channel counts and
112    /// implements its own routing logic. Used for panners, mixers, and
113    /// channel splitters/mergers.
114    Explicit,
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn channel_layout_channel_count() {
123        assert_eq!(ChannelLayout::Mono.channel_count(), 1);
124        assert_eq!(ChannelLayout::Stereo.channel_count(), 2);
125        assert_eq!(ChannelLayout::Surround51.channel_count(), 6);
126        assert_eq!(ChannelLayout::Surround71.channel_count(), 8);
127        assert_eq!(ChannelLayout::AmbisonicFoa.channel_count(), 4);
128        assert_eq!(ChannelLayout::AmbisonicSoa.channel_count(), 9);
129        assert_eq!(ChannelLayout::AmbisonicToa.channel_count(), 16);
130        assert_eq!(ChannelLayout::Custom(12).channel_count(), 12);
131    }
132
133    #[test]
134    fn channel_layout_is_ambisonic() {
135        assert!(!ChannelLayout::Mono.is_ambisonic());
136        assert!(!ChannelLayout::Stereo.is_ambisonic());
137        assert!(!ChannelLayout::Surround51.is_ambisonic());
138        assert!(!ChannelLayout::Surround71.is_ambisonic());
139        assert!(ChannelLayout::AmbisonicFoa.is_ambisonic());
140        assert!(ChannelLayout::AmbisonicSoa.is_ambisonic());
141        assert!(ChannelLayout::AmbisonicToa.is_ambisonic());
142        assert!(!ChannelLayout::Custom(4).is_ambisonic());
143    }
144
145    #[test]
146    fn channel_layout_ambisonic_order() {
147        assert_eq!(ChannelLayout::Mono.ambisonic_order(), None);
148        assert_eq!(ChannelLayout::Stereo.ambisonic_order(), None);
149        assert_eq!(ChannelLayout::AmbisonicFoa.ambisonic_order(), Some(1));
150        assert_eq!(ChannelLayout::AmbisonicSoa.ambisonic_order(), Some(2));
151        assert_eq!(ChannelLayout::AmbisonicToa.ambisonic_order(), Some(3));
152    }
153
154    #[test]
155    fn channel_layout_from_ambisonic_order() {
156        assert_eq!(ChannelLayout::from_ambisonic_order(0), None);
157        assert_eq!(
158            ChannelLayout::from_ambisonic_order(1),
159            Some(ChannelLayout::AmbisonicFoa)
160        );
161        assert_eq!(
162            ChannelLayout::from_ambisonic_order(2),
163            Some(ChannelLayout::AmbisonicSoa)
164        );
165        assert_eq!(
166            ChannelLayout::from_ambisonic_order(3),
167            Some(ChannelLayout::AmbisonicToa)
168        );
169        assert_eq!(ChannelLayout::from_ambisonic_order(4), None);
170    }
171
172    #[test]
173    fn channel_layout_ambisonic_channel_count() {
174        assert_eq!(ChannelLayout::ambisonic_channel_count(1), 4);
175        assert_eq!(ChannelLayout::ambisonic_channel_count(2), 9);
176        assert_eq!(ChannelLayout::ambisonic_channel_count(3), 16);
177    }
178
179    #[test]
180    fn channel_layout_default() {
181        assert_eq!(ChannelLayout::default(), ChannelLayout::Stereo);
182    }
183
184    #[test]
185    fn channel_config_default() {
186        assert_eq!(ChannelConfig::default(), ChannelConfig::Parallel);
187    }
188}