The API of VCV Rack 2 has been designed to be nearly backward-compatible with v1 plugins, so updating your plugin to v2 likely only involves a version update and a recompile.
plugin.json
version string to v2The major version number (MAJOR.MINOR.REVISION
) of your plugin must match the major version of Rack, so change the version number in your plugin's plugin.json
manifest to 2
.
For example, change
{
"slug": "Fundamental",
"version": "1.2.3",
...
to
{
"slug": "Fundamental",
"version": "2.0.0",
...
If you wish, you may keep the minor and revision numbers unchanged, e.g. 2.2.3
.
Download the latest Rack 2 SDK. Clean and compile the plugin.
export RACK_SDK=/path/to/Rack-SDK
make clean
make dist
If your plugin compiles successfully, you can skip ahead to Potential runtime bugs. If not, read on to solve potential errors or warnings.
ParamWidget::reset()
and ParamWidget::randomize()
has been removedIf you would like to disable resetting or randomization of a particular parameter when the user resets/randomizes the module, set these properties in your Module
constructor.
getParamQuantity(MY_PARAM)->resetEnabled = false;
getParamQuantity(MY_PARAM)->randomizeEnabled = false;
Alternatively, you can override the following methods in your Module
class to implement custom behavior for all parameters and internal state.
void onReset(const ResetEvent& e) override {
params[MY_PARAM].setValue(0.f);
// ...
// Call super method if you wish to include default behavior
// Module::onReset(e);
}
void onRandomize(const RandomizeEvent& e) override {
params[MY_PARAM].setValue(random::uniform());
// ...
// Call super method if you wish to include default behavior
// Module::onRandomize(e);
}
ParamWidget::paramQuantity
has been replaced by getParamQuantity()
If your ParamWidget
methods use the paramQuantity
member variable, replace it with the getter method, or add this line to the top of your method.
ParamQuantity* paramQuantity = getParamQuantity();
Window::loadSvg()
has been deprecated and moved to Svg::load()
Search and replace:
perl -p -i -e 's/APP->window->loadSvg\b/Svg::load/g' src/*.{cpp,hpp}
You might encounter these issues while testing.
Font
and Image
references across multiple framesThe DAW plugin version of Rack uses a different OpenGL context each time the plugin editor window is closed and reopened.
This means that if you load and store a Font
or Image
in a widget's constructor with font = APP->window->loadFont(...)
or image = APP->window->loadImage(...)
, its OpenGL reference will be invalid if the window is reopened later.
Instead, save only its path, and fetch the font/image each frame in draw()
. Example:
void draw(const DrawArgs& args) override {
std::shared_ptr<Font> font = APP->window->loadFont(fontPath);
if (font) {
nvgFontFaceId(args.vg, font->handle);
...
}
}
loadFont()
and loadImage()
caches the font/image by its path, so this operation can be efficiently called in draw()
every frame.
While not required, these new API features in Rack 2 can enhance the usability of your modules.
Add names to your ports and lights which appear in tooltips.
For example, in your Module
constructor:
configInput(PITCH_INPUT, "1V/oct pitch");
configOutput(SIN_OUTPUT, "Sine");
configLight(PHASE_LIGHT, "Phase");
configParam()
with configButton()
or configSwitch()
as applicableFor momentary buttons and multi-state switches, displaying a real-valued parameter to the user doesn't make much sense.
Instead, use configButton()
to hide the text field in its context menu, or configSwitch()
to offer a list of choices.
configButton(TAP_PARAM);
configSwitch(SYNC_PARAM, "Sync mode", {"Soft", "Hard"});
If your module is bypassed by the user (via the context menu or key command), you can route certain inputs directly to outputs, bypassing all processing. This would make sense for a filter or reverb, or even a clock divider or quantizer, but it would not make sense for a VCO, since it generates a signal rather than processes one.
For example, in your Module
constructor:
configBypass(LEFT_INPUT, LEFT_OUTPUT);
configBypass(RIGHT_INPUT, RIGHT_OUTPUT);
Alternatively, override Module::processBypass()
to implement custom bypass behavior.
Instead of serializing large (>100 KB) buffers in toJson/fromJson()
methods which could lag the UI, read/write to the directory returned by createPatchStorageDirectory()
and getPatchStorageDirectory()
.
Example:
void onAdd(const AddEvent& e) override {
std::string path = system::join(createPatchStorageDirectory(), "wavetable.wav");
// Read file...
}
void onSave(const SaveEvent& e) override {
std::string path = system::join(createPatchStorageDirectory(), "wavetable.wav");
// Write file...
}
Note: You cannot call these methods in the Module
constructor since it is not added to the Engine at that point.