mpd/src/pcm/ReplayGainAnalyzer.hxx
2022-07-04 18:11:21 +02:00

142 lines
3.7 KiB
C++

/*
* Copyright 2021 Max Kellermann <max.kellermann@gmail.com>
*
* This library is based on af_replaygain.c from FFmpeg. Original
* copyright header:
*
* Copyright (c) 1998 - 2009 Conifer Software
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include "Buffer.hxx"
#include <array>
#include <cstdint>
#include <span>
/**
* Analyze a 44.1 kHz / stereo / float32 audio stream and calculate
* peak and ReplayGain values.
*/
class ReplayGainAnalyzer {
public:
static constexpr unsigned CHANNELS = 2;
static constexpr unsigned SAMPLE_RATE = 44100;
using sample_type = float;
using Frame = std::array<sample_type, CHANNELS>;
using DoubleFrame = std::array<double, CHANNELS>;
private:
/*
* Optimized implementation of 10th-order IIR stereo filter.
*/
struct Yule {
static constexpr std::size_t ORDER = 10;
static constexpr std::array coeff_a{
1.00000000000000, -3.47845948550071, 6.36317777566148,
-8.54751527471874, 9.47693607801280, -8.81498681370155,
6.85401540936998, -4.39470996079559, 2.19611684890774,
-0.75104302451432, 0.13149317958808,
};
static constexpr std::array coeff_b{
0.05418656406430, -0.02911007808948, -0.00848709379851,
-0.00851165645469, -0.00834990904936, 0.02245293253339,
-0.02596338512915, 0.01624864962975, -0.00240879051584,
0.00674613682247, -0.00187763777362
};
static_assert(coeff_a.size() == ORDER + 1);
static_assert(coeff_b.size() == ORDER + 1);
unsigned hist_i = ORDER;
std::array<Frame, 128> hist_a{}, hist_b{};
void Filter(const Frame *src, Frame *dst,
std::size_t n_frames) noexcept;
};
/*
* Optimized implementation of 2nd-order IIR stereo filter.
*/
struct Butter {
static constexpr std::size_t ORDER = 2;
static constexpr std::array coeff_a{
1.00000000000000, -1.96977855582618, 0.97022847566350,
};
static constexpr std::array coeff_b{
0.98500175787242, -1.97000351574484, 0.98500175787242,
};
static_assert(coeff_a.size() == ORDER + 1);
static_assert(coeff_b.size() == ORDER + 1);
unsigned hist_i = ORDER;
std::array<Frame, 128> hist_a{}, hist_b{};
void Filter(Frame *samples, std::size_t n_frames) noexcept;
};
static constexpr std::size_t STEPS_PER_DB = 100;
static constexpr unsigned MAX_DB = 120;
std::array<uint_least32_t, STEPS_PER_DB * MAX_DB> histogram{};
float peak = 0;
Yule yule;
Butter butter;
PcmBuffer buffer;
public:
ReplayGainAnalyzer() noexcept;
void Process(std::span<const Frame> src) noexcept;
float GetPeak() const noexcept {
return peak;
}
[[gnu::pure]]
float GetGain() const noexcept;
};
/**
* A variant of #ReplayGainAnalyzer which automatically calls
* Process() with windows of 50ms.
*/
class WindowReplayGainAnalyzer : public ReplayGainAnalyzer {
static constexpr std::size_t WINDOW_FRAMES = SAMPLE_RATE / 20;
std::array<Frame, WINDOW_FRAMES> window_buffer;
std::size_t window_fill = 0;
public:
void Process(std::span<const Frame> src) noexcept;
void Flush() noexcept;
private:
void CopyToBuffer(std::span<const Frame> src) noexcept;
};