Interesting library. Not sure the “immediate-mode” is well suited for plugins though - the idea seems to come from game development where everything is constantly redrawn anyway. A hypothetical plugin using such a framework might end up burning a lot of cpu doing nothing - especially if multiple instances are visible at the same time.
I believe that it’s a much better approach, whether one calls “Immediate Mode”, React paradigm or declarative UI, the basic idea being that you don’t need to also maintain the GUI update code.
If done well then there shouldn’t be a significant performance cost (mostly, text rendering needs to be cached).
For my thoughts on this topic, I recently wrote a blog post: The revolution in UI paradigms
Thanks for notifying of this - last time I looked (which was 1-2 years ago) there was a blocker due to the COM architecture. Looks like there is a com-rs crate that solves that issue now
Here are two examples for how to use egui in a vst2 plugin:
-
https://github.com/BillyDM/egui_baseview_test_vst2
- minimal example
-
https://github.com/JoshuaPostel/egui_baseview_test_vst2
- demonstrates mutating midi data
- demonstrates sending midi data to the gui
- run the gui as a standalone app (helpful for development)
There was never a blocker on COM - it was always language agnostic, VST3 is actually somewhat annoying in that it isn’t pure COM (there are a few edge cases of potential UB where interfaces expect references instead of pointers, however references are pointers in the ABI so it’s not really an issue).
I’m also one of the authors of vst3-sys, while we spent a lot of time on a macro-based solution using an older version of com-rs, we had to fork it to support Linux and MacOS while ignoring a few Windows-specific implementation details that VST3 hosts don’t care about. The crate is incompatible with recent versions of com-rs due to redefinitions of the macro APIs.
After a lot of discussion, there are more efforts to wrap VST3 in Rust for specific plugin wrapping frameworks that hand-roll the vtables in pure Rust rather then generating them using macros. Having spent many hours debugging those macros I think that’s the sanest solution.
What I don’t understand in this whole discussion is why people even bother writing compatibility layers in rust for audio plugin formats. Why not just make a c++ wrapper (possibly based on Juce/AudioProcessor) that calls a rust library and deals with the various plugin formats? Is there something that makes this annoying? All the currently used plugin formats are based on C and C++ and therefore C++ certainly is the language best suited to deal with the various standards and that way even AAX should be possible to do.
A number of reasons. The biggest is “because we can.” But this isn’t a compatibility layer. It’s an encoding of the ABI for VST3 in Rust. You would write a compatibility layer on top of this.
But here are some more reasons why a pure-Rust codebase is desirable for new projects :
- There is no performance penalty for using Rust instead of C++.
- There is a standard build system and package manager used by the entire ecosystem.
- Adding dependencies as needed is trivial (eg: cryptography, serialization, RPC, IPC, linear algebra, etc)
- Tests are built into the language. Moving to TDD for your projects is easy.
- It is rather easy to add CI/CD to your Rust projects, on most CI hosting services.
- It is much more difficult to introduce thread safety issues.
- Integrating benchmarks into your project is very easy too, check out criterion.
- The type system is more expressive than C++. Generic programming is actually rather easy, compared to templates.
- Anecdotally, it’s easier to write correct code - code review in Rust is much faster than in C++, due to the fewer number of “gotchas” in the language. You’ll often here “if it compiles, it works” said about Rust - which is very powerful reality.
- Linting and auto formatting is distributed in cargo by default, which means accepting contributions is easier (compare to JUCE’s militant formatting rules; last I heard you couldn’t use clangformat to realize them).
As to why wrapping in C++ isn’t ideal for these benefits, a big one is build complexity. But more signficantly, if you wrap the ABI and then build a plugin on top of that it only needs to be done once. If you wrap a Rust project to be called from a plugin wrapper, then bindings need to be created for every project. As well, writing on top of bindings gives you the advantage of encoding more safety constraints ; doing the opposite requires taking those constraints away (not that they can’t be guaranteed sound, just that it creates the potential for unsoundness).
The reason against using Rust for plugins (which has been mostly mitigated, but it’s been awhile since I checked in) was that every audio plugin API has an issue with embedding UIs in child windows that was incompatible with the Rust GUI ecosystem for a long time. Which is/was a major concern limiting use in production.
I’m sorry, but I don’t think you read my post correctly… I’d use C++ just to create something that works as a layer between the C/C++ plugin api used in production and a plugin-standard agnostic rust layer that would contain as much of the plugin logic as possible. But things that are very much C++ based (like the quasi COM interface of VST3, or building an AAX plugin) would be handled in C++ code - where it is easier. The goal would be to have as little C++ code as possible, but also to solve C++ problems in C++ and not in Rust.
I didn’t ask for a list of benefits Rust provides… I’m very much aware of these… that’s why I ended up posting in this thread.
Of all the things you listed, to me only one thing is a valid concern - a more complicated build. However mixing C++ and Rust seems like a small problem compared to all the other stuff needed - like building installers, code-signing & notarization.
Ah sorry for misinterpreting.
From an engineering perspective that might be the easiest way to do it, but it also means there’s a good deal more indirections in every API call: host -> plugin API -> JUCE plugin wrapper -> AudioProcessor -> Rust Impl vs host -> plugin API -> Rust Impl (hand waving away some differences between how Rust does things that removes the need for the additional indirection). I’d advocate for doing it through IPlug instead of JUCE if that was the approach, because JUCE is too big and hard to build for that purpose.
You also lose the fact that it wouldn’t be possible to implement that in a Rust crate, so it wouldn’t be available through the standard Rust packaging system. That’s the achilles heel, imho.
Not sure I understand the point about building installers/code signing/notarization - C++ doesn’t buy you anything over Rust there (it’s all the same tooling at the end of the day).
But that said at the time I was working on those I didn’t really care about AU/AAX, I just wanted to see if we could do a VST3 plugin in pure Rust, since it seemed like there was this misconception it had to be C++.
Thank you for the answers! I agree JUCE is too heavy just for this - it would be ok for a proof of concept.
What I meant about the signing-notarization etc… is that for a fully automated plugin build, a complex process is needed anyway, so I wouldn’t mind the extra complexity from mixing languages and two separate compile-steps.
hmm… if one were to build a c-compatible static library using rust and link a c++ wrapper to that, wouldn’t the LLVM linkers link time optimization be able to get rid of these indirections unless they involve converting to different types? It seems to me the performance price wouldn’t be higher than using a custom base class in c++ for multiple plugins.
