|
@@ -1,10 +1,9 @@ |
|
|
# Migrating v1 Plugins to v2 |
|
|
# 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 |
|
|
For example, change |
|
|
```json |
|
|
```json |
|
|
{ |
|
|
{ |
|
@@ -21,49 +20,132 @@ to |
|
|
``` |
|
|
``` |
|
|
If you wish, you may keep the minor and revision numbers unchanged, e.g. `2.2.3`. |
|
|
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. |
|
|
Clean and compile the plugin. |
|
|
```bash |
|
|
```bash |
|
|
export RACK_SDK=/path/to/Rack-SDK |
|
|
export RACK_SDK=/path/to/Rack-SDK |
|
|
make clean |
|
|
make clean |
|
|
make dist |
|
|
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: |
|
|
Instead, save only its path, and fetch the font/image each frame in `draw()`. Example: |
|
|
```cpp |
|
|
```cpp |
|
|
std::shared_ptr<Font> font = APP->window->loadFont(fontPath); |
|
|
|
|
|
if (font) { |
|
|
|
|
|
nvgFontFaceId(args.vg, font->handle); |
|
|
|
|
|
... |
|
|
|
|
|
|
|
|
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. |
|
|
`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. |