135 network.saturation_current = 1
e-6;
136 network.ideality = 1.1;
139 network.saturation_current = 1
e-18;
140 network.ideality = 2.0;
144 network.saturation_current = 1
e-14;
145 network.ideality = 1.0;
156 if (!std::isfinite(symmetry))
return;
157 const double s = std::clamp(symmetry, -1.0, 1.0);
158 network.leg_a = 1.0 / (1.0 + std::max(0.0, s));
159 network.leg_b = 1.0 + std::min(0.0, s);
167struct ClipperSolverConfig;
194template <
typename Res
idual,
typename Derivative>
196 double initial,
double alternate,
double tolerance,
197 int max_iterations,
int& iterations_out) {
200 if (lo > hi) std::swap(lo, hi);
201 const double pad = 1
e-9 + 0.01 * (hi - lo);
211 for (
int widen = 0; widen < 8 && f_lo * f_hi > 0.0; ++widen) {
212 const double span = std::max(hi - lo, 1
e-3);
224 double v = std::clamp(initial, lo, hi);
225 const double candidate = std::clamp(alternate, lo, hi);
226 if (std::abs(f(candidate)) < std::abs(f(v))) v = candidate;
228 for (; iterations < max_iterations; ++iterations) {
229 const double fv = f(v);
230 if (std::abs(fv) < tolerance)
break;
234 if (f_lo * fv < 0.0) {
242 const double dfv = df(v);
243 double next = dfv > 0.0 ? v - fv / dfv : v;
247 if (!std::isfinite(next) || next <= lo || next >= hi) next = 0.5 * (lo + hi);
250 iterations_out = iterations;
251 return std::isfinite(v) ? v : 0.0;
291 double history_voltage,
double history_flow,
292 double source_current,
double flow_weight,
293 double c_over_step,
double resistance,
294 double initial,
int& iterations_out) {
295 const auto residual = [&](
double v) {
296 const double flow = source_current - v / resistance - network.current(v);
297 return c_over_step * (v - history_voltage) -
298 flow_weight * (history_flow + flow);
300 const auto derivative = [&](
double v) {
302 flow_weight * (1.0 / resistance + network.conductance(v));
305 const double open_circuit = source_current * resistance;
307 residual, derivative,
308 std::min({0.0, history_voltage, open_circuit}),
309 std::max({0.0, history_voltage, open_circuit}),
310 initial, network.conduction_estimate(open_circuit, resistance),
324 double previous_source_current,
double source_current,
325 double capacitance,
double timestep,
double resistance,
326 int& iterations_out) {
327 constexpr double gamma = 2.0 - 1.4142135623730950488016887242097;
328 constexpr double bdf2_step = (1.0 - gamma) / (2.0 - gamma);
329 constexpr double stage_weight = 1.0 / (gamma * (2.0 - gamma));
331 const double source_at_stage =
332 previous_source_current + gamma * (source_current - previous_source_current);
333 const double previous_flow = previous_source_current - previous_voltage / resistance -
334 network.current(previous_voltage);
335 const double c_over_stage_t = capacitance / (gamma * timestep);
336 int stage_iterations = 0;
338 network, previous_voltage, previous_flow, source_at_stage,
339 0.5, c_over_stage_t, resistance,
340 previous_voltage, stage_iterations);
342 const double bdf2_history =
343 stage_weight * stage_voltage + (1.0 - stage_weight) * previous_voltage;
344 const double c_over_bdf2_t = capacitance / (bdf2_step * timestep);
345 int final_iterations = 0;
347 network, bdf2_history, 0.0, source_current,
348 1.0, c_over_bdf2_t, resistance,
349 stage_voltage, final_iterations);
350 iterations_out = stage_iterations + final_iterations;
361template <
typename SampleType =
float>
382 sample_rate_ = std::isfinite(sample_rate) && sample_rate > 0.0 ? sample_rate
384 timestep_ = 1.0 / sample_rate_;
394 if (std::isfinite(ohms)) resistance_ = std::max(ohms, 1.0);
400 if (std::isfinite(farads)) capacitance_ = std::max(farads, 0.0);
432 previous_source_current_ = 0.0;
433 previous_voltage_ = 0.0;
434 last_iterations_ = 0;
442 if (!std::isfinite(
static_cast<double>(input))) {
444 return SampleType{0};
446 const double vin =
static_cast<double>(input);
447 const double v = solve(vin);
448 if (!std::isfinite(v)) {
450 return SampleType{0};
452 previous_source_current_ = vin / resistance_;
453 previous_voltage_ = v;
465 return (vin - v) / resistance_ - network_.current(v);
469 double solve(
double vin) {
477 const bool memoryless = capacitance_ <= 0.0;
480 vin / resistance_, capacitance_, timestep_, resistance_,
484 const auto residual = [&](
double v) {
485 return network_.
current(v) - (vin - v) / resistance_;
489 const auto derivative = [&](
double v) {
490 return network_.
conductance(v) + 1.0 / resistance_;
494 residual, derivative,
495 std::min({0.0, previous_voltage_, vin}),
496 std::max({0.0, previous_voltage_, vin}),
498 network_.conduction_estimate(vin, resistance_),
503 junction::JunctionPair network_{};
504 double sample_rate_ = 44100.0;
505 double timestep_ = 1.0 / 44100.0;
509 double voltage_ = 0.0;
510 double previous_source_current_ = 0.0;
511 double previous_voltage_ = 0.0;
512 int last_iterations_ = 0;
534template <
typename SampleType =
float>
560 update_capacitance();
564 sample_rate_ = std::isfinite(sample_rate) && sample_rate > 0.0 ? sample_rate
566 timestep_ = 1.0 / sample_rate_;
567 ground_stage_.prepare(sample_rate_);
576 ground_stage_.set_diode_model(model);
581 ground_stage_.set_symmetry(symmetry);
585 if (!std::isfinite(ohms))
return;
586 feedback_resistance_ = std::max(ohms, 1.0);
587 update_capacitance();
591 if (std::isfinite(ohms)) input_resistance_ = std::max(ohms, 1.0);
595 if (!std::isfinite(hz))
return;
596 knee_corner_hz_ = std::clamp(hz, 1.0, 20000.0);
597 update_capacitance();
605 double linear_gain()
const {
return feedback_resistance_ / input_resistance_; }
608 previous_source_current_ = 0.0;
609 previous_voltage_ = 0.0;
610 last_iterations_ = 0;
611 ground_stage_.reset();
615 return topology_ == Topology::to_ground ? ground_stage_.last_iteration_count()
620 if (!std::isfinite(
static_cast<double>(input))) {
622 return SampleType{0};
624 const double vin =
static_cast<double>(input);
625 if (topology_ == Topology::to_ground) {
634 return ground_stage_.process(
static_cast<SampleType
>(vin *
linear_gain()));
639 const double forcing = vin / input_resistance_;
640 const double v = solve(forcing);
641 if (!std::isfinite(v)) {
643 return SampleType{0};
645 previous_source_current_ = forcing;
646 previous_voltage_ = v;
653 void update_capacitance() {
654 constexpr double two_pi = 6.283185307179586476925286766559;
655 capacitance_ = 1.0 / (two_pi * feedback_resistance_ * knee_corner_hz_);
658 double solve(
double source_current) {
660 source_current, capacitance_, timestep_,
661 feedback_resistance_, last_iterations_);
664 junction::JunctionPair network_{};
665 Topology topology_ = Topology::in_loop;
666 double sample_rate_ = 44100.0;
667 double timestep_ = 1.0 / 44100.0;
671 double capacitance_ = 0.0;
673 double previous_source_current_ = 0.0;
674 double previous_voltage_ = 0.0;
675 int last_iterations_ = 0;
678 DiodeClipperT<SampleType> ground_stage_{};
702template <
typename SampleType =
float>
711 pre_.prepare(
static_cast<SampleType
>(sample_rate));
712 post_.prepare(
static_cast<SampleType
>(sample_rate));
717 if (!std::isfinite(hz))
return;
718 pre_hz_ = std::clamp(hz, 20.0, 20000.0);
719 pre_.set_cutoff(
static_cast<SampleType
>(pre_hz_));
723 if (!std::isfinite(hz))
return;
724 post_hz_ = std::clamp(hz, 20.0, 20000.0);
725 post_.set_cutoff(
static_cast<SampleType
>(post_hz_));
731 if (!std::isfinite(db))
return;
737 if (std::isfinite(mix01)) mix_ = std::clamp(mix01, 0.0, 1.0);
749 if (!std::isfinite(
static_cast<double>(input))) {
751 return SampleType{0};
753 const auto out = pre_.process(input);
754 const double shaped =
755 static_cast<double>(out.lowpass) + pre_gain_ *
static_cast<double>(out.highpass);
756 const double result =
static_cast<double>(input) +
757 mix_ * (shaped -
static_cast<double>(input));
758 if (!std::isfinite(result)) {
760 return SampleType{0};
762 return static_cast<SampleType
>(result);
767 if (!std::isfinite(
static_cast<double>(input))) {
769 return SampleType{0};
771 const double filtered =
static_cast<double>(post_.process_lowpass(input));
772 const double result =
static_cast<double>(input) +
773 mix_ * (filtered -
static_cast<double>(input));
774 if (!std::isfinite(result)) {
776 return SampleType{0};
778 return static_cast<SampleType
>(result);
783 TptFilterT<SampleType> post_{};
784 double pre_hz_ = 720.0;
785 double post_hz_ = 4000.0;
786 double pre_gain_ = 1.0;
The diode-to-ground clipper: series resistor, shunt diode network, optional shunt capacitor.
Definition distortion.hpp:362
void reset()
Antiderivative antialiasing is NOT offered on this class, deliberately.
Definition distortion.hpp:430
void prepare(double sample_rate)
Definition distortion.hpp:381
DiodeModel Model
Definition distortion.hpp:364
double voltage() const
The node voltage without advancing — for tests that check the ODE's residual directly.
Definition distortion.hpp:460
DiodeClipperT()
Definition distortion.hpp:376
int last_iteration_count() const
Iterations the last solve took.
Definition distortion.hpp:439
SampleType process(SampleType input)
Definition distortion.hpp:441
static constexpr double kResistanceDefault
Definition distortion.hpp:369
void set_resistance(double ohms)
Definition distortion.hpp:393
static constexpr double kCapacitanceDefault
Definition distortion.hpp:374
void set_symmetry(double symmetry)
−1 = single diode (half-wave), 0 = matched pair, +1 = mismatched pair.
Definition distortion.hpp:391
void set_diode_model(Model model)
Definition distortion.hpp:388
double resistive_residual(double vin, double v) const
(vin − v)/R − i_D(v), the residual the memoryless (C = 0) solution must drive to zero.
Definition distortion.hpp:464
void set_capacitance(double farads)
Zero is the degenerate memoryless configuration: a solver limit case for tests, not a shipped preset.
Definition distortion.hpp:399
The op-amp gain stage with the diode network either at its output or across its feedback resistor.
Definition distortion.hpp:535
int last_iteration_count() const
Definition distortion.hpp:614
void set_feedback_resistance(double ohms)
Definition distortion.hpp:584
static constexpr double kFeedbackResistanceDefault
Definition distortion.hpp:543
static constexpr double kInputResistanceDefault
Definition distortion.hpp:547
static constexpr double kKneeCornerHzDefault
Definition distortion.hpp:555
void set_topology(Topology topology)
Definition distortion.hpp:571
ClipperTopology Topology
Definition distortion.hpp:538
void set_input_resistance(double ohms)
Definition distortion.hpp:590
Topology topology() const
Definition distortion.hpp:572
double linear_gain() const
The stage's linear (small-signal) gain, Rf/Rin — the bound the loop can never exceed at frequencies b...
Definition distortion.hpp:605
void set_knee_corner_hz(double hz)
Definition distortion.hpp:594
FeedbackClipperT()
Definition distortion.hpp:557
void reset()
Definition distortion.hpp:607
DiodeModel Model
Definition distortion.hpp:537
void prepare(double sample_rate)
Definition distortion.hpp:563
SampleType process(SampleType input)
Definition distortion.hpp:619
void set_diode_model(Model model)
Definition distortion.hpp:574
void set_symmetry(double symmetry)
Definition distortion.hpp:579
Pre/post tone shaping around the clipper: a two-pole tilt pair.
Definition distortion.hpp:703
void set_pre_tone_hz(double hz)
Definition distortion.hpp:716
void prepare(double sample_rate)
Definition distortion.hpp:710
SampleType process_post(SampleType input)
The post stage: a plain one-pole low-pass, the de-fizz.
Definition distortion.hpp:766
void set_post_tone_hz(double hz)
Definition distortion.hpp:722
void set_pre_gain_db(double db)
Pre-emphasis shelf gain in dB.
Definition distortion.hpp:730
void reset()
Definition distortion.hpp:740
SampleType process_pre(SampleType input)
The pre-emphasis stage: a high shelf built from the one-pole's complementary outputs,...
Definition distortion.hpp:748
static constexpr double kPreGainDbMax
Definition distortion.hpp:708
void set_tone_mix(double mix01)
Linear crossfade of the shaped stage against its bypass.
Definition distortion.hpp:736
First-order TPT (trapezoidal integration) filter.
Definition tpt_filter.hpp:30
The semiconductor junction law, once.
Round-2 effect-wave vocabulary.
Definition analysis_frontends.hpp:50
void apply_diode_model(junction::JunctionPair &network, DiodeModel model)
The (Is, n) pair for a device family.
Definition distortion.hpp:132
void apply_symmetry(junction::JunctionPair &network, double symmetry)
symmetry ∈ [−1, +1] → the two legs' reciprocal series counts.
Definition distortion.hpp:155
double solve_implicit_flow_stage(const junction::JunctionPair &network, double history_voltage, double history_flow, double source_current, double flow_weight, double c_over_step, double resistance, double initial, int &iterations_out)
Solves one implicit stage of C·dv/dt = source_current − v/R − i_D(v).
Definition distortion.hpp:290
double solve_tr_bdf2(const junction::JunctionPair &network, double previous_voltage, double previous_source_current, double source_current, double capacitance, double timestep, double resistance, int &iterations_out)
One L-stable TR-BDF2 step of C·dv/dt = source_current(t) − v/R − i_D(v).
Definition distortion.hpp:323
double solve_safeguarded_newton(Residual &&f, Derivative &&df, double lo, double hi, double initial, double alternate, double tolerance, int max_iterations, int &iterations_out)
One monotone implicit stage, solved by a SAFEGUARDED Newton iteration.
Definition distortion.hpp:195
T db_to_linear(T db)
Amplitude dB → linear gain. 0 dB → 1, −6.0206 dB → 0.5.
Definition units.hpp:71
Definition additive_bank.hpp:218
FeedbackClipperT< float > FeedbackClipper
Definition distortion.hpp:681
ToneStackT< double > ToneStack64
Definition distortion.hpp:791
DiodeClipperT< double > DiodeClipper64
Definition distortion.hpp:516
ToneStackT< float > ToneStack
Definition distortion.hpp:790
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
DiodeClipperT< float > DiodeClipper
Definition distortion.hpp:515
@ e
Definition formant_filter_bank.hpp:20
DiodeModel
Diode device families. The members differ only in (Is, n).
Definition distortion.hpp:112
@ led
Smallest Is, highest n: latest onset, steepest slope.
Definition distortion.hpp:115
@ silicon
Small-signal switching class. Clips ~0.6 V, hard.
Definition distortion.hpp:113
@ germanium
Much larger Is: turns on earlier and softer.
Definition distortion.hpp:114
FeedbackClipperT< double > FeedbackClipper64
Definition distortion.hpp:682
ClipperTopology
Where the diode pair sits relative to the gain stage.
Definition distortion.hpp:119
@ in_loop
Across the op-amp's feedback resistor. Knee tracks drive.
Definition distortion.hpp:121
@ to_ground
Shunt across the stage output. Level-independent knee.
Definition distortion.hpp:120
Definition distortion.hpp:256
static constexpr double kAdaaEpsilon
ADAA fallback guard on |v[n] − v[n−1]|.
Definition distortion.hpp:277
static constexpr double kNewtonTolerance
Convergence threshold normalized to the current produced by one volt through the modeled resistance.
Definition distortion.hpp:262
static constexpr int kMaxNewtonIterations
Hard cap on inner-loop iterations per sample.
Definition distortion.hpp:268
static constexpr int kMaxNewtonIterationsPerSample
TR-BDF2 has two implicit stages, each with the cap above.
Definition distortion.hpp:272
One junction network: the exponential law over one or two antiparallel legs.
Definition junction.hpp:94
double conductance(double v) const
di/dv — the incremental conductance a Newton solve needs.
Definition junction.hpp:123
double current(double v) const
i(v).
Definition junction.hpp:116
First-order topology-preserving transform (TPT) filter.
The real-unit vocabulary shared by every DSP block in the catalog.