====== The VanillaJuce plugin Processor ======
The class //VanillaJuceAudioProcessor// is arguably the most complicated of all, because, as an instance of the parent class //juce::AudioProcessor// it represents the entire plugin. It therefore has to include several groups of nearly-unrelated functions. Here is the class declaration:
class VanillaJuceAudioProcessor
: public AudioProcessor
, public ChangeBroadcaster
{
public:
enum
{
maxNumberOfVoices = 16
};
VanillaJuceAudioProcessor();
virtual ~VanillaJuceAudioProcessor();
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
void processBlock (AudioSampleBuffer&, MidiBuffer&) override;
AudioProcessorEditor* createEditor() override;
bool hasEditor() const override;
const String getName() const override;
bool acceptsMidi() const override;
bool producesMidi() const override;
double getTailLengthSeconds() const override;
int getNumPrograms() override;
int getCurrentProgram() override;
void setCurrentProgram (int index) override;
const String getProgramName (int index) override;
void changeProgramName (int index, const String& newName) override;
void getStateInformation (MemoryBlock& destData) override;
void setStateInformation (const void* data, int sizeInBytes) override;
void getCurrentProgramStateInformation(MemoryBlock& destData) override;
void setCurrentProgramStateInformation(const void* data, int sizeInBytes) override;
public:
SynthSound* getSound() { return pSound; }
private:
Synth synth;
SynthSound* pSound;
SynthParameters programBank[kNumberOfPrograms];
int currentProgram;
private:
void initializePrograms();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VanillaJuceAudioProcessor)
};
Many of the member functions are trivial, so I won't cover them in any detail here.
===== Constructor and Destructor =====
The constructor instantiates all other required objects and connects them as required:
VanillaJuceAudioProcessor::VanillaJuceAudioProcessor()
: currentProgram(0)
{
initializePrograms();
for (int i = 0; i < maxNumberOfVoices; ++i)
synth.addVoice(new SynthVoice());
pSound = new SynthSound(synth);
pSound->pParams = &programBank[currentProgram];
synth.addSound(pSound);
}
The destructor is actually empty. It's not necessary to call **delete** on the created //SynthVoice// and //SynthSound// objects, because the //synth// object takes ownership of them via the //add...()// calls, and will delete them itself when its destructor gets called.
===== setCurrentProgram() =====
The //setCurrentProgram()// function gets called whenever the user changes the current preset through the plugin host:
void VanillaJuceAudioProcessor::setCurrentProgram (int index)
{
currentProgram = index;
pSound->pParams = &programBank[currentProgram];
sendChangeMessage();
}
This code changes the //pParams// pointer inside the shared //SynthSound// object, which propagates the change to subsequently-played notes (we don't attempt to change currently-sounding notes in response to program changes), then calls //sendChangeMessage()// to notify the GUI editor, so it can update its display accordingly.
===== changeProgramName() =====
The //changeProgramName()// function gets called whenever the user edits the name of the current preset through the plugin host:
void VanillaJuceAudioProcessor::changeProgramName (int index, const String& newName)
{
newName.copyToUTF8(programBank[index].programName, kMaxProgramNameLength);
sendChangeMessage();
}
The present VanillaJuce GUI does not display program names, but I've included a call to //sendChangeMessage()// to notify the GUI anyway, so I won't forget it. If and when I do add a program-name display to the GUI, it will get updated whenever the name is changed through the plugin host.
===== The preset-handling functions =====
The plugin host can call //getCurrentProgramStateInformation()// to get the current preset as a "blob" of binary data (which it can e.g. save to a ''.fxp'' file), or call //setCurrentProgramStateInformation()// to do the reverse operation. In an early version of VanillaJuce, I simply did a binary copy of the //SynthParameters// ''struct'', but I realized that would cause problems as I changed the structure over time, so the new code uses JUCE's XML capabilities to save and restore the data in XML format. (I based my code on that in [[https://github.com/2DaT/Obxd|Obxd]].)
void VanillaJuceAudioProcessor::getCurrentProgramStateInformation(MemoryBlock& destData)
{
ScopedPointer xml = programBank[currentProgram].getXml();
copyXmlToBinary(*xml, destData);
}
void VanillaJuceAudioProcessor::setCurrentProgramStateInformation(const void* data, int sizeInBytes)
{
ScopedPointer xml = getXmlFromBinary(data, sizeInBytes);
programBank[currentProgram].putXml(xml);
sendChangeMessage();
}
Note that //setCurrentProgramStateInformation()// also calls //sendChangeMessage()// because it changes the current preset, and so must notify the GUI.
Most of the details are in the //getXml()// and //putXml()// member functions of //SynthParameters//. The code is unremarkable, but serves to generate and parse XML structures like this (formatted for clarity):
The //getStateInformation()// and //setStateInformation()// do the same thing for an entire bank of patches:
void VanillaJuceAudioProcessor::getStateInformation (MemoryBlock& destData)
{
XmlElement xml = XmlElement("VanillaJuce");
xml.setAttribute(String("currentProgram"), currentProgram);
XmlElement* xprogs = new XmlElement("programs");
for (int i = 0; i < kNumberOfPrograms; i++)
xprogs->addChildElement(programBank[i].getXml());
xml.addChildElement(xprogs);
copyXmlToBinary(xml, destData);
}
void VanillaJuceAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
ScopedPointer xml = getXmlFromBinary(data, sizeInBytes);
XmlElement* xprogs = xml->getFirstChildElement();
if (xprogs->hasTagName(String("programs")))
{
int i = 0;
forEachXmlChildElement(*xprogs, xpr)
{
programBank[i].setDefaultValues();
programBank[i].putXml(xpr);
i++;
}
}
setCurrentProgram(xml->getIntAttribute(String("currentProgram"), 0));
}
Note that //setStateInformation()// also calls //sendChangeMessage()// because it changes all presets, //including the current one//, and so must notify the GUI.
The XML structure for patch banks looks like this, where each '''' item is structured as above:
... 127 more program objects ...
===== Rendering sound: processBlock() =====
The //processBlock()// function, which actually renders audio, simply delegates to the //Synth// object:
void VanillaJuceAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());
}