mirror of
https://github.com/Ardour/ardour.git
synced 2026-01-06 21:55:43 +01:00
triggerbox: fix stretching and redesign to try to prevent clicks when looping clips set to launch in "Loop" mode
This commit is contained in:
parent
d2b2b73c82
commit
7458f47bb7
2 changed files with 157 additions and 79 deletions
|
|
@ -95,8 +95,8 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
|
|||
size_t index() const { return _index; }
|
||||
|
||||
/* Managed by TriggerBox */
|
||||
samplepos_t fire_samples;
|
||||
Temporal::Beats fire_beats;
|
||||
samplepos_t bang_samples;
|
||||
Temporal::Beats bang_beats;
|
||||
|
||||
XMLNode& get_state (void);
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
|
@ -122,7 +122,7 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
|
|||
void bang (TriggerBox&);
|
||||
void unbang (TriggerBox&, Temporal::Beats const & , samplepos_t);
|
||||
|
||||
Sample* run (uint32_t channel, pframes_t& nframes, bool& need_butler);
|
||||
int run (AudioBuffer&, uint32_t channel, pframes_t nframes, pframes_t offset, bool first);
|
||||
|
||||
void set_length (timecnt_t const &);
|
||||
timecnt_t current_length() const;
|
||||
|
|
@ -165,6 +165,8 @@ class LIBARDOUR_API TriggerBox : public Processor
|
|||
|
||||
DataType data_type() const { return _data_type; }
|
||||
|
||||
void stop_all ();
|
||||
|
||||
private:
|
||||
PBD::RingBuffer<Trigger*> _bang_queue;
|
||||
PBD::RingBuffer<Trigger*> _unbang_queue;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include <iostream>
|
||||
|
||||
#include <glibmm.h>
|
||||
|
||||
#include <rubberband/RubberBandStretcher.h>
|
||||
|
||||
#include "pbd/basename.h"
|
||||
|
|
@ -97,6 +99,16 @@ TriggerBox::~TriggerBox ()
|
|||
drop_triggers ();
|
||||
}
|
||||
|
||||
void
|
||||
TriggerBox::stop_all ()
|
||||
{
|
||||
/* XXX needs to be done with mutex or via thread-safe queue */
|
||||
|
||||
for (Triggers::iterator t = active_triggers.begin(); t != active_triggers.end(); ++t) {
|
||||
(*t)->stop ();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TriggerBox::drop_triggers ()
|
||||
{
|
||||
|
|
@ -258,6 +270,10 @@ 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) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -298,8 +314,8 @@ TriggerBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp
|
|||
|
||||
} else if (fire_at >= start_beats && fire_at < end_beats) {
|
||||
|
||||
(*t)->fire_samples = fire_at.samples();
|
||||
(*t)->fire_beats = fire_at.beats();
|
||||
(*t)->bang_samples = fire_at.samples();
|
||||
(*t)->bang_beats = fire_at.beats();
|
||||
active_triggers.push_back (*t);
|
||||
t = pending_on_triggers.erase (t);
|
||||
|
||||
|
|
@ -320,16 +336,15 @@ TriggerBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp
|
|||
}
|
||||
|
||||
if (off_at >= start_beats && off_at < end_beats) {
|
||||
(*t)->fire_samples = off_at.samples();
|
||||
(*t)->fire_beats = off_at.beats();
|
||||
(*t)->unbang (*this, (*t)->fire_beats, (*t)->fire_samples);
|
||||
(*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;
|
||||
}
|
||||
}
|
||||
|
||||
bool need_butler = false;
|
||||
size_t max_chans = 0;
|
||||
|
||||
for (Triggers::iterator t = active_triggers.begin(); t != active_triggers.end(); ) {
|
||||
|
|
@ -346,56 +361,45 @@ TriggerBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp
|
|||
pframes_t trigger_samples = nframes;
|
||||
|
||||
if (trigger->stop_requested()) {
|
||||
trigger_samples = nframes - (trigger->fire_samples - start_sample);
|
||||
|
||||
/* bang_samples says when to stop, so compute an offset
|
||||
*into the nframes we've been asked to provide.
|
||||
*/
|
||||
|
||||
trigger_samples = nframes - (trigger->bang_samples - start_sample);
|
||||
|
||||
} else if (!trigger->running()) {
|
||||
|
||||
trigger->bang (*this);
|
||||
dest_offset = std::max (samplepos_t (0), trigger->fire_samples - start_sample);
|
||||
dest_offset = std::max (samplepos_t (0), trigger->bang_samples - start_sample);
|
||||
trigger_samples = nframes - dest_offset;
|
||||
}
|
||||
|
||||
AudioTrigger* at = dynamic_cast<AudioTrigger*> (trigger);
|
||||
bool err = false;
|
||||
|
||||
boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (r);
|
||||
const size_t nchans = ar->n_channels ();
|
||||
max_chans = std::max (max_chans, nchans);
|
||||
if (at) {
|
||||
boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (r);
|
||||
const bool first = (t == active_triggers.begin());
|
||||
const size_t nchans = ar->n_channels ();
|
||||
|
||||
bool at_end = false;
|
||||
for (uint32_t chan = 0; !at_end && chan < nchans; ++chan) {
|
||||
max_chans = std::max (max_chans, nchans);
|
||||
|
||||
AudioBuffer& buf = bufs.get_audio (chan);
|
||||
for (uint32_t chan = 0; chan < nchans; ++chan) {
|
||||
|
||||
pframes_t to_copy = trigger_samples;
|
||||
Sample* data = at->run (chan, to_copy, need_butler);
|
||||
|
||||
if (!data) {
|
||||
at_end = true;
|
||||
break;
|
||||
} else {
|
||||
if (t == active_triggers.begin()) {
|
||||
buf.read_from (data, to_copy, dest_offset);
|
||||
if ((to_copy + dest_offset) < nframes) {
|
||||
buf.silence (nframes - to_copy, to_copy + dest_offset);
|
||||
}
|
||||
} else {
|
||||
buf.accumulate_from (data, to_copy);
|
||||
if (at->run (bufs.get_audio (chan), chan, trigger_samples, dest_offset, first)) {
|
||||
err = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
/* XXX to be written */
|
||||
|
||||
}
|
||||
if (at_end) {
|
||||
|
||||
switch (trigger->launch_style()) {
|
||||
case Trigger::Loop:
|
||||
trigger->retrigger();
|
||||
++t;
|
||||
break;
|
||||
case Trigger::Gate:
|
||||
case Trigger::Toggle:
|
||||
case Trigger::Repeat:
|
||||
t = active_triggers.erase (t);
|
||||
break;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
t = active_triggers.erase (t);
|
||||
} else {
|
||||
++t;
|
||||
}
|
||||
|
|
@ -516,6 +520,8 @@ AudioTrigger::set_length (timecnt_t const & newlen)
|
|||
return;
|
||||
}
|
||||
|
||||
std::cerr << " sl to " << newlen << " from " << _region->length() << endl;
|
||||
|
||||
/* offline stretch */
|
||||
|
||||
/* study */
|
||||
|
|
@ -534,51 +540,90 @@ AudioTrigger::set_length (timecnt_t const & newlen)
|
|||
} 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<Sample*> 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* pdata[nchans];
|
||||
for (uint32_t n = 0; n < nchans; ++n) {
|
||||
pdata[n] = data[n];
|
||||
}
|
||||
Sample* raw[nchans];
|
||||
Sample* results[nchans];
|
||||
|
||||
/* study, then process */
|
||||
|
||||
stretcher.setMaxProcessSize (data_length);
|
||||
const samplecnt_t block_size = 8192;
|
||||
samplecnt_t read = 0;
|
||||
|
||||
stretcher.setDebugLevel (0);
|
||||
stretcher.setMaxProcessSize (block_size);
|
||||
stretcher.setExpectedInputDuration (data_length);
|
||||
stretcher.study (pdata, _region->length_samples(), true);
|
||||
stretcher.process (pdata, _region->length_samples(), true);
|
||||
|
||||
/* how many samples did we end up with? */
|
||||
while (read < data_length) {
|
||||
|
||||
samplecnt_t plen = stretcher.available();
|
||||
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 ();
|
||||
|
||||
for (uint32_t n = 0; n < nchans; ++n) {
|
||||
data.push_back (new Sample[plen]);
|
||||
}
|
||||
|
||||
/* reset pdata to point to newly allocated buffers */
|
||||
|
||||
for (uint32_t n = 0; n < nchans; ++n) {
|
||||
pdata[n] = data[n];
|
||||
}
|
||||
|
||||
/* fetch it all */
|
||||
|
||||
stretcher.retrieve (pdata, plen);
|
||||
|
||||
/* store length */
|
||||
|
||||
data_length = plen;
|
||||
|
||||
data = stretched;
|
||||
data_length = processed;
|
||||
}
|
||||
|
||||
timecnt_t
|
||||
|
|
@ -657,6 +702,8 @@ AudioTrigger::retrigger ()
|
|||
for (std::vector<samplecnt_t>::iterator ri = read_index.begin(); ri != read_index.end(); ++ri) {
|
||||
(*ri) = 0;
|
||||
}
|
||||
|
||||
_running = true;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -707,16 +754,16 @@ AudioTrigger::unbang (TriggerBox& /*proc*/, Temporal::Beats const &, samplepos_t
|
|||
}
|
||||
}
|
||||
|
||||
Sample*
|
||||
AudioTrigger::run (uint32_t channel, pframes_t& nframes, bool& /* need_butler */)
|
||||
int
|
||||
AudioTrigger::run (AudioBuffer& buf, uint32_t channel, pframes_t nframes, pframes_t dest_offset, bool first)
|
||||
{
|
||||
if (!_running) {
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (read_index[channel] >= data_length) {
|
||||
_running = false;
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_stop_requested) {
|
||||
|
|
@ -728,8 +775,37 @@ AudioTrigger::run (uint32_t channel, pframes_t& nframes, bool& /* need_butler */
|
|||
|
||||
channel %= data.size();
|
||||
|
||||
nframes = (pframes_t) std::min ((samplecnt_t) nframes, (data_length - read_index[channel]));
|
||||
read_index[channel] += nframes;
|
||||
read_more:
|
||||
pframes_t nf = (pframes_t) std::min ((samplecnt_t) nframes, (data_length - read_index[channel]));
|
||||
Sample* src = data[channel] + read_index[channel];
|
||||
|
||||
return data[channel] + read_index[channel];
|
||||
if (first) {
|
||||
buf.read_from (src, nf, dest_offset);
|
||||
if ((nf + dest_offset) < nframes) {
|
||||
buf.silence (nframes - nf, nf + dest_offset);
|
||||
}
|
||||
} else {
|
||||
buf.accumulate_from (src, nf);
|
||||
}
|
||||
|
||||
read_index[channel] += nf;
|
||||
|
||||
if (nf < nframes) {
|
||||
|
||||
nframes -= nf;
|
||||
|
||||
switch (launch_style()) {
|
||||
case Trigger::Loop:
|
||||
retrigger();
|
||||
goto read_more;
|
||||
break;
|
||||
case Trigger::Gate:
|
||||
case Trigger::Toggle:
|
||||
case Trigger::Repeat:
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue