diff options
author | Robin Gareus <robin@gareus.org> | 2018-10-11 15:29:10 +0200 |
---|---|---|
committer | Robin Gareus <robin@gareus.org> | 2018-10-20 00:24:38 +0200 |
commit | 087b8d7f338c1fd695ca48b3d5e427986df6f3c3 (patch) | |
tree | cefed3440662d0723f664941e383d8ce74211665 /libs/ardour/convolver.cc | |
parent | fec787c8f4ec3acfded49948ae5b651f79f17488 (diff) |
Implement basic libardour convolution DSP
Diffstat (limited to 'libs/ardour/convolver.cc')
-rw-r--r-- | libs/ardour/convolver.cc | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/libs/ardour/convolver.cc b/libs/ardour/convolver.cc new file mode 100644 index 0000000000..bd00dccbe5 --- /dev/null +++ b/libs/ardour/convolver.cc @@ -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; + } + } +} |