Realtime Safe ValueTree (Non-Locking Solution)

I created a juce::ValueTree to represent the non-parameter state of the plugin (like various bools, ints and strings that I don’t want to be apart of the parameter APVTS) and made it a member variable of the AudioProcessor. The GUI and audio threads read from and write to the ValueTree via getter/setter methods I defined in the AudioProcessor, using juce::ScopedLock to protect access.

However, I’m getting glitching sounds because the audio thread is occasionally being blocked. There doesn’t seem to be a JUCE tutorial explaining a safe way to connect the AudioProcessor and PluginEditor via shared state while remaining realtime-safe.

Some juce forum threads mention using atomics, but I have an entire tree of variables, including nested child trees. Other suggest some kind of mirroring (std::unordered_map<juce::String, std::atomic>), but no Juce tutorial exists.

Someone else suggested using juce::ValueTreeSynchroniser and keeping a copy in the PluginEditor, but that would get messy since I have subcomponents that are listeners of the tree. It also doesn’t address how the GUI would update the true ValueTree held by the AudioProcessor. Another person mentions juce::AbstractFifo but offers no solution.

Is there a version of ValueTree that’s actually practical for storing state?

On the note of value trees when it concerns params, the documentation mentions AudioProcessorValueTreeState uses a lock internally, but I thought that wasn’t realtime-safe either. To add to my confusion, reading from APVTS is safe in audio thread, but calling setValueNotifyingHost is not? Does that mean I have to schedule this in a queue and consume it on a processBlock call? Or use juce::MessageManager::callAsync and make the GUI thread call setValueNotifyingHost? What if I want to change the param but the GUI isn’t open?

I’m just confused why JUCE says to use ValueTree as a good way to manage state, yet it’s not safe for reading and writing from both the audio and GUI thread. I think it’s the most fundamental thing the framework should provide. I wish Juce just spelled it out: where to keep the value trees, how to edit it depending on if you’re on the GUI thread or Audio Thread.

Lastly, setStateInformation will need to call “paramState.replaceState(juce::ValueTree::fromXml(*xmlParamState));” and “internalState.copyPropertiesAndChildrenFrom (juce::ValueTree::fromXml(*xmlInternalState), nullptr);” to return restore the state. In the end, if the GUI thread has write access to the internal state ValueTree and APVTS, and I made it such that the audio thread can’t write to it but it can read to some collection of atomics, then won’t this still end up needing a lock here because the audio thread is modifying the trees? Or is setStateInformation called by the messenger thread and I have mistakenly assumed it was being called by the Audio Thread because it lives on the Plugin Processor component.

I think there is much to discuss.

that’s a good question.

To answer one thing: atomic strings will generally incur a lock because hardware typically supports non-blocking atomics only if the data is smaller than 4-8 bytes in size. string classes are bigger than this.
i.e. atomics strings will block the audio thread. Therefore atomics are not suitable as a general solution to the problem.

Hey @timur - thought you might find this question interesting since you are an expert on locks. Could it be possible to get clarification in the documentation and some kind of tutorial @reuk?

Is there a version of ValueTree that’s actually practical for storing state?

If your state only contains floats, bools, etc, APVTS is the best choice. It is real-time safe to read from APVTS. If not, BOOM!, you may need to look at FIFO, CAS, RCU. From my point of view, keep away from CAS and RCU unless they are truly necessary.

To add to my confusion, reading from APVTS is safe in audio thread, but calling setValueNotifyingHost is not? Does that mean I have to schedule this in a queue and consume it on a processBlock call? Or use juce::MessageManager::callAsync and make the GUI thread call setValueNotifyingHost? What if I want to change the param but the GUI isn’t open?

Always call setValueNotifyingHost on message thread. Even if the GUI is closed, message thread would work.

I’m just confused why JUCE says to use ValueTree as a good way to manage state, yet it’s not safe for reading and writing from both the audio and GUI thread.

I mainly use ValueTree to share states between components. I have never used it to share states between audio thread & message thread.

FYI Just for people like me who have trouble deciphering the abbreviations in this thread

RCU=Read Copy Update
CAS=Compare and switch

I’m also using ValueTrees to store non automated data/parameters, but would never recommend to use them directly from audio thread.

Instead use helpers like CachedValue.

For complex datastructures I recommend prepare them in another thread and than to atomically switch pointers with atomically shared pointers.

IMHO its also „okay“ to use locks, provided you only hold them for a short time (and only once during the callback) and you make sure that there is no performance intrusion, for example by allocating memory (like every string operation)

1 Like

My findings:

Who owns what?

Plugin Processor

  • Owns the APVTS
  • Owns the Internal State ValueTree
  • Can write to both, but only via scheduling updates on the message thread. In the case of the Internal State ValueTree, we additionally update the atomic mirror which is a member variable member variable we use so that we read and write from the atomic instead, and schedule the value tree to catch up.

Plugin Editor

  • Is a listener to the internal state value tree. So when the internal state value tree updates, get the listener callback or read directly using getRawParameterValue.
  • We write to the param state directly in the audio processor, which will use param->getNormalisableRange().convertTo0to1(value) and call setValueNotifyingHost.

Who does what?

GUI thread → Will do two updates (Message thread)

  • Updates the ValueTree on the audio processor using setProperty (safe, because GUI owns the ValueTree).
  • Updates the atomic member variable we made to mirror the property on the audio processor immediately so we get fast realtime access to any properties.

Audio thread → Only updates atomics and schedules valuetree updates (Real-time audio callback)

  • NEVER modifies the ValueTree directly.
  • Updates the atomic member variable immediately
  • Schedules an async message (via MessageManager::callAsync) to later update the ValueTree on the Messenge thread. See below for the code.

Internal Value Tree Updates
Make a setInternalBool method on the audio processor. It should update the atomic. If we are the message thread, update the value tree property. If we are the audio thread or any other thread, schedule the message thread to update it async.

APVTS Updates
The GUI is painted by the message thread, but the message thread has other tasks that are unrelated the graphics. The message thread exists without there being a GUI present and is just a general thread we use to make updates.

When the audio thread wants to change a param, we schedule it via the message thread and make sure the message thread calls setValueNotifyingHost. When the GUI thread wants to change a param, it should be able to do so without issue. This means you can make a generic setParamBool on the audio processor, and check if it’s the messenger thread. If it is, we just call setValueNotifyingHost, if not, we async tell messenger thread to do it. This means there might be some delay before it’s done. This means, as you guessed it, we also want to use an atomic paradigm, so that the audio thread always is able to get the immediate truth. This means that the value tree of the param may lag behind a bit, which means you’re GUI might lag behind a bit because the UI reads from the tree, but at least the DSP will always be up-to-date with the state of things.

setStateInformation And getStateInformation
This will always be called by the plugins Messenger Thread (GUI Thread), not the audio processor thread! This means that in setStateInformation after you have replaced the APVTS and Internal State, you need to call refreshAtomicsFromTree() to update the atomic member variables corresponding to each property of the internal state tree (this is not needed for the APVTS tree because we don’t use our own atomic mirrors).

Here’s the catch. Most hosts call getState on the UI thread, but Pro Tools, GarageBand and a few others use their own worker threads. They still don’t call from the realtime audio thread, so heap allocations are fine, but you can’t assume MessageManager::isThisTheMessageThread() is true. So that means, when we get state, if we don’t want to clash with the GUI which might be writing to the tree, so we actually need to reconstructive the internal tree using the atomics.

ValueTree String Properties
We can’t safely put these inside an atomic the way we can with other primitive types. We must keep it guarded by a lock-free single-producer single-consumer queue (the GUI is producer, audio is consumer). Maybe someone can provide an example of this.

tldr:

  • Processor writes on the Message thread, not the audio thread
  • The internal ValueTree is the plugin’s true saved state and lives on the plugin processor. Atomics are runtime helpers for real-time audio. When we restore the serialized ValueTree by replacing the current tree (copyPropertiesAndChildrenFrom), we update the atomics which are member variables of the plugin processor, so they are also up to date.
  • The APVTS lives on the processor as well, it is okay for both threads to read from. Writing is okay from the GUI (messenger thread), but if needed to be done in the audio processor (audio thread) we need to schedule the update to use the messenger thread. During that window, if the audio thread reads the value, it still sees the old atomic value stored in the APVTS since we are waiting for the message thread to update the tree and set it’s internal atomic for us to read. However, if you really really need it to be immediate, you can keep an atomic mirror of this param value of your own. I assume this is needed for serious plugins like mastering ones where the audio thread always needs a completely up to date truth value as it executes, and cannot even tolerate a few ms of a param to update.
  • For APVTS parameters: Use getRawParameterValue, read directly from audio thread or GUI thread. For writing, if you’re the GUI thread it’s okay, however if you’re the audio thread, schedule it on the GUI thread.
  • For internal ValueTree properties (non-parameters): You must maintain your own atomics manually as member variables in the audio processor and load from them. If you write to it, write to it and also schedule the messenger thread to update the value tree property.

Further information:

  • We can’t start threads from the audio processor thread. The new keyword means we allocate memory on the heap, which is not realtime safe.
  • In practice, many developers use CachedValue for GUI components (to avoid constantly querying ValueTree), which is optional but can be useful if you are reading the same property many times. I don’t yet know how to use this.

Coding Examples

Assume you have these member variables on the audio processor.

// Value tree for params
juce::AudioProcessorValueTreeState apvts;

// Value tree for internal state
juce::ValueTree internalState;

// Map from property ID -> atomic<bool>
std::unordered_map<juce::Identifier, std::atomic<bool>> internalBoolAtomics;

Internal State Setter & Getter

namespace InternalStateID
{
    const juce::Identifier isMuteToggleActive {"isMuteToggleActive"};
}

void MyProcessor::setInternalBool(const juce::Identifier& propertyID, bool newValue)
{
    // Update atomic
    if (internalBoolAtomics.find(propertyID) != internalBoolAtomics.end())
        internalBoolAtomics[propertyID].store(newValue);

    // Update ValueTree
    if (juce::MessageManager::isThisTheMessageThread())
    {
        internalState.setProperty(propertyID, newValue, nullptr);
    }
    else
    {
        juce::MessageManager::callAsync([this, propertyID, newValue]()
        {
            internalState.setProperty(propertyID, newValue, nullptr);
        });
    }
}

bool MyProcessor::getInternalBool(const juce::Identifier& propertyID) const
{
    if (auto it = internalBoolAtomics.find(propertyID); it != internalBoolAtomics.end())
        return it->second.load();

    // If not found, return false by default (or you can jassertfalse)
    return false;
}

Usage:

// Adding example
internalBoolAtomics.emplace(InternalStateID::isMuteToggleActive, false);

// Setting example
processor.setInternalBool(InternalStateID::isMuteToggleActive, true);

// Getting example
bool muteIsActive = processor.getInternalBool(InternalStateID::isMuteToggleActive);

Param State Setter & Getter

namespace ParamStateID
{
    const juce::Identifier isMuteToggleActive { "isMuteToggleActive" };
}

void MyProcessor::setParamBool(const juce::Identifier& paramID, bool newValue)
{
    if (auto* param = dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(paramID.toString())))
    {
        const float normalisedValue = param->getNormalisableRange().convertTo0to1(newValue ? 1.0f : 0.0f);

        if (juce::MessageManager::isThisTheMessageThread())
        {
            param->setValueNotifyingHost(normalisedValue);
        }
        else
        {
            juce::MessageManager::callAsync([param, normalisedValue]()
            {
                param->setValueNotifyingHost(normalisedValue);
            });
        }
    }
    else
    {
        jassertfalse; // ID not found or wrong type
    }
}

bool MyProcessor::getParamBool(const juce::Identifier& paramID) const
{
    if (auto* atomicParam = apvts.getRawParameterValue(paramID.toString()))
    {
        return atomicParam->load() >= 0.5f;
    }

    jassertfalse; // ID not found
    return false;
}

Usage:

// Setting a param
processor.setParamBool(ParamStateID::isMuteToggleActive, true);

// Getting a parameter
bool muteParamIsActive = processor.getParamBool(ParamStateID::isMuteToggleActive);

The way to set the plugin state

void MyProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    auto xml = parseXml(data, sizeInBytes);

    if (xml != nullptr)
    {
        auto newPluginState = juce::ValueTree::fromXml(*xml);

        if (newPluginState.isValid())
        {
            auto newAPVTSstate = newPluginState.getChildWithName("APVTS");
            auto newInternalState = newPluginState.getChildWithName("InternalState");

            // Refresh atomics immediately (safe, even on worker thread)
            refreshAtomicsFromTree(newInternalState);

            // Schedule ValueTree replacements safely on the message thread
            juce::MessageManager::callAsync([this, apvtsStateCopy = newAPVTSstate, internalStateCopy = newInternalState]() mutable
            {
                if (apvtsStateCopy.isValid())
                    apvts.replaceState(apvtsStateCopy);

                if (internalStateCopy.isValid())
                    internalState.copyPropertiesAndChildrenFrom(internalStateCopy, nullptr);
            });
        }
    }
}




More problems?

But if plugin editor can modify the value tree, and some other worker thread in a daw like GarageBand can come along and call getStateInformation, can’t we run into a memory problem? I think so, imagine the GUI thread is writing to it while this worker thread tried to get a copy. So we re-make a tree from the atomics.

void PluginProcessor::getStateInformation(juce::MemoryBlock& destData)
{
    juce::ValueTree tree ("InternalState");

    // Add the 
    tree.setProperty(InternalStateID::isMuteToggleActive, getInternalBool(InternalStateID::isMuteToggleActive), nullptr);
    // etc. for all internal properties

    // Add the apvts directly here since it's safe to read
    // ....

    // Turn into xml
    // ....
    
    // If valid, convert the XML into a binary blob for the host.
    if (stateXml != nullptr){
        copyXmlToBinary(*stateXml, destData);
    }   
}

So this is a rough sketch of what I believe Juce is missing. A realtime thread safe value tree without locks

InternalStateManager.h

#pragma once

#include <JuceHeader.h>
#include <unordered_map>

class InternalStateManager
{
public:
    InternalStateManager(juce::ValueTree initialTree);

    // Registration API
    void registerBool(const juce::Identifier& propertyID, bool defaultValue);
    void registerFloat(const juce::Identifier& propertyID, float defaultValue);
    void registerInt(const juce::Identifier& propertyID, int defaultValue);

    // Write API
    void setBool(const juce::Identifier& propertyID, bool newValue);
    void setFloat(const juce::Identifier& propertyID, float newValue);
    void setInt(const juce::Identifier& propertyID, int newValue);

    // Read API
    bool getBool(const juce::Identifier& propertyID) const;
    float getFloat(const juce::Identifier& propertyID) const;
    int getInt(const juce::Identifier& propertyID) const;

    // Saving and restoring plugin state
    juce::ValueTree createSnapshotTree() const;
    void refreshFromTree(const juce::ValueTree& tree);

    // Access underlying ValueTree if needed (e.g., listeners)
    juce::ValueTree& getInternalStateTree();

private:
    juce::ValueTree internalState;

    std::unordered_map<juce::Identifier, std::atomic<bool>> boolAtomics;
    std::unordered_map<juce::Identifier, std::atomic<int>> intAtomics;
    std::unordered_map<juce::Identifier, std::atomic<float>> floatAtomics;

    // Internal async updater for ValueTree
    template <typename ValueType>
    void updateValueTreeAsync(const juce::Identifier& propertyID, ValueType newValue);
};


InternalStateManager.cpp

#include "InternalStateManager.h"

InternalStateManager::InternalStateManager(juce::ValueTree initialTree)
    : internalState(initialTree)
{
}

// --- Registration ---

void InternalStateManager::registerBool(const juce::Identifier& propertyID, bool defaultValue)
{
    boolAtomics.emplace(propertyID, defaultValue);

    if (!internalState.hasProperty(propertyID))
        internalState.setProperty(propertyID, defaultValue, nullptr);
}

void InternalStateManager::registerFloat(const juce::Identifier& propertyID, float defaultValue)
{
    floatAtomics.emplace(propertyID, defaultValue);

    if (!internalState.hasProperty(propertyID))
        internalState.setProperty(propertyID, defaultValue, nullptr);
}

void InternalStateManager::registerInt(const juce::Identifier& propertyID, int defaultValue)
{
    intAtomics.emplace(propertyID, defaultValue);

    if (!internalState.hasProperty(propertyID))
        internalState.setProperty(propertyID, defaultValue, nullptr);
}

// --- Write API ---

void InternalStateManager::setBool(const juce::Identifier& propertyID, bool newValue)
{
    if (auto it = boolAtomics.find(propertyID); it != boolAtomics.end())
        it->second.store(newValue);
    else
        jassertfalse;

    updateValueTreeAsync(propertyID, newValue);
}

void InternalStateManager::setFloat(const juce::Identifier& propertyID, float newValue)
{
    if (auto it = floatAtomics.find(propertyID); it != floatAtomics.end())
        it->second.store(newValue);
    else
        jassertfalse;

    updateValueTreeAsync(propertyID, newValue);
}

void InternalStateManager::setInt(const juce::Identifier& propertyID, int newValue)
{
    if (auto it = intAtomics.find(propertyID); it != intAtomics.end())
        it->second.store(newValue);
    else
        jassertfalse;

    updateValueTreeAsync(propertyID, newValue);
}

// --- Read API ---

bool InternalStateManager::getBool(const juce::Identifier& propertyID) const
{
    if (auto it = boolAtomics.find(propertyID); it != boolAtomics.end())
        return it->second.load();

    jassertfalse;
    return false;
}

float InternalStateManager::getFloat(const juce::Identifier& propertyID) const
{
    if (auto it = floatAtomics.find(propertyID); it != floatAtomics.end())
        return it->second.load();

    jassertfalse;
    return 0.0f;
}

int InternalStateManager::getInt(const juce::Identifier& propertyID) const
{
    if (auto it = intAtomics.find(propertyID); it != intAtomics.end())
        return it->second.load();

    jassertfalse;
    return 0;
}

// --- Saving/Loading State ---

juce::ValueTree InternalStateManager::createSnapshotTree() const
{
    juce::ValueTree snapshot(internalState.getType());

    for (const auto& [id, value] : boolAtomics)
        snapshot.setProperty(id, value.load(), nullptr);

    for (const auto& [id, value] : intAtomics)
        snapshot.setProperty(id, value.load(), nullptr);

    for (const auto& [id, value] : floatAtomics)
        snapshot.setProperty(id, value.load(), nullptr);

    return snapshot;
}

void InternalStateManager::refreshFromTree(const juce::ValueTree& tree)
{
    for (const auto& [id, _] : boolAtomics)
        boolAtomics[id].store(static_cast<bool>(tree.getProperty(id, false)));

    for (const auto& [id, _] : intAtomics)
        intAtomics[id].store(static_cast<int>(tree.getProperty(id, 0)));

    for (const auto& [id, _] : floatAtomics)
        floatAtomics[id].store(static_cast<float>(tree.getProperty(id, 0.0f)));
}

// --- Access Internal ValueTree ---

juce::ValueTree& InternalStateManager::getInternalStateTree()
{
    return internalState;
}

// --- Async ValueTree Update Helper ---

template <typename ValueType>
void InternalStateManager::updateValueTreeAsync(const juce::Identifier& propertyID, ValueType newValue)
{
    if (juce::MessageManager::isThisTheMessageThread())
    {
        internalState.setProperty(propertyID, newValue, nullptr);
    }
    else
    {
        juce::MessageManager::callAsync([tree = internalState, propertyID, newValue]() mutable
        {
            tree.setProperty(propertyID, newValue, nullptr);
        });
    }
}

// Explicit template instantiations (needed because updateValueTreeAsync is a template inside a .cpp file)
template void InternalStateManager::updateValueTreeAsync<bool>(const juce::Identifier&, bool);
template void InternalStateManager::updateValueTreeAsync<int>(const juce::Identifier&, int);
template void InternalStateManager::updateValueTreeAsync<float>(const juce::Identifier&, float);

Maybe I’m being foolish. This is what the APVTS was made for. It already has locks and all the getters and setters. Maybe the solution is simply to use a dummy processor to keep our non param state, and have it be a realtime safe value true that works across the GUI and Audio Thread.

    // Create a dummy processor to make a fake internal state which has built in atomics
    DummyProcessor dummy;
    auto layout = getDefaultParameterLayout();
    juce::AudioProcessorValueTreeState internalState(dummy, nullptr, "InternalState", std::move(layout));

Or maybe the correct way to keep a non param state variable is to hide it in our single APVTS?

layout.add (std::make_unique<juce::AudioParameterBool> (
    "isMuteToggleActive",      // unique ID
    "isMuteToggleActive",      // name
    false,
    juce::AudioParameterBoolAttributes()
        .withAutomatable (false)   // prevents host automation & enumeration
        .withMeta (true)));        // keeps the host’s parameter count clean