Swapping a ValueTree when changing presets?

Just been banging my head against the following for a while today and was wondering if anyone has a solution?

We have a preset file that we create a value tree from and then swap with the old preset value tree. An issue arrises when have persistent objects that you want to update with the new value properties.

struct Object
{
 
 Object(ValueTree& p):preset(p)
{
     numVoices.referTo(preset, Identifier::numVoices);
}

void swapPreset(ValueTree& newPreset)
{
    preset=newTree;
    //numVoices.referTo(preset, Identifier::numVoices);
}

CacheValue numVoices;
ValueTree preset;
}

Then in the interface we do refer a slider to the numVoices cachedValue…

struct VoicePanel
{
     VoicePanel(Engine& e) {
       voiceSlider.getValueObject().referTo(e.objectStruct.numVoices.getPropertyAsValue();
    }
     
Slider voiceSlider;
}

My question is if there is anyway that the binding between the slider and numVoices can be only done once. I don’t want VoicePanel to also be a listener on the preset ValueTree and have to always re-bind the Slider when I redirect the tree via “preset=newTree”.

My understanding up to this point was that if you refer a juce::Value to a juce::CachedValue, you don’t have to recall referTo each time the CachedValue is assigned to a new tree?

Looking at the underlying ValueTreePropertyValueSource code that is used in juce::getPropertyAsValue(), I don’t see any way of updating the ValueTree, so I think you’d have to update the binding each time.

The easiest thing is just to destroy and rebuild your whole Object structure every time instead of trying to swap new data in. This is less efficient, but it makes for much cleaner code. I tried for ages to solve these kinds of problems, but I always ended up with code that was both bloated and fragile. With ValueTrees, I find it’s best to just stick to the basic operations of adding & removing children and properties.

1 Like

Or load the preset tree as a temporary, then loop over the main tree and update the values one by one. This also lets you retain undo history.

It’s been a while, but I think this is what I ended up doing. The tree itself never changes, just values within it.

1 Like

@LiamG, I agree and we do as you describe for a lot of things, this is a special case where we have global settings, say a panel that is “Settings” for a plugin and you don’t want to trash that panel during preset change as it might be open.

@Fandusss…yes I will try this :+1:

I strongly second that. I’m also walking over the entire value tree when importing, with my own data model reflecting the tree when importing it, and also validating all data that’s being set. The interesting callback for that is “value tree redirected”. This also allows for migrating older versions of the data, in case you got newer plugin versions that changed the schema. It’s somewhat tedious reflecting the entire tree structure, but given that value trees are just variant containers that contain anything and everything, better enforce some safety.
Another benefit: You can do things like “lock” certain properties and skip them on import, or behave differently depending on the data coming from DAW setState or an internal preset browser. For example, a DAW load should probably restore UI state, while a preset switch in the plugin should not.

2 Likes

One question @jconmusic, if you are copying one tree into another, are you sure that valueTreeRedirected is called? We didn’t see any callback other than valueTreeRedirected, triggered when you assign the tree to another, that would allow us to differentiate between when a value was changed via an interface control ( valueTreePropertyChanged etc ) or when a full preset was loaded into a new tree and assigned to the existing presetTree. We need to differentiate between the two, say you turn on a module: when its from a preset load ( valueTreeRedirected ) you want to not animate an on/off switch on the interface.

We were thinking about doing the full copy earlier on, but came up against two caveats.

  1. Say you have a tree with potentially 1000s of entries. If you then have some hand coded checks for every individual child/property ranges etc to make sure its all correct, you are potential creating more bugs than your mitigating against? I do see in Tracktion they have an entire tree listener when you can catch specific properties when they change and do extra things.

  2. Another issue we were trying to overcome was when you change a preset, if you trash everything and rebuild. Say you have a sample module on the interface that has a number of controls related to a sample loaded from the preset. Rather than trashing that interface component and recreating it with the new tree, we instead keep it around and reconnect to the tree, listening again for valueTreeRedirected to let us know when to do this. My feeling is that the ValueTree hanging around is maybe a situation that is suited to Tracktion or a DAW, where the entire DAW project is based on a persistent ValueTree that hangs around as long as you have the project open. Changing a preset in this scenario would be like opening a new daw project: everything bar say the AudioProcessor gets trashed and rebuilt new from the ValueTree.

I’m just going through out the pro/cons, I think what you are describing could actually be a better solution and the more correct way to use value trees!

Hey @gavinburke
It can get pretty involved, and it’s probably too complicated to go into all details. Some more stuff I remember:
It’s reliable to detect setStateInformation. I am doing some preprocessing on the loaded tree before attaching it to my “live data model” tree in this case. In one of the products I developed, that entails going through up to 50 upgrade migrations first, and then doing some modifications to not include certain things I may not want to load, while forcing some others. It depends on the concrete application.

I vaguely remember that my data model uses different strategies to work on the tree, so you may be right that I make a difference between redirected (which is the assignment to a tree) vs. attached. I may have used the assignment for one thing and reattachment for the other, but without diving into the code I can’t remember exactly right now - but something like that was involved. I am pretty certain that for the case of setStateInformation, it comes down to a liveTree = importedTree, and the rest is taken care of by all the listeners. An assignment is causing a redirect, doing deep copies is a different challenge.

There are indeed a lot of those, and it can easily get into 1000s of properties changing. If you compare this to the amount of stuff I’m doing per sample, or to what many games need to update per frame, this is not causing any noticable lag or stutter. I’ll try to describe a bit more what’s going on.

I’m only using the value trees to hold data for the ui/message thread side of my plugins, because they provide easy serialization support and you can attach listeners to everything. I mirror this data completely for consumption by my audio processing in simpler c++ data structures, with strong control over what is updated exactly when, and different policies to govern the process. For example, some data is streamed to the audio engine (like turning a filter knob), while changes on other data may require a full rebuild of the processing chain (like changing global oversampling or introducing a new oscillator in a modular synth). So, over the years I’ve built a data model on top of value trees that ensures strong type safety, comes with it’s own listeners and data types (user definable curves come to mind), and many other things that go beyond value trees. There are factories for various components, some stuff uses binary data and the value tree just holds a bit of base64.

If you plan to grow and extend a product over a longer time, I can recommend spending the extra effort to build a reusable framework of these things (and test the foundation thoroughly).

About your second point:
My audio processing doesn’t rely on the ValueTree at all. It retains its state entirely if I swap out the tree or sub-trees, and I have some additional threads at work with various responsibilities of processing the data before handing it over to the audio processor in lock-free swaps. Resource loading (samples) and compiling processing graphs are offloaded, preprocessing convolution data, and everything that needs to allocate something. The system retains whatever it can, and it goes through various levels of protocoling and analyzing to only change what needs to be changed. There is also a garbage collection system that makes sure data is deallocated on the correct thread.

To come full circle: almost all of that is indeed triggered by value tree listeners somewhere in the data model. This works so well that my undo/redo system is based on saving/reloading entire value trees, in a brute force memento pattern approach (it’s just a few kbs of data, so why not…).

It can be a little tedious to get the ValueTree-reactive part robust, but once it’s there, defining data model schemas and getting both UI and behavior for it automagically can become both easy and reliable. I definitely became a fan of the ValueTree data structure for reactive programming in JUCE.
I wouldn’t overdo it for small/simple plugins that just have a handful of parameters though, while at a certain complexity and size, there may be other approaches to store data that work better.

Hm, I got lost here a little, so sorry for the wall of text, but maybe it gives you some ideas :slight_smile: tl;dr: valuetrees are great and fast enough for most things, but you need to constrain them a little, and they sure can be confusing.

2 Likes