90 if (!(spec.sampleRate > 0.0))
return;
107 const int nCh = std::min(buffer.getNumChannels(),
kMaxChannels);
108 const int nS = buffer.getNumSamples();
111 const T currentG =
g_.load(std::memory_order_relaxed);
112 const T currentRes =
resonance_.load(std::memory_order_relaxed);
113 const T currentDrive =
drive_.load(std::memory_order_relaxed);
114 const Mode currentMode =
mode_.load(std::memory_order_relaxed);
119 case Mode::LP6: processBlockInternal<Mode::LP6>(buffer, nCh, nS, currentG, currentRes, currentDrive);
break;
120 case Mode::LP12: processBlockInternal<Mode::LP12>(buffer, nCh, nS, currentG, currentRes, currentDrive);
break;
121 case Mode::LP18: processBlockInternal<Mode::LP18>(buffer, nCh, nS, currentG, currentRes, currentDrive);
break;
122 case Mode::LP24: processBlockInternal<Mode::LP24>(buffer, nCh, nS, currentG, currentRes, currentDrive);
break;
123 case Mode::BP12: processBlockInternal<Mode::BP12>(buffer, nCh, nS, currentG, currentRes, currentDrive);
break;
124 case Mode::HP24: processBlockInternal<Mode::HP24>(buffer, nCh, nS, currentG, currentRes, currentDrive);
break;
146 if (channel < 0 || channel >=
kMaxChannels)
return input;
148 const T g =
g_.load(std::memory_order_relaxed);
149 const T res =
resonance_.load(std::memory_order_relaxed);
150 const T drive =
drive_.load(std::memory_order_relaxed);
151 const BlockCoeffs c = makeBlockCoeffs(g, res);
154 switch (
mode_.load(std::memory_order_relaxed))
156 case Mode::LP6:
return processSampleInternal<Mode::LP6>(input, s, c, drive);
157 case Mode::LP12:
return processSampleInternal<Mode::LP12>(input, s, c, drive);
158 case Mode::LP18:
return processSampleInternal<Mode::LP18>(input, s, c, drive);
159 case Mode::LP24:
return processSampleInternal<Mode::LP24>(input, s, c, drive);
160 case Mode::BP12:
return processSampleInternal<Mode::BP12>(input, s, c, drive);
161 case Mode::HP24:
return processSampleInternal<Mode::HP24>(input, s, c, drive);
193 if (!std::isfinite(hz))
return;
194 cutoff_.store(std::max(hz, T(0)), std::memory_order_relaxed);
195 updateCoefficients();
206 if (!std::isfinite(amount))
return;
207 resonance_.store(std::clamp(amount, T(0), T(1)), std::memory_order_relaxed);
218 if (!std::isfinite(amount))
return;
219 drive_.store(std::max(amount, T(0.1)), std::memory_order_relaxed);
228 mode_.store(mode, std::memory_order_relaxed);
231 [[nodiscard]] T
getCutoff() const noexcept {
return cutoff_.load(std::memory_order_relaxed); }
233 [[nodiscard]] T
getDrive() const noexcept {
return drive_.load(std::memory_order_relaxed); }
234 [[nodiscard]]
Mode getMode() const noexcept {
return mode_.load(std::memory_order_relaxed); }
246 std::array<T, 4>
z {};
249 std::array<ChannelState, kMaxChannels>
state_ {};
256 std::atomic<T>
g_ {T(0)};
266 void updateCoefficients() noexcept
269 const T clamped = std::clamp(
cutoff_.load(std::memory_order_relaxed),
271 cutoff_.store(clamped, std::memory_order_relaxed);
272 const T preWarpedGain =
static_cast<T
>(std::tan(pi<double> *
static_cast<double>(clamped) /
spec_.
sampleRate));
273 g_.store(preWarpedGain, std::memory_order_relaxed);
279 T G, G2, G3, G4, ig, k, invFbDen;
283 [[nodiscard]]
static BlockCoeffs makeBlockCoeffs(T g, T res)
noexcept
286 c.G = g / (T(1) + g);
290 c.ig = T(1) / (T(1) + g);
292 c.invFbDen = T(1) / (T(1) + c.k * c.G4);
297 template <Mode FilterMode>
298 void processBlockInternal(AudioBufferView<T> buffer,
int nCh,
int nS, T g, T res, T drive)
noexcept
302 const BlockCoeffs c = makeBlockCoeffs(g, res);
304 for (
int ch = 0; ch < nCh; ++ch)
306 auto* channelData = buffer.getChannel(ch);
309 for (
int i = 0; i < nS; ++i)
311 channelData[i] = processSampleInternal<FilterMode>(channelData[i], state, c, drive);
322 template <Mode FilterMode>
323 [[nodiscard]] T processSampleInternal(T input, ChannelState& s,
const BlockCoeffs& c, T drive)
noexcept
326 const T Sest = c.G3 * c.ig * s.z[0]
327 + c.G2 * c.ig * s.z[1]
328 + c.G * c.ig * s.z[2]
336 fbSignal =
fastTanh(fbSignal * drive) / drive;
339 const T u = (input - c.k * fbSignal) * c.invFbDen;
344 std::array<T, 4> y {};
346 for (
int i = 0; i < 4; ++i)
348 const T v = (x - s.z[i]) * c.G;
355 if constexpr (FilterMode ==
Mode::LP6)
return y[0];
356 else if constexpr (FilterMode ==
Mode::LP12)
return y[1];
357 else if constexpr (FilterMode ==
Mode::LP18)
return y[2];
358 else if constexpr (FilterMode ==
Mode::LP24)
return y[3];
359 else if constexpr (FilterMode ==
Mode::BP12)
return y[0] - y[2];
367 return u - T(4)*y[0] + T(6)*y[1] - T(4)*y[2] + y[3];
Owning audio buffer and non-owning view for real-time DSP processing.
Describes the audio processing environment (sample rate, block size, channels).
RAII scope guard that disables denormal (subnormal) float arithmetic.
Core mathematical utilities for digital signal processing.
Non-owning view over audio channel data.
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
4-pole resonant ladder filter (Moog topology, TPT discretization).
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an audio buffer in-place (thread-safe vs. setters).
T getCutoff() const noexcept
void setDrive(T amount) noexcept
Sets the nonlinear drive amount (thread-safe, lock-free).
Mode
Defines the frequency response mode of the filter output.
@ LP24
4-pole lowpass (24 dB/oct) - classic Moog style.
@ HP24
4-pole highpass (24 dB/oct).
@ LP6
1-pole lowpass (6 dB/oct).
@ BP12
Bandpass (6 dB/oct per side).
@ LP12
2-pole lowpass (12 dB/oct).
@ LP18
3-pole lowpass (18 dB/oct).
T getResonance() const noexcept
void setMode(Mode mode) noexcept
Sets the filter output mode (thread-safe, lock-free).
T getDrive() const noexcept
void setCutoff(T hz) noexcept
Sets the cutoff frequency (thread-safe, lock-free).
std::atomic< Mode > mode_
std::array< ChannelState, kMaxChannels > state_
Mode getMode() const noexcept
void prepare(const AudioSpec &spec) noexcept
Prepares the filter with the current audio environment.
std::atomic< T > resonance_
static constexpr int kMaxChannels
void reset() noexcept
Clears the internal integrator state.
void setResonance(T amount) noexcept
Sets resonance amount (thread-safe, lock-free).
T processSample(T input, int channel) noexcept
Processes a single sample on one channel.
Main namespace for the DSPark framework.
T fastTanh(T x) noexcept
Fast tanh approximation using Pade rational function.
Describes the audio environment for a DSP processor.
double sampleRate
Sample rate in Hz.