37#include "AudioBuffer.h"
39#include "WindowFunctions.h"
89 [[nodiscard]]
static std::vector<T>
lowPass(
double sampleRate,
double cutoffHz,
90 int numTaps, T beta = T(5))
92 assert(numTaps >= 3 && (numTaps % 2 == 1));
93 numTaps = sanitizeTaps(numTaps);
94 return designSinc(cutoffHz / sampleRate, numTaps, beta,
false);
108 [[nodiscard]]
static std::vector<T>
highPass(
double sampleRate,
double cutoffHz,
109 int numTaps, T beta = T(5))
111 assert(numTaps >= 3 && (numTaps % 2 == 1));
112 numTaps = sanitizeTaps(numTaps);
113 return designSinc(cutoffHz / sampleRate, numTaps, beta,
true);
128 [[nodiscard]]
static std::vector<T>
bandPass(
double sampleRate,
double lowCutoffHz,
129 double highCutoffHz,
int numTaps,
132 assert(numTaps >= 3 && (numTaps % 2 == 1));
133 assert(lowCutoffHz < highCutoffHz);
134 numTaps = sanitizeTaps(numTaps);
136 auto lp1 = designSinc(highCutoffHz / sampleRate, numTaps, beta,
false);
137 auto lp2 = designSinc(lowCutoffHz / sampleRate, numTaps, beta,
false);
139 for (
int i = 0; i < numTaps; ++i)
140 lp1[
static_cast<size_t>(i)] -= lp2[
static_cast<size_t>(i)];
155 [[nodiscard]]
static std::vector<T>
bandStop(
double sampleRate,
double lowCutoffHz,
156 double highCutoffHz,
int numTaps,
159 assert(numTaps >= 3 && (numTaps % 2 == 1));
164 numTaps = sanitizeTaps(numTaps);
166 auto bp =
bandPass(sampleRate, lowCutoffHz, highCutoffHz, numTaps, beta);
169 int centre = numTaps / 2;
170 for (
int i = 0; i < numTaps; ++i)
171 bp[
static_cast<size_t>(i)] = -bp[
static_cast<size_t>(i)];
172 bp[
static_cast<size_t>(centre)] += T(1);
188 [[nodiscard]]
static int estimateTaps(
double sampleRate,
double transitionHz,
189 double attenuationDb)
noexcept
191 assert(sampleRate > 0.0 && transitionHz > 0.0);
194 double normTransition = std::max(transitionHz / sampleRate, 1e-6);
195 int n =
static_cast<int>(std::ceil((attenuationDb - 7.95) / (14.36 * normTransition)));
209 if (attenuationDb > 50.0)
210 return static_cast<T
>(0.1102 * (attenuationDb - 8.7));
211 else if (attenuationDb >= 21.0)
212 return static_cast<T
>(0.5842 * std::pow(attenuationDb - 21.0, 0.4)
213 + 0.07886 * (attenuationDb - 21.0));
222 [[nodiscard]]
static int sanitizeTaps(
int numTaps)
noexcept
224 if (numTaps < 3) numTaps = 3;
237 [[nodiscard]]
static std::vector<T> designSinc(
double normFreq,
int numTaps,
240 std::vector<double> design(
static_cast<size_t>(numTaps));
241 std::vector<double> window(
static_cast<size_t>(numTaps));
245 static_cast<double>(beta),
false);
247 const int centre = numTaps / 2;
248 const double fc = normFreq * 2.0;
249 constexpr double kPi = std::numbers::pi;
253 for (
int i = 0; i < numTaps; ++i)
255 const int n = i - centre;
256 const double x =
static_cast<double>(n) * kPi;
257 const double s = (n == 0) ? fc : std::sin(fc * x) / x;
258 const double c = s * window[
static_cast<size_t>(i)];
259 design[
static_cast<size_t>(i)] = c;
264 if (std::abs(sum) > 1e-10)
266 const double invSum = 1.0 / sum;
267 for (
auto& c : design) c *= invSum;
273 for (
auto& c : design) c = -c;
274 design[
static_cast<size_t>(centre)] += 1.0;
277 std::vector<T> coeffs(
static_cast<size_t>(numTaps));
278 for (
int i = 0; i < numTaps; ++i)
279 coeffs[
static_cast<size_t>(i)] =
static_cast<T
>(design[
static_cast<size_t>(i)]);
378 assert(maxTaps > 0 && numChannels > 0);
381 numChannels_ = numChannels;
389 stagingCoeffs_ = std::vector<std::atomic<T>>(
static_cast<size_t>(maxTaps_));
390 for (
auto& s : stagingCoeffs_) s.store(T(0), std::memory_order_relaxed);
391 activeCoeffs_[0].assign(
static_cast<size_t>(maxTaps_), T(0));
392 activeCoeffs_[1].assign(
static_cast<size_t>(maxTaps_), T(0));
393 stagingTaps_.store(0, std::memory_order_relaxed);
396 coeffSeq_.store(0, std::memory_order_release);
397 coeffDirty_.store(
false, std::memory_order_release);
398 reportedTaps_.store(0, std::memory_order_release);
402 delayLineBuffer_.assign(
static_cast<size_t>(numChannels_ * maxTaps_ * 2), T(0));
403 writePositions_.assign(
static_cast<size_t>(numChannels_), 0);
405 isPrepared_.store(
true, std::memory_order_release);
420 if (!isPrepared_.load(std::memory_order_acquire))
return;
422 const int numTaps =
static_cast<int>(coeffs.size());
423 if (numTaps == 0 || numTaps > maxTaps_)
return;
426 coeffSeq_.fetch_add(1, std::memory_order_acq_rel);
436 std::atomic_thread_fence(std::memory_order_release);
440 for (
int k = 0; k < numTaps; ++k)
441 stagingCoeffs_[
static_cast<size_t>(k)].store(
442 coeffs[
static_cast<size_t>(numTaps - 1 - k)], std::memory_order_relaxed);
443 stagingTaps_.store(numTaps, std::memory_order_relaxed);
444 coeffSeq_.fetch_add(1, std::memory_order_release);
445 coeffDirty_.store(
true, std::memory_order_release);
446 reportedTaps_.store(numTaps, std::memory_order_release);
454 std::fill(delayLineBuffer_.begin(), delayLineBuffer_.end(), T(0));
455 std::fill(writePositions_.begin(), writePositions_.end(), 0);
468 if (!isPrepared_.load(std::memory_order_acquire))
return;
474 const int currentTaps = activeTaps_;
475 if (currentTaps == 0)
return;
479 const T* currentCoeffs = activeCoeffs_[
static_cast<size_t>(activeIdx_)].data();
480 const int numChannels = std::min(buffer.getNumChannels(), numChannels_);
481 const int numSamples = buffer.getNumSamples();
483 for (
int ch = 0; ch < numChannels; ++ch)
485 T* data = buffer.getChannel(ch);
486 auto& wp = writePositions_[
static_cast<size_t>(ch)];
487 const int channelOffset = ch * (maxTaps_ * 2);
488 T* dl = delayLineBuffer_.data() + channelOffset;
490 for (
int i = 0; i < numSamples; ++i)
494 dl[wp + maxTaps_] = data[i];
497 const T* readPtr = dl + wp + maxTaps_ - currentTaps + 1;
503 if (++wp >= maxTaps_) wp = 0;
520 if (!isPrepared_.load(std::memory_order_acquire))
return input;
524 assert(channel >= 0 && channel < numChannels_);
525 if (channel < 0 || channel >= numChannels_)
return input;
528 if (coeffDirty_.load(std::memory_order_relaxed)) [[unlikely]]
531 const int currentTaps = activeTaps_;
532 if (currentTaps == 0)
return input;
534 const T* currentCoeffs = activeCoeffs_[
static_cast<size_t>(activeIdx_)].data();
536 auto& wp = writePositions_[
static_cast<size_t>(channel)];
537 const int channelOffset = channel * (maxTaps_ * 2);
538 T* dl = delayLineBuffer_.data() + channelOffset;
541 dl[wp + maxTaps_] = input;
543 const T* readPtr = dl + wp + maxTaps_ - currentTaps + 1;
546 if (++wp >= maxTaps_) wp = 0;
558 const int taps = reportedTaps_.load(std::memory_order_relaxed);
559 return taps > 0 ? (taps - 1) / 2 : 0;
568 static constexpr int kSeqlockMaxAttempts = 3;
578 void pullCoeffsIfDirty() noexcept
580 if (!coeffDirty_.exchange(
false, std::memory_order_acquire))
return;
585 const size_t spare =
static_cast<size_t>(activeIdx_ ^ 1);
586 T*
const dest = activeCoeffs_[spare].data();
588 bool adopted =
false;
590 for (
int attempt = 0; attempt < kSeqlockMaxAttempts; ++attempt)
592 const unsigned s0 = coeffSeq_.load(std::memory_order_acquire);
597 if ((s0 & 1u) != 0u)
continue;
600 n = std::min(stagingTaps_.load(std::memory_order_relaxed), maxTaps_);
601 for (
int k = 0; k < n; ++k)
602 dest[k] = stagingCoeffs_[
static_cast<size_t>(k)].load(std::memory_order_relaxed);
607 std::atomic_thread_fence(std::memory_order_acquire);
608 if (s0 == coeffSeq_.load(std::memory_order_relaxed))
620 coeffDirty_.store(
true, std::memory_order_release);
630 std::atomic<bool> isPrepared_{
false};
638 std::vector<std::atomic<T>> stagingCoeffs_;
639 std::atomic<int> stagingTaps_{0};
640 std::atomic<unsigned> coeffSeq_{0};
641 std::atomic<bool> coeffDirty_{
false};
642 std::atomic<int> reportedTaps_{0};
650 std::array<std::vector<T>, 2> activeCoeffs_;
655 std::vector<T> delayLineBuffer_;
656 std::vector<int> writePositions_;
Non-owning view over audio channel data.
Static methods for designing FIR filter coefficients.
static std::vector< T > highPass(double sampleRate, double cutoffHz, int numTaps, T beta=T(5))
Designs a high-pass 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 > lowPass(double sampleRate, double cutoffHz, int numTaps, T beta=T(5))
Designs a low-pass FIR filter.
static T estimateKaiserBeta(double attenuationDb) noexcept
Estimates the Kaiser beta parameter for a desired attenuation.
static std::vector< T > bandStop(double sampleRate, double lowCutoffHz, double highCutoffHz, int numTaps, T beta=T(5))
Designs a band-stop (notch) FIR filter.
static std::vector< T > bandPass(double sampleRate, double lowCutoffHz, double highCutoffHz, int numTaps, T beta=T(5))
Designs a band-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.