AudioProcessorValueTreeState::Listener & MessageManager

using an AudioProcessorValueTreeState::Listener to do some fairly large recalculation of stored variables on my audio processor, would it be possible to get around the limitation of the parameterChanged() callback (where the slider is not guaranteed to be updated within the function call), by using a messagemanager to schedule a call to do the recalculation after the function executes?

void ProbCurvAudioProcessor::parameterChanged(const String& parameterID, float newValue)
{
    if (parameterID.substring(0, 7) == "ceSlider") {
        MessageManager* mm = MessageManager::getInstance();
        mm->callAsync([&] {sumSliders(); });
        mm->callAsync([&] {refillPulsebank(); });
    }
}

void ProbCurvAudioProcessor::sumSliders()
{
    float sum = 0;
    for (int i = 0; i < *state.getRawParameterValue("numSliders"); i++) {
        sum += *sliderValues[i];
    }
    sliderssum = sum;
}

I ONLY want to do this recalculation when this parameter is updated because it’s an expensive operation and the parameter updates infrequently on user input - in this instance, is sumSliders() guaranteed to be able to access the updated value of the slider? It would be a monumental headache to forward newValue and the index of the slidervalue it would replace to all the functions that perform this calculation, is there a better way of doing this that doesn’t involve that?

Would you mind briefly explaining the overall structure of your plugin?

Just from looking at the code I wonder in what kind of situation I’d need a parameter "numSliders" e.g. a parameter that holds some information about the count of some GUI widgets – this seems like some kind of an abuse of what a parameter is designed for. And what is sliderValues – why don’t you expect it to be valid during the parameterChanged callback (if I got you right)? And in which cases should if (parameterID.substring(0, 7) == "ceSlider") be true? To be honest all this looks a bit suspicious to me, but this is all I can say without the context. I guess, some more information on what you are actually trying to achieve would help understanding your code snippet and help judging if your solution is a good choice.

Side note: I would never call mm->callAsync from within the parameterChanged callback. At least for VST3 this can be called from within the processing thread if there is automation and callAsync allocates under the hood. Furthermore, creating lambdas can allocate as well under some circumstances.

1 Like

numSliders is odd but I believe I’m not violating any major design principles here. I’m representing a probability curve as a series of discrete points, settable via sliders. The “numSliders” variable keeps track of how many discrete points a user can edit. (the max number of sliders always exist from construction of the editor, numSliders sets how many are currently drawn using setVisible)
Here is an old gif of the interface that shows this. Resizing works a little more nicely now but this shows the principle
sliderresize

sliderValues is just an array of pointers to the float value of each of these sliders, assigned using getRawParameterValue during construction. You could accomplish the same thing by calling and dereferencing the pointer returned by getRawParameterValue every time, it’s a style thing.

All my sliders have hashIDs generated from their index in the slider ownedarray, so the hashID of the first slider is “ceSlider0”, “ceSlider1”, and so on.

My understanding is that, within the callback function, it’s not guaranteed that a call to get the slider value would return the updated value, without using the newValue parameter passed in to the callback function. I’d rather not use this construction because forwarding newValue to all the relevant calculations and figuring out what to replace would be a massive headache. I need to keep the sum (among other things) updated when the slider values change, but I don’t want to recalculate it for every buffer because that seems crazy inefficient. That’s why my idea was to schedule a call to do these calculations from the callback function - it would let me trigger the code infrequently, but still get all the updated values and ensure correctness. But it feels awful, hence why I’m posting here haha

image
the code where sliderValues is created in the audio processor constructor

1 Like

The wrapper will update the parameters before calling processBlock() on the audio thread.
They are sent synchronous on the thread they arrive.
Inside the parameterChanged() callback it is NOT guaranteed, that OTHER parameters are already updated, which is mentioned in the docs. The parameter that you are notified about is updated though.
Doing anything asynchronous here is a bad idea, consider bouncing offline, when you advance very fast, while an asynchronous update is bound to arrive late.

getRawParameterValue (paramID) is a map lookup with a string hash value. It is fast, but still best avoided to be called too often.
It is better to store the returned std::atomic<float>* pointer in a member variable and you can read it with no problem. You can also put the pointers into a vector or even an std::array. But remember, the number of parameters cannot change during runtime.

Another word about parameterChanged(), it arrives most of the time from the audio thread, but it can just as well arrive from the gui thread. It is a bit troublesome.

1 Like

the std::atomic* variable you mentioned is what sliderValues is for, I just have an vector full of them, one for each slider.

is there ANY way to trigger recalculation such that it isn’t happening during every single process block? I guess if this is efficient enough to run in a single processblock it should be efficient enough to run in EVERY processblock, but…

Like, in the case where i had two separate calculations that relied on each other. i could hypothetically get them such that i wouldn’t drop a buffer if i recalculated one, but i would drop a buffer if i recalculated both in the same buffer. Is there no better way to schedule these? I don’t care about asynchronicity (I would prefer they not be asynchronous as a best practice), but what other options are there?

I’m not sure if I get your case -I guess you need this recalculation to be done on the audio thread because things must stay coherent for each processing callback, but you were using callAsync which is kind of opposite. For a simple case, you could set an atomic flag in parameterChanged, and check it in processBlock. In my case, where I have this kind of thing everywhere between many parameters, I keep a parallel structure updated in processBlock, and use a ring buffer as intermediary.

1 Like

Your flag idea makes a lot of sense, and is probably what I’ll end up implementing.

I guess this is just me being new to JUCE: the tutorial projects I began learning from have been referring to a “processor” and an “editor” thread, and I think I internalized this “message” thread referred to in the docs as being the same as the “processor” thread for some reason. It sounds like you’re saying that’s not the case - is there a good resource for reading up on JUCE’s threading paradigm, that you know of?

It’s a bit confusing if you’ve not dealt with it before. Processor and editor are objects -they’re storage entities, not execution entities like processes and threads. The message thread runs the message / main event loop. UI events run there, but also other stuff, like AudioProcessorValueTreeState flushes, AudioProcessor::prepareToPlay (typically), Timers (but not HighResolutionTimers), AsyncUpdaters, etc. The audio thread is where AudioProcessor::processBlock runs, and has a higher priority. If you block the audio thread, you may get dropouts. If you block the message thread, you may get an unresponsive UI. Ideally the audio thread should do what’s strictly necessary for a realtime output. Other things can be moved to the message thread if they’re reasonably light. For heavy asynchronous stuff, like loading big files, you may need separate threads. There’s a certain correlation between AudioProcessor and the audio thread, and AudioProcessorEditor and the message thread, but it’s just conceptual.