memlnaut-nisps/nisps/dsp/filter.hpp

150 lines
4.9 KiB
C++
Raw Normal View History

// nisps/dsp/filter.hpp — Chamberlin SVF + OnePole smoother.
//
// ChamberlinSVF
// State-variable filter with low/high/band outputs from a single topology.
// Pre-warped via `f = 2 * sin(pi * fc / sr)` and damped by `q = 1 / Q`.
// Stable up to ~0.45 * sample_rate; clamp upstream if you need more headroom.
//
// OnePoleSmoother<NCh>
// Per-channel one-pole low-pass, used to smooth ML-output parameter vectors
// before they hit the audio path. Time constant in milliseconds; the
// coefficient `b1` is computed once per `setup()` call.
#pragma once
#include <array>
#include <cmath>
#include <cstddef>
#include "../core/perf.hpp"
namespace nisps {
class ChamberlinSVF {
public:
ChamberlinSVF() noexcept = default;
void setup(float sample_rate) noexcept {
sample_rate_ = sample_rate;
inv_sr_ = 1.f / sample_rate;
}
void reset() noexcept {
low_ = 0.f;
band_ = 0.f;
}
// Lowpass output. cutoff in Hz, resonance >= 0.5 (1 = no resonance).
NISPS_HOT NISPS_FORCE_INLINE float lowpass(float input, float cutoff,
float resonance) noexcept {
static const float kPi = 3.14159265358979323846f;
const float omega = 2.f * kPi * cutoff * inv_sr_;
const float f = 2.f * std::sin(omega * 0.5f);
const float q = 1.f / resonance;
const float qadjust = 1.f + q * f + f * f;
low_ += f * band_;
const float high = input - low_ - q * band_;
band_ += f * high / qadjust;
return low_;
}
NISPS_HOT NISPS_FORCE_INLINE float highpass(float input, float cutoff,
float resonance) noexcept {
static const float kPi = 3.14159265358979323846f;
const float omega = 2.f * kPi * cutoff * inv_sr_;
const float f = 2.f * std::sin(omega * 0.5f);
const float q = 1.f / resonance;
const float qadjust = 1.f + q * f + f * f;
low_ += f * band_;
const float high = input - low_ - q * band_;
band_ += f * high / qadjust;
return high;
}
NISPS_HOT NISPS_FORCE_INLINE float bandpass(float input, float cutoff,
float resonance) noexcept {
static const float kPi = 3.14159265358979323846f;
const float omega = 2.f * kPi * cutoff * inv_sr_;
const float f = 2.f * std::sin(omega * 0.5f);
const float q = 1.f / resonance;
const float qadjust = 1.f + q * f + f * f;
low_ += f * band_;
const float high = input - low_ - q * band_;
band_ += f * high / qadjust;
return band_;
}
private:
float sample_rate_ = 48000.f;
float inv_sr_ = 1.f / 48000.f;
float low_ = 0.f;
float band_ = 0.f;
};
template <std::size_t NCh>
class OnePoleSmoother {
public:
OnePoleSmoother() noexcept = default;
void setup(float time_ms, float sample_rate) noexcept {
// b1 such that step response decays 90% in `time_ms`.
// b1 = 0.1 ^ (1 / (time_ms * 1e-3 * sr))
b1_ = std::pow(0.1f, 1.f / (time_ms * 0.001f * sample_rate));
for (auto& v : y_) v = 0.f;
}
NISPS_HOT NISPS_FORCE_INLINE void process(const float* x, float* y) noexcept {
for (std::size_t c = 0; c < NCh; ++c) {
const float xv = x[c];
const float yv = xv + b1_ * (y_[c] - xv);
y_[c] = yv;
y[c] = yv;
}
}
private:
float b1_ = 0.f;
std::array<float, NCh> y_{};
};
// Single-channel envelope follower (positive-peak), abs-rectifier.
// if |x| > env: env = attack_coef * (env - |x|) + |x|
// else: env = release_coef * (env - |x|) + |x|
// Coefficients computed via `pow(0.01, 1 / (ms * sr * 1e-3))` — same as
// maximilian's `maxiEnvelopeFollowerType`.
class EnvelopeFollower {
public:
EnvelopeFollower() noexcept = default;
void setup(float sample_rate, float attack_ms, float release_ms) noexcept {
sample_rate_ = sample_rate;
set_attack(attack_ms);
set_release(release_ms);
}
void set_attack(float attack_ms) noexcept {
attack_ = std::pow(0.01f, 1.f / (attack_ms * sample_rate_ * 0.001f));
}
void set_release(float release_ms) noexcept {
release_ = std::pow(0.01f, 1.f / (release_ms * sample_rate_ * 0.001f));
}
NISPS_HOT NISPS_FORCE_INLINE float play(float input) noexcept {
const float a = std::fabs(input);
if (a > env_) env_ = attack_ * (env_ - a) + a;
else env_ = release_ * (env_ - a) + a;
return env_;
}
void reset() noexcept { env_ = 0.f; }
float value() const noexcept { return env_; }
private:
float sample_rate_ = 48000.f;
float attack_ = 0.f;
float release_ = 0.f;
float env_ = 0.f;
};
} // namespace nisps