HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_latency_below.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cmath> // isnan()
4#include <memory>
5
6#include "matchers/hart_correlation_latency_detector.hpp"
7#include "matchers/hart_latency_detector.hpp"
8#include "matchers/hart_matcher.hpp"
9#include "matchers/hart_onset_latency_detector.hpp"
12#include "hart_utils.hpp" // make_unique(), nan()
13
14namespace hart
15{
16/// @brief Checks whether the output signal latency is below a specified amount
17/// @details
18/// Compares input and output audio and measures the observed latency between them.
19/// In a multi-channel setup, latency is measured independently for every applicable
20/// channel, and the largest detected latency is used.
21///
22/// The latency detection method is selected via `Method`:
23/// - `Method::onset` detects latency as a difference between the first threshold crossing
24/// in the input and output signals.
25/// - `Method::correlation` uses normalized cross-correlation and finds the lag that best
26/// aligns the output waveform to the input.
27///
28/// `SilencePolicy` defines how the matcher behaves when latency cannot be reliably
29/// determined on one or more channels.
30///
31/// @tparam SampleType Type of audio samples, typically `float` or `double`
32/// @ingroup Matchers
33template <typename SampleType>
35 public Matcher<SampleType, LatencyBelow<SampleType>>
36{
37public:
38 /// @brief Selects the method used to detect latency
39 enum class Method
40 {
41 onset, ///< Detect latency from the first threshold crossing in input and output
42 correlation ///< Detect latency using best waveform alignment via cross-correlation
43 };
44
45 /// @brief Creates a matcher that expects latency between input and output below a specified value
46 /// @param maxLatencySeconds Maximum allowed detected latency in seconds
47 /// @param method Latency detection method. See `Method` for details.
48 /// @param threshold Method-specific detection threshold:
49 /// - for `Method::onset`, this is the minimal sample level in dB required to detect onset.
50 /// - for `Method::correlation`, this is the minimum absolute correlation required for latency
51 /// detection to be considered valid
52 ///
53 /// NaN value will trigger a sensible method-specific default, namely:
54 /// - `-120` (in dB) as sample value threshold for onset-based detection
55 /// - `0.5` as absolute normalized cross-correlation value threshold for correlation-based detection
56 /// @param silencePolicy Defines how channels with insufficient measurable signal are handled:
57 /// - `SilencePolicy::strict` fails if any applicable channel cannot produce a valid latency estimate
58 /// - `SilencePolicy::relaxed` ignores such channels and uses valid ones only
60 double maxLatencySeconds,
61 Method method = Method::onset,
62 double threshold = hart::nan<double>(),
64 ) :
65 m_maxLatencySeconds (maxLatencySeconds),
66 m_method (method),
67 m_silencePolicy (silencePolicy)
68 {
69 const bool thresholdDefaultValueRequested = std::isnan (threshold);
70
71 if (method == Method::onset)
72 {
73 m_threshold = thresholdDefaultValueRequested ? -120.0 : threshold;
74 m_latencyDetector = hart::make_unique<OnsetLatencyDetector<SampleType>> (
75 maxLatencySeconds,
76 silencePolicy,
77 m_threshold
78 );
79 }
80 else
81 {
82 m_threshold = thresholdDefaultValueRequested ? 0.5 : threshold;
83 m_latencyDetector = hart::make_unique<CorrelationLatencyDetector<SampleType>> (
84 maxLatencySeconds,
85 silencePolicy,
86 m_threshold
87 );
88 }
89
90 // Sanity checks
91 hassert (m_latencyDetector != nullptr);
92 hassert (! std::isnan (m_threshold));
93 }
94
95 LatencyBelow (const LatencyBelow& other):
96 m_maxLatencySeconds (other.m_maxLatencySeconds),
97 m_method (other.m_method),
98 m_silencePolicy (other.m_silencePolicy),
99 m_threshold (other.m_threshold),
100 m_latencyDetector (
101 other.m_latencyDetector != nullptr
102 ? other.m_latencyDetector->copy()
103 : nullptr
104 )
105 {
106 hassert (other.m_latencyDetector != nullptr); // Sanity check
107 }
108
109 LatencyBelow (LatencyBelow&& other) noexcept = default;
110
112 {
113 if (this == &other)
114 return *this;
115
116 hassert (other.m_latencyDetector != nullptr); // Sanity check
117
118 m_maxLatencySeconds = other.m_maxLatencySeconds;
119 m_method = other.m_method;
120 m_silencePolicy = other.m_silencePolicy;
121 m_threshold = other.m_threshold;
122
123 m_latencyDetector =
124 other.m_latencyDetector != nullptr
125 ? other.m_latencyDetector->copy()
126 : nullptr;
127
128 return *this;
129 }
130
131 LatencyBelow& operator= (LatencyBelow&& other) noexcept = default;
132
133 ~LatencyBelow() override = default;
134
135 void prepare (double sampleRateHz, size_t numInputChannels, size_t numOutputChannels, size_t maxBlockSizeFrames) override
136 {
137 hassert (numInputChannels == numOutputChannels);
138 hassert (m_latencyDetector != nullptr);
139 m_latencyDetector->prepare (
140 sampleRateHz,
141 numOutputChannels,
142 maxBlockSizeFrames
143 );
144 }
145
146 bool canOperatePerBlock() const override
147 {
148 return false;
149 }
150
151 void reset() override
152 {
153 hassert (m_latencyDetector != nullptr);
154 m_latencyDetector->reset();
155 }
156
157 bool supportsChannelLayout (size_t numInputChannels, size_t numOutputChannels) const override
158 {
159 return numInputChannels == numOutputChannels;
160 }
161
162 bool match (AnalysisContext<SampleType> context) override
163 {
164 const AudioBuffer<SampleType>& inputAudio = context.inputAudio();
165 const AudioBuffer<SampleType>& observedOutputAudio = context.outputAudio();
166
167 hassert (m_latencyDetector != nullptr);
168 return m_latencyDetector->match (
169 inputAudio,
170 observedOutputAudio,
171 [this] (size_t channel) { return this->appliesToChannel (channel); }
172 );
173 }
174
176 {
177 hassert (m_latencyDetector != nullptr);
178 return m_latencyDetector->getFailureDetails();
179 }
180
181 void represent (std::ostream& stream) const override
182 {
183 stream
184 << "LatencyBelow ("
185 << secPrecision << m_maxLatencySeconds << "_s, ";
186
187 if (m_method == Method::onset)
188 {
189 stream
190 << "Method::onset, "
191 << dbPrecision << m_threshold << "_dB, ";
192 }
193 else {
194 stream
195 << "Method::correlation, "
196 << correlationPrecision << m_threshold << ", ";
197 }
198
199 stream
200 << "SilencePolicy::" << (m_silencePolicy == SilencePolicy::strict ? "strict" : "relaxed")
201 << ')';
202 }
203
204private:
205 double m_maxLatencySeconds;
206 Method m_method;
207 SilencePolicy m_silencePolicy;
208 double m_threshold;
209
210 std::unique_ptr<LatencyDetector<SampleType>> m_latencyDetector;
211};
212
214
215} // namespace hart
Contains audio-related artefacts useful for analysis by matchers.
Container for audio data.
Checks whether the output signal latency is below a specified amount.
LatencyBelow(LatencyBelow &&other) noexcept=default
Method
Selects the method used to detect latency.
@ correlation
Detect latency using best waveform alignment via cross-correlation.
@ onset
Detect latency from the first threshold crossing in input and output.
MatcherFailureDetails getFailureDetails() const override
Returns a description of why the match has failed.
void represent(std::ostream &stream) const override
Makes a text representation of this Matcher for test failure outputs.
void prepare(double sampleRateHz, size_t numInputChannels, size_t numOutputChannels, size_t maxBlockSizeFrames) override
Prepare for processing It is guaranteed that all subsequent process() calls will be in line with the ...
bool match(AnalysisContext< SampleType > context) override
Tells the host if the piece of audio satisfies Matcher's condition or not.
LatencyBelow(double maxLatencySeconds, Method method=Method::onset, double threshold=hart::nan< double >(), SilencePolicy silencePolicy=SilencePolicy::strict)
Creates a matcher that expects latency between input and output below a specified value.
bool canOperatePerBlock() const override
Tells the host if it can operate on a block-by-block basis.
LatencyBelow(const LatencyBelow &other)
~LatencyBelow() override=default
LatencyBelow & operator=(const LatencyBelow &other)
LatencyBelow & operator=(LatencyBelow &&other) noexcept=default
void reset() override
Resets the matcher to its initial state.
bool supportsChannelLayout(size_t numInputChannels, size_t numOutputChannels) const override
Tells the host whether this Matcher is capable of operating on audio with a specific number of channe...
Base for audio matchers.
#define hassert(condition)
Triggers a HartAssertException if the condition is false
std::ostream & secPrecision(std::ostream &stream)
Sets number of decimal places for values in seconds.
static std::ostream & correlationPrecision(std::ostream &stream)
Sets number of decimal places for correlation values.
std::ostream & dbPrecision(std::ostream &stream)
Sets number of decimal places for values in decibels.
FloatType nan()
Returns a quiet NaN value for the given floating-point type.
SilencePolicy
Defines how silence in various algorithms.
#define HART_MATCHER_DECLARE_ALIASES_FOR(ClassName)