# Migrating v1 Plugins to v2 *This document is a work-in-progress and will be finalized when the Rack 2 ABI is declared stable.* The API of VCV Rack 2 has been designed to be nearly backward-compatible with v1 plugins. This means that 90% of plugins will only require a version update and a recompile (a 1-line edit, 15 seconds of work). For the other 10% of plugins using advanced or unstable API features, updating to v2 is easy and involves following a few search-and-replace steps. Additionally, Rack 2 includes several new optional enhancements that plugin developers can use to improve usability of their plugins in [section 3](#3-New-optional-v2-API-features). ### 1.1) Update `plugin.json` version string to v2 The 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 ```json { "slug": "Fundamental", "version": "1.2.3", ... ``` to ```json { "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](https://vcvrack.com/downloads/). Clean and compile the plugin. ```bash export RACK_SDK=/path/to/Rack-SDK make clean make dist ``` If your plugin compiles successfully, you can skip ahead to [Potential runtime bugs](#2-Potential-runtime-bugs). If not, read on to solve potential errors or warnings. ### 1.2) `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. ```cpp ParamQuantity* paramQuantity = getParamQuantity(); ``` ### 1.3) `ParamWidget::reset()` and `ParamWidget::randomize()` has been removed If 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. ```cpp 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. ```cpp 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); } ``` ### 1.4) `Window::loadSvg()` has been deprecated and moved to `Svg::load()` Search and replace: ```bash perl -p -i -e 's/APP->window->loadSvg\b/Svg::load/g' src/*.{cpp,hpp} ``` ## 2) Potential runtime bugs You might encounter these issues while testing. ### 2.1) Don't store `Font` and `Image` references across multiple frames The 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: ```cpp void draw(const DrawArgs& args) override { std::shared_ptr 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. ## 3) New optional v2 API features While not required, these new API features in Rack 2 can enhance the usability of your modules. ### 3.2) Add Port and Light labels Add names to your ports and lights which appear in tooltips. For example, in your `Module` constructor: ```cpp configInput(PITCH_INPUT, "1V/oct pitch"); configOutput(SIN_OUTPUT, "Sine"); configLight(PHASE_LIGHT, "Phase"); ``` ### 3.3) Replace `configParam()` with `configButton()` or `configSwitch()` as applicable For 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. ```cpp configButton(TAP_PARAM); configSwitch(SYNC_PARAM, "Sync mode", {"Soft", "Hard"}); ``` ### 3.4) Add bypass routes 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: ```cpp configBypass(LEFT_INPUT, LEFT_OUTPUT); configBypass(RIGHT_INPUT, RIGHT_OUTPUT); ``` Alternatively, override `Module::processBypass()` to implement custom bypass behavior. ### 3.5) Store large data in the module's patch storage directory 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: ```cpp 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.