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)));
264 template <
bool IsDerivative>
266 T thresh, T maxRed, T coefSmooth,
int refreshInterval, T& blockMaxGr)
noexcept
268 for (
int i = 0; i < numSamples; ++i)
273 for (
int ch = 0; ch < numCh; ++ch)
275 T dataIn = buffer.getChannel(ch)[i];
278 if constexpr (IsDerivative)
280 auto& sr = derivShift_[ch];
281 for (
int k = kDerivLen - 1; k > 0; --k) sr[k] = sr[k - 1];
285 for (
int k = 0; k < kDerivLen - 1; ++k)
287 sumDiffs += std::abs(sr[k] - sr[k + 1]);
289 level = sumDiffs / T(kDerivLen - 1);
293 level = std::abs(detector_[ch].processSample(dataIn, 0));
296 if (level > maxLevel) maxLevel = level;
300 T coeff = (maxLevel > envelope_) ? attackCoeff_ : releaseCoeff_;
301 envelope_ += coeff * (maxLevel - envelope_);
305 T overDb = envDb - thresh;
306 T grDb = (overDb > T(0)) ? -std::min(overDb, maxRed) : T(0);
308 if (-grDb > blockMaxGr) blockMaxGr = -grDb;
311 for (
int ch = 0; ch < numCh; ++ch)
313 T& smoothedGr = smoothedGrDb_[ch];
314 smoothedGr += coefSmooth * (grDb - smoothedGr);
317 if ((i & (refreshInterval - 1)) == 0)
319 updateDynamicPeakCoeffs(ch, smoothedGr);
322 T* channelData = buffer.getChannel(ch);
323 channelData[i] = reduction_[ch].
processSample(channelData[i], 0);
332 void updateFiltersIfDirty() noexcept
334 if (!filterParamsDirty_.exchange(
false, std::memory_order_acquire))
337 T freq = frequency_.load(std::memory_order_relaxed);
338 T bw = bandwidth_.load(std::memory_order_relaxed);
344 const double fEff = std::clamp(
static_cast<double>(freq), 1.0,
345 std::max(1.0, sampleRate_ * 0.499));
348 for (
int ch = 0; ch < kMaxChannels; ++ch)
349 detector_[ch].setCoeffs(c);
354 double w0 = twoPi<double> * fEff / sampleRate_;
355 precomputedCos_ = std::cos(w0);
356 precomputedAlpha_ = std::sin(w0) / (2.0 *
static_cast<double>(bw));
364 void updateDynamicPeakCoeffs(
int ch, T gainDb)
noexcept
367 const double A = std::pow(10.0,
static_cast<double>(gainDb) / 40.0);
369 const double b0 = 1.0 + precomputedAlpha_ * A;
370 const double b1 = -2.0 * precomputedCos_;
371 const double b2 = 1.0 - precomputedAlpha_ * A;
372 const double a0 = 1.0 + precomputedAlpha_ / A;
373 const double a1 = -2.0 * precomputedCos_;
374 const double a2 = 1.0 - precomputedAlpha_ / A;
377 const double a0Inv = 1.0 / a0;
380 coeffs.b0 = b0 * a0Inv;
381 coeffs.b1 = b1 * a0Inv;
382 coeffs.b2 = b2 * a0Inv;
383 coeffs.a1 = a1 * a0Inv;
384 coeffs.a2 = a2 * a0Inv;
389 void updateEnvelopeCoeffs() noexcept
391 if (sampleRate_ <= 0.0)
return;
392 const double atkSec =
static_cast<double>(attackMs_.load(std::memory_order_relaxed)) * 0.001;
393 const double relSec =
static_cast<double>(releaseMs_.load(std::memory_order_relaxed)) * 0.001;
394 attackCoeff_ =
static_cast<T
>(1.0 - std::exp(-1.0 / (sampleRate_ * std::max(atkSec, 1e-6))));
395 releaseCoeff_ =
static_cast<T
>(1.0 - std::exp(-1.0 / (sampleRate_ * std::max(relSec, 1e-6))));
398 static constexpr int kMaxChannels = 2;
399 static constexpr int kDerivLen = 8;
401 double sampleRate_ = 44100.0;
402 int numChannels_ = 0;
404 std::atomic<T> frequency_ { T(7000) };
405 std::atomic<T> bandwidth_ { T(2) };
406 std::atomic<T> threshold_ { T(-20) };
407 std::atomic<T> maxReduction_ { T(12) };
408 std::atomic<T> attackMs_ { T(0.5) };
409 std::atomic<T> releaseMs_ { T(20) };
412 std::atomic<bool> envCoefsDirty_ {
false };
413 std::atomic<bool> filterParamsDirty_ {
true };
415 T attackCoeff_ = T(0);
416 T releaseCoeff_ = T(0);
417 T coefSmoothCoeff_ = T(0);
418 std::atomic<T> gainReduction_ { T(0) };
420 Biquad<T, 1> detector_[kMaxChannels]{};
421 Biquad<T, 1> reduction_[kMaxChannels]{};
424 T smoothedGrDb_[kMaxChannels]{};
426 std::array<std::array<T, kDerivLen>, kMaxChannels> derivShift_ {};
429 double precomputedCos_ = 0.0;
430 double precomputedAlpha_ = 0.0;