28#include "../Core/AudioSpec.h"
29#include "../Core/AudioBuffer.h"
30#include "../Core/Biquad.h"
31#include "../Core/DenormalGuard.h"
32#include "../Core/DspMath.h"
33#include "../Core/FFT.h"
34#include "../Core/StateBlob.h"
55template <FloatType T,
int MaxBands = 16>
113 for (
int i = 0; i < MaxBands; ++i)
137 while (fftPow2 < targetFftSize) fftPow2 <<= 1;
147 pb.assign(
static_cast<size_t>(
lpBlock_ * 2), T(0));
159 lpDirty_.store(
true, std::memory_order_release);
183 const int gN = buffer.getNumSamples();
184 for (
int ch = 0; ch < buffer.getNumChannels(); ++ch)
186 T* d = buffer.getChannel(ch);
187 for (
int i = 0; i < gN; ++i)
188 if (!std::isfinite(d[i])) d[i] = T(0);
193 if (
configDirty_.exchange(
false, std::memory_order_acquire))
196 lpDirty_.store(
true, std::memory_order_release);
204 if (
lpDirty_.exchange(
false, std::memory_order_acquire))
212 const int activeBands =
numBands_.load(std::memory_order_relaxed);
213 for (
int i = 0; i < activeBands; ++i)
216 bands_[i].processBlock(buffer);
236 &&
configDirty_.exchange(
false, std::memory_order_acquire))
239 const int n =
numBands_.load(std::memory_order_relaxed);
240 for (
int i = 0; i < n; ++i)
241 bands_[i].applyParametersNow();
242 lpDirty_.store(
true, std::memory_order_release);
246 const int activeBands =
numBands_.load(std::memory_order_relaxed);
248 for (
int i = 0; i < activeBands; ++i)
251 sample =
bands_[i].processSample(sample, channel);
261 for (
int i = 0; i < MaxBands; ++i)
265 std::fill(pb.begin(), pb.end(), T(0));
276 void setBand(
int index, T frequency, T gainDb)
278 setBand(index, frequency, gainDb, T(0.707));
288 void setBand(
int index, T frequency, T gainDb, T q)
312 if (index < 0 || index >= MaxBands)
return;
318 if (!std::isfinite(cfg.
q)) cfg.
q = prev.
q;
326 int currentBands =
numBands_.load(std::memory_order_relaxed);
327 if (index >= currentBands)
328 numBands_.store(index + 1, std::memory_order_relaxed);
340 int validCount = std::clamp(count, 1, MaxBands);
341 numBands_.store(validCount, std::memory_order_relaxed);
343 const T logMin = std::log(T(80));
344 const T logMax = std::log(T(16000));
346 for (
int i = 0; i < validCount; ++i)
348 T t = (validCount > 1) ?
static_cast<T
>(i) /
static_cast<T
>(validCount - 1) : T(0.5);
351 cfg.
frequency = std::exp(logMin + t * (logMax - logMin));
367 return numBands_.load(std::memory_order_relaxed);
393 if (index < 0 || index >= MaxBands)
return {};
404 if (index >= 0 && index < MaxBands)
407 bandEnabled_[index].store(enabled, std::memory_order_release);
423 const int m = std::clamp(
static_cast<int>(mode), 0,
427 lpDirty_.store(
true, std::memory_order_release);
433 return filterMode_.load(std::memory_order_relaxed);
455 softMode_.store(enabled, std::memory_order_relaxed);
462 return softMode_.load(std::memory_order_relaxed);
478 for (
int i = 0; i < numPoints; ++i)
479 magnitudes[i] = T(1);
481 const int activeBands =
numBands_.load(std::memory_order_relaxed);
482 for (
int b = 0; b < activeBands; ++b)
489 for (
int i = 0; i < numPoints; ++i)
492 for (
int s = 0; s < ns; ++s)
493 mag *= st[s].getMagnitude(
static_cast<double>(frequencies[i]),
spec_.
sampleRate);
494 magnitudes[i] =
static_cast<T
>(
static_cast<double>(magnitudes[i]) * mag);
511 [[nodiscard]] std::vector<uint8_t>
getState()
const
514 const int n =
numBands_.load(std::memory_order_relaxed);
515 w.
write(
"numBands", n);
517 w.
write(
"filterMode",
static_cast<int32_t
>(
filterMode_.load(std::memory_order_relaxed)));
520 for (
int i = 0; i < n; ++i)
523 std::snprintf(key,
sizeof(key),
"b%d.freq", i);
525 std::snprintf(key,
sizeof(key),
"b%d.gain", i);
526 w.
write(key,
static_cast<float>(cfg.
gain));
527 std::snprintf(key,
sizeof(key),
"b%d.q", i);
528 w.
write(key,
static_cast<float>(cfg.
q));
529 std::snprintf(key,
sizeof(key),
"b%d.type", i);
530 w.
write(key,
static_cast<int32_t
>(cfg.
type));
531 std::snprintf(key,
sizeof(key),
"b%d.slope", i);
533 std::snprintf(key,
sizeof(key),
"b%d.on", i);
544 const int n = std::clamp(r.
read(
"numBands", 0), 0, MaxBands);
548 r.
read(
"filterMode",
static_cast<int32_t
>(
filterMode_.load(std::memory_order_relaxed)))));
551 for (
int i = 0; i < n; ++i)
554 std::snprintf(key,
sizeof(key),
"b%d.freq", i);
556 std::snprintf(key,
sizeof(key),
"b%d.gain", i);
557 cfg.
gain =
static_cast<T
>(r.
read(key, 0.0f));
558 std::snprintf(key,
sizeof(key),
"b%d.q", i);
559 cfg.
q =
static_cast<T
>(r.
read(key, 0.707f));
560 std::snprintf(key,
sizeof(key),
"b%d.type", i);
562 std::snprintf(key,
sizeof(key),
"b%d.slope", i);
564 std::snprintf(key,
sizeof(key),
"b%d.on", i);
568 numBands_.store(n, std::memory_order_relaxed);
581 if (
softMode_.load(std::memory_order_relaxed))
583 const T absGain = std::abs(cfg.gain);
584 const T maxQ = T(1) + T(8) / (absGain + T(1));
585 q = std::min(q, maxQ);
596 const bool matched =
matchedBells_.load(std::memory_order_relaxed);
597 int activeBands =
numBands_.load(std::memory_order_relaxed);
599 for (
int i = 0; i < activeBands; ++i)
604 float freq =
static_cast<float>(cfg.frequency);
605 float gain =
static_cast<float>(cfg.gain);
606 float q =
static_cast<float>(
effectiveQ(cfg));
608 filter.setMatchedPeak(matched);
637 q = std::max(q, 0.05);
638 const double A = std::pow(10.0, std::abs(gainDb) / 40.0);
639 const double denom = A + 1.0 / A;
640 const double invS = (1.0 / (q * q) - 2.0) / denom + 1.0;
641 if (invS <= 1.0)
return 1.0;
642 return std::clamp(1.0 / invS, 0.0001, 1.0);
653 double f =
static_cast<double>(cfg.frequency);
654 double g =
static_cast<double>(cfg.gain);
655 double q =
static_cast<double>(cfg.q);
689 const double f = std::clamp(
static_cast<double>(cfg.frequency), 10.0, sr * 0.499);
690 const T q = std::max(
effectiveQ(cfg), T(0.1));
698 if (casc.hasFirstOrder)
701 for (
int s = 0; s < casc.numSecondOrder; ++s)
703 const double stageQ =
static_cast<double>(casc.qValues[s]);
732 std::fill_n(mag, numBins, T(1));
733 const int activeBands =
numBands_.load(std::memory_order_relaxed);
739 for (
int b = 0; b < activeBands; ++b)
746 for (
int k = 0; k < numBins; ++k)
748 const double freq = sr *
static_cast<double>(k) /
static_cast<double>(
lpFftSize_);
750 for (
int s = 0; s < ns; ++s)
751 m *= st[s].getMagnitude(freq, sr);
752 mag[k] =
static_cast<T
>(
static_cast<double>(mag[k]) * m);
758 for (
int k = 0; k < numBins; ++k)
766 const int halfM = M / 2;
769 for (
int i = 0; i < M; ++i)
775 double t =
static_cast<double>(i) /
static_cast<double>(M - 1);
776 double window = 0.35875
777 - 0.48829 * std::cos(2.0 * pi<double> * t)
778 + 0.14128 * std::cos(4.0 * pi<double> * t)
779 - 0.01168 * std::cos(6.0 * pi<double> * t);
804 const int nCh = std::min(buffer.getNumChannels(),
static_cast<int>(
lpPrevBlock_.size()));
805 const int L = buffer.getNumSamples();
812 const int overlapSize = M - 1;
814 for (
int ch = 0; ch < nCh; ++ch)
816 T* channelData = buffer.getChannel(ch);
823 for (
int i = 0; i < overlapSize; ++i)
825 for (
int i = 0; i < L; ++i)
826 lpFftIn_[overlapSize + i] = channelData[i];
827 for (
int i = overlapSize + L; i < N; ++i)
832 for (
int i = 0; i < overlapSize; ++i)
839 int numBins = N / 2 + 1;
840 for (
int k = 0; k < numBins; ++k)
847 lpFftOut_[2 * k] = realX * realH - imagX * imagH;
848 lpFftOut_[2 * k + 1] = realX * imagH + imagX * realH;
856 for (
int i = 0; i < L; ++i)
859 channelData[i] =
lpFftIn_[offset + i];
871 std::array<FilterEngine<T>, MaxBands>
bands_ {};
Multi-mode cascaded biquad filter engine with real-time parameter smoothing.
Non-owning view over audio channel data.
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
Parametric multi-band EQ using cascaded biquads or FFT overlap-save convolution.
std::vector< T > lpTempFreq_
bool setState(const uint8_t *data, size_t size)
Restores bands and modes from a blob (tolerant; rejects foreign ids).
BandType
Filter type for each EQ band.
@ LowShelf
Shelf: boosts/cuts below frequency.
@ BandPass
Bandpass around frequency.
@ Tilt
Tilt EQ: pivots spectrum around frequency.
@ LowPass
Removes frequencies above cutoff.
@ Peak
Parametric bell (boost/cut around frequency).
@ Notch
Narrow rejection at frequency.
@ HighShelf
Shelf: boosts/cuts above frequency.
@ HighPass
Removes frequencies below cutoff.
void processLinearPhase(AudioBufferView< T > buffer) noexcept
Linear-phase processing via overlap-save FFT convolution.
void setBand(int index, const BandConfig &config)
Configures a band with full control over all parameters. Thread-safe.
bool getSoftMode() const noexcept
Returns whether soft mode is enabled.
std::vector< T > lpFftOut_
std::vector< T > lpKernel_
std::atomic< bool > configDirty_
T processSample(T input, int channel) noexcept
Processes a single sample through all enabled bands (IIR mode only).
std::vector< uint8_t > getState() const
Serializes bands and modes (setup/UI threads; allocates).
void setBand(int index, T frequency, T gainDb)
Configures a band with frequency and gain (Peak filter).
std::atomic< bool > lpDirty_
void reset() noexcept
Resets all filter states to zero to prevent ringing on playback start.
FilterEngine< T > & getBandFilter(int index)
Direct access to a band's underlying FilterEngine.
std::atomic< bool > matchedBells_
T effectiveQ(const BandConfig &cfg) const noexcept
The band Q the audio path actually uses (soft mode caps it by gain).
void prepare(const AudioSpec &spec)
Prepares all bands and allocates necessary resources for processing.
int buildBandStages(const BandConfig &cfg, BiquadCoeffs *stages) const noexcept
Fills stages with the ACTUAL biquad cascade for a band (per-stage Butterworth Q for multi-stage LP/HP...
static constexpr int kLpMaxBlockSize
std::vector< std::vector< T > > lpPrevBlock_
FilterMode getFilterMode() const noexcept
Returns the current filter mode.
std::atomic< int > numBands_
std::vector< T > lpKernelSpace_
int getNumBands() const noexcept
Returns the number of active bands.
void setMatchedBells(bool enabled) noexcept
Switches Peak bands to the Orfanidis matched (de-cramped) design.
void setNumBands(int count)
Sets the number of active bands with auto-logarithmic spacing.
std::unique_ptr< FFTReal< T > > lpFft_
void setBandEnabled(int index, bool enabled) noexcept
Enables or disables a band without changing its parameters.
std::vector< T > lpMagScratch_
std::vector< T > lpImpulse_
void recomputeLinearPhaseKernel() noexcept
Mathematically robust Linear Phase kernel computation.
FilterMode
Filter processing mode.
@ LinearPhase
FFT-based overlap-save (block-size latency, zero phase distortion).
@ MinimumPhase
IIR biquads (zero latency, minimum phase shift). Default.
std::vector< T > lpFftIn_
void setFilterMode(FilterMode mode) noexcept
Sets the filter processing mode (Minimum Phase or Linear Phase).
~Equalizer()=default
Non-virtual destructor to prevent vtable instantiation (zero virtual dispatch).
const FilterEngine< T > & getBandFilter(int index) const
Const overload.
std::atomic< bool > softMode_
void setBand(int index, T frequency, T gainDb, T q)
Configures a band with frequency, gain, and Q.
BandConfig getBandConfig(int index) const noexcept
Returns the current configuration of a band.
std::array< std::atomic< bool >, MaxBands > bandEnabled_
std::array< BandConfig, MaxBands > configs_
static double shelfSlopeFromQ(double q, double gainDb) noexcept
Converts a user-facing shelf Q into the RBJ shelf slope S.
void updateActiveFilters() noexcept
Translates BandConfigs into internal FilterEngine parameters safely. Called by processBlock when conf...
int getLatency() const noexcept
Returns the latency in samples.
int lpBlock_
Max block size the LP engine was sized for (= its latency).
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an audio buffer in-place.
std::atomic< FilterMode > filterMode_
void setSoftMode(bool enabled) noexcept
Enables soft mode (anti-ringing Q reduction dynamically based on gain).
BiquadCoeffs computeBandCoeffs(const BandConfig &cfg) const noexcept
Computes single-biquad coefficients for a band (analysis/kernel).
void getMagnitudeForFrequencyArray(const T *frequencies, T *magnitudes, int numPoints) const noexcept
Computes the combined magnitude response of all enabled bands.
std::array< FilterEngine< T >, MaxBands > bands_
Professional multi-mode filter with cascaded biquad stages.
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...
Tolerant reader: missing keys yield defaults, unknown keys are skipped.
float read(const char *key, float defaultValue) const
Reads a float, or defaultValue when the key is absent.
bool isValid() const noexcept
uint32_t processorId() const noexcept
Serializes key/value parameters into a versioned blob.
std::vector< uint8_t > blob() const
Finalizes and returns the blob.
void write(const char *key, float value)
Writes a float parameter.
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").
Describes the audio environment for a DSP processor.
constexpr bool isValid() const noexcept
Checks if the specification contains valid, processable parameters.
int numChannels
Number of audio channels (e.g., 1 = mono, 2 = stereo).
int maxBlockSize
Maximum number of samples per processing block.
double sampleRate
Sample rate in Hz.
Stores normalised biquad coefficients (b0, b1, b2, a1, a2), always double.
static BiquadCoeffs makeFirstOrderHighPass(double sampleRate, double frequency) noexcept
First-order (6 dB/oct) high-pass filter.
static BiquadCoeffs makePeakMatched(double sampleRate, double freq, double Q, double gainDb) noexcept
Peaking filter with prescribed Nyquist gain (Orfanidis design).
static BiquadCoeffs makeHighPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
High-pass filter.
static BiquadCoeffs makeBandPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Band-pass filter (constant 0 dB peak gain).
static BiquadCoeffs makePeak(double sampleRate, double freq, double Q, double gainDb) noexcept
Peak (parametric EQ) filter.
static BiquadCoeffs makeFirstOrderLowPass(double sampleRate, double frequency) noexcept
First-order (6 dB/oct) low-pass filter.
static BiquadCoeffs makeTilt(double sampleRate, double pivotFreq, double gainDb) noexcept
Creates a first-order tilt filter.
static BiquadCoeffs makeLowPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Low-pass filter.
static BiquadCoeffs makeLowShelf(double sampleRate, double freq, double gainDb, double slope=1.0) noexcept
Low-shelf filter.
static BiquadCoeffs makeHighShelf(double sampleRate, double freq, double gainDb, double slope=1.0) noexcept
High-shelf filter.
static BiquadCoeffs makeNotch(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Notch (band-reject) filter.
Full configuration for a single EQ band.
T q
Q factor (0.1 = wide, 10 = narrow).
T gain
Gain in dB (Peak, Shelf, Tilt).
bool enabled
False to bypass this band.
int slope
Slope in dB/oct (LP/HP only: 6-48).
BandType type
Filter type for this band.
T frequency
Center/cutoff frequency in Hz.