HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_spectral_log_log_slope.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm> // max()
4#include <cmath> // isnan(), sqrt(), log()
5#include <complex> // norm()
6#include <utility> // pair
7
10#include "metrics/hart_metric_query.hpp"
11#include "metrics/hart_metrics_common.hpp" // ChannelSubsets
12#include "hart_slice.hpp"
14#include "hart_units.hpp" // Unit
15#include "hart_utils.hpp" // nan(), floatsEqual(), centsToRatio()
16
17// TODO: Document it properly
18
19namespace hart
20{
21
22/// @brief Calculates slope of the spectrum in logX-logY domain
23/// @ingroup Metrics
24inline MetricQuery<double> spectralLogLogSlope (const Spectrum& spectrum, double smoothingCents = 1200.0)
25{
26 if (smoothingCents <= 0.0)
27 HART_THROW_OR_RETURN (ValueError, "smoothingCents should be a non-negative band width", {});
28
29 MetricQuery<double>::SingleChannelMetricEvaluator evaluator =
30 [&spectrum, smoothingCents]
31 (size_t channel, const Slice& slice, Unit requestedUnit)
32 -> double
33 {
34 hassert (channel < spectrum.getNumChannels());
35 hassert (! std::isnan (spectrum.getSampleRateHz()));
36
37 const std::pair<size_t, size_t> binIndices = spectrum.getBinIndices (slice);
38 const size_t startBin = std::max<size_t> (1, binIndices.first);
39 const size_t stopBin = binIndices.second;
40
41 if (slice.isEmpty() || stopBin - startBin == 0)
42 return hart::nan<double>();
43
44 hassert (startBin < stopBin);
45 hassert (stopBin <= spectrum.getNumBins());
46
47 struct LogLogPoint
48 {
49 double logFrequency;
50 double logPower;
51 };
52
53 std::vector<LogLogPoint> logLogPoints;
54 const double bandRatio = centsToRatio (smoothingCents);
55 const double stopFrequencyHz = spectrum.getBinFrequencyHz (stopBin - 1);
56 double currentBandStartHz = spectrum.getBinFrequencyHz (startBin);
57 double currentBandEndHz = currentBandStartHz * bandRatio;
58
59 while (currentBandEndHz <= stopFrequencyHz)
60 {
61 AccurateSum<double> bandPower;
62 size_t numCurrentBandBins = 0;
63
64 for (size_t bin = startBin; bin < stopBin; ++bin)
65 {
66 const double frequencyHz = spectrum.getBinFrequencyHz (bin);
67
68 if (frequencyHz < currentBandStartHz)
69 continue;
70
71 if (frequencyHz >= currentBandEndHz)
72 break;
73
74 bandPower += std::norm (spectrum.getBinValue (channel, bin));
75 ++numCurrentBandBins;
76 }
77
78 if (numCurrentBandBins > 0)
79 {
80 const double binMeanPower = bandPower.getValue() / static_cast<double> (numCurrentBandBins);
81
82 if (floatsEqual (binMeanPower, 0.0))
83 continue;
84
85 // Geometric centre of a logarithmic band
86 const double centreFrequencyHz = std::sqrt (currentBandStartHz * currentBandEndHz);
87
88 logLogPoints.push_back ({
89 std::log (centreFrequencyHz),
90 std::log (binMeanPower)
91 });
92 }
93
94 currentBandStartHz = currentBandEndHz;
95 currentBandEndHz = currentBandStartHz * bandRatio;
96 }
97
98 // Ordinary least-squares slope
99 AccurateSum<double> sumX;
100 AccurateSum<double> sumY;
101
102 for (const LogLogPoint logLogPoint : logLogPoints)
103 {
104 sumX += logLogPoint.logFrequency;
105 sumY += logLogPoint.logPower;
106 }
107
108 const double meanX = sumX.getValue() / logLogPoints.size();
109 const double meanY = sumY.getValue() / logLogPoints.size();
110
111 AccurateSum<double> covariance;
112 AccurateSum<double> varianceX;
113
114 for (const LogLogPoint logLogPoint : logLogPoints)
115 {
116 const double dx = logLogPoint.logFrequency - meanX;
117 const double dy = logLogPoint.logPower - meanY;
118
119 covariance += dx * dy;
120 varianceX += dx * dx;
121 }
122
123 // TODO: Slope can be in db per oct, so add support for dB/oct at some point:
124 // slope_db_per_oct = beta * 10 * log10(2)
125 const double slopeUnitless = covariance.getValue() / varianceX.getValue();
126 constexpr double threeDb = 3.010299956639812; // 10 * log10 (2)
127
128 switch (requestedUnit)
129 {
130 case Unit::native:
131 case Unit::none: return slopeUnitless;
132
133 case Unit::dB_per_octave: return slopeUnitless * threeDb;
134
135 default: HART_THROW_OR_RETURN (hart::UnitError, "Unsupported unit", nan<double>());
136 }
137
138 if (requestedUnit != Unit::native && requestedUnit != Unit::none)
139 HART_THROW_OR_RETURN (hart::UnitError, "Unsupported unit", hart::nan<double>());
140
141 };
142
143 const size_t numChannels = spectrum.getNumChannels();
144 return MetricQuery<double> (
145 std::move (evaluator),
146 numChannels,
148 );
149}
150
151} // 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 getBinFrequencyHz(size_t binIndex) const
Returns frequency corresponding to a bin index.
size_t getNumBins() const
Returns number of frequency bins per channel.
std::complex< double > getBinValue(size_t channel, size_t binIndex) const
Returns complex value of a frequency bin, by bin index.
size_t getNumChannels() const
Returns number of channels.
Thrown when some metric is requested to return a value in an unsupported unit.
Thrown when an inappropriate value is encountered.
#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 > spectralLogLogSlope(const Spectrum &spectrum, double smoothingCents=1200.0)
Calculates slope of the spectrum in logX-logY domain.
FloatType nan()
Returns a quiet NaN value for the given floating-point type.
static SampleType floatsEqual(SampleType a, SampleType b, SampleType epsilon=(SampleType) 1e-8)
Compares two floating point numbers within a given tolerance.
double centsToRatio(double cents)
Converts frequency difference in cents to frequence ratio.
Unit
Represents a physical unit.
@ none
Unitless value.
@ native
Default (native) unit of whatever returns some value.
@ dB_per_octave
Slope of something in decibels per octave.
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