Confused by ValueTree state recall


I’m having trouble with rebuilding my plugin state after recalling with setStateInformation. Everything is based on the talk Using JUCE value trees and modern C++ to build large scale applications (ADC’17).

The weird behavior is, that I’m able to create all elements as intended during the first run of the plugin. After I close and reopened the DAW only the UI parts of my state get rebuild, but not my DSP classes. I’ve been stuck at this problem for quite a while now. I was playing around with some of my basic classes and noticed the following:

struct Processor
    Processor(juce::ValueTree valueTree, juce::UndoManager* um)
        : state {std::move(valueTree)}, gain_ {state, "gain", um, 1.0f}
    auto setGain(float newGain) -> void { gain_ = newGain; }
    auto getGain() const -> float { return gain_.get(); }

    juce::ValueTree state;

    juce::CachedValue<float> gain_;
auto A = Processor {juce::ValueTree {"TEST"}};
CHECK(A.getGain() == 1.0f); // SUCCEEDS

CHECK(A.getGain() == 3.0f); // SUCCEEDS

auto B = Processor {A.state.createCopy()};
CHECK(B.getGain() == 3.0f); // SUCCEEDS

auto C  = Processor {juce::ValueTree {"TEST"}};
CHECK(C.getGain() == 1.0f); // SUCCEEDS

C.state = A.state.createCopy();
CHECK(A.getGain() == 3.0f); // SUCCEEDS
CHECK(C.getGain() == 3.0f); // FAILS: gain == 1.0f

Why does the last line fail?

juce::CachedValue listens for property changes but not for trees being redirected. When you invoke the assignment operator on a juce::ValueTree, listeners will receive the valueTreeRedirected() callback, but not any valueTreePropertyChanged callbacks.

In your Processor, you’d need to listen to the value tree, and respond to the redirection by refer the cached value to tghe new tree:

void valueTreeRedirected (juce::ValueTree& newTree) override
    gain_.referTo (newTree, "gain", undoManager_);

    // Edit:
    // newTree here should still refer to the same tree you're listening to
    // in which case I'm not sure you need to reassign the CachedValue like this.
    // Instead, you may only need to call gain_.forceUpdateOfCachedValue();

(Note how you’ll need to keep a pointer to the undo manager you want to use as a member. I wouldn’t recommend using a raw pointer, maybe a std::shared_ptr?).

FWIW I’ve run into a similar problem to this before and thought it would be nice if juce::CachedValues had an option to specify whether or not to follow redirects on the tree they’re listening to.

I guess there’s a chance the new tree won’t have a property with the same name as the cached value was referred to, but it could just silently fail and carry on listening to the original tree like it does now.

Ok, it seems that while this behavior is not what I expected, it’s not the main source of problems for me. But it pointed me into the right direction. As mentioned in the first post, after I reopen a previously saved session, only the UI elements rebuild themselves. My current guess is that at the point where the UI components get constructed, the state has already been loaded with setStateInformation, so the ValueTree’s passed to the constructors have state, while the DSP classes get constructed in the main constructor of the plugin, before setStateInformation.

Is this assumption correct?

setStateInformation will be called after the processor’s constructor, but before prepareToPlay (IIRC). You should always do any initialisation for audio stuff in prepareToPlay

The problem is not the initialization for playback, but the actual creation of the processor classes from the valuetree state. The ValueTreeObjectList classes I use don’t trigger a rebuild if the valuetree was redirected. One solution would be to manually rebuild the root element by using a unique_ptr everytime the setStateInformation gets called, but then I would not be able to pass pointers from DSP classes to UI elements because they would eventually dangle when the DSP classes get rebuild.