DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
TruePeakDetector.h
Go to the documentation of this file.
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 auto& tp = states_[static_cast<size_t>(channel)];
76
77 // Mirrored write: every sample lands at writePos and writePos + 16,
78 // so the latest 12-tap window is always contiguous in memory and each
79 // phase collapses to one linear dot product (SIMD) instead of a
80 // masked ring walk per tap.
81 tp.history[static_cast<size_t>(tp.writePos)] = sample;
82 tp.history[static_cast<size_t>(tp.writePos + kHistSize)] = sample;
83 const int newest = tp.writePos;
84 tp.writePos = (tp.writePos + 1) & kHistMask;
85
86 // Window holding x[n-11] .. x[n] in forward (oldest-first) order.
87 const T* window = &tp.history[static_cast<size_t>(newest + kHistSize - (kTaps - 1))];
88
89 T peak = std::abs(sample);
90 for (int phase = 0; phase < kPhases; ++phase)
91 {
92 const T interp = simd::dotProduct(
93 kReversedCoeffs[static_cast<size_t>(phase)].data(), window, kTaps);
94 const T a = std::abs(interp);
95 if (a > peak) peak = a;
96 }
97 return peak;
98 }
99
101 [[nodiscard]] static constexpr int getLatency() noexcept { return kTaps / 2; }
102
103private:
104 static constexpr int kTaps = 12;
105 static constexpr int kPhases = 4;
106 static constexpr int kHistSize = 16;
107 static constexpr int kHistMask = kHistSize - 1;
108
109 struct State
110 {
113 T history[kHistSize * 2] = {};
114 int writePos = 0;
115 };
116
127 [[nodiscard]] static constexpr std::array<std::array<T, kTaps>, kPhases>
128 buildReversedPhaseTable() noexcept
129 {
130 constexpr double kAnnex2[kPhases][kTaps] = {
131 { 0.0017089843750, 0.0109863281250, -0.0196533203125,
132 0.0332031250000, -0.0594482421875, 0.1373291015625,
133 0.9721679687500, -0.1022949218750, 0.0476074218750,
134 -0.0266113281250, 0.0148925781250, -0.0083007812500 },
135 { -0.0291748046875, 0.0292968750000, -0.0517578125000,
136 0.0891113281250, -0.1665039062500, 0.4650878906250,
137 0.7797851562500, -0.2003173828125, 0.1015625000000,
138 -0.0582275390625, 0.0330810546875, -0.0189208984375 },
139 { -0.0189208984375, 0.0330810546875, -0.0582275390625,
140 0.1015625000000, -0.2003173828125, 0.7797851562500,
141 0.4650878906250, -0.1665039062500, 0.0891113281250,
142 -0.0517578125000, 0.0292968750000, -0.0291748046875 },
143 { -0.0083007812500, 0.0148925781250, -0.0266113281250,
144 0.0476074218750, -0.1022949218750, 0.9721679687500,
145 0.1373291015625, -0.0594482421875, 0.0332031250000,
146 -0.0196533203125, 0.0109863281250, 0.0017089843750 },
147 };
148
149 std::array<std::array<T, kTaps>, kPhases> result {};
150 for (int phase = 0; phase < kPhases; ++phase)
151 for (int k = 0; k < kTaps; ++k)
152 result[static_cast<size_t>(phase)][static_cast<size_t>(kTaps - 1 - k)]
153 = static_cast<T>(kAnnex2[phase][k]);
154 return result;
155 }
156
159 static constexpr std::array<std::array<T, kTaps>, kPhases> kReversedCoeffs =
160 buildReversedPhaseTable();
161
162 std::array<State, MaxChannels> states_ {};
163};
164
165} // namespace dspark
Core mathematical utilities for digital signal processing.
SIMD-accelerated buffer operations for real-time audio processing.
Per-channel 4x-oversampled inter-sample peak estimator.
static constexpr int getLatency() noexcept
Group delay of the interpolation FIR in samples (per phase).
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.