DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
TruePeakDetector.h
1// DSPark -- Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi -- MIT License
3
4#pragma once
5
23#include "DspMath.h"
24#include "SimdOps.h"
25
26#include <algorithm>
27#include <array>
28#include <cassert>
29#include <cmath>
30
31namespace dspark {
32
46template <FloatType T, int MaxChannels = 16>
48{
49public:
51 void reset() noexcept
52 {
53 for (auto& s : states_)
54 s = {};
55 }
56
68 [[nodiscard]] T processSample(T sample, int channel) noexcept
69 {
70 assert(channel >= 0 && channel < MaxChannels);
71 // Release-safe clamp to the nearest valid channel. (Redirecting an
72 // out-of-range index to channel 0 would corrupt that channel's
73 // history with a foreign stream.)
74 channel = std::clamp(channel, 0, MaxChannels - 1);
75 return processState(sample, states_[static_cast<size_t>(channel)]);
76 }
77
84 [[nodiscard]] T getTailPeak(int channel) const noexcept
85 {
86 assert(channel >= 0 && channel < MaxChannels);
87 channel = std::clamp(channel, 0, MaxChannels - 1);
88 auto state = states_[static_cast<size_t>(channel)];
89 T peak = T(0);
90 for (int i = 0; i < kTaps - 1; ++i)
91 peak = std::max(peak, processState(T(0), state));
92 return peak;
93 }
94
96 [[nodiscard]] static constexpr int getLatency() noexcept { return kTaps / 2; }
97
106 [[nodiscard]] static constexpr int getTaps() noexcept { return kTaps; }
107
108private:
109 static constexpr int kTaps = 12;
110 static constexpr int kPhases = 4;
111 static constexpr int kHistSize = 16;
112 static constexpr int kHistMask = kHistSize - 1;
113
114 struct State
115 {
118 T history[kHistSize * 2] = {};
119 int writePos = 0;
120 };
121
122 [[nodiscard]] static T processState(T sample, State& tp) noexcept
123 {
124 // Mirroring makes the latest FIR window contiguous for the shared dot product.
125 tp.history[static_cast<size_t>(tp.writePos)] = sample;
126 tp.history[static_cast<size_t>(tp.writePos + kHistSize)] = sample;
127 const int newest = tp.writePos;
128 tp.writePos = (tp.writePos + 1) & kHistMask;
129 const T* window = &tp.history[static_cast<size_t>(newest + kHistSize - (kTaps - 1))];
130 T peak = std::abs(sample);
131 for (int phase = 0; phase < kPhases; ++phase)
132 {
133 const T interp = simd::dotProduct(
134 kReversedCoeffs[static_cast<size_t>(phase)].data(), window, kTaps);
135 const T a = std::abs(interp);
136 if (a > peak) peak = a;
137 }
138 return peak;
139 }
140
151 [[nodiscard]] static constexpr std::array<std::array<T, kTaps>, kPhases>
152 buildReversedPhaseTable() noexcept
153 {
154 constexpr double kAnnex2[kPhases][kTaps] = {
155 { 0.0017089843750, 0.0109863281250, -0.0196533203125,
156 0.0332031250000, -0.0594482421875, 0.1373291015625,
157 0.9721679687500, -0.1022949218750, 0.0476074218750,
158 -0.0266113281250, 0.0148925781250, -0.0083007812500 },
159 { -0.0291748046875, 0.0292968750000, -0.0517578125000,
160 0.0891113281250, -0.1665039062500, 0.4650878906250,
161 0.7797851562500, -0.2003173828125, 0.1015625000000,
162 -0.0582275390625, 0.0330810546875, -0.0189208984375 },
163 { -0.0189208984375, 0.0330810546875, -0.0582275390625,
164 0.1015625000000, -0.2003173828125, 0.7797851562500,
165 0.4650878906250, -0.1665039062500, 0.0891113281250,
166 -0.0517578125000, 0.0292968750000, -0.0291748046875 },
167 { -0.0083007812500, 0.0148925781250, -0.0266113281250,
168 0.0476074218750, -0.1022949218750, 0.9721679687500,
169 0.1373291015625, -0.0594482421875, 0.0332031250000,
170 -0.0196533203125, 0.0109863281250, 0.0017089843750 },
171 };
172
173 std::array<std::array<T, kTaps>, kPhases> result {};
174 for (int phase = 0; phase < kPhases; ++phase)
175 for (int k = 0; k < kTaps; ++k)
176 result[static_cast<size_t>(phase)][static_cast<size_t>(kTaps - 1 - k)]
177 = static_cast<T>(kAnnex2[phase][k]);
178 return result;
179 }
180
183 static constexpr std::array<std::array<T, kTaps>, kPhases> kReversedCoeffs =
184 buildReversedPhaseTable();
185
186 std::array<State, MaxChannels> states_ {};
187};
188
189} // namespace dspark
Per-channel 4x-oversampled inter-sample peak estimator.
static constexpr int getTaps() noexcept
Input samples one estimate is computed from (taps per phase).
static constexpr int getLatency() noexcept
Group delay of the interpolation FIR in samples (per phase).
T getTailPeak(int channel) const noexcept
Measures the zero-extended interpolation tail of one channel. Evaluates getTaps()-1 zero frames on a ...
T processSample(T sample, int channel) noexcept
Feeds one sample and returns the local true-peak estimate.
void reset() noexcept
Clears all channel histories. Safe on the audio thread.
float dotProduct(const float *DSPARK_RESTRICT a, const float *DSPARK_RESTRICT b, int count) noexcept
Computes the dot product of two arrays.
Definition SimdOps.h:419
Main namespace for the DSPark framework.