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. Very small overlaps near the signal boundary
27/// are ignored so that a tiny slice of matching samples cannot masquerade as a valid lag.
28///
29/// `SilencePolicy` defines how the matcher behaves when latency cannot be reliably
30/// determined on one or more channels.
31///
32/// @tparam SampleType Type of audio samples, typically `float` or `double`
33/// @ingroup Matchers
34template <typename SampleType>
36 public Matcher<SampleType, LatencyBelow<SampleType>>
37{
38public:
39 /// @brief Selects the method used to detect latency
40 enum class Method
41 {
42 onset, ///< Detect latency from the first threshold crossing in input and output
43 correlation ///< Detect latency using best waveform alignment via cross-correlation
44 };
45
46 /// @brief Creates a matcher that expects latency between input and output below a specified value
47 /// @param maxLatencySeconds Maximum allowed detected latency in seconds
48 /// @param method Latency detection method. See `Method` for details.
49 /// @param threshold Method-specific detection threshold:
50 /// - for `Method::onset`, this is the minimal sample level in dB required to detect onset.
51 /// - for `Method::correlation`, this is the minimum absolute correlation required for latency
52 /// detection to be considered valid
53 ///
54 /// NaN value will trigger a sensible method-specific default, namely:
55 /// - `-120` (in dB) as sample value threshold for onset-based detection
56 /// - `0.5` as absolute normalized cross-correlation value threshold for correlation-based detection
57 /// @param silencePolicy Defines how channels with insufficient measurable signal are handled:
58 /// - `SilencePolicy::strict` fails if any applicable channel cannot produce a valid latency estimate
59 /// - `SilencePolicy::relaxed` ignores such channels and uses valid ones only
61 double maxLatencySeconds,
62 Method method = Method::onset,
63 double threshold = hart::nan<double>(),
65 ) :
66 m_maxLatencySeconds (maxLatencySeconds),
67 m_method (method),
68 m_silencePolicy (silencePolicy)
69 {
70 const bool thresholdDefaultValueRequested = std::isnan (threshold);
71
72 if (method == Method::onset)
73 {
74 m_threshold = thresholdDefaultValueRequested ? -120.0 : threshold;
75 m_latencyDetector = hart::make_unique<OnsetLatencyDetector<SampleType>> (
76 maxLatencySeconds,
77 silencePolicy,
78 m_threshold
79 );
80 }
81 else
82 {
83 m_threshold = thresholdDefaultValueRequested ? 0.5 : threshold;
84 m_latencyDetector = hart::make_unique<CorrelationLatencyDetector<SampleType>> (
85 maxLatencySeconds,
86 silencePolicy,
87 m_threshold
88 );
89 }
90
91 // Sanity checks
92 hassert (m_latencyDetector != nullptr);
93 hassert (! std::isnan (m_threshold));
94 }
95
96 LatencyBelow (const LatencyBelow& other):
97 m_maxLatencySeconds (other.m_maxLatencySeconds),
98 m_method (other.m_method),
99 m_silencePolicy (other.m_silencePolicy),
100 m_threshold (other.m_threshold),
101 m_latencyDetector (
102 other.m_latencyDetector != nullptr
103 ? other.m_latencyDetector->copy()
104 : nullptr
105 )
106 {
107 hassert (other.m_latencyDetector != nullptr); // Sanity check
108 }
109
110 LatencyBelow (LatencyBelow&& other) noexcept = default;
111
113 {
114 if (this == &other)
115 return *this;
116
117 hassert (other.m_latencyDetector != nullptr); // Sanity check
118
119 m_maxLatencySeconds = other.m_maxLatencySeconds;
120 m_method = other.m_method;
121 m_silencePolicy = other.m_silencePolicy;
122 m_threshold = other.m_threshold;
123
124 m_latencyDetector =
125 other.m_latencyDetector != nullptr
126 ? other.m_latencyDetector->copy()
127 : nullptr;
128
129 return *this;
130 }
131
132 LatencyBelow& operator= (LatencyBelow&& other) noexcept = default;
133
134 ~LatencyBelow() override = default;
135
136 void prepare (double sampleRateHz, size_t numInputChannels, size_t numOutputChannels, size_t maxBlockSizeFrames) override
137 {
138 hassert (numInputChannels == numOutputChannels);
139 hassert (m_latencyDetector != nullptr);
140 m_latencyDetector->prepare (
141 sampleRateHz,
142 numOutputChannels,
143 maxBlockSizeFrames
144 );
145 }
146
147 bool canOperatePerBlock() const override
148 {
149 return false;
150 }
151
152 void reset() override
153 {
154 hassert (m_latencyDetector != nullptr);
155 m_latencyDetector->reset();
156 }
157
158 bool supportsChannelLayout (size_t numInputChannels, size_t numOutputChannels) const override
159 {
160 return numInputChannels == numOutputChannels;
161 }
162
163 bool match (AnalysisContext<SampleType> context) override
164 {
165 const AudioBuffer<SampleType>& inputAudio = context.inputAudio();
166 const AudioBuffer<SampleType>& observedOutputAudio = context.outputAudio();
167
168 hassert (m_latencyDetector != nullptr);
169 return m_latencyDetector->match (
170 inputAudio,
171 observedOutputAudio,
172 [this] (size_t channel) { return this->appliesToChannel (channel); }
173 );
174 }
175
177 {
178 hassert (m_latencyDetector != nullptr);
179 return m_latencyDetector->getFailureDetails();
180 }
181
182 void represent (std::ostream& stream) const override
183 {
184 stream
185 << "LatencyBelow ("
186 << secPrecision << m_maxLatencySeconds << "_s, ";
187
188 if (m_method == Method::onset)
189 {
190 stream
191 << "Method::onset, "
192 << dbPrecision << m_threshold << "_dB, ";
193 }
194 else {
195 stream
196 << "Method::correlation, "
197 << correlationPrecision << m_threshold << ", ";
198 }
199
200 stream
201 << "SilencePolicy::" << (m_silencePolicy == SilencePolicy::strict ? "strict" : "relaxed")
202 << ')';
203 }
204
205private:
206 double m_maxLatencySeconds;
207 Method m_method;
208 SilencePolicy m_silencePolicy;
209 double m_threshold;
210
211 std::unique_ptr<LatencyDetector<SampleType>> m_latencyDetector;
212};
213
215
216} // namespace hart
Contains audio-related artefacts useful for analysis by matchers.
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)