Store string in AudioProcessorValueTreeState

There is a 6 year old discussion on this, but I thinks it’s not applicable anymore because I don’t think you can do

AudioProcessorValueTreeState::getOrCreateChildValueTree()

Is there any other way to store run-time created strings in the AudioProcessorValueTreeState?

Nobody? I was hoping to learn something new from this forum…

I think that’s because APVTS seems to be a class designed for beginners and I (and some others said so in this forum) ditched this class when they had a chance.
I personally don’t think what you want to do is possible having thread safety in mind. I suggest you look into a different approach. E.g. maintaining a separate ValueTree parallel to your APVTS to store all the stuff in, that you need in addition.

OK - so if I’d like to combine+write and subsequent read+split two valuetrees from one file, then I’d have to do this with XmlElement methods, like addChildElement , getChildElement, removeChildElement ?

Let’s see what AudioProcessorValueTreeState is:
It is an attempt to put the whole state of a plugin into one object for managing and serialising.
It has two parts:

  • the list of parameters
    This allows to connect GUI stuff using the Attachment classes. It also allows to access an atomic float of the value using getRawParameterValue(parameterID). That function should have never seen the light of day, because it lacks the context you have, when you talk to the RangedAudioParameter subclasses.

  • a ValueTree that can easily be used to serialise and deserialise the state in getStateInformation and setStateInformation.
    This ValueTree is exposed as public variable ValueTree state, and it is perfectly fine to add your own subnodes and parameters there, so they get serialised together with the parameters.

To summarize: The ValueTree has a representation of the parameter values, but they are distinct from the atomic values of the actual parameters.

Even though some APIs might allow other data types, JUCE only exposes numeric types to the host.
There is no string that could be automised by a host via juce. But you can certainly store it as scalar not automated value alongside in the public state.

Yes :wink:

Rincewind,
thanks!

daniel,
I do not want automated strings, :upside_down_face:, I want to store run-time created strings, together with the apvts, in an xml file, and then split this xml again, into an apvts, and *all other stuff", after reading the file. Get/setstateinfo remains untouched.

This is the part I don’t agree with. Just putting in custom data into the value tree results in threading problems very easily. As getStateInformation might not be called on the message thread, you need to put in some synchronisation. When you use just the APVTS with audio parameters, this is taken care of by JUCE. But adding new Childs to the state of APVTS doesn’t become automatically thread safe for which I think it shouldn’t be done at all.

Even not if just for the sake of writing one combined xml file?

You can merge stuff together all the way you want in setStateInformation. You just need to make sure you avoid concurrent read/writes to the ValueTree. APVTS does exactly that as long as you leave it be.

When you add your own data structure based on ValueTree, you’ll need to take care of thread security yourself.

Understood, I only intend to add an “other stuff” tree to a copy of the APVTS, before writing to an xml file for later retrieval…
Get/setstateinfo remains untouched.

Ok, so this is trivial:

auto addons = treeState.state.getOrCreateChildWithName ("addons", nullptr);
addons.setProperty ("text", "Lorem Ipsum", nullptr);

Whenever you save the apvts, this text is preserved.
And you can retrieve via:

auto addons = treeState.state.getOrCreateChildWithName ("addons", nullptr);
auto text = addons.getProperty ("text", juce::String()).toString();

The usual save and load routine in getStateInformation:

void MyAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
{
    juce::MemoryOutputStream stream(destData, false);
    state.state.writeToStream (stream);
}

void MyAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    auto tree = juce::ValueTree::readFromData (data, size_t (sizeInBytes));
    if (tree.isValid()) {
        state.state = copyPropertiesAndChildrenFrom (tree, nullptr);
    }
}

getStateInformation should not be called from a sensitive thread. I would even assume it’s the message thread, but certainly not a realtime thread. You can test using JUCE_ASSERT_MESSAGE_THREAD.

I actually found a good example for this thread safety in one of my projects:

const String commentPrefix = "COMMENT_";
const Identifier commentValueTreeName = "COMMENTS";

std::unique_ptr<XmlElement> ParameterWrapper::saveState()
{
  // Yes, this is thread safe: https://forum.juce.com/t/audioprocessorvaluetreestate-thread-safety/21811/42
  auto valueTree = parameters.copyState();
  
  {
    ScopedLock guard(commentsLock);
    if (auto comments = valueTree.getChildWithName(commentValueTreeName);  comments.isValid())
      comments.copyPropertiesAndChildrenFrom(parameterComments, nullptr);
    else
      valueTree.addChild(parameterComments.createCopy(), 0, nullptr);
  }
  
  return std::unique_ptr<XmlElement>(valueTree.createXml());
}


void ParameterWrapper::loadState(const XmlElement& xmlState)
{
  // Yes, this is thread safe: https://forum.juce.com/t/audioprocessorvaluetreestate-thread-safety/21811/42
  if (xmlState.hasTagName(parameters.state.getType()))
  {
    auto valueTree = ValueTree::fromXml(xmlState);
    extractCommentsFromState(valueTree);
    parameters.replaceState(valueTree);
  }
}


void ParameterWrapper::extractCommentsFromState(const ValueTree& valueTree)
{
  ScopedLock guard(commentsLock);
  if (valueTree.getChildWithName(commentValueTreeName).isValid())
    parameterComments.copyPropertiesAndChildrenFrom(valueTree.getChildWithName(commentValueTreeName), nullptr);
}


juce::String ParameterWrapper::getParameterComment(const juce::String& paramId) const
{
  ScopedLock guard(commentsLock);
  return parameterComments.getProperty(commentPrefix + paramId);
}

You can of course play around with that a bit. This is now guarded against any thread accessing getParameterComment. You could also take a message thread lock when reading/writing the “parameterComments” and assert that you are on the message thread when calling getParameterComment.