prepare loudness normalization

This commit is contained in:
Robin Gareus 2016-05-02 13:58:51 +02:00
parent bd461fe202
commit 7547f02c07
5 changed files with 268 additions and 60 deletions

View File

@ -20,20 +20,13 @@
#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

View File

@ -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

View 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];

View File

@ -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;
}

View 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'):