Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
parameter.h
1// SPDX-FileCopyrightText: © 2025 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/format.h"
11#include "utils/math.h"
12#include "utils/serialization.h"
13#include "utils/units.h"
14#include "utils/uuid_identifiable_object.h"
15
16namespace zrythm::dsp
17{
18
19class ParameterRange
20{
21 Q_GADGET
22
23public:
24 enum class Type : std::uint8_t
25 {
30
37
40
48
51
54
62 };
63
67 enum class Unit : std::uint8_t
68 {
69 None,
70 Hz,
71 MHz,
72 Db,
73 Degrees,
74 Seconds,
75
78
81 };
82
83 static utils::Utf8String unit_to_string (Unit unit)
84 {
85 constexpr std::array<std::u8string_view, 8> port_unit_strings = {
86 u8"none", u8"Hz", u8"MHz", u8"dB", u8"°", u8"s", u8"ms", u8"μs",
87 };
88 return port_unit_strings.at (ENUM_VALUE_TO_INT (unit));
89 }
90
91public:
92 ParameterRange () = default;
93
94 ParameterRange (Type type, float min, float max, float zero = 0.f, float def = 0.f)
95 : type_ (type), minf_ (min), maxf_ (max),
96 zerof_ (std::clamp (zero, min, max)), deff_ (std::clamp (def, min, max))
97 {
98 }
99
100 static ParameterRange make_toggle (bool default_val)
101 {
102 return { Type::Toggle, 0.f, 1.f, 0.f, default_val ? 1.f : 0.f };
103 }
104 static ParameterRange make_gain (float max_val)
105 {
106 return { Type::GainAmplitude, 0.f, max_val, 0.f, 1.f };
107 }
108
109 constexpr float clamp_to_range (float val) const
110 {
111 return std::clamp (val, minf_, maxf_);
112 }
113
114 Q_INVOKABLE float convertFrom0To1 (float normalized_val) const
115 {
116 if (type_ == Type::Logarithmic)
117 {
118 const auto minf =
119 utils::math::floats_equal (minf_, 0.f) ? 1e-20f : minf_;
120 const auto maxf =
121 utils::math::floats_equal (maxf_, 0.f) ? 1e-20f : maxf_;
122 normalized_val =
123 utils::math::floats_equal (normalized_val, 0.f) ? 1e-20f : normalized_val;
124
125 /* see http://lv2plug.in/ns/ext/port-props/port-props.html#rangeSteps */
126 return minf * std::pow (maxf / minf, normalized_val);
127 }
128 if (type_ == Type::Toggle)
129 {
130 return normalized_val >= 0.001f ? 1.f : 0.f;
131 }
132 if (type_ == Type::GainAmplitude)
133 {
134 return utils::math::get_amp_val_from_fader (normalized_val);
135 }
136
137 return minf_ + (normalized_val * (maxf_ - minf_));
138 }
139
140 Q_INVOKABLE float convertTo0To1 (float real_val) const
141 {
142
143 if (type_ == Type::Logarithmic)
144 {
145 const auto minf =
146 utils::math::floats_equal (minf_, 0.f) ? 1e-20f : minf_;
147 const auto maxf =
148 utils::math::floats_equal (maxf_, 0.f) ? 1e-20f : maxf_;
149 real_val = utils::math::floats_equal (real_val, 0.f) ? 1e-20f : real_val;
150
151 /* see http://lv2plug.in/ns/ext/port-props/port-props.html#rangeSteps */
152 return std::log (real_val / minf) / std::log (maxf / minf);
153 }
154 if (type_ == Type::Toggle)
155 {
156 return real_val;
157 }
158 if (type_ == Type::GainAmplitude)
159 {
160 return utils::math::get_fader_val_from_amp (real_val);
161 }
162
163 const auto sizef = maxf_ - minf_;
164 return (sizef - (maxf_ - real_val)) / sizef;
165 }
166
167 bool is_toggled (float normalized_val) const
168 {
169 assert (type_ == ParameterRange::Type::Toggle);
170 return utils::math::floats_equal (convertFrom0To1 (normalized_val), 1.f);
171 }
172
173 NLOHMANN_DEFINE_TYPE_INTRUSIVE (
174 ParameterRange,
175 type_,
176 minf_,
177 maxf_,
178 zerof_,
179 deff_,
180 unit_)
181
182public:
183 Type type_{ Type::Linear };
184
187
191 float minf_{ 0.f };
192 float maxf_{ 1.f };
193
199 float zerof_{ 0.f };
200
204 float deff_{ 0.f };
205
206 BOOST_DESCRIBE_CLASS (
208 (),
209 (type_, unit_, minf_, maxf_, zerof_, deff_),
210 (),
211 ())
212};
213
221class ProcessorParameter
222 : public QObject,
224 public utils::UuidIdentifiableObject<ProcessorParameter>
225{
226 Q_OBJECT
227 Q_PROPERTY (
228 float baseValue READ baseValue WRITE setBaseValue NOTIFY baseValueChanged)
229 Q_PROPERTY (QString label READ label CONSTANT)
230 Q_PROPERTY (QString description READ description CONSTANT)
231 Q_PROPERTY (ParameterRange range READ range CONSTANT)
232 Q_PROPERTY (bool automatable READ automatable CONSTANT)
233 QML_ELEMENT
234 QML_UNCREATABLE ("")
235
236public:
237 struct UniqueId final
238 : type_safe::strong_typedef<UniqueId, utils::Utf8String>,
239 type_safe::strong_typedef_op::equality_comparison<UniqueId>,
240 type_safe::strong_typedef_op::relational_comparison<UniqueId>
241 {
242 using type_safe::strong_typedef<UniqueId, utils::Utf8String>::strong_typedef;
243
244 explicit UniqueId () = default;
245
246 static_assert (StrongTypedef<UniqueId>);
247
248 std::size_t hash () const { return qHash (type_safe::get (*this).view ()); }
249 };
250 static_assert (std::regular<UniqueId>);
251
252 using Resolver =
253 std::function<QPointer<ProcessorParameter> (const UniqueId &unique_id)>;
254
255 ProcessorParameter (
256 PortRegistry &port_registry,
257 UniqueId unique_id,
258 ParameterRange range,
259 utils::Utf8String label,
260 QObject * parent = nullptr);
261
276 std::function<std::optional<float> (units::sample_t sample_position)>;
277
278 // ========================================================================
279 // QML Interface
280 // ========================================================================
281
282 QString label () const { return label_.to_qstring (); }
283 QString description () const { return description_->to_qstring (); }
284 bool automatable () const { return automatable_; }
285
286 ParameterRange range () const { return range_; }
287
288 float baseValue () const { return base_value_.load (); }
289 void setBaseValue (float newValue) [[clang::blocking]]
290 {
291 newValue = std::clamp (newValue, 0.f, 1.f);
292 if (qFuzzyCompare (base_value_, newValue))
293 return;
294 base_value_ = newValue;
295 Q_EMIT baseValueChanged (newValue);
296 }
297 Q_INVOKABLE void resetBaseValueToDefault ()
298 {
299 setBaseValue (range_.convertTo0To1 (range_.deff_));
300 }
301 Q_SIGNAL void baseValueChanged (float value);
302
307 Q_INVOKABLE float currentValue () const { return last_modulated_value_; }
308
314 */
315 Q_INVOKABLE float valueAfterAutomationApplied () const
316 {
317 return last_automated_value_.load ();
318 }
319
320 Q_INVOKABLE void beginUserGesture ()
321 {
322 during_gesture_.store (true);
323 Q_EMIT userGestureStarted ();
324 }
325 Q_INVOKABLE void endUserGesture ()
326 {
327 during_gesture_.store (false);
328 Q_EMIT userGestureFinished ();
329 }
330 Q_SIGNAL void userGestureStarted ();
331 Q_SIGNAL void userGestureFinished ();
332
333 // ========================================================================
334
335 // ========================================================================
336 // IProcessable Implementation
337 // ========================================================================
339 utils::Utf8String get_node_name () const override;
340
347 void process_block (
348 EngineProcessTimeInfo time_nfo,
349 const dsp::ITransport &transport) noexcept override;
352 const graph::GraphNode * node,
353 units::sample_rate_t sample_rate,
354 nframes_t max_block_length) override;
355 void release_resources () override;
356
357 // ========================================================================
358
359 void set_automation_provider (AutomationValueProvider provider)
360 {
361 automation_value_provider_ = provider;
362 }
363 void unset_automation_provider () { automation_value_provider_.reset (); }
364
365 PortUuidReference get_modulation_input_port_ref () const
366 {
367 return modulation_input_uuid_;
368 }
369
370 void set_description (utils::Utf8String descr)
371 {
372 description_ = std::move (descr);
373 }
374
375 void set_automatable (bool automatable) { automatable_ = automatable; }
376
377 const auto &get_unique_id () const { return unique_id_; }
378
379private:
380 // Some of these don't need serialization because they'll be created
381 // even when loading from json.
382 // We just need to serialize IDs, the value, and possibly the label too for
383 // debugging.
384 static constexpr auto kUniqueIdKey = "uniqueId"sv;
385 // static constexpr auto kTypeKey = "type"sv;
386 // static constexpr auto kRangeKey = "range"sv;
387 // static constexpr auto kUnitKey = "unit"sv;
388 static constexpr auto kLabelKey = "label"sv;
389 // static constexpr auto kSymbolKey = "symbol"sv;
390 // static constexpr auto kDescriptionKey = "description"sv;
391 // static constexpr auto kAutomatableKey = "automatable"sv;
392 // static constexpr auto kHiddenKey = "hidden"sv;
393 static constexpr auto kBaseValueKey = "baseValue"sv;
394 static constexpr auto kModulationSourcePortIdKey = "modulationSourcePortId"sv;
395 friend void to_json (nlohmann::json &j, const ProcessorParameter &p)
396 {
397 j[kUniqueIdKey] = p.unique_id_;
398 j[kLabelKey] = p.label_;
399 j[kBaseValueKey] = p.base_value_.load ();
400 j[kModulationSourcePortIdKey] = p.modulation_input_uuid_;
401 }
402 friend void from_json (const nlohmann::json &j, ProcessorParameter &p)
403 {
404 j.at (kUniqueIdKey).get_to (p.unique_id_);
405 j.at (kLabelKey).get_to (p.label_);
406 float base_val{};
407 j.at (kBaseValueKey).get_to (base_val);
408 p.base_value_.store (base_val);
409 j.at (kModulationSourcePortIdKey).get_to (p.modulation_input_uuid_);
410 }
411
412private:
416 UniqueId unique_id_;
417
418 ParameterRange range_;
419
421 utils::Utf8String label_;
422
426 std::atomic<float> base_value_;
427
433 std::atomic_bool during_gesture_;
434
442 std::atomic<float> last_automated_value_;
443
453 std::atomic<float> last_modulated_value_;
454
461 PortUuidReference modulation_input_uuid_;
462
463 // Processing cache
464 dsp::CVPort * modulation_input_{};
465
471 std::optional<AutomationValueProvider> automation_value_provider_;
472
474 std::optional<utils::Utf8String> symbol_;
475
477 std::optional<utils::Utf8String> description_;
478
479 // Automatable (via automation provider or modulation)
480 bool automatable_{ true };
481
482 // Not on GUI
483 bool hidden_{};
484
485 BOOST_DESCRIBE_CLASS (
486 ProcessorParameter,
487 (utils::UuidIdentifiableObject<ProcessorParameter>),
488 (),
489 (),
490 (unique_id_,
491 range_,
492 label_,
493 base_value_,
494 modulation_input_uuid_,
495 symbol_,
496 description_,
497 automatable_,
498 hidden_))
499};
500
501inline auto
502format_as (const ProcessorParameter::UniqueId &id)
503{
504 return type_safe::get (id).view ();
505}
506
507using ProcessorParameterPtrVariant = std::variant<ProcessorParameter *>;
508}
509
510DEFINE_UUID_HASH_SPECIALIZATION (zrythm::dsp::ProcessorParameter::Uuid)
511
512namespace zrythm::dsp
513{
520class ProcessorParameterRegistry
521 : public utils::
522 OwningObjectRegistry<ProcessorParameterPtrVariant, ProcessorParameter>
523{
524public:
525 ProcessorParameterRegistry (
526 dsp::PortRegistry &port_registry,
527 QObject * parent = nullptr)
529 ProcessorParameterPtrVariant,
530 ProcessorParameter> (parent),
531 port_registry_ (port_registry)
532 {
533 }
534
536 find_by_unique_id (const ProcessorParameter::UniqueId &id) const
537 {
538 const auto &map = get_hash_map ();
539 for (const auto &kv : map)
540 {
541 if (std::get<ProcessorParameter *> (kv.second)->get_unique_id () == id)
542 {
543 return std::get<ProcessorParameter *> (kv.second);
544 }
545 }
546 return nullptr;
547 }
548
550 find_by_unique_id_or_throw (const ProcessorParameter::UniqueId &id) const
551 {
552 auto * val = find_by_unique_id (id);
553 if (val == nullptr) [[unlikely]]
554 {
555 throw std::runtime_error (
556 fmt::format ("Processor Parameter with unique id {} not found", id));
557 }
558 return val;
559 }
560
561private:
562 struct ProcessorParameterRegistryBuilder
563 {
564 ProcessorParameterRegistryBuilder (dsp::PortRegistry &port_registry)
565 : port_registry_ (port_registry)
566 {
567 }
568
569 template <typename T> std::unique_ptr<T> build () const
570 {
571 return std::make_unique<T> (
572 port_registry_, ProcessorParameter::UniqueId (u8""), ParameterRange{},
573 u8"");
574 }
575
576 dsp::PortRegistry &port_registry_;
577 };
578
579 friend void
580 from_json (const nlohmann::json &j, ProcessorParameterRegistry &reg)
581 {
582 from_json_with_builder (
583 j, reg, ProcessorParameterRegistryBuilder{ reg.port_registry_ });
584 }
585
586private:
587 dsp::PortRegistry &port_registry_;
588};
589
590using ProcessorParameterUuidReference = utils::UuidReference<
592
593} // namespace zrythm::dsp
594
595namespace std
596{
597template <> struct hash<zrythm::dsp::ProcessorParameter::UniqueId>
598{
599 size_t operator() (const zrythm::dsp::ProcessorParameter::UniqueId &id) const
600 {
601 return id.hash ();
602 }
603};
604}
Interface for transport.
Definition itransport.h:17
NLOHMANN_DEFINE_TYPE_INTRUSIVE(ParameterRange, type_, minf_, maxf_, zerof_, deff_, unit_) public Unit unit_
Parameter unit.
Definition parameter.h:186
float minf_
Minimum, maximum and zero values for this parameter.
Definition parameter.h:191
float zerof_
The zero position of the port.
Definition parameter.h:199
@ GainAmplitude
Parameter is a gain amplitude (0-2.0).
Definition parameter.h:47
@ Enumeration
Port's only reasonable values are its scale points.
Definition parameter.h:53
@ Logarithmic
Logarithmic-scaled float parameter.
Definition parameter.h:50
@ Linear
Linearly-scaled float parameter.
Definition parameter.h:29
@ Integer
Whether the port is an integer.
Definition parameter.h:39
@ Toggle
Whether the port is a toggle (on/off).
Definition parameter.h:36
@ Trigger
Trigger parameters are set to on to trigger a change during processing and then turned off at the end...
Definition parameter.h:61
Unit
Unit to be displayed in the UI.
Definition parameter.h:68
float deff_
Default value.
Definition parameter.h:204
Processor parameter that accepts automation and modulation sources and integrates with QML and the DS...
Definition parameter.h:225
std::function< std::optional< float >(units::sample_t sample_position)> AutomationValueProvider
Provides the automation value for a given sample position.
Definition parameter.h:274
void release_resources() override
Called to release resources allocated by prepare_for_processing().
void prepare_for_processing(const graph::GraphNode *node, units::sample_rate_t sample_rate, nframes_t max_block_length) override
Called to allocate resources required for processing.
void process_block(EngineProcessTimeInfo time_nfo, const dsp::ITransport &transport) 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:306
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:314
Represents a node in a DSP graph.
Definition graph_node.h:129
Interface for objects that can be processed in the DSP graph.
Definition graph_node.h:53
A registry that owns and manages objects identified by a UUID.
Lightweight UTF-8 string wrapper with safe conversions.
Definition utf8_string.h:38
Base class for objects that need to be uniquely identified by UUID.
A reference-counted RAII wrapper for a UUID in a registry.
uint32_t nframes_t
Frame count.
Definition types.h:58
constexpr bool floats_equal(T a, T b)
Checks if 2 floating point numbers are equal.
Definition math.h:76
String utilities.
Definition algorithms.h:12
Common struct to pass around during processing to avoid repeating the data in function arguments.
Definition types.h:133