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/registry_utils.h"
8
9namespace zrythm::structure::arrangement
10{
14class ArrangerObjectFactory
15{
16public:
17 using SampleRateProvider = std::function<units::sample_rate_t ()>;
18 using BpmProvider = std::function<float ()>;
19
21 {
22 using MusicalModeGetter = std::function<bool ()>;
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::TempoMap &tempo_map_;
29 utils::IObjectRegistry &registry_;
30 MusicalModeGetter musical_mode_getter_;
31 LastTimelineObjectLengthProvider last_timeline_obj_len_provider_;
32 LastEditorObjectLengthProvider last_editor_obj_len_provider_;
33 AutomationCurveAlgorithmProvider automation_curve_algorithm_provider_;
34 };
35
36 ArrangerObjectFactory (
37 Dependencies dependencies,
38 SampleRateProvider sample_rate_provider,
39 BpmProvider bpm_provider)
40 : dependencies_ (std::move (dependencies)),
41 sample_rate_provider_ (std::move (sample_rate_provider)),
42 bpm_provider_ (std::move (bpm_provider))
43 {
44 }
45
46 template <structure::arrangement::FinalArrangerObjectSubclass ObjT>
47 class Builder
48 {
49 friend class ArrangerObjectFactory;
50
51 private:
52 explicit Builder (Dependencies dependencies)
53 : dependencies_ (std::move (dependencies))
54 {
55 }
56
57 Builder &with_clip (dsp::FileAudioSourceUuidReference clip_id)
58 requires (std::is_same_v<ObjT, AudioRegion>)
59 {
60 clip_id_.emplace (std::move (clip_id));
61 return *this;
62 }
63
64 public:
65 Builder &with_start_ticks (units::precise_tick_t start_ticks)
66 {
67 start_ticks_ = start_ticks;
68
69 return *this;
70 }
71
72 Builder &with_end_ticks (units::precise_tick_t end_ticks)
73 requires (BoundedObject<ObjT>)
74 {
75 end_ticks_ = end_ticks;
76 return *this;
77 }
78
79 Builder &with_name (const QString &name)
80 requires (NamedObject<ObjT>)
81 {
82 name_ = name;
83 return *this;
84 }
85
86 Builder &with_pitch (const int pitch)
87 requires (std::is_same_v<ObjT, MidiNote>)
88 {
89 pitch_ = pitch;
90 return *this;
91 }
92
93 Builder &with_velocity (const int vel)
94 requires (std::is_same_v<ObjT, MidiNote>)
95 {
96 velocity_ = vel;
97 return *this;
98 }
99
100 Builder &with_midi_channel (const int channel)
101 requires (
102 std::is_same_v<ObjT, MidiNote> || std::is_same_v<ObjT, MidiControlEvent>)
103 {
104 midi_channel_ = channel;
105 return *this;
106 }
107
108 Builder &with_automatable_value (const double automatable_val)
109 requires (std::is_same_v<ObjT, AutomationPoint>)
110 {
111 automatable_value_ = automatable_val;
112 return *this;
113 }
114
115 Builder &with_chord_descriptor (const int chord_descriptor_index)
116 requires (std::is_same_v<ObjT, ChordObject>)
117 {
118 chord_descriptor_index_ = chord_descriptor_index;
119 return *this;
120 }
121
122 Builder &with_scale (utils::QObjectUniquePtr<dsp::MusicalScale> scale)
123 requires (std::is_same_v<ObjT, ScaleObject>)
124 {
125 scale_ = std::move (scale);
126 return *this;
127 }
128
129 Builder &with_marker_type (Marker::MarkerType marker_type)
130 requires (std::is_same_v<ObjT, Marker>)
131 {
132 marker_type_ = marker_type;
133 return *this;
134 }
135
139 std::unique_ptr<ObjT> build_empty () const
140 {
141 std::unique_ptr<ObjT> ret;
142 if constexpr (std::is_same_v<ObjT, AudioRegion>)
143 {
144 ret = std::make_unique<ObjT> (
145 dependencies_.tempo_map_, dependencies_.registry_,
146 dependencies_.musical_mode_getter_);
147 }
148 else if constexpr (RegionObject<ObjT>)
149 {
150 ret = std::make_unique<ObjT> (
151 dependencies_.tempo_map_, dependencies_.registry_);
152 }
153 else if constexpr (std::is_same_v<ObjT, Marker>)
154 {
155 ret = std::make_unique<ObjT> (
156 dependencies_.tempo_map_,
157 marker_type_.value_or (Marker::MarkerType::Custom));
158 }
159 else if constexpr (std::is_same_v<ObjT, AudioSourceObject>)
160 {
161 utils::audio::AudioBuffer dummy_buf (1, 1);
162 dummy_buf.clear ();
163 auto file_audio_source = utils::create_object<dsp::FileAudioSource> (
164 dependencies_.registry_, dummy_buf,
165 utils::audio::BitDepth::BIT_DEPTH_32, units::sample_rate (44100),
166 120, u8"dummy");
167 ret = std::make_unique<ObjT> (
168 dependencies_.tempo_map_, dependencies_.registry_,
169 file_audio_source);
170 }
171 else
172 {
173 ret = std::make_unique<ObjT> (dependencies_.tempo_map_);
174 }
175 return ret;
176 }
177
178 auto build_in_registry ()
179 {
180 auto obj_ref = [&] () {
181 auto obj_unique_ptr = build_empty ();
182 dependencies_.registry_.register_object (*obj_unique_ptr);
183 structure::arrangement::ArrangerObjectUuidReference ret_ref{
184 obj_unique_ptr->get_uuid (), dependencies_.registry_
185 };
186 obj_unique_ptr.release ();
187 return ret_ref;
188 }();
189
190 auto * obj = obj_ref.template get_object_as<ObjT> ();
191
192 if constexpr (RegionObject<ObjT>)
193 {
194 obj->loopRange ()->setTrackBounds (true);
195 }
196
197 if (start_ticks_)
198 {
199 if (!end_ticks_ && !clip_id_)
200 {
201 if constexpr (BoundedObject<ObjT>)
202 {
203 double len_ticks{};
204 if constexpr (TimelineObject<ObjT>)
205 {
206 len_ticks =
207 dependencies_.last_timeline_obj_len_provider_ ();
208 }
209 else
210 {
211 len_ticks = dependencies_.last_editor_obj_len_provider_ ();
212 }
213 get_object_bounds (*obj)->length ()->setTicks (len_ticks);
214 }
215 }
216 obj->position ()->setTicks ((*start_ticks_).in (units::ticks));
217 }
218
219 if (clip_id_)
220 {
221 if constexpr (std::is_same_v<ObjT, AudioRegion>)
222 {
223 auto source_object = utils::create_object<AudioSourceObject> (
224 dependencies_.registry_, dependencies_.tempo_map_,
225 dependencies_.registry_, clip_id_.value ());
226 obj->set_source (source_object);
227 obj->bounds ()->length ()->setSamples (
228 clip_id_.value ()
229 .template get_object_as<dsp::FileAudioSource> ()
230 ->get_num_frames ());
231 }
232 }
233
234 if (end_ticks_)
235 {
236 if constexpr (BoundedObject<ObjT>)
237 {
238 get_object_bounds (*obj)->length ()->setTicks (
239 (*end_ticks_).in (units::ticks) -obj->position ()->ticks ());
240 }
241 }
242
243 if (name_)
244 {
245 if constexpr (NamedObject<ObjT>)
246 {
247 if constexpr (RegionObject<ObjT>)
248 {
249 obj->name ()->setName (*name_);
250 }
251 else
252 {
253 obj->name ()->setName (*name_);
254 }
255 }
256 }
257
258 if (pitch_)
259 {
260 if constexpr (std::is_same_v<ObjT, MidiNote>)
261 {
262 obj->setPitch (*pitch_);
263 }
264 }
265
266 if (velocity_)
267 {
268 if constexpr (std::is_same_v<ObjT, MidiNote>)
269 {
270 obj->setVelocity (*velocity_);
271 }
272 }
273
274 if (midi_channel_)
275 {
276 if constexpr (std::is_same_v<ObjT, MidiNote>)
277 {
278 obj->setMidiChannel (*midi_channel_);
279 }
280 else if constexpr (std::is_same_v<ObjT, MidiControlEvent>)
281 {
282 obj->setChannel (*midi_channel_);
283 }
284 }
285
286 if (automatable_value_)
287 {
288 if constexpr (std::is_same_v<ObjT, AutomationPoint>)
289 {
290 obj->setValue (static_cast<float> (*automatable_value_));
291 }
292 }
293
294 if (scale_)
295 {
296 if constexpr (std::is_same_v<ObjT, ScaleObject>)
297 {
298 obj->setScale (scale_.release ());
299 }
300 }
301
302 if constexpr (std::is_same_v<ObjT, AutomationPoint>)
303 {
304 obj->curveOpts ()->setAlgorithm (
305 dependencies_.automation_curve_algorithm_provider_ ());
306 }
307
308 if (chord_descriptor_index_)
309 {
310 if constexpr (std::is_same_v<ObjT, ChordObject>)
311 {
312 obj->setChordDescriptorIndex (*chord_descriptor_index_);
313 }
314 }
315
316 return obj_ref;
317 }
318
319 private:
320 Dependencies dependencies_;
321 std::optional<dsp::FileAudioSourceUuidReference> clip_id_;
322 std::optional<units::precise_tick_t> start_ticks_;
323 std::optional<units::precise_tick_t> end_ticks_;
324 std::optional<QString> name_;
325 std::optional<int> pitch_;
326 std::optional<double> automatable_value_;
327 std::optional<int> chord_descriptor_index_;
328 utils::QObjectUniquePtr<dsp::MusicalScale> scale_;
329 std::optional<int> velocity_;
330 std::optional<int> midi_channel_;
331 std::optional<Marker::MarkerType> marker_type_;
332 };
333
334 template <typename ObjT> auto get_builder () const
335 {
336 return Builder<ObjT> (dependencies_);
337 }
338
339public:
344 dsp::FileAudioSourceUuidReference clip_id,
345 units::precise_tick_t startTicks) const
346 {
347 auto obj =
348 get_builder<structure::arrangement::AudioRegion> ()
349 .with_start_ticks (startTicks)
350 .with_clip (std::move (clip_id))
351 .build_in_registry ();
352 return obj;
353 }
354
355 auto create_audio_region_from_file (
356 const QString &absPath,
357 units::precise_tick_t startTicks)
358 {
359 auto clip = utils::create_object<dsp::FileAudioSource> (
360 dependencies_.registry_, utils::Utf8String::from_qstring (absPath),
361 sample_rate_provider_ (), bpm_provider_ ());
362 return create_audio_region_with_clip (std::move (clip), startTicks);
363 }
364
372 const utils::audio::AudioBuffer &buf,
373 utils::audio::BitDepth bit_depth,
374 const utils::Utf8String &clip_name,
375 units::precise_tick_t start_ticks) const
376 {
377 auto clip = utils::create_object<dsp::FileAudioSource> (
378 dependencies_.registry_, buf, bit_depth, sample_rate_provider_ (),
379 bpm_provider_ (), clip_name);
380 return create_audio_region_with_clip (std::move (clip), start_ticks);
381 }
382
390 template <structure::arrangement::EditorObject ChildT>
392 units::precise_tick_t startTicks,
393 std::variant<int, double> value)
394 {
395 auto builder =
396 std::move (get_builder<ChildT> ().with_start_ticks (startTicks));
397 if constexpr (std::is_same_v<ChildT, MidiNote>)
398 {
399 const auto ival = std::get<int> (value);
400 assert (ival >= 0 && ival < 128);
401 builder.with_pitch (ival);
402 }
403 else if constexpr (std::is_same_v<ChildT, AutomationPoint>)
404 {
405 builder.with_automatable_value (std::get<double> (value));
406 }
407 else if constexpr (std::is_same_v<ChildT, ChordObject>)
408 {
409 builder.with_chord_descriptor (std::get<int> (value));
410 }
411 return builder.build_in_registry ();
412 }
413
414 template <structure::arrangement::FinalArrangerObjectSubclass ObjT>
415 auto clone_new_object_identity (const ObjT &other) const
416 {
417 if constexpr (std::is_same_v<ObjT, AudioRegion>)
418 {
419 return utils::clone_object (
420 other, dependencies_.registry_, utils::ObjectCloneType::NewIdentity,
421 dependencies_.tempo_map_, dependencies_.registry_,
422 dependencies_.musical_mode_getter_);
423 }
424 else if constexpr (RegionObject<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, AudioRegion>)
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_region_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 AudioRegion from it.
auto create_audio_region_with_clip(dsp::FileAudioSourceUuidReference clip_id, units::precise_tick_t startTicks) const
To be used by the backend.
auto create_editor_object(units::precise_tick_t startTicks, std::variant< int, double > value)
Used to create and add editor objects.
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
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