86 if (!prepared_.load(std::memory_order_relaxed))
return info;
90 for (
int i = 0; i < kNumBins; ++i)
93 info.
rangeBlocks += lraHistogram_[i].load(std::memory_order_relaxed);
109 void prepare(
double sampleRate,
int numChannels = 2) noexcept
112 if (!std::isfinite(sampleRate) || sampleRate <= 0.0)
return;
114 sampleRate_ = sampleRate;
116 computeKWeighting(sampleRate);
120 blockSamples_ =
static_cast<int>(std::clamp(sampleRate * 0.1, 1.0, 1.0e9));
123 prepared_.store(
true, std::memory_order_relaxed);
129 prepare(spec.sampleRate, spec.numChannels);
142 const int nCh = buffer.getNumChannels();
143 const int nS = buffer.getNumSamples();
145 process(buffer.getChannel(0), buffer.getChannel(1), nS);
147 process(buffer.getChannel(0), nS);
155 void process(
const T* data,
int numSamples)
noexcept
157 if (!prepared_.load(std::memory_order_relaxed) || data ==
nullptr || numSamples <= 0)
160 T tpMax = truePeakMax_.load(std::memory_order_relaxed);
161 for (
int i = 0; i < numSamples; ++i)
170 if (std::isfinite(tp0))
171 tpMax = std::max(tpMax, tp0);
173 invalidateMeasurement();
175 double filtered = applyKWeighting(
static_cast<double>(data[i]), 0);
176 currentBlockPower_ += filtered * filtered;
177 if (!std::isfinite(currentBlockPower_))
178 invalidateMeasurement();
180 if (++currentBlockSamples_ >= blockSamples_)
183 truePeakMax_.store(tpMax, std::memory_order_relaxed);
192 void process(
const T* left,
const T* right,
int numSamples)
noexcept
194 if (!prepared_.load(std::memory_order_relaxed)
195 || left ==
nullptr || right ==
nullptr || numSamples <= 0)
198 T tpMax = truePeakMax_.load(std::memory_order_relaxed);
199 for (
int i = 0; i < numSamples; ++i)
205 if (std::isfinite(tpL))
206 tpMax = std::max(tpMax, tpL);
208 invalidateMeasurement();
209 if (std::isfinite(tpR))
210 tpMax = std::max(tpMax, tpR);
212 invalidateMeasurement();
214 double filtL = applyKWeighting(
static_cast<double>(left[i]), 0);
215 double filtR = applyKWeighting(
static_cast<double>(right[i]), 1);
217 currentBlockPower_ += (filtL * filtL + filtR * filtR);
218 if (!std::isfinite(currentBlockPower_))
219 invalidateMeasurement();
221 if (++currentBlockSamples_ >= blockSamples_)
224 truePeakMax_.store(tpMax, std::memory_order_relaxed);
233 return calculateLUFSFromBlocks(4);
242 return calculateLUFSFromBlocks(30);
254 double sumPowerUngated = 0.0;
255 uint64_t countUngated = 0;
257 for (
int i = 0; i < kNumBins; ++i)
259 uint32_t binCount = histogram_[i].load(std::memory_order_relaxed);
262 double binPower = lufsToPower(kMinHistogramLUFS +
static_cast<double>(i) * kBinWidth);
263 sumPowerUngated += binPower * binCount;
264 countUngated += binCount;
268 if (countUngated == 0)
return T(-100);
270 double meanPowerUngated = sumPowerUngated / countUngated;
271 double ungatedLUFS = powerToLUFS(meanPowerUngated);
274 double relativeGateLUFS = ungatedLUFS - 10.0;
275 int relativeGateBin =
static_cast<int>(std::floor((relativeGateLUFS - kMinHistogramLUFS) / kBinWidth));
276 relativeGateBin = std::clamp(relativeGateBin, 0, kNumBins - 1);
278 double sumPowerGated = 0.0;
279 uint64_t countGated = 0;
281 for (
int i = relativeGateBin; i < kNumBins; ++i)
283 uint32_t binCount = histogram_[i].load(std::memory_order_relaxed);
286 double binPower = lufsToPower(kMinHistogramLUFS +
static_cast<double>(i) * kBinWidth);
287 sumPowerGated += binPower * binCount;
288 countGated += binCount;
292 if (countGated == 0)
return T(-100);
294 return static_cast<T
>(powerToLUFS(sumPowerGated / countGated));
308 return measurementValid_.load(std::memory_order_relaxed);
317 return gainToDecibels(truePeakMax_.load(std::memory_order_relaxed));
331 if (!prepared_.load(std::memory_order_relaxed))
return;
332 T peak = truePeakMax_.load(std::memory_order_relaxed);
333 for (
int channel = 0; channel < kMaxChannels; ++channel)
336 if (std::isfinite(tail))
337 peak = std::max(peak, tail);
339 invalidateMeasurement();
341 truePeakMax_.store(peak, std::memory_order_relaxed);
363 double sumPower = 0.0;
365 for (
int i = 0; i < kNumBins; ++i)
367 const uint32_t c = lraHistogram_[i].load(std::memory_order_relaxed);
370 sumPower += lufsToPower(kMinHistogramLUFS + i * kBinWidth) * c;
374 if (count == 0)
return T(0);
376 const double relGateLUFS = powerToLUFS(sumPower / count) - 20.0;
377 int gateBin =
static_cast<int>(std::floor((relGateLUFS - kMinHistogramLUFS) / kBinWidth));
378 gateBin = std::clamp(gateBin, 0, kNumBins - 1);
381 uint64_t gatedCount = 0;
382 for (
int i = gateBin; i < kNumBins; ++i)
383 gatedCount += lraHistogram_[i].load(std::memory_order_relaxed);
384 if (gatedCount == 0)
return T(0);
390 const auto rank10 =
static_cast<uint64_t
>(std::llround(0.10 *
static_cast<double>(gatedCount - 1)));
391 const auto rank95 =
static_cast<uint64_t
>(std::llround(0.95 *
static_cast<double>(gatedCount - 1)));
393 double p10 = 0.0, p95 = 0.0;
394 uint64_t running = 0;
396 for (
int i = gateBin; i < kNumBins; ++i)
398 const uint32_t c = lraHistogram_[i].load(std::memory_order_relaxed);
399 if (c == 0)
continue;
402 if (!have10 && running > rank10)
404 p10 = kMinHistogramLUFS + i * kBinWidth;
407 if (running > rank95)
409 p95 = kMinHistogramLUFS + i * kBinWidth;
413 return static_cast<T
>(std::max(0.0, p95 - p10));
425 for (
int ch = 0; ch < kMaxChannels; ++ch)
431 for (
auto& power : blockPowers_)
432 power.store(0.0, std::memory_order_relaxed);
434 for (
auto& bin : histogram_)
435 bin.store(0, std::memory_order_relaxed);
436 for (
auto& bin : lraHistogram_)
437 bin.store(0, std::memory_order_relaxed);
440 truePeakMax_.store(T(0), std::memory_order_relaxed);
442 blockWritePos_.store(0, std::memory_order_relaxed);
443 totalCommittedBlocks_ = 0;
444 currentBlockPower_ = 0.0;
445 currentBlockSamples_ = 0;
446 measurementValid_.store(
true, std::memory_order_relaxed);
450 static constexpr int kMaxChannels = 2;
453 static constexpr double kMinHistogramLUFS = -70.0;
454 static constexpr double kMaxHistogramLUFS = 30.0;
455 static constexpr double kBinWidth = 0.1;
456 static constexpr int kNumBins = 1000;
459 struct BiquadState {
double z1 = 0.0, z2 = 0.0; };
460 struct BiquadCoeff {
double b0 = 1.0, b1 = 0.0, b2 = 0.0, a1 = 0.0, a2 = 0.0; };
462 void computeKWeighting(
double sr)
476 pre_ = { shelf.b0, shelf.b1, shelf.b2, shelf.a1, shelf.a2 };
478 rlb_ = { rlb.b0, rlb.b1, rlb.b2, rlb.a1, rlb.a2 };
481 double applyBiquad(
double input,
const BiquadCoeff& c, BiquadState& s)
noexcept
484 double output = c.b0 * input + s.z1;
485 s.z1 = c.b1 * input - c.a1 * output + s.z2;
486 s.z2 = c.b2 * input - c.a2 * output + 1e-18;
490 double applyKWeighting(
double input,
int channel)
noexcept
492 double x = applyBiquad(input, pre_, preState_[channel]);
493 return applyBiquad(x, rlb_, rlbState_[channel]);
496 void commitBlock() noexcept
498 const double meanPower = currentBlockPower_ / currentBlockSamples_;
499 currentBlockPower_ = 0.0;
500 currentBlockSamples_ = 0;
507 if (!std::isfinite(meanPower))
509 invalidateMeasurement();
510 for (
int ch = 0; ch < kMaxChannels; ++ch)
519 int currentPos = blockWritePos_.load(std::memory_order_relaxed);
520 blockPowers_[currentPos].store(meanPower, std::memory_order_relaxed);
522 int nextPos = (currentPos + 1) % 30;
523 blockWritePos_.store(nextPos, std::memory_order_release);
524 ++totalCommittedBlocks_;
530 if (totalCommittedBlocks_ >= 4)
532 double gating400 = meanPower;
533 for (
int back = 1; back < 4; ++back)
535 int idx = (currentPos - back + 30) % 30;
536 gating400 += blockPowers_[idx].load(std::memory_order_relaxed);
540 const double lufs = powerToLUFS(gating400);
541 if (!std::isfinite(gating400) || !std::isfinite(lufs))
543 invalidateMeasurement();
545 else if (lufs >= kMinHistogramLUFS)
547 int binIndex =
static_cast<int>(std::round((lufs - kMinHistogramLUFS) / kBinWidth));
548 if (binIndex >= kNumBins)
549 invalidateMeasurement();
550 binIndex = std::clamp(binIndex, 0, kNumBins - 1);
551 incrementHistogram(histogram_[binIndex]);
559 if (totalCommittedBlocks_ >= 30)
561 const double stLufs =
static_cast<double>(calculateLUFSFromBlocks(30));
562 if (!std::isfinite(stLufs))
564 invalidateMeasurement();
566 else if (stLufs >= kMinHistogramLUFS)
568 int binIndex =
static_cast<int>(std::round((stLufs - kMinHistogramLUFS) / kBinWidth));
569 if (binIndex >= kNumBins)
570 invalidateMeasurement();
571 binIndex = std::clamp(binIndex, 0, kNumBins - 1);
572 incrementHistogram(lraHistogram_[binIndex]);
577 void invalidateMeasurement() noexcept
579 measurementValid_.store(
false, std::memory_order_relaxed);
582 void incrementHistogram(std::atomic<uint32_t>& bin)
noexcept
584 if (bin.load(std::memory_order_relaxed)
585 == std::numeric_limits<uint32_t>::max())
587 invalidateMeasurement();
590 bin.fetch_add(1, std::memory_order_relaxed);
593 T calculateLUFSFromBlocks(
int numBlocks)
const noexcept
596 int currentPos = blockWritePos_.load(std::memory_order_acquire);
598 for (
int i = 0; i < numBlocks; ++i)
600 int idx = (currentPos - 1 - i + 30) % 30;
601 sum += blockPowers_[idx].load(std::memory_order_relaxed);
604 return static_cast<T
>(powerToLUFS(sum / numBlocks));
607 [[nodiscard]]
static double powerToLUFS(
double meanPower)
noexcept
609 if (meanPower <= 1e-10)
return -100.0;
610 return std::max(-100.0, -0.691 + 10.0 * std::log10(meanPower));
613 [[nodiscard]]
static double lufsToPower(
double lufs)
noexcept
615 return std::pow(10.0, (lufs + 0.691) / 10.0);
618 double sampleRate_ = 48000.0;
619 std::atomic<bool> prepared_{
false };
621 BiquadCoeff pre_, rlb_;
622 BiquadState preState_[kMaxChannels], rlbState_[kMaxChannels];
624 int blockSamples_ = 4800;
625 double currentBlockPower_ = 0.0;
626 int currentBlockSamples_ = 0;
629 std::array<std::atomic<double>, 30> blockPowers_;
630 std::atomic<int> blockWritePos_{0};
631 int64_t totalCommittedBlocks_ = 0;
634 std::array<std::atomic<uint32_t>, kNumBins> histogram_;
637 std::array<std::atomic<uint32_t>, kNumBins> lraHistogram_;
640 std::atomic<bool> measurementValid_ {
true };
643 TruePeakDetector<T, kMaxChannels> truePeak_;
644 std::atomic<T> truePeakMax_ { T(0) };