Zrythm v2.0.0-alpha.1
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
arranger_object_factory.h
1// SPDX-FileCopyrightText: © 2025 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include "structure/arrangement/arranger_object_all.h"
7#include "utils/qt.h"
8#include "utils/registry_utils.h"
9
10namespace zrythm::structure::arrangement
11{
15class ArrangerObjectFactory
16{
17public:
18 using SampleRateProvider = std::function<units::sample_rate_t ()>;
19 using BpmProvider = std::function<units::bpm_t ()>;
20
22 {
23 using LastTimelineObjectLengthProvider = std::function<double ()>;
24 using LastEditorObjectLengthProvider = std::function<double ()>;
25 using AutomationCurveAlgorithmProvider =
26 std::function<dsp::CurveOptions::Algorithm ()>;
27
28 const dsp::TempoMapWrapper &tempo_map_;
29 utils::IObjectRegistry &registry_;
30 LastTimelineObjectLengthProvider last_timeline_obj_len_provider_;
31 LastEditorObjectLengthProvider last_editor_obj_len_provider_;
32 AutomationCurveAlgorithmProvider automation_curve_algorithm_provider_;
33 };
34
35 ArrangerObjectFactory (
36 Dependencies dependencies,
37 SampleRateProvider sample_rate_provider,
38 BpmProvider bpm_provider)
39 : dependencies_ (std::move (dependencies)),
40 sample_rate_provider_ (std::move (sample_rate_provider)),
41 bpm_provider_ (std::move (bpm_provider))
42 {
43 }
44
45 template <structure::arrangement::FinalArrangerObjectSubclass ObjT>
46 class Builder
47 {
48 friend class ArrangerObjectFactory;
49
50 private:
51 explicit Builder (Dependencies dependencies)
52 : dependencies_ (std::move (dependencies))
53 {
54 }
55
56 Builder &with_clip (dsp::FileAudioSourceUuidReference clip_id)
57 requires (std::is_same_v<ObjT, AudioClip>)
58 {
59 clip_id_.emplace (std::move (clip_id));
60 return *this;
61 }
62
63 public:
64 Builder &with_start_ticks (units::precise_tick_t start_ticks)
65 {
66 start_ticks_ = start_ticks;
67
68 return *this;
69 }
70
71 Builder &with_end_ticks (units::precise_tick_t end_ticks)
72 requires (BoundedObject<ObjT>)
73 {
74 end_ticks_ = end_ticks;
75 return *this;
76 }
77
78 Builder &with_name (const QString &name)
79 requires (NamedObject<ObjT>)
80 {
81 name_ = name;
82 return *this;
83 }
84
85 Builder &with_pitch (const int pitch)
86 requires (std::is_same_v<ObjT, MidiNote>)
87 {
88 pitch_ = pitch;
89 return *this;
90 }
91
92 Builder &with_velocity (const int vel)
93 requires (std::is_same_v<ObjT, MidiNote>)
94 {
95 velocity_ = vel;
96 return *this;
97 }
98
99 Builder &with_midi_channel (const int channel)
100 requires (
101 std::is_same_v<ObjT, MidiNote> || std::is_same_v<ObjT, MidiControlEvent>)
102 {
103 midi_channel_ = channel;
104 return *this;
105 }
106
107 Builder &with_automatable_value (const double automatable_val)
108 requires (std::is_same_v<ObjT, AutomationPoint>)
109 {
110 automatable_value_ = automatable_val;
111 return *this;
112 }
113
114 Builder &with_chord_descriptor (
115 dsp::MusicalNote root,
116 dsp::ChordType type,
117 dsp::ChordAccent accent = dsp::ChordAccent::None,
118 int inversion = 0,
119 std::optional<dsp::MusicalNote> bass = std::nullopt)
120 requires (std::is_same_v<ObjT, ChordObject>)
121 {
122 chord_descr_ = utils::make_qobject_unique<dsp::ChordDescriptor> (
123 root, type, accent, inversion, bass);
124 return *this;
125 }
126
127 Builder &with_scale (utils::QObjectUniquePtr<dsp::MusicalScale> scale)
128 requires (std::is_same_v<ObjT, ScaleObject>)
129 {
130 scale_ = std::move (scale);
131 return *this;
132 }
133
134 Builder &with_marker_type (Marker::MarkerType marker_type)
135 requires (std::is_same_v<ObjT, Marker>)
136 {
137 marker_type_ = marker_type;
138 return *this;
139 }
140
144 std::unique_ptr<ObjT> build_empty () const
145 {
146 std::unique_ptr<ObjT> ret;
147 if constexpr (ClipObject<ObjT>)
148 {
149 ret = std::make_unique<ObjT> (
150 dependencies_.tempo_map_, dependencies_.registry_);
151 }
152 else if constexpr (std::is_same_v<ObjT, Marker>)
153 {
154 ret = std::make_unique<ObjT> (
155 dependencies_.tempo_map_,
156 marker_type_.value_or (Marker::MarkerType::Custom));
157 }
158 else if constexpr (std::is_same_v<ObjT, AudioSourceObject>)
159 {
160 utils::audio::AudioBuffer dummy_buf (1, 1);
161 dummy_buf.clear ();
162 auto file_audio_source = utils::create_object<dsp::FileAudioSource> (
163 dependencies_.registry_, dummy_buf,
164 utils::audio::BitDepth::BIT_DEPTH_32, units::sample_rate (44100),
165 units::bpm (120.0), u8"dummy");
166 ret = std::make_unique<ObjT> (
167 dependencies_.tempo_map_, dependencies_.registry_,
168 file_audio_source);
169 }
170 else
171 {
172 ret = std::make_unique<ObjT> (dependencies_.tempo_map_);
173 }
174 return ret;
175 }
176
177 auto build_in_registry ()
178 {
179 auto obj_ref = [&] () {
180 auto obj_unique_ptr = build_empty ();
181 dependencies_.registry_.register_object (*obj_unique_ptr);
183 obj_unique_ptr->get_uuid (), dependencies_.registry_
184 };
185 obj_unique_ptr.release ();
186 return ret_ref;
187 }();
188
189 auto * obj = obj_ref.template get_object_as<ObjT> ();
190
191 if constexpr (ClipObject<ObjT>)
192 {
193 obj->setTrackBounds (true);
194 }
195
196 if (start_ticks_)
197 {
198 if (!end_ticks_ && !clip_id_)
199 {
200 if constexpr (BoundedObject<ObjT>)
201 {
202 double len_ticks{};
203 if constexpr (TimelineObject<ObjT>)
204 {
205 len_ticks =
206 dependencies_.last_timeline_obj_len_provider_ ();
207 }
208 else
209 {
210 len_ticks = dependencies_.last_editor_obj_len_provider_ ();
211 }
212 obj->length ()->setTicks (len_ticks);
213 }
214 }
215 obj->position ()->setTicks ((*start_ticks_).in (units::ticks));
216 }
217
218 if (clip_id_)
219 {
220 if constexpr (std::is_same_v<ObjT, AudioClip>)
221 {
222 auto source_object = utils::create_object<AudioSourceObject> (
223 dependencies_.registry_, dependencies_.tempo_map_,
224 dependencies_.registry_, clip_id_.value ());
225 obj->set_source (source_object);
226 // set_source() calls init_length_from_clip(), which sets the
227 // clip length to the clip's full duration in the format for the
228 // effective musical mode.
229 }
230 }
231
232 if (end_ticks_)
233 {
234 if constexpr (BoundedObject<ObjT>)
235 {
236 set_end_from_timeline_ticks (
237 *obj, dsp::TimelineTick{ *end_ticks_ });
238 }
239 }
240
241 if (name_)
242 {
243 if constexpr (NamedObject<ObjT>)
244 {
245 if constexpr (ClipObject<ObjT>)
246 {
247 obj->name ()->setName (*name_);
248 }
249 else
250 {
251 obj->name ()->setName (*name_);
252 }
253 }
254 }
255
256 if (pitch_)
257 {
258 if constexpr (std::is_same_v<ObjT, MidiNote>)
259 {
260 obj->setPitch (*pitch_);
261 }
262 }
263
264 if (velocity_)
265 {
266 if constexpr (std::is_same_v<ObjT, MidiNote>)
267 {
268 obj->setVelocity (*velocity_);
269 }
270 }
271
272 if (midi_channel_)
273 {
274 if constexpr (std::is_same_v<ObjT, MidiNote>)
275 {
276 obj->setMidiChannel (*midi_channel_);
277 }
278 else if constexpr (std::is_same_v<ObjT, MidiControlEvent>)
279 {
280 obj->setChannel (*midi_channel_);
281 }
282 }
283
284 if (automatable_value_)
285 {
286 if constexpr (std::is_same_v<ObjT, AutomationPoint>)
287 {
288 obj->setValue (static_cast<float> (*automatable_value_));
289 }
290 }
291
292 if (scale_)
293 {
294 if constexpr (std::is_same_v<ObjT, ScaleObject>)
295 {
296 obj->setScale (scale_.release ());
297 }
298 }
299
300 if constexpr (std::is_same_v<ObjT, AutomationPoint>)
301 {
302 obj->curveOpts ()->setAlgorithm (
303 dependencies_.automation_curve_algorithm_provider_ ());
304 }
305
306 if (chord_descr_)
307 {
308 if constexpr (std::is_same_v<ObjT, ChordObject>)
309 {
310 auto * dest = obj->chordDescriptor ();
311 dest->setRootNote (chord_descr_->rootNote ());
312 dest->setChordType (chord_descr_->chordType ());
313 dest->setChordAccent (chord_descr_->chordAccent ());
314 dest->setInversion (chord_descr_->inversion ());
315 if (chord_descr_->hasBass ())
316 dest->setBassNote (chord_descr_->bassNote ());
317 }
318 }
319
320 return obj_ref;
321 }
322
323 private:
324 Dependencies dependencies_;
325 std::optional<dsp::FileAudioSourceUuidReference> clip_id_;
326 std::optional<units::precise_tick_t> start_ticks_;
327 std::optional<units::precise_tick_t> end_ticks_;
328 std::optional<QString> name_;
329 std::optional<int> pitch_;
330 std::optional<double> automatable_value_;
331 utils::QObjectUniquePtr<dsp::ChordDescriptor> chord_descr_;
332 utils::QObjectUniquePtr<dsp::MusicalScale> scale_;
333 std::optional<int> velocity_;
334 std::optional<int> midi_channel_;
335 std::optional<Marker::MarkerType> marker_type_;
336 };
337
338 template <typename ObjT> auto get_builder () const
339 {
340 return Builder<ObjT> (dependencies_);
341 }
342
343public:
348 dsp::FileAudioSourceUuidReference clip_id,
349 units::precise_tick_t startTicks) const
350 {
351 auto obj =
352 get_builder<structure::arrangement::AudioClip> ()
353 .with_start_ticks (startTicks)
354 .with_clip (std::move (clip_id))
355 .build_in_registry ();
356 return obj;
357 }
358
359 auto create_audio_clip_from_file (
360 const QString &absPath,
361 units::precise_tick_t startTicks)
362 {
363 auto clip = utils::create_object<dsp::FileAudioSource> (
364 dependencies_.registry_, utils::Utf8String::from_qstring (absPath),
365 sample_rate_provider_ ());
366 return create_audio_clip_with_clip (std::move (clip), startTicks);
367 }
368
376 const utils::audio::AudioBuffer &buf,
377 utils::audio::BitDepth bit_depth,
378 const utils::Utf8String &clip_name,
379 units::precise_tick_t start_ticks) const
380 {
381 auto clip = utils::create_object<dsp::FileAudioSource> (
382 dependencies_.registry_, buf, bit_depth, sample_rate_provider_ (),
383 bpm_provider_ (), clip_name);
384 return create_audio_clip_with_clip (std::move (clip), start_ticks);
385 }
386
394 template <structure::arrangement::EditorObject ChildT>
396 units::precise_tick_t startTicks,
397 std::variant<int, double> value)
398 {
399 auto builder =
400 std::move (get_builder<ChildT> ().with_start_ticks (startTicks));
401 if constexpr (std::is_same_v<ChildT, MidiNote>)
402 {
403 const auto ival = std::get<int> (value);
404 assert (ival >= 0 && ival < 128);
405 builder.with_pitch (ival);
406 }
407 else if constexpr (std::is_same_v<ChildT, AutomationPoint>)
408 {
409 builder.with_automatable_value (std::get<double> (value));
410 }
411 else if constexpr (std::is_same_v<ChildT, ChordObject>)
412 {
413 // Value was formerly a chord index into ChordEditor; now interpreted
414 // as root note for the inline ChordDescriptor.
415 const auto root = static_cast<dsp::MusicalNote> (std::get<int> (value));
416 builder.with_chord_descriptor (root, dsp::ChordType::Major);
417 }
418 return builder.build_in_registry ();
419 }
420
421 template <structure::arrangement::FinalArrangerObjectSubclass ObjT>
422 auto clone_new_object_identity (const ObjT &other) const
423 {
424 if constexpr (ClipObject<ObjT>)
425 {
426 return utils::clone_object (
427 other, dependencies_.registry_, utils::ObjectCloneType::NewIdentity,
428 dependencies_.tempo_map_, dependencies_.registry_);
429 }
430 else if constexpr (std::is_same_v<ObjT, Marker>)
431 {
432 return utils::clone_object (
433 other, dependencies_.registry_, utils::ObjectCloneType::NewIdentity,
434 dependencies_.tempo_map_, other.markerType ());
435 }
436 else if constexpr (std::is_same_v<ObjT, AudioSourceObject>)
437 {
438 return utils::clone_object (
439 other, dependencies_.registry_, utils::ObjectCloneType::NewIdentity,
440 dependencies_.tempo_map_, dependencies_.registry_,
441 other.audio_source_ref ());
442 }
443 else
444 {
445 return utils::clone_object (
446 other, dependencies_.registry_, utils::ObjectCloneType::NewIdentity,
447 dependencies_.tempo_map_);
448 }
449 }
450
451// deprecated - no use case for snapshots, just serialize straight to/from json
452#if 0
453 template <structure::arrangement::FinalArrangerObjectSubclass ObjT>
454 auto clone_object_snapshot (const ObjT &other, QObject &owner) const
455 {
456 ObjT * new_obj{};
457 if constexpr (std::is_same_v<ObjT, AudioClip>)
458 {
459 // TODO
460 new_obj = utils::clone_qobject (
461 other, &owner, utils::ObjectCloneType::Snapshot, object_registry_,
462 file_audio_source_registry_);
463 }
464 else if constexpr (std::is_same_v<ObjT, Marker>)
465 {
466 new_obj = utils::clone_qobject (
467 other, &owner, utils::ObjectCloneType::Snapshot, object_registry_,
468 [] (const auto &name) { return true; });
469 }
470 else
471 {
472 new_obj = utils::clone_qobject (
473 other, &owner, utils::ObjectCloneType::Snapshot, object_registry_);
474 }
475 return new_obj;
476 }
477#endif
478
479private:
480 Dependencies dependencies_;
481 SampleRateProvider sample_rate_provider_;
482 BpmProvider bpm_provider_;
483};
484}
Algorithm
The algorithm to use for curves.
Definition curve.h:33
std::unique_ptr< ObjT > build_empty() const
Returns an instantiated object to be used for deserialization.
auto create_audio_clip_from_audio_buffer(const utils::audio::AudioBuffer &buf, utils::audio::BitDepth bit_depth, const utils::Utf8String &clip_name, units::precise_tick_t start_ticks) const
Creates and registers a new AudioClip and then creates and returns an AudioClip from it.
auto create_editor_object(units::precise_tick_t startTicks, std::variant< int, double > value)
Used to create and add editor objects.
auto create_audio_clip_with_clip(dsp::FileAudioSourceUuidReference clip_id, units::precise_tick_t startTicks) const
To be used by the backend.
Abstract interface for a UUID-keyed object registry.
A unique pointer for QObject objects that also works with QObject-based ownership.
Definition qt.h:36
Typed, reference-counted UUID reference into an IObjectRegistry.
Lightweight UTF-8 string wrapper with safe conversions.
Definition utf8_string.h:37
@ NewIdentity
Creates a separately identified object.
Definition icloneable.h:30
@ Snapshot
Creates a snapshot of the object with the same identity.
Definition icloneable.h:23