triggerbox: architectural changes to facilitate fast-forward
This commit is contained in:
parent
c9607d2fed
commit
f77e9aa6c8
@ -1359,6 +1359,7 @@ public:
|
||||
|
||||
int32_t first_cue_within (samplepos_t s, samplepos_t e, bool& was_recorded);
|
||||
void cue_bang (int32_t);
|
||||
CueEvents const & cue_events() const { return _cue_events; }
|
||||
|
||||
protected:
|
||||
friend class AudioEngine;
|
||||
@ -2311,21 +2312,12 @@ private:
|
||||
void setup_thread_local_variables ();
|
||||
void cue_marker_change (Location*);
|
||||
|
||||
struct CueEvent {
|
||||
int32_t cue;
|
||||
samplepos_t time;
|
||||
|
||||
CueEvent (int32_t c, samplepos_t t) : cue (c), time (t) {}
|
||||
};
|
||||
|
||||
struct CueEventTimeComparator {
|
||||
bool operator() (CueEvent const & c, samplepos_t s) {
|
||||
return c.time < s;
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::vector<CueEvent> CueEvents;
|
||||
|
||||
CueEvents _cue_events;
|
||||
void sync_cues ();
|
||||
void sync_cues_from_list (Locations::LocationList const &);
|
||||
|
@ -248,7 +248,10 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
|
||||
virtual void set_legato_offset (timepos_t const & offset) = 0;
|
||||
|
||||
virtual double position_as_fraction() const = 0;
|
||||
virtual void set_expected_end_sample (Temporal::TempoMap::SharedPtr const &, Temporal::BBT_Time const &, samplepos_t) = 0;
|
||||
|
||||
Temporal::BBT_Time compute_start (Temporal::TempoMap::SharedPtr const &, samplepos_t start, samplepos_t end, Temporal::BBT_Offset const & q, samplepos_t& start_samples, bool& will_start);
|
||||
virtual samplepos_t compute_end (Temporal::TempoMap::SharedPtr const &, Temporal::BBT_Time const &, samplepos_t) = 0;
|
||||
virtual void start_and_roll_to (samplepos_t position) = 0;
|
||||
|
||||
/* because follow actions involve probability is it easier to code the will-not-follow case */
|
||||
|
||||
@ -284,10 +287,15 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
||||
void maybe_compute_next_transition (samplepos_t start_sample, Temporal::Beats const & start, Temporal::Beats const & end, pframes_t& nframes, pframes_t& dest_offset);
|
||||
|
||||
|
||||
bool compute_quantized_transition (samplepos_t start_sample, Temporal::Beats const & start, Temporal::Beats const & end,
|
||||
Temporal::BBT_Time& t_bbt, Temporal::Beats& t_beats, samplepos_t& t_samples,
|
||||
Temporal::TempoMap::SharedPtr const & tmap, Temporal::BBT_Offset const & q);
|
||||
|
||||
pframes_t compute_next_transition (samplepos_t start_sample, Temporal::Beats const & start, Temporal::Beats const & end, pframes_t nframes,
|
||||
Temporal::BBT_Time& t_bbt, Temporal::timepos_t& t_time,
|
||||
Temporal::Beats& t_beats, samplepos_t& t_samples,
|
||||
Temporal::TempoMap::SharedPtr& tmap);
|
||||
Temporal::BBT_Time& t_bbt, Temporal::Beats& t_beats, samplepos_t& t_samples,
|
||||
Temporal::TempoMap::SharedPtr const & tmap);
|
||||
|
||||
void set_next_trigger (int n);
|
||||
int next_trigger() const { return _next_trigger; }
|
||||
@ -389,7 +397,13 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
|
||||
AudioTrigger (uint32_t index, TriggerBox&);
|
||||
~AudioTrigger ();
|
||||
|
||||
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);
|
||||
template<bool actually_run> pframes_t audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample,
|
||||
Temporal::Beats const & start, Temporal::Beats const & end,
|
||||
pframes_t nframes, pframes_t dest_offset, double bpm);
|
||||
|
||||
pframes_t run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, Temporal::Beats const & start, Temporal::Beats const & end, pframes_t nframes, pframes_t dest_offset, double bpm) {
|
||||
return audio_run<true> (bufs, start_sample, end_sample, start, end, nframes, dest_offset, bpm);
|
||||
}
|
||||
|
||||
StretchMode stretch_mode() const { return _stretch_mode; }
|
||||
void set_stretch_mode (StretchMode);
|
||||
@ -424,7 +438,8 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
|
||||
RubberBand::RubberBandStretcher* stretcher() { return (_stretcher); }
|
||||
|
||||
SegmentDescriptor get_segment_descriptor () const;
|
||||
void set_expected_end_sample (Temporal::TempoMap::SharedPtr const &, Temporal::BBT_Time const &, samplepos_t);
|
||||
samplepos_t compute_end (Temporal::TempoMap::SharedPtr const &, Temporal::BBT_Time const &, samplepos_t);
|
||||
void start_and_roll_to (samplepos_t position);
|
||||
|
||||
bool stretching () const;
|
||||
|
||||
@ -468,7 +483,12 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
|
||||
MIDITrigger (uint32_t index, TriggerBox&);
|
||||
~MIDITrigger ();
|
||||
|
||||
pframes_t run (BufferSet&, samplepos_t start_sample, samplepos_t end_sample, Temporal::Beats const & start_beats, Temporal::Beats const & end_beats, pframes_t nframes, pframes_t offset, double bpm);
|
||||
template<bool actually_run> pframes_t midi_run (BufferSet&, samplepos_t start_sample, samplepos_t end_sample,
|
||||
Temporal::Beats const & start_beats, Temporal::Beats const & end_beats, pframes_t nframes, pframes_t offset, double bpm);
|
||||
|
||||
pframes_t run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, Temporal::Beats const & start, Temporal::Beats const & end, pframes_t nframes, pframes_t dest_offset, double bpm) {
|
||||
return midi_run<true> (bufs, start_sample, end_sample, start, end, nframes, dest_offset, bpm);
|
||||
}
|
||||
|
||||
void set_start (timepos_t const &);
|
||||
void set_end (timepos_t const &);
|
||||
@ -493,7 +513,8 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
||||
SegmentDescriptor get_segment_descriptor () const;
|
||||
void set_expected_end_sample (Temporal::TempoMap::SharedPtr const &, Temporal::BBT_Time const &, samplepos_t);
|
||||
samplepos_t compute_end (Temporal::TempoMap::SharedPtr const &, Temporal::BBT_Time const &, samplepos_t);
|
||||
void start_and_roll_to (samplepos_t position);
|
||||
|
||||
void set_patch_change (Evoral::PatchChange<MidiBuffer::TimeType> const &);
|
||||
Evoral::PatchChange<MidiBuffer::TimeType> const & patch_change (uint8_t) const;
|
||||
@ -623,6 +644,8 @@ class LIBARDOUR_API TriggerBox : public Processor
|
||||
bool unbang_trigger (TriggerPtr);
|
||||
void add_trigger (TriggerPtr);
|
||||
|
||||
void fast_forward (CueEvents const &, samplepos_t transport_postiion);
|
||||
|
||||
void set_pending (uint32_t slot, Trigger*);
|
||||
|
||||
XMLNode& get_state (void);
|
||||
@ -665,6 +688,9 @@ class LIBARDOUR_API TriggerBox : public Processor
|
||||
void request_reload (int32_t slot, void*);
|
||||
void set_region (uint32_t slot, boost::shared_ptr<Region> region);
|
||||
|
||||
void non_realtime_transport_stop (samplepos_t now, bool flush);
|
||||
void non_realtime_locate (samplepos_t now);
|
||||
|
||||
void enqueue_trigger_source (PBD::ID queued);
|
||||
|
||||
/* valid only within the ::run() call tree */
|
||||
|
@ -875,6 +875,14 @@ struct FollowAction {
|
||||
std::string to_string() const;
|
||||
};
|
||||
|
||||
struct CueEvent {
|
||||
int32_t cue;
|
||||
samplepos_t time;
|
||||
|
||||
CueEvent (int32_t c, samplepos_t t) : cue (c), time (t) {}
|
||||
};
|
||||
|
||||
typedef std::vector<CueEvent> CueEvents;
|
||||
|
||||
} // namespace ARDOUR
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/make_shared.hpp>
|
||||
@ -447,6 +448,7 @@ void
|
||||
Trigger::set_region_internal (boost::shared_ptr<Region> r)
|
||||
{
|
||||
_region = r;
|
||||
cerr << index() << " aka " << this << " region set to " << r << endl;
|
||||
}
|
||||
|
||||
void
|
||||
@ -628,11 +630,71 @@ Trigger::process_state_requests (BufferSet& bufs, pframes_t dest_offset)
|
||||
}
|
||||
}
|
||||
|
||||
Temporal::BBT_Time
|
||||
Trigger::compute_start (Temporal::TempoMap::SharedPtr const & tmap, samplepos_t start, samplepos_t end, Temporal::BBT_Offset const & q, samplepos_t& start_samples, bool& will_start)
|
||||
{
|
||||
Temporal::Beats start_beats (tmap->quarters_at (timepos_t (start)));
|
||||
Temporal::Beats end_beats (tmap->quarters_at (timepos_t (end)));
|
||||
|
||||
Temporal::BBT_Time t_bbt;
|
||||
Temporal::Beats t_beats;
|
||||
|
||||
if (!compute_quantized_transition (start, start_beats, end_beats, t_bbt, t_beats, start_samples, tmap, q)) {
|
||||
will_start = false;
|
||||
return Temporal::BBT_Time ();
|
||||
}
|
||||
|
||||
will_start = true;
|
||||
return t_bbt;
|
||||
}
|
||||
|
||||
bool
|
||||
Trigger::compute_quantized_transition (samplepos_t start_sample, Temporal::Beats const & start_beats, Temporal::Beats const & end_beats,
|
||||
Temporal::BBT_Time& t_bbt, Temporal::Beats& t_beats, samplepos_t& t_samples,
|
||||
Temporal::TempoMap::SharedPtr const & tmap, Temporal::BBT_Offset const & q)
|
||||
{
|
||||
/* XXX need to use global grid here is quantization == zero */
|
||||
|
||||
/* Given the value of @param start, determine, based on the
|
||||
* quantization, the next time for a transition.
|
||||
*/
|
||||
|
||||
if (q < Temporal::BBT_Offset (0, 0, 0)) {
|
||||
/* negative quantization == do not quantize */
|
||||
|
||||
t_samples = start_sample;
|
||||
t_beats = start_beats;
|
||||
t_bbt = tmap->bbt_at (t_beats);
|
||||
} else if (q.bars == 0) {
|
||||
t_beats = start_beats.round_up_to_multiple (Temporal::Beats (q.beats, q.ticks));
|
||||
t_bbt = tmap->bbt_at (t_beats);
|
||||
t_samples = tmap->sample_at (t_beats);
|
||||
} else {
|
||||
t_bbt = tmap->bbt_at (timepos_t (start_beats));
|
||||
t_bbt = t_bbt.round_up_to_bar ();
|
||||
/* bars are 1-based; 'every 4 bars' means 'on bar 1, 5, 9, ...' */
|
||||
t_bbt.bars = 1 + ((t_bbt.bars-1) / q.bars * q.bars);
|
||||
t_beats = tmap->quarters_at (t_bbt);
|
||||
t_samples = tmap->sample_at (t_bbt);
|
||||
}
|
||||
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 quantized with %5 transition at %2, sb %3 eb %4\n", index(), t_samples, start_beats, end_beats, q));
|
||||
|
||||
/* See if this time falls within the range of time given to us */
|
||||
|
||||
if (t_beats < start_beats || t_beats > end_beats) {
|
||||
/* transition time not reached */
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pframes_t
|
||||
Trigger::compute_next_transition (samplepos_t start_sample, Temporal::Beats const & start, Temporal::Beats const & end, pframes_t nframes,
|
||||
Temporal::BBT_Time& t_bbt, Temporal::timepos_t& t_time,
|
||||
Temporal::Beats& t_beats, samplepos_t& t_samples,
|
||||
Temporal::TempoMap::SharedPtr& tmap)
|
||||
Temporal::BBT_Time& t_bbt, Temporal::Beats& t_beats, samplepos_t& t_samples,
|
||||
Temporal::TempoMap::SharedPtr const & tmap)
|
||||
{
|
||||
using namespace Temporal;
|
||||
|
||||
@ -651,43 +713,11 @@ Trigger::compute_next_transition (samplepos_t start_sample, Temporal::Beats cons
|
||||
q = BBT_Offset(1,0,0);
|
||||
}
|
||||
|
||||
/* XXX need to use global grid here is quantization == zero */
|
||||
|
||||
/* Given the value of @param start, determine, based on the
|
||||
* quantization, the next time for a transition.
|
||||
*/
|
||||
|
||||
if (q < Temporal::BBT_Offset (0, 0, 0)) {
|
||||
/* negative quantization == do not quantize */
|
||||
|
||||
t_samples = start_sample;
|
||||
t_beats = start;
|
||||
t_time = timepos_t (start);
|
||||
t_bbt = tmap->bbt_at (t_beats);
|
||||
} else if (q.bars == 0) {
|
||||
Temporal::Beats t_beats = start.round_up_to_multiple (Temporal::Beats (q.beats, q.ticks));
|
||||
t_bbt = tmap->bbt_at (t_beats);
|
||||
t_time = timepos_t (t_beats);
|
||||
} else {
|
||||
t_bbt = tmap->bbt_at (timepos_t (start));
|
||||
t_bbt = t_bbt.round_up_to_bar ();
|
||||
/* bars are 1-based; 'every 4 bars' means 'on bar 1, 5, 9, ...' */
|
||||
t_bbt.bars = 1 + ((t_bbt.bars-1) / q.bars * q.bars);
|
||||
t_time = timepos_t (tmap->quarters_at (t_bbt));
|
||||
}
|
||||
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 quantized with %5 transition at %2, sb %3 eb %4\n", index(), t_time.beats(), start, end, q));
|
||||
|
||||
/* See if this time falls within the range of time given to us */
|
||||
|
||||
if (t_time.beats() < start || t_time > end) {
|
||||
/* transition time not reached */
|
||||
if (!compute_quantized_transition (start_sample, start, end, t_bbt, t_beats, t_samples, tmap, q)) {
|
||||
/* no transition */
|
||||
return 0;
|
||||
}
|
||||
|
||||
t_samples = t_time.samples();
|
||||
t_beats = t_time.beats ();
|
||||
|
||||
switch (_state) {
|
||||
case WaitingToStop:
|
||||
nframes = t_samples - start_sample;
|
||||
@ -724,11 +754,10 @@ Trigger::maybe_compute_next_transition (samplepos_t start_sample, Temporal::Beat
|
||||
return;
|
||||
}
|
||||
|
||||
timepos_t transition_time (BeatTime);
|
||||
Temporal::BBT_Time transition_bbt;
|
||||
TempoMap::SharedPtr tmap (TempoMap::use());
|
||||
|
||||
if (!compute_next_transition (start_sample, start, end, nframes, transition_bbt, transition_time, transition_beats, transition_samples, tmap)) {
|
||||
if (!compute_next_transition (start_sample, start, end, nframes, transition_bbt, transition_beats, transition_samples, tmap)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -753,7 +782,7 @@ Trigger::maybe_compute_next_transition (samplepos_t start_sample, Temporal::Beat
|
||||
|
||||
nframes = transition_samples - start_sample;
|
||||
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 will stop somewhere in the middle of run(), specifically at %2 (%3) vs expected end at %4\n", name(), transition_time, transition_time.beats(), expected_end_sample));
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 will stop somewhere in the middle of run(), specifically at %2 (%3) vs expected end at %4\n", name(), transition_beats, expected_end_sample));
|
||||
|
||||
/* offset within the buffer(s) for output remains
|
||||
unchanged, since we will write from the first
|
||||
@ -764,7 +793,7 @@ Trigger::maybe_compute_next_transition (samplepos_t start_sample, Temporal::Beat
|
||||
case WaitingToStart:
|
||||
retrigger ();
|
||||
_state = Running;
|
||||
set_expected_end_sample (tmap, transition_bbt, transition_samples);
|
||||
compute_end (tmap, transition_bbt, transition_samples);
|
||||
PropertyChanged (ARDOUR::Properties::running);
|
||||
|
||||
/* trigger will start somewhere within this process
|
||||
@ -783,7 +812,7 @@ Trigger::maybe_compute_next_transition (samplepos_t start_sample, Temporal::Beat
|
||||
case WaitingForRetrigger:
|
||||
retrigger ();
|
||||
_state = Running;
|
||||
set_expected_end_sample (tmap, transition_bbt, transition_samples);
|
||||
compute_end (tmap, transition_bbt, transition_samples);
|
||||
PropertyChanged (ARDOUR::Properties::running);
|
||||
|
||||
/* trigger is just running normally, and will fill
|
||||
@ -1015,7 +1044,12 @@ AudioTrigger::current_pos() const
|
||||
}
|
||||
|
||||
void
|
||||
AudioTrigger::set_expected_end_sample (Temporal::TempoMap::SharedPtr const & tmap, Temporal::BBT_Time const & transition_bbt, samplepos_t transition_sample)
|
||||
AudioTrigger::start_and_roll_to (samplepos_t position)
|
||||
{
|
||||
}
|
||||
|
||||
samplepos_t
|
||||
AudioTrigger::compute_end (Temporal::TempoMap::SharedPtr const & tmap, Temporal::BBT_Time const & transition_bbt, samplepos_t transition_sample)
|
||||
{
|
||||
/* Our task here is to set:
|
||||
|
||||
@ -1067,7 +1101,7 @@ AudioTrigger::set_expected_end_sample (Temporal::TempoMap::SharedPtr const & tma
|
||||
usable_length = (data.length - _start_offset);
|
||||
}
|
||||
|
||||
/* called from set_expected_end_sample() 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
|
||||
* define the last_readable_sample we can use as data.
|
||||
*/
|
||||
@ -1092,6 +1126,8 @@ AudioTrigger::set_expected_end_sample (Temporal::TempoMap::SharedPtr const & tma
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
return expected_end_sample;
|
||||
}
|
||||
|
||||
void
|
||||
@ -1409,16 +1445,18 @@ AudioTrigger::retrigger ()
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 retriggered to %2\n", _index, read_index));
|
||||
}
|
||||
|
||||
template<bool actually_run>
|
||||
pframes_t
|
||||
AudioTrigger::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample,
|
||||
Temporal::Beats const & start, Temporal::Beats const & end,
|
||||
pframes_t nframes, pframes_t dest_offset, double bpm)
|
||||
AudioTrigger::audio_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample,
|
||||
Temporal::Beats const & start, Temporal::Beats const & end,
|
||||
pframes_t nframes, pframes_t dest_offset, double bpm)
|
||||
{
|
||||
boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>(_region);
|
||||
/* We do not modify the I/O of our parent route, so we process only min (bufs.n_audio(),region.channels()) */
|
||||
const uint32_t nchans = std::min (bufs.count().n_audio(), ar->n_channels());
|
||||
int avail = 0;
|
||||
BufferSet& scratch (_box.session().get_scratch_buffers (ChanCount (DataType::AUDIO, nchans)));
|
||||
BufferSet* scratch;
|
||||
std::unique_ptr<BufferSet> scratchp;
|
||||
std::vector<Sample*> bufp(nchans);
|
||||
const bool do_stretch = stretching();
|
||||
|
||||
@ -1447,8 +1485,19 @@ AudioTrigger::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sa
|
||||
* purpose, we use a generic variable name ('bufp') to refer to them.
|
||||
*/
|
||||
|
||||
for (uint32_t chn = 0; chn < bufs.count().n_audio(); ++chn) {
|
||||
bufp[chn] = scratch.get_audio (chn).data();
|
||||
if (actually_run) {
|
||||
scratch = &(_box.session().get_scratch_buffers (ChanCount (DataType::AUDIO, nchans)));
|
||||
|
||||
for (uint32_t chn = 0; chn < bufs.count().n_audio(); ++chn) {
|
||||
bufp[chn] = scratch->get_audio (chn).data();
|
||||
}
|
||||
} else {
|
||||
scratchp.reset (new BufferSet ());
|
||||
scratchp->ensure_buffers (DataType::AUDIO, nchans, nframes);
|
||||
/* have to set up scratch as a raw ptr so that the actually_run
|
||||
and !actually_run case can use the same code syntax
|
||||
*/
|
||||
scratch = scratchp.get();
|
||||
}
|
||||
|
||||
/* tell the stretcher what we are doing for this ::run() call */
|
||||
@ -1488,11 +1537,9 @@ AudioTrigger::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sa
|
||||
}
|
||||
|
||||
while (to_pad > 0) {
|
||||
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 (samplecnt_t n = 0; n < limit; ++n) {
|
||||
bufp[chn][n] = 0.f;
|
||||
}
|
||||
memset (bufp[chn], 0, sizeof (Sample) * limit);
|
||||
}
|
||||
|
||||
_stretcher->process (&bufp[0], limit, false);
|
||||
@ -1537,7 +1584,7 @@ AudioTrigger::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sa
|
||||
avail = _stretcher->available ();
|
||||
|
||||
if (to_drop && avail) {
|
||||
samplecnt_t this_drop = std::min (std::min ((samplecnt_t) avail, to_drop), (samplecnt_t) scratch.get_audio (0).capacity());
|
||||
samplecnt_t 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 ();
|
||||
@ -1605,18 +1652,21 @@ AudioTrigger::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sa
|
||||
|
||||
/* deliver to buffers */
|
||||
|
||||
for (uint32_t chn = 0; chn < bufs.count().n_audio(); ++chn) {
|
||||
if (actually_run) { /* constexpr, will be handled at compile time */
|
||||
|
||||
uint32_t channel = chn % data.size();
|
||||
AudioBuffer& buf (bufs.get_audio (chn));
|
||||
Sample* src = do_stretch ? bufp[channel] : (data[channel] + read_index);
|
||||
for (uint32_t chn = 0; chn < bufs.count().n_audio(); ++chn) {
|
||||
|
||||
gain_t gain = _velocity_gain * _gain; //incorporate the gain from velocity_effect
|
||||
uint32_t channel = chn % data.size();
|
||||
AudioBuffer& buf (bufs.get_audio (chn));
|
||||
Sample* src = do_stretch ? bufp[channel] : (data[channel] + read_index);
|
||||
|
||||
if (gain != 1.0f) {
|
||||
buf.accumulate_with_gain_from (src, from_stretcher, gain, dest_offset);
|
||||
} else {
|
||||
buf.accumulate_from (src, from_stretcher, dest_offset);
|
||||
gain_t gain = _velocity_gain * _gain; //incorporate the gain from velocity_effect
|
||||
|
||||
if (gain != 1.0f) {
|
||||
buf.accumulate_with_gain_from (src, from_stretcher, gain, dest_offset);
|
||||
} else {
|
||||
buf.accumulate_from (src, from_stretcher, dest_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1812,7 +1862,12 @@ MIDITrigger::probably_oneshot () const
|
||||
}
|
||||
|
||||
void
|
||||
MIDITrigger::set_expected_end_sample (Temporal::TempoMap::SharedPtr const & tmap, Temporal::BBT_Time const & transition_bbt, samplepos_t)
|
||||
MIDITrigger::start_and_roll_to (samplepos_t position)
|
||||
{
|
||||
}
|
||||
|
||||
samplepos_t
|
||||
MIDITrigger::compute_end (Temporal::TempoMap::SharedPtr const & tmap, Temporal::BBT_Time const & transition_bbt, samplepos_t)
|
||||
{
|
||||
Temporal::Beats end_by_follow_length = tmap->quarters_at (tmap->bbt_walk (transition_bbt, _follow_length));
|
||||
Temporal::Beats end_by_data_length = transition_beats + data_length;
|
||||
@ -1844,6 +1899,8 @@ MIDITrigger::set_expected_end_sample (Temporal::TempoMap::SharedPtr const & tmap
|
||||
timecnt_t len (Temporal::Beats (q.beats, q.ticks), timepos_t (Temporal::Beats()));
|
||||
final_beat = len.beats ();
|
||||
}
|
||||
/* XXX FIX ME */
|
||||
return 0;
|
||||
}
|
||||
|
||||
SegmentDescriptor
|
||||
@ -2120,12 +2177,13 @@ MIDITrigger::reload (BufferSet&, void*)
|
||||
{
|
||||
}
|
||||
|
||||
template<bool actually_run>
|
||||
pframes_t
|
||||
MIDITrigger::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample,
|
||||
Temporal::Beats const & start_beats, Temporal::Beats const & end_beats,
|
||||
pframes_t nframes, pframes_t dest_offset, double bpm)
|
||||
MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample,
|
||||
Temporal::Beats const & start_beats, Temporal::Beats const & end_beats,
|
||||
pframes_t nframes, pframes_t dest_offset, double bpm)
|
||||
{
|
||||
MidiBuffer& mb (bufs.get_midi (0));
|
||||
MidiBuffer* mb (actually_run? &bufs.get_midi (0) : 0);
|
||||
typedef Evoral::Event<MidiModel::TimeType> MidiEvent;
|
||||
const timepos_t region_start_time = _region->start();
|
||||
const Temporal::Beats region_start = region_start_time.beats();
|
||||
@ -2156,7 +2214,6 @@ MIDITrigger::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sam
|
||||
|
||||
MidiEvent const & event (*iter);
|
||||
|
||||
|
||||
/* Event times are in beats, relative to start of source
|
||||
* file. We need to convert to region-relative time, and then
|
||||
* a session timeline time, which is defined by the time at
|
||||
@ -2182,27 +2239,26 @@ MIDITrigger::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sam
|
||||
break;
|
||||
}
|
||||
|
||||
/* Now we have to convert to a position within the buffer we
|
||||
* are writing to.
|
||||
*
|
||||
* start_sample has already been been adjusted to reflect a
|
||||
* previous Trigger's processing during this run cycle, so we
|
||||
* can ignore dest_offset (which is necessary for audio
|
||||
* triggers where the data is a continuous data stream, but not
|
||||
* required here).
|
||||
*/
|
||||
if (actually_run) { /* compile-time const expr */
|
||||
|
||||
samplepos_t buffer_samples = timeline_samples - start_sample;
|
||||
/* Now we have to convert to a position within the buffer we
|
||||
* are writing to.
|
||||
*
|
||||
* start_sample has already been been adjusted to reflect a
|
||||
* previous Trigger's processing during this run cycle, so we
|
||||
* can ignore dest_offset (which is necessary for audio
|
||||
* triggers where the data is a continuous data stream, but not
|
||||
* required here).
|
||||
*/
|
||||
|
||||
Evoral::Event<MidiBuffer::TimeType> ev (Evoral::MIDI_EVENT, buffer_samples, event.size(), const_cast<uint8_t*>(event.buffer()), false);
|
||||
samplepos_t buffer_samples = timeline_samples - start_sample;
|
||||
|
||||
if (_gain != 1.0f && ev.is_note()) {
|
||||
ev.scale_velocity (_gain);
|
||||
}
|
||||
Evoral::Event<MidiBuffer::TimeType> ev (Evoral::MIDI_EVENT, buffer_samples, event.size(), const_cast<uint8_t*>(event.buffer()), false);
|
||||
|
||||
if (_gain != 1.0f && ev.is_note()) {
|
||||
ev.scale_velocity (_gain);
|
||||
}
|
||||
|
||||
if (_channel_map[ev.channel()] > 0) {
|
||||
ev.set_channel (_channel_map[ev.channel()]);
|
||||
}
|
||||
|
||||
if (ev.is_pgm_change() || (ev.is_cc() && ((ev.cc_number() == MIDI_CTL_LSB_BANK) || (ev.cc_number() == MIDI_CTL_MSB_BANK)))) {
|
||||
if (_patch_change[ev.channel()].is_set() || _box.ignore_patch_changes ()) {
|
||||
@ -2210,10 +2266,14 @@ MIDITrigger::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sam
|
||||
++iter;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("given et %1 TS %7 rs %8 ts %2 bs %3 ss %4 do %5, inserting %6\n", maybe_last_event_timeline_beats, timeline_samples, buffer_samples, start_sample, dest_offset, ev, transition_beats, region_start));
|
||||
mb.insert_event (ev);
|
||||
if (_channel_map[ev.channel()] > 0) {
|
||||
ev.set_channel (_channel_map[ev.channel()]);
|
||||
}
|
||||
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("given et %1 TS %7 rs %8 ts %2 bs %3 ss %4 do %5, inserting %6\n", maybe_last_event_timeline_beats, timeline_samples, buffer_samples, start_sample, dest_offset, ev, transition_beats, region_start));
|
||||
mb->insert_event (ev);
|
||||
}
|
||||
|
||||
_box.tracker->track (event.buffer());
|
||||
|
||||
@ -2225,9 +2285,9 @@ MIDITrigger::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sam
|
||||
}
|
||||
|
||||
|
||||
if (_state == Stopping) {
|
||||
if (actually_run && _state == Stopping) { /* first clause is a compile-time constexpr */
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 was stopping, now stopped\n", index()));
|
||||
_box.tracker->resolve_notes (mb, nframes-1);
|
||||
_box.tracker->resolve_notes (*mb, nframes-1);
|
||||
}
|
||||
|
||||
if (iter == model->end()) {
|
||||
@ -2405,6 +2465,138 @@ TriggerBox::set_ignore_patch_changes (bool yn)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TriggerBox::fast_forward (CueEvents const & cues, samplepos_t transport_position)
|
||||
{
|
||||
if (cues.empty() || cues.front().time > transport_position) {
|
||||
std::cerr << "no cues before " << transport_position << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
using namespace Temporal;
|
||||
TempoMap::SharedPtr tmap (TempoMap::use());
|
||||
|
||||
CueEvents::const_iterator c = cues.begin();
|
||||
samplepos_t pos = c->time;
|
||||
TriggerPtr prev;
|
||||
Temporal::BBT_Time start_bbt;
|
||||
samplepos_t start_samples;
|
||||
|
||||
while (pos < transport_position && c != cues.end() && c->time < transport_position) {
|
||||
|
||||
CueEvents::const_iterator nxt_cue = c; ++nxt_cue;
|
||||
|
||||
cerr << "Current cue: " << (char) ('A' + c->cue) << endl;
|
||||
|
||||
TriggerPtr trig (all_triggers[c->cue]);
|
||||
|
||||
if (!trig->region() || trig->cue_isolated()) {
|
||||
cerr << "trig " << trig << ' ' << trig->index() << " ignored, region : " << trig->region() << " iso " << trig->cue_isolated() << endl;
|
||||
c = nxt_cue;
|
||||
continue;
|
||||
}
|
||||
|
||||
samplepos_t limit;
|
||||
|
||||
if (nxt_cue == cues.end()) {
|
||||
limit = transport_position;
|
||||
cerr << "limit is trans. pos\n";
|
||||
} else {
|
||||
limit = nxt_cue->time;
|
||||
cerr << "limit is next cue at " << nxt_cue->time << endl;
|
||||
}
|
||||
|
||||
bool will_start = true;
|
||||
|
||||
start_bbt = trig->compute_start (tmap, pos, limit, trig->quantization(), start_samples, will_start);
|
||||
|
||||
if (!will_start) {
|
||||
/* trigger will not start between this cue and the next */
|
||||
cerr << "trigger " << trig->index() << " will not start before " << limit << endl;
|
||||
c = nxt_cue;
|
||||
pos = limit;
|
||||
continue;
|
||||
}
|
||||
cerr << "trig " << trig->index() << " starts at " << start_bbt << endl;
|
||||
|
||||
/* XXX need to determine when the trigger will actually start
|
||||
* (due to its quantization)
|
||||
*/
|
||||
|
||||
/* we now consider this trigger to be running. Let's see when
|
||||
* it ends...
|
||||
*/
|
||||
|
||||
samplepos_t trig_ends_at = trig->compute_end (tmap, start_bbt, start_samples);
|
||||
|
||||
cerr << "trig " << trig->index() << " ends at " << trig_ends_at << " vs. " << transport_position << " aka " << trig->transition_beats << endl;
|
||||
|
||||
if (nxt_cue != cues.end() && trig_ends_at >= nxt_cue->time) {
|
||||
/* trigger will be interrupted by next cue .
|
||||
*
|
||||
*/
|
||||
trig_ends_at = tmap->sample_at (tmap->bbt_at (timepos_t (nxt_cue->time)).round_up_to_bar ());
|
||||
std::cerr << "trig " << trig->index() << " will be interrupted by cue " << (char) ('A' + nxt_cue->cue) << " at " << trig_ends_at << " aka " << tmap->bbt_at (timepos_t (nxt_cue->time)).round_up_to_bar() << endl;
|
||||
}
|
||||
|
||||
if (trig_ends_at >= transport_position) {
|
||||
prev = trig;
|
||||
/* we're done. prev now indicates the trigger that
|
||||
would have started most recently before the
|
||||
transport position.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
cerr << "trigger ended at " << trig_ends_at << " get next\n";
|
||||
|
||||
int dnt = determine_next_trigger (trig->index());
|
||||
|
||||
if (dnt < 0) {
|
||||
/* no trigger follows the current one. Back to
|
||||
looking for another cue.
|
||||
*/
|
||||
cerr << "next trigger said none\n";
|
||||
c = nxt_cue;
|
||||
continue;
|
||||
}
|
||||
|
||||
cerr << "moving onto " << dnt << endl;
|
||||
|
||||
prev = trig;
|
||||
pos = trig_ends_at;
|
||||
}
|
||||
|
||||
cerr << "DONE. pos = " << pos << " prev " << prev << endl;
|
||||
|
||||
if (pos >= transport_position || !prev) {
|
||||
/* nothing to do */
|
||||
cerr << "No trigger active at " << transport_position << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
/* prev now points to a trigger that would start before
|
||||
* transport_position and would still be running at
|
||||
* transport_position. We need to run it in a special mode that ensures
|
||||
* that
|
||||
*
|
||||
* 1) for MIDI, we know the state at transport position
|
||||
* 2) for audio, the stretcher is in the correct state
|
||||
*/
|
||||
|
||||
cerr << "will fake-roll " << prev->index() << " to " << transport_position << endl;
|
||||
prev->start_and_roll_to (transport_position);
|
||||
|
||||
_currently_playing = prev;
|
||||
|
||||
/* currently playing is now ready to keep running at transport position
|
||||
*
|
||||
* Note that a MIDITrigger will have set a flag so that when we call
|
||||
* ::run() again, it will dump its current MIDI state before anything
|
||||
* else.
|
||||
*/
|
||||
}
|
||||
|
||||
void
|
||||
TriggerBox::set_region (uint32_t slot, boost::shared_ptr<Region> region)
|
||||
{
|
||||
@ -3557,6 +3749,18 @@ TriggerBox::position_as_fraction () const
|
||||
return cp->position_as_fraction ();
|
||||
}
|
||||
|
||||
void
|
||||
TriggerBox::non_realtime_transport_stop (samplepos_t now, bool /*flush*/)
|
||||
{
|
||||
fast_forward (_session.cue_events(), now);
|
||||
}
|
||||
|
||||
void
|
||||
TriggerBox::non_realtime_locate (samplepos_t now)
|
||||
{
|
||||
fast_forward (_session.cue_events(), now);
|
||||
}
|
||||
|
||||
/* Thread */
|
||||
|
||||
MultiAllocSingleReleasePool* TriggerBoxThread::Request::pool = 0;
|
||||
|
Loading…
Reference in New Issue
Block a user