New AudioProcessorParameter class


#1

Hello folks

I've been experimenting with some better parameter-handling classes for audio plugins, and have just pushed some new code with an AudioProcessorParameter class.

There's more work to follow, including some ValueTree-based state and parameter management utility classes that build on top of this, but wanted to get things started with this base class and get some feedback. Essentially it just allows you to create parameter-holding subclasses and add them to an AudioProcessor object.

This shouldn't break any existing code (let me know if it does!), but these new classes should be a more elegant approach to parameter handling than the old set of virtual methods that take a parameter index and force you to write a big switch statement or implement your own parameter classes.


#2

Looks good, Jules!


#3

Thanks Jules, I'll try to use it in the next project and see how it goes.


#4

Thanks for the updates Jules!

I'm having a little trouble figuring out how to implement the abstract AudioProcessorParameter class.

Say I'm making a Delay class. I want a feedback parameter, so within the Delay class I declare a FeedbackParam class and inherit AudioProcessorParameter. I override all the pure virtual methods:


class Delay {
    public:
        Delay();
        ~Delay();
        /*
        more Delay method prototypes here
        */
    private:
        class FeedbackParam : public AudioProcessorParameter {
        public:
            FeedbackParam();
            ~FeedbackParam();
            // override all pure virtual methods
            float getValue() const override { return 0.0f; }
            void setValue(float newValue) override {}
            float getDefaultValue() const override { return 0.0f; }
            String getName(int maximumStringLength) const override { return ""; }
            String getLabel() const override { return ""; }
            float getValueForText(const String& text) const override { return 0.0f; }
    };

        FeedbackParam fb;
        friend class AudioProcessor
};
/*
in cpp file implement all Delay methods
*/

However when I do this I get a bunch of linker errors claiming that the symbols for the non pure-virtual methods for AudioProcessorParameter cannot be found. The defaults for these are implemented in AudioProcessor.cpp, and I'm not required to override these methods, correct?

Cheers

m


#5

Yes, that's true, can't see any obvious mistakes there. Maybe you're not using the latest version of the juce module cpps somehow?


#6

Thats what I thought at first but I've double checked all the files in the project and they're up to date.

I've narrowed down the issue. It looks like if I implement the "Test" class's constructor outside of its class decleration scope the linker errors occur:

​​
#include "JuceHeader.h"
class Test {
public:
public:
    Test(float fSampleRate);
    ~Test() {}
    
private:
    class Param : public AudioProcessorParameter {
    public:
        Param() {}
        ~Param() {}
        
        float getValue() const override { return 0.0f; }
        void setValue(float newValue) override {}
        float getDefaultValue() const override {return 0.0f; }
        String getName(int maximumStringLength) const override {return "";}
        String getLabel() const override {return "";}
        float getValueForText(const String& text) const override {return 0.0f;}
        
        
    };
    Param m_param;
};
// if this is implemented in decleration above, no linker errors occur
Test::Test(float) {}

Yet if I declare it right in the class decleration scope it works fine.


#7

I'm not sure if I'm missing something but how does one update the processor when the value has changed? Suppose I have a parameter that when changed also needs to recalculate some other members in my processor, how should I proceed? Should I maybe do something like the following:

class DelayParam : public AudioProcessorParameter

{

  
public:

     
    float getValue() const          { return currentValue; }

    void setValue (float newValue)  { 
        currentValue = newValue; 
        proc->updateParameters();
    }

private:

    DelayProcessor *proc;    

    float currentValue;
    

};

Or is there some more elegant way to handle that?

 

Also I think it would be very good to have a minimum/maximum value for this parameter class, and maybe support scaling between internal and control ranges. What do you think ?


#8

How to add button (and checkboxes)-like parameters with addParameter() ? By default AudioProcessorParameter class has setValue(), getValue() etc. that returns *float* values, those type would requires bool return types.


#9

Look at the FloatParameter class in the demo plugin. You'll need to make a class like that (for example you can call it BooleanParameter). Ultimately, you will need to map any parameter type to floating point values between zero and one as this is used internally by all DAWs. For example, the setValue and getValue of your class might read:

class BooleanParameter : public AudioProcessorParameter
{
private:
    bool boolValue;

public:
  . // add other code (constructor, destructor, ...) here
  .
  .

  float getValue() const override
  {
     return boolValue ? 1.0f : 0.0f;
  }

  void setValue (float newValue) override
  {
     if (newValue != 0.0f)
        boolValue = true;
     else
        boolValue = false;
  }
  .
  .  // other code goes here
  .
  .  
};

I hope this gets you started!

 

 


#10

I think this is not good practice,  the value should also be stored internally as float value, to be consistent with host automation!!! 

(if the host request the value again!!! Host sets 0.4 -> gets 1.0 )

Instead there should be a getBoolean/setBoolean() function.

Also if a parameter has two states, i also recommend to have 0. -- 0.499.. is the first state && 0.5-1.0 the second state 

(This is not c interface, the parameter is a human readable data source, which can be manipulated with various user-interfaces controls, like automation curves etc, in your implementation parameter 0.0000000001  behaves like 1.0)

 

 


#11

Thanks Fabian, 

by the way in the DAW editor it's shown as a slider... would be interested to show it as a checbox instead. Maybe a little bit modification in AU_Wrapper is needed or am I missing something? 

Thank you


#12

BTW: I think the parameter-storing should be part of the juce-implementation, only the translation to should be part of the individual plugin-programming, (would break current implentation)


#13

This looks interesting. How can I get a notification when the parameter value (or an AudioParameterInt's range) is changed? It would be useful for linking Sliders to parameters.

I can think of two solutions:

1. Give the Parameter a Value (or ValueSource) object. The Slider could then refer to that object. Disadvantage: No notification when the range is changed.

2. Make an AudioProcessorParameter::Listener class to inherit from, with methods: parameterValueChanged and parameterRangeChanged

I prefer (2), because I think it could be more type-safe than using dynamic Values. But let me know what you think!


#14

Hey all, 

 

I am using a few audio processor parameter classes and I think I have found a problem or am not understanding something. I noticed this with my AudioProcessorParameterInt however looking at the code I think this exists in all types.

 

In the operator= function for each parameter this block of code exists:


const float normalisedValue = range.convertTo0to1 (newValue);
   
if (value != normalisedValue)
    setValueNotifyingHost (normalisedValue);

This makes sense as the incoming value gets made into a 0 to 1 float and compared against the value member which is (supposedly) a 0 to 1 float. However, if you look where the value variable is set:

setValue (float newValue)                      { value = range.convertFrom0to1 (newValue); }

so the value variable is actually converted from a 0 to 1 into whatever the object is. This is still fine because we are using setvaluenotifyinghost however it does render the != operator meaningless (because value is not 0 to 1 and normalised value is 0 to 1). This is very aparent in the AudioParameterInt class when you try to change from 1 to the maximum value, it will not change because in that case, value == normalisedValue. An easy fix for this is to change the operator= function to this:


const float normalisedValue = range.convertTo0to1 (newValue);

if (range.convert0to1(value) != normalisedValue)
    setValueNotifyingHost (normalisedValue);

or compare value and newValue.