The two-transistor direct-coupled feedback pair — the fuzz circuit whose character comes from its FEEDBACK, not from its clipping. More...
#include <pulp/signal/dc_blocker.hpp>#include <pulp/signal/junction.hpp>#include <pulp/signal/denormal.hpp>#include <pulp/signal/oversampling.hpp>#include <pulp/signal/rng.hpp>#include <pulp/signal/units.hpp>#include <algorithm>#include <cmath>#include <cstdint>Go to the source code of this file.
Classes | |
| class | pulp::signal::FuzzPairT< SampleType > |
| The two-transistor direct-coupled feedback pair. More... | |
Namespaces | |
| namespace | pulp |
| namespace | pulp::signal |
Typedefs | |
| using | pulp::signal::FuzzPair = FuzzPairT<float> |
| using | pulp::signal::FuzzPair64 = FuzzPairT<double> |
Enumerations | |
| enum class | pulp::signal::FuzzDevice : std::uint8_t { pulp::signal::germanium , pulp::signal::silicon } |
| The two device families. They differ only in (n, Is). More... | |
The two-transistor direct-coupled feedback pair — the fuzz circuit whose character comes from its FEEDBACK, not from its clipping.
A diode clipper distorts because a diode conducts. This circuit distorts because two exponential junctions are wrapped in a feedback loop that runs deliberately close to instability, and the loop's proximity to that edge is the sag and bloom the family is known for. Everything characteristic about it follows from that one structural fact:
Each stage is a common-emitter amplifier with emitter degeneration, in normalised units, biased at 1.0 calibration unit of collector current:
The collector load is what bounds the circuit. Without it the junction current grows without limit — at the germanium defaults a 0.9 V drive gives i ≈ 7.2e7 — and a feedback path fed by an unbounded current has no solution at all. R_c is set so the quiescent collector sits mid-rail, which is also where the clipping headroom is symmetric.
The emitter degeneration R_e is what makes the two stages composable, and it is the element a reduced model is most tempted to drop. Drop it and R_c — pinned by the mid-rail bias — forces a stage gain of ~19, so a stage's whole usable input window is 50 mV while the stage before it hands over 85 % of a full rail. Every setting then clips identically, the cleanup has nothing to show, and the solver never leaves the steep part of the exponential. With it, gm_eff = gm/(1 + gm·R_e), the gain approaches the resistor ratio R_c/R_e, and the two stages' scales match. See kEmitterDegeneration.
The stages couple through collector-voltage DEVIATION from each stage's own quiescent point, which is what the feedback resistor's DC servo achieves in the real circuit while keeping the interstage coupling direct.
The pair is solved by a fixed-count safeguarded 2×2 Newton descent. The small-signal loop-gain bound keeps the operating point stable, but does not by itself bound nonlinear conditioning at a collector-rail kink or after a full-scale discontinuity. The solve therefore uses a capped ladder of trust-region Newton and residual-gradient candidates and accepts the one with the smallest coupled residual. The full device/control/source-impedance grid, including full-scale square transitions, independently asserts the residual tolerance.
The loop is explicitly bounded rather than unity-compensated (series law 1). Compensating it to unity would remove the module's reason for existing: this circuit is supposed to run near the edge.
Starvation lowers the current available to the SECOND stage — the supply feeding its collector node is what runs down — and therefore lowers that stage's quiescent bias, rather than clamping the current mid-solve. Two distinctions, both of which decide whether the module gates or merely goes quiet:
prepare() may allocate — it builds the oversampler's filter state. set_*, process(), process_block() and reset() never allocate, never lock, and never perform I/O. The implicit solve runs a COMPILE-TIME-FIXED iteration count, so the worst-case time per sample is bounded and not data-dependent. All randomness is the seeded house OuWalkT, rewound by reset(). The seed is fixed and never automated (series law 2); hosts may toggle the separate drift-enable setup control without changing the deterministic sequence.
References: Ebers & Moll, "Large-Signal Behavior of Junction Transistors", Proc. IRE 42(12):1761–1772, 1954, for the forward-active junction law. ElectroSmash's public Fuzz Face analysis for the topology and its documented behaviour only — no component values are reproduced and every default here is independently chosen and independently ranged.