8#include "structure/arrangement/arranger_object_all.h"
9#include "utils/uuid_identifiable_object.h"
11namespace zrythm::structure::arrangement
17enum class ResizeType : std::uint8_t
40template <BoundedObject ObjectT>
42resize_bounded_object (ObjectT &obj,
bool left, ResizeType type,
double ticks)
44 z_trace (
"resizing object( left: {}, type: {}, ticks: {})", left, type, ticks);
50 if (type != ResizeType::Fade)
52 obj.getPosition ()->setTicks (obj.getPosition ()->ticks () + ticks);
56 obj.get_fade_range ().startOffset->setTicks (
57 obj.get_fade_range ().startOffset->ticks () - ticks);
60 if (type == ResizeType::Loop)
65 obj.get_loop_range ().get_loop_length_in_ticks ();
68 const auto loop_start_pos =
69 obj.get_loop_range ().loopStartPosition ()->ticks ();
71 obj.get_loop_range ().clipStartPosition ()->ticks ();
72 if (clip_start_pos >= loop_start_pos)
74 clip_start_pos += ticks;
76 while (clip_start_pos < loop_start_pos)
78 clip_start_pos += loop_len;
80 obj.get_loop_range ().clipStartPosition ()->setTicks (
86 const auto loop_end_pos =
87 obj.get_loop_range ().loopEndPosition ()->ticks ();
88 while (clip_start_pos > loop_end_pos)
90 clip_start_pos += -loop_len;
92 obj.get_loop_range ().clipStartPosition ()->setTicks (
98 obj.get_loop_range ().loopEndPosition ()->setTicks (
99 obj.get_loop_range ().loopEndPosition ()->ticks () - ticks);
103 is_derived_from_template_v<ArrangerObjectOwner, ObjectT>)
105 obj.add_ticks_to_children (-ticks);
113 if (type != ResizeType::Fade)
115 obj.length ()->setTicks (obj.length ()->ticks () + ticks);
117 const auto change_ratio =
118 (obj.length ()->ticks ()) / (obj.length ()->ticks () - ticks);
120 if (type != ResizeType::Loop)
125 type == ResizeType::StretchTempoChange
126 || type == ResizeType::Stretch)
128 obj.get_loop_range ().loopEndPosition ()->setTicks (
129 obj.get_loop_range ().loopEndPosition ()->ticks ()
134 obj.get_loop_range ().loopEndPosition ()->setTicks (
135 obj.get_loop_range ().loopEndPosition ()->ticks ()
141 type == ResizeType::StretchTempoChange
142 || type == ResizeType::Stretch)
144 obj.get_loop_range ().loopStartPosition ()->setTicks (
145 obj.get_loop_range ().loopStartPosition ()->ticks ()
154 type == ResizeType::StretchTempoChange
155 || type == ResizeType::Stretch)
157 obj.get_fade_range ().endOffset ()->setTicks (
158 obj.get_fade_range ().endOffset ()->ticks () * change_ratio);
159 obj.get_fade_range ().startOffset ()->setTicks (
160 obj.get_fade_range ().startOffset ()->ticks ()
167 if (type == ResizeType::Stretch)
169 if constexpr (std::derived_from<ObjT, Region>)
171 double new_length = get_length_in_ticks ();
173 if (type != ResizeType::StretchTempoChange)
178 if (during_ui_action)
180 self->stretch_ratio_ =
181 new_length / self->before_length_;
187 double stretch_ratio = new_length / before_length;
190 self->stretch (stretch_ratio);
192 catch (
const ZrythmException &ex)
194 throw ZrythmException (
195 "Failed to stretch region");
215template <RegionObject RegionT>
217stretch_region_contents (RegionT &r,
double ratio)
219 z_debug (
"stretching region {} (ratio {:f})", r, ratio);
223 if constexpr (std::is_same_v<RegionT, AudioRegion>)
225 auto *
clip = r.get_clip ();
226 auto new_clip_id = AUDIO_POOL->duplicate_clip (
clip->get_uuid (),
false);
227 auto * new_clip = AUDIO_POOL->get_clip (new_clip_id);
228 r.set_clip_id (new_clip->get_uuid ());
231 AUDIO_ENGINE->get_sample_rate (), new_clip->get_num_channels (), ratio,
234 auto buf = new_clip->get_samples ();
235 buf.interleave_samples ();
236 auto stretched_buf = stretcher->stretch_interleaved (buf);
237 stretched_buf.deinterleave_samples (new_clip->get_num_channels ());
238 new_clip->clear_frames ();
239 new_clip->expand_with_frames (stretched_buf);
240 auto num_frames_per_channel = new_clip->get_num_frames ();
241 z_return_if_fail (num_frames_per_channel > 0);
243 AUDIO_POOL->write_clip (*new_clip,
false,
false);
246 dsp::Position new_end_pos (
247 static_cast<int64_t
> (num_frames_per_channel),
248 AUDIO_ENGINE->ticks_per_frame_);
250 r.loopEndPosition ()->setSamples (num_frames_per_channel);
251 r.length ()->setSamples (num_frames_per_channel);
255 auto objs = r.get_children_view ();
256 for (
auto * obj : objs)
258 using ObjT = base_type<
decltype (obj)>;
260 double before_ticks = obj->get_position ().ticks_;
261 double new_ticks = before_ticks * ratio;
262 dsp::Position tmp (new_ticks, AUDIO_ENGINE->frames_per_tick_);
263 obj->position_setter_validated (tmp, AUDIO_ENGINE->ticks_per_frame_);
265 if constexpr (std::derived_from<ObjT, BoundedObject>)
268 before_ticks = obj->end_pos_->ticks_;
269 new_ticks = before_ticks * ratio;
270 tmp = dsp::Position (new_ticks, AUDIO_ENGINE->frames_per_tick_);
271 obj->end_position_setter_validated (
272 tmp, AUDIO_ENGINE->ticks_per_frame_);
287 using VariantType =
typename Base::VariantType;
288 using ArrangerObjectUuid =
typename Base::UuidType;
291 static auto name_projection (
const VariantType &obj_var)
295 using ObjT = base_type<
decltype (obj)>;
300 return obj->name ()->get_name ();
302 else if constexpr (std::is_same_v<ObjT, Marker>)
304 return obj->name ()->get_name ();
309 throw std::runtime_error (
310 "Name projection called on non-named object");
315 static auto position_ticks_projection (
const VariantType &obj_var)
318 [&] (
auto &&obj) {
return obj->position ()->ticks (); }, obj_var);
320 static auto end_position_ticks_with_start_position_fallback_projection (
321 const VariantType &obj_var)
325 using ObjT = base_type<
decltype (obj)>;
326 auto ticks = obj->position ()->ticks ();
331 return ticks + obj->bounds ()->length ()->ticks ();
335 return ticks + obj->bounds ()->length ()->ticks ();
345 static auto midi_note_pitch_projection (
const VariantType &obj_var)
347 return std::get<MidiNote *> (obj_var)->pitch ();
349 static auto looped_projection (
const VariantType &obj_var)
352 [] (
const auto &obj) {
353 using ObjT = base_type<
decltype (obj)>;
356 return obj->loopRange ()->looped ();
363 static auto is_timeline_object_projection (
const VariantType &obj_var)
366 [] (
const auto &ptr) {
return TimelineObject<base_type<
decltype (ptr)>>; },
369 static auto is_editor_object_projection (
const VariantType &obj_var)
371 return !is_timeline_object_projection (obj_var);
373 static auto deletable_projection (
const VariantType &obj_var)
376 [&] (
auto &&obj) {
return is_arranger_object_deletable (*obj); }, obj_var);
378 static auto cloneable_projection (
const VariantType &obj_var)
380 return deletable_projection (obj_var);
382 static auto renameable_projection (
const VariantType &obj_var)
385 [] (
const auto &ptr) {
389 && deletable_projection (obj_var);
391 static auto bounded_projection (
const VariantType &obj_var)
394 [] (
const auto &ptr) {
return BoundedObject<base_type<
decltype (ptr)>>; },
397 static auto is_region_projection (
const VariantType &obj_var)
400 [] (
const auto &ptr) {
return RegionObject<base_type<
decltype (ptr)>>; },
403 static auto bounds_projection (
const VariantType &obj_var)
407 using ObjT = base_type<
decltype (ptr)>;
410 return get_object_bounds (*ptr);
414 throw std::runtime_error (
"Not a bounded object");
422 std::vector<VariantType>
423 create_snapshots (
const auto &object_factory, QObject &owner)
const
425 return std::ranges::to<std::vector> (
426 *
this | std::views::transform ([&] (
const auto &obj_var) {
428 [&] (
auto &&obj) -> VariantType {
429 return object_factory.clone_object_snapshot (*obj, owner);
436 auto create_new_identities (
const auto &object_factory)
const
437 -> std::vector<ArrangerObjectUuidReference>
439 return std::ranges::to<std::vector> (
440 *
this | std::views::transform ([&] (
const auto &obj_var) {
442 [&] (
auto &&obj) -> VariantType {
443 return object_factory.clone_new_object_identity (*obj);
475 -> std::pair<VariantType,
double>;
482 auto [min_it, max_it] =
483 std::ranges::minmax_element (midi_notes, {}, &MidiNote::pitch);
484 return { *min_it, *max_it };
493 return !std::ranges::all_of (*
this, deletable_projection);
502 return !std::ranges::all_of (*
this, cloneable_projection);
509 return !std::ranges::all_of (*
this, renameable_projection);
525 auto merge () const -> ArrangerObjectUuidReference;
532 bool all_on_same_lane () const;
534 bool contains_looped ()
const
536 return std::ranges::any_of (*
this, looped_projection);
539 bool can_be_merged ()
const;
541 double get_length_in_ticks ()
const
555 units::sample_t pos_samples,
556 bool include_region_end =
false)
const
558 auto view = *
this | std::views::filter (bounded_projection);
559 auto it = std::ranges::find_if (view, [&] (
const auto &r_var) {
560 auto bounds = bounds_projection (r_var);
561 return bounds->is_hit (pos_samples, include_region_end);
563 return it != view.end () ? std::make_optional (*it) : std::nullopt;
576 for (
const auto &prev_mn : prev.template get_elements_by_type<MidiNote> ())
578 if (std::ranges::none_of (*
this, [&prev_mn] (
const auto &obj) {
579 auto mn = std::get<MidiNote *> (obj);
580 return *mn == *prev_mn;
583 prev_mn->listen (
false);
601 template <BoundedObject BoundedObjectT>
603 const BoundedObjectT &self,
606 -> std::pair<ArrangerObjectUuidReference, ArrangerObjectUuidReference>
608 const auto &tempo_map = self.get_tempo_map ();
610 auto new_object1_ref = factory.clone_new_object_identity (self);
611 auto new_object2_ref = factory.clone_new_object_identity (self);
612 const auto get_derived_object = [] (
auto &obj_ref) {
613 return std::get<BoundedObjectT *> (obj_ref.get_object ());
616 z_debug (
"splitting objects...");
620 auto local_pos = [&] () {
621 auto local_frames = global_pos;
624 local_frames = timeline_frames_to_local (self, global_pos,
true);
633 ArrangerObjectSpan::bounds_projection (get_derived_object (new_object1_ref))
637 - get_derived_object (new_object1_ref)->position ()->samples ());
643 if (!self.loopRange ()->is_looped ())
645 if constexpr (std::is_same_v<BoundedObjectT, AudioRegion>)
650 auto prev_r1_ref = new_object1_ref;
652 get_derived_object (prev_r1_ref)->get_clip ();
653 assert (prev_r1_clip);
655 prev_r1_clip->get_num_channels (),
656 static_cast<int> (local_pos.frames_)
658 for (
int i = 0; i < prev_r1_clip->get_num_channels (); ++i)
661 i, 0, prev_r1_clip->get_samples (), i, 0,
662 static_cast<int> (local_pos.frames_));
664 assert (!get_derived_object (prev_r1_ref)->get_name ().empty ());
665 assert (local_pos.frames_ >= 0);
667 factory.create_audio_region_from_audio_buffer_FIXME (
668 get_derived_object (prev_r1_ref)->get_lane (), frames,
669 prev_r1_clip->get_bit_depth (),
670 get_derived_object (prev_r1_ref)->get_name (),
671 get_derived_object (prev_r1_ref)->get_position ().ticks_);
673 get_derived_object (new_object1_ref)->get_clip_id ()
674 != get_derived_object (prev_r1_ref)->get_clip_id ());
682 get_derived_object (new_object1_ref)->get_children_view ())
684 if (child->position ()->samples () > local_pos)
686 get_derived_object (new_object1_ref)
687 ->remove_object (child->get_uuid ());
701 get_derived_object (new_object2_ref)
703 ->clipStartPosition ()
704 ->setSamples (local_pos);
706 get_derived_object (new_object2_ref)->position ()->setSamples (global_pos);
707 int64_t r2_local_end =
708 ArrangerObjectSpan::bounds_projection (get_derived_object (new_object2_ref))
709 ->get_end_position_samples (
true);
711 get_derived_object (new_object2_ref)->position ()->samples ();
716 if (!self.loopRange ()->is_looped ())
718 get_derived_object (new_object2_ref)
720 ->setTrackBounds (
true);
723 if constexpr (std::is_same_v<BoundedObjectT, AudioRegion>)
727 auto prev_r2_ref = new_object2_ref;
729 get_derived_object (prev_r2_ref)->get_clip ();
730 assert (prev_r2_clip);
731 assert (r2_local_end > 0);
733 prev_r2_clip->get_num_channels (), (int) r2_local_end
735 for (
int i = 0; i < prev_r2_clip->get_num_channels (); ++i)
738 i, 0, prev_r2_clip->get_samples (), i, local_pos,
741 assert (!get_derived_object (prev_r2_ref)->get_name ().empty ());
742 assert (r2_local_end >= 0);
744 factory.create_audio_region_from_audio_buffer_FIXME (
745 get_derived_object (prev_r2_ref)->get_lane (), tmp,
746 prev_r2_clip->get_bit_depth (),
747 get_derived_object (prev_r2_ref)->get_name (), local_pos);
749 get_derived_object (new_object2_ref)->get_clip_id ()
750 != get_derived_object (prev_r2_ref)->get_clip_id ());
756 const double ticks_to_subtract =
757 tempo_map.samples_to_tick (units::samples (local_pos))
759 get_derived_object (new_object2_ref)
760 ->add_ticks_to_children (-ticks_to_subtract);
767 get_derived_object (new_object2_ref)->get_children_view ())
769 if (child->position ().frames_ < 0)
770 get_derived_object (new_object2_ref)
771 ->remove_object (child->get_uuid ());
783 auto track_var = self.get_track ();
787 if constexpr (std::is_same_v<BoundedObjectT, AutomationRegion>)
789 at = self.get_automation_track ();
791 get_derived_object (new_object1_ref)
792 ->generate_name (self.get_name (), at, track);
793 get_derived_object (new_object2_ref)
794 ->generate_name (self.get_name (), at, track);
800 return std::make_pair (new_object1_ref, new_object2_ref);
813 static void copy_arranger_object_identifier (
814 const VariantType &dest,
815 const VariantType &src);
819static_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)
void clip(std::span< float > buf, float minf, float maxf)
Clamp the buffer to min/max.