From d8d3abc454c5d5667946428fee7017009671d5d1 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Sun, 30 Nov 2025 08:49:22 -0700 Subject: [PATCH 01/11] AudioTriggerPropertiesBox: base clock displays on region, not trigger data --- gtk2_ardour/audio_trigger_properties_box.cc | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gtk2_ardour/audio_trigger_properties_box.cc b/gtk2_ardour/audio_trigger_properties_box.cc index e387a98e02..4d870c8e60 100644 --- a/gtk2_ardour/audio_trigger_properties_box.cc +++ b/gtk2_ardour/audio_trigger_properties_box.cc @@ -30,6 +30,7 @@ #include "widgets/tooltips.h" +#include "ardour/audioregion.h" #include "ardour/location.h" #include "ardour/profile.h" #include "ardour/session.h" @@ -230,8 +231,14 @@ AudioTriggerPropertiesBox::on_trigger_changed (const PBD::PropertyChange& pc) _start_clock.set_mode (mode); _length_clock.set_mode (mode); - _start_clock.set (at->start_offset ()); - _length_clock.set (at->current_length ()); // set_duration() ? + std::shared_ptr ar (std::dynamic_pointer_cast (at->the_region())); + if (ar) { + _start_clock.set (ar->start()); + _length_clock.set (timepos_t (ar->length())); + } else { + _start_clock.set (timepos_t (0)); + _length_clock.set (timepos_t (0)); + } } if ( pc.contains (Properties::tempo_meter) || pc.contains (Properties::follow_length)) { From 23eea1705dabe4e88ffe164eea1b8323e9f47201 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Sun, 30 Nov 2025 08:49:40 -0700 Subject: [PATCH 02/11] slight tidy up of code in AudioClipEditor's ::position_lines() --- gtk2_ardour/audio_clip_editor.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gtk2_ardour/audio_clip_editor.cc b/gtk2_ardour/audio_clip_editor.cc index 1497354132..f619dc00e5 100644 --- a/gtk2_ardour/audio_clip_editor.cc +++ b/gtk2_ardour/audio_clip_editor.cc @@ -430,11 +430,11 @@ AudioClipEditor::position_lines () return; } - double width = sample_to_pixel (_region->start().samples()); - start_line->set (ArdourCanvas::Rect (0., 0., width, _visible_canvas_height)); + double start_x1 = sample_to_pixel (_region->start().samples()); + double end_x0 = start_x1 + sample_to_pixel (_region->length().samples()); - double offset = sample_to_pixel ((_region->start() + _region->length()).samples()); - end_line->set_position (ArdourCanvas::Duple (offset, 0.)); + start_line->set (ArdourCanvas::Rect (0., 0., start_x1, _visible_canvas_height)); + end_line->set_position (ArdourCanvas::Duple (end_x0, 0.)); end_line->set (ArdourCanvas::Rect (0., 0., ArdourCanvas::COORD_MAX, _visible_canvas_height)); } From cd4bc46bc7893180bd97f5dd48a344dfbb5485e7 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Sun, 30 Nov 2025 08:50:21 -0700 Subject: [PATCH 03/11] AudioClipEditor: improve positioning of playhead during clip playback --- gtk2_ardour/audio_clip_editor.cc | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/gtk2_ardour/audio_clip_editor.cc b/gtk2_ardour/audio_clip_editor.cc index f619dc00e5..17dc08094d 100644 --- a/gtk2_ardour/audio_clip_editor.cc +++ b/gtk2_ardour/audio_clip_editor.cc @@ -789,15 +789,23 @@ AudioClipEditor::maybe_update () if (playing_trigger->active ()) { if (playing_trigger->the_region()) { - _playhead_cursor->set_position (playing_trigger->current_pos().samples() + playing_trigger->the_region()->start().samples()); + + /* We can't know the precise sample + * position because we may be + * stretching. So figure out + */ + std::shared_ptr at (std::dynamic_pointer_cast (playing_trigger)); + if (at) { + const double f = playing_trigger->position_as_fraction (); + _playhead_cursor->set_position (playing_trigger->the_region()->start().samples() + (f * at->data_length())); + } } } else { _playhead_cursor->set_position (0); } } -#if 0 - } else if (view->midi_region()) { + } else if (_region) { /* Timeline region editor */ @@ -806,13 +814,13 @@ AudioClipEditor::maybe_update () } samplepos_t pos = _session->transport_sample(); - samplepos_t spos = view->midi_region()->source_position().samples(); + samplepos_t spos = _region->source_position().samples(); if (pos < spos) { _playhead_cursor->set_position (0); } else { _playhead_cursor->set_position (pos - spos); } -#endif + } else { _playhead_cursor->set_position (0); } From 6eec7c6f1b1475b57f07f749aa4b24eabe731c60 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Sun, 30 Nov 2025 08:50:56 -0700 Subject: [PATCH 04/11] AudioTriggerPropertiesBox: set start/set length ops via clocks removed These need to use a different API (operating on the region, not directly on the trigger), or the Trigger API needs revisiting. --- gtk2_ardour/audio_trigger_properties_box.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gtk2_ardour/audio_trigger_properties_box.cc b/gtk2_ardour/audio_trigger_properties_box.cc index 4d870c8e60..d5651b40d7 100644 --- a/gtk2_ardour/audio_trigger_properties_box.cc +++ b/gtk2_ardour/audio_trigger_properties_box.cc @@ -312,11 +312,11 @@ AudioTriggerPropertiesBox::beats_changed () void AudioTriggerPropertiesBox::start_clock_changed () { - trigger()->set_start(_start_clock.last_when()); + /* XXX do something, probably to the region */ } void AudioTriggerPropertiesBox::length_clock_changed () { - trigger()->set_length(_length_clock.current_duration()); //? + /* XXX do something, probably to the region */ } From 2068aa12acd69ce759eafb645a6716b0c9762c28 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Sun, 30 Nov 2025 08:54:23 -0700 Subject: [PATCH 05/11] Triggerbox: remove unused and ill-conceived API from triggers This API was imagined as useful when Triggerbox started, but we never use them and likely never will. If some part of them is required in the future, it will likely be in a different form. --- libs/ardour/ardour/triggerbox.h | 14 -------- libs/ardour/triggerbox.cc | 64 --------------------------------- 2 files changed, 78 deletions(-) diff --git a/libs/ardour/ardour/triggerbox.h b/libs/ardour/ardour/triggerbox.h index 4aec665729..77d06d2949 100644 --- a/libs/ardour/ardour/triggerbox.h +++ b/libs/ardour/ardour/triggerbox.h @@ -289,7 +289,6 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { 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 reload (BufferSet&, void*) = 0; virtual void io_change () {} virtual void set_legato_offset (timepos_t const & offset) = 0; @@ -320,9 +319,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 */ - virtual timepos_t current_length() const = 0; /* offset from start() */ - virtual timepos_t natural_length() const = 0; /* offset from start() */ - void process_state_requests (BufferSet& bufs, pframes_t dest_offset); bool active() const { return _state >= Running; } @@ -552,9 +548,6 @@ class LIBARDOUR_API AudioTrigger : public Trigger { 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 */ - void reload (BufferSet&, void*); void io_change (); bool probably_oneshot () const; @@ -652,9 +645,6 @@ class LIBARDOUR_API MIDITrigger : public Trigger { void set_length (timecnt_t const &); timepos_t start_offset () const; timepos_t end() 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 */ - void reload (BufferSet&, void*); bool probably_oneshot () const; void tempo_map_changed(); @@ -926,7 +916,6 @@ class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_fro TriggerPtr get_next_trigger (); TriggerPtr peek_next_trigger (); - void request_reload (int32_t slot, void*); void set_region (uint32_t slot, std::shared_ptr region); void non_realtime_transport_stop (samplepos_t now, bool flush); @@ -1049,7 +1038,6 @@ class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_fro struct Request { enum Type { Use, - Reload, }; Type type; @@ -1077,8 +1065,6 @@ class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_fro void process_requests (BufferSet&); void process_request (BufferSet&, Request*); - void reload (BufferSet& bufs, int32_t slot, void* ptr); - void cancel_locate_armed (); void fast_forward_nothing_to_do (); int handle_stopped_trigger (BufferSet& bufs, pframes_t dest_offset); diff --git a/libs/ardour/triggerbox.cc b/libs/ardour/triggerbox.cc index a60d6252a5..1a82c278b1 100644 --- a/libs/ardour/triggerbox.cc +++ b/libs/ardour/triggerbox.cc @@ -1717,24 +1717,6 @@ AudioTrigger::set_user_data_length (samplecnt_t s) _user_data_length = s; send_property_change (ARDOUR::Properties::length); } - -timepos_t -AudioTrigger::current_length() const -{ - if (_region) { - return timepos_t (data.length); - } - return timepos_t (Temporal::BeatTime); -} - -timepos_t -AudioTrigger::natural_length() const -{ - if (_region) { - return timepos_t::from_superclock (_region->length().magnitude()); - } - return timepos_t (Temporal::BeatTime); -} int AudioTrigger::set_region_in_worker_thread_from_capture (std::shared_ptr r) { @@ -2321,8 +2303,6 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t return covered_frames; } -void -AudioTrigger::reload (BufferSet&, void*) { } @@ -2952,24 +2932,6 @@ MIDITrigger::set_length (timecnt_t const & newlen) } -timepos_t -MIDITrigger::current_length() const -{ - if (_region) { - return timepos_t (data_length); - } - return timepos_t (Temporal::BeatTime); -} - -timepos_t -MIDITrigger::natural_length() const -{ - if (_region) { - return timepos_t::from_ticks (_region->length().magnitude()); - } - return timepos_t (Temporal::BeatTime); -} - void MIDITrigger::estimate_midi_patches () { @@ -3116,11 +3078,6 @@ MIDITrigger::retrigger () DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 retriggered to start, ts = %2\n", _index, transition_beats)); } -void -MIDITrigger::reload (BufferSet&, void*) -{ -} - void MIDITrigger::tempo_map_changed () { @@ -5566,15 +5523,6 @@ TriggerBox::Request::operator delete (void *ptr, size_t /*size*/) return pool->release (ptr); } -void -TriggerBox::request_reload (int32_t slot, void* ptr) -{ - Request* r = new Request (Request::Reload); - r->slot = slot; - r->ptr = ptr; - requests.write (&r, 1); -} - void TriggerBox::process_requests (BufferSet& bufs) { @@ -5591,23 +5539,11 @@ TriggerBox::process_request (BufferSet& bufs, Request* req) switch (req->type) { case Request::Use: break; - case Request::Reload: - reload (bufs, req->slot, req->ptr); - break; } delete req; /* back to the pool, RT-safe */ } -void -TriggerBox::reload (BufferSet& bufs, int32_t slot, void* ptr) -{ - if (slot >= (int32_t) all_triggers.size()) { - return; - } - all_triggers[slot]->reload (bufs, ptr); -} - double TriggerBox::position_as_fraction () const { From 1605f35811976654767781221c2ec582164ffe3f Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Sun, 30 Nov 2025 09:58:27 -0700 Subject: [PATCH 06/11] NO-OP: explanatory comment --- libs/ardour/triggerbox.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libs/ardour/triggerbox.cc b/libs/ardour/triggerbox.cc index 1a82c278b1..dc41f23bc2 100644 --- a/libs/ardour/triggerbox.cc +++ b/libs/ardour/triggerbox.cc @@ -2089,6 +2089,16 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t } while (to_pad > 0) { + /* It may seem wasteful to resilence each channel + buffer for each loop iteration here. But there's no + inherent guarantee that passing them to the + stretcher will leave them silent (logically, it + must, but that's not part of the stretcher API). + + Also, in many cases, we only actually do this once + (depending on the ratio of the audioengine buffer + size and the stretcher's latency). + */ const samplecnt_t limit = std::min ((samplecnt_t) scratch->get_audio (0).capacity(), to_pad); for (uint32_t chn = 0; chn < nchans; ++chn) { memset (bufp[chn], 0, sizeof (Sample) * limit); From 79488873820f66cef28c213401ad98aafaba2f0a Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Sun, 30 Nov 2025 10:58:46 -0700 Subject: [PATCH 07/11] 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 From 52af610937cc17e554c3e66d94a227176c6d8280 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Mon, 1 Dec 2025 14:15:23 +0100 Subject: [PATCH 08/11] Clarify MIDI randomize script description --- share/scripts/brutalize_midi.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/scripts/brutalize_midi.lua b/share/scripts/brutalize_midi.lua index bfe8077b6e..7692982aca 100644 --- a/share/scripts/brutalize_midi.lua +++ b/share/scripts/brutalize_midi.lua @@ -1,7 +1,7 @@ ardour { ["type"] = "EditorAction", name = "Brutalize MIDI", license = "MIT", author = "Ardour Team", - description = [[Randomize MIDI Note position (de-quantize).]] + description = [[Randomize MIDI Note position (de-quantize) of selected MIDI regions.]] } function factory () return function () From 9089151f68d0758dfc95b4d65b8a61bf1eb43789 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Mon, 1 Dec 2025 15:11:07 +0100 Subject: [PATCH 09/11] Prepare for Strip Import API overhaul --- gtk2_ardour/strip_import_dialog.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtk2_ardour/strip_import_dialog.cc b/gtk2_ardour/strip_import_dialog.cc index eb68b24673..157a78057e 100644 --- a/gtk2_ardour/strip_import_dialog.cc +++ b/gtk2_ardour/strip_import_dialog.cc @@ -364,7 +364,7 @@ StripImportDialog::parse_route_state (std::string const& path) goto out; } - _extern_map = _session->parse_route_state (path, _match_pbd_id); + //_extern_map = _session->parse_route_state (path, _match_pbd_id); out: if (_extern_map.empty ()) { From 00af254b04340c8a828195d9e377218a6d7c8ddf Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Mon, 1 Dec 2025 16:45:31 +0100 Subject: [PATCH 10/11] Extend strip import API, include additional information --- libs/ardour/ardour/session.h | 26 +++++++++++++++++++-- libs/ardour/session_state.cc | 45 +++++++++++++++++++++++++----------- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index 65828b35d2..845dc504e2 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -664,17 +664,39 @@ public: std::vector possible_states() const; static std::vector possible_states (std::string path); - enum RouteGroupImportMode { IgnoreRouteGroup, UseRouteGroup, CreateRouteGroup }; + struct RouteImportInfo { + RouteImportInfo (std::string const& n, PresentationInfo const& p, int mb) + : name (n) + , pi (p) + , mixbus (mb) + {} + + std::string name; + PresentationInfo pi; + int mixbus; + + bool operator< (RouteImportInfo const& o) { + if (mixbus != o.mixbus) { + return mixbus < o.mixbus; + } + return name < o.name; + } + + bool operator== (RouteImportInfo const& o) { + return mixbus == o.mixbus && name == o.name; + } + }; + bool export_route_state (std::shared_ptr rl, const std::string& path, bool with_sources); int import_route_state (const std::string& path, std::map const&, RouteGroupImportMode rgim = CreateRouteGroup); - std::map parse_route_state (const std::string& path, bool& match_pbd_id); + std::map parse_route_state (const std::string& path, bool& match_pbd_id); /// The instant xml file is written to the session directory void add_instant_xml (XMLNode&, bool write_to_config = true); diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 6de1b6b4e0..ab34667110 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -1198,16 +1198,8 @@ Session::export_route_state (std::shared_ptr rl, const string& path, } static bool -allow_import_route_state (XMLNode const& node, int version) +allow_import_route_state (PresentationInfo const& pi) { - XMLNode* pnode = node.child (PresentationInfo::state_node_name.c_str ()); - if (!pnode) { - return false; - } - - PresentationInfo pi (PresentationInfo::Flag (0)); - pi.set_state (*pnode, version); - if (pi.special (false)) { // |SurroundMaster|MonitorOut|Auditioner return false; } @@ -1218,10 +1210,24 @@ allow_import_route_state (XMLNode const& node, int version) return true; } -std::map +static bool +allow_import_route_state (XMLNode const& node, int version) +{ + XMLNode* pnode = node.child (PresentationInfo::state_node_name.c_str ()); + if (!pnode) { + return false; + } + + PresentationInfo pi (PresentationInfo::Flag (0)); + pi.set_state (*pnode, version); + + return allow_import_route_state (pi); +} + +std::map Session::parse_route_state (const string& path, bool& match_pbd_id) { - std::map rv; + std::map rv; XMLTree tree; if (!tree.read (path)) { @@ -1256,11 +1262,24 @@ Session::parse_route_state (const string& path, bool& match_pbd_id) continue; } - if (!allow_import_route_state (*rxml, version)) { + XMLNode* pnode = rxml->child (PresentationInfo::state_node_name.c_str ()); + if (!pnode) { continue; } - rv[id] = name; + PresentationInfo pi (PresentationInfo::Flag (0)); + pi.set_state (*pnode, version); + + if (!allow_import_route_state (pi)) { + continue; + } + + int mixbus = 0; +#ifdef MIXBUS + rxml->get_property (X_("mixbus-num"), mixbus) +#endif + + rv.emplace (id, RouteImportInfo (name, pi, mixbus)); } } return rv; From 5ef94b05111df203692b463406e773ea4a1f8421 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Mon, 1 Dec 2025 18:36:47 +0100 Subject: [PATCH 11/11] Strip Import: use new API, and meta-data --- gtk2_ardour/strip_import_dialog.cc | 58 ++++++++++++++++++++---------- gtk2_ardour/strip_import_dialog.h | 20 +++++------ 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/gtk2_ardour/strip_import_dialog.cc b/gtk2_ardour/strip_import_dialog.cc index 157a78057e..5aeff03821 100644 --- a/gtk2_ardour/strip_import_dialog.cc +++ b/gtk2_ardour/strip_import_dialog.cc @@ -364,7 +364,7 @@ StripImportDialog::parse_route_state (std::string const& path) goto out; } - //_extern_map = _session->parse_route_state (path, _match_pbd_id); + _extern_map = _session->parse_route_state (path, _match_pbd_id); out: if (_extern_map.empty ()) { @@ -425,12 +425,24 @@ StripImportDialog::refill_import_table () _strip_table.attach (*manage (new ArdourHSpacer (1.0)), 0, 4, 1, 2, EXPAND | FILL, SHRINK, 4, 8); /* clang-format on */ + std::vector> sorted_map; + for (auto const& i : _import_map) { + sorted_map.push_back (i); + } + std::sort (sorted_map.begin (), sorted_map.end (), [=] (auto& a, auto& b) { + try { + return _route_map.at (a.first).pi.order () < _route_map.at (b.first).pi.order (); + } catch (...) { + } + return a.second < b.second; + }); + /* Refill table */ int r = 1; - for (auto& [rid, eid] : _import_map) { + for (auto& [rid, eid] : sorted_map /*_import_map*/) { ++r; if (_route_map.find (rid) != _route_map.end ()) { - l = manage (new Label (_route_map[rid], 0, 0.5)); + l = manage (new Label (_route_map.at (rid).name, 0, 0.5)); } else { l = manage (new Label (_("New Track"), 0, 0.5)); l->set_use_markup (); @@ -442,10 +454,10 @@ StripImportDialog::refill_import_table () #else using namespace Menu_Helpers; ArdourDropdown* dd = manage (new ArdourDropdown ()); - for (auto& [eid, ename] : _extern_map) { - dd->add_menu_elem (MenuElem (Gtkmm2ext::markup_escape_text (ename), sigc::bind (sigc::mem_fun (*this, &StripImportDialog::change_mapping), dd, rid, eid, ename))); + for (auto& [eid, einfo] : _extern_map) { + dd->add_menu_elem (MenuElem (Gtkmm2ext::markup_escape_text (einfo.name), sigc::bind (sigc::mem_fun (*this, &StripImportDialog::change_mapping), dd, rid, eid, einfo.name))); } - dd->set_text (_extern_map[eid]); + dd->set_text (_extern_map.at (eid).name); _strip_table.attach (*dd, 2, 3, r, r + 1, EXPAND | FILL, SHRINK); #endif @@ -485,18 +497,18 @@ StripImportDialog::refill_import_table () _add_rid_dropdown->add_menu_elem (MenuElem (_(" -- New Track -- "), sigc::bind (sigc::mem_fun (*this, &StripImportDialog::prepare_mapping), false, next_new, _("New Track")))); sizing_texts.push_back (_(" -- New Track -- ")); - for (auto& [rid, rname] : _route_map) { + for (auto& [rid, rinfo] : _route_map) { if (_import_map.find (rid) != _import_map.end ()) { continue; } - _add_rid_dropdown->add_menu_elem (MenuElem (Gtkmm2ext::markup_escape_text (rname), sigc::bind (sigc::mem_fun (*this, &StripImportDialog::prepare_mapping), false, rid, rname))); - sizing_texts.push_back (rname); + _add_rid_dropdown->add_menu_elem (MenuElem (Gtkmm2ext::markup_escape_text (rinfo.name), sigc::bind (sigc::mem_fun (*this, &StripImportDialog::prepare_mapping), false, rid, rinfo.name))); + sizing_texts.push_back (rinfo.name); } _add_eid_dropdown = manage (new ArdourWidgets::ArdourDropdown ()); - for (auto& [eid, ename] : _extern_map) { - _add_eid_dropdown->add_menu_elem (MenuElem (Gtkmm2ext::markup_escape_text (ename), sigc::bind (sigc::mem_fun (*this, &StripImportDialog::prepare_mapping), true, eid, ename))); - sizing_texts.push_back (ename); + for (auto& [eid, einfo] : _extern_map) { + _add_eid_dropdown->add_menu_elem (MenuElem (Gtkmm2ext::markup_escape_text (einfo.name), sigc::bind (sigc::mem_fun (*this, &StripImportDialog::prepare_mapping), true, eid, einfo.name))); + sizing_texts.push_back (einfo.name); } _add_rid_dropdown->set_sizing_texts (sizing_texts); @@ -588,7 +600,7 @@ StripImportDialog::import_all_strips () _import_map.clear (); int64_t next_id = std::numeric_limits::max () - 1 - _extern_map.size (); - for (auto& [eid, ename] : _extern_map) { + for (auto& [eid, einfo] : _extern_map) { PBD::ID next_new = PBD::ID (next_id++); _import_map[next_new] = eid; } @@ -604,17 +616,23 @@ StripImportDialog::set_default_mapping (bool and_idle_update) if (_match_pbd_id) { /* try a 1:1 mapping */ - for (auto& [eid, ename] : _extern_map) { + for (auto& [eid, einfo] : _extern_map) { if (_route_map.find (eid) != _route_map.end ()) { _import_map[eid] = eid; } } } else { /* match by name */ - for (auto& [eid, ename] : _extern_map) { + for (auto& [eid, einfo] : _extern_map) { // TODO consider building a reverse [pointer] map - for (auto& [rid, rname] : _route_map) { - if (ename == rname) { + for (auto& [rid, rinfo] : _route_map) { +#ifdef MIXBUS + if (einfo.mixbus > 0 && einfo.mixbus == rinfo.mixbus) { + _import_map[rid] = eid; + break; + } +#endif + if (einfo == rinfo) { _import_map[rid] = eid; break; } @@ -632,7 +650,11 @@ StripImportDialog::setup_strip_import_page () _route_map.clear (); for (auto const& r : *_session->get_routes ()) { - _route_map[r->id ()] = r->name (); +#ifdef MIXBUS + _route_map.emplace (r->id (), Session::RouteImportInfo (r->name (), r->presentation_info (), c->mixbus ())); +#else + _route_map.emplace (r->id (), Session::RouteImportInfo (r->name (), r->presentation_info (), 0)); +#endif } set_default_mapping (false); diff --git a/gtk2_ardour/strip_import_dialog.h b/gtk2_ardour/strip_import_dialog.h index 9f1d6c2725..61e0daf5ea 100644 --- a/gtk2_ardour/strip_import_dialog.h +++ b/gtk2_ardour/strip_import_dialog.h @@ -28,17 +28,14 @@ #include #include -#include "ardour/search_paths.h" #include "pbd/id.h" +#include "ardour/search_paths.h" +#include "ardour/session.h" + #include "ardour/template_utils.h" #include "ardour_dialog.h" -namespace ARDOUR -{ - class Session; -} - namespace ArdourWidgets { class ArdourButton; @@ -126,11 +123,12 @@ private: ArdourWidgets::ArdourButton* _reset_mapping; ArdourWidgets::ArdourButton* _import_strips; - bool _match_pbd_id; - std::string _path; - std::map _extern_map; - std::map _route_map; - std::map _import_map; + bool _match_pbd_id; + std::string _path; + std::map _import_map; + + std::map _extern_map; + std::map _route_map; PBD::ID _add_rid; PBD::ID _add_eid;