Should I use SliderListener for component updates and ValueTree Listener for audio thread updates?

I’m new to the idea of managing threads and I found that I get errors when I update my components using the value tree parameter listener. So should I use SliderListener for updating/repainting components(UI thread) and value tree listener for updates in the audio thread? Or is there a better way to do that?

Btw the slider and the parameters are attached using sliderAttachments, the reason I can’t just do a param.get() to update my components is because I don’t want to keep repainting, I need to use a listener to repaint only when a param/slider is changed.

Update: Sorry I wasn’t really clear in my post originally, but I’m actually trying to repaint a custom component to display my wavetable. I understand how the attachments work, I just need a way to repaint my wavetableDisplay component only when a slider value is changed. I originally tried inheriting the ValueTree listener class on my wavetableDisplay component, but it gives errors sometimes since I’m accessing the the audiothread from the UI thread. So I was wondering is there any other way to do this?
I just tried using Slider::Listener but the problem is the slider is part of the wavetableDisplay’s parent component, so I can’t call the slider’s addListener function inside wavetableDisplay.

Not sure, where you actually struggle, the SliderAttachment is a listener to the parameter and will repaint your slider only once the value has changed. There is no need for a continuous repaint when using attachments.

I think there is something else going on that the poster doesn’t make clear. I make a guess there’s some custom component involved for controlling/displaying the parameters.

Sorry I wasn’t really clear in my post, but I’m actually trying to repaint a custom component to display my wavetable. I understand how the attachments work, I just need a way to repaint my wavetableDisplay component only when a slider value is changed. I originally tried inheriting the ValueTree listener class on my wavetableDisplay component, but it gives errors sometimes since I’m accessing the the audiothread from the UI thread. So I was wondering is there any other way to do this?
I just tried using Slider::Listener but the problem is the slider is part of the wavetableDisplay’s parent component, so I can’t call the slider’s addListener function inside wavetableDisplay.

If you want to do it that way (not necessarily the best approach, though), you can pass a pointer or reference of the main GUI editor or the Sliders involved into the wavetable display component. Alternatively you can still make your wavetable component be a Slider::Listener, but do the listener adding from the main component instead of the wavetable componene itself. However, if you are already having thread safety issues, none of this is likely going to help anyway.

The problem with using custom components with the AudioProcessorValueTreeState is that it isn’t officially supported. You just need to figure out some approach that works.

1 Like

There is nothing wrong making the ProcessorEditor a parameterListener and listen additionally to the audioProcessorValueTree, and call repaint the wavetable component in that callback.

Thanks! I added the listener through the parent component and it worked! However, that came with new errors…
I’m guessing since the UI thread and audio thread are separate, the slider and parameter aren’t perfectly in sync.(the slider is the wavetable position btw)
So when I change the sliderValue fast from let’s say 256 to 0, the wave it fetches from the audio thread is not the exact wave selected at the current moment. For example if wave index 0 is square wave, wave index 1 is sinewave, and wave index 256 is noise, when I turn the slider all the way down to 0, it will fetch the sinewave instead of the square wave. Is there any way to make sure the part of “fetching the wavetable” is in sync with sliderValueChanged()?

That sounds dodgy. The parameter’s thread safety is governed for by the AudioProcessorValueTreeState. But your component and the player should both only read from the wave-table, in which case it is ok to read in parallel at any time.

I guess, your issue is more with your data model than with the parameters…

1 Like

I came up with a solution, I made my wavetableDisplay component both a slider listener and a parameter listener. When the slider changes the component repaints and when the attached parameter changes the component fetches the current wave. Idk how efficient that is but it works :slight_smile:

Sounds good. The Slider Listener shouldn’t be necessary, since changing the slider should always result in a callback from the parameter, unless an automation overrides it (set to “read”). But it doesn’t hurt probably…

1 Like

Hmmm, for some reason when I call repaint() in the parameterChanged function I sometimes get the “JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED” error in the Component::internalRepaintUnchecked function. That’s why I’m inheriting the sliderListener for the repaint and parameterListener for fetching the wave. But do you know why that manager is locked error happens when I try to repaint in parameterChanged and what does it mean?

Sorry, I didn’t consider, on which thread the parameterChanged messages occur, seems to be not the message thread then.

Every method on a component, that accesses the bounds or other information, that may be shared with the OS needs to happen on the message thread to be thread safe with the OS. For that there is the MessageManagerLock. Most times it is automatically acquired when JUCE calls e.g. paint(), resized() etc. To be sure, it is actually called from the message thread, there is the JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED that you mention.

If you look at the comment just above the assert, there is the explanation (JUCE does that for every assert, that they put an explanation next to it!):

// if component methods are being called from threads other than the message
// thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED

So there are three options:

  1. setting a MessageManagerLock l; before the repaint() to acquire a lock.
    BUT DON’T do that, it means holding up the execution (which is likely to be the audio thread, since it is not the message thread), until the repaint was scheduled (which is waiting for the MessageManager queue)

  2. Issue the repaint as next call on the MessageManager’s queue:
    MessageManager::callAsync ([ptr = Component::SafePointer<MyComponent>(compToRepaint)] { if(ptr != nullptr) ptr->repaint(); });
    This solution is probably good enough, but callAsync is actually discouraged on the audio thread, since it can allocate a new message, which is a couple of bytes only, but it still allocates.

  3. Make your editor an AsyncUpdater, from the parameterChanged call triggerUpdate() and override the handleAsyncUpdate() to call repaint on your component.
    This is the best solution for that kind of problems.

2 Likes

Thank you so much for the help! Everything works now :slight_smile: