13
0

RegionFX: include plugin tail with pre-fade Fx

This commit is contained in:
Robin Gareus 2024-08-14 04:13:25 +02:00
parent c2169d6d51
commit c16e31012b
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
7 changed files with 136 additions and 41 deletions

View File

@ -171,6 +171,8 @@ class LIBARDOUR_API AudioRegion : public Region, public AudioReadable
bool remove_plugin (std::shared_ptr<RegionFxPlugin>);
void reorder_plugins (RegionFxList const&);
timecnt_t tail () const;
/* automation */
std::shared_ptr<Evoral::Control>
@ -264,6 +266,7 @@ class LIBARDOUR_API AudioRegion : public Region, public AudioReadable
void apply_region_fx (BufferSet&, samplepos_t, samplepos_t, samplecnt_t);
void fx_latency_changed (bool no_emit);
void fx_tail_changed (bool no_emit);
void copy_plugin_state (std::shared_ptr<const AudioRegion>);
mutable samplepos_t _fx_pos;
@ -274,6 +277,7 @@ class LIBARDOUR_API AudioRegion : public Region, public AudioReadable
mutable BufferSet _readcache;
mutable samplepos_t _cache_start;
mutable samplepos_t _cache_end;
mutable samplecnt_t _cache_tail;
mutable std::atomic<bool> _invalidated;
protected:

View File

@ -402,7 +402,7 @@ protected:
void _set_sort_id ();
std::shared_ptr<RegionList> regions_touched_locked (timepos_t const & start, timepos_t const & end);
std::shared_ptr<RegionList> regions_touched_locked (timepos_t const & start, timepos_t const & end, bool with_tail);
void notify_region_removed (std::shared_ptr<Region>);
void notify_region_added (std::shared_ptr<Region>);

View File

@ -145,6 +145,8 @@ public:
timepos_t end() const;
timepos_t nt_last() const { return end().decrement(); }
virtual timecnt_t tail () const { return timecnt_t (0); }
timepos_t source_position () const;
timecnt_t source_relative_position (Temporal::timepos_t const &) const;
timecnt_t region_relative_position (Temporal::timepos_t const &) const;
@ -301,8 +303,8 @@ public:
* OverlapEnd: the range overlaps the end of this region.
* OverlapExternal: the range overlaps all of this region.
*/
Temporal::OverlapType coverage (timepos_t const & start, timepos_t const & end) const {
return Temporal::coverage_exclusive_ends (position(), nt_last(), start, end);
Temporal::OverlapType coverage (timepos_t const & start, timepos_t const & end, bool with_tail = false) const {
return Temporal::coverage_exclusive_ends (position(), with_tail ? nt_last() + tail() : nt_last(), start, end);
}
bool exact_equivalent (std::shared_ptr<const Region>) const;
@ -564,6 +566,7 @@ protected:
protected:
virtual bool _add_plugin (std::shared_ptr<RegionFxPlugin>, std::shared_ptr<RegionFxPlugin>, bool) { return false; }
virtual void fx_latency_changed (bool no_emit);
virtual void fx_tail_changed (bool no_emit);
virtual void send_change (const PBD::PropertyChange&);
virtual int _set_state (const XMLNode&, int version, PBD::PropertyChange& what_changed, bool send_signal);
@ -584,6 +587,7 @@ protected:
mutable Glib::Threads::RWLock _fx_lock;
uint32_t _fx_latency;
uint32_t _fx_tail;
RegionFxList _plugins;
PBD::Property<bool> _sync_marked;

View File

@ -176,7 +176,7 @@ ARDOUR::timecnt_t
AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, timepos_t const & start, timecnt_t const & cnt, uint32_t chan_n)
{
DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Playlist %1 read @ %2 for %3, channel %4, regions %5 mixdown @ %6 gain @ %7\n",
name(), start, cnt, chan_n, regions.size(), mixdown_buffer, gain_buffer));
name(), start.samples(), cnt.samples(), chan_n, regions.size(), mixdown_buffer, gain_buffer));
samplecnt_t const scnt (cnt.samples ());
@ -204,7 +204,7 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, ti
/* Find all the regions that are involved in the bit we are reading,
and sort them by descending layer and ascending position.
*/
std::shared_ptr<RegionList> all = regions_touched_locked (start, start + cnt);
std::shared_ptr<RegionList> all = regions_touched_locked (start, start + cnt, true);
all->sort (ReadSorter ());
/* This will be a list of the bits of our read range that we have
@ -236,7 +236,7 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, ti
*/
Temporal::Range rrange = ar->range_samples ();
Temporal::Range region_range (max (rrange.start(), start),
min (rrange.end(), start + cnt));
min (rrange.end() + ar->tail (), start + cnt));
/* ... and then remove the bits that are already done */
@ -256,9 +256,9 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, ti
/* Cut this range down to just the body and mark it done */
Temporal::Range body = ar->body_range ();
if (body.start() < d.end() && body.end() > d.start()) {
if (body.start() < d.end().earlier (ar->tail ()) && body.end() > d.start()) {
d.set_start (max (d.start(), body.start()));
d.set_end (min (d.end(), body.end()));
d.set_end (min (d.end().earlier (ar->tail ()), body.end()));
done.add (d);
}
}
@ -285,8 +285,13 @@ AudioPlaylist::read (Sample *buf, Sample *mixdown_buffer, float *gain_buffer, ti
assert (soffset + read_cnt <= scnt);
samplecnt_t nread = i->region->read_at (buf + soffset, mixdown_buffer, gain_buffer, read_pos, read_cnt, chan_n);
if (nread != read_cnt) {
std::cerr << name() << " tried to read " << read_cnt << " from " << nread << " in " << i->region->name() << " using range "
<< i->range.start() << " .. " << i->range.end() << " len " << i->range.length() << std::endl;
std::cerr << name() << " tried to read " << read_cnt
<< " got " << nread
<< " in " << i->region->name()
<< " for chn " << chan_n
<< " to offset " << soffset
<< " using range " << i->range.start().samples() << " .. " << i->range.end().samples()
<< " len " << i->range.length().samples() << std::endl;
#ifndef NDEBUG
/* forward error to DiskReader::audio_read. This does 2 things:
* - error "DiskReader %1: when refilling, cannot read ..."

View File

@ -254,6 +254,7 @@ AudioRegion::init ()
connect_to_header_position_offset_changed ();
_fx_pos = _cache_start = _cache_end = -1;
_cache_tail = 0;
_fx_block_size = 0;
_fx_latent_read = false;
}
@ -347,6 +348,7 @@ AudioRegion::AudioRegion (std::shared_ptr<const AudioRegion> other)
connect_to_header_position_offset_changed ();
_fx_pos = _cache_start = _cache_end = -1;
_cache_tail = 0;
_fx_block_size = 0;
_fx_latent_read = false;
@ -375,6 +377,7 @@ AudioRegion::AudioRegion (std::shared_ptr<const AudioRegion> other, timecnt_t co
connect_to_header_position_offset_changed ();
_fx_pos = _cache_start = _cache_end = -1;
_cache_tail = 0;
_fx_block_size = 0;
_fx_latent_read = false;
@ -401,6 +404,7 @@ AudioRegion::AudioRegion (std::shared_ptr<const AudioRegion> other, const Source
connect_to_header_position_offset_changed ();
_fx_pos = _cache_start = _cache_end = -1;
_cache_tail = 0;
_fx_block_size = 0;
_fx_latent_read = false;
@ -521,6 +525,16 @@ AudioRegion::set_fade_before_fx (bool yn)
}
}
timecnt_t
AudioRegion::tail () const
{
if (_fade_before_fx && has_region_fx ()) {
return timecnt_t (_session.sample_rate ()); // TODO use plugin API
} else {
return timecnt_t (0);
}
}
/** @param buf Buffer to put peak data in.
* @param npeaks Number of peaks to read (ie the number of PeakDatas in buf)
* @param offset Start position, as an offset from the start of this region's source.
@ -614,24 +628,33 @@ AudioRegion::read_at (Sample* buf,
/* WORK OUT WHERE TO GET DATA FROM */
samplecnt_t to_read;
const samplepos_t psamples = position().samples();
const samplecnt_t lsamples = _length.val().samples();
const samplecnt_t tsamples = tail ().samples ();
assert (pos >= psamples);
sampleoffset_t const internal_offset = pos - psamples;
sampleoffset_t internal_offset = pos - psamples;
sampleoffset_t suffix = 0;
if (internal_offset >= lsamples) {
if (internal_offset >= lsamples + tsamples) {
return 0; /* read nothing */
}
if (internal_offset > lsamples) {
suffix = internal_offset - lsamples;
internal_offset = lsamples;
}
const samplecnt_t esamples = lsamples - internal_offset;
assert (esamples >= 0);
if ((to_read = min (cnt, esamples)) == 0) {
if (min (cnt, esamples + tsamples) <= 0) {
return 0; /* read nothing */
}
/* does not include tail */
samplecnt_t const to_read = max<samplecnt_t> (0, min (cnt, esamples));
samplecnt_t const can_read = max<samplecnt_t> (0, min (cnt, esamples + tsamples));
/* COMPUTE DETAILS OF ANY FADES INVOLVED IN THIS READ
*
@ -699,16 +722,17 @@ AudioRegion::read_at (Sample* buf,
Glib::Threads::Mutex::Lock cl (_cache_lock);
if (chan_n == 0 && _invalidated.exchange (false)) {
_cache_start = _cache_end = -1;
_cache_tail = 0;
}
boost::scoped_array<gain_t> gain_array;
boost::scoped_array<Sample> mixdown_array;
// TODO optimize mono reader, w/o plugins -> old code
if (n_chn > 1 && _cache_start < _cache_end && internal_offset >= _cache_start && internal_offset + to_read <= _cache_end) {
DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Region '%1' channel: %2 copy from cache %3 - %4 to_read: %5\n",
name(), chan_n, internal_offset, internal_offset + to_read, to_read));
copy_vector (mixdown_buffer, _readcache.get_audio (chan_n).data (internal_offset - _cache_start), to_read);
if (n_chn > 1 && _cache_start < _cache_end && internal_offset + suffix >= _cache_start && internal_offset + suffix + can_read <= _cache_end) {
DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Region '%1' channel: %2 copy from cache %3 - %4 to_read: %5 can_read: %6\n",
name(), chan_n, internal_offset + suffix, internal_offset + suffix + can_read, to_read, can_read));
copy_vector (mixdown_buffer, _readcache.get_audio (chan_n).data (internal_offset + suffix - _cache_start), can_read);
cl.release ();
} else {
Glib::Threads::RWLock::ReaderLock lm (_fx_lock);
@ -716,24 +740,21 @@ AudioRegion::read_at (Sample* buf,
uint32_t fx_latency = _fx_latency;
lm.release ();
ChanCount cc (DataType::AUDIO, n_channels ());
_readcache.ensure_buffers (cc, to_read + _fx_latency);
samplecnt_t n_read = to_read; //< data to read from disk
samplecnt_t n_proc = to_read; //< silence pad data to process
samplepos_t n_tail = 0; // further silence pad, read tail from FX
samplepos_t readat = pos;
sampleoffset_t offset = internal_offset;
//printf ("READ Cache end %ld pos %ld\n", _cache_end, readat);
if (_cache_end != readat && fx_latency > 0) {
if (tsamples > 0 && cnt >= esamples) {
n_tail = can_read - n_read;
n_proc += n_tail;
}
if (_cache_end != internal_offset + suffix && fx_latency > 0) {
_fx_latent_read = true;
n_proc += fx_latency;
n_read = min (to_read + fx_latency, esamples);
mixdown_array.reset (new Sample[n_proc]);
mixdown_buffer = mixdown_array.get ();
gain_array.reset (new gain_t[n_proc]);
gain_buffer = gain_array.get ();
}
if (!_fx_latent_read && fx_latency > 0) {
@ -742,13 +763,20 @@ AudioRegion::read_at (Sample* buf,
n_read = max<samplecnt_t> (0, min (to_read, lsamples - offset));
}
DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Region '%1' channel: %2 read: %3 - %4 (%5) to_read: %6 offset: %7 with fx: %8 fx_latency: %9\n",
name(), chan_n, readat, readat + n_read, n_read, to_read, internal_offset, have_fx, fx_latency));
if (n_proc > to_read) {
mixdown_array.reset (new Sample[n_proc]);
mixdown_buffer = mixdown_array.get ();
gain_array.reset (new gain_t[n_proc]);
gain_buffer = gain_array.get ();
}
DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Region '%1' channel: %2 read: %3 - %4 (%5) to_read: %6 offset: %7 with fx: %8 fx_latency: %9 fx_tail %10\n",
name(), chan_n, readat, readat + n_read, n_read, to_read, internal_offset, have_fx, fx_latency, n_tail));
ChanCount cc (DataType::AUDIO, n_channels ());
_readcache.ensure_buffers (cc, n_proc);
if (n_read < n_proc) {
//printf ("SILENCE PAD rd: %ld proc: %ld\n", n_read, n_proc);
/* silence pad, process tail of latent effects */
memset (&mixdown_buffer[n_read], 0, sizeof (Sample)* (n_proc - n_read));
_readcache.silence (n_proc - n_read, n_read);
@ -756,6 +784,7 @@ AudioRegion::read_at (Sample* buf,
/* reset in case read fails we return early */
_cache_start = _cache_end = -1;
_cache_tail = 0;
for (uint32_t chn = 0; chn < n_chn; ++chn) {
/* READ DATA FROM THE SOURCE INTO mixdown_buffer.
@ -838,26 +867,27 @@ AudioRegion::read_at (Sample* buf,
/* apply region FX to all channels */
if (have_fx) {
const_cast<AudioRegion*>(this)->apply_region_fx (_readcache, offset, offset + n_proc, n_proc);
const_cast<AudioRegion*>(this)->apply_region_fx (_readcache, offset + suffix, offset + suffix + n_proc, n_proc);
}
/* for mono regions without plugins, mixdown_buffer is valid as-is */
if (n_chn > 1 || have_fx) {
/* copy data for current channel */
if (chan_n < n_channels()) {
copy_vector (mixdown_buffer, _readcache.get_audio (chan_n).data (), to_read);
copy_vector (mixdown_buffer, _readcache.get_audio (chan_n).data (), to_read + n_tail);
} else {
if (Config->get_replicate_missing_region_channels()) {
chan_n = chan_n % n_channels ();
copy_vector (mixdown_buffer, _readcache.get_audio (chan_n).data (), to_read);
copy_vector (mixdown_buffer, _readcache.get_audio (chan_n).data (), to_read + n_tail);
} else {
memset (mixdown_buffer, 0, sizeof (Sample) * to_read);
memset (mixdown_buffer, 0, sizeof (Sample) * (to_read + n_tail));
}
}
}
_cache_start = internal_offset;
_cache_end = internal_offset + to_read;
_cache_start = internal_offset + suffix;
_cache_end = internal_offset + suffix + to_read + n_tail;
_cache_tail = n_tail;
cl.release ();
}
@ -974,8 +1004,16 @@ AudioRegion::read_at (Sample* buf,
mix_buffers_no_gain (buf + fade_in_limit, mixdown_buffer + fade_in_limit, N);
}
}
samplecnt_t T = _cache_tail;
if (T > 0) {
T = min (T, can_read);
DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Region %1 adding FX tail of %2 cut to_read %3 at %4 total len = %5 cnt was %6\n",
name (), _cache_tail, T, to_read, to_read + T, cnt));
/* AudioPlaylist::read reads regions in reverse order, so we can add the tail here */
mix_buffers_no_gain (buf + to_read, mixdown_buffer + to_read, T);
}
return to_read;
return to_read + T;
}
/** Read data directly from one of our sources, accounting for the situation when the track has a different channel
@ -2445,6 +2483,7 @@ AudioRegion::_add_plugin (std::shared_ptr<RegionFxPlugin> rfx, std::shared_ptr<R
}
rfx->LatencyChanged.connect_same_thread (*this, boost::bind (&AudioRegion::fx_latency_changed, this, false));
rfx->plugin()->TailChanged.connect_same_thread (*this, boost::bind (&AudioRegion::fx_tail_changed, this, false));
rfx->set_block_size (_session.get_block_size ());
if (from_set_state) {
@ -2463,6 +2502,8 @@ AudioRegion::_add_plugin (std::shared_ptr<RegionFxPlugin> rfx, std::shared_ptr<R
rfx->set_default_automation (len_as_tpos ());
fx_latency_changed (true);
fx_tail_changed (true);
if (!_invalidated.exchange (true)) {
send_change (PropertyChange (Properties::region_fx)); // trigger DiskReader overwrite
}
@ -2484,6 +2525,7 @@ AudioRegion::remove_plugin (std::shared_ptr<RegionFxPlugin> fx)
fx->drop_references ();
fx_latency_changed (true);
fx_tail_changed (true);
if (!_invalidated.exchange (true)) {
send_change (PropertyChange (Properties::region_fx)); // trigger DiskReader overwrite
@ -2523,6 +2565,27 @@ AudioRegion::fx_latency_changed (bool no_emit)
}
}
void
AudioRegion::fx_tail_changed (bool no_emit)
{
uint32_t t = 0;
for (auto const& rfx : _plugins) {
t = max<uint32_t> (t, rfx->plugin()->effective_tail ());
}
if (t == _fx_tail) {
return;
}
_fx_tail = t;
if (no_emit) {
return;
}
if (!_invalidated.exchange (true)) {
send_change (PropertyChange (Properties::region_fx)); // trigger DiskReader overwrite
}
}
void
AudioRegion::apply_region_fx (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, samplecnt_t n_samples)
{

View File

@ -1989,16 +1989,16 @@ std::shared_ptr<RegionList>
Playlist::regions_touched (timepos_t const & start, timepos_t const & end)
{
RegionReadLock rlock (this);
return regions_touched_locked (start, end);
return regions_touched_locked (start, end, false);
}
std::shared_ptr<RegionList>
Playlist::regions_touched_locked (timepos_t const & start, timepos_t const & end)
Playlist::regions_touched_locked (timepos_t const & start, timepos_t const & end, bool with_tail)
{
std::shared_ptr<RegionList> rlist (new RegionList);
for (auto & r : regions) {
if (r->coverage (start, end) != Temporal::OverlapNone) {
if (r->coverage (start, end, with_tail) != Temporal::OverlapNone) {
rlist->push_back (r);
}
}

View File

@ -296,6 +296,7 @@ Region::Region (Session& s, timepos_t const & start, timecnt_t const & length, c
: SessionObject(s, name)
, _type (type)
, _fx_latency (0)
, _fx_tail (0)
, REGION_DEFAULT_STATE (start,length)
, _last_length (length)
, _first_edit (EditChangesNothing)
@ -312,6 +313,7 @@ Region::Region (const SourceList& srcs)
: SessionObject(srcs.front()->session(), "toBeRenamed")
, _type (srcs.front()->type())
, _fx_latency (0)
, _fx_tail (0)
, REGION_DEFAULT_STATE(_type == DataType::MIDI ? timepos_t (Temporal::Beats()) : timepos_t::from_superclock (0),
_type == DataType::MIDI ? timecnt_t (Temporal::Beats()) : timecnt_t::from_superclock (0))
, _last_length (_type == DataType::MIDI ? timecnt_t (Temporal::Beats()) : timecnt_t::from_superclock (0))
@ -332,6 +334,7 @@ Region::Region (std::shared_ptr<const Region> other)
: SessionObject(other->session(), other->name())
, _type (other->data_type())
, _fx_latency (0)
, _fx_tail (0)
, REGION_COPY_STATE (other)
, _last_length (other->_last_length)
, _first_edit (EditChangesNothing)
@ -391,6 +394,7 @@ Region::Region (std::shared_ptr<const Region> other, timecnt_t const & offset)
: SessionObject(other->session(), other->name())
, _type (other->data_type())
, _fx_latency (0)
, _fx_tail (0)
, REGION_COPY_STATE (other)
, _last_length (other->_last_length)
, _first_edit (EditChangesNothing)
@ -437,6 +441,7 @@ Region::Region (std::shared_ptr<const Region> other, const SourceList& srcs)
: SessionObject (other->session(), other->name())
, _type (srcs.front()->type())
, _fx_latency (0)
, _fx_tail (0)
, REGION_COPY_STATE (other)
, _last_length (other->_last_length)
, _first_edit (EditChangesID)
@ -1589,6 +1594,7 @@ Region::_set_state (const XMLNode& node, int version, PropertyChange& what_chang
}
if (changed) {
fx_latency_changed (true);
fx_tail_changed (true);
send_change (PropertyChange (Properties::region_fx)); // trigger DiskReader overwrite
RegionFxChanged (); /* EMIT SIGNAL */
}
@ -2449,3 +2455,16 @@ Region::fx_latency_changed (bool)
}
_fx_latency = l;
}
void
Region::fx_tail_changed (bool)
{
uint32_t t = 0;
for (auto const& rfx : _plugins) {
t = max<uint32_t> (t, rfx->plugin()->effective_tail ());
}
if (t == _fx_tail) {
return;
}
_fx_tail = t;
}