102 void prepare(
double sampleRate,
int windowSize = 0,
int hopSize = 0)
104 if (!std::isfinite(sampleRate) || sampleRate <= 0.0)
109 sampleRate_ = sampleRate;
117 const double target = sampleRate_ * (kAutoSpanRef / kAutoSpanRate);
118 int n = kAutoMinWindow;
119 while (n < kAutoMaxWindow &&
static_cast<double>(n) < target) n <<= 1;
124 windowSize_ = std::clamp(windowSize, 64, 1 << 20);
126 halfWindow_ = windowSize_ / 2;
127 hopSize_ = (hopSize <= 0) ? std::max(1, windowSize_ / 4)
128 : std::clamp(hopSize, 1, windowSize_);
132 buffer_.assign(
static_cast<size_t>(windowSize_) * 2, T(0));
133 yinBuffer_.assign(
static_cast<size_t>(halfWindow_), T(0));
134 rawDiff_.assign(
static_cast<size_t>(halfWindow_), T(0));
140 while (fftSize_ < windowSize_ * 2) fftSize_ <<= 1;
141 fftTime_.assign(
static_cast<size_t>(fftSize_), T(0));
142 specHalf_.assign(
static_cast<size_t>(fftSize_) + 2, T(0));
143 specFull_.assign(
static_cast<size_t>(fftSize_) + 2, T(0));
144 corrTime_.assign(
static_cast<size_t>(fftSize_), T(0));
145 prefixSq_.assign(
static_cast<size_t>(windowSize_) + 1, T(0));
148 samplesSinceLastDetect_ = 0;
150 frequency_.store(T(0), std::memory_order_relaxed);
151 confidence_.store(T(0), std::memory_order_relaxed);
153 fft_ = std::make_unique<FFTReal<T>>(
static_cast<size_t>(fftSize_));
190 for (
const T sample : samples)
193 buffer_[
static_cast<size_t>(writePos_)] = sample;
194 buffer_[
static_cast<size_t>(writePos_ + windowSize_)] = sample;
197 if (writePos_ >= windowSize_)
202 samplesSinceLastDetect_++;
203 if (samplesSinceLastDetect_ >= hopSize_)
206 samplesSinceLastDetect_ = 0;
214 return frequency_.load(std::memory_order_relaxed);
220 return confidence_.load(std::memory_order_relaxed);
227 if (freq <= T(0))
return -1;
228 return static_cast<int>(std::round(T(69) + T(12) * std::log2(freq / T(440))));
235 if (freq <= T(0))
return T(0);
236 T midiExact = T(69) + T(12) * std::log2(freq / T(440));
237 return (midiExact - std::round(midiExact)) * T(100);
246 if (!std::isfinite(threshold))
return;
247 threshold_.store(std::clamp(threshold, T(0.01), T(0.5)), std::memory_order_relaxed);
253 return threshold_.load(std::memory_order_relaxed);
267 [[nodiscard]]
int getHopSize() const noexcept {
return hopSize_; }
272 std::fill(buffer_.begin(), buffer_.end(), T(0));
274 samplesSinceLastDetect_ = 0;
275 frequency_.store(T(0), std::memory_order_relaxed);
276 confidence_.store(T(0), std::memory_order_relaxed);
280 void detect() noexcept
284 const T* currentWindow = &buffer_[
static_cast<size_t>(writePos_)];
293 for (
int i = 0; i < windowSize_; ++i) {
294 energy += currentWindow[i] * currentWindow[i];
297 if (energy < T(1e-10) || !std::isfinite(energy))
299 frequency_.store(T(0), std::memory_order_relaxed);
300 confidence_.store(T(0), std::memory_order_relaxed);
309 const int W = halfWindow_;
313 for (
int i = 0; i < windowSize_; ++i)
314 prefixSq_[
static_cast<size_t>(i + 1)] =
315 prefixSq_[
static_cast<size_t>(i)] + currentWindow[i] * currentWindow[i];
316 const T e1 = prefixSq_[
static_cast<size_t>(W)];
319 std::fill(fftTime_.begin(), fftTime_.end(), T(0));
320 std::copy(currentWindow, currentWindow + W, fftTime_.begin());
321 fft_->forward(fftTime_.data(), specHalf_.data());
323 std::fill(fftTime_.begin(), fftTime_.end(), T(0));
324 std::copy(currentWindow, currentWindow + windowSize_, fftTime_.begin());
325 fft_->forward(fftTime_.data(), specFull_.data());
327 const int numBins = fftSize_ / 2 + 1;
328 for (
int k = 0; k < numBins; ++k)
330 const T aRe = specHalf_[
static_cast<size_t>(2 * k)];
331 const T aIm = specHalf_[
static_cast<size_t>(2 * k + 1)];
332 const T bRe = specFull_[
static_cast<size_t>(2 * k)];
333 const T bIm = specFull_[
static_cast<size_t>(2 * k + 1)];
335 specFull_[
static_cast<size_t>(2 * k)] = aRe * bRe + aIm * bIm;
336 specFull_[
static_cast<size_t>(2 * k + 1)] = aRe * bIm - aIm * bRe;
338 fft_->inverse(specFull_.data(), corrTime_.data());
341 const T threshold = threshold_.load(std::memory_order_relaxed);
342 yinBuffer_[0] = T(1);
345 for (
int tau = 1; tau < W; ++tau)
347 const T e2 = prefixSq_[
static_cast<size_t>(tau + W)] - prefixSq_[
static_cast<size_t>(tau)];
348 T d = e1 + e2 - T(2) * corrTime_[
static_cast<size_t>(tau)];
349 if (d < T(0)) d = T(0);
351 rawDiff_[
static_cast<size_t>(tau)] = d;
353 yinBuffer_[
static_cast<size_t>(tau)] =
354 (runningSum > T(0)) ? d *
static_cast<T
>(tau) / runningSum : T(0);
358 int tauEstimate = -1;
359 for (
int tau = 2; tau < halfWindow_; ++tau)
361 if (yinBuffer_[
static_cast<size_t>(tau)] < threshold)
363 while (tau + 1 < halfWindow_ &&
364 yinBuffer_[
static_cast<size_t>(tau + 1)] < yinBuffer_[
static_cast<size_t>(tau)])
375 frequency_.store(T(0), std::memory_order_relaxed);
376 confidence_.store(T(0), std::memory_order_relaxed);
382 T betterTau = parabolicInterp(tauEstimate);
383 T finalConfidence = std::clamp(T(1) - yinBuffer_[
static_cast<size_t>(tauEstimate)], T(0), T(1));
385 frequency_.store(
static_cast<T
>(sampleRate_) / betterTau, std::memory_order_relaxed);
386 confidence_.store(finalConfidence, std::memory_order_relaxed);
401 [[nodiscard]] T parabolicInterp(
int tau)
const noexcept
403 if (tau < 2 || tau >= halfWindow_ - 1)
404 return static_cast<T
>(tau);
407 const int centre = tau;
408 if (rawDiff_[
static_cast<size_t>(centre - 1)] < rawDiff_[
static_cast<size_t>(tau)]) tau = centre - 1;
409 if (rawDiff_[
static_cast<size_t>(centre + 1)] < rawDiff_[
static_cast<size_t>(tau)]) tau = centre + 1;
410 if (tau < 2 || tau >= halfWindow_ - 1)
411 return static_cast<T
>(tau);
413 const double s0 =
static_cast<double>(rawDiff_[
static_cast<size_t>(tau - 1)]);
414 const double s1 =
static_cast<double>(rawDiff_[
static_cast<size_t>(tau)]);
415 const double s2 =
static_cast<double>(rawDiff_[
static_cast<size_t>(tau + 1)]);
417 const double denom = s0 - 2.0 * s1 + s2;
420 if (!(denom > 1e-30))
421 return static_cast<T
>(tau);
423 const double vertex = std::clamp((s0 - s2) / (2.0 * denom), -0.5, 0.5);
424 const double w = 6.283185307179586 /
static_cast<double>(tau);
425 const double offset = std::atan(2.0 * vertex * std::tan(0.5 * w)) / w;
426 return static_cast<T
>(
static_cast<double>(tau) + offset);
431 static constexpr double kAutoSpanRef = 2048.0;
432 static constexpr double kAutoSpanRate = 48000.0;
433 static constexpr int kAutoMinWindow = 512;
434 static constexpr int kAutoMaxWindow = 16384;
436 double sampleRate_ = 44100.0;
437 int windowSize_ = 2048;
438 int halfWindow_ = 1024;
441 int samplesSinceLastDetect_ = 0;
442 std::atomic<T> threshold_{ T(0.10) };
445 std::atomic<T> frequency_{T(0)};
446 std::atomic<T> confidence_{T(0)};
449 std::vector<T> buffer_;
450 std::vector<T> yinBuffer_;
451 std::vector<T> rawDiff_;
455 std::unique_ptr<FFTReal<T>> fft_;
456 std::vector<T> fftTime_;
457 std::vector<T> specHalf_;
458 std::vector<T> specFull_;
459 std::vector<T> corrTime_;
460 std::vector<T> prefixSq_;