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;
}
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));
}
@ -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<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 {
_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);
}

View file

@ -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<AudioRegion> ar (std::dynamic_pointer_cast<AudioRegion> (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)) {
@ -305,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 */
}

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);
/* 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 */
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 (_("<i>New Track</i>"), 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<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++);
_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);

View file

@ -28,17 +28,14 @@
#include <ytkmm/treestore.h>
#include <ytkmm/treeview.h>
#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;
@ -128,10 +125,11 @@ private:
bool _match_pbd_id;
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, ARDOUR::Session::RouteImportInfo> _extern_map;
std::map<PBD::ID, ARDOUR::Session::RouteImportInfo> _route_map;
PBD::ID _add_rid;
PBD::ID _add_eid;
bool _default_mapping;

View file

@ -664,17 +664,39 @@ public:
std::vector<std::string> possible_states() const;
static std::vector<std::string> 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<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);
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
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,
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 reload (BufferSet&, void*) = 0;
virtual void io_change () {}
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 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; }
@ -446,7 +438,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
std::shared_ptr<Region> _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;
@ -519,7 +511,8 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
std::atomic<PendingSwap*> 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 {
@ -546,15 +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 */
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;
@ -585,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;
RubberBand::RubberBandStretcher* _stretcher;
samplepos_t _start_offset;
/* computed during run */
@ -620,8 +613,8 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
virtual void setup_stretcher ();
void drop_data ();
int load_data (std::shared_ptr<AudioRegion>);
void drop_data (AudioData&);
int load_data (std::shared_ptr<AudioRegion>, AudioData&);
void estimate_tempo ();
void reset_stretcher ();
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 &);
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();
@ -695,6 +685,8 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
std::vector<int> 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; }
@ -706,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;
@ -926,7 +919,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> region);
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 {
enum Type {
Use,
Reload,
};
Type type;
@ -1077,8 +1068,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);

View file

@ -1198,16 +1198,8 @@ Session::export_route_state (std::shared_ptr<RouteList> 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<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)
{
std::map<PBD::ID, std::string> rv;
std::map<PBD::ID, RouteImportInfo> 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;

View file

@ -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<Region> 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));
expected_end_sample = end_by_beats;
DEBUG_TRACE (DEBUG::Triggers, string_compose ("stretching, 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));
}
} 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,30 +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);
}
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<Region> r)
{
@ -1771,12 +1744,10 @@ AudioTrigger::set_region_in_worker_thread_internal (std::shared_ptr<Region> 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 */
@ -1897,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)
{
@ -1919,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));
@ -1960,27 +1921,25 @@ AudioTrigger::captured (SlotArmInfo& ai)
}
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();
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;
}
@ -1995,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 */
@ -2098,15 +2057,26 @@ 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) {
/* 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);
@ -2145,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.
@ -2155,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
@ -2180,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<samplecnt_t> (from_stretcher, std::max<samplecnt_t> (0, final_processed_sample - process_index));
// 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_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",
@ -2199,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;
@ -2218,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<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 (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;
@ -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));
}
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_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;
}
@ -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 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);
@ -2321,9 +2305,20 @@ AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t
return covered_frames;
}
void
AudioTrigger::reload (BufferSet&, void*)
Trigger::PendingSwap*
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
@ -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));
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);
}
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)
@ -2724,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;
}
@ -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
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));
}
void
MIDITrigger::reload (BufferSet&, void*)
{
}
void
MIDITrigger::tempo_map_changed ()
{
@ -3165,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<MIDIPendingSwap*> (&ps));
assert (mps);
_model->render (_model->read_lock(), *mps->rt_midibuffer);
return 0;
}
void
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()));
} 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 */
@ -3471,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 {
@ -4919,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 {
@ -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 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
@ -5566,15 +5587,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 +5603,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
{

View file

@ -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 ()