lv2hm/
lib.rs

1use lilv_sys::*;
2
3use std::ffi::{ CStr };
4use std::ptr;
5use std::ffi;
6use std::collections::HashMap;
7use std::pin::Pin;
8
9use urid::*;
10use lv2_urid::*;
11
12// find plugins in /usr/lib/lv2
13// URI list with lv2ls
14// https://docs.rs/lilv-sys/0.2.1/lilv_sys/index.html
15// https://github.com/wmedrano/Olivia/tree/main/lilv
16// UB fixed thanks to rust lang discord:
17// https://discord.com/channels/273534239310479360/592856094527848449/837709621111947285
18
19const LILV_URI_CONNECTION_OPTIONAL: &[u8; 48usize] = b"http://lv2plug.in/ns/lv2core#connectionOptional\0";
20pub const LV2_URID_MAP: &[u8; 34usize] = b"http://lv2plug.in/ns/ext/urid#map\0";
21
22pub struct Lv2Host{
23    world: *mut LilvWorld,
24    lilv_plugins: *const LilvPlugins,
25    plugin_cap: usize,
26    plugins: Vec<Plugin>,
27    uri_home: Vec<String>,
28    plugin_names: HashMap<String, usize>,
29    dead_list: Vec<usize>,
30    #[allow(dead_code)]
31    buffer_len: usize,
32    sr: f64,
33    in_buf: Vec<f32>,
34    out_buf: Vec<f32>,
35    atom_buf: [u8; 1024],
36    atom_urid_bytes: [u8; 4],
37    midi_urid_bytes: [u8; 4],
38    #[allow(dead_code)]
39    host_map: Pin<Box<HostMap<HashURIDMapper>>>,
40    pub map_interface: lv2_sys::LV2_URID_Map,
41}
42
43#[derive(Clone, Copy, PartialEq, Eq, Debug)]
44pub enum AddPluginError{
45    CapacityReached,
46    MoreThanTwoInOrOutAudioPorts(usize, usize),
47    MoreThanOneAtomPort(usize),
48    WorldIsNull,
49    PluginIsNull,
50    PortNeitherInputOrOutput,
51    PortNeitherControlOrAudioOrOptional,
52}
53
54#[derive(Clone, Copy, PartialEq, Eq, Debug)]
55pub enum ApplyError{
56    PluginIndexOutOfBound,
57    LeftRightInputLenUnequal,
58    MoreFramesThanBufferLen,
59}
60
61impl Lv2Host{
62    pub fn new(plugin_cap: usize, buffer_len: usize, sample_rate: usize) -> Self{
63        // setup feature map
64        let mut host_map: Pin<Box<HostMap<HashURIDMapper>>> = Box::pin(HashURIDMapper::new().into());
65        let map_interface = host_map.as_mut().make_map_interface();
66        let map = LV2Map::new(&map_interface);
67        let midi_urid_bytes = map.map_str("http://lv2plug.in/ns/ext/midi#MidiEvent").unwrap().get().to_le_bytes();
68        let atom_urid_bytes = map.map_str("http://lv2plug.in/ns/ext/atom#Sequence").unwrap().get().to_le_bytes();
69
70        let (world, lilv_plugins) = unsafe{
71            let world = lilv_world_new();
72            lilv_world_load_all(world);
73            let lilv_plugins = lilv_world_get_all_plugins(world);
74            (world, lilv_plugins)
75        };
76        Lv2Host{
77            world,
78            lilv_plugins,
79            plugin_cap,
80            plugins: Vec::with_capacity(plugin_cap), // don't let it resize: keep memory where it is
81            uri_home: Vec::new(), // need to keep strings alive otherwise lilv memory goes boom
82            plugin_names: HashMap::new(),
83            dead_list: Vec::new(),
84            buffer_len,
85            sr: sample_rate as f64,
86            in_buf: vec![0.0; buffer_len * 2], // also don't let it resize
87            out_buf: vec![0.0; buffer_len * 2], // also don't let it resize
88            atom_buf: [0; 1024],
89            atom_urid_bytes,
90            midi_urid_bytes,
91            host_map,
92            map_interface,
93        }
94    }
95
96    pub fn get_index(&self, name: &str) -> Option<usize>{
97        self.plugin_names.get(name).copied()
98    }
99
100    #[allow(clippy::not_unsafe_ptr_arg_deref)]
101    pub fn add_plugin(&mut self, uri: &str, name: String) -> Result<usize, AddPluginError>{
102        let feature_vec = vec![lv2_raw::core::LV2Feature {
103            uri: LV2_URID_MAP.as_ptr() as *const i8,
104            data: &mut self.map_interface as *mut lv2_sys::LV2_URID_Map as *mut std::ffi::c_void,
105        }];
106        let mapfp = feature_vec.as_ptr() as *const lv2_raw::core::LV2Feature;
107        let features = vec![mapfp, std::ptr::null::<lv2_raw::core::LV2Feature>()];
108
109        let features_ptr = features.as_ptr() as *const *const lv2_raw::core::LV2Feature;
110        let replace_index = self.dead_list.pop();
111        if self.plugins.len() == self.plugin_cap && replace_index == None{
112            return Err(AddPluginError::CapacityReached);
113        }
114        let mut uri_src = uri.to_owned();
115        uri_src.push('\0'); // make sure it's null terminated
116        self.uri_home.push(uri_src);
117        let plugin = unsafe{
118            let uri = lilv_new_uri(self.world, (&self.uri_home[self.uri_home.len() - 1]).as_ptr() as *const i8);
119            let plugin = lilv_plugins_get_by_uri(self.lilv_plugins, uri);
120            lilv_node_free(uri);
121            plugin
122        };
123
124        let (mut ports, port_names, n_audio_in, n_audio_out, n_atom_in) = unsafe {
125            create_ports(self.world, plugin)?
126        };
127
128        if n_audio_in > 2 || n_audio_out > 2 {
129            return Err(AddPluginError::MoreThanTwoInOrOutAudioPorts(n_audio_in, n_audio_out));
130        }
131
132        if n_atom_in > 1 {
133            return Err(AddPluginError::MoreThanOneAtomPort(n_atom_in));
134        }
135
136        let instance = unsafe{
137            //let map_feature_uri = lilv_new_uri(self.world, LV2_URID_MAP.as_ptr() as *const i8);
138            //let map_feature_bool = lilv_plugin_has_feature(plugin, map_feature_uri);
139            //println!("supports map: {}", map_feature_bool); // prints true, supports map
140            let instance = lilv_plugin_instantiate(plugin, self.sr, features_ptr);
141            let (mut i, mut o) = (0, 0);
142            for p in &mut ports{
143                match p.ptype{
144                    PortType::Control => {
145                        lilv_instance_connect_port(instance, p.index, &mut p.value as *mut f32 as *mut ffi::c_void);
146                    },
147                    PortType::Audio => {
148                        if p.is_input {
149                            lilv_instance_connect_port(instance, p.index, self.in_buf.as_ptr().offset(i*self.buffer_len as isize) as *mut ffi::c_void);
150                            i += 1;
151                        } else {
152                            lilv_instance_connect_port(instance, p.index, self.out_buf.as_ptr().offset(o*self.buffer_len as isize) as *mut ffi::c_void);
153                            o += 1;
154                        }
155                    },
156                    PortType::Atom => {
157                        lilv_instance_connect_port(instance, p.index, self.atom_buf.as_ptr() as *mut ffi::c_void);
158                    }
159                    PortType::Other => {
160                        lilv_instance_connect_port(instance, p.index, ptr::null_mut());
161                    }
162                }
163            }
164
165            lilv_instance_activate(instance);
166            instance
167        };
168
169        let p = Plugin{
170            instance,
171            port_names,
172            ports,
173        };
174
175        if let Some(i) = replace_index{
176            self.plugins[i] = p;
177            self.plugin_names.insert(name, i);
178        } else {
179            self.plugins.push(p);
180            self.plugin_names.insert(name, self.plugins.len() - 1);
181        }
182
183        Ok(self.plugins.len() - 1)
184    }
185
186    pub fn remove_plugin(&mut self, name: &str) -> bool{
187        if let Some(index) = self.plugin_names.get(name){
188            unsafe{
189                lilv_instance_deactivate(self.plugins[*index].instance);
190                // can't do this?
191                // lilv_instance_free(self.plugins[*index].instance);
192            }
193            self.dead_list.push(*index);
194            self.plugin_names.remove(name);
195            true
196        } else {
197            false
198        }
199    }
200
201    fn set_port_value(plug: &mut Plugin, port: &str, value: Option<f32>) -> bool{
202            let port_index = plug.port_names.get(port);
203            if port_index.is_none() { return false; }
204            let port_index = port_index.unwrap();
205            let min = plug.ports[*port_index].min;
206            let max = plug.ports[*port_index].max;
207            let value = if let Some(v) = value { v }
208            else { plug.ports[*port_index].def };
209            plug.ports[*port_index].value = value.max(min).min(max);
210            true
211    }
212
213    pub fn set_value(&mut self, plugin: &str, port: &str, value: f32) -> bool{
214        if let Some(index) = self.plugin_names.get(plugin){
215            let plug = &mut self.plugins[*index];
216            Self::set_port_value(plug, port, Some(value))
217        } else {
218            false
219        }
220    }
221
222    pub fn reset_value(&mut self, plugin: &str, port: &str) -> bool{
223        if let Some(index) = self.plugin_names.get(plugin){
224            let plug = &mut self.plugins[*index];
225            Self::set_port_value(plug, port, None)
226        } else {
227            false
228        }
229    }
230
231    pub fn get_plugin_sheet(&self, index: usize) -> PluginSheet{
232        let plug = &self.plugins[index];
233        let mut ains = 0;
234        let mut aouts = 0;
235        let mut controls = Vec::new();
236        for port in &plug.ports{
237            if port.ptype == PortType::Audio{
238                if port.is_input{
239                    ains += 1;
240                } else {
241                    aouts += 1;
242                }
243            } else if port.ptype == PortType::Control && port.is_input{
244                controls.push((port.name.clone(), port.def, port.min, port.max));
245            }
246        }
247        PluginSheet{
248            audio_ins: ains,
249            audio_outs: aouts,
250            controls,
251        }
252    }
253
254    pub fn apply(&mut self, index: usize, input: [u8; 3], input_frame: (f32, f32)) -> (f32, f32) {
255        if index >= self.plugins.len() { return (0.0, 0.0); }
256        self.in_buf[0] = input_frame.0;
257        self.in_buf[1] = input_frame.1;
258        let plugin = &mut self.plugins[index];
259        midi_into_atom_buffer(self.midi_urid_bytes, self.atom_urid_bytes, vec![(0, input)], &mut self.atom_buf);
260        unsafe {
261            lilv_instance_run(plugin.instance, 1);
262        }
263        (self.out_buf[0], self.out_buf[1])
264    }
265
266    pub fn apply_multi(&mut self, index: usize, input: Vec<(u64, [u8; 3])>, input_frame: [&[f32]; 2]) -> Result<[&[f32]; 2], ApplyError>{
267        midi_into_atom_buffer(self.midi_urid_bytes, self.atom_urid_bytes, input, &mut self.atom_buf);
268        if index >= self.plugins.len() { return Err(ApplyError::PluginIndexOutOfBound); }
269        let frames = input_frame[0].len();
270        if frames != input_frame[1].len(){
271            return Err(ApplyError::LeftRightInputLenUnequal);
272        }
273        if frames > self.buffer_len{
274            return Err(ApplyError::MoreFramesThanBufferLen);
275        }
276        for (i, (l, r)) in input_frame[0].iter().zip(input_frame[1].iter()).enumerate(){
277            self.in_buf[i] = *l;
278            self.in_buf[i + self.buffer_len] = *r;
279        }
280        let plugin = &mut self.plugins[index];
281        unsafe{
282            lilv_instance_run(plugin.instance, frames as u32);
283        }
284        Ok([&self.out_buf[..frames], &self.out_buf[self.buffer_len..][..frames]])
285    }
286}
287
288fn midi_into_atom_buffer(typebytes: [u8; 4], seqbytes: [u8; 4], midibytes: Vec<(u64, [u8; 3])>, atom_buf: &mut [u8; 1024]) {
289    // size gets written at the end
290    // SELF.ATOM_BUF[0..4] = [8,0,0,0];
291    let mut pos = 4; // current offset
292    // type
293    copy_bytes(atom_buf, &seqbytes, &mut pos); // type: sequence
294    copy_bytes(atom_buf, &[0,0,0,0,0,0,0,0,], &mut pos); // frame
295
296    for (ea_time, ea_midi) in midibytes {
297        // timestamp
298        copy_bytes(atom_buf, &ea_time.to_le_bytes(), &mut pos); // subframe
299
300        copy_bytes(atom_buf, &3_u32.to_le_bytes(), &mut pos); // size
301        copy_bytes(atom_buf, &typebytes, &mut pos); // type: midi
302        copy_bytes(atom_buf, &ea_midi, &mut pos);
303        let extra = 8 - (pos % 8);
304        if extra != 8 {
305            for idx in 0..extra {
306                atom_buf[pos..][idx] = 0;
307            }
308            pos += extra;
309        }
310    }
311    pos -= 8; // length shouldn't include size and type of top-level atom
312    copy_bytes(atom_buf, &(pos as u32).to_le_bytes(), &mut 0); // size
313}
314
315fn copy_bytes(to: &mut [u8], from: &[u8], at: &mut usize) {
316    for (to, from) in to[*at..].iter_mut().zip(from.iter()) {
317        *to = *from;
318    }
319    *at += from.len();
320}
321
322
323impl Drop for Lv2Host{
324    fn drop(&mut self){
325        unsafe{
326            for (i, plugin) in self.plugins.iter().enumerate(){
327                if self.dead_list.contains(&i){
328                    continue;
329                }
330                lilv_instance_deactivate(plugin.instance);
331                lilv_instance_free(plugin.instance);
332            }
333            lilv_world_free(self.world);
334        }
335    }
336}
337
338struct Plugin{
339    instance: *mut LilvInstance,
340    port_names: HashMap<String, usize>,
341    ports: Vec<Port>,
342}
343
344#[derive(Debug)]
345pub struct PluginSheet{
346    pub audio_ins: usize,
347    pub audio_outs: usize,
348    pub controls: Vec<(String, f32, f32, f32)>,
349}
350
351#[derive(PartialEq,Eq,Debug)]
352enum PortType{ Control, Audio, Atom, Other }
353
354#[derive(Debug)]
355struct Port{
356    lilv_port: *const LilvPort,
357    index: u32,
358    optional: bool,
359    is_input: bool,
360    ptype: PortType,
361    value: f32,
362    def: f32,
363    min: f32,
364    max: f32,
365    name: String,
366}
367
368type CreatePortsRes = (Vec<Port>, HashMap<String, usize>, usize, usize, usize);
369
370// ([ports], [(port_name, index)], n_audio_in, n_audio_out, n_atom_in)
371unsafe fn create_ports(world: *mut LilvWorld, plugin: *const LilvPluginImpl) -> Result<CreatePortsRes, AddPluginError>{
372    if world.is_null() { return Err(AddPluginError::WorldIsNull); }
373    if plugin.is_null() { return Err(AddPluginError::PluginIsNull); }
374
375    let mut ports = Vec::new();
376    let mut names = HashMap::new();
377    let mut n_audio_in = 0;
378    let mut n_audio_out = 0;
379    let mut n_atom_in = 0;
380
381    let n_ports = lilv_plugin_get_num_ports(plugin);
382
383    let mins = vec![0.0f32; n_ports as usize];
384    let maxs = vec![0.0f32; n_ports as usize];
385    let defs = vec![0.0f32; n_ports as usize];
386    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);
387
388    let lv2_input_port = lilv_new_uri(world, LILV_URI_INPUT_PORT.as_ptr() as *const i8);
389    let lv2_output_port = lilv_new_uri(world, LILV_URI_OUTPUT_PORT.as_ptr() as *const i8);
390    let lv2_audio_port = lilv_new_uri(world, LILV_URI_AUDIO_PORT.as_ptr() as *const i8);
391    let lv2_control_port = lilv_new_uri(world, LILV_URI_CONTROL_PORT.as_ptr() as *const i8);
392    let lv2_atom_port = lilv_new_uri(world, LILV_URI_ATOM_PORT.as_ptr() as *const i8);
393    let lv2_connection_optional = lilv_new_uri(world, LILV_URI_CONNECTION_OPTIONAL.as_ptr() as *const i8);
394
395    for i in 0..n_ports{
396        let lport = lilv_plugin_get_port_by_index(plugin, i);
397        let def = defs[i as usize];
398        let min = mins[i as usize];
399        let max = maxs[i as usize];
400        let value = if def.is_nan() { 0.0 } else { def };
401
402        let lilv_name = lilv_port_get_name(plugin, lport);
403        let lilv_str = lilv_node_as_string(lilv_name);
404        let c_str = CStr::from_ptr(lilv_str as *const i8);
405        let name = c_str.to_str().expect("Lv2hm: could not build port name string.").to_owned();
406
407        names.insert(name.clone(), i as usize);
408
409        let optional = lilv_port_has_property(plugin, lport, lv2_connection_optional);
410
411        let is_input = if lilv_port_is_a(plugin, lport, lv2_input_port) { true }
412        else if !lilv_port_is_a(plugin, lport, lv2_output_port) && !optional { return Err(AddPluginError::PortNeitherInputOrOutput); }
413        else { false };
414
415        let ptype = if lilv_port_is_a(plugin, lport, lv2_control_port) { PortType::Control }
416        else if lilv_port_is_a(plugin, lport, lv2_audio_port) {
417            if is_input{
418                n_audio_in += 1;
419            } else {
420                n_audio_out += 1;
421            }
422            PortType::Audio
423        }
424        else if lilv_port_is_a(plugin, lport, lv2_atom_port) && is_input{
425            n_atom_in += 1;
426            PortType::Atom
427        }
428        else if !optional { return Err(AddPluginError::PortNeitherControlOrAudioOrOptional); }
429        else { PortType::Other };
430
431        ports.push(Port{
432            lilv_port: lport,
433            index: i,
434            ptype, is_input, optional, value, def, min, max, name,
435        });
436    }
437
438    lilv_node_free(lv2_connection_optional);
439    lilv_node_free(lv2_atom_port);
440    lilv_node_free(lv2_control_port);
441    lilv_node_free(lv2_audio_port);
442    lilv_node_free(lv2_output_port);
443    lilv_node_free(lv2_input_port);
444
445    Ok((ports, names, n_audio_in, n_audio_out, n_atom_in))
446}