Zrythm v2.0.0-DEV
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
8namespace zrythm::structure::arrangement
9{
13class ArrangerObjectFactory
14{
15public:
16 using SampleRateProvider = std::function<units::sample_rate_t ()>;
17 using BpmProvider = std::function<float ()>;
18
20 {
21 using MusicalModeGetter = std::function<bool ()>;
22 using LastTimelineObjectLengthProvider = std::function<double ()>;
23 using LastEditorObjectLengthProvider = std::function<double ()>;
24 using AutomationCurveAlgorithmProvider =
25 std::function<dsp::CurveOptions::Algorithm ()>;
26
27 const dsp::TempoMap &tempo_map_;
28 ArrangerObjectRegistry &object_registry_;
29 dsp::FileAudioSourceRegistry &file_audio_source_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 (double start_ticks)
66 {
67 start_ticks_ = start_ticks;
68
69 return *this;
70 }
71
72 Builder &with_end_ticks (double 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_automatable_value (const double automatable_val)
101 requires (std::is_same_v<ObjT, AutomationPoint>)
102 {
103 automatable_value_ = automatable_val;
104 return *this;
105 }
106
107 Builder &with_chord_descriptor (const int chord_descriptor_index)
108 requires (std::is_same_v<ObjT, ChordObject>)
109 {
110 chord_descriptor_index_ = chord_descriptor_index;
111 return *this;
112 }
113
114 Builder &with_scale (utils::QObjectUniquePtr<dsp::MusicalScale> scale)
115 requires (std::is_same_v<ObjT, ScaleObject>)
116 {
117 scale_ = std::move (scale);
118 return *this;
119 }
120
121 Builder &with_marker_type (Marker::MarkerType marker_type)
122 requires (std::is_same_v<ObjT, Marker>)
123 {
124 marker_type_ = marker_type;
125 return *this;
126 }
127
131 std::unique_ptr<ObjT> build_empty () const
132 {
133 std::unique_ptr<ObjT> ret;
134 if constexpr (std::is_same_v<ObjT, AudioRegion>)
135 {
136 ret = std::make_unique<ObjT> (
137 dependencies_.tempo_map_, dependencies_.object_registry_,
138 dependencies_.file_audio_source_registry_,
139 dependencies_.musical_mode_getter_);
140 }
141 else if constexpr (RegionObject<ObjT>)
142 {
143 ret = std::make_unique<ObjT> (
144 dependencies_.tempo_map_, dependencies_.object_registry_,
145 dependencies_.file_audio_source_registry_);
146 }
147 else if constexpr (std::is_same_v<ObjT, Marker>)
148 {
149 ret = std::make_unique<ObjT> (
150 dependencies_.tempo_map_,
151 marker_type_.value_or (Marker::MarkerType::Custom));
152 }
153 else if constexpr (std::is_same_v<ObjT, AudioSourceObject>)
154 {
155 auto file_audio_source =
156 dependencies_.file_audio_source_registry_.create_object<
158 1, units::samples (1), units::sample_rate (44100), 120, u8"dummy");
159 ret = std::make_unique<ObjT> (
160 dependencies_.tempo_map_, dependencies_.file_audio_source_registry_,
161 file_audio_source);
162 }
163 else
164 {
165 ret = std::make_unique<ObjT> (dependencies_.tempo_map_);
166 }
167 return ret;
168 }
169
170 auto build_in_registry ()
171 {
172 auto obj_ref = [&] () {
173 auto obj_unique_ptr = build_empty ();
174 dependencies_.object_registry_.register_object (obj_unique_ptr.get ());
175 structure::arrangement::ArrangerObjectUuidReference ret_ref{
176 obj_unique_ptr->get_uuid (), dependencies_.object_registry_
177 };
178 obj_unique_ptr.release ();
179 return ret_ref;
180 }();
181
182 auto * obj = std::get<ObjT *> (obj_ref.get_object ());
183
184 if constexpr (RegionObject<ObjT>)
185 {
186 obj->loopRange ()->setTrackBounds (true);
187 }
188
189 if (start_ticks_)
190 {
191 if (!end_ticks_ && !clip_id_)
192 {
193 if constexpr (BoundedObject<ObjT>)
194 {
195 double len_ticks{};
196 if constexpr (TimelineObject<ObjT>)
197 {
198 len_ticks =
199 dependencies_.last_timeline_obj_len_provider_ ();
200 }
201 else
202 {
203 len_ticks = dependencies_.last_editor_obj_len_provider_ ();
204 }
205 get_object_bounds (*obj)->length ()->setTicks (len_ticks);
206 }
207 }
208 obj->position ()->setTicks (*start_ticks_);
209 }
210
211 if (clip_id_)
212 {
213 if constexpr (std::is_same_v<ObjT, AudioRegion>)
214 {
215 auto source_object = dependencies_.object_registry_.create_object<
216 AudioSourceObject> (
217 dependencies_.tempo_map_,
218 dependencies_.file_audio_source_registry_, clip_id_.value ());
219 obj->set_source (source_object);
220 obj->bounds ()->length ()->setSamples (
221 clip_id_.value ()
222 .template get_object_as<dsp::FileAudioSource> ()
223 ->get_num_frames ());
224 }
225 }
226
227 if (end_ticks_)
228 {
229 if constexpr (BoundedObject<ObjT>)
230 {
231 get_object_bounds (*obj)->length ()->setTicks (
232 *end_ticks_ - obj->position ()->ticks ());
233 }
234 }
235
236 if (name_)
237 {
238 if constexpr (NamedObject<ObjT>)
239 {
240 if constexpr (RegionObject<ObjT>)
241 {
242 obj->name ()->setName (*name_);
243 }
244 else
245 {
246 obj->name ()->setName (*name_);
247 }
248 }
249 }
250
251 if (pitch_)
252 {
253 if constexpr (std::is_same_v<ObjT, MidiNote>)
254 {
255 obj->setPitch (*pitch_);
256 }
257 }
258
259 if (velocity_)
260 {
261 if constexpr (std::is_same_v<ObjT, MidiNote>)
262 {
263 obj->setVelocity (*velocity_);
264 }
265 }
266
267 if (automatable_value_)
268 {
269 if constexpr (std::is_same_v<ObjT, AutomationPoint>)
270 {
271 obj->setValue (static_cast<float> (*automatable_value_));
272 }
273 }
274
275 if (scale_)
276 {
277 if constexpr (std::is_same_v<ObjT, ScaleObject>)
278 {
279 obj->setScale (scale_.release ());
280 }
281 }
282
283 if constexpr (std::is_same_v<ObjT, AutomationPoint>)
284 {
285 obj->curveOpts ()->setAlgorithm (
286 dependencies_.automation_curve_algorithm_provider_ ());
287 }
288
289 if (chord_descriptor_index_)
290 {
291 if constexpr (std::is_same_v<ObjT, ChordObject>)
292 {
293 obj->setChordDescriptorIndex (*chord_descriptor_index_);
294 }
295 }
296
297 return obj_ref;
298 }
299
300 private:
301 Dependencies dependencies_;
302 std::optional<dsp::FileAudioSourceUuidReference> clip_id_;
303 std::optional<double> start_ticks_;
304 std::optional<double> end_ticks_;
305 std::optional<QString> name_;
306 std::optional<int> pitch_;
307 std::optional<double> automatable_value_;
308 std::optional<int> chord_descriptor_index_;
309 utils::QObjectUniquePtr<dsp::MusicalScale> scale_;
310 std::optional<int> velocity_;
311 std::optional<Marker::MarkerType> marker_type_;
312 };
313
314 template <typename ObjT> auto get_builder () const
315 {
316 return Builder<ObjT> (dependencies_);
317 }
318
319public:
324 dsp::FileAudioSourceUuidReference clip_id,
325 double startTicks) const
326 {
327 auto obj =
328 get_builder<structure::arrangement::AudioRegion> ()
329 .with_start_ticks (startTicks)
330 .with_clip (std::move (clip_id))
331 .build_in_registry ();
332 return obj;
333 }
334
335 auto create_empty_audio_region_for_recording (
336 int num_channels,
337 const utils::Utf8String &clip_name,
338 double start_ticks)
339 {
340 auto clip = dependencies_.file_audio_source_registry_.create_object<
342 num_channels, units::samples (1), sample_rate_provider_ (),
343 bpm_provider_ (), clip_name);
344 return create_audio_region_with_clip (std::move (clip), start_ticks);
345 }
346
347 auto create_audio_region_from_file (const QString &absPath, double startTicks)
348 {
349 auto clip = dependencies_.file_audio_source_registry_.create_object<
351 utils::Utf8String::from_qstring (absPath), sample_rate_provider_ (),
352 bpm_provider_ ());
353 return create_audio_region_with_clip (std::move (clip), startTicks);
354 }
355
363 const utils::audio::AudioBuffer &buf,
364 utils::audio::BitDepth bit_depth,
365 const utils::Utf8String &clip_name,
366 double start_ticks) const
367 {
368 auto clip = dependencies_.file_audio_source_registry_.create_object<
370 buf, bit_depth, sample_rate_provider_ (), bpm_provider_ (), clip_name);
371 return create_audio_region_with_clip (std::move (clip), start_ticks);
372 }
373
381 template <structure::arrangement::RegionObject RegionT>
382 auto create_editor_object (double startTicks, std::variant<int, double> value)
383 requires (!std::is_same_v<RegionT, structure::arrangement::AudioRegion>)
384 {
385 using ChildT = typename RegionT::ArrangerObjectChildType;
386 auto builder =
387 std::move (get_builder<ChildT> ().with_start_ticks (startTicks));
388 if constexpr (std::is_same_v<ChildT, MidiNote>)
389 {
390 const auto ival = std::get<int> (value);
391 assert (ival >= 0 && ival < 128);
392 builder.with_pitch (ival);
393 }
394 else if constexpr (std::is_same_v<ChildT, AutomationPoint>)
395 {
396 builder.with_automatable_value (std::get<double> (value));
397 }
398 else if constexpr (std::is_same_v<ChildT, ChordObject>)
399 {
400 builder.with_chord_descriptor (std::get<int> (value));
401 }
402 return builder.build_in_registry ();
403 }
404
405 template <structure::arrangement::FinalArrangerObjectSubclass ObjT>
406 auto clone_new_object_identity (const ObjT &other) const
407 {
408 if constexpr (std::is_same_v<ObjT, AudioRegion>)
409 {
410 return dependencies_.object_registry_.clone_object (
411 other, dependencies_.tempo_map_, dependencies_.object_registry_,
412 dependencies_.file_audio_source_registry_,
413 dependencies_.musical_mode_getter_);
414 }
415 else if constexpr (RegionObject<ObjT>)
416 {
417 return dependencies_.object_registry_.clone_object (
418 other, dependencies_.tempo_map_, dependencies_.object_registry_,
419 dependencies_.file_audio_source_registry_);
420 }
421 else if constexpr (std::is_same_v<ObjT, Marker>)
422 {
423 return dependencies_.object_registry_.clone_object (
424 other, dependencies_.tempo_map_, other.markerType ());
425 }
426 else if constexpr (std::is_same_v<ObjT, AudioSourceObject>)
427 {
428 return dependencies_.object_registry_.clone_object (
429 other, dependencies_.tempo_map_,
430 dependencies_.file_audio_source_registry_, other.audio_source_ref ());
431 }
432 else
433 {
434 return dependencies_.object_registry_.clone_object (
435 other, dependencies_.tempo_map_);
436 }
437 }
438
439// deprecated - no use case for snapshots, just serialize straight to/from json
440#if 0
441 template <structure::arrangement::FinalArrangerObjectSubclass ObjT>
442 auto clone_object_snapshot (const ObjT &other, QObject &owner) const
443 {
444 ObjT * new_obj{};
445 if constexpr (std::is_same_v<ObjT, AudioRegion>)
446 {
447 // TODO
448 new_obj = utils::clone_qobject (
449 other, &owner, utils::ObjectCloneType::Snapshot, object_registry_,
450 file_audio_source_registry_);
451 }
452 else if constexpr (std::is_same_v<ObjT, Marker>)
453 {
454 new_obj = utils::clone_qobject (
455 other, &owner, utils::ObjectCloneType::Snapshot, object_registry_,
456 [] (const auto &name) { return true; });
457 }
458 else
459 {
460 new_obj = utils::clone_qobject (
461 other, &owner, utils::ObjectCloneType::Snapshot, object_registry_);
462 }
463 return new_obj;
464 }
465#endif
466
467private:
468 Dependencies dependencies_;
469 SampleRateProvider sample_rate_provider_;
470 BpmProvider bpm_provider_;
471};
472}
Algorithm
The algorithm to use for curves.
Definition curve.h:33
Audio clips for the pool.
std::unique_ptr< ObjT > build_empty() const
Returns an instantiated object to be used for deserialization.
auto create_editor_object(double startTicks, std::variant< int, double > value)
Used to create and add editor objects.
auto create_audio_region_with_clip(dsp::FileAudioSourceUuidReference clip_id, double startTicks) const
To be used by the backend.
auto create_audio_region_from_audio_buffer(const utils::audio::AudioBuffer &buf, utils::audio::BitDepth bit_depth, const utils::Utf8String &clip_name, double start_ticks) const
Creates and registers a new AudioClip and then creates and returns an AudioRegion from it.
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
@ Snapshot
Creates a snapshot of the object with the same identity.
Definition icloneable.h:23