HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_crest_factor.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cmath> // abs(), sqrt()
4
8#include "metrics/hart_metric_query.hpp"
9#include "metrics/hart_metrics_common.hpp" // ChannelSubsets
10#include "hart_slice.hpp"
11#include "hart_units.hpp" // Unit
12#include "hart_utils.hpp" // nan(), inf, ratioToDecibels()
13
14namespace hart
15{
16
17/// @brief Calculates linear crest factor for a single channel of an audio buffer
18/// @details
19/// Crest factor is defined as the ratio between the absolute peak value and RMS value:
20/// @f[
21/// \frac{\max_n \left|x[n]\right|}{\sqrt{\frac{1}{N}\sum_n x[n]^2}}
22/// @f]
23///
24/// (`max (abs (x[n])) / sqrt ((1 / N) * sum (x[n]^2))`)
25///
26/// Calculates values independently for each channel. Use a reducer to
27/// get a scalar value (see @ref Reducers). Supports `Unit::linear` (default) and
28/// `Unit::dB` units. Decibel conversion is performed as `20 * log10 (x)`, i.e.
29/// the amplitude-ratio form, see `hart::ratioToDecibels()`.
30///
31/// Usage example:
32/// @code
33/// // One channel, default unit (linear)
34/// const double cfLinear = crestFactor (monoBuffer).get();
35///
36/// // One channel, decibels
37/// const double cfDb = crestFactor (monoBuffer).as (dB).get();
38///
39/// // Calculates crest factor for each channel as linear value, which is a default (native) unit
40/// // for this metric, then returns index of the largest value. Since channel subset is default
41/// // (no ch() call), the returns index is the same as channel's index.
42/// const size_t mostDynamicChannel = crestFactor (multiChannelBuffer).as (linear).get (argmax());
43///
44/// // Calculates crest factor for channels 0, 3 and 5 in dB, then returns maximum of those three
45/// const double cfMaxDb = crestFactor (multiChannelBuffer).as (dB).ch ({0, 3, 5}).get (max());
46/// @endcode
47///
48/// @param buffer Input audio buffer
49/// @return Chainable `MetricQuery`, which calculates crest factor in linear ratio units or dB
50/// - Returns `NaN` if the audio buffer contains zero frames.
51/// - Returns `inf` if the selected channel is silent, making RMS equal to (or close to) zero.
52/// @tparam SampleType Floating point sample type of the audio buffer, typically `float` or `double`
53/// @throws hart::IndexError if the channel index is out of bounds, or slice boundary is out of range
54/// @ingroup Metrics
55template <typename SampleType>
56MetricQuery<double> crestFactor (const AudioBuffer<SampleType>& buffer)
57{
58 typename MetricQuery<double>::SingleChannelMetricEvaluator evaluator =
59 [&buffer]
60 (size_t channel, Slice slice, Unit requestedUnit)
61 -> double
62 {
63 hassert (channel < buffer.getNumChannels());
64
65 if (slice.isEmpty())
66 return hart::nan<double>();
67
68 const auto sliceFrameIndices = buffer.getFrameIndices (slice);
69 const size_t sliceStart = sliceFrameIndices.first;
70 const size_t sliceStop = sliceFrameIndices.second;
71 const size_t numFrames = sliceStop - sliceStart;
72 hassert (numFrames != 0);
73 hassert (sliceStart < sliceStop);
74 hassert (sliceStop <= buffer.getNumFrames());
75
76 const SampleType* channelData = buffer[channel];
77
78 double peakLinear = 0.0;
79 AccurateSum<double> sumSquares;
80
81 for (size_t frame = sliceStart; frame < sliceStop; ++frame)
82 {
83 const double x = static_cast<double> (channelData[frame]);
84 const double absX = std::abs (x);
85
86 if (absX > peakLinear)
87 peakLinear = absX;
88
89 sumSquares += x * x;
90 }
91
92 const double meanSquare = sumSquares.get<double>() / numFrames;
93
94 if (floatsEqual (meanSquare, 0.0))
95 return hart::inf;
96
97 const double rms = std::sqrt (meanSquare);
98 const double crestFactorLinear = peakLinear / rms;
99
100 switch (requestedUnit)
101 {
102 case Unit::native:
103 case Unit::linear: return crestFactorLinear;
104
105 case Unit::dB: return hart::ratioToDecibels (crestFactorLinear);
106
107 default: HART_THROW_OR_RETURN (hart::UnitError, "Unsupported unit", hart::nan<double>());
108 }
109 };
110
111 const size_t numChannels = buffer.getNumChannels();
112 return MetricQuery<double> (
113 std::move (evaluator),
114 numChannels,
116 );
117}
118
119} // namespace hart
Implements Kahan algorithm for floating point accumulations.
AccurateSum & operator+=(SampleType value)
Adds a value to a sum, tracking the potential floating point error.
Container for audio data.
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.
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 > crestFactor(const AudioBuffer< SampleType > &buffer)
Calculates linear crest factor for a single channel of an audio buffer.
FloatType nan()
Returns a quiet NaN value for the given floating-point type.
constexpr double inf
Infinity.
static SampleType ratioToDecibels(SampleType valueLinear)
Converts linear value (ratio) 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