add basic VAMP plugin for EBUr128 analysis

FeatureSet will be extended to report detailed analysis.
This commit is contained in:
Robin Gareus 2015-11-03 07:51:39 +01:00
parent 2f7c91c7da
commit 72ec5e9a01
6 changed files with 726 additions and 1 deletions

View File

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

View File

@ -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 <vamp-sdk/Plugin.h>
#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

View File

@ -0,0 +1,340 @@
// ------------------------------------------------------------------------
//
// Copyright (C) 2010-2011 Fons Adriaensen <fons@linuxaudio.org>
// Copyright (C) 2015 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., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// ------------------------------------------------------------------------
#include <string.h>
#include <math.h>
#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;
}
};

View File

@ -0,0 +1,137 @@
// ------------------------------------------------------------------------
//
// Copyright (C) 2010-2011 Fons Adriaensen <fons@linuxaudio.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., 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

View File

@ -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<SpectralCentroid> spectralCentroidAdapter;
static Vamp::PluginAdapter<PercussionOnsetDetector> percussionOnsetAdapter;
static Vamp::PluginAdapter<AmplitudeFollower> amplitudeAdapter;
static Vamp::PluginAdapter<OnsetDetector> onsetDetectorAdapter;
static Vamp::PluginAdapter<VampEBUr128> VampEBUr128Adapter;
#ifdef HAVE_AUBIO
static Vamp::PluginAdapter<Onset> 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;
}

View File

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