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_utils.h"
12#include "utils/units.h"
13#include "utils/uuid_identifiable_object.h"
14
15#include <nlohmann/json_fwd.hpp>
16
17namespace zrythm::dsp
18{
19
20class ParameterRange
21{
22 Q_GADGET
23
24public:
25 enum class Type : std::uint8_t
26 {
31
38
41
49
52
55
63 };
64
68 enum class Unit : std::uint8_t
69 {
70 None,
71 Hz,
72 MHz,
73 Db,
74 Degrees,
75 Seconds,
76
79
82 };
83
84 static utils::Utf8String unit_to_string (Unit unit)
85 {
86 constexpr std::array<std::u8string_view, 8> port_unit_strings = {
87 u8"none", u8"Hz", u8"MHz", u8"dB", u8"°", u8"s", u8"ms", u8"μs",
88 };
89 return port_unit_strings.at (ENUM_VALUE_TO_INT (unit));
90 }
91
92public:
93 ParameterRange () = default;
94
95 ParameterRange (Type type, float min, float max, float zero = 0.f, float def = 0.f)
96 : type_ (type), minf_ (min), maxf_ (max),
97 zerof_ (std::clamp (zero, min, max)), deff_ (std::clamp (def, min, max))
98 {
99 }
100
101 static ParameterRange make_toggle (bool default_val)
102 {
103 return { Type::Toggle, 0.f, 1.f, 0.f, default_val ? 1.f : 0.f };
104 }
105 static ParameterRange make_gain (float max_val)
106 {
107 return { Type::GainAmplitude, 0.f, max_val, 0.f, 1.f };
108 }
109
110 constexpr float clamp_to_range (float val) const
111 {
112 return std::clamp (val, minf_, maxf_);
113 }
114
115 Q_INVOKABLE float convertFrom0To1 (float normalized_val) const
116 {
117 if (type_ == Type::Logarithmic)
118 {
119 const auto minf =
120 utils::math::floats_equal (minf_, 0.f) ? 1e-20f : minf_;
121 const auto maxf =
122 utils::math::floats_equal (maxf_, 0.f) ? 1e-20f : maxf_;
123 normalized_val =
124 utils::math::floats_equal (normalized_val, 0.f) ? 1e-20f : normalized_val;
125
126 /* see http://lv2plug.in/ns/ext/port-props/port-props.html#rangeSteps */
127 return minf * std::pow (maxf / minf, normalized_val);
128 }
129 if (type_ == Type::Toggle)
130 {
131 return normalized_val >= 0.001f ? 1.f : 0.f;
132 }
133 if (type_ == Type::GainAmplitude)
134 {
135 return utils::math::get_amp_val_from_fader (normalized_val);
136 }
137
138 return minf_ + (normalized_val * (maxf_ - minf_));
139 }
140
141 Q_INVOKABLE float convertTo0To1 (float real_val) const
142 {
143
144 if (type_ == Type::Logarithmic)
145 {
146 const auto minf =
147 utils::math::floats_equal (minf_, 0.f) ? 1e-20f : minf_;
148 const auto maxf =
149 utils::math::floats_equal (maxf_, 0.f) ? 1e-20f : maxf_;
150 real_val = utils::math::floats_equal (real_val, 0.f) ? 1e-20f : real_val;
151
152 /* see http://lv2plug.in/ns/ext/port-props/port-props.html#rangeSteps */
153 return std::log (real_val / minf) / std::log (maxf / minf);
154 }
155 if (type_ == Type::Toggle)
156 {
157 return real_val;
158 }
159 if (type_ == Type::GainAmplitude)
160 {
161 return utils::math::get_fader_val_from_amp (real_val);
162 }
163
164 const auto sizef = maxf_ - minf_;
165 return (sizef - (maxf_ - real_val)) / sizef;
166 }
167
168 bool is_toggled (float normalized_val) const
169 {
170 assert (type_ == ParameterRange::Type::Toggle);
171 return utils::math::floats_equal (convertFrom0To1 (normalized_val), 1.f);
172 }
173
174 friend void to_json (nlohmann::json &j, const ParameterRange &p);
175 friend void from_json (const nlohmann::json &j, ParameterRange &p);
176
177public:
178 Type type_{ Type::Linear };
179
182
186 float minf_{ 0.f };
187 float maxf_{ 1.f };
188
194 float zerof_{ 0.f };
195
199 float deff_{ 0.f };
200
201 BOOST_DESCRIBE_CLASS (
203 (),
204 (type_, unit_, minf_, maxf_, zerof_, deff_),
205 (),
206 ())
207};
208
216class ProcessorParameter
217 : public QObject,
219 public utils::UuidIdentifiableObject<ProcessorParameter>
220{
221 Q_OBJECT
222 Q_PROPERTY (
223 float baseValue READ baseValue WRITE setBaseValue NOTIFY baseValueChanged)
224 Q_PROPERTY (QString label READ label CONSTANT)
225 Q_PROPERTY (QString description READ description CONSTANT)
226 Q_PROPERTY (ParameterRange range READ range CONSTANT)
227 Q_PROPERTY (bool automatable READ automatable CONSTANT)
228 QML_ELEMENT
229 QML_UNCREATABLE ("")
230
231public:
232 struct UniqueId final
233 : type_safe::strong_typedef<UniqueId, utils::Utf8String>,
234 type_safe::strong_typedef_op::equality_comparison<UniqueId>,
235 type_safe::strong_typedef_op::relational_comparison<UniqueId>
236 {
237 using type_safe::strong_typedef<UniqueId, utils::Utf8String>::strong_typedef;
238
239 explicit UniqueId () = default;
240
241 static_assert (StrongTypedef<UniqueId>);
242
243 std::size_t hash () const { return qHash (type_safe::get (*this).view ()); }
244 };
245 static_assert (std::regular<UniqueId>);
246
247 using Resolver =
248 std::function<QPointer<ProcessorParameter> (const UniqueId &unique_id)>;
249
250 ProcessorParameter (
251 PortRegistry &port_registry,
252 UniqueId unique_id,
253 ParameterRange range,
254 utils::Utf8String label,
255 QObject * parent = nullptr);
256
271 std::function<std::optional<float> (units::sample_t sample_position)>;
272
273 // ========================================================================
274 // QML Interface
275 // ========================================================================
276
277 QString label () const { return label_.to_qstring (); }
278 QString description () const { return description_->to_qstring (); }
279 bool automatable () const { return automatable_; }
280
281 ParameterRange range () const { return range_; }
282
283 float baseValue () const { return base_value_.load (); }
284 void setBaseValue (float newValue) [[clang::blocking]]
285 {
286 newValue = std::clamp (newValue, 0.f, 1.f);
287 if (qFuzzyCompare (base_value_, newValue))
288 return;
289 base_value_ = newValue;
290 Q_EMIT baseValueChanged (newValue);
291 }
292 Q_INVOKABLE void resetBaseValueToDefault ()
293 {
294 setBaseValue (range_.convertTo0To1 (range_.deff_));
295 }
296 Q_SIGNAL void baseValueChanged (float value);
297
302 Q_INVOKABLE float currentValue () const { return last_modulated_value_; }
303
309 */
310 Q_INVOKABLE float valueAfterAutomationApplied () const
311 {
312 return last_automated_value_.load ();
313 }
314
315 Q_INVOKABLE void beginUserGesture ()
316 {
317 during_gesture_.store (true);
318 Q_EMIT userGestureStarted ();
319 }
320 Q_INVOKABLE void endUserGesture ()
321 {
322 during_gesture_.store (false);
323 Q_EMIT userGestureFinished ();
324 }
325 Q_SIGNAL void userGestureStarted ();
326 Q_SIGNAL void userGestureFinished ();
327
328 // ========================================================================
329
330 // ========================================================================
331 // IProcessable Implementation
332 // ========================================================================
334 utils::Utf8String get_node_name () const override;
335
342 void process_block (
343 EngineProcessTimeInfo time_nfo,
344 const dsp::ITransport &transport,
345 const dsp::TempoMap &tempo_map) noexcept override;
348 const graph::GraphNode * node,
349 units::sample_rate_t sample_rate,
350 nframes_t max_block_length) override;
351 void release_resources () override;
352
353 // ========================================================================
354
355 void set_automation_provider (AutomationValueProvider provider)
356 {
357 automation_value_provider_ = provider;
358 }
359 void unset_automation_provider () { automation_value_provider_.reset (); }
360
361 PortUuidReference get_modulation_input_port_ref () const
362 {
363 return modulation_input_uuid_;
364 }
365
366 void set_description (utils::Utf8String descr)
367 {
368 description_ = std::move (descr);
369 }
370
371 void set_automatable (bool automatable) { automatable_ = automatable; }
372
373 const auto &get_unique_id () const { return unique_id_; }
374
375private:
376 // Some of these don't need serialization because they'll be created
377 // even when loading from json.
378 // We just need to serialize IDs, the value, and possibly the label too for
379 // debugging.
380 static constexpr auto kUniqueIdKey = "uniqueId"sv;
381 // static constexpr auto kTypeKey = "type"sv;
382 // static constexpr auto kRangeKey = "range"sv;
383 // static constexpr auto kUnitKey = "unit"sv;
384 static constexpr auto kLabelKey = "label"sv;
385 // static constexpr auto kSymbolKey = "symbol"sv;
386 // static constexpr auto kDescriptionKey = "description"sv;
387 // static constexpr auto kAutomatableKey = "automatable"sv;
388 // static constexpr auto kHiddenKey = "hidden"sv;
389 static constexpr auto kBaseValueKey = "baseValue"sv;
390 static constexpr auto kModulationSourcePortIdKey = "modulationSourcePortId"sv;
391 friend void to_json (nlohmann::json &j, const ProcessorParameter &p);
392 friend void from_json (const nlohmann::json &j, ProcessorParameter &p);
393
394private:
398 UniqueId unique_id_;
399
400 ParameterRange range_;
401
403 utils::Utf8String label_;
404
408 std::atomic<float> base_value_;
409
415 std::atomic_bool during_gesture_;
416
424 std::atomic<float> last_automated_value_;
425
435 std::atomic<float> last_modulated_value_;
436
443 PortUuidReference modulation_input_uuid_;
444
445 // Processing cache
446 dsp::CVPort * modulation_input_{};
447
453 std::optional<AutomationValueProvider> automation_value_provider_;
454
456 std::optional<utils::Utf8String> symbol_;
457
459 std::optional<utils::Utf8String> description_;
460
461 // Automatable (via automation provider or modulation)
462 bool automatable_{ true };
463
464 // Not on GUI
465 bool hidden_{};
466
467 BOOST_DESCRIBE_CLASS (
468 ProcessorParameter,
469 (utils::UuidIdentifiableObject<ProcessorParameter>),
470 (),
471 (),
472 (unique_id_,
473 range_,
474 label_,
475 base_value_,
476 modulation_input_uuid_,
477 symbol_,
478 description_,
479 automatable_,
480 hidden_))
481};
482
483inline auto
484format_as (const ProcessorParameter::UniqueId &id)
485{
486 return type_safe::get (id).view ();
487}
488
489using ProcessorParameterPtrVariant = std::variant<ProcessorParameter *>;
490}
491
492DEFINE_UUID_HASH_SPECIALIZATION (zrythm::dsp::ProcessorParameter::Uuid)
493
494namespace zrythm::dsp
495{
502class ProcessorParameterRegistry
503 : public utils::
504 OwningObjectRegistry<ProcessorParameterPtrVariant, ProcessorParameter>
505{
506public:
507 ProcessorParameterRegistry (
508 dsp::PortRegistry &port_registry,
509 QObject * parent = nullptr)
511 ProcessorParameterPtrVariant,
512 ProcessorParameter> (parent),
513 port_registry_ (port_registry)
514 {
515 }
516
518 find_by_unique_id (const ProcessorParameter::UniqueId &id) const
519 {
520 const auto &map = get_hash_map ();
521 for (const auto &kv : map)
522 {
523 if (std::get<ProcessorParameter *> (kv.second)->get_unique_id () == id)
524 {
525 return std::get<ProcessorParameter *> (kv.second);
526 }
527 }
528 return nullptr;
529 }
530
532 find_by_unique_id_or_throw (const ProcessorParameter::UniqueId &id) const
533 {
534 auto * val = find_by_unique_id (id);
535 if (val == nullptr) [[unlikely]]
536 {
537 throw std::runtime_error (
538 fmt::format ("Processor Parameter with unique id {} not found", id));
539 }
540 return val;
541 }
542
543private:
544 struct ProcessorParameterRegistryBuilder
545 {
546 ProcessorParameterRegistryBuilder (dsp::PortRegistry &port_registry)
547 : port_registry_ (port_registry)
548 {
549 }
550
551 template <typename T> std::unique_ptr<T> build () const
552 {
553 return std::make_unique<T> (
554 port_registry_, ProcessorParameter::UniqueId (u8""), ParameterRange{},
555 u8"");
556 }
557
558 dsp::PortRegistry &port_registry_;
559 };
560
561 friend void
562 from_json (const nlohmann::json &j, ProcessorParameterRegistry &reg)
563 {
564 from_json_with_builder (
565 j, reg, ProcessorParameterRegistryBuilder{ reg.port_registry_ });
566 }
567
568private:
569 dsp::PortRegistry &port_registry_;
570};
571
572using ProcessorParameterUuidReference = utils::UuidReference<
574
575} // namespace zrythm::dsp
576
577namespace std
578{
579template <> struct hash<zrythm::dsp::ProcessorParameter::UniqueId>
580{
581 size_t operator() (const zrythm::dsp::ProcessorParameter::UniqueId &id) const
582 {
583 return id.hash ();
584 }
585};
586}
Interface for transport.
Definition itransport.h:17
float minf_
Minimum, maximum and zero values for this parameter.
Definition parameter.h:186
float zerof_
The zero position of the port.
Definition parameter.h:194
Unit unit_
Parameter unit.
Definition parameter.h:181
@ GainAmplitude
Parameter is a gain amplitude (0-2.0).
Definition parameter.h:48
@ Enumeration
Port's only reasonable values are its scale points.
Definition parameter.h:54
@ Logarithmic
Logarithmic-scaled float parameter.
Definition parameter.h:51
@ Linear
Linearly-scaled float parameter.
Definition parameter.h:30
@ Integer
Whether the port is an integer.
Definition parameter.h:40
@ Toggle
Whether the port is a toggle (on/off).
Definition parameter.h:37
@ Trigger
Trigger parameters are set to on to trigger a change during processing and then turned off at the end...
Definition parameter.h:62
Unit
Unit to be displayed in the UI.
Definition parameter.h:69
float deff_
Default value.
Definition parameter.h:199
Processor parameter that accepts automation and modulation sources and integrates with QML and the DS...
Definition parameter.h:220
void process_block(EngineProcessTimeInfo time_nfo, const dsp::ITransport &transport, const dsp::TempoMap &tempo_map) noexcept override
Processes automation and modulation.
std::function< std::optional< float >(units::sample_t sample_position)> AutomationValueProvider
Provides the automation value for a given sample position.
Definition parameter.h:269
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.
Q_INVOKABLE float currentValue() const
Returns the current (normalized) value after any automation and modulation has been applied.
Definition parameter.h:301
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:309
Represents a node in a DSP graph.
Definition graph_node.h:131
Interface for objects that can be processed in the DSP graph.
Definition graph_node.h:54
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_utils.h:75
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