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}