DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
Filters.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
42#include "../Core/AudioBuffer.h"
43#include "../Core/AudioSpec.h"
44#include "../Core/Biquad.h"
45#include "../Core/DspMath.h"
46#include "../Core/Smoothers.h"
47#include "../Core/AnalogRandom.h"
48#include "../Core/DenormalGuard.h"
49#include "../Core/StateBlob.h"
50
51#include <algorithm>
52#include <array>
53#include <atomic>
54#include <cmath>
55#include <cstdint>
56#include <vector>
57
58namespace dspark {
59
70template <typename T, int MaxChannels = 16>
72{
73public:
74 // Removed virtual destructor to avoid vptr overhead and maintain cache alignment.
75 ~FilterEngine() = default;
76
80 enum class Shape
81 {
84 };
85
86 // -- Lifecycle -----------------------------------------------------------
87
93 void prepare(const AudioSpec& spec)
94 {
95 if (!spec.isValid()) return;
96 spec_ = spec;
97 freqSmoother_.reset(spec.sampleRate, 30.0f, 0.707f, 1000.0f);
98 resSmoother_.reset(spec.sampleRate, 20.0f, 0.707f);
99 gainSmoother_.reset(spec.sampleRate, 20.0f, 0.0f);
100 // Coefficients depend on the sample rate: force a rebuild on the next
101 // block. (Without this, re-preparing at a new rate with unchanged
102 // parameters kept the OLD rate's coefficients: the cutoff landed at
103 // freq * newRate / oldRate.)
104 lastFreq_ = -1.0f;
105 if (driftEnabled_.load(std::memory_order_relaxed))
106 driftGen_.prepare(spec.sampleRate); // re-sync the drift LFO clock
107 reset();
108 }
109
114 void reset() noexcept
115 {
116 for (auto& stage : stages_) stage.reset();
120 }
121
122 // -- Configuration -------------------------------------------------------
123
130 void setLowPass(float freq, float Q = 0.707f, int slopeDb = 12)
131 {
133 slopeDb_ = slopeDb;
134 numStages_ = slopeToStages(slopeDb);
135 setFrequency(freq);
136 setResonance(Q);
137 }
138
145 void setHighPass(float freq, float Q = 0.707f, int slopeDb = 12)
146 {
148 slopeDb_ = slopeDb;
149 numStages_ = slopeToStages(slopeDb);
150 setFrequency(freq);
151 setResonance(Q);
152 }
153
159 void setBandPass(float freq, float Q = 0.707f)
160 {
162 slopeDb_ = 12;
163 numStages_ = 1;
164 setFrequency(freq);
165 setResonance(Q);
166 }
167
174 void setPeaking(float freq, float gainDb, float Q = 1.0f)
175 {
177 slopeDb_ = 12;
178 numStages_ = 1;
179 setFrequency(freq);
180 setResonance(Q);
181 setGain(gainDb);
182 }
183
190 void setLowShelf(float freq, float gainDb, float slope = 1.0f)
191 {
193 slopeDb_ = 12;
194 numStages_ = 1;
195 shelfSlope_ = slope;
196 setFrequency(freq);
197 setGain(gainDb);
198 }
199
206 void setHighShelf(float freq, float gainDb, float slope = 1.0f)
207 {
209 slopeDb_ = 12;
210 numStages_ = 1;
211 shelfSlope_ = slope;
212 setFrequency(freq);
213 setGain(gainDb);
214 }
215
221 void setNotch(float freq, float Q = 10.0f)
222 {
224 slopeDb_ = 12;
225 numStages_ = 1;
226 setFrequency(freq);
227 setResonance(Q);
228 }
229
235 void setAllPass(float freq, float Q = 0.707f)
236 {
238 slopeDb_ = 12;
239 numStages_ = 1;
240 setFrequency(freq);
241 setResonance(Q);
242 }
243
249 void setTilt(float centerFreq, float gainDb)
250 {
252 slopeDb_ = 12;
253 numStages_ = 1;
254 setFrequency(centerFreq);
255 setGain(gainDb);
256 }
257
267 void setMatchedPeak(bool enabled) noexcept
268 {
269 matchedPeak_.store(enabled, std::memory_order_relaxed);
270 }
271
273 [[nodiscard]] bool isMatchedPeak() const noexcept
274 {
275 return matchedPeak_.load(std::memory_order_relaxed);
276 }
277
279 [[nodiscard]] Shape getShape() const noexcept { return shape_.load(std::memory_order_relaxed); }
280
282 [[nodiscard]] int getSlopeDb() const noexcept { return slopeDb_.load(std::memory_order_relaxed); }
283
285 struct CascadeInfo { bool hasFirstOrder = false; int numSecondOrder = 0; float qValues[4] = {}; };
286
297 [[nodiscard]] static CascadeInfo cascadeForSlope(int slopeDb, float userQ = 0.707f) noexcept
298 {
299 auto c = computeCascade(slopeDb);
300 CascadeInfo info;
301 info.hasFirstOrder = c.hasFirstOrder;
302 info.numSecondOrder = c.numSecondOrder;
303 for (int i = 0; i < c.numSecondOrder && i < 4; ++i) info.qValues[i] = c.qValues[i];
304 if (std::isfinite(userQ) && info.numSecondOrder > 0)
305 info.qValues[info.numSecondOrder - 1] *= userQ / 0.707f;
306 return info;
307 }
308
322 void setShape(Shape newShape, int slopeDb = 12) noexcept
323 {
324 newShape = static_cast<Shape>(std::clamp(static_cast<int>(newShape), 0,
325 static_cast<int>(Shape::Tilt)));
326 shape_ = newShape;
327 slopeDb_ = slopeDb;
328 switch (newShape)
329 {
330 case Shape::LowPass:
331 case Shape::HighPass:
332 numStages_ = slopeToStages(slopeDb);
333 break;
334 default:
335 slopeDb_ = 12;
336 numStages_ = 1;
337 break;
338 }
339 }
340
341 // -- Real-time parameter changes (Thread-safe) ---------------------------
342
347 void setFrequency(float freq) noexcept
348 {
349 if (!std::isfinite(freq)) return;
350 targetFreq_.store(freq, std::memory_order_relaxed);
351 }
352
363 void setResonance(float Q) noexcept
364 {
365 if (!std::isfinite(Q)) return;
366 targetRes_.store(Q, std::memory_order_relaxed);
367 }
368
373 void setGain(float dB) noexcept
374 {
375 if (!std::isfinite(dB)) return;
376 targetGain_.store(dB, std::memory_order_relaxed);
377 }
378
384 void setNonlinearity(T amount) noexcept
385 {
386 if (!std::isfinite(amount)) return;
387 targetNonlinearity_.store(static_cast<float>(std::clamp(amount, T(0), T(1))), std::memory_order_relaxed);
388 }
389
390 // -- Analog drift --------------------------------------------------------
391
401 void enableAnalogDrift(AnalogRandom::AnalogComponent component, float intensity = 0.5f)
402 {
403 if (!std::isfinite(intensity)) return;
404 driftIntensity_.store(intensity, std::memory_order_relaxed);
405 driftGen_.setAnalogDefault(component);
407 driftEnabled_.store(true, std::memory_order_release);
408 }
409
413 void disableAnalogDrift() noexcept { driftEnabled_.store(false, std::memory_order_relaxed); }
414
415 // -- Processing ----------------------------------------------------------
416
424 void processBlock(AudioBufferView<T> buffer) noexcept
425 {
426 DenormalGuard guard;
427 const int nCh = std::min(buffer.getNumChannels(), MaxChannels);
428 const int nS = buffer.getNumSamples();
429
430 // Front-door non-finite guard (M-006 C1): the recursive biquad cascade
431 // latches a NaN/Inf input permanently. Scrub non-finite input to 0
432 // before the cascade. No-op on finite input (metrics byte-identical).
433 for (int ch = 0; ch < nCh; ++ch)
434 {
435 T* d = buffer.getChannel(ch);
436 for (int i = 0; i < nS; ++i)
437 if (!std::isfinite(d[i])) d[i] = T(0);
438 }
439
440 // Update smoothers with latest atomic targets
441 freqSmoother_.setTargetValue(targetFreq_.load(std::memory_order_relaxed));
442 resSmoother_.setTargetValue(targetRes_.load(std::memory_order_relaxed));
443 gainSmoother_.setTargetValue(targetGain_.load(std::memory_order_relaxed));
444 T nonLin = static_cast<T>(targetNonlinearity_.load(std::memory_order_relaxed));
445 const bool drift = driftEnabled_.load(std::memory_order_relaxed);
446 const float driftInt = driftIntensity_.load(std::memory_order_relaxed);
447
448 const bool needSmoothing = freqSmoother_.isSmoothing() || resSmoother_.isSmoothing() || gainSmoother_.isSmoothing();
449 const bool dynamicPath = needSmoothing || drift || (nonLin > T(0));
450
451 constexpr int kChunkSize = 16; // Optimized chunk size for Biquad coefficient updates
452
453 if (!dynamicPath)
454 {
455 // -- Fast Path: SIMD Friendly, Outer Channel Loop --
456 if (nS > 0)
457 {
458 float f = freqSmoother_.getCurrentValue(); // Value is static
459 float q = resSmoother_.getCurrentValue();
460 float g = gainSmoother_.getCurrentValue();
461
462 float nyquist = static_cast<float>(spec_.sampleRate) * 0.499f;
463 f = std::clamp(f, 10.0f, nyquist);
464 q = std::max(q, 0.1f);
465
466 // Skip the trig-heavy coefficient rebuild when absolutely
467 // nothing changed since the last block (the common case).
468 const Shape sh = shape_.load(std::memory_order_relaxed);
469 const int sdb = slopeDb_.load(std::memory_order_relaxed);
470 const bool mp = matchedPeak_.load(std::memory_order_relaxed);
471 const float ssl = shelfSlope_.load(std::memory_order_relaxed);
472 if (f != lastFreq_ || q != lastQ_ || g != lastGain_ ||
473 sh != lastShape_ || sdb != lastSlopeDb_ || mp != lastMatched_ ||
474 ssl != lastShelfSlope_)
475 {
476 updateCoefficients(f, q, g);
477 lastFreq_ = f; lastQ_ = q; lastGain_ = g;
478 lastShape_ = sh; lastSlopeDb_ = sdb; lastMatched_ = mp;
479 lastShelfSlope_ = ssl;
480 }
481
482 const int ns = numStages_.load(std::memory_order_relaxed);
483 for (int ch = 0; ch < nCh; ++ch)
484 {
485 T* channelData = buffer.getChannel(ch);
486 for (int i = 0; i < nS; ++i)
487 {
488 // The cascade stays in the biquad core's own precision:
489 // re-quantising to T between stages would put back part
490 // of the error the double core exists to remove.
491 double sample = static_cast<double>(channelData[i]);
492 for (int s = 0; s < ns; ++s)
493 sample = stages_[s].processSampleCore(sample, ch);
494 channelData[i] = static_cast<T>(sample);
495 }
496 }
497 }
498 }
499 else
500 {
501 // -- Dynamic Path: Modulated / Smoothed (Chunked updates) --
502 // Processes audio in small chunks to avoid per-sample trigonometric calculations.
503 for (int i = 0; i < nS; ++i)
504 {
505 float freq = freqSmoother_.getNextValue();
506 float res = resSmoother_.getNextValue();
507 float gain = gainSmoother_.getNextValue();
508
509 // Only calculate trig/coefficients every kChunkSize samples to save CPU
510 if (i % kChunkSize == 0)
511 {
512 float driftValue = drift ? (driftGen_.getNextSample() * driftInt) : 0.0f;
513 freq *= (1.0f + driftValue);
514
515 if (nonLin > T(0))
516 {
517 // Signal-dependent cutoff modulation ("dielectric" FM,
518 // chunk-rate). Reformulated as a bounded depth control:
519 // freq *= 1 + depth * 2 * g(level), g = x/(1+x) in [0,1)
520 // so depth -> 0 is exactly neutral and depth = 1 sweeps
521 // up to one octave on loud material. (The previous
522 // |2-(x+n)/n| form DIVERGED as the knob approached 0.)
523 T avgAbs = T(0);
524 for (int ch = 0; ch < nCh; ++ch)
525 avgAbs += std::abs(buffer.getChannel(ch)[i]);
526 avgAbs /= static_cast<T>(nCh);
527
528 const T bounded = avgAbs / (T(1) + avgAbs);
529 freq *= static_cast<float>(T(1) + nonLin * T(2) * bounded);
530 }
531
532 float nyquist = static_cast<float>(spec_.sampleRate) * 0.499f;
533 updateCoefficients(std::clamp(freq, 10.0f, nyquist), std::max(res, 0.1f), gain);
534 lastFreq_ = -1.0f; // invalidate the static-path cache
535 }
536 else if (drift)
537 {
538 (void)driftGen_.getNextSample(); // Advance LFO phase to keep sync
539 }
540
541 // Process inner loops
542 const int ns = numStages_.load(std::memory_order_relaxed);
543 for (int ch = 0; ch < nCh; ++ch)
544 {
545 double sample = static_cast<double>(buffer.getChannel(ch)[i]);
546 for (int s = 0; s < ns; ++s)
547 sample = stages_[s].processSampleCore(sample, ch);
548 buffer.getChannel(ch)[i] = static_cast<T>(sample);
549 }
550 }
551 }
552 }
553
565 T processSample(T input, int channel) noexcept
566 {
567 if (channel < 0 || channel >= MaxChannels) return input;
568 double sample = static_cast<double>(input);
569 const int ns = numStages_.load(std::memory_order_relaxed);
570 for (int s = 0; s < ns; ++s)
571 sample = stages_[s].processSampleCore(sample, channel);
572 return static_cast<T>(sample);
573 }
574
583 void applyParametersNow() noexcept
584 {
585 freqSmoother_.setTargetValue(targetFreq_.load(std::memory_order_relaxed));
586 resSmoother_.setTargetValue(targetRes_.load(std::memory_order_relaxed));
587 gainSmoother_.setTargetValue(targetGain_.load(std::memory_order_relaxed));
591
592 float f = freqSmoother_.getCurrentValue();
593 float q = resSmoother_.getCurrentValue();
594 float g = gainSmoother_.getCurrentValue();
595 const float nyquist = static_cast<float>(spec_.sampleRate) * 0.499f;
596 f = std::clamp(f, 10.0f, nyquist);
597 q = std::max(q, 0.1f);
598
599 updateCoefficients(f, q, g);
600 lastFreq_ = f; lastQ_ = q; lastGain_ = g;
601 lastShape_ = shape_.load(std::memory_order_relaxed);
602 lastSlopeDb_ = slopeDb_.load(std::memory_order_relaxed);
603 lastMatched_ = matchedPeak_.load(std::memory_order_relaxed);
604 lastShelfSlope_ = shelfSlope_.load(std::memory_order_relaxed);
605 }
606
607
609 [[nodiscard]] std::vector<uint8_t> getState() const
610 {
611 StateWriter w(stateId("FENG"), 1);
612 w.write("shape", static_cast<int32_t>(shape_.load(std::memory_order_relaxed)));
613 w.write("slope", slopeDb_.load(std::memory_order_relaxed));
614 w.write("shelfSlope", shelfSlope_.load(std::memory_order_relaxed));
615 w.write("freq", targetFreq_.load(std::memory_order_relaxed));
616 w.write("res", targetRes_.load(std::memory_order_relaxed));
617 w.write("gain", targetGain_.load(std::memory_order_relaxed));
618 w.write("nonlin", static_cast<float>(targetNonlinearity_.load(std::memory_order_relaxed)));
619 return w.blob();
620 }
621
623 bool setState(const uint8_t* data, size_t size)
624 {
625 StateReader r(data, size);
626 if (!r.isValid() || r.processorId() != stateId("FENG")) return false;
627 const auto shape = static_cast<Shape>(r.read("shape", 0));
628 const float freq = r.read("freq", 1000.0f);
629 const float res = r.read("res", 0.707f);
630 const float gain = r.read("gain", 0.0f);
631 switch (shape)
632 {
633 case Shape::LowShelf:
634 setLowShelf(freq, gain, r.read("shelfSlope", 1.0f));
635 break;
636 case Shape::HighShelf:
637 setHighShelf(freq, gain, r.read("shelfSlope", 1.0f));
638 break;
639 case Shape::Peak:
640 setPeaking(freq, gain, res);
641 break;
642 case Shape::Tilt:
643 setTilt(freq, gain);
644 break;
645 default:
646 setShape(shape, r.read("slope", 12));
647 setFrequency(freq);
648 setResonance(res);
649 setGain(gain);
650 break;
651 }
652 setNonlinearity(static_cast<T>(r.read("nonlin", 0.0f)));
653 return true;
654 }
655
656protected:
657 static constexpr int kMaxStages = 4;
658 static constexpr int kMaxOrder = 8;
659
661 {
662 int order = 0;
663 bool hasFirstOrder = false;
665 float qValues[kMaxStages] {};
666 };
667
668 static ButterworthCascade computeCascade(int slopeDb) noexcept
669 {
670 static constexpr float qTable[kMaxOrder + 1][kMaxStages] = {
671 {}, {},
672 { 0.7071f },
673 { 1.0f },
674 { 0.5412f, 1.3066f },
675 { 0.6180f, 1.6180f },
676 { 0.5176f, 0.7071f, 1.9319f },
677 { 0.5549f, 0.8019f, 2.2470f },
678 { 0.5098f, 0.6013f, 0.9000f, 2.5628f }
679 };
680
681 ButterworthCascade result {};
682 result.order = std::clamp(slopeDb / 6, 1, kMaxOrder);
683 result.hasFirstOrder = (result.order % 2 != 0);
684 result.numSecondOrder = result.order / 2;
685 for (int i = 0; i < result.numSecondOrder; ++i)
686 result.qValues[i] = qTable[result.order][i];
687 return result;
688 }
689
690 static int slopeToStages(int slopeDb) noexcept
691 {
692 int order = std::clamp(slopeDb / 6, 1, kMaxOrder);
693 return (order + 1) / 2;
694 }
695
696 void updateCoefficients(float freq, float Q, float gainDb) noexcept
697 {
698 double sr = spec_.sampleRate;
699 double f = static_cast<double>(freq);
700
701 // Snapshot atomic topology once.
702 const Shape sh = shape_.load(std::memory_order_relaxed);
703 const int sdb = slopeDb_.load(std::memory_order_relaxed);
704 const double ssl = static_cast<double>(shelfSlope_.load(std::memory_order_relaxed));
705
706 auto cascade = computeCascade(sdb);
707 int stageIdx = 0;
708
709 if (cascade.hasFirstOrder)
710 {
711 BiquadCoeffs c;
712 switch (sh)
713 {
716 default: c = BiquadCoeffs::makeFirstOrderLowPass(sr, f); break;
717 }
718 stages_[stageIdx++].setCoeffs(c);
719 }
720
721 for (int s = 0; s < cascade.numSecondOrder; ++s)
722 {
723 float stageQ = cascade.qValues[s];
724
725 if (sh == Shape::Peak || sh == Shape::BandPass ||
726 sh == Shape::Notch || sh == Shape::AllPass)
727 stageQ = Q;
728 else if ((sh == Shape::LowPass || sh == Shape::HighPass) &&
729 s == cascade.numSecondOrder - 1)
730 {
731 // The user Q scales the FINAL (most resonant) stage of the
732 // Butterworth cascade: at the 0.707 default the ratio is
733 // exactly 1 (bit-identical Butterworth), larger values add the
734 // classic resonant peak at the cutoff. Before this, the table
735 // silently overrode the user Q for every LP/HP slope, so
736 // setLowPass(f, 5.0f) sounded identical to Butterworth and
737 // setResonance() was dead for LP/HP.
738 stageQ *= Q / 0.707f;
739 }
740
741 BiquadCoeffs c;
742 switch (sh)
743 {
744 case Shape::LowPass: c = BiquadCoeffs::makeLowPass(sr, f, stageQ); break;
745 case Shape::HighPass: c = BiquadCoeffs::makeHighPass(sr, f, stageQ); break;
746 case Shape::BandPass: c = BiquadCoeffs::makeBandPass(sr, f, stageQ); break;
747 case Shape::Peak:
748 c = matchedPeak_.load(std::memory_order_relaxed)
749 ? BiquadCoeffs::makePeakMatched(sr, f, stageQ, gainDb)
750 : BiquadCoeffs::makePeak(sr, f, stageQ, gainDb);
751 break;
752 case Shape::LowShelf: c = BiquadCoeffs::makeLowShelf(sr, f, gainDb, ssl); break;
753 case Shape::HighShelf: c = BiquadCoeffs::makeHighShelf(sr, f, gainDb, ssl); break;
754 case Shape::Notch: c = BiquadCoeffs::makeNotch(sr, f, stageQ); break;
755 case Shape::AllPass: c = BiquadCoeffs::makeAllPass(sr, f, stageQ); break;
756 case Shape::Tilt: c = BiquadCoeffs::makeTilt(sr, f, gainDb); break;
757 }
758 stages_[stageIdx++].setCoeffs(c);
759 }
760
761 numStages_ = stageIdx;
762 }
763
765 // Topology state is atomic: setLowPass()/setShape()/... may be called from a
766 // control thread while processBlock() reads these on the audio thread.
767 // (Reads happen once per block, so atomics impose no hot-path cost.)
768 std::atomic<Shape> shape_ { Shape::LowPass };
769 std::atomic<int> numStages_ { 1 };
770 std::atomic<int> slopeDb_ { 12 };
771 std::atomic<float> shelfSlope_ { 1.0f };
772 std::atomic<bool> matchedPeak_ { false };
773
774 std::array<Biquad<T, MaxChannels>, kMaxStages> stages_ {};
775
778
779 // Atomics for thread-safe UI->Audio communication
780 std::atomic<float> targetFreq_{1000.0f};
781 std::atomic<float> targetRes_{0.707f};
782 std::atomic<float> targetGain_{0.0f};
783 std::atomic<float> targetNonlinearity_{0.0f};
784
785 // Atomic: enable/disable may come from a control thread while the audio
786 // thread reads them each block (the generator itself is only reconfigured
787 // from the setup thread, as enableAnalogDrift documents).
788 std::atomic<bool> driftEnabled_ { false };
789 std::atomic<float> driftIntensity_ { 0.0f };
791
792 // Static-path coefficient cache (skip rebuilds when nothing changed).
793 float lastFreq_ = -1.0f;
794 float lastQ_ = -1.0f;
795 float lastGain_ = -1e9f;
797 int lastSlopeDb_ = -1;
798 bool lastMatched_ = false;
799 float lastShelfSlope_ = -1.0f;
800};
801
802} // namespace dspark
Main generator class for analog-style random modulation.
void prepare(double sampleRate) noexcept
Prepare the generator with the audio sample rate.
void setAnalogDefault(AnalogComponent component) noexcept
Real getNextSample() noexcept
Generate and return the next modulation sample.
Non-owning view over audio channel data.
Definition AudioBuffer.h:50
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
Professional multi-mode filter with cascaded biquad stages.
Definition Filters.h:72
Smoothers::StateVariableSmoother freqSmoother_
Definition Filters.h:776
void setNonlinearity(T amount) noexcept
Sets the nonlinearity amount. Thread-safe.
Definition Filters.h:384
int getSlopeDb() const noexcept
Returns the active slope in dB/oct (LP/HP only - others = 12).
Definition Filters.h:282
std::atomic< float > targetGain_
Definition Filters.h:782
void setHighShelf(float freq, float gainDb, float slope=1.0f)
Configures a high-shelf EQ filter.
Definition Filters.h:206
static constexpr int kMaxOrder
Definition Filters.h:658
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
Definition Filters.h:609
void setLowPass(float freq, float Q=0.707f, int slopeDb=12)
Configures a low-pass filter.
Definition Filters.h:130
void setHighPass(float freq, float Q=0.707f, int slopeDb=12)
Configures a high-pass filter.
Definition Filters.h:145
std::atomic< Shape > shape_
Definition Filters.h:768
static constexpr int kMaxStages
Definition Filters.h:657
Smoothers::LinearSmoother resSmoother_
Definition Filters.h:777
std::atomic< float > targetFreq_
Definition Filters.h:780
Shape
Supported filter shapes.
Definition Filters.h:81
Smoothers::LinearSmoother gainSmoother_
Definition Filters.h:777
void setTilt(float centerFreq, float gainDb)
Configures a tilt EQ filter (boost highs/cut lows or vice versa).
Definition Filters.h:249
void applyParametersNow() noexcept
Applies pending parameter targets immediately and rebuilds the coefficients (no smoothing: values jum...
Definition Filters.h:583
void setAllPass(float freq, float Q=0.707f)
Configures an all-pass filter (shifts phase, flat frequency response).
Definition Filters.h:235
std::atomic< float > shelfSlope_
Definition Filters.h:771
static CascadeInfo cascadeForSlope(int slopeDb, float userQ=0.707f) noexcept
Returns the exact Butterworth cascade (first-order flag + per-stage Q values) used internally for a g...
Definition Filters.h:297
void setLowShelf(float freq, float gainDb, float slope=1.0f)
Configures a low-shelf EQ filter.
Definition Filters.h:190
void setBandPass(float freq, float Q=0.707f)
Configures a band-pass filter (fixed 12 dB/oct).
Definition Filters.h:159
std::atomic< bool > matchedPeak_
Definition Filters.h:772
std::atomic< int > numStages_
Definition Filters.h:769
std::atomic< bool > driftEnabled_
Definition Filters.h:788
std::atomic< float > targetNonlinearity_
Definition Filters.h:783
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
Definition Filters.h:623
void reset() noexcept
Resets the internal state of all cascaded biquads and smoothers. Prevents clicks when relocating play...
Definition Filters.h:114
void setGain(float dB) noexcept
Sets the target gain in dB. Thread-safe.
Definition Filters.h:373
void updateCoefficients(float freq, float Q, float gainDb) noexcept
Definition Filters.h:696
std::atomic< float > targetRes_
Definition Filters.h:781
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an entire block of audio data. Internally branches into static, smoothed,...
Definition Filters.h:424
std::atomic< float > driftIntensity_
Definition Filters.h:789
void setFrequency(float freq) noexcept
Sets the target cutoff/center frequency. Thread-safe.
Definition Filters.h:347
static ButterworthCascade computeCascade(int slopeDb) noexcept
Definition Filters.h:668
std::array< Biquad< T, MaxChannels >, kMaxStages > stages_
Definition Filters.h:774
void setResonance(float Q) noexcept
Sets the target resonance/Q. Thread-safe.
Definition Filters.h:363
bool isMatchedPeak() const noexcept
Returns whether Peak bells use the matched (de-cramped) design.
Definition Filters.h:273
Shape getShape() const noexcept
Returns the active filter shape.
Definition Filters.h:279
void enableAnalogDrift(AnalogRandom::AnalogComponent component, float intensity=0.5f)
Enables analog-style low-frequency modulation of the cutoff.
Definition Filters.h:401
AnalogRandom::Generator< float > driftGen_
Definition Filters.h:790
float lastShelfSlope_
Shelf S: a slope-only change must rebuild coeffs.
Definition Filters.h:799
void prepare(const AudioSpec &spec)
Initializes the filter engine with the current audio specification.
Definition Filters.h:93
void setPeaking(float freq, float gainDb, float Q=1.0f)
Configures a peaking / bell EQ filter.
Definition Filters.h:174
T processSample(T input, int channel) noexcept
Processes a single sample without parameter smoothing or coefficient updates.
Definition Filters.h:565
void setNotch(float freq, float Q=10.0f)
Configures a notch (band-reject) filter.
Definition Filters.h:221
std::atomic< int > slopeDb_
Definition Filters.h:770
void setShape(Shape newShape, int slopeDb=12) noexcept
Switches the filter topology while keeping the current frequency, resonance, and gain unchanged.
Definition Filters.h:322
void disableAnalogDrift() noexcept
Disables analog-style drift modulation. Thread-safe.
Definition Filters.h:413
static int slopeToStages(int slopeDb) noexcept
Definition Filters.h:690
void setMatchedPeak(bool enabled) noexcept
Selects the Orfanidis matched (de-cramped) design for Peak bells.
Definition Filters.h:267
Tolerant reader: missing keys yield defaults, unknown keys are skipped.
Definition StateBlob.h:160
float read(const char *key, float defaultValue) const
Reads a float, or defaultValue when the key is absent.
Definition StateBlob.h:203
bool isValid() const noexcept
Definition StateBlob.h:198
uint32_t processorId() const noexcept
Definition StateBlob.h:199
Serializes key/value parameters into a versioned blob.
Definition StateBlob.h:52
std::vector< uint8_t > blob() const
Finalizes and returns the blob.
Definition StateBlob.h:104
void write(const char *key, float value)
Writes a float parameter.
Definition StateBlob.h:70
Main namespace for the DSPark framework.
constexpr uint32_t stateId(const char(&tag)[5]) noexcept
Builds a FOURCC processor id, e.g. dspark::stateId("COMP").
Definition StateBlob.h:639
Describes the audio environment for a DSP processor.
Definition AudioSpec.h:35
constexpr bool isValid() const noexcept
Checks if the specification contains valid, processable parameters.
Definition AudioSpec.h:67
double sampleRate
Sample rate in Hz.
Definition AudioSpec.h:43
Stores normalised biquad coefficients (b0, b1, b2, a1, a2), always double.
Definition Biquad.h:91
static BiquadCoeffs makeFirstOrderHighPass(double sampleRate, double frequency) noexcept
First-order (6 dB/oct) high-pass filter.
Definition Biquad.h:418
static BiquadCoeffs makePeakMatched(double sampleRate, double freq, double Q, double gainDb) noexcept
Peaking filter with prescribed Nyquist gain (Orfanidis design).
Definition Biquad.h:223
static BiquadCoeffs makeHighPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
High-pass filter.
Definition Biquad.h:127
static BiquadCoeffs makeAllPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
All-pass filter.
Definition Biquad.h:366
static BiquadCoeffs makeBandPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Band-pass filter (constant 0 dB peak gain).
Definition Biquad.h:156
static BiquadCoeffs makePeak(double sampleRate, double freq, double Q, double gainDb) noexcept
Peak (parametric EQ) filter.
Definition Biquad.h:185
static BiquadCoeffs makeFirstOrderLowPass(double sampleRate, double frequency) noexcept
First-order (6 dB/oct) low-pass filter.
Definition Biquad.h:395
static BiquadCoeffs makeTilt(double sampleRate, double pivotFreq, double gainDb) noexcept
Creates a first-order tilt filter.
Definition Biquad.h:444
static BiquadCoeffs makeLowPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Low-pass filter.
Definition Biquad.h:103
static BiquadCoeffs makeLowShelf(double sampleRate, double freq, double gainDb, double slope=1.0) noexcept
Low-shelf filter.
Definition Biquad.h:283
static BiquadCoeffs makeHighShelf(double sampleRate, double freq, double gainDb, double slope=1.0) noexcept
High-shelf filter.
Definition Biquad.h:313
static BiquadCoeffs makeNotch(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Notch (band-reject) filter.
Definition Biquad.h:342
Per-stage Butterworth cascade layout for an LP/HP slope (for analysis).
Definition Filters.h:285
Linear ramp smoother for predictable, uniform interpolation.
Definition Smoothers.h:56
void reset(double sampleRate, float rampTimeMilliseconds, float initialValue=0.0f) noexcept
Definition Smoothers.h:327
void setTargetValue(float newTarget) noexcept
Definition Smoothers.h:336
bool isSmoothing() const noexcept
Definition Smoothers.h:364
float getCurrentValue() const noexcept
Definition Smoothers.h:64
Second-order state variable filter (SVF) smoother (TPT implementation).
Definition Smoothers.h:245
float getCurrentValue() const noexcept
Definition Smoothers.h:253
void reset(double sampleRate, float timeConstantMilliseconds, float q=0.707f, float initialValue=0.0f) noexcept
Definition Smoothers.h:618
void setTargetValue(float newTarget) noexcept
Definition Smoothers.h:645