Skip to main content

truce_core/
bus.rs

1/// Describes the audio bus configuration of a plugin.
2///
3/// By convention, the **first** input bus is the main audio in
4/// (effects + analyzers) and any subsequent input buses are sidechain
5/// inputs. The first output bus is the main audio out. Format
6/// wrappers (CLAP / VST3 / AU / AAX / LV2) rely on this ordering when
7/// they translate into format-specific main/aux bus designations, and
8/// `BusConfig::kind` lets call-sites that need it ask the bus
9/// directly rather than re-deriving the convention.
10///
11/// Construct via [`Self::new`] / [`Self::stereo`] + the `with_*`
12/// builders rather than struct literal - `#[non_exhaustive]` so
13/// pre-1.0 future fields don't break downstream.
14#[derive(Clone, Debug, Default)]
15#[non_exhaustive]
16pub struct BusLayout {
17    pub inputs: Vec<BusConfig>,
18    pub outputs: Vec<BusConfig>,
19}
20
21/// Constructed by [`BusLayout`]'s `with_*` builders. Marked
22/// `#[non_exhaustive]` to keep the struct literal as a private
23/// detail of the builder methods.
24#[derive(Clone, Debug)]
25#[non_exhaustive]
26pub struct BusConfig {
27    pub name: &'static str,
28    pub channels: ChannelConfig,
29    pub kind: BusKind,
30}
31
32/// Whether a bus is the plugin's main audio I/O or a secondary
33/// sidechain / aux bus. Format wrappers use this to set the
34/// per-bus role flag the host expects (`kBusType_Main` /
35/// `kBusType_Aux` in VST3, `is_sidechain` in CLAP, etc.).
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub enum BusKind {
38    Main,
39    Sidechain,
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub enum ChannelConfig {
44    Mono,
45    Stereo,
46    Custom(u32),
47}
48
49impl ChannelConfig {
50    #[must_use]
51    pub fn channel_count(&self) -> u32 {
52        match self {
53            Self::Mono => 1,
54            Self::Stereo => 2,
55            Self::Custom(n) => *n,
56        }
57    }
58}
59
60impl BusLayout {
61    #[must_use]
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    #[must_use]
67    pub fn stereo() -> Self {
68        Self::new()
69            .with_input("Main", ChannelConfig::Stereo)
70            .with_output("Main", ChannelConfig::Stereo)
71    }
72
73    /// Append a main audio input bus. First call → main audio in;
74    /// subsequent calls → sidechain inputs (use [`Self::with_sidechain_input`]
75    /// if you prefer to be explicit).
76    #[must_use]
77    pub fn with_input(mut self, name: &'static str, channels: ChannelConfig) -> Self {
78        let kind = if self.inputs.is_empty() {
79            BusKind::Main
80        } else {
81            BusKind::Sidechain
82        };
83        self.inputs.push(BusConfig {
84            name,
85            channels,
86            kind,
87        });
88        self
89    }
90
91    /// Append a sidechain input bus. Equivalent to [`Self::with_input`]
92    /// after the first input has already been added, but lets call
93    /// sites express intent.
94    #[must_use]
95    pub fn with_sidechain_input(mut self, name: &'static str, channels: ChannelConfig) -> Self {
96        self.inputs.push(BusConfig {
97            name,
98            channels,
99            kind: BusKind::Sidechain,
100        });
101        self
102    }
103
104    #[must_use]
105    pub fn with_output(mut self, name: &'static str, channels: ChannelConfig) -> Self {
106        self.outputs.push(BusConfig {
107            name,
108            channels,
109            kind: BusKind::Main,
110        });
111        self
112    }
113
114    /// Return the indices of all sidechain input buses.
115    pub fn sidechain_input_indices(&self) -> impl Iterator<Item = usize> + '_ {
116        self.inputs
117            .iter()
118            .enumerate()
119            .filter(|(_, b)| b.kind == BusKind::Sidechain)
120            .map(|(i, _)| i)
121    }
122
123    #[must_use]
124    pub fn total_input_channels(&self) -> u32 {
125        self.inputs.iter().map(|b| b.channels.channel_count()).sum()
126    }
127
128    #[must_use]
129    pub fn total_output_channels(&self) -> u32 {
130        self.outputs
131            .iter()
132            .map(|b| b.channels.channel_count())
133            .sum()
134    }
135}