AudioProcessorParameterListener


#1

Now that AudioProcessorParameter exists, it would be nice, useful, and clean to have a AudioProcessorParameterListener class.

Something like this:

class AudioProcessorParameterListener
{
public:
    virtual ~AudioProcessorParameterListener() {};
    virtual void audioProcessorParameterChanged (AudioProcessorParameter* parameter, float newValue) = 0;
};

AudioProcessorParameter.h:

class JUCE_API  AudioProcessorParameter
{
...
public:
    virtual void setValue (float newValue); // has a default implementation, not = 0
    virtual void addListener (AudioProcessorParameterListener* newListener);
    virtual void removeListener (AudioProcessorParameterListener* listenerToRemove);
protected:
    void sendParamChangeMessageToListeners (float newValue);
private:
    Array<AudioProcessorParameterListener*> listeners;
...
};

AudioProcessorParameter.cpp:

void AudioProcessorParameter::addListener (AudioProcessorParameterListener* const newListener)
{
    const ScopedLock sl (listenerLock);
    listeners.addIfNotAlreadyThere (newListener);
}

void AudioProcessorParameter::removeListener (AudioProcessorParameterListener* const listenerToRemove)
{
    const ScopedLock sl (listenerLock);
    listeners.removeFirstMatchingValue (listenerToRemove);
}

AudioProcessorParameterListener* AudioProcessorParameter::getListenerLocked (const int index) const noexcept
{
    const ScopedLock sl (listenerLock);
    return listeners [index];
}

void AudioProcessorParameter::sendParamChangeMessageToListeners (const float newValue)
{
    for (int i = listeners.size(); --i >= 0;)
        if (AudioProcessorParameterListener* l = getListenerLocked (i))
            l->audioProcessorParameterChanged (this, newValue);
}

void AudioProcessorParameter::setValue (float newValue)
{
    sendParamChangeMessageToListeners (newValue);
}

Cheers, Yair


#2

Thanks for the suggestion, but I was going to start pushing in a different direction with the way I'd recommend using parameters. In the plugins we're building internally we're now using lambdas a lot, and finding that to be a good pattern. I'll be publishing some helper classes based on that very soon.

Also, there's no reason for this to go into the base class. The same thing would work perfectly well as a subclass, which would be a better solution since it wouldn't bloat everyone's parameter objects with unused arrays. Since few people would use this, and many plugins have thousands of parameters, putting it in the base class would probably be a bad move!


#3

+1 lambdas, which can be used instead for any kind of listeners. Would like to see some example code :) The whole listener architecture makes the code untidy. Object initialization/characterization and defining callback-procedures should be on the same place.


#4

+ some thing like a look-free call queue, which should be synchronized in the audio-thread (and if no audio callback happens on a fallback-thread) and some thread safe data-synchronization technics (like this https://github.com/bazrush/juce-toys) should be part of juce, because its a recurrent design pattern in audio-software   

something like:

AudioProcessor::CallFunctionOnAudioThread(<Lambda>) would be great

 

 


#5

Some context for this suggestion. I'd later like to have something like this "ParamToggleLink":

class ParamToggleLink : public AudioProcessorParameter::Listener, public Button::Listener
{
public:
    ParamToggleLink();
    ParamToggleLink (AudioProcessorParameter*, Button*);
    void link (AudioProcessorParameter*, Button*);

private:
    virtual void audioProcessorParameterChanged (AudioProcessorParameter*, float) override;
    virtual void buttonClicked (Button*) override;
    virtual void buttonStateChanged (Button*) override;

    AudioProcessorParameter* parameter;
    Button* button;
};

This gets initialized in the plugin editor's initializiation, and replaces the editor being a button listener itself and then comparing to the different buttons in its callback, as well as maintaining their state via either a timerCallback such as in JUCE's example plugin or via being a listener for the AudioProcessor with a big switch statement for all the parameters in its callback.

Similarly I'd like to have something of this sort for Sliders. Except afaik there I need to subclass juce::Slider in order to implement its getTextFromValue and getValueFromText methods to call the parameter's methods.

Cheers, Yair


#7

@jules , fast-forward to the future
It seems your “hints” became a reality…

https://juce.com/doc/structAudioProcessorValueTreeState_1_1Listener

I’ve stumbled some of those threads while trying to do some black-magic with evil AudioProcessorListener.

AudioProcessorValueTreeState::Listener seems to be great solution for handling parameter changes between many elements (host, your UI, processor).


#8

Do I understand correctly that something exactly like this was added in JUCE? Is this suggestion no longer a bad move?