Loading plugin state from ValueTree results in unexpected base64:parameters property

When I use te::Plugin::restorePluginStateFromValueTree(preset) to restore the state of a plugin, a subsequent call to te::Plugin::flushPluginStateToValueTree() leaves the state ValueTree with an unexpected te::IDs::parameters property like this:

<PLUGIN type="delay" windowLocked="1" id="1050" enabled="1" feedback="-30.0"
        base64:parameters="13.lUVYjIVXisF....v.C">
  <MACROPARAMETERS id="1051"/>
  <MODIFIERASSIGNMENTS/>
</PLUGIN>

The base64:parameters property prevents the plugin from using the updated ā€˜feedbackā€™ setting that I am attempting to recall.

Stepping through the code, I see

  • The base64:parameters property is being added by void AutomatableEditItem::saveChangedParametersToState() but I havenā€™t been able to figure out what the parameters property is for.
  • Updating internal plugin parameters via the .state ValueTree updates the value returned by AutomatableParameter::getCurrentValue(), but NOT the value returned by AutomatableParameter::getCurrentExplicitValue(). This discrepancy causes saveChangedParametersToState() to add the base64:parameters property to the pluginā€™s state.

How can I correctly recall te::Plugin configuration from a ValueTree?

Thanks a lot for any advice!

The parameters property is there due to modifiers. It is basically a way of recording any parameters that have been changed due to some kind of modifier (i.e. via a call to updateToFollowCurve or updateFromAutomationSources).

If you look at AutomatableParameter::setParameterValue, youā€™ll see that the value the pluginā€™s parameter is set to is based on the currentBaseValue and the currentModifierValue.

AutomatableParameter::setParameter sets currentParameterValue and is set when the user moves a parameter from a UI element.


Itā€™s a bit tricky to explain but without it, setting a plugin parameter would change the state of the plugin. If this then gets saved and reloaded, any modifiers that are being used in the Edit (e.g. an LFO) simply get added to that saved value. This means thereā€™s no way of knowing what the LFO should be applied to.

Hence the parameters property which stores the values explicitly set so we can restore these after a preset chunk is loaded.


I think in your case, you simply shouldnā€™t be updating the ValueTree state directly but going via one of the AutomatableParameters instead?

I was hoping to load and save presets for arbitrary plugins (similar to .trkpreset files). Is there a way I can do this that plays nicely with modifiers?

I think I may have misunderstood your actual problem.

Why canā€™t you just call flushPluginStateToValueTree and then take a copy of the state, then later use restorePluginStateFromValueTree?

Thatā€™s all the .trkpreset files doā€¦


(As I mentioned I think you problem stems from:

rather than the preset save/load functionality?)

I am using the restorePluginStateFromValueTree method.

  1. I call restorePluginStateFromValueTree to recall the plugins state.
  2. The next time I call flushPluginStateToValueTree(), the state gets a parameters property, which effectively undoes the preset recall that I attempted to do.

For some reason, restorePluginStateFromValueTree updates the values returned by getCurrentValue() but not the values returned by getCurrentExplicitValue(), causing saveChangedParametersToState() to add the parameters property.

The code is really simple:

std::cout << "Current Config: " << currentConfig.toXmlString() << std::endl << std::endl;
std::cout << "Preset  Config: " << preset.toXmlString() << std::endl << std::endl;
std::cout << "Before loading: " << plugin->state.toXmlString() << std::endl << std::endl;

plugin->restorePluginStateFromValueTree(preset);
plugin->flushPluginStateToValueTree();

std::cout << "After loading: " << plugin->state.toXmlString() << std::endl;

I thought maybe I had to wait to call flushPluginStateToValueTree (until the tree watchers handle the change, and update the plugin), but it doesnā€™t matter when I call it, it always adds the parameters property.

Ah ok, I think I know what you mean. Itā€™s because youā€™re calling flushPluginStateToValueTree twice and restorePluginStateFromValueTree isnā€™t calling AutomatableParameter::setParameter.

Does calling AutomatableEditItem::restoreChangedParametersFromState on the plugin after restorePluginStateFromValueTree help?

That does sound like the function I need ā€“ itā€™s currently a protected method ā€“ I tried moving under the public access specifier, and Iā€™m still getting the same results.

Update: AutomatableEditItem::restoreChangedParametersFromState reads from the parameters property, and writes to the properties on the ValueTree. This doesnā€™t help, because the ValueTree properties (feedback="-30.0" in my example) are correct. The parameters property that contains old values that I am trying to update.

What plugin are you trying to do this with?
I think the ExternalPlugin overrides this to do the correct behaviour?

If itā€™s an internal one, is there any chance you could construct a failing unit test I can replicate with? (grep the module for existing UnitTest subclasses as an example).

Yes, I can look into that. I have bee testing this with the internal delay plugin.

@dave96 Iā€™m going to work on this sometime in the coming days, but before I do ā€“ I donā€™t see an easy way to run the existing test in the tracktion_engine repo. I see the TempoSequenceTests : public UnitTest class, but no instances or subclasses of juceā€™s UnitTestRunner.

Should I just send you a PIP that includes the tests?

An instance of UnitTest runner isnā€™t really something that should live in a library like that.
There is however and example project that runs all the unit tests: https://github.com/Tracktion/tracktion_engine/blob/develop/examples/TestRunner.h

This gets built and run by our CI on every commit.

If you can simply create the unit test subclass, I can add it where appropriate to the Engine code, fix the Engine so it passes and then it will be part of on going tests.

Iā€™m just a bit slammed with NAMM at the moment and having a failing unit test make fixing this a lot quicker.

Got it ( I was looking at the master branch, so I didnā€™t see the TestRunner example)

Here is the unit test:

@dave96 did you get a chance to look at the unit test? No worries if not, but I wanted to check that you didnā€™t miss the link.

Sorry, I have seen it and itā€™s on my list. Iā€™ve been thinking about the appropriate solution and it might just be to set the parameters in restoreStateFromValueTree rather than directly set the cached values. I think that would work.

The other alternative is to modify AttachedValue to respond to changes in the ValueTree but I have a feeling that will result in recursive updatesā€¦

Iā€™ll try and look tomorrow.

Thanks for the test though, thatā€™s extremely helpful.

1 Like


Ok, Iā€™ve added the test and a fix for the Delay plugin now.
Can you check this works for you and let me know?

If it does, Iā€™ll add the fix to the other internal plugins.

@dave96 delay preset recall is working great. Thank you!

I think you mentioned this earlier, but I also tested with external plugins, and those work fine too.

Iā€™m not sure if this is a bug or not, but I noticed that the ā€œchorusā€ internal plugin has no
automatable parameters: (plugin->getAutomatableParameters() returns an empty list). It looks like it should have depthMs, width, mixProportion, and speedHz.

Yeah, those internal plugins are very old. I seem to remember that some parameters canā€™t be automated due to the implementation. If you change those parameters youā€™ll get break-ups in the sound.

In our premium plugins (DAW Essentials) we do lots of things to work around that.