Rack development blog

This thread contains news, tutorials, development builds, and generally anything about Rack development that is too technical for https://twitter.com/vcvrack.

Merging all previous development blogs since May 14, 2019 into this thread.

8 Likes

I’m finished with the code review of all header files. The only things left to do before the v1 API is considered stable is

  • Finalize v1-gpl license exception and merge with v1 branch.
  • Migrate one VCV plugin and one third-party open-source plugin myself (I’ll choose one at random and send a PR) while revising https://vcvrack.com/manual/Migrate1.html.
13 Likes

https://vcvrack.com/manual/Migrate1.html is now complete. I aim to stabilize the v1 API, merge back into the v1 branch, and release development builds today.

11 Likes

The VCV Rack v1 plugin API is now stable unless “emergency” bugs are discovered which require API changes.

Developers may begin porting their plugins to Rack v1. See https://vcvrack.com/manual/Migrate1.html for a migration guide.

Note that the ABI will not be stable until v1.0 is released, so you will need to rebuild your plugin against the Rack v1.0 SDK when it is available.

Development builds:

28 Likes

Added the "brand" property to plugin.json manifests. The brand is used as a prefix to module names, e.g. Fundamental VCF, VCV Parametra, Vult Flux, Befaco Rampage.

Most plugins don’t need this. If the brand is not given or blank, the plugin name is used as the brand name. If you’ve made multiple plugins, you should set the brand of all your plugins to the same string, e.g. Vult. (In Vult’s case, it’s a good idea to add the word “free” to the module names to distinguish between their commercial version.)

This breaks the plugin ABI, but no API change is needed unless you want to add a brand name to your plugin. Another dev build will be available in about a week.

Why is this needed?

Here’s a table of a few plugins and their properties. (In the case of third-party plugins, I’m writing what I think the properties should be.)

Plugin slug Plugin name Brand Author
Fundamental Fundamental VCV VCV
VCV-Parametra Parametra VCV VCV
Befaco Befaco Befaco VCV
VultModulesFree Vult Modules Free Vult Leonardo Laguna Ruiz
Hora-AnalogDrums Hora Analog Drums Hora Hora

Note the diversity of values in the Brand column. In some cases, it’s the author name. In some cases, it’s the plugin name. In others, it’s not exactly equal to either.

But in all cases, the brand should be the most natural prefix when saying the module’s full name.

2 Likes

As some of you know, in order to produce a Linux plugin build that works on all Linux distros supported by Rack, you must compile against glibc 2.23 and libstdc++ 5.4.0, which is what Ubuntu 16.04 uses. This is because the ABIs of the GNU implementations of the C and C++ standard library change every few years.

Previously the only way to correctly do this is to use Ubuntu 16.04 (and thus an old version of GCC) for building plugins, either in a virtual machine, Docker container, or installed on a system.

Thanks to wheybags’ glibc_version_header, you can add the following to your plugin’s Makefile and build on any Linux distro, even bleeding edge Arch with GCC 9 if you want.

FLAGS += -include force_link_glibc_2.23.h

This works by specifying each glibc symbol version explicitly instead of defaulting to the newest versions (which might be beyond 2.23). This header is not included by default because it only works in probably ~75% of cases. Your plugin will still fail to load in Ubuntu 16.04 if you

  • use certain libstdc++ code. Most of the C++ stdlib is header-only but not all of it. glibc_version_header only fixes glibc symbols, not libstdc++ symbols. Furthermore, libstdc++ links to glibc and will use newer symbols unless you compile libstdc++ yourself and statically link it (which is more complicated than it’s worth).
  • build dependencies without passing it that flag.
  • use dlopen, since libdl occassionally changes its ABI but doesn’t offer versioned symbols (at least in my distro’s libdl build).

So if you use glibc_version_header, be sure to give your binary to an Ubuntu 16.04 user to test that it loads. Or test by running

objdump -p plugin.so

and looking at the Version References: section for higher versions of GLIBC and GLIBCXX.

This post is only relevant if you are building your own Linux plugin binaries. None of the above is needed for

  • Windows, since Mingw-w64 uses Microsoft’s MSVCRT.DLL for its C stdlib and Rack bundles libstdc++6.dll
  • Mac, since Rack and plugins are built with Apple LLVM’s -mmacosx-version-min=10.7, which is a fantastic feature that tells clang to link to the libc ABI used by Mac 10.7.
  • Source code git repos submitted to https://github.com/VCVRack/library or private ZIP files emailed to VCV, since VCV’s build service uses an Ubuntu 16.04 Docker container for Linux builds.

(The length and complexity of this post is one of the reasons why Linux is often not targeted by commercial software vendors, such as VST developers.)

4 Likes

I’m considering removing favorite modules and sorting by most recently used. Discuss at https://github.com/VCVRack/Rack/issues/1292

7 Likes

Ports now encode their connectedness state in their number of channels. Inputs and outputs are now connected iff (if and only if) channels > 0 and disconnected iff channels == 0.

The behavior of Port::setChannels has changed slightly:

  • If you call setChannels(0), it will actually set the number of channels to 1 but will clear all voltages to 0V. In English this means “a cable is still technically connected but is carrying zero voltage as if it’s disconnected.”
  • If you call setChannels(channels) when the port is disconnected, your request is ignored, and the number of channels will remain at 0. In English, “I try to push N channels out of the port, but since no cable is connected, there are still technically 0 channels coming out of the port.”

Monophonic modules don’t need to change anything. Most polyphonic modules don’t need to change anything. However, consider the following code.

int channels = inputs[AUDIO_INPUT].getChannels();
for (int c = 0; c < channels; c++) {
	...
	outputs[AUDIO_OUTPUT].setVoltage(v, c);
}
outputs[AUDIO_OUTPUT].setChannels(channels);

If AUDIO_INPUT is disconnected, channels is 0, the for-loop is skipped, and AUDIO_OUTPUT will have 1 channel, which is set to 0V. This is very elegant behavior for such simple code for an FX processor module. But users want their filters to self-resonate when no cable is patched. In this case I would suggest limiting the minimum number of output channels to 1, e.g.

int channels = std::max(1, inputs[AUDIO_INPUT].getChannels());

Summary: If you have a polyphonic module that should produce nonzero CV/audio when your “primary” input (which defines the number of channels) is disconnected, add std::max(1, ...) to your code.

5 Likes

All monophonic modules should now sum the channel voltages of its audio inputs (but not CV inputs). See the last paragraph of https://vcvrack.com/manual/VoltageStandards.html#polyphony.

The reasoning is that people will plug poly cables into your mono module “by accident”, and summing is the least surprising, and perhaps even welcoming, result.

4 Likes

What is the formula for scaling parameter values for display using configParam(..., displayBase, displayMultiplier, displayOffset)?

Where v is the parameter value:

  • Linear displayBase = 0: v * displayMultiplier + displayOffset
  • Logarithmic displayBase < 0: log(v) / log(-displayBase) * displayMultiplier + displayOffset
  • Exponential displayBase > 0: pow(displayBase, v) * displayMultiplier + displayOffset

Useful examples:

  • configParam(PW_PARAM, 0, 1, 0.5f, "Pulse width", "%", 0, 100) displays as “Pulse width 50%” at the default value
  • configParam(FREQ_PARAM, -54, 54, 0, "Frequency", " Hz", dsp::FREQ_SEMITONE, dsp::FREQ_C4) displays as “Frequency 261.6 Hz”
  • Or, configParam(FREQ_PARAM, -4, 4, 0, "Frequency", " Hz", 2, dsp::FREQ_C4) since octaves are easier to work with than semitones.
  • configParam(LVL_PARAM, 0, M_SQRT2, 1, "Ch 1 level", " dB", -10, 40) displays as “Ch 1 level 0 dB” and scales with gain = pow(v, 2) in the DSP kernel.
2 Likes

All VCV GitHub repositories now default to the v1 branch. Template has been deprecated, as it has been replaced by the helper.py script.

5 Likes

The plugins Core and Fundamental now have the brand “VCV”, so their modules are now officially called “VCV Audio-8”, “VCV VCO-1”, “VCV Scope”, etc.

1 Like

Another dev build:

I should have mentioned this in the last dev build post, but because the Rack v1 ABI is not yet stable, plugin binaries are only guaranteed to work with the exact version they are compiled against. When releasing a dev build of your plugin, you must specify to users which Rack version it is compatible with.

5 Likes

Reminder: If you compile Rack from source, all plugins must be recompiled whenever you pull and recompile Rack, until the ABI is stabilized. Please do not post issues to GitHub until you have recompiled all Rack plugins against the current source tree or a matching SDK version.

EDIT: I have realized that it is also necessary to delete your <Rack user dir> directory after upgrading if you run these binaries in non-dev mode (without the -d flag). This forces the Fundamental plugin to be re-copied to the <Rack user dir>/plugins directory.

2 Likes

The VCV Plugin Manager is now called VCV Library, and the server is now live for v1. The Library client is now functional in the latest Rack source. Report any bugs with the Library client/server.

I’m opening a discussion at Smoothing user migration to Rack v1 that addresses a problem many users have brought up.

I’ve added CONTRIBUTING.md if you’re into those kinds of things.

6 Likes

Decibel measurements used on VU meters, etc. should be based on 10V = 0 dB. https://vcvrack.com/manual/VoltageStandards.html#levels

2 Likes

Added getVoltageSimd(), getPolyVoltageSimd(), getNormalVoltageSimd(), getNormalPolyVoltageSimd(), and setVoltageSimd() to Port. These make working with SIMD easier for polyphonic modules. Example:

for (int c = 0; c < channels; c += 4) {
	float_4 in = inputs[IN_INPUT].getVoltageSimd<float_4>(c);
	float_4 cv = inputs[CV_INPUT].getPolyVoltageSimd<float_4>(c);
	float_4 out = in * simd::fmax(0.f, cv) / 10.f;
	outputs[OUT_OUTPUT].setVoltageSimd(out, c);
}
5 Likes

The Rack v1 plugin ABI is now stable! To celebrate, here are dev builds of the latest source.

If you have open-source plugins, please notify the Library team after your plugin is migrated to the v1 API if you have not already done so. Builds using this SDK will be made for you.

If you have freeware or commercial plugins, make Mac/Windows/Linux builds and send them to contact@vcvrack.com after your plugin is migrated to the v1 API.

28 Likes