13
0

Implement [surround] LUFS meter

This commit is contained in:
Robin Gareus 2023-12-19 02:57:09 +01:00
parent 88c796c8f2
commit 0ada2df2e6
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
3 changed files with 473 additions and 0 deletions

View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2016,2023 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 _lufs_meter_h_
#define _lufs_meter_h_
#include <cstdint>
#include <functional>
#include <map>
#include "pbd/stack_allocator.h"
#include "ardour/libardour_visibility.h"
namespace ARDOUR {
class LIBARDOUR_API LUFSMeter
{
public:
LUFSMeter (double samplerate, uint32_t n_channels);
LUFSMeter (LUFSMeter const& other) = delete;
~LUFSMeter ();
void run (float const** data, uint32_t n_samples);
void reset ();
float integrated_loudness () const;
float max_momentary () const;
float dbtp () const;
private:
void init ();
float process (float const** data, const uint32_t n_samples, uint32_t offset);
float sumfrag (uint32_t) const;
void calc_true_peak (float const** data, const uint32_t n_samples);
float upsample_x4 (int chn, float const x);
float upsample_x2 (int chn, float const x);
std::function< float(int, const float) > upsample;
const float _g[5] = { 1.0, 1.0, 1.0, 1.41, 1.41 };
/* config */
double _samplerate;
uint32_t _n_channels;
uint32_t _n_fragment;
/* filter coeff */
float _a0, _a1, _a2;
float _b1, _b2;
float _c3, _c4;
/* state */
uint32_t _frag_pos;
float _frag_pwr;
uint32_t _block_cnt;
float _block_pwr;
float _power[8];
uint32_t _pow_idx;
float _thresh_rel;
float _maxloudn_M;
float _integrated;
float _dbtp;
#if defined(_MSC_VER)
typedef std::map<int, uint32_t> History;
#else
typedef std::map<int, uint32_t, std::less<int>, PBD::StackAllocator<std::pair<const int, uint32_t>, 1000>> History;
#endif
History _hist;
struct FilterState {
void reset ();
void sanitize ();
float z1, z2, z3, z4;
};
FilterState _fst[5];
float* _z[5];
};
} // namespace ARDOUR
#endif

371
libs/ardour/lufs_meter.cc Normal file
View File

@ -0,0 +1,371 @@
/*
* Copyright (C) 2016,2023 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 <algorithm>
#include <cmath>
#include <cstring>
#ifdef COMPILER_MSVC
#include <float.h>
#define isfinite_local(val) (bool)_finite ((double)val)
#else
#define isfinite_local std::isfinite
#endif
#include "pbd/failed_constructor.h"
#include "ardour/dB.h"
#include "ardour/lufs_meter.h"
using namespace ARDOUR;
void
LUFSMeter::FilterState::reset ()
{
z1 = z2 = z3 = z4 = 0;
}
void
LUFSMeter::FilterState::sanitize ()
{
z1 = !isfinite_local (z1) ? 0 : z1;
z2 = !isfinite_local (z2) ? 0 : z2;
z3 = !isfinite_local (z3) ? 0 : z3;
z4 = !isfinite_local (z4) ? 0 : z4;
}
LUFSMeter::LUFSMeter (double samplerate, uint32_t n_channels)
: _samplerate (samplerate)
, _n_channels (n_channels)
{
if (_n_channels > 5 || _n_channels == 0) {
throw failed_constructor ();
}
_n_fragment = samplerate / 10;
using std::placeholders::_1;
using std::placeholders::_2;
if (samplerate > 48000) {
upsample = std::bind (&LUFSMeter::upsample_x2, this, _1, _2);
} else {
upsample = std::bind (&LUFSMeter::upsample_x4, this, _1, _2);
}
for (uint32_t c = 0; c < 5; ++c) {
_z[c] = new float[48];
}
init ();
reset ();
}
LUFSMeter::~LUFSMeter ()
{
for (uint32_t c = 0; c < 5; ++c) {
delete[] _z[c];
}
}
void
LUFSMeter::init ()
{
float a, b, c, d, r, u, w1, w2;
/* shelf */
r = 1 / tan (4712.3890f / _samplerate);
w1 = r / 1.121f;
w2 = r * 1.121f;
u = 1.4085f + 210.0f / _samplerate;
a = w1 * u;
b = w1 * w1;
c = w2 * u;
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;
/* HP */
r = 48.0f / _samplerate;
a = 4.9886075f * r;
b = 6.2298014f * r * r;
r = 1 + a + b;
a *= 2 / r;
b *= 4 / r;
_c3 = a + b;
_c4 = b;
/* normalize */
r = 1.004995f / r;
_a0 *= r;
_a1 *= r;
_a2 *= r;
}
void
LUFSMeter::reset ()
{
for (uint32_t c = 0; c < _n_channels; ++c) {
_fst[c].reset ();
memset (_z[c], 0, 48 * sizeof (float));
}
_frag_pos = _n_fragment;
_frag_pwr = 1e-30f;
_maxloudn_M = -200;
_integrated = -200;
_thresh_rel = -70;
_block_pwr = 0.0;
_block_cnt = 0;
_pow_idx = 0;
_dbtp = 0;
memset (_power, 0, 8 * sizeof (float));
_hist.clear ();
}
void
LUFSMeter::run (float const** data, uint32_t n_samples)
{
uint32_t offset = 0;
calc_true_peak (data, n_samples);
while (n_samples > 0) {
uint32_t n = (_frag_pos < n_samples) ? _frag_pos : n_samples;
_frag_pwr += process (data, n, offset);
_frag_pos -= n;
offset += n;
n_samples -= n;
if (_frag_pos == 0) {
/* every 100 ms */
_power[_pow_idx++] = _frag_pwr / (float)_n_fragment;
_pow_idx &= 7;
_frag_pwr = 1e-30f;
_frag_pos = _n_fragment;
const float sum_m = sumfrag (4); // 400ms
const float loudness_m = -0.691f + 10.f * log10f (sum_m);
_maxloudn_M = std::max<float> (_maxloudn_M, loudness_m);
/* observe 400ms window every 100ms */
if (loudness_m > -70.f) {
_block_pwr += sum_m;
++_block_cnt;
/* see ITU-R BS.1770-3, page 6 */
_thresh_rel = -10.691 + 10.f * log10f (_block_pwr / _block_cnt);
}
if (loudness_m > -100.f) {
_hist[round (loudness_m * 10.f)] += 1;
}
if (_hist.size () == 0) {
continue;
}
if (_thresh_rel < (--_hist.end ())->first * 0.1) {
int b = _thresh_rel * 10.f;
while (_hist.find (b) == _hist.end ()) {
++b; // += .1LU
}
int n = 0;
double sum = 0.0;
for (auto i = _hist.find (b); i != _hist.end (); ++i) {
n += i->second;
const double s = powf (10.0, (i->first * 0.1 + 0.691) * 0.1);
sum += i->second * s;
}
if (n > 0) {
_integrated = -0.691f + 10.f * log10f (sum / n);
}
}
}
}
}
float
LUFSMeter::process (float const** data, const uint32_t n_samples, uint32_t off)
{
float l = 0;
for (uint32_t c = 0; c < _n_channels; ++c) {
float const* d = data[c];
FilterState& z = _fst[c];
float s = 0;
for (uint32_t i = 0; i < n_samples; ++i) {
float x = d[i + off] - _b1 * z.z1 - _b2 * z.z2 + 1e-15f;
float y = _a0 * x + _a1 * z.z1 + _a2 * z.z2 - _c3 * z.z3 - _c4 * z.z4;
z.z2 = z.z1;
z.z1 = x;
z.z4 += z.z3;
z.z3 += y;
s += y * y;
}
l += s * _g[c];
z.sanitize ();
}
if (_n_channels == 1) {
l *= 2;
}
return l;
}
float
LUFSMeter::sumfrag (uint32_t n_frag) const
{
float s = 0;
int k = (8 + _pow_idx - n_frag) & 7;
for (uint32_t i = 0; i < n_frag; i++) {
s += _power[(i + k) & 7];
}
return s / n_frag;
}
float
LUFSMeter::integrated_loudness () const
{
return _integrated;
}
float
LUFSMeter::max_momentary () const
{
return _maxloudn_M;
}
float
LUFSMeter::dbtp () const
{
return accurate_coefficient_to_dB (_dbtp);
}
float
LUFSMeter::upsample_x2 (int chn, float const x)
{
float* r = _z[chn];
float u[2];
r[47] = x;
/* 2x upsample for true-peak analysis, cosine windowed sinc. */
/* clang-format off */
u[0] = r[47];
u[1] = r[ 0] * -1.450055e-05f + r[ 1] * +1.359163e-04f + r[ 2] * -3.928527e-04f + r[ 3] * +8.006445e-04f
+ r[ 4] * -1.375510e-03f + r[ 5] * +2.134915e-03f + r[ 6] * -3.098103e-03f + r[ 7] * +4.286860e-03f
+ r[ 8] * -5.726614e-03f + r[ 9] * +7.448018e-03f + r[10] * -9.489286e-03f + r[11] * +1.189966e-02f
+ r[12] * -1.474471e-02f + r[13] * +1.811472e-02f + r[14] * -2.213828e-02f + r[15] * +2.700557e-02f
+ r[16] * -3.301023e-02f + r[17] * +4.062971e-02f + r[18] * -5.069345e-02f + r[19] * +6.477499e-02f
+ r[20] * -8.625619e-02f + r[21] * +1.239454e-01f + r[22] * -2.101678e-01f + r[23] * +6.359382e-01f
+ r[24] * +6.359382e-01f + r[25] * -2.101678e-01f + r[26] * +1.239454e-01f + r[27] * -8.625619e-02f
+ r[28] * +6.477499e-02f + r[29] * -5.069345e-02f + r[30] * +4.062971e-02f + r[31] * -3.301023e-02f
+ r[32] * +2.700557e-02f + r[33] * -2.213828e-02f + r[34] * +1.811472e-02f + r[35] * -1.474471e-02f
+ r[36] * +1.189966e-02f + r[37] * -9.489286e-03f + r[38] * +7.448018e-03f + r[39] * -5.726614e-03f
+ r[40] * +4.286860e-03f + r[41] * -3.098103e-03f + r[42] * +2.134915e-03f + r[43] * -1.375510e-03f
+ r[44] * +8.006445e-04f + r[45] * -3.928527e-04f + r[46] * +1.359163e-04f + r[47] * -1.450055e-05f;
/* clang-format on */
for (int i = 0; i < 47; ++i) {
r[i] = r[i + 1];
}
return std::max (u[0], u[1]);
}
float
LUFSMeter::upsample_x4 (int chn, float const x)
{
float* r = _z[chn];
float u[4];
r[47] = x;
/* 4x upsample for true-peak analysis, cosine windowed sinc.
* This effectively introduces a latency of 23 samples
*/
/* clang-format off */
u[0] = r[47];
u[1] = r[ 0] * -2.330790e-05f + r[ 1] * +1.321291e-04f + r[ 2] * -3.394408e-04f + r[ 3] * +6.562235e-04f
+ r[ 4] * -1.094138e-03f + r[ 5] * +1.665807e-03f + r[ 6] * -2.385230e-03f + r[ 7] * +3.268371e-03f
+ r[ 8] * -4.334012e-03f + r[ 9] * +5.604985e-03f + r[10] * -7.109989e-03f + r[11] * +8.886314e-03f
+ r[12] * -1.098403e-02f + r[13] * +1.347264e-02f + r[14] * -1.645206e-02f + r[15] * +2.007155e-02f
+ r[16] * -2.456432e-02f + r[17] * +3.031531e-02f + r[18] * -3.800644e-02f + r[19] * +4.896667e-02f
+ r[20] * -6.616853e-02f + r[21] * +9.788141e-02f + r[22] * -1.788607e-01f + r[23] * +9.000753e-01f
+ r[24] * +2.993829e-01f + r[25] * -1.269367e-01f + r[26] * +7.922398e-02f + r[27] * -5.647748e-02f
+ r[28] * +4.295093e-02f + r[29] * -3.385706e-02f + r[30] * +2.724946e-02f + r[31] * -2.218943e-02f
+ r[32] * +1.816976e-02f + r[33] * -1.489313e-02f + r[34] * +1.217411e-02f + r[35] * -9.891211e-03f
+ r[36] * +7.961470e-03f + r[37] * -6.326144e-03f + r[38] * +4.942202e-03f + r[39] * -3.777065e-03f
+ r[40] * +2.805240e-03f + r[41] * -2.006106e-03f + r[42] * +1.362416e-03f + r[43] * -8.592768e-04f
+ r[44] * +4.834383e-04f + r[45] * -2.228007e-04f + r[46] * +6.607267e-05f + r[47] * -2.537056e-06f;
u[2] = r[ 0] * -1.450055e-05f + r[ 1] * +1.359163e-04f + r[ 2] * -3.928527e-04f + r[ 3] * +8.006445e-04f
+ r[ 4] * -1.375510e-03f + r[ 5] * +2.134915e-03f + r[ 6] * -3.098103e-03f + r[ 7] * +4.286860e-03f
+ r[ 8] * -5.726614e-03f + r[ 9] * +7.448018e-03f + r[10] * -9.489286e-03f + r[11] * +1.189966e-02f
+ r[12] * -1.474471e-02f + r[13] * +1.811472e-02f + r[14] * -2.213828e-02f + r[15] * +2.700557e-02f
+ r[16] * -3.301023e-02f + r[17] * +4.062971e-02f + r[18] * -5.069345e-02f + r[19] * +6.477499e-02f
+ r[20] * -8.625619e-02f + r[21] * +1.239454e-01f + r[22] * -2.101678e-01f + r[23] * +6.359382e-01f
+ r[24] * +6.359382e-01f + r[25] * -2.101678e-01f + r[26] * +1.239454e-01f + r[27] * -8.625619e-02f
+ r[28] * +6.477499e-02f + r[29] * -5.069345e-02f + r[30] * +4.062971e-02f + r[31] * -3.301023e-02f
+ r[32] * +2.700557e-02f + r[33] * -2.213828e-02f + r[34] * +1.811472e-02f + r[35] * -1.474471e-02f
+ r[36] * +1.189966e-02f + r[37] * -9.489286e-03f + r[38] * +7.448018e-03f + r[39] * -5.726614e-03f
+ r[40] * +4.286860e-03f + r[41] * -3.098103e-03f + r[42] * +2.134915e-03f + r[43] * -1.375510e-03f
+ r[44] * +8.006445e-04f + r[45] * -3.928527e-04f + r[46] * +1.359163e-04f + r[47] * -1.450055e-05f;
u[3] = r[ 0] * -2.537056e-06f + r[ 1] * +6.607267e-05f + r[ 2] * -2.228007e-04f + r[ 3] * +4.834383e-04f
+ r[ 4] * -8.592768e-04f + r[ 5] * +1.362416e-03f + r[ 6] * -2.006106e-03f + r[ 7] * +2.805240e-03f
+ r[ 8] * -3.777065e-03f + r[ 9] * +4.942202e-03f + r[10] * -6.326144e-03f + r[11] * +7.961470e-03f
+ r[12] * -9.891211e-03f + r[13] * +1.217411e-02f + r[14] * -1.489313e-02f + r[15] * +1.816976e-02f
+ r[16] * -2.218943e-02f + r[17] * +2.724946e-02f + r[18] * -3.385706e-02f + r[19] * +4.295093e-02f
+ r[20] * -5.647748e-02f + r[21] * +7.922398e-02f + r[22] * -1.269367e-01f + r[23] * +2.993829e-01f
+ r[24] * +9.000753e-01f + r[25] * -1.788607e-01f + r[26] * +9.788141e-02f + r[27] * -6.616853e-02f
+ r[28] * +4.896667e-02f + r[29] * -3.800644e-02f + r[30] * +3.031531e-02f + r[31] * -2.456432e-02f
+ r[32] * +2.007155e-02f + r[33] * -1.645206e-02f + r[34] * +1.347264e-02f + r[35] * -1.098403e-02f
+ r[36] * +8.886314e-03f + r[37] * -7.109989e-03f + r[38] * +5.604985e-03f + r[39] * -4.334012e-03f
+ r[40] * +3.268371e-03f + r[41] * -2.385230e-03f + r[42] * +1.665807e-03f + r[43] * -1.094138e-03f
+ r[44] * +6.562235e-04f + r[45] * -3.394408e-04f + r[46] * +1.321291e-04f + r[47] * -2.330790e-05f;
/* clang-format on */
for (int i = 0; i < 47; ++i) {
r[i] = r[i + 1];
}
float p1 = std::max (fabsf (u[0]), fabsf (u[1]));
float p2 = std::max (fabsf (u[2]), fabsf (u[3]));
return std::max (p1, p2);
}
void
LUFSMeter::calc_true_peak (float const** data, const uint32_t n_samples)
{
for (uint32_t c = 0; c < _n_channels; ++c) {
float const* d = data[c];
for (uint32_t i = 0; i < n_samples; ++i) {
float peak = upsample (c, d[i]);
_dbtp = std::max (_dbtp, peak);
}
}
}

View File

@ -122,6 +122,7 @@ libardour_sources = [
'lua_api.cc',
'luaproc.cc',
'luascripting.cc',
'lufs_meter.cc',
'meter.cc',
'midi_automation_list_binder.cc',
'midi_buffer.cc',