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::mono`] / [`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 mono() -> Self {
68        Self::new()
69            .with_input("Main", ChannelConfig::Mono)
70            .with_output("Main", ChannelConfig::Mono)
71    }
72
73    #[must_use]
74    pub fn stereo() -> Self {
75        Self::new()
76            .with_input("Main", ChannelConfig::Stereo)
77            .with_output("Main", ChannelConfig::Stereo)
78    }
79
80    /// The default audio-effect layout set: stereo and mono (stereo
81    /// first, so it's the default width). Return this from `bus_layouts()`
82    /// for an ordinary in/out effect and the host offers it on both stereo
83    /// and mono tracks, instead of a stereo-only effect that's hidden on
84    /// mono ones. The effect's `process` must handle either width - loop
85    /// over `buffer.channels()` rather than assuming two.
86    #[must_use]
87    pub fn stereo_and_mono() -> Vec<Self> {
88        vec![Self::stereo(), Self::mono()]
89    }
90
91    /// The output-only counterpart of [`Self::stereo_and_mono`]: stereo and
92    /// mono output buses with no input, for an instrument that produces
93    /// audio from MIDI. Offered on both stereo and mono tracks. The
94    /// instrument's `process` must handle either output width - guard any
95    /// write past the first channel with `buffer.num_output_channels()`.
96    #[must_use]
97    pub fn stereo_and_mono_output() -> Vec<Self> {
98        vec![
99            Self::new().with_output("Main", ChannelConfig::Stereo),
100            Self::new().with_output("Main", ChannelConfig::Mono),
101        ]
102    }
103
104    /// Append a main audio input bus. First call → main audio in;
105    /// subsequent calls → sidechain inputs (use [`Self::with_sidechain_input`]
106    /// if you prefer to be explicit).
107    #[must_use]
108    pub fn with_input(mut self, name: &'static str, channels: ChannelConfig) -> Self {
109        let kind = if self.inputs.is_empty() {
110            BusKind::Main
111        } else {
112            BusKind::Sidechain
113        };
114        self.inputs.push(BusConfig {
115            name,
116            channels,
117            kind,
118        });
119        self
120    }
121
122    /// Append a sidechain input bus. Equivalent to [`Self::with_input`]
123    /// after the first input has already been added, but lets call
124    /// sites express intent.
125    #[must_use]
126    pub fn with_sidechain_input(mut self, name: &'static str, channels: ChannelConfig) -> Self {
127        self.inputs.push(BusConfig {
128            name,
129            channels,
130            kind: BusKind::Sidechain,
131        });
132        self
133    }
134
135    #[must_use]
136    pub fn with_output(mut self, name: &'static str, channels: ChannelConfig) -> Self {
137        self.outputs.push(BusConfig {
138            name,
139            channels,
140            kind: BusKind::Main,
141        });
142        self
143    }
144
145    /// Return the indices of all sidechain input buses.
146    pub fn sidechain_input_indices(&self) -> impl Iterator<Item = usize> + '_ {
147        self.inputs
148            .iter()
149            .enumerate()
150            .filter(|(_, b)| b.kind == BusKind::Sidechain)
151            .map(|(i, _)| i)
152    }
153
154    #[must_use]
155    pub fn total_input_channels(&self) -> u32 {
156        self.inputs.iter().map(|b| b.channels.channel_count()).sum()
157    }
158
159    #[must_use]
160    pub fn total_output_channels(&self) -> u32 {
161        self.outputs
162            .iter()
163            .map(|b| b.channels.channel_count())
164            .sum()
165    }
166}