Last active
October 15, 2024 18:44
-
-
Save rygrob/be9dfc6391800bf7ba77ec8cad46f5ae to your computer and use it in GitHub Desktop.
TPT Filters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef tpt_filters_h | |
#define tpt_filters_h | |
#include <cmath> | |
#include <numbers> | |
// MARK: - One-pole Filter | |
template<typename X> | |
struct Tpt_1pole { | |
enum class Type { | |
lpf, hpf, apf | |
}; | |
Tpt_1pole() = default; | |
Tpt_1pole(Type type) : _type{type} {} | |
struct Params { | |
X fc = 1000; | |
}; | |
auto reset(float sr) -> void | |
{ | |
sp = 1 / sr; | |
_s = 0; | |
prepare({}); | |
} | |
auto prepare(const Params& params) -> void | |
{ | |
this->params = params; | |
const auto g = std::tan(std::numbers::pi_v<X> * params.fc * sp); | |
_G = g / (1 + g); | |
} | |
auto process(X x) -> X | |
{ | |
// See: https://www.discodsp.net/VAFilterDesign_2.1.2.pdf (p. 76) | |
const auto v = _G * (x - _s); | |
const auto yl = v + _s; | |
const auto yh = x - yl; | |
const auto ya = yl - yh; | |
_s = yl + v; | |
switch (_type) { | |
case Type::lpf: | |
return yl; | |
case Type::hpf: | |
return yh; | |
case Type::apf: | |
return ya; | |
} | |
} | |
private: | |
const Type _type{Type::lpf}; | |
Params params{}; | |
X sp = 1 / 48000.f; | |
X _G{}; | |
X _s{}; | |
}; | |
// MARK: - SVF | |
template<typename X> | |
struct Tpt_svf { | |
enum class Type { | |
lpf, hpf, bpfq, bpf1 | |
}; | |
Tpt_svf() = default; | |
Tpt_svf(Type type) : _type(type) {}; | |
struct Params { | |
X fc = 1000; | |
X q = 0.707f; | |
}; | |
auto reset(float sr) -> void | |
{ | |
sp = 1 / sr; | |
_s1 = _s2 = 0; | |
prepare({}); | |
} | |
auto prepare(const Params& params) -> void | |
{ | |
this->params = params; | |
_g = std::tan(std::numbers::pi_v<X> * params.fc * sp); | |
_R2 = 1 / params.q; | |
_d = 1 / (1 + _R2 * _g + _g * _g); | |
} | |
auto process(X x) -> X | |
{ | |
// See: https://www.discodsp.net/VAFilterDesign_2.1.2.pdf (p. 109) | |
const auto g1 = _R2 + _g; | |
const auto yh = _d * (x - g1 * _s1 - _s2); | |
const auto yb = yh * _g + _s1; | |
const auto yl = yb * _g + _s2; | |
_s1 = yh * _g + yb; | |
_s2 = yb * _g + yl; | |
switch (_type) { | |
case Type::lpf: | |
return yl; | |
case Type::hpf: | |
return yh; | |
case Type::bpfq: | |
return yb; | |
case Type::bpf1: | |
return _R2 * yb; | |
} | |
} | |
private: | |
const Type _type{Type::lpf}; | |
Params params{}; | |
X sp = 1 / 48000.f; | |
X _g{}; | |
X _d{}; | |
X _R2{}; | |
X _s1{}; | |
X _s2{}; | |
}; | |
// MARK: - Ladder Filter | |
template<typename X> | |
struct Tpt_ladder { | |
enum class Type { | |
lpf, hpf | |
}; | |
Tpt_ladder() = default; | |
Tpt_ladder(Type type) : _type{type} {}; | |
struct Params { | |
X fc = 1000; | |
X k = 0; // 0...4 | |
}; | |
auto reset(float sr) -> void | |
{ | |
sp = 1 / sr; | |
_s0 = _s1 = _s2 = _s3 = 0; | |
prepare({}); | |
} | |
auto prepare(const Params& params) -> void | |
{ | |
this->params = params; | |
const auto g = std::tan(std::numbers::pi_v<X> * params.fc * sp); | |
_g = g; | |
_G = [&]() { | |
switch (_type) { | |
case Type::lpf: | |
return g / (1 + g); | |
case Type::hpf: | |
return 1 / (1 + g); | |
} | |
}(); | |
} | |
auto process(X x) -> X | |
{ | |
// See: https://www.discodsp.net/VAFilterDesign_2.1.2.pdf (p. 137) | |
const auto k = params.k; | |
const auto G = _G; | |
// Get state scalar, so we can say y = G * x + S. | |
const auto gp = [&]() { | |
switch (_type) { | |
case Type::lpf: | |
return 1 - G; // 1 / (1 + g) | |
case Type::hpf: | |
return -G; | |
} | |
}(); | |
const auto s0 = _s0 * gp; | |
const auto s1 = _s1 * gp; | |
const auto s2 = _s2 * gp; | |
const auto s3 = _s3 * gp; | |
const auto big_G = G*G*G*G; | |
const auto S = G*G*G*s0 + G*G*s1 + G*s2 + s3; | |
const auto u = (x - k * S) / (1 + k * big_G); | |
const auto y0 = G * u + s0; | |
const auto y1 = G * y0 + s1; | |
const auto y2 = G * y1 + s2; | |
const auto y3 = G * y2 + s3; | |
_s0 = next_state(y0, _s0); | |
_s1 = next_state(y1, _s1); | |
_s2 = next_state(y2, _s2); | |
_s3 = next_state(y3, _s3); | |
return y3; | |
} | |
private: | |
const Type _type{Type::lpf}; | |
Params params{}; | |
X sp = 1 / 48000.f; | |
X _g{}; | |
X _G{}; | |
X _s0{}; | |
X _s1{}; | |
X _s2{}; | |
X _s3{}; | |
auto next_state(X y, X s) -> X | |
{ | |
switch (_type) { | |
case Type::lpf: | |
return 2 * y - s; | |
case Type::hpf: | |
return 2 * _g * y + s; // This is indeed 2 * ylp - s | |
} | |
} | |
}; | |
// MARK: - Transposed Sallen-Key Filter | |
template<typename X> | |
struct Tpt_tsk { | |
enum class Type { | |
lpf, hpf | |
}; | |
Tpt_tsk() = default; | |
Tpt_tsk(Type type) : _type{type} {}; | |
struct Params { | |
X fc = 1000; | |
X k = 0; // 0...2 | |
}; | |
auto reset(float sr) -> void | |
{ | |
sp = 1 / sr; | |
_s0 = _s1 = 0; | |
prepare({}); | |
} | |
auto prepare(const Params& params) -> void | |
{ | |
this->params = params; | |
const auto g = std::tan(std::numbers::pi_v<X> * params.fc * sp); | |
_g = g; | |
_G = [&]() { | |
switch (_type) { | |
case Type::lpf: | |
return g / (1 + g); | |
case Type::hpf: | |
return 1 / (1 + g); | |
} | |
}(); | |
} | |
auto process(X x) -> X | |
{ | |
// See: https://www.discodsp.net/VAFilterDesign_2.1.2.pdf (p. 153) | |
const auto k = params.k; | |
const auto G = _G; | |
// Get state scalar, so we can say y = G * x + S. | |
const auto gp = [&]() { | |
switch (_type) { | |
case Type::lpf: | |
return 1 - G; // 1 / (1 + g) | |
case Type::hpf: | |
return -G; | |
} | |
}(); | |
const auto s0 = _s0 * gp; | |
const auto s1 = _s1 * gp; | |
// Also: https://www.youtube.com/watch?v=WatTtm8Py48 | |
const auto u = (-G*G*k*x - G*k*s0 + G*k*x + k*s0 - k*s1) / (G*G*k - G*k + 1); | |
const auto xp = x + u; | |
const auto y0 = G * xp + s0; | |
const auto y1 = G * y0 + s1; | |
// In lpf mode, for example, we can also get: | |
// const auto y0h = xp - y0; | |
// const auto y1h = y0 - y1; // yb | |
// const auto yh = y0h - y1h; // yh | |
_s0 = next_state(y0, _s0); | |
_s1 = next_state(y1, _s1); | |
return y1; | |
} | |
private: | |
const Type _type{Type::lpf}; | |
Params params{}; | |
X sp = 1 / 48000.f; | |
X _g{}; | |
X _G{}; | |
X _s0{}; | |
X _s1{}; | |
auto next_state(X y, X s) -> X | |
{ | |
switch (_type) { | |
case Type::lpf: | |
return 2 * y - s; | |
case Type::hpf: | |
return 2 * _g * y + s; // This is indeed 2 * ylp - s | |
} | |
} | |
}; | |
// MARK: - Diode Ladder Filter | |
template<typename X> | |
struct Tpt_diode { | |
struct Params { | |
X fc = 1000; | |
X k = 0; // 0...16 | |
}; | |
auto reset(float sr) -> void | |
{ | |
sp = 1 / sr; | |
_s1 = _s2 = _s3 = _s4 = 0; | |
prepare({}); | |
} | |
auto prepare(const Params& params) -> void | |
{ | |
this->params = params; | |
const auto g = std::tan(std::numbers::pi_v<X> * params.fc * sp); | |
_G = g / (1 + g); | |
} | |
auto process(X x) -> X | |
{ | |
// See: https://www.discodsp.net/VAFilterDesign_2.1.2.pdf (p. 170) | |
const auto k = params.k; | |
const auto G = _G; | |
const auto gp = 1 - G; // 1 / (1 + g) | |
const auto s1 = _s1 * gp; | |
const auto s2 = _s2 * gp; | |
const auto s3 = _s3 * gp; | |
const auto s4 = _s4 * gp; | |
const auto gh = 0.5f * G; | |
const auto g34 = gh; | |
const auto g23 = gh / (1 - gh * gh); | |
const auto g12 = gh / (1 - gh * g23); | |
const auto g01 = 2 * gh / (1 - 2 * gh * g12); | |
const auto s34 = s4; | |
const auto s23 = (gh * s4 + s3) / (1 - gh * gh); | |
const auto s12 = (gh * s23 + s2) / (1 - gh * g23); | |
const auto s01 = (2 * gh * s12 + s1) / (1 - 2 * gh * g12); | |
const auto g04 = g01*g12*g23*g34; | |
const auto s04 = g12*g23*g34*s01 + g23*g34*s12 + g34*s23 + s34; | |
const auto u = (x - k * s04) / (1 + k * g04); | |
const auto y1 = g01 * u + s01; | |
const auto y2 = g12 * y1 + s12; | |
const auto y3 = g23 * y2 + s23; | |
const auto y4 = g34 * y3 + s34; | |
_s1 = 2 * y1 - _s1; | |
_s2 = 2 * y2 - _s2; | |
_s3 = 2 * y3 - _s3; | |
_s4 = 2 * y4 - _s4; | |
return y4; | |
} | |
private: | |
Params params{}; | |
X sp = 1 / 48000.f; | |
X _G{}; | |
X _s1{}; | |
X _s2{}; | |
X _s3{}; | |
X _s4{}; | |
}; | |
#endif /* tpt_filters_h */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment