If you’re using APVTS, having lambdas to update parameters from the UI to processBlock is not a good idea. UI elements update on the message thread, and automation comes from any thread. If you receive automation through the UI, you’ll get it late. Also, you may not have a UI at all (for example, when the editor is closed). You can’t count on the UI to do anything for the audio process -solve the audio process as UI-less, and plug the UI into it. In any case, you can’t update from one thread to the other without synchronization (at the very least, atomics).
There’s only one parameterChanged callback. You register for each parameter individually (probably with a loop in the processor constructor), but they all go to the same callback, which gets a parameter ID and a value. Without the need for synchronization, you could call your individual updateParameter() functions from there, or do it all there. But you do need synchronization.
If you go the fifo queue route, you need an MPSC one (because parameterChanged can be called from more than one thread). You need a key-value type like
struct Message { IdType id; float value; }
Then as a member of your processor
FifoType<Message> messages;
In parameterChanged
messages.push ({ id, value });
Then once per call in processBlock
Message msg;
while (messages.pop (msg)) {
if (msg.id == param1id) // update your audio-thread values here
else if (msg.id == param2id) // ...
// ...etc
}
You can use juce::Identifier as IdType. Ideally, param1,2…id should be juce::Identifier constants stored somewhere, to avoid Identifier constructions, which are expensive. Another option (which I use) is to map (with std::map) String ids to an enum, then use the enum as IdType. This allows to replace the if-else-if chain with a switch.
(edit) To clarify -if you’re using parameter values directly, there’s no advantage in having another set of atomics updated from listener callbacks -you’re replicating APVTS. In that case, just use getRawParameterValue. If you need to adapt parameter values for the process and it’s too costly to do it again for every block, that’s where you need a queue. For example, I have a set of frequencies, from which I need to compute the integral of the transfer function of a whole filter graph as a sum on 16384 points. Obviously I can’t do that again for every block