From 6a6201a53a3734bab6565c754acb1fe275cfcf09 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Tue, 17 Aug 2021 12:19:51 -0400 Subject: [PATCH] Add a few sections to Migrate2. --- Migrate2.md | 132 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 107 insertions(+), 25 deletions(-) diff --git a/Migrate2.md b/Migrate2.md index 23a26a6..402d725 100644 --- a/Migrate2.md +++ b/Migrate2.md @@ -1,10 +1,9 @@ # Migrating v1 Plugins to v2 +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. -The API of VCV Rack v2 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. -## Update plugin version to v2 - -The major version number (`vMAJOR.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`. +### 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 { @@ -21,49 +20,132 @@ to ``` If you wish, you may keep the minor and revision numbers unchanged, e.g. `2.2.3`. -Download the latest [Rack v2 SDK](https://vcvrack.com/downloads/). +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 are ready to test and release. -Or, you may take advantage of new v2 features below. +If your plugin compiles successfully, you can skip ahead to [Potential runtime bugs](#Potential-runtime-bugs). +If not, read on to solve potential errors or warnings. + + +### 1.2) `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.3) `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.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} +``` -## Potential bugs -### Avoid keeping `Font` and `Image` references across multiple frames +## 2) Potential runtime bugs +You might encounter these issues while testing. -*Rack for DAWs* uses a different OpenGL context each time the plugin editor window is closed and reopened. -This means that if you load 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. + +### 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 -std::shared_ptr font = APP->window->loadFont(fontPath); -if (font) { - nvgFontFaceId(args.vg, font->handle); - ... +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. -## Optional v2 API features -### SVG load function -`Window::loadSvg()` has been moved to `Svg::load()`. Search and replace: -```bash -perl -i -pe 's/APP->window->loadSvg\b/Svg::load/g' src/* +## 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"}); ``` -### Port labels -TOOD +### 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. -### Light labels -TODO +### 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()`. -### TODO +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.