Per [Audio] Region Fx
* apply effects during region-read in non-rt context * Add multi-channel audioregion read cache to process stereo effects
This commit is contained in:
parent
8c5f68e4fb
commit
ffe3dafe29
|
@ -23,6 +23,7 @@
|
|||
#ifndef __ardour_audio_region_h__
|
||||
#define __ardour_audio_region_h__
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
|
||||
|
@ -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<std::shared_ptr<Region> >&) const;
|
||||
|
||||
bool remove_plugin (std::shared_ptr<RegionFxPlugin>);
|
||||
void reorder_plugins (RegionFxList const&);
|
||||
|
||||
/* automation */
|
||||
|
||||
std::shared_ptr<Evoral::Control>
|
||||
|
@ -249,11 +257,27 @@ class LIBARDOUR_API AudioRegion : public Region, public AudioReadable
|
|||
|
||||
std::shared_ptr<ARDOUR::Region> 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<const AudioRegion>);
|
||||
|
||||
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<bool> _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<RegionFxPlugin>, std::shared_ptr<RegionFxPlugin>, bool);
|
||||
|
||||
int _set_state (const XMLNode&, int version, PBD::PropertyChange& what_changed, bool send_signal);
|
||||
};
|
||||
|
||||
|
|
|
@ -79,11 +79,14 @@ namespace Properties {
|
|||
LIBARDOUR_API extern PBD::PropertyDescriptor<std::string> tags;
|
||||
LIBARDOUR_API extern PBD::PropertyDescriptor<uint64_t> reg_group;
|
||||
LIBARDOUR_API extern PBD::PropertyDescriptor<bool> contents; // type doesn't matter here, used for signal only
|
||||
LIBARDOUR_API extern PBD::PropertyDescriptor<bool> 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<std::shared_ptr<Source> > SourceList;
|
||||
typedef std::list<std::shared_ptr<RegionFxPlugin>> RegionFxList;
|
||||
|
||||
static void make_property_quarks ();
|
||||
|
||||
static PBD::Signal2<void,std::shared_ptr<RegionList>, const PBD::PropertyChange&> RegionsPropertyChanged;
|
||||
|
||||
PBD::Signal0<void> RegionFxChanged;
|
||||
|
||||
typedef std::map <PBD::PropertyChange, RegionList> 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<RegionFxPlugin>, std::shared_ptr<RegionFxPlugin> pos = std::shared_ptr<RegionFxPlugin> ());
|
||||
virtual bool remove_plugin (std::shared_ptr<RegionFxPlugin>) { 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<RegionFxPlugin> 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<RegionFxPlugin> ();
|
||||
}
|
||||
|
||||
void foreach_plugin (boost::function<void(std::weak_ptr<RegionFxPlugin>)> method) const {
|
||||
Glib::Threads::RWLock::ReaderLock lm (_fx_lock);
|
||||
for (auto const& i : _plugins) {
|
||||
method (std::weak_ptr<RegionFxPlugin> (i));
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual XMLNode& state () const;
|
||||
|
||||
|
@ -528,6 +562,8 @@ 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);
|
||||
|
||||
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<bool> _sync_marked;
|
||||
PBD::Property<bool> _left_of_split;
|
||||
PBD::Property<bool> _right_of_split;
|
||||
|
|
|
@ -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<const AudioRegion> 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<RegionFxPlugin> rfx (new RegionFxPlugin (_session, Temporal::AudioTime));
|
||||
rfx->set_state (state, Stateful::current_state_version);
|
||||
if (!_add_plugin (rfx, std::shared_ptr<RegionFxPlugin>(), 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<const AudioRegion> 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<const AudioRegion> 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<const AudioRegion> 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_t> gain_array;
|
||||
boost::scoped_array<Sample> 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<samplecnt_t> (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<AudioRegion*>(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<RegionFxPlugin> wfx)
|
||||
{
|
||||
shared_ptr<RegionFxPlugin> 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<RegionFxPlugin> wfx)
|
||||
{
|
||||
shared_ptr<RegionFxPlugin> 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<RegionFxPlugin> rfx, std::shared_ptr<RegionFxPlugin> 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<AutomationControl> ac (std::dynamic_pointer_cast<AutomationControl>(ec));
|
||||
std::weak_ptr<AutomationControl> wc (ac);
|
||||
ec->Changed.connect_same_thread (*this, [this, wc] (bool, PBD::Controllable::GroupControlDisposition)
|
||||
{
|
||||
std::shared_ptr<AutomationControl> 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<RegionFxPlugin> 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 <pframes_t> (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<AudioRegion*>(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;
|
||||
}
|
||||
|
|
|
@ -715,19 +715,17 @@ DiskReader::overwrite_existing_audio ()
|
|||
boost::scoped_array<float> 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<ReaderChannelInfo*> (chan);
|
||||
if (chunk1_cnt) {
|
||||
for (auto const& chan : *c) {
|
||||
Sample* buf = chan->rbuf->buffer ();
|
||||
ReaderChannelInfo* rci = dynamic_cast<ReaderChannelInfo*> (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<ReaderChannelInfo*> (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;
|
||||
|
|
|
@ -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 ();
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1700,6 +1700,7 @@ Playlist::region_changed (const PropertyChange& what_changed, std::shared_ptr<Re
|
|||
our_interests.add (Properties::opaque);
|
||||
our_interests.add (Properties::contents);
|
||||
our_interests.add (Properties::time_domain);
|
||||
our_interests.add (Properties::region_fx);
|
||||
|
||||
bounds.add (Properties::start);
|
||||
bounds.add (Properties::length);
|
||||
|
|
|
@ -36,11 +36,13 @@
|
|||
#include "ardour/audioregion.h"
|
||||
#include "ardour/debug.h"
|
||||
#include "ardour/filter.h"
|
||||
#include "ardour/lua_api.h"
|
||||
#include "ardour/playlist.h"
|
||||
#include "ardour/playlist_source.h"
|
||||
#include "ardour/profile.h"
|
||||
#include "ardour/region.h"
|
||||
#include "ardour/region_factory.h"
|
||||
#include "ardour/region_fx_plugin.h"
|
||||
#include "ardour/session.h"
|
||||
#include "ardour/source.h"
|
||||
#include "ardour/tempo.h"
|
||||
|
@ -82,6 +84,7 @@ namespace ARDOUR {
|
|||
PBD::PropertyDescriptor<std::string> tags;
|
||||
PBD::PropertyDescriptor<uint64_t> reg_group;
|
||||
PBD::PropertyDescriptor<bool> contents;
|
||||
PBD::PropertyDescriptor<bool> region_fx;
|
||||
|
||||
/* these properties are used as a convenience for announcing changes to state, but aren't stored as properties */
|
||||
PBD::PropertyDescriptor<Temporal::TimeDomain> 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<const Region> 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<const Region> other)
|
|||
Region::Region (std::shared_ptr<const Region> 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<const Region> other, timecnt_t const & offset)
|
|||
Region::Region (std::shared_ptr<const Region> 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<RegionFxPlugin> rfx (new RegionFxPlugin (_session, time_domain ()));
|
||||
rfx->set_state (*child, version);
|
||||
if (!_add_plugin (rfx, std::shared_ptr<RegionFxPlugin>(), 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<RegionFxPlugin> rfx (new RegionFxPlugin (_session, time_domain (), p));
|
||||
return add_plugin (rfx);
|
||||
}
|
||||
|
||||
bool
|
||||
Region::add_plugin (std::shared_ptr<RegionFxPlugin> rfx, std::shared_ptr<RegionFxPlugin> 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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue