85 explicit Resistor(T resistanceOhms) noexcept : value_(resistanceOhms) {}
87 void setResistance(T ohms)
noexcept { value_ = std::max(ohms, T(1e-9)); }
90 void updatePorts() noexcept { R_ =
static_cast<double>(value_); }
91 void reset() noexcept { a_ = 0; }
94 [[nodiscard]] T
reflected() noexcept {
return T(0); }
98 [[nodiscard]] T
getVoltage() const noexcept {
return a_ * T(0.5); }
102 return static_cast<T
>(
static_cast<double>(a_) * 0.5 / R_);
112template <FloatType T>
116 explicit Capacitor(T farads) noexcept : value_(farads) {}
120 void prepare(
double sampleRate)
noexcept { fs_ = sampleRate; }
121 void updatePorts() noexcept { R_ = 1.0 / (2.0 * fs_ *
static_cast<double>(value_)); }
122 void reset() noexcept { state_ = 0; a_ = 0; b_ = 0; }
125 [[nodiscard]] T
reflected() noexcept { b_ = state_;
return b_; }
126 void incident(T a)
noexcept { a_ = a; state_ = a; }
128 [[nodiscard]] T
getVoltage() const noexcept {
return (a_ + b_) * T(0.5); }
131 return static_cast<T
>(
static_cast<double>(a_ - b_) * 0.5 / R_);
136 double fs_ = 48000.0;
138 T state_ = 0, a_ = 0, b_ = 0;
142template <FloatType T>
146 explicit Inductor(T henries) noexcept : value_(henries) {}
148 void setInductance(T henries)
noexcept { value_ = std::max(henries, T(1e-12)); }
150 void prepare(
double sampleRate)
noexcept { fs_ = sampleRate; }
151 void updatePorts() noexcept { R_ = 2.0 * fs_ *
static_cast<double>(value_); }
152 void reset() noexcept { state_ = 0; a_ = 0; b_ = 0; }
155 [[nodiscard]] T
reflected() noexcept { b_ = -state_;
return b_; }
156 void incident(T a)
noexcept { a_ = a; state_ = a; }
158 [[nodiscard]] T
getVoltage() const noexcept {
return (a_ + b_) * T(0.5); }
161 return static_cast<T
>(
static_cast<double>(a_ - b_) * 0.5 / R_);
166 double fs_ = 48000.0;
168 T state_ = 0, a_ = 0, b_ = 0;
177template <FloatType T>
182 : value_(seriesResistanceOhms) {}
196 [[nodiscard]] T
getVoltage() const noexcept {
return (a_ + vs_) * T(0.5); }
222template <FloatType T,
typename Child1,
typename Child2>
226 Series(Child1& c1, Child2& c2) noexcept : c1_(c1), c2_(c2) {}
230 c1_.prepare(sampleRate);
231 c2_.prepare(sampleRate);
237 const double r1 = c1_.portResistance();
238 const double r2 = c2_.portResistance();
240 gamma1_ =
static_cast<T
>(r1 / R_);
242 void reset() noexcept { c1_.reset(); c2_.reset(); a1_ = 0; a2_ = 0; }
248 a1_ = c1_.reflected();
249 a2_ = c2_.reflected();
257 const T sum = a1_ + a2_ - a3;
258 c1_.incident(a1_ - gamma1_ * sum);
259 c2_.incident(a2_ - (sum - gamma1_ * sum));
276template <FloatType T,
typename Child1,
typename Child2>
280 Parallel(Child1& c1, Child2& c2) noexcept : c1_(c1), c2_(c2) {}
284 c1_.prepare(sampleRate);
285 c2_.prepare(sampleRate);
291 const double g1 = 1.0 / c1_.portResistance();
292 const double g2 = 1.0 / c2_.portResistance();
293 R_ = 1.0 / (g1 + g2);
294 d1_ =
static_cast<T
>(g1 / (g1 + g2));
296 void reset() noexcept { c1_.reset(); c2_.reset(); a1_ = 0; a2_ = 0; bUp_ = 0; }
302 a1_ = c1_.reflected();
303 a2_ = c2_.reflected();
304 bUp_ = d1_ * a1_ + (T(1) - d1_) * a2_;
311 const T bNode = bUp_ + a3;
312 c1_.incident(bNode - a1_);
313 c2_.incident(bNode - a2_);
321 T a1_ = 0, a2_ = 0, bUp_ = 0;
325template <FloatType T,
typename Child>
331 void prepare(
double sampleRate)
noexcept { c_.prepare(sampleRate); }
333 void reset() noexcept { c_.reset(); }
335 [[nodiscard]]
double portResistance() const noexcept {
return c_.portResistance(); }
336 [[nodiscard]] T
reflected() noexcept {
return -c_.reflected(); }
353template <FloatType T,
typename Tree>
363 tree_.prepare(sampleRate);
367 void reset() noexcept { tree_.reset(); }
371 const T a = tree_.reflected();
372 tree_.incident(T(2) * vs_ - a);
394[[nodiscard]]
inline double solveMonotonic(
double a,
double seed, F&& evaluate)
noexcept
396 double lo = std::min(0.0, a);
397 double hi = std::max(0.0, a);
398 double v = std::clamp(seed, lo, hi);
399 double prevAbsF = 1e300;
401 for (
int it = 0; it < 48; ++it)
403 double f = 0.0, fp = 1.0;
406 const double absF = std::abs(f);
417 const double vn = v - f / fp;
418 if (vn > lo && vn < hi && absF < 0.7 * prevAbsF)
435template <FloatType T,
typename Tree>
440 T idealityTimesVt = T(1.752 * 0.02585)) noexcept
441 : tree_(tree), is_(saturationCurrent), nvt_(idealityTimesVt) {}
448 tree_.prepare(sampleRate);
453 void reset() noexcept { tree_.reset(); v_ = 0.0; }
457 const double a =
static_cast<double>(tree_.reflected());
458 const double k = 2.0 * tree_.portResistance() *
static_cast<double>(is_);
459 const double nvt =
static_cast<double>(nvt_);
465 const double seed = nvt * std::asinh(a / k);
467 v_ = detail::solveMonotonic(a, seed, [k, nvt, a](
double v,
double& f,
double& fp)
471 const double x = std::clamp(v / nvt, -700.0, 700.0);
472 f = v + k * std::sinh(x) - a;
473 fp = 1.0 + (k / nvt) * std::cosh(x);
476 tree_.incident(
static_cast<T
>(2.0 * v_ - a));
480 [[nodiscard]] T
getVoltage() const noexcept {
return static_cast<T
>(v_); }
493template <FloatType T,
typename Tree>
497 explicit DiodeRoot(Tree& tree, T saturationCurrent = T(2.52e-9),
498 T idealityTimesVt = T(1.752 * 0.02585)) noexcept
499 : tree_(tree), is_(saturationCurrent), nvt_(idealityTimesVt) {}
506 tree_.prepare(sampleRate);
511 void reset() noexcept { tree_.reset(); v_ = 0.0; }
515 const double a =
static_cast<double>(tree_.reflected());
516 const double k = tree_.portResistance() *
static_cast<double>(is_);
517 const double nvt =
static_cast<double>(nvt_);
521 const double seed = (a > 0.0) ? nvt * std::log1p(a / k) : a;
523 v_ = detail::solveMonotonic(a, seed, [k, nvt, a](
double v,
double& f,
double& fp)
525 const double x = std::clamp(v / nvt, -700.0, 700.0);
526 f = v + k * std::expm1(x) - a;
527 fp = 1.0 + (k / nvt) * std::exp(x);
530 tree_.incident(
static_cast<T
>(2.0 * v_ - a));
534 [[nodiscard]] T
getVoltage() const noexcept {
return static_cast<T
>(v_); }
561template <
FloatType T,
typename... Children>
565 static constexpr int kNumPorts =
static_cast<int>(
sizeof...(Children)) + 1;
567 static_assert(
sizeof...(Children) >= 1,
"RType needs at least one child");
574 RType(
const std::array<std::pair<int, int>,
static_cast<size_t>(
kNumPorts)>& portNodes,
575 int numNodes, Children&... children) noexcept
576 : children_(children...), portNodes_(portNodes),
577 numNodes_(std::clamp(numNodes, 1,
kMaxNodes))
579 assert(numNodes >= 1 && numNodes <=
kMaxNodes);
584 for (
auto& [p, m] : portNodes_)
586 assert(p >= -1 && p < numNodes_ && m >= -1 && m < numNodes_);
587 p = std::clamp(p, -1, numNodes_ - 1);
588 m = std::clamp(m, -1, numNodes_ - 1);
594 std::apply([&](
auto&... ch) { (ch.prepare(sampleRate), ...); }, children_);
599 std::apply([&](
auto&... ch) { (ch.updatePorts(), ...); }, children_);
604 std::apply([&](
auto&... ch)
605 { ((portR_[
static_cast<size_t>(i++)] = ch.portResistance()), ...); },
611 assembleConductance(
false);
614 stampCurrent(rhs, 0, 1.0);
616 double rth = portVoltage(rhs, 0);
617 portR_[0] = std::clamp(rth, 1e-6, 1e12);
621 assembleConductance(
true);
626 stampCurrent(v, j, 1.0 / portR_[
static_cast<size_t>(j)]);
630 const double m = portVoltage(v, i);
631 s_[
static_cast<size_t>(i)][
static_cast<size_t>(j)] =
632 2.0 * m - (i == j ? 1.0 : 0.0);
641 std::apply([&](
auto&... ch) { (ch.reset(), ...); }, children_);
650 std::apply([&](
auto&... ch)
651 { ((a_[
static_cast<size_t>(i++)] =
static_cast<double>(ch.reflected())), ...); },
655 b0 += s_[0][
static_cast<size_t>(j)] * a_[
static_cast<size_t>(j)];
656 return static_cast<T
>(b0);
661 a_[0] =
static_cast<double>(aUp);
663 std::apply([&](
auto&... ch)
665 ((ch.incident(
static_cast<T
>(rowDot(i))), ++i), ...);
671 [[nodiscard]]
double rowDot(
int row)
const noexcept
675 acc += s_[
static_cast<size_t>(row)][
static_cast<size_t>(j)] * a_[
static_cast<size_t>(j)];
679 void assembleConductance(
bool includePort0)
noexcept
681 for (
auto& row : g_) row.fill(0.0);
682 for (
int j = includePort0 ? 0 : 1; j <
kNumPorts; ++j)
684 const double g = 1.0 / portR_[
static_cast<size_t>(j)];
685 const int p = portNodes_[
static_cast<size_t>(j)].first;
686 const int m = portNodes_[
static_cast<size_t>(j)].second;
687 if (p >= 0) g_[
static_cast<size_t>(p)][
static_cast<size_t>(p)] += g;
688 if (m >= 0) g_[
static_cast<size_t>(m)][
static_cast<size_t>(m)] += g;
689 if (p >= 0 && m >= 0)
691 g_[
static_cast<size_t>(p)][
static_cast<size_t>(m)] -= g;
692 g_[
static_cast<size_t>(m)][
static_cast<size_t>(p)] -= g;
697 void stampCurrent(
double* rhs,
int port,
double amps)
const noexcept
699 const int p = portNodes_[
static_cast<size_t>(port)].first;
700 const int m = portNodes_[
static_cast<size_t>(port)].second;
701 if (p >= 0) rhs[p] += amps;
702 if (m >= 0) rhs[m] -= amps;
705 [[nodiscard]]
double portVoltage(
const double* v,
int port)
const noexcept
707 const int p = portNodes_[
static_cast<size_t>(port)].first;
708 const int m = portNodes_[
static_cast<size_t>(port)].second;
709 return (p >= 0 ? v[p] : 0.0) - (m >= 0 ? v[m] : 0.0);
713 void factor() noexcept
715 const int n = numNodes_;
716 for (
int i = 0; i < n; ++i)
718 for (
int j = 0; j < n; ++j)
719 lu_[
static_cast<size_t>(i)][
static_cast<size_t>(j)] =
720 g_[
static_cast<size_t>(i)][
static_cast<size_t>(j)];
721 piv_[
static_cast<size_t>(i)] = i;
723 for (
int k = 0; k < n; ++k)
726 for (
int i = k + 1; i < n; ++i)
727 if (std::abs(lu_[
static_cast<size_t>(i)][
static_cast<size_t>(k)])
728 > std::abs(lu_[
static_cast<size_t>(p)][
static_cast<size_t>(k)]))
732 std::swap(lu_[
static_cast<size_t>(p)], lu_[
static_cast<size_t>(k)]);
733 std::swap(piv_[
static_cast<size_t>(p)], piv_[
static_cast<size_t>(k)]);
735 double d = lu_[
static_cast<size_t>(k)][
static_cast<size_t>(k)];
736 if (std::abs(d) < 1e-300)
737 d = (d >= 0.0 ? 1e-300 : -1e-300);
738 const double invD = 1.0 / d;
739 for (
int i = k + 1; i < n; ++i)
741 const double f = lu_[
static_cast<size_t>(i)][
static_cast<size_t>(k)] * invD;
742 lu_[
static_cast<size_t>(i)][
static_cast<size_t>(k)] = f;
743 for (
int j = k + 1; j < n; ++j)
744 lu_[
static_cast<size_t>(i)][
static_cast<size_t>(j)]
745 -= f * lu_[
static_cast<size_t>(k)][
static_cast<size_t>(j)];
751 void solve(
double* rhs)
const noexcept
753 const int n = numNodes_;
755 for (
int i = 0; i < n; ++i)
756 y[i] = rhs[piv_[
static_cast<size_t>(i)]];
757 for (
int i = 0; i < n; ++i)
758 for (
int j = 0; j < i; ++j)
759 y[i] -= lu_[
static_cast<size_t>(i)][
static_cast<size_t>(j)] * y[j];
760 for (
int i = n - 1; i >= 0; --i)
762 for (
int j = i + 1; j < n; ++j)
763 y[i] -= lu_[
static_cast<size_t>(i)][
static_cast<size_t>(j)] * y[j];
764 double d = lu_[
static_cast<size_t>(i)][
static_cast<size_t>(i)];
765 if (std::abs(d) < 1e-300)
766 d = (d >= 0.0 ? 1e-300 : -1e-300);
769 for (
int i = 0; i < n; ++i)
773 std::tuple<Children&...> children_;
774 std::array<std::pair<int, int>,
static_cast<size_t>(
kNumPorts)> portNodes_;
777 std::array<double, static_cast<size_t>(
kNumPorts)> portR_ {};
778 std::array<double, static_cast<size_t>(
kNumPorts)> a_ {};
779 std::array<std::array<double, static_cast<size_t>(
kNumPorts)>,
782 std::array<std::array<double, kMaxNodes>,
kMaxNodes> g_ {};
783 std::array<std::array<double, kMaxNodes>,
kMaxNodes> lu_ {};
784 std::array<int, kMaxNodes> piv_ {};
806template <FloatType T>
814 explicit ToneStackFMV(
double sourceResistance = 1e3,
double loadResistance = 1e6)
815 : rOut_(static_cast<T>(sourceResistance)), c1_(T(0.25e-9)),
816 r1Top_(T(125e3)), r1Bot_(T(125e3)), r4_(T(56e3)), c2_(T(20e-9)),
817 r2_(T(500e3)), c3_(T(20e-9)), r3Top_(T(12.5e3)), r3Bot_(T(12.5e3)),
818 rLoad_(static_cast<T>(loadResistance)),
819 rtype_({ { { kSrc, -1 },
832 rOut_, c1_, r1Top_, r1Bot_, r4_, c2_, r2_, c3_, r3Top_, r3Bot_, rLoad_),
840 root_.prepare(sampleRate);
845 void reset() noexcept { root_.reset(); }
855 treble_ = std::clamp(treble, T(0), T(1));
856 bass_ = std::clamp(bass, T(0), T(1));
857 middle_ = std::clamp(middle, T(0), T(1));
859 const double t =
static_cast<double>(treble_);
860 const double l =
static_cast<double>(bass_) *
static_cast<double>(bass_);
861 const double m =
static_cast<double>(middle_);
862 constexpr double kRmin = 0.5;
864 r1Top_.setResistance(
static_cast<T
>((1.0 - t) * 250e3 + kRmin));
865 r1Bot_.setResistance(
static_cast<T
>(t * 250e3 + kRmin));
866 r2_.setResistance(
static_cast<T
>(l * 1e6 + kRmin));
867 r3Top_.setResistance(
static_cast<T
>((1.0 - m) * 25e3 + kRmin));
868 r3Bot_.setResistance(
static_cast<T
>(m * 25e3 + kRmin));
875 root_.setVoltage(input);
877 return rLoad_.getVoltage();
881 static constexpr int kSrc = 0, kVi = 1, kA = 2, kVo = 3,
882 kB = 4, kS = 5, kC = 6, kW = 7;
883 static constexpr int kNumNodes = 8;
899 T treble_ = T(0.5), bass_ = T(0.5), middle_ = T(0.5);
Core mathematical utilities for digital signal processing.
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
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 (Thévenin 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 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.
ToneStackFMV(double sourceResistance=1e3, double loadResistance=1e6)
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.