107 detector_.prepare(spec.
sampleRate, windowSize,
108 (windowSize > 0) ? windowSize / 4 : 0);
109 monoScratch_.assign(
static_cast<size_t>(std::max(spec.
maxBlockSize, 1)), T(0));
110 prepared_.store(
true, std::memory_order_relaxed);
126 samplesSinceValid_ = std::numeric_limits<int64_t>::max() / 2;
127 smoothedHz_.store(T(0), std::memory_order_relaxed);
128 tracking_.store(
false, std::memory_order_relaxed);
140 if (!std::isfinite(minHz) || !std::isfinite(maxHz))
return;
141 minHz = std::max(minHz, T(10));
142 maxHz = std::max(maxHz, minHz);
143 minHz_.store(minHz, std::memory_order_relaxed);
144 maxHz_.store(maxHz, std::memory_order_relaxed);
150 if (!std::isfinite(threshold))
return;
151 confidence_.store(std::clamp(threshold, T(0), T(1)), std::memory_order_relaxed);
161 if (!std::isfinite(msPerOctave))
return;
162 glideMs_.store(std::max(msPerOctave, T(0)), std::memory_order_relaxed);
166 [[nodiscard]] T
getMinHz() const noexcept {
return minHz_.load(std::memory_order_relaxed); }
169 [[nodiscard]] T
getMaxHz() const noexcept {
return maxHz_.load(std::memory_order_relaxed); }
175 [[nodiscard]] T
getGlide() const noexcept {
return glideMs_.load(std::memory_order_relaxed); }
181 [[nodiscard]]
int getWindowSize() const noexcept {
return detector_.getWindowSize(); }
188 return smoothedHz_.load(std::memory_order_relaxed);
192 [[nodiscard]] T
getRawHz() const noexcept {
return detector_.getFrequencyHz(); }
195 [[nodiscard]] T
getConfidence() const noexcept {
return detector_.getConfidence(); }
200 return tracking_.load(std::memory_order_relaxed);
211 if (!prepared_.load(std::memory_order_relaxed))
return;
212 const int nCh = buffer.getNumChannels();
213 const int nS = buffer.getNumSamples();
214 if (nCh <= 0 || nS <= 0)
return;
219 const int chunk = std::min(nS - i,
static_cast<int>(monoScratch_.size()));
222 const T* src = buffer.getChannel(0) + i;
223 std::copy(src, src + chunk, monoScratch_.begin());
227 const T invCh = T(1) /
static_cast<T
>(nCh);
228 std::fill(monoScratch_.begin(), monoScratch_.begin() + chunk, T(0));
229 for (
int ch = 0; ch < nCh; ++ch)
231 const T* src = buffer.getChannel(ch) + i;
232 for (
int k = 0; k < chunk; ++k)
233 monoScratch_[
static_cast<size_t>(k)] += src[k] * invCh;
236 pushSamples({ monoScratch_.data(),
static_cast<size_t>(chunk) });
247 if (!prepared_.load(std::memory_order_relaxed) || samples.empty())
return;
248 detector_.pushSamples(samples);
249 update(
static_cast<int>(samples.size()));
254 void update(
int numSamples)
noexcept
256 const T raw = detector_.getFrequencyHz();
257 const T conf = detector_.getConfidence();
258 const bool valid = conf >= confidence_.load(std::memory_order_relaxed)
259 && raw >= minHz_.load(std::memory_order_relaxed)
260 && raw <= maxHz_.load(std::memory_order_relaxed);
264 double st = 12.0 * std::log2(
static_cast<double>(raw) / 440.0);
268 targetSt_ = currentSt_ = st;
273 double dev = st - targetSt_;
274 if (std::abs(dev) > 7.0)
278 for (
const double oct : { -24.0, -12.0, 12.0, 24.0 })
280 if (std::abs(st + oct - targetSt_) < 1.5)
283 dev = st - targetSt_;
289 if (std::abs(dev) > 7.0)
292 if (pendingCount_ > 0 && std::abs(st - pendingSt_) < 1.0)
294 if (++pendingCount_ >= 3)
312 samplesSinceValid_ = 0;
316 samplesSinceValid_ += numSamples;
321 const double glideMs =
static_cast<double>(glideMs_.load(std::memory_order_relaxed));
324 currentSt_ = targetSt_;
329 const double maxStep = 12.0 *
static_cast<double>(numSamples)
330 / (glideMs * 0.001 * sampleRate_);
331 currentSt_ += std::clamp(targetSt_ - currentSt_, -maxStep, maxStep);
333 smoothedHz_.store(
static_cast<T
>(440.0 * std::exp2(currentSt_ / 12.0)),
334 std::memory_order_relaxed);
337 tracking_.store(hasTarget_
338 && samplesSinceValid_ <
static_cast<int64_t
>(0.25 * sampleRate_),
339 std::memory_order_relaxed);
343 double sampleRate_ = 48000.0;
344 std::atomic<bool> prepared_{
false };
346 PitchDetector<T> detector_;
347 std::vector<T> monoScratch_;
349 bool hasTarget_ =
false;
350 double targetSt_ = 0.0;
351 double currentSt_ = 0.0;
352 double pendingSt_ = 0.0;
353 int pendingCount_ = 0;
354 int64_t samplesSinceValid_ = 0;
356 std::atomic<T> minHz_ { T(60) };
357 std::atomic<T> maxHz_ { T(1200) };
358 std::atomic<T> confidence_ { T(0.85) };
359 std::atomic<T> glideMs_ { T(60) };
361 std::atomic<T> smoothedHz_ { T(0) };
362 std::atomic<bool> tracking_ {
false };