From 79488873820f66cef28c213401ad98aafaba2f0a Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Sun, 30 Nov 2025 10:58:46 -0700 Subject: [PATCH] TriggerBox: radically redesign handling of region changes by AudioTrigger This now follows MIDITrigger - when a region's bounds are changed, we reload the data corresponding to the region into memory, queue up a PendingSwap and then have Trigger::check_edit_swap() switch to the new data when necessary (synchronously with ::run). This comment also removes AudioTrigger::_start_offset because there is never any start offet - the data in memory is always precisely the data corresponding to the region. If the region bounds are modified, we reload the correct data into memory. This also applies to the recently added _user_data_length - again, the data in memory always corresponds to the full span of the region/clip being used in process context. This differs a little from MIDITrigger, where we do in fact load the entire source and maintain trigger-only bounds. That's because the amount of data for MIDI is generally small, and so it makes more sense to just put it all in memory and adjust which parts of it we use. For audio, the region could be minutes (or hours!) into an audio source, and there's no point having all that data in memory when we do not need it. These changes now make editing clip boundaries in AudioClipEditor generally functional, though some polishing is still in the works. --- libs/ardour/ardour/triggerbox.h | 37 ++-- libs/ardour/triggerbox.cc | 340 ++++++++++++++++++-------------- 2 files changed, 217 insertions(+), 160 deletions(-) diff --git a/libs/ardour/ardour/triggerbox.h b/libs/ardour/ardour/triggerbox.h index 77d06d2949..d3839dc0c1 100644 --- a/libs/ardour/ardour/triggerbox.h +++ b/libs/ardour/ardour/triggerbox.h @@ -286,9 +286,6 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { virtual pframes_t run (BufferSet&, samplepos_t start_sample, samplepos_t end_sample, Temporal::Beats const & start, Temporal::Beats const & end, pframes_t nframes, pframes_t offset, double bpm, pframes_t& quantize_offset) = 0; - virtual void set_start (timepos_t const &) = 0; - virtual void set_end (timepos_t const &) = 0; - virtual void set_length (timecnt_t const &) = 0; virtual void io_change () {} virtual void set_legato_offset (timepos_t const & offset) = 0; @@ -318,7 +315,6 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { virtual bool probably_oneshot () const = 0; - virtual timepos_t start_offset () const = 0; /* offset from start of data */ void process_state_requests (BufferSet& bufs, pframes_t dest_offset); bool active() const { return _state >= Running; } @@ -442,7 +438,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { std::shared_ptr _region; samplecnt_t process_index; - samplepos_t final_processed_sample; /* where we stop playing, in process time, compare with process_index */ + samplepos_t final_process_index; /* where we stop playing, in process time, compare with process_index */ UIState ui_state; TriggerBox& _box; UIRequests _requests; @@ -515,7 +511,8 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { std::atomic pending_swap; std::atomic old_pending_swap; - virtual void adjust_bounds (Temporal::timepos_t const & start, Temporal::timepos_t const & end, Temporal::timecnt_t const & length, bool from_region) = 0; + virtual int load_pending_data (PendingSwap&) = 0; + virtual PendingSwap* pending_factory() const = 0; }; class LIBARDOUR_API AudioTrigger : public Trigger { @@ -542,12 +539,8 @@ class LIBARDOUR_API AudioTrigger : public Trigger { double segment_beatcnt () { return _beatcnt; } void set_segment_beatcnt (double count); - void set_start (timepos_t const &); - 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 */ void io_change (); bool probably_oneshot () const; @@ -578,28 +571,35 @@ class LIBARDOUR_API AudioTrigger : public Trigger { AudioData () : length (0), capacity (0) {} ~AudioData (); + AudioData& operator= (AudioData& other); /* really move semantics */ samplecnt_t append (Sample const * src, samplecnt_t cnt, uint32_t chan); void alloc (samplecnt_t cnt, uint32_t nchans); void reset () { length = 0; } + void drop (); }; 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; } + + struct AudioPendingSwap : public PendingSwap { + AudioData audio_data; + + AudioPendingSwap() {} + ~AudioPendingSwap() {} + }; void check_edit_swap (timepos_t const &, bool playing, BufferSet&); protected: void retrigger (); - void adjust_bounds (Temporal::timepos_t const & start, Temporal::timepos_t const & end, Temporal::timecnt_t const & length, bool from_region); + PendingSwap* pending_factory() const; + int load_pending_data (PendingSwap&); private: - AudioData data; - samplecnt_t _user_data_length; + AudioData data; RubberBand::RubberBandStretcher* _stretcher; - samplepos_t _start_offset; /* computed during run */ @@ -613,8 +613,8 @@ class LIBARDOUR_API AudioTrigger : public Trigger { virtual void setup_stretcher (); - void drop_data (); - int load_data (std::shared_ptr); + void drop_data (AudioData&); + int load_data (std::shared_ptr, AudioData&); void estimate_tempo (); void reset_stretcher (); void _startup (BufferSet&, pframes_t dest_offset, Temporal::BBT_Offset const &); @@ -685,6 +685,8 @@ class LIBARDOUR_API MIDITrigger : public Trigger { std::vector const & channel_map() const { return _channel_map; } void check_edit_swap (timepos_t const &, bool playing, BufferSet&); + PendingSwap* pending_factory() const; + RTMidiBufferBeats const & rt_midi_buffer() const { return *rt_midibuffer.load(); } Temporal::Beats play_start() const { return _play_start; } @@ -696,6 +698,7 @@ class LIBARDOUR_API MIDITrigger : public Trigger { void retrigger (); void _arm (Temporal::BBT_Offset const &); void adjust_bounds (Temporal::timepos_t const & start, Temporal::timepos_t const & end, Temporal::timecnt_t const & length, bool from_region); + int load_pending_data (PendingSwap&); private: PBD::ID data_source; diff --git a/libs/ardour/triggerbox.cc b/libs/ardour/triggerbox.cc index dc41f23bc2..286b8531ad 100644 --- a/libs/ardour/triggerbox.cc +++ b/libs/ardour/triggerbox.cc @@ -234,7 +234,7 @@ Trigger::Trigger (uint32_t n, TriggerBox& b) , _name (Properties::name, "") , _color (Properties::color, 0xBEBEBEFF) , process_index (0) - , final_processed_sample (0) + , final_process_index (0) , _box (b) , _state (Stopped) , _playout (false) @@ -726,6 +726,10 @@ Trigger::set_region (std::shared_ptr r, bool use_thread) /* load data, do analysis in another thread */ TriggerBox::worker->set_region (_box, index(), r); } else { + /* despite the name, this runs in the current thread. The name + comes from the fact that this is normally called from a worker + thread. It executes in thread it was called in. + */ set_region_in_worker_thread (r); } } @@ -769,7 +773,8 @@ Trigger::region_property_change (PropertyChange const & what_changed) void Trigger::bounds_changed (Temporal::timepos_t const & start, Temporal::timepos_t const & end, Temporal::timecnt_t const & len) { - PendingSwap* pending = new PendingSwap; + PendingSwap* pending = pending_factory(); + assert (pending); pending->play_start = start; pending->play_end = end; @@ -777,6 +782,8 @@ Trigger::bounds_changed (Temporal::timepos_t const & start, Temporal::timepos_t pending->loop_end = pending->play_end; pending->length = len; + load_pending_data (*pending); + /* 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)); @@ -804,7 +811,7 @@ Trigger::position_as_fraction () const return 0.0; } - return process_index / (double) final_processed_sample; + return process_index / (double) final_process_index; } void @@ -1373,6 +1380,37 @@ Trigger::start_and_roll_to (samplepos_t start_pos, samplepos_t end_position, Tri /*--------------------*/ +void +AudioTrigger::AudioData::drop () +{ + for (auto& d : *this) { + delete [] d; + } + + clear (); +} + +AudioTrigger::AudioData& +AudioTrigger::AudioData::operator= (AudioTrigger::AudioData& other) +{ + /* This is really implementing move semantics between two AudioData objects */ + + drop (); + + reserve (other.size()); + for (auto & sample_ptr : other) { + push_back (sample_ptr); + } + length = other.length; + capacity = other.capacity; + + other.clear (); + other.length = 0; + other.capacity = 0; + + return *this; +} + AudioTrigger::AudioData::~AudioData () { for (auto & s : *this) { @@ -1410,8 +1448,7 @@ AudioTrigger::AudioData::append (Sample const * src, samplecnt_t cnt, uint32_t c AudioTrigger::AudioTrigger (uint32_t n, TriggerBox& b) : Trigger (n, b) - , _stretcher (0) - , _start_offset (0) + , _stretcher (nullptr) , read_index (0) , last_readable_sample (0) , _legato_offset (0) @@ -1424,7 +1461,7 @@ AudioTrigger::AudioTrigger (uint32_t n, TriggerBox& b) AudioTrigger::~AudioTrigger () { - drop_data (); + data.drop (); delete _stretcher; } @@ -1546,9 +1583,6 @@ XMLNode& AudioTrigger::get_state () const { XMLNode& node (Trigger::get_state()); - - node.set_property (X_("start"), timepos_t (_start_offset)); - return node; } @@ -1561,41 +1595,18 @@ AudioTrigger::set_state (const XMLNode& node, int version) return -1; } - node.get_property (X_("start"), t); - _start_offset = t.samples(); - /* we've changed our internal values; we need to update our queued UIState or they will be lost when UIState is applied */ copy_to_ui_state (); return 0; } -void -AudioTrigger::set_start (timepos_t const & s) -{ - /* XXX better minimum size needed */ - _start_offset = std::max (samplepos_t (4096), s.samples ()); -} - -void -AudioTrigger::set_end (timepos_t const & e) -{ - assert (!data.empty()); - set_length (timecnt_t (e.samples() - _start_offset, timepos_t (_start_offset))); -} - void AudioTrigger::set_legato_offset (timepos_t const & offset) { _legato_offset = offset.samples(); } -timepos_t -AudioTrigger::start_offset () const -{ - return timepos_t (_start_offset); -} - void AudioTrigger::start_and_roll_to (samplepos_t start_pos, samplepos_t end_position, uint32_t cnt) { @@ -1609,7 +1620,7 @@ AudioTrigger::compute_end (Temporal::TempoMap::SharedPtr const & tmap, Temporal: expected_end_sample: (TIMELINE!) the sample position where the data for the clip should run out (taking stretch into account) 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 + final_process_index: (DATA RELATIVE!) the sample where the trigger stops and the follow action if any takes effect Things that affect these values: @@ -1626,51 +1637,37 @@ AudioTrigger::compute_end (Temporal::TempoMap::SharedPtr const & tmap, Temporal: */ 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); + /* This is tempo-sensitive - we actually compute the sample position bc + beats after the transition sample, using either the follow length + or _beatcnt + */ - DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 computing end with fl %2 bc %3 dl %4 udl %5\n", index(), - _follow_length, bc.str(), data.length, _user_data_length)); + const Temporal::BBT_Offset beat_length = internal_use_follow_length() ? _follow_length : Temporal::BBT_Offset (0, bc.get_beats(), bc.get_ticks()); + samplepos_t end_by_beats = tmap->sample_at (tmap->bbt_walk (transition_bba, beat_length)); - DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 SO %9 @ %2 / %3 / %4 ends: FL %5 (from %6) BC %7 DL %8\n", + /* These are non-tempo-sensitive, and represent data-centric sample counts. */ + samplepos_t end_by_data_length = transition_sample + data.length; + + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 computing end with fl %2 bc %3 dl %4 EDL %5\n", index(), + _follow_length, bc.str(), data.length, end_by_data_length)); + + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 @ %2 / %3 / %4 ends: FL %5 BC %6 from %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)); + _follow_length, end_by_beats, + internal_use_follow_length() ? _follow_length : Temporal::BBT_Offset (0, bc.get_beats(), bc.get_ticks()), + end_by_data_length)); 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); - DEBUG_TRACE (DEBUG::Triggers, string_compose ("stretch and use follow, end up at %1\n", expected_end_sample)); - } else { - expected_end_sample = end_by_beatcnt; - DEBUG_TRACE (DEBUG::Triggers, string_compose ("stretch and no-follow, end up at %1\n", expected_end_sample)); - } + expected_end_sample = end_by_beats; + DEBUG_TRACE (DEBUG::Triggers, string_compose ("stretching, end up at %1\n", expected_end_sample)); } else { - if (internal_use_follow_length()) { - expected_end_sample = std::min (end_by_follow_length, end_by_fixed_samples); - DEBUG_TRACE (DEBUG::Triggers, string_compose ("no-stretch and use follow, end up at %1\n", expected_end_sample)); - } else { - expected_end_sample = end_by_fixed_samples; - DEBUG_TRACE (DEBUG::Triggers, string_compose ("no-stretch and no-follow, end up at %1\n", expected_end_sample)); - } + expected_end_sample = std::min (end_by_beats, end_by_data_length); + DEBUG_TRACE (DEBUG::Triggers, string_compose ("no-stretch, end up at %1\n", expected_end_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 = std::min (end_by_follow_length - transition_samples, end_by_fixed_samples - transition_samples); - } else { - usable_length = end_by_fixed_samples - transition_samples; - } + final_process_index = expected_end_sample - transition_sample; + samplecnt_t usable_length = end_by_data_length - transition_samples; + DEBUG_TRACE (DEBUG::Triggers, string_compose ("usable length from end-by-samples %1 - transition @ %2 = %3\n", end_by_data_length, transition_samples, usable_length)); /* called from compute_end() when we know the time (audio & * musical time domains when we start starting. Our job here is to @@ -1681,7 +1678,7 @@ AudioTrigger::compute_end (Temporal::TempoMap::SharedPtr const & tmap, Temporal: if (launch_style() != Repeat || (q == Temporal::BBT_Offset())) { - last_readable_sample = _start_offset + usable_length; + last_readable_sample = usable_length; } else { @@ -1693,14 +1690,14 @@ AudioTrigger::compute_end (Temporal::TempoMap::SharedPtr const & tmap, Temporal: /* XXX MUST HANDLE BAR-LEVEL QUANTIZATION */ timecnt_t len (Temporal::Beats (q.beats, q.ticks), timepos_t (Temporal::Beats())); - last_readable_sample = _start_offset + len.samples(); + last_readable_sample = len.samples(); } - effective_length = tmap->quarters_at_sample (transition_sample + final_processed_sample) - tmap->quarters_at_sample (transition_sample); + effective_length = tmap->quarters_at_sample (transition_sample + final_process_index) - tmap->quarters_at_sample (transition_sample); _transition_bbt = transition_bbt; - DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1: final sample %2 vs ees %3 ls %4\n", index(), final_processed_sample, expected_end_sample, last_readable_sample)); + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1: final process index %2 expected end (timeline) sample %3 final read index %4\n", index(), final_process_index, expected_end_sample, last_readable_sample)); return timepos_t (expected_end_sample); } @@ -1711,12 +1708,6 @@ AudioTrigger::set_length (timecnt_t const & newlen) /* XXX what */ } -void -AudioTrigger::set_user_data_length (samplecnt_t s) -{ - _user_data_length = s; - send_property_change (ARDOUR::Properties::length); -} int AudioTrigger::set_region_in_worker_thread_from_capture (std::shared_ptr r) { @@ -1753,12 +1744,10 @@ AudioTrigger::set_region_in_worker_thread_internal (std::shared_ptr r, b } if (!from_capture) { - load_data (ar); + load_data (ar, data); } - if (from_capture) { - set_name (r->name()); - } + set_name (ar->name()); estimate_tempo (); /* NOTE: if this is an existing clip (D+D copy) then it will likely have a SD tempo, and that short-circuits minibpm for us */ @@ -1879,15 +1868,6 @@ AudioTrigger::setup_stretcher () _stretcher->setMaxProcessSize (rb_blocksize); } -void -AudioTrigger::drop_data () -{ - for (auto& d : data) { - delete [] d; - } - data.clear (); -} - void AudioTrigger::captured (SlotArmInfo& ai) { @@ -1901,7 +1881,6 @@ 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)); @@ -1942,27 +1921,25 @@ AudioTrigger::captured (SlotArmInfo& ai) } int -AudioTrigger::load_data (std::shared_ptr ar) +AudioTrigger::load_data (std::shared_ptr ar, AudioData& audio_data) { const uint32_t nchans = ar->n_channels(); - drop_data (); + audio_data.drop (); try { samplecnt_t len = ar->length_samples(); - data.alloc (len, nchans); + audio_data.alloc (len, nchans); for (uint32_t n = 0; n < nchans; ++n) { - ar->read (data[n], 0, len, n); + ar->read (audio_data[n], 0, len, n); } - data.length = len; - _user_data_length = len; - set_name (ar->name()); + audio_data.length = len; } catch (...) { - drop_data (); + audio_data.drop (); return -1; } @@ -1977,7 +1954,7 @@ AudioTrigger::retrigger () update_properties (); reset_stretcher (); - read_index = _start_offset + _legato_offset; + read_index = _legato_offset; retrieved = 0; _legato_offset = 0; /* used one time only */ @@ -2080,12 +2057,13 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t #ifdef HAVE_RUBBERBAND_3_0_0 to_pad = _stretcher->getPreferredStartPad(); to_drop = _stretcher->getStartDelay(); + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 requires padding of %2 dropping %3 (RB v3\n", name(), to_pad, to_drop)); #else to_pad = _stretcher->getLatency(); to_drop = to_pad; + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 requires padding of %2 dropping %3 (RB < v3\n", name(), to_pad, to_drop)); #endif got_stretcher_padding = true; - DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 requires %2 padding %3\n", name(), to_pad)); } while (to_pad > 0) { @@ -2137,6 +2115,10 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t in[chn] = data[chn % data.size ()] + read_index; } +#ifndef NDEBUG + samplecnt_t required = _stretcher->getSamplesRequired(); + samplecnt_t pre_avail = _stretcher->available (); +#endif /* Note: RubberBandStretcher's process() and retrieve() API's accepts Sample** * as their first argument. This code may appear to only be processing the first * channel, but actually processes them all in one pass. @@ -2147,14 +2129,17 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t read_index += to_stretcher; avail = _stretcher->available (); + samplecnt_t this_drop = 0; + if (to_drop && avail) { - samplecnt_t this_drop = std::min (std::min ((samplecnt_t) avail, to_drop), (samplecnt_t) scratch->get_audio (0).capacity()); + this_drop = std::min (std::min ((samplecnt_t) avail, to_drop), (samplecnt_t) scratch->get_audio (0).capacity()); _stretcher->retrieve (&bufp[0], this_drop); to_drop -= this_drop; avail = _stretcher->available (); } - DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 process %2 at-end %3 avail %4 of %5\n", name(), to_stretcher, at_end, avail, nframes)); + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 process %2 (ri now %6) at-end %3 avail %4 (was %7) of %5 (required was %8)\n", + name(), to_stretcher, at_end, avail, nframes, read_index, pre_avail, required)); } /* we've fed the stretcher enough data to have @@ -2172,18 +2157,19 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t } /* fetch the stretch */ - - retrieved += _stretcher->retrieve (&bufp[0], from_stretcher); + samplecnt_t this_retrieve = _stretcher->retrieve (&bufp[0], from_stretcher); + retrieved += this_retrieve; if (read_index >= last_readable_sample) { - DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 no more data to deliver to stretcher, but retrieved %2 to put current end at %3 vs %4 / %5 pi %6\n", - index(), retrieved, transition_samples + retrieved, expected_end_sample, final_processed_sample, process_index)); + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 no more data to deliver to stretcher, but with ri %8 or %9 retrieved %7(%2) to put current end at %3 vs %4 / %5 pi %6\n", + index(), retrieved, transition_samples + retrieved, expected_end_sample, final_process_index, process_index, this_retrieve, + read_index, last_readable_sample)); if (transition_samples + retrieved > expected_end_sample) { /* final pull from stretched data into output buffers */ - // cerr << "FS#2 from ees " << final_processed_sample << " - " << process_index << " & " << from_stretcher; - from_stretcher = std::min (from_stretcher, std::max (0, final_processed_sample - process_index)); + // cerr << "FS#2 from ees " << final_process_index << " - " << process_index << " & " << from_stretcher; + from_stretcher = std::min (from_stretcher, std::max (0, final_process_index - process_index)); // cerr << " => " << from_stretcher << endl; DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 total retrieved data %2 exceeds theoretical size %3, truncate from_stretcher to %4\n", @@ -2191,13 +2177,13 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t if (from_stretcher == 0) { - if (process_index < final_processed_sample) { - DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached (EX) end, entering playout mode to cover %2 .. %3\n", index(), process_index, final_processed_sample)); + if (process_index < final_process_index) { + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached (EX) end, entering playout mode to cover %2 .. %3\n", index(), process_index, final_process_index)); _playout = true; } else { _state = Stopped; _loop_cnt++; - DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached (EX) end, now stopped, retrieved %2, avail %3 pi %4 vs fs %5 LC now %6\n", index(), retrieved, avail, process_index, final_processed_sample, _loop_cnt)); + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached (EX) end, now stopped, retrieved %2, avail %3 pi %4 vs fs %5 LC now %6\n", index(), retrieved, avail, process_index, final_process_index, _loop_cnt)); } break; @@ -2210,7 +2196,6 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t /* no stretch */ assert (last_readable_sample >= read_index); from_stretcher = std::min (nframes, last_readable_sample - read_index); - // cerr << "FS#3 from lrs " << last_readable_sample << " - " << read_index << " = " << from_stretcher << endl; } @@ -2259,8 +2244,8 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t if (read_index >= last_readable_sample && (!do_stretch || avail <= 0)) { - if (process_index < final_processed_sample) { - DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached end, entering playout mode to cover %2 .. %3\n", index(), process_index, final_processed_sample)); + if (process_index < final_process_index) { + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached end, entering playout mode to cover %2 .. %3 avail = %4\n", index(), process_index, final_process_index, avail)); _playout = true; } else { _state = Stopped; @@ -2268,6 +2253,11 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached end, now stopped, retrieved %2, avail %3 LC now %4\n", index(), retrieved, avail, _loop_cnt)); } break; + } else if (process_index >= final_process_index) { + _state = Stopped; + _loop_cnt++; + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached end via pi alone, now stopped, retrieved %2, avail %3 LC now %4\n", index(), retrieved, avail, _loop_cnt)); + break; } } @@ -2283,18 +2273,18 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t } const pframes_t remaining_frames_for_run= orig_nframes - covered_frames; - const pframes_t remaining_frames_till_final = final_processed_sample - process_index; + const pframes_t remaining_frames_till_final = final_process_index - process_index; const pframes_t to_fill = std::min (remaining_frames_till_final, remaining_frames_for_run); DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 playout mode, remaining in run %2 till final %3 @ %5 ts %7 vs pi @ %6 to fill %4\n", - index(), remaining_frames_for_run, remaining_frames_till_final, to_fill, final_processed_sample, process_index, transition_samples)); + index(), remaining_frames_for_run, remaining_frames_till_final, to_fill, final_process_index, process_index, transition_samples)); if (remaining_frames_till_final != 0) { process_index += to_fill; covered_frames += to_fill; - if (process_index < final_processed_sample) { + if (process_index < final_process_index) { /* more playout to be done */ return covered_frames; } @@ -2305,6 +2295,8 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 playout finished, LC now %4\n", index(), _loop_cnt)); } + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 run() finished, ri %2 pi %3 r/p %4\n", index(), read_index, process_index, (double) read_index / process_index)); + if (_state == Stopped || _state == Stopping) { /* note: neither argument is used in the audio case */ when_stopped_during_run (bufs, dest_offset); @@ -2313,7 +2305,20 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t return covered_frames; } +Trigger::PendingSwap* +AudioTrigger::pending_factory () const { + return new AudioPendingSwap; +} + +int +AudioTrigger::load_pending_data (PendingSwap& ps) +{ + AudioPendingSwap* aps (dynamic_cast (&ps)); + assert (aps); + std::shared_ptr ar (std::dynamic_pointer_cast (_region)); + load_data (ar, aps->audio_data); + return 0; } void @@ -2333,21 +2338,53 @@ 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); + + /* Need to use the region's tempo (map) to convert between time domains here */ + + if (stretching()) { + + Temporal::TempoMap::SharedPtr rmap (_region->tempo_map()); + assert (rmap); + + if (pending->length.time_domain() == Temporal::BeatTime) { + _beatcnt = Temporal::DoubleableBeats (pending->length.beats()).to_double(); + } else { + _beatcnt = Temporal::DoubleableBeats (rmap->quarters_at_sample (pending->length.samples())).to_double(); + } + } + + /* Switch over data, which spans region->start() to region->end() aka + * region->start() + region->length() + */ + + AudioPendingSwap* aps (dynamic_cast (pending)); + assert (aps); + data = aps->audio_data; + + /* pending->audio_data is now unusable */ + + if (playing) { + + /* if the start has been moved past the current process + * position, we need to do something drastic. + */ + + if (pending->play_start > process_index) { + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1/%2 new start %3 is past process index %4\n", _box.order(), index(), pending->play_start, process_index)); + jump_stop (bufs, 0); + startup (bufs, 0, _quantization); + return; + } + + Temporal::Beats elen_ignored; + compute_end (Temporal::TempoMap::use(), _transition_bbt, transition_samples, elen_ignored); + } + + /* adjust read index to point to the same sample, if possible */ + old_pending_swap.store (pending); } - -void -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 (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(); -} - /*--------------------*/ MIDITrigger::MIDITrigger (uint32_t n, TriggerBox& b) @@ -2714,7 +2751,7 @@ MIDITrigger::compute_end (Temporal::TempoMap::SharedPtr const & tmap, Temporal:: timepos_t e (final_beat); - final_processed_sample = e.samples() - transition_samples; + final_process_index = e.samples() - transition_samples; return e; } @@ -3132,6 +3169,21 @@ MIDITrigger::tempo_map_changed () map_change = true; } +Trigger::PendingSwap* +MIDITrigger::pending_factory () const +{ + return new MIDIPendingSwap; +} + +int +MIDITrigger::load_pending_data (PendingSwap& ps) +{ + MIDIPendingSwap* mps (dynamic_cast (&ps)); + assert (mps); + _model->render (_model->read_lock(), *mps->rt_midibuffer); + return 0; +} + void MIDITrigger::model_contents_changed () { @@ -3407,28 +3459,28 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 not done with playout, all frames covered\n", index())); } else { /* finishing up playout */ - samplepos_t final_processed_sample = tmap->sample_at (timepos_t (final_beat)); + samplepos_t final_process_index = tmap->sample_at (timepos_t (final_beat)); if (map_change) { - if ((start_sample > final_processed_sample) || (final_processed_sample - start_sample > orig_nframes)) { + if ((start_sample > final_process_index) || (final_process_index - start_sample > orig_nframes)) { nframes = 0; _loop_cnt++; _state = Stopping; } else { - nframes = orig_nframes - (final_processed_sample - start_sample); + nframes = orig_nframes - (final_process_index - start_sample); } } else { - nframes = orig_nframes - (final_processed_sample - start_sample); + nframes = orig_nframes - (final_process_index - start_sample); _loop_cnt++; _state = Stopped; } - DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 playout done, nf = %2 fb %3 fs %4 %5 LC %6\n", index(), nframes, final_beat, final_processed_sample, start_sample, _loop_cnt)); + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 playout done, nf = %2 fb %3 fs %4 %5 LC %6\n", index(), nframes, final_beat, final_process_index, start_sample, _loop_cnt)); } } else { - const samplepos_t final_processed_sample = tmap->sample_at (timepos_t (final_beat)); - const samplecnt_t nproc = (final_processed_sample - start_sample); + const samplepos_t final_process_index = tmap->sample_at (timepos_t (final_beat)); + const samplecnt_t nproc = (final_process_index - start_sample); if (nproc > orig_nframes) { /* tempo map changed, probably */ @@ -3438,7 +3490,7 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en } _loop_cnt++; _state = Stopped; - DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached final event, now stopped, nf = %2 fb %3 fs %4 %5 LC %6\n", index(), nframes, final_beat, final_processed_sample, start_sample, _loop_cnt)); + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached final event, now stopped, nf = %2 fb %3 fs %4 %5 LC %6\n", index(), nframes, final_beat, final_process_index, start_sample, _loop_cnt)); } } else { @@ -4886,6 +4938,8 @@ TriggerBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp const Location* const loop_loc = _loop_location; + DEBUG_TRACE (DEBUG::Triggers, string_compose ("**** Triggerbox::run(%1) from %2 to %3 nf = %4\n", order(), start_sample, end_sample, nframes)); + if (!loop_loc) { run_cycle (bufs, start_sample, end_sample, speed, nframes); } else { @@ -4923,7 +4977,7 @@ TriggerBox::run_cycle (BufferSet& bufs, samplepos_t start_sample, samplepos_t en const Temporal::Beats __end_beats (timepos_t (end_sample).beats()); const double __bpm = __tmap->quarters_per_minute_at (timepos_t (__start_beats)); - DEBUG_TRACE (DEBUG::Triggers, string_compose ("**** Triggerbox::run() for %6, ss %1 es %2 sb %3 eb %4 bpm %5 nf %7\n", start_sample, end_sample, __start_beats, __end_beats, __bpm, order(), nframes)); + DEBUG_TRACE (DEBUG::Triggers, string_compose ("**** Triggerbox::run_cycle() for %6, ss %1 es %2 sb %3 eb %4 bpm %5 nf %7\n", start_sample, end_sample, __start_beats, __end_beats, __bpm, order(), nframes)); } #endif