6#include "structure/arrangement/arranger_object_all.h"
7#include "utils/debug.h"
8#include "utils/uuid_identifiable_object.h"
10namespace zrythm::structure::arrangement
16enum class ResizeType : basic_enum_base_type_t
39template <BoundedObject ObjectT>
41resize_bounded_object (ObjectT &obj,
bool left, ResizeType type,
double ticks)
43 z_trace (
"resizing object( left: {}, type: {}, ticks: {})", left, type, ticks);
49 if (type != ResizeType::Fade)
51 obj.getPosition ()->setTicks (obj.getPosition ()->ticks () + ticks);
55 obj.get_fade_range ().startOffset->setTicks (
56 obj.get_fade_range ().startOffset->ticks () - ticks);
59 if (type == ResizeType::Loop)
64 obj.get_loop_range ().get_loop_length_in_ticks ();
67 const auto loop_start_pos =
68 obj.get_loop_range ().loopStartPosition ()->ticks ();
70 obj.get_loop_range ().clipStartPosition ()->ticks ();
71 if (clip_start_pos >= loop_start_pos)
73 clip_start_pos += ticks;
75 while (clip_start_pos < loop_start_pos)
77 clip_start_pos += loop_len;
79 obj.get_loop_range ().clipStartPosition ()->setTicks (
85 const auto loop_end_pos =
86 obj.get_loop_range ().loopEndPosition ()->ticks ();
87 while (clip_start_pos > loop_end_pos)
89 clip_start_pos += -loop_len;
91 obj.get_loop_range ().clipStartPosition ()->setTicks (
97 obj.get_loop_range ().loopEndPosition ()->setTicks (
98 obj.get_loop_range ().loopEndPosition ()->ticks () - ticks);
102 is_derived_from_template_v<ArrangerObjectOwner, ObjectT>)
104 obj.add_ticks_to_children (-ticks);
112 if (type != ResizeType::Fade)
114 obj.length ()->setTicks (obj.length ()->ticks () + ticks);
116 const auto change_ratio =
117 (obj.length ()->ticks ()) / (obj.length ()->ticks () - ticks);
119 if (type != ResizeType::Loop)
124 type == ResizeType::StretchTempoChange
125 || type == ResizeType::Stretch)
127 obj.get_loop_range ().loopEndPosition ()->setTicks (
128 obj.get_loop_range ().loopEndPosition ()->ticks ()
133 obj.get_loop_range ().loopEndPosition ()->setTicks (
134 obj.get_loop_range ().loopEndPosition ()->ticks ()
140 type == ResizeType::StretchTempoChange
141 || type == ResizeType::Stretch)
143 obj.get_loop_range ().loopStartPosition ()->setTicks (
144 obj.get_loop_range ().loopStartPosition ()->ticks ()
153 type == ResizeType::StretchTempoChange
154 || type == ResizeType::Stretch)
156 obj.get_fade_range ().endOffset ()->setTicks (
157 obj.get_fade_range ().endOffset ()->ticks () * change_ratio);
158 obj.get_fade_range ().startOffset ()->setTicks (
159 obj.get_fade_range ().startOffset ()->ticks ()
166 if (type == ResizeType::Stretch)
168 if constexpr (std::derived_from<ObjT, Region>)
170 double new_length = get_length_in_ticks ();
172 if (type != ResizeType::StretchTempoChange)
177 if (during_ui_action)
179 self->stretch_ratio_ =
180 new_length / self->before_length_;
186 double stretch_ratio = new_length / before_length;
189 self->stretch (stretch_ratio);
191 catch (
const ZrythmException &ex)
193 throw ZrythmException (
194 "Failed to stretch region");
214template <RegionObject RegionT>
216stretch_region_contents (RegionT &r,
double ratio)
218 z_debug (
"stretching region {} (ratio {:f})", r, ratio);
222 if constexpr (std::is_same_v<RegionT, AudioRegion>)
224 auto * clip = r.get_clip ();
225 auto new_clip_id = AUDIO_POOL->duplicate_clip (clip->get_uuid (),
false);
226 auto * new_clip = AUDIO_POOL->get_clip (new_clip_id);
227 r.set_clip_id (new_clip->get_uuid ());
230 AUDIO_ENGINE->get_sample_rate (), new_clip->get_num_channels (), ratio,
233 auto buf = new_clip->get_samples ();
234 buf.interleave_samples ();
235 auto stretched_buf = stretcher->stretch_interleaved (buf);
236 stretched_buf.deinterleave_samples (new_clip->get_num_channels ());
237 new_clip->clear_frames ();
238 new_clip->expand_with_frames (stretched_buf);
239 auto num_frames_per_channel = new_clip->get_num_frames ();
240 z_return_if_fail (num_frames_per_channel > 0);
242 AUDIO_POOL->write_clip (*new_clip,
false,
false);
245 dsp::Position new_end_pos (
247 AUDIO_ENGINE->ticks_per_frame_);
249 r.loopEndPosition ()->setSamples (num_frames_per_channel);
250 r.length ()->setSamples (num_frames_per_channel);
254 auto objs = r.get_children_view ();
255 for (
auto * obj : objs)
257 using ObjT = base_type<
decltype (obj)>;
259 double before_ticks = obj->get_position ().ticks_;
260 double new_ticks = before_ticks * ratio;
261 dsp::Position tmp (new_ticks, AUDIO_ENGINE->frames_per_tick_);
262 obj->position_setter_validated (tmp, AUDIO_ENGINE->ticks_per_frame_);
264 if constexpr (std::derived_from<ObjT, BoundedObject>)
267 before_ticks = obj->end_pos_->ticks_;
268 new_ticks = before_ticks * ratio;
269 tmp = dsp::Position (new_ticks, AUDIO_ENGINE->frames_per_tick_);
270 obj->end_position_setter_validated (
271 tmp, AUDIO_ENGINE->ticks_per_frame_);
286 using VariantType =
typename Base::VariantType;
287 using ArrangerObjectUuid =
typename Base::UuidType;
290 static auto name_projection (
const VariantType &obj_var)
294 using ObjT = base_type<
decltype (obj)>;
299 return obj->name ()->get_name ();
301 else if constexpr (std::is_same_v<ObjT, Marker>)
303 return obj->name ()->get_name ();
308 throw std::runtime_error (
309 "Name projection called on non-named object");
314 static auto position_ticks_projection (
const VariantType &obj_var)
317 [&] (
auto &&obj) {
return obj->position ()->ticks (); }, obj_var);
319 static auto end_position_ticks_with_start_position_fallback_projection (
320 const VariantType &obj_var)
324 using ObjT = base_type<
decltype (obj)>;
325 auto ticks = obj->position ()->ticks ();
330 return ticks + obj->bounds ()->length ()->ticks ();
334 return ticks + obj->bounds ()->length ()->ticks ();
344 static auto midi_note_pitch_projection (
const VariantType &obj_var)
346 return std::get<MidiNote *> (obj_var)->pitch ();
348 static auto looped_projection (
const VariantType &obj_var)
351 [] (
const auto &obj) {
352 using ObjT = base_type<
decltype (obj)>;
355 return obj->loopRange ()->looped ();
362 static auto is_timeline_object_projection (
const VariantType &obj_var)
365 [] (
const auto &ptr) {
return TimelineObject<base_type<
decltype (ptr)>>; },
368 static auto is_editor_object_projection (
const VariantType &obj_var)
370 return !is_timeline_object_projection (obj_var);
372 static auto deletable_projection (
const VariantType &obj_var)
375 [&] (
auto &&obj) {
return is_arranger_object_deletable (*obj); }, obj_var);
377 static auto cloneable_projection (
const VariantType &obj_var)
379 return deletable_projection (obj_var);
381 static auto renameable_projection (
const VariantType &obj_var)
384 [] (
const auto &ptr) {
388 && deletable_projection (obj_var);
390 static auto bounded_projection (
const VariantType &obj_var)
393 [] (
const auto &ptr) {
return BoundedObject<base_type<
decltype (ptr)>>; },
396 static auto is_region_projection (
const VariantType &obj_var)
399 [] (
const auto &ptr) {
return RegionObject<base_type<
decltype (ptr)>>; },
402 static auto bounds_projection (
const VariantType &obj_var)
406 using ObjT = base_type<
decltype (ptr)>;
409 return get_object_bounds (*ptr);
413 throw std::runtime_error (
"Not a bounded object");
421 std::vector<VariantType>
422 create_snapshots (
const auto &object_factory, QObject &owner)
const
424 return std::ranges::to<std::vector> (
425 *
this | std::views::transform ([&] (
const auto &obj_var) {
427 [&] (
auto &&obj) -> VariantType {
428 return object_factory.clone_object_snapshot (*obj, owner);
435 auto create_new_identities (
const auto &object_factory)
const
436 -> std::vector<ArrangerObjectUuidReference>
438 return std::ranges::to<std::vector> (
439 *
this | std::views::transform ([&] (
const auto &obj_var) {
441 [&] (
auto &&obj) -> VariantType {
442 return object_factory.clone_new_object_identity (*obj);
474 -> std::pair<VariantType,
double>;
481 auto [min_it, max_it] =
482 std::ranges::minmax_element (midi_notes, {}, &MidiNote::pitch);
483 return { *min_it, *max_it };
492 return !std::ranges::all_of (*
this, deletable_projection);
501 return !std::ranges::all_of (*
this, cloneable_projection);
508 return !std::ranges::all_of (*
this, renameable_projection);
524 auto merge () const -> ArrangerObjectUuidReference;
531 bool all_on_same_lane () const;
533 bool contains_looped ()
const
535 return std::ranges::any_of (*
this, looped_projection);
538 bool can_be_merged ()
const;
540 double get_length_in_ticks ()
const
554 units::sample_t pos_samples,
555 bool include_region_end =
false)
const
557 auto view = *
this | std::views::filter (bounded_projection);
558 auto it = std::ranges::find_if (view, [&] (
const auto &r_var) {
559 auto bounds = bounds_projection (r_var);
560 return bounds->is_hit (pos_samples, include_region_end);
562 return it != view.end () ? std::make_optional (*it) : std::nullopt;
575 for (
const auto &prev_mn : prev.template get_elements_by_type<MidiNote> ())
577 if (std::ranges::none_of (*
this, [&prev_mn] (
const auto &obj) {
578 auto mn = std::get<MidiNote *> (obj);
579 return *mn == *prev_mn;
582 prev_mn->listen (
false);
600 template <BoundedObject BoundedObjectT>
602 const BoundedObjectT &self,
605 -> std::pair<ArrangerObjectUuidReference, ArrangerObjectUuidReference>
607 const auto &tempo_map = self.get_tempo_map ();
609 auto new_object1_ref = factory.clone_new_object_identity (self);
610 auto new_object2_ref = factory.clone_new_object_identity (self);
611 const auto get_derived_object = [] (
auto &obj_ref) {
612 return std::get<BoundedObjectT *> (obj_ref.get_object ());
615 z_debug (
"splitting objects...");
619 auto local_pos = [&] () {
620 auto local_frames = global_pos;
623 local_frames = timeline_frames_to_local (self, global_pos,
true);
632 ArrangerObjectSpan::bounds_projection (get_derived_object (new_object1_ref))
636 - get_derived_object (new_object1_ref)->position ()->samples ());
642 if (!self.loopRange ()->is_looped ())
644 if constexpr (std::is_same_v<BoundedObjectT, AudioRegion>)
649 auto prev_r1_ref = new_object1_ref;
651 get_derived_object (prev_r1_ref)->get_clip ();
652 assert (prev_r1_clip);
654 prev_r1_clip->get_num_channels (),
655 static_cast<int> (local_pos.frames_)
657 for (
int i = 0; i < prev_r1_clip->get_num_channels (); ++i)
660 i, 0, prev_r1_clip->get_samples (), i, 0,
661 static_cast<int> (local_pos.frames_));
663 assert (!get_derived_object (prev_r1_ref)->get_name ().empty ());
664 assert (local_pos.frames_ >= 0);
666 factory.create_audio_region_from_audio_buffer_FIXME (
667 get_derived_object (prev_r1_ref)->get_lane (), frames,
668 prev_r1_clip->get_bit_depth (),
669 get_derived_object (prev_r1_ref)->get_name (),
670 get_derived_object (prev_r1_ref)->get_position ().ticks_);
672 get_derived_object (new_object1_ref)->get_clip_id ()
673 != get_derived_object (prev_r1_ref)->get_clip_id ());
681 get_derived_object (new_object1_ref)->get_children_view ())
683 if (child->position ()->samples () > local_pos)
685 get_derived_object (new_object1_ref)
686 ->remove_object (child->get_uuid ());
700 get_derived_object (new_object2_ref)
702 ->clipStartPosition ()
703 ->setSamples (local_pos);
705 get_derived_object (new_object2_ref)->position ()->setSamples (global_pos);
707 ArrangerObjectSpan::bounds_projection (get_derived_object (new_object2_ref))
708 ->get_end_position_samples (
true);
710 get_derived_object (new_object2_ref)->position ()->samples ();
715 if (!self.loopRange ()->is_looped ())
717 get_derived_object (new_object2_ref)
719 ->setTrackBounds (
true);
722 if constexpr (std::is_same_v<BoundedObjectT, AudioRegion>)
726 auto prev_r2_ref = new_object2_ref;
728 get_derived_object (prev_r2_ref)->get_clip ();
729 assert (prev_r2_clip);
730 assert (r2_local_end > 0);
732 prev_r2_clip->get_num_channels (), (int) r2_local_end
734 for (
int i = 0; i < prev_r2_clip->get_num_channels (); ++i)
737 i, 0, prev_r2_clip->get_samples (), i, local_pos,
740 assert (!get_derived_object (prev_r2_ref)->get_name ().empty ());
741 assert (r2_local_end >= 0);
743 factory.create_audio_region_from_audio_buffer_FIXME (
744 get_derived_object (prev_r2_ref)->get_lane (), tmp,
745 prev_r2_clip->get_bit_depth (),
746 get_derived_object (prev_r2_ref)->get_name (), local_pos);
748 get_derived_object (new_object2_ref)->get_clip_id ()
749 != get_derived_object (prev_r2_ref)->get_clip_id ());
755 const double ticks_to_subtract =
756 tempo_map.samples_to_tick (units::samples (local_pos))
758 get_derived_object (new_object2_ref)
759 ->add_ticks_to_children (-ticks_to_subtract);
766 get_derived_object (new_object2_ref)->get_children_view ())
768 if (child->position ().frames_ < 0)
769 get_derived_object (new_object2_ref)
770 ->remove_object (child->get_uuid ());
782 auto track_var = self.get_track ();
786 if constexpr (std::is_same_v<BoundedObjectT, AutomationRegion>)
788 at = self.get_automation_track ();
790 get_derived_object (new_object1_ref)
791 ->generate_name (self.get_name (), at, track);
792 get_derived_object (new_object2_ref)
793 ->generate_name (self.get_name (), at, track);
799 return std::make_pair (new_object1_ref, new_object2_ref);
812 static void copy_arranger_object_identifier (
813 const VariantType &dest,
814 const VariantType &src);
818static_assert (std::ranges::random_access_range<ArrangerObjectSpan>);
static std::unique_ptr< Stretcher > create_rubberband(unsigned samplerate, unsigned channels, double time_ratio, double pitch_ratio, bool realtime)
Create a new Stretcher using the rubberband backend.
Adds length functionality to arranger objects.
Track span that offers helper methods on a range of tracks.
bool can_be_pasted() const
Returns if the selections can be pasted at the current place/playhead.
auto merge() const -> ArrangerObjectUuidReference
Checks whether an object matches the given parameters.
std::optional< VariantType > get_bounded_object_at_position(units::sample_t pos_samples, bool include_region_end=false) const
Returns the region at the given position, or NULL.
bool contains_unclonable_object() const
Returns if the selections contain an unclonable object (such as the start marker).
bool contains_undeletable_object() const
Returns if the selections contain an undeletable object (such as the start marker).
static auto split_bounded_object(const BoundedObjectT &self, const auto &factory, signed_frame_t global_pos) -> std::pair< ArrangerObjectUuidReference, ArrangerObjectUuidReference >
Splits the given object at the given position and returns a pair of newly-created objects (with uniqu...
bool contains_unrenamable_object() const
Whether the selections contain an unrenameable object (such as the start marker).
auto get_first_object_and_pos() const -> std::pair< VariantType, double >
Gets first object of the given type (if any, otherwise matches all types) and its start position.
auto get_last_object_and_pos(bool ends_last) const -> std::pair< VariantType, double >
Gets last object of the given type (if any, otherwise matches all types) and its end (if applicable,...
std::vector< VariantType > sort_by_indices(bool desc)
Sorts the selections by their indices (eg, for regions, their track indices, then the lane indices,...
A MIDI note inside a Region shown in the piano roll.
Lightweight UTF-8 string wrapper with safe conversions.
A unified view over UUID-identified objects that supports:
static auto type_transformation(const VariantType &var)
int_fast64_t signed_frame_t
Signed type for frame index.