-
Notifications
You must be signed in to change notification settings - Fork 12
The Juce Project
After creating and setting up the project with the Projucer application there are four files in the source folder:
PluginProcessor.h
PluginProcessor.cpp
PluginEditor.h
PluginEditor.cpp
Due to C++ conventions the definition and declaration of functions and classes are often split into different files. This is done to make the code more readable and more modular. The declarations are put into a header file (.h
). The content of this file are "included" into another file via a #include "file.h"
statement. The #include
statement is substituted for the code in the specified file by the compiler.
The header files declare two classes:
PluginAudioProcessor
PluginAudioProcessorEditor
The Projucer creates the files as such that the classes are named *NameOfProject*AudioProcessor
and *NameOfProject*AudioProcessorEditor
respectively.
I will be calling these classes PluginAudioProcessor
and PluginAudioProcessorEditor
for brevity.
These two classes implement all the main functionality of the plugin: the PluginAudioProcessor
class handles all processing of audio or MIDI and the PluginAudioProcessorEditor
class everything concerning GUI and user interaction.
So why are the classes split up that way? When the program runs the two classes are instantiated as two objects. These objects will be running on two seperate threads. The PluginAudioProcessor
object will run on the "audio thread" and the PluginAudioProcessorEditor
will run on the "message thread" GUIs often have to wait for either the user to input something or for callbacks from the operating system. The audio processing has to happen in real time so it is never a good idea to do anything asynchronous (like GUI or loading files) on the audio thread.
This file declares the class PluginAudioProcessor
. The class contains a number of attributes (member variables) and methods (member functions) not all of which are of importance to us right now. PluginAudioProcessor
is a subclass of AudioProcessor
. This basically means that this class implements all of the features of it's superclass and adds or changes some.
This behavior is called inheritance and we will come across this some more later on.
C++ classes have so called access specifiers for their members. The access specifiers are:
- protected (we're not really interested in this for now)
- private
- public
These keywords govern how we can interact with the class from the outside. Public members can be accessed from anywhere in the program whereas private members can only be accessed from inside the class.
Before the class is declared we find some statements at the top:
#ifndef PLUGINPROCESSOR_H_INCLUDED
#define PLUGINPROCESSOR_H_INCLUDED
#include "../JuceLibraryCode/JuceHeader.h"
The first statements are so called Header Guards they assure that the file is not included more than once. At the end of the file the #ifndef
block is closed with #endif
.
The first two functions in our class are the constructor and the deconstructor:
PluginAudioProcessor();
~PluginAudioProcessor();
These are special functions responsible for initializing the class as well as allocating and deallocating space for the class in memory.
The next two functions:
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
are executed whenever there is a change in the audio configuration (for example on the start of the program or when switching audio devices) or when audio shuts down. They handle setting correct values for the sampling rate and block size or releasing resources like loaded samples when the program shuts down.
Something you might have noticed is the keyword override
at the end of the function declaration. This tells us that the function is overriding a virtual function declared in a class this class inherits from. For me this was the source for a lot of frustrating compiler errors and failing builds because most classes you can inherit from in JUCE have some virtual functions that you have to implement. This is important to keep in mind when adding inheritances to your class!
The next function we are interested in is:
void processBlock (AudioSampleBuffer&, MidiBuffer&) override;
This is the heart of the program. All audio and midi processing happens here. The function is called once every audio-block. It has references to the current audio-block as an AudioSampleBuffer
and all the midi events since the last function call as a MidiBuffer
passed into it as arguments.
What follows are a bunch of house-keeping functions I won't go into detail on. They set various values and flags required for running the plugin.
Right at the end there are two more interesting functions:
void getStateInformation (MemoryBlock& destData) override;
void setStateInformation (const void* data, int sizeInBytes) override;
These functions allow for storing and recalling all the parameters of the plugin. This lets us achieve some persistence for the plugin and we can also implement presets in this way.
This file looks a lot more manageable.
When we look at the constructor we will see that it gets passed a reference to the PluginAudioProcessor
object into it. This reference is also defined as a private member PluginAudioProcessor& processor
. This lets us access the AudioProcessor from inside the AudioProcessorEditor which is very useful whenever we want the GUI to change some aspect of audio processing (like a volume slider).
Apart from the constructor and the deconstructor there are only two more functions:
void paint (Graphics&) override;
void resized() override;
The paint
function handles the drawing of the GUI. The resized
function is called whenever the window of the application is resized or opened. In this function we will set the position for all the GUI elements.