From 00fcf6719c348c84da0e9b4d8a689c485234dde0 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Sun, 2 Feb 2020 20:54:58 +0100 Subject: [PATCH] Update DSP::Convolution Expose zita-convolver bindings, to allow for custom NxM convolution matrices, and dedicated FIR processors. --- libs/ardour/ardour/convolver.h | 123 ++++++++++--- libs/ardour/convolver.cc | 311 +++++++++++++++++++++------------ libs/ardour/luabindings.cc | 19 +- 3 files changed, 313 insertions(+), 140 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 + * Copyright (C) 2018-2020 Robin Gareus * * 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 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 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; + + sampleoffset_t _offset; + samplecnt_t _length; + uint32_t _channel; + }; + + std::vector _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 > _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 + * Copyright (C) 2018-2020 Robin Gareus * * 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,149 +37,104 @@ 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, + 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 (!readable || readable->readable_length () <= offset || readable->n_channels () <= channel) { + 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 (); - } + _impdata.push_back (ImpData (c_in, c_out, readable, gain, pre_delay, offset, length)); + return true; +} - AudioEngine::instance ()->BufferSizeChanged.connect_same_thread (*this, boost::bind (&Convolver::reconfigure, this)); - - 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::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); - - /* map channels - * - Mono: - * always use first only - * - MonoToStereo: - * mono-file: use 1st for M -> L, M -> R - * else: use first two channels - * - Stereo - * mono-file: use 1st for both L -> L, R -> R, no x-over - * stereo-file: L -> L, R -> R -- no L/R, R/L x-over - * 3chan-file: ignore 3rd channel, use as stereo-file. - * 4chan file: L -> L, L -> R, R -> R, R -> L - */ - - uint32_t n_imp = n_inputs () * n_outputs (); - uint32_t n_chn = _readables.size (); - - if (_irc == Stereo && n_chn == 3) { - /* ignore 3rd channel */ - n_chn = 2; - } - if (_irc == Stereo && n_chn <= 2) { - /* ignore x-over */ - n_imp = 2; - } - -#ifndef NDEBUG - printf ("Convolver::reconfigure 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) { - int ir_c = c % n_chn; - int io_o = c % n_outputs (); - int io_i; - - if (n_imp == 2 && _irc == Stereo) { - /* (imp, in, out) - * Stereo (2, 2, 2) 1: L -> L, 2: R -> R - */ - io_i = c % n_inputs (); - } else { - /* (imp, in, out) - * Mono (1, 1, 1) 1: M -> M - * MonoToStereo (2, 1, 2) 1: M -> L, 2: M -> R - * Stereo (4, 2, 2) 1: L -> L, 2: L -> R, 3: R -> L, 4: R -> R - */ - io_i = (c / n_outputs ()) % n_inputs (); - } - - - boost::shared_ptr r = _readables[ir_c]; - assert (r->readable_length () == _max_size); - assert (r->n_channels () == 1); - - const float chan_gain = _ir_settings.gain * _ir_settings.channel_gain[c]; - const uint32_t chan_delay = _ir_settings.pre_delay + _ir_settings.channel_delay[c]; - -#ifndef NDEBUG - 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 + /*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::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, _max_size - pos); - samplecnt_t ns = r->read (ir, pos, to_read, 0); + samplecnt_t to_read = std::min ((uint32_t)8192, ir_len - pos); + samplecnt_t ns = i->read (ir, pos, to_read); if (ns == 0) { - assert (pos == _max_size); break; } - if (chan_gain != 1.f) { + if (ir_gain != 1.f) { for (samplecnt_t i = 0; i < ns; ++i) { - ir[i] *= chan_gain; + ir[i] *= ir_gain; } } rv = _convproc.impdata_create ( - /*i/o map */ io_i, io_o, - /*stride, de-interleave */ 1, - ir, - chan_delay + pos, chan_delay + pos + ns); + /*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; @@ -192,7 +149,7 @@ Convolver::reconfigure () } if (rv == 0) { - rv = _convproc.start_process (pbd_absolute_rt_priority (PBD_SCHED_FIFO, AudioEngine::instance()->client_real_time_priority() - 2), PBD_SCHED_FIFO); + 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 @@ -211,14 +168,142 @@ Convolver::reconfigure () #endif } -bool -Convolver::ready () const +void +Convolution::run (BufferSet& bufs, ChanMapping const& in_map, ChanMapping const& out_map, pframes_t n_samples, samplecnt_t offset) { - return _configured && _convproc.state () == Convproc::ST_PROC; + 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 > 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: + * always use first only + * - MonoToStereo: + * mono-file: use 1st for M -> L, M -> R + * else: use first two channels + * - Stereo + * mono-file: use 1st for both L -> L, R -> R, no x-over + * stereo-file: L -> L, R -> R -- no L/R, R/L x-over + * 3chan-file: ignore 3rd channel, use as stereo-file. + * 4chan file: L -> L, L -> R, R -> R, R -> L + */ + + uint32_t n_imp = n_inputs () * n_outputs (); + uint32_t n_chn = readables.size (); + + if (_irc == Stereo && n_chn == 3) { + /* ignore 3rd channel */ + n_chn = 2; + } + if (_irc == Stereo && n_chn <= 2) { + /* ignore x-over */ + n_imp = 2; + } + +#ifndef NDEBUG + 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; ++c) { + int ir_c = c % n_chn; + int io_o = c % n_outputs (); + int io_i; + + if (n_imp == 2 && _irc == Stereo) { + /* (imp, in, out) + * Stereo (2, 2, 2) 1: L -> L, 2: R -> R + */ + io_i = c % n_inputs (); + } else { + /* (imp, in, out) + * Mono (1, 1, 1) 1: M -> M + * MonoToStereo (2, 1, 2) 1: M -> L, 2: M -> R + * Stereo (4, 2, 2) 1: L -> L, 2: L -> R, 3: R -> L, 4: R -> R + */ + io_i = (c / n_outputs ()) % n_inputs (); + } + + boost::shared_ptr r = readables[ir_c]; + assert (r->n_channels () == 1); + + const float chan_gain = _ir_settings.gain * _ir_settings.channel_gain[c]; + const uint32_t chan_delay = _ir_settings.pre_delay + _ir_settings.channel_delay[c]; + +#ifndef NDEBUG + 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 + + add_impdata (io_i, io_o, r, chan_gain, chan_delay); + } + + 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 ("Convolution") + .addConstructor () + .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 ("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 ("Convolver") + .deriveClass ("Convolver") .addConstructor () - .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 */