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:
run_adding & co. have been removed for the fact that I don’t support them anyways.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
Plugintrait, 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:
Warning: audio might be a little bit crunchy, also sorry safari users!
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