2025-03-29 03:17:06 +01:00
|
|
|
/*
|
|
|
|
|
* Copyright (C) 2025 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.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#ifdef WAF_BUILD
|
|
|
|
|
#include "gtk2ardour-config.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-08-26 18:11:03 +02:00
|
|
|
#include "pbd/unwind.h"
|
2025-03-26 23:57:52 +01:00
|
|
|
#include "pbd/types_convert.h"
|
|
|
|
|
|
2025-03-29 03:17:06 +01:00
|
|
|
#include "ardour/rt_safe_delete.h"
|
|
|
|
|
#include "ardour/session.h"
|
|
|
|
|
|
|
|
|
|
#include "gui_thread.h"
|
|
|
|
|
#include "rta_manager.h"
|
|
|
|
|
#include "timers.h"
|
|
|
|
|
#include "ui_config.h"
|
|
|
|
|
|
2025-03-26 23:57:52 +01:00
|
|
|
#include "pbd/i18n.h"
|
|
|
|
|
|
2025-03-29 03:17:06 +01:00
|
|
|
using namespace ARDOUR;
|
|
|
|
|
|
|
|
|
|
RTAManager* RTAManager::_instance = 0;
|
|
|
|
|
|
|
|
|
|
RTAManager*
|
|
|
|
|
RTAManager::instance ()
|
|
|
|
|
{
|
|
|
|
|
if (!_instance) {
|
|
|
|
|
_instance = new RTAManager;
|
|
|
|
|
}
|
|
|
|
|
return _instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTAManager::RTAManager ()
|
2025-03-26 23:57:52 +01:00
|
|
|
: _active (false)
|
|
|
|
|
, _speed (ARDOUR::DSP::PerceptualAnalyzer::Moderate)
|
|
|
|
|
, _warp (ARDOUR::DSP::PerceptualAnalyzer::Medium)
|
2025-08-26 18:11:03 +02:00
|
|
|
, _clearing (false)
|
2025-03-29 03:17:06 +01:00
|
|
|
{
|
2025-10-15 01:42:41 +02:00
|
|
|
UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &RTAManager::parameter_changed));
|
2025-03-29 03:17:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTAManager::~RTAManager ()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
XMLNode&
|
|
|
|
|
RTAManager::get_state () const
|
|
|
|
|
{
|
|
|
|
|
XMLNode* node = new XMLNode ("RTAManager");
|
2025-03-26 23:57:52 +01:00
|
|
|
node->set_property (X_("rta-warp"), _warp);
|
|
|
|
|
node->set_property (X_("rta-speed"), _speed);
|
|
|
|
|
for (auto& r : _rta) {
|
|
|
|
|
node->add_child (X_("RouteID"))->set_property (X_("id"), r.route ()->id ());
|
|
|
|
|
}
|
2025-03-29 03:17:06 +01:00
|
|
|
return *node;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
RTAManager::set_session (ARDOUR::Session* s)
|
|
|
|
|
{
|
|
|
|
|
if (!s) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
SessionHandlePtr::set_session (s);
|
|
|
|
|
|
2025-03-26 23:57:52 +01:00
|
|
|
XMLNode* node = _session->instant_xml (X_("RTAManager"));
|
|
|
|
|
if (node) {
|
|
|
|
|
node->get_property ("rta-warp", _warp);
|
|
|
|
|
node->get_property ("rta-speed", _speed);
|
|
|
|
|
SettingsChanged (); /* EMIT SIGNAL */
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-29 03:17:06 +01:00
|
|
|
if (_session->master_out ()) {
|
|
|
|
|
attach (_session->master_out ());
|
|
|
|
|
}
|
2025-03-26 23:57:52 +01:00
|
|
|
|
|
|
|
|
if (node) {
|
|
|
|
|
for (auto const& n : node->children ()) {
|
|
|
|
|
if (n->name () != X_("RouteID")) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
PBD::ID id;
|
|
|
|
|
if (!n->get_property (X_("id"), id)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
std::shared_ptr<ARDOUR::Route> route = _session->route_by_id (id);
|
|
|
|
|
if (route) {
|
|
|
|
|
attach (route);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-29 03:17:06 +01:00
|
|
|
_update_connection = Timers::super_rapid_connect (sigc::mem_fun (*this, &RTAManager::run_rta));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
RTAManager::session_going_away ()
|
|
|
|
|
{
|
|
|
|
|
ENSURE_GUI_THREAD (*this, &RTAManager::session_going_away);
|
|
|
|
|
_update_connection.disconnect ();
|
2025-03-26 23:57:52 +01:00
|
|
|
_rta.clear ();
|
2025-03-29 03:17:06 +01:00
|
|
|
|
|
|
|
|
SessionHandlePtr::session_going_away ();
|
|
|
|
|
_session = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-15 01:42:41 +02:00
|
|
|
void
|
|
|
|
|
RTAManager::parameter_changed (std::string p)
|
|
|
|
|
{
|
|
|
|
|
if (p == "max-active-rta") {
|
|
|
|
|
limit_active_rta ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-29 03:17:06 +01:00
|
|
|
void
|
|
|
|
|
RTAManager::set_active (bool en)
|
|
|
|
|
{
|
2025-03-26 23:57:52 +01:00
|
|
|
if (_active == en) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_active = en;
|
|
|
|
|
if (_active) {
|
|
|
|
|
for (auto& r : _rta) {
|
|
|
|
|
r.reset ();
|
|
|
|
|
r.delivery ()->set_analysis_active (true);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (auto const& r : _rta) {
|
|
|
|
|
r.delivery ()->set_analysis_active (false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
RTAManager::set_rta_speed (DSP::PerceptualAnalyzer::Speed s)
|
|
|
|
|
{
|
|
|
|
|
if (_speed == s) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_speed = s;
|
|
|
|
|
for (auto& r : _rta) {
|
|
|
|
|
r.set_rta_speed (s);
|
|
|
|
|
}
|
|
|
|
|
SettingsChanged (); /* EMIT SIGNAL */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
RTAManager::set_rta_warp (DSP::PerceptualAnalyzer::Warp w)
|
|
|
|
|
{
|
|
|
|
|
if (_warp == w) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_warp = w;
|
|
|
|
|
for (auto& r : _rta) {
|
|
|
|
|
r.set_rta_warp (w);
|
|
|
|
|
}
|
|
|
|
|
SettingsChanged (); /* EMIT SIGNAL */
|
2025-03-29 03:17:06 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-15 01:42:41 +02:00
|
|
|
void
|
|
|
|
|
RTAManager::limit_active_rta ()
|
|
|
|
|
{
|
|
|
|
|
uint32_t max_rta = UIConfiguration::instance().get_max_active_rta ();
|
|
|
|
|
if (max_rta > 1 && _rta.size () >= max_rta) {
|
|
|
|
|
#if 0
|
|
|
|
|
remove (_rta.back ().route ());
|
|
|
|
|
#else
|
|
|
|
|
for (auto const& r : _rta) {
|
|
|
|
|
if (r.route()->is_master ()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
remove (r.route ());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-29 03:17:06 +01:00
|
|
|
void
|
|
|
|
|
RTAManager::attach (std::shared_ptr<ARDOUR::Route> route)
|
|
|
|
|
{
|
2025-03-26 23:57:52 +01:00
|
|
|
for (auto const& r : _rta) {
|
|
|
|
|
if (r.route () == route) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-15 01:42:41 +02:00
|
|
|
|
|
|
|
|
limit_active_rta ();
|
|
|
|
|
|
2025-08-28 18:09:45 +02:00
|
|
|
std::list<RTA>::iterator i;
|
2025-03-26 23:57:52 +01:00
|
|
|
try {
|
2025-08-28 18:09:45 +02:00
|
|
|
/* render master first (as background) because it is the sum of other individual channels */
|
|
|
|
|
if (route->is_master ()) {
|
|
|
|
|
_rta.emplace_front (route);
|
|
|
|
|
i = _rta.begin();
|
|
|
|
|
} else {
|
|
|
|
|
_rta.emplace_back (route);
|
|
|
|
|
i = --_rta.end();
|
|
|
|
|
}
|
2025-03-26 23:57:52 +01:00
|
|
|
} catch (...) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-28 18:09:45 +02:00
|
|
|
i->set_rta_speed (_speed);
|
|
|
|
|
i->set_rta_warp (_warp);
|
|
|
|
|
i->delivery ()->set_analysis_active (_active);
|
2025-03-26 23:57:52 +01:00
|
|
|
|
2025-04-01 15:52:58 +02:00
|
|
|
route->gui_changed ("rta", this); /* EMIT SIGNAL */
|
2025-03-26 23:57:52 +01:00
|
|
|
route->DropReferences.connect (*this, invalidator (*this), std::bind (&RTAManager::route_removed, this, std::weak_ptr<Route> (route)), gui_context ());
|
2025-03-29 03:17:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
RTAManager::remove (std::shared_ptr<ARDOUR::Route> route)
|
|
|
|
|
{
|
2025-08-26 18:11:03 +02:00
|
|
|
if (_clearing) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-03-26 23:57:52 +01:00
|
|
|
_rta.remove_if ([route] (RTAManager::RTA const& r) { return r.route () == route; });
|
2025-04-01 15:52:58 +02:00
|
|
|
route->gui_changed ("rta", this); /* EMIT SIGNAL */
|
2025-04-09 18:41:45 +02:00
|
|
|
|
|
|
|
|
if (_rta.empty ()) {
|
2025-08-20 12:49:57 +02:00
|
|
|
SignalReady (); /* EMIT SIGNAL */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
RTAManager::clear ()
|
|
|
|
|
{
|
2025-08-26 18:11:03 +02:00
|
|
|
PBD::Unwinder<bool> uw (_clearing, true);
|
|
|
|
|
|
2025-08-20 12:49:57 +02:00
|
|
|
std::list<RTA>::iterator i = _rta.begin ();
|
|
|
|
|
while (i != _rta.end ()) {
|
|
|
|
|
std::shared_ptr<ARDOUR::Route> route = i->route ();
|
|
|
|
|
if (route->is_master ()) {
|
|
|
|
|
++i;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
i = _rta.erase (i);
|
|
|
|
|
route->gui_changed ("rta", this); /* EMIT SIGNAL */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_rta.empty ()) {
|
2025-04-09 18:41:45 +02:00
|
|
|
SignalReady (); /* EMIT SIGNAL */
|
|
|
|
|
}
|
2025-03-29 03:17:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
RTAManager::attached (std::shared_ptr<ARDOUR::Route> route) const
|
|
|
|
|
{
|
2025-03-26 23:57:52 +01:00
|
|
|
return std::find_if (_rta.begin (), _rta.end (), [route] (RTAManager::RTA const& r) { return r.route () == route; }) != _rta.end ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
RTAManager::route_removed (std::weak_ptr<Route> wr)
|
|
|
|
|
{
|
|
|
|
|
if (!_session || _session->inital_connect_or_deletion_in_progress ()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::shared_ptr<Route> route (wr.lock ());
|
|
|
|
|
if (route) {
|
|
|
|
|
remove (route);
|
|
|
|
|
}
|
2025-03-29 03:17:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
RTAManager::run_rta ()
|
|
|
|
|
{
|
2025-03-26 23:57:52 +01:00
|
|
|
bool have_new_data = false;
|
|
|
|
|
|
|
|
|
|
for (auto& r : _rta) {
|
|
|
|
|
have_new_data |= r.run ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (have_new_data) {
|
|
|
|
|
SignalReady (); /* EMIT SIGNAL */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ****************************************************************************/
|
|
|
|
|
|
|
|
|
|
RTAManager::RTA::RTA (std::shared_ptr<ARDOUR::Route> r)
|
|
|
|
|
: _route (r)
|
|
|
|
|
, _rate (r->session ().nominal_sample_rate ())
|
|
|
|
|
, _blocksize (_rate > 64000 ? 16384 : 8192)
|
|
|
|
|
, _stepsize (_blocksize / 4) // must be >= PerceptualAnalyzer::fftlen (512) and <= blocksize and a power of two
|
|
|
|
|
, _offset (0)
|
|
|
|
|
, _speed (ARDOUR::DSP::PerceptualAnalyzer::Moderate)
|
|
|
|
|
, _warp (ARDOUR::DSP::PerceptualAnalyzer::Medium)
|
|
|
|
|
{
|
|
|
|
|
if (!init ()) {
|
|
|
|
|
throw failed_constructor ();
|
|
|
|
|
}
|
|
|
|
|
_route->io_changed.connect (_route_connections, invalidator (*this), std::bind (&RTAManager::RTA::route_io_changed, this), gui_context ());
|
|
|
|
|
_route->processors_changed.connect (_route_connections, invalidator (*this), std::bind (&RTAManager::RTA::route_io_changed, this), gui_context ());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTAManager::RTA::~RTA ()
|
|
|
|
|
{
|
|
|
|
|
_route_connections.drop_connections ();
|
|
|
|
|
delivery ()->set_analysis_active (false);
|
|
|
|
|
RTABufferListPtr unset;
|
|
|
|
|
delivery ()->set_analysis_buffers (unset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<Route>
|
|
|
|
|
RTAManager::RTA::route () const
|
|
|
|
|
{
|
|
|
|
|
return _route;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<Delivery>
|
|
|
|
|
RTAManager::RTA::delivery () const
|
|
|
|
|
{
|
|
|
|
|
return _route->main_outs ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<ARDOUR::DSP::PerceptualAnalyzer*> const&
|
|
|
|
|
RTAManager::RTA::analyzers () const
|
|
|
|
|
{
|
|
|
|
|
return _analyzers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
RTAManager::RTA::init ()
|
|
|
|
|
{
|
|
|
|
|
for (auto a : _analyzers) {
|
|
|
|
|
delete a;
|
|
|
|
|
}
|
|
|
|
|
_analyzers.clear ();
|
|
|
|
|
_ringbuffers.reset ();
|
|
|
|
|
|
|
|
|
|
uint32_t n_audio = delivery ()->input_streams ().n_audio ();
|
|
|
|
|
bool was_active = delivery ()->analysis_active ();
|
|
|
|
|
|
|
|
|
|
delivery ()->set_analysis_active (false);
|
|
|
|
|
|
|
|
|
|
if (n_audio == 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_ringbuffers = RTABufferListPtr (new RTABufferList (), std::bind (&rt_safe_delete<RTABufferList>, &_route->session (), _1));
|
|
|
|
|
for (uint32_t n = 0; n < n_audio; ++n) {
|
|
|
|
|
_ringbuffers->emplace_back (new RTARingBuffer (_rate), std::bind (&rt_safe_delete<RTARingBuffer>, &_route->session (), _1));
|
|
|
|
|
_analyzers.emplace_back (new PerceptualAnalyzer (_rate, _blocksize));
|
|
|
|
|
_analyzers.back ()->set_speed (_speed);
|
|
|
|
|
_analyzers.back ()->set_speed (_warp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert (_analyzers.size () == _ringbuffers->size ());
|
|
|
|
|
|
|
|
|
|
delivery ()->set_analysis_buffers (_ringbuffers);
|
|
|
|
|
delivery ()->set_analysis_active (was_active);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
RTAManager::RTA::route_io_changed ()
|
|
|
|
|
{
|
|
|
|
|
if (_analyzers.size () != delivery ()->input_streams ().n_audio ()) {
|
|
|
|
|
init ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
RTAManager::RTA::reset ()
|
|
|
|
|
{
|
|
|
|
|
for (auto const& a : _analyzers) {
|
|
|
|
|
a->reset ();
|
|
|
|
|
memset (a->ipdata (), 0, _blocksize * sizeof (float));
|
|
|
|
|
}
|
|
|
|
|
for (auto& r : *_ringbuffers) {
|
|
|
|
|
r->increment_read_idx (r->read_space ());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
RTAManager::RTA::set_rta_speed (ARDOUR::DSP::PerceptualAnalyzer::Speed s)
|
|
|
|
|
{
|
|
|
|
|
_speed = s;
|
|
|
|
|
for (auto& a : _analyzers) {
|
|
|
|
|
a->set_speed (s);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
RTAManager::RTA::set_rta_warp (ARDOUR::DSP::PerceptualAnalyzer::Warp w)
|
|
|
|
|
{
|
|
|
|
|
_warp = w;
|
|
|
|
|
for (auto& a : _analyzers) {
|
|
|
|
|
a->set_wfact (w);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
RTAManager::RTA::run ()
|
|
|
|
|
{
|
|
|
|
|
bool have_new_data = false;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
for (auto& rb : *_ringbuffers) {
|
|
|
|
|
if (rb->read_space () < _stepsize) {
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* we can process all channels */
|
|
|
|
|
auto rtaiter = _analyzers.begin ();
|
|
|
|
|
for (auto& rb : *_ringbuffers) {
|
|
|
|
|
rb->read ((*rtaiter)->ipdata () + _offset, _stepsize);
|
|
|
|
|
(*rtaiter)->process (_stepsize);
|
|
|
|
|
have_new_data |= (*rtaiter)->power ()->_valid;
|
|
|
|
|
++rtaiter;
|
|
|
|
|
}
|
|
|
|
|
_offset = (_offset + _stepsize) % _blocksize;
|
|
|
|
|
}
|
|
|
|
|
out:
|
|
|
|
|
return have_new_data;
|
2025-03-29 03:17:06 +01:00
|
|
|
}
|