The semiconductor junction law, once. More...
#include <algorithm>#include <cmath>Go to the source code of this file.
Classes | |
| struct | pulp::signal::junction::JunctionPair |
| One junction network: the exponential law over one or two antiparallel legs. More... | |
Namespaces | |
| namespace | pulp |
| namespace | pulp::signal |
| namespace | pulp::signal::junction |
Functions | |
| double | pulp::signal::junction::safe_exp (double x) |
| exp(x) with the argument clamped into the safe range. | |
| double | pulp::signal::junction::exprel (double x) |
| (exp(x) − 1) / x, with the removable singularity at 0 handled. | |
Variables | |
| constexpr double | pulp::signal::junction::kThermalVoltage = 0.02585 |
| Thermal voltage k·T/q at 300 K, in volts. | |
| constexpr double | pulp::signal::junction::kMaxExponent = 80.0 |
| Ceiling on an exponent argument, so a transient overshoot cannot produce inf. | |
The semiconductor junction law, once.
The same exponential shows up everywhere in circuit-modelled audio — diode clippers, transistor fuzz pairs, optical and diode-bridge compressors, tape bias stages, tube approximations. Every one of them is
with a different (Is, n) and a different circuit wrapped around it. Written per module it becomes several implementations of one physical constant and one transcendental, each with its own overflow clamp and its own idea of what V_T is. This header is the one implementation.
What it owns: the thermal voltage, the overflow-safe exponent, and the three closed forms every consumer needs — the current, its derivative (the incremental conductance a Newton solve wants), and its antiderivative (what antiderivative-antialiasing wants).
What it does NOT own: device families. A diode's silicon row and a transistor's silicon row are different numbers for different components, and collapsing them into one table would be a false economy — the consuming module declares its own [design parameter] rows and hands them here.
JunctionPair generalises to the antiparallel networks clippers use, and parameterises them by the RECIPROCAL series-diode count per leg:
Reciprocals rather than counts is what makes the parameter continuous: "this leg is absent" is b = 0, a finite value, where a count would have to be infinite. A single junction is the b = 0 case, so one type covers both.
F1 is even when the positive and negative legs match, because the current is then odd. An asymmetric pair is neither odd nor even, and neither is its antiderivative. Worth stating because silently forcing even parity would erase the even-harmonic behavior that set_symmetry exists to create.
It is computed through exprel rather than the algebraically obvious θ/b·(exp(−b·v/θ) − 1), which divides by a vanishing b as a leg is removed and produces a huge value that only cancels in a later subtraction — correct on paper, catastrophic in floating point.
RT contract: everything here is pure, stateless scalar arithmetic over POD. Nothing allocates, locks, or performs I/O, so all of it is safe per sample on the audio thread. Each call costs one or two exp evaluations.
Reference: Shockley (1949) for the diode law; Ebers & Moll, "Large-Signal Behavior of Junction Transistors", Proc. IRE 42(12):1761–1772, 1954, for the transistor forward-active term. They are the same equation.