DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
NoiseGate.h
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
55#include "../Core/DspMath.h"
56#include "../Core/AudioSpec.h"
57#include "../Core/AudioBuffer.h"
58#include "../Core/DenormalGuard.h"
59#include "../Core/RingBuffer.h"
60#include "../Core/StateBlob.h"
61
62#include <algorithm>
63#include <array>
64#include <atomic>
65#include <cmath>
66#include <cstddef>
67#include <cstdint>
68#include <numbers>
69#include <utility>
70#include <vector>
71
72namespace dspark {
73
80template <FloatType T>
82{
83public:
84 ~NoiseGate() = default; // Removed virtual to prevent vptr injection in header-only DSP
85
87 enum class State
88 {
89 Closed,
90 Open,
91 Hold
92 };
93
95 enum class GateMode
96 {
97 Amplitude,
99 };
100
112 void prepare(double sampleRate, int numChannels = 2) noexcept
113 {
114 if (!(sampleRate > 0.0) || !std::isfinite(sampleRate)) return; // NaN/inf-safe gate
115 sampleRate_ = sampleRate;
116 lookaheadChannels_ = std::clamp(numChannels, 1, kMaxLookaheadChannels);
117 maxLookaheadSamples_ = static_cast<int>(std::ceil(sampleRate * kMaxLookaheadMs / 1000.0));
118 for (int ch = 0; ch < lookaheadChannels_; ++ch)
119 lookahead_[static_cast<size_t>(ch)].prepare(maxLookaheadSamples_ + 1);
120 syncParams();
121 reset();
122 }
123
128 void prepare(const AudioSpec& spec) noexcept { prepare(spec.sampleRate, spec.numChannels); }
129
139 void processBlock(AudioBufferView<T> buffer) noexcept
140 {
141 DenormalGuard guard;
143
144 const int nCh = buffer.getNumChannels();
145 const int nS = buffer.getNumSamples();
146 if (nCh <= 0 || nS <= 0) return;
147
148 const bool freqMode = (cachedGateMode_ == GateMode::Frequency);
149
150 for (int i = 0; i < nS; ++i)
151 {
152 // Stereo / N-channel linked detection: peak across ALL channels.
153 // The sidechain HPF runs per channel BEFORE the peak link; channels
154 // beyond the HPF state array are detected unfiltered (sharing a
155 // filter between channels would corrupt its history).
156 T level = T(0);
157 T det0 = T(0);
158 for (int ch = 0; ch < nCh; ++ch)
159 {
160 T s = buffer.getChannel(ch)[i];
162 s = applyScHpf(s, ch);
163 if (ch == 0) det0 = s;
164 level = std::max(level, std::abs(s));
165 }
166
167 // Zero-crossing runs on the same (filtered) signal the detector
168 // sees, exactly like the per-sample path.
170
171 T envelope = computeEnvelopeFollower(level);
172 updateStateMachine(envelope);
173 T gain = getCurrentGain();
174
175 for (int ch = 0; ch < nCh; ++ch)
176 {
177 T* d = buffer.getChannel(ch);
178 const T x = delayed(d[i], ch);
179 // Frequency mode keeps per-channel filter state only for the first
180 // kMaxChannels; any extra channels fall back to amplitude gating.
181 d[i] = (freqMode && ch < kMaxChannels) ? applyFrequencyGate(x, ch)
182 : x * gain;
183 }
184 }
185 }
186
197 void processBlock(AudioBufferView<T> audio, AudioBufferView<T> sidechain) noexcept
198 {
199 DenormalGuard guard;
201
202 const int nCh = audio.getNumChannels();
203 const int nS = audio.getNumSamples();
204 const int scCh = sidechain.getNumChannels();
205 if (nCh <= 0 || nS <= 0) return;
206
207 // NOTE: no std::assume_aligned - view pointers are not guaranteed 32-byte
208 // aligned (sub-views / driver buffers), and assuming so is UB. __restrict
209 // still conveys no-aliasing to the compiler for the mono fast path.
210 if (nCh == 1 && scCh == 1)
211 {
212 T* __restrict outL = audio.getChannel(0);
213 const T* __restrict scL = sidechain.getChannel(0);
214 for (int i = 0; i < nS; ++i)
215 outL[i] = processSampleInternal(outL[i], scL[i], 0);
216 }
217 else
218 {
219 const bool freqMode = (cachedGateMode_ == GateMode::Frequency);
220
221 for (int i = 0; i < nS; ++i)
222 {
223 T scMax = T(0);
224 T det0 = T(0);
225 for (int c = 0; c < scCh; ++c)
226 {
227 T s = sidechain.getChannel(c)[i];
229 s = applyScHpf(s, c);
230 if (c == 0) det0 = s;
231 T a = std::abs(s);
232 if (a > scMax) scMax = a;
233 }
234
236
237 T envelope = computeEnvelopeFollower(scMax);
238 updateStateMachine(envelope);
239 T gain = getCurrentGain();
240
241 for (int ch = 0; ch < nCh; ++ch)
242 {
243 T* d = audio.getChannel(ch);
244 const T x = delayed(d[i], ch);
245 d[i] = (freqMode && ch < kMaxChannels) ? applyFrequencyGate(x, ch)
246 : x * gain;
247 }
248 }
249 }
250 }
251
252 // -- Parameters (Thread-Safe Setters) -----------------------------------------
253
258 void setThreshold(T dB) noexcept
259 {
260 if (!std::isfinite(dB)) return;
261 threshold_.store(dB, std::memory_order_relaxed);
262 paramsDirty_.store(true, std::memory_order_release);
263 }
264
269 void setHysteresis(T dB) noexcept
270 {
271 if (!std::isfinite(dB)) return;
272 hysteresis_.store(std::max(T(0), dB), std::memory_order_relaxed);
273 paramsDirty_.store(true, std::memory_order_release);
274 }
275
277 void setAttack(T ms) noexcept
278 {
279 if (!std::isfinite(ms)) return;
280 attackMs_.store(std::max(T(0.01), ms), std::memory_order_relaxed);
281 paramsDirty_.store(true, std::memory_order_release);
282 }
283
285 void setHold(T ms) noexcept
286 {
287 if (!std::isfinite(ms)) return;
288 holdMs_.store(std::max(T(0), ms), std::memory_order_relaxed);
289 paramsDirty_.store(true, std::memory_order_release);
290 }
291
293 void setRelease(T ms) noexcept
294 {
295 if (!std::isfinite(ms)) return;
296 releaseMs_.store(std::max(T(0.01), ms), std::memory_order_relaxed);
297 paramsDirty_.store(true, std::memory_order_release);
298 }
299
304 void setRange(T dB) noexcept
305 {
306 if (!std::isfinite(dB)) return;
307 rangeDb_.store(std::min(T(0), dB), std::memory_order_relaxed);
308 paramsDirty_.store(true, std::memory_order_release);
309 }
310
312 void setDuckMode(bool enabled) noexcept
313 {
314 duckMode_.store(enabled, std::memory_order_relaxed);
315 paramsDirty_.store(true, std::memory_order_release);
316 }
317
319 void setGateMode(GateMode mode) noexcept
320 {
321 const int m = std::clamp(static_cast<int>(mode), 0, static_cast<int>(GateMode::Frequency));
322 gateMode_.store(static_cast<GateMode>(m), std::memory_order_relaxed);
323 paramsDirty_.store(true, std::memory_order_release);
324 }
325
337 void setLookahead(T ms) noexcept
338 {
339 if (!std::isfinite(ms)) return;
340 lookaheadMs_.store(std::clamp(ms, T(0), static_cast<T>(kMaxLookaheadMs)), std::memory_order_relaxed);
341 paramsDirty_.store(true, std::memory_order_release);
342 }
343
345 [[nodiscard]] T getLookahead() const noexcept { return lookaheadMs_.load(std::memory_order_relaxed); }
346
348 [[nodiscard]] int getLatency() const noexcept
349 {
350 return lookaheadSamplesFor(lookaheadMs_.load(std::memory_order_relaxed));
351 }
352
354 void setAdaptiveHold(bool enabled) noexcept
355 {
356 adaptiveHold_.store(enabled, std::memory_order_relaxed);
357 paramsDirty_.store(true, std::memory_order_release);
358 }
359
370 void setSidechainHPF(bool enabled, double cutoffHz = 80.0) noexcept
371 {
372 scHpfEnabled_.store(enabled, std::memory_order_relaxed);
373 if (std::isfinite(cutoffHz) && cutoffHz > 0.0)
374 scHpfFreq_.store(static_cast<T>(cutoffHz), std::memory_order_relaxed);
375 paramsDirty_.store(true, std::memory_order_release);
376 }
377
378 // -- Single Sample Processing -------------------------------------------------
379
391 [[nodiscard]] T processSample(T input) noexcept
392 {
394 return processSampleInternal(input, input, 0);
395 }
396
403 [[nodiscard]] T processSampleWithSidechain(T input, T sidechain) noexcept
404 {
406 return processSampleInternal(input, sidechain, 0);
407 }
408
412 void reset() noexcept
413 {
416 envelopeState_ = T(0);
417 holdCounter_ = 0;
418 scHpfState_.fill(T(0));
419 scHpfPrev_.fill(T(0));
420 for (int ch = 0; ch < kMaxChannels; ++ch)
421 {
422 freqLpState_[ch] = T(0);
423 freqHpState_[ch] = T(0);
424 freqHpPrev_[ch] = T(0);
425 freqLpFreq_[ch] = T(20000);
426 freqHpFreq_[ch] = T(20);
427 }
428 zeroCrossCount_ = 0;
430 prevSign_ = false;
432 for (auto& line : lookahead_) line.reset();
433 }
434
435
437 [[nodiscard]] std::vector<uint8_t> getState() const
438 {
439 StateWriter w(stateId("GATE"), 1);
440 w.write("threshold", static_cast<float>(threshold_.load(std::memory_order_relaxed)));
441 w.write("hysteresis", static_cast<float>(hysteresis_.load(std::memory_order_relaxed)));
442 w.write("attack", static_cast<float>(attackMs_.load(std::memory_order_relaxed)));
443 w.write("hold", static_cast<float>(holdMs_.load(std::memory_order_relaxed)));
444 w.write("release", static_cast<float>(releaseMs_.load(std::memory_order_relaxed)));
445 w.write("range", static_cast<float>(rangeDb_.load(std::memory_order_relaxed)));
446 w.write("duck", duckMode_.load(std::memory_order_relaxed));
447 w.write("gateMode", static_cast<int32_t>(gateMode_.load(std::memory_order_relaxed)));
448 w.write("adaptiveHold", adaptiveHold_.load(std::memory_order_relaxed));
449 w.write("scHpf", scHpfEnabled_.load(std::memory_order_relaxed));
450 w.write("scHpfFreq", static_cast<float>(scHpfFreq_.load(std::memory_order_relaxed)));
451 w.write("lookahead", static_cast<float>(lookaheadMs_.load(std::memory_order_relaxed)));
452 return w.blob();
453 }
454
456 bool setState(const uint8_t* data, size_t size)
457 {
458 StateReader r(data, size);
459 if (!r.isValid() || r.processorId() != stateId("GATE")) return false;
460 setThreshold(static_cast<T>(r.read("threshold", -40.0f)));
461 setHysteresis(static_cast<T>(r.read("hysteresis", 4.0f)));
462 setAttack(static_cast<T>(r.read("attack", 0.5f)));
463 setHold(static_cast<T>(r.read("hold", 50.0f)));
464 setRelease(static_cast<T>(r.read("release", 100.0f)));
465 setRange(static_cast<T>(r.read("range", -80.0f)));
466 setDuckMode(r.read("duck", false));
467 setGateMode(static_cast<GateMode>(r.read("gateMode", 0)));
468 setAdaptiveHold(r.read("adaptiveHold", false));
469 setSidechainHPF(r.read("scHpf", false),
470 static_cast<double>(r.read("scHpfFreq", 80.0f)));
471 setLookahead(static_cast<T>(r.read("lookahead", 0.0f)));
472 return true;
473 }
474
475protected:
476 static constexpr int kMaxChannels = 2;
477
478 void syncParamsIfDirty() noexcept
479 {
480 // Plain load first: the exchange RMW is only paid when a publication
481 // is actually pending (this runs per sample in the per-sample path).
482 if (paramsDirty_.load(std::memory_order_acquire)
483 && paramsDirty_.exchange(false, std::memory_order_acquire))
484 syncParams();
485 }
486
487 void syncParams() noexcept
488 {
489 T fs = static_cast<T>(sampleRate_);
490 if (!(fs > T(0))) return; // NaN-safe (prepare() already gates the rate)
491
492 // Cache logical switches to prevent atomic loads in hot path
493 cachedDuck_ = duckMode_.load(std::memory_order_relaxed);
494 cachedGateMode_ = gateMode_.load(std::memory_order_relaxed);
495 cachedAdaptiveHold_ = adaptiveHold_.load(std::memory_order_relaxed);
496 cachedScHpfEnabled_ = scHpfEnabled_.load(std::memory_order_relaxed);
497
498 // Compute thresholds in LINEAR domain to eliminate log10 in hot path
499 T thDb = threshold_.load(std::memory_order_relaxed);
500 T hystDb = hysteresis_.load(std::memory_order_relaxed);
503 cachedRangeLinear_ = decibelsToGain(rangeDb_.load(std::memory_order_relaxed));
504
505 T attMs = std::max(attackMs_.load(std::memory_order_relaxed), T(0.01));
506 T relMs = std::max(releaseMs_.load(std::memory_order_relaxed), T(0.01));
507 attackCoeff_ = T(1) - std::exp(T(-1) / (fs * attMs / T(1000)));
508 releaseCoeff_ = T(1) - std::exp(T(-1) / (fs * relMs / T(1000)));
509
510 // Fixed ~2ms release for the detector envelope (prevents bass chattering)
511 detectorReleaseCoeff_ = T(1) - std::exp(T(-1) / (fs * T(0.002)));
512
513 // Parameter smoothing coefficient for Frequency Mode (prevents hardcoded 0.001 dependency)
514 freqSmoothCoeff_ = T(1) - std::exp(T(-1) / (fs * T(0.02))); // 20ms smoothing
515
516 // Cap the sample count in double before the cast (a cast of an
517 // out-of-int-range double is undefined behaviour).
518 T hMs = std::max(holdMs_.load(std::memory_order_relaxed), T(0));
519 holdSamples_ = static_cast<int>(std::min(fs * static_cast<double>(hMs) / 1000.0, 1.0e9));
520
521 // The setter rejects invalid cutoffs; the clamp keeps the coefficient
522 // in the stable range even against a hostile in-memory value.
523 const double scFreq = std::clamp(
524 static_cast<double>(scHpfFreq_.load(std::memory_order_relaxed)),
525 1.0, sampleRate_ * 0.45);
526 scHpfCoeff_ = static_cast<T>(std::exp(-std::numbers::pi * 2.0 * scFreq / static_cast<double>(fs)));
527 scHpfA0_ = (T(1) + scHpfCoeff_) / T(2); // Normalization to prevent high-frequency boost
528
529 cachedNyquist_ = static_cast<T>(fs * 0.5);
530 cachedFsInvPi2_ = static_cast<T>(std::numbers::pi * 2.0 / fs);
531
532 lookaheadSamples_ = lookaheadSamplesFor(lookaheadMs_.load(std::memory_order_relaxed));
533 }
534
536 [[nodiscard]] int lookaheadSamplesFor(T ms) const noexcept
537 {
538 const double samples = std::round(static_cast<double>(ms) * sampleRate_ / 1000.0);
539 return std::clamp(static_cast<int>(std::min(samples, 1.0e9)), 0, maxLookaheadSamples_);
540 }
541
544 [[nodiscard]] inline T delayed(T x, int ch) noexcept
545 {
546 if (lookaheadSamples_ <= 0 || ch >= lookaheadChannels_) return x;
547 auto& line = lookahead_[static_cast<size_t>(ch)];
548 line.push(x);
549 return line.read(lookaheadSamples_);
550 }
551
555 [[nodiscard]] T computeEnvelopeFollower(T rawLevel) noexcept
556 {
557 if (rawLevel > envelopeState_)
558 envelopeState_ = rawLevel; // Instant attack for precise triggering
559 else
561
562 return envelopeState_;
563 }
564
565 void updateStateMachine(T envelopeLinear) noexcept
566 {
567 bool above = envelopeLinear > cachedThresholdLinear_;
568 bool below = envelopeLinear < cachedCloseThresholdLinear_;
569
570 if (cachedDuck_)
571 std::swap(above, below);
572
573 int effectiveHoldSamples = holdSamples_;
574 if (cachedAdaptiveHold_ && estimatedPeriod_ > effectiveHoldSamples)
575 effectiveHoldSamples = estimatedPeriod_;
576
577 switch (state_)
578 {
579 case State::Closed:
580 if (above) state_ = State::Open;
581 break;
582
583 case State::Open:
584 if (below)
585 {
587 holdCounter_ = effectiveHoldSamples;
588 }
589 break;
590
591 case State::Hold:
592 if (above)
593 {
595 }
596 else
597 {
598 --holdCounter_;
599 if (holdCounter_ <= 0)
601 }
602 break;
603 }
604 }
605
606 void updateZeroCrossing(T sample) noexcept
607 {
608 bool sign = sample >= T(0);
609 if (sign != prevSign_)
611 prevSign_ = sign;
612
615 {
616 if (zeroCrossCount_ > 0)
618 else
620
621 zeroCrossCount_ = 0;
623 }
624 }
625
626 [[nodiscard]] inline T getCurrentGain() noexcept
627 {
628 T targetGain = (state_ == State::Open || state_ == State::Hold) ? T(1) : cachedRangeLinear_;
629
630 // Fast path for established states to avoid unnecessary math
631 if (targetGain == gateGain_) return gateGain_;
632
633 T coeff = (targetGain > gateGain_) ? attackCoeff_ : releaseCoeff_;
634 gateGain_ += coeff * (targetGain - gateGain_);
635 return gateGain_;
636 }
637
638 [[nodiscard]] T applyFrequencyGate(T input, int ch) noexcept
639 {
640 T gateOpenness = gateGain_;
641
642 T targetLp = T(20) + (cachedNyquist_ - T(20)) * gateOpenness;
643 T targetHp = T(20) + (cachedNyquist_ * T(0.4)) * (T(1) - gateOpenness);
644
645 freqLpFreq_[ch] += freqSmoothCoeff_ * (targetLp - freqLpFreq_[ch]);
646 freqHpFreq_[ch] += freqSmoothCoeff_ * (targetHp - freqHpFreq_[ch]);
647
648 // Bilinear/Euler approximation to avoid std::exp() in the audio inner loop
649 T lpCoeff = std::min(T(1), freqLpFreq_[ch] * cachedFsInvPi2_);
650 freqLpState_[ch] += lpCoeff * (input - freqLpState_[ch]);
651
652 T hpCoeff = T(1) - std::min(T(1), freqHpFreq_[ch] * cachedFsInvPi2_);
653 T hpOut = hpCoeff * (freqHpState_[ch] + freqLpState_[ch] - freqHpPrev_[ch]);
654
655 freqHpPrev_[ch] = freqLpState_[ch];
656 freqHpState_[ch] = hpOut;
657
658 return hpOut;
659 }
660
662 [[nodiscard]] T applyScHpf(T input, int ch) noexcept
663 {
664 T output = scHpfA0_ * (input - scHpfPrev_[ch]) + scHpfCoeff_ * scHpfState_[ch];
665 scHpfPrev_[ch] = input;
666 scHpfState_[ch] = output;
667 return output;
668 }
669
670 [[nodiscard]] T processSampleInternal(T input, T sidechain, int ch) noexcept
671 {
673 sidechain = applyScHpf(sidechain, ch);
674
675 T rawLevel = std::abs(sidechain);
676
678 updateZeroCrossing(sidechain);
679
680 T envelope = computeEnvelopeFollower(rawLevel);
681 updateStateMachine(envelope);
682 input = delayed(input, ch);
683
685 {
686 (void)getCurrentGain();
687 return applyFrequencyGate(input, ch);
688 }
689
690 return input * getCurrentGain();
691 }
692
693 double sampleRate_ = 48000.0;
694
695 std::atomic<T> threshold_ { T(-40) };
696 std::atomic<T> hysteresis_ { T(4) };
697 std::atomic<T> attackMs_ { T(0.5) };
698 std::atomic<T> holdMs_ { T(50) };
699 std::atomic<T> releaseMs_ { T(100) };
700 std::atomic<T> rangeDb_ { T(-80) };
701 std::atomic<bool> duckMode_ { false };
702 std::atomic<GateMode> gateMode_ { GateMode::Amplitude };
703 std::atomic<bool> adaptiveHold_ { false };
704 std::atomic<bool> paramsDirty_ { true };
705
706 std::atomic<bool> scHpfEnabled_ { false };
707 std::atomic<T> scHpfFreq_ { T(80) };
708
709 // Lookahead (audio delayed; detection runs on the undelayed signal).
710 static constexpr double kMaxLookaheadMs = 10.0;
711 static constexpr int kMaxLookaheadChannels = 16;
712 std::atomic<T> lookaheadMs_ { T(0) };
716 std::array<RingBuffer<T>, kMaxLookaheadChannels> lookahead_ {};
717
718 // Cached internal variables
721 T cachedRangeLinear_ = T(0.0001);
722 bool cachedDuck_ = false;
726 T cachedNyquist_ = T(24000);
728
729 static constexpr int kMaxScChannels = 16;
730 T scHpfCoeff_ = T(0.995);
731 T scHpfA0_ = T(0.9975);
732 std::array<T, kMaxScChannels> scHpfState_ {};
733 std::array<T, kMaxScChannels> scHpfPrev_ {};
734
735 T attackCoeff_ = T(0);
738
739 // Fast Envelope Follower State
742
744 T gateGain_ = T(0);
746
748 std::array<T, kMaxChannels> freqLpState_ {};
749 std::array<T, kMaxChannels> freqHpState_ {};
750 std::array<T, kMaxChannels> freqHpPrev_ {};
751 std::array<T, kMaxChannels> freqLpFreq_ {};
752 std::array<T, kMaxChannels> freqHpFreq_ {};
753
754 static constexpr int kZeroCrossWindow = 2048;
757 bool prevSign_ = false;
759};
760
761} // namespace dspark
Non-owning view over audio channel data.
Definition AudioBuffer.h:50
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
High-performance noise gate with state machine, hysteresis, and zero-allocation processing.
Definition NoiseGate.h:82
T getLookahead() const noexcept
Returns the lookahead in milliseconds.
Definition NoiseGate.h:345
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
Definition NoiseGate.h:456
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an AudioBufferView in-place.
Definition NoiseGate.h:139
int getLatency() const noexcept
Latency in samples (the lookahead), correct right after setLookahead().
Definition NoiseGate.h:348
void prepare(double sampleRate, int numChannels=2) noexcept
Prepares the noise gate for processing.
Definition NoiseGate.h:112
std::array< T, kMaxScChannels > scHpfPrev_
Definition NoiseGate.h:733
std::array< T, kMaxChannels > freqLpState_
Definition NoiseGate.h:748
T applyFrequencyGate(T input, int ch) noexcept
Definition NoiseGate.h:638
T processSample(T input) noexcept
Processes a single mono sample.
Definition NoiseGate.h:391
void setLookahead(T ms) noexcept
Sets the lookahead: the audio is delayed so the gate opens (and closes) ahead of it....
Definition NoiseGate.h:337
void setSidechainHPF(bool enabled, double cutoffHz=80.0) noexcept
Configures sidechain High-Pass filter.
Definition NoiseGate.h:370
void setRange(T dB) noexcept
Sets maximum attenuation range.
Definition NoiseGate.h:304
int lookaheadChannels_
Channels with a prepared delay line.
Definition NoiseGate.h:715
std::array< T, kMaxScChannels > scHpfState_
Definition NoiseGate.h:732
void processBlock(AudioBufferView< T > audio, AudioBufferView< T > sidechain) noexcept
Processes audio with an external sidechain signal.
Definition NoiseGate.h:197
T processSampleWithSidechain(T input, T sidechain) noexcept
Processes a mono sample using an external sidechain.
Definition NoiseGate.h:403
std::array< RingBuffer< T >, kMaxLookaheadChannels > lookahead_
Definition NoiseGate.h:716
static constexpr int kZeroCrossWindow
Definition NoiseGate.h:754
void setAttack(T ms) noexcept
Sets attack time in milliseconds. Non-finite values are ignored.
Definition NoiseGate.h:277
void updateStateMachine(T envelopeLinear) noexcept
Definition NoiseGate.h:565
std::atomic< T > lookaheadMs_
Definition NoiseGate.h:712
void setRelease(T ms) noexcept
Sets release time in milliseconds. Non-finite values are ignored.
Definition NoiseGate.h:293
void prepare(const AudioSpec &spec) noexcept
Prepares from AudioSpec (unified API).
Definition NoiseGate.h:128
static constexpr int kMaxChannels
Definition NoiseGate.h:476
std::atomic< T > threshold_
Definition NoiseGate.h:695
void setHold(T ms) noexcept
Sets hold time in milliseconds. Non-finite values are ignored.
Definition NoiseGate.h:285
std::array< T, kMaxChannels > freqLpFreq_
Definition NoiseGate.h:751
void setGateMode(GateMode mode) noexcept
Selects the processing mode (Amplitude or Frequency; wild enum values clamp).
Definition NoiseGate.h:319
void updateZeroCrossing(T sample) noexcept
Definition NoiseGate.h:606
GateMode
Operating mode for the gate processing.
Definition NoiseGate.h:96
@ Frequency
Gatelope-style: narrows bandpass dynamically instead of direct gain reduction.
@ Amplitude
Standard amplitude gain reduction (default).
~NoiseGate()=default
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
Definition NoiseGate.h:437
std::array< T, kMaxChannels > freqHpState_
Definition NoiseGate.h:749
void syncParamsIfDirty() noexcept
Definition NoiseGate.h:478
std::atomic< T > holdMs_
Definition NoiseGate.h:698
T processSampleInternal(T input, T sidechain, int ch) noexcept
Definition NoiseGate.h:670
std::atomic< T > releaseMs_
Definition NoiseGate.h:699
T getCurrentGain() noexcept
Definition NoiseGate.h:626
std::atomic< GateMode > gateMode_
Definition NoiseGate.h:702
std::array< T, kMaxChannels > freqHpFreq_
Definition NoiseGate.h:752
State
Gate state machine phases.
Definition NoiseGate.h:88
@ Closed
Gate is fully closed (applying range attenuation).
@ Hold
Gate is open but counting down hold time before closing.
@ Open
Gate is fully open (passing audio).
static constexpr double kMaxLookaheadMs
Definition NoiseGate.h:710
std::atomic< T > rangeDb_
Definition NoiseGate.h:700
int lookaheadSamplesFor(T ms) const noexcept
Definition NoiseGate.h:536
GateMode cachedGateMode_
Definition NoiseGate.h:723
std::atomic< T > attackMs_
Definition NoiseGate.h:697
T computeEnvelopeFollower(T rawLevel) noexcept
Updates envelope state to prevent intra-cycle chattering.
Definition NoiseGate.h:555
static constexpr int kMaxScChannels
Definition NoiseGate.h:729
std::atomic< T > hysteresis_
Definition NoiseGate.h:696
void setHysteresis(T dB) noexcept
Sets the hysteresis amount (gap between open and close thresholds).
Definition NoiseGate.h:269
T applyScHpf(T input, int ch) noexcept
Per-channel one-pole sidechain high-pass (unity gain at Nyquist).
Definition NoiseGate.h:662
void setThreshold(T dB) noexcept
Sets the opening threshold.
Definition NoiseGate.h:258
std::atomic< T > scHpfFreq_
Definition NoiseGate.h:707
void setDuckMode(bool enabled) noexcept
Toggles ducking mode (invert gate logic).
Definition NoiseGate.h:312
std::array< T, kMaxChannels > freqHpPrev_
Definition NoiseGate.h:750
int maxLookaheadSamples_
Allocated delay (0 before prepare()).
Definition NoiseGate.h:714
void reset() noexcept
Resets DSP state (clears filters, sets state to Closed).
Definition NoiseGate.h:412
void setAdaptiveHold(bool enabled) noexcept
Toggles adaptive hold based on zero-crossing rate.
Definition NoiseGate.h:354
std::atomic< bool > paramsDirty_
Definition NoiseGate.h:704
std::atomic< bool > duckMode_
Definition NoiseGate.h:701
static constexpr int kMaxLookaheadChannels
Definition NoiseGate.h:711
int lookaheadSamples_
Audio-side value (consumed in syncParams()).
Definition NoiseGate.h:713
void syncParams() noexcept
Definition NoiseGate.h:487
std::atomic< bool > adaptiveHold_
Definition NoiseGate.h:703
T delayed(T x, int ch) noexcept
Definition NoiseGate.h:544
std::atomic< bool > scHpfEnabled_
Definition NoiseGate.h:706
Tolerant reader: missing keys yield defaults, unknown keys are skipped.
Definition StateBlob.h:161
float read(const char *key, float defaultValue) const
Reads a float, or defaultValue when the key is absent.
Definition StateBlob.h:204
bool isValid() const noexcept
Definition StateBlob.h:199
uint32_t processorId() const noexcept
Definition StateBlob.h:200
Serializes key/value parameters into a versioned blob.
Definition StateBlob.h:53
std::vector< uint8_t > blob() const
Finalizes and returns the blob.
Definition StateBlob.h:105
void write(const char *key, float value)
Writes a float parameter.
Definition StateBlob.h:71
Main namespace for the DSPark framework.
T decibelsToGain(T dB, T minusInfinityDb=T(-100)) noexcept
Converts a value in decibels to linear gain.
Definition DspMath.h:74
constexpr uint32_t stateId(const char(&tag)[5]) noexcept
Builds a FOURCC processor id, e.g. dspark::stateId("COMP").
Definition StateBlob.h:651
Describes the audio environment for a DSP processor.
Definition AudioSpec.h:37