# ValueTree Listener callback

**URL:** <https://forum.juce.com/t/valuetree-listener-callback/66448>\
**Category:** Development\
**Created:** [June 24, 2025, 11:54am UTC](https://forum.juce.com/t/valuetree-listener-callback/66448 "2025-06-24T11:54:49Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![roelV](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/roelv/32/21271_2.png) [@roelV](https://forum.juce.com/u/roelV)\
**Post date:** [June 24, 2025, 11:54am UTC](https://forum.juce.com/t/valuetree-listener-callback/66448/1 "2025-06-24T11:54:49Z")

</div>

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:

```cpp
   #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.

```cpp
#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?

```auto
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
```

---

<div class="post-metadata">

**Author:** ![jimc](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/jimc/32/16067_2.png) [@jimc](https://forum.juce.com/u/jimc)\
**Post date:** [June 26, 2025, 6:29pm UTC](https://forum.juce.com/t/valuetree-listener-callback/66448/2 "2025-06-26T18:29:14Z")

</div>

Looking at that call stack I dont’ think the scoped value setter will have gone out of scope. setValue is still at the bottom of your stack trace there.

I would put a breakpoint in setValue. When you hit the breakpoint add a watchpoint on the ignoreCallbacks so that the debugger will break when the value changes, hit play and see what happens.

---

<div class="post-metadata">

**Author:** ![roelV](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/roelv/32/21271_2.png) [@roelV](https://forum.juce.com/u/roelV)\
**Post date:** [June 26, 2025, 8:14pm UTC](https://forum.juce.com/t/valuetree-listener-callback/66448/3 "2025-06-26T20:14:27Z")

</div>

I tried your suggestion but unfortunately the debugger is unable to watch the variable due to “compiler optimizations”. I am in debug mode though, so that is kind of strange. Maybe i need some additional compiler flags.

But I actually already fixed the problem since it was coming from somewhere else (which was not really visible from the debugger).

I am syincing parameters between plugin instances and the syncing triggered the `valueTreePropertyChanged` callback while i was dragging the slider connected to that parameter. That caused `beginChangeGesture` to get called twice without a prior call to `endChangeGesture`.

I fixed it by checking if a gesture has started for the parameter, if it has started then it won’t call `setValueAsCompleteGesture`.

Thanks for your help anyways 🙂
