Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::signal::LongTermSpectrum Class Reference

#include <loudness_compensation.hpp>

Public Types

enum class  Import { prior , warm }
 How import_bands() treats a saved estimate. More...

Public Member Functions

void prepare (double sample_rate, int channels, const LoudnessCompensationConfig &config, std::span< const double > prior={})
 Control thread; allocates.
void reset () noexcept
 Cold start: back to the prior, and the frame grid restarts at the next sample.
void restart_grid () noexcept
 A transport jump or host reset: the frame grid restarts at the next sample (so how the stream is chopped still cannot matter), and the estimate – what the material sounds like – is kept.
void begin_candidate () noexcept
 A detector run began: collect the would-be new material's own energy average, so a confirmed change starts from what has been heard since, not from zero.
void cancel_candidate () noexcept
 The run broke: that was a passing moment, not new material.
void restart () noexcept
 The material changed.
void merge_with_previous () noexcept
 The material went back to what it was before the last change: it alternates (a kick-only bar and a full one, a hit and its tail).
void settle_alternation () noexcept
 The material came back to what the LAST restart went to, after something else in between that never confirmed a change (it alternates, and the slow estimate already holds both halves): stop chasing it – keep the estimate, average over alternating_time_constant_seconds, and remember both halves.
bool has_arrived () const noexcept
LegSpectra arrived_shape_legs () const noexcept
bool has_previous () const noexcept
bool alternating () const noexcept
LegSpectra previous_shape_legs () const noexcept
LegSpectra alternate_shape_legs (int which) const noexcept
 The two remembered halves of an alternation (0: the earlier, 1: the later).
std::uint64_t merges () const noexcept
bool prepared_for (double sample_rate, int channels, const LoudnessCompensationConfig &config) const noexcept
 The estimator has the geometry prepare() would give these arguments.
std::int64_t frames_since_restart () const noexcept
 Audible frames averaged since the last restart (or cold start).
void wet_becomes_dry () noexcept
 Freeze released: the wet leg becomes the live input again, whose spectrum the dry leg kept warm through the hold.
void legs_uncorrelated () noexcept
 Freeze engaged: the wet leg is now a held past, unrelated to the live input from here on.
template<typename OnFrame>
void push (const float *const *wet, const float *const *dry, int channels, int num_samples, OnFrame &&on_frame) noexcept
 Feed planar samples of the wet leg and, when tracking it, the dry leg (nullptr: the dry leg is the wet one).
template<typename OnFrame>
void push (const float *const *x, int channels, int num_samples, OnFrame &&on_frame) noexcept
void push (const float *const *x, int channels, int num_samples) noexcept
void export_bands (SpectrumBands &out) const noexcept
 The slow estimate, band-compressed (allocation-free).
void import_bands (const SpectrumBands &in, Import how=Import::warm) noexcept
 A band's energy is spread evenly over the bins whose centres fall in it (or given to the bin nearest its centre).
bool prepared () const noexcept
int fft_size () const noexcept
int hop () const noexcept
int samples_to_next_frame () const noexcept
 Samples until the next frame boundary of the grid.
int bins () const noexcept
double bin_hz () const noexcept
double sample_rate () const noexcept
double hop_seconds () const noexcept
const LoudnessCompensationConfig & config () const noexcept
std::uint64_t observed_frames () const noexcept
std::uint64_t gated_frames () const noexcept
 Frames under the absolute gate (silence).
std::uint64_t level_drops () const noexcept
std::uint64_t restarts () const noexcept
double last_frame_lufs () const noexcept
 K-weighted loudness of the last frame's wet leg (LUFS-equivalent).
double prior_weight () const noexcept
 The prior's remaining share of the estimate (1 until material is heard).
LegSpectra prior_legs () const noexcept
 The prior's per-bin share (sums to one), as legs.
void set_prior_fade_scale (double scale) noexcept
 How fast the prior fades from the next frame on, as a multiple of prior_seconds' rate (1: as configured).
bool last_frame_audible () const noexcept
 Whether the last frame was audible (above the absolute gate).
std::span< const double > spectrum () const noexcept
 The slow (wet) estimate per bin, K-weighted, normalised to sum 1.
LegSpectra legs () const noexcept
 All the legs' slow estimates on the wet leg's scale.
LegSpectra fast_legs () const noexcept
 The same of the fast estimate.
LegSpectra fast_shape_legs () const noexcept
 The change detector's pair: fast and slow averages of each audible frame's wet SHAPE (level-independent), as coherent single-leg spectra.
LegSpectra slow_shape_legs () const noexcept
std::int64_t shape_frames () const noexcept
double weight (double lo_hz, double hi_hz) const noexcept
 The slow wet estimate's energy between two frequencies; the whole weighs 1.
const SpectrumCdf & cdf () const noexcept

Static Public Member Functions

static int fft_size_for (double sample_rate, const LoudnessCompensationConfig &config) noexcept
 The FFT size prepare() picks for a rate and config.
static int bins_for (double sample_rate, const LoudnessCompensationConfig &config) noexcept

Member Enumeration Documentation

◆ Import

How import_bands() treats a saved estimate.

Enumerator
prior 

As a better prior: playback starts at the saved estimate's level and the material's own estimate takes over exactly as from a cold start (a restored session may play different material).

warm 

As the estimate itself (the same stream, carried across a change of geometry).

Member Function Documentation

◆ alternate_shape_legs()

LegSpectra pulp::signal::LongTermSpectrum::alternate_shape_legs ( int which) const
inlinenodiscardnoexcept

The two remembered halves of an alternation (0: the earlier, 1: the later).

◆ alternating()

bool pulp::signal::LongTermSpectrum::alternating ( ) const
inlinenodiscardnoexcept

◆ arrived_shape_legs()

LegSpectra pulp::signal::LongTermSpectrum::arrived_shape_legs ( ) const
inlinenodiscardnoexcept

◆ begin_candidate()

void pulp::signal::LongTermSpectrum::begin_candidate ( )
inlinenoexcept

A detector run began: collect the would-be new material's own energy average, so a confirmed change starts from what has been heard since, not from zero.

The frames whose windows still reach back across the change (one window from the frame that began the run) are left out: energy-weighted, a little of a louder past would bias it for seconds.

◆ bin_hz()

double pulp::signal::LongTermSpectrum::bin_hz ( ) const
inlinenodiscardnoexcept

◆ bins()

int pulp::signal::LongTermSpectrum::bins ( ) const
inlinenodiscardnoexcept

◆ bins_for()

int pulp::signal::LongTermSpectrum::bins_for ( double sample_rate,
const LoudnessCompensationConfig & config )
inlinestaticnodiscardnoexcept

◆ cancel_candidate()

void pulp::signal::LongTermSpectrum::cancel_candidate ( )
inlinenoexcept

The run broke: that was a passing moment, not new material.

◆ cdf()

const SpectrumCdf & pulp::signal::LongTermSpectrum::cdf ( ) const
inlinenodiscardnoexcept

◆ config()

const LoudnessCompensationConfig & pulp::signal::LongTermSpectrum::config ( ) const
inlinenodiscardnoexcept

◆ export_bands()

void pulp::signal::LongTermSpectrum::export_bands ( SpectrumBands & out) const
inlinenoexcept

The slow estimate, band-compressed (allocation-free).

◆ fast_legs()

LegSpectra pulp::signal::LongTermSpectrum::fast_legs ( ) const
inlinenodiscardnoexcept

The same of the fast estimate.

◆ fast_shape_legs()

LegSpectra pulp::signal::LongTermSpectrum::fast_shape_legs ( ) const
inlinenodiscardnoexcept

The change detector's pair: fast and slow averages of each audible frame's wet SHAPE (level-independent), as coherent single-leg spectra.

◆ fft_size()

int pulp::signal::LongTermSpectrum::fft_size ( ) const
inlinenodiscardnoexcept

◆ fft_size_for()

int pulp::signal::LongTermSpectrum::fft_size_for ( double sample_rate,
const LoudnessCompensationConfig & config )
inlinestaticnodiscardnoexcept

The FFT size prepare() picks for a rate and config.

◆ frames_since_restart()

std::int64_t pulp::signal::LongTermSpectrum::frames_since_restart ( ) const
inlinenodiscardnoexcept

Audible frames averaged since the last restart (or cold start).

◆ gated_frames()

std::uint64_t pulp::signal::LongTermSpectrum::gated_frames ( ) const
inlinenodiscardnoexcept

Frames under the absolute gate (silence).

◆ has_arrived()

bool pulp::signal::LongTermSpectrum::has_arrived ( ) const
inlinenodiscardnoexcept

◆ has_previous()

bool pulp::signal::LongTermSpectrum::has_previous ( ) const
inlinenodiscardnoexcept

◆ hop()

int pulp::signal::LongTermSpectrum::hop ( ) const
inlinenodiscardnoexcept

◆ hop_seconds()

double pulp::signal::LongTermSpectrum::hop_seconds ( ) const
inlinenodiscardnoexcept

◆ import_bands()

void pulp::signal::LongTermSpectrum::import_bands ( const SpectrumBands & in,
Import how = Import::warm )
inlinenoexcept

A band's energy is spread evenly over the bins whose centres fall in it (or given to the bin nearest its centre).

The frame grid restarts.

◆ last_frame_audible()

bool pulp::signal::LongTermSpectrum::last_frame_audible ( ) const
inlinenodiscardnoexcept

Whether the last frame was audible (above the absolute gate).

◆ last_frame_lufs()

double pulp::signal::LongTermSpectrum::last_frame_lufs ( ) const
inlinenodiscardnoexcept

K-weighted loudness of the last frame's wet leg (LUFS-equivalent).

◆ legs()

LegSpectra pulp::signal::LongTermSpectrum::legs ( ) const
inlinenodiscardnoexcept

All the legs' slow estimates on the wet leg's scale.

◆ legs_uncorrelated()

void pulp::signal::LongTermSpectrum::legs_uncorrelated ( )
inlinenoexcept

Freeze engaged: the wet leg is now a held past, unrelated to the live input from here on.

◆ level_drops()

std::uint64_t pulp::signal::LongTermSpectrum::level_drops ( ) const
inlinenodiscardnoexcept

◆ merge_with_previous()

void pulp::signal::LongTermSpectrum::merge_with_previous ( )
inlinenoexcept

The material went back to what it was before the last change: it alternates (a kick-only bar and a full one, a hit and its tail).

The slow estimate becomes the mix of both and averages over alternating_time_constant_seconds from here, so the gain holds steady instead of chasing each half.

◆ merges()

std::uint64_t pulp::signal::LongTermSpectrum::merges ( ) const
inlinenodiscardnoexcept

◆ observed_frames()

std::uint64_t pulp::signal::LongTermSpectrum::observed_frames ( ) const
inlinenodiscardnoexcept

◆ prepare()

void pulp::signal::LongTermSpectrum::prepare ( double sample_rate,
int channels,
const LoudnessCompensationConfig & config,
std::span< const double > prior = {} )
inline

Control thread; allocates.

prior: per-bin power on this estimator's grid (see bins_for()), any scale; empty means flat. The estimate IS the prior until the first audible frame (both legs, coherent).

◆ prepared()

bool pulp::signal::LongTermSpectrum::prepared ( ) const
inlinenodiscardnoexcept

◆ prepared_for()

bool pulp::signal::LongTermSpectrum::prepared_for ( double sample_rate,
int channels,
const LoudnessCompensationConfig & config ) const
inlinenodiscardnoexcept

The estimator has the geometry prepare() would give these arguments.

◆ previous_shape_legs()

LegSpectra pulp::signal::LongTermSpectrum::previous_shape_legs ( ) const
inlinenodiscardnoexcept

◆ prior_legs()

LegSpectra pulp::signal::LongTermSpectrum::prior_legs ( ) const
inlinenodiscardnoexcept

The prior's per-bin share (sums to one), as legs.

◆ prior_weight()

double pulp::signal::LongTermSpectrum::prior_weight ( ) const
inlinenodiscardnoexcept

The prior's remaining share of the estimate (1 until material is heard).

◆ push() [1/3]

template<typename OnFrame>
void pulp::signal::LongTermSpectrum::push ( const float *const * wet,
const float *const * dry,
int channels,
int num_samples,
OnFrame && on_frame )
inlinenoexcept

Feed planar samples of the wet leg and, when tracking it, the dry leg (nullptr: the dry leg is the wet one).

on_frame(samples_consumed) runs after every frame the call completes (audible or gated), with the estimate already updated. Prepared channels beyond channels are fed silence for these samples, so a narrower call never re-reads older audio.

◆ push() [2/3]

void pulp::signal::LongTermSpectrum::push ( const float *const * x,
int channels,
int num_samples )
inlinenoexcept

◆ push() [3/3]

template<typename OnFrame>
void pulp::signal::LongTermSpectrum::push ( const float *const * x,
int channels,
int num_samples,
OnFrame && on_frame )
inlinenoexcept

◆ reset()

void pulp::signal::LongTermSpectrum::reset ( )
inlinenoexcept

Cold start: back to the prior, and the frame grid restarts at the next sample.

◆ restart()

void pulp::signal::LongTermSpectrum::restart ( )
inlinenoexcept

The material changed.

Both estimates start again from the candidate (the new material's energy average since the change was first seen), or empty when there is none, and go on as running means. Energy weighting is why: seeded from anything that still holds the old material, a louder past would outweigh quieter new material for seconds. The material before is remembered (merge_with_previous), and the detector's slow shape is re-armed on the new material.

◆ restart_grid()

void pulp::signal::LongTermSpectrum::restart_grid ( )
inlinenoexcept

A transport jump or host reset: the frame grid restarts at the next sample (so how the stream is chopped still cannot matter), and the estimate – what the material sounds like – is kept.

◆ restarts()

std::uint64_t pulp::signal::LongTermSpectrum::restarts ( ) const
inlinenodiscardnoexcept

◆ sample_rate()

double pulp::signal::LongTermSpectrum::sample_rate ( ) const
inlinenodiscardnoexcept

◆ samples_to_next_frame()

int pulp::signal::LongTermSpectrum::samples_to_next_frame ( ) const
inlinenodiscardnoexcept

Samples until the next frame boundary of the grid.

◆ set_prior_fade_scale()

void pulp::signal::LongTermSpectrum::set_prior_fade_scale ( double scale)
inlinenoexcept

How fast the prior fades from the next frame on, as a multiple of prior_seconds' rate (1: as configured).

A caller that sees the material agree with a RESTORED prior slows the fade, so the few frames the material's own estimate starts from do not swing it.

◆ settle_alternation()

void pulp::signal::LongTermSpectrum::settle_alternation ( )
inlinenoexcept

The material came back to what the LAST restart went to, after something else in between that never confirmed a change (it alternates, and the slow estimate already holds both halves): stop chasing it – keep the estimate, average over alternating_time_constant_seconds, and remember both halves.

◆ shape_frames()

std::int64_t pulp::signal::LongTermSpectrum::shape_frames ( ) const
inlinenodiscardnoexcept

◆ slow_shape_legs()

LegSpectra pulp::signal::LongTermSpectrum::slow_shape_legs ( ) const
inlinenodiscardnoexcept

◆ spectrum()

std::span< const double > pulp::signal::LongTermSpectrum::spectrum ( ) const
inlinenodiscardnoexcept

The slow (wet) estimate per bin, K-weighted, normalised to sum 1.

◆ weight()

double pulp::signal::LongTermSpectrum::weight ( double lo_hz,
double hi_hz ) const
inlinenodiscardnoexcept

The slow wet estimate's energy between two frequencies; the whole weighs 1.

◆ wet_becomes_dry()

void pulp::signal::LongTermSpectrum::wet_becomes_dry ( )
inlinenoexcept

Freeze released: the wet leg becomes the live input again, whose spectrum the dry leg kept warm through the hold.

Both estimates take the FAST dry estimate (the live input as it is now, not as it was when the hold began), and the slow one goes on as a running mean.


The documentation for this class was generated from the following file: