DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
Filters.h
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
43#include "../Core/AudioBuffer.h"
44#include "../Core/AudioSpec.h"
45#include "../Core/Biquad.h"
46#include "../Core/DspMath.h"
47#include "../Core/Smoothers.h"
48#include "../Core/AnalogRandom.h"
49#include "../Core/DenormalGuard.h"
50#include "../Core/StateBlob.h"
51
52#include <algorithm>
53#include <array>
54#include <atomic>
55#include <cmath>
56#include <cstdint>
57#include <vector>
58
59namespace dspark {
60
71template <typename T, int MaxChannels = 16>
73{
74public:
75 // Removed virtual destructor to avoid vptr overhead and maintain cache alignment.
76 ~FilterEngine() = default;
77
81 enum class Shape
82 {
85 };
86
87 // -- Lifecycle -----------------------------------------------------------
88
94 void prepare(const AudioSpec& spec)
95 {
96 if (!spec.isValid()) return;
97 spec_ = spec;
98 freqSmoother_.reset(spec.sampleRate, 30.0f, 0.707f, 1000.0f);
99 resSmoother_.reset(spec.sampleRate, 20.0f, 0.707f);
100 gainSmoother_.reset(spec.sampleRate, 20.0f, 0.0f);
101 // Coefficients depend on the sample rate: force a rebuild on the next
102 // block. (Without this, re-preparing at a new rate with unchanged
103 // parameters kept the OLD rate's coefficients: the cutoff landed at
104 // freq * newRate / oldRate.)
105 lastFreq_ = -1.0f;
106 if (driftEnabled_.load(std::memory_order_relaxed))
107 driftGen_.prepare(spec.sampleRate); // re-sync the drift LFO clock
108 reset();
109 }
110
115 void reset() noexcept
116 {
117 for (auto& stage : stages_) stage.reset();
118 // A pinned drift restarts from its seed, so a reset render replays it.
119 if (const std::uint64_t seed = driftSeed_.load(std::memory_order_relaxed); seed != 0)
120 driftGen_.reseed(seed);
124 }
125
126 // -- Configuration -------------------------------------------------------
127
134 void setLowPass(float freq, float Q = 0.707f, int slopeDb = 12)
135 {
137 slopeDb_ = slopeDb;
138 numStages_ = slopeToStages(slopeDb);
139 setFrequency(freq);
140 setResonance(Q);
141 }
142
149 void setHighPass(float freq, float Q = 0.707f, int slopeDb = 12)
150 {
152 slopeDb_ = slopeDb;
153 numStages_ = slopeToStages(slopeDb);
154 setFrequency(freq);
155 setResonance(Q);
156 }
157
163 void setBandPass(float freq, float Q = 0.707f)
164 {
166 slopeDb_ = 12;
167 numStages_ = 1;
168 setFrequency(freq);
169 setResonance(Q);
170 }
171
178 void setPeaking(float freq, float gainDb, float Q = 1.0f)
179 {
181 slopeDb_ = 12;
182 numStages_ = 1;
183 setFrequency(freq);
184 setResonance(Q);
185 setGain(gainDb);
186 }
187
194 void setLowShelf(float freq, float gainDb, float slope = 1.0f)
195 {
197 slopeDb_ = 12;
198 numStages_ = 1;
199 setShelfSlope(slope);
200 setFrequency(freq);
201 setGain(gainDb);
202 }
203
210 void setHighShelf(float freq, float gainDb, float slope = 1.0f)
211 {
213 slopeDb_ = 12;
214 numStages_ = 1;
215 setShelfSlope(slope);
216 setFrequency(freq);
217 setGain(gainDb);
218 }
219
225 void setNotch(float freq, float Q = 10.0f)
226 {
228 slopeDb_ = 12;
229 numStages_ = 1;
230 setFrequency(freq);
231 setResonance(Q);
232 }
233
239 void setAllPass(float freq, float Q = 0.707f)
240 {
242 slopeDb_ = 12;
243 numStages_ = 1;
244 setFrequency(freq);
245 setResonance(Q);
246 }
247
253 void setTilt(float centerFreq, float gainDb)
254 {
256 slopeDb_ = 12;
257 numStages_ = 1;
258 setFrequency(centerFreq);
259 setGain(gainDb);
260 }
261
271 void setMatchedPeak(bool enabled) noexcept
272 {
273 matchedPeak_.store(enabled, std::memory_order_relaxed);
274 }
275
277 [[nodiscard]] bool isMatchedPeak() const noexcept
278 {
279 return matchedPeak_.load(std::memory_order_relaxed);
280 }
281
294 void setShelfSlope(float slope) noexcept
295 {
296 if (!std::isfinite(slope)) return;
297 shelfSlope_.store(std::clamp(slope, 0.0001f, 1.0f), std::memory_order_relaxed);
298 }
299
301 [[nodiscard]] float getShelfSlope() const noexcept
302 {
303 return shelfSlope_.load(std::memory_order_relaxed);
304 }
305
307 [[nodiscard]] Shape getShape() const noexcept { return shape_.load(std::memory_order_relaxed); }
308
310 [[nodiscard]] int getSlopeDb() const noexcept { return slopeDb_.load(std::memory_order_relaxed); }
311
313 struct CascadeInfo { bool hasFirstOrder = false; int numSecondOrder = 0; float qValues[4] = {}; };
314
325 [[nodiscard]] static CascadeInfo cascadeForSlope(int slopeDb, float userQ = 0.707f) noexcept
326 {
327 auto c = computeCascade(slopeDb);
328 CascadeInfo info;
329 info.hasFirstOrder = c.hasFirstOrder;
330 info.numSecondOrder = c.numSecondOrder;
331 for (int i = 0; i < c.numSecondOrder && i < 4; ++i) info.qValues[i] = c.qValues[i];
332 if (std::isfinite(userQ) && info.numSecondOrder > 0)
333 info.qValues[info.numSecondOrder - 1] *= userQ / 0.707f;
334 return info;
335 }
336
350 void setShape(Shape newShape, int slopeDb = 12) noexcept
351 {
352 newShape = static_cast<Shape>(std::clamp(static_cast<int>(newShape), 0,
353 static_cast<int>(Shape::Tilt)));
354 shape_ = newShape;
355 slopeDb_ = slopeDb;
356 switch (newShape)
357 {
358 case Shape::LowPass:
359 case Shape::HighPass:
360 numStages_ = slopeToStages(slopeDb);
361 break;
362 default:
363 slopeDb_ = 12;
364 numStages_ = 1;
365 break;
366 }
367 }
368
369 // -- Real-time parameter changes (Thread-safe) ---------------------------
370
375 void setFrequency(float freq) noexcept
376 {
377 if (!std::isfinite(freq)) return;
378 targetFreq_.store(freq, std::memory_order_relaxed);
379 }
380
391 void setResonance(float Q) noexcept
392 {
393 if (!std::isfinite(Q)) return;
394 targetRes_.store(Q, std::memory_order_relaxed);
395 }
396
401 void setGain(float dB) noexcept
402 {
403 if (!std::isfinite(dB)) return;
404 targetGain_.store(dB, std::memory_order_relaxed);
405 }
406
412 void setNonlinearity(T amount) noexcept
413 {
414 if (!std::isfinite(amount)) return;
415 targetNonlinearity_.store(static_cast<float>(std::clamp(amount, T(0), T(1))), std::memory_order_relaxed);
416 }
417
418 // -- Analog drift --------------------------------------------------------
419
429 void enableAnalogDrift(AnalogRandom::AnalogComponent component, float intensity = 0.5f)
430 {
431 if (!std::isfinite(intensity)) return;
432 driftIntensity_.store(intensity, std::memory_order_relaxed);
433 driftGen_.setAnalogDefault(component);
435 driftEnabled_.store(true, std::memory_order_release);
436 }
437
441 void disableAnalogDrift() noexcept { driftEnabled_.store(false, std::memory_order_relaxed); }
442
456 void setDriftSeed(std::uint64_t seed) noexcept
457 {
458 driftSeed_.store(seed, std::memory_order_relaxed);
459 if (seed != 0) driftGen_.reseed(seed);
460 }
461
464 [[nodiscard]] std::uint64_t getDriftSeed() const noexcept
465 {
466 return driftSeed_.load(std::memory_order_relaxed);
467 }
468
469 // -- Processing ----------------------------------------------------------
470
478 void processBlock(AudioBufferView<T> buffer) noexcept
479 {
480 DenormalGuard guard;
481 const int nCh = std::min(buffer.getNumChannels(), MaxChannels);
482 const int nS = buffer.getNumSamples();
483
484 // Front-door non-finite guard: the recursive biquad cascade latches
485 // a NaN/Inf input permanently. Scrub non-finite input to 0
486 // before the cascade. No-op on finite input (metrics byte-identical).
487 for (int ch = 0; ch < nCh; ++ch)
488 {
489 T* d = buffer.getChannel(ch);
490 for (int i = 0; i < nS; ++i)
491 if (!std::isfinite(d[i])) d[i] = T(0);
492 }
493
494 // Update smoothers with latest atomic targets
495 freqSmoother_.setTargetValue(targetFreq_.load(std::memory_order_relaxed));
496 resSmoother_.setTargetValue(targetRes_.load(std::memory_order_relaxed));
497 gainSmoother_.setTargetValue(targetGain_.load(std::memory_order_relaxed));
498 T nonLin = static_cast<T>(targetNonlinearity_.load(std::memory_order_relaxed));
499 const bool drift = driftEnabled_.load(std::memory_order_relaxed);
500 const float driftInt = driftIntensity_.load(std::memory_order_relaxed);
501
502 const bool needSmoothing = freqSmoother_.isSmoothing() || resSmoother_.isSmoothing() || gainSmoother_.isSmoothing();
503 const bool dynamicPath = needSmoothing || drift || (nonLin > T(0));
504
505 constexpr int kChunkSize = 16; // Optimized chunk size for Biquad coefficient updates
506
507 if (!dynamicPath)
508 {
509 // -- Fast Path: SIMD Friendly, Outer Channel Loop --
510 if (nS > 0)
511 {
512 float f = freqSmoother_.getCurrentValue(); // Value is static
513 float q = resSmoother_.getCurrentValue();
514 float g = gainSmoother_.getCurrentValue();
515
516 float nyquist = static_cast<float>(spec_.sampleRate) * 0.499f;
517 f = std::clamp(f, 10.0f, nyquist);
518 q = std::max(q, 0.1f);
519
520 // Skip the trig-heavy coefficient rebuild when absolutely
521 // nothing changed since the last block (the common case).
522 const Shape sh = shape_.load(std::memory_order_relaxed);
523 const int sdb = slopeDb_.load(std::memory_order_relaxed);
524 const bool mp = matchedPeak_.load(std::memory_order_relaxed);
525 const float ssl = shelfSlope_.load(std::memory_order_relaxed);
526 if (f != lastFreq_ || q != lastQ_ || g != lastGain_ ||
527 sh != lastShape_ || sdb != lastSlopeDb_ || mp != lastMatched_ ||
528 ssl != lastShelfSlope_)
529 {
530 updateCoefficients(f, q, g);
531 lastFreq_ = f; lastQ_ = q; lastGain_ = g;
532 lastShape_ = sh; lastSlopeDb_ = sdb; lastMatched_ = mp;
533 lastShelfSlope_ = ssl;
534 }
535
536 const int ns = numStages_.load(std::memory_order_relaxed);
537 for (int ch = 0; ch < nCh; ++ch)
538 {
539 T* channelData = buffer.getChannel(ch);
540 for (int i = 0; i < nS; ++i)
541 {
542 // The cascade stays in the biquad core's own precision:
543 // re-quantising to T between stages would put back part
544 // of the error the double core exists to remove.
545 double sample = static_cast<double>(channelData[i]);
546 for (int s = 0; s < ns; ++s)
547 sample = stages_[s].processSampleCore(sample, ch);
548 channelData[i] = static_cast<T>(sample);
549 }
550 }
551 }
552 }
553 else
554 {
555 // -- Dynamic Path: Modulated / Smoothed (Chunked updates) --
556 // Processes audio in small chunks to avoid per-sample trigonometric calculations.
557 for (int i = 0; i < nS; ++i)
558 {
559 float freq = freqSmoother_.getNextValue();
560 float res = resSmoother_.getNextValue();
561 float gain = gainSmoother_.getNextValue();
562
563 // Only calculate trig/coefficients every kChunkSize samples to save CPU
564 if (i % kChunkSize == 0)
565 {
566 float driftValue = drift ? (driftGen_.getNextSample() * driftInt) : 0.0f;
567 freq *= (1.0f + driftValue);
568
569 if (nonLin > T(0))
570 {
571 // Signal-dependent cutoff modulation ("dielectric" FM,
572 // chunk-rate). Reformulated as a bounded depth control:
573 // freq *= 1 + depth * 2 * g(level), g = x/(1+x) in [0,1)
574 // so depth -> 0 is exactly neutral and depth = 1 sweeps
575 // up to one octave on loud material. (The previous
576 // |2-(x+n)/n| form DIVERGED as the knob approached 0.)
577 T avgAbs = T(0);
578 for (int ch = 0; ch < nCh; ++ch)
579 avgAbs += std::abs(buffer.getChannel(ch)[i]);
580 avgAbs /= static_cast<T>(nCh);
581
582 const T bounded = avgAbs / (T(1) + avgAbs);
583 freq *= static_cast<float>(T(1) + nonLin * T(2) * bounded);
584 }
585
586 float nyquist = static_cast<float>(spec_.sampleRate) * 0.499f;
587 updateCoefficients(std::clamp(freq, 10.0f, nyquist), std::max(res, 0.1f), gain);
588 lastFreq_ = -1.0f; // invalidate the static-path cache
589 }
590 else if (drift)
591 {
592 (void)driftGen_.getNextSample(); // Advance LFO phase to keep sync
593 }
594
595 // Process inner loops
596 const int ns = numStages_.load(std::memory_order_relaxed);
597 for (int ch = 0; ch < nCh; ++ch)
598 {
599 double sample = static_cast<double>(buffer.getChannel(ch)[i]);
600 for (int s = 0; s < ns; ++s)
601 sample = stages_[s].processSampleCore(sample, ch);
602 buffer.getChannel(ch)[i] = static_cast<T>(sample);
603 }
604 }
605 }
606 }
607
619 T processSample(T input, int channel) noexcept
620 {
621 if (channel < 0 || channel >= MaxChannels) return input;
622 double sample = static_cast<double>(input);
623 const int ns = numStages_.load(std::memory_order_relaxed);
624 for (int s = 0; s < ns; ++s)
625 sample = stages_[s].processSampleCore(sample, channel);
626 return static_cast<T>(sample);
627 }
628
637 void applyParametersNow() noexcept
638 {
639 freqSmoother_.setTargetValue(targetFreq_.load(std::memory_order_relaxed));
640 resSmoother_.setTargetValue(targetRes_.load(std::memory_order_relaxed));
641 gainSmoother_.setTargetValue(targetGain_.load(std::memory_order_relaxed));
645
646 float f = freqSmoother_.getCurrentValue();
647 float q = resSmoother_.getCurrentValue();
648 float g = gainSmoother_.getCurrentValue();
649 const float nyquist = static_cast<float>(spec_.sampleRate) * 0.499f;
650 f = std::clamp(f, 10.0f, nyquist);
651 q = std::max(q, 0.1f);
652
653 updateCoefficients(f, q, g);
654 lastFreq_ = f; lastQ_ = q; lastGain_ = g;
655 lastShape_ = shape_.load(std::memory_order_relaxed);
656 lastSlopeDb_ = slopeDb_.load(std::memory_order_relaxed);
657 lastMatched_ = matchedPeak_.load(std::memory_order_relaxed);
658 lastShelfSlope_ = shelfSlope_.load(std::memory_order_relaxed);
659 }
660
661
663 [[nodiscard]] std::vector<uint8_t> getState() const
664 {
665 StateWriter w(stateId("FENG"), 1);
666 w.write("shape", static_cast<int32_t>(shape_.load(std::memory_order_relaxed)));
667 w.write("slope", slopeDb_.load(std::memory_order_relaxed));
668 w.write("shelfSlope", shelfSlope_.load(std::memory_order_relaxed));
669 w.write("freq", targetFreq_.load(std::memory_order_relaxed));
670 w.write("res", targetRes_.load(std::memory_order_relaxed));
671 w.write("gain", targetGain_.load(std::memory_order_relaxed));
672 w.write("nonlin", static_cast<float>(targetNonlinearity_.load(std::memory_order_relaxed)));
673 return w.blob();
674 }
675
677 bool setState(const uint8_t* data, size_t size)
678 {
679 StateReader r(data, size);
680 if (!r.isValid() || r.processorId() != stateId("FENG")) return false;
681 const auto shape = static_cast<Shape>(r.read("shape", 0));
682 const float freq = r.read("freq", 1000.0f);
683 const float res = r.read("res", 0.707f);
684 const float gain = r.read("gain", 0.0f);
685 switch (shape)
686 {
687 case Shape::LowShelf:
688 setLowShelf(freq, gain, r.read("shelfSlope", 1.0f));
689 break;
690 case Shape::HighShelf:
691 setHighShelf(freq, gain, r.read("shelfSlope", 1.0f));
692 break;
693 case Shape::Peak:
694 setPeaking(freq, gain, res);
695 break;
696 case Shape::Tilt:
697 setTilt(freq, gain);
698 break;
699 default:
700 setShape(shape, r.read("slope", 12));
701 setFrequency(freq);
702 setResonance(res);
703 setGain(gain);
704 break;
705 }
706 setNonlinearity(static_cast<T>(r.read("nonlin", 0.0f)));
707 return true;
708 }
709
710protected:
711 static constexpr int kMaxStages = 4;
712 static constexpr int kMaxOrder = 8;
713
715 {
716 int order = 0;
717 bool hasFirstOrder = false;
719 float qValues[kMaxStages] {};
720 };
721
722 static ButterworthCascade computeCascade(int slopeDb) noexcept
723 {
724 static constexpr float qTable[kMaxOrder + 1][kMaxStages] = {
725 {}, {},
726 { 0.7071f },
727 { 1.0f },
728 { 0.5412f, 1.3066f },
729 { 0.6180f, 1.6180f },
730 { 0.5176f, 0.7071f, 1.9319f },
731 { 0.5549f, 0.8019f, 2.2470f },
732 { 0.5098f, 0.6013f, 0.9000f, 2.5628f }
733 };
734
735 ButterworthCascade result {};
736 result.order = std::clamp(slopeDb / 6, 1, kMaxOrder);
737 result.hasFirstOrder = (result.order % 2 != 0);
738 result.numSecondOrder = result.order / 2;
739 for (int i = 0; i < result.numSecondOrder; ++i)
740 result.qValues[i] = qTable[result.order][i];
741 return result;
742 }
743
744 static int slopeToStages(int slopeDb) noexcept
745 {
746 int order = std::clamp(slopeDb / 6, 1, kMaxOrder);
747 return (order + 1) / 2;
748 }
749
750 void updateCoefficients(float freq, float Q, float gainDb) noexcept
751 {
752 double sr = spec_.sampleRate;
753 double f = static_cast<double>(freq);
754
755 // Snapshot atomic topology once.
756 const Shape sh = shape_.load(std::memory_order_relaxed);
757 const int sdb = slopeDb_.load(std::memory_order_relaxed);
758 const double ssl = static_cast<double>(shelfSlope_.load(std::memory_order_relaxed));
759
760 auto cascade = computeCascade(sdb);
761 int stageIdx = 0;
762
763 if (cascade.hasFirstOrder)
764 {
765 BiquadCoeffs c;
766 switch (sh)
767 {
770 default: c = BiquadCoeffs::makeFirstOrderLowPass(sr, f); break;
771 }
772 stages_[stageIdx++].setCoeffs(c);
773 }
774
775 for (int s = 0; s < cascade.numSecondOrder; ++s)
776 {
777 float stageQ = cascade.qValues[s];
778
779 if (sh == Shape::Peak || sh == Shape::BandPass ||
780 sh == Shape::Notch || sh == Shape::AllPass)
781 stageQ = Q;
782 else if ((sh == Shape::LowPass || sh == Shape::HighPass) &&
783 s == cascade.numSecondOrder - 1)
784 {
785 // The user Q scales the FINAL (most resonant) stage of the
786 // Butterworth cascade: at the 0.707 default the ratio is
787 // exactly 1 (bit-identical Butterworth), larger values add the
788 // classic resonant peak at the cutoff. Before this, the table
789 // silently overrode the user Q for every LP/HP slope, so
790 // setLowPass(f, 5.0f) sounded identical to Butterworth and
791 // setResonance() was dead for LP/HP.
792 stageQ *= Q / 0.707f;
793 }
794
795 BiquadCoeffs c;
796 switch (sh)
797 {
798 case Shape::LowPass: c = BiquadCoeffs::makeLowPass(sr, f, stageQ); break;
799 case Shape::HighPass: c = BiquadCoeffs::makeHighPass(sr, f, stageQ); break;
800 case Shape::BandPass: c = BiquadCoeffs::makeBandPass(sr, f, stageQ); break;
801 case Shape::Peak:
802 c = matchedPeak_.load(std::memory_order_relaxed)
803 ? BiquadCoeffs::makePeakMatched(sr, f, stageQ, gainDb)
804 : BiquadCoeffs::makePeak(sr, f, stageQ, gainDb);
805 break;
806 case Shape::LowShelf: c = BiquadCoeffs::makeLowShelf(sr, f, gainDb, ssl); break;
807 case Shape::HighShelf: c = BiquadCoeffs::makeHighShelf(sr, f, gainDb, ssl); break;
808 case Shape::Notch: c = BiquadCoeffs::makeNotch(sr, f, stageQ); break;
809 case Shape::AllPass: c = BiquadCoeffs::makeAllPass(sr, f, stageQ); break;
810 case Shape::Tilt: c = BiquadCoeffs::makeTilt(sr, f, gainDb); break;
811 }
812 stages_[stageIdx++].setCoeffs(c);
813 }
814
815 numStages_ = stageIdx;
816 }
817
819 // Topology state is atomic: setLowPass()/setShape()/... may be called from a
820 // control thread while processBlock() reads these on the audio thread.
821 // (Reads happen once per block, so atomics impose no hot-path cost.)
822 std::atomic<Shape> shape_ { Shape::LowPass };
823 std::atomic<int> numStages_ { 1 };
824 std::atomic<int> slopeDb_ { 12 };
825 std::atomic<float> shelfSlope_ { 1.0f };
826 std::atomic<bool> matchedPeak_ { false };
827
828 std::array<Biquad<T, MaxChannels>, kMaxStages> stages_ {};
829
832
833 // Atomics for thread-safe UI->Audio communication
834 std::atomic<float> targetFreq_{1000.0f};
835 std::atomic<float> targetRes_{0.707f};
836 std::atomic<float> targetGain_{0.0f};
837 std::atomic<float> targetNonlinearity_{0.0f};
838
839 // Atomic: enable/disable may come from a control thread while the audio
840 // thread reads them each block (the generator itself is only reconfigured
841 // from the setup thread, as enableAnalogDrift documents).
842 std::atomic<bool> driftEnabled_ { false };
843 std::atomic<float> driftIntensity_ { 0.0f };
844 std::atomic<std::uint64_t> driftSeed_ { 0 };
846
847 // Static-path coefficient cache (skip rebuilds when nothing changed).
848 float lastFreq_ = -1.0f;
849 float lastQ_ = -1.0f;
850 float lastGain_ = -1e9f;
852 int lastSlopeDb_ = -1;
853 bool lastMatched_ = false;
854 float lastShelfSlope_ = -1.0f;
855};
856
857} // namespace dspark
Main generator class for analog-style random modulation.
void reseed(std::uint64_t newSeed) noexcept
Request a lock-free reseed of the internal PRNG.
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:73
Smoothers::StateVariableSmoother freqSmoother_
Definition Filters.h:830
void setNonlinearity(T amount) noexcept
Sets the nonlinearity amount. Thread-safe.
Definition Filters.h:412
int getSlopeDb() const noexcept
Returns the active slope in dB/oct (LP/HP only - others = 12).
Definition Filters.h:310
std::atomic< float > targetGain_
Definition Filters.h:836
float getShelfSlope() const noexcept
Returns the shelf slope S used by the shelf shapes.
Definition Filters.h:301
void setHighShelf(float freq, float gainDb, float slope=1.0f)
Configures a high-shelf EQ filter.
Definition Filters.h:210
static constexpr int kMaxOrder
Definition Filters.h:712
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
Definition Filters.h:663
void setLowPass(float freq, float Q=0.707f, int slopeDb=12)
Configures a low-pass filter.
Definition Filters.h:134
void setHighPass(float freq, float Q=0.707f, int slopeDb=12)
Configures a high-pass filter.
Definition Filters.h:149
std::atomic< Shape > shape_
Definition Filters.h:822
static constexpr int kMaxStages
Definition Filters.h:711
Smoothers::LinearSmoother resSmoother_
Definition Filters.h:831
std::atomic< float > targetFreq_
Definition Filters.h:834
std::atomic< std::uint64_t > driftSeed_
0 = the instance's unique seed.
Definition Filters.h:844
Shape
Supported filter shapes.
Definition Filters.h:82
Smoothers::LinearSmoother gainSmoother_
Definition Filters.h:831
void setTilt(float centerFreq, float gainDb)
Configures a tilt EQ filter (boost highs/cut lows or vice versa).
Definition Filters.h:253
void applyParametersNow() noexcept
Applies pending parameter targets immediately and rebuilds the coefficients (no smoothing: values jum...
Definition Filters.h:637
void setAllPass(float freq, float Q=0.707f)
Configures an all-pass filter (shifts phase, flat frequency response).
Definition Filters.h:239
std::atomic< float > shelfSlope_
Definition Filters.h:825
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:325
void setLowShelf(float freq, float gainDb, float slope=1.0f)
Configures a low-shelf EQ filter.
Definition Filters.h:194
void setBandPass(float freq, float Q=0.707f)
Configures a band-pass filter (fixed 12 dB/oct).
Definition Filters.h:163
std::atomic< bool > matchedPeak_
Definition Filters.h:826
std::atomic< int > numStages_
Definition Filters.h:823
std::atomic< bool > driftEnabled_
Definition Filters.h:842
std::atomic< float > targetNonlinearity_
Definition Filters.h:837
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
Definition Filters.h:677
void setShelfSlope(float slope) noexcept
Sets the shelf slope S on its own (LowShelf/HighShelf shapes).
Definition Filters.h:294
void reset() noexcept
Resets the internal state of all cascaded biquads and smoothers. Prevents clicks when relocating play...
Definition Filters.h:115
void setGain(float dB) noexcept
Sets the target gain in dB. Thread-safe.
Definition Filters.h:401
void updateCoefficients(float freq, float Q, float gainDb) noexcept
Definition Filters.h:750
std::atomic< float > targetRes_
Definition Filters.h:835
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an entire block of audio data. Internally branches into static, smoothed,...
Definition Filters.h:478
std::atomic< float > driftIntensity_
Definition Filters.h:843
void setFrequency(float freq) noexcept
Sets the target cutoff/center frequency. Thread-safe.
Definition Filters.h:375
static ButterworthCascade computeCascade(int slopeDb) noexcept
Definition Filters.h:722
std::array< Biquad< T, MaxChannels >, kMaxStages > stages_
Definition Filters.h:828
void setResonance(float Q) noexcept
Sets the target resonance/Q. Thread-safe.
Definition Filters.h:391
bool isMatchedPeak() const noexcept
Returns whether Peak bells use the matched (de-cramped) design.
Definition Filters.h:277
Shape getShape() const noexcept
Returns the active filter shape.
Definition Filters.h:307
void enableAnalogDrift(AnalogRandom::AnalogComponent component, float intensity=0.5f)
Enables analog-style low-frequency modulation of the cutoff.
Definition Filters.h:429
AnalogRandom::Generator< float > driftGen_
Definition Filters.h:845
float lastShelfSlope_
Shelf S: a slope-only change must rebuild coeffs.
Definition Filters.h:854
std::uint64_t getDriftSeed() const noexcept
The drift seed set by setDriftSeed(); 0 when the drift uses the instance's own unique seed.
Definition Filters.h:464
void prepare(const AudioSpec &spec)
Initializes the filter engine with the current audio specification.
Definition Filters.h:94
void setPeaking(float freq, float gainDb, float Q=1.0f)
Configures a peaking / bell EQ filter.
Definition Filters.h:178
T processSample(T input, int channel) noexcept
Processes a single sample without parameter smoothing or coefficient updates.
Definition Filters.h:619
void setNotch(float freq, float Q=10.0f)
Configures a notch (band-reject) filter.
Definition Filters.h:225
std::atomic< int > slopeDb_
Definition Filters.h:824
void setDriftSeed(std::uint64_t seed) noexcept
Makes the analog drift reproducible.
Definition Filters.h:456
void setShape(Shape newShape, int slopeDb=12) noexcept
Switches the filter topology while keeping the current frequency, resonance, and gain unchanged.
Definition Filters.h:350
void disableAnalogDrift() noexcept
Disables analog-style drift modulation. Thread-safe.
Definition Filters.h:441
static int slopeToStages(int slopeDb) noexcept
Definition Filters.h:744
void setMatchedPeak(bool enabled) noexcept
Selects the analog-matched (de-cramped) design for Peak bells.
Definition Filters.h:271
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.
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
constexpr bool isValid() const noexcept
Checks if the specification contains valid, processable parameters.
Definition AudioSpec.h:71
double sampleRate
Sample rate in Hz.
Definition AudioSpec.h:45
Stores normalised biquad coefficients (b0, b1, b2, a1, a2), always double.
Definition Biquad.h:106
static BiquadCoeffs makeFirstOrderHighPass(double sampleRate, double frequency) noexcept
First-order (6 dB/oct) high-pass filter.
Definition Biquad.h:449
static BiquadCoeffs makePeakMatched(double sampleRate, double freq, double Q, double gainDb) noexcept
Analog-matched ("de-cramped") peaking filter (Vicanek design).
Definition Biquad.h:247
static BiquadCoeffs makeHighPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
High-pass filter.
Definition Biquad.h:142
static BiquadCoeffs makeAllPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
All-pass filter.
Definition Biquad.h:397
static BiquadCoeffs makeBandPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Band-pass filter (constant 0 dB peak gain).
Definition Biquad.h:171
static BiquadCoeffs makePeak(double sampleRate, double freq, double Q, double gainDb) noexcept
Peak (parametric EQ) filter.
Definition Biquad.h:200
static BiquadCoeffs makeFirstOrderLowPass(double sampleRate, double frequency) noexcept
First-order (6 dB/oct) low-pass filter.
Definition Biquad.h:426
static BiquadCoeffs makeTilt(double sampleRate, double pivotFreq, double gainDb) noexcept
Creates a first-order tilt filter.
Definition Biquad.h:475
static BiquadCoeffs makeLowPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Low-pass filter.
Definition Biquad.h:118
static BiquadCoeffs makeLowShelf(double sampleRate, double freq, double gainDb, double slope=1.0) noexcept
Low-shelf filter.
Definition Biquad.h:314
static BiquadCoeffs makeHighShelf(double sampleRate, double freq, double gainDb, double slope=1.0) noexcept
High-shelf filter.
Definition Biquad.h:344
static BiquadCoeffs makeNotch(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Notch (band-reject) filter.
Definition Biquad.h:373
Per-stage Butterworth cascade layout for an LP/HP slope (for analysis).
Definition Filters.h:313
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:625
void setTargetValue(float newTarget) noexcept
Definition Smoothers.h:652