JUCE Tutorial from MadKat Music

Summary

While not the most glamorous audio application to start off with, I think an EQ will be a great start to learning the JUCE framework! I’ll be following the above tutorial as a springboard for learning and implementing the JUCE framework here.

Part 1 - Introduction

  • Goal is to create a simple EQ, with a high and low cut (with gain and CF), and a central peak (with gain, Q, and freq)
  • JUCE is a framework built on C++ for developing audio applications and plugins.
  • Projucer is used to create projects and automatically populating the classes for you.

Part 2 - Setting up the Project

  • Follow steps here
  • Audio plugins in JUCE have two main .cpp / .h pairs, PluginProcessor and PluginEditor.
    • PluginProcessor handles our DSP and audio manipulation code
    • PluginEditor handles the GUI

Part 3 - Creating Audio Parameters

PluginProcessor.h holds our class, which is pre-populated with a few class methods, the most important being

  • void prepareToPlay (double sampleRate, int samplesPerBlock) override; docs page
  • void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;

We’re going to start by creating what is called a AudioProcessorValueStateTree, in our PluginProcessor header file, a class that will hold all of the values for our plugin parameters. However, one of the values it requires in its construction is a parameter layout, which we must first declare, and then define in our PluginProccessor.cpp file (snippet shown below).

    // PluginProcessor.cpp
    // Define the create layout function declared in the header file which returns a parameter layout
    juce::AudioProcessorValueTreeState::ParameterLayout SimpleEQAudioProcessor::createParameterLayout()
    {
        // Create new parameter layout object
        juce::AudioProcessorValueTreeState::ParameterLayout layout;

        // Add parameters to layout object
        // New parameter for low cut frequency
        layout.add(std::make_unique<juce::AudioParameterFloat>(
            "LowCut Freq", 
            "LowCut Freq", 
            juce::NormalisableRange<float>(20.0f, 20000.0f,1.0f,1.0f),
            20.0f
            ));
        
        // Create a string array to hold the dB/Oct choices
        juce::StringArray slopeArray;
        for (int i=0; i < 4; i++)
        {
            // Create new string
            juce::String str;
            // Store iterated value e.g. 12 + 0*12 = 12..24..36..48
            str << (12 + i * 12);
            str << " dB/Oct";
            slopeArray.add(str);
        }

        // New parameter for Low Cut Slope
        layout.add(std::make_unique<juce::AudioParameterChoice>(
            "LowCut Slope", // parameter ID
            "LowCut Slope", // parmeter name
            slopeArray, // array of choices
            0 // default index
        ));
        
        // return the layout
        return layout;
}
    // PluginProcessor.h (in public)
    // Declare parameter layout function that returns a parmateter layout
    static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();

    // Declare ValueStateTree object that holds all of the parameter values
    juce::AudioProcessorValueTreeState apvts {*this, nullptr, "Parameters", createParameterLayout()};

Creating the parameter layout meant using some JUCE types, notably the juce::AudioProcessorValueTreeState::ParameterLayout, juce::AudioParameterFloat>, and juce::AudioParameterChoice types. For the latter two, they were initialised as unique pointers using std::make_unique<datatype>() keyword. Additionally, juce::NormalisableRange<float>()was encountered, which is a in-built JUCE function that provides a range of values, it requires min, max, interval, and slew (linearity).

Part 4 - Setting up the DSP