Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
parameter.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <atomic>
5#include <bit>
6#include <cctype>
7#include <cmath>
8#include <cstdint>
9#include <functional>
10#include <limits>
11#include <optional>
12#include <string>
13#include <string_view>
14#include <vector>
15
16namespace pulp::state {
17
23 float base = 0.0f;
24 float offset = 0.0f;
25
26 constexpr float effective() const noexcept {
27 return base + offset;
28 }
29 constexpr BaseOffsetValue without_offset() const noexcept {
30 return {base, 0.0f};
31 }
32};
33
40
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)
51}
52
55using ParamID = uint32_t;
56
66
79
107
132 float min = 0.0f;
133 float max = 1.0f;
134 float default_value = 0.0f;
135 float step = 0.0f;
136 float skew = 1.0f;
137 bool symmetric_skew = false;
138
142 static ParamRange linear(float lo, float hi, float def = 0.0f,
143 float step_value = 0.0f) {
144 return ParamRange{lo, hi, def, step_value, 1.0f, false};
145 }
146
151 static ParamRange with_center(float lo, float hi, float center,
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);
157 }
158 return r;
159 }
160
163 bool is_linear() const { return skew == 1.0f && !symmetric_skew; }
164
168 float normalize(float value) const {
169 if (max == min) return 0.0f;
170 // Bit-identical linear fast path — preserves legacy behavior exactly.
171 if (is_linear()) {
172 return std::clamp((value - min) / (max - min), 0.0f, 1.0f);
173 }
174 float proportion = std::clamp((value - min) / (max - min), 0.0f, 1.0f);
175 if (symmetric_skew) {
176 const float centerd = (proportion * 2.0f) - 1.0f; // [-1, 1]
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;
180 }
181 return std::pow(proportion, skew);
182 }
183
188 float denormalize(float normalized) const {
189 // Bit-identical linear fast path — preserves legacy behavior exactly.
190 if (is_linear()) {
191 auto value = min + normalized * (max - min);
192 if (step > 0.0f) {
193 value = min + std::round((value - min) / step) * step;
194 }
195 return std::clamp(value, min, max);
196 }
197 normalized = std::clamp(normalized, 0.0f, 1.0f);
198 float proportion;
199 if (symmetric_skew) {
200 const float centerd = (normalized * 2.0f) - 1.0f; // [-1, 1]
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;
204 } else {
205 proportion = std::pow(normalized, 1.0f / skew);
206 }
207 float value = min + proportion * (max - min);
208 if (step > 0.0f) {
209 value = min + std::round((value - min) / step) * step;
210 }
211 return std::clamp(value, min, max);
212 }
213};
214
237template<typename T = float>
239 T min = T(0);
240 T max = T(1);
241 T interval = T(0);
242 T skew = T(1);
243 bool symmetric_skew = false;
244
245 constexpr SkewedRange() = default;
246 constexpr SkewedRange(T lo, T hi, T step = T(0), T skew_value = T(1),
247 bool symmetric = false)
248 : min(lo), max(hi), interval(step),
249 skew(skew_value), symmetric_skew(symmetric) {}
250
254 static SkewedRange with_center(T lo, T hi, T center, T step = T(0)) {
255 SkewedRange r{lo, hi, step};
256 if (hi > lo && center > lo && center < hi) {
257 const T proportion = (center - lo) / (hi - lo);
258 // log(0.5) / log(proportion) gives the skew that lands `center` at 0.5.
259 r.skew = std::log(T(0.5)) / std::log(proportion);
260 }
261 return r;
262 }
263
269 T normalize(T value) const {
270 if (max == min) return T(0);
271 T proportion = (value - min) / (max - min);
272 proportion = std::clamp(proportion, T(0), T(1));
273 if (symmetric_skew) {
274 const T centerd = (proportion * T(2)) - T(1); // [-1, 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);
278 }
279 return std::pow(proportion, skew);
280 }
281
284 T denormalize(T normalized) const {
285 normalized = std::clamp(normalized, T(0), T(1));
286 T proportion;
287 if (symmetric_skew) {
288 const T centerd = (normalized * T(2)) - T(1); // [-1, 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);
292 } else {
293 proportion = std::pow(normalized, T(1) / skew);
294 }
295 T value = min + proportion * (max - min);
296 if (interval > T(0)) {
297 value = min + std::round((value - min) / interval) * interval;
298 }
299 return std::clamp(value, min, max);
300 }
301
303 T snap(T value) const {
304 if (interval <= T(0)) return std::clamp(value, min, max);
305 T snapped = min + std::round((value - min) / interval) * interval;
306 return std::clamp(snapped, min, max);
307 }
308};
309
310
316struct ParamInfo {
317 ParamID id = 0;
318 std::string name;
319 std::string unit;
321 int group_id = 0;
322
324 std::function<std::string(float)> to_string;
326 std::function<float(const std::string&)> from_string;
327
330
335
351 bool is_trigger = false;
352
356
360 std::vector<std::string> value_labels;
361
373 bool modulatable = true;
374
385 bool hidden = false;
386
397 bool read_only = false;
398
407 bool automatable = true;
408
412 bool auto_resets() const {
414 }
415};
416
417inline bool is_discrete_param(const ParamInfo& info) {
418 // Semantic kind is author-declared metadata. A continuous parameter may
419 // still use range.step to quantize its plain value without being exposed
420 // to hosts as an integer/indexed control.
421 return info.kind != ParamKind::Continuous;
422}
423
434inline float value_from_host_normalized(const ParamInfo& info, double normalized) {
435 const ParamRange& range = info.range;
436 if (is_discrete_param(info))
437 return range.denormalize(static_cast<float>(normalized));
438 if (!range.is_linear()) {
439 ParamRange exact = range;
440 exact.step = 0.0f;
441 return exact.denormalize(static_cast<float>(normalized));
442 }
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);
447}
448
453inline double host_normalized_from_value(const ParamInfo& info, float value) {
454 const ParamRange& range = info.range;
455 if (!range.is_linear() || range.max == range.min)
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,
459 1.0);
460}
461
462inline bool is_boolean_param(const ParamInfo& info) {
463 return info.kind == ParamKind::Toggle;
464}
465
466inline uint32_t param_value_count(const ParamInfo& info) {
467 if (!info.value_labels.empty())
468 return static_cast<uint32_t>(info.value_labels.size());
469 if (!is_discrete_param(info)) return 0;
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;
473}
474
475inline float constrain_param_value(const ParamInfo& info, float value) {
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
479 ? info.range.step
480 : (is_discrete_param(info) ? 1.0f : 0.0f);
481 if (step > 0.0f) {
482 value = info.range.min
483 + std::round((value - info.range.min) / step) * step;
484 }
485 return std::clamp(value, info.range.min, info.range.max);
486}
487
488inline std::optional<float> param_value_for_label(const ParamInfo& info,
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);
499 }
500 }
501 return std::nullopt;
502}
503
514inline bool is_bypass_param(const ParamInfo& info) {
515 if (info.designation == ParamDesignation::Bypass) return true;
516 if (info.designation != ParamDesignation::None) return false;
517 return info.name == "Bypass" && info.range.step >= 1.0f &&
518 info.range.min == 0.0f && info.range.max == 1.0f;
519}
520
537inline bool is_modulatable_param(const ParamInfo& info) {
538 if (!info.modulatable)
539 return false;
540 if (is_bypass_param(info))
541 return false;
542 if (is_discrete_param(info))
543 return false;
544 // A host may not offset a value it may not write. Without this the CLAP
545 // adapter would advertise READONLY and MODULATABLE on the same parameter.
546 if (info.read_only)
547 return false;
548 return !info.auto_resets();
549}
550
560inline bool is_hidden_param(const ParamInfo& info) {
561 if (!info.hidden)
562 return false;
563 return !is_bypass_param(info);
564}
565
574inline bool is_read_only_param(const ParamInfo& info) {
575 if (!info.read_only)
576 return false;
577 if (is_bypass_param(info))
578 return false;
579 return !info.auto_resets();
580}
581
590inline bool is_automatable_param(const ParamInfo& info) {
591 if (!info.automatable)
592 return false;
593 return !is_read_only_param(info);
594}
595
608 public:
612 using PackedIdentity = std::uint64_t;
613 using VersionedValue = PackedIdentity; // source compatibility alias
614
615 ParamValue() = default;
616
618 explicit ParamValue(float initial) : value_(pack(initial, 0)) {}
619
620 ParamValue(ParamValue&& other) noexcept
621 : value_(other.value_.load()),
622 committed_generation_(other.committed_generation_.load()) {}
623 ParamValue& operator=(ParamValue&& other) noexcept {
624 value_.store(other.value_.load());
625 committed_generation_.store(other.committed_generation_.load());
626 return *this;
627 }
628 ParamValue(const ParamValue& other)
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());
634 return *this;
635 }
636
638 float get() const { return unpack_value(value_.load(std::memory_order_relaxed)); }
639
642 void set(float v) noexcept {
643 (void)set_revisioned(v, 0, 8);
644 }
645
647 return value_.load(std::memory_order_relaxed);
648 }
649
655 std::uint64_t committed_generation() const {
656 return committed_generation_.load(std::memory_order_relaxed);
657 }
658
659 static VersionedValue make_versioned_value(float value, std::uint64_t revision) {
660 return pack(value, static_cast<std::uint32_t>(revision));
661 }
662
664 return unpack_value(value);
665 }
666
667 static std::uint32_t revision_from_versioned(VersionedValue value) {
668 return unpack_generation(value);
669 }
670
671 bool revision_available() const noexcept {
672 return revisions_available(1);
673 }
674
675 bool revisions_available(std::uint32_t writes) const noexcept {
676 const auto revision = revision_from_versioned(versioned_value());
677 return revision <= std::numeric_limits<std::uint32_t>::max() - writes;
678 }
679
683 bool set_versioned_if_newer(float value, std::uint64_t generation,
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)
688 return false;
689 if (committed_generation_.compare_exchange_strong(
690 committed, generation, std::memory_order_relaxed,
691 std::memory_order_relaxed))
692 break;
693 }
694 if (committed_generation_.load(std::memory_order_relaxed) != generation)
695 return false;
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)
699 return false;
700 const auto revision = unpack_generation(current);
701 if (revision == std::numeric_limits<std::uint32_t>::max())
702 return false;
703 if (value_.compare_exchange_weak(current, pack(value, revision + 1),
704 std::memory_order_acq_rel, std::memory_order_acquire))
705 return true;
706 }
707 return false;
708 }
709
719 bool set_packed_revisioned(float value, std::uint64_t generation,
720 std::uint32_t max_attempts) noexcept {
721 if (!claim_generation(generation, max_attempts))
722 return false;
723 return publish_after_generation_claim(value, generation, max_attempts);
724 }
725
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))
732 return false;
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);
736 }
737
742 bool compare_exchange_packed(VersionedValue& expected, float desired,
743 std::uint64_t generation) {
744 const auto current_revision = unpack_generation(expected);
745 if (current_revision == std::numeric_limits<std::uint32_t>::max())
746 return false;
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))
750 return false;
751 expected = replacement;
752 raise_committed_generation(generation);
753 return true;
754 }
755
758 float get_modulated() const {
759 return unpack_value(value_.load(std::memory_order_relaxed))
760 + unpack_value(mod_offset_.load(std::memory_order_relaxed));
761 }
762
763 float get_mod_offset() const {
764 return unpack_value(mod_offset_.load(std::memory_order_relaxed));
765 }
766
769 return {get(), get_mod_offset()};
770 }
771
773 void set_mod_offset(float offset) {
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);
777 }
778
779 bool set_mod_versioned_if_newer(float offset, std::uint64_t generation,
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)
784 return false;
785 if (committed_mod_generation_.compare_exchange_strong(
786 committed, generation, std::memory_order_relaxed,
787 std::memory_order_relaxed))
788 break;
789 }
790 if (committed_mod_generation_.load(std::memory_order_relaxed) != generation)
791 return false;
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)
796 return false;
797 if (mod_offset_.compare_exchange_strong(current, desired,
798 std::memory_order_relaxed,
799 std::memory_order_relaxed))
800 return true;
801 }
802 return false;
803 }
804
808 void add_mod_offset(float delta) {
810 }
811
813 void reset_mod() { set_mod_offset(0.0f); }
814
819 void set_display_modulation(float plain) noexcept {
820 display_mod_.store(plain, std::memory_order_relaxed);
821 }
822 void clear_display_modulation() noexcept {
823 display_mod_.store(std::numeric_limits<float>::quiet_NaN(), std::memory_order_relaxed);
824 }
825 float display_modulation() const noexcept {
826 return display_mod_.load(std::memory_order_relaxed);
827 }
828
830 float get_normalized(const ParamRange& range) const {
831 return range.normalize(get());
832 }
833
835 void set_normalized(float n, const ParamRange& range) {
836 set(range.denormalize(n));
837 }
838
839 private:
840 friend class ParamValueTest;
841
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)
846 return false;
847 if (committed_generation_.compare_exchange_strong(
848 committed, generation, std::memory_order_acq_rel, std::memory_order_acquire))
849 return true;
850 }
851 return false;
852 }
853
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)
857 return false;
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)
861 return false;
862 const auto revision = unpack_generation(current);
863 if (revision == std::numeric_limits<std::uint32_t>::max())
864 return false;
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))
868 return true;
869 }
870 return false;
871 }
872
873 // Raise-only update. Lowering the mark would revoke the claim of an
874 // ordinary writer that claimed a newer generation and has not published
875 // yet, and would let an older writer claim past it.
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)) {
881 }
882 }
883
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())
890 return false;
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);
895 return true;
896 }
897 }
898 return false;
899 }
900
901 static VersionedValue pack(float value, std::uint32_t generation) {
902 return (static_cast<VersionedValue>(generation) << 32) |
903 std::bit_cast<std::uint32_t>(value);
904 }
905
906 static float unpack_value(VersionedValue value) {
907 return std::bit_cast<float>(static_cast<std::uint32_t>(value));
908 }
909
910 static std::uint32_t unpack_generation(VersionedValue value) {
911 return static_cast<std::uint32_t>(value >> 32);
912 }
913
914 std::atomic<VersionedValue> value_{pack(0.0f, 0)};
915 // Generation high-water mark. Ordinary writers claim it before publishing
916 // (claim_generation) and abort publication if a newer claim replaced
917 // theirs; every other writer may only raise it. The packed value CAS
918 // remains the publication boundary.
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()};
923
924 // The packed base value/revision identity and modulation offset are read
925 // and written on the audio thread through lock-free atomics. Fail the build if a
926 // target would implement either with a hidden mutex. See
927 // docs/guides/dsp-threading.md "The three rules".
928 static_assert(std::atomic<VersionedValue>::is_always_lock_free,
929 "Pulp requires lock-free atomic<uint64_t> for versioned real-time "
930 "parameter access");
931};
932
936using ParamChangeCallback = std::function<void(ParamID id, float new_value)>;
937
938} // namespace pulp::state
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