From 48f625e7ef841bbfd83a2a40335a941b6fc6a8e9 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Fri, 21 Nov 2025 22:39:04 -0700 Subject: [PATCH] continued work on getting audio clip bounds editing to work --- libs/ardour/ardour/triggerbox.h | 22 ++--- libs/ardour/triggerbox.cc | 142 +++++++++++++++++--------------- 2 files changed, 88 insertions(+), 76 deletions(-) diff --git a/libs/ardour/ardour/triggerbox.h b/libs/ardour/ardour/triggerbox.h index 7d6c279d05..4aec665729 100644 --- a/libs/ardour/ardour/triggerbox.h +++ b/libs/ardour/ardour/triggerbox.h @@ -436,7 +436,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { static PBD::Signal TriggerPropertyChange; void region_property_change (PBD::PropertyChange const &); - virtual void bounds_changed (Temporal::timepos_t const & start, Temporal::timepos_t const & end); + virtual void bounds_changed (Temporal::timepos_t const & start, Temporal::timepos_t const & end, Temporal::timecnt_t const & len); protected: struct UIRequests { @@ -507,12 +507,11 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { virtual void _arm (Temporal::BBT_Offset const &); struct PendingSwap { - Temporal::Beats play_start; - Temporal::Beats play_end; - Temporal::Beats loop_start; - Temporal::Beats loop_end; - Temporal::Beats length; - + timepos_t play_start; + timepos_t play_end; + timepos_t loop_start; + timepos_t loop_end; + timecnt_t length; virtual ~PendingSwap() {} }; @@ -520,7 +519,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { std::atomic pending_swap; std::atomic old_pending_swap; - virtual void adjust_bounds (Temporal::Beats const & start, Temporal::Beats const & end, Temporal::Beats const & length, bool from_region) = 0; + virtual void adjust_bounds (Temporal::timepos_t const & start, Temporal::timepos_t const & end, Temporal::timecnt_t const & length, bool from_region) = 0; }; class LIBARDOUR_API AudioTrigger : public Trigger { @@ -551,6 +550,7 @@ class LIBARDOUR_API AudioTrigger : public Trigger { void set_end (timepos_t const &); void set_legato_offset (timepos_t const &); void set_length (timecnt_t const &); + void set_user_data_length (samplecnt_t); timepos_t start_offset () const; /* offset from start of data */ timepos_t current_length() const; /* offset from start of data */ timepos_t natural_length() const; /* offset from start of data */ @@ -594,15 +594,17 @@ class LIBARDOUR_API AudioTrigger : public Trigger { Sample const * audio_data (size_t n) const; size_t data_length() const { return data.length; } + samplecnt_t user_data_length() const { return _user_data_length; } void check_edit_swap (timepos_t const &, bool playing, BufferSet&); protected: void retrigger (); - void adjust_bounds (Temporal::Beats const & start, Temporal::Beats const & end, Temporal::Beats const & length, bool from_region); + void adjust_bounds (Temporal::timepos_t const & start, Temporal::timepos_t const & end, Temporal::timecnt_t const & length, bool from_region); private: AudioData data; + samplecnt_t _user_data_length; RubberBand::RubberBandStretcher* _stretcher; samplepos_t _start_offset; @@ -703,7 +705,7 @@ class LIBARDOUR_API MIDITrigger : public Trigger { protected: void retrigger (); void _arm (Temporal::BBT_Offset const &); - void adjust_bounds (Temporal::Beats const & start, Temporal::Beats const & end, Temporal::Beats const & length, bool from_region); + void adjust_bounds (Temporal::timepos_t const & start, Temporal::timepos_t const & end, Temporal::timecnt_t const & length, bool from_region); private: PBD::ID data_source; diff --git a/libs/ardour/triggerbox.cc b/libs/ardour/triggerbox.cc index 90a1ea674e..e9b7e430f1 100644 --- a/libs/ardour/triggerbox.cc +++ b/libs/ardour/triggerbox.cc @@ -757,11 +757,37 @@ Trigger::set_region_internal (std::shared_ptr r) void Trigger::region_property_change (PropertyChange const & what_changed) { - //std::cerr << "region prop change\n"; + if (!_region) { + return; + } + if (what_changed.contains (Properties::start) || what_changed.contains (Properties::length)) { - //std::cerr << "bounds changed\n"; - //PBD::stacktrace (std::cerr, 23); - bounds_changed (_region->start(), _region->end()); + bounds_changed (_region->start(), _region->end(), _region->length()); + } +} + +void +Trigger::bounds_changed (Temporal::timepos_t const & start, Temporal::timepos_t const & end, Temporal::timecnt_t const & len) +{ + PendingSwap* pending = new PendingSwap; + + pending->play_start = start; + pending->play_end = end; + pending->loop_start = pending->play_start; + pending->loop_end = pending->play_end; + pending->length = len; + + /* And set it. RT thread will find this and do what needs to be done */ + + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1/%2 pushed pending swap @ %3 for bounds change\n", _box.order(), index(), pending)); + pending_swap.store (pending); + + /* Clean up a previous RT midi buffer swap (if there is one) */ + + PendingSwap* old = old_pending_swap.exchange (nullptr); + + if (old) { + delete old; } } @@ -1345,31 +1371,6 @@ Trigger::start_and_roll_to (samplepos_t start_pos, samplepos_t end_position, Tri } } -void -Trigger::bounds_changed (Temporal::timepos_t const & start, Temporal::timepos_t const & end) -{ - PendingSwap* pending = new PendingSwap; - - pending->play_start = start.beats(); - pending->play_end = end.beats(); - pending->loop_start = pending->play_start; - pending->loop_end = pending->play_end; - pending->length = pending->play_end - pending->play_start; - - /* And set it. RT thread will find this and do what needs to be done */ - - DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1/%2 pushed pending swap @ %3 for bounds change\n", _box.order(), index(), pending)); - pending_swap.store (pending); - - /* Clean up a previous RT midi buffer swap (if there is one) */ - - PendingSwap* old = old_pending_swap.exchange (nullptr); - - if (old) { - delete old; - } -} - /*--------------------*/ AudioTrigger::AudioData::~AudioData () @@ -1607,8 +1608,8 @@ AudioTrigger::compute_end (Temporal::TempoMap::SharedPtr const & tmap, Temporal: /* Our task here is to set: expected_end_sample: (TIMELINE!) the sample position where the data for the clip should run out (taking stretch into account) - last_readable_sample: the sample in the data where we stop reading - final_processed_sample: the sample where the trigger stops and the follow action if any takes effect + last_readable_sample: (DATA RELATIVE!) the sample in the data where we stop reading + final_processed_sample: (DATA RELATIVE!) the sample where the trigger stops and the follow action if any takes effect Things that affect these values: @@ -1620,19 +1621,27 @@ AudioTrigger::compute_end (Temporal::TempoMap::SharedPtr const & tmap, Temporal: const Temporal::BBT_Argument transition_bba (superclock_t (0), transition_bbt); - samplepos_t end_by_follow_length = tmap->sample_at (tmap->bbt_walk (transition_bba, _follow_length)); - samplepos_t end_by_data_length = transition_sample + (data.length - _start_offset); /* this could still blow up if the data is less than 1 tick long, but we should handle that elsewhere. */ const Temporal::Beats bc (Temporal::Beats::from_double (_beatcnt)); + + samplepos_t end_by_follow_length = tmap->sample_at (tmap->bbt_walk (transition_bba, _follow_length)); samplepos_t end_by_beatcnt = tmap->sample_at (tmap->bbt_walk (transition_bba, Temporal::BBT_Offset (0, bc.get_beats(), bc.get_ticks()))); + samplepos_t end_by_user_data_length = transition_sample + (_user_data_length - _start_offset); + samplepos_t end_by_data_length = transition_sample + (data.length - _start_offset); + samplepos_t end_by_fixed_samples = std::min (end_by_user_data_length, end_by_data_length); DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 SO %9 @ %2 / %3 / %4 ends: FL %5 (from %6) BC %7 DL %8\n", index(), transition_sample, transition_beats, transition_bbt, end_by_follow_length, _follow_length, end_by_beatcnt, end_by_data_length, _start_offset)); if (stretching()) { + /* NOTES: beatcnt here will reflect the stretch, which is OK + Because we are stretching. But .. that's wrong when we're not + stretching. So we really need another value: something like + data_length but user-definable. + */ if (internal_use_follow_length()) { expected_end_sample = std::min (end_by_follow_length, end_by_beatcnt); } else { @@ -1640,24 +1649,20 @@ AudioTrigger::compute_end (Temporal::TempoMap::SharedPtr const & tmap, Temporal: } } else { if (internal_use_follow_length()) { - expected_end_sample = std::min (end_by_follow_length, end_by_data_length); + expected_end_sample = std::min (end_by_follow_length, end_by_fixed_samples); } else { - expected_end_sample = end_by_data_length; + expected_end_sample = end_by_fixed_samples; } } - if (internal_use_follow_length()) { - final_processed_sample = end_by_follow_length - transition_sample; - } else { - final_processed_sample = expected_end_sample - transition_sample; - } + final_processed_sample = expected_end_sample - transition_sample; samplecnt_t usable_length; if (internal_use_follow_length() && (end_by_follow_length < end_by_data_length)) { usable_length = end_by_follow_length - transition_samples; } else { - usable_length = (data.length - _start_offset); + usable_length = std::min ((end_by_beatcnt - transition_samples), (data.length - _start_offset)); } /* called from compute_end() when we know the time (audio & @@ -1696,7 +1701,14 @@ AudioTrigger::compute_end (Temporal::TempoMap::SharedPtr const & tmap, Temporal: void AudioTrigger::set_length (timecnt_t const & newlen) { - /* XXX what? */ + /* XXX what */ +} + +void +AudioTrigger::set_user_data_length (samplecnt_t s) +{ + _user_data_length = s; + send_property_change (ARDOUR::Properties::length); } timepos_t @@ -1763,7 +1775,7 @@ AudioTrigger::set_region_in_worker_thread_internal (std::shared_ptr r, b /* given an initial tempo guess, we need to set our operating tempo and beat_cnt value. * this may be reset momentarily with user-settings (UIState) from a d+d operation */ - set_segment_tempo(_estimated_tempo); + set_segment_tempo (_estimated_tempo); if (!from_capture) { setup_stretcher (); @@ -1808,11 +1820,9 @@ AudioTrigger::set_region_in_worker_thread_internal (std::shared_ptr r, b void AudioTrigger::estimate_tempo () { - double beatcount; - ARDOUR::estimate_audio_tempo_region (_region, data[0], data.length, _box.session().sample_rate(), _estimated_tempo, _meter, beatcount); + ARDOUR::estimate_audio_tempo_region (_region, data[0], data.length, _box.session().sample_rate(), _estimated_tempo, _meter, _beatcnt); /* initialize our follow_length to match the beatcnt ... user can later change this value to have the clip end sooner or later than its data length */ - set_follow_length(Temporal::BBT_Offset( 0, rint(beatcount), 0)); - + set_follow_length (Temporal::BBT_Offset ( 0, floor (_beatcnt), 0)); } bool @@ -1821,8 +1831,8 @@ AudioTrigger::probably_oneshot () const assert (_segment_tempo != 0.); if ((data.length < (_box.session().sample_rate()/2)) || //less than 1/2 second - (_segment_tempo > 140) || //minibpm thinks this is really fast - (_segment_tempo < 60)) { //minibpm thinks this is really slow + (_segment_tempo > 140) || //minibpm thinks this is really fast + (_segment_tempo < 60)) { //minibpm thinks this is really slow return true; } @@ -1902,6 +1912,7 @@ AudioTrigger::captured (SlotArmInfo& ai) data.length = ai.audio_buf.length; data.capacity = ai.audio_buf.capacity; + _user_data_length = data.length; DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1/%2 captured a total of %3\n", _box.order(), _index, data.length)); @@ -1958,6 +1969,7 @@ AudioTrigger::load_data (std::shared_ptr ar) } data.length = len; + _user_data_length = len; set_name (ar->name()); } catch (...) { @@ -2324,21 +2336,19 @@ AudioTrigger::check_edit_swap (timepos_t const & time, bool playing, BufferSet& } DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1/%2 noticed pending swap @ %3\n", _box.order(), index(), pending)); - adjust_bounds (pending->play_start, pending->play_end, pending->length, true); old_pending_swap.store (pending); } void -AudioTrigger::adjust_bounds (Temporal::Beats const & start, Temporal::Beats const & end, Temporal::Beats const & length, bool) +AudioTrigger::adjust_bounds (Temporal::timepos_t const & start, Temporal::timepos_t const & end, Temporal::timecnt_t const & length, bool) { Temporal::TempoMap::SharedPtr tmap (Temporal::TempoMap::use()); - set_start (timepos_t (tmap->sample_at (start))); - std::cerr << "Old beatcnt: " << _beatcnt; - _beatcnt = Temporal::DoubleableBeats (end - start).to_double(); - std::cerr << " new " << _beatcnt << std::endl; - + set_start (timepos_t (start.samples())); + set_user_data_length (length.samples()); + /* XXX not 100% sure that we should set this here or not */ + _beatcnt = Temporal::DoubleableBeats (length.beats()).to_double(); } /*--------------------*/ @@ -2461,29 +2471,29 @@ MIDITrigger::setup_event_indices () } void -MIDITrigger::adjust_bounds (Temporal::Beats const & start, Temporal::Beats const & end, Temporal::Beats const & length, bool from_region) +MIDITrigger::adjust_bounds (Temporal::timepos_t const & start, Temporal::timepos_t const & end, Temporal::timecnt_t const & length, bool from_region) { if (!from_region && _region) { _region->set_length (timecnt_t (length, timepos_t (start))); } - _play_start = start; - _play_end = end; + _play_start = start.beats(); + _play_end = end.beats(); /* Note that in theory we may be able to get loop start/end from the * SMF and it could different from the data start/end */ - _loop_start = start; - _loop_end = end; + _loop_start = _play_start; + _loop_end = _play_end; - data_length = length; - _follow_length = Temporal::BBT_Offset (0, length.get_beats(), 0); + data_length = length.beats(); + _follow_length = Temporal::BBT_Offset (0, data_length.get_beats(), 0); set_length (timecnt_t (length)); - DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1/%2 new bounds %3..%4 %5..%6 len %7 of %7\n", _box.order(), index(), _play_start, _play_end, _loop_start, _loop_end, length, rt_midibuffer.load()->size())); - setup_event_indices (); + + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1/%2 new bounds %3..%4 %5..%6 len %7 of %7\n", _box.order(), index(), _play_start, _play_end, _loop_start, _loop_end, length, rt_midibuffer.load()->size())); } void @@ -2516,7 +2526,7 @@ MIDITrigger::captured (SlotArmInfo& ai) Temporal::TempoMap::SharedPtr tmap (Temporal::TempoMap::use()); timecnt_t dur = tmap->convert_duration (timecnt_t (ai.captured), timepos_t (ai.start_samples), Temporal::BeatTime); - adjust_bounds (Temporal::Beats(), dur.beats(), dur.beats(), false); + adjust_bounds (timepos_t::zero (Temporal::BeatTime), timepos_t (dur.beats()), dur, false); iter = 0; _follow_action0 = FollowAction::Again;