prepare loudness normalization
authorRobin Gareus <robin@gareus.org>
Mon, 2 May 2016 11:58:51 +0000 (13:58 +0200)
committerRobin Gareus <robin@gareus.org>
Mon, 2 May 2016 13:44:13 +0000 (15:44 +0200)
libs/audiographer/audiographer/general/analyser.h
libs/audiographer/audiographer/general/loudness_reader.h [new file with mode: 0644]
libs/audiographer/src/general/analyser.cc
libs/audiographer/src/general/loudness_reader.cc [new file with mode: 0644]
libs/audiographer/wscript

index e8ca534954245df7835773ce3075242194ff9ce3..9bd49b33c84b80d836a9b784e03303932b29cdeb 100644 (file)
 #define AUDIOGRAPHER_ANALYSER_H
 
 #include <fftw3.h>
-
-#include <vamp-hostsdk/PluginLoader.h>
-#include <vamp-sdk/Plugin.h>
-
-#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<float>, public Sink<float>
+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<float>, public Sink<flo
                _result.norm_gain_factor = gain;
        }
 
-       using Sink<float>::process;
-
        static const float fft_range_db;
 
+       using Sink<float>::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<float>, public Sink<flo
        float*     _fft_data_out;
        float*     _fft_power;
        fftwf_plan _fft_plan;
-
 };
 
 } // namespace
diff --git a/libs/audiographer/audiographer/general/loudness_reader.h b/libs/audiographer/audiographer/general/loudness_reader.h
new file mode 100644 (file)
index 0000000..7431f5e
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef AUDIOGRAPHER_LOUDNESS_READER_H
+#define AUDIOGRAPHER_LOUDNESS_READER_H
+
+#include <vamp-hostsdk/PluginLoader.h>
+#include <vamp-sdk/Plugin.h>
+
+#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<float>, public Sink<float>
+{
+  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<float> const & c);
+
+       using Sink<float>::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
index a04597640311d43d0975e614e0d074e1713a3a53..84120b6f532fe97b1660f107674362df79cbfaa2 100644 (file)
@@ -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<float> 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 (file)
index 0000000..0c2e361
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2016 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 "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<float> 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<float>::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;
+}
index d1863cf49b3c3c55e250bd61e1aece0b2414b643..7ac9f8f4fa2593332b81e4ccfcc1edd6278574cc 100644 (file)
@@ -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'):