Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
parameter.h
1// SPDX-FileCopyrightText: © 2025-2026 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include "dsp/audio_port.h"
7#include "dsp/cv_port.h"
8#include "dsp/graph_node.h"
9#include "dsp/midi_port.h"
10#include "utils/math_utils.h"
11#include "utils/traits.h"
12#include "utils/units.h"
13#include "utils/uuid_identifiable_object.h"
14
15#include <QtQmlIntegration/qqmlintegration.h>
16
17#include <nlohmann/json_fwd.hpp>
18
19namespace zrythm::dsp
20{
21
22class ParameterRange
23{
24 Q_GADGET
25 QML_VALUE_TYPE (parameterRange)
26
27public:
28 enum class Type : std::uint8_t
29 {
34
41
44
52
55
58
66 };
67 Q_ENUM (Type)
68
69
72 enum class Unit : std::uint8_t
73 {
74 None,
75 Hz,
76 MHz,
77 Db,
78 Degrees,
79 Seconds,
80
83
86 };
87
88 static utils::Utf8String unit_to_string (Unit unit);
89
90public:
91 ParameterRange () = default;
92
93 ParameterRange (Type type, float min, float max, float zero = 0.f, float def = 0.f)
94 : type_ (type), minf_ (min), maxf_ (max),
95 zerof_ (std::clamp (zero, min, max)), deff_ (std::clamp (def, min, max))
96 {
97 }
98
99 static ParameterRange make_toggle (bool default_val)
100 {
101 return { Type::Toggle, 0.f, 1.f, 0.f, default_val ? 1.f : 0.f };
102 }
103 static ParameterRange make_gain (float max_val)
104 {
105 return { Type::GainAmplitude, 0.f, max_val, 0.f, 1.f };
106 }
107
108 static ParameterRange make_enumeration (
109 std::vector<utils::Utf8String> labels,
110 size_t default_index = 0)
111 {
112 if (labels.empty ())
113 {
114 throw std::invalid_argument (
115 "Enumeration parameter requires at least one label");
116 }
117 if (default_index >= labels.size ())
118 {
119 throw std::invalid_argument (
120 "default_index out of range for enumeration labels");
121 }
122 const auto count = static_cast<float> (labels.size ());
123 ParameterRange range{
124 Type::Enumeration, 0.f, count - 1.f, 0.f,
125 static_cast<float> (default_index)
126 };
127 range.enum_labels_ = std::move (labels);
128 return range;
129 }
130
134 Q_INVOKABLE size_t enumCount () const { return enum_labels_.size (); }
135
139 Q_INVOKABLE size_t enumIndex (float normalized_val) const
140 {
141 const auto real = convertFrom0To1 (normalized_val);
142 assert (!enum_labels_.empty ());
143 const auto idx = static_cast<size_t> (std::max (std::round (real), 0.f));
144 return std::min (idx, enum_labels_.size () - 1);
145 }
146
150 Q_INVOKABLE float normalizedEnumValue (size_t index) const
151 {
152 assert (index < enum_labels_.size ());
153 if (enum_labels_.size () == 1)
154 return 0.f;
155 return convertTo0To1 (static_cast<float> (index));
156 }
157
161 const auto &enum_label (size_t index) const
162 {
163 return enum_labels_.at (index);
164 }
165
166 Q_INVOKABLE QString enumLabel (int index) const
167 {
168 if (index < 0 || static_cast<size_t> (index) >= enum_labels_.size ())
169 return {};
170 return enum_label (static_cast<size_t> (index)).to_qstring ();
171 }
172
173 template <EnumType E> float normalized_from_enum (E value) const
174 {
175 assert (type_ == Type::Enumeration);
176 const auto index = static_cast<size_t> (value);
177 assert (index < enum_labels_.size ());
178 return normalizedEnumValue (index);
179 }
180
181 template <EnumType E> E enum_value (float normalized_val) const
182 {
183 assert (type_ == Type::Enumeration);
184 return static_cast<E> (enumIndex (normalized_val));
185 }
186
187 constexpr float clamp_to_range (float val) const
188 {
189 return std::clamp (val, minf_, maxf_);
190 }
191
192 Q_INVOKABLE float convertFrom0To1 (float normalized_val) const;
193 Q_INVOKABLE float convertTo0To1 (float real_val) const;
194
195 Q_INVOKABLE bool isToggled (float normalized_val) const
196 {
197 assert (type_ == ParameterRange::Type::Toggle);
198 return utils::math::floats_equal (convertFrom0To1 (normalized_val), 1.f);
199 }
200
201 friend void to_json (nlohmann::json &j, const ParameterRange &p);
202 friend void from_json (const nlohmann::json &j, ParameterRange &p);
203
204public:
205 Type type_{ Type::Linear };
206
209
213 float minf_{ 0.f };
214 float maxf_{ 1.f };
215
221 float zerof_{ 0.f };
222
226 float deff_{ 0.f };
227
231 std::vector<utils::Utf8String> enum_labels_;
232
233 BOOST_DESCRIBE_CLASS (
234 ParameterRange,
235 (),
236 (type_, unit_, minf_, maxf_, zerof_, deff_),
237 (),
238 ())
239};
240
248class ProcessorParameter
249 : public QObject,
251 public utils::UuidIdentifiableObject<ProcessorParameter>
252{
253 Q_OBJECT
254 Q_PROPERTY (
255 float baseValue READ baseValue WRITE setBaseValue NOTIFY baseValueChanged)
256 Q_PROPERTY (QString label READ label CONSTANT)
257 Q_PROPERTY (QString description READ description CONSTANT)
258 Q_PROPERTY (zrythm::dsp::ParameterRange range READ range CONSTANT)
259 Q_PROPERTY (bool automatable READ automatable CONSTANT)
260 QML_ELEMENT
261 QML_UNCREATABLE ("")
262
263public:
264 struct UniqueId final
265 : type_safe::strong_typedef<UniqueId, utils::Utf8String>,
266 type_safe::strong_typedef_op::equality_comparison<UniqueId>,
267 type_safe::strong_typedef_op::relational_comparison<UniqueId>
268 {
269 using type_safe::strong_typedef<UniqueId, utils::Utf8String>::strong_typedef;
270
271 explicit UniqueId () = default;
272
273 static_assert (StrongTypedef<UniqueId>);
274
275 std::size_t hash () const { return qHash (type_safe::get (*this).view ()); }
276 };
277 static_assert (std::regular<UniqueId>);
278
279 using Resolver =
280 std::function<QPointer<ProcessorParameter> (const UniqueId &unique_id)>;
281
282 ProcessorParameter (
283 PortRegistry &port_registry,
284 UniqueId unique_id,
285 ParameterRange range,
286 utils::Utf8String label,
287 QObject * parent = nullptr);
288
303 std::function<std::optional<float> (units::sample_t sample_position)>;
304
305 // ========================================================================
306 // QML Interface
307 // ========================================================================
308
309 QString label () const { return label_.to_qstring (); }
310 QString description () const { return description_->to_qstring (); }
311 bool automatable () const { return automatable_; }
312
313 const auto &range () const { return range_; }
314
315 float baseValue () const { return base_value_.load (); }
316 void setBaseValue (float newValue) [[clang::blocking]]
317 {
318 newValue = std::clamp (newValue, 0.f, 1.f);
319 if (qFuzzyCompare (base_value_, newValue))
320 return;
321 base_value_ = newValue;
322 Q_EMIT baseValueChanged (newValue);
323 }
324 Q_INVOKABLE void resetBaseValueToDefault ()
325 {
326 setBaseValue (range_.convertTo0To1 (range_.deff_));
327 }
328 Q_SIGNAL void baseValueChanged (float value);
329
334 Q_INVOKABLE float currentValue () const { return last_modulated_value_; }
335
341 */
342 Q_INVOKABLE float valueAfterAutomationApplied () const
343 {
344 return last_automated_value_.load ();
345 }
346
347 Q_INVOKABLE void beginUserGesture ()
348 {
349 during_gesture_.store (true);
350 Q_EMIT userGestureStarted ();
351 }
352 Q_INVOKABLE void endUserGesture ()
353 {
354 during_gesture_.store (false);
355 Q_EMIT userGestureFinished ();
356 }
357 Q_SIGNAL void userGestureStarted ();
358 Q_SIGNAL void userGestureFinished ();
359
360 // ========================================================================
361
362 // ========================================================================
363 // IProcessable Implementation
364 // ========================================================================
366 utils::Utf8String get_node_name () const override;
367
374 void process_block (
376 const dsp::ITransport &transport,
377 const dsp::TempoMap &tempo_map) noexcept override;
380 const graph::GraphNode * node,
381 units::sample_rate_t sample_rate,
382 units::sample_u32_t max_block_length) override;
383 void release_resources () override;
384
385 // ========================================================================
386
387 void set_automation_provider (AutomationValueProvider provider)
388 {
389 automation_value_provider_ = provider;
390 }
391 void unset_automation_provider () { automation_value_provider_.reset (); }
392
393 PortUuidReference get_modulation_input_port_ref () const
394 {
395 return modulation_input_uuid_;
396 }
397
398 void set_description (utils::Utf8String descr)
399 {
400 description_ = std::move (descr);
401 }
402
403 void set_automatable (bool automatable) { automatable_ = automatable; }
404
405 bool hidden () const { return hidden_; }
406
407 const auto &get_unique_id () const { return unique_id_; }
408
409private:
410 // Serialization keys
411 static constexpr auto kUniqueIdKey = "uniqueId"sv;
412 static constexpr auto kRangeKey = "range"sv;
413 static constexpr auto kLabelKey = "label"sv;
414 static constexpr auto kSymbolKey = "symbol"sv;
415 static constexpr auto kDescriptionKey = "description"sv;
416 static constexpr auto kAutomatableKey = "automatable"sv;
417 static constexpr auto kHiddenKey = "hidden"sv;
418 static constexpr auto kBaseValueKey = "baseValue"sv;
419 static constexpr auto kModulationSourcePortIdKey = "modulationSourcePortId"sv;
420 friend void to_json (nlohmann::json &j, const ProcessorParameter &p);
421 friend void from_json (const nlohmann::json &j, ProcessorParameter &p);
422
423private:
427 UniqueId unique_id_;
428
437 ParameterRange range_;
438
440 utils::Utf8String label_;
441
445 std::atomic<float> base_value_;
446
452 std::atomic_bool during_gesture_;
453
461 std::atomic<float> last_automated_value_;
462
472 std::atomic<float> last_modulated_value_;
473
480 PortUuidReference modulation_input_uuid_;
481
482 // Processing cache
483 dsp::CVPort * modulation_input_{};
484
490 std::optional<AutomationValueProvider> automation_value_provider_;
491
493 std::optional<utils::Utf8String> symbol_;
494
496 std::optional<utils::Utf8String> description_;
497
498 // Automatable (via automation provider or modulation)
499 bool automatable_{ true };
500
501 // Not on GUI
502 bool hidden_{};
503
504 BOOST_DESCRIBE_CLASS (
505 ProcessorParameter,
506 (utils::UuidIdentifiableObject<ProcessorParameter>),
507 (),
508 (),
509 (unique_id_,
510 range_,
511 label_,
512 base_value_,
513 modulation_input_uuid_,
514 symbol_,
515 description_,
516 automatable_,
517 hidden_))
518};
519
520inline auto
521format_as (const ProcessorParameter::UniqueId &id)
522{
523 return type_safe::get (id).view ();
524}
525
526using ProcessorParameterPtrVariant = std::variant<ProcessorParameter *>;
527}
528
529DEFINE_UUID_HASH_SPECIALIZATION (zrythm::dsp::ProcessorParameter::Uuid)
530
531namespace zrythm::dsp
532{
539class ProcessorParameterRegistry
540 : public utils::
541 OwningObjectRegistry<ProcessorParameterPtrVariant, ProcessorParameter>
542{
543public:
544 ProcessorParameterRegistry (
545 dsp::PortRegistry &port_registry,
546 QObject * parent = nullptr)
548 ProcessorParameterPtrVariant,
549 ProcessorParameter> (parent),
550 port_registry_ (port_registry)
551 {
552 }
553
555 find_by_unique_id (const ProcessorParameter::UniqueId &id) const
556 {
557 const auto &map = get_hash_map ();
558 for (const auto &kv : map)
559 {
560 if (std::get<ProcessorParameter *> (kv.second)->get_unique_id () == id)
561 {
562 return std::get<ProcessorParameter *> (kv.second);
563 }
564 }
565 return nullptr;
566 }
567
569 find_by_unique_id_or_throw (const ProcessorParameter::UniqueId &id) const
570 {
571 auto * val = find_by_unique_id (id);
572 if (val == nullptr) [[unlikely]]
573 {
574 throw std::runtime_error (
575 fmt::format ("Processor Parameter with unique id {} not found", id));
576 }
577 return val;
578 }
579
580private:
581 struct ProcessorParameterRegistryBuilder
582 {
583 ProcessorParameterRegistryBuilder (dsp::PortRegistry &port_registry)
584 : port_registry_ (port_registry)
585 {
586 }
587
588 template <typename T> std::unique_ptr<T> build () const
589 {
590 return std::make_unique<T> (
591 port_registry_, ProcessorParameter::UniqueId (u8""), ParameterRange{},
592 u8"");
593 }
594
595 dsp::PortRegistry &port_registry_;
596 };
597
598 friend void
599 from_json (const nlohmann::json &j, ProcessorParameterRegistry &reg)
600 {
601 from_json_with_builder (
602 j, reg, ProcessorParameterRegistryBuilder{ reg.port_registry_ });
603 }
604
605private:
606 dsp::PortRegistry &port_registry_;
607};
608
609using ProcessorParameterUuidReference = utils::UuidReference<
611
612} // namespace zrythm::dsp
613
614namespace std
615{
616template <> struct hash<zrythm::dsp::ProcessorParameter::UniqueId>
617{
618 size_t operator() (const zrythm::dsp::ProcessorParameter::UniqueId &id) const
619 {
620 return id.hash ();
621 }
622};
623}
Interface for transport.
Definition itransport.h:16
std::vector< utils::Utf8String > enum_labels_
Labels for Enumeration type parameters.
Definition parameter.h:231
float minf_
Minimum, maximum and zero values for this parameter.
Definition parameter.h:213
Q_INVOKABLE size_t enumCount() const
Returns the number of enum entries.
Definition parameter.h:134
const auto & enum_label(size_t index) const
Returns the label for a given enum index.
Definition parameter.h:161
Q_INVOKABLE float normalizedEnumValue(size_t index) const
Returns the normalized value for a given enum index.
Definition parameter.h:150
float zerof_
The zero position of the port.
Definition parameter.h:221
Unit unit_
Parameter unit.
Definition parameter.h:208
Q_INVOKABLE size_t enumIndex(float normalized_val) const
Converts a normalized value to an enum index.
Definition parameter.h:139
@ GainAmplitude
Parameter is a gain amplitude (0-2.0).
Definition parameter.h:51
@ Enumeration
Port's only reasonable values are its scale points.
Definition parameter.h:57
@ Logarithmic
Logarithmic-scaled float parameter.
Definition parameter.h:54
@ Linear
Linearly-scaled float parameter.
Definition parameter.h:33
@ Integer
Whether the port is an integer.
Definition parameter.h:43
@ Toggle
Whether the port is a toggle (on/off).
Definition parameter.h:40
@ Trigger
Trigger parameters are set to on to trigger a change during processing and then turned off at the end...
Definition parameter.h:65
Unit
Unit to be displayed in the UI.
Definition parameter.h:73
float deff_
Default value.
Definition parameter.h:226
Processor parameter that accepts automation and modulation sources and integrates with QML and the DS...
Definition parameter.h:252
std::function< std::optional< float >(units::sample_t sample_position)> AutomationValueProvider
Provides the automation value for a given sample position.
Definition parameter.h:301
void prepare_for_processing(const graph::GraphNode *node, units::sample_rate_t sample_rate, units::sample_u32_t max_block_length) override
Called to allocate resources required for processing.
void release_resources() override
Called to release resources allocated by prepare_for_processing().
void process_block(dsp::graph::ProcessBlockInfo time_nfo, const dsp::ITransport &transport, const dsp::TempoMap &tempo_map) noexcept override
Processes automation and modulation.
Q_INVOKABLE float currentValue() const
Returns the current (normalized) value after any automation and modulation has been applied.
Definition parameter.h:333
utils::Utf8String get_node_name() const override
Returns a human friendly name of the node.
Q_INVOKABLE float valueAfterAutomationApplied() const
Returns the value after automation, but before modulation has been applied.
Definition parameter.h:341
Represents a node in a DSP graph.
Definition graph_node.h:173
Interface for objects that can be processed in the DSP graph.
Definition graph_node.h:99
A registry that owns and manages objects identified by a UUID.
Lightweight UTF-8 string wrapper with safe conversions.
Definition utf8_string.h:37
Base class for objects that need to be uniquely identified by UUID.
A reference-counted RAII wrapper for a UUID in a registry.
constexpr bool floats_equal(T a, T b)
Checks if 2 floating point numbers are equal.
Definition math_utils.h:74
String utilities.
Common struct to pass around during processing to avoid repeating the data in function arguments.
Definition graph_node.h:51