The two-transistor direct-coupled feedback pair. More...
#include <fuzz_pair.hpp>
Public Types | |
| using | Device = FuzzDevice |
Public Member Functions | |
| FuzzPairT () | |
| void | prepare (double sample_rate) |
| Builds the oversampler's filter state. May allocate. | |
| void | reset () |
| void | set_device (Device device) |
| Device | device () const |
| void | set_fuzz (double amount01) |
| Feedback-network engagement, 0..1. | |
| void | set_bias_starve (double starve01) |
| 0 = healthy supply, 1 = fully starved. | |
| void | set_source_impedance_kohm (double kohm) |
| The Thévenin source impedance at the input jack: pickup, cable and — the part that matters — whatever the guitar's volume pot contributes as it is rolled back. | |
| void | set_output_level_db (double db) |
| void | set_mix (double mix01) |
| void | set_oversampling_enabled (bool on) |
| Test-only escape hatch: runs the nonlinearity at the base rate so the aliasing the oversampler removes can be measured. | |
| void | set_seed (std::uint32_t seed) |
| void | set_drift_enabled (bool on) |
| Enables the seeded thermal-drift channel. | |
| double | loading_factor () const |
| The divider ratio the source impedance produces. | |
| double | bias_voltage () const |
| The STARVED stage's quiescent junction voltage: the conduction knee at whatever current is still available to it. | |
| double | base_bias_voltage () const |
| The starved stage's quiescent BASE voltage: its junction knee plus the drop its quiescent current develops across the emitter degeneration. | |
| double | quiescent_collector () const |
| Where the starved stage's collector rests. | |
| double | stage_gain () const |
| One stage's small-signal gain R_c·gm_eff at the nominal operating point. | |
| double | input_scale_volts () const |
| Volts of base drive a full-scale input produces, derived from the nominal stage's own rail-to-rail swing. | |
| double | available_current () const |
| Available collector current after starvation. | |
| double | loop_gain () const |
| The linearised small-signal loop gain at the CURRENT operating point. | |
| double | worst_residual () const |
| The largest solver residual seen since reset() — so the fixed iteration count's adequacy is verified rather than assumed. | |
| int | latency_samples () const noexcept |
| Reported exactly, never estimated (series law 5). | |
| SampleType | process (SampleType input) |
| void | process_block (const SampleType *in, SampleType *out, int n) |
Static Public Attributes | |
| static constexpr double | kGermaniumIdeality = 1.15 |
| [design parameter] germanium n 1.15 (range 1.05 .. 1.30). | |
| static constexpr double | kGermaniumSaturation = 5e-6 |
| [design parameter] germanium Is 5e-6 calibration units (range 1e-7 .. 5e-5). | |
| static constexpr double | kSiliconIdeality = 1.03 |
| [design parameter] silicon n 1.03 (range 1.00 .. 1.08). | |
| static constexpr double | kSiliconSaturation = 5e-12 |
| [design parameter] silicon Is 5e-12 calibration units (range 1e-13 .. 5e-11). | |
| static constexpr double | kThermalVoltage = junction::kThermalVoltage |
| The shared thermal voltage. | |
| static constexpr double | kNominalCollectorCurrent = 1.0 |
| Quiescent collector current, the unit everything else is relative to. | |
| static constexpr double | kQuiescentCollector = 0.5 |
| Where the quiescent collector sits on the normalised rail. | |
| static constexpr double | kEmitterDegeneration = 0.18 |
| Emitter degeneration R_e, in the same normalised volts-per-calibration- unit as the collector load. | |
| static constexpr double | kStarveExponent = 1.6 |
| Starvation exponent: how sharply available current falls with the starve control. | |
| static constexpr double | kMinAvailableCurrent = 1e-3 |
| Floor on available current, so a fully starved stage is near cutoff rather than exactly at it (which would make the bias voltage −inf). | |
| static constexpr double | kInputImpedanceKohm = 68.0 |
| The fuzz stage's own input impedance at Q1's base, in kΩ. | |
| static constexpr double | kFeedbackConductanceMin = 0.05 |
| Feedback conductance travel. | |
| static constexpr double | kFeedbackConductanceMax = 1.0 |
| static constexpr double | kCouplingGain = 0.85 |
| Interstage coupling loss, Q1's collector into Q2's base. | |
| static constexpr double | kLoopGainCeiling = 0.94 |
| The enforced loop-gain ceiling. | |
| static constexpr int | kSolverIterations = 24 |
| Fixed solver iterations. | |
| static constexpr double | kNewtonStepThermal = 2.0 |
| Per-iteration Newton step limit, in thermal voltages. | |
| static constexpr double | kResidualTolerance = 1e-6 |
| Residual tolerance the fixed iteration count must clear across the whole parameter sweep. | |
| static constexpr double | kInputDriveRails = 0.4 |
| How far a full-scale input drives the first stage, measured in RAIL-TO-RAIL SWINGS of that stage rather than in volts. | |
| static constexpr double | kGermaniumDriftOctaves = 0.6 |
| Thermal-drift depth, in octaves of saturation current. | |
| static constexpr double | kSiliconDriftOctaves = 0.05 |
| static constexpr double | kDcBlockerCornerHz = 12.0 |
| DC-blocker corner, in Hz. | |
| static constexpr int | kOversampleFactor = 4 |
| Oversampling factor. | |
The two-transistor direct-coupled feedback pair.
| using pulp::signal::FuzzPairT< SampleType >::Device = FuzzDevice |
|
inline |
|
inline |
Available collector current after starvation.
|
inline |
The starved stage's quiescent BASE voltage: its junction knee plus the drop its quiescent current develops across the emitter degeneration.
|
inline |
The STARVED stage's quiescent junction voltage: the conduction knee at whatever current is still available to it.
Unstarved this is the §3 worked example's value — ~363 mV germanium, ~693 mV silicon — because unstarved is exactly the nominal current. The base sits one emitter drop above it, see base_bias_voltage().
This reports Stage 2 because Stage 2 is the one the starve control moves; Stage 1 stays at the nominal knee by construction.
|
inline |
|
inline |
Volts of base drive a full-scale input produces, derived from the nominal stage's own rail-to-rail swing.
|
inlinenoexcept |
Reported exactly, never estimated (series law 5).
A linear-phase FIR pair cannot be zero-latency, so this is not claimed to be.
|
inline |
The divider ratio the source impedance produces.
Both the attenuation and the loop-gain loading factor — one number, deliberately.
|
inline |
The linearised small-signal loop gain at the CURRENT operating point.
This is both the stability margin and the solver's contraction factor. The linearised small-signal loop gain at the DC operating point — the quantity kLoopGainCeiling bounds, and the one the parameter sweep asserts.
It is β · g_couple² · (R_c·gm_eff1) · (R_c·gm_eff2) · loading, where gm_eff = gm/(1 + gm·R_e) is the DEGENERATED transconductance at each stage's own operating current. The two differ under starvation, which is why the loop gain falls only in proportion to the surviving stage rather than as its square — the feedback path stays alive far enough into starvation to amplify the operating point's asymmetry, which is the stated mechanism for the octave-up fold.
Two collector loads appear because the loop passes through both stages' collector nodes; the specification's own formula omits them, which is what leaves it unbounded (it evaluates to ~560 at the defaults). Loading multiplies it, so a higher source impedance LOWERS the loop gain — the behaviour the spec's acceptance test 2 demands, though its §5.2 prose says the opposite. See adjudication A-14.
This is not a separate formula bolted alongside the solve: it is exactly the off-diagonal product over the diagonal product of the Jacobian in solve_stage, so the number reported here IS the solver's contraction factor rather than an estimate of it.
|
inline |
Builds the oversampler's filter state. May allocate.
|
inline |
|
inline |
|
inline |
Where the starved stage's collector rests.
Unstarved it is mid-rail; starving walks it toward the supply rail, and the headroom asymmetry that creates is what folds the waveform into even harmonics.
|
inline |
|
inline |
0 = healthy supply, 1 = fully starved.
Lowers the quiescent bias toward cutoff, which is what makes the stage gate rather than go quiet.
|
inline |
|
inline |
Enables the seeded thermal-drift channel.
Off by default so a render is exactly repeatable without relying on the seed discipline.
|
inline |
Feedback-network engagement, 0..1.
At 1 the loop gain approaches the tested ceiling.
|
inline |
|
inline |
|
inline |
Test-only escape hatch: runs the nonlinearity at the base rate so the aliasing the oversampler removes can be measured.
|
inline |
|
inline |
The Thévenin source impedance at the input jack: pickup, cable and — the part that matters — whatever the guitar's volume pot contributes as it is rolled back.
ONE ratio with TWO consequences: it attenuates the signal AND lightens Q1's input loading, which lowers the loop gain. Both push toward cleaner, which is why this circuit family cleans up so much more dramatically than a plain gain stage.
|
inline |
One stage's small-signal gain R_c·gm_eff at the nominal operating point.
A design consequence of the collector load and the emitter degeneration together, not of the bias point alone.
|
inline |
The largest solver residual seen since reset() — so the fixed iteration count's adequacy is verified rather than assumed.
|
staticconstexpr |
Interstage coupling loss, Q1's collector into Q2's base.
[design parameter] default 0.85, range 0.6 .. 1.0.
|
staticconstexpr |
DC-blocker corner, in Hz.
The starved operating point introduces a real DC step, so this has to actually remove it while staying below the lowest musical fundamental. [design parameter] default 12 Hz, range 2 .. 30 Hz.
|
staticconstexpr |
Emitter degeneration R_e, in the same normalised volts-per-calibration- unit as the collector load.
This is the element that makes the stage gain a DESIGN CHOICE instead of an accident of the bias point, and leaving it out is not a simplification — it is a different circuit. Without it, putting the quiescent collector mid-rail fixes R_c = 0.5/I_q, and the stage gain R_c·gm collapses to 0.5/(n·V_T) ≈ 19 no matter what else is chosen. A gain of 19 means the base-voltage window that swings the collector across its whole rail is ~50 mV, while the interstage coupling hands the next stage 85 % of a full rail — a 14x overdrive before any input arrives, so the second stage is a square wave at every setting and every source impedance. The circuit's signature cleanup then does not exist to be measured, and the solver never leaves the steepest part of the exponential.
With degeneration the emitter voltage rises with the current the base asks for, so gm_eff = gm/(1 + gm·R_e) and the stage gain approaches the resistor ratio R_c/R_e — independent of the operating current. That is how the real circuit does it too: the Fuzz-Face-class second stage sits on an emitter resistor precisely so its input window is volts-scale and matches the previous collector's swing.
The default gives a stage gain near 2.4, so a FULL-rail swing out of the first stage overdrives the second by about 2x — enough that a hot input slams the pair, little enough that a rolled-back guitar does not. [design parameter] default 0.18, range 0.05 .. 0.5.
|
staticconstexpr |
|
staticconstexpr |
Feedback conductance travel.
The top of the range is what kLoopGainCeiling constrains. [design parameter] min 0.05 (range 0 .. 0.2), max 1.0 (range 0.5 .. 1.5).
|
staticconstexpr |
Thermal-drift depth, in octaves of saturation current.
Germanium's reverse saturation current is far more temperature-sensitive than silicon's, which is why germanium units are famous for changing character with the weather. [design parameter] germanium 0.6 (range 0.1 .. 1.5), silicon 0.05 (range 0 .. 0.2).
|
staticconstexpr |
[design parameter] germanium n 1.15 (range 1.05 .. 1.30).
|
staticconstexpr |
[design parameter] germanium Is 5e-6 calibration units (range 1e-7 .. 5e-5).
|
staticconstexpr |
How far a full-scale input drives the first stage, measured in RAIL-TO-RAIL SWINGS of that stage rather than in volts.
This has to be relative, not absolute, and getting it wrong is silent: an absolute scale of a few hundred millivolts slams a stage whose whole input window is tens of millivolts, so every input level clips identically and the source-impedance cleanup has nothing to show. Expressed as a multiple of the stage's own swing it tracks the device row and the collector load automatically — series law 7.
The swing it is measured against is the NOMINAL one, taken at the unstarved operating point, because the input network is a fixed property of the circuit. Referring it to the starved operating point instead would turn the drive up by exactly as much as starvation turned the stage's gain down, and bias starvation would be close to inaudible.
The linear window is half a swing either side of the quiescent point, so the number to compare against is 0.5. At the default, a full-scale input at a low source impedance lands past that before the feedback loop amplifies it further — it clips hard — while the same input through the ~27 % divider of a rolled-back guitar volume stays inside it. That difference IS the cleanup the circuit is famous for, and Acceptance Test 2 measures precisely the gap.
At the default, a full-scale input through a 10 kΩ source arrives as 0.35 swings, which the feedback loop's ~2.8x closed-loop gain turns into about two full windows — hard clipping — while the same input through 220 kΩ arrives as 0.09 and stays under a quarter of a window. [design parameter] default 0.4, range 0.2 .. 3.0 rail-to-rail swings.
|
staticconstexpr |
The fuzz stage's own input impedance at Q1's base, in kΩ.
Forms the divider with the source impedance. [design parameter] default 68 kΩ, range 20 .. 200 kΩ.
|
staticconstexpr |
The enforced loop-gain ceiling.
Deliberately short of the 1.0 instability boundary: the floor of its range keeps audible sag headroom, the ceiling keeps the measured maximum safely stable. It is also the solver's contraction condition — see the file doc block. [design parameter] default 0.94, range 0.85 .. 0.98.
|
staticconstexpr |
Floor on available current, so a fully starved stage is near cutoff rather than exactly at it (which would make the bias voltage −inf).
[design parameter] default 1e-3, range 1e-5 .. 1e-2.
|
staticconstexpr |
Per-iteration Newton step limit, in thermal voltages.
Junction-limiting: the standard remedy for Newton on an exponential, where an unlimited step taken from the steep region lands somewhere it takes many iterations to climb back from. [design parameter] 2 thermal voltages.
|
staticconstexpr |
Quiescent collector current, the unit everything else is relative to.
[design parameter] default 1.0, range 0.5 .. 2.0 calibration units.
|
staticconstexpr |
Oversampling factor.
The junction law is a hard exponential and the loop runs near its gain ceiling, so harmonic content extends well past Nyquist at the base rate. ADAA is not used: its published formulations target MEMORYLESS nonlinearities, and this is a stateful coupled solve with feedback whose loop has no closed-form antiderivative. [design parameter] default 4, range 2 .. 8.
|
staticconstexpr |
Where the quiescent collector sits on the normalised rail.
Mid-rail gives symmetric clipping headroom. [design parameter] default 0.5, range 0.3 .. 0.7.
|
staticconstexpr |
Residual tolerance the fixed iteration count must clear across the whole parameter sweep.
Verified rather than assumed. [design parameter] default 1e-6, range 1e-8 .. 1e-4 calibration units.
|
staticconstexpr |
|
staticconstexpr |
[design parameter] silicon n 1.03 (range 1.00 .. 1.08).
|
staticconstexpr |
[design parameter] silicon Is 5e-12 calibration units (range 1e-13 .. 5e-11).
|
staticconstexpr |
Fixed solver iterations.
Not a loop bound to be tuned at runtime — the RT contract depends on it being compile-time constant. [design parameter] 24 iterations, sufficient for the fixed trust-region ladder to clear the residual tolerance at the nonlinear rail kinks.
|
staticconstexpr |
Starvation exponent: how sharply available current falls with the starve control.
[design parameter] default 1.6, range 1.0 .. 3.0.
|
staticconstexpr |
The shared thermal voltage.
Named here so this module's own formulas read in its terms, but there is exactly one definition, in junction.hpp.