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
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
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
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)
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
Now we have the following structure:
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.
#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), 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
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
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:
...
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;
}
}
...
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;
}
}
...
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:
...
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
#!/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:
DPFTutorial/
└── dpf/ (git submodule)
└── plugins/
└── MyAmp/
└── 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
:
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.
Top comments (2)
Beautifully written and paced tutorial - I just fail on the very last command:
DPFTutorial/plugins/MyAmp$: make
I get:
I am running this from the superdirectory of DPFTutorial (in my case called "dev") is that correct?
If I navigate instead into the MyAmp folder and run make I get
MyAmp.cpp:19:20: error: ‘d_const’ was not declared in this scope; did you mean ‘d_cconst’?
But I presume that comes from not running from the super directory. But do you have any ideas what was wrong with the original make command?
As I say, really well written tutorial though
Steve
Sorry for the delay, it's been a while since I've checked the comments.
For the question, can you give me an overview of your plugin folder? If there's a makefile it should be able to at least not give this erro.
The second error message is actually correct, it should be
d_cconst
instead ofd_const
, I'll fix the code.Anyway thanks for the feedback, I hope I helped even if just a little.