====== 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()); }