HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_esr.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm> // min()
4
8#include "metrics/hart_metric_query.hpp"
9#include "metrics/hart_metrics_common.hpp" // ChannelSubsets
10#include "hart_slice.hpp"
11#include "hart_utils.hpp" // nan(), floatsEqual()
12#include "hart_units.hpp" // Unit
13
14namespace hart
15{
16
17/// @brief Calculates error-to-signal ratio (ESR)
18/// @details ESR is a useful way to express the degree of similarity between
19/// two signals or waveforms, calculated as:
20///
21/// @f[
22/// ESR=\frac{\sum_{k=0}^{N-1} (x - y) ^ 2}{\sum_{k=0}^{N-1} x^2}$$
23/// @f]
24///
25/// (sum ((x - y) ** 2) / sum (x ** 2))
26///
27/// Where x is a signal represented by @p referenceBuffer and y is represented
28/// by @p estimatedBuffer. It's a ratio, so appropriate units are `Unit::native`
29/// and `Unit::ratio`. ESR = 0 means two signals are identical.
30/// @param referenceBuffer A buffer representing x in the formula above
31/// @param estimatedBuffer A buffer representing y in the formula above
32/// @return Chainable `MetricQuery`, which calculates per-channel ESR values
33template <typename SampleType>
34MetricQuery<double> esr (const AudioBuffer<SampleType>& referenceBuffer, const AudioBuffer<SampleType>& estimatedBuffer)
35{
36 if ((referenceBuffer.hasSampleRate() || estimatedBuffer.hasSampleRate()) && referenceBuffer.getSampleRateHz() != estimatedBuffer.getSampleRateHz())
37 HART_THROW_OR_RETURN (hart::SampleRateError, "Audio buffers must have equal sample rates", {});
38
39 if (estimatedBuffer.getNumFrames() != referenceBuffer.getNumFrames())
40 HART_THROW_OR_RETURN (hart::SizeError, "Buffers' sizes don't match", hart::nan<double>());
41
42 typename MetricQuery<double>::ChannelPairMetricEvaluator evaluator =
43 [&referenceBuffer, &estimatedBuffer]
44 (size_t referenceBufferChannel, size_t estimatedBufferChannel, Slice slice, Unit requestedUnit)
45 -> double
46 {
47 hassert (estimatedBuffer.getNumFrames() == referenceBuffer.getNumFrames());
48
49 if (referenceBufferChannel >= referenceBuffer.getNumChannels())
50 HART_THROW_OR_RETURN (hart::IndexError, "Reference buffer's channel index is out of bounds", hart::nan<double>());
51
52 if (estimatedBufferChannel >= estimatedBuffer.getNumChannels())
53 HART_THROW_OR_RETURN (hart::IndexError, "Estimated buffer's channel index is out of bounds", hart::nan<double>());
54
55 // TODO: Support percent or dB?
56 if (requestedUnit != Unit::native && requestedUnit != Unit::ratio)
57 HART_THROW_OR_RETURN (hart::UnitError, "ESR does not support requested unit", hart::nan<double>());
58
59 if (slice.isEmpty())
60 return hart::nan<double>();
61
62 const auto sliceFrameIndices = referenceBuffer.getFrameIndices (slice);
63 const size_t sliceStart = sliceFrameIndices.first;
64 const size_t sliceStop = sliceFrameIndices.second;
65 hassert (sliceStop > sliceStart);
66 hassert (sliceStop <= referenceBuffer.getNumFrames());
67
68 const size_t numFrames = sliceStop - sliceStart;
69 hassert (numFrames != 0);
70
71 AccurateSum<double> signalPower;
72 AccurateSum<double> noisePower;
73
74 const SampleType* referenceSamples = referenceBuffer[referenceBufferChannel] + sliceStart;
75 const SampleType* estimatedSamples = estimatedBuffer[estimatedBufferChannel] + sliceStart;
76
77 for (size_t frame = 0; frame < numFrames; ++frame)
78 {
79 const double x = static_cast<double> (referenceSamples[frame]);
80 const double y = static_cast<double> (estimatedSamples[frame]);
81 const double noise = x - y;
82
83 signalPower += x * x;
84 noisePower += noise * noise;
85 }
86
87 if (floatsEqual<double> (signalPower, 0.0))
88 return hart::nan<double>();
89
90 return noisePower.getValue() / signalPower.getValue();
91 };
92
93 const size_t numPairs = std::min (referenceBuffer.getNumChannels(), estimatedBuffer.getNumChannels());
94 return MetricQuery<double> (
95 std::move (evaluator),
96 referenceBuffer.getNumChannels(),
97 estimatedBuffer.getNumChannels(),
99 );
100}
101
102} // 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.
Container for audio data.
Thrown when a container index is out of range.
Manages the metrics calculations.
Thrown when sample rate is mismatched.
Thrown when an unexpected container size is encountered.
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 ...
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.
MetricQuery< double > esr(const AudioBuffer< SampleType > &referenceBuffer, const AudioBuffer< SampleType > &estimatedBuffer)
Calculates error-to-signal ratio (ESR)
Definition hart_esr.hpp:34
Unit
Represents a physical unit.
@ ratio
Generic ratio.
@ native
Default (native) unit of whatever returns some value.
Helpers to generate common default channel subsets.
static std::vector< std::pair< size_t, size_t > > diagonalChannelPairs(size_t numChannels)
Represents a slice of analysis data.
bool isEmpty() const