Audio plugin development with DPF: first plugin

reis0

Arthur O. Reis

Posted on June 18, 2022

Audio plugin development with DPF: first plugin

What is DPF?

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:

mkdir -v DPFTutorial && cd DPFTutorial
git init
Enter fullscreen mode Exit fullscreen mode

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

git submodule add https://github.com/DISTRHO/DPF dpf
Enter fullscreen mode Exit fullscreen mode

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):

mkdir -v plugins
Enter fullscreen mode Exit fullscreen mode

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

projectdir/
└── dpf/ (git submodule)
└── plugins/
    └── MyPlugin (not created yet)
Enter fullscreen mode Exit fullscreen mode

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:

mkdir -v MyAmp
Enter fullscreen mode Exit fullscreen mode

Now we have the following structure:

DPFTutorial/
└── dpf/ (git submodule)
└── plugins/
    └── MyAmp/
Enter fullscreen mode Exit fullscreen mode

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.

#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
Enter fullscreen mode Exit fullscreen mode

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), 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:

...

enum Parameters {
    kGain,
    kParameterCount
};

#endif
Enter fullscreen mode Exit fullscreen mode

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:

#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
Enter fullscreen mode Exit fullscreen mode

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:

    // 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:
...
Enter fullscreen mode Exit fullscreen mode

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 My Amp DPF Tutorial, 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:

// 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;
        }
    }
...
Enter fullscreen mode Exit fullscreen mode

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:

...
    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;
        }
    }
...
Enter fullscreen mode Exit fullscreen mode

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:

...
    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:
...
Enter fullscreen mode Exit fullscreen mode

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), 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:

DPFTutorial/
└── dpf/ (git submodule)
└── plugins/
    └── MyAmp/
        └── DistrhoPluginInfo.h
        └── MyAmp.cpp
        └── Makefile
Enter fullscreen mode Exit fullscreen mode
#!/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)

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

Enter fullscreen mode Exit fullscreen mode

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:

DPFTutorial/
└── dpf/ (git submodule)
└── plugins/
    └── MyAmp/
└── Makefile
Enter fullscreen mode Exit fullscreen mode
#!/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

Enter fullscreen mode Exit fullscreen mode

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:

DPFTutorial/plugins/MyAmp$: make
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
reis0
Arthur O. Reis

Posted on June 18, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related