6#include "structure/arrangement/arranger_object_all.h"
7#include "utils/uuid_identifiable_object.h"
9namespace zrythm::structure::arrangement
15enum class ResizeType : basic_enum_base_type_t
38template <BoundedObject ObjectT>
40resize_bounded_object (ObjectT &obj,
bool left, ResizeType type,
double ticks)
42 z_trace (
"resizing object( left: {}, type: {}, ticks: {})", left, type, ticks);
48 if (type != ResizeType::Fade)
50 obj.getPosition ()->setTicks (obj.getPosition ()->ticks () + ticks);
54 obj.get_fade_range ().startOffset->setTicks (
55 obj.get_fade_range ().startOffset->ticks () - ticks);
58 if (type == ResizeType::Loop)
63 obj.get_loop_range ().get_loop_length_in_ticks ();
66 const auto loop_start_pos =
67 obj.get_loop_range ().loopStartPosition ()->ticks ();
69 obj.get_loop_range ().clipStartPosition ()->ticks ();
70 if (clip_start_pos >= loop_start_pos)
72 clip_start_pos += ticks;
74 while (clip_start_pos < loop_start_pos)
76 clip_start_pos += loop_len;
78 obj.get_loop_range ().clipStartPosition ()->setTicks (
84 const auto loop_end_pos =
85 obj.get_loop_range ().loopEndPosition ()->ticks ();
86 while (clip_start_pos > loop_end_pos)
88 clip_start_pos += -loop_len;
90 obj.get_loop_range ().clipStartPosition ()->setTicks (
96 obj.get_loop_range ().loopEndPosition ()->setTicks (
97 obj.get_loop_range ().loopEndPosition ()->ticks () - ticks);
101 is_derived_from_template_v<ArrangerObjectOwner, ObjectT>)
103 obj.add_ticks_to_children (-ticks);
111 if (type != ResizeType::Fade)
113 obj.length ()->setTicks (obj.length ()->ticks () + ticks);
115 const auto change_ratio =
116 (obj.length ()->ticks ()) / (obj.length ()->ticks () - ticks);
118 if (type != ResizeType::Loop)
123 type == ResizeType::StretchTempoChange
124 || type == ResizeType::Stretch)
126 obj.get_loop_range ().loopEndPosition ()->setTicks (
127 obj.get_loop_range ().loopEndPosition ()->ticks ()
132 obj.get_loop_range ().loopEndPosition ()->setTicks (
133 obj.get_loop_range ().loopEndPosition ()->ticks ()
139 type == ResizeType::StretchTempoChange
140 || type == ResizeType::Stretch)
142 obj.get_loop_range ().loopStartPosition ()->setTicks (
143 obj.get_loop_range ().loopStartPosition ()->ticks ()
152 type == ResizeType::StretchTempoChange
153 || type == ResizeType::Stretch)
155 obj.get_fade_range ().endOffset ()->setTicks (
156 obj.get_fade_range ().endOffset ()->ticks () * change_ratio);
157 obj.get_fade_range ().startOffset ()->setTicks (
158 obj.get_fade_range ().startOffset ()->ticks ()
165 if (type == ResizeType::Stretch)
167 if constexpr (std::derived_from<ObjT, Region>)
169 double new_length = get_length_in_ticks ();
171 if (type != ResizeType::StretchTempoChange)
176 if (during_ui_action)
178 self->stretch_ratio_ =
179 new_length / self->before_length_;
185 double stretch_ratio = new_length / before_length;
188 self->stretch (stretch_ratio);
190 catch (
const ZrythmException &ex)
192 throw ZrythmException (
193 "Failed to stretch region");
213template <RegionObject RegionT>
215stretch_region_contents (RegionT &r,
double ratio)
217 z_debug (
"stretching region {} (ratio {:f})", r, ratio);
221 if constexpr (std::is_same_v<RegionT, AudioRegion>)
223 auto * clip = r.get_clip ();
224 auto new_clip_id = AUDIO_POOL->duplicate_clip (clip->get_uuid (),
false);
225 auto * new_clip = AUDIO_POOL->get_clip (new_clip_id);
226 r.set_clip_id (new_clip->get_uuid ());
229 AUDIO_ENGINE->get_sample_rate (), new_clip->get_num_channels (), ratio,
232 auto buf = new_clip->get_samples ();
233 buf.interleave_samples ();
234 auto stretched_buf = stretcher->stretch_interleaved (buf);
235 stretched_buf.deinterleave_samples (new_clip->get_num_channels ());
236 new_clip->clear_frames ();
237 new_clip->expand_with_frames (stretched_buf);
238 auto num_frames_per_channel = new_clip->get_num_frames ();
239 z_return_if_fail (num_frames_per_channel > 0);
241 AUDIO_POOL->write_clip (*new_clip,
false,
false);
244 dsp::Position new_end_pos (
245 static_cast<int64_t
> (num_frames_per_channel),
246 AUDIO_ENGINE->ticks_per_frame_);
248 r.loopEndPosition ()->setSamples (num_frames_per_channel);
249 r.length ()->setSamples (num_frames_per_channel);
253 auto objs = r.get_children_view ();
254 for (
auto * obj : objs)
256 using ObjT = base_type<
decltype (obj)>;
258 double before_ticks = obj->get_position ().ticks_;
259 double new_ticks = before_ticks * ratio;
260 dsp::Position tmp (new_ticks, AUDIO_ENGINE->frames_per_tick_);
261 obj->position_setter_validated (tmp, AUDIO_ENGINE->ticks_per_frame_);
263 if constexpr (std::derived_from<ObjT, BoundedObject>)
266 before_ticks = obj->end_pos_->ticks_;
267 new_ticks = before_ticks * ratio;
268 tmp = dsp::Position (new_ticks, AUDIO_ENGINE->frames_per_tick_);
269 obj->end_position_setter_validated (
270 tmp, AUDIO_ENGINE->ticks_per_frame_);
285 using VariantType =
typename Base::VariantType;
286 using ArrangerObjectUuid =
typename Base::UuidType;
289 static auto name_projection (
const VariantType &obj_var)
293 using ObjT = base_type<
decltype (obj)>;
298 return obj->name ()->get_name ();
300 else if constexpr (std::is_same_v<ObjT, Marker>)
302 return obj->name ()->get_name ();
307 throw std::runtime_error (
308 "Name projection called on non-named object");
313 static auto position_ticks_projection (
const VariantType &obj_var)
316 [&] (
auto &&obj) {
return obj->position ()->ticks (); }, obj_var);
318 static auto end_position_ticks_with_start_position_fallback_projection (
319 const VariantType &obj_var)
323 using ObjT = base_type<
decltype (obj)>;
324 auto ticks = obj->position ()->ticks ();
329 return ticks + obj->bounds ()->length ()->ticks ();
333 return ticks + obj->bounds ()->length ()->ticks ();
343 static auto midi_note_pitch_projection (
const VariantType &obj_var)
345 return std::get<MidiNote *> (obj_var)->pitch ();
347 static auto looped_projection (
const VariantType &obj_var)
350 [] (
const auto &obj) {
351 using ObjT = base_type<
decltype (obj)>;
354 return obj->loopRange ()->looped ();
361 static auto is_timeline_object_projection (
const VariantType &obj_var)
364 [] (
const auto &ptr) {
return TimelineObject<base_type<
decltype (ptr)>>; },
367 static auto is_editor_object_projection (
const VariantType &obj_var)
369 return !is_timeline_object_projection (obj_var);
371 static auto deletable_projection (
const VariantType &obj_var)
374 [&] (
auto &&obj) {
return is_arranger_object_deletable (*obj); }, obj_var);
376 static auto cloneable_projection (
const VariantType &obj_var)
378 return deletable_projection (obj_var);
380 static auto renameable_projection (
const VariantType &obj_var)
383 [] (
const auto &ptr) {
387 && deletable_projection (obj_var);
389 static auto bounded_projection (
const VariantType &obj_var)
392 [] (
const auto &ptr) {
return BoundedObject<base_type<
decltype (ptr)>>; },
395 static auto is_region_projection (
const VariantType &obj_var)
398 [] (
const auto &ptr) {
return RegionObject<base_type<
decltype (ptr)>>; },
401 static auto bounds_projection (
const VariantType &obj_var)
405 using ObjT = base_type<
decltype (ptr)>;
408 return get_object_bounds (*ptr);
412 throw std::runtime_error (
"Not a bounded object");
420 std::vector<VariantType>
421 create_snapshots (
const auto &object_factory, QObject &owner)
const
423 return std::ranges::to<std::vector> (
424 *
this | std::views::transform ([&] (
const auto &obj_var) {
426 [&] (
auto &&obj) -> VariantType {
427 return object_factory.clone_object_snapshot (*obj, owner);
434 auto create_new_identities (
const auto &object_factory)
const
435 -> std::vector<ArrangerObjectUuidReference>
437 return std::ranges::to<std::vector> (
438 *
this | std::views::transform ([&] (
const auto &obj_var) {
440 [&] (
auto &&obj) -> VariantType {
441 return object_factory.clone_new_object_identity (*obj);
473 -> std::pair<VariantType,
double>;
480 auto [min_it, max_it] =
481 std::ranges::minmax_element (midi_notes, {}, &MidiNote::pitch);
482 return { *min_it, *max_it };
491 return !std::ranges::all_of (*
this, deletable_projection);
500 return !std::ranges::all_of (*
this, cloneable_projection);
507 return !std::ranges::all_of (*
this, renameable_projection);
523 auto merge () const -> ArrangerObjectUuidReference;
530 bool all_on_same_lane () const;
532 bool contains_looped ()
const
534 return std::ranges::any_of (*
this, looped_projection);
537 bool can_be_merged ()
const;
539 double get_length_in_ticks ()
const
553 units::sample_t pos_samples,
554 bool include_region_end =
false)
const
556 auto view = *
this | std::views::filter (bounded_projection);
557 auto it = std::ranges::find_if (view, [&] (
const auto &r_var) {
558 auto bounds = bounds_projection (r_var);
559 return bounds->is_hit (pos_samples, include_region_end);
561 return it != view.end () ? std::make_optional (*it) : std::nullopt;
574 for (
const auto &prev_mn : prev.template get_elements_by_type<MidiNote> ())
576 if (std::ranges::none_of (*
this, [&prev_mn] (
const auto &obj) {
577 auto mn = std::get<MidiNote *> (obj);
578 return *mn == *prev_mn;
581 prev_mn->listen (
false);
599 template <BoundedObject BoundedObjectT>
601 const BoundedObjectT &self,
604 -> std::pair<ArrangerObjectUuidReference, ArrangerObjectUuidReference>
606 const auto &tempo_map = self.get_tempo_map ();
608 auto new_object1_ref = factory.clone_new_object_identity (self);
609 auto new_object2_ref = factory.clone_new_object_identity (self);
610 const auto get_derived_object = [] (
auto &obj_ref) {
611 return std::get<BoundedObjectT *> (obj_ref.get_object ());
614 z_debug (
"splitting objects...");
618 auto local_pos = [&] () {
619 auto local_frames = global_pos;
622 local_frames = timeline_frames_to_local (self, global_pos,
true);
631 ArrangerObjectSpan::bounds_projection (get_derived_object (new_object1_ref))
635 - get_derived_object (new_object1_ref)->position ()->samples ());
641 if (!self.loopRange ()->is_looped ())
643 if constexpr (std::is_same_v<BoundedObjectT, AudioRegion>)
648 auto prev_r1_ref = new_object1_ref;
650 get_derived_object (prev_r1_ref)->get_clip ();
651 assert (prev_r1_clip);
653 prev_r1_clip->get_num_channels (),
654 static_cast<int> (local_pos.frames_)
656 for (
int i = 0; i < prev_r1_clip->get_num_channels (); ++i)
659 i, 0, prev_r1_clip->get_samples (), i, 0,
660 static_cast<int> (local_pos.frames_));
662 assert (!get_derived_object (prev_r1_ref)->get_name ().empty ());
663 assert (local_pos.frames_ >= 0);
665 factory.create_audio_region_from_audio_buffer_FIXME (
666 get_derived_object (prev_r1_ref)->get_lane (), frames,
667 prev_r1_clip->get_bit_depth (),
668 get_derived_object (prev_r1_ref)->get_name (),
669 get_derived_object (prev_r1_ref)->get_position ().ticks_);
671 get_derived_object (new_object1_ref)->get_clip_id ()
672 != get_derived_object (prev_r1_ref)->get_clip_id ());
680 get_derived_object (new_object1_ref)->get_children_view ())
682 if (child->position ()->samples () > local_pos)
684 get_derived_object (new_object1_ref)
685 ->remove_object (child->get_uuid ());
699 get_derived_object (new_object2_ref)
701 ->clipStartPosition ()
702 ->setSamples (local_pos);
704 get_derived_object (new_object2_ref)->position ()->setSamples (global_pos);
705 int64_t r2_local_end =
706 ArrangerObjectSpan::bounds_projection (get_derived_object (new_object2_ref))
707 ->get_end_position_samples (
true);
709 get_derived_object (new_object2_ref)->position ()->samples ();
714 if (!self.loopRange ()->is_looped ())
716 get_derived_object (new_object2_ref)
718 ->setTrackBounds (
true);
721 if constexpr (std::is_same_v<BoundedObjectT, AudioRegion>)
725 auto prev_r2_ref = new_object2_ref;
727 get_derived_object (prev_r2_ref)->get_clip ();
728 assert (prev_r2_clip);
729 assert (r2_local_end > 0);
731 prev_r2_clip->get_num_channels (), (int) r2_local_end
733 for (
int i = 0; i < prev_r2_clip->get_num_channels (); ++i)
736 i, 0, prev_r2_clip->get_samples (), i, local_pos,
739 assert (!get_derived_object (prev_r2_ref)->get_name ().empty ());
740 assert (r2_local_end >= 0);
742 factory.create_audio_region_from_audio_buffer_FIXME (
743 get_derived_object (prev_r2_ref)->get_lane (), tmp,
744 prev_r2_clip->get_bit_depth (),
745 get_derived_object (prev_r2_ref)->get_name (), local_pos);
747 get_derived_object (new_object2_ref)->get_clip_id ()
748 != get_derived_object (prev_r2_ref)->get_clip_id ());
754 const double ticks_to_subtract =
755 tempo_map.samples_to_tick (units::samples (local_pos))
757 get_derived_object (new_object2_ref)
758 ->add_ticks_to_children (-ticks_to_subtract);
765 get_derived_object (new_object2_ref)->get_children_view ())
767 if (child->position ().frames_ < 0)
768 get_derived_object (new_object2_ref)
769 ->remove_object (child->get_uuid ());
781 auto track_var = self.get_track ();
785 if constexpr (std::is_same_v<BoundedObjectT, AutomationRegion>)
787 at = self.get_automation_track ();
789 get_derived_object (new_object1_ref)
790 ->generate_name (self.get_name (), at, track);
791 get_derived_object (new_object2_ref)
792 ->generate_name (self.get_name (), at, track);
798 return std::make_pair (new_object1_ref, new_object2_ref);
811 static void copy_arranger_object_identifier (
812 const VariantType &dest,
813 const VariantType &src);
817static_assert (std::ranges::random_access_range<ArrangerObjectSpan>);
static std::unique_ptr< Stretcher > create_rubberband(units::sample_rate_t 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.
static auto split_bounded_object(const BoundedObjectT &self, const auto &factory, int64_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_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).
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)