diff options
author | Robin Gareus <robin@gareus.org> | 2020-02-02 20:54:58 +0100 |
---|---|---|
committer | Robin Gareus <robin@gareus.org> | 2020-02-06 17:30:22 +0100 |
commit | 00fcf6719c348c84da0e9b4d8a689c485234dde0 (patch) | |
tree | bc0ded0020102328dfeae6d3e8556c448b78146e | |
parent | bfebe43a02bbafacfaccd7d2f0bb3f10d13067fd (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.h | 123 | ||||
-rw-r--r-- | libs/ardour/convolver.cc | 287 | ||||
-rw-r--r-- | libs/ardour/luabindings.cc | 19 |
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 */ |