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:

ladspa-graph.svg
Graph of the LADSPA, 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

Cargo.toml (excerpt)
[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:

Putting it all together to make a plugin:

lib.rs
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:

Our source audio, some fun piano notes from keymashing LMMS.

Apply our fancy new plugin, and we get:

While it might sound the same, this is in fact a different file.

While this simple demo “works”, it’s not very demonstrative. So, using some other simple plugins I made, I made this:

Every instrument in this has at least one effect powered by Rust!
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