Both sides previous revision
Previous revision
Next revision
|
Previous revision
|
overview [2017/08/30 16:09] shane [The SynthSound object] |
overview [2017/08/30 16:43] (current) shane [The SynthSound object and class juce::SynthesiserSound] |
| |
The processor needs to be able to notify the GUI editor when it changes one or more synth parameters (e.g. when a new preset is selected), so it can update the GUI display. This can be done in any number of ways, but I chose to have the //VanillaJuceAudioProcessor// class also derive from the //juce::ChangeBroadcaster//, and the //VanillaJuceAudioProcessorEditor// inherit from //juce::ChangeListener//. The processor calls its //sendChangeMessage()// function to notify the editor, which results in a call to the editor's //changeListenerCallback()// function. | The processor needs to be able to notify the GUI editor when it changes one or more synth parameters (e.g. when a new preset is selected), so it can update the GUI display. This can be done in any number of ways, but I chose to have the //VanillaJuceAudioProcessor// class also derive from the //juce::ChangeBroadcaster//, and the //VanillaJuceAudioProcessorEditor// inherit from //juce::ChangeListener//. The processor calls its //sendChangeMessage()// function to notify the editor, which results in a call to the editor's //changeListenerCallback()// function. |
| |
| To understand how parameter changes are propagated in the reverse direction---from GUI to synthesizer---we need the following overview some of the objects which make up the DSP aspect of VanillaJuce. |
| |
===== The "Synth" objects ===== | ===== The "Synth" objects ===== |
</code> | </code> |
| |
Just by looking at this, you don't have to delve into the source for class //juce::Synthesiser// to see that its voice-assigning code most likely calls //canPlaySound()// to ensure that a given //juce::SynthesiserVoice//-derived object can actually play the given sound, and if so, calls //startNote()// with the current MIDI note number, key-down velocity and pitch-wheel position, plus a pointer to the sound object. Hence, unless we choose to add a lot of extra member variables to our //SynthVoice// class, the only way our voice objects know what sound to make will be via the //SynthesiserSound* sound// parameter to //startNote()//. | Just by looking at this, you don't have to delve into the source for class //juce::Synthesiser// to see that its voice-assigning code most likely calls //canPlaySound()// to ensure that a given voice can actually play the given sound, and if so, calls //startNote()// with the current MIDI note number, key-down velocity and pitch-wheel position, plus a pointer to the sound object. Hence, unless we choose to add a lot of extra member variables to our //SynthVoice// class, the only way our voice objects know what sound to make will be via the //SynthesiserSound* sound// parameter to //startNote()//. |
| |
| So, here is the VanillaJuce //SynthSound// class declaration: |
| <code cpp> |
| class SynthSound : public SynthesiserSound |
| { |
| private: |
| Synth& synth; |
| |
| public: |
| SynthSound(Synth& ownerSynth); |
| |
| // our sound applies to all notes, all channels |
| bool appliesToNote(int /*midiNoteNumber*/) override { return true; } |
| bool appliesToChannel(int /*midiChannel*/) override { return true; } |
| |
| // pointer to currently-used parameters bundle |
| SynthParameters* pParams; |
| |
| // call to notify owner Synth, that parameters have changed |
| void parameterChanged(); |
| }; |
| </code> |
| Member variable //synth// object is a reference to the //Synth// object (which never changes). |
| //pParams// is a pointer to the currently-selected preset. I've made //pParams// public so the //VanillaJuceAudioProcessor// object (which creates and "owns" the one //SynthSound// object) can change it whenever a different preset is selected, so it points to the appropriate entry in the //programBank// array. |
| |
| All the //Gui...// class constructors take a //SynthSound*// argument, so they can use the //pParams// member to access the current parameter values, in order to display and modify them. Furthermore, whenever any part of the GUI changes a parameter value, it calls the //parameterChanged()// function, which is just this: |
| <code cpp> |
| void SynthSound::parameterChanged() |
| { |
| synth.soundParameterChanged(); |
| } |
| </code> |
| //Synth::soundParameterChanged()// simply iterates over all active (currently-sounding) voices, and calls their //soundParameterChanged()// function. (I looked at the code for //juce::Synthesiser// to see how it handles iterating over all voices.) |
| <code cpp> |
| void Synth::soundParameterChanged() |
| { |
| // Some sound parameter has been changed. Notify all active voices. |
| const ScopedLock sl(lock); |
| |
| for (int i = 0; i < voices.size(); ++i) |
| { |
| SynthVoice* const voice = dynamic_cast<SynthVoice*>(voices.getUnchecked(i)); |
| if (voice->isVoiceActive()) |
| voice->soundParameterChanged(); |
| } |
| } |
| </code> |
| The code for //SynthVoice::soundParameterChanged()// is not so trivial, but all it really does is re-initialize the currently sounding note so that the sound changes to reflect whatever was changed in the GUI. |
| |