42 float maximum)
noexcept {
43 if (!std::isfinite(value.base))
45 if (!std::isfinite(value.offset))
47 if (!std::isfinite(minimum) || !std::isfinite(maximum) || minimum > maximum ||
48 value.effective() < minimum || value.effective() > maximum)
143 float step_value = 0.0f) {
144 return ParamRange{lo, hi, def, step_value, 1.0f,
false};
152 float def = 0.0f,
float step_value = 0.0f) {
153 ParamRange r{lo, hi, def, step_value, 1.0f,
false};
154 if (hi > lo && center > lo && center < hi) {
155 const float proportion = (center - lo) / (hi - lo);
156 r.skew = std::log(0.5f) / std::log(proportion);
169 if (
max ==
min)
return 0.0f;
172 return std::clamp((value -
min) / (
max -
min), 0.0f, 1.0f);
174 float proportion = std::clamp((value -
min) / (
max -
min), 0.0f, 1.0f);
176 const float centerd = (proportion * 2.0f) - 1.0f;
177 const float magnitude = std::pow(std::abs(centerd),
skew);
178 const float signed_mag = centerd < 0.0f ? -magnitude : magnitude;
179 return (signed_mag + 1.0f) * 0.5f;
181 return std::pow(proportion,
skew);
191 auto value =
min + normalized * (
max -
min);
195 return std::clamp(value,
min,
max);
197 normalized = std::clamp(normalized, 0.0f, 1.0f);
200 const float centerd = (normalized * 2.0f) - 1.0f;
201 const float magnitude = std::pow(std::abs(centerd), 1.0f /
skew);
202 const float signed_mag = centerd < 0.0f ? -magnitude : magnitude;
203 proportion = (signed_mag + 1.0f) * 0.5f;
205 proportion = std::pow(normalized, 1.0f /
skew);
207 float value =
min + proportion * (
max -
min);
211 return std::clamp(value,
min,
max);
237template<
typename T =
float>
246 constexpr SkewedRange(T lo, T hi, T step = T(0), T skew_value = T(1),
247 bool symmetric =
false)
256 if (hi > lo && center > lo && center < hi) {
257 const T proportion = (center - lo) / (hi - lo);
259 r.skew = std::log(T(0.5)) / std::log(proportion);
270 if (
max ==
min)
return T(0);
271 T proportion = (value -
min) / (
max -
min);
272 proportion = std::clamp(proportion, T(0), T(1));
274 const T centerd = (proportion * T(2)) - T(1);
275 const T magnitude = std::pow(std::abs(centerd),
skew);
276 const T signed_mag = centerd < T(0) ? -magnitude : magnitude;
277 return (signed_mag + T(1)) * T(0.5);
279 return std::pow(proportion,
skew);
285 normalized = std::clamp(normalized, T(0), T(1));
288 const T centerd = (normalized * T(2)) - T(1);
289 const T magnitude = std::pow(std::abs(centerd), T(1) /
skew);
290 const T signed_mag = centerd < T(0) ? -magnitude : magnitude;
291 proportion = (signed_mag + T(1)) * T(0.5);
293 proportion = std::pow(normalized, T(1) /
skew);
295 T value =
min + proportion * (
max -
min);
299 return std::clamp(value,
min,
max);
306 return std::clamp(snapped,
min,
max);
437 return range.
denormalize(
static_cast<float>(normalized));
441 return exact.denormalize(
static_cast<float>(normalized));
443 const double n = std::clamp(normalized, 0.0, 1.0);
444 const double plain =
static_cast<double>(range.
min) +
445 n * (
static_cast<double>(range.
max) -
static_cast<double>(range.
min));
446 return std::clamp(
static_cast<float>(plain), range.
min, range.
max);
456 return static_cast<double>(range.
normalize(value));
457 const double span =
static_cast<double>(range.
max) -
static_cast<double>(range.
min);
458 return std::clamp((
static_cast<double>(value) -
static_cast<double>(range.
min)) / span, 0.0,
467 if (!info.value_labels.empty())
468 return static_cast<uint32_t
>(info.value_labels.size());
470 const float step = info.range.step > 0.0f ? info.range.step : 1.0f;
471 const float span = std::max(0.0f, info.range.max - info.range.min);
472 return static_cast<uint32_t
>(std::llround(span / step)) + 1u;
476 if (!std::isfinite(value))
return info.range.default_value;
477 value = std::clamp(value, info.range.min, info.range.max);
478 const float step = info.range.step > 0.0f
482 value = info.range.min
483 + std::round((value - info.range.min) / step) * step;
485 return std::clamp(value, info.range.min, info.range.max);
489 std::string_view text) {
490 while (!text.empty() && std::isspace(
static_cast<unsigned char>(text.front())))
491 text.remove_prefix(1);
492 while (!text.empty() && std::isspace(
static_cast<unsigned char>(text.back())))
493 text.remove_suffix(1);
494 const float step = info.range.step > 0.0f ? info.range.step : 1.0f;
495 for (std::size_t i = 0; i < info.value_labels.size(); ++i) {
496 if (text == info.value_labels[i]) {
498 info, info.range.min +
static_cast<float>(i) * step);
517 return info.name ==
"Bypass" && info.range.step >= 1.0f &&
518 info.range.min == 0.0f && info.range.max == 1.0f;
538 if (!info.modulatable)
548 return !info.auto_resets();
579 return !info.auto_resets();
591 if (!info.automatable)
618 explicit ParamValue(
float initial) : value_(pack(initial, 0)) {}
621 : value_(other.value_.load()),
622 committed_generation_(other.committed_generation_.load()) {}
624 value_.store(other.value_.load());
625 committed_generation_.store(other.committed_generation_.load());
629 : value_(other.value_.load()),
630 committed_generation_(other.committed_generation_.load()) {}
632 value_.store(other.value_.load());
633 committed_generation_.store(other.committed_generation_.load());
638 float get()
const {
return unpack_value(value_.load(std::memory_order_relaxed)); }
642 void set(
float v)
noexcept {
643 (void)set_revisioned(v, 0, 8);
647 return value_.load(std::memory_order_relaxed);
656 return committed_generation_.load(std::memory_order_relaxed);
660 return pack(value,
static_cast<std::uint32_t
>(revision));
664 return unpack_value(value);
668 return unpack_generation(value);
677 return revision <= std::numeric_limits<std::uint32_t>::max() - writes;
684 std::uint32_t max_attempts) {
685 auto committed = committed_generation_.load(std::memory_order_relaxed);
686 for (std::uint32_t attempt = 0; attempt < max_attempts; ++attempt) {
687 if (committed >= generation)
689 if (committed_generation_.compare_exchange_strong(
690 committed, generation, std::memory_order_relaxed,
691 std::memory_order_relaxed))
694 if (committed_generation_.load(std::memory_order_relaxed) != generation)
696 auto current = value_.load(std::memory_order_acquire);
697 for (std::uint32_t attempt = 0; attempt < max_attempts; ++attempt) {
698 if (committed_generation_.load(std::memory_order_relaxed) != generation)
700 const auto revision = unpack_generation(current);
701 if (revision == std::numeric_limits<std::uint32_t>::max())
703 if (value_.compare_exchange_weak(current, pack(value, revision + 1),
704 std::memory_order_acq_rel, std::memory_order_acquire))
720 std::uint32_t max_attempts)
noexcept {
721 if (!claim_generation(generation, max_attempts))
723 return publish_after_generation_claim(value, generation, max_attempts);
727 std::uint64_t expected_generation,
728 float desired, std::uint64_t generation) {
729 if (!committed_generation_.compare_exchange_strong(
730 expected_generation, generation, std::memory_order_relaxed,
731 std::memory_order_relaxed))
733 return value_.compare_exchange_strong(
734 expected, pack(desired,
static_cast<std::uint32_t
>(generation)),
735 std::memory_order_relaxed, std::memory_order_relaxed);
743 std::uint64_t generation) {
744 const auto current_revision = unpack_generation(expected);
745 if (current_revision == std::numeric_limits<std::uint32_t>::max())
747 const auto replacement = pack(desired, current_revision + 1);
748 if (!value_.compare_exchange_strong(expected, replacement, std::memory_order_acq_rel,
749 std::memory_order_acquire))
751 expected = replacement;
752 raise_committed_generation(generation);
759 return unpack_value(value_.load(std::memory_order_relaxed))
760 + unpack_value(mod_offset_.load(std::memory_order_relaxed));
764 return unpack_value(mod_offset_.load(std::memory_order_relaxed));
774 const auto current = mod_offset_.load(std::memory_order_relaxed);
775 mod_offset_.store(pack(offset, unpack_generation(current)),
776 std::memory_order_relaxed);
780 std::uint32_t max_attempts) {
781 auto committed = committed_mod_generation_.load(std::memory_order_relaxed);
782 for (std::uint32_t attempt = 0; attempt < max_attempts; ++attempt) {
783 if (committed >= generation)
785 if (committed_mod_generation_.compare_exchange_strong(
786 committed, generation, std::memory_order_relaxed,
787 std::memory_order_relaxed))
790 if (committed_mod_generation_.load(std::memory_order_relaxed) != generation)
792 auto current = mod_offset_.load(std::memory_order_relaxed);
793 const auto desired = pack(offset,
static_cast<std::uint32_t
>(generation));
794 for (std::uint32_t attempt = 0; attempt < max_attempts; ++attempt) {
795 if (committed_mod_generation_.load(std::memory_order_relaxed) != generation)
797 if (mod_offset_.compare_exchange_strong(current, desired,
798 std::memory_order_relaxed,
799 std::memory_order_relaxed))
820 display_mod_.store(plain, std::memory_order_relaxed);
823 display_mod_.store(std::numeric_limits<float>::quiet_NaN(), std::memory_order_relaxed);
826 return display_mod_.load(std::memory_order_relaxed);
842 bool claim_generation(std::uint64_t generation, std::uint32_t max_attempts)
noexcept {
843 auto committed = committed_generation_.load(std::memory_order_relaxed);
844 for (std::uint32_t attempt = 0; attempt < max_attempts; ++attempt) {
845 if (committed >= generation)
847 if (committed_generation_.compare_exchange_strong(
848 committed, generation, std::memory_order_acq_rel, std::memory_order_acquire))
854 bool publish_after_generation_claim(
float value, std::uint64_t generation,
855 std::uint32_t max_attempts)
noexcept {
856 if (committed_generation_.load(std::memory_order_acquire) != generation)
858 auto current = value_.load(std::memory_order_acquire);
859 for (std::uint32_t attempt = 0; attempt < max_attempts; ++attempt) {
860 if (committed_generation_.load(std::memory_order_acquire) != generation)
862 const auto revision = unpack_generation(current);
863 if (revision == std::numeric_limits<std::uint32_t>::max())
865 const auto replacement = pack(value, revision + 1);
866 if (value_.compare_exchange_weak(current, replacement, std::memory_order_acq_rel,
867 std::memory_order_acquire))
876 void raise_committed_generation(std::uint64_t generation)
noexcept {
877 auto committed = committed_generation_.load(std::memory_order_relaxed);
878 while (committed < generation &&
879 !committed_generation_.compare_exchange_weak(
880 committed, generation, std::memory_order_acq_rel, std::memory_order_relaxed)) {
884 bool set_revisioned(
float value, std::uint64_t generation,
885 std::uint32_t max_attempts)
noexcept {
886 auto current = value_.load(std::memory_order_acquire);
887 for (std::uint32_t attempt = 0; attempt < max_attempts; ++attempt) {
888 const auto revision = unpack_generation(current);
889 if (revision == std::numeric_limits<std::uint32_t>::max())
891 const auto replacement = pack(value, revision + 1);
892 if (value_.compare_exchange_weak(current, replacement, std::memory_order_acq_rel,
893 std::memory_order_acquire)) {
894 raise_committed_generation(generation);
901 static VersionedValue pack(
float value, std::uint32_t generation) {
903 std::bit_cast<std::uint32_t>(value);
907 return std::bit_cast<float>(
static_cast<std::uint32_t
>(value));
911 return static_cast<std::uint32_t
>(
value >> 32);
914 std::atomic<VersionedValue> value_{pack(0.0f, 0)};
919 std::atomic<std::uint64_t> committed_generation_{0};
920 std::atomic<VersionedValue> mod_offset_{pack(0.0f, 0)};
921 std::atomic<std::uint64_t> committed_mod_generation_{0};
922 std::atomic<float> display_mod_{std::numeric_limits<float>::quiet_NaN()};
928 static_assert(std::atomic<VersionedValue>::is_always_lock_free,
929 "Pulp requires lock-free atomic<uint64_t> for versioned real-time "
BaseOffsetValue get_base_offset() const noexcept
Read the explicit base-vs-offset pair used by every automation route.
Definition parameter.hpp:768
bool revision_available() const noexcept
Definition parameter.hpp:671
bool set_mod_versioned_if_newer(float offset, std::uint64_t generation, std::uint32_t max_attempts)
Definition parameter.hpp:779
void reset_mod()
Clear the modulation offset to zero.
Definition parameter.hpp:813
ParamValue & operator=(ParamValue &&other) noexcept
Definition parameter.hpp:623
static float value_from_versioned(VersionedValue value)
Definition parameter.hpp:663
bool set_versioned_if_newer(float value, std::uint64_t generation, std::uint32_t max_attempts)
Legacy generation-ordered writer retained for source compatibility.
Definition parameter.hpp:683
void set(float v) noexcept
Write a new base value through the same bounded revision CAS as every other base-value writer.
Definition parameter.hpp:642
std::uint64_t PackedIdentity
Packed base identity: low 32 bits are float bits, high 32 bits are a monotonic per-parameter revision...
Definition parameter.hpp:612
ParamValue(float initial)
Definition parameter.hpp:618
ParamValue(ParamValue &&other) noexcept
Definition parameter.hpp:620
float get() const
Read the current base value (lock-free, relaxed ordering).
Definition parameter.hpp:638
std::uint64_t committed_generation() const
Highest StateStore generation claimed or committed by a write to this parameter.
Definition parameter.hpp:655
VersionedValue versioned_value() const
Definition parameter.hpp:646
bool compare_exchange_versioned(VersionedValue &expected, std::uint64_t expected_generation, float desired, std::uint64_t generation)
Definition parameter.hpp:726
bool set_packed_revisioned(float value, std::uint64_t generation, std::uint32_t max_attempts) noexcept
StateStore's ordinary base-value publication boundary.
Definition parameter.hpp:719
void add_mod_offset(float delta)
Add a delta to the current modulation offset (for stacking modulators).
Definition parameter.hpp:808
static std::uint32_t revision_from_versioned(VersionedValue value)
Definition parameter.hpp:667
float display_modulation() const noexcept
Definition parameter.hpp:825
void set_mod_offset(float offset)
Set the absolute modulation offset (replaces any existing offset).
Definition parameter.hpp:773
bool revisions_available(std::uint32_t writes) const noexcept
Definition parameter.hpp:675
void clear_display_modulation() noexcept
Definition parameter.hpp:822
float get_normalized(const ParamRange &range) const
Read the current value mapped to [0, 1] via the given range.
Definition parameter.hpp:830
float get_modulated() const
Read the modulated value: base + mod_offset.
Definition parameter.hpp:758
ParamValue(const ParamValue &other)
Definition parameter.hpp:628
void set_display_modulation(float plain) noexcept
The value a plugin's OWN modulator is playing for this parameter, in plain units, published for displ...
Definition parameter.hpp:819
PackedIdentity VersionedValue
Definition parameter.hpp:613
bool compare_exchange_packed(VersionedValue &expected, float desired, std::uint64_t generation)
Conditional feedback publication boundary.
Definition parameter.hpp:742
float get_mod_offset() const
Definition parameter.hpp:763
void set_normalized(float n, const ParamRange &range)
Write a normalized [0, 1] value, denormalized via the given range.
Definition parameter.hpp:835
friend class ParamValueTest
Definition parameter.hpp:840
static VersionedValue make_versioned_value(float value, std::uint64_t revision)
Definition parameter.hpp:659
ParamValue & operator=(const ParamValue &other)
Definition parameter.hpp:631
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
ParamRate
Parameter update-rate classification.
Definition parameter.hpp:62
@ AudioRate
Definition parameter.hpp:64
@ ControlRate
Definition parameter.hpp:63
bool is_bypass_param(const ParamInfo &info)
Decide whether info is a bypass control.
Definition parameter.hpp:514
ParamDesignation
Declared semantic role of a parameter.
Definition parameter.hpp:102
@ Reset
Definition parameter.hpp:105
@ Bypass
Definition parameter.hpp:104
@ None
Definition parameter.hpp:103
bool is_modulatable_param(const ParamInfo &info)
Decide whether a host may apply a modulation offset to info.
Definition parameter.hpp:537
@ None
Definition modulation_lane.hpp:54
bool is_hidden_param(const ParamInfo &info)
Whether the host should omit this parameter from its generic parameter list and automation lanes.
Definition parameter.hpp:560
std::function< void(ParamID id, float new_value)> ParamChangeCallback
Callback signature for parameter change notifications.
Definition parameter.hpp:936
ParamKind
Author-declared parameter semantics.
Definition parameter.hpp:73
@ Continuous
Definition parameter.hpp:74
@ Integer
Definition parameter.hpp:75
@ Toggle
Definition parameter.hpp:76
@ Enum
Definition parameter.hpp:77
bool is_discrete_param(const ParamInfo &info)
Definition parameter.hpp:417
float value_from_host_normalized(const ParamInfo &info, double normalized)
The plain value a host means by a normalized write.
Definition parameter.hpp:434
double host_normalized_from_value(const ParamInfo &info, float value)
The normalized value to report to a host for value: the inverse of value_from_host_normalized(),...
Definition parameter.hpp:453
bool is_boolean_param(const ParamInfo &info)
Definition parameter.hpp:462
uint32_t param_value_count(const ParamInfo &info)
Definition parameter.hpp:466
float constrain_param_value(const ParamInfo &info, float value)
Definition parameter.hpp:475
std::optional< float > param_value_for_label(const ParamInfo &info, std::string_view text)
Definition parameter.hpp:488
BaseOffsetRefusal validate_base_offset(BaseOffsetValue value, float minimum, float maximum) noexcept
Definition parameter.hpp:41
BaseOffsetRefusal
Definition parameter.hpp:34
@ NonFiniteBase
Definition parameter.hpp:36
@ OutOfRange
Definition parameter.hpp:38
@ None
Definition parameter.hpp:35
@ NonFiniteOffset
Definition parameter.hpp:37
uint32_t ParamID
Unique parameter identifier, stable across plugin versions.
Definition parameter.hpp:55
bool is_read_only_param(const ParamInfo &info)
Whether the host may read this parameter but must not write it.
Definition parameter.hpp:574
bool is_automatable_param(const ParamInfo &info)
Whether the host may record and play back automation for this parameter.
Definition parameter.hpp:590
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
Canonical automation value shared by graph, Processor, and timeline delivery.
Definition parameter.hpp:22
constexpr float effective() const noexcept
Definition parameter.hpp:26
float base
Definition parameter.hpp:23
float offset
Definition parameter.hpp:24
constexpr BaseOffsetValue without_offset() const noexcept
Definition parameter.hpp:29
Immutable parameter metadata, registered once at plugin initialization.
Definition parameter.hpp:316
std::function< std::string(float)> to_string
Convert a raw value to a display string (e.g., "440.0 Hz").
Definition parameter.hpp:324
bool hidden
Hide this parameter from the host's generic parameter list.
Definition parameter.hpp:385
bool auto_resets() const
True when this parameter should auto-reset to its default after each process block — i....
Definition parameter.hpp:412
ParamDesignation designation
Declared semantic role.
Definition parameter.hpp:334
int group_id
Group for hierarchical organization. 0 = ungrouped.
Definition parameter.hpp:321
bool read_only
Advertise this parameter as readable but not host-writable.
Definition parameter.hpp:397
ParamKind kind
Semantic value kind.
Definition parameter.hpp:355
std::function< float(const std::string &)> from_string
Parse a display string back to a raw value.
Definition parameter.hpp:326
std::string unit
Display unit: "dB", "Hz", "%", etc.
Definition parameter.hpp:319
bool is_trigger
Momentary "trigger" parameter.
Definition parameter.hpp:351
float smoothing_ramp_seconds
0 = off / immediate control-rate updates.
Definition parameter.hpp:329
bool automatable
Whether the host may record and play back automation for this parameter.
Definition parameter.hpp:407
bool modulatable
Whether a host may apply a modulation offset to this parameter.
Definition parameter.hpp:373
ParamRate rate
Definition parameter.hpp:328
ParamRange range
Definition parameter.hpp:320
std::string name
Definition parameter.hpp:318
std::vector< std::string > value_labels
Ordered display/parse labels for Toggle and Enum parameters.
Definition parameter.hpp:360
Defines the numeric range of a parameter, including normalization.
Definition parameter.hpp:131
bool is_linear() const
True when this range is a plain affine (linear) map — the common case.
Definition parameter.hpp:163
float min
Definition parameter.hpp:132
float denormalize(float normalized) const
Map a normalized [0, 1] value back to the real range.
Definition parameter.hpp:188
static ParamRange with_center(float lo, float hi, float center, float def=0.0f, float step_value=0.0f)
Build a shaped range whose normalized midpoint (0.5) maps to center.
Definition parameter.hpp:151
float skew
1 = linear, <1 weights min, >1 weights max.
Definition parameter.hpp:136
bool symmetric_skew
Mirror the curve around the center (bipolar).
Definition parameter.hpp:137
float max
Definition parameter.hpp:133
float normalize(float value) const
Map a real value to the normalized [0, 1] range.
Definition parameter.hpp:168
float default_value
Definition parameter.hpp:134
static ParamRange linear(float lo, float hi, float def=0.0f, float step_value=0.0f)
Build a linear range covering [lo, hi] with the given default and step.
Definition parameter.hpp:142
float step
Quantization step. 0 = continuous.
Definition parameter.hpp:135
T snap(T value) const
Round a real value down to the nearest legal step.
Definition parameter.hpp:303
constexpr SkewedRange()=default
T denormalize(T normalized) const
Map a normalized [0, 1] value back to the real range.
Definition parameter.hpp:284
T skew
1 = linear, <1 weights min, >1 weights max.
Definition parameter.hpp:242
T min
Definition parameter.hpp:239
bool symmetric_skew
Definition parameter.hpp:243
T max
Definition parameter.hpp:240
T interval
Step size. 0 = continuous.
Definition parameter.hpp:241
static SkewedRange with_center(T lo, T hi, T center, T step=T(0))
Build a SkewedRange whose normalized midpoint maps to the supplied real-valued center.
Definition parameter.hpp:254
T normalize(T value) const
Map a real value in [min, max] to a normalized value in [0, 1].
Definition parameter.hpp:269
constexpr SkewedRange(T lo, T hi, T step=T(0), T skew_value=T(1), bool symmetric=false)
Definition parameter.hpp:246