Assignment 0.5: Hello PGM

Due: TBD
Deliverable: None

In this assignment, we will walk you through incorporating PluginGUIMagic (PGM) into your JUCE project. In the previous , you installed Projucer and built your first JUCE audio-plugin project.

This guide accompanies the lecture video on adding PGM to JUCE.

Key steps to importing PGM into your JUCE PluginProcessor:

  1. Add the foleys_gui_magic module to your Projucer project
  2. Change juce::AudioProcessor to foleys::MagicProcessor
  3. Remove the function overrides
    • juce::AudioProcessorEditor* createEditor()
    • bool hasEditor()
    • void getStateInformation (juce::MemoryBlock& destData)
    • void setStateInformation (const void* data, int sizeInBytes)
  4. "Optional" for now: Set up an AudioProcessorValueTree to prepare the plugin for parameters

1. Add Plugin GUI Magic (PGM)

After creating your Projucer audio-plugin project, add the foleys_gui_magic JUCE module into your project:

The PluginGUIMagic module foleys_gui_magic depends on other JUCE modules such as the juce_dsp module. Include these additional dependencies by clicking on the "Add missing dependencies" button at the lower left:

Launch your project in your IDE

2. (I got the) MagicProcessor (in me)

In PluginProcessor.h, swap the parent class of your plugin juce::AudioProcessor for foleys::MagicProcessor

Reflect this change in your constructor implementation in PluginProcessor.cpp

3. Remove the editor function overrides

foleys::MagicProcessor handles the implementation of

Comment out or delete these override declarations in PluginProcessor.h, and similarly take out their (empty) implementations in PluginProcess.cpp. After building and running your standalone plugin app, you should see the PGM Editor alongside an empty GUI:

As a last bit of tidying up, go into Projucer and delete the files PluginEditor.h and PluginEditor.cpp

Saving your layout

PGM uses an XML file to describe the GUI layout. First, use the PGM Editor to create a layout that says "Hello World" by dragging the Label object into the Editor view hierarchy, and setting the text to be "Hello World":

Now save your GUI in your project source directory as Layout.xml using File > Save XML:

JUCE plugin resources, such as this GUI XML file, binary images, and other audio/data files, are normally stored inside your plugin executable as "JUCE Binary Data". This solves a lot of platform-dependency problems. Projucer handles this so that your IDE sees it all as BinaryData source code (check out BinaryData.cpp in your Projucer-generated project). For this to work, you must include the XML layout file in your Projucer project, and regenerate your project from Projucer every time you change the XML (or edit the generated BinaryData source yourself, which is sometimes faster). In the "+" menu shown in the screenshot below, select "Add Existing Files..." and then choose your saved Layout.xml file:

Now that you have your GUI layout stored inside the plugin executable, you can initialize the GUI at plugin startup. Since PluginProcessor inherits from MagicProcessor, it has access to the foleys::MagicProcessorState magicState, which can load your GUI XML.

In your PluginProcessor.cpp constructor:

magicState.setGuiValueTree(BinaryData::Layout_xml, BinaryData::Layout_xmlSize);

This of course assumes you named your GUI XML file Layout.xml.

The next time you instantiate your plugin, your GUI should be automatically initialized!

Audio Processor Value Tree State (say that five times fast)

Controllability is key to plugins. To do this we need to initialize an AudioProcessorValueTreeState object with our AudioProcessorParameter descriptions. The JUCE tutorial on adding plug-in parameters might be helpful to read at this point. There is also a nice video on the topic.

To be notified of parameter changes, JUCE and PGM support the listener pattern, more generally called the Observer Pattern. In this software design pattern, the GUI editor and anybody else can register to be a listener for changes in any AudioProcessorParameter. When a parameter is changed, such as by received MIDI or slider motion, all parameter listeners are notified with the new value of the parameter.

Add a second private inheritance from juce::AudioProcessorValueTreeState::Listener. Your class definition in PluginProcessor.hshould now look like

class PGMAudioProcessor  : public foleys::MagicProcessor,
    private juce::AudioProcessorValueTreeState::Listener

This listener class requires that you implement a parameter-change "callback" function:

void parameterChanged(const juce::String
    &parameterID, float newValue) override;
Create a treeState private member to hold the parameters in a nicely general ValueTree structure:
juce::AudioProcessorValueTreeState treeState;
You also need a function that creates all the plugin parameters (and their groups) at startup:
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
Initialize treeState in your constructor after initializing your parent MagicProcessor.
treeState(*this, nullptr, JucePlugin_Name,
    createParameterLayout())
This initializes your treeState with the parameter-tree returned by createParameterLayout. The treeState is now uniquely tied to the MagicProcessor, and must have the same lifetime. Only the parameter values can be altered after this initialization; the ParameterLayout is now fixed. Note that this "ParameterLayout" has nothing to do with your GUI layout. It is an optionally hierarchical tree-structure containing all of your AudioProcessorParameters that PGM will use to generate selection menus when assigning a parameter to a GUI widget such as a slider or button. (In the AudioProcessor, this tree gets flattened into a linear list, presumably because some plugin hosts can only deal with that.) It is also more than just a "layout". It is all your parameters, together with their default/min/max/step values, as well as any hierarchical grouping info. See the AudioProcessorValueTreeState reference doc for more.

Your final constructor should look something like this:

You of course need to implement your createParameterLayout function which returns a juce::AudioProcessorValueTreeState::ParameterLayout. For now, this can be empty like so:

juce::AudioProcessorValueTreeState::ParameterLayout PGMAudioProcessor::createParameterLayout()
{
  juce::AudioProcessorValueTreeState::ParameterLayout layout;
  return layout;
}

Finally, create an empty implementation of parameterChanged in PluginProcessor.cpp. Now you are all ready to tackle Assignment 1!

An easy way to add parameters to your ParameterLayout is to copy and modify code from some example, such as the PGM SignalGenerator example in ~/PGM/examples/SignalGenerator/Source/. Search for "parameter" in that directory to find example implementations of createParameterLayout() and parameterChanged(), along with the needed parameters themselves, such as mainFreq.

If you find the PGM examples hard to read, try watching the lecture videos introducing some of the PGM examples in detail.