49#include "../Core/DspMath.h"
50#include "../Core/Biquad.h"
51#include "../Core/AudioSpec.h"
52#include "../Core/AudioBuffer.h"
53#include "../Core/StateBlob.h"
109 void prepare(
double sampleRate,
int numChannels = 2,
double cutoffHz = -1.0)
111 if (!(sampleRate > 0.0))
return;
146 order_.store(std::clamp(order, 1, 10), std::memory_order_relaxed);
152 return order_.load(std::memory_order_relaxed);
168 if (!std::isfinite(hz))
return;
169 cutoffHz_.store(std::max(hz, T(1)), std::memory_order_relaxed);
175 return cutoffHz_.load(std::memory_order_relaxed);
194 const int nCh = std::min(buffer.getNumChannels(),
numChannels_);
195 const int nS = buffer.getNumSamples();
198 if (currentOrder <= 1)
200 for (
int ch = 0; ch < nCh; ++ch)
206 for (
int ch = 0; ch < nCh; ++ch)
207 runCascade(buffer.getChannel(ch), nS, ch, numStages);
223 if (data ==
nullptr || numSamples <= 0)
return;
227 if (currentOrder <= 1)
254 if (channel < 0 || channel >=
kMaxChannels)
return input;
256 const auto ch =
static_cast<size_t>(channel);
257 const double x =
static_cast<double>(input);
264 for (
int s = 0; s < numStages; ++s)
266 auto& sec =
sections_[
static_cast<size_t>(s)];
267 v =
stepSection(sec.b0, sec.a1, sec.a2, sec.state[ch], v);
269 return static_cast<T
>(v);
284 [[nodiscard]] std::vector<uint8_t>
getState()
const
287 w.
write(
"order", order_.load(std::memory_order_relaxed));
288 w.
write(
"cutoff",
static_cast<float>(cutoffHz_.load(std::memory_order_relaxed)));
297 setOrder(r.
read(
"order", 1));
298 setCutoff(
static_cast<T
>(r.
read(
"cutoff", 5.0f)));
310 const T cutoff = cutoffHz_.load(std::memory_order_relaxed);
311 const int order = order_.load(std::memory_order_relaxed);
312 if (cutoff != lastCutoff_ || order != lastOrder_)
314 forceUpdateCoefficients(cutoff);
320 const T cutoff = explicitCutoff > T(0) ? explicitCutoff : cutoffHz_.load(std::memory_order_relaxed);
321 const double fc =
static_cast<double>(cutoff);
325 const int order = order_.load(std::memory_order_relaxed);
328 onePoleR_ = std::exp(-std::numbers::pi * 2.0 * fc / sampleRate_);
331 static constexpr float qTable[6][kMaxBiquadStages] = {
334 { 0.5412f, 1.3066f },
335 { 0.5177f, 0.7071f, 1.9319f },
336 { 0.5098f, 0.6013f, 0.8999f, 2.5628f },
337 { 0.5062f, 0.5612f, 0.7071f, 1.1013f, 3.1962f }
340 const int tableIdx = std::clamp(order / 2, 1, 5);
341 for (
int s = 0; s < tableIdx; ++s)
349 sampleRate_, fc,
static_cast<double>(qTable[tableIdx][s]));
350 assert(c.b1 == -2.0 * c.b0 && c.b2 == c.b0
351 &&
"RBJ high-pass numerator is no longer b0 * (1, -2, 1)");
353 auto& sec = sections_[
static_cast<size_t>(s)];
359 lastCutoff_ = cutoff;
366 struct SectionState {
double x1 = 0.0, x2 = 0.0, y1 = 0.0, y2 = 0.0; };
371 double b0 = 0.0, a1 = 0.0, a2 = 0.0;
372 std::array<SectionState, kMaxChannels> state {};
378 const double y = (x - z.x1) + r * z.y1;
389 const double d = (x - z.x1) - (z.x1 - z.x2);
390 const double y = b0 * d - a1 * z.y1 - a2 * z.y2;
391 z.x2 = z.x1; z.x1 = x;
392 z.y2 = z.y1; z.y1 = y;
407 static constexpr double kDenormalFloor = 1e-30;
411 if (std::abs(z.y1) + std::abs(z.x1) < kDenormalFloor)
417 if (std::abs(z.y1) + std::abs(z.y2) + std::abs(z.x1) + std::abs(z.x2) < kDenormalFloor)
421 void runOnePole(T* data,
int numSamples,
int channel)
noexcept
423 const double r = onePoleR_;
424 OnePoleState z = onePole_[
static_cast<size_t>(channel)];
425 for (
int i = 0; i < numSamples; ++i)
426 data[i] =
static_cast<T
>(stepOnePole(r, z,
static_cast<double>(data[i])));
428 onePole_[
static_cast<size_t>(channel)] = z;
431 void runCascade(T* data,
int numSamples,
int channel,
int numStages)
noexcept
437 double b0[kMaxBiquadStages] {}, a1[kMaxBiquadStages] {}, a2[kMaxBiquadStages] {};
439 const auto ch =
static_cast<size_t>(channel);
440 for (
int s = 0; s < numStages; ++s)
442 const auto& sec = sections_[
static_cast<size_t>(s)];
443 b0[s] = sec.b0; a1[s] = sec.a1; a2[s] = sec.a2;
444 z[s] = sec.state[ch];
447 for (
int i = 0; i < numSamples; ++i)
449 double v =
static_cast<double>(data[i]);
450 for (
int s = 0; s < numStages; ++s)
451 v = stepSection(b0[s], a1[s], a2[s], z[s], v);
452 data[i] =
static_cast<T
>(v);
455 for (
int s = 0; s < numStages; ++s)
457 flushDenormals(z[s]);
458 sections_[
static_cast<size_t>(s)].state[ch] = z[s];
462 double sampleRate_ = 48000.0;
463 int numChannels_ = 2;
464 double onePoleR_ = 0.0;
466 std::atomic<int> order_ { 1 };
467 std::atomic<T> cutoffHz_ { T(5) };
468 T lastCutoff_ = T(-1);
472 std::array<OnePoleState, kMaxChannels> onePole_ {};
473 std::array<Section, kMaxBiquadStages> sections_ {};
Non-owning view over audio channel data.
DC blocking filter with configurable Butterworth order (1-10).
void prepare(double sampleRate, int numChannels=2, double cutoffHz=-1.0)
Prepares the DC blocker, resetting internal states and precalculating coefficients.
void setOrder(int order) noexcept
Sets the filter order (1-10). Thread-safe.
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
std::array< OnePoleState, kMaxChannels > onePole_
std::atomic< T > cutoffHz_
void runOnePole(T *data, int numSamples, int channel) noexcept
static constexpr int kMaxBiquadStages
DCBlocker() noexcept
Builds a filter coherent with the documented defaults (5 Hz @ 48 kHz).
T processSample(int channel, T input) noexcept
Processes a single sample for a given channel.
void forceUpdateCoefficients(T explicitCutoff=T(0)) noexcept
void setCutoff(T hz) noexcept
Requests a cutoff frequency update.
void reset() noexcept
Clears the internal history states to zero.
std::atomic< int > order_
static void flushDenormals(OnePoleState &z) noexcept
int getOrder() const noexcept
Returns the current filter order.
double onePoleR_
Derived from the documented defaults in the constructor.
void runCascade(T *data, int numSamples, int channel, int numStages) noexcept
void updateCoefficientsIfNeeded() noexcept
static void flushDenormals(SectionState &z) noexcept
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an AudioBufferView in-place.
void processBlock(int channel, T *data, int numSamples) noexcept
Processes a block of samples for one channel in-place.
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
static double stepSection(double b0, double a1, double a2, SectionState &z, double x) noexcept
Direct Form I with the numerator applied as a second difference.
std::array< Section, kMaxBiquadStages > sections_
void prepare(const AudioSpec &spec)
Prepares from AudioSpec (unified API); keeps the configured cutoff.
static double stepOnePole(double r, OnePoleState &z, double x) noexcept
y[n] = (x[n] - x[n-1]) + R*y[n-1]; the difference zeroes DC exactly.
static constexpr int kMaxChannels
T getCutoff() const noexcept
Returns the requested cutoff frequency in Hz.
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.
int numChannels
Number of audio channels (e.g., 1 = mono, 2 = stereo).
double sampleRate
Sample rate in Hz.
static BiquadCoeffs makeHighPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
High-pass filter.
Second-order section: b0 * (1 - z^-1)^2 / (1 + a1 z^-1 + a2 z^-2).