VST2 plugins in Go
Hello there! I would like to announce the thing nobody asked for: now you can write VST2 plugins in go!
What is VST2?
VST2 is the audio plugin interface designed by Steinberg. It’s the most popular audio plugin format. Why did I build a go VST2 library? Well, I really like go and I think it would be cool to enable this capability for my favourite language. But mostly I did it for fun.
Special thanks to @emicklei for asking me if it was possible!
Demo
Ok, how about a (de)gain plugin? It’s almost like a classic example gain plugin, but gain value only scales 0 to 1. Because I was too excited to spend time on proper scale.
Code
// +build plugin
package main
import (
"pipelined.dev/audio/vst2"
)
func init() {
vst2.PluginAllocator = func(h vst2.Host) (vst2.Plugin, vst2.Dispatcher) {
gain := vst2.Parameter{
Name: "Gain",
Unit: "db",
Value: 1,
}
channels := 2
return vst2.Plugin{
InputChannels: channels,
OutputChannels: channels,
Parameters: []*vst2.Parameter{
&gain,
},
ProcessDoubleFunc: func(in, out vst2.DoubleBuffer) {
for c := 0; c < channels; c++ {
for i := 0; i < in.Frames; i++ {
out.Channel(c)[i] = in.Channel(c)[i] * float64(gain.Value)
}
}
},
}, vst2.Dispatcher{}
}
}
func main() {}
A couple of important moments here:
- The library provides API to build both VST2 hosts and plugins. A different set of exported C functions is required for that. Build tag
plugin
enables the plugin API of the library at the build time. vst2.PluginAllocator
is a global function variable that will be called every time new plugin instance is created. It must be set at init;- Because plugin will be built either with the build mode
c-shared
orc-archive
, it must comply with certain requirements.
Build
VST2 plugins have different formats in the modern operating systems:
- Windows: .dll file;
- Linux: .so file;
- OS X: Bundled Mach-O bundle.
To build plugin on Linux/Windows you can just run the following command:
go build --buildmode c-shared --tags plugin
It’ll produce an output file that is ready to use as-is. In theory. I only tested OS X. OOPS.
To build on OS X you need to do a few more things:
-
Run go build command to build Mach-O dylib:
go build --buildmode c-archive --tags plugin -o demoplugin.a
-
Compile Mach-O bundle:
clang -bundle -o demoplugin -all_load demoplugin.a
-
Create a VST bundle. To do that you need to create a specific folder structure and place there Mach-O bundle along with Info.plist file with plugin metadata. Or you can use a script from Rust vst2 library (it’s a nice project, don’t hesitate to star it) that can be found here. There is an issue to add a bundler command to automate this step.
Does it work?
Yes! But not every host detects the plugin. Reaper does that successfully, but not Ableton.
On top of that:
- Internally each method of plugin has to be wrapped by CGO bridge function and guarded by mutex, because go cannot call C functions directly. It’s not a big problem, but with a lot of plugin instances it might have a performance impact;
- C to Go context switch overhead;
- Go-based plugins cannot be used by Go-based hosts. It was probably the most surprising point for me. When I tried to use demo plugin with existing host tests - runtime was panicking with this. Apparently it’s not possible to use library built with buildmode c-shared because any call to the function of that library will start a new go runtime and it’s just not supported. Another OOPS.
- Yes, from the previous point: each go-based plugin will start its own go runtime.
- No custom GUI. In theory it’s possible, but not so obvious to build.
Implementing VST3
VST3 was considered, but it’ll require much more effort to implement go wrapper for it. API surface is much bigger which would require much more dances with CGO. On top of that, when I started this project (4 years ago) VST3 wasn’t even supported by Ableton (it was added in version 10). As of now, I don’t see much value in implementing it.
Was it worth it?
Of course! It was very exciting to see plugin built with Go working in the DAW.
Give it a try. Not all opcodes and callbacks are implemented, but API is there and should be expanding with new dispatch/callback functions. Pull requests are very welcome!