From 8ba7df9105fc84298a952c8704636f7f57c164b4 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Mon, 26 Oct 2020 15:52:17 +0100 Subject: [PATCH] Add input port signal meters, scopes and monitors --- libs/ardour/ardour/port_manager.h | 77 ++++++++++++++- libs/ardour/audioengine.cc | 2 +- libs/ardour/luabindings.cc | 25 ++++- libs/ardour/port_manager.cc | 158 ++++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+), 4 deletions(-) diff --git a/libs/ardour/ardour/port_manager.h b/libs/ardour/ardour/port_manager.h index fd8fd102b0..9a8c33e7b9 100644 --- a/libs/ardour/ardour/port_manager.h +++ b/libs/ardour/ardour/port_manager.h @@ -42,11 +42,55 @@ class PortEngine; class AudioBackend; class Session; +class CircularSampleBuffer; +class CircularEventBuffer; + class LIBARDOUR_API PortManager { public: - typedef std::map > Ports; - typedef std::list > PortList; + struct DPM { + DPM () + { + reset (); + } + void reset () + { + level = 0; + peak = 0; + } + Sample level; + Sample peak; + }; + + struct MPM { + MPM () + { + reset (); + } + void reset () + { + memset (chn_active, 0, sizeof (float) * 17); + } + bool active (int chn) const { + if (chn < 0 || chn > 16) { + return false; + } + return chn_active[chn] > 0.1; + } + /* 0..15: MIDI Channel Event, 16: System Common Message */ + float chn_active[17]; + }; + + typedef std::map> Ports; + typedef std::list> PortList; + + typedef boost::shared_ptr AudioPortScope; + typedef std::map AudioPortScopes; + typedef std::map AudioPortMeters; + + typedef boost::shared_ptr MIDIPortMonitor; + typedef std::map MIDIPortMonitors; + typedef std::map MIDIPortMeters; PortManager (); virtual ~PortManager () {} @@ -199,6 +243,26 @@ public: */ PBD::Signal5, std::string, boost::weak_ptr, std::string, bool> PortConnectedOrDisconnected; + void reset_input_meters (); + + AudioPortMeters const& input_meters () const + { + return _audio_port_meters; + } + AudioPortScopes const& input_scopes () const + { + return _audio_port_scopes; + } + + MIDIPortMeters const& midi_meters () const + { + return _midi_port_meters; + } + MIDIPortMonitors const& midi_monitors () const + { + return _midi_port_monitors; + } + protected: boost::shared_ptr _backend; @@ -245,6 +309,15 @@ protected: void filter_midi_ports (std::vector&, MidiPortFlags, MidiPortFlags); void set_port_buffer_sizes (pframes_t); + +private: + void run_input_meters (pframes_t, samplecnt_t); + + AudioPortMeters _audio_port_meters; + AudioPortScopes _audio_port_scopes; + MIDIPortMeters _midi_port_meters; + MIDIPortMonitors _midi_port_monitors; + volatile gint _reset_meters; }; } // namespace ARDOUR diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc index b3fed5113b..93880a48e8 100644 --- a/libs/ardour/audioengine.cc +++ b/libs/ardour/audioengine.cc @@ -301,7 +301,7 @@ AudioEngine::process_callback (pframes_t nframes) if (_session && _init_countdown > 0) { --_init_countdown; /* Warm up caches */ - PortManager::cycle_start (nframes); + PortManager::cycle_start (nframes, _session); _session->process (nframes); PortManager::silence (nframes); PortManager::cycle_end (nframes); diff --git a/libs/ardour/luabindings.cc b/libs/ardour/luabindings.cc index 2e300c6e3e..b9de0a8d04 100644 --- a/libs/ardour/luabindings.cc +++ b/libs/ardour/luabindings.cc @@ -819,7 +819,7 @@ LuaBindings::common (lua_State* L) .beginClass ("PropertyChange") // TODO add special handling (std::set), PropertyID is a GQuark. - // -> direct map to lua table beginStdSet()A + // -> direct map to lua table beginStdSet() // // expand templated PropertyDescriptor .addFunction ("containsBool", &PBD::PropertyChange::contains) @@ -1396,6 +1396,19 @@ LuaBindings::common (lua_State* L) .endNamespace () /* ARDOUR::MidiModel */ + .beginClass ("DPM") + .addVoidConstructor () + .addFunction ("reset", &PortManager::DPM::reset) + .addData ("level", &PortManager::DPM::level, false) + .addData ("peak", &PortManager::DPM::peak, false) + .endClass () + + .beginClass ("MPM") + .addVoidConstructor () + .addFunction ("reset", &PortManager::MPM::reset) + .addFunction ("active", &PortManager::MPM::active) + .endClass () + .beginClass ("PresetRecord") .addVoidConstructor () .addData ("uri", &Plugin::PresetRecord::uri, false) @@ -1774,6 +1787,14 @@ LuaBindings::common (lua_State* L) .beginStdMap > ("RegionMap") .endClass () + // typedef std::map PortManager::AudioPortMeters; + .beginStdMap ("AudioPortMeters") + .endClass () + + // typedef std::map PortManager::MIDIPortMeters; + .beginStdMap ("MIDIPortMeters") + .endClass () + // typedef std::list > ProcessorList .beginStdList > ("ProcessorList") .endClass () @@ -2335,6 +2356,8 @@ LuaBindings::common (lua_State* L) .addFunction ("get_physical_inputs", &PortManager::get_physical_inputs) .addFunction ("n_physical_outputs", &PortManager::n_physical_outputs) .addFunction ("n_physical_inputs", &PortManager::n_physical_inputs) + .addFunction ("reset_input_meters", &PortManager::reset_input_meters) + .addFunction ("input_meters", &PortManager::input_meters) .addRefFunction ("get_connections", &PortManager::get_connections) .addRefFunction ("get_ports", (int (PortManager::*)(DataType, PortManager::PortList&))&PortManager::get_ports) .addRefFunction ("get_backend_ports", (int (PortManager::*)(const std::string&, DataType, PortFlags, std::vector&))&PortManager::get_ports) diff --git a/libs/ardour/port_manager.cc b/libs/ardour/port_manager.cc index 02da3b219d..f801017478 100644 --- a/libs/ardour/port_manager.cc +++ b/libs/ardour/port_manager.cc @@ -37,6 +37,7 @@ #include "ardour/async_midi_port.h" #include "ardour/audio_backend.h" #include "ardour/audio_port.h" +#include "ardour/circular_buffer.h" #include "ardour/debug.h" #include "ardour/filesystem_paths.h" #include "ardour/midi_port.h" @@ -633,6 +634,12 @@ PortManager::reestablish_ports () return -1; } + _audio_port_scopes.clear(); + _audio_port_meters.clear(); + _midi_port_monitors.clear(); + _midi_port_meters.clear(); + run_input_meters (0, 0); + return 0; } @@ -821,6 +828,8 @@ PortManager::cycle_start (pframes_t nframes, Session* s) } } } + + run_input_meters (nframes, s ? s->nominal_sample_rate () : 0); } void @@ -1403,3 +1412,152 @@ PortManager::check_for_ambiguous_latency (bool log) const } return rv; } + +void +PortManager::reset_input_meters () +{ + g_atomic_int_set (&_reset_meters, 1); +} + +/* Cache dB -> coefficient calculation for dB/sec falloff. + * @n_samples engine-buffer-size + * @rate engine sample-rate + * @return coefficiant taking user preferences meter_falloff (dB/sec) into account + */ +struct FallOffCache +{ + FallOffCache () + : _falloff (1.0) + , _cfg_db_s (0) + , _n_samples (0) + , _rate (0) + {} + + float calc (pframes_t n_samples, samplecnt_t rate) + { + if (n_samples == 0 || rate == 0) { + return 1.0; + } + + if (Config->get_meter_falloff () != _cfg_db_s || n_samples != _n_samples || rate != _rate) { + _cfg_db_s = Config->get_meter_falloff (); + _n_samples = n_samples; + _rate = rate; +#ifdef _GNU_SOURCE + _falloff = exp10f (-0.05f * _cfg_db_s * _n_samples / _rate); +#else + _falloff = powf (10.f, -0.05f * _cfg_db_s * _n_samples / _rate); +#endif + } + + return _falloff; + } + + private: + float _falloff; + float _cfg_db_s; + pframes_t _n_samples; + samplecnt_t _rate; +}; + +static FallOffCache falloff_cache; + +void +PortManager::run_input_meters (pframes_t n_samples, samplecnt_t rate) +{ + const bool reset = g_atomic_int_compare_and_exchange (&_reset_meters, 1, 0); + + const float falloff = falloff_cache.calc (n_samples, rate); + + /* calculate peak of all physical inputs (readable ports) */ + std::vector port_names; + get_physical_inputs (DataType::AUDIO, port_names); + for (std::vector::iterator p = port_names.begin(); p != port_names.end(); ++p) { + if (port_is_mine (*p)) { + continue; + } + PortEngine::PortHandle ph = _backend->get_port_by_name (*p); + if (!ph) { + continue; + } + if (n_samples == 0) { + /* allocate using default c'tor */ + _audio_port_meters[*p]; + _audio_port_scopes[*p] = AudioPortScope (new CircularSampleBuffer (524288)); // 2^19 ~ 1MB / port + continue; + } + + if (reset) { + _audio_port_meters[*p].reset (); + } + + Sample* buf = (Sample*) _backend->get_buffer (ph, n_samples); + if (!buf) { + continue; + } + + _audio_port_scopes[*p]->write (buf, n_samples); + + + /* falloff */ + if (_audio_port_meters[*p].level > 1e-10) { + _audio_port_meters[*p].level *= falloff; + } else { + _audio_port_meters[*p].level = 0; + } + + _audio_port_meters[*p].level = compute_peak (buf, n_samples, reset ? 0 : _audio_port_meters[*p].level); + _audio_port_meters[*p].level = std::min (_audio_port_meters[*p].level, 100.f); // cut off at +40dBFS for falloff. + _audio_port_meters[*p].peak = std::max (_audio_port_meters[*p].peak, _audio_port_meters[*p].level); + } + + /* MIDI */ + port_names.clear (); + get_physical_inputs (DataType::MIDI, port_names); + for (std::vector::iterator p = port_names.begin(); p != port_names.end(); ++p) { + if (port_is_mine (*p)) { + continue; + } + PortEngine::PortHandle ph = _backend->get_port_by_name (*p); + if (!ph) { + continue; + } + + if (n_samples == 0) { + /* allocate using default c'tor */ + _midi_port_meters[*p]; + _midi_port_monitors[*p] = MIDIPortMonitor (new CircularEventBuffer (32)); + continue; + } + + for (size_t i = 0; i < 17; ++i) { + /* falloff */ + if (_midi_port_meters[*p].chn_active[i] > 1e-10) { + _midi_port_meters[*p].chn_active[i] *= falloff; + } else { + _midi_port_meters[*p].chn_active[i] = 0; + } + } + + void* buffer = _backend->get_buffer (ph, n_samples); + const pframes_t event_count = _backend->get_midi_event_count (buffer); + + for (pframes_t i = 0; i < event_count; ++i) { + pframes_t timestamp; + size_t size; + uint8_t const* buf; + _backend->midi_event_get (timestamp, size, &buf, buffer, i); + if (buf[0] == 0xfe) { + /* ignore active sensing */ + continue; + } + if ((buf[0] & 0xf0) == 0xf0) { + _midi_port_meters[*p].chn_active[16] = 1.0; + } else { + int chn = (buf[0] & 0x0f); + _midi_port_meters[*p].chn_active[chn] = 1.0; + } + _midi_port_monitors[*p]->write (buf, size); + } + } +}