Pieter Penninckx

Deprecating the rsynth crate

rsynth was a library for writing audio applications and plugins in the Rust programming language, initially developed by doomy. I took over the maintenance of the project and made significant changes to the design. The project grew in features and even reached over 100 stars on GitHub (which is a lot for this project, see below why). However, it was barely used, couldn't catch-up with the evolution of audio plugin standards and eventually lost my interest. After checking if doomy had still plans for it (he didn't), I decided to retire the project to make room for other stuff for myself and to point potential users towards other libraries.

In this blog-post, I discuss why the project was deprecated, its heritage and some lessons learned.

Why deprecate the project?

As it goes, there are several reasons why the project was deprecated.

Changing context

Initially, rsynth only supported VST 2.4. Myself not being a particular fan of VST 2.4, I added support for jack, offline rendering and in-memory rendering (for testing). Fast forwards five years, Steinberg is dancing on VST 2's grave, jack is being challenged by pipewire and Steinberg's approach to the VST 2 standard gave rise to yet another new standard: CLAP (not to be confused with the crate for Command Line Argument Parsing that has the same name). Also, I wanted LV2 support. When Robbert van der Helm, of yabridge fame initiated nih-plug as a direct “competitor” of rsynth, I knew that rsynth's fate was sealed.

Little to no usage

I searched for it. It looks like only my own crate print_chords used rsyth. Since it only used jack, I ported it to using the jack crate directly. This took me a couple of hours.

Intermezzo: what to do if you happen to be the one user that I didn't know about?

If, by any chance, you are actually using rsynth, the obvious solution is using nih-plug instead.

There's another option that I offer for consideration and that I plan to use myself. Instead of using a crate that tries to abstract away the differences between the plugin standards, you can proceed as follows. First, write the plugin as a “core” library (a rust crate). This is anyhow something I'd recommend, also if you use nih-plug, for instance. Per plugin standard that you want to support, create a separate crate that depends both on the “core” library and on an a library that is dedicated to that particular plugin standard (such as the lv2 crate, the clack crate and ... well ... something else for VST 3).

You may be surprised to hear “don't use a crate that tries to abstract away the differences between plugin standards” from somebody who spent a lot of time developing, well, a crate that tries to abstract away the differences between the plugin standards. The reason I recommend considering the other approach is that it gives you the possibility to use all the features of the specific plugin standard, while avoiding code duplication by putting everything that is common in a separate crate. Yes, you may have to learn the details about all the plugin standards, but I think that any abstraction layer will eventually leak details of the underlying plugin standards anyway (unless maybe it tries to stick to only the features that are offered by all common standards). The authors of abstraction layers are not really to blame for this. In my experience, it's just very hard to abstract over multiple standards that are also moving targets on their own. If you then want to keep some level of stability, well, that's just not doable.

From the point of view of an ecosystem, one can provide templates that can be used as a starting point and customized at will.

Heritage

Now for the good news: several crates have been spun off of rsynth:

  • vecstorage: re-use the memory of a Vec to store items of different types (in particular with different lifetimes)
  • midi-consts: the name says it all
  • timestamp-stretcher: convert time-stamps from one format (e.g. midi ticks) to another (e.g. audio frames). It's agnostic about the format of the time stamps, supports a dynamic conversion-rate and is very precise (i.e.: no “drifting”)
  • midi-reader-writer: reading + writing midi files, currently mostly a wrapper around midly
  • polyphony: I moved all the voice-stealing logic from earlier versions of rsynth to this crate. I'm happy with the design, but “it can use some love”.

I also split off two other crates which I will not mention by name because you shouldn't use them. They emerged in an attempt to implement a certain design that failed so spectacularly that it deserves a dedicated blog post.

This leads me to the lessons learned.

Lessons learned

I like learning from somebody else's mistakes more than learning from my own mistakes, the hard way. So I offer my “lessons learned” to you in the hopes that it helps you. Of course, your context may be different.

Use a simple design

Before this project, I asked myself “What's the best (most elegant, ...) design that gets the job done?” Now I ask myself: “What's the simplest design that gets the job done?” Usually, not all design requirements are known upfront and adding complexity at the start will only make your life harder. You don't have to believe me, but remember my words when you're fighting with the borrow checker.

Use a modular design

This obviously served me well, otherwise I would not have been able to show off so many cool crates that grew out of rsynth.

This is a general thing, e.g. the lapce editor uses the rope data structure from the now-abandoned xi editor.

Another advantage is that it assists/forces you to have a modular design, which is easier to reason about since you don't need to understand the whole application in order to understand its parts.

Of course, the perfect design doesn't come on day one, so it's only natural when components start as modules, evolving with the rest of the application, to be split off in a separate crate only later when a certain level of stability has been reached.

Don't start your own project from scratch, build on somebody else's project

I don't know how Robbert van der Helm thinks about this since he named his project after the Not Invented Here syndrome, but taking over somebody else's project worked for me in this case. It brings in another view (with many details), it's a starting point and you get a co-developer and some visibility. Don't underestimate the effort it takes to reach the same level on your own.

Don't try to abstract away too much

In an intermediate design, rsynth tried to abstract away ... too much. This made the design overly complicated. If, while designing an API, you struggle to give the API user control over things you are abstracting away, you may want to reconsider if it was a good idea to abstract this away in the first place. I know this may be a little vague. I would need another blog post to describe this more in depth.

Don't do as I do, but use ports instead

This lesson learned is specific to audio development.

Rsyth uses &[&[f32]] and &mut[&mut[f32]] to represent input and output channels, respectively. In hindsight, I would have used ports like the lv2 or jack crate. This allows a much more flexible set-up. When using &[&[f32]] and &mut[&mut[f32]], you have to come up with something else to represent midi. And controls. And starting/stopping (in case of stand-alone applications). And delay reporting. And time/position. And whatever current or future standard-specific input-output may come on your way. A set-up using ports can accommodate for that more easily.

Meta-data isn't a compile-time constant

This lesson learned is specific to audio development.

Especially for stand-alone applications, where the meta-data may depend on the command-line arguments, meta-data isn't a compile-time constant.

Acknowledgments

First of, a big thanks to doomy for initiating the project and trusting me to take it over.

Also thanks to

  • Matthias Geier for your source code contribution which cleaned up some code
  • the authors of the crates that rsynth depends on
  • everybody who boosted my ego by starring rsynth on GitHub
  • the people from the Rust Audio community – I learned so much from you.