125 int fs = std::clamp(fftSize, kMinFft, kMaxFft);
127 while (pow2 < fs) pow2 <<= 1;
129 numBins_ = fftSize_ / 2 + 1;
132 hop_ = std::max(1,
static_cast<int>(std::lround(sampleRate_ / 200.0)));
135 hop_ = std::clamp(hop_, 1, fftSize_);
137 latencySamples_.store(
static_cast<int64_t
>(fftSize_) + hop_,
138 std::memory_order_relaxed);
145 localizationOffset_ =
static_cast<int>(std::lround(kLocalizationLead
146 *
static_cast<double>(fftSize_)));
149 primeFrames_ = fftSize_ / hop_ + 2;
153 window_.assign(
static_cast<size_t>(fftSize_), T(0));
155 ring_.assign(
static_cast<size_t>(fftSize_) * 2, T(0));
157 fft_time_.assign(
static_cast<size_t>(fftSize_), T(0));
158 fft_spec_.assign(
static_cast<size_t>(fftSize_) + 2, T(0));
159 mag_.assign(
static_cast<size_t>(numBins_), T(0));
160 phase_.assign(
static_cast<size_t>(numBins_), T(0));
161 prevPhase_.assign(
static_cast<size_t>(numBins_), T(0));
162 prevPhase2_.assign(
static_cast<size_t>(numBins_), T(0));
163 prevMag_.assign(
static_cast<size_t>(numBins_), T(0));
164 whitenPeak_.assign(
static_cast<size_t>(numBins_), T(0));
172 bandCur_.assign(
static_cast<size_t>(numBands_), T(0));
173 bandPrev_.assign(
static_cast<size_t>(numBands_), T(0));
174 bandMaxPrev_.assign(
static_cast<size_t>(numBands_), T(0));
177 preMaxFrames_ = msToFrames(30.0);
178 postMaxFrames_ = msToFrames(30.0);
179 preAvgFrames_ = msToFrames(100.0);
180 postAvgFrames_ = msToFrames(70.0);
181 waitFrames_ = msToFrames(30.0);
185 odfHistLen_ = std::max(preAvgFrames_, preMaxFrames_) + 4;
186 odfHist_.assign(
static_cast<size_t>(odfHistLen_), T(0));
191 const int maxPending = (fftSize_ + hop_) / hop_ + 4;
192 pending_.assign(
static_cast<size_t>(std::max(8, maxPending)),
202 threshold_.store(kDefaultDelta, std::memory_order_relaxed);
203 whitening_.store(
false, std::memory_order_relaxed);
205 fft_ = std::make_unique<FFTReal<T>>(
static_cast<size_t>(fftSize_));
217 if (!std::isfinite(deltaAboveMean))
return;
218 threshold_.store(std::max(T(0), deltaAboveMean), std::memory_order_relaxed);
224 whitening_.store(on, std::memory_order_relaxed);
236 if (fft_ ==
nullptr || in.getNumChannels() < 1)
return;
237 const T* ch0 = in.getChannel(0);
238 pushSamples(std::span<const T>(ch0,
static_cast<size_t>(in.getNumSamples())));
250 if (fft_ ==
nullptr)
return;
252 bool firedThisCall =
false;
254 for (
const T s : samples)
256 const T x = std::isfinite(s) ? s : T(0);
258 ring_[
static_cast<size_t>(writePos_)] = x;
259 ring_[
static_cast<size_t>(writePos_ + fftSize_)] = x;
260 if (++writePos_ >= fftSize_) writePos_ = 0;
265 while (pendingCount_ > 0)
267 const PendingOnset& p = pending_[
static_cast<size_t>(pendingHead_)];
268 if (totalSamples_ - 1 >= p.reportSample)
270 lastOnsetSample_.store(p.referenceSample, std::memory_order_relaxed);
271 onsetStrength_.store(p.strength, std::memory_order_relaxed);
272 firedThisCall =
true;
273 pendingHead_ = (pendingHead_ + 1) %
static_cast<int>(pending_.size());
279 if (++hopCounter_ >= hop_)
286 onsetLatched_.store(firedThisCall, std::memory_order_relaxed);
294 return onsetLatched_.load(std::memory_order_relaxed);
300 return onsetStrength_.load(std::memory_order_relaxed);
309 return lastOnsetSample_.load(std::memory_order_relaxed);
315 return static_cast<int>(latencySamples_.load(std::memory_order_relaxed));
318 [[nodiscard]]
int getFftSize() const noexcept {
return fftSize_; }
319 [[nodiscard]]
int getHopSize() const noexcept {
return hop_; }
320 [[nodiscard]]
int getNumBands() const noexcept {
return numBands_; }
334 std::vector<int64_t> out;
342 std::vector<int64_t> odfRef;
343 odf.reserve(
static_cast<size_t>(n / std::max(1, hop_) + 2));
344 odfRef.reserve(odf.capacity());
347 const Method m = method_.load(std::memory_order_relaxed);
348 const bool whiten = whitening_.load(std::memory_order_relaxed);
352 for (
int i = 0; i < n; ++i)
354 const T v = std::isfinite(x[i]) ? x[i] : T(0);
355 ring_[
static_cast<size_t>(writePos_)] = v;
356 ring_[
static_cast<size_t>(writePos_ + fftSize_)] = v;
357 if (++writePos_ >= fftSize_) writePos_ = 0;
362 const T value = computeOdf(m, whiten);
363 odf.push_back(value);
364 odfRef.push_back(referenceSample(total));
369 const T delta = threshold_.load(std::memory_order_relaxed);
370 const int nf =
static_cast<int>(odf.size());
371 int64_t lastFrame = -kBig;
372 for (
int f = 0; f < nf; ++f)
374 if (f < primeFrames_)
continue;
376 for (
int j = f - preMaxFrames_; j <= f + postMaxFrames_; ++j)
378 if (j < 0 || j >= nf)
continue;
379 if (odf[
static_cast<size_t>(j)] > odf[
static_cast<size_t>(f)]) { isMax =
false;
break; }
381 if (!isMax)
continue;
383 T sum = T(0);
int cnt = 0;
384 for (
int j = f - preAvgFrames_; j <= f + postAvgFrames_; ++j)
386 if (j < 0 || j >= nf)
continue;
387 sum += odf[
static_cast<size_t>(j)]; ++cnt;
389 const T mean = (cnt > 0) ? sum /
static_cast<T
>(cnt) : T(0);
390 if (odf[
static_cast<size_t>(f)] < mean + delta)
continue;
391 if (f - lastFrame <= waitFrames_)
continue;
393 out.push_back(odfRef[
static_cast<size_t>(f)]);
402 void reset() noexcept { resetState(); }
406 static constexpr int kMinFft = 64;
407 static constexpr int kMaxFft = 1 << 16;
408 static constexpr int64_t kBig = int64_t(1) << 60;
409 static constexpr T kDefaultDelta = T(0.03);
410 static constexpr double kLocalizationLead = 0.34;
411 static constexpr double kFMin = 27.5;
412 static constexpr double kFMaxHz = 16000.0;
413 static constexpr int kBandsPerOctave = 24;
417 int64_t referenceSample = 0;
418 int64_t reportSample = 0;
426 [[nodiscard]] int64_t referenceSample(int64_t firePos)
const noexcept
428 return firePos -
static_cast<int64_t
>(fftSize_) / 2
429 +
static_cast<int64_t
>(localizationOffset_);
432 [[nodiscard]]
int msToFrames(
double ms)
const noexcept
434 return std::max(1,
static_cast<int>(std::lround(ms * 0.001 * sampleRate_
435 /
static_cast<double>(hop_))));
442 void computeSpectrum() noexcept
444 const T* w = window_.data();
445 const T* r = &ring_[
static_cast<size_t>(writePos_)];
446 for (
int k = 0; k < fftSize_; ++k)
447 fft_time_[
static_cast<size_t>(k)] = r[k] * w[k];
449 fft_->forward(fft_time_.data(), fft_spec_.data());
451 for (
int k = 0; k < numBins_; ++k)
453 const T re = fft_spec_[
static_cast<size_t>(2 * k)];
454 const T im = fft_spec_[
static_cast<size_t>(2 * k + 1)];
455 mag_[
static_cast<size_t>(k)] = std::sqrt(re * re + im * im);
456 phase_[
static_cast<size_t>(k)] = std::atan2(im, re);
461 void applyWhitening() noexcept
463 for (
int k = 0; k < numBins_; ++k)
465 T& pk = whitenPeak_[
static_cast<size_t>(k)];
466 const T decayed = pk * kWhitenDecay;
467 const T m = mag_[
static_cast<size_t>(k)];
468 pk = std::max({ m, kWhitenFloor, decayed });
469 mag_[
static_cast<size_t>(k)] = m / pk;
475 T computeOdf(
Method m,
bool whiten)
noexcept
478 if (whiten) applyWhitening();
485 for (
int k = 0; k < numBins_; ++k)
487 const T d = mag_[
static_cast<size_t>(k)] - prevMag_[
static_cast<size_t>(k)];
488 if (d > T(0)) odf += d;
490 odf /=
static_cast<T
>(numBins_);
497 for (
int k = 0; k < numBins_; ++k)
499 const T target = princArg(T(2) * prevPhase_[
static_cast<size_t>(k)]
500 - prevPhase2_[
static_cast<size_t>(k)]);
501 const T pm = prevMag_[
static_cast<size_t>(k)];
502 const T cm = mag_[
static_cast<size_t>(k)];
503 const T re = cm * std::cos(phase_[
static_cast<size_t>(k)])
504 - pm * std::cos(target);
505 const T im = cm * std::sin(phase_[
static_cast<size_t>(k)])
506 - pm * std::sin(target);
507 if (cm >= pm) odf += std::sqrt(re * re + im * im);
509 odf /=
static_cast<T
>(numBins_);
516 filterLogBands(bandCur_);
517 for (
int b = 0; b < numBands_; ++b)
519 const T d = bandCur_[
static_cast<size_t>(b)]
520 - bandMaxPrev_[
static_cast<size_t>(b)];
521 if (d > T(0)) odf += d;
523 odf /=
static_cast<T
>(numBands_);
526 bandPrev_ = bandCur_;
527 maxFilterFreq(bandPrev_, bandMaxPrev_);
534 rotatePhaseHistory();
535 std::copy(mag_.begin(), mag_.end(), prevMag_.begin());
541 void rotatePhaseHistory() noexcept
543 std::copy(prevPhase_.begin(), prevPhase_.end(), prevPhase2_.begin());
544 std::copy(phase_.begin(), phase_.end(), prevPhase_.begin());
548 void analyzeFrame() noexcept
550 const Method m = method_.load(std::memory_order_relaxed);
551 const bool whiten = whitening_.load(std::memory_order_relaxed);
552 const T value = computeOdf(m, whiten);
555 odfHist_[
static_cast<size_t>(odfWrite_)] = value;
556 odfWrite_ = (odfWrite_ + 1) % odfHistLen_;
562 if (frameIndex_ < 2) { lastConfirmOdf_ = value;
return; }
564 const T prev = odfAt(1);
565 const T curr = value;
568 bool isMax = (prev >= curr);
571 for (
int j = 2; j <= preMaxFrames_ + 1 && j < frameIndex_; ++j)
573 if (odfAt(j) > prev) { isMax =
false;
break; }
580 T sum = T(0);
int cnt = 0;
581 for (
int j = 1; j <= preAvgFrames_ && j < frameIndex_; ++j)
583 sum += odfAt(j); ++cnt;
585 const T mean = (cnt > 0) ? sum /
static_cast<T
>(cnt) : T(0);
586 const T delta = threshold_.load(std::memory_order_relaxed);
589 const int64_t candFrame = frameIndex_ - 1;
590 if (candFrame >= primeFrames_ && prev >= mean + delta
591 && candFrame - lastOnsetFrame_ > waitFrames_)
595 const int64_t candFirePos = totalSamples_ - hop_;
596 const int64_t ref = referenceSample(candFirePos);
597 scheduleOnset(ref, prev);
598 lastOnsetFrame_ = candFrame;
604 [[nodiscard]] T odfAt(
int j)
const noexcept
606 int idx = odfWrite_ - 1 - j;
608 if (idx < 0) idx += odfHistLen_;
609 return odfHist_[
static_cast<size_t>(idx)];
612 void scheduleOnset(int64_t referenceSample, T strength)
noexcept
614 if (pendingCount_ >=
static_cast<int>(pending_.size()))
return;
615 const int tail = (pendingHead_ + pendingCount_) %
static_cast<int>(pending_.size());
616 PendingOnset& p = pending_[
static_cast<size_t>(tail)];
617 p.referenceSample = referenceSample;
618 p.reportSample = referenceSample + latencySamples_.load(std::memory_order_relaxed);
619 p.strength = strength;
628 void buildFilterBank()
634 const double binHz = sampleRate_ /
static_cast<double>(fftSize_);
635 const double fMax = std::min(kFMaxHz, sampleRate_ * 0.5 * 0.999);
638 std::vector<int> centres;
639 for (
int i = 0; ; ++i)
641 const double f = kFMin * std::pow(2.0,
static_cast<double>(i)
642 /
static_cast<double>(kBandsPerOctave));
644 int bin =
static_cast<int>(std::lround(f / binHz));
645 bin = std::clamp(bin, 0, numBins_ - 1);
646 if (centres.empty() || bin > centres.back())
647 centres.push_back(bin);
652 for (
size_t j = 1; j + 1 < centres.size(); ++j)
654 const int lo = centres[j - 1];
655 const int ce = centres[j];
656 const int hi = centres[j + 1];
657 if (!(lo < ce && ce < hi))
continue;
659 fbStart_.push_back(lo);
660 fbOffset_.push_back(
static_cast<int>(fbWeights_.size()));
661 for (
int k = lo; k <= hi; ++k)
665 wv =
static_cast<T
>(
static_cast<double>(k - lo)
666 /
static_cast<double>(ce - lo));
668 wv =
static_cast<T
>(
static_cast<double>(hi - k)
669 /
static_cast<double>(hi - ce));
670 fbWeights_.push_back(wv);
675 for (
int b = 0; b < numBands_; ++b)
677 const int off = fbOffset_[
static_cast<size_t>(b)];
678 const int nextOff = (b + 1 < numBands_)
679 ? fbOffset_[
static_cast<size_t>(b + 1)]
680 :
static_cast<int>(fbWeights_.size());
681 fbCount_.push_back(nextOff - off);
683 if (numBands_ < 1) numBands_ = 1;
687 void filterLogBands(std::vector<T>& out)
noexcept
689 for (
int b = 0; b < numBands_ && b < static_cast<int>(fbStart_.size()); ++b)
691 const int start = fbStart_[
static_cast<size_t>(b)];
692 const int off = fbOffset_[
static_cast<size_t>(b)];
693 const int cnt = fbCount_[
static_cast<size_t>(b)];
695 for (
int i = 0; i < cnt; ++i)
697 const int k = start + i;
698 if (k >= 0 && k < numBins_)
699 acc += mag_[
static_cast<size_t>(k)]
700 * fbWeights_[
static_cast<size_t>(off + i)];
702 out[
static_cast<size_t>(b)] = std::log10(acc + T(1));
707 void maxFilterFreq(
const std::vector<T>& in, std::vector<T>& out)
const noexcept
709 for (
int b = 0; b < numBands_; ++b)
711 T mx = in[
static_cast<size_t>(b)];
712 if (b > 0) mx = std::max(mx, in[
static_cast<size_t>(b - 1)]);
713 if (b + 1 < numBands_) mx = std::max(mx, in[
static_cast<size_t>(b + 1)]);
714 out[
static_cast<size_t>(b)] = mx;
718 static T princArg(T x)
noexcept
721 const T twoPiT = twoPi<T>;
722 T y = x - twoPiT * std::floor(x / twoPiT + T(0.5));
726 void resetState() noexcept
728 std::fill(ring_.begin(), ring_.end(), T(0));
729 std::fill(prevMag_.begin(), prevMag_.end(), T(0));
730 std::fill(prevPhase_.begin(), prevPhase_.end(), T(0));
731 std::fill(prevPhase2_.begin(), prevPhase2_.end(), T(0));
732 std::fill(whitenPeak_.begin(), whitenPeak_.end(), T(0));
733 std::fill(bandPrev_.begin(), bandPrev_.end(), T(0));
734 std::fill(bandMaxPrev_.begin(), bandMaxPrev_.end(), T(0));
735 std::fill(odfHist_.begin(), odfHist_.end(), T(0));
742 lastConfirmOdf_ = T(0);
743 lastOnsetFrame_ = -kBig;
747 onsetLatched_.store(
false, std::memory_order_relaxed);
748 onsetStrength_.store(T(0), std::memory_order_relaxed);
749 lastOnsetSample_.store(-1, std::memory_order_relaxed);
753 static constexpr T kWhitenDecay = T(0.9995);
754 static constexpr T kWhitenFloor = T(1e-4);
757 double sampleRate_ = 44100.0;
763 int localizationOffset_ = 0;
764 int primeFrames_ = 12;
766 std::unique_ptr<FFTReal<T>> fft_;
768 std::vector<T> window_;
769 std::vector<T> ring_;
770 std::vector<T> fft_time_;
771 std::vector<T> fft_spec_;
773 std::vector<T> phase_;
774 std::vector<T> prevPhase_, prevPhase2_, prevMag_, whitenPeak_;
777 std::vector<int> fbStart_;
778 std::vector<T> fbWeights_;
779 std::vector<int> fbOffset_;
780 std::vector<int> fbCount_;
782 std::vector<T> bandCur_, bandPrev_, bandMaxPrev_;
785 int preMaxFrames_ = 6, postMaxFrames_ = 6;
786 int preAvgFrames_ = 20, postAvgFrames_ = 14;
790 std::vector<T> odfHist_;
791 int odfHistLen_ = 32;
793 int64_t frameIndex_ = 0;
794 T lastConfirmOdf_ = T(0);
795 int64_t lastOnsetFrame_ = -kBig;
800 int64_t totalSamples_ = 0;
803 std::vector<PendingOnset> pending_;
804 int pendingHead_ = 0;
805 int pendingCount_ = 0;
809 std::atomic<T> threshold_ { kDefaultDelta };
810 std::atomic<bool> whitening_ {
false };
811 std::atomic<bool> onsetLatched_ {
false };
812 std::atomic<T> onsetStrength_ { T(0) };
813 std::atomic<int64_t> lastOnsetSample_ { -1 };
814 std::atomic<int64_t> latencySamples_ { 2269 };