86 [[nodiscard]]
static std::vector<T>
lowPass(
double sampleRate,
double cutoffHz,
87 int numTaps, T beta = T(5)) noexcept
89 assert(numTaps >= 3 && (numTaps % 2 == 1));
90 numTaps = sanitizeTaps(numTaps);
91 return designSinc(cutoffHz / sampleRate, numTaps, beta,
false);
105 [[nodiscard]]
static std::vector<T>
highPass(
double sampleRate,
double cutoffHz,
106 int numTaps, T beta = T(5)) noexcept
108 assert(numTaps >= 3 && (numTaps % 2 == 1));
109 numTaps = sanitizeTaps(numTaps);
110 return designSinc(cutoffHz / sampleRate, numTaps, beta,
true);
125 [[nodiscard]]
static std::vector<T>
bandPass(
double sampleRate,
double lowCutoffHz,
126 double highCutoffHz,
int numTaps,
127 T beta = T(5)) noexcept
129 assert(numTaps >= 3 && (numTaps % 2 == 1));
130 assert(lowCutoffHz < highCutoffHz);
131 numTaps = sanitizeTaps(numTaps);
133 auto lp1 = designSinc(highCutoffHz / sampleRate, numTaps, beta,
false);
134 auto lp2 = designSinc(lowCutoffHz / sampleRate, numTaps, beta,
false);
136 for (
int i = 0; i < numTaps; ++i)
137 lp1[
static_cast<size_t>(i)] -= lp2[
static_cast<size_t>(i)];
152 [[nodiscard]]
static std::vector<T>
bandStop(
double sampleRate,
double lowCutoffHz,
153 double highCutoffHz,
int numTaps,
154 T beta = T(5)) noexcept
156 assert(numTaps >= 3 && (numTaps % 2 == 1));
161 numTaps = sanitizeTaps(numTaps);
163 auto bp =
bandPass(sampleRate, lowCutoffHz, highCutoffHz, numTaps, beta);
166 int centre = numTaps / 2;
167 for (
int i = 0; i < numTaps; ++i)
168 bp[
static_cast<size_t>(i)] = -bp[
static_cast<size_t>(i)];
169 bp[
static_cast<size_t>(centre)] += T(1);
185 [[nodiscard]]
static int estimateTaps(
double sampleRate,
double transitionHz,
186 double attenuationDb)
noexcept
188 assert(sampleRate > 0.0 && transitionHz > 0.0);
191 double normTransition = std::max(transitionHz / sampleRate, 1e-6);
192 int n =
static_cast<int>(std::ceil((attenuationDb - 7.95) / (14.36 * normTransition)));
206 if (attenuationDb > 50.0)
207 return static_cast<T
>(0.1102 * (attenuationDb - 8.7));
208 else if (attenuationDb >= 21.0)
209 return static_cast<T
>(0.5842 * std::pow(attenuationDb - 21.0, 0.4)
210 + 0.07886 * (attenuationDb - 21.0));
219 [[nodiscard]]
static int sanitizeTaps(
int numTaps)
noexcept
221 if (numTaps < 3) numTaps = 3;
234 [[nodiscard]]
static std::vector<T> designSinc(
double normFreq,
int numTaps,
235 T beta,
bool invert)
noexcept
237 std::vector<double> design(
static_cast<size_t>(numTaps));
238 std::vector<double> window(
static_cast<size_t>(numTaps));
242 static_cast<double>(beta),
false);
244 const int centre = numTaps / 2;
245 const double fc = normFreq * 2.0;
246 constexpr double kPi = std::numbers::pi;
250 for (
int i = 0; i < numTaps; ++i)
252 const int n = i - centre;
253 const double x =
static_cast<double>(n) * kPi;
254 const double s = (n == 0) ? fc : std::sin(fc * x) / x;
255 const double c = s * window[
static_cast<size_t>(i)];
256 design[
static_cast<size_t>(i)] = c;
261 if (std::abs(sum) > 1e-10)
263 const double invSum = 1.0 / sum;
264 for (
auto& c : design) c *= invSum;
270 for (
auto& c : design) c = -c;
271 design[
static_cast<size_t>(centre)] += 1.0;
274 std::vector<T> coeffs(
static_cast<size_t>(numTaps));
275 for (
int i = 0; i < numTaps; ++i)
276 coeffs[
static_cast<size_t>(i)] =
static_cast<T
>(design[
static_cast<size_t>(i)]);
314 assert(maxTaps > 0 && numChannels > 0);
317 numChannels_ = numChannels;
322 stagingCoeffs_.assign(
static_cast<size_t>(maxTaps_), T(0));
323 activeCoeffs_.assign(
static_cast<size_t>(maxTaps_), T(0));
324 stagingTaps_.store(0, std::memory_order_relaxed);
326 coeffSeq_.store(0, std::memory_order_release);
327 coeffDirty_.store(
false, std::memory_order_release);
328 reportedTaps_.store(0, std::memory_order_release);
332 delayLineBuffer_.assign(
static_cast<size_t>(numChannels_ * maxTaps_ * 2), T(0));
333 writePositions_.assign(
static_cast<size_t>(numChannels_), 0);
335 isPrepared_.store(
true, std::memory_order_release);
350 if (!isPrepared_.load(std::memory_order_acquire))
return;
352 const int numTaps =
static_cast<int>(coeffs.size());
353 if (numTaps == 0 || numTaps > maxTaps_)
return;
356 coeffSeq_.fetch_add(1, std::memory_order_acq_rel);
358 for (
int k = 0; k < numTaps; ++k)
359 stagingCoeffs_[
static_cast<size_t>(k)] = coeffs[
static_cast<size_t>(numTaps - 1 - k)];
360 stagingTaps_.store(numTaps, std::memory_order_relaxed);
361 coeffSeq_.fetch_add(1, std::memory_order_release);
362 coeffDirty_.store(
true, std::memory_order_release);
363 reportedTaps_.store(numTaps, std::memory_order_release);
371 std::fill(delayLineBuffer_.begin(), delayLineBuffer_.end(), T(0));
372 std::fill(writePositions_.begin(), writePositions_.end(), 0);
385 if (!isPrepared_.load(std::memory_order_acquire))
return;
391 const int currentTaps = activeTaps_;
392 if (currentTaps == 0)
return;
394 const T* currentCoeffs = activeCoeffs_.data();
395 const int numChannels = std::min(buffer.getNumChannels(), numChannels_);
396 const int numSamples = buffer.getNumSamples();
398 for (
int ch = 0; ch < numChannels; ++ch)
400 T* data = buffer.getChannel(ch);
401 auto& wp = writePositions_[
static_cast<size_t>(ch)];
402 const int channelOffset = ch * (maxTaps_ * 2);
403 T* dl = delayLineBuffer_.data() + channelOffset;
405 for (
int i = 0; i < numSamples; ++i)
409 dl[wp + maxTaps_] = data[i];
412 const T* readPtr = dl + wp + maxTaps_ - currentTaps + 1;
418 if (++wp >= maxTaps_) wp = 0;
435 if (!isPrepared_.load(std::memory_order_acquire))
return input;
439 assert(channel >= 0 && channel < numChannels_);
440 if (channel < 0 || channel >= numChannels_)
return input;
443 if (coeffDirty_.load(std::memory_order_relaxed)) [[unlikely]]
446 const int currentTaps = activeTaps_;
447 if (currentTaps == 0)
return input;
449 const T* currentCoeffs = activeCoeffs_.data();
451 auto& wp = writePositions_[
static_cast<size_t>(channel)];
452 const int channelOffset = channel * (maxTaps_ * 2);
453 T* dl = delayLineBuffer_.data() + channelOffset;
456 dl[wp + maxTaps_] = input;
458 const T* readPtr = dl + wp + maxTaps_ - currentTaps + 1;
461 if (++wp >= maxTaps_) wp = 0;
473 const int taps = reportedTaps_.load(std::memory_order_relaxed);
474 return taps > 0 ? (taps - 1) / 2 : 0;
480 void pullCoeffsIfDirty() noexcept
482 if (!coeffDirty_.exchange(
false, std::memory_order_acquire))
return;
486 s0 = coeffSeq_.load(std::memory_order_acquire);
489 n = std::min(stagingTaps_.load(std::memory_order_relaxed), maxTaps_);
490 for (
int k = 0; k < n; ++k)
491 activeCoeffs_[
static_cast<size_t>(k)] = stagingCoeffs_[
static_cast<size_t>(k)];
496 std::atomic_thread_fence(std::memory_order_acquire);
497 s1 = coeffSeq_.load(std::memory_order_relaxed);
498 }
while ((s0 & 1u) != 0u || s0 != s1);
504 std::atomic<bool> isPrepared_{
false};
507 std::vector<T> stagingCoeffs_;
508 std::atomic<int> stagingTaps_{0};
509 std::atomic<unsigned> coeffSeq_{0};
510 std::atomic<bool> coeffDirty_{
false};
511 std::atomic<int> reportedTaps_{0};
514 std::vector<T> activeCoeffs_;
518 std::vector<T> delayLineBuffer_;
519 std::vector<int> writePositions_;
Owning audio buffer and non-owning view for real-time DSP processing.
SIMD-accelerated buffer operations for real-time audio processing.
Standard window functions for spectral analysis and FIR filter design.
Non-owning view over audio channel data.
Static methods for designing FIR filter coefficients.
static std::vector< T > bandStop(double sampleRate, double lowCutoffHz, double highCutoffHz, int numTaps, T beta=T(5)) noexcept
Designs a band-stop (notch) FIR filter.
static int estimateTaps(double sampleRate, double transitionHz, double attenuationDb) noexcept
Estimates the required number of taps for a given specification.
static std::vector< T > bandPass(double sampleRate, double lowCutoffHz, double highCutoffHz, int numTaps, T beta=T(5)) noexcept
Designs a band-pass FIR filter.
static T estimateKaiserBeta(double attenuationDb) noexcept
Estimates the Kaiser beta parameter for a desired attenuation.
static std::vector< T > lowPass(double sampleRate, double cutoffHz, int numTaps, T beta=T(5)) noexcept
Designs a low-pass FIR filter.
static std::vector< T > highPass(double sampleRate, double cutoffHz, int numTaps, T beta=T(5)) noexcept
Designs a high-pass FIR filter.
FIR filter using direct-form convolution with a mirrored delay line.
void reset() noexcept
Resets all delay lines to zero, clearing the filter's memory.
void processBlock(AudioBufferView< T > buffer) noexcept
Processes a full audio buffer in-place.
void prepare(int maxTaps, int numChannels)
Pre-allocates memory and initializes the delay lines.
T processSample(T input, int channel) noexcept
Processes a single sample through the FIR filter.
void setCoefficients(std::span< const T > coeffs) noexcept
Sets the filter coefficients asynchronously.
int getLatency() const noexcept
Returns the filter's group delay (latency) in samples.
float dotProduct(const float *DSPARK_RESTRICT a, const float *DSPARK_RESTRICT b, int count) noexcept
Computes the dot product of two arrays.
Main namespace for the DSPark framework.
static void kaiser(T *output, int size, T beta, bool periodic=true) noexcept
Kaiser window with configurable shape parameter beta.