Page 1 of 1

Introductory How to Build a Native LMMS Effects Plugin

Posted: Fri Dec 05, 2025 10:14 am
by ewanpettigrew

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 lmms

Step 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.


Re: Introductory How to Build a Native LMMS Effects Plugin

Posted: Sun Dec 07, 2025 3:05 am
by ewanpettigrew

I will provide an example of building the most basic LMMS Native plugin within Ubuntu. The code is at the end of the post.

Building Our First LMMS Plugin: "SimpleAmp"
This guide discusses the creation of the simplest possible LMMS native audio effect, an amplifier with a single volume knob. It consists of just two files:

. SimpleAmp.cpp (The logic, interface, and sound processing)
. CMakeLists.txt (The build instructions)

Part 1: The Code (SimpleAmp.cpp)
In LMMS, a plugin isn't just one part it's a cooperation between three parts:
. The Descriptor: The plugin's ID card.
. The Controls (Model): The data (numbers behind the knobs).
. The View (GUI): What the user sees.
. The Effect (DSP): The actual audio processing.

In the real word, this would consist of multiple files. However, for simplicity and learning, I have reduced this to one file (+the CMakeLists.txt).

1. The Headers
At the top, we include what externals created by LMMS developers we need.
. Effect.h: The foundation for all audio effects.
. Knob.h: Gives us the standard LMMS knob widget.
. plugin_export.h: Required for making the plugin visible to the computer.

2. The Plugin Descriptor
This is the ID of your plugin. LMMS reads this before it even loads the plugin to know what it is.

Code: Select all

Plugin::Descriptor PLUGIN_EXPORT SimpleAmp_plugin_descriptor = { ... }

• Internal Name: SimpleAmp (used by the system).
• Display Name: SimpleAmp (seen in the menu).
• Type: Plugin::Type::Effect (Tells LMMS this is an FX, not an instrument).

3. The Controls
The SimpleAmpControls class holds the actual data. It doesn't draw anything. It only remembers settings.
• FloatModel m_gainModel: This is the variable that stores our volume.
o We set it to range from 0.0 (silence) to 2.0 (200% volume), starting at 1.0 (normal volume).
• controlCount(): This tells LMMS "I have 1 parameter."

4. The View
The SimpleAmpDialog class defines how the plugin looks.
• QVBoxLayout: We create a vertical layout.
• Knob: We create a visual knob.
• k->setModel(...): This is where we connect the visual knob to the m_gainModel data. When the user turns the knob, the model updates automatically.

5. The Effect
The SimpleAmp class is where the sound his processed. This is the Digital Signal Processing (DSP).
The processImpl() function runs thousands of times per second. It receives chunks of audio (buf).

Code: Select all

for(fpp_t i = 0; i < frames; ++i) {
    buf[i][0] *= g; // Left Channel
    buf[i][1] *= g; // Right Channel

}
This takes the audio coming in, multiplies it by our g (gain) value, and writes it back out.

6. The Entry Point
Finally, lmms_plugin_main is the entry point. When you drag the plugin into the song editor, LMMS calls this function to create a new instance of your effect.

Part 2: The Build List (CMakeLists.txt)
LMMS uses CMake to compile code. The plugin build file is very simple

Code: Select all

INCLUDE(BuildPlugin)      # Load LMMS'plugin tools

BUILD_PLUGIN(SimpleAmp    # Name of the output file
    SimpleAmp.cpp         # Source code to compile
)

This tells the compiler: "Take SimpleAmp.cpp, link it with the LMMS libraries, and turn it into a plugin file."
________________________________________

To compile and run this (LMMS must be compiled again when new ‘native’ plugins are added.
.. Add SimpleAmp to the list in lmms/cmake/modules/PluginList.cmake , so that it is found by the complier.
. Create a folder SimpleAmp within lmms/plugins
. Create two files and paste in the contents for SimpleAmp.cpp and CMakeLists.txt

Code: Select all

cd ~/lmms/build
cmake ..
make -j$(nproc)

SimpleAmp.cpp

Code: Select all

// LMMS plugin framework headers. They give us access to Effect, UI controls, models
// =====================================================
#include "Effect.h"              // Base class for audio effects
#include "EffectControls.h"      // Base class for effect UI control data
#include "EffectControlDialog.h" // Base class for UI dialogs (plugin windows)
#include "Knob.h"                // UI knob widget
#include "plugin_export.h"       // Required to export plugin to LMMS runtime

// Qt headers for basic UI layout containers (LMMS uses Qt)
// =====================================================
#include <QVBoxLayout>           // Vertical layout container

// Must use the LMMS namespace to shorten type names
using namespace lmms;

// Plugin Descriptor
extern "C"     // Required so the plugin loader can see the symbol
{
    // Create a descriptor struct
    Plugin::Descriptor PLUGIN_EXPORT SimpleAmp_plugin_descriptor =
    {
        "SimpleAmp",                     // Internal name (not visible)
        "SimpleAmp",                     // Display name (what LMMS shows)
        QT_TRANSLATE_NOOP("PluginBrowser","Simple gain plugin"), //description
        "Your Name",                     // Author
        0x0100,                          // Version number (hex encoded)
        Plugin::Type::Effect,            // This is an audio effect
        nullptr,                         // Optional icon (nullptr means none)
        nullptr,                         // Min LMMS version (nullptr = any)
        nullptr                          // Max LMMS version (nullptr = any)
    };
}

// UI Controls Class

class SimpleAmpControls : public EffectControls
{
public:
    // FloatModel is LMMSs automation float parameter class
    FloatModel m_gainModel;

    // Controls constructor
    //   initial = 1.0 (normal volume)
    //   min     = 0.0 (mute)
    //   max     = 2.0 (2x louder)
    //   step    = 0.01 (automation resolution)
    //   owner   = this controls object
    //   name    = "Gain" (shown in UI)
    SimpleAmpControls(Effect* e) :
        EffectControls(e),
        m_gainModel(1.f, 0.f, 2.f, 0.01f, this, tr("Gain"))
    {}

    // How many parameters we have
    int controlCount() override { return 1; }

    // Build the UI window for our plugin
    gui::EffectControlDialog* createView() override;

    // Name used when saving/loading into LMMS project XML
    QString nodeName() const override { return "SimpleAmpControls"; }

    // For this plugin, we can leave saving/loading empty
    void saveSettings(QDomDocument&, QDomElement&) override {}
    void loadSettings(const QDomElement&) override {}
};

// UI Dialog

class SimpleAmpDialog : public gui::EffectControlDialog
{
public:
    SimpleAmpDialog(SimpleAmpControls* c) :
        gui::EffectControlDialog(c)     // call base constructor
    {
        // Vertical layout
        auto* lay = new QVBoxLayout(this);

        // Create knob widget
        // Using default constructor (no style enum needed)
        lmms::gui::Knob* k = new lmms::gui::Knob(this);

        // Bind knob to the FloatModel
        k->setModel(&c->m_gainModel);

        // Insert the knob into the layout
        lay->addWidget(k);
    }
};

// Controls class tells LMMS how to build dialog
gui::EffectControlDialog* SimpleAmpControls::createView()
{
    return new SimpleAmpDialog(this);
}

// Audio DSP class, LMMS calls processImpl() repeatedly for each audio block.
class SimpleAmp : public Effect
{
public:

    // Constructor:

    SimpleAmp(Model* parent,
              const Descriptor::SubPluginFeatures::Key* key) :
        Effect(&SimpleAmp_plugin_descriptor, parent, key),
        m_controls(this)
    {}

    // The audio callback
    ProcessStatus processImpl(SampleFrame* buf, const fpp_t frames) override
    {
        // Read current gain value
        const float g = m_controls.m_gainModel.value();

        // Loop through every stereo sample
        for(fpp_t i = 0; i < frames; ++i)
        {
            buf[i][0] *= g;   // Left channel
            buf[i][1] *= g;   // Right channel
        }

        return ProcessStatus::Continue;   // LMMS should continue processing
    }

    // Asks where the UI control models are stored
    EffectControls* controls() override
    {
        return &m_controls;
    }

private:
    // Instance of controls class
    SimpleAmpControls m_controls;
};


// Plugin Entry Point
extern "C"
{
    PLUGIN_EXPORT Plugin*
    lmms_plugin_main(Model* parent, void* data)
    {
        // Create a new plugin instance and return it to LMMS
        return new SimpleAmp(
            parent,
            static_cast<const Plugin::Descriptor::SubPluginFeatures::Key*>(data));
    }
}

CMakeLists.txt

Code: Select all

INCLUDE(BuildPlugin)      # import LMMS plugin build helpers

BUILD_PLUGIN(SimpleAmp     # build a plugin named SimpleAmp
    SimpleAmp.cpp          # only source file
)                          # end plugin definition

Re: Introductory How to Build a Native LMMS Effects Plugin

Posted: Sun Dec 07, 2025 6:37 am
by musikbear
ewanpettigrew wrote:
Fri Dec 05, 2025 10:14 am

My aim was to build an effect plugin for LMMS.
::

Good stuff!
I lost time and even interests fighting with windows, and then Dom told me that i also diddent have suitable hardware at all :/
I botched my virtual machine during setup, so i will just add that if anyone decide to use Oracle VM, then do NOT expect to be able to expand the VM after you have created it. It is not possible without using Partition Magic, and that is then to be ..trusted with your windows-partition.....! :yuck:
Do NOT do that!
My environment ought to have been 35 GB, and i only allocated 10
HUGE mistake! Unfortunately Ming-compiler (Linux) is not doing the best of jobs/ dont like QT, im not sure witch, but LMMS Ming-version has issues