HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
Metrics

Common audio-related metrics. More...

Classes

class  MetricQuery< ValueType >
 Manages the metrics calculations. More...
 

Functions

template<typename SampleType >
MetricQuery< double > centreTime (const ImpulseResponse< SampleType > &ir)
 Calculates the centre time of an impulse response.
 
template<typename SampleType >
MetricQuery< double > channelCorrelation (const AudioBuffer< SampleType > &buffer)
 Calculates zero-lag normalized cross-correlation between two channels of an audio buffer.
 
template<typename SampleType >
MetricQuery< double > crestFactor (const AudioBuffer< SampleType > &buffer)
 Calculates linear crest factor for a single channel of an audio buffer.
 
template<typename SampleType >
MetricQuery< double > esr (const AudioBuffer< SampleType > &referenceBuffer, const AudioBuffer< SampleType > &estimatedBuffer)
 Calculates error-to-signal ratio (ESR)
 
MetricQuery< double > interpolatedPeakFrequency (const Spectrum &spectrum)
 Returns the center frequency of the loudest FFT bin.
 
template<typename SampleType >
MetricQuery< double > lagAtMaxCrossCorrelation (const AudioBuffer< SampleType > &bufferA, const AudioBuffer< SampleType > &bufferB, double maxLagSeconds, double minAbsBestCorrelation=0.5, CorrelationSearchMode searchMode=bestAbsoluteCorrelation)
 Calculates lag corresponding to maximum normalized cross-correlation between two audio buffers.
 
MetricQuery< double > logSpectralDistance (const Spectrum &spectrumA, const Spectrum &spectrumB, Normalise normaliseLevels=Normalise::no, double smoothingCents=100.0)
 Calculates difference between two spectra in log-frequency domain.
 
MetricQuery< double > loudestBinFrequency (const Spectrum &spectrum)
 Returns the center frequency of the loudest FFT bin.
 
MetricQuery< double > loudestBinMagnitude (const Spectrum &spectrum)
 Calculates the magnitude of the loudest FFT bin.
 
template<typename SampleType >
MetricQuery< double > maxCrossCorrelation (const AudioBuffer< SampleType > &bufferA, const AudioBuffer< SampleType > &bufferB, double maxLagSeconds, CorrelationSearchMode searchMode=bestAbsoluteCorrelation)
 Calculates maximum normalized cross-correlation between two audio buffers.
 
MetricQuery< double > quinns2 (const Spectrum &spectrum)
 Returns somewhat accurate loudest frequency in the spectrum.
 
template<typename SampleType >
MetricQuery< double > rms (const AudioBuffer< SampleType > &buffer)
 Calculates root mean square (RMS) of a signal.
 
template<typename SampleType >
MetricQuery< double > rt60 (const ImpulseResponse< SampleType > &ir, RT60::Method method=RT60::Method::edt)
 Estimates the RT60 reverberation time of an impulse response.
 
template<typename SampleType >
MetricQuery< double > samplePeak (const AudioBuffer< SampleType > &audioBuffer)
 Calculates Sample Peak of an audio buffer.
 
template<typename SampleType >
MetricQuery< double > snr (const AudioBuffer< SampleType > &signalPlusNoise, const AudioBuffer< SampleType > &signal)
 Calculates signal-to-noise ratio (SNR)
 
template<typename SampleType >
MetricQuery< double > snra (const AudioBuffer< SampleType > &estimatedBufferAtNativeSR, const AudioBuffer< SampleType > &referenceBufferAtHighSR)
 Calculates signal-to-aliasing-noise ratio (a.k.a. SNRA, SNRa or sometimes SANR)
 
MetricQuery< double > spectralCentroid (const Spectrum &spectrum, SpectralCentroid::Weighting weighting=SpectralCentroid::Weighting::magnitude)
 Calculates spectral centroid.
 
MetricQuery< double > spectralFlatness (const Spectrum &spectrum, double floorLinear=1e-16)
 Calculates spectral flatness, also known as Wiener entropy, or tonality coefficient.
 
MetricQuery< double > spectralLogLogSlope (const Spectrum &spectrum, double smoothingCents=1200.0)
 Calculates slope of the spectrum in logX-logY domain.
 
MetricQuery< double > thd (const Spectrum &spectrum, THD::ExperimentSetup experimentSetup)
 Calculates the total harmonic distortion (THD) of a spectrum.
 
template<typename SampleType >
MetricQuery< double > truePeak (const AudioBuffer< SampleType > &audioBuffer, Oversampling oversamplingRatio=Oversampling::x4, typename TruePeak< SampleType >::FilterQuality filterQuality=TruePeak< SampleType >::FilterQuality::low)
 Estimates true peak (inter-sample peak) level.
 
template<typename SampleType >
MetricQuery< double > zcr (const AudioBuffer< SampleType > &buffer)
 Calculates zero-crossing rate (ZCR) of a signal.
 

Detailed Description

Common audio-related metrics.

Function Documentation

◆ centreTime()

template<typename SampleType >
MetricQuery< double > centreTime ( const ImpulseResponse< SampleType > &  ir)

Calculates the centre time of an impulse response.

Centre time (center time) is the energy-weighted mean arrival time of the impulse response. It describes where the energy of the response is centred in time: responses with more energy occurring later have a larger centre time. Can be used to verify shape of the decay, in combination with hart::rt60().

Calculated as:

\[ T_s=\frac{\sum_{n=0}^{N-1} t_n h[n]^2} {\sum_{n=0}^{N-1} h[n]^2} \]

(T_s = sum(t_n * h[n]^2) / sum(h[n]^2)),

where \(h[n]\) is the impulse response sample, and \(t_n\) is its time position (offset) in seconds.

Supported units are Unit::seconds, Unit::native (same as seconds) and Unit::frames. If Unit::frames is requested, the result will be a fractional value.

The result is NaN if the impulse response contains no energy.

Template Parameters
SampleTypeFloating-point sample type of the impulse response.
Parameters
irImpulse response to analyse.
Returns
A MetricQuery containing the centre time for each channel. Either in seconds, or in frames, depending on requested unit. May return NaN.

Definition at line 50 of file hart_centre_time.hpp.

◆ channelCorrelation()

template<typename SampleType >
MetricQuery< double > channelCorrelation ( const AudioBuffer< SampleType > &  buffer)

Calculates zero-lag normalized cross-correlation between two channels of an audio buffer.

Operates per specified pairs of channels, use a reducer to get a scalar value (see Reducers). If custom channel subset is not specified via ch(), defaults to a set of all unique unordered channel pairs, e.g. {{0, 1}} for stereo buffer, or {{0, 1}, {0, 2}, {1, 2}, {1, 3}, {2, 3}} for 3-channel buffer. A specific order of pairs is not guaranteed, unless you explicitly pass a custom list of channel pairs via ch().

Returns a unitless value, suppoert Unit::none and Unit::native, which are the same here, so there's no need to request any unit with a chained as() call.

Usage examples

// All defaults - correlation between channels 0 and 1 (left and right)
channelCorrelation (stereoBuffer).get();
// Highest value of three cross-correlations (channels 0 vs 1, 0 vs 2 and 1 vs 3)
channelCorrelation (multichannelBuffer).ch ({{0, 1}, {0, 2}, {1, 3}}).get (max());
MetricQuery< double > channelCorrelation(const AudioBuffer< SampleType > &buffer)
Calculates zero-lag normalized cross-correlation between two channels of an audio buffer.

Be careful when you want to specify only one channel pair:

// /!\ Resolves to 2 matched channel pairs - 0 vs 0 and 1 vs 1.
// Probably not what you're looking for!
channelCorrelation (stereoBuffer).ch ({0, 1});
// Just one pair - 0 vs 1. Note the double curly braces.
channelCorrelation (stereoBuffer).ch ({{0, 1}});

Uses the normalized cross-correlation formula:

\[ \rho = \frac{\sum_n x[n]\,y[n]} {\sqrt{\left(\sum_n x[n]^2\right)\left(\sum_n y[n]^2\right)}} \]

(sum (x[n] * y[n]) / sqrt (sum (x[n]^2) * sum (y[n]^2)))

where x and y are the selected channels of the same buffer.

The returned value is in the range [-1, 1]:

  • 1.0 means perfectly correlated channels
  • 0.0 means no linear correlation
  • -1.0 means perfectly inverted polarity

The function returns NaN if correlation is undefined, such as when:

  • one of the selected channels is silent
  • the buffer contains zero frames
Parameters
bufferInput audio buffer
Returns
Chainable MetricQuery, which calculates normalized correlation coefficient per pair of channels, or NaN if correlation is undefined
Template Parameters
SampleTypeFloating point sample type, typically float or double
Exceptions
hart::IndexErrorif either channel index is out of bounds

Definition at line 70 of file hart_channel_correlation.hpp.

◆ crestFactor()

template<typename SampleType >
MetricQuery< double > crestFactor ( const AudioBuffer< SampleType > &  buffer)

Calculates linear crest factor for a single channel of an audio buffer.

Crest factor is defined as the ratio between the absolute peak value and RMS value:

\[ \frac{\max_n \left|x[n]\right|}{\sqrt{\frac{1}{N}\sum_n x[n]^2}} \]

(max (abs (x[n])) / sqrt ((1 / N) * sum (x[n]^2)))

Calculates values independently for each channel. Use a reducer to get a scalar value (see Reducers). Supports Unit::linear (default) and Unit::dB units. Decibel conversion is performed as 20 * log10 (x), i.e. the amplitude-ratio form, see hart::ratioToDecibels().

Usage example:

// One channel, default unit (linear)
const double cfLinear = crestFactor (monoBuffer).get();
// One channel, decibels
const double cfDb = crestFactor (monoBuffer).as (dB).get();
// Calculates crest factor for each channel as linear value, which is a default (native) unit
// for this metric, then returns index of the largest value. Since channel subset is default
// (no ch() call), the returns index is the same as channel's index.
const size_t mostDynamicChannel = crestFactor (multiChannelBuffer).as (linear).get (argmax());
// Calculates crest factor for channels 0, 3 and 5 in dB, then returns maximum of those three
const double cfMaxDb = crestFactor (multiChannelBuffer).as (dB).ch ({0, 3, 5}).get (max());
MetricQuery< double > crestFactor(const AudioBuffer< SampleType > &buffer)
Calculates linear crest factor for a single channel of an audio buffer.
@ dB
Value of something in decibels. Can represent voltage, power, or a domain-specific unit like "LUFS" o...
@ linear
Value of a sample (voltage) in a linear domain.
Returns the zero-based index of the largest element in the range.
Parameters
bufferInput audio buffer
Returns
Chainable MetricQuery, which calculates crest factor in linear ratio units or dB
  • Returns NaN if the audio buffer contains zero frames.
  • Returns inf if the selected channel is silent, making RMS equal to (or close to) zero.
Template Parameters
SampleTypeFloating point sample type of the audio buffer, typically float or double
Exceptions
hart::IndexErrorif the channel index is out of bounds, or slice boundary is out of range

Definition at line 56 of file hart_crest_factor.hpp.

◆ esr()

template<typename SampleType >
MetricQuery< double > esr ( const AudioBuffer< SampleType > &  referenceBuffer,
const AudioBuffer< SampleType > &  estimatedBuffer 
)

Calculates error-to-signal ratio (ESR)

ESR is a useful way to express the degree of similarity between two signals or waveforms, calculated as:

\[ ESR=\frac{\sum_{k=0}^{N-1} (x - y) ^ 2}{\sum_{k=0}^{N-1} x^2}$$ \]

(sum ((x - y) ** 2) / sum (x ** 2))

Where x is a signal represented by referenceBuffer and y is represented by estimatedBuffer. It's a ratio, so appropriate units are Unit::native and Unit::ratio. ESR = 0 means two signals are identical.

Parameters
referenceBufferA buffer representing x in the formula above
estimatedBufferA buffer representing y in the formula above
Returns
Chainable MetricQuery, which calculates per-channel ESR values

Definition at line 35 of file hart_esr.hpp.

◆ interpolatedPeakFrequency()

MetricQuery< double > interpolatedPeakFrequency ( const Spectrum spectrum)
inline

Returns the center frequency of the loudest FFT bin.

Finds the maximum-magnitude FFT bin independently for each channel. Use reducers to combine multi-channel results (see Reducers).

Supports Unit::Hz (native/default) unit, so requesting a unit explicitly via MetricQuery::as() is not required.

This metric operates on FFT bins exactly as stored in the Spectrum. In a not-so-likely event where multiple bins have exactly the same magnitube, the lowest frequency will be returned. This is not intended for precise pitch tracking, but still useful for some types of tests, based around looking for peaks in a band of frequencies, but not a specific frequency.

For more precise peak frequency estimation, consider using Quinn's second estimator (hart::quinns2() metric) instead.

Usage examples:

// Loudest bin frequency in Hz
const double loudestFreqHz = loudestBinFrequency (monoSpectrum).get();
// Loudest bin frequency, but only inside a frequency range
const double loudestMidBanFreqHz =
loudestBinFrequency (monoSpectrum)
.at (Slice::frequency (500_Hz, 2000_Hz))
.get();
// Loudest bin frequency, but only inside a specific channel subset.
Calculated per chanel, then averaged.
const double loudestFreqHz =
loudestBinMagnitude (multiChannelSpectrum)
.ch ({0, 2, 4})
.get (mean());
MetricQuery< double > loudestBinMagnitude(const Spectrum &spectrum)
Calculates the magnitude of the loudest FFT bin.
MetricQuery< double > loudestBinFrequency(const Spectrum &spectrum)
Returns the center frequency of the loudest FFT bin.
Parameters
spectrumInput frequency-domain spectrum
Exceptions
hart::UnitErrorif unsupported unit is requested

Definition at line 58 of file hart_interpolated_peak_frequency.hpp.

◆ lagAtMaxCrossCorrelation()

template<typename SampleType >
MetricQuery< double > lagAtMaxCrossCorrelation ( const AudioBuffer< SampleType > &  bufferA,
const AudioBuffer< SampleType > &  bufferB,
double  maxLagSeconds,
double  minAbsBestCorrelation = 0.5,
CorrelationSearchMode  searchMode = bestAbsoluteCorrelation 
)

Calculates lag corresponding to maximum normalized cross-correlation between two audio buffers.

Searches for the lag producing the strongest normalized cross-correlation independently for each selected pair of channels.

Cross-correlation is calculated using the following formula:

\[ \frac{\sum_n x[n]\,y[n+k]} {\sqrt{ \left(\sum_n x[n]^2\right) \left(\sum_n y[n+k]^2\right) }} \]

(sum (x[n] * y[n + k]) / sqrt (sum (x[n]^2) * sum (y[n + k]^2)))

where:

  • x[n] is the left-hand-side signal
  • y[n + k] is the right-hand-side signal shifted by lag k
  • k is searched in the range [-maxLag, +maxLag]

Positive lag means that bufferB is delayed relative to bufferA.

Depending on searchMode, the metric either:

  • searches for the largest signed correlation value
  • or searches for the largest absolute correlation value

Correlation is calculated independently for each selected pair of channels. Use a reducer to combine multiple lag values into a scalar.

Supports Unit::frames (default/native) and Unit::seconds. For conversion to seconds, it uses sample rate metadata contained in the provided buffers.

Usage examples:

// Detect latency in frames
const double lagFrames = lagAtMaxCrossCorrelation (input, output, 100_ms).get();
// Same, but returned in seconds
const double lagSeconds = lagAtMaxCrossCorrelation (input, output, 100_ms)
.as (seconds)
.get();
// Strongest lag across matched stereo channels
const double maxLagFrames = lagAtMaxCrossCorrelation (input, output, 100_ms)
.get (max());
// Custom channel mapping
// Lags between:
// - input, channel 0 vs output, channel 1
// - input, channel 3 vs output, channel 3
// - input, channel 1 vs output, channel 2
const double swappedLagFrames = lagAtMaxCrossCorrelation (multiChanneInput, multiChanneOutput, 100_ms)
.ch ({{0, 1}, {3, 3}, {1, 2}})
.get (mean());
MetricQuery< double > lagAtMaxCrossCorrelation(const AudioBuffer< SampleType > &bufferA, const AudioBuffer< SampleType > &bufferB, double maxLagSeconds, double minAbsBestCorrelation=0.5, CorrelationSearchMode searchMode=bestAbsoluteCorrelation)
Calculates lag corresponding to maximum normalized cross-correlation between two audio buffers.
@ seconds
Time stamps, intervals, durations.
Returns the largest element in the range.

Notes:

  • Gain differences do not affect the result due to normalization.
  • Returned lag may be negative.
  • If no valid overlap exists, returns NaN.
Parameters
bufferALeft-hand-side audio buffer
bufferBRight-hand-side audio buffer
maxLagSecondsMaximum lag to search in seconds
minAbsBestCorrelationIf best correlation (rectified) is under this value, then signals will be considered to not have valid overlap, and result will be NaN
searchModeControls how the best lag is selected
Returns
MetricQuery containing lag values corresponding to best cross-correlation
Template Parameters
SampleTypeFloating point sample type, typically float or double
Exceptions
hart::ValueErrorIf maxLagSeconds is negative
hart::SampleRateErrorIf sample rates differ
hart::IndexErrorIf requested channel indices are out of range
hart::UnitErrorIf unsupported unit is requested

Definition at line 99 of file hart_lag_at_max_cross_correlation.hpp.

◆ logSpectralDistance()

MetricQuery< double > logSpectralDistance ( const Spectrum spectrumA,
const Spectrum spectrumB,
Normalise  normaliseLevels = Normalise::no,
double  smoothingCents = 100.0 
)
inline

Calculates difference between two spectra in log-frequency domain.

Definition at line 24 of file hart_log_spectral_distance.hpp.

◆ loudestBinFrequency()

MetricQuery< double > loudestBinFrequency ( const Spectrum spectrum)
inline

Returns the center frequency of the loudest FFT bin.

Finds the maximum-magnitude FFT bin independently for each channel. Use reducers to combine multi-channel results (see Reducers).

Supports Unit::Hz (native/default) unit, so requesting a unit explicitly via MetricQuery::as() is not required.

This metric operates on FFT bins exactly as stored in the Spectrum. In a not-so-likely event where multiple bins have exactly the same magnitube, the lowest frequency will be returned. This is not intended for precise pitch tracking, but still useful for some types of tests, based around looking for peaks in a band of frequencies, but not a specific frequency.

For more precise peak frequency estimation, consider using Quinn's second estimator (hart::quinns2() metric) instead.

Usage examples:

// Loudest bin frequency in Hz
const double loudestFreqHz = loudestBinFrequency (monoSpectrum).get();
// Loudest bin frequency, but only inside a frequency range
const double loudestMidBanFreqHz =
loudestBinFrequency (monoSpectrum)
.at (Slice::frequency (500_Hz, 2000_Hz))
.get();
// Loudest bin frequency, but only inside a specific channel subset.
Calculated per chanel, then averaged.
const double loudestFreqHz =
loudestBinMagnitude (multiChannelSpectrum)
.ch ({0, 2, 4})
.get (mean());
Parameters
spectrumInput frequency-domain spectrum
Exceptions
hart::UnitErrorif unsupported unit is requested

Definition at line 58 of file hart_loudest_bin_frequency.hpp.

◆ loudestBinMagnitude()

MetricQuery< double > loudestBinMagnitude ( const Spectrum spectrum)
inline

Calculates the magnitude of the loudest FFT bin.

Finds the maximum-magnitude FFT bin independently for each channel. Use reducers to combine multi-channel results (see Reducers).

Supports:

This metric operates on FFT bins exactly as stored in the Spectrum.

Typical use cases:

  • alias detection
  • spur detection
  • harmonic inspection
  • FFT sanity checks
  • spectral leakage diagnostics

Usage examples:

// Loudest spectral component in dB
const double loudestDb = loudestBinMagnitude (spectrum).as (dB).get();
// Loudest bin only inside a frequency range
const double loudestMidBandDb =
.as (dB)
.at (Slice::frequency (500_Hz, 2000_Hz))
.get();
// Loudest bin, but only inside a specific channel subset, as a
// linear value (not dB). Calculated per chanel, then averaged.
const double loudestBinLinear =
.as (linear)
.ch ({0, 2, 4})
.get (mean());
Parameters
spectrumInput frequency-domain spectrum
Exceptions
hart::UnitErrorif unsupported unit is requested

Definition at line 60 of file hart_loudest_bin_magnitude.hpp.

◆ maxCrossCorrelation()

template<typename SampleType >
MetricQuery< double > maxCrossCorrelation ( const AudioBuffer< SampleType > &  bufferA,
const AudioBuffer< SampleType > &  bufferB,
double  maxLagSeconds,
CorrelationSearchMode  searchMode = bestAbsoluteCorrelation 
)

Calculates maximum normalized cross-correlation between two audio buffers.

Searches for the best normalized cross-correlation value within a specified lag range independently for each selected pair of channels.

Cross-correlation is calculated using the following formula:

\[ \frac{\sum_n x[n]\,y[n+k]} {\sqrt{ \left(\sum_n x[n]^2\right) \left(\sum_n y[n+k]^2\right) }} \]

(sum (x[n] * y[n + k]) / sqrt (sum (x[n]^2) * sum (y[n + k]^2)))

where:

  • x[n] is the left-hand-side signal
  • y[n + k] is the right-hand-side signal shifted by lag k
  • k is searched in the range [-maxLag, +maxLag]

The result is normalized to the range [-1, 1], where:

  • +1 means perfect positive correlation
  • -1 means perfect negative correlation (polarity inversion)
  • 0 means no linear correlation

Depending on searchMode, the metric either:

  • searches for the largest signed correlation value
  • or searches for the largest absolute correlation value while still returning the original signed correlation.

Correlation is calculated independently for each selected pair of channels. Use a reducer to combine multiple channel-pair results into a scalar.

Usage examples:

// Mono signals, default channel mapping {0,0}
const double corr = maxCrossCorrelation (monoInput, monoOutput, 100_ms).get();
// Same, but polarity-invariant lag search
const double corrAbs = maxCrossCorrelation (
monoInput,
monoOutput,
100_ms,
).get();
// Stereo signals, strongest matched pair correlation
const double maxCorr = maxCrossCorrelation (stereoInput, stereoOutput, 100_ms).get (max());
// Cross-map channels explicitly
const double swappedCorr = maxCrossCorrelation (input, output, 100_ms)
.ch ({ {0, 1}, {1, 0} })
.get (min());
// Detect polarity inversion
const double corrSigned = maxCrossCorrelation (
input,
invertedOutput,
100_ms,
).get();
HART_EXPECT_LT (corrSigned, 0.0);
#define HART_EXPECT_LT(lhs, rhs)
MetricQuery< double > maxCrossCorrelation(const AudioBuffer< SampleType > &bufferA, const AudioBuffer< SampleType > &bufferB, double maxLagSeconds, CorrelationSearchMode searchMode=bestAbsoluteCorrelation)
Calculates maximum normalized cross-correlation between two audio buffers.

Notes:

  • Gain differences do not affect the result due to normalization.
  • DC offset may reduce correlation.
  • Heavy non-linear processing may significantly reduce correlation.
  • Returned value remains signed even in bestAbsoluteCorrelation mode.
  • If no valid overlap exists, returns NaN.

Supports only Unit::native and Unit::none units.

Parameters
bufferALeft-hand-side audio buffer
bufferBRight-hand-side audio buffer
maxLagSecondsMaximum lag to search in seconds
searchModeControls how the best lag is selected, see `CorrelationSearchMode`
Returns
MetricQuery containing signed normalized cross-correlation values
Template Parameters
SampleTypeFloating point sample type, typically float or double
Exceptions
hart::ValueErrorIf maxLagSeconds is negative
hart::SampleRateErrorIf sample rates differ
hart::IndexErrorIf requested channel indices are out of range
hart::UnitErrorIf unsupported unit is requested

Definition at line 109 of file hart_max_cross_correlation.hpp.

◆ quinns2()

MetricQuery< double > quinns2 ( const Spectrum spectrum)
inline

Returns somewhat accurate loudest frequency in the spectrum.

Implements algorithm commonly referred to as "Quinn's Second Estimator", described by B. G. Quinn in "Estimating frequency by interpolation using Fourier coefficients", IEEE Transactions on Signal Processing, Vol. 42, No. 5, pp. 1264-1268.

It's provides a quite accurate way if interpolating frequency value, that is somewhere in between FFT bin centers. Note that it's undefined near DC and Nyquist frequencies, and it will return NaN for those bins. For those edge cases, consider using a more simple hart::loudestBinFrequency() metric instead.

Supports Unit::Hz (native/default) unit, so requesting a unit explicitly via MetricQuery::as() is not required.

This metric operates on FFT bins exactly as stored in the Spectrum. In a not-so-likely event where multiple bins have exactly the same magnitube, the lowest frequency will be returned.

Usage examples:

// Estimated loudest frequency in Hz
const double loudestFreqHz = quinns2 (monoSpectrum).get();
// Estimated loudest frequency inside a frequency band
const double loudestMidBandFreqHz =
quinns2 (monoSpectrum)
.at (Slice::frequency (500_Hz, 2000_Hz))
.get();
// Loudest frequency, but only inside a specific channel subset.
Calculated per chanel, then averaged.
const double loudestFreqHz =
quinns2 (multiChannelSpectrum)
.ch ({0, 2, 4})
.get (mean());
MetricQuery< double > quinns2(const Spectrum &spectrum)
Returns somewhat accurate loudest frequency in the spectrum.
Parameters
spectrumInput frequency-domain spectrum
Exceptions
hart::UnitErrorif unsupported unit is requested

Definition at line 61 of file hart_quinns2.hpp.

◆ rms()

template<typename SampleType >
MetricQuery< double > rms ( const AudioBuffer< SampleType > &  buffer)

Calculates root mean square (RMS) of a signal.

RMS a metric that expresses the average magnitude, or effective energy level, of an audio signal over time. It is commonly used to estimate perceived loudness, and to measure overall signal level.

RMS is calculated this way:

\[ \mathrm{RMS} = \sqrt{\frac{1}{N} \sum_{n=0}^{N-1} x[n]^2} \]

(RMS = sqrt((1 / N) * sum(x[n] ** 2))),

where x[n] is audio sample value from one channel, and N is number of frames in the provided buffer.

Can be expressed as ratio or decibels, supports Unit:native, Unit::linear, Unit::dB units. Value in decibels is calculated as a ratio, not power.

Template Parameters
SampleType
Parameters
bufferAudio buffer to calculate RMS at
Returns
Chainable MetricQuery object, which calculates RMS as linear ratio or decibels

Definition at line 41 of file hart_rms.hpp.

◆ rt60()

template<typename SampleType >
MetricQuery< double > rt60 ( const ImpulseResponse< SampleType > &  ir,
RT60::Method  method = RT60::Method::edt 
)

Estimates the RT60 reverberation time of an impulse response.

RT60 is the time required for reverberant energy to decay by 60 dB. The metric is calculated from the impulse response using Schroeder backward integration, followed by linear regression over the range specified by method. The fitted decay slope is then extrapolated to 60 dB.

EDT, T20 and T30, specified by method, are different estimators of the same RT60 quantity. For an exponential decay they're expected to produce identical results; for more complex decay curves you can use combination of those.

Supported units are Unit::seconds, Unit::native (same as seconds) and Unit::frames. If Unit::frames is requested, the result will be a fractional value.

This metric is based on ISO 3382 standard.

Note
Slices aren't yet supported by this metric, and will be ignored.
Attention
The supplied impulse response is assumed to represent a decaying response. If the requested decay range cannot be observed, or a valid decay slope cannot be estimated, the result will be NaN. Also, note that DSPs that produce no decay or ringing at all (e. g., a system that just applies linear gain, or a stateless waveshaper) will result in NaN, and not zero.

Also, make sure that provided IR is long enough to contain a portion of slope specified by method, otherwise the estimation will result NaN. See RT60::Method options documentation for details, and ISO 3382 for a more in-depth description.

Template Parameters
SampleTypeFloating-point sample type of the impulse response.
Parameters
irImpulse response to analyze
methodRT60 estimation method
Returns
A MetricQuery containing the estimated decay time for each channel. Either in seconds, or in frames, depending on requested unit. Can be NaN.
See also
RT60::Method

Definition at line 87 of file hart_rt60.hpp.

◆ samplePeak()

template<typename SampleType >
MetricQuery< double > samplePeak ( const AudioBuffer< SampleType > &  audioBuffer)

Calculates Sample Peak of an audio buffer.

Calculates rectified peak values for each channel. Use a reducer to get a scalar value (see Reducers). Supports Unit::linear (default) and Unit::dB units. Usage example:

HART_EXPECT_FLOAT_EQ (samplePeak (monoBuffer).as (dB).get(), -3_dB, 1e-2) << "Peaks below 3 dB";
HART_EXPECT_LT (samplePeak (monoBuffer).as (linear).get(), 1.0, 1e-3) << "Peaks below unity gain (in linear domain)";
HART_EXPECT_LT (samplePeak (stereoBuffer).as (dB).get (max()), -3_dB) << "Loudest channel peaks below 3 dB";
#define HART_EXPECT_FLOAT_EQ(lhs, rhs, tolerance)
MetricQuery< double > samplePeak(const AudioBuffer< SampleType > &audioBuffer)
Calculates Sample Peak of an audio buffer.
Note
It doesn't estimate inter-sample peaks. For true (inter-sample) peaks, consider using the truePeak() metric or TruePeaksBelow matcher.
Parameters
audioBufferBuffer to measure sample peaks in.
Exceptions
hart::IndexErrorif slice's boundary is out of audio buffer's range
hart::UnitErrorif unsupported unit is requested

Definition at line 33 of file hart_sample_peak.hpp.

◆ snr()

template<typename SampleType >
MetricQuery< double > snr ( const AudioBuffer< SampleType > &  signalPlusNoise,
const AudioBuffer< SampleType > &  signal 
)

Calculates signal-to-noise ratio (SNR)

SNR expresses the ratio between the energy of a reference signal and the energy of the error, or noise, present in an estimated signal.

The noise component is calculated as the sample-by-sample difference between the estimated and reference signals.

SNR is calculated this way:

\[ \mathrm{SNR} = \frac {\sum_{n=0}^{N-1} r[n]^2} {\sum_{n=0}^{N-1} \left(x[n] - r[n]\right)^2} \]

(SNR = sum(r[n] ** 2) / sum((x[n] - r[n]) ** 2)),

where x[n] is a sample from the estimated signal, r[n] is the corresponding sample from the reference signal, and N is the number of frames being analyzed.

Higher values indicate a closer match to the reference signal, and thus lower noise. Identical signals produce positive infinity, and this metric will return +inf in those cases.

Can be expressed as an energy ratio or decibels. Supports Unit::ratio, Unit::native (same as ratio), and Unit::dB. Values in decibels are calculated as a power ratio:

\[ \mathrm{SNR_{dB}} = 10 \log_{10}\left(\mathrm{SNR}\right) \]

(SNR_dB = 10 * log10(SNR)).

The two buffers are expected to represent aligned versions of the same signal. Differences in gain, latency, phase, or other deterministic signal properties are included in the measured noise/error.

Template Parameters
SampleType
Parameters
signalPlusNoiseEstimated or measured signal
signalReference signal to compare against
Returns
Chainable MetricQuery object which calculates SNR as a linear energy ratio or in decibels. May return NaN or +inf.

Definition at line 64 of file hart_snr.hpp.

◆ snra()

template<typename SampleType >
MetricQuery< double > snra ( const AudioBuffer< SampleType > &  estimatedBufferAtNativeSR,
const AudioBuffer< SampleType > &  referenceBufferAtHighSR 
)

Calculates signal-to-aliasing-noise ratio (a.k.a. SNRA, SNRa or sometimes SANR)

SNRa estimates the amount of aliasing introduced by an audio processing algorithm by comparing its output at the native sample rate against a higher-sample-rate render.

SNRa is particularly suitable for non-linear DSP such as waveshapers, saturation, clipping, nonlinear filters, limiters, nonlinear circuit models, and anti-aliased waveform generators. It may be unsuitable for algorithms whose behavior changes materially with sample rate, such as fixed-rate processing engines or some fixed-size STFT-based algorithms.

This metric implies a specific experiment:

  1. You render audio through your DSP at native sample rate. This will be considered as "signal + aliasing noise", and amount of aliasing will be evaluated in this piece of audio.
  2. Render audio through your DSP at the same starting state, and same duration of audio (expressed in seconds, not frames), but at higher sample rate. Due to high sample rate, this will be consiudered as "ideal signal with no aliasing", assuming selected sample rate is high enough to make aliasing negligible.
  3. Pass the AudioBuffer's to this metric. They're expected to have matching lengths in seconds, but different sample rates - all resampling will be done by this metric internally.

You may pick the exact high sample rate value yourself. This sample rate doesn't have to be an integer multiple o the "native" sample rate, although typically you might still use something like 4x or 8x of native sample rate. Obviously, the higher the better.

Keep in mind that "high sample rate" refers to a "host" sample rate, and doesn't take into account any internal oversampling your DSP testee might have. If your DSP has internal oversampling, keep it on in both renders at the same setting. So, if your DSP has internal 4x OS, and native SR is 44.1 kHz, you keep internal oversampling on, and do a first render at 44.1 kHz, and then a second render at, say, 196 kHz (which, assuming your DSP does 4x OS internally, will result in a whopping 784 kHz internally, but HART renders everuthing offline, so you don't have to worry about potential non-realtime performance here).

The reference (high SR) buffer is internally resampled to the native sample rate. Aliasing noise is then defined as the sample-by-sample difference betweenthe native-rate output and this resampled reference.

SNRa is calculated as:

\[ \mathrm{SNR_a} = \frac {\sum_{n=0}^{N-1} r[n]^2} {\sum_{n=0}^{N-1} \left(x[n] - r[n]\right)^2} \]

(SNRa = sum(r[n] ** 2) / sum((x[n] - r[n]) ** 2)),

where x[n] is a sample from the output rendered at the native sample rate, r[n] is the corresponding sample from the higher-sample-rate reference after it has been resampled to the native sample rate, and N is the number of frames being analyzed.

Higher values indicate less aliasing, so higher is better. A perfectly matching native-rate and reference signal produces positive infinity, and this metric will return +inf.

Can be requested as an energy ratio or decibels. Supports Unit::ratio, Unit::native (default, same as Unit::ratio), and Unit::dB. Values in decibels are calculated as a power ratio:

\[ \mathrm{SNR_{a}},dB = 10 \log_{10}\left(\mathrm{SNR_a}\right) \]

(SNRa_dB = 10 * log10(SNRa)).

As mentioned above, this metric assumes that the two buffers are equivalent renders of the same algorithm and state, with the reference buffer rendered at a higher sample rate. In particular:

  • Changing the sample rate must preserve the intended behavior of the algorithm: i.e. all the timings, frequencies etc. must retain their real-world values in their physical units, taking sample rate into account. Same goes fot FFT sizes.
  • The higher sample rate must actually propagate into the processing being evaluated rather than being internally converted back to a fixed rate. E.g. ML-based DSP models runing at fixed SR internally do not qualify.
  • Both renders must begin from equivalent state and receive equivalent control and modulation trajectories.
  • Stochastic processing must be deterministic between the two renders, or otherwise reproduce an equivalent stochastic trajectory.
  • If your DSP algorithm involves any internal oversampling, make sure to keep it on in both renders, and at the same setting.

The reference audio resampling operation may introduce boundary transients at the beginning and end of the buffer. For accurate measurements, render guard regions around the desired analysis interval, and use MetricQuery::at() with an appropriate hart::Slice to measure only an interior, settled slice of audio.

See tests/test_metrics.cpp, in particular the "Metrics - SNRa - Distortion effect with aliasing" and "Metrics - SNRa - Clean effect with no aliasing" tests, for complete experiment examples.

Template Parameters
SampleTypeType of audio buffers' values, typically float or double
Parameters
estimatedBufferAtNativeSROutput rendered at the native sample rate. This will be considered to be a "signal + aliasing noise" when estimating SNRa.
referenceBufferAtHighSREquivalent reference output rendered at a higher sample rate. This audio buffer will be considered to be an ideal signal with no aliasing ("signal") when estimating SNRa. This signal should be captured at a higher sample rate that estimatedBufferAtNativeSR, and will be internally resampled. Also, note that "high sample rate" refers to a "host" sample rate, and doesn't take into account any internal oversampling your DSP testee might have.
Returns
Chainable MetricQuery object which calculates SNRa as a linear energy ratio or in decibels. May return NaN or +inf.

Definition at line 133 of file hart_snra.hpp.

◆ spectralCentroid()

MetricQuery< double > spectralCentroid ( const Spectrum spectrum,
SpectralCentroid::Weighting  weighting = SpectralCentroid::Weighting::magnitude 
)
inline

Calculates spectral centroid.

Commonly used to numerically express amound of "brightness" of a sound.

You have an option to pick one of two common weighting methods:

  1. Magnitude-weighted:

    \[ C_{mag}=\frac{\sum_{k=0}^{N-1} f_k |X_k|}{\sum_{k=0}^{N-1} |X_k|} \]

(C_mag = sum(f_k * abs(X_k)) / sum(abs(X_k))),

  1. Power-weighted:

    \[ C_{pow}=\frac{\sum_{k=0}^{N-1} f_k |X_k|^2}{\sum_{k=0}^{N-1} |X_k|^2} \]

C_pow = sum(f_k * abs(X_k)^2) / sum(abs(X_k)^2)

Where fk is a center frequency of each bin, and Xk is a magnitude of this bin. In both cases the result is measured in Hertz, so accepted units are Unit::native and Unit::Hz, which are the same.

See Using Metrics And Reducers for how to use metrics that return a MetricQuery object line this one.

Parameters
spectrumSpectrum of a single to operate on
weightingType of weighting (see description above)
Returns
Chainable MetricQuery, which calculates per-channel spectral centroid values in Hz

Definition at line 55 of file hart_spectral_centroid.hpp.

◆ spectralFlatness()

MetricQuery< double > spectralFlatness ( const Spectrum spectrum,
double  floorLinear = 1e-16 
)
inline

Calculates spectral flatness, also known as Wiener entropy, or tonality coefficient.

Useful to judge how noise-like spectrum is.

You have an option to pick one of two common weighting methods:

  1. Magnitude-weighted:

    \[ \mathrm{SpectralFlatness} = \frac{ \exp \left( \frac{1}{N} \sum_{n=0}^{N-1} \ln(x[n]) \right) }{ \frac{1}{N} \sum_{n=0}^{N-1} x[n] } \]

(SpectralFlatness = exp(sum (log (x[n])) / N) / (sum(x[n]) / N),

Where x[n] is a magnitude of a bin, and N is number of bins. The result can be represented in Unit::linear (default/ native) or Unit::dB. For decibel value, it will be converted as power (not voltage).

Typical values:

  • 0.0 (-oo dB) - highly tonal / sparse spectrum
  • 1.0 (0 dB) - perfectly flat spectrum
  • NaN - silence

See Using Metrics And Reducers for how to use metrics like this one.

Parameters
spectrumSpectrum of a single to operate on
floorLinearBin magnitude threshold for numerical stability. Each bin's magnitude will be evaluated as x[n] = max (binMagnitudes[n], floorLinear).
Returns
Chainable MetricQuery, which calculates per-channel spectral flatness values

Definition at line 53 of file hart_spectral_flatness.hpp.

◆ spectralLogLogSlope()

MetricQuery< double > spectralLogLogSlope ( const Spectrum spectrum,
double  smoothingCents = 1200.0 
)
inline

Calculates slope of the spectrum in logX-logY domain.

Definition at line 24 of file hart_spectral_log_log_slope.hpp.

◆ thd()

MetricQuery< double > thd ( const Spectrum spectrum,
THD::ExperimentSetup  experimentSetup 
)
inline

Calculates the total harmonic distortion (THD) of a spectrum.

THD is calculated as the square root of the summed harmonic power divided by the fundamental bin power:

\[ \mathrm{THD} = \sqrt{ \frac{\sum_{h=2}^{H} |X[h k_1]|^2} {|X[k_1]|^2} } \]

(THD = sqrt(sum(norm(harmonic bins)) / norm(fundamental bin))),

where \( k_1 \) is the FFT bin containing the fundamental, and \( H \) is the maximum harmonic number requested. Harmonics at or above the Nyquist frequency are ignored.

For an accurate measurement, the input should be a pure sine whose frequency lies exactly at the centre of an FFT bin. The analysed signal should also contain exactly the same number of frames as the FFT, without zero padding. Otherwise, truncation and zero padding cause spectral leakage, which may appear as harmonic energy and artificially increase the measured THD.

To ensure those conditions are met, you're expected to obtain a "tuned" experiment setup, obtained through hart::THD::ExperimentSetupTuner, which will snap all of your desired experiment parameters to the values optimized for no-spill FFT. Those will be the values you're supposed to run the entire test render with.

Example:

// Assuming you have specific render duration and input signal frequency in mind:
constexpr double desiredRenderDurationSeconds = 100_ms; // Or any other duration you want
constexpr double desiredFrequencyHz = 1_kHz; // Or any other frequency you want
// Snap the desired parameters to optimal values
.withFrequency (frequencyHz)
.withDuration (desiredRenderDurationSeconds)
.tune();
processAudioWith (SomeDSP())
.withInputSignal (SineWave (setup.frequencyHz)) // Tuned frequency...
.withDuration (setup.durationSeconds) // ...and tuned render duration
.expectTrue (
[setup] (const hart::AudioBuffer<float>& output)
{
return HART_FLOAT_EQ (
hart::thd (hart::Spectrum (output), setup).get(),
0.0,
1e-8
);
},
"THD ~= 0")
.process();
Container for audio data.
Produces a sine wave at fixed frequency.
Frequency-domain representation of a multi-channel audio signal.
Configures and tunes an experiment setup for accurate THD measurement.
Definition hart_thd.hpp:162
ExperimentSetupTuner & withDuration(double desiredDurationSeconds)
Sets desired render time of the test case in seconds.
Definition hart_thd.hpp:204
ExperimentSetupTuner & withFrequency(double desiredFrequencyHz)
Sets the desired frequency of the input signal.
Definition hart_thd.hpp:170
ExperimentSetup tune() const
Call it at the end of the builder chain to produce a hart::THD::ExperimentSetup instance.
Definition hart_thd.hpp:261
#define HART_FLOAT_EQ(lhs, rhs, tolerance)
MetricQuery< double > thd(const Spectrum &spectrum, THD::ExperimentSetup experimentSetup)
Calculates the total harmonic distortion (THD) of a spectrum.
Definition hart_thd.hpp:428
AudioTestBuilder< typename std::decay< DSPType >::type::SampleTypePublicAlias > processAudioWith(DSPType &&dsp)
Call this to start building your test using a DSP object.
A tuned setup for optimal THD measurement.
Definition hart_thd.hpp:30
const double durationSeconds
Optimized duration of audio for FFT with no padding.
Definition hart_thd.hpp:42
const double frequencyHz
Optimized frequency of the input sine wave.
Definition hart_thd.hpp:34

Supported units:

See also
hart::THD::ExperimentSetup
hart::THD::ExperimentSetupTuner
Parameters
spectrumSpectrum of the output signal to analyse, assuming the input was a pure sine wave
experimentSetupOptimized experiment setup values obtained through hart::THD::ExperimentSetupTuner().
Returns
A MetricQuery containing THD as a linear amplitude ratio. May return NaN.

Definition at line 428 of file hart_thd.hpp.

◆ truePeak()

template<typename SampleType >
MetricQuery< double > truePeak ( const AudioBuffer< SampleType > &  audioBuffer,
Oversampling  oversamplingRatio = Oversampling::x4,
typename TruePeak< SampleType >::FilterQuality  filterQuality = TruePeak<SampleType>::FilterQuality::low 
)

Estimates true peak (inter-sample peak) level.

It checks inter-sample peaks by observing oversampled signal, following ITU-R BS.1770-5 guidelines. Some of the implementation choices are exposed via arguments, such as oversampling factor and number of taps in the internal poly-phase FIR filter, as the standard does not specify the exact values.

Supports values in dB TP (Unit::dB) and linear domain (Unit::linear). Operating at default unit (Unit::native) will yield values in dB TP.

Shares the same implementation as TruePeaksBelow matcher, but lets you make more versatile expressions.

Parameters
audioBufferBuffer to estimate true peaks in
oversamplingRatioOversampling for the estimator. Higher OS ratios are expected to result in more accurate estimations.
filterQualityRepresent number of taps for the internal FIR filter. Higher will result in more accurate estimate. Note that even the highest filter quality is way lower than what is used in actual DAC oversamplers, but it's okay, since we're merely estimating here.

Definition at line 265 of file hart_true_peak.hpp.

◆ zcr()

template<typename SampleType >
MetricQuery< double > zcr ( const AudioBuffer< SampleType > &  buffer)

Calculates zero-crossing rate (ZCR) of a signal.

Useful to estimate frequency of stationary monophonic signals. Supports Unit::native and Unit::Hz units, which both result in the same value.

Template Parameters
SampleTypetype of audio buffer data, typically float or double
Parameters
bufferAudio buffer to calculate ZCR at
Returns
Chainable MetricQuery object, which calculates RMS as linear ratio or decibels

Definition at line 23 of file hart_zcr.hpp.