From 7547f02c07875ba053e1c095e542f85291d7af5f Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Mon, 2 May 2016 13:58:51 +0200 Subject: [PATCH] prepare loudness normalization --- .../audiographer/general/analyser.h | 23 +-- .../audiographer/general/loudness_reader.h | 64 ++++++ libs/audiographer/src/general/analyser.cc | 46 +---- .../src/general/loudness_reader.cc | 194 ++++++++++++++++++ libs/audiographer/wscript | 1 + 5 files changed, 268 insertions(+), 60 deletions(-) create mode 100644 libs/audiographer/audiographer/general/loudness_reader.h create mode 100644 libs/audiographer/src/general/loudness_reader.cc diff --git a/libs/audiographer/audiographer/general/analyser.h b/libs/audiographer/audiographer/general/analyser.h index e8ca534954..9bd49b33c8 100644 --- a/libs/audiographer/audiographer/general/analyser.h +++ b/libs/audiographer/audiographer/general/analyser.h @@ -20,20 +20,13 @@ #define AUDIOGRAPHER_ANALYSER_H #include - -#include -#include - -#include "audiographer/visibility.h" -#include "audiographer/sink.h" -#include "audiographer/utils/listed_source.h" - +#include "loudness_reader.h" #include "ardour/export_analysis.h" namespace AudioGrapher { -class LIBAUDIOGRAPHER_API Analyser : public ListedSource, public Sink +class LIBAUDIOGRAPHER_API Analyser : public LoudnessReader { public: Analyser (float sample_rate, unsigned int channels, framecnt_t bufsize, framecnt_t n_samples); @@ -46,27 +39,20 @@ class LIBAUDIOGRAPHER_API Analyser : public ListedSource, public Sink::process; - static const float fft_range_db; + using Sink::process; + private: float fft_power_at_bin (const uint32_t b, const float norm) const; ARDOUR::ExportAnalysis _result; - Vamp::Plugin* _ebur128_plugin; - Vamp::Plugin** _dbtp_plugin; - float _sample_rate; - unsigned int _channels; - framecnt_t _bufsize; framecnt_t _n_samples; framecnt_t _pos; framecnt_t _spp; framecnt_t _fpp; - float* _bufs[2]; - float* _hann_window; uint32_t _fft_data_size; double _fft_freq_per_bin; @@ -74,7 +60,6 @@ class LIBAUDIOGRAPHER_API Analyser : public ListedSource, public Sink + * + * 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 AUDIOGRAPHER_LOUDNESS_READER_H +#define AUDIOGRAPHER_LOUDNESS_READER_H + +#include +#include + +#include "audiographer/visibility.h" +#include "audiographer/sink.h" +#include "audiographer/routines.h" +#include "audiographer/utils/listed_source.h" + +namespace AudioGrapher +{ + +class LIBAUDIOGRAPHER_API LoudnessReader : public ListedSource, public Sink +{ + public: + LoudnessReader (float sample_rate, unsigned int channels, framecnt_t bufsize); + ~LoudnessReader (); + + void reset (); + + float get_normalize_gain (float target_lufs, float target_dbtp); + float get_peak (float target_lufs = -23.f, float target_dbtp = -1.f) { + return 1.f / get_normalize_gain (target_lufs, target_dbtp); + } + + virtual void process (ProcessContext const & c); + + using Sink::process; + + protected: + Vamp::Plugin* _ebur_plugin; + Vamp::Plugin** _dbtp_plugin; + + float _sample_rate; + unsigned int _channels; + framecnt_t _bufsize; + framecnt_t _pos; + float* _bufs[2]; +}; + +} // namespace + + +#endif // AUDIOGRAPHER_LOUDNESS_READER_H diff --git a/libs/audiographer/src/general/analyser.cc b/libs/audiographer/src/general/analyser.cc index a045976403..84120b6f53 100644 --- a/libs/audiographer/src/general/analyser.cc +++ b/libs/audiographer/src/general/analyser.cc @@ -24,11 +24,7 @@ using namespace AudioGrapher; const float Analyser::fft_range_db (120); // dB Analyser::Analyser (float sample_rate, unsigned int channels, framecnt_t bufsize, framecnt_t n_samples) - : _ebur128_plugin (0) - , _dbtp_plugin (0) - , _sample_rate (sample_rate) - , _channels (channels) - , _bufsize (bufsize / channels) + : LoudnessReader (sample_rate, channels, bufsize) , _n_samples (n_samples) , _pos (0) { @@ -36,33 +32,8 @@ Analyser::Analyser (float sample_rate, unsigned int channels, framecnt_t bufsize assert (bufsize % channels == 0); assert (bufsize > 1); assert (_bufsize > 0); - if (channels > 0 && channels <= 2) { - using namespace Vamp::HostExt; - PluginLoader* loader (PluginLoader::getInstance ()); - _ebur128_plugin = loader->loadPlugin ("libardourvampplugins:ebur128", sample_rate, PluginLoader::ADAPT_ALL_SAFE); - assert (_ebur128_plugin); - _ebur128_plugin->reset (); - if (!_ebur128_plugin->initialise (channels, _bufsize, _bufsize)) { - delete _ebur128_plugin; - _ebur128_plugin = 0; - } - } - _dbtp_plugin = (Vamp::Plugin**) malloc (sizeof(Vamp::Plugin*) * channels); - for (unsigned int c = 0; c < _channels; ++c) { - using namespace Vamp::HostExt; - PluginLoader* loader (PluginLoader::getInstance ()); - _dbtp_plugin[c] = loader->loadPlugin ("libardourvampplugins:dBTP", sample_rate, PluginLoader::ADAPT_ALL_SAFE); - assert (_dbtp_plugin[c]); - _dbtp_plugin[c]->reset (); - if (!_dbtp_plugin[c]->initialise (1, _bufsize, _bufsize)) { - delete _dbtp_plugin[c]; - _dbtp_plugin[c] = 0; - } - } - _bufs[0] = (float*) malloc (sizeof (float) * _bufsize); - _bufs[1] = (float*) malloc (sizeof (float) * _bufsize); const size_t peaks = sizeof (_result.peaks) / sizeof (ARDOUR::PeakData::PeakDatum) / 4; _spp = ceil ((_n_samples + 2.f) / (float) peaks); @@ -123,13 +94,6 @@ Analyser::Analyser (float sample_rate, unsigned int channels, framecnt_t bufsize Analyser::~Analyser () { - delete _ebur128_plugin; - for (unsigned int c = 0; c < _channels; ++c) { - delete _dbtp_plugin[c]; - } - free (_dbtp_plugin); - free (_bufs[0]); - free (_bufs[1]); fftwf_destroy_plan (_fft_plan); fftwf_free (_fft_data_in); fftwf_free (_fft_data_out); @@ -180,8 +144,8 @@ Analyser::process (ProcessContext const & ctx) } } - if (_ebur128_plugin) { - _ebur128_plugin->process (_bufs, Vamp::RealTime::fromSeconds ((double) _pos / _sample_rate)); + if (_ebur_plugin) { + _ebur_plugin->process (_bufs, Vamp::RealTime::fromSeconds ((double) _pos / _sample_rate)); } float const * const data = ctx.data (); @@ -268,8 +232,8 @@ Analyser::result () } } - if (_ebur128_plugin) { - Vamp::Plugin::FeatureSet features = _ebur128_plugin->getRemainingFeatures (); + if (_ebur_plugin) { + Vamp::Plugin::FeatureSet features = _ebur_plugin->getRemainingFeatures (); if (!features.empty () && features.size () == 3) { _result.loudness = features[0][0].values[0]; _result.loudness_range = features[1][0].values[0]; diff --git a/libs/audiographer/src/general/loudness_reader.cc b/libs/audiographer/src/general/loudness_reader.cc new file mode 100644 index 0000000000..0c2e361fde --- /dev/null +++ b/libs/audiographer/src/general/loudness_reader.cc @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2016 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 "audiographer/general/loudness_reader.h" +#include "pbd/fastlog.h" + +using namespace AudioGrapher; + +LoudnessReader::LoudnessReader (float sample_rate, unsigned int channels, framecnt_t bufsize) + : _ebur_plugin (0) + , _dbtp_plugin (0) + , _sample_rate (sample_rate) + , _channels (channels) + , _bufsize (bufsize / channels) + , _pos (0) +{ + //printf ("NEW LoudnessReader %p r:%.1f c:%d f:%ld\n", this, sample_rate, channels, bufsize); + assert (bufsize % channels == 0); + assert (bufsize > 1); + assert (_bufsize > 0); + + if (channels > 0 && channels <= 2) { + using namespace Vamp::HostExt; + PluginLoader* loader (PluginLoader::getInstance ()); + _ebur_plugin = loader->loadPlugin ("libardourvampplugins:ebur128", sample_rate, PluginLoader::ADAPT_ALL_SAFE); + assert (_ebur_plugin); + _ebur_plugin->reset (); + if (!_ebur_plugin->initialise (channels, _bufsize, _bufsize)) { + delete _ebur_plugin; + _ebur_plugin = 0; + } + } + + _dbtp_plugin = (Vamp::Plugin**) malloc (sizeof(Vamp::Plugin*) * channels); + for (unsigned int c = 0; c < _channels; ++c) { + using namespace Vamp::HostExt; + PluginLoader* loader (PluginLoader::getInstance ()); + _dbtp_plugin[c] = loader->loadPlugin ("libardourvampplugins:dBTP", sample_rate, PluginLoader::ADAPT_ALL_SAFE); + assert (_dbtp_plugin[c]); + _dbtp_plugin[c]->reset (); + if (!_dbtp_plugin[c]->initialise (1, _bufsize, _bufsize)) { + delete _dbtp_plugin[c]; + _dbtp_plugin[c] = 0; + } + } + + _bufs[0] = (float*) malloc (sizeof (float) * _bufsize); + _bufs[1] = (float*) malloc (sizeof (float) * _bufsize); +} + +LoudnessReader::~LoudnessReader () +{ + delete _ebur_plugin; + for (unsigned int c = 0; c < _channels; ++c) { + delete _dbtp_plugin[c]; + } + free (_dbtp_plugin); + free (_bufs[0]); + free (_bufs[1]); +} + +void +LoudnessReader::reset () +{ + if (_ebur_plugin) { + _ebur_plugin->reset (); + } + + for (unsigned int c = 0; c < _channels; ++c) { + if (_dbtp_plugin[c]) { + _dbtp_plugin[c]->reset (); + } + } +} + +void +LoudnessReader::process (ProcessContext const & ctx) +{ + const framecnt_t n_samples = ctx.frames () / ctx.channels (); + assert (ctx.channels () == _channels); + assert (ctx.frames () % ctx.channels () == 0); + assert (n_samples <= _bufsize); + //printf ("PROC %p @%ld F: %ld, S: %ld C:%d\n", this, _pos, ctx.frames (), n_samples, ctx.channels ()); + + unsigned processed_channels = 0; + if (_ebur_plugin) { + assert (_channels <= 2); + processed_channels = _channels; + framecnt_t s; + float const * d = ctx.data (); + for (s = 0; s < n_samples; ++s) { + for (unsigned int c = 0; c < _channels; ++c, ++d) { + _bufs[c][s] = *d; + } + } + for (; s < _bufsize; ++s) { + for (unsigned int c = 0; c < _channels; ++c) { + _bufs[c][s] = 0.f; + } + } + _ebur_plugin->process (_bufs, Vamp::RealTime::fromSeconds ((double) _pos / _sample_rate)); + if (_dbtp_plugin[0]) { + _dbtp_plugin[0]->process (&_bufs[0], Vamp::RealTime::fromSeconds ((double) _pos / _sample_rate)); + } + if (_channels == 2 && _dbtp_plugin[1]) { + _dbtp_plugin[0]->process (&_bufs[1], Vamp::RealTime::fromSeconds ((double) _pos / _sample_rate)); + } + } + + for (unsigned int c = processed_channels; c < _channels; ++c) { + if (!_dbtp_plugin[c]) { + continue; + } + framecnt_t s; + float const * const d = ctx.data (); + for (s = 0; s < n_samples; ++s) { + _bufs[0][s] = d[s * _channels + c]; + } + for (; s < _bufsize; ++s) { + _bufs[0][s] = 0.f; + } + _dbtp_plugin[c]->process (_bufs, Vamp::RealTime::fromSeconds ((double) _pos / _sample_rate)); + } + + _pos += n_samples; + ListedSource::output (ctx); +} + +float +LoudnessReader::get_normalize_gain (float target_lufs, float target_dbtp) +{ + float dBTP = 0; + float LUFS = -200; + uint32_t have_lufs = 0; + uint32_t have_dbtp = 0; + + if (_ebur_plugin) { + Vamp::Plugin::FeatureSet features = _ebur_plugin->getRemainingFeatures (); + if (!features.empty () && features.size () == 3) { + const float lufs = features[0][0].values[0]; + LUFS = std::max (LUFS, lufs); + ++have_lufs; + } + } + + for (unsigned int c = 0; c < _channels; ++c) { + if (_dbtp_plugin[c]) { + Vamp::Plugin::FeatureSet features = _dbtp_plugin[c]->getRemainingFeatures (); + if (!features.empty () && features.size () == 2) { + const float dbtp = features[0][0].values[0]; + dBTP = std::max (dBTP, dbtp); + ++have_dbtp; + } + } + } + + float g = 100000.0; // +100dB + bool set = false; + if (have_lufs && LUFS > -180.0f && target_lufs <= 0.f) { + const float ge = pow (10.f, (target_lufs * 0.05f)) / pow (10.f, (LUFS * 0.05f)); + //printf ("LU: %f LUFS, %f\n", LUFS, ge); + g = std::min (g, ge); + set = true; + } + + // TODO check that all channels were used.. ? (have_dbtp == _channels) + if (have_dbtp && dBTP > 0.f && target_dbtp <= 0.f) { + const float ge = pow (10.f, (target_dbtp * 0.05f)) / dBTP; + //printf ("TP:(%d chn) %fdBTP -> %f\n", have_dbtp, dBTP, ge); + g = std::min (g, ge); + set = true; + } + + if (!set) { + g = 1.f; + } + //printf ("LF %f / %f\n", g, 1.f / g); + return g; +} diff --git a/libs/audiographer/wscript b/libs/audiographer/wscript index d1863cf49b..7ac9f8f4fa 100644 --- a/libs/audiographer/wscript +++ b/libs/audiographer/wscript @@ -66,6 +66,7 @@ def build(bld): 'src/debug_utils.cc', 'src/general/analyser.cc', 'src/general/broadcast_info.cc', + 'src/general/loudness_reader.cc', 'src/general/normalizer.cc' ] if bld.is_defined('HAVE_SAMPLERATE'): -- 2.30.2