Understanding LV2
This page describes basic LV2 related concepts for those unfamiliar with the subject.
Contents
LV2 Basics
LV2 plugins are audio plugins, this means they process and/or generate audio, MIDI and/or control data. Just like other audio plugins they require a host, in case of MOD they are hosted in mod-host on the device for data processing and accessible in the browser on a remote client for the GUI through mod-ui.
For programming LV2 plugins, experience with C/C++ is required. (The LV2 developers have a handy webpage with examples at https://lv2plug.in/book/)
The LV2 plugin contains two parts: code and data. The code part contains C or C compatible language (e.g. C++) files which are responsible for processes like instantiation and port connection (this means where the plugin can find the locations of the data to read and analyse and write output to) as well as the DSP components of the plugin. The data part contains Turtle files which are used to link all files together in a “bundle” such that the host can quickly scan the plugin for critical information (in the manifest.ttl) and loads more detailed meta-data (in the pluginname.ttl) once the plugin is analysed. This means a host can quickly scan for manifests to discover plugins, process the other Turtle files to generate a UI and use the code portion of the bundle to process audio data once loaded.
Set-up to develop MOD plugins on windows
In a broad overview we will need to set up a virtual machine running linux, installing necessary packages, installing the MOD toolchain and building, testing and deploying an example plugin.
Setting up a virtual machine:
To set up the virtual machine (VM) we are going to use VirtualBox. This allows the user to run another computer on their system. Make a new machine, choose Linux/Ubuntu 64 and make sure to allocate at minimum 2gb ram and 50gb of memory. It’s smart to choose a drive that has a quick connection to your computer, a VM on an SSD is going to run much quicker than a VM on an external drive using an old USB connection.
Next we need an installation image to use in the VM. Download any Ubuntu image from the internet and install Ubuntu. After installing make sure to save the state of the VM as restarting the VM will make you reinstall the image, throw an error saying you installed it already and make things harder. Once installed make sure to run an update check through the system settings.
The Mod toolchain and necessary packages:
Before installing the Mod toolchain there are some dependencies that need to be installed in order to get the toolchain to work. Run the following command:
# install dependencies (note multi-line command)
sudo apt install acl bc curl cvs git mercurial rsync subversion wget \
bison bzip2 flex gawk gperf gzip help2man nano perl patch tar texinfo unzip \
automake binutils build-essential cpio libtool libncurses-dev pkg-config libtool-bin
N.B. if this throws an error try installing them separately, also check if python is installed since these commands make that assumption. Next is the plugin builder and bootstrapping:
# clone MPB
git clone https://github.com/moddevices/mod-plugin-builder.git
cd mod-plugin-builder
# bootstrap the cross-compiler
./bootstrap.sh modduox minimal
N.B. modduox can be replaced with moddwarf or modduo.
Building and testing:
To build and test a plugin navigate to the mod-plugin-builder folder. Follow the instructions from the README.md to build a plugin. As a test let’s build the pitchshifter by typing:
./build modduox mod-pitchshifter
If everything works,it should have built the plugin in ../mod-workdir/modduox/. To now push it to the unit, navigate to folder containing the .lv2 directory and with the device connected through USB type:
tar cz <pluginname>.lv2 | base64 | curl -F 'package=@-' http://192.168.51.1/sdk/install
N.B. change the pluginname in the command. Once you connect to the web UI, the plugin should show up (showing a ‘local’-tag in the components window).
Logging values:
To log values when programming an lv2 plugin use the lv2_logger. When programming using C there are examples on how to do so in the lv2 book. However, when using C++ you will be forced to wrap it in a “C-wrapper”. This complicates the logging process as you can only log features within the wrapping implementation’s portion of C-code. The easiest way I found throughout my testing is to add a logger and map to the cpp header file, log messages throughout functions as they are implemented in the C-wrapper and instantiate the logger and map as you would when you are programming using C using the appropriate headers from the lv2 book examples. N.B. you cannot log messages from the implementation, not even when you write a “extern “C”” wrapped log function with a local logger that’s properly instantiated.
//log feature in the header
LV2_URID_MAP* map;
LV2_Log_Logger logger;
//instantiation in the C-wrapper instantiation function, <effect> is a PLUGIN_CLASS
const char* missing = lv2_features_query(
features,
LV2_LOG__log, &<effect>->logger.log, false,
LV2_URID__map, &<effect>->map, true,
NULL);
lv2_log_logger_set_map(&<effect>->logger, <effect>->map);
if (missing) {
lv2_log_error(&<effect>->logger, “Missing feature <%s>\n”, missing);
free(<effect>);
return NULL;
}
//logging example, for example on parameter change
lv2_log_note(&<effect>->logger, “printed parameter: %f”, <effect>->getParameter());
Starting from scratch:
To start writing a new plugin navigate to the /mod-plugin-builder/ folder. From there let’s navigate to the /plugin/package/ folder and make a new folder among the list of others.
mkdir <our-name>
N.B. all lower case characters and ‘-’ to denote spaces. Navigate to the folder.
cd <our-name>
From here it expects a makefile with the same name as our folder, so let’s make that.
touch <our-name>.mk
Within the makefile there are a couple of constants and commands to be declared. Similarly to the folder and file names these also need to adhere to naming conventions in order for the build script to execute the makefile correctly. This means all upper case and ‘_’ to denote spaces so <our-name> becomes <OUR_NAME>. The constants and commands are as follows:
<OUR_NAME>_SITE_METHOD
<OUR_NAME>_SITE
<OUR_NAME>_VERSION
<OUR_NAME>_DEPENDENCIES
<OUR_NAME>_BUNDLES
<OUR_NAME>_TARGET_WAF/OUR_NAME_TARGET_MAKE
<OUR_NAME>_CONFIGURE_CMDS
<OUR_NAME>_BUILD_CMDS
<OUR_NAME>_INSTALL_TARGET_CMDS
$(eval $(generic-package))
Automatic file generation
From the users point of view:
Running the script presents the user with a window providing them a UI to guide them through the generation options. This is divided into several pages as follows:
Output formats:
Since DPF has the ability to output multiple plugin formats the output format page provides the option to select which of these are included. Among these are vst, lv2, jack and of course the different MOD devices. In the case of formats that separate the DSP files from the UI of the plugin, the page allows for only one of the two as well as both to be generated.
DSP file options:
The DSP portion of the plugin is the backend where all audio and MIDI processing takes place. There is a section with global settings, this is a set with all parameters that specify how it interfaces with other plugins and the host it is loaded into. This list includes:
- Number of audio inputs
- Number of audio inputs
- Whether the plugin is a synth/generator (or an audio effect instead)
- Whether the plugin has MIDI input
- Whether the plugin has MIDI output
- Whether the plugin introduces latency
- Whether the plugin wants to recieve time position information
- Whether the plugin provides internal programs
- Whether the plugin uses states
The next section lets the user specify what additional headers (and respective implementation files) should be generated as part of the plugin. This section is useful as it allows for the separation of code for different sections of the plugin, setting up a modular project structure and making it easy to specify definitions of desired objects to be used in the main plugin file. Although it is possible to specify empty headers, which will generate a skeleton with the bare minimum code constant from header to header to minimize busy work, it is also possible to include a number of predefined headers. These include:
- biquad filter
- delay line
- LFO
- envelope
- oscillator (w/ waveform interpolation)
- noise/random generator
The final section lets the user specify, if they wish, how many parameters and programs/presets are generated and what they should be called, again to minimize busy work implementing functions.
UI file options:
While there is generally no need for a UI when developing for the MOD platform, the UI tab does provide the option to either generate one using the options from the MOD UI builder or the option to simply generate rows with knobs (and respective labels) on a canvas.
Once the tool finishes generating the files, the bin folder should have a folder with the name of the plugin that can be used with the DPF repo in order to build.
Requirements, constraints and preferences:
Requirements:
- Generation of a skeleton of files to make plugins:
- makefile (w/ MOD build targets)
- header and implementation of the plugin’s DSP portion
- turtle files for the parameter portion
- Files should be well commented and have written out function names (i.e. minimize abbreviations to keep things as clear as possible)
Constraints:
- The skeleton should use the Distrho Plugin Framework
- The plugins have to be lv2 bundles
- The plugins have to be buildable for the MOD platform
Preferences:
- Have a UI to make choosing options for file generation easy
- Have licenses/notices added to files
- README generation
- Automatic pushing to a connected MOD device after compiling
- Adaptive file generation based on options for the PluginInfo-header:
- MIDI i/o
- Audio i/o
- is a synth
- has programs/states
- Have the user specify the number of parameters:
- name
- type
- set-/getParameter implementation
- UI generation
- Simple DSP headers for new developers to get started:
- biquad filter
- delay line
- LFO
- envelope
- oscillator (w/ waveform interpolation)
- noise/random generator
File structuring:
The generation tool should make a set of files, this part will outline the structure of each of these:
makefile
Should follow the DPF example makefile structure, add MOD specific build commands and fill in the file names of the generated files in the correct places. DistrhoPlugin.hpp Should be the same as the DPF example and fill in the options (i. e. name, URI, num_inputs, etc.) based on user input. This should include the warnings and explanations from the example.
<name>.hpp
General header file structure where all the information getter functions are directly implemented because they are simple enough and would clutter the implementation. Functions generally speak for themselves through their name, except for the activate and run functions, they should get a small explanation. Run function should have appropriate inputs based on the number of inputs, outputs and MIDI i/o. All parameters, if automatically generated, should also be added to private members along with the explanation that those were generated by user input. Preset structs should be specified, in case of user input not wanting presets, should be commented out.
<name>.cpp
General implementation file structure. Functions should have a simple explanation either before their implementation as to what their purpose is or within the function brackets to give the user an inclination as to what idiosyncratic or complex processing should/could go on within the function (e. g. MIDI processing). Parameter setter and getter functions should be implemented based on user input/user specified parameters.
manifest.ttl
General manifest file based on user input. Should have most URIs explained.
<name>.ttl
General file structure based on user input. Should have most URIs explained. Should link to the lv2 documentation to explain additional parameter specifications such as exponential param ranges, units, scale points etc.
readme.md
Should either be a general readme file, telling the user to input a small explanation about their plugin, or could be used to explain the structure and purposes of each of the generated files.