Tutorial: Create a basic Audio/MIDI plugin, Part 2: Coding your plug-in

Table of Contents


This tutorial follows on from Tutorial: Create a basic Audio/MIDI plugin, Part 1: Setting up, and will talk through starting from a clean project and ending up with a fully functioning, if somewhat simple, plug-in that can react to incoming MIDI notes.

Level: Intermediate

Platforms: Windows, macOS, Linux

Plugin Format: VST, VST3, AU, Standalone

Classes: AudioProcessorEditor, AudioProcessor, Slider, MidiMessage, MidiBuffer

Getting started

Launch the Projucer and create a new audio plug-in project with the name TutorialPlugin. If you don't remember how to do that, please refer to Tutorial: Projucer Part 1: Getting started with the Projucer.

Orientation

A newly-created audio plug-in project contains two main classes: PluginProcessor handles the audio and MIDI IO and processing logic, and PluginEditor handles any on screen GUI controls or visualisations.

When passing information between these two it is best to consider the processor as the parent of the editor. There is only one plug-in processor whereas you can create multiple editors. Each editor has a reference to the processor such that it can edit or access information and parameters from the audio thread. It is the editor’s job to set and get information on this processor thread and not the other way around.

The main function we will be editing in the PluginProcessor.cpp file is the processBlock() method. This receives and produces both audio and MIDI data to the plug-in output. The main function we will change in the PluginEditor.cpp file is the constructor, where we initialise and set up our window and GUI objects, and also the paint() method where we can draw extra controls and custom GUI components.

The editor constructor currently has one method call — setSize (400, 300) — which sets the size of our plug-in window. Let's make a smaller window of (200, 200) for this simple application.

TutorialPluginAudioProcessorEditor::TutorialPluginAudioProcessorEditor (TutorialPluginAudioProcessor& p)
: AudioProcessorEditor (&p), processor (p)
{
// This is where our plugin’s editor size is set.
setSize (200, 200);
}

Create a simple GUI control

We will create a slider object to change the volume of MIDI messages as they come in.

Create a new Slider object in the Editor header file called midiVolume:

class TutorialPluginAudioProcessorEditor : public AudioProcessorEditor
{
public:
TutorialPluginAudioProcessorEditor (TutorialPluginAudioProcessor&);
~TutorialPluginAudioProcessorEditor();
//===================================================================
void paint (Graphics&) override;
void resized() override;
private:
// This reference is provided as a quick way for your editor to
// access the processor object that created it.
TutorialPluginAudioProcessor& processor;
Slider midiVolume;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TutorialPluginAudioProcessorEditor)
};
Note
The AudioProcessorEditor plays the same role in an Audio Plug-in that the main content component has in a standalone app. See Tutorial: The main component.

We can set the properties of this slider with various functions in the editor constructor. We must also call addAndMakeVisible (&midiVolume) to attach our slider to the editor. There are many different slider styles and parameters to use and experiment with in your own project. For this tutorial adjust the slider parameters such that your editor constructor looks like this:

TutorialPluginAudioProcessorEditor::TutorialPluginAudioProcessorEditor (TutorialPluginAudioProcessor& p)
: AudioProcessorEditor (&p), processor (p)
{
// This is where our plugin’s editor size is set.
setSize (200, 200);
// these define the parameters of our slider object
midiVolume.setSliderStyle (Slider::LinearBarVertical);
midiVolume.setRange(0.0, 127.0, 1.0);
midiVolume.setTextBoxStyle (Slider::NoTextBox, false, 90, 0);
midiVolume.setPopupDisplayEnabled (true, false, this);
midiVolume.setTextValueSuffix (" Volume");
midiVolume.setValue(1.0);
// this function adds the slider to the editor
addAndMakeVisible (&midiVolume);
}

JUCE windows have a method called resized() that is called once at the initialisation of the window and every time the window is resized by the user (if resizing is enabled). This is a good place to set the size and position of our sliders (and other GUI components) so they can be positioned relative to the window bounds.

void TutorialPluginAudioProcessorEditor::resized()
{
// sets the position and size of the slider with arguments (x, y, width, height)
midiVolume.setBounds (40, 30, 20, getHeight() - 60);
}

Lets also change the "Hello World" text to "Midi Volume" in the paint() function and move it to the top. This function is where all custom shapes and GUI elements are drawn to the window.

void TutorialPluginAudioProcessorEditor::paint (Graphics& g)
{
// fill the whole window white
// set the current drawing colour to black
// set the font size and draw text to the screen
g.setFont (15.0f);
g.drawFittedText ("Midi Volume", 0, 0, getWidth(), 30, Justification::centred, 1);
}
Note
You can learn more about Components and their methods paint() and resized() in Tutorial: The Graphics class and Tutorial: Parent and child components.

Running this program should create a plug-in that looks like this in the host editor:

tutorial_code_basic_plugin_1.png

Pass control information to the processor class

We now have an control that we can adjust, but that doesn’t actually control anything. We need to intercept the incoming MIDI data and replace the note on volume with the volume of our slider, and this is done in the processor. In order to get the slider value to control the MIDI effect on the processor thread we need to create a new variable on the processor thread that we can use the slider to change.

Create a new public float variable called noteOnVel in the processor class header. This is the variable that we will set with the slider.

public:
float noteOnVel;

We need to set this value whenever the slider is changed. To do this we use a slider listener callback function. Any class can inherit slider listener functionality but for the purposes of this tutorial we will add this functionality to the editor class.

Note
For a more in-depth description of listeners please see Tutorial: Listeners and Broadcasters.

Add the inheritance and the default callback function so the editor class looks like this:

class TutorialPluginAudioProcessorEditor : public AudioProcessorEditor,
{
public:
TutorialPluginAudioProcessorEditor (TutorialPluginAudioProcessor&);
~TutorialPluginAudioProcessorEditor();
//==================================================================
// This is just a standard Juce paint method...
void paint (Graphics& g) override;
void resized() override;
private:
void sliderValueChanged (Slider* slider) override;
//==================================================================
// This reference is provided as a quick way for your editor to
// access the processor object that created it.
TutorialPluginAudioProcessor& processor;
Slider midiVolume;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TutorialPluginAudioProcessorEditor)
};

Now we add the slider listener to our volume slider in the editor constructor:

TutorialPluginAudioProcessorEditor::TutorialPluginAudioProcessorEditor (TutorialPluginAudioProcessor& p)
: AudioProcessorEditor (&p), processor (p)
{
// ...
// add the listener to the slider
midiVolume.addListener (this);
}

...and insert the listener function that sets our public processor volume variable:

void TutorialPluginAudioProcessorEditor::sliderValueChanged (Slider* slider)
{
processor.noteOnVel = midiVolume.getValue();
}

We now have a slider that controls our variable in the processor class. We now need to use this processor variable to alter our MIDI data.

Modify MIDI notes

The processBlock() method in the processor class receives and produces both MIDI and audio buffers in real time. We are going to iterate through the midi buffer to intercept signals of noteOn type and set their velocity to the value of our slider.

The MIDI messages are all passed through this function. To alter the MIDI as it passes through we create a new MidiBuffer object called processedMidi and append our modified MIDI signals to this new buffer before swapping it with the original at the end (this avoids direct modification problems). Remove the current code in the processBlock() method (this handles the audio buffer, which we do not need for this tutorial) and replace it with the code below.

void TutorialPluginAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
buffer.clear();
MidiBuffer processedMidi;
int time;
for (MidiBuffer::Iterator i (midiMessages); i.getNextEvent (m, time);)
{
if (m.isNoteOn())
{
uint8 newVel = (uint8)noteOnVel;
}
else if (m.isNoteOff())
{
}
else if (m.isAftertouch())
{
}
else if (m.isPitchWheel())
{
}
processedMidi.addEvent (m, time);
}
midiMessages.swapWith (processedMidi);
}

Run the plug-in in the host environment and you will see that all MIDI note on signals are coming through our plug-in have the value set with our slider. The if() statement above can be also used to modify and apply various transformations and effects to other types of incoming MIDI signals. With these methods you can build more complex effects and GUIs.

Exercise
Experiment with other GUI components such as buttons and sliders, check the JUCE DemoRunner for a taste of JUCE’s capabilities, and refer back to the API documentation for more information.
Note
Generating audio using the incoming MIDI notes will be covered in a future tutorial (see Tutorial: Build a MIDI synthesiser). For now, please have a look at the AudioPluginDemo, which is located in JUCE/examples/Plugins.

Summary

After reading this tutorial, you should be able to:

See also