use lilv_sys::*;
use std::ffi::{ CStr };
use std::ptr;
use std::ffi;
use std::collections::HashMap;
use std::pin::Pin;
use urid::*;
use lv2_urid::*;
const LILV_URI_CONNECTION_OPTIONAL: &[u8; 48usize] = b"http://lv2plug.in/ns/lv2core#connectionOptional\0";
pub const LV2_URID_MAP: &[u8; 34usize] = b"http://lv2plug.in/ns/ext/urid#map\0";
pub struct Lv2Host{
world: *mut LilvWorld,
lilv_plugins: *const LilvPlugins,
plugin_cap: usize,
plugins: Vec<Plugin>,
uri_home: Vec<String>,
plugin_names: HashMap<String, usize>,
dead_list: Vec<usize>,
#[allow(dead_code)]
buffer_len: usize,
sr: f64,
in_buf: Vec<f32>,
out_buf: Vec<f32>,
atom_buf: [u8; 1024],
atom_urid_bytes: [u8; 4],
midi_urid_bytes: [u8; 4],
#[allow(dead_code)]
host_map: Pin<Box<HostMap<HashURIDMapper>>>,
pub map_interface: lv2_sys::LV2_URID_Map,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum AddPluginError{
CapacityReached,
MoreThanTwoInOrOutAudioPorts(usize, usize),
MoreThanOneAtomPort(usize),
WorldIsNull,
PluginIsNull,
PortNeitherInputOrOutput,
PortNeitherControlOrAudioOrOptional,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ApplyError{
PluginIndexOutOfBound,
LeftRightInputLenUnequal,
MoreFramesThanBufferLen,
}
impl Lv2Host{
pub fn new(plugin_cap: usize, buffer_len: usize, sample_rate: usize) -> Self{
let mut host_map: Pin<Box<HostMap<HashURIDMapper>>> = Box::pin(HashURIDMapper::new().into());
let map_interface = host_map.as_mut().make_map_interface();
let map = LV2Map::new(&map_interface);
let midi_urid_bytes = map.map_str("http://lv2plug.in/ns/ext/midi#MidiEvent").unwrap().get().to_le_bytes();
let atom_urid_bytes = map.map_str("http://lv2plug.in/ns/ext/atom#Sequence").unwrap().get().to_le_bytes();
let (world, lilv_plugins) = unsafe{
let world = lilv_world_new();
lilv_world_load_all(world);
let lilv_plugins = lilv_world_get_all_plugins(world);
(world, lilv_plugins)
};
Lv2Host{
world,
lilv_plugins,
plugin_cap,
plugins: Vec::with_capacity(plugin_cap), uri_home: Vec::new(), plugin_names: HashMap::new(),
dead_list: Vec::new(),
buffer_len,
sr: sample_rate as f64,
in_buf: vec![0.0; buffer_len * 2], out_buf: vec![0.0; buffer_len * 2], atom_buf: [0; 1024],
atom_urid_bytes,
midi_urid_bytes,
host_map,
map_interface,
}
}
pub fn get_index(&self, name: &str) -> Option<usize>{
self.plugin_names.get(name).copied()
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn add_plugin(&mut self, uri: &str, name: String) -> Result<usize, AddPluginError>{
let feature_vec = vec![lv2_raw::core::LV2Feature {
uri: LV2_URID_MAP.as_ptr() as *const i8,
data: &mut self.map_interface as *mut lv2_sys::LV2_URID_Map as *mut std::ffi::c_void,
}];
let mapfp = feature_vec.as_ptr() as *const lv2_raw::core::LV2Feature;
let features = vec![mapfp, std::ptr::null::<lv2_raw::core::LV2Feature>()];
let features_ptr = features.as_ptr() as *const *const lv2_raw::core::LV2Feature;
let replace_index = self.dead_list.pop();
if self.plugins.len() == self.plugin_cap && replace_index == None{
return Err(AddPluginError::CapacityReached);
}
let mut uri_src = uri.to_owned();
uri_src.push('\0'); self.uri_home.push(uri_src);
let plugin = unsafe{
let uri = lilv_new_uri(self.world, (&self.uri_home[self.uri_home.len() - 1]).as_ptr() as *const i8);
let plugin = lilv_plugins_get_by_uri(self.lilv_plugins, uri);
lilv_node_free(uri);
plugin
};
let (mut ports, port_names, n_audio_in, n_audio_out, n_atom_in) = unsafe {
create_ports(self.world, plugin)?
};
if n_audio_in > 2 || n_audio_out > 2 {
return Err(AddPluginError::MoreThanTwoInOrOutAudioPorts(n_audio_in, n_audio_out));
}
if n_atom_in > 1 {
return Err(AddPluginError::MoreThanOneAtomPort(n_atom_in));
}
let instance = unsafe{
let instance = lilv_plugin_instantiate(plugin, self.sr, features_ptr);
let (mut i, mut o) = (0, 0);
for p in &mut ports{
match p.ptype{
PortType::Control => {
lilv_instance_connect_port(instance, p.index, &mut p.value as *mut f32 as *mut ffi::c_void);
},
PortType::Audio => {
if p.is_input {
lilv_instance_connect_port(instance, p.index, self.in_buf.as_ptr().offset(i*self.buffer_len as isize) as *mut ffi::c_void);
i += 1;
} else {
lilv_instance_connect_port(instance, p.index, self.out_buf.as_ptr().offset(o*self.buffer_len as isize) as *mut ffi::c_void);
o += 1;
}
},
PortType::Atom => {
lilv_instance_connect_port(instance, p.index, self.atom_buf.as_ptr() as *mut ffi::c_void);
}
PortType::Other => {
lilv_instance_connect_port(instance, p.index, ptr::null_mut());
}
}
}
lilv_instance_activate(instance);
instance
};
let p = Plugin{
instance,
port_names,
ports,
};
if let Some(i) = replace_index{
self.plugins[i] = p;
self.plugin_names.insert(name, i);
} else {
self.plugins.push(p);
self.plugin_names.insert(name, self.plugins.len() - 1);
}
Ok(self.plugins.len() - 1)
}
pub fn remove_plugin(&mut self, name: &str) -> bool{
if let Some(index) = self.plugin_names.get(name){
unsafe{
lilv_instance_deactivate(self.plugins[*index].instance);
}
self.dead_list.push(*index);
self.plugin_names.remove(name);
true
} else {
false
}
}
fn set_port_value(plug: &mut Plugin, port: &str, value: Option<f32>) -> bool{
let port_index = plug.port_names.get(port);
if port_index.is_none() { return false; }
let port_index = port_index.unwrap();
let min = plug.ports[*port_index].min;
let max = plug.ports[*port_index].max;
let value = if let Some(v) = value { v }
else { plug.ports[*port_index].def };
plug.ports[*port_index].value = value.max(min).min(max);
true
}
pub fn set_value(&mut self, plugin: &str, port: &str, value: f32) -> bool{
if let Some(index) = self.plugin_names.get(plugin){
let plug = &mut self.plugins[*index];
Self::set_port_value(plug, port, Some(value))
} else {
false
}
}
pub fn reset_value(&mut self, plugin: &str, port: &str) -> bool{
if let Some(index) = self.plugin_names.get(plugin){
let plug = &mut self.plugins[*index];
Self::set_port_value(plug, port, None)
} else {
false
}
}
pub fn get_plugin_sheet(&self, index: usize) -> PluginSheet{
let plug = &self.plugins[index];
let mut ains = 0;
let mut aouts = 0;
let mut controls = Vec::new();
for port in &plug.ports{
if port.ptype == PortType::Audio{
if port.is_input{
ains += 1;
} else {
aouts += 1;
}
} else if port.ptype == PortType::Control && port.is_input{
controls.push((port.name.clone(), port.def, port.min, port.max));
}
}
PluginSheet{
audio_ins: ains,
audio_outs: aouts,
controls,
}
}
pub fn apply(&mut self, index: usize, input: [u8; 3], input_frame: (f32, f32)) -> (f32, f32) {
if index >= self.plugins.len() { return (0.0, 0.0); }
self.in_buf[0] = input_frame.0;
self.in_buf[1] = input_frame.1;
let plugin = &mut self.plugins[index];
midi_into_atom_buffer(self.midi_urid_bytes, self.atom_urid_bytes, vec![(0, input)], &mut self.atom_buf);
unsafe {
lilv_instance_run(plugin.instance, 1);
}
(self.out_buf[0], self.out_buf[1])
}
pub fn apply_multi(&mut self, index: usize, input: Vec<(u64, [u8; 3])>, input_frame: [&[f32]; 2]) -> Result<[&[f32]; 2], ApplyError>{
midi_into_atom_buffer(self.midi_urid_bytes, self.atom_urid_bytes, input, &mut self.atom_buf);
if index >= self.plugins.len() { return Err(ApplyError::PluginIndexOutOfBound); }
let frames = input_frame[0].len();
if frames != input_frame[1].len(){
return Err(ApplyError::LeftRightInputLenUnequal);
}
if frames > self.buffer_len{
return Err(ApplyError::MoreFramesThanBufferLen);
}
for (i, (l, r)) in input_frame[0].iter().zip(input_frame[1].iter()).enumerate(){
self.in_buf[i] = *l;
self.in_buf[i + self.buffer_len] = *r;
}
let plugin = &mut self.plugins[index];
unsafe{
lilv_instance_run(plugin.instance, frames as u32);
}
Ok([&self.out_buf[..frames], &self.out_buf[self.buffer_len..][..frames]])
}
}
fn midi_into_atom_buffer(typebytes: [u8; 4], seqbytes: [u8; 4], midibytes: Vec<(u64, [u8; 3])>, atom_buf: &mut [u8; 1024]) {
let mut pos = 4; copy_bytes(atom_buf, &seqbytes, &mut pos); copy_bytes(atom_buf, &[0,0,0,0,0,0,0,0,], &mut pos);
for (ea_time, ea_midi) in midibytes {
copy_bytes(atom_buf, &ea_time.to_le_bytes(), &mut pos);
copy_bytes(atom_buf, &3_u32.to_le_bytes(), &mut pos); copy_bytes(atom_buf, &typebytes, &mut pos); copy_bytes(atom_buf, &ea_midi, &mut pos);
let extra = 8 - (pos % 8);
if extra != 8 {
for idx in 0..extra {
atom_buf[pos..][idx] = 0;
}
pos += extra;
}
}
pos -= 8; copy_bytes(atom_buf, &(pos as u32).to_le_bytes(), &mut 0); }
fn copy_bytes(to: &mut [u8], from: &[u8], at: &mut usize) {
for (to, from) in to[*at..].iter_mut().zip(from.iter()) {
*to = *from;
}
*at += from.len();
}
impl Drop for Lv2Host{
fn drop(&mut self){
unsafe{
for (i, plugin) in self.plugins.iter().enumerate(){
if self.dead_list.contains(&i){
continue;
}
lilv_instance_deactivate(plugin.instance);
lilv_instance_free(plugin.instance);
}
lilv_world_free(self.world);
}
}
}
struct Plugin{
instance: *mut LilvInstance,
port_names: HashMap<String, usize>,
ports: Vec<Port>,
}
#[derive(Debug)]
pub struct PluginSheet{
pub audio_ins: usize,
pub audio_outs: usize,
pub controls: Vec<(String, f32, f32, f32)>,
}
#[derive(PartialEq,Eq,Debug)]
enum PortType{ Control, Audio, Atom, Other }
#[derive(Debug)]
struct Port{
lilv_port: *const LilvPort,
index: u32,
optional: bool,
is_input: bool,
ptype: PortType,
value: f32,
def: f32,
min: f32,
max: f32,
name: String,
}
type CreatePortsRes = (Vec<Port>, HashMap<String, usize>, usize, usize, usize);
unsafe fn create_ports(world: *mut LilvWorld, plugin: *const LilvPluginImpl) -> Result<CreatePortsRes, AddPluginError>{
if world.is_null() { return Err(AddPluginError::WorldIsNull); }
if plugin.is_null() { return Err(AddPluginError::PluginIsNull); }
let mut ports = Vec::new();
let mut names = HashMap::new();
let mut n_audio_in = 0;
let mut n_audio_out = 0;
let mut n_atom_in = 0;
let n_ports = lilv_plugin_get_num_ports(plugin);
let mins = vec![0.0f32; n_ports as usize];
let maxs = vec![0.0f32; n_ports as usize];
let defs = vec![0.0f32; n_ports as usize];
lilv_plugin_get_port_ranges_float(plugin, mins.as_ptr() as *mut f32, maxs.as_ptr() as *mut f32, defs.as_ptr() as *mut f32);
let lv2_input_port = lilv_new_uri(world, LILV_URI_INPUT_PORT.as_ptr() as *const i8);
let lv2_output_port = lilv_new_uri(world, LILV_URI_OUTPUT_PORT.as_ptr() as *const i8);
let lv2_audio_port = lilv_new_uri(world, LILV_URI_AUDIO_PORT.as_ptr() as *const i8);
let lv2_control_port = lilv_new_uri(world, LILV_URI_CONTROL_PORT.as_ptr() as *const i8);
let lv2_atom_port = lilv_new_uri(world, LILV_URI_ATOM_PORT.as_ptr() as *const i8);
let lv2_connection_optional = lilv_new_uri(world, LILV_URI_CONNECTION_OPTIONAL.as_ptr() as *const i8);
for i in 0..n_ports{
let lport = lilv_plugin_get_port_by_index(plugin, i);
let def = defs[i as usize];
let min = mins[i as usize];
let max = maxs[i as usize];
let value = if def.is_nan() { 0.0 } else { def };
let lilv_name = lilv_port_get_name(plugin, lport);
let lilv_str = lilv_node_as_string(lilv_name);
let c_str = CStr::from_ptr(lilv_str as *const i8);
let name = c_str.to_str().expect("Lv2hm: could not build port name string.").to_owned();
names.insert(name.clone(), i as usize);
let optional = lilv_port_has_property(plugin, lport, lv2_connection_optional);
let is_input = if lilv_port_is_a(plugin, lport, lv2_input_port) { true }
else if !lilv_port_is_a(plugin, lport, lv2_output_port) && !optional { return Err(AddPluginError::PortNeitherInputOrOutput); }
else { false };
let ptype = if lilv_port_is_a(plugin, lport, lv2_control_port) { PortType::Control }
else if lilv_port_is_a(plugin, lport, lv2_audio_port) {
if is_input{
n_audio_in += 1;
} else {
n_audio_out += 1;
}
PortType::Audio
}
else if lilv_port_is_a(plugin, lport, lv2_atom_port) && is_input{
n_atom_in += 1;
PortType::Atom
}
else if !optional { return Err(AddPluginError::PortNeitherControlOrAudioOrOptional); }
else { PortType::Other };
ports.push(Port{
lilv_port: lport,
index: i,
ptype, is_input, optional, value, def, min, max, name,
});
}
lilv_node_free(lv2_connection_optional);
lilv_node_free(lv2_atom_port);
lilv_node_free(lv2_control_port);
lilv_node_free(lv2_audio_port);
lilv_node_free(lv2_output_port);
lilv_node_free(lv2_input_port);
Ok((ports, names, n_audio_in, n_audio_out, n_atom_in))
}