diff --git a/libs/ardour/ardour/debug.h b/libs/ardour/ardour/debug.h index 2a2d44f915..b6b1fdadd9 100644 --- a/libs/ardour/ardour/debug.h +++ b/libs/ardour/ardour/debug.h @@ -97,6 +97,7 @@ namespace PBD { LIBARDOUR_API extern DebugBits TempoMap; LIBARDOUR_API extern DebugBits TempoMath; LIBARDOUR_API extern DebugBits Transport; + LIBARDOUR_API extern DebugBits Triggers; LIBARDOUR_API extern DebugBits US2400; LIBARDOUR_API extern DebugBits VCA; LIBARDOUR_API extern DebugBits VST3Callbacks; diff --git a/libs/ardour/ardour/triggerbox.h b/libs/ardour/ardour/triggerbox.h index 4892d6c89a..4b8eccee58 100644 --- a/libs/ardour/ardour/triggerbox.h +++ b/libs/ardour/ardour/triggerbox.h @@ -19,6 +19,7 @@ #ifndef __ardour_triggerbox_h__ #define __ardour_triggerbox_h__ +#include #include #include #include @@ -52,15 +53,32 @@ class TriggerBox; class LIBARDOUR_API Trigger : public PBD::Stateful { public: + enum State { + None = 0, /* mostly for _requested_state */ + Stopped = 1, + WaitingToStart = 2, + Running = 3, + WaitingToStop = 4, + Stopping = 5 + }; + Trigger (size_t index, TriggerBox&); virtual ~Trigger() {} static void make_property_quarks (); - virtual void bang (TriggerBox&) = 0; - virtual void unbang (TriggerBox&, Temporal::Beats const &, samplepos_t) = 0; + /* semantics of "bang" depend on the trigger */ + void bang (); + void unbang (); + /* explicitly call for the trigger to stop */ + virtual void stop(); + /* explicitly call for the trigger to start */ + virtual void start(); - bool running() const { return _running; } + void process_state_requests (); + + bool active() const { return _state >= Running; } + State state() const { return _state; } enum LaunchStyle { Loop, /* runs till stopped, reclick just restarts */ @@ -96,10 +114,6 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { virtual timecnt_t current_length() const = 0; virtual timecnt_t natural_length() const = 0; - bool stop_requested() const { return _stop_requested; } - virtual void stop(); - virtual void retrigger () {} - size_t index() const { return _index; } /* Managed by TriggerBox */ @@ -117,10 +131,14 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { ChangeTriggers = 0x8 }; + bool maybe_compute_start_or_stop (Temporal::Beats const & start, Temporal::Beats const & end); + protected: TriggerBox& _box; - bool _running; - bool _stop_requested; + State _state; + std::atomic _requested_state; + std::atomic _bang; + std::atomic _unbang; size_t _index; LaunchStyle _launch_style; FollowAction _follow_action; @@ -128,6 +146,8 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { Temporal::BBT_Offset _quantization; void set_region_internal (boost::shared_ptr); + void request_state (State s); + virtual void retrigger() = 0; }; class LIBARDOUR_API AudioTrigger : public Trigger { @@ -135,9 +155,6 @@ class LIBARDOUR_API AudioTrigger : public Trigger { AudioTrigger (size_t index, TriggerBox&); ~AudioTrigger (); - void bang (TriggerBox&); - void unbang (TriggerBox&, Temporal::Beats const & , samplepos_t); - RunResult run (AudioBuffer&, uint32_t channel, pframes_t& nframes, pframes_t offset, bool first); void set_length (timecnt_t const &); @@ -145,6 +162,8 @@ class LIBARDOUR_API AudioTrigger : public Trigger { timecnt_t natural_length() const; int set_region (boost::shared_ptr); + + protected: void retrigger (); private: diff --git a/libs/ardour/debug.cc b/libs/ardour/debug.cc index 1c4b65fd2d..d2918a29fb 100644 --- a/libs/ardour/debug.cc +++ b/libs/ardour/debug.cc @@ -92,6 +92,7 @@ PBD::DebugBits PBD::DEBUG::TXLTC = PBD::new_debug_bit ("tx-ltc"); PBD::DebugBits PBD::DEBUG::TempoMap = PBD::new_debug_bit ("tempomap"); PBD::DebugBits PBD::DEBUG::TempoMath = PBD::new_debug_bit ("tempomath"); PBD::DebugBits PBD::DEBUG::Transport = PBD::new_debug_bit ("transport"); +PBD::DebugBits PBD::DEBUG::Triggers = PBD::new_debug_bit ("triggers"); PBD::DebugBits PBD::DEBUG::US2400 = PBD::new_debug_bit ("us2400"); PBD::DebugBits PBD::DEBUG::VCA = PBD::new_debug_bit ("vca"); PBD::DebugBits PBD::DEBUG::VST3Callbacks = PBD::new_debug_bit ("VST3Callbacks"); diff --git a/libs/ardour/enums.cc b/libs/ardour/enums.cc index 79e0682b95..386fd9182c 100644 --- a/libs/ardour/enums.cc +++ b/libs/ardour/enums.cc @@ -47,6 +47,7 @@ #include "ardour/track.h" #include "ardour/transport_fsm.h" #include "ardour/transport_master.h" +#include "ardour/triggerbox.h" #include "ardour/types.h" using namespace std; @@ -154,7 +155,8 @@ setup_enum_writer () LoopFadeChoice _LoopFadeChooice; TransportState _TransportState; LocateTransportDisposition _LocateTransportDisposition; - + Trigger::State _TriggerState; + #define REGISTER(e) enum_writer.register_distinct (typeid(e).name(), i, s); i.clear(); s.clear() #define REGISTER_BITS(e) enum_writer.register_bits (typeid(e).name(), i, s); i.clear(); s.clear() #define REGISTER_ENUM(e) i.push_back (e); s.push_back (#e) @@ -836,6 +838,14 @@ setup_enum_writer () REGISTER_ENUM (MustRoll); REGISTER_ENUM (RollIfAppropriate); REGISTER (_LocateTransportDisposition); + + REGISTER_CLASS_ENUM (Trigger, None); + REGISTER_CLASS_ENUM (Trigger, Stopped); + REGISTER_CLASS_ENUM (Trigger, WaitingToStart); + REGISTER_CLASS_ENUM (Trigger, Running); + REGISTER_CLASS_ENUM (Trigger, WaitingToStop); + REGISTER_CLASS_ENUM (Trigger, Stopping); + REGISTER (_TriggerState); } } /* namespace ARDOUR */ diff --git a/libs/ardour/triggerbox.cc b/libs/ardour/triggerbox.cc index 28da67095f..6b35ef65d7 100644 --- a/libs/ardour/triggerbox.cc +++ b/libs/ardour/triggerbox.cc @@ -5,12 +5,14 @@ #include #include "pbd/basename.h" +#include "pbd/compose.h" #include "pbd/failed_constructor.h" #include "temporal/tempo.h" #include "ardour/audioregion.h" #include "ardour/audio_buffer.h" +#include "ardour/debug.h" #include "ardour/midi_buffer.h" #include "ardour/region_factory.h" #include "ardour/session.h" @@ -30,6 +32,477 @@ namespace ARDOUR { } } +Trigger::Trigger (size_t n, TriggerBox& b) + : _box (b) + , _state (Stopped) + , _requested_state (None) + , _bang (0) + , _unbang (0) + , _index (n) + , _launch_style (Loop) + , _follow_action (Stop) + , _quantization (Temporal::BBT_Offset (0, 1, 0)) +{ +} + +void +Trigger::bang () +{ + _bang.fetch_add (1); + std::cerr << "trigger " << index() << " banged to " << _bang.load() << std::endl; +} + +void +Trigger::unbang () +{ + _unbang.fetch_add (1); +} + +void +Trigger::set_follow_action (FollowAction f) +{ + _follow_action = f; +} + +void +Trigger::set_launch_style (LaunchStyle l) +{ + _launch_style = l; +} + +XMLNode& +Trigger::get_state (void) +{ + XMLNode* node = new XMLNode (X_("Trigger")); + return *node; +} + +int +Trigger::set_state (const XMLNode&, int version) +{ + return 0; +} +void +Trigger::set_quantization (Temporal::BBT_Offset const & q) +{ + _quantization = q; +} + +void +Trigger::set_region_internal (boost::shared_ptr r) +{ + _region = r; +} + +Temporal::BBT_Offset +Trigger::quantization () const +{ + return _quantization; +} + +void +Trigger::stop () +{ + request_state (Stopped); +} + +void +Trigger::start () +{ + request_state (Running); +} + +void +Trigger::request_state (State s) +{ + _requested_state.store (s); +} + +void +Trigger::process_state_requests () +{ + State new_state = _requested_state.exchange (None); + + if (new_state == _state) { + return; + } + + switch (new_state) { + case Stopped: + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 %2 -> %3\n", index(), enum_2_string (_state), enum_2_string (WaitingToStop))); + _state = WaitingToStop; + break; + case Running: + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 %2 -> %3\n", index(), enum_2_string (_state), enum_2_string (WaitingToStart))); + _state = WaitingToStart; + break; + default: + break; + } + + /* now check bangs/unbangs */ + + int x; + + while ((x = _bang.load ())) { + + _bang.fetch_sub (1); + + switch (_state) { + case None: + abort (); + break; + + case Running: + switch (launch_style()) { + case Loop: + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 retrigger\n", index())); + retrigger (); + break; + case Gate: + case Toggle: + case Repeat: + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 %2 -> %3\n", index(), enum_2_string (Running), enum_2_string (Stopped))); + _state = Stopped; + } + break; + + case Stopped: + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 %2 -> %3\n", index(), enum_2_string (Stopped), enum_2_string (WaitingToStart))); + _state = WaitingToStart; + break; + + case WaitingToStart: + break; + + case WaitingToStop: + break; + + case Stopping: + break; + } + } + + + while ((x = _unbang.load ())) { + + _unbang.fetch_sub (1); + + if (active() &&launch_style() == Trigger::Gate) { + _state = WaitingToStop; + } + } +} + +bool +Trigger::maybe_compute_start_or_stop (Temporal::Beats const & start, Temporal::Beats const & end) +{ + timepos_t ev_time (Temporal::BeatTime); + + if (_quantization.bars == 0) { + ev_time = timepos_t (start.snap_to (Temporal::Beats (_quantization.beats, _quantization.ticks))); + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 quantized with %5 start at %2, sb %3 eb %4\n", index(), ev_time, start, end, _quantization)); + } else { + /* XXX not yet handled */ + } + + if (ev_time.beats() >= start && ev_time < end) { + bang_samples = ev_time.samples(); + bang_beats = ev_time.beats (); + + if (_state == WaitingToStop) { + _state = Stopping; + } else { + retrigger (); + _state = Running; + } + + return true; + } + + + return false; +} + +/*--------------------*/ + +AudioTrigger::AudioTrigger (size_t n, TriggerBox& b) + : Trigger (n, b) + , data (0) + , data_length (0) +{ +} + +AudioTrigger::~AudioTrigger () +{ + for (std::vector::iterator d = data.begin(); d != data.end(); ++d) { + delete *d; + } +} + +void +AudioTrigger::set_length (timecnt_t const & newlen) +{ + using namespace RubberBand; + using namespace Temporal; + + if (!_region) { + return; + } + + boost::shared_ptr ar (boost::dynamic_pointer_cast (_region)); + + /* load raw data */ + + load_data (ar); + + if (newlen == _region->length()) { + /* no stretch required */ + return; + } + + std::cerr << " sl to " << newlen << " from " << _region->length() << endl; + + /* offline stretch */ + + /* study */ + + const uint32_t nchans = ar->n_channels(); + + RubberBandStretcher::Options options = RubberBandStretcher::Option (RubberBandStretcher::OptionProcessOffline|RubberBandStretcher::OptionStretchPrecise); + RubberBandStretcher stretcher (_box.session().sample_rate(), nchans, options, 1.0, 1.0); + + /* Compute stretch ratio */ + + double new_ratio; + + if (newlen.time_domain() == AudioTime) { + new_ratio = (double) newlen.samples() / data_length; + } else { + /* XXX what to use for position ??? */ + const timecnt_t dur = TempoMap::use()->convert_duration (newlen, timepos_t (0), AudioTime); + std::cerr << "new dur = " << dur << " S " << dur.samples() << " vs " << data_length << endl; + new_ratio = (double) dur.samples() / data_length; + } + + stretcher.setTimeRatio (new_ratio); + + const samplecnt_t expected_length = ceil (data_length * new_ratio) + 16; /* extra space for safety */ + std::vector stretched; + + for (uint32_t n = 0; n < nchans; ++n) { + stretched.push_back (new Sample[expected_length]); + } + + /* RB expects array-of-ptr-to-Sample, so set one up */ + + Sample* raw[nchans]; + Sample* results[nchans]; + + /* study, then process */ + + const samplecnt_t block_size = 16384; + samplecnt_t read = 0; + + stretcher.setDebugLevel (0); + stretcher.setMaxProcessSize (block_size); + stretcher.setExpectedInputDuration (data_length); + + while (read < data_length) { + + for (uint32_t n = 0; n < nchans; ++n) { + raw[n] = data[n] + read; + } + + samplecnt_t to_read = std::min (block_size, data_length - read); + read += to_read; + + stretcher.study (raw, to_read, (read >= data_length)); + } + + read = 0; + + samplecnt_t processed = 0; + samplecnt_t avail; + + while (read < data_length) { + + for (uint32_t n = 0; n < nchans; ++n) { + raw[n] = data[n] + read; + } + + samplecnt_t to_read = std::min (block_size, data_length - read); + read += to_read; + + stretcher.process (raw, to_read, (read >= data_length)); + + while ((avail = stretcher.available()) > 0) { + + for (uint32_t n = 0; n < nchans; ++n) { + results[n] = stretched[n] + processed; + } + + processed += stretcher.retrieve (results, avail); + } + } + + /* collect final chunk of data, possible delayed by thread activity in stretcher */ + + while ((avail = stretcher.available()) >= 0) { + + if (avail == 0) { + Glib::usleep (10000); + continue; + } + + for (uint32_t n = 0; n < nchans; ++n) { + results[n] = stretched[n] + processed; + } + + processed += stretcher.retrieve (results, avail); + } + + /* allocate new data buffers */ + + drop_data (); + data = stretched; + data_length = processed; +} + +timecnt_t +AudioTrigger::current_length() const +{ + if (_region) { + return timecnt_t (data_length); + } + return timecnt_t (Temporal::BeatTime); +} + +timecnt_t +AudioTrigger::natural_length() const +{ + if (_region) { + return _region->length(); + } + return timecnt_t (Temporal::BeatTime); +} + +int +AudioTrigger::set_region (boost::shared_ptr r) +{ + boost::shared_ptr ar = boost::dynamic_pointer_cast (r); + + if (!ar) { + return -1; + } + + set_region_internal (r); + + /* this will load data, but won't stretch it for now */ + + set_length (r->length ()); + + PropertyChanged (ARDOUR::Properties::name); + + return 0; +} + +void +AudioTrigger::drop_data () +{ + for (uint32_t n = 0; n < data.size(); ++n) { + delete [] data[n]; + } + data.clear (); +} + +int +AudioTrigger::load_data (boost::shared_ptr ar) +{ + const uint32_t nchans = ar->n_channels(); + + data_length = ar->length_samples(); + + drop_data (); + + try { + for (uint32_t n = 0; n < nchans; ++n) { + data.push_back (new Sample[data_length]); + read_index.push_back (0); + ar->read (data[n], 0, data_length, n); + } + } catch (...) { + drop_data (); + return -1; + } + + return 0; +} + +void +AudioTrigger::retrigger () +{ + for (std::vector::iterator ri = read_index.begin(); ri != read_index.end(); ++ri) { + (*ri) = 0; + } +} + +Trigger::RunResult +AudioTrigger::run (AudioBuffer& buf, uint32_t channel, pframes_t& nframes, pframes_t dest_offset, bool first) +{ + if (read_index[channel] >= data_length) { + return RemoveTrigger; + } + + if (!active()) { + return RemoveTrigger; + } + + + channel %= data.size(); + + pframes_t nf = (pframes_t) std::min ((samplecnt_t) nframes, (data_length - read_index[channel])); + Sample* src = data[channel] + read_index[channel]; + + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 running with nf %2\n", index(), nf)); + + if (first) { + buf.read_from (src, nf, dest_offset); + } else { + buf.accumulate_from (src, nf); + } + + read_index[channel] += nf; + + if ((nframes - nf) != 0) { + /* did not get all samples, must have reached the end, figure out what do to */ + nframes = nf; + return at_end (); + } + + return Relax; +} + +Trigger::RunResult +AudioTrigger::at_end () +{ + switch (launch_style()) { + case Trigger::Loop: + retrigger(); + return ReadMore; + default: + break; + } + + if (follow_action() == Stop) { + return RunResult (RemoveTrigger|FillSilence); + } + + return RunResult (RemoveTrigger|ChangeTriggers|FillSilence); +} + +/**************/ + void Trigger::make_property_quarks () { @@ -171,62 +644,6 @@ TriggerBox::add_trigger (Trigger* trigger) all_triggers.push_back (trigger); } -bool -TriggerBox::bang_trigger (Trigger* trigger) -{ - return _bang_queue.write (&trigger, 1) == 1; -} - -bool -TriggerBox::unbang_trigger (Trigger* trigger) -{ - return _unbang_queue.write (&trigger, 1) == 1; -} - -void -TriggerBox::process_ui_trigger_requests () -{ - /* bangs */ - - RingBuffer::rw_vector vec; - _bang_queue.get_read_vector (&vec); - - for (uint32_t n = 0; n < vec.len[0]; ++n) { - Trigger* t = vec.buf[0][n]; - pending_on_triggers.push_back (t); - } - - for (uint32_t n = 0; n < vec.len[1]; ++n) { - Trigger* t = vec.buf[1][n]; - pending_on_triggers.push_back (t); - } - - if (vec.len[0] || vec.len[1]) { - - if (!_session.transport_state_rolling()) { - _session.start_transport_from_processor (); - } - } - - _bang_queue.increment_read_idx (vec.len[0] + vec.len[1]); - - /* unbangs */ - - _unbang_queue.get_read_vector (&vec); - - for (uint32_t n = 0; n < vec.len[0]; ++n) { - Trigger* t = vec.buf[0][n]; - pending_off_triggers.push_back (t); - } - - for (uint32_t n = 0; n < vec.len[1]; ++n) { - Trigger* t = vec.buf[1][n]; - pending_off_triggers.push_back (t); - } - - _unbang_queue.increment_read_idx (vec.len[0] + vec.len[1]); -} - void TriggerBox::process_midi_trigger_requests (BufferSet& bufs) { @@ -257,24 +674,11 @@ TriggerBox::process_midi_trigger_requests (BufferSet& bufs) if ((*ev).is_note_on()) { - if (!t->running()) { - if (find (pending_on_triggers.begin(), pending_on_triggers.end(), t) == pending_on_triggers.end()) { - pending_on_triggers.push_back (t); - } - } else { - t->bang (*this); - } - - if (!_session.transport_state_rolling()) { - _session.start_transport_from_processor (); - } + t->bang (); } else if ((*ev).is_note_off()) { - if (t->running() && t->launch_style() == Trigger::Gate) { - pending_off_triggers.push_back (t); - } - + t->unbang (); } } } @@ -283,122 +687,145 @@ TriggerBox::process_midi_trigger_requests (BufferSet& bufs) void TriggerBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes, bool result_required) { - static int64_t rcnt = 0; - - rcnt++; if (start_sample < 0) { + /* we can't do anything under these conditions (related to + latency compensation + */ return; } - process_ui_trigger_requests (); process_midi_trigger_requests (bufs); - if (active_triggers.empty() && pending_on_triggers.empty()) { - /* nothing to do */ + size_t run_cnt = 0; + + for (size_t n = 0; n < all_triggers.size(); ++n) { + all_triggers[n]->process_state_requests (); + + /* now recheck all states so that we know if we have any + * active triggers. + */ + + switch (all_triggers[n]->state()) { + case Trigger::Running: + case Trigger::WaitingToStart: + case Trigger::WaitingToStop: + case Trigger::Stopping: + run_cnt++; + default: + break; + } + } + + if (run_cnt == 0) { + /* this saves us from the cost of the tempo map lookups. + XXX if these were passed in to ::run(), we could possibly + skip this condition. + */ return; } + /* transport must be active for triggers */ + + if (!_session.transport_state_rolling()) { + _session.start_transport_from_processor (); + } + timepos_t start (start_sample); timepos_t end (end_sample); Temporal::Beats start_beats (start.beats()); Temporal::Beats end_beats (end.beats()); Temporal::TempoMap::SharedPtr tmap (Temporal::TempoMap::use()); + bool will_start = false; + bool will_stop = false; - for (Triggers::iterator t = pending_on_triggers.begin(); t != pending_on_triggers.end(); ) { + for (size_t n = 0; n < all_triggers.size(); ++n) { - Temporal::BBT_Offset q = (*t)->quantization (); - timepos_t fire_at (Temporal::BeatTime); - - if (q.bars == 0) { - fire_at = timepos_t (start_beats.snap_to (Temporal::Beats (q.beats, q.ticks))); - } else { - /* XXX not yet handled */ - } - - if ((*t)->running()) { - - if (fire_at >= start_beats && fire_at < end_beats) { - (*t)->bang (*this); - t = pending_on_triggers.erase (t); - } else { - ++t; - } - - } else if (fire_at >= start_beats && fire_at < end_beats) { - - (*t)->bang_samples = fire_at.samples(); - (*t)->bang_beats = fire_at.beats(); - active_triggers.push_back (*t); - t = pending_on_triggers.erase (t); - - } else { - ++t; - } - } - - for (Triggers::iterator t = pending_off_triggers.begin(); t != pending_off_triggers.end(); ) { - - Temporal::BBT_Offset q = (*t)->quantization (); - timepos_t off_at (Temporal::BeatTime); - - if (q.bars == 0) { - off_at = timepos_t (start_beats.snap_to (Temporal::Beats (q.beats, q.ticks))); - } else { - /* XXX not yet handled */ - } - - if (off_at >= start_beats && off_at < end_beats) { - (*t)->bang_samples = off_at.samples(); - (*t)->bang_beats = off_at.beats(); - (*t)->unbang (*this, (*t)->bang_beats, (*t)->bang_samples); - t = pending_off_triggers.erase (t); - } else { - ++t; - } } size_t max_chans = 0; Trigger::RunResult rr; + bool first = false; - for (Triggers::iterator t = active_triggers.begin(); t != active_triggers.end(); ) { + /* Now actually run all currently active triggers */ - Trigger* trigger = (*t); - boost::shared_ptr r = trigger->region(); + for (size_t n = 0; n < all_triggers.size(); ++n) { - if (!r) { - t = active_triggers.erase (t); + Trigger& trigger (*all_triggers[n]); + + if (trigger.state() < Trigger::WaitingToStart) { continue; } - sampleoffset_t dest_offset = 0; - pframes_t trigger_samples = nframes; + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 might run, state %2\n", trigger.index(), enum_2_string (trigger.state()))); - if (trigger->stop_requested()) { + boost::shared_ptr r = trigger.region(); - /* bang_samples says when to stop, so compute an offset - * into the nframes we've been asked to provide. - */ + sampleoffset_t dest_offset; + pframes_t trigger_samples; - trigger_samples = nframes - (trigger->bang_samples - start_sample); + switch (trigger.state()) { + case Trigger::None: + case Trigger::Stopped: + abort (); + break; - } else if (!trigger->running()) { + case Trigger::Running: + case Trigger::Stopping: + break; - trigger->bang (*this); - dest_offset = std::max (samplepos_t (0), trigger->bang_samples - start_sample); - trigger_samples = nframes - dest_offset; + case Trigger::WaitingToStop: + will_stop = trigger.maybe_compute_start_or_stop (start_beats, end_beats); + break; + + case Trigger::WaitingToStart: + will_start = trigger.maybe_compute_start_or_stop (start_beats, end_beats); + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 waiting to start, will start? %2\n", trigger.index(), will_start)); + break; } - AudioTrigger* at = dynamic_cast (trigger); + if (will_stop) { + + /* trigger will reach it's end somewhere within this + * process cycle, so compute the number of samples it + * should generate. + */ + + trigger_samples = nframes - (trigger.bang_samples - start_sample); + dest_offset = 0; + + } else if (will_start) { + + /* trigger will start somewhere within this process + * cycle. Compute the sample offset where any audio + * should end up, and the number of samples it should generate. + */ + + dest_offset = std::max (samplepos_t (0), trigger.bang_samples - start_sample); + trigger_samples = nframes - dest_offset; + + } else { + + /* trigger is just running normally, and will fill + * buffers entirely. + */ + + dest_offset = 0; + trigger_samples = nframes; + } + + AudioTrigger* at = dynamic_cast (&trigger); if (at) { + boost::shared_ptr ar = boost::dynamic_pointer_cast (r); - const bool first = (t == active_triggers.begin()); const size_t nchans = ar->n_channels (); pframes_t nf = trigger_samples; max_chans = std::max (max_chans, nchans); + DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 run with ts %2 do %3\n", trigger.index(), trigger_samples, dest_offset)); + read_more: for (uint32_t chan = 0; chan < nchans; ++chan) { @@ -417,6 +844,8 @@ TriggerBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp } } + first = false; + if (rr == Trigger::ReadMore) { trigger_samples -= nf; goto read_more; @@ -428,19 +857,10 @@ TriggerBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp } - if (rr & Trigger::RemoveTrigger) { - t = active_triggers.erase (t); - continue; - } - - ++t; - if (rr & Trigger::ChangeTriggers) { /* XXX do this! */ std::cerr << "Should change triggers!\n"; } - - } ChanCount cc (DataType::AUDIO, max_chans); @@ -448,6 +868,7 @@ TriggerBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp bufs.set_count (cc); } + XMLNode& TriggerBox::get_state (void) { @@ -461,391 +882,3 @@ TriggerBox::set_state (const XMLNode&, int version) } /*--------------------*/ - -Trigger::Trigger (size_t n, TriggerBox& b) - : _box (b) - , _running (false) - , _stop_requested (false) - , _index (n) - , _launch_style (Loop) - , _follow_action (Stop) - , _quantization (Temporal::BBT_Offset (0, 1, 0)) -{ -} - -void -Trigger::set_follow_action (FollowAction f) -{ - _follow_action = f; -} - -void -Trigger::set_launch_style (LaunchStyle l) -{ - _launch_style = l; -} - -XMLNode& -Trigger::get_state (void) -{ - XMLNode* node = new XMLNode (X_("Trigger")); - return *node; -} - -int -Trigger::set_state (const XMLNode&, int version) -{ - return 0; -} -void -Trigger::set_quantization (Temporal::BBT_Offset const & q) -{ - _quantization = q; -} - -void -Trigger::set_region_internal (boost::shared_ptr r) -{ - _region = r; -} - -Temporal::BBT_Offset -Trigger::quantization () const -{ - return _quantization; -} - -void -Trigger::stop () -{ - _stop_requested = true; -} - -/*--------------------*/ - -AudioTrigger::AudioTrigger (size_t n, TriggerBox& b) - : Trigger (n, b) - , data (0) - , data_length (0) -{ -} - -AudioTrigger::~AudioTrigger () -{ - for (std::vector::iterator d = data.begin(); d != data.end(); ++d) { - delete *d; - } -} - -void -AudioTrigger::set_length (timecnt_t const & newlen) -{ - using namespace RubberBand; - using namespace Temporal; - - if (!_region) { - return; - } - - boost::shared_ptr ar (boost::dynamic_pointer_cast (_region)); - - /* load raw data */ - - load_data (ar); - - if (newlen == _region->length()) { - /* no stretch required */ - return; - } - - std::cerr << " sl to " << newlen << " from " << _region->length() << endl; - - /* offline stretch */ - - /* study */ - - const uint32_t nchans = ar->n_channels(); - - RubberBandStretcher::Options options = RubberBandStretcher::Option (RubberBandStretcher::OptionProcessOffline|RubberBandStretcher::OptionStretchPrecise); - RubberBandStretcher stretcher (_box.session().sample_rate(), nchans, options, 1.0, 1.0); - - /* Compute stretch ratio */ - - double new_ratio; - - if (newlen.time_domain() == AudioTime) { - new_ratio = (double) newlen.samples() / data_length; - } else { - /* XXX what to use for position ??? */ - const timecnt_t dur = TempoMap::use()->convert_duration (newlen, timepos_t (0), AudioTime); - std::cerr << "new dur = " << dur << " S " << dur.samples() << " vs " << data_length << endl; - new_ratio = (double) dur.samples() / data_length; - } - - stretcher.setTimeRatio (new_ratio); - - const samplecnt_t expected_length = ceil (data_length * new_ratio) + 16; /* extra space for safety */ - std::vector stretched; - - for (uint32_t n = 0; n < nchans; ++n) { - stretched.push_back (new Sample[expected_length]); - } - - /* RB expects array-of-ptr-to-Sample, so set one up */ - - Sample* raw[nchans]; - Sample* results[nchans]; - - /* study, then process */ - - const samplecnt_t block_size = 16384; - samplecnt_t read = 0; - - stretcher.setDebugLevel (0); - stretcher.setMaxProcessSize (block_size); - stretcher.setExpectedInputDuration (data_length); - - while (read < data_length) { - - for (uint32_t n = 0; n < nchans; ++n) { - raw[n] = data[n] + read; - } - - samplecnt_t to_read = std::min (block_size, data_length - read); - read += to_read; - - stretcher.study (raw, to_read, (read >= data_length)); - } - - read = 0; - - samplecnt_t processed = 0; - samplecnt_t avail; - - while (read < data_length) { - - for (uint32_t n = 0; n < nchans; ++n) { - raw[n] = data[n] + read; - } - - samplecnt_t to_read = std::min (block_size, data_length - read); - read += to_read; - - stretcher.process (raw, to_read, (read >= data_length)); - - while ((avail = stretcher.available()) > 0) { - - for (uint32_t n = 0; n < nchans; ++n) { - results[n] = stretched[n] + processed; - } - - processed += stretcher.retrieve (results, avail); - } - } - - while ((avail = stretcher.available()) >= 0) { - - if (avail == 0) { - Glib::usleep (10000); - continue; - } - - for (uint32_t n = 0; n < nchans; ++n) { - results[n] = stretched[n] + processed; - } - - processed += stretcher.retrieve (results, avail); - } - - /* allocate new data buffers */ - - drop_data (); - data = stretched; - data_length = processed; -} - -timecnt_t -AudioTrigger::current_length() const -{ - if (_region) { - return timecnt_t (data_length); - } - return timecnt_t (Temporal::BeatTime); -} - -timecnt_t -AudioTrigger::natural_length() const -{ - if (_region) { - return _region->length(); - } - return timecnt_t (Temporal::BeatTime); -} - -int -AudioTrigger::set_region (boost::shared_ptr r) -{ - boost::shared_ptr ar = boost::dynamic_pointer_cast (r); - - if (!ar) { - return -1; - } - - set_region_internal (r); - - /* this will load data, but won't stretch it for now */ - - set_length (r->length ()); - - PropertyChanged (ARDOUR::Properties::name); - - return 0; -} - -void -AudioTrigger::drop_data () -{ - for (uint32_t n = 0; n < data.size(); ++n) { - delete [] data[n]; - } - data.clear (); -} - -int -AudioTrigger::load_data (boost::shared_ptr ar) -{ - const uint32_t nchans = ar->n_channels(); - - data_length = ar->length_samples(); - - drop_data (); - - try { - for (uint32_t n = 0; n < nchans; ++n) { - data.push_back (new Sample[data_length]); - read_index.push_back (0); - ar->read (data[n], 0, data_length, n); - } - } catch (...) { - drop_data (); - return -1; - } - - return 0; -} - -void -AudioTrigger::retrigger () -{ - for (std::vector::iterator ri = read_index.begin(); ri != read_index.end(); ++ri) { - (*ri) = 0; - } - - PropertyChanged (Properties::running); -} - -void -AudioTrigger::bang (TriggerBox& /*proc*/) -{ - /* user "hit" the trigger in a way that means "start" */ - - switch (_launch_style) { - case Loop: - retrigger (); - break; - case Gate: - retrigger (); - break; - case Toggle: - if (_running) { - _stop_requested = true; - } else { - retrigger (); - } - break; - case Repeat: - retrigger (); - break; - } - - _running = true; - PropertyChanged (Properties::running); -} - -void -AudioTrigger::unbang (TriggerBox& /*proc*/, Temporal::Beats const &, samplepos_t) -{ - /* user "hit" the trigger in a way that means "stop" */ - - switch (_launch_style) { - case Loop: - /* do nothing, wait for next "start" */ - break; - case Gate: - _stop_requested = true; - break; - case Toggle: - /* do nothing ... wait for next "start" */ - break; - case Repeat: - _stop_requested = true; - break; - } -} - -Trigger::RunResult -AudioTrigger::run (AudioBuffer& buf, uint32_t channel, pframes_t& nframes, pframes_t dest_offset, bool first) -{ - assert (read_index[channel] < data_length); - - if (!_running) { - return RemoveTrigger; - } - - if (_stop_requested) { - /* XXX need fade out machinery instead of immediate stop */ - _running = false; - PropertyChanged (Properties::running); - _stop_requested = false; - return RemoveTrigger; - } - - channel %= data.size(); - - pframes_t nf = (pframes_t) std::min ((samplecnt_t) nframes, (data_length - read_index[channel])); - Sample* src = data[channel] + read_index[channel]; - - if (first) { - buf.read_from (src, nf, dest_offset); - } else { - buf.accumulate_from (src, nf); - } - - read_index[channel] += nf; - - if ((nframes - nf) != 0) { - /* did not get all samples, must have reached the end, figure out what do to */ - nframes = nf; - return at_end (); - } - - return Relax; -} - -Trigger::RunResult -AudioTrigger::at_end () -{ - switch (launch_style()) { - case Trigger::Loop: - retrigger(); - return ReadMore; - default: - break; - } - - if (follow_action() == Stop) { - return RunResult (RemoveTrigger|FillSilence); - } - - return RunResult (RemoveTrigger|ChangeTriggers|FillSilence); -}