getRawParameterValue()

https://www.juce.com/doc/tutorial_audio_processor_value_tree_state

In this example:

  • Why is getRawParameterValue() called in processBlock - isn’t it better and ok to get the pointers somewhere that only gets called once (e.g. during construction of the Audio Processor)? I note that it does a linear search through the parameters every time this is called.
  • Are the raw values thread-safe? I’m guessing ‘sort of’ ? :slight_smile:

Each time you have a call to PRocessBlock, the parameters may have changed.
In other frameworks, you would have a setValue() call of some sorts, so you wouldn’t have to check each time in the processBlock function these values.
I think this is the main drawback of JUCE at the moment (with the fact that you don’t get a parameter hierarchy).

I’m dealing with this right now.

In addition to the setValue() method there’s the (soon to be deprecated) parameterChanged() method in AudioProcesssor, where each plugin parameter is assigned an index and you can use a giant switch statement to set internal variables whenever a parameter is called.

One thing that I’m working on now is to derive a class from AudioProcessorParameter and give it a callback in the constructor. For example something like this

CustomParameter : public AudioProcessorParameter
{
public:
    CustomParameter (std::function <void(void)> callback)
    :    valueChanged (callback)
    { 
    // rest of constructor
    }
    void setValue (float newValue) override
    {
        value.set (newValue);
        valueChanged();
    }
    float getValue() const override
    {
        return value.get();
    }
    // .... implementation of other virtual methods etc etc

private:
    std::function <void (void)> valueChanged;
    Atomic <float> rawValue;
};

Then say we have a gain plugin, we can make the parameter class (which acts as a wrapper for an atomic variable) talk to an underlying variable in the plugin processor class by initializing the callback with a lambda expression when the parameter is constructed.

class GainPlugin : public AudioProcessor
{
public:
    GainPlugin ()
    : /* yada yada yada */        
    {
        //initialize the callback with a lambda expression
        addParameter (gainParam = new CustomParameter ([this] () -> void { this->gainVal = this->gainParam->getValue(); }) 
    }
    //..... etc etc
    void processBlock (AudioSampleBuffer& buffer, MidiBuffer&) override
    {
        for (int channel = 0; channel < buffer.getNumChannels())
        {
            float* channelData = buffer.getWritePointer (channel);

             for (int sample = 0; sample < buffer.getNumSamples())
                 channelData[sample] = gain * channelData[sample];
        }
    }
private:
    ScopedPointer<CustomParameter> gainParameter;
    float gain;
};

I will say that I haven’t tested this thoroughly, but I needed to roll my own parameter class anyway and this made things easier. Hope this helps or gives you other ideas, I personally think the parameter classes are a little too dense and too OOP-y. I would much rather keep the parameterChanged() method around.

You don’t have to check all the parameters during every processBlock. Just add a parameter listener any/all of your parameters and then deal with whatever changed in the parameterChanged() handler.

You’ll probably end up with something like this:

void YourPlugin::parameterChanged(const String& parmID, float newValue)
{
    if(parmID == kGlide)
    {
        // do whatever
    }
    else if(parmID == kOverdrive)
    {
        // whatever
    }
}

I don’t know why the team didn’t make the paramID an int to make it an easy switch statement, but there are ways around that.

An unordered_map with callbacks/lambdas :stuck_out_tongue:
The listener idea is nice, indeed better than what we have. I assume this is https://www.juce.com/doc/classAudioProcessorListener?
Thanks!

Unfortunately this has caveats, see this thread:

The best way I found was using the SliderAttachments in AudioProcessorValueTreeState etc.
And if you like that pattern, I have the same done for the “normal” ValueTree, see here:

2 Likes

Thanks…
This is getting hairier and hairier, and no tutorial to explain everything :confused:

Well, there is a tutorial - I’m just becoming suspicious of it :slight_smile:

https://www.juce.com/doc/tutorial_audio_processor_value_tree_state

me too… It’s only for really simple stuff, and as soon as you start working with state (saving the plugin state, restoring it, changing presets), all hell breaks loose.

I don’t believe so. I just recently switched to using it in a very-close-to-release beta plugin with 76 parameters and it’s working very well…with some added parameter wrapper classes for utility functions.

My getStateInformation() looks like this:

ScopedPointer<XmlElement> allXml = new XmlElement(kEntireStateTag);
XmlElement* parmsXml(parms.state.createXml());
XmlElement* globalsXml(globals.createXml());   //instrument options
allXml->addChildElement(parmsXml);
allXml->addChildElement(globalsXml);
copyXmlToBinary (*allXml, destData);

And my setStateInformation() looks like this (minus some error checking):

ScopedPointer<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
XmlElement* parmsXml = xmlState->getChildByName(kParmsStateTag);
XmlElement* globalsXml = xmlState->getChildByName(kGlobalsStateTag);
auto state = ValueTree::fromXml(*parmsXml);
parms.state = state;

And thanks to the Button & Slider attachments, the UI is always in sync. I did have to roll my own class for a MultiButton Attachment, but I basically modified the SliderAttachment code and made it work for an array of Buttons.

Honestly, having switched to the ValueTree parameters and the attachment class simplified my UI code tremendously.Everything stays in sync easily.

But there could definitely be some more example code for this as well as some more robustness to the AudioProcessorValueTreeState class and it’s parameters.

The base class for the attachments should be exposed in the header file really - i had to do two custom attachment jobs…

3 Likes

I had a binary state befre that I wanted to keep. But this worked, it’s the preset that didn’t. Now, I will try reading an XML again…
I still have the huge parameter mess. I’m not sure checking dozens of parametes in a ValueTree is really efficient!

Well, you’re really only checking the one that changed. Granted, you end up with a ton of if statements on a String. I don’t know why they didn’t make the paramID an int. In my wrapper class, I have an int ID and use that for a switch statement.

Indeed, tbut that’s when I have the ValueTree Listener.
The “harder” part was that you don’t have all the information in the tutorial, you need to dig here and there and there is no official proper pattern to solve these simple issues.

1 Like

Yes, the JUCE tutorial situation could use some attention.

My quick’n’dirty solution was to add getRawValue function to AudioProcessorParameterWithID:

virtual float getRawValue() const { jassert(0); return 0; }

jassert just to prevent usage directly…

and to AudioProcessorValueTreeState::Parameter:

float getRawValue() const override { return value; }

But seriously: No string searches in processBlock!

  • Jussi
1 Like

…there is also jassertfalse;

Hi!

I’m new to JUCE and wonder if reading raw parameter values is possible in a fast way without always calling mytree.getRawParameterValue everytime. (costs much in processBlock())

I’d like to get back to this point of jimc’ original question:

I didn’t find any answer to this part of the question. (hope, i didn’t oversee it).
However I’ve tried exactly doing this in the constructor of my processor:

mytree.createAndAddParameter("filterCutoff", "Filter Cutoff", "filter Cutoff", filterCutoffRange, 400.0f, nullptr, nullptr);
valueFilterCutoff = tree.getRawParameterValue("filterCutoff");

(valueFilterCutoff is a float* which is then derefenced to read the raw value later)

It seems to work perfectly, but is there something, that I have to keep in mind? Can the pointers change suddenly somehow?

I don’t think the pointers will change.

Aah, actually I did oversee the point in the tutorial. Sorry.
But I’m happy to know, that it works this way. :slight_smile:
Thanks!