68 void prepare(
double sampleRate,
int windowSize = 2048,
int hopSize = 512)
70 if (!std::isfinite(sampleRate) || sampleRate <= 0.0)
75 sampleRate_ = sampleRate;
76 windowSize_ = std::clamp(windowSize, 64, 1 << 20);
77 halfWindow_ = windowSize_ / 2;
78 hopSize_ = std::clamp(hopSize, 1, windowSize_);
82 buffer_.assign(
static_cast<size_t>(windowSize_) * 2, T(0));
83 yinBuffer_.assign(
static_cast<size_t>(halfWindow_), T(0));
89 while (fftSize_ < windowSize_ * 2) fftSize_ <<= 1;
90 fftTime_.assign(
static_cast<size_t>(fftSize_), T(0));
91 specHalf_.assign(
static_cast<size_t>(fftSize_) + 2, T(0));
92 specFull_.assign(
static_cast<size_t>(fftSize_) + 2, T(0));
93 corrTime_.assign(
static_cast<size_t>(fftSize_), T(0));
94 prefixSq_.assign(
static_cast<size_t>(windowSize_) + 1, T(0));
97 samplesSinceLastDetect_ = 0;
99 frequency_.store(T(0), std::memory_order_relaxed);
100 confidence_.store(T(0), std::memory_order_relaxed);
102 fft_ = std::make_unique<FFTReal<T>>(
static_cast<size_t>(fftSize_));
124 for (
const T sample : samples)
127 buffer_[
static_cast<size_t>(writePos_)] = sample;
128 buffer_[
static_cast<size_t>(writePos_ + windowSize_)] = sample;
131 if (writePos_ >= windowSize_)
136 samplesSinceLastDetect_++;
137 if (samplesSinceLastDetect_ >= hopSize_)
140 samplesSinceLastDetect_ = 0;
148 return frequency_.load(std::memory_order_relaxed);
154 return confidence_.load(std::memory_order_relaxed);
161 if (freq <= T(0))
return -1;
162 return static_cast<int>(std::round(T(69) + T(12) * std::log2(freq / T(440))));
169 if (freq <= T(0))
return T(0);
170 T midiExact = T(69) + T(12) * std::log2(freq / T(440));
171 return (midiExact - std::round(midiExact)) * T(100);
180 if (!std::isfinite(threshold))
return;
181 threshold_.store(std::clamp(threshold, T(0.01), T(0.5)), std::memory_order_relaxed);
187 return threshold_.load(std::memory_order_relaxed);
193 std::fill(buffer_.begin(), buffer_.end(), T(0));
195 samplesSinceLastDetect_ = 0;
196 frequency_.store(T(0), std::memory_order_relaxed);
197 confidence_.store(T(0), std::memory_order_relaxed);
201 void detect() noexcept
205 const T* currentWindow = &buffer_[
static_cast<size_t>(writePos_)];
214 for (
int i = 0; i < windowSize_; ++i) {
215 energy += currentWindow[i] * currentWindow[i];
218 if (energy < T(1e-10) || !std::isfinite(energy))
220 frequency_.store(T(0), std::memory_order_relaxed);
221 confidence_.store(T(0), std::memory_order_relaxed);
230 const int W = halfWindow_;
234 for (
int i = 0; i < windowSize_; ++i)
235 prefixSq_[
static_cast<size_t>(i + 1)] =
236 prefixSq_[
static_cast<size_t>(i)] + currentWindow[i] * currentWindow[i];
237 const T e1 = prefixSq_[
static_cast<size_t>(W)];
240 std::fill(fftTime_.begin(), fftTime_.end(), T(0));
241 std::copy(currentWindow, currentWindow + W, fftTime_.begin());
242 fft_->forward(fftTime_.data(), specHalf_.data());
244 std::fill(fftTime_.begin(), fftTime_.end(), T(0));
245 std::copy(currentWindow, currentWindow + windowSize_, fftTime_.begin());
246 fft_->forward(fftTime_.data(), specFull_.data());
248 const int numBins = fftSize_ / 2 + 1;
249 for (
int k = 0; k < numBins; ++k)
251 const T aRe = specHalf_[
static_cast<size_t>(2 * k)];
252 const T aIm = specHalf_[
static_cast<size_t>(2 * k + 1)];
253 const T bRe = specFull_[
static_cast<size_t>(2 * k)];
254 const T bIm = specFull_[
static_cast<size_t>(2 * k + 1)];
256 specFull_[
static_cast<size_t>(2 * k)] = aRe * bRe + aIm * bIm;
257 specFull_[
static_cast<size_t>(2 * k + 1)] = aRe * bIm - aIm * bRe;
259 fft_->inverse(specFull_.data(), corrTime_.data());
262 const T threshold = threshold_.load(std::memory_order_relaxed);
263 yinBuffer_[0] = T(1);
266 for (
int tau = 1; tau < W; ++tau)
268 const T e2 = prefixSq_[
static_cast<size_t>(tau + W)] - prefixSq_[
static_cast<size_t>(tau)];
269 T d = e1 + e2 - T(2) * corrTime_[
static_cast<size_t>(tau)];
270 if (d < T(0)) d = T(0);
273 yinBuffer_[
static_cast<size_t>(tau)] =
274 (runningSum > T(0)) ? d *
static_cast<T
>(tau) / runningSum : T(0);
278 int tauEstimate = -1;
279 for (
int tau = 2; tau < halfWindow_; ++tau)
281 if (yinBuffer_[
static_cast<size_t>(tau)] < threshold)
283 while (tau + 1 < halfWindow_ &&
284 yinBuffer_[
static_cast<size_t>(tau + 1)] < yinBuffer_[
static_cast<size_t>(tau)])
295 frequency_.store(T(0), std::memory_order_relaxed);
296 confidence_.store(T(0), std::memory_order_relaxed);
303 T betterTau = parabolicInterp(tauEstimate);
304 T finalConfidence = std::clamp(T(1) - yinBuffer_[
static_cast<size_t>(tauEstimate)], T(0), T(1));
306 frequency_.store(
static_cast<T
>(sampleRate_) / betterTau, std::memory_order_relaxed);
307 confidence_.store(finalConfidence, std::memory_order_relaxed);
310 [[nodiscard]] T parabolicInterp(
int tau)
const noexcept
312 if (tau < 1 || tau >= halfWindow_ - 1)
313 return static_cast<T
>(tau);
315 T s0 = yinBuffer_[
static_cast<size_t>(tau - 1)];
316 T s1 = yinBuffer_[
static_cast<size_t>(tau)];
317 T s2 = yinBuffer_[
static_cast<size_t>(tau + 1)];
319 T denom = s0 - T(2) * s1 + s2;
322 if (std::abs(denom) < T(1e-12))
323 return static_cast<T
>(tau);
325 T adjustment = (s0 - s2) / (T(2) * denom);
326 return static_cast<T
>(tau) + adjustment;
329 double sampleRate_ = 44100.0;
330 int windowSize_ = 2048;
331 int halfWindow_ = 1024;
334 int samplesSinceLastDetect_ = 0;
335 std::atomic<T> threshold_{ T(0.10) };
338 std::atomic<T> frequency_{T(0)};
339 std::atomic<T> confidence_{T(0)};
342 std::vector<T> buffer_;
343 std::vector<T> yinBuffer_;
347 std::unique_ptr<FFTReal<T>> fft_;
348 std::vector<T> fftTime_;
349 std::vector<T> specHalf_;
350 std::vector<T> specFull_;
351 std::vector<T> corrTime_;
352 std::vector<T> prefixSq_;