Implement basic libardour convolution DSP
authorRobin Gareus <robin@gareus.org>
Thu, 11 Oct 2018 13:29:10 +0000 (15:29 +0200)
committerRobin Gareus <robin@gareus.org>
Fri, 19 Oct 2018 22:24:38 +0000 (00:24 +0200)
libs/ardour/ardour/convolver.h [new file with mode: 0644]
libs/ardour/convolver.cc [new file with mode: 0644]
libs/ardour/wscript

diff --git a/libs/ardour/ardour/convolver.h b/libs/ardour/ardour/convolver.h
new file mode 100644 (file)
index 0000000..9d858a1
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 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 the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+#ifndef _ardour_convolver_h_
+#define _ardour_convolver_h_
+
+#include <vector>
+
+#include "zita-convolver/zita-convolver.h"
+
+#include "ardour/libardour_visibility.h"
+#include "ardour/readable.h"
+
+namespace ARDOUR { namespace DSP {
+
+class LIBARDOUR_API Convolver : public SessionHandleRef {
+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
+       };
+
+       Convolver (Session&, std::string const&, IRChannelConfig irc = Mono, uint32_t pre_delay = 0);
+
+       void run (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;
+       uint32_t _initial_delay;
+
+       uint32_t _n_samples;
+       uint32_t _max_size;
+       uint32_t _offset;
+       bool     _configured;
+};
+
+} } /* namespace */
+#endif
diff --git a/libs/ardour/convolver.cc b/libs/ardour/convolver.cc
new file mode 100644 (file)
index 0000000..bd00dcc
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2018 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 the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#include <assert.h>
+
+#include "pbd/error.h"
+#include "pbd/pthread_utils.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/audiofilesource.h"
+#include "ardour/convolver.h"
+#include "ardour/session.h"
+#include "ardour/srcfilesource.h"
+#include "ardour/source_factory.h"
+
+#include "pbd/i18n.h"
+
+using namespace ARDOUR::DSP;
+using namespace ArdourZita;
+
+Convolver::Convolver (Session& session, std::string const& path, IRChannelConfig irc, uint32_t pre_delay)
+       : SessionHandleRef (session)
+       , _irc (irc)
+       , _initial_delay (pre_delay)
+       , _n_samples (0)
+       , _max_size (0)
+       , _offset (0)
+       , _configured (false)
+{
+       ARDOUR::SoundFileInfo sf_info;
+       std::string error_msg;
+
+       if (!AudioFileSource::get_soundfile_info (path, sf_info, error_msg)) {
+               PBD::error << string_compose(_("Convolver: cannot open IR \"%1\": %2"), path, error_msg) << endmsg;
+               throw failed_constructor ();
+       }
+
+       if (sf_info.length > 0x1000000 /*2^24*/) {
+               PBD::error << string_compose(_("Convolver: IR \"%1\" file too long."), path) << endmsg;
+               throw failed_constructor ();
+       }
+
+       for (unsigned int n = 0; n < sf_info.channels; ++n) {
+               try {
+                       boost::shared_ptr<AudioFileSource> afs;
+                       afs = boost::dynamic_pointer_cast<AudioFileSource> (
+                                       SourceFactory::createExternal (DataType::AUDIO, _session,
+                                               path, n,
+                                               Source::Flag (ARDOUR::AudioFileSource::NoPeakFile), false));
+
+                       if (afs->sample_rate() != _session.nominal_sample_rate()) {
+                               boost::shared_ptr<SrcFileSource> sfs (new SrcFileSource(_session, afs, ARDOUR::SrcBest));
+                               _readables.push_back(sfs);
+                       } else {
+                               _readables.push_back(afs);
+                       }
+               } catch (failed_constructor& err) {
+                       PBD::error << string_compose(_("Convolver: Could not open IR \"%1\"."), path) << endmsg;
+                       throw failed_constructor ();
+               }
+       }
+
+       if (_readables.empty()) {
+               PBD::error << string_compose (_("Convolver: IR \"%1\" no usable audio-channels sound."), path) << endmsg;
+               throw failed_constructor ();
+       }
+
+       AudioEngine::instance ()->BufferSizeChanged.connect_same_thread (*this, boost::bind (&Convolver::reconfigure, this));
+
+       reconfigure ();
+}
+
+void
+Convolver::reconfigure ()
+{
+       _convproc.stop_process ();
+       _convproc.cleanup ();
+       _convproc.set_options (0);
+
+       assert (!_readables.empty());
+
+       _offset = 0;
+       _n_samples = _session.get_block_size();
+       _max_size = _readables[0]->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;
+       }
+
+       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 > n_chn && _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();
+               }
+
+#ifndef NDEBUG
+               printf ("Convolver map: IR-chn %d: in %d -> out %d\n", ir_c + 1, io_i + 1, io_o + 1);
+#endif
+
+               boost::shared_ptr<Readable> r = _readables[ir_c % n_chn];
+               assert (r->readable_length () == _max_size);
+               assert (r->n_channels () == 1);
+
+               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;
+                       }
+
+                       rv = _convproc.impdata_create (
+                                       /*i/o map */ io_i, io_o,
+                                       /*stride, de-interleave */1,
+                                       ir,
+                                       _initial_delay + pos, _initial_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
+}
+
+bool
+Convolver::ready () const {
+       return _configured && _convproc.state () == Convproc::ST_PROC;
+}
+
+void
+Convolver::run (float* buf, uint32_t n_samples)
+{
+       assert (_convproc.state () == Convproc::ST_PROC);
+       assert (_irc == Mono);
+
+       uint32_t done = 0;
+       uint32_t remain = n_samples;
+
+       while (remain > 0) {
+               uint32_t ns = std::min (remain, _n_samples - _offset);
+
+               float* const       in  = _convproc.inpdata (/*channel*/0);
+               float const* const out = _convproc.outdata (/*channel*/0);
+
+               memcpy (&in[_offset], &buf[done], sizeof (float) * ns);
+               memcpy (&buf[done], &out[_offset], sizeof (float) * ns);
+
+               _offset += ns;
+               done    += ns;
+               remain  -= ns;
+
+               if (_offset == _n_samples) {
+                       _convproc.process (/*sync, freewheeling*/ true);
+                       _offset = 0;
+               }
+       }
+}
+
+void
+Convolver::run_stereo (float* left, float* right, uint32_t n_samples)
+{
+       assert (_convproc.state () == Convproc::ST_PROC);
+       assert (_irc != Mono);
+
+       uint32_t done = 0;
+       uint32_t remain = n_samples;
+
+       while (remain > 0) {
+               uint32_t ns = std::min (remain, _n_samples - _offset);
+
+               memcpy (&_convproc.inpdata(0)[_offset], &left[done], sizeof (float) * ns);
+               if (_irc >= Stereo) {
+                       memcpy (&_convproc.inpdata(1)[_offset], &right[done], sizeof (float) * ns);
+               }
+               memcpy (&left[done],  &_convproc.outdata(0)[_offset], sizeof (float) * ns);
+               memcpy (&right[done], &_convproc.outdata(1)[_offset], sizeof (float) * ns);
+
+               _offset += ns;
+               done    += ns;
+               remain  -= ns;
+
+               if (_offset == _n_samples) {
+                       _convproc.process (true);
+                       _offset = 0;
+               }
+       }
+}
index 7766d8467a2fe270b0efa3b12151665c2703329a..e579c27b101965cdb9e89e1dd19f7007dc29bfac 100644 (file)
@@ -58,6 +58,7 @@ libardour_sources = [
         'config_text.cc',
         'control_group.cc',
         'control_protocol_manager.cc',
+        'convolver.cc',
         'cycle_timer.cc',
         'data_type.cc',
         'default_click.cc',
@@ -402,6 +403,7 @@ def build(bld):
                         'liblua',
                         'libptformat',
                         'zita-resampler',
+                        'zita-convolver',
                         ]
     if bld.env['build_target'] != 'mingw':
         obj.uselib += ['DL']