88 updateEnvelopeCoeffs();
89 coefSmoothCoeff_ =
static_cast<T
>(1.0 - std::exp(-1.0 / (sampleRate_ * 0.001)));
93 filterParamsDirty_.store(
true, std::memory_order_release);
94 updateFiltersIfDirty();
103 int numCh = std::min(buffer.getNumChannels(), std::min(numChannels_, kMaxChannels));
104 int numSamples = buffer.getNumSamples();
105 if (numSamples == 0 || numCh == 0)
return;
107 updateFiltersIfDirty();
109 if (envCoefsDirty_.exchange(
false, std::memory_order_acquire))
110 updateEnvelopeCoeffs();
112 T thresh = threshold_.load(std::memory_order_relaxed);
113 T maxRed = maxReduction_.load(std::memory_order_relaxed);
114 auto mode = detectionMode_.load(std::memory_order_relaxed);
116 constexpr int kCoefRefreshInterval = 16;
117 const T coefSmooth = coefSmoothCoeff_;
123 processInternal<true>(buffer, numCh, numSamples, thresh, maxRed, coefSmooth, kCoefRefreshInterval, blockMaxGr);
127 processInternal<false>(buffer, numCh, numSamples, thresh, maxRed, coefSmooth, kCoefRefreshInterval, blockMaxGr);
130 gainReduction_.store(blockMaxGr, std::memory_order_relaxed);
136 for (
int ch = 0; ch < kMaxChannels; ++ch)
138 detector_[ch].
reset();
139 reduction_[ch].
reset();
140 smoothedGrDb_[ch] = T(0);
141 derivShift_[ch].fill(T(0));
144 gainReduction_.store(T(0), std::memory_order_relaxed);
152 if (!std::isfinite(ms))
return;
153 attackMs_.store(std::clamp(ms, T(0.1), T(20)), std::memory_order_relaxed);
154 envCoefsDirty_.store(
true, std::memory_order_release);
160 if (!std::isfinite(ms))
return;
161 releaseMs_.store(std::clamp(ms, T(1), T(500)), std::memory_order_relaxed);
162 envCoefsDirty_.store(
true, std::memory_order_release);
173 if (!std::isfinite(hz))
return;
174 frequency_.store(hz, std::memory_order_relaxed);
175 filterParamsDirty_.store(
true, std::memory_order_release);
181 if (!std::isfinite(octaves))
return;
182 T bw = std::max(octaves, T(0.1));
183 T q = T(1) / (T(2) * std::sinh(T(0.34657359) * bw));
184 bandwidth_.store(q, std::memory_order_relaxed);
185 filterParamsDirty_.store(
true, std::memory_order_release);
191 if (!std::isfinite(db))
return;
192 threshold_.store(db, std::memory_order_relaxed);
198 if (!std::isfinite(db))
return;
199 maxReduction_.store(std::abs(db), std::memory_order_relaxed);
205 const int v = std::clamp(
static_cast<int>(mode),
208 detectionMode_.store(
static_cast<DetectionMode>(v), std::memory_order_relaxed);
212 [[nodiscard]] T
getGainReductionDb() const noexcept {
return gainReduction_.load(std::memory_order_relaxed); }
213 [[nodiscard]] T
getFrequency() const noexcept {
return frequency_.load(std::memory_order_relaxed); }
214 [[nodiscard]] T
getThreshold() const noexcept {
return threshold_.load(std::memory_order_relaxed); }
216 [[nodiscard]] T
getAttack() const noexcept {
return attackMs_.load(std::memory_order_relaxed); }
217 [[nodiscard]] T
getRelease() const noexcept {
return releaseMs_.load(std::memory_order_relaxed); }
222 return static_cast<T
>(std::asinh(1.0 / (2.0 *
static_cast<double>(
223 bandwidth_.load(std::memory_order_relaxed)))) / 0.34657359);
227 [[nodiscard]] T
getReduction() const noexcept {
return maxReduction_.load(std::memory_order_relaxed); }
230 [[nodiscard]] std::vector<uint8_t>
getState()
const
235 w.
write(
"frequency",
static_cast<float>(frequency_.load(std::memory_order_relaxed)));
238 w.
write(
"threshold",
static_cast<float>(threshold_.load(std::memory_order_relaxed)));
239 w.
write(
"reduction",
static_cast<float>(maxReduction_.load(std::memory_order_relaxed)));
240 w.
write(
"attack",
static_cast<float>(attackMs_.load(std::memory_order_relaxed)));
241 w.
write(
"release",
static_cast<float>(releaseMs_.load(std::memory_order_relaxed)));
242 w.
write(
"detection",
static_cast<int32_t
>(detectionMode_.load(std::memory_order_relaxed)));
262 template <
bool IsDerivative>
264 T thresh, T maxRed, T coefSmooth,
int refreshInterval, T& blockMaxGr)
noexcept
266 for (
int i = 0; i < numSamples; ++i)
271 for (
int ch = 0; ch < numCh; ++ch)
273 T dataIn = buffer.getChannel(ch)[i];
276 if constexpr (IsDerivative)
278 auto& sr = derivShift_[ch];
279 for (
int k = kDerivLen - 1; k > 0; --k) sr[k] = sr[k - 1];
283 for (
int k = 0; k < kDerivLen - 1; ++k)
285 sumDiffs += std::abs(sr[k] - sr[k + 1]);
287 level = sumDiffs / T(kDerivLen - 1);
291 level = std::abs(detector_[ch].processSample(dataIn, 0));
294 if (level > maxLevel) maxLevel = level;
298 T coeff = (maxLevel > envelope_) ? attackCoeff_ : releaseCoeff_;
299 envelope_ += coeff * (maxLevel - envelope_);
303 T overDb = envDb - thresh;
304 T grDb = (overDb > T(0)) ? -std::min(overDb, maxRed) : T(0);
306 if (-grDb > blockMaxGr) blockMaxGr = -grDb;
309 for (
int ch = 0; ch < numCh; ++ch)
311 T& smoothedGr = smoothedGrDb_[ch];
312 smoothedGr += coefSmooth * (grDb - smoothedGr);
315 if ((i & (refreshInterval - 1)) == 0)
317 updateDynamicPeakCoeffs(ch, smoothedGr);
320 T* channelData = buffer.getChannel(ch);
321 channelData[i] = reduction_[ch].
processSample(channelData[i], 0);
330 void updateFiltersIfDirty() noexcept
332 if (!filterParamsDirty_.exchange(
false, std::memory_order_acquire))
335 T freq = frequency_.load(std::memory_order_relaxed);
336 T bw = bandwidth_.load(std::memory_order_relaxed);
342 const double fEff = std::clamp(
static_cast<double>(freq), 1.0,
343 std::max(1.0, sampleRate_ * 0.499));
346 for (
int ch = 0; ch < kMaxChannels; ++ch)
347 detector_[ch].setCoeffs(c);
352 double w0 = twoPi<double> * fEff / sampleRate_;
353 precomputedCos_ = std::cos(w0);
354 precomputedAlpha_ = std::sin(w0) / (2.0 *
static_cast<double>(bw));
362 void updateDynamicPeakCoeffs(
int ch, T gainDb)
noexcept
365 const double A = std::pow(10.0,
static_cast<double>(gainDb) / 40.0);
367 const double b0 = 1.0 + precomputedAlpha_ * A;
368 const double b1 = -2.0 * precomputedCos_;
369 const double b2 = 1.0 - precomputedAlpha_ * A;
370 const double a0 = 1.0 + precomputedAlpha_ / A;
371 const double a1 = -2.0 * precomputedCos_;
372 const double a2 = 1.0 - precomputedAlpha_ / A;
375 const double a0Inv = 1.0 / a0;
378 coeffs.b0 = b0 * a0Inv;
379 coeffs.b1 = b1 * a0Inv;
380 coeffs.b2 = b2 * a0Inv;
381 coeffs.a1 = a1 * a0Inv;
382 coeffs.a2 = a2 * a0Inv;
387 void updateEnvelopeCoeffs() noexcept
389 if (sampleRate_ <= 0.0)
return;
390 const double atkSec =
static_cast<double>(attackMs_.load(std::memory_order_relaxed)) * 0.001;
391 const double relSec =
static_cast<double>(releaseMs_.load(std::memory_order_relaxed)) * 0.001;
392 attackCoeff_ =
static_cast<T
>(1.0 - std::exp(-1.0 / (sampleRate_ * std::max(atkSec, 1e-6))));
393 releaseCoeff_ =
static_cast<T
>(1.0 - std::exp(-1.0 / (sampleRate_ * std::max(relSec, 1e-6))));
396 static constexpr int kMaxChannels = 2;
397 static constexpr int kDerivLen = 8;
399 double sampleRate_ = 44100.0;
400 int numChannels_ = 0;
402 std::atomic<T> frequency_ { T(7000) };
403 std::atomic<T> bandwidth_ { T(2) };
404 std::atomic<T> threshold_ { T(-20) };
405 std::atomic<T> maxReduction_ { T(12) };
406 std::atomic<T> attackMs_ { T(0.5) };
407 std::atomic<T> releaseMs_ { T(20) };
410 std::atomic<bool> envCoefsDirty_ {
false };
411 std::atomic<bool> filterParamsDirty_ {
true };
413 T attackCoeff_ = T(0);
414 T releaseCoeff_ = T(0);
415 T coefSmoothCoeff_ = T(0);
416 std::atomic<T> gainReduction_ { T(0) };
418 Biquad<T, 1> detector_[kMaxChannels]{};
419 Biquad<T, 1> reduction_[kMaxChannels]{};
422 T smoothedGrDb_[kMaxChannels]{};
424 std::array<std::array<T, kDerivLen>, kMaxChannels> derivShift_ {};
427 double precomputedCos_ = 0.0;
428 double precomputedAlpha_ = 0.0;