Search guides and the APICtrl K
  1. API reference
  2. Plugin model

value_tree_state.h

AudioProcessorValueTreeState: the reference's parameter object, built on this SDK's own parameters so ported code has somewhere to land.

#include <sonore/value_tree_state.h>
On this page

Design notes

Why it exists, when the pieces already did

The reference's APVTS bundles four jobs: the parameter list, state beyond the parameters, undo, and binding widgets to values. This SDK has all four as separate things -- ParamInfo, StateBag, UndoHistory, and the attachments in gfx/plugin_editor.h -- and still does: a plugin that wants parameters without undo takes what it needs. What it lacked was the SHAPE. A processor ported from the reference builds a ParameterLayout of AudioParameterFloat and AudioParameterChoice, reads getRawParameterValue() in its process call, listens with parameterChanged(), saves copyState(), and binds sliders with SliderAttachment. Every one of those lines had to be rewritten by hand, and a hand rewrite of a parameter table is where a range or a default goes quietly wrong. This is that shape, made of the existing pieces, so those lines change their namespace and nothing else.

How it maps onto the real parameters

Every parameter in the layout produces a ParamInfo, and parameterInfos() is the table a PluginDescriptor points at -- the same order, so the DSP's p[i] is layout entry i. Ranges, skew, defaults, steps and value names go across exactly; text conversion defaults to formatParamValue and parseParamValue, the functions every wrapper and the host's own text boxes already use, so the knob, the automation lane and the host's generic panel cannot disagree. A layout entry with a curve ParamInfo cannot express -- a symmetric skew -- is reported by layoutProblem() rather than silently drawn linear by every host.

The threads, which is the part that matters

The value of each parameter is one std::atomic<float>, the one getRawParameterValue() returns. The audio thread READS it, and does nothing else that could wait: updateFromHost(), the one call a DSP makes on the audio thread, stores the block's host values into those atomics with relaxed stores and sets a bit in an atomic mask. No lock, no allocation, no listener. Everything else -- listeners, gestures, undo, state -- is the message thread's, and a change that arrived from the audio side reaches listeners when the message thread calls dispatchPendingChanges() (or syncFromHost(), which also does it) on its UI tick.

That is a deliberate difference from the reference, whose listeners are called on whichever thread changed the value, the audio thread included. A listener that repaints, allocates or locks was then running on the audio thread, and the reference's documentation spends a paragraph warning about it. Here a listener runs where it is safe to do anything, always.

Undo

Through UndoHistory, which stores whole states. A gesture -- a drag, a click on a switch, a typed value -- is one step, opened when the first gesture begins and recorded when the last one ends, and only if something actually changed: a click that moved nothing is not an undo step. Undoing pushes the restored values to the host inside gestures of their own, so the DAW's automation and the DSP follow the undo rather than the editor alone.

A step restores only the parameters the EDIT changed, by their values in the snapshot; the snapshot names them (under "@changed"). Everything else may have moved since for reasons that are not this edit -- automation on another control while the user dragged this one, a value the host set -- and restoring the whole snapshot put each back where it was and sent it to the host as an edit, which a DAW writing automation records over the lane. The non-parameter state is the same: a step puts it back only if an editState inside it changed it (marked "@state"). Something written to state directly is not undoable, and is not undone by undoing a knob either -- a tab the user moved to, a sample the plugin loaded since.

Listeners edit too -- a parameter linked to the one moved, a mode that resets a gain -- and what they do is part of whatever they answer. An edit a listener makes while another edit is under way joins that edit's step, as nested actions join one transaction in the reference, so one user action is one undo. While an undo or redo is being applied nothing is recorded: pushing there threw away the redo just made, so a plugin with one linked parameter could never redo anything. And a listener's write to a parameter the step itself restores is dropped -- the step says where that one goes; letting the listener answer a half-restored state undid a mode switch to the reset gain rather than the user's. Undo and redo are refused while an edit is under way, as they are while a gesture is open.

Types 9

template <typename ValueType> class NormalisableRange

A range with an optional step and a power-law skew: the reference's NormalisableRange, with its arithmetic. ParamInfo::skew is the same exponent, so a range with an ordinary skew maps onto a parameter exactly.

A parameter's id, with the version hint the reference attaches to it. The hint has no meaning in this SDK's formats -- the parameter's position and id are what a session keys on -- and is kept so ported code compiles.

One parameter of a layout. The value lives here, in the atomic the audio thread reads; the declaration lives in info(), the ParamInfo the wrappers read. Not copyable, because the APVTS and the audio thread hold its address.

class AudioParameterFloat : public RangedAudioParameter

A continuous control. The interval, when there is one, is applied to every value -- a UI edit and the host's automation alike.

class AudioParameterInt : public RangedAudioParameter

Whole numbers from min to max, each a step a host can offer.

class AudioParameterBool : public RangedAudioParameter

On or off. Its two positions are named "Off" and "On", so a host's lane, the generic editor and a toggle's label all say the same words.

class AudioParameterChoice : public RangedAudioParameter

One of a list of named choices; the value is the index.

A named group of parameters, nestable, as the reference builds them. Flattened into the layout in order; each parameter's ParamInfo::group becomes the path of group NAMES joined by the separator -- "Filter" or "Voice|Filter" -- which is what every format's grouping shows.

The parameters, their state, their listeners and their undo, as one object. See the file comment for the threads; in short, the audio thread calls getRawParameterValue() and updateFromHost() and nothing else.

↑↓ to move↵ to openEvery public name in the headers, and every guide