use sim_kernel::{Error, Result};
use sim_lib_audio_graph_core::{PortDecl, PortDir, PortMedia};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PluginFormat {
Clap,
Lv2,
Vst3,
Wasm,
Sim,
}
impl PluginFormat {
pub fn as_str(self) -> &'static str {
match self {
Self::Clap => "clap",
Self::Lv2 => "lv2",
Self::Vst3 => "vst3",
Self::Wasm => "wasm",
Self::Sim => "sim",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PluginId {
pub format: PluginFormat,
pub stable_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PluginLoadSpec {
format: PluginFormat,
location: String,
}
impl PluginLoadSpec {
pub fn new(format: PluginFormat, location: impl Into<String>) -> Result<Self> {
let location = location.into();
if location.trim().is_empty() {
return Err(Error::Eval(
"plugin load location cannot be empty".to_owned(),
));
}
Ok(Self { format, location })
}
pub fn format(&self) -> PluginFormat {
self.format
}
pub fn location(&self) -> &str {
&self.location
}
pub fn require_format(&self, expected: PluginFormat) -> Result<()> {
if self.format == expected {
Ok(())
} else {
Err(Error::TypeMismatch {
expected: expected.as_str(),
found: self.format.as_str(),
})
}
}
}
impl PluginId {
pub fn new(format: PluginFormat, stable_id: impl Into<String>) -> Result<Self> {
let stable_id = stable_id.into();
if stable_id.trim().is_empty() {
return Err(Error::Eval("plugin stable id cannot be empty".to_owned()));
}
Ok(Self { format, stable_id })
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParameterKind {
Float,
Integer,
Boolean,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ParameterDescriptor {
pub id: u32,
pub stable_id: String,
pub name: String,
pub kind: ParameterKind,
pub min: f64,
pub max: f64,
pub default: f64,
pub automatable: bool,
}
impl ParameterDescriptor {
pub fn new(
id: u32,
stable_id: impl Into<String>,
name: impl Into<String>,
min: f64,
max: f64,
default: f64,
) -> Result<Self> {
let stable_id = stable_id.into();
let name = name.into();
if stable_id.trim().is_empty() {
return Err(Error::Eval(
"parameter stable id cannot be empty".to_owned(),
));
}
if name.trim().is_empty() {
return Err(Error::Eval("parameter name cannot be empty".to_owned()));
}
if min > max {
return Err(Error::Eval(format!(
"parameter {stable_id} min {min} exceeds max {max}"
)));
}
Ok(Self {
id,
stable_id,
name,
kind: ParameterKind::Float,
min,
max,
default: default.clamp(min, max),
automatable: true,
})
}
pub fn with_kind(mut self, kind: ParameterKind) -> Self {
self.kind = kind;
self
}
pub fn plain_to_normalized(&self, value: f64) -> f64 {
if (self.max - self.min).abs() <= f64::EPSILON {
return 0.0;
}
((value.clamp(self.min, self.max) - self.min) / (self.max - self.min)).clamp(0.0, 1.0)
}
pub fn normalized_to_plain(&self, normalized: f64) -> f64 {
self.min + normalized.clamp(0.0, 1.0) * (self.max - self.min)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PluginDescriptor {
pub id: PluginId,
pub name: String,
pub vendor: String,
pub version: String,
pub ports: Vec<PortDecl>,
pub parameters: Vec<ParameterDescriptor>,
pub latency_frames: u32,
}
impl PluginDescriptor {
pub fn new(
id: PluginId,
name: impl Into<String>,
vendor: impl Into<String>,
version: impl Into<String>,
) -> Result<Self> {
let name = name.into();
if name.trim().is_empty() {
return Err(Error::Eval("plugin name cannot be empty".to_owned()));
}
Ok(Self {
id,
name,
vendor: vendor.into(),
version: version.into(),
ports: Vec::new(),
parameters: Vec::new(),
latency_frames: 0,
})
}
pub fn audio_effect(
format: PluginFormat,
stable_id: impl Into<String>,
name: impl Into<String>,
channels: u16,
) -> Result<Self> {
let mut descriptor = Self::new(
PluginId::new(format, stable_id)?,
name,
"sim",
env!("CARGO_PKG_VERSION"),
)?;
descriptor.ports.push(PortDecl::new(
"audio-in",
PortMedia::Audio,
PortDir::In,
channels,
));
descriptor.ports.push(PortDecl::new(
"audio-out",
PortMedia::Audio,
PortDir::Out,
channels,
));
descriptor
.ports
.push(PortDecl::new("events-in", PortMedia::Event, PortDir::In, 1));
descriptor.ports.push(PortDecl::new(
"events-out",
PortMedia::Event,
PortDir::Out,
1,
));
Ok(descriptor)
}
pub fn with_parameter(mut self, parameter: ParameterDescriptor) -> Self {
self.parameters.push(parameter);
self
}
pub fn parameter(&self, id: u32) -> Option<&ParameterDescriptor> {
self.parameters.iter().find(|parameter| parameter.id == id)
}
}