HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_spectral_flatness.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cmath> // log10(), pow()
4
7#include "metrics/hart_metric_query.hpp"
8#include "metrics/hart_metrics_common.hpp" // ChannelSubsets
9#include "hart_slice.hpp"
11#include "hart_units.hpp" // Unit
12#include "hart_utils.hpp" // nan(), floatsEqual(), powerToDecibels
13
14namespace hart
15{
16
17/// @brief Calculates spectral flatness, also known as Wiener entropy, or tonality coefficient.
18/// @details Useful to judge how noise-like spectrum is.
19///
20/// You have an option to pick one of two common weighting methods:
21///
22/// 1. Magnitude-weighted:
23/// @f[
24/// \mathrm{SpectralFlatness} =
25/// \frac{
26/// \exp \left(
27/// \frac{1}{N}
28/// \sum_{n=0}^{N-1} \ln(x[n])
29/// \right)
30/// }{
31/// \frac{1}{N}
32/// \sum_{n=0}^{N-1} x[n]
33/// }
34/// @f]
35///
36/// (SpectralFlatness = exp(sum (log (x[n])) / N) / (sum(x[n]) / N),
37///
38/// Where x[n] is a magnitude of a bin, and N is number of bins.
39/// The result can be represented in `Unit::linear` (default/ native) or `Unit::dB`.
40/// For decibel value, it will be converted as power (not voltage).
41///
42/// Typical values:
43/// - 0.0 (-oo dB) - highly tonal / sparse spectrum
44/// - 1.0 (0 dB) - perfectly flat spectrum
45/// - NaN - silence
46///
47/// See @ref UsingMetricsAndReducers for how to use metrics like this one.
48/// @param spectrum Spectrum of a single to operate on
49/// @param floorLinear Bin magnitude threshold for numerical stability. Each bin's magnitude will be
50/// evaluated as x[n] = max (binMagnitudes[n], floorLinear).
51/// @return Chainable `MetricQuery`, which calculates per-channel spectral flatness values
52/// @ingroup Metrics
53inline MetricQuery<double> spectralFlatness (const Spectrum& spectrum, double floorLinear = 1e-16)
54{
55 typename MetricQuery<double>::SingleChannelMetricEvaluator evaluator =
56 [&spectrum, floorLinear]
57 (size_t channel, const Slice& slice, Unit requestedUnit)
58 -> double
59 {
60 hassert (channel < spectrum.getNumChannels());
61 hassert (! std::isnan (spectrum.getSampleRateHz()));
62
63 const std::pair<size_t, size_t> binIndices = spectrum.getBinIndices (slice);
64 const size_t startBin = binIndices.first;
65 const size_t stopBin = binIndices.second;
66 const size_t numBinsInSlice = stopBin - startBin;
67
68 if (slice.isEmpty() || numBinsInSlice == 0)
69 return hart::nan<double>();
70
71 hassert (startBin < stopBin);
72 hassert (stopBin <= spectrum.getNumBins());
73
74 AccurateSum<double> logSum;
75 AccurateSum<double> magnitudeSum;
76
77 for (size_t bin = startBin; bin < stopBin; ++bin)
78 {
79 const double magnitudeLinear = std::max (spectrum.getBinMagnitude (channel, bin), floorLinear);
80 logSum += std::log (magnitudeLinear);
81 magnitudeSum += magnitudeLinear;
82 }
83
84 if (floatsEqual (magnitudeSum.getValue(), 0.0))
85 return hart::nan<double>();
86
87 const double oneOverN = 1.0 / static_cast<double> (numBinsInSlice);
88 const double spectralFlatnessLinear = std::exp (oneOverN * logSum.getValue()) / (oneOverN * magnitudeSum.getValue());
89
90 // TODO: Add percent unit, maybe?
91 switch (requestedUnit)
92 {
93 case Unit::native:
94 case Unit::linear: return spectralFlatnessLinear;
95
96 case Unit::dB: return hart::powerToDecibels (spectralFlatnessLinear);
97
98 default: HART_THROW_OR_RETURN (hart::UnitError, "Unsupported unit", hart::nan<double>());
99 }
100 };
101
102 const size_t numChannels = spectrum.getNumChannels();
103 return MetricQuery<double> (
104 std::move (evaluator),
105 numChannels,
107 );
108}
109
110} // namespace hart
Implements Kahan algorithm for floating point accumulations.
SampleType getValue() const
AccurateSum & operator+=(SampleType value)
Adds a value to a sum, tracking the potential floating point error.
Manages the metrics calculations.
MetricQuery(SingleChannelMetricEvaluator evaluator, size_t totalNumChannels, std::vector< size_t > &&defaultChannelsToProcess)
Create a metric query object for a metric that operates on one channel at a time.
Frequency-domain representation of a multi-channel audio signal.
double getSampleRateHz() const
Returns sample rate in Hz.
std::pair< size_t, size_t > getBinIndices(const Slice &slice) const
Returns a pair of indices representing a provided slice.
double getBinMagnitude(size_t channel, size_t binIndex) const
Returns magnitude of a frequency bin, by bin index.
size_t getNumBins() const
Returns number of frequency bins per channel.
size_t getNumChannels() const
Returns number of channels.
Thrown when some metric is requested to return a value in an unsupported unit.
#define hassert(condition)
Triggers a HartAssertException if the condition is false
#define HART_THROW_OR_RETURN(ExceptionType, message, returnValue)
Throws an exception if HART_DO_NOT_THROW_EXCEPTIONS is set, prints a message and returns a specified ...
MetricQuery< double > spectralFlatness(const Spectrum &spectrum, double floorLinear=1e-16)
Calculates spectral flatness, also known as Wiener entropy, or tonality coefficient.
FloatType nan()
Returns a quiet NaN value for the given floating-point type.
static SampleType powerToDecibels(SampleType valueLinear)
Converts linear value (power) to dB.
static SampleType floatsEqual(SampleType a, SampleType b, SampleType epsilon=(SampleType) 1e-8)
Compares two floating point numbers within a given tolerance.
Unit
Represents a physical unit.
@ dB
Value of something in decibels. Can represent voltage, power, or a domain-specific unit like "LUFS" o...
@ native
Default (native) unit of whatever returns some value.
@ linear
Value of a sample (voltage) in a linear domain.
Helpers to generate common default channel subsets.
static std::vector< size_t > allChannels(size_t numChannels)
Represents a slice of analysis data.
bool isEmpty() const