Every plugin format wants something slightly different from you. VST3 wants a component and a controller, with a bus layout it will cache and judge you for changing. AU wants a factory, a matching bundle identifier, and a view that it owns. A standalone app wants none of that and a window instead.
The usual answer is a framework that hides all three behind one interface. We wrote our own, called SND, and the plugins are built on that.
What "write it once" means in practice
You write a Processor and an editor. SND builds them as a VST3, an AU and a standalone app from the same source. No per-format rewrites, no #ifdef mazes, and no third-party abstraction deciding what your editor is allowed to be.
The formats still differ underneath, and some of that difference is real work rather than something a framework can wish away:
- The AU is a native AUv2 shell with its own factory and a bundle identifier
that matches. Wrapping a VST3 in an AU costume gets found out.
- Hosts cache a unit's bus layout against its version. Change the layout without
bumping the version and the host will keep handing you the old one.
prepare()has to survive a host calling Reset at any point, including
halfway through something you thought was atomic.
Those are the things that bite. A framework that papers over them just moves the bite somewhere less obvious.
Why not JUCE
JUCE is good, and most of this industry runs on it. Two reasons we did not use it.
The first is control. When the UI toolkit is someone else's, every unusual thing you want becomes a negotiation with it. Our filter's panel has a modulation arc on every knob that reaches exactly where the parameter actually goes, a randomiser overlay with a lock per source, and a shape strip you can drag a curve out of. None of that is hard. All of it is tedious through a toolkit that was not expecting it.
The second is the licence, which has its own post.
What you get instead
SND is seven modules: audio I/O and file decode, the UI toolkit, plugin hosting and the client SDK, MIDI, FFT and STFT, a state tree with undo, and the per-OS platform bits. Everything is vendored or fetched by CMake, so there is no package manager to set up.
The renderer is whatever the platform already has. macOS draws through Metal on a CAMetalLayer, so a plugin editor never touches OpenGL. Windows and Linux use a WGL or GLX context. Widget code is identical across all three; only the surface underneath changes.
It is available to licence if you want to build on it too.