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 Lv2PortKind {
Audio,
Control,
AtomSequence,
}
impl Lv2PortKind {
fn media(self) -> PortMedia {
match self {
Self::Audio => PortMedia::Audio,
Self::Control => PortMedia::Control,
Self::AtomSequence => PortMedia::Event,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Lv2Port {
pub index: u32,
pub symbol: String,
pub name: String,
pub kind: Lv2PortKind,
pub dir: PortDir,
pub channels: u16,
}
impl Lv2Port {
pub fn new(
index: u32,
symbol: impl Into<String>,
name: impl Into<String>,
kind: Lv2PortKind,
dir: PortDir,
channels: u16,
) -> Result<Self> {
let symbol = symbol.into();
let name = name.into();
if symbol.trim().is_empty() {
return Err(Error::Eval("LV2 port symbol cannot be empty".to_owned()));
}
if name.trim().is_empty() {
return Err(Error::Eval("LV2 port name cannot be empty".to_owned()));
}
if channels == 0 {
return Err(Error::Eval(format!(
"LV2 port {symbol} must expose at least one graph lane"
)));
}
Ok(Self {
index,
symbol,
name,
kind,
dir,
channels,
})
}
pub fn audio_input(index: u32, symbol: impl Into<String>, channels: u16) -> Result<Self> {
Self::new(
index,
symbol,
"Audio Input",
Lv2PortKind::Audio,
PortDir::In,
channels,
)
}
pub fn audio_output(index: u32, symbol: impl Into<String>, channels: u16) -> Result<Self> {
Self::new(
index,
symbol,
"Audio Output",
Lv2PortKind::Audio,
PortDir::Out,
channels,
)
}
pub fn control_input(
index: u32,
symbol: impl Into<String>,
name: impl Into<String>,
) -> Result<Self> {
Self::new(index, symbol, name, Lv2PortKind::Control, PortDir::In, 1)
}
pub fn atom_input(index: u32, symbol: impl Into<String>) -> Result<Self> {
Self::new(
index,
symbol,
"Events In",
Lv2PortKind::AtomSequence,
PortDir::In,
1,
)
}
pub fn to_port_decl(&self) -> PortDecl {
PortDecl::new(
self.symbol.clone(),
self.kind.media(),
self.dir,
self.channels,
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Lv2PluginDescriptor {
pub uri: String,
pub name: String,
pub vendor: String,
pub version: String,
pub ports: Vec<Lv2Port>,
pub parameters: Vec<ParameterDescriptor>,
}
impl Lv2PluginDescriptor {
pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Result<Self> {
let uri = uri.into();
let name = name.into();
if uri.trim().is_empty() {
return Err(Error::Eval("LV2 plugin URI cannot be empty".to_owned()));
}
if name.trim().is_empty() {
return Err(Error::Eval("LV2 plugin name cannot be empty".to_owned()));
}
Ok(Self {
uri,
name,
vendor: "sim".to_owned(),
version: env!("CARGO_PKG_VERSION").to_owned(),
ports: Vec::new(),
parameters: Vec::new(),
})
}
pub fn with_port(mut self, port: Lv2Port) -> Self {
self.ports.push(port);
self
}
pub fn with_parameter(mut self, parameter: ParameterDescriptor) -> Self {
self.parameters.push(parameter);
self
}
pub fn port_decls(&self) -> Vec<PortDecl> {
self.ports.iter().map(Lv2Port::to_port_decl).collect()
}
pub fn to_plugin_descriptor(&self) -> Result<PluginDescriptor> {
let mut descriptor = PluginDescriptor::new(
PluginId::new(PluginFormat::Lv2, self.uri.clone())?,
self.name.clone(),
self.vendor.clone(),
self.version.clone(),
)?;
descriptor.ports = self.port_decls();
descriptor.parameters = self.parameters.clone();
Ok(descriptor)
}
}
pub fn lv2_gain_lv2_descriptor() -> Result<Lv2PluginDescriptor> {
Ok(
Lv2PluginDescriptor::new("https://sim.dev/lv2/gain", "SIM LV2 Gain")?
.with_port(Lv2Port::audio_input(0, "audio-in", 2)?)
.with_port(Lv2Port::audio_output(1, "audio-out", 2)?)
.with_port(Lv2Port::control_input(2, "gain", "Gain")?)
.with_parameter(
ParameterDescriptor::new(2, "gain", "Gain", 0.0, 2.0, 1.0)?
.with_kind(ParameterKind::Float),
),
)
}
pub fn lv2_gain_descriptor() -> Result<PluginDescriptor> {
lv2_gain_lv2_descriptor()?.to_plugin_descriptor()
}