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"
54#include "../Core/detail/DcBlock.h"
110 void prepare(
double sampleRate,
int numChannels = 2,
double cutoffHz = -1.0)
112 if (!(sampleRate > 0.0))
return;
147 order_.store(std::clamp(order, 1, 10), std::memory_order_relaxed);
153 return order_.load(std::memory_order_relaxed);
169 if (!std::isfinite(hz))
return;
170 cutoffHz_.store(std::max(hz, T(1)), std::memory_order_relaxed);
176 return cutoffHz_.load(std::memory_order_relaxed);
195 const int nCh = std::min(buffer.getNumChannels(),
numChannels_);
196 const int nS = buffer.getNumSamples();
199 if (currentOrder <= 1)
201 for (
int ch = 0; ch < nCh; ++ch)
207 for (
int ch = 0; ch < nCh; ++ch)
208 runCascade(buffer.getChannel(ch), nS, ch, numStages);
224 if (data ==
nullptr || numSamples <= 0)
return;
228 if (currentOrder <= 1)
255 if (channel < 0 || channel >=
kMaxChannels)
return input;
257 const auto ch =
static_cast<size_t>(channel);
258 const double x =
static_cast<double>(input);
265 for (
int s = 0; s < numStages; ++s)
267 auto& sec =
sections_[
static_cast<size_t>(s)];
268 v =
stepSection(sec.b0, sec.a1, sec.a2, sec.state[ch], v);
270 return static_cast<T
>(v);
285 [[nodiscard]] std::vector<uint8_t>
getState()
const
288 w.
write(
"order", order_.load(std::memory_order_relaxed));
289 w.
write(
"cutoff",
static_cast<float>(cutoffHz_.load(std::memory_order_relaxed)));
298 setOrder(r.
read(
"order", 1));
299 setCutoff(
static_cast<T
>(r.
read(
"cutoff", 5.0f)));
311 const T cutoff = cutoffHz_.load(std::memory_order_relaxed);
312 const int order = order_.load(std::memory_order_relaxed);
313 if (cutoff != lastCutoff_ || order != lastOrder_)
315 forceUpdateCoefficients(cutoff);
321 const T cutoff = explicitCutoff > T(0) ? explicitCutoff : cutoffHz_.load(std::memory_order_relaxed);
322 const double fc =
static_cast<double>(cutoff);
326 const int order = order_.load(std::memory_order_relaxed);
329 onePoleR_ = std::exp(-std::numbers::pi * 2.0 * fc / sampleRate_);
332 static constexpr float qTable[6][kMaxBiquadStages] = {
335 { 0.5412f, 1.3066f },
336 { 0.5177f, 0.7071f, 1.9319f },
337 { 0.5098f, 0.6013f, 0.8999f, 2.5628f },
338 { 0.5062f, 0.5612f, 0.7071f, 1.1013f, 3.1962f }
341 const int tableIdx = std::clamp(order / 2, 1, 5);
342 for (
int s = 0; s < tableIdx; ++s)
350 sampleRate_, fc,
static_cast<double>(qTable[tableIdx][s]));
351 assert(c.b1 == -2.0 * c.b0 && c.b2 == c.b0
352 &&
"RBJ high-pass numerator is no longer b0 * (1, -2, 1)");
354 auto& sec = sections_[
static_cast<size_t>(s)];
360 lastCutoff_ = cutoff;
367 struct SectionState {
double x1 = 0.0, x2 = 0.0, y1 = 0.0, y2 = 0.0; };
372 double b0 = 0.0, a1 = 0.0, a2 = 0.0;
373 std::array<SectionState, kMaxChannels> state {};
387 const double d = (x - z.x1) - (z.x1 - z.x2);
388 const double y = b0 * d - a1 * z.y1 - a2 * z.y2;
389 z.x2 = z.x1; z.x1 = x;
390 z.y2 = z.y1; z.y1 = y;
405 static constexpr double kDenormalFloor = 1e-30;
409 if (std::abs(z.y1) + std::abs(z.x1) < kDenormalFloor)
415 if (std::abs(z.y1) + std::abs(z.y2) + std::abs(z.x1) + std::abs(z.x2) < kDenormalFloor)
419 void runOnePole(T* data,
int numSamples,
int channel)
noexcept
421 const double r = onePoleR_;
422 OnePoleState z = onePole_[
static_cast<size_t>(channel)];
423 for (
int i = 0; i < numSamples; ++i)
424 data[i] =
static_cast<T
>(stepOnePole(r, z,
static_cast<double>(data[i])));
426 onePole_[
static_cast<size_t>(channel)] = z;
429 void runCascade(T* data,
int numSamples,
int channel,
int numStages)
noexcept
435 double b0[kMaxBiquadStages] {}, a1[kMaxBiquadStages] {}, a2[kMaxBiquadStages] {};
437 const auto ch =
static_cast<size_t>(channel);
438 for (
int s = 0; s < numStages; ++s)
440 const auto& sec = sections_[
static_cast<size_t>(s)];
441 b0[s] = sec.b0; a1[s] = sec.a1; a2[s] = sec.a2;
442 z[s] = sec.state[ch];
445 for (
int i = 0; i < numSamples; ++i)
447 double v =
static_cast<double>(data[i]);
448 for (
int s = 0; s < numStages; ++s)
449 v = stepSection(b0[s], a1[s], a2[s], z[s], v);
450 data[i] =
static_cast<T
>(v);
453 for (
int s = 0; s < numStages; ++s)
455 flushDenormals(z[s]);
456 sections_[
static_cast<size_t>(s)].state[ch] = z[s];
460 double sampleRate_ = 48000.0;
461 int numChannels_ = 2;
462 double onePoleR_ = 0.0;
464 std::atomic<int> order_ { 1 };
465 std::atomic<T> cutoffHz_ { T(5) };
466 T lastCutoff_ = T(-1);
470 std::array<OnePoleState, kMaxChannels> onePole_ {};
471 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.
double dcBlockStep(double pole, DcBlockState &state, double input) noexcept
Applies (1 - z^-1) / (1 - pole * z^-1), with a pole in [0, 1).
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).
Previous input and output of the one-pole DC blocker.