ladspa plugins in rust
— in which i make ffi bindings, and music!As I procrastinate working on either language dev or rewriting my website (using said language dev), I’ve made Rust bindings to the LADSPA audio plugin interface¹. LADSPA provides a way for DAW plugins (mainly used for audio processing, effects) to actually interact with the “host” (the DAW) and pass around audio data as well as metadata. As the S
in the name implies, it’s a pretty simple interface, consider this flowchart:
Now that one knows how LADSPA works, lets try and implement an interface for it in Rust.
step 1: make a shared library
This is pretty easy, just add
[lib] crate-type = ["dylib"]
And the Rust compiler will output a lib[name].so
file.
step 2: do everything else
…
Because I didn’t feel like writing a bunch of unsafe code inside of a plugin, I made a safe interface for it. Said interface is structured fairly similarly, except for:
- Instead of manually writing
ladspa_descriptor
, I have a macrocollect_plugins!(Type, …)
that generates said function, lazily initializing an array of descriptors the first time it’s called, and indexing said array. - Instead of the descriptor just being a giant struct with a bunch of fields, the safe abstraction is instead having the plugin instance struct implement
Plugin
trait, which contains all the relevant info needed to construct the descriptor - Ports are grouped together into a “Port Collection” type, this allows for static typing of port direction & type, a fun bonus!³
- And a bunch of other things are replaced with more statically typed versions of themselves.
Putting it all together to make a plugin:
use ladspa_new::mark::{Audio, Input, Output}; use ladspa_new::{collect_plugins, Plugin, PluginInfo, Port, PortCollection, PortInfo, Properties}; // Define the port collection, that holds all our ports with good names pub struct Ports<'d> { input: Port<'d, Input, Audio>, output: Port<'d, Output, Audio>, } // This macro impl's PortCollection for us, as well as letting us provide metadata PortCollection! { for Ports, input: PortInfo::float("Audio in"), output: PortInfo::float("Audio out"), } // The plugin instance struct, if we stored any data between run calls it would go here, but since we aren't it can just be a unit struct pub struct InvertPlugin; impl Plugin for InvertPlugin { // Just specifying the port collection type type Ports<'d> = Ports<'d>; // This function returns the plugin's metadata, just some basic things fn info() -> PluginInfo { PluginInfo { label: "invert", name: "Invert", maker: "you", copyright: "impl Copy for InvertPlugin {}", properties: Properties::new().hard_rt_capable(true), } } // Since we don't need to know the sample rate at all, we can just ignore the value fn new(_: usize) -> Option<Self> { Some(Self) } // The actual code of our plugin fn run(&mut self, ports: Self::Ports<'_>) { // This is currently the worst part of my implementation, having to iterate a range instead of the ports directly. At some point in the future I'll figure out a fix for this! for i in 0..ports.input.len() { let val = ports.input.get(i); // Our *amazing* effect, inverting the waveform ports.output.set(i, -val); } } } // Finally, register our plugin, if you have multiple plugins in the same library this would contain all of the plugin types collect_plugins!(InvertPlugin);
And now we can take some audio:
Apply our fancy new plugin, and we get:
While this simple demo “works”, it’s not very demonstrative. So, using some other simple plugins I made, I made this:
As you can tell, I’m not the best at making music things, but this gets the point across.
i wanna try it!!
Currently, I’m still working out a couple things, and the crate likely won’t be published for a few days, once it is, I’ll add a message here:
… It still hasn’t happened yet
Until then, check out my other published crates: punch-card
and miny
:3
I’m currently working on migrating my website to be an entirely different website, including having a new domain!
-michael