Control everything feat. AudioProcessorValueTreeState

I want to use the AudioProcessorParameters and AudioProcessorValueTreeState for Processors, that are not AudioProcessors.

For that a base class would be needed, similar to:

class JUCE_API  ControllableProcessorBase
{
public:
    ControllableProcessorBase() = default;

    virtual ~ControllableProcessorBase() = default;

    virtual void addParameter (AudioProcessorParameter*) = 0;
    
    virtual void addParameterGroup (std::unique_ptr<AudioProcessorParameterGroup>) = 0;
    
    virtual const AudioProcessorParameterGroup& getParameterTree() = 0;
    
    virtual const OwnedArray<AudioProcessorParameter>& getParameters() const noexcept = 0;

    virtual void addListener (AudioProcessorListener*) = 0;
    virtual void removeListener (AudioProcessorListener*) = 0;
};

and the AudioProcessorParameter needs to refer to the ControllableProcessorBase instead of an AudioProcessor. There are more but solvable threads to pursue into the various classes.

One thing is GenericAudioProcessorEditor uses the LegacyParameters. I would rewrite that to use the current interface and as a side benefit to support ParameterGroups.

A few design decisions:

  • I already wrote a patch to template AudioProcessorValueTreeState as
    template<class ProcessorType> ProcessorValueTreeState<ProcessorType> and
    using AudioProcessorValueTreeState = ProcessorValueTreeState<AudioProcessor>;
  • Alternatively we can add the ControllableProcessorBase as described above
  • if we do that, should we move all parameter handling and ownership to the base class or use it only as an interface?
  • The AudioProcessorListener needs some adaption, is this still used? And if so, should we change the signature to send a ControllableProcessorBase* pointer?

It would be great to open the possibilities of JUCE towards additional pipelines.

Just adding the pull request for this feature:

Hope it fall “on benevolent eyes”, let me know, if it needs changes…

In order to control other Processors in a processing pipeline, a separate interface is needed, so the AudioProcessorParameters, the AudioProcessorValueTreeState and the GenericAudioProcessorEditor can be used. A use case is e.g. a VideoProcessor in a NLE.

This PR adds this base ControllableProcessorBase and moves all addParameter etc. functionality into this base.
The AudioProcessorParameter was changed to use the interface of ControllableProcessorBase instead of AudioProcessor.
The GenericAudioProcessorEditor uses the LegacyAudioParameter and the AudioProcessorListener, so it remains unchanged (but intact of course). A new GenericProcessorEditor will be added later, that will only use the new interface of AudioProcessorParameter and AudioParameterGroup as well.

If wished, the names AudioProcessorParameter and AudioProcessorValueTreeState could be changed for consistency to ProcessorParameter and ProcessorValueTreeState and an alias provided with the old names.

Disclaimer: all copyright of this PR is hereby waived to JUCE and its legal owners.

1 Like

I don’t think ControllableProcessorBase is the way to go here.

It certainly achieves what you want, but adding more complexity to an already overburdened class wouldn’t be an overall improvement to the API. The default response to this objection is something along the lines of ‘But this takes functionality out of AudioProcessor!’, which is true, but at the cost of additional layers of inheritance all over the place. You’ve managed to take the “Audio” out of “AudioProcessor”, but for consistency we’d need to do the same for AudioProcessorParameter, AudioProcessorParameterGroup, AudioProcessorListener, AudioProcessorEditor and maybe a few others that I’ve missed. Using inheritance like you’ve done is the only way I can see to do this without breaking everyone’s code (AudioProcessor is probably the root of most JUCE apps), but the complexity overhead is just too great. Renaming and aliasing doesn’t provide an easy answer here - an example would be that AudioProcessorParameter can have a category of compressorLimiterGainReductionMeter which isn’t generic enough for a ProcessorParameter class.

The other approach to this is thinking about how to make the AudioProcessorValueTreeState and the various ::*Attachment classes more generic. If these sat on top of a more general wrapper around a ValueTree and a ParameterGroup then you could perhaps dispense with the AudioProcessor class entirely and use your own structure created using composition. You’re still likely to run into naming problems, with “Audio” sprinkled everywhere` but it would, perhaps, be a much less invasive set of changes.

1 Like

Thank you for your feedback. I am aware, that not many people will need any processor other than AudioProcessor, that’s why I was hesitant to do the refactoring in the first place. But one day, people want to control other processors as well, and I think my video engine is not very far fetched, regardless who does it…

My first approach was indeed to template the AudioProcessorValueTreeState. I’ll attach the patch here.
patch_template_APVTS.txt (35.9 KB)

Otherwise I will have to create my own parameter classes just to achieve the very same thing, which would be very sad…

BTW. I don’t mind doing the extra work of removing the Audio from the Parameter classes and adding using-aliases…

It’s not as simple as using aliases; the classes I mentioned above have built-in audio-ness that make them unsuitable for generic use, like a category of compressorLimiterGainReductionMeter.

I agree that it would be lovely to have all the parameter controls separate from anything audio related, but I can’t see a way of doing this without making a huge mess.

Soul is a perfect fit for this though…

does it do video? :wink:

And what does that mean for JUCE? Will the AudioProcessor and things be deprecated by Soul? I didn’t think so…

Ok, I see, if I can come up with a polymorphic adapter to wrap around the different processors, and create my own four parameter classes… will be another good days work sigh

I worked around this now by writing a polymorphic adapter class and re-implementing my own ProcessorParameter and ProcessorParameterValueTreeState (but with a shorter name)…

Do you want to keep the PR for future reference or shall I delete it?

I think it can go.

Whilst it’s certainly a decent chunk of boilerplate, if we did decide to go that route then we would take the same approach and could get ourselves to the same endpoint without too much trouble. Without a significant shift in direction the PR will just sit there forever.

The JUCE team certainly appreciates your efforts, but this one is not going to make it into the API.

Alright, that’s what I thought.

No worries, was worth a shot, and I learned some things while doing it… :slight_smile: