19template <
typename SampleType>
20class CorrelationLatencyDetector :
21 public LatencyDetector<SampleType>
24 CorrelationLatencyDetector (
double maxLatencySeconds,
SilencePolicy silencePolicy,
double absCorrelationThreshold) :
25 m_maxLatencySeconds (maxLatencySeconds),
26 m_silencePolicy (silencePolicy),
27 m_absCorrelationThreshold (absCorrelationThreshold)
29 if (absCorrelationThreshold > 1.0)
32 if (absCorrelationThreshold < 0.0)
40 std::unique_ptr<LatencyDetector<SampleType>> copy()
const override
42 return hart::make_unique<CorrelationLatencyDetector<SampleType>> (*
this);
45 void prepare (
double sampleRateHz, size_t , size_t )
override
47 m_sampleRateHz = sampleRateHz;
52 m_hadValidData =
false;
55 m_detectedLatencyFrames = 0;
56 m_bestCorrelation = 0.0;
60 const AudioBuffer<SampleType>& inputAudio,
61 const AudioBuffer<SampleType>& observedOutputAudio,
62 const std::function<
bool (size_t)>& appliesToChannel
65 const size_t numFrames = inputAudio.getNumFrames();
69 m_hadValidData =
false;
77 const size_t onePercentOfDurationFrames = (numFrames + 99) / 100;
78 const size_t minOverlapFrames = std::max<size_t> (2, onePercentOfDurationFrames);
80 if (numFrames < minOverlapFrames)
82 m_hadValidData =
false;
86 const size_t maxLagFrames = numFrames - minOverlapFrames;
87 bool anyValidChannel =
false;
88 size_t worstLatencyFrames = 0;
89 size_t worstChannel = 0;
91 for (size_t channel = 0; channel < inputAudio.getNumChannels(); ++channel)
93 if (! appliesToChannel (channel))
96 const SampleType* x = inputAudio[channel];
97 const SampleType* y = observedOutputAudio[channel];
98 std::vector<
double> prefixSumsSqX (numFrames + 1, 0.0);
99 std::vector<
double> prefixSumsSqY (numFrames + 1, 0.0);
103 for (size_t frame = 0; frame < numFrames; ++frame)
105 const double xVal =
static_cast<
double> (x[frame]);
106 const double yVal =
static_cast<
double> (y[frame]);
108 runningSumSqX
+= xVal * xVal;
109 runningSumSqY
+= yVal * yVal;
110 prefixSumsSqX[frame + 1] = runningSumSqX;
111 prefixSumsSqY[frame + 1] = runningSumSqY;
116 bool channelValid =
false;
118 for (size_t lag = 0; lag <= maxLagFrames; ++lag)
121 const size_t inputOverlapBeginFrame = 0;
122 const size_t outputOverlapBeginFrame = lag;
123 const size_t overlapSizeFrames = numFrames - lag;
125 if (overlapSizeFrames < minOverlapFrames)
128 const size_t inputOverlapEndFrame = inputOverlapBeginFrame + overlapSizeFrames;
129 const size_t outputOverlapEndFrame = outputOverlapBeginFrame + overlapSizeFrames;
130 const double sumSqX = prefixSumsSqX[inputOverlapEndFrame] - prefixSumsSqX[inputOverlapBeginFrame];
131 const double sumSqY = prefixSumsSqY[outputOverlapEndFrame] - prefixSumsSqY[outputOverlapBeginFrame];
133 for (size_t overlapFrame = 0; overlapFrame < overlapSizeFrames; ++overlapFrame)
135 const double inputValue =
static_cast<
double> (x[inputOverlapBeginFrame + overlapFrame]);
136 const double outputValue =
static_cast<
double> (y[outputOverlapBeginFrame + overlapFrame]);
137 dotProduct
+= inputValue * outputValue;
144 const double correlation = dotProduct / std::sqrt (sumSqX * sumSqY);
145 const double absCorrelation = std::abs (correlation);
147 if (absCorrelation > bestAbsCorrelation)
149 bestAbsCorrelation = absCorrelation;
157 if (! channelValid || bestAbsCorrelation < m_absCorrelationThreshold)
161 m_hadValidData =
false;
162 m_failureChannel = channel;
169 anyValidChannel =
true;
171 if (bestLag > worstLatencyFrames)
173 worstLatencyFrames = bestLag;
174 worstChannel = channel;
175 m_bestCorrelation = bestAbsCorrelation;
179 if (!anyValidChannel)
181 m_hadValidData =
false;
185 m_hadValidData =
true;
186 m_detectedLatencyFrames = worstLatencyFrames;
187 const double latencySeconds = m_detectedLatencyFrames / m_sampleRateHz;
189 if (latencySeconds <= m_maxLatencySeconds)
192 m_failureChannel = worstChannel;
200 details
.frame = m_failureFrame;
202 if (! m_hadValidData)
204 details
.description =
"Latency could not be determined with sufficient correlation";
208 const double latencySeconds = m_detectedLatencyFrames / m_sampleRateHz;
210 std::stringstream descriptionStream;
212 <<
"Detected latency: "
214 << m_detectedLatencyFrames <<
" frames), "
222 const double m_maxLatencySeconds;
224 const double m_absCorrelationThreshold;
225 double m_sampleRateHz = 0.0;
227 bool m_hadValidData =
false;
228 size_t m_detectedLatencyFrames = 0;
229 double m_bestCorrelation = 0.0;
230 size_t m_failureChannel = 0;
231 size_t m_failureFrame = 0;
Implements Kahan algorithm for floating point accumulations.
AccurateSum(SampleType initialSum=(SampleType) 0)
Inits AccurateSum with a specific value.
AccurateSum & operator+=(SampleType value)
Adds a value to a sum, tracking the potential floating point error.
Thrown when an inappropriate value is encountered.
#define HART_THROW_OR_RETURN_VOID(ExceptionType, message)
Throws an exception if HART_DO_NOT_THROW_EXCEPTIONS is set, prints a message and returns otherwise.
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.
SilencePolicy
Defines how silence in various algorithms.
constexpr double inf
Infinity.
static SampleType floatsEqual(SampleType a, SampleType b, SampleType epsilon=(SampleType) 1e-8)
Compares two floating point numbers within a given tolerance.
Details about matcher failure.
size_t channel
Index of channel at which the failure was detected.
std::string description
Readable description of why the match has failed.
size_t frame
Index of frame at which the match has failed.