Hi all,
I am trying to create an attachment that keeps a property in a juce::ValueTree and a juce::RangedAudioParameter in sync. But i’m hitting the following assertion:
#if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING
// This means you've called beginChangeGesture twice in succession without
// a matching call to endChangeGesture. That might be fine in most hosts,
// but it would be better to avoid doing it.
jassert (! isPerformingGesture);
isPerformingGesture = true;
#endif
Below is the PropertyAttachment class i made.
#pragma once
#include <JuceHeader.h>
class PropertyAttachment : private juce::ValueTree::Listener {
public:
PropertyAttachment(juce::RangedAudioParameter& param, juce::CachedValue<float>& p)
: attachment(param, [this](float v) { setValue(v); })
, property(p)
{
attachment.sendInitialUpdate();
property.getValueTree().addListener(this);
}
~PropertyAttachment()
{
property.getValueTree().removeListener(this);
}
private:
void valueTreePropertyChanged(juce::ValueTree& tree, const juce::Identifier& ID) override
{
if (property.getPropertyID() == ID)
propertyChanged();
}
void propertyChanged()
{
if (! ignoreCallbacks)
attachment.setValueAsCompleteGesture(property.get());
}
void setValue(float value)
{
juce::ScopedValueSetter<bool> svs(ignoreCallbacks, true);
property.setValue(value, nullptr);
}
juce::ParameterAttachment attachment;
juce::CachedValue<float>& property;
bool ignoreCallbacks { false };
};
Here is what is triggering the assertion:
When setValue() is called (due to dragging a slider which is attached to the parameter), I set ignoreCallbacks = true using ScopedValueSetter before calling property.setValue(...). This should suppress the call to attachment.setValueAsCompleteGesture() that comes from the ValueTree notifying its listeners.
But during debugging, I noticed that propertyChanged() still gets called with ignoreCallbacks == false, which, i think, shouldn’t be possible. This leads to the assertion when attachment.setValueAsCompleteGesture() tries to call beginChangeGesture() a second time (the first time is called when the slider started dragging).
Below is the call stack. It looks like ignoreCallbacks is already false when the listener is being invoked, even though ScopedValueSetter should still be in scope.
My question: Is it possible that the listener callback is being triggered after ScopedValueSetter goes out of scope, even though everything appears synchronous?
Or is there something else I’m missing about how ValueTree notifies its listeners?
juce::AudioProcessorParameter::beginChangeGesture() juce_AudioProcessor.cpp:1549
juce::ParameterAttachment::beginGesture() juce_ParameterAttachments.cpp:74
`juce::ParameterAttachment::setValueAsCompleteGesture'::`2'::<lambda_1>::operator()(float) juce_ParameterAttachments.cpp:63
juce::ParameterAttachment::callIfParameterValueChanged<`juce::ParameterAttachment::setValueAsCompleteGesture'::`2'::<lambda_1> >(float,<lambda_1> &&) juce_ParameterAttachments.cpp:97
juce::ParameterAttachment::setValueAsCompleteGesture(float) juce_ParameterAttachments.cpp:61
PropertyAttachment::propertyChanged() PropertyAttachment.h:32
PropertyAttachment::valueTreePropertyChanged(juce::ValueTree &, const juce::Identifier &) PropertyAttachment.h:26
`juce::ValueTree::SharedObject::sendPropertyChangeMessage'::`2'::<lambda_1>::operator()(juce::ValueTree::Listener &) juce_ValueTree.cpp:107
juce::ListenerList<juce::ValueTree::Listener,juce::Array<juce::ValueTree::Listener *,juce::DummyCriticalSection,0> >::callCheckedExcluding<`juce::ValueTree::SharedObject::sendPropertyChangeMessage'::`2'::<lambda_1> &,juce::ListenerList<juce::ValueTree::Listener,juce::Array<juce::ValueTree::Listener *,juce::DummyCriticalSection,0> >::DummyBailOutChecker>(juce::ValueTree::Listener *,const juce::ListenerList<juce::ValueTree::Listener,juce::Array<juce::ValueTree::Listener *,juce::DummyCriticalSection,0> >::DummyBailOutChecker &,<lambda_1> &) juce_ListenerList.h:271
juce::ListenerList<juce::ValueTree::Listener,juce::Array<juce::ValueTree::Listener *,juce::DummyCriticalSection,0> >::callExcluding<`juce::ValueTree::SharedObject::sendPropertyChangeMessage'::`2'::<lambda_1> &>(juce::ValueTree::Listener *,<lambda_1> &) juce_ListenerList.h:203
juce::ValueTree::SharedObject::callListeners<`juce::ValueTree::SharedObject::sendPropertyChangeMessage'::`2'::<lambda_1> >(juce::ValueTree::Listener *,<lambda_1>) juce_ValueTree.cpp:93
juce::ValueTree::SharedObject::callListenersForAllParents<`juce::ValueTree::SharedObject::sendPropertyChangeMessage'::`2'::<lambda_1> >(juce::ValueTree::Listener *,<lambda_1>) juce_ValueTree.cpp:101
juce::ValueTree::SharedObject::sendPropertyChangeMessage(const juce::Identifier &, juce::ValueTree::Listener *) juce_ValueTree.cpp:107
juce::ValueTree::SharedObject::setProperty(const juce::Identifier &, const juce::var &, juce::UndoManager *, juce::ValueTree::Listener *) juce_ValueTree.cpp:145
juce::ValueTree::setPropertyExcludingListener(juce::ValueTree::Listener *, const juce::Identifier &, const juce::var &, juce::UndoManager *) juce_ValueTree.cpp:780
juce::ValueTree::setProperty(const juce::Identifier &, const juce::var &, juce::UndoManager *) juce_ValueTree.cpp:770
juce::CachedValue::setValue(const float &, juce::UndoManager *) juce_CachedValue.h:263
PropertyAttachment::setValue(float) PropertyAttachment.h:38
