From 72ec5e9a0139b6b5eb8debaffd88f0ebd69df501 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 3 Nov 2015 07:51:39 +0100 Subject: [PATCH] add basic VAMP plugin for EBUr128 analysis FeatureSet will be extended to report detailed analysis. --- libs/vamp-plugins/EBUr128.cpp | 170 +++++++++++++++ libs/vamp-plugins/EBUr128.h | 73 +++++++ libs/vamp-plugins/ebu_r128_proc.cc | 340 +++++++++++++++++++++++++++++ libs/vamp-plugins/ebu_r128_proc.h | 137 ++++++++++++ libs/vamp-plugins/plugins.cpp | 5 +- libs/vamp-plugins/wscript | 2 + 6 files changed, 726 insertions(+), 1 deletion(-) create mode 100644 libs/vamp-plugins/EBUr128.cpp create mode 100644 libs/vamp-plugins/EBUr128.h create mode 100644 libs/vamp-plugins/ebu_r128_proc.cc create mode 100644 libs/vamp-plugins/ebu_r128_proc.h diff --git a/libs/vamp-plugins/EBUr128.cpp b/libs/vamp-plugins/EBUr128.cpp new file mode 100644 index 0000000000..1a3b9a401c --- /dev/null +++ b/libs/vamp-plugins/EBUr128.cpp @@ -0,0 +1,170 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include "EBUr128.h" + +using std::string; +using std::vector; +using std::cerr; +using std::endl; + + +VampEBUr128::VampEBUr128(float inputSampleRate) + : Plugin(inputSampleRate) + , m_stepSize(0) +{ +} + +VampEBUr128::~VampEBUr128() +{ +} + +string +VampEBUr128::getIdentifier() const +{ + return "ebur128"; +} + +string +VampEBUr128::getName() const +{ + return "EBU R128 Loudness"; +} + +string +VampEBUr128::getDescription() const +{ + return "Loudness measurements according to the EBU Recommendation 128"; +} + +string +VampEBUr128::getMaker() const +{ + return "Harrison Consoles"; +} + +int +VampEBUr128::getPluginVersion() const +{ + return 2; +} + +string +VampEBUr128::getCopyright() const +{ + return "GPL version 2 or later"; +} + +bool +VampEBUr128::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + m_stepSize = std::min(stepSize, blockSize); + m_channels = channels; + + ebu.init (m_channels, m_inputSampleRate); + + return true; +} + +void +VampEBUr128::reset() +{ + ebu.reset (); +} + +VampEBUr128::OutputList +VampEBUr128::getOutputDescriptors() const +{ + OutputList list; + + OutputDescriptor zc; + zc.identifier = "loundless"; + zc.name = "Integrated loudness"; + zc.description = "Integrated loudness"; + zc.unit = "LUFS"; + zc.hasFixedBinCount = true; + zc.binCount = 0; + zc.hasKnownExtents = false; + zc.isQuantized = false; + zc.sampleType = OutputDescriptor::OneSamplePerStep; + list.push_back(zc); + + zc.identifier = "range"; + zc.name = "Integrated loudness Range"; + zc.description = "Dynamic Range of the audio"; + zc.unit = "LU"; + zc.hasFixedBinCount = true; + zc.binCount = 0; + zc.hasKnownExtents = false; + zc.isQuantized = false; + zc.sampleType = OutputDescriptor::OneSamplePerStep; + list.push_back(zc); + + return list; +} + +VampEBUr128::FeatureSet +VampEBUr128::process(const float *const *inputBuffers, + Vamp::RealTime timestamp) +{ + if (m_stepSize == 0) { + cerr << "ERROR: VampEBUr128::process: " + << "VampEBUr128 has not been initialised" + << endl; + return FeatureSet(); + } + + ebu.integr_start (); // noop if already started + ebu.process (m_stepSize, inputBuffers); + + return FeatureSet(); +} + +VampEBUr128::FeatureSet +VampEBUr128::getRemainingFeatures() +{ + FeatureSet returnFeatures; + + Feature loudness; + loudness.hasTimestamp = false; + loudness.values.push_back(ebu.integrated()); + returnFeatures[0].push_back(loudness); + + Feature range; + range.hasTimestamp = false; + range.values.push_back(ebu.range_max () - ebu.range_min ()); + returnFeatures[1].push_back(range); + + return returnFeatures; +} diff --git a/libs/vamp-plugins/EBUr128.h b/libs/vamp-plugins/EBUr128.h new file mode 100644 index 0000000000..026aa400db --- /dev/null +++ b/libs/vamp-plugins/EBUr128.h @@ -0,0 +1,73 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef _EBUR128_PLUGIN_H_ +#define _EBUR128_PLUGIN_H_ + +#include +#include "ebu_r128_proc.h" + +class VampEBUr128 : public Vamp::Plugin +{ +public: + VampEBUr128(float inputSampleRate); + virtual ~VampEBUr128(); + + size_t getMinChannelCount() const { return 1; } + size_t getMaxChannelCount() const { return 2; } + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const { return TimeDomain; } + + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + size_t m_stepSize; + size_t m_channels; + +private: + Fons::Ebu_r128_proc ebu; +}; + + +#endif diff --git a/libs/vamp-plugins/ebu_r128_proc.cc b/libs/vamp-plugins/ebu_r128_proc.cc new file mode 100644 index 0000000000..5675171e9e --- /dev/null +++ b/libs/vamp-plugins/ebu_r128_proc.cc @@ -0,0 +1,340 @@ +// ------------------------------------------------------------------------ +// +// Copyright (C) 2010-2011 Fons Adriaensen +// Copyright (C) 2015 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// ------------------------------------------------------------------------ + + +#include +#include +#include "ebu_r128_proc.h" + +namespace Fons { + +float Ebu_r128_hist::_bin_power [100] = { 0.0f }; +float Ebu_r128_proc::_chan_gain [5] = { 1.0f, 1.0f, 1.0f, 1.41f, 1.41f }; + + +Ebu_r128_hist::Ebu_r128_hist (void) +{ + _histc = new int [751]; + initstat (); + reset (); +} + + +Ebu_r128_hist::~Ebu_r128_hist (void) +{ + delete[] _histc; +} + + +void Ebu_r128_hist::reset (void) +{ + memset (_histc, 0, 751 * sizeof (float)); + _count = 0; + _error = 0; +} + + +void Ebu_r128_hist::initstat (void) +{ + int i; + + if (_bin_power [0]) return; + for (i = 0; i < 100; i++) + { + _bin_power [i] = powf (10.0f, i / 100.0f); + } +} + + +void Ebu_r128_hist::addpoint (float v) +{ + int k; + + k = (int) floorf (10 * v + 700.5f); + if (k < 0) return; + if (k > 750) + { + k = 750; + _error++; + } + _histc [k]++; + _count++; +} + + +float Ebu_r128_hist::integrate (int i) +{ + int j, k, n; + float s; + + j = i % 100; + n = 0; + s = 0; + while (i <= 750) + { + k = _histc [i++]; + n += k; + s += k * _bin_power [j++]; + if (j == 100) + { + j = 0; + s /= 10.0f; + } + } + return s / n; +} + + +void Ebu_r128_hist::calc_integ (float *vi, float *th) +{ + int k; + float s; + + if (_count < 50) + { + *vi = -200.0f; + return; + } + s = integrate (0); +// Original threshold was -8 dB below result of first integration +// if (th) *th = 10 * log10f (s) - 8.0f; +// k = (int)(floorf (100 * log10f (s) + 0.5f)) + 620; +// Threshold redefined to -10 dB below result of first integration + if (th) *th = 10 * log10f (s) - 10.0f; + k = (int)(floorf (100 * log10f (s) + 0.5f)) + 600; + if (k < 0) k = 0; + s = integrate (k); + *vi = 10 * log10f (s); +} + + +void Ebu_r128_hist::calc_range (float *v0, float *v1, float *th) +{ + int i, j, k, n; + float a, b, s; + + if (_count < 20) + { + *v0 = -200.0f; + *v1 = -200.0f; + return; + } + s = integrate (0); + if (th) *th = 10 * log10f (s) - 20.0f; + k = (int)(floorf (100 * log10f (s) + 0.5)) + 500; + if (k < 0) k = 0; + for (i = k, n = 0; i <= 750; i++) n += _histc [i]; + a = 0.10f * n; + b = 0.95f * n; + for (i = k, s = 0; s < a; i++) s += _histc [i]; + for (j = 750, s = n; s > b; j--) s -= _histc [j]; + *v0 = (i - 701) / 10.0f; + *v1 = (j - 699) / 10.0f; +} + + + + +Ebu_r128_proc::Ebu_r128_proc (void) +{ + reset (); +} + + +Ebu_r128_proc::~Ebu_r128_proc (void) +{ +} + + +void Ebu_r128_proc::init (int nchan, float fsamp) +{ + _nchan = nchan; + _fsamp = fsamp; + _fragm = (int) fsamp / 20; + detect_init (_fsamp); + reset (); +} + + +void Ebu_r128_proc::reset (void) +{ + _integr = false; + _frcnt = _fragm; + _frpwr = 1e-30f; + _wrind = 0; + _div1 = 0; + _div2 = 0; + _loudness_M = -200.0f; + _loudness_S = -200.0f; + memset (_power, 0, 64 * sizeof (float)); + integr_reset (); + detect_reset (); +} + + +void Ebu_r128_proc::integr_reset (void) +{ + _hist_M.reset (); + _hist_S.reset (); + _maxloudn_M = -200.0f; + _maxloudn_S = -200.0f; + _integrated = -200.0f; + _integ_thr = -200.0f; + _range_min = -200.0f; + _range_max = -200.0f; + _range_thr = -200.0f; + _div1 = _div2 = 0; +} + + +void Ebu_r128_proc::process (int nfram, const float *const *input) +{ + int i, k; + + for (i = 0; i < _nchan; i++) _ipp [i] = input [i]; + while (nfram) + { + k = (_frcnt < nfram) ? _frcnt : nfram; + _frpwr += detect_process (k); + _frcnt -= k; + if (_frcnt == 0) + { + _power [_wrind++] = _frpwr / _fragm; + _frcnt = _fragm; + _frpwr = 1e-30f; + _wrind &= 63; + _loudness_M = addfrags (8); + _loudness_S = addfrags (60); + if (!isfinite(_loudness_M) || _loudness_M < -200.f) _loudness_M = -200.0f; + if (!isfinite(_loudness_S) || _loudness_S < -200.f) _loudness_S = -200.0f; + if (_loudness_M > _maxloudn_M) _maxloudn_M = _loudness_M; + if (_loudness_S > _maxloudn_S) _maxloudn_S = _loudness_S; + if (_integr) + { + if (++_div1 == 2) + { + _hist_M.addpoint (_loudness_M); + _div1 = 0; + } + if (++_div2 == 10) + { + _hist_S.addpoint (_loudness_S); + _div2 = 0; + _hist_M.calc_integ (&_integrated, &_integ_thr); + _hist_S.calc_range (&_range_min, &_range_max, &_range_thr); + } + } + } + for (i = 0; i < _nchan; i++) _ipp [i] += k; + nfram -= k; + } +} + + +float Ebu_r128_proc::addfrags (int nfrag) +{ + int i, k; + float s; + + s = 0; + k = (_wrind - nfrag) & 63; + for (i = 0; i < nfrag; i++) s += _power [(i + k) & 63]; + return -0.6976f + 10 * log10f (s / nfrag); +} + + +void Ebu_r128_proc::detect_init (float fsamp) +{ + float a, b, c, d, r, u1, u2, w1, w2; + + r = 1 / tan (4712.3890f / fsamp); + w1 = r / 1.12201f; + w2 = r * 1.12201f; + u1 = u2 = 1.4085f + 210.0f / fsamp; + a = u1 * w1; + b = w1 * w1; + c = u2 * w2; + d = w2 * w2; + r = 1 + a + b; + _a0 = (1 + c + d) / r; + _a1 = (2 - 2 * d) / r; + _a2 = (1 - c + d) / r; + _b1 = (2 - 2 * b) / r; + _b2 = (1 - a + b) / r; + r = 48.0f / fsamp; + a = 4.9886075f * r; + b = 6.2298014f * r * r; + r = 1 + a + b; + a *= 2 / r; + b *= 4 / r; + _c3 = a + b; + _c4 = b; + r = 1.004995f / r; + _a0 *= r; + _a1 *= r; + _a2 *= r; +} + + +void Ebu_r128_proc::detect_reset (void) +{ + for (int i = 0; i < MAXCH; i++) _fst [i].reset (); +} + + +float Ebu_r128_proc::detect_process (int nfram) +{ + int i, j; + float si, sj; + float x, y, z1, z2, z3, z4; + float const *p; + Ebu_r128_fst *S; + + si = 0; + for (i = 0, S = _fst; i < _nchan; i++, S++) + { + z1 = S->_z1; + z2 = S->_z2; + z3 = S->_z3; + z4 = S->_z4; + p = _ipp [i]; + sj = 0; + for (j = 0; j < nfram; j++) + { + x = p [j] - _b1 * z1 - _b2 * z2 + 1e-15f; + y = _a0 * x + _a1 * z1 + _a2 * z2 - _c3 * z3 - _c4 * z4; + z2 = z1; + z1 = x; + z4 += z3; + z3 += y; + sj += y * y; + } + if (_nchan == 1) si = 2 * sj; + else si += _chan_gain [i] * sj; + S->_z1 = !isfinite(z1) ? 0 : z1; + S->_z2 = !isfinite(z2) ? 0 : z2; + S->_z3 = !isfinite(z3) ? 0 : z3; + S->_z4 = !isfinite(z4) ? 0 : z4; + } + return si; +} + +}; diff --git a/libs/vamp-plugins/ebu_r128_proc.h b/libs/vamp-plugins/ebu_r128_proc.h new file mode 100644 index 0000000000..126be8b0f2 --- /dev/null +++ b/libs/vamp-plugins/ebu_r128_proc.h @@ -0,0 +1,137 @@ +// ------------------------------------------------------------------------ +// +// Copyright (C) 2010-2011 Fons Adriaensen +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// ------------------------------------------------------------------------ + + +#ifndef _EBU_R128_PROC_H +#define _EBU_R128_PROC_H + +#define MAXCH 5 + +namespace Fons { + +class Ebu_r128_fst +{ +private: + + friend class Ebu_r128_proc; + + void reset (void) { _z1 = _z2 = _z3 = _z4 = 0; } + + float _z1, _z2, _z3, _z4; +}; + + +class Ebu_r128_hist +{ +private: + + Ebu_r128_hist (void); + ~Ebu_r128_hist (void); + + friend class Ebu_r128_proc; + + void reset (void); + void initstat (void); + void addpoint (float v); + float integrate (int ind); + void calc_integ (float *vi, float *th); + void calc_range (float *v0, float *v1, float *th); + + int *_histc; + int _count; + int _error; + + static float _bin_power [100]; +}; + + + +class Ebu_r128_proc +{ +public: + + Ebu_r128_proc (void); + ~Ebu_r128_proc (void); + + void init (int nchan, float fsamp); + void reset (void); + void process (int nfram, const float *const *input); + void integr_reset (void); + void integr_pause (void) { _integr = false; } + void integr_start (void) { _integr = true; } + + float loudness_M (void) const { return _loudness_M; } + float maxloudn_M (void) const { return _maxloudn_M; } + float loudness_S (void) const { return _loudness_S; } + float maxloudn_S (void) const { return _maxloudn_S; } + float integrated (void) const { return _integrated; } + float integ_thr (void) const { return _integ_thr; } + float range_min (void) const { return _range_min; } + float range_max (void) const { return _range_max; } + float range_thr (void) const { return _range_thr; } + + const int *histogram_M (void) const { return _hist_M._histc; } + const int *histogram_S (void) const { return _hist_S._histc; } + int hist_M_count (void) const { return _hist_M._count; } + int hist_S_count (void) const { return _hist_S._count; } + +private: + + float addfrags (int nfrag); + void detect_init (float fsamp); + void detect_reset (void); + float detect_process (int nfram); + + bool _integr; // Integration on/off. + int _nchan; // Number of channels, 2 or 5. + float _fsamp; // Sample rate. + int _fragm; // Fragmenst size, 1/20 second. + int _frcnt; // Number of samples remaining in current fragment. + float _frpwr; // Power accumulated for current fragment. + float _power [64]; // Array of fragment powers. + int _wrind; // Write index into _frpwr + int _div1; // M period counter, 200 ms; + int _div2; // S period counter, 1s; + float _loudness_M; + float _maxloudn_M; + float _loudness_S; + float _maxloudn_S; + float _integrated; + float _integ_thr; + float _range_min; + float _range_max; + float _range_thr; + + // Filter coefficients and states. + float _a0, _a1, _a2; + float _b1, _b2; + float _c3, _c4; + float const *_ipp [MAXCH]; + Ebu_r128_fst _fst [MAXCH]; + Ebu_r128_hist _hist_M; + Ebu_r128_hist _hist_S; + + // Default channel gains. + static float _chan_gain [5]; +}; + +}; + +#endif diff --git a/libs/vamp-plugins/plugins.cpp b/libs/vamp-plugins/plugins.cpp index d7db2d90c0..b12a2786c8 100644 --- a/libs/vamp-plugins/plugins.cpp +++ b/libs/vamp-plugins/plugins.cpp @@ -46,6 +46,7 @@ #include "PercussionOnsetDetector.h" #include "AmplitudeFollower.h" #include "OnsetDetect.h" +#include "EBUr128.h" #ifdef HAVE_AUBIO #include "Onset.h" #endif @@ -55,6 +56,7 @@ static Vamp::PluginAdapter spectralCentroidAdapter; static Vamp::PluginAdapter percussionOnsetAdapter; static Vamp::PluginAdapter amplitudeAdapter; static Vamp::PluginAdapter onsetDetectorAdapter; +static Vamp::PluginAdapter VampEBUr128Adapter; #ifdef HAVE_AUBIO static Vamp::PluginAdapter onsetAdapter; #endif @@ -70,8 +72,9 @@ const VampPluginDescriptor *vampGetPluginDescriptor(unsigned int version, case 2: return percussionOnsetAdapter.getDescriptor(); case 3: return amplitudeAdapter.getDescriptor(); case 4: return onsetDetectorAdapter.getDescriptor(); + case 5: return VampEBUr128Adapter.getDescriptor(); #ifdef HAVE_AUBIO - case 5: return onsetAdapter.getDescriptor(); + case 6: return onsetAdapter.getDescriptor(); #endif default: return 0; } diff --git a/libs/vamp-plugins/wscript b/libs/vamp-plugins/wscript index 06d440fa57..131ec03685 100644 --- a/libs/vamp-plugins/wscript +++ b/libs/vamp-plugins/wscript @@ -38,6 +38,8 @@ def build(bld): obj.source = ''' plugins.cpp AmplitudeFollower.cpp + EBUr128.cpp + ebu_r128_proc.cc OnsetDetect.cpp PercussionOnsetDetector.cpp SpectralCentroid.cpp -- 2.30.2