70 void prepare(
double sampleRate,
int numChannels = 2) noexcept
73 if (!std::isfinite(sampleRate) || sampleRate <= 0.0)
return;
75 sampleRate_ = sampleRate;
77 computeKWeighting(sampleRate);
81 blockSamples_ =
static_cast<int>(std::clamp(sampleRate * 0.1, 1.0, 1.0e9));
84 prepared_.store(
true, std::memory_order_relaxed);
90 prepare(spec.sampleRate, spec.numChannels);
103 const int nCh = buffer.getNumChannels();
104 const int nS = buffer.getNumSamples();
106 process(buffer.getChannel(0), buffer.getChannel(1), nS);
108 process(buffer.getChannel(0), nS);
116 void process(
const T* data,
int numSamples)
noexcept
118 if (!prepared_.load(std::memory_order_relaxed) || data ==
nullptr || numSamples <= 0)
121 T tpMax = truePeakMax_.load(std::memory_order_relaxed);
122 for (
int i = 0; i < numSamples; ++i)
131 if (std::isfinite(tp0)) tpMax = std::max(tpMax, tp0);
133 double filtered = applyKWeighting(
static_cast<double>(data[i]), 0);
134 currentBlockPower_ += filtered * filtered;
136 if (++currentBlockSamples_ >= blockSamples_)
139 truePeakMax_.store(tpMax, std::memory_order_relaxed);
148 void process(
const T* left,
const T* right,
int numSamples)
noexcept
150 if (!prepared_.load(std::memory_order_relaxed)
151 || left ==
nullptr || right ==
nullptr || numSamples <= 0)
154 T tpMax = truePeakMax_.load(std::memory_order_relaxed);
155 for (
int i = 0; i < numSamples; ++i)
161 if (std::isfinite(tpL)) tpMax = std::max(tpMax, tpL);
162 if (std::isfinite(tpR)) tpMax = std::max(tpMax, tpR);
164 double filtL = applyKWeighting(
static_cast<double>(left[i]), 0);
165 double filtR = applyKWeighting(
static_cast<double>(right[i]), 1);
167 currentBlockPower_ += (filtL * filtL + filtR * filtR);
169 if (++currentBlockSamples_ >= blockSamples_)
172 truePeakMax_.store(tpMax, std::memory_order_relaxed);
181 return calculateLUFSFromBlocks(4);
190 return calculateLUFSFromBlocks(30);
202 double sumPowerUngated = 0.0;
203 uint32_t countUngated = 0;
205 for (
int i = 0; i < kNumBins; ++i)
207 uint32_t binCount = histogram_[i].load(std::memory_order_relaxed);
210 double binPower = lufsToPower(kMinHistogramLUFS +
static_cast<double>(i) * kBinWidth);
211 sumPowerUngated += binPower * binCount;
212 countUngated += binCount;
216 if (countUngated == 0)
return T(-100);
218 double meanPowerUngated = sumPowerUngated / countUngated;
219 double ungatedLUFS = powerToLUFS(meanPowerUngated);
222 double relativeGateLUFS = ungatedLUFS - 10.0;
223 int relativeGateBin =
static_cast<int>(std::floor((relativeGateLUFS - kMinHistogramLUFS) / kBinWidth));
224 relativeGateBin = std::clamp(relativeGateBin, 0, kNumBins - 1);
226 double sumPowerGated = 0.0;
227 uint32_t countGated = 0;
229 for (
int i = relativeGateBin; i < kNumBins; ++i)
231 uint32_t binCount = histogram_[i].load(std::memory_order_relaxed);
234 double binPower = lufsToPower(kMinHistogramLUFS +
static_cast<double>(i) * kBinWidth);
235 sumPowerGated += binPower * binCount;
236 countGated += binCount;
240 if (countGated == 0)
return T(-100);
242 return static_cast<T
>(powerToLUFS(sumPowerGated / countGated));
251 return gainToDecibels(truePeakMax_.load(std::memory_order_relaxed));
266 double sumPower = 0.0;
268 for (
int i = 0; i < kNumBins; ++i)
270 const uint32_t c = lraHistogram_[i].load(std::memory_order_relaxed);
273 sumPower += lufsToPower(kMinHistogramLUFS + i * kBinWidth) * c;
277 if (count == 0)
return T(0);
279 const double relGateLUFS = powerToLUFS(sumPower / count) - 20.0;
280 int gateBin =
static_cast<int>(std::floor((relGateLUFS - kMinHistogramLUFS) / kBinWidth));
281 gateBin = std::clamp(gateBin, 0, kNumBins - 1);
284 uint32_t gatedCount = 0;
285 for (
int i = gateBin; i < kNumBins; ++i)
286 gatedCount += lraHistogram_[i].load(std::memory_order_relaxed);
287 if (gatedCount == 0)
return T(0);
289 const auto target10 =
static_cast<uint32_t
>(0.10 * gatedCount);
290 const auto target95 =
static_cast<uint32_t
>(0.95 * gatedCount);
292 double p10 = kMinHistogramLUFS, p95 = kMaxHistogramLUFS;
293 uint32_t running = 0;
295 for (
int i = gateBin; i < kNumBins; ++i)
297 running += lraHistogram_[i].load(std::memory_order_relaxed);
298 if (!have10 && running >= target10)
300 p10 = kMinHistogramLUFS + i * kBinWidth;
303 if (running >= target95)
305 p95 = kMinHistogramLUFS + i * kBinWidth;
309 return static_cast<T
>(std::max(0.0, p95 - p10));
321 for (
int ch = 0; ch < kMaxChannels; ++ch)
327 for (
auto& power : blockPowers_)
328 power.store(0.0, std::memory_order_relaxed);
330 for (
auto& bin : histogram_)
331 bin.store(0, std::memory_order_relaxed);
332 for (
auto& bin : lraHistogram_)
333 bin.store(0, std::memory_order_relaxed);
336 truePeakMax_.store(T(0), std::memory_order_relaxed);
338 blockWritePos_.store(0, std::memory_order_relaxed);
339 totalCommittedBlocks_ = 0;
340 currentBlockPower_ = 0.0;
341 currentBlockSamples_ = 0;
345 static constexpr int kMaxChannels = 2;
348 static constexpr double kMinHistogramLUFS = -70.0;
349 static constexpr double kMaxHistogramLUFS = 30.0;
350 static constexpr double kBinWidth = 0.1;
351 static constexpr int kNumBins = 1000;
354 struct BiquadState {
double z1 = 0.0, z2 = 0.0; };
355 struct BiquadCoeff {
double b0 = 1.0, b1 = 0.0, b2 = 0.0, a1 = 0.0, a2 = 0.0; };
357 void computeKWeighting(
double sr)
367 const double G = 3.999843853973347;
368 const double Q = 0.7071752369554196;
369 const double fc = 1681.9744509555319;
370 const double K = std::tan(std::numbers::pi * fc / sr);
371 const double Vh = std::pow(10.0, G / 20.0);
372 const double Vb = std::pow(Vh, 0.4996667741545416);
373 const double a0 = 1.0 + K / Q + K * K;
374 pre_.b0 = (Vh + Vb * K / Q + K * K) / a0;
375 pre_.b1 = 2.0 * (K * K - Vh) / a0;
376 pre_.b2 = (Vh - Vb * K / Q + K * K) / a0;
377 pre_.a1 = 2.0 * (K * K - 1.0) / a0;
378 pre_.a2 = (1.0 - K / Q + K * K) / a0;
381 const double Q = 0.5003270373238773;
382 const double fc = 38.13547087602444;
383 const double K = std::tan(std::numbers::pi * fc / sr);
384 const double a0 = 1.0 + K / Q + K * K;
390 rlb_.a1 = 2.0 * (K * K - 1.0) / a0;
391 rlb_.a2 = (1.0 - K / Q + K * K) / a0;
395 double applyBiquad(
double input,
const BiquadCoeff& c, BiquadState& s)
noexcept
398 double output = c.b0 * input + s.z1;
399 s.z1 = c.b1 * input - c.a1 * output + s.z2;
400 s.z2 = c.b2 * input - c.a2 * output + 1e-18;
404 double applyKWeighting(
double input,
int channel)
noexcept
406 double x = applyBiquad(input, pre_, preState_[channel]);
407 return applyBiquad(x, rlb_, rlbState_[channel]);
410 void commitBlock() noexcept
412 const double meanPower = currentBlockPower_ / currentBlockSamples_;
413 currentBlockPower_ = 0.0;
414 currentBlockSamples_ = 0;
421 if (!std::isfinite(meanPower))
423 for (
int ch = 0; ch < kMaxChannels; ++ch)
432 int currentPos = blockWritePos_.load(std::memory_order_relaxed);
433 blockPowers_[currentPos].store(meanPower, std::memory_order_relaxed);
435 int nextPos = (currentPos + 1) % 30;
436 blockWritePos_.store(nextPos, std::memory_order_release);
437 ++totalCommittedBlocks_;
443 if (totalCommittedBlocks_ >= 4)
445 double gating400 = meanPower;
446 for (
int back = 1; back < 4; ++back)
448 int idx = (currentPos - back + 30) % 30;
449 gating400 += blockPowers_[idx].load(std::memory_order_relaxed);
453 const double lufs = powerToLUFS(gating400);
454 if (lufs >= kMinHistogramLUFS)
456 int binIndex =
static_cast<int>(std::round((lufs - kMinHistogramLUFS) / kBinWidth));
457 binIndex = std::clamp(binIndex, 0, kNumBins - 1);
458 histogram_[binIndex].fetch_add(1, std::memory_order_relaxed);
464 if (totalCommittedBlocks_ >= 30 && (totalCommittedBlocks_ % 10) == 0)
466 const double stLufs =
static_cast<double>(calculateLUFSFromBlocks(30));
467 if (stLufs >= kMinHistogramLUFS)
469 int binIndex =
static_cast<int>(std::round((stLufs - kMinHistogramLUFS) / kBinWidth));
470 binIndex = std::clamp(binIndex, 0, kNumBins - 1);
471 lraHistogram_[binIndex].fetch_add(1, std::memory_order_relaxed);
476 T calculateLUFSFromBlocks(
int numBlocks)
const noexcept
479 int currentPos = blockWritePos_.load(std::memory_order_acquire);
481 for (
int i = 0; i < numBlocks; ++i)
483 int idx = (currentPos - 1 - i + 30) % 30;
484 sum += blockPowers_[idx].load(std::memory_order_relaxed);
487 return static_cast<T
>(powerToLUFS(sum / numBlocks));
490 [[nodiscard]]
static double powerToLUFS(
double meanPower)
noexcept
492 if (meanPower <= 1e-10)
return -100.0;
493 return -0.691 + 10.0 * std::log10(meanPower);
496 [[nodiscard]]
static double lufsToPower(
double lufs)
noexcept
498 return std::pow(10.0, (lufs + 0.691) / 10.0);
501 double sampleRate_ = 48000.0;
502 std::atomic<bool> prepared_{
false };
504 BiquadCoeff pre_, rlb_;
505 BiquadState preState_[kMaxChannels], rlbState_[kMaxChannels];
507 int blockSamples_ = 4800;
508 double currentBlockPower_ = 0.0;
509 int currentBlockSamples_ = 0;
512 std::array<std::atomic<double>, 30> blockPowers_;
513 std::atomic<int> blockWritePos_{0};
514 int64_t totalCommittedBlocks_ = 0;
517 std::array<std::atomic<uint32_t>, kNumBins> histogram_;
520 std::array<std::atomic<uint32_t>, kNumBins> lraHistogram_;
523 TruePeakDetector<T, kMaxChannels> truePeak_;
524 std::atomic<T> truePeakMax_ { T(0) };