Week 2: Introducing Plugin GUI Magic (PGM)

Plugin GUI Magic (PGM) is a JUCE module that supports easily creating, editing, saving, and loading JUCE GUIs at runtime.

PGM Demo Video: Plugin Gui Magic Equalizer Example (JUCE Audio Plugin)

Getting Started with PGM

Clone the main branch of plugin gui magic:

git clone https://github.com/ffAudio/foleys_gui_magic.git

For starters, look over the PGM distribution. It basically contains a single JUCE Module named foleys_gui_magic that you will load into your project using the JUCE Projucer
(or CMake with JUCE support added).
We cover using PGM in the videos linked below.

Basic Idea of PGM

  1. Imagine some readable XML description of your GUI Layout
  2. Read the XML into memory as a JUCE ValueTree
  3. Lay it out onscreen using CSS FlexBox
  4. Support live editing of both the onscreen GUI layout, and a ValueTree display, with edits to one appearing also in the other
  5. Save the XML to file for later reloading (final GUI deployment reads this file at launch time, without a PGM editor)

XML on disk reads into a ValueTree in memory

XML has tags and attributes:
<Slider caption="Gain" parameter="gain"
            min-width="20" min-height="10" max-height="80"/>
These become node types and properties in a juce::ValueTree, which manages a juce::NamedValueSet containing child nodes and named juce::var properties.
A juce::var is similar to a var in JavaScript and wraps a single int, bool, float, string, or array.

JUCE ValueTree

Example ValueTree Usage in C++:

Create a property having ID "Gain" with the value 0.1 stored in a juce::var:
juce::ValueTree svt ("Slider"); // ValueTree Type set forever to "Slider"
svt.setProperty(/* const Identifier& */ "Gain", /* const var& */ 0.1, /* UndoManager* */ nullptr);
juce::ValueTree sbvt ("SliderBank");
sbvt.addChild(svt, /* index */ -1, /* undoMgr */ nullptr);
// ...

Notes:

Understanding JUCE Plugin Parameters

PGM, following JUCE-recommended best practice, requires us to create a juce::AudioProcessorValueTreeState (APVTS) in our processor to hold all of our plugin parameter info in a hierarchically grouped ParameterLayout:

  1. We create our parameters before our processor is fully created, and add them to our ParameterLayout that we pass to the APVTS constructor.
  2. We create the APVTS ourselves (in our derived class of foleys::MagicProcessor which is a derived class of juce::AudioProcessor).
  3. During the APVTS construction, which happens during our processor initialization, we bind ourselves (the processor - "this") permanently and uniquely to the APVTS as the processorToConnectTo.
  4. Summary of the above three points:
    AudioProcessor()
        APVTS()
           createParameterLayout()
           create and return the parameters in a juce::AudioProcessorParameterGroup
           MOVE the parameters into the parameters array OWNED BY THE APVTS
        Later you can access the parameters via APVTS methods such as:
           std::atomic* getRawParameterValue (StringRef parameterID)
           RangedAudioParameter* getParameter (StringRef parameterID)
  5. From then on, the PGM GUI Editor (including any MIDI controllers driving the PGM parameters, etc.) will directly write the plugin parameters in memory, and the AudioProcessor will read them from the same memory.
  6. Notes:
    • Since writing and reading of the same parameter memory may occur on two different CPU threads (we discussed the Message Thread and Audio Thread in the last lecture), std::atomic parameter pointers are used to prevent a so-called "torn" read or write due to a collision of two or more threads.
    • On most CPUs, a single-word parameter (such as a float) is thread-safe for reading and writing thanks to a built-in memory barrier for this purpose, and the Faust compiler assumes this.
    • When the C++ compiler knows a read/write is thread-safe, using std::atomic should do nothing and cost nothing at run time. It is therefore best practice to declare audio processor parameters using std::atomic and let the C++ compiler decide what is needed for thread safety, if anything.
  7. Recommended Reading
    • Read carefully the documentation for the first APVTS constructor.
      Note that "controlling the processor" in this context means manipulating its parameter values.
      You can probably get by simply following parameter handling in the PGM examples (if followed precisely),
      but a better plan is to study the PGM examples after digesting the following:
    • Read the reference doc for AudioProcessorValueTreeState (APVTS).
    • Read the reference doc for the APVTS contained-class ParameterLayout, which we must create and pass to the APVTS constructor.
    • Read the reference doc for the two types of classes that can be added to a ParameterLayout:
    • In class we discussed virtual function definition and when to have them in header files versus .cpp files.
      Since this discussion was quite incomplete, I recorded an interesting chat with GPT-4 on these topics.

This is the most advanced C++ we will have to deal with in Music 320c, involving unique smart pointers and a rigid construction/initialization process.

Be aware that this APVTS-based method of handling plugin parameters is relatively recent, so you'll find older JUCE code, even in official JUCE tutorials, that uses the older (deprecated) processor-based management of the plugin parameters. In particular, createAndAddParameter() is deprecated, so avoid examples using that.

Class Recordings ("Introducing Plugin GUI Magic (PGM)")

Supplementary

The following are classified as supplementary (i.e., optional) because ...
  1. We can use PGM without knowing about its internals (though extending it will require such knowledge)
  2. Last year's recordings are available for more and alternative coverage of the topics, but should not be necessary
  3. Using ChatGPT-4 to write createParameterLayout() is genuinely useful, but arguably outside our scope
That said . . .

  1. PGM Software Overview
  2. Last Year's Class Videos
  3. Lecture Recordings (making of):

    1. 2023-04-13:   Full HD (1920 x 1080)   |   HD (1280 x 720)   |   SD (640 x 480)
    2. 2023-04-18:   Full HD (1920 x 1080)   |   HD (1280 x 720)
    3. 2023-04-20 (first part contains updates regarding this page):   Full HD (1.1 GB, 1920 x 1080)   |   HD (0.2 GB, 1280 x 720)

  4. Using PGM and creating plugin parameters:
    1. Video: GUI Rapid Prototyping in PGM (before any parameters exist)
    2. Video: Creating JUCE Plugin Parameters for PGM
    3. Video: Rebuilding the PGM Equalizer Example GUI from Scratch

  5. Using ChatGPT-4 to write createParameterLayout()