Skip to main content

beamer_core/
bus_config.rs

1//! Cached bus configuration for format wrappers.
2//!
3//! This module provides types for caching bus configuration after it has been
4//! extracted from the plugin or host. This avoids repeated queries and provides
5//! fast access during audio processing.
6
7use crate::plugin::{BusInfo, BusLayout, BusType, Descriptor};
8use crate::types::{MAX_BUSES, MAX_CHANNELS};
9
10/// Lightweight bus information for caching.
11///
12/// Contains only the data needed for buffer allocation and validation.
13/// This is distinct from [`BusInfo`] which contains full metadata (name, etc.)
14/// for host queries.
15#[derive(Clone, Copy, Debug, Default)]
16pub struct CachedBusInfo {
17    /// Number of channels in this bus.
18    pub channel_count: usize,
19    /// Bus type (main or auxiliary).
20    pub bus_type: BusType,
21}
22
23impl CachedBusInfo {
24    /// Create a new cached bus info.
25    pub const fn new(channel_count: usize, bus_type: BusType) -> Self {
26        Self {
27            channel_count,
28            bus_type,
29        }
30    }
31
32    /// Create from a full BusInfo (drops name and is_default_active).
33    pub fn from_bus_info(info: &BusInfo) -> Self {
34        Self {
35            channel_count: info.channel_count as usize,
36            bus_type: info.bus_type,
37        }
38    }
39}
40
41/// Cached bus configuration from plugin or host.
42///
43/// Stores bus and channel information for fast access during audio processing.
44/// This provides a common representation used by both VST3 and AU wrappers.
45#[derive(Clone, Debug)]
46pub struct CachedBusConfig {
47    /// Number of input buses.
48    pub input_bus_count: usize,
49    /// Number of output buses.
50    pub output_bus_count: usize,
51    /// Input bus information.
52    pub input_buses: Vec<CachedBusInfo>,
53    /// Output bus information.
54    pub output_buses: Vec<CachedBusInfo>,
55}
56
57impl CachedBusConfig {
58    /// Create a new cached bus configuration.
59    ///
60    /// # Panics
61    ///
62    /// Panics if bus counts exceed MAX_BUSES.
63    pub fn new(input_buses: Vec<CachedBusInfo>, output_buses: Vec<CachedBusInfo>) -> Self {
64        assert!(
65            input_buses.len() <= MAX_BUSES,
66            "Input bus count {} exceeds MAX_BUSES ({})",
67            input_buses.len(),
68            MAX_BUSES
69        );
70        assert!(
71            output_buses.len() <= MAX_BUSES,
72            "Output bus count {} exceeds MAX_BUSES ({})",
73            output_buses.len(),
74            MAX_BUSES
75        );
76
77        Self {
78            input_bus_count: input_buses.len(),
79            output_bus_count: output_buses.len(),
80            input_buses,
81            output_buses,
82        }
83    }
84
85    /// Create from a plugin's bus configuration.
86    pub fn from_plugin<P: Descriptor>(plugin: &P) -> Self {
87        let input_bus_count = plugin.input_bus_count();
88        let output_bus_count = plugin.output_bus_count();
89
90        let input_buses: Vec<CachedBusInfo> = (0..input_bus_count)
91            .filter_map(|i| plugin.input_bus_info(i).map(|b| CachedBusInfo::from_bus_info(&b)))
92            .collect();
93
94        let output_buses: Vec<CachedBusInfo> = (0..output_bus_count)
95            .filter_map(|i| plugin.output_bus_info(i).map(|b| CachedBusInfo::from_bus_info(&b)))
96            .collect();
97
98        Self {
99            input_bus_count,
100            output_bus_count,
101            input_buses,
102            output_buses,
103        }
104    }
105
106    /// Get information about an input bus.
107    ///
108    /// Returns `None` if the bus index is out of bounds.
109    pub fn input_bus_info(&self, bus: usize) -> Option<&CachedBusInfo> {
110        self.input_buses.get(bus)
111    }
112
113    /// Get information about an output bus.
114    ///
115    /// Returns `None` if the bus index is out of bounds.
116    pub fn output_bus_info(&self, bus: usize) -> Option<&CachedBusInfo> {
117        self.output_buses.get(bus)
118    }
119
120    /// Get the total number of input channels across all buses.
121    pub fn total_input_channels(&self) -> usize {
122        self.input_buses.iter().map(|b| b.channel_count).sum()
123    }
124
125    /// Get the total number of output channels across all buses.
126    pub fn total_output_channels(&self) -> usize {
127        self.output_buses.iter().map(|b| b.channel_count).sum()
128    }
129
130    /// Convert to a BusLayout for plugin preparation.
131    ///
132    /// This enables passing the cached bus configuration to the plugin's
133    /// `prepare()` method via `FullAudioSetup`.
134    pub fn to_bus_layout(&self) -> BusLayout {
135        BusLayout {
136            main_input_channels: self
137                .input_bus_info(0)
138                .map(|b| b.channel_count as u32)
139                .unwrap_or(0),
140            main_output_channels: self
141                .output_bus_info(0)
142                .map(|b| b.channel_count as u32)
143                .unwrap_or(0),
144            aux_input_count: self.input_bus_count.saturating_sub(1),
145            aux_output_count: self.output_bus_count.saturating_sub(1),
146        }
147    }
148
149    /// Validate that this configuration doesn't exceed system limits.
150    ///
151    /// Checks that:
152    /// - Bus counts are within MAX_BUSES
153    /// - Channel counts per bus are within MAX_CHANNELS
154    ///
155    /// Returns `Ok(())` if valid, or `Err` with a descriptive message.
156    pub fn validate(&self) -> Result<(), String> {
157        // Validate bus counts
158        if self.input_bus_count > MAX_BUSES {
159            return Err(format!(
160                "Plugin declares {} input buses, but MAX_BUSES is {}",
161                self.input_bus_count, MAX_BUSES
162            ));
163        }
164        if self.output_bus_count > MAX_BUSES {
165            return Err(format!(
166                "Plugin declares {} output buses, but MAX_BUSES is {}",
167                self.output_bus_count, MAX_BUSES
168            ));
169        }
170
171        // Validate channel counts for each input bus
172        for (i, bus) in self.input_buses.iter().enumerate() {
173            if bus.channel_count > MAX_CHANNELS {
174                return Err(format!(
175                    "Input bus {} declares {} channels, but MAX_CHANNELS is {}",
176                    i, bus.channel_count, MAX_CHANNELS
177                ));
178            }
179        }
180
181        // Validate channel counts for each output bus
182        for (i, bus) in self.output_buses.iter().enumerate() {
183            if bus.channel_count > MAX_CHANNELS {
184                return Err(format!(
185                    "Output bus {} declares {} channels, but MAX_CHANNELS is {}",
186                    i, bus.channel_count, MAX_CHANNELS
187                ));
188            }
189        }
190
191        Ok(())
192    }
193}
194
195impl Default for CachedBusConfig {
196    /// Create a default stereo configuration (2in/2out, main bus only).
197    fn default() -> Self {
198        Self::new(
199            vec![CachedBusInfo::new(2, BusType::Main)],
200            vec![CachedBusInfo::new(2, BusType::Main)],
201        )
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn test_default_config() {
211        let config = CachedBusConfig::default();
212        assert_eq!(config.input_bus_count, 1);
213        assert_eq!(config.output_bus_count, 1);
214        assert_eq!(config.total_input_channels(), 2);
215        assert_eq!(config.total_output_channels(), 2);
216    }
217
218    #[test]
219    fn test_validate_success() {
220        let config = CachedBusConfig::default();
221        assert!(config.validate().is_ok());
222    }
223
224    #[test]
225    fn test_to_bus_layout() {
226        let config = CachedBusConfig::new(
227            vec![
228                CachedBusInfo::new(2, BusType::Main),
229                CachedBusInfo::new(2, BusType::Aux),
230            ],
231            vec![CachedBusInfo::new(2, BusType::Main)],
232        );
233        let layout = config.to_bus_layout();
234        assert_eq!(layout.main_input_channels, 2);
235        assert_eq!(layout.main_output_channels, 2);
236        assert_eq!(layout.aux_input_count, 1);
237        assert_eq!(layout.aux_output_count, 0);
238    }
239
240    #[test]
241    fn test_cached_bus_info_from_bus_info() {
242        let bus_info = BusInfo {
243            name: "Test Bus",
244            bus_type: BusType::Aux,
245            channel_count: 4,
246            is_default_active: true,
247        };
248        let cached = CachedBusInfo::from_bus_info(&bus_info);
249        assert_eq!(cached.channel_count, 4);
250        assert_eq!(cached.bus_type, BusType::Aux);
251    }
252
253    #[test]
254    fn test_empty_config() {
255        let config = CachedBusConfig::new(vec![], vec![]);
256        assert_eq!(config.input_bus_count, 0);
257        assert_eq!(config.output_bus_count, 0);
258        assert_eq!(config.total_input_channels(), 0);
259        assert_eq!(config.total_output_channels(), 0);
260        assert!(config.validate().is_ok());
261    }
262}