Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::format::aax::ParameterMirror Class Reference

Writes editor-store changes back to the AAX parameter manager without the two mirrors chasing each other. More...

#include <aax_editor.hpp>

Public Types

using ValueReader = std::function<bool(const char* aax_param_id, float& out_value)>
 Reads the value the AAX parameter manager currently holds.
using ValueWriter = std::function<bool(const char* aax_param_id, float value)>
 Requests value for aax_param_id — AAX_IParameter::SetValueWithFloat.

Public Member Functions

 ParameterMirror (const PluginDefinition &definition, ValueReader reader, ValueWriter writer)
bool on_store_value_changed (state::ParamID id, float store_value)
 Editor store → AAX parameter manager.
std::uint64_t suppressed_count () const
 Echoes absorbed.
std::uint64_t refused_count () const
 Corrective writes the fuse refused.
bool is_fused (state::ParamID id) const
 Whether id's fuse is currently open, i.e.

Static Public Attributes

static constexpr std::uint32_t kCorrectionFuseLimit = 8
 Consecutive corrective writes of the same store value before the fuse opens on a parameter.

Detailed Description

Writes editor-store changes back to the AAX parameter manager without the two mirrors chasing each other.

The manager is the value authority; the editor's store is a mirror. Both directions are live at once, so the write-back needs a rule that terminates:

  • editor edit: the store changes, AAX still holds the old value, the write goes through. AAX_IParameter::SetValueWithFloat does not store the value — it asks the host for it, and the value arrives later through UpdateParameterNormalizedValue, which is also what a fader move or a control surface produces. The editor's own edits therefore come back to the model on exactly the host path an external change takes.
  • that return leg: the parameter manager is updated before the model mirrors the value into the store, so by the time this rule runs AAX already holds the settled value and nothing more is sent. The loop closes here.

The rule is a statement about the two values rather than an "I am syncing" flag, so it holds wherever and whenever the store listener lands. It is load-bearing rather than a mere optimization: StateStore::set_value() notifies unconditionally, even when the value did not change, so the echo has no fixed point of its own and would ping-pong at the host's dispatch rate.

The two sides are not in the same coordinate system, so the rule cannot compare them directly. Both legs are lossy, in different ways:

  • AAX's value is NormalizedToReal(...) of whatever normalized value the host last sent. It snaps to ParamRange::step only when a step is set.
  • the store's value has additionally been through constrain_stored_value, which applies an implicit step of 1 to any non-Continuous ParamKind even when the range declares no step.

So for, say, an Integer parameter with no explicit step, the two are structurally different values and aax_value == store_value is never true. Nor does writing the store's value fix that: SetValueWithFloat does not store what it is given (see above), and denormalize(normalize(k)) != k in float for many k, so the correction lands somewhere else again and the next echo re-triggers it. That is an unbounded automation write stream, not a mirror.

The rule therefore never compares the two reals. It asks whether there is anything for a write to do, which is two separate questions — both needed, neither implying the other:

  • Do they already agree? A write does not send a real value anywhere: SetValue posts RealToNormalized(value) and the host's answer comes back normalized. So if the normalized value this would post is the one the host already has, posting it cannot change anything, whatever the two reals look like. This is what makes an echo die, and what keeps a skewed range from chasing its own rounding: the store's extra constraint and the taper's float error both wash out of the normalized domain.
  • Would the write move AAX at all? For a range whose real grid and normalized grid do not line up — a discrete kind over a wide skewed range is the case that bites — the two sides can disagree in normalized space and yet a write lands AAX exactly back where it already is. Those values are a genuine standoff: the store will not yield its quantum and AAX cannot represent it. Writing forever is the one option that helps nobody, so the mirror recognizes the no-op and stops.

Exact float comparison is right here rather than an epsilon: the taper is a pure function of the range, so two values that normalize to the same float are the same parameter value as far as AAX is concerned. An epsilon would only invent a second, arbitrary notion of "close enough" on top of the one AAX already defines.

Free of AAX types: the manager is reached through two sinks, so the whole contract is exercised by tests that never see the SDK.

Member Typedef Documentation

◆ ValueReader

using pulp::format::aax::ParameterMirror::ValueReader = std::function<bool(const char* aax_param_id, float& out_value)>

Reads the value the AAX parameter manager currently holds.

Returns false — leaving out_value untouched — when the value could not be read.

◆ ValueWriter

using pulp::format::aax::ParameterMirror::ValueWriter = std::function<bool(const char* aax_param_id, float value)>

Requests value for aax_param_id — AAX_IParameter::SetValueWithFloat.

Returns false when the request was refused.

Constructor & Destructor Documentation

◆ ParameterMirror()

pulp::format::aax::ParameterMirror::ParameterMirror ( const PluginDefinition & definition,
ValueReader reader,
ValueWriter writer )
inline

Member Function Documentation

◆ is_fused()

bool pulp::format::aax::ParameterMirror::is_fused ( state::ParamID id) const
inline

Whether id's fuse is currently open, i.e.

the next repeat of the same value would be refused.

◆ on_store_value_changed()

bool pulp::format::aax::ParameterMirror::on_store_value_changed ( state::ParamID id,
float store_value )
inline

Editor store → AAX parameter manager.

Wire to a StateStore value listener. Returns true when a write was issued.

◆ refused_count()

std::uint64_t pulp::format::aax::ParameterMirror::refused_count ( ) const
inline

Corrective writes the fuse refused.

Non-zero means some parameter's round trip does not converge — a bug here or a host quirk, never routine.

◆ suppressed_count()

std::uint64_t pulp::format::aax::ParameterMirror::suppressed_count ( ) const
inline

Echoes absorbed.

Non-zero is the healthy case — it counts the write-backs the rule swallowed rather than bouncing at the host.

Member Data Documentation

◆ kCorrectionFuseLimit

std::uint32_t pulp::format::aax::ParameterMirror::kCorrectionFuseLimit = 8
staticconstexpr

Consecutive corrective writes of the same store value before the fuse opens on a parameter.

A converging correction takes exactly one; a knob drag carries a new value each time and resets the count. Only a value that is written, echoed, and written again unchanged accumulates — which is precisely the shape of a mirror that is not converging.


The documentation for this class was generated from the following file:
  • /home/runner/work/pulp/pulp/core/format/include/pulp/format/aax_editor.hpp