1use sim_kernel::{Error, Result};
2use sim_lib_audio_graph_core::{PortDecl, PortDir, PortMedia};
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub enum PluginFormat {
7 Clap,
9 Lv2,
11 Vst3,
13 Wasm,
15 Sim,
17}
18
19impl PluginFormat {
20 pub fn as_str(self) -> &'static str {
22 match self {
23 Self::Clap => "clap",
24 Self::Lv2 => "lv2",
25 Self::Vst3 => "vst3",
26 Self::Wasm => "wasm",
27 Self::Sim => "sim",
28 }
29 }
30}
31
32#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub struct PluginId {
35 pub format: PluginFormat,
37 pub stable_id: String,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct PluginLoadSpec {
44 format: PluginFormat,
45 location: String,
46}
47
48impl PluginLoadSpec {
49 pub fn new(format: PluginFormat, location: impl Into<String>) -> Result<Self> {
56 let location = location.into();
57 if location.trim().is_empty() {
58 return Err(Error::Eval(
59 "plugin load location cannot be empty".to_owned(),
60 ));
61 }
62 Ok(Self { format, location })
63 }
64
65 pub fn format(&self) -> PluginFormat {
67 self.format
68 }
69
70 pub fn location(&self) -> &str {
72 &self.location
73 }
74
75 pub fn require_format(&self, expected: PluginFormat) -> Result<()> {
82 if self.format == expected {
83 Ok(())
84 } else {
85 Err(Error::TypeMismatch {
86 expected: expected.as_str(),
87 found: self.format.as_str(),
88 })
89 }
90 }
91}
92
93impl PluginId {
94 pub fn new(format: PluginFormat, stable_id: impl Into<String>) -> Result<Self> {
100 let stable_id = stable_id.into();
101 if stable_id.trim().is_empty() {
102 return Err(Error::Eval("plugin stable id cannot be empty".to_owned()));
103 }
104 Ok(Self { format, stable_id })
105 }
106}
107
108#[derive(Clone, Copy, Debug, PartialEq, Eq)]
110pub enum ParameterKind {
111 Float,
113 Integer,
115 Boolean,
117}
118
119#[derive(Clone, Debug, PartialEq)]
121pub struct ParameterDescriptor {
122 pub id: u32,
124 pub stable_id: String,
126 pub name: String,
128 pub kind: ParameterKind,
130 pub min: f64,
132 pub max: f64,
134 pub default: f64,
136 pub automatable: bool,
138}
139
140impl ParameterDescriptor {
141 pub fn new(
150 id: u32,
151 stable_id: impl Into<String>,
152 name: impl Into<String>,
153 min: f64,
154 max: f64,
155 default: f64,
156 ) -> Result<Self> {
157 let stable_id = stable_id.into();
158 let name = name.into();
159 if stable_id.trim().is_empty() {
160 return Err(Error::Eval(
161 "parameter stable id cannot be empty".to_owned(),
162 ));
163 }
164 if name.trim().is_empty() {
165 return Err(Error::Eval("parameter name cannot be empty".to_owned()));
166 }
167 if min > max {
168 return Err(Error::Eval(format!(
169 "parameter {stable_id} min {min} exceeds max {max}"
170 )));
171 }
172 Ok(Self {
173 id,
174 stable_id,
175 name,
176 kind: ParameterKind::Float,
177 min,
178 max,
179 default: default.clamp(min, max),
180 automatable: true,
181 })
182 }
183
184 pub fn with_kind(mut self, kind: ParameterKind) -> Self {
186 self.kind = kind;
187 self
188 }
189
190 pub fn plain_to_normalized(&self, value: f64) -> f64 {
195 if (self.max - self.min).abs() <= f64::EPSILON {
196 return 0.0;
197 }
198 ((value.clamp(self.min, self.max) - self.min) / (self.max - self.min)).clamp(0.0, 1.0)
199 }
200
201 pub fn normalized_to_plain(&self, normalized: f64) -> f64 {
206 self.min + normalized.clamp(0.0, 1.0) * (self.max - self.min)
207 }
208}
209
210#[derive(Clone, Debug, PartialEq)]
213pub struct PluginDescriptor {
214 pub id: PluginId,
216 pub name: String,
218 pub vendor: String,
220 pub version: String,
222 pub ports: Vec<PortDecl>,
224 pub parameters: Vec<ParameterDescriptor>,
226 pub latency_frames: u32,
228}
229
230impl PluginDescriptor {
231 pub fn new(
237 id: PluginId,
238 name: impl Into<String>,
239 vendor: impl Into<String>,
240 version: impl Into<String>,
241 ) -> Result<Self> {
242 let name = name.into();
243 if name.trim().is_empty() {
244 return Err(Error::Eval("plugin name cannot be empty".to_owned()));
245 }
246 Ok(Self {
247 id,
248 name,
249 vendor: vendor.into(),
250 version: version.into(),
251 ports: Vec::new(),
252 parameters: Vec::new(),
253 latency_frames: 0,
254 })
255 }
256
257 pub fn audio_effect(
268 format: PluginFormat,
269 stable_id: impl Into<String>,
270 name: impl Into<String>,
271 channels: u16,
272 ) -> Result<Self> {
273 let mut descriptor = Self::new(
274 PluginId::new(format, stable_id)?,
275 name,
276 "sim",
277 env!("CARGO_PKG_VERSION"),
278 )?;
279 descriptor.ports.push(PortDecl::new(
280 "audio-in",
281 PortMedia::Audio,
282 PortDir::In,
283 channels,
284 ));
285 descriptor.ports.push(PortDecl::new(
286 "audio-out",
287 PortMedia::Audio,
288 PortDir::Out,
289 channels,
290 ));
291 descriptor
292 .ports
293 .push(PortDecl::new("events-in", PortMedia::Event, PortDir::In, 1));
294 descriptor.ports.push(PortDecl::new(
295 "events-out",
296 PortMedia::Event,
297 PortDir::Out,
298 1,
299 ));
300 Ok(descriptor)
301 }
302
303 pub fn with_parameter(mut self, parameter: ParameterDescriptor) -> Self {
305 self.parameters.push(parameter);
306 self
307 }
308
309 pub fn parameter(&self, id: u32) -> Option<&ParameterDescriptor> {
312 self.parameters.iter().find(|parameter| parameter.id == id)
313 }
314}