Compare commits

...

11 commits

Author SHA1 Message Date
Robin Gareus
5ef94b0511
Strip Import: use new API, and meta-data 2025-12-01 18:38:45 +01:00
Robin Gareus
00af254b04
Extend strip import API, include additional information 2025-12-01 16:51:10 +01:00
Robin Gareus
9089151f68
Prepare for Strip Import API overhaul 2025-12-01 15:11:07 +01:00
Robin Gareus
52af610937
Clarify MIDI randomize script description 2025-12-01 14:15:23 +01:00
Paul Davis
7948887382 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.
2025-11-30 11:07:42 -07:00
Paul Davis
1605f35811 NO-OP: explanatory comment 2025-11-30 11:07:42 -07:00
Paul Davis
2068aa12ac 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.
2025-11-30 11:07:42 -07:00
Paul Davis
6eec7c6f1b 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.
2025-11-30 11:07:42 -07:00
Paul Davis
cd4bc46bc7 AudioClipEditor: improve positioning of playhead during clip playback 2025-11-30 11:07:42 -07:00
Paul Davis
23eea1705d slight tidy up of code in AudioClipEditor's ::position_lines() 2025-11-30 11:07:42 -07:00
Paul Davis
d8d3abc454 AudioTriggerPropertiesBox: base clock displays on region, not trigger data 2025-11-30 11:07:42 -07:00
9 changed files with 360 additions and 295 deletions

View file

@ -430,11 +430,11 @@ AudioClipEditor::position_lines ()
return; return;
} }
double width = sample_to_pixel (_region->start().samples()); double start_x1 = sample_to_pixel (_region->start().samples());
start_line->set (ArdourCanvas::Rect (0., 0., width, _visible_canvas_height)); double end_x0 = start_x1 + sample_to_pixel (_region->length().samples());
double offset = sample_to_pixel ((_region->start() + _region->length()).samples()); start_line->set (ArdourCanvas::Rect (0., 0., start_x1, _visible_canvas_height));
end_line->set_position (ArdourCanvas::Duple (offset, 0.)); end_line->set_position (ArdourCanvas::Duple (end_x0, 0.));
end_line->set (ArdourCanvas::Rect (0., 0., ArdourCanvas::COORD_MAX, _visible_canvas_height)); end_line->set (ArdourCanvas::Rect (0., 0., ArdourCanvas::COORD_MAX, _visible_canvas_height));
} }
@ -789,15 +789,23 @@ AudioClipEditor::maybe_update ()
if (playing_trigger->active ()) { if (playing_trigger->active ()) {
if (playing_trigger->the_region()) { 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<ARDOUR::AudioTrigger> at (std::dynamic_pointer_cast<AudioTrigger> (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 { } else {
_playhead_cursor->set_position (0); _playhead_cursor->set_position (0);
} }
} }
#if 0 } else if (_region) {
} else if (view->midi_region()) {
/* Timeline region editor */ /* Timeline region editor */
@ -806,13 +814,13 @@ AudioClipEditor::maybe_update ()
} }
samplepos_t pos = _session->transport_sample(); 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) { if (pos < spos) {
_playhead_cursor->set_position (0); _playhead_cursor->set_position (0);
} else { } else {
_playhead_cursor->set_position (pos - spos); _playhead_cursor->set_position (pos - spos);
} }
#endif
} else { } else {
_playhead_cursor->set_position (0); _playhead_cursor->set_position (0);
} }

View file

@ -30,6 +30,7 @@
#include "widgets/tooltips.h" #include "widgets/tooltips.h"
#include "ardour/audioregion.h"
#include "ardour/location.h" #include "ardour/location.h"
#include "ardour/profile.h" #include "ardour/profile.h"
#include "ardour/session.h" #include "ardour/session.h"
@ -230,8 +231,14 @@ AudioTriggerPropertiesBox::on_trigger_changed (const PBD::PropertyChange& pc)
_start_clock.set_mode (mode); _start_clock.set_mode (mode);
_length_clock.set_mode (mode); _length_clock.set_mode (mode);
_start_clock.set (at->start_offset ()); std::shared_ptr<AudioRegion> ar (std::dynamic_pointer_cast<AudioRegion> (at->the_region()));
_length_clock.set (at->current_length ()); // set_duration() ? 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)) { if ( pc.contains (Properties::tempo_meter) || pc.contains (Properties::follow_length)) {
@ -305,11 +312,11 @@ AudioTriggerPropertiesBox::beats_changed ()
void void
AudioTriggerPropertiesBox::start_clock_changed () AudioTriggerPropertiesBox::start_clock_changed ()
{ {
trigger()->set_start(_start_clock.last_when()); /* XXX do something, probably to the region */
} }
void void
AudioTriggerPropertiesBox::length_clock_changed () AudioTriggerPropertiesBox::length_clock_changed ()
{ {
trigger()->set_length(_length_clock.current_duration()); //? /* XXX do something, probably to the region */
} }

View file

@ -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); _strip_table.attach (*manage (new ArdourHSpacer (1.0)), 0, 4, 1, 2, EXPAND | FILL, SHRINK, 4, 8);
/* clang-format on */ /* clang-format on */
std::vector<std::pair<PBD::ID, PBD::ID>> 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 */ /* Refill table */
int r = 1; int r = 1;
for (auto& [rid, eid] : _import_map) { for (auto& [rid, eid] : sorted_map /*_import_map*/) {
++r; ++r;
if (_route_map.find (rid) != _route_map.end ()) { 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 { } else {
l = manage (new Label (_("<i>New Track</i>"), 0, 0.5)); l = manage (new Label (_("<i>New Track</i>"), 0, 0.5));
l->set_use_markup (); l->set_use_markup ();
@ -442,10 +454,10 @@ StripImportDialog::refill_import_table ()
#else #else
using namespace Menu_Helpers; using namespace Menu_Helpers;
ArdourDropdown* dd = manage (new ArdourDropdown ()); ArdourDropdown* dd = manage (new ArdourDropdown ());
for (auto& [eid, ename] : _extern_map) { for (auto& [eid, einfo] : _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))); 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); _strip_table.attach (*dd, 2, 3, r, r + 1, EXPAND | FILL, SHRINK);
#endif #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")))); _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 -- ")); 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 ()) { if (_import_map.find (rid) != _import_map.end ()) {
continue; 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))); _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 (rname); sizing_texts.push_back (rinfo.name);
} }
_add_eid_dropdown = manage (new ArdourWidgets::ArdourDropdown ()); _add_eid_dropdown = manage (new ArdourWidgets::ArdourDropdown ());
for (auto& [eid, ename] : _extern_map) { for (auto& [eid, einfo] : _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))); _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 (ename); sizing_texts.push_back (einfo.name);
} }
_add_rid_dropdown->set_sizing_texts (sizing_texts); _add_rid_dropdown->set_sizing_texts (sizing_texts);
@ -588,7 +600,7 @@ StripImportDialog::import_all_strips ()
_import_map.clear (); _import_map.clear ();
int64_t next_id = std::numeric_limits<uint64_t>::max () - 1 - _extern_map.size (); int64_t next_id = std::numeric_limits<uint64_t>::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++); PBD::ID next_new = PBD::ID (next_id++);
_import_map[next_new] = eid; _import_map[next_new] = eid;
} }
@ -604,17 +616,23 @@ StripImportDialog::set_default_mapping (bool and_idle_update)
if (_match_pbd_id) { if (_match_pbd_id) {
/* try a 1:1 mapping */ /* 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 ()) { if (_route_map.find (eid) != _route_map.end ()) {
_import_map[eid] = eid; _import_map[eid] = eid;
} }
} }
} else { } else {
/* match by name */ /* match by name */
for (auto& [eid, ename] : _extern_map) { for (auto& [eid, einfo] : _extern_map) {
// TODO consider building a reverse [pointer] map // TODO consider building a reverse [pointer] map
for (auto& [rid, rname] : _route_map) { for (auto& [rid, rinfo] : _route_map) {
if (ename == rname) { #ifdef MIXBUS
if (einfo.mixbus > 0 && einfo.mixbus == rinfo.mixbus) {
_import_map[rid] = eid;
break;
}
#endif
if (einfo == rinfo) {
_import_map[rid] = eid; _import_map[rid] = eid;
break; break;
} }
@ -632,7 +650,11 @@ StripImportDialog::setup_strip_import_page ()
_route_map.clear (); _route_map.clear ();
for (auto const& r : *_session->get_routes ()) { 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); set_default_mapping (false);

View file

@ -28,17 +28,14 @@
#include <ytkmm/treestore.h> #include <ytkmm/treestore.h>
#include <ytkmm/treeview.h> #include <ytkmm/treeview.h>
#include "ardour/search_paths.h"
#include "pbd/id.h" #include "pbd/id.h"
#include "ardour/search_paths.h"
#include "ardour/session.h"
#include "ardour/template_utils.h" #include "ardour/template_utils.h"
#include "ardour_dialog.h" #include "ardour_dialog.h"
namespace ARDOUR
{
class Session;
}
namespace ArdourWidgets namespace ArdourWidgets
{ {
class ArdourButton; class ArdourButton;
@ -128,10 +125,11 @@ private:
bool _match_pbd_id; bool _match_pbd_id;
std::string _path; std::string _path;
std::map<PBD::ID, std::string> _extern_map;
std::map<PBD::ID, std::string> _route_map;
std::map<PBD::ID, PBD::ID> _import_map; std::map<PBD::ID, PBD::ID> _import_map;
std::map<PBD::ID, ARDOUR::Session::RouteImportInfo> _extern_map;
std::map<PBD::ID, ARDOUR::Session::RouteImportInfo> _route_map;
PBD::ID _add_rid; PBD::ID _add_rid;
PBD::ID _add_eid; PBD::ID _add_eid;
bool _default_mapping; bool _default_mapping;

View file

@ -664,17 +664,39 @@ public:
std::vector<std::string> possible_states() const; std::vector<std::string> possible_states() const;
static std::vector<std::string> possible_states (std::string path); static std::vector<std::string> possible_states (std::string path);
enum RouteGroupImportMode { enum RouteGroupImportMode {
IgnoreRouteGroup, IgnoreRouteGroup,
UseRouteGroup, UseRouteGroup,
CreateRouteGroup 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<RouteList> rl, const std::string& path, bool with_sources); bool export_route_state (std::shared_ptr<RouteList> rl, const std::string& path, bool with_sources);
int import_route_state (const std::string& path, std::map<PBD::ID, PBD::ID> const&, RouteGroupImportMode rgim = CreateRouteGroup); int import_route_state (const std::string& path, std::map<PBD::ID, PBD::ID> const&, RouteGroupImportMode rgim = CreateRouteGroup);
std::map<PBD::ID, std::string> parse_route_state (const std::string& path, bool& match_pbd_id); std::map<PBD::ID, RouteImportInfo> parse_route_state (const std::string& path, bool& match_pbd_id);
/// The instant xml file is written to the session directory /// The instant xml file is written to the session directory
void add_instant_xml (XMLNode&, bool write_to_config = true); void add_instant_xml (XMLNode&, bool write_to_config = true);

View file

@ -286,10 +286,6 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
virtual pframes_t run (BufferSet&, samplepos_t start_sample, samplepos_t end_sample, virtual pframes_t run (BufferSet&, samplepos_t start_sample, samplepos_t end_sample,
Temporal::Beats const & start, Temporal::Beats const & end, Temporal::Beats const & start, Temporal::Beats const & end,
pframes_t nframes, pframes_t offset, double bpm, pframes_t& quantize_offset) = 0; 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 reload (BufferSet&, void*) = 0;
virtual void io_change () {} virtual void io_change () {}
virtual void set_legato_offset (timepos_t const & offset) = 0; virtual void set_legato_offset (timepos_t const & offset) = 0;
@ -319,10 +315,6 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
virtual bool probably_oneshot () const = 0; 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); void process_state_requests (BufferSet& bufs, pframes_t dest_offset);
bool active() const { return _state >= Running; } bool active() const { return _state >= Running; }
@ -446,7 +438,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
std::shared_ptr<Region> _region; std::shared_ptr<Region> _region;
samplecnt_t process_index; 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; UIState ui_state;
TriggerBox& _box; TriggerBox& _box;
UIRequests _requests; UIRequests _requests;
@ -519,7 +511,8 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
std::atomic<PendingSwap*> pending_swap; std::atomic<PendingSwap*> pending_swap;
std::atomic<PendingSwap*> old_pending_swap; std::atomic<PendingSwap*> 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 { class LIBARDOUR_API AudioTrigger : public Trigger {
@ -546,15 +539,8 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
double segment_beatcnt () { return _beatcnt; } double segment_beatcnt () { return _beatcnt; }
void set_segment_beatcnt (double count); 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_legato_offset (timepos_t const &);
void set_length (timecnt_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 */
void reload (BufferSet&, void*);
void io_change (); void io_change ();
bool probably_oneshot () const; bool probably_oneshot () const;
@ -585,28 +571,35 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
AudioData () : length (0), capacity (0) {} AudioData () : length (0), capacity (0) {}
~AudioData (); ~AudioData ();
AudioData& operator= (AudioData& other); /* really move semantics */
samplecnt_t append (Sample const * src, samplecnt_t cnt, uint32_t chan); samplecnt_t append (Sample const * src, samplecnt_t cnt, uint32_t chan);
void alloc (samplecnt_t cnt, uint32_t nchans); void alloc (samplecnt_t cnt, uint32_t nchans);
void reset () { length = 0; } void reset () { length = 0; }
void drop ();
}; };
Sample const * audio_data (size_t n) const; Sample const * audio_data (size_t n) const;
size_t data_length() const { return data.length; } 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&); void check_edit_swap (timepos_t const &, bool playing, BufferSet&);
protected: protected:
void retrigger (); 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: private:
AudioData data; AudioData data;
samplecnt_t _user_data_length;
RubberBand::RubberBandStretcher* _stretcher; RubberBand::RubberBandStretcher* _stretcher;
samplepos_t _start_offset;
/* computed during run */ /* computed during run */
@ -620,8 +613,8 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
virtual void setup_stretcher (); virtual void setup_stretcher ();
void drop_data (); void drop_data (AudioData&);
int load_data (std::shared_ptr<AudioRegion>); int load_data (std::shared_ptr<AudioRegion>, AudioData&);
void estimate_tempo (); void estimate_tempo ();
void reset_stretcher (); void reset_stretcher ();
void _startup (BufferSet&, pframes_t dest_offset, Temporal::BBT_Offset const &); void _startup (BufferSet&, pframes_t dest_offset, Temporal::BBT_Offset const &);
@ -652,9 +645,6 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
void set_length (timecnt_t const &); void set_length (timecnt_t const &);
timepos_t start_offset () const; timepos_t start_offset () const;
timepos_t end() const; /* offset from start of data */ 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; bool probably_oneshot () const;
void tempo_map_changed(); void tempo_map_changed();
@ -695,6 +685,8 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
std::vector<int> const & channel_map() const { return _channel_map; } std::vector<int> const & channel_map() const { return _channel_map; }
void check_edit_swap (timepos_t const &, bool playing, BufferSet&); void check_edit_swap (timepos_t const &, bool playing, BufferSet&);
PendingSwap* pending_factory() const;
RTMidiBufferBeats const & rt_midi_buffer() const { return *rt_midibuffer.load(); } RTMidiBufferBeats const & rt_midi_buffer() const { return *rt_midibuffer.load(); }
Temporal::Beats play_start() const { return _play_start; } Temporal::Beats play_start() const { return _play_start; }
@ -706,6 +698,7 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
void retrigger (); void retrigger ();
void _arm (Temporal::BBT_Offset const &); 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); 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: private:
PBD::ID data_source; PBD::ID data_source;
@ -926,7 +919,6 @@ class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_fro
TriggerPtr get_next_trigger (); TriggerPtr get_next_trigger ();
TriggerPtr peek_next_trigger (); TriggerPtr peek_next_trigger ();
void request_reload (int32_t slot, void*);
void set_region (uint32_t slot, std::shared_ptr<Region> region); void set_region (uint32_t slot, std::shared_ptr<Region> region);
void non_realtime_transport_stop (samplepos_t now, bool flush); void non_realtime_transport_stop (samplepos_t now, bool flush);
@ -1049,7 +1041,6 @@ class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_fro
struct Request { struct Request {
enum Type { enum Type {
Use, Use,
Reload,
}; };
Type type; Type type;
@ -1077,8 +1068,6 @@ class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_fro
void process_requests (BufferSet&); void process_requests (BufferSet&);
void process_request (BufferSet&, Request*); void process_request (BufferSet&, Request*);
void reload (BufferSet& bufs, int32_t slot, void* ptr);
void cancel_locate_armed (); void cancel_locate_armed ();
void fast_forward_nothing_to_do (); void fast_forward_nothing_to_do ();
int handle_stopped_trigger (BufferSet& bufs, pframes_t dest_offset); int handle_stopped_trigger (BufferSet& bufs, pframes_t dest_offset);

View file

@ -1198,16 +1198,8 @@ Session::export_route_state (std::shared_ptr<RouteList> rl, const string& path,
} }
static bool 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 if (pi.special (false)) { // |SurroundMaster|MonitorOut|Auditioner
return false; return false;
} }
@ -1218,10 +1210,24 @@ allow_import_route_state (XMLNode const& node, int version)
return true; return true;
} }
std::map<PBD::ID, std::string> 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<PBD::ID, Session::RouteImportInfo>
Session::parse_route_state (const string& path, bool& match_pbd_id) Session::parse_route_state (const string& path, bool& match_pbd_id)
{ {
std::map<PBD::ID, std::string> rv; std::map<PBD::ID, RouteImportInfo> rv;
XMLTree tree; XMLTree tree;
if (!tree.read (path)) { if (!tree.read (path)) {
@ -1256,11 +1262,24 @@ Session::parse_route_state (const string& path, bool& match_pbd_id)
continue; continue;
} }
if (!allow_import_route_state (*rxml, version)) { XMLNode* pnode = rxml->child (PresentationInfo::state_node_name.c_str ());
if (!pnode) {
continue; 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; return rv;

View file

@ -234,7 +234,7 @@ Trigger::Trigger (uint32_t n, TriggerBox& b)
, _name (Properties::name, "") , _name (Properties::name, "")
, _color (Properties::color, 0xBEBEBEFF) , _color (Properties::color, 0xBEBEBEFF)
, process_index (0) , process_index (0)
, final_processed_sample (0) , final_process_index (0)
, _box (b) , _box (b)
, _state (Stopped) , _state (Stopped)
, _playout (false) , _playout (false)
@ -726,6 +726,10 @@ Trigger::set_region (std::shared_ptr<Region> r, bool use_thread)
/* load data, do analysis in another thread */ /* load data, do analysis in another thread */
TriggerBox::worker->set_region (_box, index(), r); TriggerBox::worker->set_region (_box, index(), r);
} else { } 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); set_region_in_worker_thread (r);
} }
} }
@ -769,7 +773,8 @@ Trigger::region_property_change (PropertyChange const & what_changed)
void void
Trigger::bounds_changed (Temporal::timepos_t const & start, Temporal::timepos_t const & end, Temporal::timecnt_t const & len) 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_start = start;
pending->play_end = end; 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->loop_end = pending->play_end;
pending->length = len; pending->length = len;
load_pending_data (*pending);
/* And set it. RT thread will find this and do what needs to be done */ /* 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)); 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 0.0;
} }
return process_index / (double) final_processed_sample; return process_index / (double) final_process_index;
} }
void 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 () AudioTrigger::AudioData::~AudioData ()
{ {
for (auto & s : *this) { 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) AudioTrigger::AudioTrigger (uint32_t n, TriggerBox& b)
: Trigger (n, b) : Trigger (n, b)
, _stretcher (0) , _stretcher (nullptr)
, _start_offset (0)
, read_index (0) , read_index (0)
, last_readable_sample (0) , last_readable_sample (0)
, _legato_offset (0) , _legato_offset (0)
@ -1424,7 +1461,7 @@ AudioTrigger::AudioTrigger (uint32_t n, TriggerBox& b)
AudioTrigger::~AudioTrigger () AudioTrigger::~AudioTrigger ()
{ {
drop_data (); data.drop ();
delete _stretcher; delete _stretcher;
} }
@ -1546,9 +1583,6 @@ XMLNode&
AudioTrigger::get_state () const AudioTrigger::get_state () const
{ {
XMLNode& node (Trigger::get_state()); XMLNode& node (Trigger::get_state());
node.set_property (X_("start"), timepos_t (_start_offset));
return node; return node;
} }
@ -1561,41 +1595,18 @@ AudioTrigger::set_state (const XMLNode& node, int version)
return -1; 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 */ /* 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 (); copy_to_ui_state ();
return 0; 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 void
AudioTrigger::set_legato_offset (timepos_t const & offset) AudioTrigger::set_legato_offset (timepos_t const & offset)
{ {
_legato_offset = offset.samples(); _legato_offset = offset.samples();
} }
timepos_t
AudioTrigger::start_offset () const
{
return timepos_t (_start_offset);
}
void void
AudioTrigger::start_and_roll_to (samplepos_t start_pos, samplepos_t end_position, uint32_t cnt) 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) 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 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: 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)); 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)); /* This is tempo-sensitive - we actually compute the sample position bc
samplepos_t end_by_beatcnt = tmap->sample_at (tmap->bbt_walk (transition_bba, Temporal::BBT_Offset (0, bc.get_beats(), bc.get_ticks()))); beats after the transition sample, using either the follow length
samplepos_t end_by_user_data_length = transition_sample + (_user_data_length - _start_offset); or _beatcnt
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 computing end with fl %2 bc %3 dl %4 udl %5\n", index(), const Temporal::BBT_Offset beat_length = internal_use_follow_length() ? _follow_length : Temporal::BBT_Offset (0, bc.get_beats(), bc.get_ticks());
_follow_length, bc.str(), data.length, _user_data_length)); 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, 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()) { if (stretching()) {
/* NOTES: beatcnt here will reflect the stretch, which is OK expected_end_sample = end_by_beats;
Because we are stretching. But .. that's wrong when we're not DEBUG_TRACE (DEBUG::Triggers, string_compose ("stretching, end up at %1\n", expected_end_sample));
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 { } else {
expected_end_sample = end_by_beatcnt; expected_end_sample = std::min (end_by_beats, end_by_data_length);
DEBUG_TRACE (DEBUG::Triggers, string_compose ("stretch and no-follow, end up at %1\n", expected_end_sample)); DEBUG_TRACE (DEBUG::Triggers, string_compose ("no-stretch, 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));
}
} }
final_processed_sample = expected_end_sample - transition_sample; final_process_index = expected_end_sample - transition_sample;
samplecnt_t usable_length = end_by_data_length - transition_samples;
samplecnt_t usable_length; 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));
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;
}
/* called from compute_end() when we know the time (audio & /* called from compute_end() when we know the time (audio &
* musical time domains when we start starting. Our job here is to * 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())) { if (launch_style() != Repeat || (q == Temporal::BBT_Offset())) {
last_readable_sample = _start_offset + usable_length; last_readable_sample = usable_length;
} else { } else {
@ -1693,14 +1690,14 @@ AudioTrigger::compute_end (Temporal::TempoMap::SharedPtr const & tmap, Temporal:
/* XXX MUST HANDLE BAR-LEVEL QUANTIZATION */ /* XXX MUST HANDLE BAR-LEVEL QUANTIZATION */
timecnt_t len (Temporal::Beats (q.beats, q.ticks), timepos_t (Temporal::Beats())); 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; _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); return timepos_t (expected_end_sample);
} }
@ -1711,30 +1708,6 @@ 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
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 int
AudioTrigger::set_region_in_worker_thread_from_capture (std::shared_ptr<Region> r) AudioTrigger::set_region_in_worker_thread_from_capture (std::shared_ptr<Region> r)
{ {
@ -1771,12 +1744,10 @@ AudioTrigger::set_region_in_worker_thread_internal (std::shared_ptr<Region> r, b
} }
if (!from_capture) { if (!from_capture) {
load_data (ar); load_data (ar, data);
} }
if (from_capture) { set_name (ar->name());
set_name (r->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 */ 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 */
@ -1897,15 +1868,6 @@ AudioTrigger::setup_stretcher ()
_stretcher->setMaxProcessSize (rb_blocksize); _stretcher->setMaxProcessSize (rb_blocksize);
} }
void
AudioTrigger::drop_data ()
{
for (auto& d : data) {
delete [] d;
}
data.clear ();
}
void void
AudioTrigger::captured (SlotArmInfo& ai) AudioTrigger::captured (SlotArmInfo& ai)
{ {
@ -1919,7 +1881,6 @@ AudioTrigger::captured (SlotArmInfo& ai)
data.length = ai.audio_buf.length; data.length = ai.audio_buf.length;
data.capacity = ai.audio_buf.capacity; 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)); DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1/%2 captured a total of %3\n", _box.order(), _index, data.length));
@ -1960,27 +1921,25 @@ AudioTrigger::captured (SlotArmInfo& ai)
} }
int int
AudioTrigger::load_data (std::shared_ptr<AudioRegion> ar) AudioTrigger::load_data (std::shared_ptr<AudioRegion> ar, AudioData& audio_data)
{ {
const uint32_t nchans = ar->n_channels(); const uint32_t nchans = ar->n_channels();
drop_data (); audio_data.drop ();
try { try {
samplecnt_t len = ar->length_samples(); samplecnt_t len = ar->length_samples();
data.alloc (len, nchans); audio_data.alloc (len, nchans);
for (uint32_t n = 0; n < nchans; ++n) { 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; audio_data.length = len;
_user_data_length = len;
set_name (ar->name());
} catch (...) { } catch (...) {
drop_data (); audio_data.drop ();
return -1; return -1;
} }
@ -1995,7 +1954,7 @@ AudioTrigger::retrigger ()
update_properties (); update_properties ();
reset_stretcher (); reset_stretcher ();
read_index = _start_offset + _legato_offset; read_index = _legato_offset;
retrieved = 0; retrieved = 0;
_legato_offset = 0; /* used one time only */ _legato_offset = 0; /* used one time only */
@ -2098,15 +2057,26 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t
#ifdef HAVE_RUBBERBAND_3_0_0 #ifdef HAVE_RUBBERBAND_3_0_0
to_pad = _stretcher->getPreferredStartPad(); to_pad = _stretcher->getPreferredStartPad();
to_drop = _stretcher->getStartDelay(); 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 #else
to_pad = _stretcher->getLatency(); to_pad = _stretcher->getLatency();
to_drop = to_pad; 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 #endif
got_stretcher_padding = true; got_stretcher_padding = true;
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 requires %2 padding %3\n", name(), to_pad));
} }
while (to_pad > 0) { 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); const samplecnt_t limit = std::min ((samplecnt_t) scratch->get_audio (0).capacity(), to_pad);
for (uint32_t chn = 0; chn < nchans; ++chn) { for (uint32_t chn = 0; chn < nchans; ++chn) {
memset (bufp[chn], 0, sizeof (Sample) * limit); memset (bufp[chn], 0, sizeof (Sample) * limit);
@ -2145,6 +2115,10 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t
in[chn] = data[chn % data.size ()] + read_index; 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** /* Note: RubberBandStretcher's process() and retrieve() API's accepts Sample**
* as their first argument. This code may appear to only be processing the first * as their first argument. This code may appear to only be processing the first
* channel, but actually processes them all in one pass. * channel, but actually processes them all in one pass.
@ -2155,14 +2129,17 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t
read_index += to_stretcher; read_index += to_stretcher;
avail = _stretcher->available (); avail = _stretcher->available ();
samplecnt_t this_drop = 0;
if (to_drop && avail) { 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); _stretcher->retrieve (&bufp[0], this_drop);
to_drop -= this_drop; to_drop -= this_drop;
avail = _stretcher->available (); 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 /* we've fed the stretcher enough data to have
@ -2180,18 +2157,19 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t
} }
/* fetch the stretch */ /* fetch the stretch */
samplecnt_t this_retrieve = _stretcher->retrieve (&bufp[0], from_stretcher);
retrieved += _stretcher->retrieve (&bufp[0], from_stretcher); retrieved += this_retrieve;
if (read_index >= last_readable_sample) { 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", 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_processed_sample, process_index)); 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) { if (transition_samples + retrieved > expected_end_sample) {
/* final pull from stretched data into output buffers */ /* final pull from stretched data into output buffers */
// cerr << "FS#2 from ees " << final_processed_sample << " - " << process_index << " & " << from_stretcher; // cerr << "FS#2 from ees " << final_process_index << " - " << process_index << " & " << from_stretcher;
from_stretcher = std::min<samplecnt_t> (from_stretcher, std::max<samplecnt_t> (0, final_processed_sample - process_index)); from_stretcher = std::min<samplecnt_t> (from_stretcher, std::max<samplecnt_t> (0, final_process_index - process_index));
// cerr << " => " << from_stretcher << endl; // 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", DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 total retrieved data %2 exceeds theoretical size %3, truncate from_stretcher to %4\n",
@ -2199,13 +2177,13 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t
if (from_stretcher == 0) { if (from_stretcher == 0) {
if (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_processed_sample)); 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; _playout = true;
} else { } else {
_state = Stopped; _state = Stopped;
_loop_cnt++; _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; break;
@ -2218,7 +2196,6 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t
/* no stretch */ /* no stretch */
assert (last_readable_sample >= read_index); assert (last_readable_sample >= read_index);
from_stretcher = std::min<samplecnt_t> (nframes, last_readable_sample - read_index); from_stretcher = std::min<samplecnt_t> (nframes, last_readable_sample - read_index);
// cerr << "FS#3 from lrs " << last_readable_sample << " - " << read_index << " = " << from_stretcher << endl;
} }
@ -2267,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 (read_index >= last_readable_sample && (!do_stretch || avail <= 0)) {
if (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\n", index(), process_index, final_processed_sample)); 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; _playout = true;
} else { } else {
_state = Stopped; _state = Stopped;
@ -2276,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)); 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; 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;
} }
} }
@ -2291,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_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); 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", 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) { if (remaining_frames_till_final != 0) {
process_index += to_fill; process_index += to_fill;
covered_frames += to_fill; covered_frames += to_fill;
if (process_index < final_processed_sample) { if (process_index < final_process_index) {
/* more playout to be done */ /* more playout to be done */
return covered_frames; return covered_frames;
} }
@ -2313,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 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) { if (_state == Stopped || _state == Stopping) {
/* note: neither argument is used in the audio case */ /* note: neither argument is used in the audio case */
when_stopped_during_run (bufs, dest_offset); when_stopped_during_run (bufs, dest_offset);
@ -2321,9 +2305,20 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t
return covered_frames; return covered_frames;
} }
void Trigger::PendingSwap*
AudioTrigger::reload (BufferSet&, void*) AudioTrigger::pending_factory () const
{ {
return new AudioPendingSwap;
}
int
AudioTrigger::load_pending_data (PendingSwap& ps)
{
AudioPendingSwap* aps (dynamic_cast<AudioPendingSwap*> (&ps));
assert (aps);
std::shared_ptr<AudioRegion> ar (std::dynamic_pointer_cast<AudioRegion> (_region));
load_data (ar, aps->audio_data);
return 0;
} }
void void
@ -2343,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)); 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<AudioPendingSwap*> (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); 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) MIDITrigger::MIDITrigger (uint32_t n, TriggerBox& b)
@ -2724,7 +2751,7 @@ MIDITrigger::compute_end (Temporal::TempoMap::SharedPtr const & tmap, Temporal::
timepos_t e (final_beat); timepos_t e (final_beat);
final_processed_sample = e.samples() - transition_samples; final_process_index = e.samples() - transition_samples;
return e; return e;
} }
@ -2952,24 +2979,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 void
MIDITrigger::estimate_midi_patches () MIDITrigger::estimate_midi_patches ()
{ {
@ -3116,11 +3125,6 @@ MIDITrigger::retrigger ()
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 retriggered to start, ts = %2\n", _index, transition_beats)); DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 retriggered to start, ts = %2\n", _index, transition_beats));
} }
void
MIDITrigger::reload (BufferSet&, void*)
{
}
void void
MIDITrigger::tempo_map_changed () MIDITrigger::tempo_map_changed ()
{ {
@ -3165,6 +3169,21 @@ MIDITrigger::tempo_map_changed ()
map_change = true; map_change = true;
} }
Trigger::PendingSwap*
MIDITrigger::pending_factory () const
{
return new MIDIPendingSwap;
}
int
MIDITrigger::load_pending_data (PendingSwap& ps)
{
MIDIPendingSwap* mps (dynamic_cast<MIDIPendingSwap*> (&ps));
assert (mps);
_model->render (_model->read_lock(), *mps->rt_midibuffer);
return 0;
}
void void
MIDITrigger::model_contents_changed () MIDITrigger::model_contents_changed ()
{ {
@ -3440,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())); DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 not done with playout, all frames covered\n", index()));
} else { } else {
/* finishing up playout */ /* 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 (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; nframes = 0;
_loop_cnt++; _loop_cnt++;
_state = Stopping; _state = Stopping;
} else { } else {
nframes = orig_nframes - (final_processed_sample - start_sample); nframes = orig_nframes - (final_process_index - start_sample);
} }
} else { } else {
nframes = orig_nframes - (final_processed_sample - start_sample); nframes = orig_nframes - (final_process_index - start_sample);
_loop_cnt++; _loop_cnt++;
_state = Stopped; _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 { } else {
const samplepos_t final_processed_sample = tmap->sample_at (timepos_t (final_beat)); const samplepos_t final_process_index = tmap->sample_at (timepos_t (final_beat));
const samplecnt_t nproc = (final_processed_sample - start_sample); const samplecnt_t nproc = (final_process_index - start_sample);
if (nproc > orig_nframes) { if (nproc > orig_nframes) {
/* tempo map changed, probably */ /* tempo map changed, probably */
@ -3471,7 +3490,7 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
} }
_loop_cnt++; _loop_cnt++;
_state = Stopped; _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 { } else {
@ -4919,6 +4938,8 @@ TriggerBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp
const Location* const loop_loc = _loop_location; 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) { if (!loop_loc) {
run_cycle (bufs, start_sample, end_sample, speed, nframes); run_cycle (bufs, start_sample, end_sample, speed, nframes);
} else { } else {
@ -4956,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 Temporal::Beats __end_beats (timepos_t (end_sample).beats());
const double __bpm = __tmap->quarters_per_minute_at (timepos_t (__start_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 #endif
@ -5566,15 +5587,6 @@ TriggerBox::Request::operator delete (void *ptr, size_t /*size*/)
return pool->release (ptr); 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 void
TriggerBox::process_requests (BufferSet& bufs) TriggerBox::process_requests (BufferSet& bufs)
{ {
@ -5591,23 +5603,11 @@ TriggerBox::process_request (BufferSet& bufs, Request* req)
switch (req->type) { switch (req->type) {
case Request::Use: case Request::Use:
break; break;
case Request::Reload:
reload (bufs, req->slot, req->ptr);
break;
} }
delete req; /* back to the pool, RT-safe */ 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 double
TriggerBox::position_as_fraction () const TriggerBox::position_as_fraction () const
{ {

View file

@ -1,7 +1,7 @@
ardour { ["type"] = "EditorAction", name = "Brutalize MIDI", ardour { ["type"] = "EditorAction", name = "Brutalize MIDI",
license = "MIT", license = "MIT",
author = "Ardour Team", 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 () function factory () return function ()