Skip to content

Starting Off LV2 Plugin Development

2026-02

This page is an introduction to developing a minimal audio plugin using LV2. It summarizes information from the main resources (official LV2 documentation) and provides some further details/decisions I found/made on my own. The goal is not to look at DSP programming, but rather to set up a basic custom toolchain for LV2 plugin development.

Main Resources

Main resources regarding LV2 plugin development:

Feel free to read those resources to gain basic knowledge about the LV2 framework and its terminology. Below you find a non-exhaustive summary of the aspects that are most relevant from my perspective:

  • Plugins consist of code (C/Cpp) and data (.ttl files). One goal is to keep code minimal and describe as much as possible in the data files.
  • LV2 plugins are installed in a bundle. This enables to group multiple plugins in some collection (like the collections Calf, x42-plugins, LSP plugins, ...). Each bundlle has manifest.ttl listing content of the bundle. The manifest.ttl should be kept as small as possible, i.e. it should only give the necessary bundle metadata and link to the contained plugin .ttl files. Further details are then to be listed in the respective plugin-specific .ttl files. The reason is that this manifest.ttl is scanned by the plugin host to discover new plugins. If the manifest.ttl is too large, scanning can take longer than necessary.
  • Plugins are executed by a plugin host. This could be your DAW or something else. I use Carla.

Files

The files that are developed throughout this page can be downloaded below.

001 | Copying a Suitable, Already Existing Plugin

Choose and copy an already existing plugin from the list of example plugins. This plugin you can then use as a template or for reference for your own plugin. For example, if you know you need MIDI input, check a plugin with MIDI input to find out how to implement that.

For this page, I chose the simple amplifier. This results in the following directory structure.

010 | Adjusting the Plugin & Writing Code

The following parts of the plugin are adjusted in the following ways. Note that <bundle-name> may be considered equal to <plugin-name> if the bundle contains only one plugin.

Bundle name. Adjust the name of the directory to <bundle-name>.lv2.

Plugin name. Rename the plugin .ttl file and the plugin .c file to resemble the name of your plugin. Change the filenames in manifest.ttl.in accordingly.

Plugin URI. The URI does not need to be accessible by web. It is only used as an identifier. Using an existing URI, e.g. a link to the git repository of the project or to the documentation of the plugin might be of advantage for the user. However, if you do not want to add this information or if you only want to use LV2 plugins for DSP prototyping etc., and just need some URI as an identifier of your plugin, it is possible to choose a URI that cannot exist on the web by definition. A top-level domain that will never be resolvable per RFC 6761 is .invalid. Hence, a minimal URI might be http://<plugin-name>.invalid/. In case of a multi-plugin bundle one could choose the format http://<bundle-name>.invalid/<plugin-name>. Another alternative could be a user-specific domain resulting in http://<user-name>.invalid/<plugin-name>. After choosing the URI, adjust the URI in the LV2_Descriptor inside the .c file and inside the .ttl files.

Plugin parameters. Since this is about creating a minimal toolchain for making plugins, I will leave the parameters as they are.

Plugin code. Since this is about creating a minimal toolchain for making plugins, I will leave the code as it is.

Applied to the current setup (plugin name: amp + one plugin -> bundle name: amp), this results in the following directory structure with the URI http://amp.invalid.

amp.lv2
├── amp.c
├── amp.ttl
└── manifest.ttl.in

Note that the manifest.ttl.in is a template of the final manifest.ttl file. It contains variables that will be substituted by the build system thus producing the manifest.ttl. Details are written further below.

011 | Building & Installing

To be able to use the plugin in a plugin host, it needs to be built and installed. This includes

  • resolving any config plugins (i.e. resolving manifest.ttl.in to manifest.ttl)
  • building a shared library from the source,
  • and copying this shared library, the manifest.ttl and the <plugin-name>.ttl to a specific library where it can be found by a plugin host.

A plugin host finds an LV2 plugin if it lies in one of multiple specific directories. Following the LV2 filesystem hierarchy standard, user-specific plugins are to be saved in ~/.lv2.

For building and installing, I wrote a meson build script. It can be downloaded from the files section. To execute the build pipeline, call the following.

meson setup build [--reconfigure]
ninja install -C build

100 | Validating the Data

To ensure that the .ttl files are correct, linting tools can be used. the official LV2 documentation on linting .ttl files recommends sord_validate. However, I found this tool a bit unhandy to use. As an alternative, I used lv2lint. The "downside" of it is that the plugin needs to be installed before linting can be checked. The reason is that lv2lint not only checks for syntax, but tries to load the plugin and checks for errors. To run lv2lint, type the following.

lv2lint <project-uri>

101 | Loading the Plugin

After building and validating the plugin, it can be loaded into your plugin host. In Carla, one needs to click Add Plugin -> Refresh -> Scan. Afterwards, the plugin should be available. If it is not, try re-starting Carla and refresh the available plugins again. This is particularly necessary (at least in my case) when building the plugin while Carla is already open. Carla wont find the plugin on refresh unless it is re-started.