113 void prepare(
double sampleRate,
int maxDelaySamples)
115 if (!(sampleRate > 0.0) || !std::isfinite(sampleRate))
return;
116 sampleRate_ = sampleRate;
117 maxDelay_ = std::max(1, maxDelaySamples);
119 while (frame_ < 8 * maxDelay_ && frame_ < (1 << 26)) frame_ <<= 1;
121 fft_ = std::make_unique<FFTReal<double>>(
static_cast<size_t>(frame_));
122 window_.resize(
static_cast<size_t>(frame_));
123 for (
int i = 0; i < frame_; ++i)
124 window_[
static_cast<size_t>(i)] = 0.5 - 0.5 * std::cos(2.0 * pi<double> * i / frame_);
127 historyLength_ = frame_ + 2 * maxDelay_ + 2;
128 refHistory_.assign(
static_cast<size_t>(historyLength_), 0.0);
129 delHistory_.assign(
static_cast<size_t>(historyLength_), 0.0);
130 bufA_.assign(
static_cast<size_t>(frame_ + 2), 0.0);
131 bufB_.assign(
static_cast<size_t>(frame_ + 2), 0.0);
132 cross_.assign(
static_cast<size_t>(frame_ / 2 + 1), std::complex<double> {});
133 corr_.assign(
static_cast<size_t>(frame_ + 2), 0.0);
141 std::fill(refHistory_.begin(), refHistory_.end(), 0.0);
142 std::fill(delHistory_.begin(), delHistory_.end(), 0.0);
143 std::fill(cross_.begin(), cross_.end(), std::complex<double> {});
145 nextFrameEnd_ =
static_cast<int64_t
>(frame_) + maxDelay_;
156 if (!prepared_)
return;
157 const int n = std::min(reference.getNumSamples(), delivery.getNumSamples());
158 const int rc = reference.getNumChannels(), dc = delivery.getNumChannels();
159 if (n <= 0 || rc <= 0 || dc <= 0)
return;
160 for (
int i = 0; i < n; ++i)
162 double r = 0.0, d = 0.0;
163 for (
int c = 0; c < rc; ++c) r += static_cast<double>(reference.getChannel(c)[i]);
164 for (
int c = 0; c < dc; ++c) d += static_cast<double>(delivery.getChannel(c)[i]);
165 const size_t slot =
static_cast<size_t>(written_ % historyLength_);
166 refHistory_[slot] = std::isfinite(r) ? r / rc : 0.0;
167 delHistory_[slot] = std::isfinite(d) ? d / dc : 0.0;
169 if (written_ == nextFrameEnd_)
172 nextFrameEnd_ += hop_;
181 if (!prepared_ || frames_ < 1)
return out;
183 bool inverted =
false;
184 if (!searchPeak(lag, inverted))
return out;
185 const double sign = inverted ? -1.0 : 1.0;
190 double d = lag -
static_cast<double>(offset_);
191 for (
int it = 0; it < 8; ++it)
193 double num = 0.0, den = 0.0;
194 for (
int k = 1; k < frame_ / 2; ++k)
196 const double w = 2.0 * pi<double> * k / frame_;
197 const std::complex<double> s = cross_[
static_cast<size_t>(k)] * sign
198 * std::polar(1.0, w * d);
199 const double mag = std::abs(s);
200 if (!(mag > 0.0))
continue;
201 const double phi = std::arg(s);
202 num += mag * w * phi;
205 if (!(den > 0.0))
break;
206 const double step = -num / den;
208 if (std::abs(step) < 1e-9)
break;
211 std::complex<double> agree {};
213 for (
int k = 1; k < frame_ / 2; ++k)
215 const double w = 2.0 * pi<double> * k / frame_;
216 const std::complex<double> s = cross_[
static_cast<size_t>(k)] * sign;
217 agree += s * std::polar(1.0, w * d);
218 total += std::abs(s);
222 out.
confidence = (total > 0.0) ? std::clamp(std::abs(agree) / total, 0.0, 1.0) : 0.0;
224 out.
valid = total > 0.0;
233 double sampleRate,
int maxDelaySamples)
236 est.
prepare(sampleRate, maxDelaySamples);
237 est.
push(reference, delivery);
239 const int flush = est.frame_ + 2 * est.maxDelay_;
240 std::vector<T> zeros(
static_cast<size_t>(std::min(flush, 1 << 16)), T(0));
241 const T* z = zeros.data();
242 for (
int left = flush; left > 0; left -=
static_cast<int>(zeros.size()))
244 const int n = std::min(left,
static_cast<int>(zeros.size()));
253 void accumulateFrame() noexcept
262 if (searchPeak(lag, inv))
264 const int newOffset =
static_cast<int>(std::lround(lag));
265 if (newOffset != offset_)
269 for (
int k = 0; k <= frame_ / 2; ++k)
271 const double w = 2.0 * pi<double> * k / frame_;
272 cross_[
static_cast<size_t>(k)] *= std::polar(1.0, w * (newOffset - offset_));
279 const int64_t refStart = written_ - maxDelay_ - frame_;
280 const int64_t delStart = refStart + offset_;
281 for (
int i = 0; i < frame_; ++i)
283 const double w = window_[
static_cast<size_t>(i)];
284 bufA_[
static_cast<size_t>(i)] = w * sample(refHistory_, refStart + i);
285 bufB_[
static_cast<size_t>(i)] = w * sample(delHistory_, delStart + i);
287 fft_->forward(bufA_.data(), bufA_.data());
288 fft_->forward(bufB_.data(), bufB_.data());
289 for (
int k = 0; k <= frame_ / 2; ++k)
291 const std::complex<double> a(bufA_[
static_cast<size_t>(2 * k)], bufA_[
static_cast<size_t>(2 * k + 1)]);
292 const std::complex<double> b(bufB_[
static_cast<size_t>(2 * k)], bufB_[
static_cast<size_t>(2 * k + 1)]);
293 cross_[
static_cast<size_t>(k)] += b * std::conj(a);
298 [[nodiscard]]
double sample(
const std::vector<double>& h, int64_t index)
const noexcept
300 if (index < 0 || index >= written_ || index < written_ - historyLength_)
return 0.0;
301 return h[
static_cast<size_t>(index % historyLength_)];
305 bool searchPeak(
double& lag,
bool& inverted)
noexcept
307 double peakMag = 0.0;
308 for (
const auto& s : cross_) peakMag = std::max(peakMag, std::abs(s));
309 if (!(peakMag > 0.0))
return false;
310 const double floor = peakMag * 1e-9;
311 for (
int k = 0; k <= frame_ / 2; ++k)
314 const double w = 2.0 * pi<double> * k / frame_;
315 std::complex<double> s = cross_[
static_cast<size_t>(k)] * std::polar(1.0, -w * offset_);
316 const double m = std::abs(s);
317 s = (m > floor) ? s / m : std::complex<double> {};
318 corr_[
static_cast<size_t>(2 * k)] = s.real();
319 corr_[
static_cast<size_t>(2 * k + 1)] = s.imag();
321 fft_->inverse(corr_.data(), corr_.data());
322 auto at = [&](
int tau) {
323 const int m = ((tau % frame_) + frame_) % frame_;
324 return corr_[
static_cast<size_t>(m)];
327 double bestAbs = -1.0;
328 for (
int tau = -maxDelay_; tau <= maxDelay_; ++tau)
330 const double v = std::abs(at(tau));
331 if (v > bestAbs) { bestAbs = v; best = tau; }
333 inverted = at(best) < 0.0;
334 const double s = inverted ? -1.0 : 1.0;
335 const double ym = s * at(best - 1), y0 = s * at(best), yp = s * at(best + 1);
336 const double den = ym - 2.0 * y0 + yp;
337 const double frac = (den < 0.0) ? std::clamp(0.5 * (ym - yp) / den, -0.5, 0.5) : 0.0;
338 lag =
static_cast<double>(best) + frac;
342 double sampleRate_ = 48000.0;
346 int historyLength_ = 0;
347 bool prepared_ =
false;
348 std::unique_ptr<FFTReal<double>> fft_;
349 std::vector<double> window_, refHistory_, delHistory_, bufA_, bufB_, corr_;
350 std::vector<std::complex<double>> cross_;
351 int64_t written_ = 0;
352 int64_t nextFrameEnd_ = 0;
static Result estimate(AudioBufferView< const T > reference, AudioBufferView< const T > delivery, double sampleRate, int maxDelaySamples)
Whole-signal convenience: one estimate over two buffers. Allocates.