use sim_kernel::{Error, Result};
use sim_lib_audio_graph_core::{PortDecl, PortDir, PortMedia};
use sim_lib_plugin_core::{
ParameterDescriptor, ParameterKind, PluginDescriptor, PluginFormat, PluginId,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Vst3BusKind {
Audio,
Event,
}
impl Vst3BusKind {
fn media(self) -> PortMedia {
match self {
Self::Audio => PortMedia::Audio,
Self::Event => PortMedia::Event,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Vst3Bus {
pub name: String,
pub kind: Vst3BusKind,
pub dir: PortDir,
pub channels: u16,
}
impl Vst3Bus {
pub fn new(
name: impl Into<String>,
kind: Vst3BusKind,
dir: PortDir,
channels: u16,
) -> Result<Self> {
let name = name.into();
if name.trim().is_empty() {
return Err(Error::Eval("VST3 bus name cannot be empty".to_owned()));
}
if channels == 0 {
return Err(Error::Eval(format!(
"VST3 bus {name} must expose at least one graph lane"
)));
}
Ok(Self {
name,
kind,
dir,
channels,
})
}
pub fn audio_input(name: impl Into<String>, channels: u16) -> Result<Self> {
Self::new(name, Vst3BusKind::Audio, PortDir::In, channels)
}
pub fn audio_output(name: impl Into<String>, channels: u16) -> Result<Self> {
Self::new(name, Vst3BusKind::Audio, PortDir::Out, channels)
}
pub fn event_input(name: impl Into<String>) -> Result<Self> {
Self::new(name, Vst3BusKind::Event, PortDir::In, 1)
}
pub fn to_port_decl(&self) -> PortDecl {
PortDecl::new(
self.name.clone(),
self.kind.media(),
self.dir,
self.channels,
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Vst3ParamInfo {
pub id: u32,
pub stable_id: String,
pub title: String,
pub units: String,
pub min: f64,
pub max: f64,
pub default: f64,
pub automatable: bool,
}
impl Vst3ParamInfo {
pub fn new(
id: u32,
stable_id: impl Into<String>,
title: impl Into<String>,
min: f64,
max: f64,
default: f64,
) -> Result<Self> {
let stable_id = stable_id.into();
let title = title.into();
if stable_id.trim().is_empty() {
return Err(Error::Eval(
"VST3 parameter stable id cannot be empty".to_owned(),
));
}
if title.trim().is_empty() {
return Err(Error::Eval(
"VST3 parameter title cannot be empty".to_owned(),
));
}
if min > max {
return Err(Error::Eval(format!(
"VST3 parameter {stable_id} min {min} exceeds max {max}"
)));
}
Ok(Self {
id,
stable_id,
title,
units: String::new(),
min,
max,
default: default.clamp(min, max),
automatable: true,
})
}
pub fn to_parameter_descriptor(&self) -> Result<ParameterDescriptor> {
Ok(ParameterDescriptor::new(
self.id,
self.stable_id.clone(),
self.title.clone(),
self.min,
self.max,
self.default,
)?
.with_kind(ParameterKind::Float))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Vst3PluginDescriptor {
pub class_id: String,
pub name: String,
pub vendor: String,
pub version: String,
pub buses: Vec<Vst3Bus>,
pub parameters: Vec<Vst3ParamInfo>,
}
impl Vst3PluginDescriptor {
pub fn new(class_id: impl Into<String>, name: impl Into<String>) -> Result<Self> {
let class_id = class_id.into();
let name = name.into();
if class_id.trim().is_empty() {
return Err(Error::Eval("VST3 class id cannot be empty".to_owned()));
}
if name.trim().is_empty() {
return Err(Error::Eval("VST3 plugin name cannot be empty".to_owned()));
}
Ok(Self {
class_id,
name,
vendor: "sim".to_owned(),
version: env!("CARGO_PKG_VERSION").to_owned(),
buses: Vec::new(),
parameters: Vec::new(),
})
}
pub fn with_bus(mut self, bus: Vst3Bus) -> Self {
self.buses.push(bus);
self
}
pub fn with_parameter(mut self, parameter: Vst3ParamInfo) -> Self {
self.parameters.push(parameter);
self
}
pub fn port_decls(&self) -> Vec<PortDecl> {
self.buses.iter().map(Vst3Bus::to_port_decl).collect()
}
pub fn to_plugin_descriptor(&self) -> Result<PluginDescriptor> {
let mut descriptor = PluginDescriptor::new(
PluginId::new(PluginFormat::Vst3, self.class_id.clone())?,
self.name.clone(),
self.vendor.clone(),
self.version.clone(),
)?;
descriptor.ports = self.port_decls();
descriptor.parameters = self
.parameters
.iter()
.map(Vst3ParamInfo::to_parameter_descriptor)
.collect::<Result<Vec<_>>>()?;
Ok(descriptor)
}
}
pub fn vst3_gain_vst3_descriptor() -> Result<Vst3PluginDescriptor> {
Ok(
Vst3PluginDescriptor::new("53494d2d4741494e2d56535433000001", "SIM VST3 Gain")?
.with_bus(Vst3Bus::audio_input("audio-in", 2)?)
.with_bus(Vst3Bus::audio_output("audio-out", 2)?)
.with_bus(Vst3Bus::event_input("events-in")?)
.with_parameter(Vst3ParamInfo::new(0, "gain", "Gain", 0.0, 2.0, 1.0)?),
)
}
pub fn vst3_gain_descriptor() -> Result<PluginDescriptor> {
vst3_gain_vst3_descriptor()?.to_plugin_descriptor()
}