sim_lib_plugin_vst3/descriptor.rs
1use sim_kernel::{Error, Result};
2use sim_lib_audio_graph_core::{PortDecl, PortDir, PortMedia};
3use sim_lib_plugin_core::{
4 ParameterDescriptor, ParameterKind, PluginDescriptor, PluginFormat, PluginId,
5};
6
7/// The medium carried by a VST3 bus.
8///
9/// Maps onto the graph port media when a [`Vst3Bus`] is lowered to a `PortDecl`.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum Vst3BusKind {
12 /// An audio bus carrying sample lanes.
13 Audio,
14 /// An event bus carrying note and control events.
15 Event,
16}
17
18impl Vst3BusKind {
19 fn media(self) -> PortMedia {
20 match self {
21 Self::Audio => PortMedia::Audio,
22 Self::Event => PortMedia::Event,
23 }
24 }
25}
26
27/// A VST3 input or output bus.
28///
29/// Describes one audio or event bus by name, direction, and lane count;
30/// [`to_port_decl`](Vst3Bus::to_port_decl) lowers it into a graph `PortDecl`.
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct Vst3Bus {
33 /// The display name of the bus.
34 pub name: String,
35 /// Whether the bus carries audio or events.
36 pub kind: Vst3BusKind,
37 /// The data direction (input or output) of the bus.
38 pub dir: PortDir,
39 /// The number of graph lanes (channels) the bus exposes.
40 pub channels: u16,
41}
42
43impl Vst3Bus {
44 /// Builds a bus, validating that `name` is non-empty and `channels` is
45 /// non-zero.
46 ///
47 /// Returns an error when the name is blank or when no lane is requested.
48 pub fn new(
49 name: impl Into<String>,
50 kind: Vst3BusKind,
51 dir: PortDir,
52 channels: u16,
53 ) -> Result<Self> {
54 let name = name.into();
55 if name.trim().is_empty() {
56 return Err(Error::Eval("VST3 bus name cannot be empty".to_owned()));
57 }
58 if channels == 0 {
59 return Err(Error::Eval(format!(
60 "VST3 bus {name} must expose at least one graph lane"
61 )));
62 }
63 Ok(Self {
64 name,
65 kind,
66 dir,
67 channels,
68 })
69 }
70
71 /// Builds an audio input bus named `name` with `channels` lanes.
72 pub fn audio_input(name: impl Into<String>, channels: u16) -> Result<Self> {
73 Self::new(name, Vst3BusKind::Audio, PortDir::In, channels)
74 }
75
76 /// Builds an audio output bus named `name` with `channels` lanes.
77 pub fn audio_output(name: impl Into<String>, channels: u16) -> Result<Self> {
78 Self::new(name, Vst3BusKind::Audio, PortDir::Out, channels)
79 }
80
81 /// Builds a single-lane event input bus named `name`.
82 pub fn event_input(name: impl Into<String>) -> Result<Self> {
83 Self::new(name, Vst3BusKind::Event, PortDir::In, 1)
84 }
85
86 /// Lowers this bus into a graph `PortDecl` with matching media, direction,
87 /// and lane count.
88 pub fn to_port_decl(&self) -> PortDecl {
89 PortDecl::new(
90 self.name.clone(),
91 self.kind.media(),
92 self.dir,
93 self.channels,
94 )
95 }
96}
97
98/// A VST3 parameter declaration.
99///
100/// Captures the host-facing identity and value range of one automatable
101/// parameter; [`to_parameter_descriptor`](Vst3ParamInfo::to_parameter_descriptor)
102/// lowers it into the shared plugin-core `ParameterDescriptor`.
103#[derive(Clone, Debug, PartialEq)]
104pub struct Vst3ParamInfo {
105 /// The numeric VST3 parameter id.
106 pub id: u32,
107 /// The stable string id used for persistence and lookup.
108 pub stable_id: String,
109 /// The human-readable parameter title.
110 pub title: String,
111 /// The display units, empty when the parameter is unitless.
112 pub units: String,
113 /// The minimum parameter value.
114 pub min: f64,
115 /// The maximum parameter value.
116 pub max: f64,
117 /// The default value, clamped into the `min..=max` range.
118 pub default: f64,
119 /// Whether the host may automate the parameter.
120 pub automatable: bool,
121}
122
123impl Vst3ParamInfo {
124 /// Builds a parameter, validating ids and range.
125 ///
126 /// Requires non-empty `stable_id` and `title` and `min <= max`; `default`
127 /// is clamped into the range, `units` starts empty, and `automatable`
128 /// defaults to `true`. Returns an error when validation fails.
129 pub fn new(
130 id: u32,
131 stable_id: impl Into<String>,
132 title: impl Into<String>,
133 min: f64,
134 max: f64,
135 default: f64,
136 ) -> Result<Self> {
137 let stable_id = stable_id.into();
138 let title = title.into();
139 if stable_id.trim().is_empty() {
140 return Err(Error::Eval(
141 "VST3 parameter stable id cannot be empty".to_owned(),
142 ));
143 }
144 if title.trim().is_empty() {
145 return Err(Error::Eval(
146 "VST3 parameter title cannot be empty".to_owned(),
147 ));
148 }
149 if min > max {
150 return Err(Error::Eval(format!(
151 "VST3 parameter {stable_id} min {min} exceeds max {max}"
152 )));
153 }
154 Ok(Self {
155 id,
156 stable_id,
157 title,
158 units: String::new(),
159 min,
160 max,
161 default: default.clamp(min, max),
162 automatable: true,
163 })
164 }
165
166 /// Lowers this parameter into a plugin-core `ParameterDescriptor` with a
167 /// `Float` kind.
168 pub fn to_parameter_descriptor(&self) -> Result<ParameterDescriptor> {
169 Ok(ParameterDescriptor::new(
170 self.id,
171 self.stable_id.clone(),
172 self.title.clone(),
173 self.min,
174 self.max,
175 self.default,
176 )?
177 .with_kind(ParameterKind::Float))
178 }
179}
180
181/// A VST3-shaped plugin descriptor.
182///
183/// Collects the class identity, bus layout, and parameter set of a VST3 export;
184/// [`to_plugin_descriptor`](Vst3PluginDescriptor::to_plugin_descriptor) lowers
185/// it into the shared plugin-core `PluginDescriptor` consumed by the host.
186#[derive(Clone, Debug, PartialEq)]
187pub struct Vst3PluginDescriptor {
188 /// The VST3 class id identifying the plugin.
189 pub class_id: String,
190 /// The plugin display name.
191 pub name: String,
192 /// The vendor name (defaults to `sim`).
193 pub vendor: String,
194 /// The plugin version (defaults to this crate's package version).
195 pub version: String,
196 /// The declared input and output buses.
197 pub buses: Vec<Vst3Bus>,
198 /// The declared parameters.
199 pub parameters: Vec<Vst3ParamInfo>,
200}
201
202impl Vst3PluginDescriptor {
203 /// Builds an empty descriptor with the given `class_id` and `name`.
204 ///
205 /// Validates that both are non-empty, sets `vendor` to `sim` and `version`
206 /// to this crate's package version, and starts with no buses or parameters.
207 pub fn new(class_id: impl Into<String>, name: impl Into<String>) -> Result<Self> {
208 let class_id = class_id.into();
209 let name = name.into();
210 if class_id.trim().is_empty() {
211 return Err(Error::Eval("VST3 class id cannot be empty".to_owned()));
212 }
213 if name.trim().is_empty() {
214 return Err(Error::Eval("VST3 plugin name cannot be empty".to_owned()));
215 }
216 Ok(Self {
217 class_id,
218 name,
219 vendor: "sim".to_owned(),
220 version: env!("CARGO_PKG_VERSION").to_owned(),
221 buses: Vec::new(),
222 parameters: Vec::new(),
223 })
224 }
225
226 /// Appends `bus` to the descriptor and returns it, for builder chaining.
227 pub fn with_bus(mut self, bus: Vst3Bus) -> Self {
228 self.buses.push(bus);
229 self
230 }
231
232 /// Appends `parameter` to the descriptor and returns it, for builder
233 /// chaining.
234 pub fn with_parameter(mut self, parameter: Vst3ParamInfo) -> Self {
235 self.parameters.push(parameter);
236 self
237 }
238
239 /// Lowers every declared bus into a graph `PortDecl`.
240 pub fn port_decls(&self) -> Vec<PortDecl> {
241 self.buses.iter().map(Vst3Bus::to_port_decl).collect()
242 }
243
244 /// Lowers this descriptor into a plugin-core `PluginDescriptor`.
245 ///
246 /// Builds a `Vst3`-format `PluginId` from the class id and populates the
247 /// resulting descriptor's ports and parameters from this descriptor's buses
248 /// and parameter set. Returns an error if any conversion fails.
249 pub fn to_plugin_descriptor(&self) -> Result<PluginDescriptor> {
250 let mut descriptor = PluginDescriptor::new(
251 PluginId::new(PluginFormat::Vst3, self.class_id.clone())?,
252 self.name.clone(),
253 self.vendor.clone(),
254 self.version.clone(),
255 )?;
256 descriptor.ports = self.port_decls();
257 descriptor.parameters = self
258 .parameters
259 .iter()
260 .map(Vst3ParamInfo::to_parameter_descriptor)
261 .collect::<Result<Vec<_>>>()?;
262 Ok(descriptor)
263 }
264}
265
266/// Builds the built-in VST3 gain fixture descriptor.
267///
268/// Declares stereo audio input and output buses, an event input bus, and a
269/// single `Gain` parameter ranging `0.0..=2.0` with a default of `1.0`.
270pub fn vst3_gain_vst3_descriptor() -> Result<Vst3PluginDescriptor> {
271 Ok(
272 Vst3PluginDescriptor::new("53494d2d4741494e2d56535433000001", "SIM VST3 Gain")?
273 .with_bus(Vst3Bus::audio_input("audio-in", 2)?)
274 .with_bus(Vst3Bus::audio_output("audio-out", 2)?)
275 .with_bus(Vst3Bus::event_input("events-in")?)
276 .with_parameter(Vst3ParamInfo::new(0, "gain", "Gain", 0.0, 2.0, 1.0)?),
277 )
278}
279
280/// Builds the gain fixture as a plugin-core `PluginDescriptor`.
281///
282/// Convenience over [`vst3_gain_vst3_descriptor`] that immediately lowers the
283/// fixture with [`Vst3PluginDescriptor::to_plugin_descriptor`].
284pub fn vst3_gain_descriptor() -> Result<PluginDescriptor> {
285 vst3_gain_vst3_descriptor()?.to_plugin_descriptor()
286}