From 6811f36f19c5a4179cbb7ae1711bca71a41d0a11 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Thu, 7 Mar 2024 12:14:34 +0100 Subject: [PATCH] Per [Audio] Region Fx * apply effects during region-read in non-rt context * Add multi-channel audioregion read cache to process stereo effects --- libs/ardour/ardour/audioregion.h | 34 ++- libs/ardour/ardour/region.h | 40 +++ libs/ardour/audioregion.cc | 411 +++++++++++++++++++++++++++++-- libs/ardour/disk_reader.cc | 32 ++- libs/ardour/globals.cc | 5 +- libs/ardour/luabindings.cc | 5 + libs/ardour/playlist.cc | 1 + libs/ardour/region.cc | 101 ++++++++ 8 files changed, 586 insertions(+), 43 deletions(-) diff --git a/libs/ardour/ardour/audioregion.h b/libs/ardour/ardour/audioregion.h index 0b8215bfa4..c8e447218b 100644 --- a/libs/ardour/ardour/audioregion.h +++ b/libs/ardour/ardour/audioregion.h @@ -23,6 +23,7 @@ #ifndef __ardour_audio_region_h__ #define __ardour_audio_region_h__ +#include #include #include @@ -32,6 +33,7 @@ #include "ardour/ardour.h" #include "ardour/automatable.h" #include "ardour/automation_list.h" +#include "ardour/buffer_set.h" #include "ardour/interthread_info.h" #include "ardour/logcurve.h" #include "ardour/region.h" @@ -60,7 +62,8 @@ class Playlist; class Session; class Filter; class AudioSource; - +class RegionFxPlugin; +class PlugInsertBase; class LIBARDOUR_API AudioRegion : public Region, public AudioReadable { @@ -116,10 +119,12 @@ class LIBARDOUR_API AudioRegion : public Region, public AudioReadable samplecnt_t readable_length_samples() const { return length_samples(); } uint32_t n_channels() const { return _sources.size(); } - samplecnt_t read_at (Sample *buf, Sample *mixdown_buf, float *gain_buf, - samplepos_t position, - samplecnt_t cnt, - uint32_t chan_n = 0) const; + samplecnt_t read_at (Sample* buf, + Sample* mixdown_buf, + gain_t* gain_buf, + samplepos_t position, + samplecnt_t cnt, + uint32_t chan_n = 0) const; samplecnt_t master_read_at (Sample* buf, samplepos_t position, @@ -159,6 +164,9 @@ class LIBARDOUR_API AudioRegion : public Region, public AudioReadable int separate_by_channel (std::vector >&) const; + bool remove_plugin (std::shared_ptr); + void reorder_plugins (RegionFxList const&); + /* automation */ std::shared_ptr @@ -249,11 +257,27 @@ class LIBARDOUR_API AudioRegion : public Region, public AudioReadable std::shared_ptr get_single_other_xfade_region (bool start) const; + void apply_region_fx (BufferSet&, samplepos_t, samplepos_t, samplecnt_t); + void fx_latency_changed (bool no_emit); + void copy_plugin_state (std::shared_ptr); + + mutable samplepos_t _fx_pos; + pframes_t _fx_block_size; + mutable bool _fx_latent_read; + + mutable Glib::Threads::Mutex _cache_lock; + mutable BufferSet _readcache; + mutable samplepos_t _cache_start; + mutable samplepos_t _cache_end; + mutable std::atomic _invalidated; + protected: /* default constructor for derived (compound) types */ AudioRegion (Session& s, timepos_t const &, timecnt_t const &, std::string name); + bool _add_plugin (std::shared_ptr, std::shared_ptr, bool); + int _set_state (const XMLNode&, int version, PBD::PropertyChange& what_changed, bool send_signal); }; diff --git a/libs/ardour/ardour/region.h b/libs/ardour/ardour/region.h index e288300481..2f51aaf5d7 100644 --- a/libs/ardour/ardour/region.h +++ b/libs/ardour/ardour/region.h @@ -79,11 +79,14 @@ namespace Properties { LIBARDOUR_API extern PBD::PropertyDescriptor tags; LIBARDOUR_API extern PBD::PropertyDescriptor reg_group; LIBARDOUR_API extern PBD::PropertyDescriptor contents; // type doesn't matter here, used for signal only + LIBARDOUR_API extern PBD::PropertyDescriptor region_fx; // type doesn't matter here, used for signal only }; class Playlist; class Filter; class ExportSpecification; +class Plugin; +class RegionFxPlugin; enum LIBARDOUR_API RegionEditState { EditChangesNothing = 0, @@ -107,11 +110,14 @@ class LIBARDOUR_API Region { public: typedef std::vector > SourceList; + typedef std::list> RegionFxList; static void make_property_quarks (); static PBD::Signal2, const PBD::PropertyChange&> RegionsPropertyChanged; + PBD::Signal0 RegionFxChanged; + typedef std::map ChangeMap; virtual ~Region(); @@ -503,6 +509,34 @@ public: void move_cue_marker (CueMarker const &, timepos_t const & region_relative_position); void rename_cue_marker (CueMarker&, std::string const &); + /* Region Fx */ + bool load_plugin (ARDOUR::PluginType type, std::string const& name); + bool add_plugin (std::shared_ptr, std::shared_ptr pos = std::shared_ptr ()); + virtual bool remove_plugin (std::shared_ptr) { return false; } + virtual void reorder_plugins (RegionFxList const&); + + bool has_region_fx () const { + Glib::Threads::RWLock::ReaderLock lm (_fx_lock); + return !_plugins.empty (); + } + + std::shared_ptr nth_plugin (uint32_t n) const { + Glib::Threads::RWLock::ReaderLock lm (_fx_lock); + for (auto const& i : _plugins) { + if (0 == n--) { + return i; + } + } + return std::shared_ptr (); + } + + void foreach_plugin (boost::function)> method) const { + Glib::Threads::RWLock::ReaderLock lm (_fx_lock); + for (auto const& i : _plugins) { + method (std::weak_ptr (i)); + } + } + protected: virtual XMLNode& state () const; @@ -528,6 +562,8 @@ protected: } protected: + virtual bool _add_plugin (std::shared_ptr, std::shared_ptr, bool) { return false; } + virtual void fx_latency_changed (bool no_emit); void send_change (const PBD::PropertyChange&); virtual int _set_state (const XMLNode&, int version, PBD::PropertyChange& what_changed, bool send_signal); @@ -546,6 +582,10 @@ protected: DataType _type; + mutable Glib::Threads::RWLock _fx_lock; + uint32_t _fx_latency; + RegionFxList _plugins; + PBD::Property _sync_marked; PBD::Property _left_of_split; PBD::Property _right_of_split; diff --git a/libs/ardour/audioregion.cc b/libs/ardour/audioregion.cc index 7d39ea18e7..f172143547 100644 --- a/libs/ardour/audioregion.cc +++ b/libs/ardour/audioregion.cc @@ -46,6 +46,7 @@ #include "ardour/audioengine.h" #include "ardour/analysis_graph.h" #include "ardour/audioregion.h" +#include "ardour/buffer_manager.h" #include "ardour/session.h" #include "ardour/dB.h" #include "ardour/debug.h" @@ -53,6 +54,7 @@ #include "ardour/playlist.h" #include "ardour/audiofilesource.h" #include "ardour/region_factory.h" +#include "ardour/region_fx_plugin.h" #include "ardour/runtime_functions.h" #include "ardour/sndfilesource.h" #include "ardour/transient_detector.h" @@ -243,6 +245,33 @@ AudioRegion::init () listen_to_my_curves (); connect_to_analysis_changed (); connect_to_header_position_offset_changed (); + + _fx_pos = _cache_start = _cache_end = -1; + _fx_block_size = 0; + _fx_latent_read = false; +} + +void +AudioRegion::copy_plugin_state (std::shared_ptr other) +{ + /* state cannot copied in Region, because when running Region's c'tor + * the AudioRegion does not yet exist, and virtual _add_plugin + * of the parent class is called + */ + Glib::Threads::RWLock::ReaderLock lm (other->_fx_lock); + for (auto const& i : other->_plugins) { + XMLNode& state = i->get_state (); + state.remove_property ("count"); + PBD::Stateful::ForceIDRegeneration force_ids; + std::shared_ptr rfx (new RegionFxPlugin (_session, Temporal::AudioTime)); + rfx->set_state (state, Stateful::current_state_version); + if (!_add_plugin (rfx, std::shared_ptr(), true)) { + continue; + } + _plugins.push_back (rfx); + delete &state; + } + fx_latency_changed (true); } /** Constructor for use by derived types only */ @@ -289,6 +318,12 @@ AudioRegion::AudioRegion (std::shared_ptr other) connect_to_analysis_changed (); connect_to_header_position_offset_changed (); + _fx_pos = _cache_start = _cache_end = -1; + _fx_block_size = 0; + _fx_latent_read = false; + + copy_plugin_state (other); + assert(_type == DataType::AUDIO); assert (_sources.size() == _master_sources.size()); } @@ -311,6 +346,12 @@ AudioRegion::AudioRegion (std::shared_ptr other, timecnt_t co connect_to_analysis_changed (); connect_to_header_position_offset_changed (); + _fx_pos = _cache_start = _cache_end = -1; + _fx_block_size = 0; + _fx_latent_read = false; + + copy_plugin_state (other); + assert(_type == DataType::AUDIO); assert (_sources.size() == _master_sources.size()); } @@ -331,6 +372,12 @@ AudioRegion::AudioRegion (std::shared_ptr other, const Source connect_to_analysis_changed (); connect_to_header_position_offset_changed (); + _fx_pos = _cache_start = _cache_end = -1; + _fx_block_size = 0; + _fx_latent_read = false; + + copy_plugin_state (other); + assert (_sources.size() == _master_sources.size()); } @@ -350,6 +397,9 @@ AudioRegion::AudioRegion (SourceList& srcs) AudioRegion::~AudioRegion () { + for (auto const& rfx : _plugins) { + rfx->drop_references (); + } } void @@ -491,7 +541,9 @@ AudioRegion::master_read_at (Sample* buf, samplepos_t position, samplecnt_t cnt, * @param chan_n Channel number to read. */ samplecnt_t -AudioRegion::read_at (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, +AudioRegion::read_at (Sample* buf, + Sample* mixdown_buffer, + gain_t* gain_buffer, samplepos_t pos, samplecnt_t cnt, uint32_t chan_n) const @@ -505,8 +557,9 @@ AudioRegion::read_at (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, */ assert (cnt >= 0); + uint32_t const n_chn = n_channels (); - if (n_channels() == 0) { + if (n_chn == 0) { return 0; } @@ -523,7 +576,10 @@ AudioRegion::read_at (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, return 0; /* read nothing */ } - if ((to_read = min (cnt, lsamples - internal_offset)) == 0) { + const samplecnt_t esamples = lsamples - internal_offset; + assert (esamples >= 0); + + if ((to_read = min (cnt, esamples)) == 0) { return 0; /* read nothing */ } @@ -593,32 +649,117 @@ AudioRegion::read_at (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, } } - /* READ DATA FROM THE SOURCE INTO mixdown_buffer. - We can never read directly into buf, since it may contain data - from a region `below' this one in the stack, and our fades (if they exist) - may need to mix with the existing data. - */ - - if (read_from_sources (_sources, lsamples, mixdown_buffer, pos, to_read, chan_n) != to_read) { - return 0; + Glib::Threads::Mutex::Lock cl (_cache_lock); + if (chan_n == 0 && _invalidated.exchange (false)) { + _cache_start = _cache_end = -1; } - /* APPLY REGULAR GAIN CURVES AND SCALING TO mixdown_buffer */ + boost::scoped_array gain_array; + boost::scoped_array mixdown_array; - if (envelope_active()) { - _envelope->curve().get_vector (timepos_t (internal_offset), timepos_t (internal_offset + to_read), gain_buffer, to_read); + // 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); + cl.release (); + } else { + Glib::Threads::RWLock::ReaderLock lm (_fx_lock); + bool have_fx = !_plugins.empty (); + uint32_t fx_latency = _fx_latency; + lm.release (); - if (_scale_amplitude != 1.0f) { - for (samplecnt_t n = 0; n < to_read; ++n) { - mixdown_buffer[n] *= gain_buffer[n] * _scale_amplitude; + DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Region '%1' channel: %2 read at %3 - %4 to_read: %5 with fx: %6\n", + name(), chan_n, internal_offset, internal_offset + to_read, to_read, have_fx)); + + 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 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) { + _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) { + offset += fx_latency; + readat += fx_latency; + n_read = max (0, min (to_read, lsamples - offset)); + } + + _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); + } + + /* reset in case read fails we return early */ + _cache_start = _cache_end = -1; + + for (uint32_t chn = 0; chn < n_chn; ++chn) { + /* READ DATA FROM THE SOURCE INTO mixdown_buffer. + * We can never read directly into buf, since it may contain data + * from a region `below' this one in the stack, and our fades (if they exist) + * may need to mix with the existing data. + */ + + if (read_from_sources (_sources, lsamples, mixdown_buffer, readat, n_read, chn) != n_read) { + return 0; // XXX } - } else { - for (samplecnt_t n = 0; n < to_read; ++n) { - mixdown_buffer[n] *= gain_buffer[n]; + + /* APPLY REGULAR GAIN CURVES AND SCALING TO mixdown_buffer */ + if (envelope_active()) { + _envelope->curve().get_vector (timepos_t (offset), timepos_t (offset + n_read), gain_buffer, n_read); + + if (_scale_amplitude != 1.0f) { + for (samplecnt_t n = 0; n < n_read; ++n) { + mixdown_buffer[n] *= gain_buffer[n] * _scale_amplitude; + } + } else { + for (samplecnt_t n = 0; n < n_read; ++n) { + mixdown_buffer[n] *= gain_buffer[n]; + } + } + } else if (_scale_amplitude != 1.0f) { + apply_gain_to_buffer (mixdown_buffer, n_read, _scale_amplitude); + } + + /* for mono regions no cache is required, unless there are + * regionFX, which use the _readcache BufferSet. + */ + if (n_chn > 1 || have_fx) { + _readcache.get_audio (chn).read_from (mixdown_buffer, n_proc); } } - } else if (_scale_amplitude != 1.0f) { - apply_gain_to_buffer (mixdown_buffer, to_read, _scale_amplitude); + + /* apply region FX to all channels */ + if (have_fx) { + const_cast(this)->apply_region_fx (_readcache, offset, offset + 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 */ + copy_vector (mixdown_buffer, _readcache.get_audio (chan_n).data (), to_read); + } + + _cache_start = internal_offset; + _cache_end = internal_offset + to_read; + cl.release (); } /* APPLY FADES TO THE DATA IN mixdown_buffer AND MIX THE RESULTS INTO @@ -723,7 +864,7 @@ AudioRegion::read_at (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, if (is_opaque) { DEBUG_TRACE (DEBUG::AudioPlayback, string_compose ("Region %1 memcpy into buf @ %2 + %3, from mixdown buffer @ %4 + %5, len = %6 cnt was %7\n", name(), buf, fade_in_limit, mixdown_buffer, fade_in_limit, N, cnt)); - memcpy (buf + fade_in_limit, mixdown_buffer + fade_in_limit, N * sizeof (Sample)); + copy_vector (buf + fade_in_limit, mixdown_buffer + fade_in_limit, N); } else { mix_buffers_no_gain (buf + fade_in_limit, mixdown_buffer + fade_in_limit, N); } @@ -1313,10 +1454,20 @@ AudioRegion::recompute_at_end () based on the the existing curve. */ + timepos_t tend (len_as_tpos ()); + _envelope->freeze (); - _envelope->truncate_end (len_as_tpos ()); + _envelope->truncate_end (tend); _envelope->thaw (); + foreach_plugin ([tend](std::weak_ptr wfx) + { + shared_ptr rfx = wfx.lock (); + if (rfx) { + rfx->truncate_automation_end (tend); + } + }); + suspend_property_changes(); if (_left_of_split) { @@ -1340,7 +1491,16 @@ AudioRegion::recompute_at_start () { /* as above, but the shift was from the front */ - _envelope->truncate_start (timecnt_t::from_samples (length().samples ())); + timecnt_t tas (timecnt_t::from_samples (length().samples ())); + _envelope->truncate_start (tas); + + foreach_plugin ([tas](std::weak_ptr wfx) + { + shared_ptr rfx = wfx.lock (); + if (rfx) { + rfx->truncate_automation_start (tas); + } + }); suspend_property_changes(); @@ -2112,3 +2272,204 @@ errout: return to_read == 0; } + +bool +AudioRegion::_add_plugin (std::shared_ptr rfx, std::shared_ptr before, bool from_set_state) +{ + ChanCount in (DataType::AUDIO, n_channels ()); + ChanCount out (in); + + if (!rfx->can_support_io_configuration (in, out)) { + return false; + } + if (in.n_audio () > out.n_audio ()) { + return false; + } + if (!rfx->configure_io (in, out)) { + return false; + } + + ChanCount fx_cc; + { + Glib::Threads::RWLock::ReaderLock lm (_fx_lock, Glib::Threads::NOT_LOCK); + if (!from_set_state) { + lm.acquire(); + } + ChanCount cc (DataType::AUDIO, n_channels ()); + fx_cc = ChanCount::max (in, out); + fx_cc = ChanCount::max (fx_cc, rfx->required_buffers ()); + for (auto const& i : _plugins) { + fx_cc = ChanCount::max (fx_cc, i->required_buffers ()); + } + } + + DEBUG_TRACE (DEBUG::RegionFx, string_compose ("Audio Region Fx required ChanCount: %1\n", fx_cc)); + + _session.ensure_buffers_unlocked (fx_cc); + + /* subscribe to parameter changes */ + ControllableSet acs; + rfx->automatables (acs); + for (auto& ec : acs) { + std::shared_ptr ac (std::dynamic_pointer_cast(ec)); + std::weak_ptr wc (ac); + ec->Changed.connect_same_thread (*this, [this, wc] (bool, PBD::Controllable::GroupControlDisposition) + { + std::shared_ptr ac = wc.lock (); + if (ac && ac->automation_playback ()) { + return; + } + if (!_invalidated.exchange (true)) { + send_change (PropertyChange (Properties::region_fx)); // trigger DiskReader overwrite + } + }); + if (!ac->alist ()) { + continue; + } + ac->alist()->StateChanged.connect_same_thread (*this, [this] () + { + if (!_invalidated.exchange (true)) { + send_change (PropertyChange (Properties::region_fx)); // trigger DiskReader overwrite + } + }); + } + + rfx->LatencyChanged.connect_same_thread (*this, boost::bind (&AudioRegion::fx_latency_changed, this, false)); + rfx->set_block_size (_session.get_block_size ()); + + if (from_set_state) { + return true; + } + + { + Glib::Threads::RWLock::WriterLock lm (_fx_lock); + RegionFxList::iterator loc = _plugins.end (); + if (before) { + loc = find (_plugins.begin (), _plugins.end (), before); + } + _plugins.insert (loc, rfx); + } + + rfx->set_default_automation (len_as_tpos ()); + + fx_latency_changed (true); + if (!_invalidated.exchange (true)) { + send_change (PropertyChange (Properties::region_fx)); // trigger DiskReader overwrite + } + RegionFxChanged (); /* EMIT SIGNAL */ + return true; +} + +bool +AudioRegion::remove_plugin (std::shared_ptr fx) +{ + Glib::Threads::RWLock::WriterLock lm (_fx_lock); + auto i = find (_plugins.begin(), _plugins.end(), fx); + if (i == _plugins.end ()) { + return false; + } + _plugins.erase (i); + + lm.release (); + + fx->drop_references (); + fx_latency_changed (true); + + if (!_invalidated.exchange (true)) { + send_change (PropertyChange (Properties::region_fx)); // trigger DiskReader overwrite + } + RegionFxChanged (); /* EMIT SIGNAL */ + return true; +} + +void +AudioRegion::reorder_plugins (RegionFxList const& new_order) +{ + Region::reorder_plugins (new_order); + if (!_invalidated.exchange (true)) { + send_change (PropertyChange (Properties::region_fx)); // trigger DiskReader overwrite + } + RegionFxChanged (); /* EMIT SIGNAL */ +} + +void +AudioRegion::fx_latency_changed (bool no_emit) +{ + uint32_t l = 0; + for (auto const& rfx : _plugins) { + l += rfx->effective_latency (); + } + if (l == _fx_latency) { + return; + } + _fx_latency = l; + + 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) +{ + Glib::Threads::RWLock::ReaderLock lm (_fx_lock); + + if (_plugins.empty ()) { + return; + } + + pframes_t block_size = _session.get_block_size (); + if (_fx_block_size != block_size) { + _fx_block_size = block_size; + for (auto const& rfx : _plugins) { + rfx->set_block_size (_session.get_block_size ()); + } + } + + ARDOUR::ProcessThread* pt = new ProcessThread (); // TODO -> move to butler ? + pt->get_buffers (); + + samplecnt_t latency_offset = 0; + + for (auto const& rfx : _plugins) { + if (_fx_pos != start_sample) { + rfx->flush (); + } + samplecnt_t remain = n_samples; + samplecnt_t offset = 0; + samplecnt_t latency = rfx->effective_latency (); + + while (remain > 0) { + pframes_t run = std::min (remain, block_size); + if (!rfx->run (bufs, start_sample + offset - latency_offset, end_sample + offset - latency_offset, position().samples(), run, offset)) { + lm.release (); + /* this triggers a re-read */ + const_cast(this)->remove_plugin (rfx); + return; + } + remain -= run; + offset += run; + } + + if (_fx_latent_read && latency > 0) { + for (uint32_t c = 0; c < n_channels (); ++c) { + Sample* to = _readcache.get_audio (c).data(); + Sample* from = _readcache.get_audio (c).data(latency); + // XXX can left to right copy_vector() work here? + memmove (to, from, (n_samples - latency) * sizeof(Sample)); + } + n_samples -= latency; + } + if (!_fx_latent_read) { + latency_offset += latency; + } + } + _fx_pos = end_sample; + _fx_latent_read = false; + pt->drop_buffers (); + delete pt; +} diff --git a/libs/ardour/disk_reader.cc b/libs/ardour/disk_reader.cc index ba00425161..d672bf3da4 100644 --- a/libs/ardour/disk_reader.cc +++ b/libs/ardour/disk_reader.cc @@ -715,19 +715,17 @@ DiskReader::overwrite_existing_audio () boost::scoped_array gain_buffer (new float[to_overwrite]); uint32_t n = 0; bool ret = true; - samplepos_t start; + samplepos_t start = overwrite_sample; - for (auto const& chan : *c) { - Sample* buf = chan->rbuf->buffer (); - ReaderChannelInfo* rci = dynamic_cast (chan); + if (chunk1_cnt) { + for (auto const& chan : *c) { + Sample* buf = chan->rbuf->buffer (); + ReaderChannelInfo* rci = dynamic_cast (chan); - /* Note that @p start is passed by reference and will be - * updated by the ::audio_read() call - */ - - start = overwrite_sample; - - if (chunk1_cnt) { + /* Note that @p start is passed by reference and will be + * updated by the ::audio_read() call + */ + start = overwrite_sample; if (audio_read (sum_buffer.get (), mixdown_buffer.get (), gain_buffer.get (), start, chunk1_cnt, rci, n, reversed) != (samplecnt_t)chunk1_cnt) { error << string_compose (_("DiskReader %1: when overwriting(1), cannot read %2 from playlist at sample %3"), id (), chunk1_cnt, overwrite_sample) << endmsg; ret = false; @@ -735,9 +733,21 @@ DiskReader::overwrite_existing_audio () continue; } memcpy (buf + chunk1_offset, sum_buffer.get (), sizeof (float) * chunk1_cnt); + ++n; } + } + + overwrite_sample = start; + + /* sequence read chunks. first read data at same position for all channels */ + + n = 0; + for (auto const& chan : *c) { + Sample* buf = chan->rbuf->buffer (); + ReaderChannelInfo* rci = dynamic_cast (chan); if (chunk2_cnt) { + start = overwrite_sample; if (audio_read (sum_buffer.get (), mixdown_buffer.get (), gain_buffer.get (), start, chunk2_cnt, rci, n, reversed) != (samplecnt_t)chunk2_cnt) { error << string_compose (_("DiskReader %1: when overwriting(2), cannot read %2 from playlist at sample %3"), id (), chunk2_cnt, overwrite_sample) << endmsg; ret = false; diff --git a/libs/ardour/globals.cc b/libs/ardour/globals.cc index 65d5e6ea76..734de6adf5 100644 --- a/libs/ardour/globals.cc +++ b/libs/ardour/globals.cc @@ -726,10 +726,11 @@ ARDOUR::init (bool try_optimization, const char* localedir, bool with_gui) * each cycle). Session Export uses one, and the GUI requires * buffers (for plugin-analysis, auditioner updates) but not * concurrently. + * Last but not least, the butler needs one for RegionFX. * - * In theory (hw + 3) should be sufficient, let's add one for luck. + * In theory (hw + 4) should be sufficient, let's add one for luck. */ - BufferManager::init (hardware_concurrency () + 4); + BufferManager::init (hardware_concurrency () + 5); PannerManager::instance ().discover_panners (); diff --git a/libs/ardour/luabindings.cc b/libs/ardour/luabindings.cc index acc029961d..ea3f63c916 100644 --- a/libs/ardour/luabindings.cc +++ b/libs/ardour/luabindings.cc @@ -1637,6 +1637,11 @@ LuaBindings::common (lua_State* L) .addFunction ("has_transients", &Region::has_transients) .addFunction ("transients", (AnalysisFeatureList (Region::*)())&Region::transients) + .addFunction ("load_plugin", &Region::load_plugin) + .addFunction ("add_plugin", &Region::add_plugin) + .addFunction ("remove_plugin", &Region::add_plugin) + .addFunction ("nth_plugin", &Region::nth_plugin) + /* editing operations */ .addFunction ("set_length", &Region::set_length) .addFunction ("set_start", &Region::set_start) diff --git a/libs/ardour/playlist.cc b/libs/ardour/playlist.cc index c8ecb7347b..cc97894222 100644 --- a/libs/ardour/playlist.cc +++ b/libs/ardour/playlist.cc @@ -1700,6 +1700,7 @@ Playlist::region_changed (const PropertyChange& what_changed, std::shared_ptr tags; PBD::PropertyDescriptor reg_group; PBD::PropertyDescriptor contents; + PBD::PropertyDescriptor region_fx; /* these properties are used as a convenience for announcing changes to state, but aren't stored as properties */ PBD::PropertyDescriptor time_domain; @@ -184,6 +187,8 @@ Region::make_property_quarks () DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for tags = %1\n", Properties::tags.property_id)); Properties::contents.property_id = g_quark_from_static_string (X_("contents")); DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for contents = %1\n", Properties::contents.property_id)); + Properties::region_fx.property_id = g_quark_from_static_string (X_("region-fx")); + DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for region-fx = %1\n", Properties::region_fx.property_id)); Properties::time_domain.property_id = g_quark_from_static_string (X_("time_domain")); DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for time_domain = %1\n", Properties::time_domain.property_id)); Properties::reg_group.property_id = g_quark_from_static_string (X_("rgroup")); @@ -290,6 +295,7 @@ Region::register_properties () Region::Region (Session& s, timepos_t const & start, timecnt_t const & length, const string& name, DataType type) : SessionObject(s, name) , _type (type) + , _fx_latency (0) , REGION_DEFAULT_STATE (start,length) , _last_length (length) , _first_edit (EditChangesNothing) @@ -305,6 +311,7 @@ Region::Region (Session& s, timepos_t const & start, timecnt_t const & length, c Region::Region (const SourceList& srcs) : SessionObject(srcs.front()->session(), "toBeRenamed") , _type (srcs.front()->type()) + , _fx_latency (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)) @@ -326,6 +333,7 @@ Region::Region (const SourceList& srcs) Region::Region (std::shared_ptr other) : SessionObject(other->session(), other->name()) , _type (other->data_type()) + , _fx_latency (0) , REGION_COPY_STATE (other) , _last_length (other->_last_length) , _first_edit (EditChangesNothing) @@ -384,6 +392,7 @@ Region::Region (std::shared_ptr other) Region::Region (std::shared_ptr other, timecnt_t const & offset) : SessionObject(other->session(), other->name()) , _type (other->data_type()) + , _fx_latency (0) , REGION_COPY_STATE (other) , _last_length (other->_last_length) , _first_edit (EditChangesNothing) @@ -429,6 +438,7 @@ Region::Region (std::shared_ptr other, timecnt_t const & offset) Region::Region (std::shared_ptr other, const SourceList& srcs) : SessionObject (other->session(), other->name()) , _type (srcs.front()->type()) + , _fx_latency (0) , REGION_COPY_STATE (other) , _last_length (other->_last_length) , _first_edit (EditChangesID) @@ -1447,6 +1457,13 @@ Region::state () const node->add_child_copy (*_extra_xml); } + { + Glib::Threads::RWLock::ReaderLock lm (_fx_lock); + for (auto const & p : _plugins) { + node->add_child_nocopy (p->get_state ()); + } + } + return *node; } @@ -1552,6 +1569,30 @@ Region::_set_state (const XMLNode& node, int version, PropertyChange& what_chang _valid_transients = false; } + { + Glib::Threads::RWLock::WriterLock lm (_fx_lock); + bool changed = !_plugins.empty (); + + _plugins.clear (); + + for (auto const& child : node.children ()) { + if (child->name() == X_("RegionFXPlugin")) { + std::shared_ptr rfx (new RegionFxPlugin (_session, time_domain ())); + rfx->set_state (*child, version); + if (!_add_plugin (rfx, std::shared_ptr(), true)) { + continue; + } + _plugins.push_back (rfx); + changed = true; + } + } + if (changed) { + fx_latency_changed (true); + send_change (PropertyChange (Properties::region_fx)); // trigger DiskReader overwrite + RegionFxChanged (); /* EMIT SIGNAL */ + } + } + return 0; } @@ -2347,3 +2388,63 @@ Region::finish_domain_bounce (Temporal::DomainBounceInfo& cmd) send_change (Properties::length); } + +bool +Region::load_plugin (ARDOUR::PluginType type, std::string const& name) +{ + PluginInfoPtr pip = LuaAPI::new_plugin_info (name, type); + if (!pip) { + return false; + } + PluginPtr p (pip->load (_session)); + if (!p) { + return false; + } + std::shared_ptr rfx (new RegionFxPlugin (_session, time_domain (), p)); + return add_plugin (rfx); +} + +bool +Region::add_plugin (std::shared_ptr rfx, std::shared_ptr pos) +{ + return _add_plugin (rfx, pos, false); +} + +void +Region::reorder_plugins (RegionFxList const& new_order) +{ + Glib::Threads::RWLock::WriterLock lm (_fx_lock); + + RegionFxList as_it_will_be; + RegionFxList::iterator oiter = _plugins.begin (); + RegionFxList::const_iterator niter = new_order.begin (); + + while (niter != new_order.end ()) { + if (oiter == _plugins.end ()) { + as_it_will_be.insert (as_it_will_be.end (), niter, new_order.end ()); + while (niter != new_order.end ()) { + ++niter; + } + break; + } + if (find (new_order.begin (), new_order.end (), *oiter) != new_order.end ()) { + as_it_will_be.push_back (*niter); + ++niter; + } + oiter = _plugins.erase (oiter); + } + _plugins.insert (oiter, as_it_will_be.begin (), as_it_will_be.end ()); +} + +void +Region::fx_latency_changed (bool) +{ + uint32_t l = 0; + for (auto const& rfx : _plugins) { + l += rfx->effective_latency (); + } + if (l == _fx_latency) { + return; + } + _fx_latency = l; +}