My aim was to build an effect plugin for LMMS.
If you have ever tried to build LMMS from source on Windows to develop a plugin, you have probably hit a wall. Setting up the build environment is difficult due to dependencies.
Any native plugins created for LMMS cannot simply be compiled and dropped into the plugin folder. LMMS as a whole must be compiled with the plugin included.
VSTs can only be used for instruments (if I am not mistaken) and LADSPA plugins do not have as many features as native LMMS plugins (if I am not mistaken). Also to note, LMMS native plugins use QT framework.
After spending hours trying to compile on Windows, I eventually moved to using an Azure Virtual Machine since I had leftover Azure credit.
Below is the workflow I used from setting up the VM to getting the plugin running.
The Setup (The VM Workaround)
Instead of investing more time fighting Windows dependencies, I created a Linux virtual machine. I don't have spare SATA slots for another drive to dual boot, so a VM was easier.
The details of my Azure VM were:
• Platform: Azure VM.
• OS: Linux (Ubuntu 22.04).
• Size: Standard D2s v3 (2 vCPUs, 8 GB memory).
The First Problem: RDP vs. X2Go
Using Remote Desktop (RDP) gave me a fast and responsive UI for coding but no audio output. I spent hours trying to make PulseAudio work in RDP.
X2Go, on the other hand, gave me working audio, but the screen refresh rate was extremely slow. Note: X2Go also required PulseAudio. However, I had no issues with PulseAudio on X2Go.
The Solution:
• Code and compile using Remote Desktop Connection.
• Test and listen using X2Go (Slow graphics, but working sound).
Creating the Plugin (The Copy and Paste from Simplest Plugin Method)
Writing a plugin from scratch is error prone. The easiest way was to start to clone an existing plugin that was simple and worked. The Amplifier plugin was perfect for this.
Prerequisite: Make sure that you can compile LMMS and it runs in its unmodified state.
Step 1: Clone the Files
Navigate to the LMMS source directory and copy the Amplifier folder:
Bash
cp -r ~/lmms/plugins/Amplifier ~/lmms/plugins/ YourPluginName
Step 2: Rename Files
Rename every file in that folder from "Amplifier" to "YourPluginName":
Then, open every file and perform a find and replace:
• Find: Amplifier
• Replace: YourPluginName
Step 3: Register the Plugin
LMMS needs to know that your plugin exists. Therefore you must edit PluginList.cmake in the cmake/modules directory and add YourPluginName to the list of plugins.
Step 4: The Code
Now you may move from "Amplifier" logic (simple volume multiplication) to YourPluginName logic.
The Main Workings (YourPluginName.cpp,.h)
In the original Amplifier, the processImpl function just multiplied the audio buffer by a volume float. In your new plugin, you will be doing something else. At this point in time clean up the contents of YourPluginName.cpp until you have the skeleton.
Code: Select all
#include "YourPluginName.h"
#include "embed.h"
#include "plugin_export.h"
namespace lmms
{
extern "C"
{
Plugin::Descriptor PLUGIN_EXPORT YourPluginName_plugin_descriptor =
{
LMMS_STRINGIFY(PLUGIN_NAME),
"YourPluginName",
QT_TRANSLATE_NOOP("PluginBrowser", "A native YourPluginName plugin"),
"email address",
0x0100,
Plugin::Type::Effect,
new PixmapLoader("lmms-plugin-logo"),
nullptr,
nullptr,
} ;
}
YouPluginNameEffect::YourPluginNameEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key) :
Effect(&YourPluginName_plugin_descriptor, parent, key),
m_ampControls(this)
{
}
Effect::ProcessStatus YourPluginNameEffect::processImpl(SampleFrame* buf, const fpp_t frames)
{
//Place the code in here!
}
extern "C"
{
// necessary for getting instance out of shared lib
PLUGIN_EXPORT Plugin* lmms_plugin_main(Model* parent, void* data)
{
return new YourPluginNameEffect(parent, static_cast<const Plugin::Descriptor::SubPluginFeatures::Key*>(data));
}
}
} // namespace lmmsStep 5: Test Compile
Test compile by running:
sudo rm -rf ~/lmms/build
mkdir ~/lmms/build
cd ~/lmms/build
cmake ..
make -j$(nproc)
After the first compile, you should only have to compile what you change each time (based on dates of files) by make -j$(nproc). Each time you edit the plugin, recompile should not take that long. Check to see that YourPluginName shows up in the list of effects and that it opens.
Step 6: Your New DSP Logic Code (YourPluginName.cpp)
If the first part worked, backup and enter your DSP code in //Place the code in here!
Update YourPluginName.h so the prototypes match what you define in the .cpp
Hard Code the Sample Rate (44100 Hz)
LMMS processes all internal audio at 44.1 kHz, even if your operating system or sound device runs at a different rate. Many simple LMMS plugins (like Amplifier) do not expose a sampleRate() function, and calling it will cause a build error. To avoid this and keep beginner plugins simple and stable, simply hard code float rate = 44100.0f;.
Step 7: The Interface (GUI Customization)
The original Amplifier had controls. YourPluginName will require different controls. This requires modifying the Controls (backend) and the Dialog (frontend). The amplifier effect used a Model View Controller style separation. You don't just add a knob, you must define the data (Model) and then the look (View).
The Backend (YourPluginNameControls.h/cpp)
This file defines the Models. A model is a container for a number that handles thread safety and notifies the GUI when it changes.
FloatModel
• Knobs
• Sliders
• Gain
• Frequency
IntModel
• Mode selectors
• Dropdown indexes
• Discrete step parameters
BoolModel
• on/off controls
• Checkboxes
• LED buttons
• Toggles
EnumModel
• Fixed list presets
• Named modes
• Selection boxes
StringModel
• Text input boxes
• User formulas
• File paths
• Naming fields
Note: If you use the standard LMMS Knob widget in your GUI, you should always pair it with a FloatModel, even if you are selecting integers (like Pattern 1, 2, 3). Pairing a Knob with an IntModel can cause crashes in some versions of LMMS. The Fix: Use a FloatModel (range 0.0 to 3.0) and cast it to an int in your code: int pattern = (int)m_patternModel.value()
The Frontend (YourPluginNameControlsDialog.cpp)
Now you need to visualise the model. The amplifier plugin done this in the createView function (or the constructor of the view class depending on implementation age).
You may utilise LMMS widgets like Knob or Led.
Every plugin has a Controls class (inherited from EffectControls).
This class holds parameters (Models) and constructs the GUI when needed.
Controls must implement createView(), which returns your GUI window
return new YourPluginControlDialog(this)
Your GUI class must inherit from EffectControlDialog, which is the base QWidget LMMS uses for plugin editors.
The GUI (control dialog) is built in its constructor, where you:
• Create knobs, sliders, text boxes
• Bind them to Models using widget->setModel(&model)
• Use Qt layouts (QGridLayout, etc.) to position controls
Models handle:
• Value storage
• Automation
• Save/load of plugin settings
• Notifications when parameters change
When the user opens the plugin editor, LMMS calls createView() once and shows the returned window.
The DSP never interacts with the GUI directly. The DSP reads Models, which are updated by the GUI.
The simplest way to add artwork is to edit this vector graphics file artwork.svg.
Step 8: Building, Running and Testing
After each small step on each file, I would suggest building and running, then reverting to the old version if there is an error.
To compile the plugin on Ubuntu VM:
Bash
sudo rm -rf ~/lmms/build
mkdir ~/lmms/build
cd ~/lmms/build
cmake ..
make -j$(nproc)
sudo make install
Once running, test with the spectrum analyser plugin. Look for aliasing, create a unit impulse, create a sine wave at different frequencies and see how the output of the plugin responds and make changes and go through the cycle.
Note:
• The plugin class must inherit from Effect.
• The signature of processImpl must remain the same.
• The plugin must expose lmms_plugin_main() via extern "C".
• The descriptor must match LMMS’ plugin format.
Note: If you modify your header file (.h) for example, adding a new variable or changing a class definition, you must perform a clean build by deleting the build folder contents (rm -rf *) and running cmake again.
If you only run make, the compiler might use old "cached" object files that don't know about your new variables. This results in immediate crashes (Segmentation Faults) when the plugin loads because the memory layout doesn't match what the code expects.
Step 9: Adding a Graph (Optional)
If your plugin needs to display a curve (like a Bode plot for example), LMMS has a built in Graph widget.
To add a graph:
Backend (YourPluginNameControls.h/.cpp)
• Add a graphModel
• graphModel m_graphModel
Frontend (YourPluginNameControlDialog.cpp)
• Create the graph widget and attach the model
Updating the graph
• Any time your data changes, call m_graphModel.setSamples() again.
Step 10: Tips for Debugging
Build in debug mode
cd ~/lmms/build
rm -rf *
cmake -DCMAKE_BUILD_TYPE=Debug ..
make -j$(nproc)
and then run
gdb ./lmms
run
if you get segmentation errors
End Note:
I couldn’t get the source for LMMS to build on Windows, so I switched to a Linux VM. Maybe I’ll solve it one day. However, I’m sharing what I learned because what I have learned may be valuable to others. Importantly, I don’t believe that you need to be a C++ expert, just the basics of programming and DSP are enough to get started. Also to note, I did try this on WSL on Windows 11, in which it would compile, but crashed on running due to audio issues.