summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Gareus <robin@gareus.org>2020-02-02 20:54:58 +0100
committerRobin Gareus <robin@gareus.org>2020-02-06 17:30:22 +0100
commit00fcf6719c348c84da0e9b4d8a689c485234dde0 (patch)
treebc0ded0020102328dfeae6d3e8556c448b78146e
parentbfebe43a02bbafacfaccd7d2f0bb3f10d13067fd (diff)
Update DSP::Convolution
Expose zita-convolver bindings, to allow for custom NxM convolution matrices, and dedicated FIR processors.
-rw-r--r--libs/ardour/ardour/convolver.h123
-rw-r--r--libs/ardour/convolver.cc287
-rw-r--r--libs/ardour/luabindings.cc19
3 files changed, 301 insertions, 128 deletions
diff --git a/libs/ardour/ardour/convolver.h b/libs/ardour/ardour/convolver.h
index 642f9b0b04..2aca6d6917 100644
--- a/libs/ardour/ardour/convolver.h
+++ b/libs/ardour/ardour/convolver.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2018-2020 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,24 +23,120 @@
#include "zita-convolver/zita-convolver.h"
#include "ardour/libardour_visibility.h"
+
+#include "ardour/buffer_set.h"
+#include "ardour/chan_mapping.h"
#include "ardour/readable.h"
namespace ARDOUR { namespace DSP {
-class LIBARDOUR_API Convolver : public SessionHandleRef {
+class LIBARDOUR_API Convolution : public SessionHandleRef
+{
public:
+ Convolution (Session&, uint32_t n_in, uint32_t n_out);
+ virtual ~Convolution () {}
+
+ bool add_impdata (
+ uint32_t c_in,
+ uint32_t c_out,
+ boost::shared_ptr<Readable> r,
+ float gain = 1.0,
+ uint32_t pre_delay = 0,
+ sampleoffset_t offset = 0,
+ samplecnt_t length = 0,
+ uint32_t channel = 0);
+
+ bool ready () const;
+ uint32_t latency () const { return _n_samples; }
+ uint32_t n_inputs () const { return _n_inputs; }
+ uint32_t n_outputs () const { return _n_outputs; }
+
+ void restart ();
+ void run (BufferSet&, ChanMapping const&, ChanMapping const&, pframes_t, samplecnt_t);
+
+protected:
+ ArdourZita::Convproc _convproc;
+
+ uint32_t _n_samples;
+ uint32_t _max_size;
+ uint32_t _offset;
+ bool _configured;
+
+private:
+ class ImpData : public Readable
+ {
+ public:
+ ImpData (uint32_t ci, uint32_t co, boost::shared_ptr<Readable> r, float g, float d, sampleoffset_t s = 0, samplecnt_t l = 0, uint32_t c = 0)
+ : c_in (ci)
+ , c_out (co)
+ , gain (g)
+ , delay (d)
+ , _readable (r)
+ , _offset (s)
+ , _length (l)
+ , _channel (c)
+ {}
+
+ uint32_t c_in;
+ uint32_t c_out;
+ float gain;
+ uint32_t delay;
+
+ samplecnt_t read (Sample* s, samplepos_t pos, samplecnt_t cnt, int c = -1) const {
+ return _readable->read (s, pos + _offset, cnt, _channel);
+ }
+
+ samplecnt_t readable_length () const {
+ samplecnt_t rl = _readable->readable_length ();
+ if (rl < _offset) {
+ return 0;
+ } else if (_length > 0) {
+ return std::min (rl - _offset, _length);
+ } else {
+ return rl - _offset;
+ }
+ }
+
+ uint32_t n_channels () const {
+ return _readable->n_channels ();
+ }
+
+ private:
+ boost::shared_ptr<Readable> _readable;
+ sampleoffset_t _offset;
+ samplecnt_t _length;
+ uint32_t _channel;
+ };
+
+ std::vector<ImpData> _impdata;
+ uint32_t _n_inputs;
+ uint32_t _n_outputs;
+};
+
+class LIBARDOUR_API Convolver : public Convolution
+{
+public:
enum IRChannelConfig {
Mono, ///< 1 in, 1 out; 1ch IR
MonoToStereo, ///< 1 in, 2 out, stereo IR M -> L, M -> R
Stereo, ///< 2 in, 2 out, stereo IR L -> L, R -> R || 4 chan IR L -> L, L -> R, R -> R, R -> L
};
+ static uint32_t ircc_in (IRChannelConfig irc) {
+ return irc < Stereo ? 1 : 2;
+ }
+
+ static uint32_t ircc_out (IRChannelConfig irc) {
+ return irc == Mono ? 1 : 2;
+ }
+
struct IRSettings {
- IRSettings () {
- gain = 1.0;
+ IRSettings ()
+ {
+ gain = 1.0;
pre_delay = 0.0;
- channel_gain[0] = channel_gain[1] = channel_gain[2] = channel_gain[3] = 1.0;
+ channel_gain[0] = channel_gain[1] = channel_gain[2] = channel_gain[3] = 1.0;
channel_delay[0] = channel_delay[1] = channel_delay[2] = channel_delay[3] = 0;
};
@@ -66,31 +162,16 @@ public:
}
};
-
Convolver (Session&, std::string const&, IRChannelConfig irc = Mono, IRSettings irs = IRSettings ());
- void run (float*, uint32_t);
+ void run_mono (float*, uint32_t);
void run_stereo (float* L, float* R, uint32_t);
- uint32_t latency () const { return _n_samples; }
-
- uint32_t n_inputs () const { return _irc < Stereo ? 1 : 2; }
- uint32_t n_outputs () const { return _irc == Mono ? 1 : 2; }
-
- bool ready () const;
-
private:
- void reconfigure ();
std::vector<boost::shared_ptr<Readable> > _readables;
- ArdourZita::Convproc _convproc;
IRChannelConfig _irc;
IRSettings _ir_settings;
-
- uint32_t _n_samples;
- uint32_t _max_size;
- uint32_t _offset;
- bool _configured;
};
} } /* namespace */
diff --git a/libs/ardour/convolver.cc b/libs/ardour/convolver.cc
index 2b00f0894e..916fd35fe8 100644
--- a/libs/ardour/convolver.cc
+++ b/libs/ardour/convolver.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018-2019 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2018-2020 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,13 +21,15 @@
#include "pbd/error.h"
#include "pbd/pthread_utils.h"
+#include "ardour/audio_buffer.h"
#include "ardour/audioengine.h"
#include "ardour/audiofilesource.h"
#include "ardour/convolver.h"
+#include "ardour/dsp_filter.h"
#include "ardour/readable.h"
#include "ardour/session.h"
-#include "ardour/srcfilesource.h"
#include "ardour/source_factory.h"
+#include "ardour/srcfilesource.h"
#include "pbd/i18n.h"
@@ -35,63 +37,204 @@ using namespace ARDOUR::DSP;
using namespace ArdourZita;
using ARDOUR::Session;
-
-Convolver::Convolver (
- Session& session,
- std::string const& path,
- IRChannelConfig irc,
- IRSettings irs)
- : SessionHandleRef (session)
- , _irc (irc)
- , _ir_settings (irs)
- , _n_samples (0)
- , _max_size (0)
- , _offset (0)
- , _configured (false)
+Convolution::Convolution (Session& session, uint32_t n_in, uint32_t n_out)
+ : SessionHandleRef (session)
+ , _n_samples (0)
+ , _max_size (0)
+ , _offset (0)
+ , _configured (false)
+ , _n_inputs (n_in)
+ , _n_outputs (n_out)
{
- _readables = Readable::load (_session, path);
+ AudioEngine::instance ()->BufferSizeChanged.connect_same_thread (*this, boost::bind (&Convolution::restart, this));
+}
- if (_readables.empty ()) {
- PBD::error << string_compose (_("Convolver: IR \"%1\" no usable audio-channels sound."), path) << endmsg;
- throw failed_constructor ();
+bool
+Convolution::add_impdata (
+ uint32_t c_in,
+ uint32_t c_out,
+ boost::shared_ptr<Readable> readable,
+ float gain,
+ uint32_t pre_delay,
+ sampleoffset_t offset,
+ samplecnt_t length,
+ uint32_t channel)
+{
+ if (_configured || c_in >= _n_inputs || c_out >= _n_outputs) {
+ return false;
}
-
- if (_readables[0]->readable_length () > 0x1000000 /*2^24*/) {
- PBD::error << string_compose(_("Convolver: IR \"%1\" file too long."), path) << endmsg;
- throw failed_constructor ();
+ if (!readable || readable->readable_length () <= offset || readable->n_channels () <= channel) {
+ return false;
}
- AudioEngine::instance ()->BufferSizeChanged.connect_same_thread (*this, boost::bind (&Convolver::reconfigure, this));
+ _impdata.push_back (ImpData (c_in, c_out, readable, gain, pre_delay, offset, length));
+ return true;
+}
- reconfigure ();
+bool
+Convolution::ready () const
+{
+ return _configured && _convproc.state () == Convproc::ST_PROC;
}
void
-Convolver::reconfigure ()
+Convolution::restart ()
{
_convproc.stop_process ();
_convproc.cleanup ();
_convproc.set_options (0);
- assert (!_readables.empty ());
-
_offset = 0;
+ _max_size = 0;
_n_samples = _session.get_block_size ();
- _max_size = _readables[0]->readable_length ();
+
+ for (std::vector<ImpData>::const_iterator i = _impdata.begin (); i != _impdata.end (); ++i) {
+ _max_size = std::max (_max_size, (uint32_t)i->readable_length ());
+ }
uint32_t power_of_two;
for (power_of_two = 1; 1U << power_of_two < _n_samples; ++power_of_two) ;
_n_samples = 1 << power_of_two;
int n_part = std::min ((uint32_t)Convproc::MAXPART, 4 * _n_samples);
+
int rv = _convproc.configure (
- /*in*/ n_inputs (),
- /*out*/ n_outputs (),
- /*max-convolution length */ _max_size,
- /*quantum, nominal-buffersize*/ _n_samples,
- /*Convproc::MINPART*/ _n_samples,
- /*Convproc::MAXPART*/ n_part,
- /*density*/ 0);
+ /*in*/ _n_inputs,
+ /*out*/ _n_outputs,
+ /*max-convolution length */ _max_size,
+ /*quantum, nominal-buffersize*/ _n_samples,
+ /*Convproc::MINPART*/ _n_samples,
+ /*Convproc::MAXPART*/ n_part,
+ /*density 0 = auto, i/o dependent */ 0);
+
+ for (std::vector<ImpData>::const_iterator i = _impdata.begin (); i != _impdata.end (); ++i) {
+ uint32_t pos = 0;
+
+ const float ir_gain = i->gain;
+ const uint32_t ir_delay = i->delay;
+ const uint32_t ir_len = i->readable_length ();
+
+ while (true) {
+ float ir[8192];
+
+ samplecnt_t to_read = std::min ((uint32_t)8192, ir_len - pos);
+ samplecnt_t ns = i->read (ir, pos, to_read);
+
+ if (ns == 0) {
+ break;
+ }
+
+ if (ir_gain != 1.f) {
+ for (samplecnt_t i = 0; i < ns; ++i) {
+ ir[i] *= ir_gain;
+ }
+ }
+
+ rv = _convproc.impdata_create (
+ /*i/o map */ i->c_in, i->c_out,
+ /*stride, de-interleave */ 1,
+ ir,
+ ir_delay + pos, ir_delay + pos + ns);
+
+ if (rv != 0) {
+ break;
+ }
+
+ pos += ns;
+
+ if (pos == _max_size) {
+ break;
+ }
+ }
+ }
+
+ if (rv == 0) {
+ rv = _convproc.start_process (pbd_absolute_rt_priority (PBD_SCHED_FIFO, AudioEngine::instance ()->client_real_time_priority () - 2), PBD_SCHED_FIFO);
+ }
+
+ assert (rv == 0); // bail out in debug builds
+
+ if (rv != 0) {
+ _convproc.stop_process ();
+ _convproc.cleanup ();
+ _configured = false;
+ return;
+ }
+
+ _configured = true;
+
+#ifndef NDEBUG
+ _convproc.print (stdout);
+#endif
+}
+
+void
+Convolution::run (BufferSet& bufs, ChanMapping const& in_map, ChanMapping const& out_map, pframes_t n_samples, samplecnt_t offset)
+{
+ if (!ready ()) {
+ process_map (&bufs, in_map, out_map, n_samples, offset, DataType::AUDIO);
+ return;
+ }
+
+ uint32_t done = 0;
+ uint32_t remain = n_samples;
+
+ while (remain > 0) {
+ uint32_t ns = std::min (remain, _n_samples - _offset);
+
+ for (uint32_t c = 0; c < _n_inputs; ++c) {
+ bool valid;
+ const uint32_t idx = in_map.get (DataType::AUDIO, c, &valid);
+ if (!valid) {
+ ::memset (&_convproc.inpdata (c)[_offset], 0, sizeof (float) * ns);
+ } else {
+ AudioBuffer const& ab (bufs.get_audio (idx));
+ memcpy (&_convproc.inpdata (c)[_offset], ab.data (done + offset), sizeof (float) * ns);
+ }
+ }
+
+ for (uint32_t c = 0; c < _n_outputs; ++c) {
+ bool valid;
+ const uint32_t idx = out_map.get (DataType::AUDIO, c, &valid);
+ if (valid) {
+ AudioBuffer& ab (bufs.get_audio (idx));
+ memcpy (ab.data (done + offset), &_convproc.outdata (c)[_offset], sizeof (float) * ns);
+ }
+ }
+
+ _offset += ns;
+ done += ns;
+ remain -= ns;
+
+ if (_offset == _n_samples) {
+ _convproc.process (/*sync, freewheeling*/ true);
+ _offset = 0;
+ }
+ }
+}
+
+/* ****************************************************************************/
+
+Convolver::Convolver (
+ Session& session,
+ std::string const& path,
+ IRChannelConfig irc,
+ IRSettings irs)
+ : Convolution (session, ircc_in (irc), ircc_out (irc))
+ , _irc (irc)
+ , _ir_settings (irs)
+{
+ std::vector<boost::shared_ptr<Readable> > readables = Readable::load (_session, path);
+
+ if (readables.empty ()) {
+ PBD::error << string_compose (_("Convolver: IR \"%1\" no usable audio-channels sound."), path) << endmsg;
+ throw failed_constructor ();
+ }
+
+ if (readables[0]->readable_length () > 0x1000000 /*2^24*/) {
+ PBD::error << string_compose (_("Convolver: IR \"%1\" file too long."), path) << endmsg;
+ throw failed_constructor ();
+ }
/* map channels
* - Mono:
@@ -107,7 +250,7 @@ Convolver::reconfigure ()
*/
uint32_t n_imp = n_inputs () * n_outputs ();
- uint32_t n_chn = _readables.size ();
+ uint32_t n_chn = readables.size ();
if (_irc == Stereo && n_chn == 3) {
/* ignore 3rd channel */
@@ -119,12 +262,12 @@ Convolver::reconfigure ()
}
#ifndef NDEBUG
- printf ("Convolver::reconfigure Nin=%d Nout=%d Nimp=%d Nchn=%d\n", n_inputs (), n_outputs (), n_imp, n_chn);
+ printf ("Convolver: Nin=%d Nout=%d Nimp=%d Nchn=%d\n", n_inputs (), n_outputs (), n_imp, n_chn);
#endif
assert (n_imp <= 4);
- for (uint32_t c = 0; c < n_imp && rv == 0; ++c) {
+ for (uint32_t c = 0; c < n_imp; ++c) {
int ir_c = c % n_chn;
int io_o = c % n_outputs ();
int io_i;
@@ -143,9 +286,7 @@ Convolver::reconfigure ()
io_i = (c / n_outputs ()) % n_inputs ();
}
-
- boost::shared_ptr<Readable> r = _readables[ir_c];
- assert (r->readable_length () == _max_size);
+ boost::shared_ptr<Readable> r = readables[ir_c];
assert (r->n_channels () == 1);
const float chan_gain = _ir_settings.gain * _ir_settings.channel_gain[c];
@@ -155,70 +296,14 @@ Convolver::reconfigure ()
printf ("Convolver map: IR-chn %d: in %d -> out %d (gain: %.1fdB delay; %d)\n", ir_c + 1, io_i + 1, io_o + 1, 20.f * log10f (chan_gain), chan_delay);
#endif
- uint32_t pos = 0;
- while (true) {
- float ir[8192];
-
- samplecnt_t to_read = std::min ((uint32_t)8192, _max_size - pos);
- samplecnt_t ns = r->read (ir, pos, to_read, 0);
-
- if (ns == 0) {
- assert (pos == _max_size);
- break;
- }
-
- if (chan_gain != 1.f) {
- for (samplecnt_t i = 0; i < ns; ++i) {
- ir[i] *= chan_gain;
- }
- }
-
- rv = _convproc.impdata_create (
- /*i/o map */ io_i, io_o,
- /*stride, de-interleave */ 1,
- ir,
- chan_delay + pos, chan_delay + pos + ns);
-
- if (rv != 0) {
- break;
- }
-
- pos += ns;
-
- if (pos == _max_size) {
- break;
- }
- }
- }
-
- if (rv == 0) {
- rv = _convproc.start_process (pbd_absolute_rt_priority (PBD_SCHED_FIFO, AudioEngine::instance()->client_real_time_priority() - 2), PBD_SCHED_FIFO);
+ add_impdata (io_i, io_o, r, chan_gain, chan_delay);
}
- assert (rv == 0); // bail out in debug builds
-
- if (rv != 0) {
- _convproc.stop_process ();
- _convproc.cleanup ();
- _configured = false;
- return;
- }
-
- _configured = true;
-
-#ifndef NDEBUG
- _convproc.print (stdout);
-#endif
-}
-
-bool
-Convolver::ready () const
-{
- return _configured && _convproc.state () == Convproc::ST_PROC;
+ Convolution::restart ();
}
void
-Convolver::run (float* buf, uint32_t n_samples)
+Convolver::run_mono (float* buf, uint32_t n_samples)
{
assert (_convproc.state () == Convproc::ST_PROC);
assert (_irc == Mono);
diff --git a/libs/ardour/luabindings.cc b/libs/ardour/luabindings.cc
index dfe5776271..adb1839bfd 100644
--- a/libs/ardour/luabindings.cc
+++ b/libs/ardour/luabindings.cc
@@ -2545,6 +2545,17 @@ LuaBindings::common (lua_State* L)
.addRefFunction ("read", &ARDOUR::LTCReader::read)
.endClass ()
+ .beginClass <DSP::Convolution> ("Convolution")
+ .addConstructor <void (*) (Session&, uint32_t, uint32_t)> ()
+ .addFunction ("add_impdata", &ARDOUR::DSP::Convolution::add_impdata)
+ .addFunction ("run", &ARDOUR::DSP::Convolution::run)
+ .addFunction ("restart", &ARDOUR::DSP::Convolution::restart)
+ .addFunction ("ready", &ARDOUR::DSP::Convolution::ready)
+ .addFunction ("latency", &ARDOUR::DSP::Convolution::latency)
+ .addFunction ("n_inputs", &ARDOUR::DSP::Convolution::n_inputs)
+ .addFunction ("n_outputs", &ARDOUR::DSP::Convolution::n_outputs)
+ .endClass ()
+
.beginClass <DSP::Convolver::IRSettings> ("IRSettings")
.addVoidConstructor ()
.addData ("gain", &DSP::Convolver::IRSettings::gain)
@@ -2555,14 +2566,10 @@ LuaBindings::common (lua_State* L)
.addFunction ("set_channel_delay", &ARDOUR::DSP::Convolver::IRSettings::set_channel_delay)
.endClass ()
- .beginClass <DSP::Convolver> ("Convolver")
+ .deriveClass <DSP::Convolver, DSP::Convolution> ("Convolver")
.addConstructor <void (*) (Session&, std::string const&, DSP::Convolver::IRChannelConfig, DSP::Convolver::IRSettings)> ()
- .addFunction ("run", &ARDOUR::DSP::Convolver::run)
+ .addFunction ("run_mono", &ARDOUR::DSP::Convolver::run_mono)
.addFunction ("run_stereo", &ARDOUR::DSP::Convolver::run_stereo)
- .addFunction ("latency", &ARDOUR::DSP::Convolver::latency)
- .addFunction ("n_inputs", &ARDOUR::DSP::Convolver::n_inputs)
- .addFunction ("n_outputs", &ARDOUR::DSP::Convolver::n_outputs)
- .addFunction ("ready", &ARDOUR::DSP::Convolver::ready)
.endClass ()
/* DSP enums */