Best practice for storing text on the APVTS

I want to let the user edit each bus name on MixMaxtrix and to store it on the patch.

I have found this:

Is this method OK with regards to thread safety?

This thread seems to be describing this approach as not thread unsafe.

The author posted the solution but I don’t think I understand what he meant.

This is connecting components to a ValueTree property.
Note that when I wrote that I wasn’t aware of the method to simply referTo a value.

ValueTree foo { "param" };
label.getTextValue().referTo (foo.getPropertyAsValue ("value", nullptr));

Which will already keep the value in the label synchronised with the property.
If you use a property of the APVTS.state or ideally a sub-tree, that would already work.

Any string operation is never thread safe, as it cannot be copied in a single operation and hence not be wrapped into an atomic.

However to store a name the thread safety is not an issue. You don’t access that value from the audio thread, so it is fine. AFAIK it is made sure that calling setStateInformation() and getStateInformation() is either on the message thread (or has a MessageManagerLock?).
Somebody please correct me if that’s wrong.

1 Like

Wow! referTo is extremely tidy, trying now… Thanks!

It seemed to work.

I’m seeing this:

-When saving into APVTS.state I see that the values are stored and loaded from the XML when loading presets.
-The labels are not refreshed on preset load. They return to default.
-If I close and open the editor, then the labels texts are there correctly.

What I think that is happening is that the tree is rebuilt when loading, as I load like this:

params.replaceState (juce::ValueTree::fromXml (*state));

So …

params.state->whatever_it_is_implemented_as_value

… changes address. Leaving that callback/binding/whatever pointing to destructed objects I guess.

Does this have a solution?

Does “referTo” implement “valueTreeRedirected” or similar?

Unfortunately no. You need to reconnect when setStateInformation happens. That is the drawback.

Then it looks that the original approach might be better. Thanks for your help!

I didn’t know that this is true. Is it? I assumed the host might do anything :open_mouth:

AFAIK there is no guarantee about set/getStateInformation() being called on the message thread.
Otherwise there wouldn’t be the need for discussions like this: [AAX][PULL REQUEST] Force Protools to do chunk calls in message thread option

From the discussion there (emphasis mine):

I tried to use the valuetree listeners method again on APVTS.state and I don’t get any of the callbacks there invoked when calling:

params.replaceState (juce::ValueTree::fromXml (*state));

I could make a copy of the APVTS.state, then load the state from the xml and then manually do a full iteration restoring the listeners from the old copy of APVTS.state, but there is no way to retrieve the listeners of a var or value tree.

I’m trying hard to avoid the processor knowing the editor, but it is proving more difficult than I thought it would be.

The “valueTreeRedirected” callback isn’t called either if I make a new children tree on APVTS.state and store my stuff there. Why?

Depends on how you restore in setStateInformation.
There is just assigning the new tree there, or copyPropertiesAndChildren and a few more methods involving manual things…

If I understand you correctly, then what you need to do is to reassign your text listeners in ValueTree::Listener::valueTreeRedirected which will be called automatically after you call params.replaceState.

Override valueTreeRedirected and repeat the same thing you do when you originally assign those listeners.

I already tried both ways and neither calls “valueTreeRedirected” for some reason. With lots of attempts of different things, calling copyProperties, assignment operators, creating a children attached to APVTS.state etc.

The approach I’m trying now is to leave the APVTS on its own and to have a separate and stable ValueTree for the editor for the whole runtime. Then when saving the state i serialize both the APVTS and that other to XML and vice versa.

This is a good approach.
Just going over the checklist - did you remember to call apvts.state.addListener(this); in the constructor of the object that listens to apvts.state properties?

Yes. I’m using the ValueTreeLabelAttachment class from Daniel above. I’m sure it must be some dumb Juce newbie mistake somewhere.

If you listen to APVTS.state, then valueTreeRedirected must be triggered when calling APVTS.replaceState().

If this doesn’t work for you, find the reason first. Don’t waste energy on the actual Values/properties, it won’t help.

tree.toXmlString() is your friend! Use it everywhere you can to detect when exactly your listener get detached. Put breakpoints in destructors and look closely at your subtrees.

I have never ever seen a callback to valueTreeRedericeted. I have a printout there that never triggered. This is Linux Reaper.

Anyways, I got it working. For reference of future users, so they don’t spend as much time as me. “gui_data” is a member of the processor that the processor never touches.

  void getStateInformation (juce::MemoryBlock& dst) override
  {
    using xml_ptr = std::unique_ptr<juce::XmlElement>;
    xml_ptr xml {apvts.copyState().createXml()};
    xml_ptr gui_xml {gui_data.createXml()};
    if (gui_xml) {
      auto edit = xml->createNewChildElement (gui_data.getType());
      *edit     = *gui_xml;
    }
#if !defined(NDEBUG)
    printf ("Save preset\n%s\n", (char const*) xml->toString().toUTF8());
#endif
    copyXmlToBinary (*xml, dst);
  }

And for restoring, notice that I have a separate “set_state” function because my plugin stores the builtin presets as a XML string on the binary, so it gets called from two places.

  //----------------------------------------------------------------------------
  void setStateInformation (void const* src, int src_bytes) override
  {
    set_state (getXmlFromBinary (src, src_bytes));
  }
  //----------------------------------------------------------------------------
  void set_state (std::unique_ptr<juce::XmlElement> xml)
  {
    if (!xml) {
      return;
    }
#if !defined(NDEBUG) && 0
    printf ("Set preset\n%s\n", (char const*) xml->toString().toUTF8());
#endif
    juce::XmlElement* gui = xml->getChildByName (gui_data.getType());
    if (gui) {
      // update expected values
      auto gui_new = juce::ValueTree::fromXml (*gui);
      for (int i = 0; i < gui_data.getNumProperties(); ++i) {
        auto key  = gui_data.getPropertyName (i);
        auto prop = gui_new.getPropertyPointer (key);
        if (prop) {
          gui_data.setProperty (key, *prop, nullptr);
          gui_new.removeProperty (key, nullptr);
        }
        else {
          // not present means empty/default.
          gui_data.setProperty (key, "", nullptr);
        }
      }
      // keep unknown values on the preset, so an old version doesn't destroy
      // new presets.
      for (int i = 0; i < gui_new.getNumProperties(); ++i) {
        auto key = gui_new.getPropertyName (i);
        gui_data.setProperty (key, gui_new.getProperty (key), nullptr);
      }
      xml->removeChildElement (gui, true);
    }
    else {
      // clear everything
      for (int i = 0; i < gui_data.getNumProperties(); ++i) {
        gui_data.setProperty (gui_data.getPropertyName (i), "", nullptr);
      }
    }
    if (xml->hasTagName (apvts.state.getType())) {
      params.replaceState (juce::ValueTree::fromXml (*xml));
    }
    preset_was_loaded (std::move (xml));
  }

Thanks for the help!

3 Likes