Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
loudness_compensation.hpp File Reference

Loudness make-up for a static (or slowly edited) spectral shape, weighted by the long-term spectrum of the material going through it. More...

#include <pulp/signal/biquad.hpp>
#include <pulp/signal/fft.hpp>
#include <pulp/signal/frequency_response.hpp>
#include <pulp/signal/multi_channel_meter.hpp>
#include <pulp/signal/windowing.hpp>
#include <algorithm>
#include <array>
#include <cmath>
#include <complex>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <span>
#include <vector>

Go to the source code of this file.

Classes

struct  pulp::signal::LoudnessCompensationConfig
 Every tunable of the stateful stages. More...
struct  pulp::signal::MakeupLimits
struct  pulp::signal::LegSpectra
 The long-term spectra of the two legs on one bin grid, K-weighted, on a common scale. More...
struct  pulp::signal::BandPowerGain
 One band of a band-wise response: its frequency span and power gain. More...
class  pulp::signal::SpectrumCdf
 The normalised cumulative of a power spectrum on a uniform bin grid. More...
class  pulp::signal::MinimumPhaseResponse
 The minimum-phase response with a given magnitude on an FFT grid, by the folded real cepstrum: ln|H| -> cepstrum -> fold onto positive quefrency -> exp(FFT). More...
struct  pulp::signal::SpectrumBands
 A band-compressed, rate-independent copy of a LongTermSpectrum's estimate, for saving with a session and restoring warm. More...
class  pulp::signal::MaterialChangeDetector
 Detects a genuine change of material from the gap between the make-up the fast and the slow estimates give the current shape. More...
class  pulp::signal::LongTermSpectrum
class  pulp::signal::MakeupTarget
 What the make-up gain is heading for. More...

Namespaces

namespace  pulp
namespace  pulp::signal

Functions

double pulp::signal::k_weighting_power (double hz, const pulp::signal::KWeightingCoefficients &k, double sample_rate) noexcept
 |K(f)|^2 for BS.1770 K-weighting at sample_rate, through Pulp's own K-weighting design and biquad response evaluator.
float pulp::signal::clamp_makeup_db_ (double ratio, MakeupLimits limits) noexcept
float pulp::signal::blend_makeup_gain_db (std::span< const std::complex< double > > response, const LegSpectra &s, double mix, MakeupLimits limits) noexcept
 Make-up, dB, for the two-leg model in the file comment: response is the wet leg's complex response per bin, s the legs' spectra on the same grid, mix the wet share.
float pulp::signal::makeup_gain_db (std::span< const double > response_power, std::span< const double > spectrum_power, MakeupLimits limits) noexcept
 The single-leg, real-response form: per-bin power response |H|^2 against a per-bin power spectrum on the same grid.
template<typename WeightFn>
float pulp::signal::band_makeup_gain_db (std::span< const BandPowerGain > bands, WeightFn &&weight, MakeupLimits limits) noexcept
 The single-leg form over bands, each weighted by weight(lo_hz, hi_hz) – the material's energy between two frequencies (SpectrumCdf::weight, or any reference weighting).

Detailed Description

Loudness make-up for a static (or slowly edited) spectral shape, weighted by the long-term spectrum of the material going through it.

This header depends on Pulp and the standard library only; it has no product-specific types in its public interface. Its focused tests link Pulp::signal and Catch2 only.

THE MODEL. A shaping effect's output is a blend of a WET leg (the shaped signal, w, through the response H) and a DRY leg (d) at a mix m:

y = m H w + (1 - m) d

With long-term, K-weighted per-bin spectra Pww = <|W|^2>, Pdd = <|D|^2> and the cross-spectrum Pwd = <W conj(D)>, the output power per bin is

m^2 |H|^2 Pww + (1-m)^2 Pdd + 2 m (1-m) Re(H Pwd)

and the make-up is the ratio of the flat (H = 1) output to this, summed over bins: -10 log10(sum out / sum flat), clamped. When the wet leg IS the dry leg (an ordinary effect) all three spectra are one P and this is sum P |m H + 1 - m|^2 / sum P; when the wet leg is unrelated to the dry one (a freeze or a hold playing while the input moves on) the cross term vanishes and the two legs add as powers. Nothing has to be told which case it is in: the cross-spectrum measures it. The phase of H matters only through the cross term, i.e. only below 100 % mix.

THE PIPELINE, exposed stage by stage:

  1. k_weighting_power() |K(f)|^2 of BS.1770 at a frequency.
  2. LongTermSpectrum stateful, RT-safe: Hann/FFT frames on a fixed sample grid, K-weighted, energy-weighted slow (3 s) and fast (0.4 s) averages of Pww (and, tracking a dry leg, Pdd and Pwd); an absolute silence gate; a level-drop rule; restart-from-fast on a change of material; a warm start from a saved SpectrumBands; a cold start from a caller-supplied prior whose say decays. MaterialChangeDetector when the fast and slow make-up disagree.
  3. MinimumPhaseResponse the minimum-phase response with a given magnitude (folded cepstrum), for a realisation that designs minimum phase.
  4. blend_makeup_gain_db() PURE: the model above. makeup_gain_db() PURE: the single-leg, real-response form. band_makeup_gain_db() PURE: the same over bands, weighted by any SpectrumCdf cumulative weighting.
  5. MakeupTarget stateful: an edit moves the target at once; the material moves it with a slew that is slow near the target and proportional to the distance far from it.

RT CONTRACT. prepare() allocates (control thread). Everything else – reset(), push(), MinimumPhaseResponse::compute(), the pure functions, SpectrumCdf::assign() into a prepared size, MakeupTarget – is allocation-free, lock-free and reads no clock. LongTermSpectrum advances on a frame grid counted in samples from prepare()/reset(), so its state after N samples does not depend on how the N samples were chunked.