85 explicit Resistor(T resistanceOhms) noexcept : value_(resistanceOhms) {}
87 void setResistance(T ohms)
noexcept { value_ = std::max(ohms, T(1e-9)); }
93 void updatePorts() noexcept { R_ =
static_cast<double>(value_); }
94 void reset() noexcept { a_ = 0; }
97 [[nodiscard]] T
reflected() noexcept {
return T(0); }
101 [[nodiscard]] T
getVoltage() const noexcept {
return a_ * T(0.5); }
105 return static_cast<T
>(
static_cast<double>(a_) * 0.5 / R_);
115template <FloatType T>
119 explicit Capacitor(T farads) noexcept : value_(farads) {}
126 void prepare(
double sampleRate)
noexcept { fs_ = sampleRate; }
127 void updatePorts() noexcept { R_ = 1.0 / (2.0 * fs_ *
static_cast<double>(value_)); }
128 void reset() noexcept { state_ = 0; a_ = 0; b_ = 0; }
141 [[nodiscard]] T
reflected() noexcept { b_ = state_;
return b_; }
142 void incident(T a)
noexcept { a_ = a; state_ = a; }
144 [[nodiscard]] T
getVoltage() const noexcept {
return (a_ + b_) * T(0.5); }
147 return static_cast<T
>(
static_cast<double>(a_ - b_) * 0.5 / R_);
152 double fs_ = 48000.0;
154 T state_ = 0, a_ = 0, b_ = 0;
158template <FloatType T>
162 explicit Inductor(T henries) noexcept : value_(henries) {}
164 void setInductance(T henries)
noexcept { value_ = std::max(henries, T(1e-12)); }
166 void prepare(
double sampleRate)
noexcept { fs_ = sampleRate; }
167 void updatePorts() noexcept { R_ = 2.0 * fs_ *
static_cast<double>(value_); }
168 void reset() noexcept { state_ = 0; a_ = 0; b_ = 0; }
171 [[nodiscard]] T
reflected() noexcept { b_ = -state_;
return b_; }
172 void incident(T a)
noexcept { a_ = a; state_ = a; }
174 [[nodiscard]] T
getVoltage() const noexcept {
return (a_ + b_) * T(0.5); }
177 return static_cast<T
>(
static_cast<double>(a_ - b_) * 0.5 / R_);
182 double fs_ = 48000.0;
184 T state_ = 0, a_ = 0, b_ = 0;
193template <FloatType T>
198 : value_(seriesResistanceOhms) {}
212 [[nodiscard]] T
getVoltage() const noexcept {
return (a_ + vs_) * T(0.5); }
238template <FloatType T,
typename Child1,
typename Child2>
242 Series(Child1& c1, Child2& c2) noexcept : c1_(c1), c2_(c2) {}
246 c1_.prepare(sampleRate);
247 c2_.prepare(sampleRate);
253 const double r1 = c1_.portResistance();
254 const double r2 = c2_.portResistance();
256 gamma1_ =
static_cast<T
>(r1 / R_);
258 void reset() noexcept { c1_.reset(); c2_.reset(); a1_ = 0; a2_ = 0; }
264 a1_ = c1_.reflected();
265 a2_ = c2_.reflected();
273 const T sum = a1_ + a2_ - a3;
274 c1_.incident(a1_ - gamma1_ * sum);
275 c2_.incident(a2_ - (sum - gamma1_ * sum));
292template <FloatType T,
typename Child1,
typename Child2>
296 Parallel(Child1& c1, Child2& c2) noexcept : c1_(c1), c2_(c2) {}
300 c1_.prepare(sampleRate);
301 c2_.prepare(sampleRate);
307 const double g1 = 1.0 / c1_.portResistance();
308 const double g2 = 1.0 / c2_.portResistance();
309 R_ = 1.0 / (g1 + g2);
310 d1_ =
static_cast<T
>(g1 / (g1 + g2));
312 void reset() noexcept { c1_.reset(); c2_.reset(); a1_ = 0; a2_ = 0; bUp_ = 0; }
318 a1_ = c1_.reflected();
319 a2_ = c2_.reflected();
320 bUp_ = d1_ * a1_ + (T(1) - d1_) * a2_;
327 const T bNode = bUp_ + a3;
328 c1_.incident(bNode - a1_);
329 c2_.incident(bNode - a2_);
337 T a1_ = 0, a2_ = 0, bUp_ = 0;
341template <FloatType T,
typename Child>
347 void prepare(
double sampleRate)
noexcept { c_.prepare(sampleRate); }
349 void reset() noexcept { c_.reset(); }
351 [[nodiscard]]
double portResistance() const noexcept {
return c_.portResistance(); }
352 [[nodiscard]] T
reflected() noexcept {
return -c_.reflected(); }
369template <FloatType T,
typename Tree>
379 tree_.prepare(sampleRate);
383 void reset() noexcept { tree_.reset(); }
387 const T a = tree_.reflected();
388 tree_.incident(T(2) * vs_ - a);
410[[nodiscard]]
inline double solveMonotonic(
double a,
double seed, F&& evaluate)
noexcept
412 double lo = std::min(0.0, a);
413 double hi = std::max(0.0, a);
414 double v = std::clamp(seed, lo, hi);
415 double prevAbsF = 1e300;
417 for (
int it = 0; it < 48; ++it)
419 double f = 0.0, fp = 1.0;
422 const double absF = std::abs(f);
433 const double vn = v - f / fp;
434 if (vn > lo && vn < hi && absF < 0.7 * prevAbsF)
451template <FloatType T,
typename Tree>
456 T idealityTimesVt = T(1.752 * 0.02585)) noexcept
457 : tree_(tree), is_(saturationCurrent), nvt_(idealityTimesVt) {}
464 tree_.prepare(sampleRate);
469 void reset() noexcept { tree_.reset(); v_ = 0.0; }
473 const double a =
static_cast<double>(tree_.reflected());
474 const double k = 2.0 * tree_.portResistance() *
static_cast<double>(is_);
475 const double nvt =
static_cast<double>(nvt_);
481 const double seed = nvt * std::asinh(a / k);
483 v_ = detail::solveMonotonic(a, seed, [k, nvt, a](
double v,
double& f,
double& fp)
487 const double x = std::clamp(v / nvt, -700.0, 700.0);
488 f = v + k * std::sinh(x) - a;
489 fp = 1.0 + (k / nvt) * std::cosh(x);
492 tree_.incident(
static_cast<T
>(2.0 * v_ - a));
496 [[nodiscard]] T
getVoltage() const noexcept {
return static_cast<T
>(v_); }
509template <FloatType T,
typename Tree>
513 explicit DiodeRoot(Tree& tree, T saturationCurrent = T(2.52e-9),
514 T idealityTimesVt = T(1.752 * 0.02585)) noexcept
515 : tree_(tree), is_(saturationCurrent), nvt_(idealityTimesVt) {}
522 tree_.prepare(sampleRate);
527 void reset() noexcept { tree_.reset(); v_ = 0.0; }
531 const double a =
static_cast<double>(tree_.reflected());
532 const double k = tree_.portResistance() *
static_cast<double>(is_);
533 const double nvt =
static_cast<double>(nvt_);
537 const double seed = (a > 0.0) ? nvt * std::log1p(a / k) : a;
539 v_ = detail::solveMonotonic(a, seed, [k, nvt, a](
double v,
double& f,
double& fp)
541 const double x = std::clamp(v / nvt, -700.0, 700.0);
542 f = v + k * std::expm1(x) - a;
543 fp = 1.0 + (k / nvt) * std::exp(x);
546 tree_.incident(
static_cast<T
>(2.0 * v_ - a));
550 [[nodiscard]] T
getVoltage() const noexcept {
return static_cast<T
>(v_); }
577template <
FloatType T,
typename... Children>
582 static constexpr int kNumPorts =
static_cast<int>(
sizeof...(Children)) + 1;
584 static_assert(
sizeof...(Children) >= 1,
"RType needs at least one child");
591 RType(
const std::array<std::pair<int, int>,
static_cast<size_t>(
kNumPorts)>& portNodes,
592 int numNodes, Children&... children) noexcept
593 : children_(children...), portNodes_(portNodes),
594 numNodes_(std::clamp(numNodes, 1,
kMaxNodes))
596 assert(numNodes >= 1 && numNodes <=
kMaxNodes);
601 for (
auto& [p, m] : portNodes_)
603 assert(p >= -1 && p < numNodes_ && m >= -1 && m < numNodes_);
604 p = std::clamp(p, -1, numNodes_ - 1);
605 m = std::clamp(m, -1, numNodes_ - 1);
611 std::apply([&](
auto&... ch) { (ch.prepare(sampleRate), ...); }, children_);
616 std::apply([&](
auto&... ch) { (ch.updatePorts(), ...); }, children_);
621 std::apply([&](
auto&... ch)
622 { ((portR_[
static_cast<size_t>(i++)] = ch.portResistance()), ...); },
628 assembleConductance(
false);
631 stampCurrent(rhs, 0, 1.0);
633 double rth = portVoltage(rhs, 0);
634 portR_[0] = std::clamp(rth, 1e-6, 1e12);
638 assembleConductance(
true);
643 stampCurrent(v, j, 1.0 / portR_[
static_cast<size_t>(j)]);
647 const double m = portVoltage(v, i);
648 s_[
static_cast<size_t>(i)][
static_cast<size_t>(j)] =
649 2.0 * m - (i == j ? 1.0 : 0.0);
658 std::apply([&](
auto&... ch) { (ch.reset(), ...); }, children_);
667 std::apply([&](
auto&... ch)
668 { ((a_[
static_cast<size_t>(i++)] =
static_cast<double>(ch.reflected())), ...); },
672 b0 += s_[0][
static_cast<size_t>(j)] * a_[
static_cast<size_t>(j)];
673 return static_cast<T
>(b0);
678 a_[0] =
static_cast<double>(aUp);
680 std::apply([&](
auto&... ch)
682 ((ch.incident(
static_cast<T
>(rowDot(i))), ++i), ...);
693 void preserveDcMode(
const std::array<
double,
static_cast<size_t>(
kNumPorts)>& mode)
noexcept
696 while (pivot > 0 && mode[
static_cast<size_t>(pivot)] == 0.0) --pivot;
697 if (pivot == 0)
return;
700 double remainder = 0.0;
703 remainder += s_[
static_cast<size_t>(i)][
static_cast<size_t>(j)]
704 * mode[
static_cast<size_t>(j)];
705 s_[
static_cast<size_t>(i)][
static_cast<size_t>(pivot)] =
706 (mode[
static_cast<size_t>(i)] - remainder) / mode[
static_cast<size_t>(pivot)];
710 [[nodiscard]]
double rowDot(
int row)
const noexcept
714 acc += s_[
static_cast<size_t>(row)][
static_cast<size_t>(j)] * a_[
static_cast<size_t>(j)];
718 void assembleConductance(
bool includePort0)
noexcept
720 for (
auto& row : g_) row.fill(0.0);
721 for (
int j = includePort0 ? 0 : 1; j <
kNumPorts; ++j)
723 const double g = 1.0 / portR_[
static_cast<size_t>(j)];
724 const int p = portNodes_[
static_cast<size_t>(j)].first;
725 const int m = portNodes_[
static_cast<size_t>(j)].second;
726 if (p >= 0) g_[
static_cast<size_t>(p)][
static_cast<size_t>(p)] += g;
727 if (m >= 0) g_[
static_cast<size_t>(m)][
static_cast<size_t>(m)] += g;
728 if (p >= 0 && m >= 0)
730 g_[
static_cast<size_t>(p)][
static_cast<size_t>(m)] -= g;
731 g_[
static_cast<size_t>(m)][
static_cast<size_t>(p)] -= g;
736 void stampCurrent(
double* rhs,
int port,
double amps)
const noexcept
738 const int p = portNodes_[
static_cast<size_t>(port)].first;
739 const int m = portNodes_[
static_cast<size_t>(port)].second;
740 if (p >= 0) rhs[p] += amps;
741 if (m >= 0) rhs[m] -= amps;
744 [[nodiscard]]
double portVoltage(
const double* v,
int port)
const noexcept
746 const int p = portNodes_[
static_cast<size_t>(port)].first;
747 const int m = portNodes_[
static_cast<size_t>(port)].second;
748 return (p >= 0 ? v[p] : 0.0) - (m >= 0 ? v[m] : 0.0);
752 void factor() noexcept
754 const int n = numNodes_;
755 for (
int i = 0; i < n; ++i)
757 for (
int j = 0; j < n; ++j)
758 lu_[
static_cast<size_t>(i)][
static_cast<size_t>(j)] =
759 g_[
static_cast<size_t>(i)][
static_cast<size_t>(j)];
760 piv_[
static_cast<size_t>(i)] = i;
762 for (
int k = 0; k < n; ++k)
765 for (
int i = k + 1; i < n; ++i)
766 if (std::abs(lu_[
static_cast<size_t>(i)][
static_cast<size_t>(k)])
767 > std::abs(lu_[
static_cast<size_t>(p)][
static_cast<size_t>(k)]))
771 std::swap(lu_[
static_cast<size_t>(p)], lu_[
static_cast<size_t>(k)]);
772 std::swap(piv_[
static_cast<size_t>(p)], piv_[
static_cast<size_t>(k)]);
774 double d = lu_[
static_cast<size_t>(k)][
static_cast<size_t>(k)];
775 if (std::abs(d) < 1e-300)
776 d = (d >= 0.0 ? 1e-300 : -1e-300);
777 const double invD = 1.0 / d;
778 for (
int i = k + 1; i < n; ++i)
780 const double f = lu_[
static_cast<size_t>(i)][
static_cast<size_t>(k)] * invD;
781 lu_[
static_cast<size_t>(i)][
static_cast<size_t>(k)] = f;
782 for (
int j = k + 1; j < n; ++j)
783 lu_[
static_cast<size_t>(i)][
static_cast<size_t>(j)]
784 -= f * lu_[
static_cast<size_t>(k)][
static_cast<size_t>(j)];
790 void solve(
double* rhs)
const noexcept
792 const int n = numNodes_;
794 for (
int i = 0; i < n; ++i)
795 y[i] = rhs[piv_[
static_cast<size_t>(i)]];
796 for (
int i = 0; i < n; ++i)
797 for (
int j = 0; j < i; ++j)
798 y[i] -= lu_[
static_cast<size_t>(i)][
static_cast<size_t>(j)] * y[j];
799 for (
int i = n - 1; i >= 0; --i)
801 for (
int j = i + 1; j < n; ++j)
802 y[i] -= lu_[
static_cast<size_t>(i)][
static_cast<size_t>(j)] * y[j];
803 double d = lu_[
static_cast<size_t>(i)][
static_cast<size_t>(i)];
804 if (std::abs(d) < 1e-300)
805 d = (d >= 0.0 ? 1e-300 : -1e-300);
808 for (
int i = 0; i < n; ++i)
812 std::tuple<Children&...> children_;
813 std::array<std::pair<int, int>,
static_cast<size_t>(
kNumPorts)> portNodes_;
816 std::array<double, static_cast<size_t>(
kNumPorts)> portR_ {};
817 std::array<double, static_cast<size_t>(
kNumPorts)> a_ {};
818 std::array<std::array<double, static_cast<size_t>(
kNumPorts)>,
821 std::array<std::array<double, kMaxNodes>,
kMaxNodes> g_ {};
822 std::array<std::array<double, kMaxNodes>,
kMaxNodes> lu_ {};
823 std::array<int, kMaxNodes> piv_ {};
845template <FloatType T>
853 explicit ToneStackFMV(
double sourceResistance = 1e3,
double loadResistance = 1e6)
854 : rOut_(static_cast<T>(sourceResistance)), c1_(T(0.25e-9)),
855 r1Top_(T(125e3)), r1Bot_(T(125e3)), r4_(T(56e3)), c2_(T(20e-9)),
856 r2_(T(500e3)), c3_(T(20e-9)), r3Top_(T(12.5e3)), r3Bot_(T(12.5e3)),
857 rLoad_(static_cast<T>(loadResistance)),
858 rtype_({ { { kSrc, -1 },
871 rOut_, c1_, r1Top_, r1Bot_, r4_, c2_, r2_, c3_, r3Top_, r3Bot_, rLoad_),
879 root_.prepare(sampleRate);
884 void reset() noexcept { root_.reset(); }
900 if (inputOffset != T(0))
902 c1_.offsetVoltage(inputOffset);
903 c2_.offsetVoltage(inputOffset);
904 c3_.offsetVoltage(inputOffset);
916 treble_ = std::clamp(treble, T(0), T(1));
917 bass_ = std::clamp(bass, T(0), T(1));
918 middle_ = std::clamp(middle, T(0), T(1));
920 const double t =
static_cast<double>(treble_);
921 const double l =
static_cast<double>(bass_) *
static_cast<double>(bass_);
922 const double m =
static_cast<double>(middle_);
923 constexpr double kRmin = 0.5;
925 r1Top_.setResistance(
static_cast<T
>((1.0 - t) * 250e3 + kRmin));
926 r1Bot_.setResistance(
static_cast<T
>(t * 250e3 + kRmin));
927 r2_.setResistance(
static_cast<T
>(l * 1e6 + kRmin));
928 r3Top_.setResistance(
static_cast<T
>((1.0 - m) * 25e3 + kRmin));
929 r3Bot_.setResistance(
static_cast<T
>(m * 25e3 + kRmin));
931 rtype_.preserveDcMode({1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0});
937 root_.setVoltage(input);
939 return rLoad_.getVoltage();
966 constexpr int kN = 10;
967 struct Element {
int p, m;
double value;
bool capacitor; };
968 const Element elements[] = {
969 { kSrc, kVi,
static_cast<double>(rOut_.getResistance()),
false },
970 { kVi, kA,
static_cast<double>(c1_.getCapacitance()),
true },
971 { kA, kVo,
static_cast<double>(r1Top_.getResistance()),
false },
972 { kVo, kB,
static_cast<double>(r1Bot_.getResistance()),
false },
973 { kVi, kS,
static_cast<double>(r4_.getResistance()),
false },
974 { kS, kB,
static_cast<double>(c2_.getCapacitance()),
true },
975 { kB, kC,
static_cast<double>(r2_.getResistance()),
false },
976 { kS, kW,
static_cast<double>(c3_.getCapacitance()),
true },
977 { kC, kW,
static_cast<double>(r3Top_.getResistance()),
false },
978 { kW, -1,
static_cast<double>(r3Bot_.getResistance()),
false },
979 { kVo, -1,
static_cast<double>(rLoad_.getResistance()),
false } };
981 double rhs[kN][4] {};
984 for (
const auto& e : elements)
986 const int p = e.p - 1, q = e.m < 0 ? -1 : e.m - 1;
989 const int row = 7 + capIndex;
990 if (p >= 0) { m[p][row] += 1.0; m[row][p] += 1.0; }
991 if (q >= 0) { m[q][row] -= 1.0; m[row][q] -= 1.0; }
992 rhs[row][capIndex] = 1.0;
997 const double g = 1.0 / e.value;
1005 if (p >= 0) m[p][p] += g;
1006 if (q >= 0) m[q][q] += g;
1007 if (p >= 0 && q >= 0) { m[p][q] -= g; m[q][p] -= g; }
1010 for (
int k = 0; k < kN; ++k)
1013 for (
int i = k + 1; i < kN; ++i)
1014 if (std::abs(m[i][k]) > std::abs(m[piv][k])) piv = i;
1016 for (
int j = 0; j < kN; ++j) std::swap(m[piv][j], m[k][j]);
1018 for (
int j = 0; j < 4; ++j) std::swap(rhs[piv][j], rhs[k][j]);
1019 for (
int i = k + 1; i < kN; ++i)
1021 const double f = m[i][k] / m[k][k];
1022 if (f == 0.0)
continue;
1023 for (
int j = k; j < kN; ++j) m[i][j] -= f * m[k][j];
1024 for (
int j = 0; j < 4; ++j) rhs[i][j] -= f * rhs[k][j];
1027 for (
int i = kN - 1; i >= 0; --i)
1028 for (
int j = 0; j < 4; ++j)
1030 double v = rhs[i][j];
1031 for (
int k = i + 1; k < kN; ++k) v -= m[i][k] * rhs[k][j];
1032 rhs[i][j] = v / m[i][i];
1035 for (
int j = 0; j < 4; ++j)
1037 for (
int k = 0; k < 3; ++k)
1039 const double dv = rhs[7 + k][j] / out.capacitance[k];
1040 if (j < 3) out.a[k][j] = dv;
1043 if (j < 3) out.c[j] = rhs[kVo - 1][j];
1044 else out.d = rhs[kVo - 1][j];
1050 static constexpr int kSrc = 0, kVi = 1, kA = 2, kVo = 3,
1051 kB = 4, kS = 5, kC = 6, kW = 7;
1052 static constexpr int kNumNodes = 8;
1068 T treble_ = T(0.5), bass_ = T(0.5), middle_ = T(0.5);
Capacitor, bilinear discretization: b[n] = a[n-1], Rp = 1/(2 fs C).
double portResistance() const noexcept
void incident(T a) noexcept
void setCapacitance(T farads) noexcept
void prepare(double sampleRate) noexcept
T getCurrent() const noexcept
T getVoltage() const noexcept
T getCapacitance() const noexcept
Current capacitance in farads.
void offsetVoltage(T offset) noexcept
Shifts the voltage reference without changing capacitor current. Stream-owner only; the finite offset...
Capacitor(T farads) noexcept
void updatePorts() noexcept
Antiparallel diode pair root (the classic clipper nonlinearity).
DiodePairRoot(Tree &tree, T saturationCurrent=T(2.52e-9), T idealityTimesVt=T(1.752 *0.02585)) noexcept
void setIdealityTimesVt(T volts) noexcept
T getVoltage() const noexcept
Voltage across the pair (the clipper output).
void prepare(double sampleRate) noexcept
void setSaturationCurrent(T amps) noexcept
Single Shockley diode root: i(v) = Is (e^{v/(n Vt)} - 1).
DiodeRoot(Tree &tree, T saturationCurrent=T(2.52e-9), T idealityTimesVt=T(1.752 *0.02585)) noexcept
void setIdealityTimesVt(T volts) noexcept
void setSaturationCurrent(T amps) noexcept
T getVoltage() const noexcept
Voltage across the diode.
void prepare(double sampleRate) noexcept
Ideal voltage source closing a linear tree: b = 2 Vs - a.
void setVoltage(T volts) noexcept
IdealVoltageSourceRoot(Tree &tree) noexcept
void prepare(double sampleRate) noexcept
Inductor, bilinear discretization: b[n] = -a[n-1], Rp = 2 fs L.
T getVoltage() const noexcept
Inductor(T henries) noexcept
void updatePorts() noexcept
void setInductance(T henries) noexcept
void incident(T a) noexcept
void prepare(double sampleRate) noexcept
double portResistance() const noexcept
T getCurrent() const noexcept
Two-port polarity inverter (flips the connected subtree's polarity).
void updatePorts() noexcept
Inverter(Child &c) noexcept
void incident(T a) noexcept
void prepare(double sampleRate) noexcept
double portResistance() const noexcept
Adapted three-port parallel connector.
void incident(T a3) noexcept
Parallel(Child1 &c1, Child2 &c2) noexcept
void prepare(double sampleRate) noexcept
double portResistance() const noexcept
void updatePorts() noexcept
N-port R-type adaptor for non-series/parallel interconnections.
void updatePorts() noexcept
RType(const std::array< std::pair< int, int >, static_cast< size_t >(kNumPorts)> &portNodes, int numNodes, Children &... children) noexcept
static constexpr int kNumPorts
static constexpr int kMaxNodes
void prepare(double sampleRate) noexcept
double portResistance() const noexcept
void incident(T aUp) noexcept
Voltage source with series resistance (Thevenin leaf): b = Vs.
void setVoltage(T volts) noexcept
double portResistance() const noexcept
void setResistance(T ohms) noexcept
void prepare(double) noexcept
T getVoltage() const noexcept
Voltage at the source terminals (after the series resistance).
void incident(T a) noexcept
ResistiveVoltageSource(T seriesResistanceOhms) noexcept
void updatePorts() noexcept
Ideal resistor. Absorbs its incident wave (b = 0).
void setResistance(T ohms) noexcept
double portResistance() const noexcept
void updatePorts() noexcept
Resistor(T resistanceOhms) noexcept
void prepare(double) noexcept
void incident(T a) noexcept
T getCurrent() const noexcept
Current through the resistor.
T getResistance() const noexcept
Current resistance in ohms.
T getVoltage() const noexcept
Voltage across the resistor (valid after the root scattered).
Adapted three-port series connector.
double portResistance() const noexcept
void updatePorts() noexcept
void prepare(double sampleRate) noexcept
void incident(T a3) noexcept
Series(Child1 &c1, Child2 &c2) noexcept
Exact Fender '59 Bassman treble/bass/middle tone stack.
void reset() noexcept
Clears capacitor states. RT-safe.
void setControls(T treble, T bass, T middle) noexcept
Sets the three controls, [0, 1] each.
AnalogStateSpace analogStateSpace() const noexcept
ToneStackFMV(double sourceResistance=1e3, double loadResistance=1e6)
void copyStateFrom(const ToneStackFMV &source, T inputOffset=T(0)) noexcept
Copies the capacitor history from an identically prepared stack.
void prepare(double sampleRate) noexcept
Prepares the network (allocates nothing).
T processSample(T input) noexcept
Processes one sample (input volts -> wiper volts).
Constrains a type to IEEE floating-point (float or double).
Main namespace for the DSPark framework.
Continuous-time state space of the same network.