Audio plugin development with DPF: first plugin
Tags: audioplugin, dpf, audio, cpp
Published: 2022-06-18 14:14:05

## What is DPF?

[DPF](https://github.com/DISTRHO/DPF), short name for Distrho Plugin Framework, is a framework for building audio plugins in C++, compared to JUCE is smaller and more "raw", but there's no commercial licensing or limitation, also it supports open formats like LADSPA and LV2. It has support for Linux and Windows, here I will assume you're using Linux, so some commands may differ in Windows.

## Setting up a DPF project

### Adding DPF to your project

One of the main difficulties I've had with DPF was understanding how to add it to my project, more directly to my git repository, so I'll focus a little more on this, specially for people who are not used to it.

First you need to create a git project, you can just create it on GitHub or GitLab, or whatever other git hosting website, and clone it, or you can do it directly on your computer (remember to add a remote later). We'll be doing the latter:

``` bash
mkdir -v DPFTutorial && cd DPFTutorial
git init
```

With these commands we've created a new folder and initialized git in it, now let's add DPF as a submodule:

```bash
git submodule add https://github.com/DISTRHO/DPF dpf
```

If you don't know about git submodules I recommend taking your time to read about, It's not complicated, but to resume you're basically adding a git project inside your git project, where you can change commits, branches and other stuff separately.

### Creating the plugins folder

Now we will create the `plugins` folder that will contain our plugin(s):

```bash
mkdir -v plugins
```

This is the default folder structure you'll find in DPF projects, I think it is pretty straightforward.

```text
projectdir/
└── dpf/ (git submodule)
└── plugins/
    └── MyPlugin (not created yet)
```

## Creating the plugin

Since we now know how to set up the correct environment, let's start with our plugin, it will be the simplest plugin you can build, an amplifier that you can turn gain up or down, since the goal here is on how to use DPF I will not show how to create sophisticated plugins.

Let's create the folder for the plugin:

```bash
mkdir -v MyAmp
```

Now we have the following structure:

```text
DPFTutorial/
└── dpf/ (git submodule)
└── plugins/
    └── MyAmp/
```

### Setting the plugin information

Now we will create the `DistrhoPluginInfo.h` file in our plugin folder, in this file resides some data about the plugin, like name, how many inputs and outputs, if it receives midi, etc. This information is set by macros, you can find more about them [here](https://distrho.github.io/DPF/group__PluginMacros.html).

```cpp
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_INFO_H_INCLUDED

#define DISTRHO_PLUGIN_NAME  "MyAmp"
#define DISTRHO_PLUGIN_URI   "https://github.com/REIS0/DPFTutorial"

#define DISTRHO_PLUGIN_NUM_INPUTS   1
#define DISTRHO_PLUGIN_NUM_OUTPUTS  1
#define DISTRHO_PLUGIN_IS_RT_SAFE   1

#endif
```

With this we've told DPF information about our plugin `DISTRHO_PLUGIN_NAME` is our plugin's name, `DISTRHO_PLUGIN_URI` for our plugin URI (which I won't go in depth of what a URI is, but you can take a [read](https://wikipedia.org/wiki/URI)), you can just use your git repository URL, `DISTRHO_PLUGIN_NUM_INPUTS` sets how many inputs our plugin will have, `DISTHRO_PLUGIN_NUM_OUTPUTS` sets how many outputs, and `DISTHRO_PLUGIN_IS_RT_SAFE` says if out plugin is safe to use in a real time context, since our plugin is really simple we set this to `1`.

### Adding parameters

Now we will set the parameters our plugin will have, for now these are only declarations, later we are going to set more information like values, their name in the GUI, etc. Add them to `DistrhoPluginInfo.h`, after the plugin information and just before the `#endif` section:

```cpp
...

enum Parameters {
    kGain,
    kParameterCount
};

#endif
```
<!-- TODO: melhorar explicacao de enum -->
The parameters are one `enum` structure, `kGain` is our only parameter, `kParameterCount` is the number of parameters we have (for those who don't know how `enum` works, basically each item corresponds to a number, so `kGain` will be 0 and `kParameterCount` will be 1, notice that 1 is the number of parameters), this will be important later when we create our plugin file.

### The plugin itself

Now let's create our plugin, first create `MyAmp.cpp` file in the same folder, then we will add some code:

```cpp
#include "DistrhoPlugin.hpp"

START_NAMESPACE_DISTRHO

class MyAmp : public Plugin {
    public:
        MyAmp() : Plugin(kParameterCount, 0, 0), gain(1.0) {}

    private:
        float gain;

        DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MyAmp);
}

Plugin *createPlugin() { return new MyAmp(); }

END_NAMESPACE_DISTRHO
```

In the first line we include the `DistrhoPlugin.hpp` to import the functions we will need, the second and the end `START_NAMESPACE_DISTRHO` and `END_NAMESPACE_DISTRHO`, as the name suggests it sets the namespace for the functions, if you don't know about namespaces there's a ton of resources on the internet to read about, for now just remember we need to add them to our code. After that we create our `MyAmp` class, as you can see is the name of our plugin, is not necessary to have the same name but is a good practice, so I highly recommend using it, then we need to inherit from the `Plugin` class, with this we set that our class will be a plugin type class, and we add a constructor for `Plugin` with the parameters: number of parameters in the plugin, number of programs in the plugin and number of states in the plugin, for this plugin we will set only the number of parameters, using the `kParameterCount` we explained earlier. Finally we set the initial value for the `gain` variable, which is where we will read and store the values for `kGain` parameter (so with this logic, we can easily assume that for each parameter we'll need a variable in our plugin class).

### Adding some "metadata" information

Now let's add some information about our plugin, this information will be more like metadata, author, licensing, etc:

```cpp
    // PUBLIC SPACE
...
    protected:

        const char *getLabel() const override { return "MyAmp"; }
        const char *getDescription() const override {
            return "Simple amp plugin.";
        }
        const char *getMaker() const override { return "REIS0"; }
        const char *getLicense() const override { return "MIT"; }
        uint32_t getVersion() const override { return d_version(1,0,0); }
        int64_t getUniqueId() const override { 
            return d_cconst('M','A','D','T'); 
        }

    private:
...
```

The functions speak for themselves, but we will have a fast look at them: `getLabel()` is for the plugin label, almost all the times you will put the plugin name, `getDescription()` to describe the plugin, `getMaker()` who made the plugin, here is my username, but you should set your own, `getLicense()` is for the license, I'm using MIT here but you can choose any you prefer, `getVersion()` for the plugin version, so if is 1.0.0 we will set as `d_version(1,0,0)`, `getUniqueId()` sets the plugin ID, this is used by the host the know your plugin and to not cause conflicts between other ones, here I've set `'M','A','D','T'`, which I've taken from **M**y **A**mp **D**PF **T**utorial, personally I prefer to set it related to the plugin name, but is up to you.

### Parameters

Let's handle our parameters now, first we need to start the parameters we set earlier, is not complicate, add this function below the metadata:

```cpp
// SOME METADATA
...
    void initParameter (uint32_t index, Parameter& parameter) override {
        switch (index) {
            case kGain:
                parameter.name = "Gain";
                parameter.symbol = "gain";
                parameter.ranges.def = 1.0f;
                parameter.ranges.min = 0.0f;
                parameter.ranges.max = 2.0f;
                break;
            default:
                break;
        }
    }
...
```

Here we use a `switch` to match the parameters, if we had more than one parameter than we would need to add more cases, since we only have `kGain` there's no need to. Let's pass through these stuff: `parameter.name` sets the name for the parameter, so when you open the plugin you'll see "Gain" on it, then we have `parameter.symbol`, we can compare this to a parameter id, so it needs to be unique, and you can't repeat for other parameters, `parameter.ranges` sets the values for our parameter, `def` is the default value when we open the plugin for the first time, `min` is minimum possible value, `max` is the maximum possible value.

For now, we only initialized the parameters and need a way to handle them, for this purpose we'll add two new functions:

```cpp
...
    float getParameterValue(uint32_t index) const override {
        switch (index) {
        case kGain:
            return gain;
        default:
            return 0.0;
        }
    }

    void setParameterValue(uint32_t index, float value) override {
        switch (index) {
        case kGain:
            gain = value;
            break;
        default:
            break;
        }
    }
...
```

The first function is a way for the plugin to know the actual value we have stored in out parameter, now we have only one parameter `kGain`, which maps to our `gain` variable, this logic is repeatable for any number of parameters. The second function is for handling when the user changes the parameter value in the plugin UI (for now we'll be using the generic UI provided by the host, but in the future we can add a custom one), to store the new value we just update our variable that corresponds to the changed parameter, in this case again `gain` is the variable mapped to the `kGain` parameter.

Now that we have everything set, we can finally write our processing logic, since the focus is working with a DPF setup I won't be going deeper into the audio processing part, also this is just an amplifier, so it will be very simple and there isn't much to explain:

```cpp
...
    void run(const float **inputs, float **outputs, uint32_t frames) override {
        const float *const in = inputs[0];
        float *const out = outputs[0];

        for (uint32_t i = 0; i < frames; i++) {
            out[i] = in[i] * gain;
        }
    }

private:
...
```

First we assign the inputs and outputs, since we only have one input and one output as we set in `DistrhoPluginInfo.h` we just get the `0` position, but if we had two, we'll need to map the `0` and `1` positions, and it goes as we increase the inputs and outputs number. Now we'll go through our `frames`, also known as samples (if you don't really know what I mean take a look [here](https://www.youtube.com/playlist?list=PLbqhA-NKGP6B6V_AiS-jbvSzdd7nbwwCw)), and make the processing, since it's just an amplifier this part is very simple.

### Makefiles

Now that we have our plugin class completed, we need to build it (recently cmake support was added to DPF, but since I don't know how to use cmake at all it will not be covered here), for this purpose we'll write two Makefiles, if you don't know what Makefiles are you can just search for it and find some good explanations and guides, the first Makefile will be in our plugin folder:

```text
DPFTutorial/
└── dpf/ (git submodule)
└── plugins/
    └── MyAmp/
        └── DistrhoPluginInfo.h
        └── MyAmp.cpp
        └── Makefile
```

```Makefile
#!/usr/bin/make -f
# Makefile for DISTRHO Plugins #
# ---------------------------- #
# Created by falkTX
#
# Modified by REIS0

VST2 ?= true
LV2 ?= true

# --------------------------------------------------------------
# Project name, used for binaries

NAME = MyAmp

# --------------------------------------------------------------
# Files to build

FILES_DSP = MyAmp.cpp

# --------------------------------------------------------------
# Do some magic

include ../../dpf/Makefile.plugins.mk

# --------------------------------------------------------------
# VST2 and LV2 targets

ifeq ($(VST2), true)
TARGETS += vst
endif

ifeq ($(LV2), true)
ifeq ($(HAVE_DGL),true)
TARGETS += lv2_sep
else
TARGETS += lv2_dsp
endif
endif

all: $(TARGETS)

# --------------------------------------------------------------

```

If you're not used to Makefiles it might look a little complicated, but don't worry, you can just copy this one and change it according to your needs, so here what we do in it: first we set what plugin formats we'll be compiling it to, in this case LV2 and VST (there's also the option as a JACK Standalone application in DPF), then we set the name of our plugin, the files needed to compile, include the DPF stuff, and then some instructions for the compilation.

The second Makefile we'll in our project root folder:

```text
DPFTutorial/
└── dpf/ (git submodule)
└── plugins/
    └── MyAmp/
└── Makefile
```

```Makefile
#!/usr/bin/make -f
# Makefile for DISTRHO Plugins #
# ---------------------------- #
# Created by falkTX
#
# Modified by REIS0

PLUGIN=MyAmp

include dpf/Makefile.base.mk

all: dgl plugins gen

# --------------------------------------------------------------

dgl:
ifeq ($(HAVE_OPENGL),true)
 $(MAKE) -C dpf/dgl opengl
endif

plugins: dgl
 $(MAKE) all -C plugins/$(PLUGIN)

ifneq ($(CROSS_COMPILING),true)
gen: plugins dpf/utils/lv2_ttl_generator
 @$(CURDIR)/dpf/utils/generate-ttl.sh
ifeq ($(MACOS),true)
 @$(CURDIR)/dpf/utils/generate-vst-bundles.sh
endif

dpf/utils/lv2_ttl_generator:
 $(MAKE) -C dpf/utils/lv2-ttl-generator
else
gen:
endif

# --------------------------------------------------------------

clean:
 $(MAKE) clean -C dpf/dgl
 $(MAKE) clean -C dpf/utils/lv2-ttl-generator
 $(MAKE) clean -C plugins/$(PLUGIN)
 rm -rf bin build

# --------------------------------------------------------------

.PHONY: plugins

```

In this one the only thing we need to change is the `PLUGIN` variable, everything else you can just leave it there, or edit if you know what you're doing.

### Compiling the plugin

With everything setup we can finally compile and test our plugin, just go to the root project folder and run `make`:

```sh
DPFTutorial/plugins/MyAmp$: make
```

If everything went well, it should generate a `bin` folder with an LV2 and VST plugin inside, LV2 is a folder and VST is just a file. Now just load it in a DAW or a host like Carla, and test if it works.

## Conclusion

And with this we finished the first DPF guide, while this was mostly a setup tutorial, which it is in my opinion the hardest part to get into when someone first encounter the DPF framework, so far I hope I've helped in any way. Maybe I'll try to make a UI focused guide in the future. You can find the whole project in the [GitHub repo](https://github.com/REIS0/DPFTutorial).