VCV Manual
Rack
Plugin Development
- Tutorial
- API Guide
- Panels
- Manifest
- Presets
- Voltage Standards
- Digital Signal Processing
- Migrating v1 Plugins to v2
- Licensing
Rack Development
Appendix
Migrating v0.6 Plugins to v1
This is a step-by-step guide for migrating plugins using the Rack v0.6 API to Rack v1. There are three phases of porting.
- Phase 1: Quickly produces a valid Rack v1 plugin.
- Phase 2: Modernizes the code by replacing deprecated v0.6 function calls with new v1 functions.
- Phase 3: Takes advantage of new Rack v1 features such as parameter labels and polyphony.
Prerequisites ¶
Install new build dependencies.
- jq for parsing JSON when building plugins
- Python 3.6+ for running Rack’s
helper.py
script - Perl for replacing text with regex.
Mac ¶
brew install jq python
Windows ¶
pacman -S mingw-w64-x86_64-jq python3
Linux ¶
On Ubuntu 18.04+:
sudo apt install jq python3
On Arch Linux:
pacman -S jq python perl
Phase 1: Using the rack0.hpp
compatibility header ¶
1.1 ¶
cd
to your plugin’s root directory.
Create a plugin.json
manifest file for your plugin using the helper script included in the Rack SDK.
python3 <Rack SDK>/helper.py createmanifest <plugin slug>
1.2 ¶
For each module in your plugin, add an entry to plugin.json
using the helper script.
python3 <Rack SDK>/helper.py createmodule <module slug>
1.3 ¶
Remove SLUG
and VERSION
from the Makefile
, and remove p->slug = ...
and p->version = ...
from your plugin’s main .cpp
file, since they are now defined in plugin.json
.
1.4 ¶
Change #include <rack.hpp>
to #include <rack0.hpp>
to access deprecated functions.
1.5 ¶
For each module, remove the author, module name, and tags from the Model::create
call, since they are now defined in plugin.json
.
For example, change
Model *modelMyModule = Model::create<MyModule, MyModuleWidget>("Template", "MyModule", "My Module", OSCILLATOR_TAG);
to
Model *modelMyModule = Model::create<MyModule, MyModuleWidget>("MyModule");
1.6 ¶
Make the following regex replacements by pasting these lines into your terminal in the root of your plugin directory.
It is a good idea to review the changes and git commit
between each step, in case an automatic replacement goes wrong.
1.6.1 ¶
Rename the plugin
variable to avoid a name collision with the plugin::
namespace.
perl -i -pe 's/\bplugin\b/pluginInstance/g' src/*
1.6.2 ¶
Rename X::create()
functions to createX()
.
perl -i -pe 's/Model::create/createModel/g' src/*
perl -i -pe 's/ParamWidget::create/createParam/g' src/*
perl -i -pe 's/ModuleLightWidget::create/createLight/g' src/*
perl -i -pe 's/Port::create/createPort/g' src/*
perl -i -pe 's/Port::OUTPUT/PortWidget::OUTPUT/g' src/*
perl -i -pe 's/Port::INPUT/PortWidget::INPUT/g' src/*
perl -i -pe 's/Widget::create/createWidget/g' src/*
perl -i -pe 's/MenuEntry::create\(\)/new MenuEntry/g' src/*
perl -i -pe 's/MenuLabel::create/createMenuLabel/g' src/*
perl -i -pe 's/MenuItem::create/createMenuItem/g' src/*
1.6.3 ¶
Rename to/fromJson()
methods to dataTo/FromJson()
.
perl -i -pe 's/toJson/dataToJson/g' src/*
perl -i -pe 's/fromJson/dataFromJson/g' src/*
1.6.4 ¶
Add config()
to the Module
constructor, and add setModule()
to the ModuleWidget
constructor.
perl -i -pe 's/: Module\(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS\) \{/{\n\t\tconfig(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);/g' src/*
perl -i -pe 's/: Module\(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS\) \{/{\n\t\tconfig(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);/g' src/*
perl -i -pe 's/: ModuleWidget\(module\) \{/{\n\t\tsetModule(module);/g' src/*
1.7 ¶
If your plugin uses any of Rack’s dsp/*.hpp
headers, remove the #include <dsp/...>
lines since they are now automatically included by rack.hpp
.
1.8 ¶
The event API has been overhauled in v1.
If you use on*()
event handler methods in your custom widgets, see event.hpp and widget/Widget.hpp for new methods and event classes.
Once completed, your plugin may compile, although many deprecation warnings may appear.
If there are too many warnings to see the errors, you may temporarily add FLAGS += -w
to your Makefile
.
1.9 ¶
Your ModuleWidget
s must gracefully handle a NULL module
argument, otherwise Rack will crash when the Module Browser attempts to display your module.
createParam()
etc. gracefully handle NULL, but if you’ve written custom widgets with a module
pointer, make sure you check if (module) ...
before accessing the pointer.
1.10 ¶
Test your plugin in Rack by adding your modules, moving parameters, clicking custom widgets, and searching your plugin in the Module Browser.
If your plugin still does not build, read the compile errors, and don’t hesitate to ask questions in the Development board of the VCV Community or Rack’s GitHub issue tracker about the v1 API. If your plugin is open-source, you may even ask the VCV Repair Team to create a pull request for you.
Phase 2: Updating your code to the Rack v1 API ¶
2.1 ¶
Once your plugin can be compiled, change #include <rack0.hpp>
back to #include <rack.hpp>
.
2.2 ¶
Add new arguments to the Module::step()
(now called process()
) and Widget::draw()
methods.
perl -i -pe 's/void (\w+::)?step\(\)/void $1process(const ProcessArgs& args)/g' src/*
perl -i -pe 's/void draw\(NVGcontext \*vg\)/void draw(const DrawArgs& args)/g' src/*
perl -i -pe 's/\bvg\b/args.vg/g' src/*
Note: Widget::step()
has not been renamed, but this replacement will incorrectly rename it.
2.3 ¶
Use the Module::process()
argument struct to access the sample rate/time.
perl -i -pe 's/engineGetSampleRate\(\)/args.sampleRate/g' src/*
perl -i -pe 's/engineGetSampleTime\(\)/args.sampleTime/g' src/*
Note: This only works inside Module::process()
methods.
You can call APP->engine->getSampleRate()
and APP->engine->getSampleTime()
anywhere in your code, although this is slightly slower than the above.
2.4 ¶
Use the APP
macro for accessing global state instead of calling global functions.
perl -i -pe 's/Font::load/APP->window->loadFont/g' src/*
perl -i -pe 's/Image::load/APP->window->loadImage/g' src/*
perl -i -pe 's/SVG::load/APP->window->loadSvg/g' src/*
2.5 ¶
Use createInput/Output()
functions instead of createPort()
.
perl -i -pe 's/, PortWidget::INPUT//g' src/*
perl -i -pe 's/addInput\(createPort/addInput(createInput/g' src/*
perl -i -pe 's/, PortWidget::OUTPUT//g' src/*
perl -i -pe 's/addOutput\(createPort/addOutput(createOutput/g' src/*
2.6 ¶
Add namespaces to global functions.
perl -i -pe 's/\bassetGlobal\b/asset::system/g' src/*
perl -i -pe 's/\bassetLocal\b/asset::user/g' src/*
perl -i -pe 's/\bassetPlugin\b/asset::plugin/g' src/*
perl -i -pe 's/\brandomUniform\b/random::uniform/g' src/*
perl -i -pe 's/\brandomNormal\b/random::normal/g' src/*
perl -i -pe 's/\brandomu32\b/random::u32/g' src/*
perl -i -pe 's/\bstringf\b/string::f/g' src/*
2.7 ¶
Change .value
to getters and setters, and change .active
to isConnected()
.
perl -i -pe 's/(params\[.*?\])\.value/$1.getValue()/g' src/*
perl -i -pe 's/(inputs\[.*?\])\.value/$1.getVoltage()/g' src/*
perl -i -pe 's/(outputs\[.*?\])\.value = (.*?);/$1.setVoltage($2);/g' src/*
perl -i -pe 's/(inputs\[.*?\])\.active/$1.isConnected()/g' src/*
perl -i -pe 's/(outputs\[.*?\])\.active/$1.isConnected()/g' src/*
2.8 ¶
Add the dsp::
namespace to dsp classes.
perl -i -pe 's/\b(quadraticBipolar|cubic|quarticBipolar|quintic|sqrtBipolar|exponentialBipolar|BooleanTrigger|SchmittTrigger|PulseGenerator|RealFFT|ComplexFFT|RCFilter|PeakFilter|SlewLimiter|ExponentialSlewLimiter|ExponentialFilter|RealTimeConvolver|MinBlepGenerator|stepEuler|stepRK2|stepRK4|SampleRateConverter|Decimator|Upsampler|RingBuffer|DoubleRingBuffer|AppleRingBuffer|VuMeter|hann|hannWindow|blackman|blackmanWindow|blackmanNuttall|blackmanNuttallWindow|blackmanHarris|blackmanHarrisWindow|Frame|VUMeter)\b/dsp::$1/g' src/*
2.9 ¶
Moved to Phase 1.6.4.
2.10 ¶
For each param in each module, copy minValue, maxValue, defaultValue
from createParam(pos, module, paramId, minValue, maxValue, defaultValue)
to a new configParam(paramId, minValue, maxValue, defaultValue)
call in your Module
constructor.
Then remove the values from createParam
.
For example, if you have the line
addParam(createParam<BlackKnob>(Vec(10, 20), module, MyModule::LEVEL_PARAM, 0.f, 10.f, 5.f));
create a new line
configParam(LEVEL_PARAM, 0.f, 10.f, 5.f);
in the MyModule
constructor, and change the original line to
addParam(createParam<BlackKnob>(Vec(10, 20), module, MyModule::LEVEL_PARAM));
You can automate this process by running
perl -nle 'print "configParam($1, \"\");" while /createParam.*?module, (.*?)\)/g' src/*
and copying the respective groups of lines into each module’s Module
constructor.
Then remove the arguments with
perl -i -pe 's/(createParam.*?module, .*?)(,.*?)\)/$1)/g' src/*
Now make sure your plugin compiles.
Phase 3: Adding new Rack v1 features ¶
You are now ready to add optional Rack v1 features to your plugin.
You may add parameters labels, units, and nonlinear scaling to be displayed when users right-click on a parameter or enable View > Parameter tooltips. See Module::configParam().
If you with to add support for polyphonic cables, see How polyphonic cables will work in Rack v1, Making your monophonic module polyphonic, and engine/Port.hpp.