From ecdaf7136dc7e9b031fdca8c33da4f33980206e1 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 3 Feb 2021 03:53:15 +0100 Subject: [PATCH] Add dedicated Monitor Port This allows for rt-safe monitoring, collecting data directly from input-ports without requiring a dedicated connection or dynamic ARDOUR::AudioPort creation. --- libs/ardour/ardour/monitor_port.h | 85 ++++++++ libs/ardour/monitor_port.cc | 340 ++++++++++++++++++++++++++++++ libs/ardour/wscript | 1 + 3 files changed, 426 insertions(+) create mode 100644 libs/ardour/ardour/monitor_port.h create mode 100644 libs/ardour/monitor_port.cc diff --git a/libs/ardour/ardour/monitor_port.h b/libs/ardour/ardour/monitor_port.h new file mode 100644 index 0000000000..8d57cd9f03 --- /dev/null +++ b/libs/ardour/ardour/monitor_port.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 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 + * 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_monitor_port_h_ +#define _ardour_monitor_port_h_ + +#include + +#include "zita-resampler/vmresampler.h" + +#include "pbd/rcu.h" + +#include "ardour/audio_buffer.h" + +namespace ARDOUR { + +class LIBARDOUR_API MonitorPort : public boost::noncopyable +{ +public: + ~MonitorPort (); + + void set_buffer_size (pframes_t); + bool silent () const; + AudioBuffer& get_audio_buffer (pframes_t); + + void add_port (std::string const&); + void remove_port (std::string const&, bool instantly = false); + bool monitoring (std::string const& = "") const; + void active_monitors (std::list &) const; + void set_active_monitors (std::list const&); + void clear_ports (bool instantly); + + PBD::Signal2 MonitorInputChanged; + +protected: + friend class PortManager; + MonitorPort (); + + void prepare (std::set&); + void monitor (Sample*, pframes_t, std::string const&); + void finalize (pframes_t); + +private: + struct MonitorInfo { + MonitorInfo () + { + gain = 0; + remove = false; + } + + float gain; + bool remove; + }; + + typedef std::map > MonitorPorts; + + SerializedRCUManager _monitor_ports; + boost::shared_ptr _cycle_ports; + + AudioBuffer* _buffer; + ArdourZita::VMResampler _src; + Sample* _input; + Sample* _data; + pframes_t _insize; + bool _silent; +}; + +} + +#endif diff --git a/libs/ardour/monitor_port.cc b/libs/ardour/monitor_port.cc new file mode 100644 index 0000000000..66bcb90378 --- /dev/null +++ b/libs/ardour/monitor_port.cc @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2021 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 + * 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 + +#include "pbd/malign.h" + +#include "ardour/audioengine.h" +#include "ardour/audio_buffer.h" +#include "ardour/monitor_port.h" +#include "ardour/port.h" +#include "ardour/rc_configuration.h" +#include "ardour/runtime_functions.h" +#include "ardour/session.h" + +#define GAIN_COEFF_DELTA (1e-5) + +using namespace ARDOUR; +using namespace std; + +MonitorPort::MonitorPort () + : _monitor_ports (new MonitorPorts) + , _buffer (new AudioBuffer (0)) + , _input (0) + , _data (0) + , _insize (0) + , _silent (false) +{ + _src.setup (Port::resampler_quality ()); + _src.set_rrfilt (10); +} + +MonitorPort::~MonitorPort () +{ + if (_input) { + cache_aligned_free (_input); + } + if (_data) { + cache_aligned_free (_data); + } + delete _buffer; +} + +void +MonitorPort::set_buffer_size (pframes_t n_samples) +{ + if (_input) { + cache_aligned_free (_input); + } + if (_data) { + cache_aligned_free (_data); + } + cache_aligned_malloc ((void**) &_input, sizeof (Sample) * n_samples); + cache_aligned_malloc ((void**) &_data, sizeof (Sample) * lrint (floor (n_samples * Config->get_max_transport_speed ()))); + _insize = n_samples; + _silent = false; +} + +bool +MonitorPort::silent () const +{ + return _silent; +} + +void +MonitorPort::prepare (std::set& portset) +{ + if (!_silent) { + memset (_input, 0, sizeof (Sample) * _insize); + _silent = true; + } + + _cycle_ports = _monitor_ports.reader (); + for (MonitorPorts::iterator i = _cycle_ports->begin (); i != _cycle_ports->end(); ++i) { + if (i->second->remove && i->second->gain == 0) { + continue; + } + portset.insert (i->first); + } +} + +void +MonitorPort::monitor (Sample* buf, pframes_t n_samples, std::string const& pn) +{ + MonitorPorts::iterator i = _cycle_ports->find (pn); + gain_t target_gain = i->second->remove ? 0.0 : 1.0; + gain_t current_gain = i->second->gain; + + if (target_gain == current_gain && target_gain == 0) { + return; + } + if (target_gain == current_gain) { + if (_silent) { + copy_vector (_input, buf, n_samples); + } else { + mix_buffers_no_gain (_input, buf, n_samples); + } + } else { + /* fade in/out */ + Session* s = AudioEngine::instance()->session (); + assert (s); + const float a = 800.f / (gain_t)s->nominal_sample_rate() ; // ~ 1/50Hz to fade by 40dB + const int max_nproc = 4; + uint32_t remain = n_samples; + uint32_t offset = 0; + + while (remain > 0) { + uint32_t n_proc = remain > max_nproc ? max_nproc : remain; + for (uint32_t i = 0; i < n_proc; ++i) { + _input[i + offset] += current_gain * buf[i + offset]; + } + current_gain += a * (target_gain - current_gain); + remain -= n_proc; + offset += n_proc; + } + if (fabsf (current_gain - target_gain) < GAIN_COEFF_DELTA) { + i->second->gain = target_gain; +#if 1 // not strictly needed + if (target_gain == 0) { + /* remove port from list, uses RCUWriter */ + remove_port (pn, true); + } +#endif + } else { + i->second->gain = current_gain; + } + } + _silent = false; +} + +void +MonitorPort::finalize (pframes_t n_samples) +{ + _src.inp_data = (float*)_input; + _src.inp_count = n_samples; + _src.out_count = Port::cycle_nframes (); + _src.set_rratio (Port::cycle_nframes () / (double)n_samples); + _src.out_data = _data; + _src.process (); + + while (_src.out_count > 0) { + *_src.out_data = _src.out_data[-1]; + ++_src.out_data; + --_src.out_count; + } + _cycle_ports.reset (); +} + +AudioBuffer& +MonitorPort::get_audio_buffer (pframes_t n_samples) +{ + /* caller must hold process lock */ + + /* _data was read and resampled as necessary in ::cycle_start */ + Sample* addr = &_data[Port::port_offset ()]; + _buffer->set_data (addr, n_samples); + return *_buffer; +} + +bool +MonitorPort::monitoring (std::string const& pn) const +{ + boost::shared_ptr mp = _monitor_ports.reader (); + if (pn.empty ()) { + for (MonitorPorts::iterator i = mp->begin (); i != mp->end(); ++i) { + if (!i->second->remove) { + return true; + } + } + return false; + } + MonitorPorts::iterator i = mp->find (pn); + if (i == mp->end ()) { + return false; + } + return !i->second->remove; +} + +void +MonitorPort::active_monitors (std::list& portlist) const +{ + boost::shared_ptr mp = _monitor_ports.reader (); + for (MonitorPorts::iterator i = mp->begin (); i != mp->end(); ++i) { + if (i->second->remove) { + continue; + } + portlist.push_back (i->first); + } +} + +void +MonitorPort::set_active_monitors (std::list const& pl) +{ + if (pl.empty () && !monitoring ()) { + return; + } + + std::list removals; + std::list additions; + + { + RCUWriter mp_rcu (_monitor_ports); + boost::shared_ptr mp = mp_rcu.get_copy (); + /* clear ports not present in portlist */ + for (MonitorPorts::iterator i = mp->begin (); i != mp->end (); ++i) { + if (std::find (pl.begin (), pl.end (), i->first) != pl.end ()) { + continue; + } + if (i->second->remove) { + continue; + } + i->second->remove = true; + removals.push_back (i->first); + } + /* add ports */ + for (std::list::const_iterator i = pl.begin (); i != pl.end (); ++i) { + std::pair it = mp->insert (make_pair (*i, boost::shared_ptr (new MonitorInfo ()))); + if (!it.second && !it.first->second->remove) { + /* already present */ + continue; + } + it.first->second->remove = false; + additions.push_back (*i); + } + } + + for (std::list::const_iterator i = removals.begin (); i != removals.end (); ++i) { + MonitorInputChanged (*i, false); /* EMIT SIGNAL */ + } + for (std::list::const_iterator i = additions.begin (); i != additions.end (); ++i) { + MonitorInputChanged (*i, true); /* EMIT SIGNAL */ + } + if (!removals.empty () || !additions.empty ()) { + AudioEngine::instance()->session ()->SoloChanged (); /* EMIT SIGNAL */ + } +} + +void +MonitorPort::add_port (std::string const& pn) +{ + Session* s = AudioEngine::instance()->session (); + if (!s) { + return; + } + assert (s->monitor_out ()); + assert (!AudioEngine::instance()->port_is_mine (pn)); + + { + RCUWriter mp_rcu (_monitor_ports); + boost::shared_ptr mp = mp_rcu.get_copy (); + std::pair it = mp->insert (make_pair (pn, boost::shared_ptr (new MonitorInfo ()))); + if (!it.second) { + if (!it.first->second->remove) { + /* already present */ + return; + } + /* in case it was recently removed and still fades */ + it.first->second->remove = false; + } + } + + MonitorInputChanged (pn, true); /* EMIT SIGNAL */ + s->SoloChanged (); /* EMIT SIGNAL */ +} + +void +MonitorPort::remove_port (std::string const& pn, bool instantly) +{ + Session* s = AudioEngine::instance()->session (); + if (!s) { + return; + } + + { + RCUWriter mp_rcu (_monitor_ports); + boost::shared_ptr mp = mp_rcu.get_copy (); + MonitorPorts::iterator i = mp->find (pn); + if (i == mp->end ()) { + return; + } + if (instantly) { + mp->erase (i); + } else { + i->second->remove = true; // queue fade out + } + } + + MonitorInputChanged (pn, false); /* EMIT SIGNAL */ + s->SoloChanged (); /* EMIT SIGNAL */ +} + +void +MonitorPort::clear_ports (bool instantly) +{ + Session* s = AudioEngine::instance()->session (); + if (!s) { + instantly = true; + } + MonitorPorts copy; + + if (instantly) { + RCUWriter mp_rcu (_monitor_ports); + boost::shared_ptr mp = mp_rcu.get_copy (); + mp->swap (copy); + assert (mp->empty ()); + } else { + boost::shared_ptr mp = _monitor_ports.reader (); + copy = *mp; + for (MonitorPorts::iterator i = copy.begin (); i != copy.end(); ++i) { + i->second->remove = true; + } + } + + for (MonitorPorts::iterator i = copy.begin (); i != copy.end(); ++i) { + MonitorInputChanged (i->first, false); /* EMIT SIGNAL */ + } + + if (!s) { + return; + } + + if (!copy.empty ()) { + s->SoloChanged (); /* EMIT SIGNAL */ + } +} diff --git a/libs/ardour/wscript b/libs/ardour/wscript index ef2761db66..4146333533 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -149,6 +149,7 @@ libardour_sources = [ 'mix.cc', 'mode.cc', 'monitor_control.cc', + 'monitor_port.cc', 'monitor_processor.cc', 'mp3fileimportable.cc', 'mp3filesource.cc',