diff --git a/.gitignore b/.gitignore index 2764d23..167f548 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ reference/di/*.wav reference/schematics/ reference/speaker/*.jpg reference/speaker/*.png # third-party impedance plots: local only (as the drawings, D-021) +build-xp/ diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 35bb87e..6e8e855 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -1081,3 +1081,93 @@ step halvings (was 2, D-042). The "max iterations ≤ configured limit" check fo Consequences: 0 first-solve failures (0 unrescued) at HOT, every knob 1.0 and CRUNCH at 2x, 8x and 16x, and in the 2x plugin model check (28 runs, max 56 iterations); accuracy and CPU unchanged (27.1 / 42.2 % in the check's timing). The budget is a cap; ordinary samples still converge in a few iterations. + +## D-065 Jacobian predictor from the kept LU factors; preamp J4 Thevenin resistance from them +Date: 2026-10-19 · Status: accepted (M7 step 1; owner: "start on the CPU optimisation") +Context: at the NAM settings (Preamp 10, Treble 8, Middle 7, Bass 4, Master 10, Presence 8) the power amp needed 3.46 +Newton iterations per solve and the preamp 1.41 (the preamp's second J4 solve 1.18, almost never 0); the J4 Thevenin +re-evaluated the four triodes and solved an 8×8 system every sample. Tried and not adopted (ampwav, 96 kHz, 20 s DI): +relaxed step halving (accept growth ×2 … ×1e6: up to 150 k failures, more iterations), tighter or looser EL34 grid / plate +step limits (2 … 100 V: more iterations or failures), chord iterations with the kept factors (no gain, output moved), +half-step LU invalidation (slower). +Decision: the Newton keeps the LU factors of its last iteration (multipliers below the diagonal, pivot rows; +`detail::LuFactors`, `solver::solveFactored`). Every root predicts with one Newton step from the last solution using them +(`RootNetworkState::jacobianPredictor`, default on), with the device's linear source ports updated (`sourceCurrents`: +the preamp's given load current, the power amp's J4 source); extrapolation (D-029) when no factors exist. The preamp's +J4 Thevenin resistance is (J⁻¹·Z[:, y])_y from the factors when this sample's solve factored (else as before). +Consequences: iterations per solve at NAM 3.46 → 2.34 (power amp), 1.41 → 0.66 (preamp), 0 failures; engine 96 kHz +4398 → 3464 ns (NAM), 2552 → 2221 ns (default), 4920 → 3850 ns (every knob 1.0). Plugin (20 s DI, 2x Full) default +26.6 → 21.6 %, NAM 42.7 → 33.4 %, every knob 10 49.7 → 37.3 %. Output vs before: −105 dB default, −50.5 dB NAM (the +same order as a Newton tolerance change 1e-4 → 1e-6 V, −53 dB). Plugin model check unchanged (CRUNCH / DRIVE / HOT +−45.3 / −48.5 / −24.0 dB, unseen −20.8 / −39.0 dB, 0 failures, max iterations 56 → 38; +`M6/20261019_x2_rates_m7_step1_full2x.md`). The exact solution is unchanged; only the starting point moves. +Found on the way: at every knob 1.0 the engine is sensitive to any perturbation after ≈ 11 s of the 20 s clip (tolerance +1e-8 vs 1e-10 V: −180 dB, then −16 … −18 dB per second, spread around the 100 Hz harmonics, no discrete tone): sample-wise +comparisons are meaningless at that setting; it is compared by Newton failures and spectra. + +## D-066 Device maths: fast Wright omega, E1^ex via exp / log; no outputs for the preamp's trial J4 solve +Date: 2026-10-19 · Status: accepted (M7 step 3a; results unchanged to rounding) +Context: after D-065 the device maths was ≈ 37 % of the engine at the NAM settings; the grid diode's Wright omega took an +exp / log1p start and 2 – 3 Fritsch–Shafer–Crowley steps (one log each) plus a confirming step whenever z ≥ −40 (one in +three calls at NAM), and pow was 10 %. +Decision: `wrightOmega` = the series e^z·(1 − y + 3/2·y² − 8/3·y³), y = e^z, below z = −10; on [−10, 40) a cubic Hermite +start from a 401-point table of the reference (`wrightOmegaReference`, kept) and one step; from 40 the asymptotic start and +one step. ≤ 1e-14 relative to the reference everywhere (the series is more accurate than the reference's iteration below +z ≈ −30), residual ≤ 8e-16·max(1, |z|) from z = −10; 5.4 vs 23 ns per call. Triode / pentode E1^ex as exp(ex·ln E1) +(≤ 1.2e-15 relative to pow, 26 % faster in a loop). The preamp's first J4 solve (re-solved after the power amp) skips its +node voltages and waves (`RootNetwork::solve(..., outputs = false)`). +Consequences: engine at 96 kHz, NAM 3464 → 3175 ns, default 2221 → 2139 ns; output unchanged at the comparison's +resolution; unit tests (device curves vs SPICE, finite-difference Jacobians) unchanged. A float LU for the Newton direction +was tried (no gain: 3163 ns) and not adopted. + +## D-067 Preamp J4: the second solve replaced by a linear load correction when small +Date: 2026-10-19 · Status: accepted (M7, owner: "do A and B", option B) +Context: with exact J4 the preamp is solved, the power amp solved against its linearised Thevenin source, and the preamp +solved again with the power amp's load current. That second solve costs at least one evaluation of the four triodes. +Decision: `Preamp::correctLoad`: from the first solution, dv = −J⁻¹·Z[:, y]·Δi_y (the direction theveninAtY already has), +triode currents moved by their conductances at the solution, committed without Newton (`RootNetwork::commit`); a full +re-solve when any port would move by more than 10 mV (`kMaxLoadCorrection`). +Consequences: measured against the full re-solve (96 kHz, 20 s DI): 10 mV −115 dB (default knobs) / −126 dB (NAM), 30 mV +−51.6 dB at NAM, 0.1 V −36 dB, unlimited −31 dB with the error present from the first second (the grid diodes: +0.1 V ≈ 4 n·Vt). At 10 mV the engine is 11 % faster at default knobs and unchanged at NAM, where the load steps are +larger. Not a model change at the chosen limit. + +## D-068 The linear J4 port out of both Newton systems +Date: 2026-10-19 · Status: accepted (M7, owner: "do A and B", option A) +Context: the preamp root's load port (given current) and the power amp's J4 source port (i = g·(v − vTh)) are linear but +were Newton ports: 9 and 11 ports instead of 8 and 10. +Decision: a device with a linear last port declares `linearPort(g, c)` (i = g·v + c; `solver::LinearLastPort`). The Newton +solves the other ports with Z' = Z_pp − Z_py·g·Z_yp / (1 + g·Z_yy) and computes the linear port exactly from them at every +evaluation (`solver::solveLinearPort`); its residual is zero by construction. The predictor and the kept factors use the +reduced system; the factors keep the conductances they were built from (`LuFactors::d`), which the preamp's Thevenin +resistance uses (rTh = Z_yy − Z_yp·D·(I + Z_pp·D)⁻¹·Z_py). Using the last iterate's conductances with the previous +iterate's factors instead was tried by mistake first and moved the NAM output by −31.5 dB. +Consequences: output vs before −128 dB (default knobs) / −56.9 dB (NAM: the convergence test no longer includes the +linear port's residual, a Newton-tolerance-level change). Engine 2–3 % faster. + +## D-069 Plugin default Power Amp Rate Half (Full selectable) +Date: 2026-10-19 · Status: accepted (owner: "Make half the default while keeping full selectable"; revises D-060) +Context: after M7 steps 1, 3a, A and B (D-065 – D-068) the plugin at 2x Full needs 18.5 / 31.1 / 34.5 % of one core (default +knobs / NAM settings / every knob 10, 20 s DI); at Half 12.8 / 21.6 / 23.7 %. The Half plugin model check after M7 +(`M6/20261019_x2_rates_m7_AB_half2x.md`): CRUNCH / DRIVE / HOT −37.7 / −37.2 / −18.9 dB, unseen settings worst −16.6 dB +(median −28.0), 0 Newton failures, non-harmonic power on 5 / 9 kHz sines −7.0 … −12.6 dB (Full: −45.3 / −48.5 / −24.0 dB, +−20.8 dB, −14.9 … −20.5 dB). The owner heard no Full / Half difference in M6 (before D-058 / D-059). +Decision: the Power Amp Rate parameter defaults to Half; Full stays selectable and remains the verified configuration. +Consequences: the default fails the plugin model check's −20 dB at HOT (−18.9 dB) and at the worst unseen setting +(−16.6 dB); deviation accepted by the owner, tolerances unchanged (VERIFICATION). Saved projects keep their stored +setting. `harness.plugin_null` and `harness.plugin_cpu` measure the new default. Reported latency at 48 kHz (FIR +oversampling) 49 → 58 samples (1.02 → 1.21 ms; the half-rate interpolator, D-053). + +## D-070 Power-amp root: maxIter 48 +Date: 2026-10-19 · Status: accepted (M7 verification; the owner's "do A and B" and D-069) +Context: the M7 automation check at Half (96 kHz, exact J4, 1e-4 V) showed 3 first-solve failures (all rescued) at every +knob 1.0 with Middle or Presence moving; Full: 0. Bisection over the M7 commits: 0 failures up to D-067 (max 24 – 26 +iterations, pre-M7 23 – 24), 3 from D-068 (the reduced Newton takes another path through the same hard samples). The Jacobian +predictor contributes (with it off: 0 failures, max 31 – 32); guards that fall back to the trend when the prediction is far +from it remove the failures but cost 8 – 18 % at the NAM settings (trend difference 100 / 30 / 10 V); falling back only when +the prediction hits a step limit costs nothing but leaves 2 of 3 (both fail from the trend as well and converge from the +last solution in 21 iterations). +Decision: the power-amp root (`PowerAmp`, b56 and the full amp) allows 48 iterations (was 32, D-052); the check's limit +follows (b56 48; full amp 64 from D-064). +Consequences: the three samples converge in 33 – 34 iterations; automation 56 runs at Half and at Full: 0 non-finite, 0 +failures; HOT acceptance (384 kHz exact J4) 0 FAIL; b56 0 FAIL. No effect on samples that converged within 32. diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index bccdeda..abd404f 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -557,12 +557,13 @@ Notes: first-solve Newton failures at 768 kHz, all rescued (D-042, D-045). 2x / 4x have no SPICE pass (D-047). ## M6 — Performance (owner scope 2026-10-14: performance work only; the other SPEC §7 M6 items stay open) +Closed by the owner on 2026-10-19 (report accepted). Steps 2–4 were not finished; the CPU work continues in M7. - [x] Step 0: baseline at the 8x default (384 kHz) - [x] Step 1: build settings (no change adopted) -- [~] Step 2: device equations -- [~] Step 3: dense linear algebra / SIMD -- [ ] Step 4: linear parts at a lower rate (analysis first) -- [x] Step 5: re-verification, M6 report (`harness/reports/M6/20261018_M6_report.md`, 2026-10-18; for owner review) +- [x] Step 2: device equations (studied; inline polynomials slower than libm, not adopted; rest moved to M7) +- [x] Step 3: dense linear algebra / SIMD (unrolled elimination adopted; rest moved to M7) +- [x] Step 4: linear parts at a lower rate (moved to M7) +- [x] Step 5: re-verification, M6 report (`harness/reports/M6/20261018_M6_report.md`, accepted 2026-10-19) - 2026-10-18: owner-supplied measured 4x12 impedance curves (T1511, T1281, G12K-85) compared with D-059: match above ≈ 200 Hz, resonance within their range; D-059 kept (D-063). - Owner 2026-10-18: handle the remaining Newton first-solve failures (HOT DI RR at 384 kHz: 2; plugin model check at 2x: @@ -858,6 +859,74 @@ Notes: b34 94 PASS (0 FAIL each); knob jumps b12 −55.9 dB, b1234 −39.1 … −55.1 dB, b34 −53.1 … −67.6 dB; M4 full acceptance 107 PASS (768 kHz and 384 kHz exact J4); HOT 20 PASS / 1 FAIL (2 rescued first-solves, as after D-061); null test 26 / 26. +## M7 — CPU optimisation (owner 2026-10-19: "close M6 and start on the CPU optimisation") +Goal: lower the plugin's CPU at its defaults (2x, Power Amp Rate Full, exact J4, Newton tol 1e-4 V, half-steps on; +D-054 – D-060, D-064), above all at high Master, toward the 20 % working budget, without loosening any tolerance and +without audible change. Owner criteria: no added latency, authentic at all knobs, CPU headroom. + +Baseline (2026-10-19, main 0e70f81; `amp2203_render plugin --factor 2`, DI GTR L, FIR oversampling): +- CPU: default knobs 26.6 % (60 s), NAM settings (Preamp 10, Treble 8, Middle 7, Bass 4, Master 10, Presence 8) + 42.7 % (20 s), every knob 10 54.0 % (60 s). Master 10 alone 33.6 %, Presence 10 alone 26.4 %. +- Engine at 96 kHz (ampwav, plugin settings, 20 s): default 2491 ns / sample, NAM 4236 ns. +- Profile at NAM (macOS `sample`, audio thread): power amp ≈ 62 %, preamp (two solves per sample) ≈ 30 %. By kind: + libm (pow, log1p, exp, log, atan) + Wright omega ≈ 29 %, LU elimination ≈ 23 %, Newton loop (J build, residual, + inlined devices) ≈ 26 %, open-circuit products ≈ 7 %. +- Newton at NAM (temporary counters): power amp 1.15 solves per sample (half-steps), 3.46 iterations per solve + (default knobs 1.36), 1.03 M step halvings on 7.65 M iterations, step limits rarely active (0.6 %); preamp 2 solves + per sample, 1.41 iterations per solve. The power amp's iteration count is what grows with Master. + +Plan (each step measured alone; adopted only if it pays): +1. Newton iterations in the power amp: initial guess (a predictor from the last Jacobian instead of linear + extrapolation), the step-halving rule, Jacobian reuse within a solve. Gate: 0 Newton failures (HOT, every knob 1.0, + plugin model check), output within the Newton tolerance of the current engine. +2. Fewer solves per sample: the half-step trigger at 2x Full; the preamp's second solve (exact J4) replaced by a + linear correction from its last Jacobian. Gate: plugin model check vs SPICE and 16x, HOT acceptance. +3. Code level, results unchanged to rounding: memory layout / padding for NEON, the linear rY port folded out of + the LU, derivatives only where used, vectorised device maths across tubes. Gate: null test vs the previous build. +4. Re-verification as M6 (full acceptance 768 / 384 kHz, HOT, block tests, knob jumps, automation, plugin model + check, null test, pluginval / auval), plugin CPU, M7 report. +Tools: `ampwav --stats` now writes the per-root iteration histograms; `harness.plugin_cpu`; `amp2203_render`. + +- [x] Step 1 (D-065): Jacobian predictor from the kept LU factors, preamp J4 Thevenin resistance from them. Plugin 2x Full + (20 s DI): default 26.6 → 21.6 %, NAM 42.7 → 33.4 %, every knob 10 49.7 → 37.3 %; plugin model check unchanged + (`M6/20261019_x2_rates_m7_step1_full2x.md`). Tried without gain: step-halving rule, EL34 step limits, chord iterations. +- Profile after step 1 (NAM, inclusive): half-steps 21 % (15 % of samples at 2x cross the 100 V trigger), LU 17 %, + triode maths 17 %, pentode maths 14 %, open-circuit products 6 %. +- Half-step trigger at 2x Full (plugin model check): off: HOT −22.7 dB, unseen worst −19.8 dB, HOT cost 31.7 → 29.6 %; + 300 V: HOT −23.4 dB, unseen −20.2 dB, 30.3 % (`M6/20261019_x2_rates_m7_hst0_full2x.md`, `…_hst300_…`). Kept at 100 V + (2 points of CPU for 1.3 dB of HOT margin and the unseen check). +- [x] Step 3a (D-066): fast Wright omega (series below z = −10, cubic Hermite table start + one Fritsch–Shafer–Crowley step, + ≤ 1e-14 relative to the reference, 4.3× faster), E1^ex as exp(ex·ln E1) (≤ 1.2e-15 relative), no node voltages / waves + for the preamp's trial J4 solve. Engine NAM 3464 → 3175 ns. Tried without gain: float LU for the Newton direction + (3175 → 3163 ns: K = 9 / 11 is latency-bound; the double rule stays). +- After steps 1 + 3a: plugin (20 s DI, 2x Full) default 26.6 → 20.4 %, NAM 42.7 → 31.0 %, every knob 10 49.7 → 34.4 % + (60 s DI: every knob 10 54.0 → 42.1 %); plugin model check unchanged (CRUNCH / DRIVE / HOT −45.3 / −48.5 / −24.0 dB, + unseen −20.8 / −39.0 dB, 0 failures, `M6/20261019_x2_rates_m7_step3a_full2x.md`). +- Owner 2026-10-19: "do A and B". Plan: (B) the preamp's second J4 solve replaced by a linear correction from the first + (dv = −J⁻¹·Z[:, y]·Δi_y, currents moved by the device conductances at the first solution), with a full re-solve when the + correction is large; measured for time, output vs the current engine and the plugin model check. (A) the linear J4 port + out of both Newton systems: the preamp's given-current port solved after the 8 triode ports, the power amp's source port + folded into its Thevenin matrix per sample (results unchanged to rounding); null test vs the current engine. +- [x] B (D-067): linear load correction below 10 mV (vs the re-solve −115 dB default / −126 dB NAM; larger limits move the + output: 30 mV −51.6 dB, unlimited −31 dB). [x] A (D-068): the linear port out of both Newton systems (−128 / −56.9 dB vs + before). Plugin (20 s DI, 2x Full): default 20.4 → 18.5 %, NAM 31.0 → 31.1 %, every knob 10 34.4 → 34.5 %: the gain is + at default knobs; at high Master the load steps exceed 10 mV. +- Options C / D measured (owner: "how much ... could affect CPU", then "re-run the Half model check"). C (half-step trigger + 300 V, engine): NAM −5 %, every knob 1.0 −8 %, default 0 %. D (Power Amp Rate Half, plugin 20 s): default 18.5 → 12.8 %, + NAM 31.1 → 21.6 %, every knob 10 34.5 → 23.7 %. Half model check after M7 (`M6/20261019_x2_rates_m7_AB_half2x.md`): + CRUNCH / DRIVE / HOT −37.7 / −37.2 / −18.9 dB (FAIL: HOT ≤ −20), unseen worst / median −16.6 / −28.0 dB, 0 failures + (before M7: 12, 1 unrescued); non-harmonic power on 5 / 9 kHz sines −7.0 … −12.6 dB (Full −14.9 … −20.5 dB). Accuracy as + before M7 (−18.9 / −16.7 dB). +- Owner 2026-10-19: Half becomes the plugin default, Full selectable (D-069; HOT −18.9 dB accepted). Latency 49 → 58 samples + at 48 kHz. +- M7 verification (2026-10-19): M4 full acceptance 768 kHz 0 FAIL; 384 kHz exact J4 0 FAIL; HOT 0 FAIL; b1234, b56, b6, b5open + 0 FAIL (b6's 5 kHz 20 V Newton median now 3, D-043 item passes); knob jumps b1234 −39.1 … −55.1 dB, b56 −62.9 dB; + automation Full 0 failures, Half 3 rescued first-solve failures → D-070 (power-amp maxIter 48), then Half and Full 0. +- Null tests 2x Half / Full 0 FAIL; pluginval (VST3, AU) SUCCESS, auval SUCCEEDED; plugin CPU at the Half default: default 13.1 %, + every knob 10 24.0 %, worst automation 24.6 %. M7 report: `harness/reports/M7/20261019_M7_report.md` (for owner review). +Risks: changes to the Newton path move the output within the tolerance (verified end to end); gains unknown until +measured; any tolerance failure is reported with options, not loosened. No new dependencies without asking. + ## Questions for the owner _(Claude Code: add questions here and also ask them in the session.)_ - Q1 (M0): git init + baseline commit? — Answered 2026-10-12: yes; repo-local author CatastrophicCoder. diff --git a/docs/SPEC.md b/docs/SPEC.md index bc272bb..37f2b72 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -152,9 +152,10 @@ DECISIONS D-033), `PhaseInverter`, `PowerStage` (verified sub-blocks of `PowerAm ### 3.4 Sample rate and oversampling - The engine runs at whatever rate `prepare()` receives. The JUCE plugin layer wraps it with `juce::dsp::Oversampling` and passes the oversampled rate. -- Plugin oversampling: a parameter with 2x / 4x / 8x / 16x, default 2x with the power amp at full rate (D-060) and the - exact J4 coupling (DECISIONS D-054, owner listening result; was 8x, D-048), the same factor at every host rate. - 2x is 96 kHz inside at 48 kHz, verified against SPICE with the D-051 tolerances (M6 X2 / A1). +- Plugin oversampling: a parameter with 2x / 4x / 8x / 16x, default 2x with the power amp at half rate (D-069; was full + rate, D-060) and the exact J4 coupling (DECISIONS D-054, owner listening result; was 8x, D-048), the same factor at + every host rate. 2x is 96 kHz inside at 48 kHz, verified against SPICE with the D-051 tolerances (M6 X2 / A1); at + half rate the HOT preset is −18.9 dB (≤ −20 dB not met; deviation accepted by the owner, D-069). - Verification runs the engine directly at 768 kHz (16 x 48 kHz) on signals generated at that rate, so no resampler sits between SPICE and the engine. See `VERIFICATION.md`. (At 384 kHz the preamp's fastest cutoff transitions were not resolved: D-034, D-035.) @@ -320,7 +321,7 @@ Must reproduce `reference/spice/tubes.lib` exactly (same equations and parameter Preamp Volume, Treble, Middle, Bass, Master, Presence (0–10 display, rotation internally), Interface Level (dBu at 0 dBFS, 0..+24 dBu, default +12.0 dBu; sets `inputVoltsPerFullScale`), Input Trim (−24..+24 dB, default 0 dB), Output Level (−48..+12 dB), Oversampling (2x/4x/8x/16x, default 2x), Power Amp Rate (Full / Half, default -Full; D-049, D-054, D-060), +Half; D-049, D-054, D-069, was Full, D-060), optional Cabinet IR on/off with file chooser (only if implemented in this repo; see 1.3). --- @@ -442,6 +443,10 @@ work (lookup tables for device equations, XSIMD for R-type matrix multiplies, li rate); tube parameter fitting; unmatched power tube pairs; integration into the existing amp-sim plugin project. +### M7 — CPU optimisation (owner 2026-10-19) +Lower the plugin's CPU at its defaults (M6 configuration), above all at high Master, toward a 20 % working budget of +one core at 48 kHz; no tolerance loosened, no added latency. Plan and results in `docs/PROGRESS.md` (M7). + --- ## 8. Coding conventions diff --git a/docs/VERIFICATION.md b/docs/VERIFICATION.md index b6be35a..f829136 100644 --- a/docs/VERIFICATION.md +++ b/docs/VERIFICATION.md @@ -143,7 +143,7 @@ See `WDF_DESIGN.md` §5.5. | Real-time plugin model (any architecture, M6), whole amp vs SPICE | ESR ≤ −20 dB on the held-out DI clips at CRUNCH, DRIVE and HOT, 48 kHz band, first 0.1 s excluded (D-051) | | NFB amount | ±0.5 dB at 100 Hz / 1 kHz / 5 kHz | | Stability | 60 s silence at max settings: |out| < 1 mV, no growth | -| Newton | 0 failures on the standard signal set; max iterations ≤ the configured limit (12; 32 for the power amp B56; 64 for the preamp B1234 and the full amp, D-064; was 32, D-052) | +| Newton | 0 failures on the standard signal set; max iterations ≤ the configured limit (12; 48 for the power amp B56, D-070; 64 for the preamp B1234 and the full amp, D-064; was 32, D-052) | ESR on heavily clipped signals is sensitive to tiny timing differences at sharp edges. If a check fails mainly because of edge timing, also report ESR after a 5 kHz low-pass on both signals and the harmonic diff --git a/docs/WDF_DESIGN.md b/docs/WDF_DESIGN.md index f754ca1..5dc59f2 100644 --- a/docs/WDF_DESIGN.md +++ b/docs/WDF_DESIGN.md @@ -138,7 +138,11 @@ Algorithm (per sample): 1. Initial guess: linear extrapolation from the two previous solutions, v = v[n−1] + (v[n−1] − v[n−2]), with each port's extrapolated step clamped to its step limit; after a sample whose solve failed, the trend restarts at zero (v[n−2] := v[n−1]) (DECISIONS D-029, D-034). -2. Up to `maxIter` (default 12; in the full amp 64 for the preamp root and 32 for the power-amp root, DECISIONS D-052, D-064) iterations; per-port step limits, upward and downward separately + Since M7 (D-065) the roots use a Jacobian predictor instead: one Newton step from v[n−1] with the LU factors + the last Newton iteration left (no new device evaluation, no new factorisation), the residual taken with the + last currents and the device's linear source ports (J4 load ports) updated; extrapolation remains when no + factors exist (first sample, after a DC solve). +2. Up to `maxIter` (default 12; in the full amp 64 for the preamp root and 48 for the power-amp root, DECISIONS D-052, D-064, D-070) iterations; per-port step limits, upward and downward separately (triode blocks: grid ports +50 V / −1000 V, plate ports ±50 V, D-034, D-037). Steps into cutoff are harmless and the device equations cannot overflow (closed-form ω, stable softplus), so the limits only shape convergence. The step is scaled uniformly so that no port exceeds its limit (this keeps the Newton @@ -148,7 +152,11 @@ Algorithm (per sample): before accepting it. 5. If not converged after `maxIter`: keep the best iterate (lowest ‖f‖), increment the failure counter. 6. Linear solve: fixed-size Gaussian elimination with partial pivoting (template on N; no heap, no - Eigen in the audio path). + Eigen in the audio path). The factors of the last iteration are kept (`detail::LuFactors`, D-065) for the + predictor and, in the preamp, for the J4 Thevenin resistance. A linear last port (the J4 load ports, i = g·v + c) + is not a Newton port: the Newton solves the others with Z' = Z_pp − Z_py·g·Z_yp / (1 + g·Z_yy) and the linear + port follows exactly at every evaluation (D-068). The preamp's second J4 solve is a linear correction when + it moves no port by more than 10 mV (D-067). Statistics (median/max iterations, failure count) are exposed for the harness. diff --git a/engine/include/amp2203/Preamp.h b/engine/include/amp2203/Preamp.h index e31707a..41e09ca 100644 --- a/engine/include/amp2203/Preamp.h +++ b/engine/include/amp2203/Preamp.h @@ -57,9 +57,14 @@ class Preamp { /// The same sample in steps, with a load current drawn at y (M6 X1, exact J4 coupling): beginSample, then /// solveLoaded (repeatable: each call restarts from the state before the sample), then finishSample. void beginSample(double vIn) noexcept; - bool solveLoaded(double iY) noexcept; + /// `final` = false: a trial solve whose node voltages and waves another call replaces (M7: not computed). + bool solveLoaded(double iY, bool final = true) noexcept; /// Linearised Thevenin source at y around the last solveLoaded(iY): v_y = vTh − rTh·i_y. - void theveninAtY(double iY, double& vTh, double& rTh) const noexcept; + void theveninAtY(double iY, double& vTh, double& rTh) noexcept; + /// After theveninAtY: the solution for a new load current from the linearisation, dv = −J⁻¹·Z[:, y]·Δi_y + /// with the triode currents moved by their conductances (M7, D-067); a full solveLoaded when any port would + /// move by more than `maxCorrection` volts or no linearisation is at hand. Returns as solveLoaded. + bool correctLoad(double iY, double maxCorrection) noexcept; void finishSample() noexcept; double output() const noexcept { return out_; } @@ -88,6 +93,8 @@ class Preamp { std::array vOc_{}; std::array, 4> saved_{}; // v, vPrev, i, vOcPrev before the sample std::array savedNode_{}; + std::array loadSens_{}; // J⁻¹·Z[:, y] from theveninAtY (M7) + bool loadSensValid_ = false; double vInNow_ = 0.0; bool ok_ = true; bool solvedOnce_ = false; // solveLoaded() ran in this sample diff --git a/engine/include/amp2203/detail/LuFactors.h b/engine/include/amp2203/detail/LuFactors.h new file mode 100644 index 0000000..96c11a4 --- /dev/null +++ b/engine/include/amp2203/detail/LuFactors.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace amp2203::detail { + +/// LU factors of a K×K Jacobian as the multiport Newton leaves them (M7): U on and above the diagonal, the +/// multipliers of L below it, and the pivot row of each elimination step. Data only; solved by +/// solver::solveFactored (engine/src/solver/MultiportNewton.h). +template +struct LuFactors { + std::array, K> m{}; + std::array piv{}; + std::array, K> d{}; // the device ∂i/∂v the Jacobian was built from (device blocks only) + bool valid = false; +}; + +} // namespace amp2203::detail diff --git a/engine/include/amp2203/detail/RootNetworkState.h b/engine/include/amp2203/detail/RootNetworkState.h index 600d90e..18ae1c9 100644 --- a/engine/include/amp2203/detail/RootNetworkState.h +++ b/engine/include/amp2203/detail/RootNetworkState.h @@ -2,6 +2,7 @@ #include +#include "amp2203/detail/LuFactors.h" #include "amp2203/detail/NewtonStats.h" namespace amp2203::detail { @@ -24,6 +25,9 @@ struct RootNetworkState { std::array i{}; // root port currents std::array vOcPrev{}; // root Thevenin voltages of the last sample (source-stepping fallback) std::array node{}; // node voltages + LuFactors lu; // the last Newton Jacobian, factored (M7) + std::array, K> didv{}; // device ∂i/∂v at the last Newton iterate (M7; device blocks only) + bool jacobianPredictor = true; // predictor: a Newton step from v with lu instead of extrapolation (M7) double iterations = 0.0; // Newton iterations of the last sample (probe) long sanitized = 0; // samples with a non-finite solution (state kept) long rescued = 0; // failed solves recovered by the source-stepping fallback (D-042) diff --git a/engine/src/Amp2203.cpp b/engine/src/Amp2203.cpp index 47977e3..dd5983e 100644 --- a/engine/src/Amp2203.cpp +++ b/engine/src/Amp2203.cpp @@ -16,6 +16,9 @@ namespace { constexpr int kDcIterations = 50; constexpr double kDcTolerance = 1e-9; // V, supply node change between fixed-point rounds constexpr double kOpenLoopR22 = 1e12; // Ω: "R22 → 1 TΩ" (SPEC §7 M4 NFB check) +/// Largest port move of the preamp's linear J4 load correction (D-067): beyond it a full re-solve. At 10 mV the +/// correction matches the re-solve to −115 dB (default knobs) / −126 dB (NAM); at 0.1 V the grid diodes make it −36 dB. +constexpr double kMaxLoadCorrection = 0.01; // V constexpr double kOpenJ4 = 1e12; // Ω: the J4 element source in the exact-J4 mode (M6 X1) /// Appends `src` to `dst`, skipping `skip` and renaming per `rename` ({from, to} pairs). @@ -141,8 +144,8 @@ double Amp2203::processSample(double x) noexcept { // the samples where it runs (the Thevenin source taken directly, no decimation filter in the loop), // the preamp alone with the held load current in between; the output through the interpolator. preamp_->beginSample(vIn_); - preamp_->solveLoaded(iY_); const bool runPower = cfg_.powerAmpRateDivisor == 1 || powerPhase_; + preamp_->solveLoaded(iY_, !runPower); // a trial solve when the power amp runs: re-solved below double u = 0.0; if (runPower) { double vTh = 0.0, rTh = 0.0; @@ -150,7 +153,7 @@ double Amp2203::processSample(double x) noexcept { power_->setPreampPort(vTh, 1.0 / rTh); u = power_->process(0.0); iY_ = power_->preampPortCurrent(); - preamp_->solveLoaded(iY_); + preamp_->correctLoad(iY_, kMaxLoadCorrection); // linear when small, else re-solved (D-067) } preamp_->finishSample(); if (cfg_.powerAmpRateDivisor == 1) { diff --git a/engine/src/PowerAmp.cpp b/engine/src/PowerAmp.cpp index eac719a..693fbdb 100644 --- a/engine/src/PowerAmp.cpp +++ b/engine/src/PowerAmp.cpp @@ -16,7 +16,7 @@ using devices::TriodesPentodesLoad; constexpr stage::RootNetwork kNet{kElements, kRoot}; // D-045 (owner decision Q21) -const solver::MultiportNewton kRootSolver = TriodesPentodes::solverK(32); +const solver::MultiportNewton kRootSolver = TriodesPentodes::solverK(48); // D-070 (was 32, D-052) const solver::MultiportNewton kDcSolver = TriodesPentodes::solverK(500); // reset() only constexpr std::array kNoRootOffsets{}; constexpr double kWindingDcConductance = 1e6; // S: the windings are shorts at DC (as PowerStage) diff --git a/engine/src/Preamp.cpp b/engine/src/Preamp.cpp index 34fb75f..f22dcd1 100644 --- a/engine/src/Preamp.cpp +++ b/engine/src/Preamp.cpp @@ -207,7 +207,7 @@ void Preamp::beginSample(double vIn) noexcept { savedNode_ = root_.node; } -bool Preamp::solveLoaded(double iY) noexcept { +bool Preamp::solveLoaded(double iY, bool final) noexcept { // the first call of a sample predicts from the last samples; a repeated call (corrected load current) // starts at the first call's solution with no trend, and the trend is then restored to the last sample's const bool repeat = solvedOnce_ && ok_; @@ -226,17 +226,40 @@ bool Preamp::solveLoaded(double iY) noexcept { const auto& newton = residualTol_ == kRootSolver.config.residualTol ? kRootSolver : tuned; root_.i[rY] = iY; incident_ = reflected_; - ok_ = kNet.solve(root_, newton, FourTriodesLoad{iY}, u_, vOc_, incident_, eOff_); + ok_ = kNet.solve(root_, newton, FourTriodesLoad{iY}, u_, vOc_, incident_, eOff_, final); if (repeat && root_.stats.failures == failures) root_.vPrev = saved_[0]; // the trend for the next sample: this sample's solution vs the last one solvedOnce_ = true; return ok_; } -void Preamp::theveninAtY(double iY, double& vTh, double& rTh) const noexcept { +void Preamp::theveninAtY(double iY, double& vTh, double& rTh) noexcept { // linearised at the solution: v = vOc − Z·i, i_p = i_p(v_p), i_y given. A change of i_y moves the triode // ports by dv_p = −(I + Z_pp·D)⁻¹·Z_py·di_y, so v_y = vTh − rTh·i_y with // rTh = Z_yy − Z_yp·D·(I + Z_pp·D)⁻¹·Z_py + // + // With a Jacobian factored by this sample's solve (M7): the Newton factors I + Z_pp·D over the 8 triode + // ports (D-068), so x = (I + Z_pp·D)⁻¹·Z_py from them, with the D they were built from (the last Newton + // iteration's, not the converged point's). Otherwise from the devices at the solution. + if (root_.lu.valid && root_.iterations > 0) { + constexpr int P = kNumRootPorts - 1; + solver::Vec w; + for (int r = 0; r < kNumRootPorts; ++r) + w[r] = root_.Z[r][rY]; + solver::solveFactored(root_.lu, w); + rTh = root_.Z[rY][rY]; + for (int r = 0; r < P; ++r) { + double dx = 0.0; + for (int m = FourTriodes::kBlockBegin[r]; m < FourTriodes::kBlockEnd[r]; ++m) + dx += root_.lu.d[r][m] * w[m]; + rTh -= root_.Z[rY][r] * dx; + } + w[rY] = rTh; + vTh = root_.v[rY] + rTh * iY; + loadSens_ = w; + loadSensValid_ = ok_; + return; + } constexpr int P = 8; solver::Vec i; solver::Mat d9; @@ -253,7 +276,8 @@ void Preamp::theveninAtY(double iY, double& vTh, double& rTh) const noexcept { x[r] = root_.Z[r][rY]; } rTh = root_.Z[rY][rY]; - if (solver::solveLinear

(A, x)) { + const bool solved = solver::solveLinear

(A, x); + if (solved) { for (int r = 0; r < P; ++r) { double dx = 0.0; for (int m = FourTriodes::kBlockBegin[r]; m < FourTriodes::kBlockEnd[r]; ++m) @@ -262,6 +286,41 @@ void Preamp::theveninAtY(double iY, double& vTh, double& rTh) const noexcept { } } vTh = root_.v[rY] + rTh * iY; + // the same direction for correctLoad: triode block x, y entry rTh; the conductances at the solution + for (int r = 0; r < P; ++r) + loadSens_[r] = x[r]; + loadSens_[rY] = rTh; + for (int r = 0; r < P; ++r) + for (int m = FourTriodes::kBlockBegin[r]; m < FourTriodes::kBlockEnd[r]; ++m) + root_.didv[r][m] = d9[r][m]; + loadSensValid_ = ok_ && solved; +} + +bool Preamp::correctLoad(double iY, double maxCorrection) noexcept { + if (!(solvedOnce_ && ok_ && loadSensValid_)) + return solveLoaded(iY); + const double di = iY - root_.i[rY]; + solver::Vec dv; + double big = 0.0; + for (int r = 0; r < kNumRootPorts; ++r) { + dv[r] = -loadSens_[r] * di; + big = std::fmax(big, std::fabs(dv[r])); + } + if (!(big <= maxCorrection)) + return solveLoaded(iY); + solver::Vec v = root_.v, i = root_.i; + for (int r = 0; r < kNumRootPorts; ++r) + v[r] += dv[r]; + // triode currents to first order: the block-diagonal ∂i/∂v of the solution (FourTriodes blocks) + for (int r = 0; r < rY; ++r) + for (int m = FourTriodes::kBlockBegin[r]; m < FourTriodes::kBlockEnd[r]; ++m) + i[r] += root_.didv[r][m] * dv[m]; + i[rY] = iY; + incident_ = reflected_; + ok_ = kNet.commit(root_, v, i, false, u_, incident_, eOff_); + root_.vPrev = saved_[0]; // the trend for the next sample: this sample's solution vs the last one + loadSensValid_ = false; + return ok_; } void Preamp::finishSample() noexcept { diff --git a/engine/src/devices/FourTriodes.h b/engine/src/devices/FourTriodes.h index 267cbd3..78508f1 100644 --- a/engine/src/devices/FourTriodes.h +++ b/engine/src/devices/FourTriodes.h @@ -67,6 +67,12 @@ struct FourTriodesLoad { i[8] = iY; d[8][8] = 0.0; } + + /// Port 8 is linear, i = g·v + c with g = 0, c = iY: solved outside the Newton (solver::LinearLastPort, D-068). + void linearPort(double& g, double& c) const noexcept { + g = 0.0; + c = iY; + } }; } // namespace amp2203::devices diff --git a/engine/src/devices/Pentode.h b/engine/src/devices/Pentode.h index 078530e..8384abf 100644 --- a/engine/src/devices/Pentode.h +++ b/engine/src/devices/Pentode.h @@ -42,7 +42,7 @@ struct Pentode { softplusSigmoid(u, sp, sg); const double e1 = vs * sp / kp; if (e1 > 0.0) { - const double f = 2.0 * std::pow(e1, ex); + const double f = 2.0 * std::exp(ex * std::log(e1)); const double df_de1 = ex * f / e1; const double df_dvgk = df_de1 * sg; const double df_dvsk = clamped ? 0.0 : df_de1 * (sp / kp - sg * vgk / vs); diff --git a/engine/src/devices/Triode.h b/engine/src/devices/Triode.h index 74328c3..51cd733 100644 --- a/engine/src/devices/Triode.h +++ b/engine/src/devices/Triode.h @@ -74,7 +74,7 @@ struct Triode { softplusSigmoid(u, sp, sg); const double e1 = vpk * sp / kp; if (e1 > 0.0) { - c.ip = 2.0 * std::pow(e1, ex) / kg1; + c.ip = 2.0 * std::exp(ex * std::log(e1)) / kg1; const double dip_de1 = ex * c.ip / e1; // 2·ex·E1^(ex−1)/kg1 c.dip_dvgk = dip_de1 * vpk * sg / s; c.dip_dvpk = dip_de1 * (sp / kp - sg * vgk * vpk * vpk / (s * s * s)); diff --git a/engine/src/devices/TriodesPentodes.h b/engine/src/devices/TriodesPentodes.h index ae20158..6af06bb 100644 --- a/engine/src/devices/TriodesPentodes.h +++ b/engine/src/devices/TriodesPentodes.h @@ -92,6 +92,12 @@ struct TriodesPentodesLoad { i[10] = g * (v[10] - vTh); d[10][10] = g; } + + /// Port 10 is linear, i = g·v − g·vTh: solved outside the Newton (solver::LinearLastPort, D-068). + void linearPort(double& gOut, double& c) const noexcept { + gOut = g; + c = -g * vTh; + } }; } // namespace amp2203::devices diff --git a/engine/src/devices/WrightOmega.h b/engine/src/devices/WrightOmega.h index 796d1af..06a99f6 100644 --- a/engine/src/devices/WrightOmega.h +++ b/engine/src/devices/WrightOmega.h @@ -1,5 +1,6 @@ #pragma once +#include #include namespace amp2203::devices { @@ -9,7 +10,7 @@ namespace amp2203::devices { /// then Fritsch–Shafer–Crowley iterations (fourth order; two suffice from these guesses, the loop /// stops when the update is below the last bit). chowdsp's Omega::omega4 is a fast approximation and /// is not accurate enough for the device tolerance (WDF_DESIGN §3.1). -inline double wrightOmega(double z) noexcept { +inline double wrightOmegaReference(double z) noexcept { if (z < -40.0) return std::exp(z); // ω = e^z·(1 − e^z + ...), relative error < 5e-18 double w; @@ -31,4 +32,57 @@ inline double wrightOmega(double z) noexcept { return w; } +namespace detail { +/// Fritsch–Shafer–Crowley step for w + ln w = z (fourth order). +inline double omegaStep(double z, double w) noexcept { + const double r = z - w - std::log(w); + const double wp1 = 1.0 + w; + const double t = wp1 * (wp1 + (2.0 / 3.0) * r); + return w + w * (r / wp1) * (t - 0.5 * r) / (t - r); +} + +/// Cubic Hermite start for the fast omega on [kZ0, kZ1] (M7, D-066): values and slopes ω' = ω/(1 + ω) of the +/// reference at a 1/8 grid; the start is within 1e-6 relative, so one step reaches double precision. +struct OmegaTable { + static constexpr double kZ0 = -10.0, kZ1 = 40.0, kH = 0.125; + static constexpr int kIntervals = 400; // (kZ1 − kZ0) / kH + std::array w{}, d{}; + OmegaTable() noexcept { + for (int k = 0; k <= kIntervals; ++k) { + const double v = wrightOmegaReference(kZ0 + kH * k); + w[static_cast(k)] = v; + d[static_cast(k)] = kH * v / (1.0 + v); + } + } +}; +inline const OmegaTable kOmegaTable{}; +} // namespace detail + +/// Wright omega for the device evaluations (M7, D-066): the same function as wrightOmegaReference to a few +/// ulp, about four times faster. z < −10: the series e^z·(1 − y + 3/2·y² − 8/3·y³), y = e^z (next term +/// 125/24·y⁴ < 3e-18 relative); −10 ≤ z < 40: cubic Hermite start from a table, then one Fritsch–Shafer– +/// Crowley step; z ≥ 40: the asymptotic start z − ln z + ln z / z, then one step. +inline double wrightOmega(double z) noexcept { + using detail::kOmegaTable; + using T = detail::OmegaTable; + if (z < T::kZ0) { + const double y = std::exp(z); + return y * (1.0 - y * (1.0 - y * (1.5 - y * (8.0 / 3.0)))); + } + double w; + if (z < T::kZ1) { + const double x = (z - T::kZ0) * (1.0 / T::kH); + const int k = static_cast(x); + const double t = x - k; + const auto i = static_cast(k); + const double u = 1.0 - t; + w = (1.0 + 2.0 * t) * u * u * kOmegaTable.w[i] + t * u * u * kOmegaTable.d[i] + + t * t * (3.0 - 2.0 * t) * kOmegaTable.w[i + 1] - t * t * u * kOmegaTable.d[i + 1]; + } else { + const double lz = std::log(z); + w = z - lz + lz / z; + } + return detail::omegaStep(z, w); +} + } // namespace amp2203::devices diff --git a/engine/src/solver/MultiportNewton.h b/engine/src/solver/MultiportNewton.h index 2975b4e..ce94d08 100644 --- a/engine/src/solver/MultiportNewton.h +++ b/engine/src/solver/MultiportNewton.h @@ -5,6 +5,7 @@ #include #include +#include "amp2203/detail/LuFactors.h" #include "amp2203/detail/NewtonStats.h" namespace amp2203::solver { @@ -13,53 +14,108 @@ template using Vec = std::array; template using Mat = std::array, K>; +template +using Pivots = std::array; namespace lu { /// Elimination step k of solveLinear, then the steps after it: every loop bound is a compile-time constant -/// (unrolled by the compiler; the same operations in the same order as the loop form; M6). -template -inline bool eliminate(Mat& J, Vec& r) noexcept { - if constexpr (k == K) { +/// (unrolled by the compiler; the same operations in the same order as the loop form; M6). The multipliers +/// are kept below the diagonal and the pivot rows in `piv`, so that J holds its LU factors afterwards (M7). +/// M ≤ K: only the leading M×M block and the first M entries of r take part (M7, D-068). +template +inline bool eliminate(Mat& J, Vec& r, Pivots& piv) noexcept { + if constexpr (k == M) { return true; } else { int p = k; double best = std::fabs(J[k][k]); - for (int i = k + 1; i < K; ++i) + for (int i = k + 1; i < M; ++i) if (const double a = std::fabs(J[i][k]); a > best) { best = a; p = i; } if (!(best > 0.0) || !std::isfinite(best)) return false; + piv[k] = p; if (p != k) { std::swap(J[p], J[k]); std::swap(r[p], r[k]); } const double inv = 1.0 / J[k][k]; // one division per pivot (D-046) - for (int i = k + 1; i < K; ++i) { + for (int i = k + 1; i < M; ++i) { const double f = J[i][k] * inv; - for (int j = k + 1; j < K; ++j) // column k below the pivot is not used again + for (int j = k + 1; j < M; ++j) J[i][j] -= f * J[k][j]; r[i] -= f * r[k]; + J[i][k] = f; // the multiplier (L), not read by the elimination itself } - return eliminate(J, r); + return eliminate(J, r, piv); + } +} + +template +inline void backSubstitute(const Mat& U, Vec& r) noexcept { + for (int i = M - 1; i >= 0; --i) { + double s = r[i]; + for (int j = i + 1; j < M; ++j) + s -= U[i][j] * r[j]; + r[i] = s / U[i][i]; } } } // namespace lu -/// Solves J·x = r in place (x returned in r) by Gaussian elimination with partial pivoting. +/// Solves J·x = r in place (x returned in r) by Gaussian elimination with partial pivoting; J is left +/// holding its LU factors with the pivot rows in `piv` (for solveFactored). /// Returns false if a pivot is zero or not finite. +template +bool solveLinear(Mat& J, Vec& r, Pivots& piv) noexcept { + if (!lu::eliminate(J, r, piv)) + return false; + lu::backSubstitute(J, r); + return true; +} + template bool solveLinear(Mat& J, Vec& r) noexcept { - if (!lu::eliminate(J, r)) - return false; - for (int i = K - 1; i >= 0; --i) { - double s = r[i]; - for (int j = i + 1; j < K; ++j) - s -= J[i][j] * r[j]; - r[i] = s / J[i][i]; + Pivots piv; + return solveLinear(J, r, piv); +} + +/// Solves J·x = r in place with the factors solveLinear left in `lu` (M7); entries from M on are untouched. +template +void solveFactored(const detail::LuFactors& lu, Vec& r) noexcept { + // the elimination swapped whole rows, multipliers included: all swaps first, then L + for (int k = 0; k < M; ++k) + if (const int p = lu.piv[static_cast(k)]; p != k) + std::swap(r[static_cast(p)], r[static_cast(k)]); + for (int k = 0; k < M; ++k) + for (int i = k + 1; i < M; ++i) + r[i] -= lu.m[i][k] * r[k]; + lu::backSubstitute(lu.m, r); +} + +/// A device whose last port is linear, i = g·v + c (the J4 load ports, M7, D-068): `linearPort(g, c)`. The Newton +/// then solves the other K − 1 ports; the linear port's voltage follows from them exactly at every evaluation. +template +concept LinearLastPort = requires(const Device& d, double& g, double& c) { d.linearPort(g, c); }; + +template +inline constexpr int kNewtonPorts = LinearLastPort ? K - 1 : K; + +/// The linear last port of a LinearLastPort device: given the other ports' currents i[0, K−1), sets v[K−1] and +/// i[K−1] from v = vOc − Z·i (row K−1). Returns nothing for other devices. +template +inline void solveLinearPort(const Device& device, const Vec& vOc, const Mat& Z, Vec& v, Vec& i) noexcept { + if constexpr (LinearLastPort) { + constexpr int y = K - 1; + double g = 0.0, c = 0.0; + device.linearPort(g, c); + double s = vOc[y] - Z[y][y] * c; + for (int m = 0; m < y; ++m) + s -= Z[y][m] * i[m]; + v[y] = s / (1.0 + Z[y][y] * g); + i[y] = g * v[y] + c; } - return true; } struct NewtonConfig { @@ -79,7 +135,9 @@ struct NewtonConfig { /// Warm start from `v` (in/out). Each step is scaled uniformly so that no port moves by more than its /// `maxStep` (keeps the Newton direction); if ‖f‖∞ grows, the step is halved up to maxHalvings times. /// Converged when ‖f‖∞ < residualTol or ‖Δv‖∞ < stepTol·max(1, ‖v‖∞). Otherwise the iterate with the -/// lowest ‖f‖∞ is kept and the failure is counted. On return `i` holds the currents at `v`. +/// lowest ‖f‖∞ is kept and the failure is counted. On return `i` holds the currents at `v`. With `lu`, the +/// Jacobian of the last iteration is factored there (lu->valid; left unchanged when no iteration ran, M7); with +/// `didvOut`, the device ∂i/∂v of the last evaluation (the returned v when converged) is left there. /// A device whose ∂i/∂v is block-diagonal declares, per column c, the row range [kBlockBegin[c], kBlockEnd[c]) /// that can be nonzero; the Jacobian J = I + Z·∂i/∂v is then built from those entries only (D-046). Entries /// outside the blocks are never read, so such a device need not clear them. @@ -100,8 +158,32 @@ class MultiportNewton { template int solve(const Device& device, const Vec& vOc, const Mat& Z, Vec& v, Vec& i, - detail::NewtonStats& stats) const noexcept { - Mat didv; + detail::NewtonStats& stats, detail::LuFactors* lu = nullptr, + Mat* didvOut = nullptr) const noexcept { + if (!lu || !didvOut) { + detail::LuFactors localLu; + Mat localD; + return solve(device, vOc, Z, v, i, stats, lu ? lu : &localLu, didvOut ? didvOut : &localD); + } + detail::LuFactors& fac = *lu; + Mat& didv = *didvOut; + // with a linear last port: J over the first M ports with Z' = Z_pp − Z_py·g·Z_yp / (1 + g·Z_yy) (D-068) + constexpr int M = kNewtonPorts; + const Mat* Zj = &Z; + Mat Zr; + if constexpr (M < K) { + double g = 0.0, c = 0.0; + device.linearPort(g, c); + if (g != 0.0) { + const double k = g / (1.0 + g * Z[M][M]); + for (int r = 0; r < M; ++r) { + const double a = Z[r][M] * k; + for (int cc = 0; cc < M; ++cc) + Zr[r][cc] = Z[r][cc] - a * Z[M][cc]; + } + Zj = &Zr; + } + } Vec f; double fn = residual(device, vOc, Z, v, i, didv, f); Vec best = v; @@ -111,27 +193,39 @@ class MultiportNewton { int it = 0; while (!(converged = fn < config.residualTol) && it < config.maxIter) { ++it; - Mat J; - for (int r = 0; r < K; ++r) - for (int c = 0; c < K; ++c) { + Mat& J = fac.m; + const Mat& Zn = *Zj; + for (int c = 0; c < M; ++c) { // the conductances these factors belong to (D-068) + if constexpr (BlockDevice) { + for (int m = Device::kBlockBegin[c]; m < Device::kBlockEnd[c]; ++m) + fac.d[m][c] = didv[m][c]; + } else { + for (int m = 0; m < M; ++m) + fac.d[m][c] = didv[m][c]; + } + } + for (int r = 0; r < M; ++r) + for (int c = 0; c < M; ++c) { double s = r == c ? 1.0 : 0.0; if constexpr (BlockDevice) { // same terms in the same order as the dense sum without its exact zeros for (int m = Device::kBlockBegin[c]; m < Device::kBlockEnd[c]; ++m) - s += Z[r][m] * didv[m][c]; + s += Zn[r][m] * didv[m][c]; } else { - for (int m = 0; m < K; ++m) - s += Z[r][m] * didv[m][c]; + for (int m = 0; m < M; ++m) + s += Zn[r][m] * didv[m][c]; } J[r][c] = s; } Vec dv; - for (int r = 0; r < K; ++r) + for (int r = 0; r < M; ++r) dv[r] = -f[r]; - if (!solveLinear(J, dv)) + dv[K - 1] = M < K ? 0.0 : dv[K - 1]; // the linear port follows in residual() + fac.valid = solveLinear(J, dv, fac.piv); + if (!fac.valid) break; double scale = 1.0; - for (int r = 0; r < K; ++r) + for (int r = 0; r < M; ++r) if (const double lim = dv[r] >= 0.0 ? maxStep[r] : limitDown(r); std::fabs(dv[r]) * scale > lim) scale = lim / std::fabs(dv[r]); for (auto& d : dv) @@ -174,11 +268,15 @@ class MultiportNewton { private: template - static double residual(const Device& device, const Vec& vOc, const Mat& Z, const Vec& v, Vec& i, + static double residual(const Device& device, const Vec& vOc, const Mat& Z, Vec& v, Vec& i, Mat& didv, Vec& f) noexcept { + constexpr int M = kNewtonPorts; device(v, i, didv); + solveLinearPort(device, vOc, Z, v, i); // exact for the linear port: its residual is 0 + if constexpr (M < K) + f[M] = 0.0; double n = 0.0; - for (int r = 0; r < K; ++r) { + for (int r = 0; r < M; ++r) { double s = v[r] - vOc[r]; for (int m = 0; m < K; ++m) s += Z[r][m] * i[m]; diff --git a/engine/src/stage/RootNetwork.h b/engine/src/stage/RootNetwork.h index dc74705..3aa4bf9 100644 --- a/engine/src/stage/RootNetwork.h +++ b/engine/src/stage/RootNetwork.h @@ -152,24 +152,59 @@ struct RootNetwork { } /// The rest of process() for open-circuit voltages from openCircuit(): Newton, node voltages, incident - /// waves. + /// waves. `outputs` = false: the root ports only (v, i), for a trial solve that another call replaces (M7). template bool solve(State& s, const solver::MultiportNewton& newton, const Device& device, const std::array& u, - const RVec& vOc, EVec& wave, const EVec& eOffset) const noexcept { + const RVec& vOc, EVec& wave, const EVec& eOffset, bool outputs = true) const noexcept { // predictor: linear extrapolation from the two previous solutions (D-029), each port's step - // clamped to its Newton step limit; after a failed sample the trend restarts at zero (D-034) + // clamped to its Newton step limit; after a failed sample the trend restarts at zero (D-034). + // Jacobian predictor (M7): one Newton step from the last solution with the last factored Jacobian, + // the residual taken with the last currents (the device's linear last port solved for this sample, D-068) RVec v = s.v; - for (int r = 0; r < K; ++r) - v[r] += std::clamp(s.v[r] - s.vPrev[r], -newton.limitDown(r), newton.maxStep[r]); + if (s.jacobianPredictor && s.lu.valid) { + constexpr int M = solver::kNewtonPorts; + RVec iNow = s.i, vNow = s.v; + solver::solveLinearPort(device, vOc, s.Z, vNow, iNow); + RVec dv; + for (int r = 0; r < M; ++r) + dv[r] = vOc[r] - vNow[r] - dot(s.Z[r].data(), iNow.data()); + solver::solveFactored(s.lu, dv); + for (int r = 0; r < M; ++r) + v[r] += std::clamp(dv[r], -newton.limitDown(r), newton.maxStep[r]); + } else { + for (int r = 0; r < K; ++r) + v[r] += std::clamp(s.v[r] - s.vPrev[r], -newton.limitDown(r), newton.maxStep[r]); + } RVec i = s.i; const auto failures = s.stats.failures; - int iterations = newton.solve(device, vOc, s.Z, v, i, s.stats); + int iterations = newton.solve(device, vOc, s.Z, v, i, s.stats, &s.lu, &s.didv); bool failed = s.stats.failures != failures; if (failed) iterations += rescue(s, newton, device, vOc, v, i, failed); s.iterations = iterations; s.vOcPrev = vOc; + if (!outputs) { + bool finiteV = true; + for (int r = 0; r < K; ++r) + finiteV = finiteV && std::isfinite(v[r]) && std::isfinite(i[r]); + if (!finiteV) { + ++s.sanitized; + return false; + } + s.vPrev = failed ? v : s.v; + s.v = v; + s.i = i; + return true; + } + return commit(s, v, i, failed, u, wave, eOffset); + } + + /// Takes root port voltages v and currents i as this sample's solution: node voltages, incident waves + /// (`wave` holds the reflected waves on entry). Used by solve() and for a solution corrected without Newton + /// (the preamp's J4 load correction, M7). Returns false (state unchanged) if a node voltage is not finite. + bool commit(State& s, const RVec& v, const RVec& i, bool failed, const std::array& u, EVec& wave, + const EVec& eOffset) const noexcept { std::array node; bool finite = true; for (int n = 0; n < N; ++n) { @@ -207,7 +242,7 @@ struct RootNetwork { for (int r = 0; r < K; ++r) vk[r] = s.vOcPrev[r] + (vOc[r] - s.vOcPrev[r]) * (static_cast(k) / m); detail::NewtonStats sub; - iterations += newton.solve(device, vk, s.Z, vs, is, sub); + iterations += newton.solve(device, vk, s.Z, vs, is, sub, &s.lu, &s.didv); ok = sub.failures == 0; } if (ok) { @@ -241,6 +276,7 @@ struct RootNetwork { std::array, K> Z{}; resistanceMatrix(Xr, Z); s.v = guess; + s.lu.valid = false; detail::NewtonStats dcStats; newton.solve(device, vOc, Z, s.v, s.i, dcStats); for (int n = 0; n < N; ++n) { diff --git a/harness/plugin_cpu.py b/harness/plugin_cpu.py index b26708f..390945d 100644 --- a/harness/plugin_cpu.py +++ b/harness/plugin_cpu.py @@ -57,7 +57,7 @@ def main(): f'rescued {resc})', flush=True) lines = ['# M6 plugin CPU under knob automation', '', f'- Date {datetime.date.today().isoformat()}, commit `{_git()}`; `amp2203_render plugin` at the plugin defaults ' - f'({a.factor}x, Power Amp Rate Full, exact J4, Newton tol 1e-4 V, half-steps on; D-054 – D-056, D-060), FIR ' + f'({a.factor}x, Power Amp Rate Half, exact J4, Newton tol 1e-4 V, half-steps on; D-054 – D-056, D-069), FIR ' f'oversampling, {a.seconds:g} s of DI GTR L at {RATE} Hz; processBlock time only.', '- Automation between host blocks: lfo = 10 Hz triangle 0 → 10 → 0, jump = 0 ↔ 10 every 50 ms; "all" moves ' 'the six knobs together. One render at a time.', '', diff --git a/harness/plugin_null.py b/harness/plugin_null.py index 6e2abfc..ec303ef 100644 --- a/harness/plugin_null.py +++ b/harness/plugin_null.py @@ -32,7 +32,7 @@ HOST_RATE = 48000 FACTOR = 2 -POWER_RATE = 'full' # the plugin default (D-060) +POWER_RATE = 'half' # the plugin default (D-069; Full before, D-060) RENDER = config.ROOT / 'build' / 'plugin' / 'amp2203_render_artefacts' / 'Release' / 'amp2203_render' LIMIT_DB = -60.0 NEWTON_TOL = 1e-4 # V, the plugin's (D-055) diff --git a/harness/reports/M3/20261019_b1234_DEFAULT.md b/harness/reports/M3/20261019_b1234_DEFAULT.md new file mode 100644 index 0000000..2f3f1e7 --- /dev/null +++ b/harness/reports/M3/20261019_b1234_DEFAULT.md @@ -0,0 +1,224 @@ +# M3 report: b1234 / DEFAULT + +- Block: B1234 Preamp (B12 + B3 + B4 in one tree, 8-port root, D-038) +- Preset: DEFAULT — Default knobs; standard signal set at the block levels +- Knobs: preampVolume=0.6, treble=0.5, middle=0.5, bass=0.5, master=0.3, presence=0.5 +- Date: 2026-10-19 +- Engine commit: `ecf9042 (dirty)` +- Testbench: `harness/testbenches/b1234.cir` sha256 `a99cb5b98a48184b`; netlist sha256 `ffd9b2812204248f`; tubes.lib sha256 `c9c9c88346c69010` +- ngspice 47, fs = 768000 Hz, SPICE reltol 1e-08 + +## Summary + +PASS 119 · FAIL 0 · INFO 15 (INFO = measured, not scored in M3) + +| Check | Item | Measured | Tolerance | Status | +|---|---|---|---|---| +| dc_rel_error | v(bpa) | 1.45e-16 | ≤ 0.005 | PASS | +| dc_rel_error | v(bpx) | 0 | ≤ 0.005 | PASS | +| dc_rel_error | v(g1a) | 0.0001717 | ≤ 0.005 | PASS | +| dc_rel_error | v(g2a) | 0.0006604 | ≤ 0.005 | PASS | +| dc_rel_error | v(in) | 0 | ≤ 0.005 | PASS | +| dc_rel_error | v(k1a) | 4.576e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(k1b) | 0.000107 | ≤ 0.005 | PASS | +| dc_rel_error | v(k2a) | 2.495e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(k2b) | 1.794e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(mw) | 1.175e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(na) | 0.0006024 | ≤ 0.005 | PASS | +| dc_rel_error | v(nb) | 0.0006604 | ≤ 0.005 | PASS | +| dc_rel_error | v(nbb) | 1.175e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(nd) | 1.794e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(ne) | 1.179e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(p1a) | 1.436e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(p1b) | 1.121e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(p2a) | 1.85e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(tt) | 8.356e-09 | ≤ 0.005 | PASS | +| dc_rel_error | v(tw) | 9.614e-09 | ≤ 0.005 | PASS | +| dc_rel_error | v(vtop) | 0.0006024 | ≤ 0.005 | PASS | +| dc_rel_error | v(w1) | 0.0006024 | ≤ 0.005 | PASS | +| dc_rel_error | v(y) | 3.257e-10 | ≤ 0.005 | PASS | +| nonlinear_esr_db | SIN-100Hz-0.01V | -73.08 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-0.01V | 0.002595 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-0.01V | 1.433e-05 pp | — | INFO | +| newton_failures | SIN-100Hz-0.01V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-0.01V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-0.01V | 1 | ≤ 64 | PASS | +| nonlinear_esr_db | SIN-1000Hz-0.01V | -60.63 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-0.01V | 0.01803 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-0.01V | 0.001323 pp | — | INFO | +| newton_failures | SIN-1000Hz-0.01V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-0.01V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-0.01V | 2 | ≤ 64 | PASS | +| nonlinear_esr_db | SIN-5000Hz-0.01V | -53.07 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-0.01V | 0.1088 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-0.01V | 0.03125 pp | — | INFO | +| newton_failures | SIN-5000Hz-0.01V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-0.01V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-0.01V | 6 | ≤ 64 | PASS | +| nonlinear_esr_db | SIN-100Hz-0.1V | -73.53 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-0.1V | 0.02799 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-0.1V | 0.001435 pp | — | INFO | +| newton_failures | SIN-100Hz-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-0.1V | 2 | ≤ 64 | PASS | +| nonlinear_esr_db | SIN-1000Hz-0.1V | -57.75 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-0.1V | 0.02889 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-0.1V | 0.007326 pp | — | INFO | +| newton_failures | SIN-1000Hz-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-0.1V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-0.1V | 6 | ≤ 64 | PASS | +| nonlinear_esr_db | SIN-5000Hz-0.1V | -47.24 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-0.1V | 0.03821 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-0.1V | 0.008787 pp | — | INFO | +| newton_failures | SIN-5000Hz-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-0.1V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-0.1V | 7 | ≤ 64 | PASS | +| nonlinear_esr_db | SIN-100Hz-0.5V | -75.29 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-0.5V | 0.002416 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-0.5V | 6.976e-05 pp | — | INFO | +| newton_failures | SIN-100Hz-0.5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-0.5V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-0.5V | 4 | ≤ 64 | PASS | +| nonlinear_esr_db | SIN-1000Hz-0.5V | -50.57 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-0.5V | 0.02096 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-0.5V | 0.005945 pp | — | INFO | +| newton_failures | SIN-1000Hz-0.5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-0.5V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-0.5V | 7 | ≤ 64 | PASS | +| nonlinear_esr_db | SIN-5000Hz-0.5V | -39.33 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-0.5V | 0.0322 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-0.5V | 0.00618 pp | — | INFO | +| newton_failures | SIN-5000Hz-0.5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-0.5V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-0.5V | 9 | ≤ 64 | PASS | +| nonlinear_esr_db_overdrive | SIN-100Hz-2V | -70.14 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-2V | 0.00951 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-2V | 0.004205 pp | — | INFO | +| newton_failures | SIN-100Hz-2V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-2V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-2V | 5 | ≤ 64 | PASS | +| nonlinear_esr_db_overdrive | SIN-1000Hz-2V | -47.39 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-2V | 0.00316 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-2V | 0.0003278 pp | — | INFO | +| newton_failures | SIN-1000Hz-2V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-2V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-2V | 7 | ≤ 64 | PASS | +| nonlinear_esr_db_overdrive | SIN-5000Hz-2V | -34.32 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-2V | 0.1697 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-2V | 0.007558 pp | — | INFO | +| newton_failures | SIN-5000Hz-2V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-2V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-2V | 9 | ≤ 64 | PASS | +| nonlinear_esr_db | SWP-lo-0.01V | -51.28 dB | ≤ -20 | PASS | +| linear_mag_db_20_10k | SWP-lo-0.01V | 1.146 dB | ≤ 0.1 | INFO | +| linear_mag_db_10k_20k | SWP-lo-0.01V | 1.073 dB | ≤ 0.3 | INFO | +| method_mag_db_spice_tran_vs_ac | SWP-lo-0.01V | 1.155 dB | — | INFO | +| newton_failures | SWP-lo-0.01V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SWP-lo-0.01V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SWP-lo-0.01V | 7 | ≤ 64 | PASS | +| nonlinear_esr_db | SWP-hi-0.1V | -46.6 dB | ≤ -20 | PASS | +| newton_failures | SWP-hi-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SWP-hi-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SWP-hi-0.1V | 9 | ≤ 64 | PASS | +| nonlinear_esr_db | TT-0.05V | -69.19 dB | ≤ -20 | PASS | +| newton_failures | TT-0.05V | 0 | ≤ 0 | PASS | +| newton_median_iterations | TT-0.05V | 1 | ≤ 3 | PASS | +| newton_max_iterations | TT-0.05V | 3 | ≤ 64 | PASS | +| nonlinear_esr_db | BURST-0.5V | -51.14 dB | ≤ -20 | PASS | +| newton_failures | BURST-0.5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | BURST-0.5V | 1 | ≤ 3 | PASS | +| newton_max_iterations | BURST-0.5V | 7 | ≤ 64 | PASS | +| nonlinear_esr_db | STEP-0.1V | -56.98 dB | ≤ -20 | PASS | +| newton_failures | STEP-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | STEP-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | STEP-0.1V | 7 | ≤ 64 | PASS | +| nonlinear_esr_db | DI-GTR L | -62.5 dB | ≤ -20 | PASS | +| newton_failures | DI-GTR L | 0 | ≤ 0 | PASS | +| newton_median_iterations | DI-GTR L | 1 | ≤ 3 | PASS | +| newton_max_iterations | DI-GTR L | 7 | ≤ 64 | PASS | +| nonlinear_esr_db | DI-GTR LL | -62.95 dB | ≤ -20 | PASS | +| newton_failures | DI-GTR LL | 0 | ≤ 0 | PASS | +| newton_median_iterations | DI-GTR LL | 1 | ≤ 3 | PASS | +| newton_max_iterations | DI-GTR LL | 7 | ≤ 64 | PASS | +| nonlinear_esr_db | DI-GTR R | -63.68 dB | ≤ -20 | PASS | +| newton_failures | DI-GTR R | 0 | ≤ 0 | PASS | +| newton_median_iterations | DI-GTR R | 1 | ≤ 3 | PASS | +| newton_max_iterations | DI-GTR R | 7 | ≤ 64 | PASS | +| nonlinear_esr_db | DI-GTR RR | -62.6 dB | ≤ -20 | PASS | +| newton_failures | DI-GTR RR | 0 | ≤ 0 | PASS | +| newton_median_iterations | DI-GTR RR | 1 | ≤ 3 | PASS | +| newton_max_iterations | DI-GTR RR | 7 | ≤ 64 | PASS | + +## Signals + +| Signal | Peak F [V] | ESR [dB] | Best lag | ESR at lag [dB] | THD F / E [%] | Power F / E [W] | ns/sample | Newton fail / median / max it | +|---|---|---|---|---|---|---|---|---| +| SIN-100Hz-0.01V | 0.1088 | -73.08 | 0 | -73.08 | 0.129 / 0.129 | — | 786.4 | 0 / 1 / 1 | +| SIN-1000Hz-0.01V | 0.4171 | -60.63 | 0 | -60.63 | 0.864 / 0.866 | — | 788.7 | 0 / 1 / 2 | +| SIN-5000Hz-0.01V | 1.39 | -53.07 | 0 | -53.07 | 6.4 / 6.43 | — | 1157.7 | 0 / 2 / 6 | +| SIN-100Hz-0.1V | 1.071 | -73.53 | 0 | -73.53 | 1.65 / 1.65 | — | 790.9 | 0 / 1 / 2 | +| SIN-1000Hz-0.1V | 1.771 | -57.75 | 0 | -57.75 | 42.6 / 42.6 | — | 972.1 | 0 / 2 / 6 | +| SIN-5000Hz-0.1V | 2.482 | -47.24 | 0 | -47.24 | 48 / 48 | — | 1214.2 | 0 / 2 / 7 | +| SIN-100Hz-0.5V | 2.723 | -75.29 | 0 | -75.29 | 15.9 / 15.9 | — | 799.8 | 0 / 1 / 4 | +| SIN-1000Hz-0.5V | 2.543 | -50.57 | 0 | -50.57 | 63.6 / 63.6 | — | 997.7 | 0 / 2 / 7 | +| SIN-5000Hz-0.5V | 2.549 | -39.33 | 0 | -39.33 | 44.7 / 44.7 | — | 1169.0 | 0 / 2 / 9 | +| SIN-100Hz-2V | 2.881 | -70.14 | 0 | -70.14 | 28.2 / 28.3 | — | 812.9 | 0 / 1 / 5 | +| SIN-1000Hz-2V | 2.798 | -47.39 | 0 | -47.39 | 63.6 / 63.6 | — | 983.8 | 0 / 2 / 7 | +| SIN-5000Hz-2V | 2.542 | -34.32 | 0 | -34.32 | 44.4 / 44.5 | — | 1157.4 | 0 / 2 / 9 | +| SWP-lo-0.01V | 1.367 | -51.28 | 0 | -51.28 | — | — | 896.5 | 0 / 1 / 7 | +| SWP-hi-0.1V | 1.928 | -46.60 | 0 | -46.60 | — | — | 960.5 | 0 / 1 / 9 | +| TT-0.05V | 1.627 | -69.19 | 0 | -69.19 | — | — | 800.9 | 0 / 1 / 3 | +| BURST-0.5V | 2.548 | -51.14 | 0 | -51.14 | — | — | 949.1 | 0 / 1 / 7 | +| STEP-0.1V | 2.403 | -56.98 | 0 | -56.98 | — | — | 784.7 | 0 / 1 / 7 | +| DI-GTR L | 2.564 | -62.50 | 0 | -62.50 | — | — | 762.3 | 0 / 1 / 7 | +| DI-GTR LL | 2.571 | -62.95 | 0 | -62.95 | — | — | 735.8 | 0 / 1 / 7 | +| DI-GTR R | 2.628 | -63.68 | 0 | -63.68 | — | — | 754.0 | 0 / 1 / 7 | +| DI-GTR RR | 2.679 | -62.60 | 0 | -62.60 | — | — | 760.0 | 0 / 1 / 7 | + +## DC operating point + +| Node | SPICE [V] | Engine [V] | Rel. error | +|---|---|---|---| +| bpa | 391.9215 | 391.9215 | 1.45e-16 | +| bpx | 404.9982 | 404.9982 | 0 | +| g1a | 0.0001 | 0.0001 | 0.0001717 | +| g2a | 0.0005 | 0.0005 | 0.0006604 | +| in | 0.0000 | 0.0000 | 0 | +| k1a | 2.5271 | 2.5270 | 4.576e-05 | +| k1b | 3.7171 | 3.7167 | 0.000107 | +| k2a | 1.4139 | 1.4138 | 2.495e-05 | +| k2b | 233.1404 | 233.1446 | 1.794e-05 | +| mw | 0.0000 | -0.0000 | 1.175e-08 | +| na | 0.0002 | 0.0002 | 0.0006024 | +| nb | 0.0005 | 0.0005 | 0.0006604 | +| nbb | 0.0000 | -0.0000 | 1.175e-08 | +| nd | 233.1404 | 233.1446 | 1.794e-05 | +| ne | 0.0000 | -0.0000 | 1.179e-08 | +| p1a | 298.3260 | 298.3303 | 1.436e-05 | +| p1b | 354.7502 | 354.7542 | 1.121e-05 | +| p2a | 232.5771 | 232.5814 | 1.85e-05 | +| tt | 0.0000 | -0.0000 | 8.356e-09 | +| tw | 0.0000 | -0.0000 | 9.614e-09 | +| vtop | 0.0002 | 0.0002 | 0.0006024 | +| w1 | 0.0002 | 0.0002 | 0.0006024 | +| y | 0.0000 | -0.0000 | 3.257e-10 | + +Engine values are the probe values after the first sample (zero input) following `reset()`. Nodes the engine does not probe yet show —. + +## Figures + +### Waveform overlay and error (most demanding signal) + +![Waveform overlay and error (most demanding signal)](20261019_b1234_DEFAULT/waveform.png) + +### Harmonics + +![Harmonics](20261019_b1234_DEFAULT/harmonics.png) + +### Magnitude response + +![Magnitude response](20261019_b1234_DEFAULT/magnitude.png) + +### Spectrogram (DI) + +![Spectrogram (DI)](20261019_b1234_DEFAULT/spectrogram.png) diff --git a/harness/reports/M3/20261019_b1234_DEFAULT/harmonics.png b/harness/reports/M3/20261019_b1234_DEFAULT/harmonics.png new file mode 100644 index 0000000..0672b69 Binary files /dev/null and b/harness/reports/M3/20261019_b1234_DEFAULT/harmonics.png differ diff --git a/harness/reports/M3/20261019_b1234_DEFAULT/magnitude.png b/harness/reports/M3/20261019_b1234_DEFAULT/magnitude.png new file mode 100644 index 0000000..2e5e9d0 Binary files /dev/null and b/harness/reports/M3/20261019_b1234_DEFAULT/magnitude.png differ diff --git a/harness/reports/M3/20261019_b1234_DEFAULT/spectrogram.png b/harness/reports/M3/20261019_b1234_DEFAULT/spectrogram.png new file mode 100644 index 0000000..3ab05ab Binary files /dev/null and b/harness/reports/M3/20261019_b1234_DEFAULT/spectrogram.png differ diff --git a/harness/reports/M3/20261019_b1234_DEFAULT/waveform.png b/harness/reports/M3/20261019_b1234_DEFAULT/waveform.png new file mode 100644 index 0000000..3f9c1e3 Binary files /dev/null and b/harness/reports/M3/20261019_b1234_DEFAULT/waveform.png differ diff --git a/harness/reports/M3/20261019_b1234_knob_jump.md b/harness/reports/M3/20261019_b1234_knob_jump.md new file mode 100644 index 0000000..15fcacf --- /dev/null +++ b/harness/reports/M3/20261019_b1234_knob_jump.md @@ -0,0 +1,14 @@ +# M3 report: B1234 knob-jump test + +- Date: 2026-10-19, engine commit `ecf9042 (dirty)`, ngspice 47, fs = 768000 Hz, SPICE reltol 1e-08 +- Knob targets 0 → 1 in 10 ms from t = 20 ms during a 1000 Hz, 0.1 V tone at the guitar input (DC 0.0000 V); other knobs at their defaults. Engine: smoothing 20 ms, matrix update every 32 samples. SPICE: pot resistances follow the same smoothed trajectory continuously. +- Window: the ramp and the 20 ms after it. Pass (D-025): no NaN/Inf, ESR ≤ -20 dB. + +| Case | Finite | ESR | Status | Peak error / ref peak | Error energy > 5 kHz vs tone | +|---|---|---|---|---|---| +| preampVolume | yes | -40.9 dB | PASS | 2.56e-02 | -44.1 dB | +| treble | yes | -54.2 dB | PASS | 5.45e-03 | -56.8 dB | +| middle | yes | -50.3 dB | PASS | 5.17e-03 | -57.9 dB | +| bass | yes | -55.1 dB | PASS | 3.54e-03 | -64.9 dB | +| master | yes | -41.7 dB | PASS | 7.99e-03 | -46.1 dB | +| preampVolume+treble+middle+bass+master | yes | -39.1 dB | PASS | 1.16e-02 | -43.4 dB | diff --git a/harness/reports/M4/20261019_b56_DEFAULT.md b/harness/reports/M4/20261019_b56_DEFAULT.md new file mode 100644 index 0000000..e484a1c --- /dev/null +++ b/harness/reports/M4/20261019_b56_DEFAULT.md @@ -0,0 +1,230 @@ +# M4 report: b56 / DEFAULT + +- Block: B56 PowerAmp (phase inverter + power stage + OT in one tree, D-041); SPICE with B7 +- Preset: DEFAULT — Default knobs; standard signal set at the block levels +- Knobs: preampVolume=0.6, treble=0.5, middle=0.5, bass=0.5, master=0.3, presence=0.5 +- Date: 2026-10-19 +- Engine commit: `ecf9042 (dirty)` +- Testbench: `harness/testbenches/b56.cir` sha256 `cf8c8de755f4bb9a`; netlist sha256 `ffd9b2812204248f`; tubes.lib sha256 `c9c9c88346c69010` +- ngspice 47, fs = 768000 Hz, SPICE reltol 1e-08 + +> SWP-lo-0.1V rendered at RELTOL 1e-07 (ngspice: timestep too small at 1e-08) + +## Summary + +PASS 106 · FAIL 0 · INFO 27 (INFO = measured, not scored in M4) + +| Check | Item | Measured | Tolerance | Status | +|---|---|---|---|---| +| dc_rel_error | v(bpx) | 6.632e-13 | ≤ 0.005 | PASS | +| dc_rel_error | v(ct) | 3.008e-15 | ≤ 0.005 | PASS | +| dc_rel_error | v(g3a) | 2.529e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(g3b) | 2.529e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(g4) | 4.919e-07 | ≤ 0.005 | PASS | +| dc_rel_error | v(g5) | 4.919e-07 | ≤ 0.005 | PASS | +| dc_rel_error | v(g6) | 4.919e-07 | ≤ 0.005 | PASS | +| dc_rel_error | v(g7) | 4.919e-07 | ≤ 0.005 | PASS | +| dc_rel_error | v(k3) | 2.526e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(nbias) | 4.636e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(nf) | 2.526e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(ng) | 2.526e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(ngn) | 4.863e-07 | ≤ 0.005 | PASS | +| dc_rel_error | v(nor) | 4.863e-07 | ≤ 0.005 | PASS | +| dc_rel_error | v(nscr) | 9.186e-15 | ≤ 0.005 | PASS | +| dc_rel_error | v(out) | 7.715e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(p3a) | 1.223e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(p3b) | 1.353e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(pa) | 3.49e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(pa1) | 3.618e-11 | ≤ 0.005 | PASS | +| dc_rel_error | v(pb) | 3.756e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(pb1) | 2.991e-11 | ≤ 0.005 | PASS | +| dc_rel_error | v(s4) | 1.558e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(s6) | 1.559e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(sl) | 1.079e-06 | ≤ 0.005 | PASS | +| dc_rel_error | v(y) | 1.287e-07 | ≤ 0.005 | PASS | +| nonlinear_esr_db | SIN-100Hz-0.1V | -91.75 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-0.1V | 0.001428 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-0.1V | 1.72e-06 pp | — | INFO | +| total_power_rel | SIN-100Hz-0.1V | 5.011e-05 | ≤ 0.05 | INFO | +| newton_failures | SIN-100Hz-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-0.1V | 2 | ≤ 48 | PASS | +| nonlinear_esr_db | SIN-1000Hz-0.1V | -80.96 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-0.1V | 0.002869 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-0.1V | 2.06e-05 pp | — | INFO | +| total_power_rel | SIN-1000Hz-0.1V | 0.0001214 | ≤ 0.05 | INFO | +| newton_failures | SIN-1000Hz-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-0.1V | 2 | ≤ 48 | PASS | +| nonlinear_esr_db | SIN-5000Hz-0.1V | -69.65 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-0.1V | 0.003285 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-0.1V | 1.375e-05 pp | — | INFO | +| total_power_rel | SIN-5000Hz-0.1V | 0.0005555 | ≤ 0.05 | INFO | +| newton_failures | SIN-5000Hz-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-0.1V | 2 | ≤ 48 | PASS | +| nonlinear_esr_db | SIN-100Hz-1V | -91.73 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-1V | 0.001392 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-1V | 1.04e-05 pp | — | INFO | +| total_power_rel | SIN-100Hz-1V | 5.01e-05 | ≤ 0.05 | INFO | +| newton_failures | SIN-100Hz-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-1V | 2 | ≤ 48 | PASS | +| nonlinear_esr_db | SIN-1000Hz-1V | -80.58 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-1V | 0.002813 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-1V | 0.000276 pp | — | INFO | +| total_power_rel | SIN-1000Hz-1V | 0.0001334 | ≤ 0.05 | INFO | +| newton_failures | SIN-1000Hz-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-1V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-1V | 2 | ≤ 48 | PASS | +| nonlinear_esr_db | SIN-5000Hz-1V | -69.55 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-1V | 0.01431 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-1V | 9.148e-06 pp | — | INFO | +| total_power_rel | SIN-5000Hz-1V | 0.0005667 | ≤ 0.05 | INFO | +| newton_failures | SIN-5000Hz-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-1V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-1V | 3 | ≤ 48 | PASS | +| nonlinear_esr_db | SIN-100Hz-5V | -94.19 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-5V | 0.001922 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-5V | 0.0003959 pp | — | INFO | +| total_power_rel | SIN-100Hz-5V | 3.615e-05 | ≤ 0.05 | INFO | +| newton_failures | SIN-100Hz-5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-5V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-5V | 2 | ≤ 48 | PASS | +| nonlinear_esr_db | SIN-1000Hz-5V | -78.07 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-5V | 0.003837 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-5V | 0.001507 pp | — | INFO | +| total_power_rel | SIN-1000Hz-5V | 2.732e-05 | ≤ 0.05 | INFO | +| newton_failures | SIN-1000Hz-5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-5V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-5V | 5 | ≤ 48 | PASS | +| nonlinear_esr_db | SIN-5000Hz-5V | -68.63 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-5V | 0.02699 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-5V | 0.002248 pp | — | INFO | +| total_power_rel | SIN-5000Hz-5V | 0.0001378 | ≤ 0.05 | INFO | +| newton_failures | SIN-5000Hz-5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-5V | 3 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-5V | 6 | ≤ 48 | PASS | +| nonlinear_esr_db_overdrive | SIN-100Hz-20V | -84.66 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-20V | 0.004908 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-20V | 1.303e-05 pp | — | INFO | +| total_power_rel | SIN-100Hz-20V | 3.482e-05 | ≤ 0.05 | INFO | +| newton_failures | SIN-100Hz-20V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-20V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-20V | 6 | ≤ 48 | PASS | +| nonlinear_esr_db_overdrive | SIN-1000Hz-20V | -70.1 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-20V | 0.03295 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-20V | 0.0004306 pp | — | INFO | +| total_power_rel | SIN-1000Hz-20V | 4.339e-05 | ≤ 0.05 | INFO | +| newton_failures | SIN-1000Hz-20V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-20V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-20V | 6 | ≤ 48 | PASS | +| nonlinear_esr_db_overdrive | SIN-5000Hz-20V | -50.21 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-20V | 0.01262 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-20V | 0.02265 pp | — | INFO | +| total_power_rel | SIN-5000Hz-20V | 0.000384 | ≤ 0.05 | INFO | +| newton_failures | SIN-5000Hz-20V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-20V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-20V | 7 | ≤ 48 | PASS | +| nonlinear_esr_db | SWP-lo-0.1V | -63.49 dB | ≤ -20 | PASS | +| linear_mag_db_20_10k | SWP-lo-0.1V | 0.01309 dB | ≤ 0.1 | INFO | +| linear_mag_db_10k_20k | SWP-lo-0.1V | 0.004612 dB | ≤ 0.3 | INFO | +| method_mag_db_spice_tran_vs_ac | SWP-lo-0.1V | 0.02452 dB | — | INFO | +| newton_failures | SWP-lo-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SWP-lo-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SWP-lo-0.1V | 2 | ≤ 48 | PASS | +| nonlinear_esr_db | SWP-hi-1V | -63.43 dB | ≤ -20 | PASS | +| newton_failures | SWP-hi-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SWP-hi-1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SWP-hi-1V | 3 | ≤ 48 | PASS | +| nonlinear_esr_db | TT-0.5V | -86.26 dB | ≤ -20 | PASS | +| newton_failures | TT-0.5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | TT-0.5V | 1 | ≤ 3 | PASS | +| newton_max_iterations | TT-0.5V | 2 | ≤ 48 | PASS | +| nonlinear_esr_db | BURST-5V | -78.02 dB | ≤ -20 | PASS | +| newton_failures | BURST-5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | BURST-5V | 1 | ≤ 3 | PASS | +| newton_max_iterations | BURST-5V | 5 | ≤ 48 | PASS | +| nonlinear_esr_db | STEP-1V | -79.13 dB | ≤ -20 | PASS | +| newton_failures | STEP-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | STEP-1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | STEP-1V | 2 | ≤ 48 | PASS | + +## Signals + +| Signal | Peak F [V] | ESR [dB] | Best lag | ESR at lag [dB] | THD F / E [%] | Power F / E [W] | ns/sample | Newton fail / median / max it | +|---|---|---|---|---|---|---|---|---| +| SIN-100Hz-0.1V | 1.9 | -91.75 | 0 | -91.75 | 0.0126 / 0.0126 | 0.1087 / 0.1087 | 952.7 | 0 / 1 / 2 | +| SIN-1000Hz-0.1V | 1.309 | -80.96 | 0 | -80.96 | 0.0852 / 0.0852 | 0.04968 / 0.04969 | 951.7 | 0 / 1 / 2 | +| SIN-5000Hz-0.1V | 1.982 | -69.65 | 0 | -69.65 | 0.0359 / 0.0359 | 0.12 / 0.1201 | 957.5 | 0 / 1 / 2 | +| SIN-100Hz-1V | 19.26 | -91.73 | 0 | -91.73 | 0.19 / 0.19 | 10.94 / 10.94 | 950.4 | 0 / 1 / 2 | +| SIN-1000Hz-1V | 14.04 | -80.58 | 0 | -80.58 | 2.88 / 2.88 | 5.416 / 5.417 | 1308.6 | 0 / 2 / 2 | +| SIN-5000Hz-1V | 20.15 | -69.55 | 0 | -69.55 | 0.881 / 0.881 | 12.28 / 12.28 | 1408.4 | 0 / 2 / 3 | +| SIN-100Hz-5V | 84.02 | -94.19 | 0 | -94.19 | 3.68 / 3.69 | 229.8 / 229.8 | 1080.0 | 0 / 1 / 2 | +| SIN-1000Hz-5V | 69.71 | -78.07 | 0 | -78.07 | 13.3 / 13.3 | 109.3 / 109.3 | 1420.6 | 0 / 2 / 5 | +| SIN-5000Hz-5V | 81.96 | -68.63 | 0 | -68.63 | 12.8 / 12.8 | 222.1 / 222.1 | 1758.5 | 0 / 3 / 6 | +| SIN-100Hz-20V | 86.97 | -84.66 | 0 | -84.66 | 23 / 23 | 273.2 / 273.2 | 1042.9 | 0 / 1 / 6 | +| SIN-1000Hz-20V | 79.87 | -70.10 | 0 | -70.10 | 57.5 / 57.5 | 146.2 / 146.2 | 1243.0 | 0 / 2 / 6 | +| SIN-5000Hz-20V | 127.7 | -50.21 | 0 | -50.21 | 45.1 / 45 | 298 / 297.9 | 1850.7 | 0 / 2 / 7 | +| SWP-lo-0.1V | 2.143 | -63.49 | 0 | -63.49 | — | — | 982.0 | 0 / 1 / 2 | +| SWP-hi-1V | 21.65 | -63.43 | 0 | -63.43 | — | — | 1172.8 | 0 / 1 / 3 | +| TT-0.5V | 13.52 | -86.26 | 0 | -86.26 | — | — | 969.6 | 0 / 1 / 2 | +| BURST-5V | 69.64 | -78.02 | 0 | -78.02 | — | — | 1169.0 | 0 / 1 / 5 | +| STEP-1V | 23.89 | -79.13 | 0 | -79.13 | — | — | 963.2 | 0 / 1 / 2 | + +## DC operating point + +| Node | SPICE [V] | Engine [V] | Rel. error | +|---|---|---|---| +| bpa | 391.9215 | — | — | +| bpx | 404.9982 | 404.9982 | 6.632e-13 | +| c20 | -60.0000 | — | — | +| ct | 491.3466 | 491.3466 | 3.008e-15 | +| g3a | 43.8295 | 43.8284 | 2.529e-05 | +| g3b | 43.8295 | 43.8284 | 2.529e-05 | +| g4 | -46.3631 | -46.3631 | 4.919e-07 | +| g5 | -46.3631 | -46.3631 | 4.919e-07 | +| g6 | -46.3631 | -46.3631 | 4.919e-07 | +| g7 | -46.3631 | -46.3631 | 4.919e-07 | +| k3 | 45.2502 | 45.2491 | 2.526e-05 | +| nbias | -46.3636 | -46.3636 | 4.636e-08 | +| nch | 491.3466 | — | — | +| nf | 43.8285 | 43.8274 | 2.526e-05 | +| ng | 13.5790 | 13.5787 | 2.526e-05 | +| ngn | -46.3631 | -46.3631 | 4.863e-07 | +| nor | -46.3631 | -46.3631 | 4.863e-07 | +| npr | 0.0000 | — | — | +| nrv | -3.6364 | — | — | +| nscr | 488.8805 | 488.8805 | 9.186e-15 | +| out | 0.0000 | 0.0000 | 7.715e-08 | +| p3a | 272.6113 | 272.6146 | 1.223e-05 | +| p3b | 263.9512 | 263.9548 | 1.353e-05 | +| pa | 490.4628 | 490.4628 | 3.49e-08 | +| pa1 | 491.3466 | 491.3466 | 3.618e-11 | +| pb | 490.3937 | 490.3937 | 3.756e-08 | +| pb1 | 491.3466 | 491.3466 | 2.991e-11 | +| raw | 505.0000 | — | — | +| s4 | 485.6164 | 485.6164 | 1.558e-08 | +| s5 | 485.6164 | — | — | +| s6 | 485.6162 | 485.6162 | 1.559e-08 | +| s7 | 485.6162 | — | — | +| sl | 0.0000 | 0.0000 | 1.079e-06 | +| spk1 | -0.0000 | — | — | +| spk2 | -0.0000 | — | — | +| y | 0.0000 | 0.0000 | 1.287e-07 | + +Engine values are the probe values after the first sample (zero input) following `reset()`. Nodes the engine does not probe yet show —. + +## Figures + +### Waveform overlay and error (most demanding signal) + +![Waveform overlay and error (most demanding signal)](20261019_b56_DEFAULT/waveform.png) + +### Harmonics + +![Harmonics](20261019_b56_DEFAULT/harmonics.png) + +### Magnitude response + +![Magnitude response](20261019_b56_DEFAULT/magnitude.png) diff --git a/harness/reports/M4/20261019_b56_DEFAULT/harmonics.png b/harness/reports/M4/20261019_b56_DEFAULT/harmonics.png new file mode 100644 index 0000000..3001fd5 Binary files /dev/null and b/harness/reports/M4/20261019_b56_DEFAULT/harmonics.png differ diff --git a/harness/reports/M4/20261019_b56_DEFAULT/magnitude.png b/harness/reports/M4/20261019_b56_DEFAULT/magnitude.png new file mode 100644 index 0000000..6ec5dc7 Binary files /dev/null and b/harness/reports/M4/20261019_b56_DEFAULT/magnitude.png differ diff --git a/harness/reports/M4/20261019_b56_DEFAULT/waveform.png b/harness/reports/M4/20261019_b56_DEFAULT/waveform.png new file mode 100644 index 0000000..8a92a71 Binary files /dev/null and b/harness/reports/M4/20261019_b56_DEFAULT/waveform.png differ diff --git a/harness/reports/M4/20261019_b5open_DEFAULT.md b/harness/reports/M4/20261019_b5open_DEFAULT.md new file mode 100644 index 0000000..7a130a4 --- /dev/null +++ b/harness/reports/M4/20261019_b5open_DEFAULT.md @@ -0,0 +1,166 @@ +# M4 report: b5open / DEFAULT + +- Block: B5-open phase inverter, R22 driven by recorded speaker voltage +- Preset: DEFAULT — Default knobs; standard signal set at the block levels +- Knobs: preampVolume=0.6, treble=0.5, middle=0.5, bass=0.5, master=0.3, presence=0.5 +- Date: 2026-10-19 +- Engine commit: `ecf9042 (dirty)` +- Testbench: `harness/testbenches/b5open.cir` sha256 `cad9e52179f41965`; netlist sha256 `ffd9b2812204248f`; tubes.lib sha256 `c9c9c88346c69010` +- ngspice 47, fs = 768000 Hz, SPICE reltol 1e-08 + +> SWP-lo-0.1V b56: source run rendered at RELTOL 1e-07 (timestep too small at 1e-08) + +## Summary + +PASS 88 · FAIL 0 · INFO 12 (INFO = measured, not scored in M4) + +| Check | Item | Measured | Tolerance | Status | +|---|---|---|---|---| +| dc_rel_error | v(g3a) | 2.529e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(g3b) | 2.529e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(k3) | 2.526e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(nf) | 2.526e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(ng) | 2.526e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(p3a) | 1.223e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(p3b) | 1.353e-05 | ≤ 0.005 | PASS | +| dc_rel_error | v(y) | 3.553e-12 | ≤ 0.005 | PASS | +| nonlinear_esr_db | SIN-100Hz-0.1V | -84.14 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-0.1V | 0.0004298 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-0.1V | 3.505e-07 pp | — | INFO | +| newton_failures | SIN-100Hz-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-0.1V | 1 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-1000Hz-0.1V | -80.61 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-0.1V | 0.0009776 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-0.1V | 7.268e-07 pp | — | INFO | +| newton_failures | SIN-1000Hz-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-0.1V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-5000Hz-0.1V | -68.37 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-0.1V | 0.01189 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-0.1V | 3.275e-05 pp | — | INFO | +| newton_failures | SIN-5000Hz-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-0.1V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-100Hz-1V | -84.13 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-1V | 0.0004838 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-1V | 3.291e-06 pp | — | INFO | +| newton_failures | SIN-100Hz-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-1V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-1000Hz-1V | -80.59 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-1V | 0.001843 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-1V | 8.535e-05 pp | — | INFO | +| newton_failures | SIN-1000Hz-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-1V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-5000Hz-1V | -68.4 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-1V | 0.03292 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-1V | 0.001451 pp | — | INFO | +| newton_failures | SIN-5000Hz-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-1V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-100Hz-5V | -83.88 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-5V | 0.000628 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-5V | 3.143e-05 pp | — | INFO | +| newton_failures | SIN-100Hz-5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-5V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-5V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-1000Hz-5V | -79.54 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-5V | 0.01625 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-5V | 0.001227 pp | — | INFO | +| newton_failures | SIN-1000Hz-5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-5V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-5V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-5000Hz-5V | -66.53 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-5V | 0.02992 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-5V | 0.02042 pp | — | INFO | +| newton_failures | SIN-5000Hz-5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-5V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-5V | 3 | ≤ 12 | PASS | +| nonlinear_esr_db_overdrive | SIN-100Hz-20V | -75.04 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-20V | 0.003229 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-20V | 0.002833 pp | — | INFO | +| newton_failures | SIN-100Hz-20V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-20V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-20V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db_overdrive | SIN-1000Hz-20V | -73.74 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-20V | 0.002388 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-20V | 0.002133 pp | — | INFO | +| newton_failures | SIN-1000Hz-20V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-20V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-20V | 3 | ≤ 12 | PASS | +| nonlinear_esr_db_overdrive | SIN-5000Hz-20V | -61.76 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-20V | 0.03531 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-20V | 0.01606 pp | — | INFO | +| newton_failures | SIN-5000Hz-20V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-20V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-20V | 4 | ≤ 12 | PASS | +| nonlinear_esr_db | SWP-lo-0.1V | -69.19 dB | ≤ -20 | PASS | +| newton_failures | SWP-lo-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SWP-lo-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SWP-lo-0.1V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | SWP-hi-1V | -69.04 dB | ≤ -20 | PASS | +| newton_failures | SWP-hi-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SWP-hi-1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SWP-hi-1V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | TT-0.5V | -83.65 dB | ≤ -20 | PASS | +| newton_failures | TT-0.5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | TT-0.5V | 1 | ≤ 3 | PASS | +| newton_max_iterations | TT-0.5V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | BURST-5V | -79.75 dB | ≤ -20 | PASS | +| newton_failures | BURST-5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | BURST-5V | 1 | ≤ 3 | PASS | +| newton_max_iterations | BURST-5V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | STEP-1V | -82.54 dB | ≤ -20 | PASS | +| newton_failures | STEP-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | STEP-1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | STEP-1V | 2 | ≤ 12 | PASS | + +## Signals + +| Signal | Peak F [V] | ESR [dB] | Best lag | ESR at lag [dB] | THD F / E [%] | Power F / E [W] | ns/sample | Newton fail / median / max it | +|---|---|---|---|---|---|---|---|---| +| SIN-100Hz-0.1V | 1.309 | -84.14 | 0 | -84.14 | 0.0292 / 0.0292 | — | 250.5 | 0 / 1 / 1 | +| SIN-1000Hz-0.1V | 1.642 | -80.61 | 0 | -80.61 | 0.0599 / 0.0599 | — | 303.0 | 0 / 1 / 2 | +| SIN-5000Hz-0.1V | 1.172 | -68.37 | 0 | -68.37 | 0.0573 / 0.0573 | — | 301.2 | 0 / 1 / 2 | +| SIN-100Hz-1V | 12.67 | -84.13 | 0 | -84.13 | 0.428 / 0.428 | — | 299.7 | 0 / 1 / 2 | +| SIN-1000Hz-1V | 16.11 | -80.59 | 0 | -80.59 | 2.09 / 2.09 | — | 302.4 | 0 / 1 / 2 | +| SIN-5000Hz-1V | 11.1 | -68.40 | 0 | -68.40 | 1.4 / 1.4 | — | 298.2 | 0 / 1 / 2 | +| SIN-100Hz-5V | 56.7 | -83.88 | 0 | -83.88 | 6.05 / 6.05 | — | 297.5 | 0 / 1 / 2 | +| SIN-1000Hz-5V | 97.08 | -79.54 | 0 | -79.54 | 7.71 / 7.71 | — | 411.6 | 0 / 2 / 2 | +| SIN-5000Hz-5V | 77.43 | -66.53 | 0 | -66.53 | 13.6 / 13.6 | — | 446.8 | 0 / 2 / 3 | +| SIN-100Hz-20V | 132.2 | -75.04 | 0 | -75.04 | 47.4 / 47.4 | — | 283.4 | 0 / 1 / 2 | +| SIN-1000Hz-20V | 140.8 | -73.74 | 0 | -73.74 | 50.7 / 50.7 | — | 361.9 | 0 / 1 / 3 | +| SIN-5000Hz-20V | 142 | -61.76 | 0 | -61.76 | 53.4 / 53.4 | — | 434.2 | 0 / 2 / 4 | +| SWP-lo-0.1V | 2.693 | -69.19 | 0 | -69.19 | — | — | 276.5 | 0 / 1 / 2 | +| SWP-hi-1V | 26.97 | -69.04 | 0 | -69.04 | — | — | 312.8 | 0 / 1 / 2 | +| TT-0.5V | 14.38 | -83.65 | 0 | -83.65 | — | — | 296.2 | 0 / 1 / 2 | +| BURST-5V | 102.3 | -79.75 | 0 | -79.75 | — | — | 361.8 | 0 / 1 / 2 | +| STEP-1V | 19.85 | -82.54 | 0 | -82.54 | — | — | 292.5 | 0 / 1 / 2 | + +## DC operating point + +| Node | SPICE [V] | Engine [V] | Rel. error | +|---|---|---|---| +| g3a | 43.8295 | 43.8284 | 2.529e-05 | +| g3b | 43.8295 | 43.8284 | 2.529e-05 | +| k3 | 45.2502 | 45.2491 | 2.526e-05 | +| nf | 43.8285 | 43.8274 | 2.526e-05 | +| ng | 13.5790 | 13.5787 | 2.526e-05 | +| npr | 0.0000 | — | — | +| p3a | 272.6113 | 272.6146 | 1.223e-05 | +| p3b | 263.9512 | 263.9548 | 1.353e-05 | +| y | 0.0000 | -0.0000 | 3.553e-12 | + +Engine values are the probe values after the first sample (zero input) following `reset()`. Nodes the engine does not probe yet show —. + +## Figures + +### Waveform overlay and error (most demanding signal) + +![Waveform overlay and error (most demanding signal)](20261019_b5open_DEFAULT/waveform.png) + +### Harmonics + +![Harmonics](20261019_b5open_DEFAULT/harmonics.png) diff --git a/harness/reports/M4/20261019_b5open_DEFAULT/harmonics.png b/harness/reports/M4/20261019_b5open_DEFAULT/harmonics.png new file mode 100644 index 0000000..24a8550 Binary files /dev/null and b/harness/reports/M4/20261019_b5open_DEFAULT/harmonics.png differ diff --git a/harness/reports/M4/20261019_b5open_DEFAULT/waveform.png b/harness/reports/M4/20261019_b5open_DEFAULT/waveform.png new file mode 100644 index 0000000..15ee21b Binary files /dev/null and b/harness/reports/M4/20261019_b5open_DEFAULT/waveform.png differ diff --git a/harness/reports/M4/20261019_b6_DEFAULT.md b/harness/reports/M4/20261019_b6_DEFAULT.md new file mode 100644 index 0000000..2218883 --- /dev/null +++ b/harness/reports/M4/20261019_b6_DEFAULT.md @@ -0,0 +1,202 @@ +# M4 report: b6 / DEFAULT + +- Block: B6 PowerStage, driven by recorded PI outputs +- Preset: DEFAULT — Default knobs; standard signal set at the block levels +- Knobs: preampVolume=0.6, treble=0.5, middle=0.5, bass=0.5, master=0.3, presence=0.5 +- Date: 2026-10-19 +- Engine commit: `ecf9042 (dirty)` +- Testbench: `harness/testbenches/b6.cir` sha256 `65ebb460f9cac218`; netlist sha256 `ffd9b2812204248f`; tubes.lib sha256 `c9c9c88346c69010` +- ngspice 47, fs = 768000 Hz, SPICE reltol 1e-08 + +> SWP-lo-0.1V b56: source run rendered at RELTOL 1e-07 (timestep too small at 1e-08) +> SWP-lo-0.1V rendered at RELTOL 1e-07 (ngspice: timestep too small at 1e-08) + +## Summary + +PASS 97 · FAIL 0 · INFO 24 (INFO = measured, not scored in M4) + +| Check | Item | Measured | Tolerance | Status | +|---|---|---|---|---| +| dc_rel_error | v(g4) | 4.919e-07 | ≤ 0.005 | PASS | +| dc_rel_error | v(g5) | 4.919e-07 | ≤ 0.005 | PASS | +| dc_rel_error | v(g6) | 4.919e-07 | ≤ 0.005 | PASS | +| dc_rel_error | v(g7) | 4.919e-07 | ≤ 0.005 | PASS | +| dc_rel_error | v(nbias) | 4.636e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(ngn) | 4.863e-07 | ≤ 0.005 | PASS | +| dc_rel_error | v(nor) | 4.863e-07 | ≤ 0.005 | PASS | +| dc_rel_error | v(out) | 3.067e-09 | ≤ 0.005 | PASS | +| dc_rel_error | v(p3a) | 1.345e-09 | ≤ 0.005 | PASS | +| dc_rel_error | v(p3b) | 7.844e-10 | ≤ 0.005 | PASS | +| dc_rel_error | v(pa) | 3.492e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(pa1) | 4.634e-11 | ≤ 0.005 | PASS | +| dc_rel_error | v(pb) | 3.764e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(pb1) | 4.633e-11 | ≤ 0.005 | PASS | +| dc_rel_error | v(s4) | 1.509e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(s6) | 1.51e-08 | ≤ 0.005 | PASS | +| dc_rel_error | v(sl) | 1.729e-11 | ≤ 0.005 | PASS | +| nonlinear_esr_db | SIN-100Hz-0.1V | -93.97 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-0.1V | 0.0001923 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-0.1V | 2.066e-07 pp | — | INFO | +| total_power_rel | SIN-100Hz-0.1V | 3.047e-05 | ≤ 0.05 | INFO | +| newton_failures | SIN-100Hz-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-0.1V | 1 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-1000Hz-0.1V | -80.69 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-0.1V | 0.0005399 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-0.1V | 8.409e-06 pp | — | INFO | +| total_power_rel | SIN-1000Hz-0.1V | 3.543e-05 | ≤ 0.05 | INFO | +| newton_failures | SIN-1000Hz-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-0.1V | 1 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-5000Hz-0.1V | -66.19 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-0.1V | 0.004325 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-0.1V | 2.394e-05 pp | — | INFO | +| total_power_rel | SIN-5000Hz-0.1V | 0.0006316 | ≤ 0.05 | INFO | +| newton_failures | SIN-5000Hz-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-0.1V | 1 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-100Hz-1V | -93.84 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-1V | 0.0004289 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-1V | 2.908e-06 pp | — | INFO | +| total_power_rel | SIN-100Hz-1V | 3.114e-05 | ≤ 0.05 | INFO | +| newton_failures | SIN-100Hz-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-1V | 1 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-1000Hz-1V | -80.25 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-1V | 0.004216 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-1V | 0.0001549 pp | — | INFO | +| total_power_rel | SIN-1000Hz-1V | 4.199e-05 | ≤ 0.05 | INFO | +| newton_failures | SIN-1000Hz-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-1V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-1V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-5000Hz-1V | -65.87 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-1V | 0.01796 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-1V | 7.348e-05 pp | — | INFO | +| total_power_rel | SIN-5000Hz-1V | 0.0006695 | ≤ 0.05 | INFO | +| newton_failures | SIN-5000Hz-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-1V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-1V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-100Hz-5V | -96.77 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-5V | 0.0004405 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-5V | 9.023e-05 pp | — | INFO | +| total_power_rel | SIN-100Hz-5V | 1.316e-05 | ≤ 0.05 | INFO | +| newton_failures | SIN-100Hz-5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-5V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-5V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-1000Hz-5V | -72.5 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-5V | 0.005786 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-5V | 0.003782 pp | — | INFO | +| total_power_rel | SIN-1000Hz-5V | 2.637e-05 | ≤ 0.05 | INFO | +| newton_failures | SIN-1000Hz-5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-5V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-5V | 5 | ≤ 12 | PASS | +| nonlinear_esr_db | SIN-5000Hz-5V | -61.44 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-5V | 0.02523 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-5V | 0.02407 pp | — | INFO | +| total_power_rel | SIN-5000Hz-5V | 0.0003556 | ≤ 0.05 | INFO | +| newton_failures | SIN-5000Hz-5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-5V | 3 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-5V | 5 | ≤ 12 | PASS | +| nonlinear_esr_db_overdrive | SIN-100Hz-20V | -92.04 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-100Hz-20V | 0.0007905 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-100Hz-20V | 5.421e-05 pp | — | INFO | +| total_power_rel | SIN-100Hz-20V | 9.873e-07 | ≤ 0.05 | INFO | +| newton_failures | SIN-100Hz-20V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-100Hz-20V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SIN-100Hz-20V | 5 | ≤ 12 | PASS | +| nonlinear_esr_db_overdrive | SIN-1000Hz-20V | -66.93 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-1000Hz-20V | 0.002626 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-1000Hz-20V | 0.001563 pp | — | INFO | +| total_power_rel | SIN-1000Hz-20V | 2.063e-05 | ≤ 0.05 | INFO | +| newton_failures | SIN-1000Hz-20V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-1000Hz-20V | 2 | ≤ 3 | PASS | +| newton_max_iterations | SIN-1000Hz-20V | 5 | ≤ 12 | PASS | +| nonlinear_esr_db_overdrive | SIN-5000Hz-20V | -44.21 dB | ≤ -20 | PASS | +| harmonics_2_5_db | SIN-5000Hz-20V | 0.05041 dB | ≤ 0.5 | PASS | +| thd_pp | SIN-5000Hz-20V | 0.06374 pp | — | INFO | +| total_power_rel | SIN-5000Hz-20V | 0.0004657 | ≤ 0.05 | INFO | +| newton_failures | SIN-5000Hz-20V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SIN-5000Hz-20V | 3 | ≤ 3 | PASS | +| newton_max_iterations | SIN-5000Hz-20V | 7 | ≤ 12 | PASS | +| nonlinear_esr_db | SWP-lo-0.1V | -62.33 dB | ≤ -20 | PASS | +| newton_failures | SWP-lo-0.1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SWP-lo-0.1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SWP-lo-0.1V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | SWP-hi-1V | -62.11 dB | ≤ -20 | PASS | +| newton_failures | SWP-hi-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | SWP-hi-1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | SWP-hi-1V | 2 | ≤ 12 | PASS | +| nonlinear_esr_db | TT-0.5V | -91.95 dB | ≤ -20 | PASS | +| newton_failures | TT-0.5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | TT-0.5V | 1 | ≤ 3 | PASS | +| newton_max_iterations | TT-0.5V | 1 | ≤ 12 | PASS | +| nonlinear_esr_db | BURST-5V | -72.49 dB | ≤ -20 | PASS | +| newton_failures | BURST-5V | 0 | ≤ 0 | PASS | +| newton_median_iterations | BURST-5V | 1 | ≤ 3 | PASS | +| newton_max_iterations | BURST-5V | 5 | ≤ 12 | PASS | +| nonlinear_esr_db | STEP-1V | -79.95 dB | ≤ -20 | PASS | +| newton_failures | STEP-1V | 0 | ≤ 0 | PASS | +| newton_median_iterations | STEP-1V | 1 | ≤ 3 | PASS | +| newton_max_iterations | STEP-1V | 2 | ≤ 12 | PASS | + +## Signals + +| Signal | Peak F [V] | ESR [dB] | Best lag | ESR at lag [dB] | THD F / E [%] | Power F / E [W] | ns/sample | Newton fail / median / max it | +|---|---|---|---|---|---|---|---|---| +| SIN-100Hz-0.1V | 1.725 | -93.97 | 0 | -93.97 | 0.03 / 0.03 | 0.08881 / 0.08881 | 461.2 | 0 / 1 / 1 | +| SIN-1000Hz-0.1V | 1.171 | -80.69 | 0 | -80.69 | 0.194 / 0.194 | 0.03997 / 0.03997 | 487.7 | 0 / 1 / 1 | +| SIN-5000Hz-0.1V | 1.777 | -66.19 | 0 | -66.19 | 0.13 / 0.13 | 0.09674 / 0.0968 | 492.0 | 0 / 1 / 1 | +| SIN-100Hz-1V | 17.31 | -93.84 | 0 | -93.84 | 0.308 / 0.308 | 8.87 / 8.87 | 470.4 | 0 / 1 / 1 | +| SIN-1000Hz-1V | 12.53 | -80.25 | 0 | -80.25 | 2.58 / 2.58 | 4.184 / 4.184 | 621.8 | 0 / 2 / 2 | +| SIN-5000Hz-1V | 18.07 | -65.87 | 0 | -65.87 | 1.27 / 1.27 | 9.717 / 9.723 | 662.4 | 0 / 2 / 2 | +| SIN-100Hz-5V | 82.21 | -96.77 | 0 | -96.77 | 2.37 / 2.37 | 211.1 / 211.1 | 500.8 | 0 / 1 / 2 | +| SIN-1000Hz-5V | 69.47 | -72.50 | 0 | -72.50 | 12 / 12 | 100.7 / 100.7 | 647.7 | 0 / 2 / 5 | +| SIN-5000Hz-5V | 81 | -61.44 | 0 | -61.44 | 11.2 / 11.2 | 213.2 / 213.1 | 827.6 | 0 / 3 / 5 | +| SIN-100Hz-20V | 86.47 | -92.04 | 0 | -92.04 | 21.2 / 21.2 | 251.6 / 251.6 | 510.8 | 0 / 1 / 5 | +| SIN-1000Hz-20V | 106.1 | -66.93 | 0 | -66.93 | 58.7 / 58.7 | 137.5 / 137.5 | 597.2 | 0 / 2 / 5 | +| SIN-5000Hz-20V | 144.7 | -44.21 | 0 | -44.21 | 46.1 / 46.1 | 286.2 / 286.1 | 821.4 | 0 / 3 / 7 | +| SWP-lo-0.1V | 1.865 | -62.33 | 0 | -62.33 | — | — | 480.0 | 0 / 1 / 2 | +| SWP-hi-1V | 18.83 | -62.11 | 0 | -62.11 | — | — | 565.7 | 0 / 1 / 2 | +| TT-0.5V | 11.82 | -91.95 | 0 | -91.95 | — | — | 483.7 | 0 / 1 / 1 | +| BURST-5V | 69.4 | -72.49 | 0 | -72.49 | — | — | 560.2 | 0 / 1 / 5 | +| STEP-1V | 20.76 | -79.95 | 0 | -79.95 | — | — | 480.2 | 0 / 1 / 2 | + +## DC operating point + +| Node | SPICE [V] | Engine [V] | Rel. error | +|---|---|---|---| +| c20 | -60.0000 | — | — | +| g4 | -46.3631 | -46.3631 | 4.919e-07 | +| g5 | -46.3631 | -46.3631 | 4.919e-07 | +| g6 | -46.3631 | -46.3631 | 4.919e-07 | +| g7 | -46.3631 | -46.3631 | 4.919e-07 | +| nbias | -46.3636 | -46.3636 | 4.636e-08 | +| ngn | -46.3631 | -46.3631 | 4.863e-07 | +| nor | -46.3631 | -46.3631 | 4.863e-07 | +| nrv | -3.6364 | — | — | +| out | 0.0000 | -0.0000 | 3.067e-09 | +| p3a | 272.6113 | 272.6113 | 1.345e-09 | +| p3b | 263.9512 | 263.9512 | 7.844e-10 | +| pa | 490.4628 | 490.4628 | 3.492e-08 | +| pa1 | 491.3466 | 491.3466 | 4.634e-11 | +| pb | 490.3937 | 490.3937 | 3.764e-08 | +| pb1 | 491.3466 | 491.3466 | 4.633e-11 | +| s4 | 485.6164 | 485.6164 | 1.509e-08 | +| s5 | 485.6164 | — | — | +| s6 | 485.6162 | 485.6162 | 1.51e-08 | +| s7 | 485.6162 | — | — | +| sl | 0.0000 | 0.0000 | 1.729e-11 | +| spk1 | 0.0000 | — | — | +| spk2 | 0.0000 | — | — | + +Engine values are the probe values after the first sample (zero input) following `reset()`. Nodes the engine does not probe yet show —. + +## Figures + +### Waveform overlay and error (most demanding signal) + +![Waveform overlay and error (most demanding signal)](20261019_b6_DEFAULT/waveform.png) + +### Harmonics + +![Harmonics](20261019_b6_DEFAULT/harmonics.png) diff --git a/harness/reports/M4/20261019_b6_DEFAULT/harmonics.png b/harness/reports/M4/20261019_b6_DEFAULT/harmonics.png new file mode 100644 index 0000000..e01db91 Binary files /dev/null and b/harness/reports/M4/20261019_b6_DEFAULT/harmonics.png differ diff --git a/harness/reports/M4/20261019_b6_DEFAULT/waveform.png b/harness/reports/M4/20261019_b6_DEFAULT/waveform.png new file mode 100644 index 0000000..57cf92a Binary files /dev/null and b/harness/reports/M4/20261019_b6_DEFAULT/waveform.png differ diff --git a/harness/reports/M4/20261019_full_acceptance.md b/harness/reports/M4/20261019_full_acceptance.md new file mode 100644 index 0000000..c32a711 --- /dev/null +++ b/harness/reports/M4/20261019_full_acceptance.md @@ -0,0 +1,197 @@ +# M4 acceptance: full amplifier vs the full netlist + +- Date: 2026-10-19, engine commit `ecf9042`, ngspice 47, fs = 768000 Hz, SPICE reltol 1e-08 +- Netlist sha256 `ffd9b2812204248f`, tubes.lib sha256 `c9c9c88346c69010` +- Engine: `ampwav --block full` (volts at the jack and across the 16 Ω load), the checks of SPEC §7 M4. + +## Summary + +PASS 107 · FAIL 0 + +| Section | Check | Item | Measured | Tolerance | Status | +|---|---|---|---|---|---| +| dc | dc_rel_error | v(ct) | 4.176e-07 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(nscr) | 4.492e-07 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(bpx) | 3.649e-06 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(bpa) | 5.679e-06 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(nbias) | 4.636e-08 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(p1a) | 1.994e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(k1a) | 3.976e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(p1b) | 1.686e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(k1b) | 0.0001011 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(p2a) | 2.195e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(k2a) | 2.104e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(k2b) | 2.14e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(p3a) | 1.576e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(p3b) | 1.707e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(k3) | 2.137e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(nf) | 2.137e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(ng) | 2.137e-05 | ≤ 0.005 | PASS | +| dc | el34_plate_ma | ip_tube_a | 8.688e-06 mA | ≤ 1 | PASS | +| dc | el34_plate_ma | ip_tube_b | 8.695e-06 mA | ≤ 1 | PASS | +| dc | el34_screen_ma | is_tube_a | 1.951e-07 mA | ≤ 0.3 | PASS | +| dc | el34_screen_ma | is_tube_b | 1.918e-07 mA | ≤ 0.3 | PASS | +| dc | ct_rel | v(ct) | 4.176e-07 | ≤ 0.01 | PASS | +| dc | nbias_v | v(nbias) | 2.15e-06 V | ≤ 0.2 | PASS | +| nfb | nfb_gain_db | closed 100 Hz | 0.001766 dB | ≤ 0.5 | PASS | +| nfb | newton_failures | closed 100 Hz | 0 | ≤ 0 | PASS | +| nfb | newton_unrescued | closed 100 Hz | 0 | — | INFO | +| nfb | newton_median_iterations | closed 100 Hz | 0 | ≤ 3 | PASS | +| nfb | newton_max_iterations | closed 100 Hz | 1 | ≤ 64 | PASS | +| nfb | nfb_gain_db | closed 1000 Hz | 0.00536 dB | ≤ 0.5 | PASS | +| nfb | newton_failures | closed 1000 Hz | 0 | ≤ 0 | PASS | +| nfb | newton_unrescued | closed 1000 Hz | 0 | — | INFO | +| nfb | newton_median_iterations | closed 1000 Hz | 1 | ≤ 3 | PASS | +| nfb | newton_max_iterations | closed 1000 Hz | 1 | ≤ 64 | PASS | +| nfb | nfb_gain_db | closed 5000 Hz | 0.01479 dB | ≤ 0.5 | PASS | +| nfb | newton_failures | closed 5000 Hz | 0 | ≤ 0 | PASS | +| nfb | newton_unrescued | closed 5000 Hz | 0 | — | INFO | +| nfb | newton_median_iterations | closed 5000 Hz | 1 | ≤ 3 | PASS | +| nfb | newton_max_iterations | closed 5000 Hz | 2 | ≤ 64 | PASS | +| nfb | nfb_gain_db | open 100 Hz | 0.00227 dB | ≤ 0.5 | PASS | +| nfb | newton_failures | open 100 Hz | 0 | ≤ 0 | PASS | +| nfb | newton_unrescued | open 100 Hz | 0 | — | INFO | +| nfb | newton_median_iterations | open 100 Hz | 1 | ≤ 3 | PASS | +| nfb | newton_max_iterations | open 100 Hz | 1 | ≤ 64 | PASS | +| nfb | nfb_gain_db | open 1000 Hz | 0.007856 dB | ≤ 0.5 | PASS | +| nfb | newton_failures | open 1000 Hz | 0 | ≤ 0 | PASS | +| nfb | newton_unrescued | open 1000 Hz | 0 | — | INFO | +| nfb | newton_median_iterations | open 1000 Hz | 1 | ≤ 3 | PASS | +| nfb | newton_max_iterations | open 1000 Hz | 1 | ≤ 64 | PASS | +| nfb | nfb_gain_db | open 5000 Hz | 0.05701 dB | ≤ 0.5 | PASS | +| nfb | newton_failures | open 5000 Hz | 0 | ≤ 0 | PASS | +| nfb | newton_unrescued | open 5000 Hz | 0 | — | INFO | +| nfb | newton_median_iterations | open 5000 Hz | 1 | ≤ 3 | PASS | +| nfb | newton_max_iterations | open 5000 Hz | 2 | ≤ 64 | PASS | +| clean | total_esr_db | CLEAN SIN-1000Hz-0.025V | -30.3 dB | ≤ -20 | PASS | +| clean | total_harmonics_2_9_db | CLEAN SIN-1000Hz-0.025V | 0.7255 dB | ≤ 1.5 | PASS | +| clean | newton_failures | CLEAN SIN-1000Hz-0.025V | 0 | ≤ 0 | PASS | +| clean | newton_unrescued | CLEAN SIN-1000Hz-0.025V | 0 | — | INFO | +| clean | newton_median_iterations | CLEAN SIN-1000Hz-0.025V | 1 | ≤ 3 | PASS | +| clean | newton_max_iterations | CLEAN SIN-1000Hz-0.025V | 3 | ≤ 64 | PASS | +| clean | total_power_rel | CLEAN SIN-1000Hz-0.025V | 0.03927 | ≤ 0.05 | PASS | +| clean | total_thd_pp | CLEAN SIN-1000Hz-0.025V | 0.2173 pp | ≤ 0.5 | PASS | +| stability | stability_out_v | max |out| | 4.265e-05 V | ≤ 0.001 | PASS | +| stability | stability_growth | RMS last 10 s − first 10 s | 4.549e-12 V | ≤ 1e-06 | PASS | +| stability | newton_failures | 60 s silence | 0 | ≤ 0 | PASS | +| stability | newton_unrescued | 60 s silence | 0 | — | INFO | +| stability | newton_median_iterations | 60 s silence | 0 | ≤ 3 | PASS | +| stability | newton_max_iterations | 60 s silence | 1 | ≤ 64 | PASS | +| overdrive | total_esr_db | OVERDRIVE SIN-220Hz-0.3V | -67.99 dB | ≤ -20 | PASS | +| overdrive | total_harmonics_2_9_db | OVERDRIVE SIN-220Hz-0.3V | 0.008614 dB | ≤ 1.5 | PASS | +| overdrive | sag_min_v | OVERDRIVE SIN-220Hz-0.3V | 0.003537 V | ≤ 2 | PASS | +| overdrive | newton_failures | OVERDRIVE SIN-220Hz-0.3V | 0 | ≤ 0 | PASS | +| overdrive | newton_unrescued | OVERDRIVE SIN-220Hz-0.3V | 0 | — | INFO | +| overdrive | newton_median_iterations | OVERDRIVE SIN-220Hz-0.3V | 1 | ≤ 3 | PASS | +| overdrive | newton_max_iterations | OVERDRIVE SIN-220Hz-0.3V | 5 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | CRUNCH SIN-220Hz-0.3V | -70.25 dB | ≤ -20 | PASS | +| crunch_drive | total_harmonics_2_9_db | CRUNCH SIN-220Hz-0.3V | 0.007954 dB | ≤ 1.5 | PASS | +| crunch_drive | newton_failures | CRUNCH SIN-220Hz-0.3V | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | CRUNCH SIN-220Hz-0.3V | 0 | — | INFO | +| crunch_drive | newton_median_iterations | CRUNCH SIN-220Hz-0.3V | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | CRUNCH SIN-220Hz-0.3V | 5 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | CRUNCH DI-GTR L | -60.8 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | CRUNCH DI-GTR L | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | CRUNCH DI-GTR L | 0 | — | INFO | +| crunch_drive | newton_median_iterations | CRUNCH DI-GTR L | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | CRUNCH DI-GTR L | 7 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | CRUNCH DI-GTR LL | -61.36 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | CRUNCH DI-GTR LL | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | CRUNCH DI-GTR LL | 0 | — | INFO | +| crunch_drive | newton_median_iterations | CRUNCH DI-GTR LL | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | CRUNCH DI-GTR LL | 7 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | CRUNCH DI-GTR R | -62.11 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | CRUNCH DI-GTR R | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | CRUNCH DI-GTR R | 0 | — | INFO | +| crunch_drive | newton_median_iterations | CRUNCH DI-GTR R | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | CRUNCH DI-GTR R | 7 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | CRUNCH DI-GTR RR | -60.94 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | CRUNCH DI-GTR RR | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | CRUNCH DI-GTR RR | 0 | — | INFO | +| crunch_drive | newton_median_iterations | CRUNCH DI-GTR RR | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | CRUNCH DI-GTR RR | 7 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | DRIVE SIN-220Hz-0.3V | -63.49 dB | ≤ -20 | PASS | +| crunch_drive | total_harmonics_2_9_db | DRIVE SIN-220Hz-0.3V | 0.03171 dB | ≤ 1.5 | PASS | +| crunch_drive | newton_failures | DRIVE SIN-220Hz-0.3V | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | DRIVE SIN-220Hz-0.3V | 0 | — | INFO | +| crunch_drive | newton_median_iterations | DRIVE SIN-220Hz-0.3V | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | DRIVE SIN-220Hz-0.3V | 4 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | DRIVE DI-GTR L | -60.24 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | DRIVE DI-GTR L | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | DRIVE DI-GTR L | 0 | — | INFO | +| crunch_drive | newton_median_iterations | DRIVE DI-GTR L | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | DRIVE DI-GTR L | 7 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | DRIVE DI-GTR LL | -61.54 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | DRIVE DI-GTR LL | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | DRIVE DI-GTR LL | 0 | — | INFO | +| crunch_drive | newton_median_iterations | DRIVE DI-GTR LL | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | DRIVE DI-GTR LL | 7 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | DRIVE DI-GTR R | -61.01 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | DRIVE DI-GTR R | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | DRIVE DI-GTR R | 0 | — | INFO | +| crunch_drive | newton_median_iterations | DRIVE DI-GTR R | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | DRIVE DI-GTR R | 7 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | DRIVE DI-GTR RR | -60.27 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | DRIVE DI-GTR RR | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | DRIVE DI-GTR RR | 0 | — | INFO | +| crunch_drive | newton_median_iterations | DRIVE DI-GTR RR | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | DRIVE DI-GTR RR | 7 | ≤ 64 | PASS | + +## DC operating point + +| Node / current | SPICE | Engine | +|---|---|---| +| ct | 491.3466 V | 491.3468 V | +| nscr | 488.8805 V | 488.8807 V | +| bpx | 404.9982 V | 404.9997 V | +| bpa | 391.9215 V | 391.9237 V | +| nbias | -46.3636 V | -46.3636 V | +| p1a | 298.3260 V | 298.3319 V | +| k1a | 2.5271 V | 2.5270 V | +| p1b | 354.7502 V | 354.7562 V | +| k1b | 3.7171 V | 3.7167 V | +| p2a | 232.5771 V | 232.5822 V | +| k2a | 1.4139 V | 1.4138 V | +| k2b | 233.1404 V | 233.1454 V | +| p3a | 272.6113 V | 272.6156 V | +| p3b | 263.9512 V | 263.9557 V | +| k3 | 45.2502 V | 45.2492 V | +| nf | 43.8285 V | 43.8275 V | +| ng | 13.5790 V | 13.5787 V | +| ip_tube_a | 28.7718 mA | 28.7718 mA | +| ip_tube_b | 28.7715 mA | 28.7715 mA | +| is_tube_a | 3.2641 mA | 3.2641 mA | +| is_tube_b | 3.2643 mA | 3.2643 mA | + +## NFB (gain in → out, dB) + +| f | SPICE closed | engine closed | SPICE open | engine open | NFB SPICE | NFB engine | +|---|---|---|---|---|---|---| +| 100 Hz | 45.93 | 45.93 | 56.34 | 56.34 | 10.42 | 10.42 | +| 1000 Hz | 53.54 | 53.55 | 57.86 | 57.86 | 4.32 | 4.32 | +| 5000 Hz | 69.59 | 69.61 | 77.50 | 77.55 | 7.91 | 7.95 | + +## Signals + +| Preset | Signal | ESR | best lag | harm. 2–9 | THD F / E | Power F / E | min v(ct) F / E | +|---|---|---|---|---|---|---|---| +| CLEAN | SIN-1000Hz-0.025V | -30.3 dB | 2 | 0.73 dB | 7.37 / 7.59 % | 81.1 / 77.9 W | — | +| OVERDRIVE | SIN-220Hz-0.3V | -68.0 dB | 0 | 0.01 dB | 19.62 / 19.62 % | 32.2 / 32.2 W | 474.38 / 474.37 V | +| CRUNCH | SIN-220Hz-0.3V | -70.3 dB | 0 | 0.01 dB | 12.01 / 12.01 % | 31.6 / 31.6 W | — | +| CRUNCH | DI-GTR L | -60.8 dB | 0 | — | — | — | — | +| CRUNCH | DI-GTR LL | -61.4 dB | 0 | — | — | — | — | +| CRUNCH | DI-GTR R | -62.1 dB | 0 | — | — | — | — | +| CRUNCH | DI-GTR RR | -60.9 dB | 0 | — | — | — | — | +| DRIVE | SIN-220Hz-0.3V | -63.5 dB | 0 | 0.03 dB | 22.53 / 22.53 % | 34.5 / 34.6 W | — | +| DRIVE | DI-GTR L | -60.2 dB | 0 | — | — | — | — | +| DRIVE | DI-GTR LL | -61.5 dB | 0 | — | — | — | — | +| DRIVE | DI-GTR R | -61.0 dB | 0 | — | — | — | — | +| DRIVE | DI-GTR RR | -60.3 dB | 0 | — | — | — | — | + +## Stability + +60 s silence at Preamp / Master / Presence 1.0: max |out| 4.27e-05 V, RMS first / last 10 s 4.24e-05 / 4.24e-05 V. + +## Delayed-NFB break + +Not applicable: since D-041 the global NFB (R22) is part of the B56 tree and has no delay (SPEC §7 M4 asked for a 1- vs 2-sample comparison of the delayed design). diff --git a/harness/reports/M5/20261019_b56_knob_jump.md b/harness/reports/M5/20261019_b56_knob_jump.md new file mode 100644 index 0000000..3e13dd0 --- /dev/null +++ b/harness/reports/M5/20261019_b56_knob_jump.md @@ -0,0 +1,9 @@ +# M5 report: B56 knob-jump test + +- Date: 2026-10-19, engine commit `ecf9042 (dirty)`, ngspice 47, fs = 768000 Hz, SPICE reltol 1e-08 +- Knob targets 0 → 1 in 10 ms from t = 20 ms during a 1000 Hz, 1 V tone on the J4 DC level (DC 0.0000 V); other knobs at their defaults. Engine: smoothing 20 ms, matrix update every 32 samples. SPICE: pot resistances follow the same smoothed trajectory continuously. +- Window: the ramp and the 20 ms after it. Pass (D-025): no NaN/Inf, ESR ≤ -20 dB. + +| Case | Finite | ESR | Status | Peak error / ref peak | Error energy > 5 kHz vs tone | +|---|---|---|---|---|---| +| presence | yes | -62.9 dB | PASS | 1.74e-03 | -67.2 dB | diff --git a/harness/reports/M6/20261018_M6_report.md b/harness/reports/M6/20261018_M6_report.md index bb954a4..8858506 100644 --- a/harness/reports/M6/20261018_M6_report.md +++ b/harness/reports/M6/20261018_M6_report.md @@ -1,6 +1,6 @@ # M6 report — performance, real-time configuration and model refinements -Status: for owner review (2026-10-18). Scope set by the owner on 2026-10-14: performance work (the other SPEC §7 M6 +Status: accepted, M6 closed by the owner on 2026-10-19 (CPU work continues in M7). Scope set by the owner on 2026-10-14: performance work (the other SPEC §7 M6 items stay open); widened by owner decisions during M6 to the accuracy target (D-051), the real-time configuration (D-054 – D-060) and reference-model refinements from listening (D-058, D-059, D-061, D-062). diff --git a/harness/reports/M6/20261019_automation_fs96000_pdiv1_j4exact.md b/harness/reports/M6/20261019_automation_fs96000_pdiv1_j4exact.md new file mode 100644 index 0000000..9fe6af6 --- /dev/null +++ b/harness/reports/M6/20261019_automation_fs96000_pdiv1_j4exact.md @@ -0,0 +1,66 @@ +# M5 automation: every knob over its full range at audio rate + +- Date: 2026-10-19, commit `ecf9042 (dirty)`, engine `ampwav --block full` at 96000 Hz +- Modulation: 10 Hz triangle 0 → 1 → 0 (`lfo`) or 0 ↔ 1 jumps every 50 ms (`jump`) on the knob targets (smoothed by the engine, 20 ms); bases: default knobs, every knob at 1.0. +- SPEC §7 M5: no NaN / Inf, no Newton failures. Newton failures count first solves that did not converge in 12 iterations; the fallback (D-042) rescued or did not rescue them. + +Runs: 56 · non-finite outputs: 0 · Newton failures (first solve): 0 · unrescued: 0 + +| Base | Knobs | Modulation | Signal | finite | peak [V] | solves | failures | rescued | unrescued | median / max it | ns/sample | +|---|---|---|---|---|---|---|---|---|---|---|---| +| default | preampVolume | lfo | SIN-220Hz-0.3V | yes | 34.7 | 192000 | 0 | 0 | 0 | 1 / 6 | 2667 | +| default | preampVolume | lfo | DI-GTR L | yes | 55.7 | 963977 | 0 | 0 | 0 | 1 / 14 | 3069 | +| default | preampVolume | jump | SIN-220Hz-0.3V | yes | 40.3 | 192059 | 0 | 0 | 0 | 1 / 22 | 2590 | +| default | preampVolume | jump | DI-GTR L | yes | 59.8 | 966452 | 0 | 0 | 0 | 1 / 15 | 2781 | +| default | treble | lfo | SIN-220Hz-0.3V | yes | 41.4 | 192001 | 0 | 0 | 0 | 1 / 22 | 2848 | +| default | treble | lfo | DI-GTR L | yes | 64.3 | 965841 | 0 | 0 | 0 | 1 / 15 | 3250 | +| default | treble | jump | SIN-220Hz-0.3V | yes | 39.1 | 192007 | 0 | 0 | 0 | 1 / 22 | 2727 | +| default | treble | jump | DI-GTR L | yes | 76.6 | 967068 | 0 | 0 | 0 | 1 / 15 | 3368 | +| default | middle | lfo | SIN-220Hz-0.3V | yes | 35.1 | 192003 | 0 | 0 | 0 | 1 / 22 | 2937 | +| default | middle | lfo | DI-GTR L | yes | 56.3 | 965788 | 0 | 0 | 0 | 1 / 12 | 3379 | +| default | middle | jump | SIN-220Hz-0.3V | yes | 39.9 | 192006 | 0 | 0 | 0 | 1 / 22 | 2695 | +| default | middle | jump | DI-GTR L | yes | 65.0 | 966106 | 0 | 0 | 0 | 1 / 18 | 3379 | +| default | bass | lfo | SIN-220Hz-0.3V | yes | 39.3 | 192005 | 0 | 0 | 0 | 1 / 22 | 2532 | +| default | bass | lfo | DI-GTR L | yes | 59.1 | 965770 | 0 | 0 | 0 | 1 / 12 | 2714 | +| default | bass | jump | SIN-220Hz-0.3V | yes | 45.3 | 192005 | 0 | 0 | 0 | 1 / 22 | 2681 | +| default | bass | jump | DI-GTR L | yes | 57.5 | 965827 | 0 | 0 | 0 | 1 / 16 | 2954 | +| default | master | lfo | SIN-220Hz-0.3V | yes | 75.8 | 217572 | 0 | 0 | 0 | 1 / 22 | 2935 | +| default | master | lfo | DI-GTR L | yes | 129.1 | 1066454 | 0 | 0 | 0 | 1 / 22 | 3500 | +| default | master | jump | SIN-220Hz-0.3V | yes | 82.8 | 229875 | 0 | 0 | 0 | 1 / 22 | 2852 | +| default | master | jump | DI-GTR L | yes | 152.9 | 1096948 | 0 | 0 | 0 | 1 / 25 | 4113 | +| default | presence | lfo | SIN-220Hz-0.3V | yes | 35.7 | 192005 | 0 | 0 | 0 | 1 / 22 | 2847 | +| default | presence | lfo | DI-GTR L | yes | 58.0 | 965946 | 0 | 0 | 0 | 1 / 15 | 3094 | +| default | presence | jump | SIN-220Hz-0.3V | yes | 66.3 | 192021 | 0 | 0 | 0 | 1 / 22 | 2660 | +| default | presence | jump | DI-GTR L | yes | 75.2 | 966991 | 0 | 0 | 0 | 1 / 15 | 3162 | +| default | all | lfo | SIN-220Hz-0.3V | yes | 77.1 | 220360 | 0 | 0 | 0 | 1 / 7 | 3533 | +| default | all | lfo | DI-GTR L | yes | 154.1 | 1082674 | 0 | 0 | 0 | 1 / 25 | 6988 | +| default | all | jump | SIN-220Hz-0.3V | yes | 138.3 | 235862 | 0 | 0 | 0 | 1 / 22 | 3499 | +| default | all | jump | DI-GTR L | yes | 171.9 | 1123594 | 0 | 0 | 0 | 1 / 26 | 6936 | +| max | preampVolume | lfo | SIN-220Hz-0.3V | yes | 79.5 | 249747 | 0 | 0 | 0 | 1 / 11 | 2937 | +| max | preampVolume | lfo | DI-GTR L | yes | 213.7 | 1254609 | 0 | 0 | 0 | 1 / 19 | 3887 | +| max | preampVolume | jump | SIN-220Hz-0.3V | yes | 138.3 | 270652 | 0 | 0 | 0 | 1 / 22 | 3393 | +| max | preampVolume | jump | DI-GTR L | yes | 216.8 | 1281858 | 0 | 0 | 0 | 1 / 21 | 4413 | +| max | treble | lfo | SIN-220Hz-0.3V | yes | 124.9 | 302151 | 0 | 0 | 0 | 1 / 22 | 4042 | +| max | treble | lfo | DI-GTR L | yes | 210.1 | 1393862 | 0 | 0 | 0 | 1 / 24 | 4939 | +| max | treble | jump | SIN-220Hz-0.3V | yes | 138.3 | 301692 | 0 | 0 | 0 | 1 / 22 | 3984 | +| max | treble | jump | DI-GTR L | yes | 215.0 | 1385739 | 0 | 0 | 0 | 1 / 23 | 4895 | +| max | middle | lfo | SIN-220Hz-0.3V | yes | 183.1 | 307742 | 0 | 0 | 0 | 1 / 22 | 4658 | +| max | middle | lfo | DI-GTR L | yes | 214.6 | 1361724 | 0 | 0 | 0 | 1 / 24 | 4763 | +| max | middle | jump | SIN-220Hz-0.3V | yes | 138.3 | 304051 | 0 | 0 | 0 | 1 / 22 | 4243 | +| max | middle | jump | DI-GTR L | yes | 214.7 | 1360147 | 0 | 0 | 0 | 1 / 29 | 4726 | +| max | bass | lfo | SIN-220Hz-0.3V | yes | 143.8 | 311162 | 0 | 0 | 0 | 1 / 22 | 4913 | +| max | bass | lfo | DI-GTR L | yes | 217.2 | 1399126 | 0 | 0 | 0 | 1 / 25 | 4694 | +| max | bass | jump | SIN-220Hz-0.3V | yes | 149.5 | 309821 | 0 | 0 | 0 | 1 / 22 | 4568 | +| max | bass | jump | DI-GTR L | yes | 215.7 | 1396010 | 0 | 0 | 0 | 1 / 20 | 4469 | +| max | master | lfo | SIN-220Hz-0.3V | yes | 75.0 | 247384 | 0 | 0 | 0 | 1 / 22 | 3091 | +| max | master | lfo | DI-GTR L | yes | 170.7 | 1187928 | 0 | 0 | 0 | 1 / 26 | 3948 | +| max | master | jump | SIN-220Hz-0.3V | yes | 138.3 | 249404 | 0 | 0 | 0 | 1 / 22 | 3302 | +| max | master | jump | DI-GTR L | yes | 181.6 | 1186712 | 0 | 0 | 0 | 1 / 31 | 3928 | +| max | presence | lfo | SIN-220Hz-0.3V | yes | 107.9 | 301617 | 0 | 0 | 0 | 1 / 22 | 4084 | +| max | presence | lfo | DI-GTR L | yes | 211.7 | 1341314 | 0 | 0 | 0 | 1 / 26 | 4010 | +| max | presence | jump | SIN-220Hz-0.3V | yes | 138.3 | 302590 | 0 | 0 | 0 | 1 / 22 | 4307 | +| max | presence | jump | DI-GTR L | yes | 212.4 | 1342728 | 0 | 0 | 0 | 1 / 22 | 3873 | +| max | all | lfo | SIN-220Hz-0.3V | yes | 77.1 | 220360 | 0 | 0 | 0 | 1 / 7 | 3777 | +| max | all | lfo | DI-GTR L | yes | 154.1 | 1082674 | 0 | 0 | 0 | 1 / 25 | 5991 | +| max | all | jump | SIN-220Hz-0.3V | yes | 138.3 | 235862 | 0 | 0 | 0 | 1 / 22 | 3400 | +| max | all | jump | DI-GTR L | yes | 171.9 | 1123594 | 0 | 0 | 0 | 1 / 26 | 5910 | diff --git a/harness/reports/M6/20261019_automation_fs96000_pdiv2_j4exact.md b/harness/reports/M6/20261019_automation_fs96000_pdiv2_j4exact.md new file mode 100644 index 0000000..8c4c419 --- /dev/null +++ b/harness/reports/M6/20261019_automation_fs96000_pdiv2_j4exact.md @@ -0,0 +1,66 @@ +# M5 automation: every knob over its full range at audio rate + +- Date: 2026-10-19, commit `ecf9042 (dirty)`, engine `ampwav --block full` at 96000 Hz +- Modulation: 10 Hz triangle 0 → 1 → 0 (`lfo`) or 0 ↔ 1 jumps every 50 ms (`jump`) on the knob targets (smoothed by the engine, 20 ms); bases: default knobs, every knob at 1.0. +- SPEC §7 M5: no NaN / Inf, no Newton failures. Newton failures count first solves that did not converge in 12 iterations; the fallback (D-042) rescued or did not rescue them. + +Runs: 56 · non-finite outputs: 0 · Newton failures (first solve): 0 · unrescued: 0 + +| Base | Knobs | Modulation | Signal | finite | peak [V] | solves | failures | rescued | unrescued | median / max it | ns/sample | +|---|---|---|---|---|---|---|---|---|---|---|---| +| default | preampVolume | lfo | SIN-220Hz-0.3V | yes | 34.7 | 144026 | 0 | 0 | 0 | 1 / 6 | 1776 | +| default | preampVolume | lfo | DI-GTR L | yes | 54.1 | 731830 | 0 | 0 | 0 | 1 / 14 | 2274 | +| default | preampVolume | jump | SIN-220Hz-0.3V | yes | 40.3 | 144312 | 0 | 0 | 0 | 1 / 22 | 1812 | +| default | preampVolume | jump | DI-GTR L | yes | 57.5 | 732766 | 0 | 0 | 0 | 1 / 15 | 2267 | +| default | treble | lfo | SIN-220Hz-0.3V | yes | 41.4 | 144029 | 0 | 0 | 0 | 1 / 22 | 2172 | +| default | treble | lfo | DI-GTR L | yes | 59.1 | 735894 | 0 | 0 | 0 | 1 / 15 | 2735 | +| default | treble | jump | SIN-220Hz-0.3V | yes | 35.6 | 144096 | 0 | 0 | 0 | 1 / 22 | 2117 | +| default | treble | jump | DI-GTR L | yes | 79.0 | 735783 | 0 | 0 | 0 | 1 / 15 | 2894 | +| default | middle | lfo | SIN-220Hz-0.3V | yes | 35.1 | 144062 | 0 | 0 | 0 | 1 / 22 | 1993 | +| default | middle | lfo | DI-GTR L | yes | 54.0 | 735125 | 0 | 0 | 0 | 1 / 12 | 2749 | +| default | middle | jump | SIN-220Hz-0.3V | yes | 39.9 | 144077 | 0 | 0 | 0 | 1 / 22 | 2174 | +| default | middle | jump | DI-GTR L | yes | 64.0 | 735151 | 0 | 0 | 0 | 1 / 18 | 2736 | +| default | bass | lfo | SIN-220Hz-0.3V | yes | 39.2 | 144078 | 0 | 0 | 0 | 1 / 22 | 1922 | +| default | bass | lfo | DI-GTR L | yes | 56.5 | 735321 | 0 | 0 | 0 | 1 / 12 | 2084 | +| default | bass | jump | SIN-220Hz-0.3V | yes | 45.3 | 144082 | 0 | 0 | 0 | 1 / 22 | 2005 | +| default | bass | jump | DI-GTR L | yes | 54.5 | 735899 | 0 | 0 | 0 | 1 / 16 | 2205 | +| default | master | lfo | SIN-220Hz-0.3V | yes | 75.8 | 163649 | 0 | 0 | 0 | 1 / 22 | 2239 | +| default | master | lfo | DI-GTR L | yes | 133.2 | 805672 | 0 | 0 | 0 | 1 / 22 | 2934 | +| default | master | jump | SIN-220Hz-0.3V | yes | 82.9 | 169587 | 0 | 0 | 0 | 1 / 22 | 2321 | +| default | master | jump | DI-GTR L | yes | 153.1 | 818998 | 0 | 0 | 0 | 1 / 27 | 3034 | +| default | presence | lfo | SIN-220Hz-0.3V | yes | 35.7 | 144076 | 0 | 0 | 0 | 1 / 22 | 2030 | +| default | presence | lfo | DI-GTR L | yes | 53.7 | 735597 | 0 | 0 | 0 | 1 / 15 | 2459 | +| default | presence | jump | SIN-220Hz-0.3V | yes | 61.8 | 144107 | 0 | 0 | 0 | 1 / 22 | 2077 | +| default | presence | jump | DI-GTR L | yes | 76.1 | 736615 | 0 | 0 | 0 | 1 / 15 | 2515 | +| default | all | lfo | SIN-220Hz-0.3V | yes | 77.1 | 166503 | 0 | 0 | 0 | 1 / 9 | 3153 | +| default | all | lfo | DI-GTR L | yes | 158.4 | 810363 | 0 | 0 | 0 | 1 / 21 | 6344 | +| default | all | jump | SIN-220Hz-0.3V | yes | 131.5 | 172871 | 0 | 0 | 0 | 1 / 22 | 2919 | +| default | all | jump | DI-GTR L | yes | 163.7 | 846000 | 0 | 0 | 0 | 1 / 28 | 6126 | +| max | preampVolume | lfo | SIN-220Hz-0.3V | yes | 139.0 | 195035 | 0 | 0 | 0 | 1 / 14 | 3055 | +| max | preampVolume | lfo | DI-GTR L | yes | 195.8 | 1014766 | 0 | 0 | 0 | 1 / 25 | 3555 | +| max | preampVolume | jump | SIN-220Hz-0.3V | yes | 139.9 | 199660 | 0 | 0 | 0 | 1 / 22 | 3509 | +| max | preampVolume | jump | DI-GTR L | yes | 209.3 | 1013260 | 0 | 0 | 0 | 1 / 32 | 3717 | +| max | treble | lfo | SIN-220Hz-0.3V | yes | 119.0 | 200298 | 0 | 0 | 0 | 1 / 22 | 3057 | +| max | treble | lfo | DI-GTR L | yes | 203.1 | 963138 | 0 | 0 | 0 | 1 / 28 | 3845 | +| max | treble | jump | SIN-220Hz-0.3V | yes | 131.5 | 201219 | 0 | 0 | 0 | 1 / 22 | 2811 | +| max | treble | jump | DI-GTR L | yes | 208.3 | 963204 | 0 | 0 | 0 | 1 / 24 | 3820 | +| max | middle | lfo | SIN-220Hz-0.3V | yes | 183.1 | 206717 | 0 | 0 | 0 | 1 / 22 | 3250 | +| max | middle | lfo | DI-GTR L | yes | 202.6 | 1013016 | 0 | 0 | 0 | 1 / 34 | 3967 | +| max | middle | jump | SIN-220Hz-0.3V | yes | 131.5 | 204279 | 0 | 0 | 0 | 1 / 22 | 3264 | +| max | middle | jump | DI-GTR L | yes | 204.8 | 1012085 | 0 | 0 | 0 | 1 / 29 | 3873 | +| max | bass | lfo | SIN-220Hz-0.3V | yes | 144.9 | 204554 | 0 | 0 | 0 | 1 / 22 | 3457 | +| max | bass | lfo | DI-GTR L | yes | 197.2 | 1020201 | 0 | 0 | 0 | 1 / 30 | 3719 | +| max | bass | jump | SIN-220Hz-0.3V | yes | 145.0 | 203571 | 0 | 0 | 0 | 1 / 22 | 3305 | +| max | bass | jump | DI-GTR L | yes | 198.2 | 1019531 | 0 | 0 | 0 | 1 / 32 | 3481 | +| max | master | lfo | SIN-220Hz-0.3V | yes | 86.0 | 184783 | 0 | 0 | 0 | 1 / 22 | 2796 | +| max | master | lfo | DI-GTR L | yes | 166.3 | 882620 | 0 | 0 | 0 | 1 / 28 | 3204 | +| max | master | jump | SIN-220Hz-0.3V | yes | 131.5 | 180618 | 0 | 0 | 0 | 1 / 22 | 2524 | +| max | master | jump | DI-GTR L | yes | 183.5 | 878362 | 0 | 0 | 0 | 1 / 30 | 3217 | +| max | presence | lfo | SIN-220Hz-0.3V | yes | 104.9 | 203081 | 0 | 0 | 0 | 1 / 22 | 2845 | +| max | presence | lfo | DI-GTR L | yes | 193.3 | 1004297 | 0 | 0 | 0 | 1 / 33 | 3264 | +| max | presence | jump | SIN-220Hz-0.3V | yes | 131.5 | 202853 | 0 | 0 | 0 | 1 / 22 | 3066 | +| max | presence | jump | DI-GTR L | yes | 203.0 | 1005211 | 0 | 0 | 0 | 1 / 33 | 3111 | +| max | all | lfo | SIN-220Hz-0.3V | yes | 77.1 | 166503 | 0 | 0 | 0 | 1 / 9 | 2800 | +| max | all | lfo | DI-GTR L | yes | 158.4 | 810363 | 0 | 0 | 0 | 1 / 21 | 5199 | +| max | all | jump | SIN-220Hz-0.3V | yes | 131.5 | 172871 | 0 | 0 | 0 | 1 / 22 | 3175 | +| max | all | jump | DI-GTR L | yes | 163.7 | 846000 | 0 | 0 | 0 | 1 / 28 | 5324 | diff --git a/harness/reports/M6/20261019_auval.log b/harness/reports/M6/20261019_auval.log new file mode 100644 index 0000000..2fcafe1 --- /dev/null +++ b/harness/reports/M6/20261019_auval.log @@ -0,0 +1,281 @@ + + AU Validation Tool + Version: 1.10.0 + Copyright 2003-2019, Apple Inc. All Rights Reserved. + Specify -h (-help) for command options + +-------------------------------------------------- +VALIDATING AUDIO UNIT: 'aufx' - 'A223' - 'Ctcd' +-------------------------------------------------- +Manufacturer String: CatastrophicCoder +AudioUnit Name: Amp2203 +Component Version: 0.0.1 (0x1) + +* * PASS +-------------------------------------------------- +TESTING OPEN TIMES: +COLD: +Time to open AudioUnit: 13.300 ms +WARM: +Time to open AudioUnit: 6.453 ms +This AudioUnit is a version 2 implementation. +FIRST TIME: +Time for initialization: 8.875 ms + +* * PASS +-------------------------------------------------- +VERIFYING DEFAULT SCOPE FORMATS: +Input Scope Bus Configuration: + Default Bus Count:1 + Bus Name: Input + Default Format: AudioStreamBasicDescription: 2 ch, 44100 Hz, Float32, deinterleaved + Has Channel Layouts: 0x640001 0x650002 0x660002 0x670002 0x6A0002 + Default Layout: + Tag=0x650002, Num Chan Descs=0 + +Output Scope Bus Configuration: + Default Bus Count:1 + Bus Name: Output + Default Format: AudioStreamBasicDescription: 2 ch, 44100 Hz, Float32, deinterleaved + Has Channel Layouts: 0x640001 0x650002 0x660002 0x670002 0x6A0002 + Default Layout: + Tag=0x650002, Num Chan Descs=0 + +* * PASS +-------------------------------------------------- +VERIFYING REQUIRED PROPERTIES: + +* * PASS +-------------------------------------------------- +VERIFYING RECOMMENDED PROPERTIES: + VERIFYING PROPERTY: Latency + PASS + VERIFYING PROPERTY: Tail Time + PASS + VERIFYING PROPERTY: Bypass Effect + PASS + +* * PASS +-------------------------------------------------- +VERIFYING OPTIONAL PROPERTIES: + +* * PASS +-------------------------------------------------- +VERIFYING SPECIAL PROPERTIES: + +VERIFYING CUSTOM UI +Cocoa Views Available: 1 + JUCE_AUCocoaViewClass_dde37fe7e4c62553 + PASS + +HAS FACTORY PRESETS + ID: 0 Name: Untitled + +VERIFYING CLASS INFO + PASS + +TESTING HOST CALLBACKS + PASS + +* * PASS +-------------------------------------------------- +PUBLISHED PARAMETER INFO: + +# # # 12 Global Scope Parameters: +Parameter ID:3016415 +Name: Bass +Parameter Type: Generic +Values: Minimum = 0.0, Default = 5.0, Maximum = 10.0 +Flags: Values Have Strings, High Resolution, Can Ramp, Readable, Writable + -parameter PASS + +Parameter ID:857833147 +Name: Preamp Volume +Parameter Type: Generic +Values: Minimum = 0.0, Default = 6.0, Maximum = 10.0 +Flags: Values Have Strings, High Resolution, Can Ramp, Readable, Writable + -parameter PASS + +Parameter ID:870817019 +Name: Presence +Parameter Type: Generic +Values: Minimum = 0.0, Default = 5.0, Maximum = 10.0 +Flags: Values Have Strings, High Resolution, Can Ramp, Readable, Writable + -parameter PASS + +Parameter ID:956930507 +Name: Interface Level +Parameter Type: Generic +Values: Minimum = 0.0, Default = 12.0, Maximum = 24.0 +Flags: Values Have Strings, High Resolution, Can Ramp, Readable, Writable + -parameter PASS + +Parameter ID:1066216034 +Name: Master +Parameter Type: Generic +Values: Minimum = 0.0, Default = 3.0, Maximum = 10.0 +Flags: Values Have Strings, High Resolution, Can Ramp, Readable, Writable + -parameter PASS + +Parameter ID:1073142165 +Name: Middle +Parameter Type: Generic +Values: Minimum = 0.0, Default = 5.0, Maximum = 10.0 +Flags: Values Have Strings, High Resolution, Can Ramp, Readable, Writable + -parameter PASS + +Parameter ID:1281885780 +Name: Treble +Parameter Type: Generic +Values: Minimum = 0.0, Default = 5.0, Maximum = 10.0 +Flags: Values Have Strings, High Resolution, Can Ramp, Readable, Writable + -parameter PASS + +Parameter ID:1305086723 +Name: Output Level +Parameter Type: Generic +Values: Minimum = -48.0, Default = 0.0, Maximum = 12.0 +Flags: Values Have Strings, High Resolution, Can Ramp, Readable, Writable + -parameter PASS + +Parameter ID:1420097723 +Name: Oversampling +Parameter Type: Indexed +Values: Minimum = 2x, Default = 2x, Maximum = 16x +Flags: Values Have Strings, High Resolution, Readable, Writable +Parameter has Value Strings +Num Strings = 4 +Value: 0, String: 2x +Value: 1, String: 4x +Value: 2, String: 8x +Value: 3, String: 16x + -parameter PASS + +Parameter ID:1706969868 +Name: Input Trim +Parameter Type: Generic +Values: Minimum = -24.0, Default = 0.0, Maximum = 24.0 +Flags: Values Have Strings, High Resolution, Can Ramp, Readable, Writable + -parameter PASS + +Parameter ID:1796311039 +Name: Power Amp Rate +Parameter Type: Indexed +Values: Minimum = Full, Default = Half, Maximum = Half +Flags: Values Have Strings, High Resolution, Readable, Writable +Parameter has Value Strings +Num Strings = 2 +Value: 0, String: Full +Value: 1, String: Half + -parameter PASS + +Parameter ID:1957034003 +Name: Oversampling Filter +Parameter Type: Indexed +Values: Minimum = Linear phase (FIR), Default = Linear phase (FIR), Maximum = Low latency (IIR) +Flags: Values Have Strings, High Resolution, Readable, Writable +Parameter has Value Strings +Num Strings = 2 +Value: 0, String: Linear phase (FIR) +Value: 1, String: Low latency (IIR) + -parameter PASS + +Testing that parameters retain value across reset and initialization + PASS + +* * PASS +-------------------------------------------------- +FORMAT TESTS: + +Reported Channel Capabilities (explicit): + [1, 1] [1, 2] [2, 1] [2, 2] + +Input/Output Channel Handling: +1-1 1-2 1-4 1-5 1-6 1-7 1-8 2-2 2-4 2-5 2-6 2-7 2-8 4-4 4-5 5-5 6-6 7-7 8-8 +X X X + +# # AudioChannelLayouts (5), Input Scope: +ChannelLayout is Writable: T +The Unit publishes the following Channel Layouts: + 0x640001, 0x650002, 0x660002, 0x670002, 0x6A0002, + +Is Audio Channel Layout Available: +Mono Stereo Binau. AU_4 Ambi. AU_5 AU_5_0 AU_6 AU_6_0 AU_7_0 AU_7_0F AU_8 AU_5_1 AU_6_1 AU_7_1 AU_7_1F +X X X + +Current Format:AudioStreamBasicDescription: 2 ch, 44100 Hz, Float32, deinterleaved +Current Format Tag = 0x650002, New Format Tag = 0x640001, Success - did not reset format with mis-matched layout + +Current Format:AudioStreamBasicDescription: 2 ch, 44100 Hz, Float32, deinterleaved +Current Format Tag = 0x650002, New Format Tag = 0x650002, Successsful + +Current Format:AudioStreamBasicDescription: 2 ch, 44100 Hz, Float32, deinterleaved +Current Format Tag = 0x650002, New Format Tag = 0x6A0002, Successsful + +# # AudioChannelLayouts (5), Output Scope: +ChannelLayout is Writable: T +The Unit publishes the following Channel Layouts: + 0x640001, 0x650002, 0x660002, 0x670002, 0x6A0002, + +Is Audio Channel Layout Available: +Mono Stereo Binau. AU_4 Ambi. AU_5 AU_5_0 AU_6 AU_6_0 AU_7_0 AU_7_0F AU_8 AU_5_1 AU_6_1 AU_7_1 AU_7_1F +X X X + +Current Format:AudioStreamBasicDescription: 2 ch, 44100 Hz, Float32, deinterleaved +Current Format Tag = 0x650002, New Format Tag = 0x640001, Success - did not reset format with mis-matched layout + +Current Format:AudioStreamBasicDescription: 2 ch, 44100 Hz, Float32, deinterleaved +Current Format Tag = 0x650002, New Format Tag = 0x650002, Successsful + +Current Format:AudioStreamBasicDescription: 2 ch, 44100 Hz, Float32, deinterleaved +Current Format Tag = 0x650002, New Format Tag = 0x6A0002, Successsful + +* * PASS +-------------------------------------------------- +RENDER TESTS: + +Input Format: AudioStreamBasicDescription: 2 ch, 44100 Hz, Float32, deinterleaved +Output Format: AudioStreamBasicDescription: 2 ch, 44100 Hz, Float32, deinterleaved +Render Test at 512 frames +Slicing Render Test at 64 frames + PASS + + +Render Test at 64 frames, sample rate: 22050 Hz +Render Test at 137 frames, sample rate: 96000 Hz +Render Test at 4096 frames, sample rate: 48000 Hz +Render Test at 4096 frames, sample rate: 192000 Hz +Render Test at 4096 frames, sample rate: 11025 Hz +Render Test at 512 frames, sample rate: 44100 Hz + PASS + +1 Channel Test: +Render Test at 512 frames + PASS + +1 to 2 Channel Render Test at 256 frames + PASS + +Checking connection semantics: +Connection format: +AudioStreamBasicDescription: 2 ch, 44100 Hz, Float32, deinterleaved + PASS + +Bad Max Frames - Render should fail + PASS + +Checking parameter setting +Using AudioUnitSetParameter +Using AudioUnitScheduleParameter + PASS + +Checking ramped parameter scheduling + PASS + +Test MIDI + PASS + +* * PASS +-------------------------------------------------- +AU VALIDATION SUCCEEDED. +-------------------------------------------------- diff --git a/harness/reports/M6/20261019_full_acceptance_fs384000_j4exact.md b/harness/reports/M6/20261019_full_acceptance_fs384000_j4exact.md new file mode 100644 index 0000000..ea91f8b --- /dev/null +++ b/harness/reports/M6/20261019_full_acceptance_fs384000_j4exact.md @@ -0,0 +1,197 @@ +# M4 acceptance: full amplifier vs the full netlist + +- Date: 2026-10-19, engine commit `ecf9042 (dirty)`, ngspice 47, fs = 384000 Hz, J4 exact, SPICE reltol 1e-08 +- Netlist sha256 `ffd9b2812204248f`, tubes.lib sha256 `c9c9c88346c69010` +- Engine: `ampwav --block full` (volts at the jack and across the 16 Ω load), the checks of SPEC §7 M4. + +## Summary + +PASS 107 · FAIL 0 + +| Section | Check | Item | Measured | Tolerance | Status | +|---|---|---|---|---|---| +| dc | dc_rel_error | v(ct) | 4.176e-07 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(nscr) | 4.492e-07 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(bpx) | 3.649e-06 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(bpa) | 5.679e-06 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(nbias) | 4.636e-08 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(p1a) | 1.994e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(k1a) | 3.976e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(p1b) | 1.686e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(k1b) | 0.0001011 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(p2a) | 2.195e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(k2a) | 2.104e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(k2b) | 2.14e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(p3a) | 1.576e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(p3b) | 1.707e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(k3) | 2.137e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(nf) | 2.137e-05 | ≤ 0.005 | PASS | +| dc | dc_rel_error | v(ng) | 2.137e-05 | ≤ 0.005 | PASS | +| dc | el34_plate_ma | ip_tube_a | 8.687e-06 mA | ≤ 1 | PASS | +| dc | el34_plate_ma | ip_tube_b | 8.694e-06 mA | ≤ 1 | PASS | +| dc | el34_screen_ma | is_tube_a | 1.95e-07 mA | ≤ 0.3 | PASS | +| dc | el34_screen_ma | is_tube_b | 1.916e-07 mA | ≤ 0.3 | PASS | +| dc | ct_rel | v(ct) | 4.176e-07 | ≤ 0.01 | PASS | +| dc | nbias_v | v(nbias) | 2.15e-06 V | ≤ 0.2 | PASS | +| nfb | nfb_gain_db | closed 100 Hz | 0.001855 dB | ≤ 0.5 | PASS | +| nfb | newton_failures | closed 100 Hz | 0 | ≤ 0 | PASS | +| nfb | newton_unrescued | closed 100 Hz | 0 | — | INFO | +| nfb | newton_median_iterations | closed 100 Hz | 1 | ≤ 3 | PASS | +| nfb | newton_max_iterations | closed 100 Hz | 1 | ≤ 64 | PASS | +| nfb | nfb_gain_db | closed 1000 Hz | 0.005811 dB | ≤ 0.5 | PASS | +| nfb | newton_failures | closed 1000 Hz | 0 | ≤ 0 | PASS | +| nfb | newton_unrescued | closed 1000 Hz | 0 | — | INFO | +| nfb | newton_median_iterations | closed 1000 Hz | 1 | ≤ 3 | PASS | +| nfb | newton_max_iterations | closed 1000 Hz | 2 | ≤ 64 | PASS | +| nfb | nfb_gain_db | closed 5000 Hz | 0.01669 dB | ≤ 0.5 | PASS | +| nfb | newton_failures | closed 5000 Hz | 0 | ≤ 0 | PASS | +| nfb | newton_unrescued | closed 5000 Hz | 0 | — | INFO | +| nfb | newton_median_iterations | closed 5000 Hz | 2 | ≤ 3 | PASS | +| nfb | newton_max_iterations | closed 5000 Hz | 2 | ≤ 64 | PASS | +| nfb | nfb_gain_db | open 100 Hz | 0.002491 dB | ≤ 0.5 | PASS | +| nfb | newton_failures | open 100 Hz | 0 | ≤ 0 | PASS | +| nfb | newton_unrescued | open 100 Hz | 0 | — | INFO | +| nfb | newton_median_iterations | open 100 Hz | 1 | ≤ 3 | PASS | +| nfb | newton_max_iterations | open 100 Hz | 1 | ≤ 64 | PASS | +| nfb | nfb_gain_db | open 1000 Hz | 0.008421 dB | ≤ 0.5 | PASS | +| nfb | newton_failures | open 1000 Hz | 0 | ≤ 0 | PASS | +| nfb | newton_unrescued | open 1000 Hz | 0 | — | INFO | +| nfb | newton_median_iterations | open 1000 Hz | 1 | ≤ 3 | PASS | +| nfb | newton_max_iterations | open 1000 Hz | 2 | ≤ 64 | PASS | +| nfb | nfb_gain_db | open 5000 Hz | 0.06051 dB | ≤ 0.5 | PASS | +| nfb | newton_failures | open 5000 Hz | 0 | ≤ 0 | PASS | +| nfb | newton_unrescued | open 5000 Hz | 0 | — | INFO | +| nfb | newton_median_iterations | open 5000 Hz | 2 | ≤ 3 | PASS | +| nfb | newton_max_iterations | open 5000 Hz | 2 | ≤ 64 | PASS | +| clean | total_esr_db | CLEAN SIN-1000Hz-0.025V | -59.65 dB | ≤ -20 | PASS | +| clean | total_harmonics_2_9_db | CLEAN SIN-1000Hz-0.025V | 0.02006 dB | ≤ 1.5 | PASS | +| clean | newton_failures | CLEAN SIN-1000Hz-0.025V | 0 | ≤ 0 | PASS | +| clean | newton_unrescued | CLEAN SIN-1000Hz-0.025V | 0 | — | INFO | +| clean | newton_median_iterations | CLEAN SIN-1000Hz-0.025V | 1 | ≤ 3 | PASS | +| clean | newton_max_iterations | CLEAN SIN-1000Hz-0.025V | 6 | ≤ 64 | PASS | +| clean | total_power_rel | CLEAN SIN-1000Hz-0.025V | 0.001223 | ≤ 0.05 | PASS | +| clean | total_thd_pp | CLEAN SIN-1000Hz-0.025V | 0.0004352 pp | ≤ 0.5 | PASS | +| stability | stability_out_v | max |out| | 4.248e-05 V | ≤ 0.001 | PASS | +| stability | stability_growth | RMS last 10 s − first 10 s | 1.251e-12 V | ≤ 1e-06 | PASS | +| stability | newton_failures | 60 s silence | 0 | ≤ 0 | PASS | +| stability | newton_unrescued | 60 s silence | 0 | — | INFO | +| stability | newton_median_iterations | 60 s silence | 0 | ≤ 3 | PASS | +| stability | newton_max_iterations | 60 s silence | 1 | ≤ 64 | PASS | +| overdrive | total_esr_db | OVERDRIVE SIN-220Hz-0.3V | -67.5 dB | ≤ -20 | PASS | +| overdrive | total_harmonics_2_9_db | OVERDRIVE SIN-220Hz-0.3V | 0.009058 dB | ≤ 1.5 | PASS | +| overdrive | sag_min_v | OVERDRIVE SIN-220Hz-0.3V | 0.003903 V | ≤ 2 | PASS | +| overdrive | newton_failures | OVERDRIVE SIN-220Hz-0.3V | 0 | ≤ 0 | PASS | +| overdrive | newton_unrescued | OVERDRIVE SIN-220Hz-0.3V | 0 | — | INFO | +| overdrive | newton_median_iterations | OVERDRIVE SIN-220Hz-0.3V | 1 | ≤ 3 | PASS | +| overdrive | newton_max_iterations | OVERDRIVE SIN-220Hz-0.3V | 6 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | CRUNCH SIN-220Hz-0.3V | -69.66 dB | ≤ -20 | PASS | +| crunch_drive | total_harmonics_2_9_db | CRUNCH SIN-220Hz-0.3V | 0.008117 dB | ≤ 1.5 | PASS | +| crunch_drive | newton_failures | CRUNCH SIN-220Hz-0.3V | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | CRUNCH SIN-220Hz-0.3V | 0 | — | INFO | +| crunch_drive | newton_median_iterations | CRUNCH SIN-220Hz-0.3V | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | CRUNCH SIN-220Hz-0.3V | 4 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | CRUNCH DI-GTR L | -59.73 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | CRUNCH DI-GTR L | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | CRUNCH DI-GTR L | 0 | — | INFO | +| crunch_drive | newton_median_iterations | CRUNCH DI-GTR L | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | CRUNCH DI-GTR L | 8 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | CRUNCH DI-GTR LL | -60.06 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | CRUNCH DI-GTR LL | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | CRUNCH DI-GTR LL | 0 | — | INFO | +| crunch_drive | newton_median_iterations | CRUNCH DI-GTR LL | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | CRUNCH DI-GTR LL | 8 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | CRUNCH DI-GTR R | -60.65 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | CRUNCH DI-GTR R | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | CRUNCH DI-GTR R | 0 | — | INFO | +| crunch_drive | newton_median_iterations | CRUNCH DI-GTR R | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | CRUNCH DI-GTR R | 8 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | CRUNCH DI-GTR RR | -59.5 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | CRUNCH DI-GTR RR | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | CRUNCH DI-GTR RR | 0 | — | INFO | +| crunch_drive | newton_median_iterations | CRUNCH DI-GTR RR | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | CRUNCH DI-GTR RR | 8 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | DRIVE SIN-220Hz-0.3V | -63.12 dB | ≤ -20 | PASS | +| crunch_drive | total_harmonics_2_9_db | DRIVE SIN-220Hz-0.3V | 0.03219 dB | ≤ 1.5 | PASS | +| crunch_drive | newton_failures | DRIVE SIN-220Hz-0.3V | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | DRIVE SIN-220Hz-0.3V | 0 | — | INFO | +| crunch_drive | newton_median_iterations | DRIVE SIN-220Hz-0.3V | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | DRIVE SIN-220Hz-0.3V | 5 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | DRIVE DI-GTR L | -59.48 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | DRIVE DI-GTR L | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | DRIVE DI-GTR L | 0 | — | INFO | +| crunch_drive | newton_median_iterations | DRIVE DI-GTR L | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | DRIVE DI-GTR L | 8 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | DRIVE DI-GTR LL | -60.63 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | DRIVE DI-GTR LL | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | DRIVE DI-GTR LL | 0 | — | INFO | +| crunch_drive | newton_median_iterations | DRIVE DI-GTR LL | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | DRIVE DI-GTR LL | 8 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | DRIVE DI-GTR R | -60.43 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | DRIVE DI-GTR R | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | DRIVE DI-GTR R | 0 | — | INFO | +| crunch_drive | newton_median_iterations | DRIVE DI-GTR R | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | DRIVE DI-GTR R | 8 | ≤ 64 | PASS | +| crunch_drive | total_esr_db | DRIVE DI-GTR RR | -59.46 dB | ≤ -20 | PASS | +| crunch_drive | newton_failures | DRIVE DI-GTR RR | 0 | ≤ 0 | PASS | +| crunch_drive | newton_unrescued | DRIVE DI-GTR RR | 0 | — | INFO | +| crunch_drive | newton_median_iterations | DRIVE DI-GTR RR | 1 | ≤ 3 | PASS | +| crunch_drive | newton_max_iterations | DRIVE DI-GTR RR | 9 | ≤ 64 | PASS | + +## DC operating point + +| Node / current | SPICE | Engine | +|---|---|---| +| ct | 491.3466 V | 491.3468 V | +| nscr | 488.8805 V | 488.8807 V | +| bpx | 404.9982 V | 404.9997 V | +| bpa | 391.9215 V | 391.9237 V | +| nbias | -46.3636 V | -46.3636 V | +| p1a | 298.3260 V | 298.3319 V | +| k1a | 2.5271 V | 2.5270 V | +| p1b | 354.7502 V | 354.7562 V | +| k1b | 3.7171 V | 3.7167 V | +| p2a | 232.5771 V | 232.5822 V | +| k2a | 1.4139 V | 1.4138 V | +| k2b | 233.1404 V | 233.1454 V | +| p3a | 272.6113 V | 272.6156 V | +| p3b | 263.9512 V | 263.9557 V | +| k3 | 45.2502 V | 45.2492 V | +| nf | 43.8285 V | 43.8275 V | +| ng | 13.5790 V | 13.5787 V | +| ip_tube_a | 28.7718 mA | 28.7718 mA | +| ip_tube_b | 28.7715 mA | 28.7715 mA | +| is_tube_a | 3.2641 mA | 3.2641 mA | +| is_tube_b | 3.2643 mA | 3.2643 mA | + +## NFB (gain in → out, dB) + +| f | SPICE closed | engine closed | SPICE open | engine open | NFB SPICE | NFB engine | +|---|---|---|---|---|---|---| +| 100 Hz | 45.93 | 45.93 | 56.34 | 56.34 | 10.42 | 10.42 | +| 1000 Hz | 53.54 | 53.55 | 57.86 | 57.86 | 4.32 | 4.32 | +| 5000 Hz | 69.59 | 69.61 | 77.50 | 77.56 | 7.91 | 7.95 | + +## Signals + +| Preset | Signal | ESR | best lag | harm. 2–9 | THD F / E | Power F / E | min v(ct) F / E | +|---|---|---|---|---|---|---|---| +| CLEAN | SIN-1000Hz-0.025V | -59.6 dB | 0 | 0.02 dB | 7.37 / 7.37 % | 81.1 / 81.2 W | — | +| OVERDRIVE | SIN-220Hz-0.3V | -67.5 dB | 0 | 0.01 dB | 19.62 / 19.62 % | 32.2 / 32.2 W | 474.38 / 474.37 V | +| CRUNCH | SIN-220Hz-0.3V | -69.7 dB | 0 | 0.01 dB | 12.01 / 12.01 % | 31.6 / 31.6 W | — | +| CRUNCH | DI-GTR L | -59.7 dB | 0 | — | — | — | — | +| CRUNCH | DI-GTR LL | -60.1 dB | 0 | — | — | — | — | +| CRUNCH | DI-GTR R | -60.7 dB | 0 | — | — | — | — | +| CRUNCH | DI-GTR RR | -59.5 dB | 0 | — | — | — | — | +| DRIVE | SIN-220Hz-0.3V | -63.1 dB | 0 | 0.03 dB | 22.53 / 22.54 % | 34.5 / 34.6 W | — | +| DRIVE | DI-GTR L | -59.5 dB | 0 | — | — | — | — | +| DRIVE | DI-GTR LL | -60.6 dB | 0 | — | — | — | — | +| DRIVE | DI-GTR R | -60.4 dB | 0 | — | — | — | — | +| DRIVE | DI-GTR RR | -59.5 dB | 0 | — | — | — | — | + +## Stability + +60 s silence at Preamp / Master / Presence 1.0: max |out| 4.25e-05 V, RMS first / last 10 s 4.24e-05 / 4.24e-05 V. + +## Delayed-NFB break + +Not applicable: since D-041 the global NFB (R22) is part of the B56 tree and has no delay (SPEC §7 M4 asked for a 1- vs 2-sample comparison of the delayed design). diff --git a/harness/reports/M6/20261019_full_acceptance_fs384000_j4exact_hot.md b/harness/reports/M6/20261019_full_acceptance_fs384000_j4exact_hot.md new file mode 100644 index 0000000..004d0b6 --- /dev/null +++ b/harness/reports/M6/20261019_full_acceptance_fs384000_j4exact_hot.md @@ -0,0 +1,52 @@ +# M4 acceptance: full amplifier vs the full netlist + +- Date: 2026-10-19, engine commit `ecf9042 (dirty)`, ngspice 47, fs = 384000 Hz, J4 exact, SPICE reltol 1e-08 +- Netlist sha256 `ffd9b2812204248f`, tubes.lib sha256 `c9c9c88346c69010` +- Engine: `ampwav --block full` (volts at the jack and across the 16 Ω load), the checks of SPEC §7 M4. + +## Summary + +PASS 21 · FAIL 0 + +| Section | Check | Item | Measured | Tolerance | Status | +|---|---|---|---|---|---| +| hot | total_esr_db | HOT SIN-220Hz-0.3V | -54.53 dB | ≤ -20 | PASS | +| hot | total_harmonics_2_9_db | HOT SIN-220Hz-0.3V | 0.008763 dB | ≤ 1.5 | PASS | +| hot | newton_failures | HOT SIN-220Hz-0.3V | 0 | ≤ 0 | PASS | +| hot | newton_unrescued | HOT SIN-220Hz-0.3V | 0 | — | INFO | +| hot | newton_median_iterations | HOT SIN-220Hz-0.3V | 1 | ≤ 3 | PASS | +| hot | newton_max_iterations | HOT SIN-220Hz-0.3V | 9 | ≤ 64 | PASS | +| hot | total_esr_db | HOT DI-GTR L | -40.27 dB | ≤ -20 | PASS | +| hot | newton_failures | HOT DI-GTR L | 0 | ≤ 0 | PASS | +| hot | newton_unrescued | HOT DI-GTR L | 0 | — | INFO | +| hot | newton_median_iterations | HOT DI-GTR L | 1 | ≤ 3 | PASS | +| hot | newton_max_iterations | HOT DI-GTR L | 16 | ≤ 64 | PASS | +| hot | total_esr_db | HOT DI-GTR LL | -41 dB | ≤ -20 | PASS | +| hot | newton_failures | HOT DI-GTR LL | 0 | ≤ 0 | PASS | +| hot | newton_unrescued | HOT DI-GTR LL | 0 | — | INFO | +| hot | newton_median_iterations | HOT DI-GTR LL | 1 | ≤ 3 | PASS | +| hot | newton_max_iterations | HOT DI-GTR LL | 16 | ≤ 64 | PASS | +| hot | total_esr_db | HOT DI-GTR R | -41.5 dB | ≤ -20 | PASS | +| hot | newton_failures | HOT DI-GTR R | 0 | ≤ 0 | PASS | +| hot | newton_unrescued | HOT DI-GTR R | 0 | — | INFO | +| hot | newton_median_iterations | HOT DI-GTR R | 1 | ≤ 3 | PASS | +| hot | newton_max_iterations | HOT DI-GTR R | 14 | ≤ 64 | PASS | +| hot | total_esr_db | HOT DI-GTR RR | -40.86 dB | ≤ -20 | PASS | +| hot | newton_failures | HOT DI-GTR RR | 0 | ≤ 0 | PASS | +| hot | newton_unrescued | HOT DI-GTR RR | 0 | — | INFO | +| hot | newton_median_iterations | HOT DI-GTR RR | 1 | ≤ 3 | PASS | +| hot | newton_max_iterations | HOT DI-GTR RR | 17 | ≤ 64 | PASS | + +## Signals + +| Preset | Signal | ESR | best lag | harm. 2–9 | THD F / E | Power F / E | min v(ct) F / E | +|---|---|---|---|---|---|---|---| +| HOT | SIN-220Hz-0.3V | -54.5 dB | 0 | 0.01 dB | 37.86 / 37.86 % | 178.4 / 178.4 W | — | +| HOT | DI-GTR L | -40.3 dB | 0 | — | — | — | — | +| HOT | DI-GTR LL | -41.0 dB | 0 | — | — | — | — | +| HOT | DI-GTR R | -41.5 dB | 0 | — | — | — | — | +| HOT | DI-GTR RR | -40.9 dB | 0 | — | — | — | — | + +## Delayed-NFB break + +Not applicable: since D-041 the global NFB (R22) is part of the B56 tree and has no delay (SPEC §7 M4 asked for a 1- vs 2-sample comparison of the delayed design). diff --git a/harness/reports/M6/20261019_plugin_cpu.md b/harness/reports/M6/20261019_plugin_cpu.md new file mode 100644 index 0000000..f71b876 --- /dev/null +++ b/harness/reports/M6/20261019_plugin_cpu.md @@ -0,0 +1,39 @@ +# M6 plugin CPU under knob automation + +- Date 2026-10-19, commit `8986f9f (dirty)`; `amp2203_render plugin` at the plugin defaults (2x, Power Amp Rate Half, exact J4, Newton tol 1e-4 V, half-steps on; D-054 – D-056, D-069), FIR oversampling, 20 s of DI GTR L at 48000 Hz; processBlock time only. +- Automation between host blocks: lfo = 10 Hz triangle 0 → 10 → 0, jump = 0 ↔ 10 every 50 ms; "all" moves the six knobs together. One render at a time. + +| Base | Knob(s) | Modulation | Host block | CPU (% of one core) | Newton failures (rescued) | +|---|---|---|---|---|---| +| default | — | static | 512 | 13.1 | 0 (0) | +| every knob 10 | — | static | 512 | 24.0 | 0 (0) | +| default | preampVolume | lfo | 512 | 13.2 | 0 (0) | +| default | preampVolume | jump | 512 | 12.9 | 0 (0) | +| default | treble | lfo | 512 | 13.8 | 0 (0) | +| default | treble | jump | 512 | 13.8 | 0 (0) | +| default | middle | lfo | 512 | 13.7 | 0 (0) | +| default | middle | jump | 512 | 13.7 | 0 (0) | +| default | bass | lfo | 512 | 13.7 | 0 (0) | +| default | bass | jump | 512 | 13.8 | 0 (0) | +| default | master | lfo | 512 | 15.5 | 0 (0) | +| default | master | jump | 512 | 15.8 | 0 (0) | +| default | presence | lfo | 512 | 14.1 | 0 (0) | +| default | presence | jump | 512 | 14.1 | 0 (0) | +| default | all | lfo | 512 | 16.3 | 0 (0) | +| default | all | jump | 512 | 17.1 | 0 (0) | +| every knob 10 | preampVolume | lfo | 512 | 23.2 | 0 (0) | +| every knob 10 | preampVolume | jump | 512 | 23.0 | 0 (0) | +| every knob 10 | treble | lfo | 512 | 23.0 | 0 (0) | +| every knob 10 | treble | jump | 512 | 22.7 | 0 (0) | +| every knob 10 | middle | lfo | 512 | 23.3 | 0 (0) | +| every knob 10 | middle | jump | 512 | 23.4 | 0 (0) | +| every knob 10 | bass | lfo | 512 | 24.6 | 0 (0) | +| every knob 10 | bass | jump | 512 | 24.4 | 0 (0) | +| every knob 10 | master | lfo | 512 | 19.2 | 0 (0) | +| every knob 10 | master | jump | 512 | 19.1 | 0 (0) | +| every knob 10 | presence | lfo | 512 | 22.8 | 0 (0) | +| every knob 10 | presence | jump | 512 | 22.9 | 0 (0) | +| every knob 10 | all | lfo | 512 | 16.3 | 0 (0) | +| every knob 10 | all | jump | 512 | 17.1 | 0 (0) | +| default | all | jump | 64 | 17.2 | 0 (0) | +| every knob 10 | all | jump | 64 | 17.2 | 0 (0) | diff --git a/harness/reports/M6/20261019_plugin_null_2x_full.md b/harness/reports/M6/20261019_plugin_null_2x_full.md new file mode 100644 index 0000000..0e5981e --- /dev/null +++ b/harness/reports/M6/20261019_plugin_null_2x_full.md @@ -0,0 +1,62 @@ +# M5 wrapper null test: plugin vs ampwav at 96 kHz with the same resampling + +- Date: 2026-10-19, commit `8986f9f (dirty)`; host rate 48000 Hz, 2x, Power Amp Rate full, exact J4 +- Plugin: `amp2203_render plugin` (the Amp2203Processor offline). Reference: the processor's oversampler up stage, `ampwav --block full --units fs --j4 exact` at 96000 Hz, the down stage. Default knobs and levels. +- Tolerance (SPEC §7 M5): ESR ≤ -60 dB. + +PASS 26 · FAIL 0 + +| Filter | Signal | Peak out [FS] | ESR | Status | Render log | +|---|---|---|---|---|---| +| fir | SIN-100Hz--40dBFS | 0.145 | -138.5 dB | PASS | processBlock 0.036 s for 0.200 s of audio: 17.8 % of one core, real-time factor 5.62 +latency 49 samples, Newton failures 0 (rescued 0), max iterations 2 | +| fir | SIN-100Hz--20dBFS | 0.870 | -138.5 dB | PASS | processBlock 0.039 s for 0.200 s of audio: 19.7 % of one core, real-time factor 5.08 +latency 49 samples, Newton failures 0 (rescued 0), max iterations 5 | +| fir | SIN-100Hz--6dBFS | 0.915 | -138.5 dB | PASS | processBlock 0.038 s for 0.200 s of audio: 18.8 % of one core, real-time factor 5.32 +latency 49 samples, Newton failures 0 (rescued 0), max iterations 6 | +| fir | SIN-1000Hz--40dBFS | 0.383 | -138.5 dB | PASS | processBlock 0.052 s for 0.200 s of audio: 25.8 % of one core, real-time factor 3.88 +latency 49 samples, Newton failures 0 (rescued 0), max iterations 7 | +| fir | SIN-1000Hz--20dBFS | 0.850 | -138.5 dB | PASS | processBlock 0.060 s for 0.200 s of audio: 30.1 % of one core, real-time factor 3.32 +latency 49 samples, Newton failures 0 (rescued 0), max iterations 7 | +| fir | SIN-1000Hz--6dBFS | 0.918 | -138.5 dB | PASS | processBlock 0.056 s for 0.200 s of audio: 28.0 % of one core, real-time factor 3.57 +latency 49 samples, Newton failures 0 (rescued 0), max iterations 13 | +| fir | SIN-5000Hz--40dBFS | 0.857 | -138.5 dB | PASS | processBlock 0.079 s for 0.200 s of audio: 39.3 % of one core, real-time factor 2.54 +latency 49 samples, Newton failures 0 (rescued 0), max iterations 15 | +| fir | SIN-5000Hz--20dBFS | 0.891 | -138.5 dB | PASS | processBlock 0.078 s for 0.200 s of audio: 38.8 % of one core, real-time factor 2.57 +latency 49 samples, Newton failures 0 (rescued 0), max iterations 17 | +| fir | SIN-5000Hz--6dBFS | 0.871 | -138.5 dB | PASS | processBlock 0.090 s for 0.200 s of audio: 45.2 % of one core, real-time factor 2.21 +latency 49 samples, Newton failures 0 (rescued 0), max iterations 35 | +| fir | DI-GTR L | 0.984 | -138.5 dB | PASS | processBlock 0.926 s for 5.000 s of audio: 18.5 % of one core, real-time factor 5.40 +latency 49 samples, Newton failures 0 (rescued 0), max iterations 13 | +| fir | DI-GTR LL | 0.861 | -138.5 dB | PASS | processBlock 0.924 s for 5.000 s of audio: 18.5 % of one core, real-time factor 5.41 +latency 49 samples, Newton failures 0 (rescued 0), max iterations 15 | +| fir | DI-GTR R | 0.944 | -138.5 dB | PASS | processBlock 0.932 s for 5.000 s of audio: 18.6 % of one core, real-time factor 5.37 +latency 49 samples, Newton failures 0 (rescued 0), max iterations 16 | +| fir | DI-GTR RR | 0.990 | -138.5 dB | PASS | processBlock 0.917 s for 5.000 s of audio: 18.3 % of one core, real-time factor 5.46 +latency 49 samples, Newton failures 0 (rescued 0), max iterations 14 | +| iir | SIN-100Hz--40dBFS | 0.145 | -138.5 dB | PASS | processBlock 0.030 s for 0.200 s of audio: 15.0 % of one core, real-time factor 6.68 +latency 4 samples, Newton failures 0 (rescued 0), max iterations 1 | +| iir | SIN-100Hz--20dBFS | 0.870 | -138.5 dB | PASS | processBlock 0.037 s for 0.200 s of audio: 18.3 % of one core, real-time factor 5.48 +latency 4 samples, Newton failures 0 (rescued 0), max iterations 4 | +| iir | SIN-100Hz--6dBFS | 0.915 | -138.5 dB | PASS | processBlock 0.046 s for 0.200 s of audio: 22.8 % of one core, real-time factor 4.38 +latency 4 samples, Newton failures 0 (rescued 0), max iterations 4 | +| iir | SIN-1000Hz--40dBFS | 0.402 | -138.5 dB | PASS | processBlock 0.054 s for 0.200 s of audio: 26.9 % of one core, real-time factor 3.71 +latency 4 samples, Newton failures 0 (rescued 0), max iterations 7 | +| iir | SIN-1000Hz--20dBFS | 0.932 | -138.5 dB | PASS | processBlock 0.047 s for 0.200 s of audio: 23.7 % of one core, real-time factor 4.22 +latency 4 samples, Newton failures 0 (rescued 0), max iterations 7 | +| iir | SIN-1000Hz--6dBFS | 1.087 | -138.5 dB | PASS | processBlock 0.051 s for 0.200 s of audio: 25.5 % of one core, real-time factor 3.92 +latency 4 samples, Newton failures 0 (rescued 0), max iterations 19 | +| iir | SIN-5000Hz--40dBFS | 0.987 | -138.5 dB | PASS | processBlock 0.066 s for 0.200 s of audio: 32.8 % of one core, real-time factor 3.05 +latency 4 samples, Newton failures 0 (rescued 0), max iterations 8 | +| iir | SIN-5000Hz--20dBFS | 1.048 | -138.5 dB | PASS | processBlock 0.091 s for 0.200 s of audio: 45.6 % of one core, real-time factor 2.19 +latency 4 samples, Newton failures 0 (rescued 0), max iterations 17 | +| iir | SIN-5000Hz--6dBFS | 1.040 | -138.5 dB | PASS | processBlock 0.091 s for 0.200 s of audio: 45.5 % of one core, real-time factor 2.20 +latency 4 samples, Newton failures 0 (rescued 0), max iterations 34 | +| iir | DI-GTR L | 0.950 | -138.5 dB | PASS | processBlock 0.894 s for 5.000 s of audio: 17.9 % of one core, real-time factor 5.59 +latency 4 samples, Newton failures 0 (rescued 0), max iterations 14 | +| iir | DI-GTR LL | 0.955 | -138.5 dB | PASS | processBlock 0.880 s for 5.000 s of audio: 17.6 % of one core, real-time factor 5.68 +latency 4 samples, Newton failures 0 (rescued 0), max iterations 15 | +| iir | DI-GTR R | 1.046 | -138.5 dB | PASS | processBlock 0.878 s for 5.000 s of audio: 17.6 % of one core, real-time factor 5.69 +latency 4 samples, Newton failures 0 (rescued 0), max iterations 16 | +| iir | DI-GTR RR | 1.049 | -138.5 dB | PASS | processBlock 0.884 s for 5.000 s of audio: 17.7 % of one core, real-time factor 5.66 +latency 4 samples, Newton failures 0 (rescued 0), max iterations 15 | diff --git a/harness/reports/M6/20261019_plugin_null_2x_half.md b/harness/reports/M6/20261019_plugin_null_2x_half.md new file mode 100644 index 0000000..77ca4ec --- /dev/null +++ b/harness/reports/M6/20261019_plugin_null_2x_half.md @@ -0,0 +1,62 @@ +# M5 wrapper null test: plugin vs ampwav at 96 kHz with the same resampling + +- Date: 2026-10-19, commit `8986f9f`; host rate 48000 Hz, 2x, Power Amp Rate half, exact J4 +- Plugin: `amp2203_render plugin` (the Amp2203Processor offline). Reference: the processor's oversampler up stage, `ampwav --block full --units fs --j4 exact` at 96000 Hz, the down stage. Default knobs and levels. +- Tolerance (SPEC §7 M5): ESR ≤ -60 dB. + +PASS 26 · FAIL 0 + +| Filter | Signal | Peak out [FS] | ESR | Status | Render log | +|---|---|---|---|---|---| +| fir | SIN-100Hz--40dBFS | 0.145 | -138.5 dB | PASS | processBlock 0.034 s for 0.200 s of audio: 17.0 % of one core, real-time factor 5.87 +latency 58 samples, Newton failures 0 (rescued 0), max iterations 2 | +| fir | SIN-100Hz--20dBFS | 0.870 | -138.5 dB | PASS | processBlock 0.047 s for 0.200 s of audio: 23.6 % of one core, real-time factor 4.24 +latency 58 samples, Newton failures 0 (rescued 0), max iterations 5 | +| fir | SIN-100Hz--6dBFS | 0.915 | -138.5 dB | PASS | processBlock 0.036 s for 0.200 s of audio: 17.9 % of one core, real-time factor 5.58 +latency 58 samples, Newton failures 0 (rescued 0), max iterations 6 | +| fir | SIN-1000Hz--40dBFS | 0.376 | -138.5 dB | PASS | processBlock 0.048 s for 0.200 s of audio: 24.0 % of one core, real-time factor 4.16 +latency 58 samples, Newton failures 0 (rescued 0), max iterations 7 | +| fir | SIN-1000Hz--20dBFS | 0.791 | -138.5 dB | PASS | processBlock 0.039 s for 0.200 s of audio: 19.3 % of one core, real-time factor 5.18 +latency 58 samples, Newton failures 0 (rescued 0), max iterations 7 | +| fir | SIN-1000Hz--6dBFS | 0.994 | -138.5 dB | PASS | processBlock 0.044 s for 0.200 s of audio: 21.8 % of one core, real-time factor 4.58 +latency 58 samples, Newton failures 0 (rescued 0), max iterations 13 | +| fir | SIN-5000Hz--40dBFS | 0.868 | -138.5 dB | PASS | processBlock 0.055 s for 0.200 s of audio: 27.7 % of one core, real-time factor 3.60 +latency 58 samples, Newton failures 0 (rescued 0), max iterations 15 | +| fir | SIN-5000Hz--20dBFS | 0.923 | -138.5 dB | PASS | processBlock 0.077 s for 0.200 s of audio: 38.7 % of one core, real-time factor 2.58 +latency 58 samples, Newton failures 0 (rescued 0), max iterations 17 | +| fir | SIN-5000Hz--6dBFS | 0.977 | -138.5 dB | PASS | processBlock 0.077 s for 0.200 s of audio: 38.5 % of one core, real-time factor 2.60 +latency 58 samples, Newton failures 0 (rescued 0), max iterations 35 | +| fir | DI-GTR L | 0.874 | -138.5 dB | PASS | processBlock 0.671 s for 5.000 s of audio: 13.4 % of one core, real-time factor 7.46 +latency 58 samples, Newton failures 0 (rescued 0), max iterations 13 | +| fir | DI-GTR LL | 0.834 | -138.5 dB | PASS | processBlock 0.674 s for 5.000 s of audio: 13.5 % of one core, real-time factor 7.41 +latency 58 samples, Newton failures 0 (rescued 0), max iterations 15 | +| fir | DI-GTR R | 0.908 | -138.5 dB | PASS | processBlock 0.675 s for 5.000 s of audio: 13.5 % of one core, real-time factor 7.40 +latency 58 samples, Newton failures 0 (rescued 0), max iterations 16 | +| fir | DI-GTR RR | 0.912 | -138.5 dB | PASS | processBlock 0.669 s for 5.000 s of audio: 13.4 % of one core, real-time factor 7.48 +latency 58 samples, Newton failures 0 (rescued 0), max iterations 14 | +| iir | SIN-100Hz--40dBFS | 0.145 | -138.5 dB | PASS | processBlock 0.032 s for 0.200 s of audio: 16.1 % of one core, real-time factor 6.21 +latency 13 samples, Newton failures 0 (rescued 0), max iterations 2 | +| iir | SIN-100Hz--20dBFS | 0.870 | -138.5 dB | PASS | processBlock 0.029 s for 0.200 s of audio: 14.5 % of one core, real-time factor 6.89 +latency 13 samples, Newton failures 0 (rescued 0), max iterations 4 | +| iir | SIN-100Hz--6dBFS | 0.915 | -138.5 dB | PASS | processBlock 0.030 s for 0.200 s of audio: 14.8 % of one core, real-time factor 6.76 +latency 13 samples, Newton failures 0 (rescued 0), max iterations 4 | +| iir | SIN-1000Hz--40dBFS | 0.394 | -138.5 dB | PASS | processBlock 0.035 s for 0.200 s of audio: 17.4 % of one core, real-time factor 5.74 +latency 13 samples, Newton failures 0 (rescued 0), max iterations 7 | +| iir | SIN-1000Hz--20dBFS | 0.863 | -138.5 dB | PASS | processBlock 0.039 s for 0.200 s of audio: 19.7 % of one core, real-time factor 5.09 +latency 13 samples, Newton failures 0 (rescued 0), max iterations 7 | +| iir | SIN-1000Hz--6dBFS | 0.997 | -138.5 dB | PASS | processBlock 0.041 s for 0.200 s of audio: 20.4 % of one core, real-time factor 4.91 +latency 13 samples, Newton failures 0 (rescued 0), max iterations 19 | +| iir | SIN-5000Hz--40dBFS | 0.880 | -138.5 dB | PASS | processBlock 0.055 s for 0.200 s of audio: 27.3 % of one core, real-time factor 3.66 +latency 13 samples, Newton failures 0 (rescued 0), max iterations 8 | +| iir | SIN-5000Hz--20dBFS | 1.020 | -138.5 dB | PASS | processBlock 0.072 s for 0.200 s of audio: 36.1 % of one core, real-time factor 2.77 +latency 13 samples, Newton failures 0 (rescued 0), max iterations 17 | +| iir | SIN-5000Hz--6dBFS | 0.991 | -138.5 dB | PASS | processBlock 0.081 s for 0.200 s of audio: 40.3 % of one core, real-time factor 2.48 +latency 13 samples, Newton failures 0 (rescued 0), max iterations 34 | +| iir | DI-GTR L | 0.998 | -138.5 dB | PASS | processBlock 0.654 s for 5.000 s of audio: 13.1 % of one core, real-time factor 7.64 +latency 13 samples, Newton failures 0 (rescued 0), max iterations 14 | +| iir | DI-GTR LL | 0.889 | -138.5 dB | PASS | processBlock 0.647 s for 5.000 s of audio: 12.9 % of one core, real-time factor 7.72 +latency 13 samples, Newton failures 0 (rescued 0), max iterations 15 | +| iir | DI-GTR R | 0.983 | -138.5 dB | PASS | processBlock 0.644 s for 5.000 s of audio: 12.9 % of one core, real-time factor 7.76 +latency 13 samples, Newton failures 0 (rescued 0), max iterations 16 | +| iir | DI-GTR RR | 0.989 | -138.5 dB | PASS | processBlock 0.686 s for 5.000 s of audio: 13.7 % of one core, real-time factor 7.29 +latency 13 samples, Newton failures 0 (rescued 0), max iterations 15 | diff --git a/harness/reports/M6/20261019_pluginval_au.log b/harness/reports/M6/20261019_pluginval_au.log new file mode 100644 index 0000000..920c2ca --- /dev/null +++ b/harness/reports/M6/20261019_pluginval_au.log @@ -0,0 +1,119 @@ +Started validating: /Users/kimmo/Library/Audio/Plug-Ins/Components/Amp2203.component +Random seed: 0x2371b54 +Validation started +Strictness level: 5 +----------------------------------------------------------------- +Starting tests in: pluginval / Scan for plugins located in: /Users/kimmo/Library/Audio/Plug-Ins/Components/Amp2203.component... +Num plugins found: 1 + +Testing plugin: AudioUnit-Amp2203-f5dabfd3-6333372f +CatastrophicCoder: Amp2203 v0.0.1 +Completed tests in pluginval / Scan for plugins located in: /Users/kimmo/Library/Audio/Plug-Ins/Components/Amp2203.component +----------------------------------------------------------------- +Starting tests in: pluginval / Open plugin (cold)... +Completed tests in pluginval / Open plugin (cold) +----------------------------------------------------------------- +Starting tests in: pluginval / Open plugin (warm)... +Running tests 1 times +Completed tests in pluginval / Open plugin (warm) +----------------------------------------------------------------- +Starting tests in: pluginval / Plugin info... + +Plugin name: Amp2203 +Alternative names: Amp2203 +SupportsDoublePrecision: no +Reported latency: 0 +Reported taillength: 0 +Completed tests in pluginval / Plugin info +----------------------------------------------------------------- +Starting tests in: pluginval / Plugin programs... +Num programs: 1 +All program names checked + +Changing program +!!! WARNING: Current program is -1... Is this correct? +Completed tests in pluginval / Plugin programs +----------------------------------------------------------------- +Starting tests in: pluginval / Editor... +Completed tests in pluginval / Editor +----------------------------------------------------------------- +Starting tests in: pluginval / Open editor whilst processing... +Completed tests in pluginval / Open editor whilst processing +----------------------------------------------------------------- +Starting tests in: pluginval / Audio processing... +Testing with sample rate [44100] and block size [64] +Testing with sample rate [44100] and block size [128] +Testing with sample rate [44100] and block size [256] +Testing with sample rate [44100] and block size [512] +Testing with sample rate [44100] and block size [1024] +Testing with sample rate [48000] and block size [64] +Testing with sample rate [48000] and block size [128] +Testing with sample rate [48000] and block size [256] +Testing with sample rate [48000] and block size [512] +Testing with sample rate [48000] and block size [1024] +Testing with sample rate [96000] and block size [64] +Testing with sample rate [96000] and block size [128] +Testing with sample rate [96000] and block size [256] +Testing with sample rate [96000] and block size [512] +Testing with sample rate [96000] and block size [1024] +Completed tests in pluginval / Audio processing +----------------------------------------------------------------- +Starting tests in: pluginval / Plugin state... +Completed tests in pluginval / Plugin state +----------------------------------------------------------------- +Starting tests in: pluginval / Automation... +Testing with sample rate [44100] and block size [64] and sub-block size [32] +Testing with sample rate [44100] and block size [128] and sub-block size [32] +Testing with sample rate [44100] and block size [256] and sub-block size [32] +Testing with sample rate [44100] and block size [512] and sub-block size [32] +Testing with sample rate [44100] and block size [1024] and sub-block size [32] +Testing with sample rate [48000] and block size [64] and sub-block size [32] +Testing with sample rate [48000] and block size [128] and sub-block size [32] +Testing with sample rate [48000] and block size [256] and sub-block size [32] +Testing with sample rate [48000] and block size [512] and sub-block size [32] +Testing with sample rate [48000] and block size [1024] and sub-block size [32] +Testing with sample rate [96000] and block size [64] and sub-block size [32] +Testing with sample rate [96000] and block size [128] and sub-block size [32] +Testing with sample rate [96000] and block size [256] and sub-block size [32] +Testing with sample rate [96000] and block size [512] and sub-block size [32] +Testing with sample rate [96000] and block size [1024] and sub-block size [32] +Completed tests in pluginval / Automation +----------------------------------------------------------------- +Starting tests in: pluginval / Editor Automation... +Completed tests in pluginval / Editor Automation +----------------------------------------------------------------- +Starting tests in: pluginval / Automatable Parameters... +Completed tests in pluginval / Automatable Parameters +----------------------------------------------------------------- +Starting tests in: pluginval / auval... +auval exited with code: 0 +Completed tests in pluginval / auval +----------------------------------------------------------------- +Starting tests in: pluginval / vst3 validator... +Completed tests in pluginval / vst3 validator +----------------------------------------------------------------- +Starting tests in: pluginval / Basic bus... +Completed tests in pluginval / Basic bus +----------------------------------------------------------------- +Starting tests in: pluginval / Listing available buses... +Inputs: + Named layouts: Mono, Stereo + Discrete layouts: None +Outputs: + Named layouts: Mono, Stereo + Discrete layouts: None +Main bus num input channels: 2 +Main bus num output channels: 2 +Completed tests in pluginval / Listing available buses +----------------------------------------------------------------- +Starting tests in: pluginval / Enabling all buses... +Completed tests in pluginval / Enabling all buses +----------------------------------------------------------------- +Starting tests in: pluginval / Disabling non-main busses... +Completed tests in pluginval / Disabling non-main busses +----------------------------------------------------------------- +Starting tests in: pluginval / Restoring default layout... +Main bus num input channels: 2 +Main bus num output channels: 2 +Completed tests in pluginval / Restoring default layout +SUCCESS diff --git a/harness/reports/M6/20261019_pluginval_vst3.log b/harness/reports/M6/20261019_pluginval_vst3.log new file mode 100644 index 0000000..09ed2b6 --- /dev/null +++ b/harness/reports/M6/20261019_pluginval_vst3.log @@ -0,0 +1,116 @@ +Started validating: /Users/kimmo/Library/Audio/Plug-Ins/VST3/Amp2203.vst3 +Random seed: 0x4227df +Validation started +Strictness level: 5 +----------------------------------------------------------------- +Starting tests in: pluginval / Scan for plugins located in: /Users/kimmo/Library/Audio/Plug-Ins/VST3/Amp2203.vst3... +Num plugins found: 1 + +Testing plugin: VST3-Amp2203-81e3417-bab24cd9 +CatastrophicCoder: Amp2203 v0.0.1 +Completed tests in pluginval / Scan for plugins located in: /Users/kimmo/Library/Audio/Plug-Ins/VST3/Amp2203.vst3 +----------------------------------------------------------------- +Starting tests in: pluginval / Open plugin (cold)... +Completed tests in pluginval / Open plugin (cold) +----------------------------------------------------------------- +Starting tests in: pluginval / Open plugin (warm)... +Running tests 1 times +Completed tests in pluginval / Open plugin (warm) +----------------------------------------------------------------- +Starting tests in: pluginval / Plugin info... + +Plugin name: Amp2203 +Alternative names: Amp2203 +SupportsDoublePrecision: yes +Reported latency: 0 +Reported taillength: 0 +Completed tests in pluginval / Plugin info +----------------------------------------------------------------- +Starting tests in: pluginval / Plugin programs... +Num programs: 0 +All program names checked +Completed tests in pluginval / Plugin programs +----------------------------------------------------------------- +Starting tests in: pluginval / Editor... +Completed tests in pluginval / Editor +----------------------------------------------------------------- +Starting tests in: pluginval / Open editor whilst processing... +Completed tests in pluginval / Open editor whilst processing +----------------------------------------------------------------- +Starting tests in: pluginval / Audio processing... +Testing with sample rate [44100] and block size [64] +Testing with sample rate [44100] and block size [128] +Testing with sample rate [44100] and block size [256] +Testing with sample rate [44100] and block size [512] +Testing with sample rate [44100] and block size [1024] +Testing with sample rate [48000] and block size [64] +Testing with sample rate [48000] and block size [128] +Testing with sample rate [48000] and block size [256] +Testing with sample rate [48000] and block size [512] +Testing with sample rate [48000] and block size [1024] +Testing with sample rate [96000] and block size [64] +Testing with sample rate [96000] and block size [128] +Testing with sample rate [96000] and block size [256] +Testing with sample rate [96000] and block size [512] +Testing with sample rate [96000] and block size [1024] +Completed tests in pluginval / Audio processing +----------------------------------------------------------------- +Starting tests in: pluginval / Plugin state... +Completed tests in pluginval / Plugin state +----------------------------------------------------------------- +Starting tests in: pluginval / Automation... +Testing with sample rate [44100] and block size [64] and sub-block size [32] +Testing with sample rate [44100] and block size [128] and sub-block size [32] +Testing with sample rate [44100] and block size [256] and sub-block size [32] +Testing with sample rate [44100] and block size [512] and sub-block size [32] +Testing with sample rate [44100] and block size [1024] and sub-block size [32] +Testing with sample rate [48000] and block size [64] and sub-block size [32] +Testing with sample rate [48000] and block size [128] and sub-block size [32] +Testing with sample rate [48000] and block size [256] and sub-block size [32] +Testing with sample rate [48000] and block size [512] and sub-block size [32] +Testing with sample rate [48000] and block size [1024] and sub-block size [32] +Testing with sample rate [96000] and block size [64] and sub-block size [32] +Testing with sample rate [96000] and block size [128] and sub-block size [32] +Testing with sample rate [96000] and block size [256] and sub-block size [32] +Testing with sample rate [96000] and block size [512] and sub-block size [32] +Testing with sample rate [96000] and block size [1024] and sub-block size [32] +Completed tests in pluginval / Automation +----------------------------------------------------------------- +Starting tests in: pluginval / Editor Automation... +Completed tests in pluginval / Editor Automation +----------------------------------------------------------------- +Starting tests in: pluginval / Automatable Parameters... +Completed tests in pluginval / Automatable Parameters +----------------------------------------------------------------- +Starting tests in: pluginval / auval... +Completed tests in pluginval / auval +----------------------------------------------------------------- +Starting tests in: pluginval / vst3 validator... +INFO: Skipping vst3 validator as validator path hasn't been set +Completed tests in pluginval / vst3 validator +----------------------------------------------------------------- +Starting tests in: pluginval / Basic bus... +Completed tests in pluginval / Basic bus +----------------------------------------------------------------- +Starting tests in: pluginval / Listing available buses... +Inputs: + Named layouts: Mono, Stereo + Discrete layouts: Discrete #1 +Outputs: + Named layouts: Mono, Stereo + Discrete layouts: Discrete #1 +Main bus num input channels: 2 +Main bus num output channels: 2 +Completed tests in pluginval / Listing available buses +----------------------------------------------------------------- +Starting tests in: pluginval / Enabling all buses... +Completed tests in pluginval / Enabling all buses +----------------------------------------------------------------- +Starting tests in: pluginval / Disabling non-main busses... +Completed tests in pluginval / Disabling non-main busses +----------------------------------------------------------------- +Starting tests in: pluginval / Restoring default layout... +Main bus num input channels: 2 +Main bus num output channels: 2 +Completed tests in pluginval / Restoring default layout +SUCCESS diff --git a/harness/reports/M6/20261019_x2_rates_m7_AB_full2x.md b/harness/reports/M6/20261019_x2_rates_m7_AB_full2x.md new file mode 100644 index 0000000..f394222 --- /dev/null +++ b/harness/reports/M6/20261019_x2_rates_m7_AB_full2x.md @@ -0,0 +1,48 @@ +# M6 A1: full engine (exact J4) at lower internal rates, plugin model check + +- Date 2026-10-19; options: tol 0.0001; rates 48 kHz × [2]. +- Plugin model check (D-051): ESR ≤ -20 dB vs SPICE (cached 384000 Hz references, 48 kHz band, first 5 s of every DI track, first 0.1 s excluded). Unseen settings: the 16 X4 validation settings, 10 s, vs the engine at 16x (first 1 s excluded). Cost: ampwav --stats on 10 s of DI GTR L, one run at a time; oversampling filters not included. + +## Summary + +| Rate | vs SPICE worst (CRUNCH / DRIVE / HOT) | Check | Unseen settings vs 16x (worst / median) | Newton failures (not rescued) / runs, max iterations | Cost default / HOT (% of one core) | +|---|---|---|---|---|---| +| 2x (96 kHz) | -45.3 / -48.5 / -24.0 dB | PASS | -20.8 / -39.0 dB | 0 (0) / 28, 38 | 18.9 / 29.0 | + +## vs SPICE per clip + +| Clip | 2x | +|---|---| +| CRUNCH GTR L | -45.8 dB | +| CRUNCH GTR LL | -45.3 dB | +| CRUNCH GTR R | -46.1 dB | +| CRUNCH GTR RR | -45.7 dB | +| DRIVE GTR L | -48.5 dB | +| DRIVE GTR LL | -49.7 dB | +| DRIVE GTR R | -49.5 dB | +| DRIVE GTR RR | -48.9 dB | +| HOT GTR L | -24.4 dB | +| HOT GTR LL | -25.3 dB | +| HOT GTR R | -25.2 dB | +| HOT GTR RR | -24.0 dB | + +## Aliasing (power outside the harmonics of f0, relative to the total; 16x for reference) + +Each cell: non-harmonic power, then Newton failures (not rescued) in that 2 s run. Non-converged samples add non-harmonic power, so a cell with many failures measures solver error as well as aliasing. + +| Preset | f0 | amplitude (FS) | 16x | 2x | +|---|---|---|---|---| +| CRUNCH | 5000 Hz | 0.1 | -25.3 dB, 0 (0) | -20.5 dB, 0 (0) | +| CRUNCH | 5000 Hz | 0.4 | -24.4 dB, 0 (0) | -17.3 dB, 0 (0) | +| CRUNCH | 9000 Hz | 0.1 | -40.9 dB, 0 (0) | -18.2 dB, 0 (0) | +| CRUNCH | 9000 Hz | 0.4 | -40.0 dB, 0 (0) | -14.9 dB, 0 (0) | +| HOT | 5000 Hz | 0.1 | -25.1 dB, 0 (0) | -16.5 dB, 0 (0) | +| HOT | 5000 Hz | 0.4 | -25.0 dB, 0 (0) | -15.2 dB, 0 (0) | +| HOT | 9000 Hz | 0.1 | -38.8 dB, 0 (0) | -16.4 dB, 0 (0) | +| HOT | 9000 Hz | 0.4 | -33.3 dB, 0 (0) | -15.0 dB, 0 (0) | + +## Cost + +| Rate | ns per engine sample (default / HOT) | % of one core at 48 kHz | +|---|---|---| +| 2x | 1970 / 3021 | 18.9 / 29.0 | diff --git a/harness/reports/M6/20261019_x2_rates_m7_AB_half2x.md b/harness/reports/M6/20261019_x2_rates_m7_AB_half2x.md new file mode 100644 index 0000000..6f529ba --- /dev/null +++ b/harness/reports/M6/20261019_x2_rates_m7_AB_half2x.md @@ -0,0 +1,48 @@ +# M6 A1: full engine (exact J4) at lower internal rates, plugin model check + +- Date 2026-10-19; options: divisor 2, tol 0.0001; rates 48 kHz × [2]. +- Plugin model check (D-051): ESR ≤ -20 dB vs SPICE (cached 384000 Hz references, 48 kHz band, first 5 s of every DI track, first 0.1 s excluded). Unseen settings: the 16 X4 validation settings, 10 s, vs the engine at 16x (first 1 s excluded). Cost: ampwav --stats on 10 s of DI GTR L, one run at a time; oversampling filters not included. + +## Summary + +| Rate | vs SPICE worst (CRUNCH / DRIVE / HOT) | Check | Unseen settings vs 16x (worst / median) | Newton failures (not rescued) / runs, max iterations | Cost default / HOT (% of one core) | +|---|---|---|---|---|---| +| 2x (96 kHz) | -37.7 / -37.2 / -18.9 dB | FAIL | -16.6 / -28.0 dB | 0 (0) / 28, 38 | 13.5 / 21.2 | + +## vs SPICE per clip + +| Clip | 2x | +|---|---| +| CRUNCH GTR L | -38.0 dB | +| CRUNCH GTR LL | -37.7 dB | +| CRUNCH GTR R | -38.9 dB | +| CRUNCH GTR RR | -38.2 dB | +| DRIVE GTR L | -37.2 dB | +| DRIVE GTR LL | -38.3 dB | +| DRIVE GTR R | -38.6 dB | +| DRIVE GTR RR | -37.5 dB | +| HOT GTR L | -18.9 dB | +| HOT GTR LL | -19.6 dB | +| HOT GTR R | -20.0 dB | +| HOT GTR RR | -19.0 dB | + +## Aliasing (power outside the harmonics of f0, relative to the total; 16x for reference) + +Each cell: non-harmonic power, then Newton failures (not rescued) in that 2 s run. Non-converged samples add non-harmonic power, so a cell with many failures measures solver error as well as aliasing. + +| Preset | f0 | amplitude (FS) | 16x | 2x | +|---|---|---|---|---| +| CRUNCH | 5000 Hz | 0.1 | -25.3 dB, 0 (0) | -12.6 dB, 0 (0) | +| CRUNCH | 5000 Hz | 0.4 | -24.4 dB, 0 (0) | -12.1 dB, 0 (0) | +| CRUNCH | 9000 Hz | 0.1 | -40.9 dB, 0 (0) | -9.2 dB, 0 (0) | +| CRUNCH | 9000 Hz | 0.4 | -40.0 dB, 0 (0) | -8.6 dB, 0 (0) | +| HOT | 5000 Hz | 0.1 | -25.1 dB, 0 (0) | -9.0 dB, 0 (0) | +| HOT | 5000 Hz | 0.4 | -25.0 dB, 0 (0) | -10.2 dB, 0 (0) | +| HOT | 9000 Hz | 0.1 | -38.8 dB, 0 (0) | -7.0 dB, 0 (0) | +| HOT | 9000 Hz | 0.4 | -33.3 dB, 0 (0) | -7.1 dB, 0 (0) | + +## Cost + +| Rate | ns per engine sample (default / HOT) | % of one core at 48 kHz | +|---|---|---| +| 2x | 1409 / 2203 | 13.5 / 21.2 | diff --git a/harness/reports/M6/20261019_x2_rates_m7_hst0_full2x.md b/harness/reports/M6/20261019_x2_rates_m7_hst0_full2x.md new file mode 100644 index 0000000..c2a7c2f --- /dev/null +++ b/harness/reports/M6/20261019_x2_rates_m7_hst0_full2x.md @@ -0,0 +1,48 @@ +# M6 A1: full engine (exact J4) at lower internal rates, plugin model check + +- Date 2026-10-19; options: tol 0.0001, trigger 0.0; rates 48 kHz × [2]. +- Plugin model check (D-051): ESR ≤ -20 dB vs SPICE (cached 384000 Hz references, 48 kHz band, first 5 s of every DI track, first 0.1 s excluded). Unseen settings: the 16 X4 validation settings, 10 s, vs the engine at 16x (first 1 s excluded). Cost: ampwav --stats on 10 s of DI GTR L, one run at a time; oversampling filters not included. + +## Summary + +| Rate | vs SPICE worst (CRUNCH / DRIVE / HOT) | Check | Unseen settings vs 16x (worst / median) | Newton failures (not rescued) / runs, max iterations | Cost default / HOT (% of one core) | +|---|---|---|---|---|---| +| 2x (96 kHz) | -45.7 / -51.1 / -22.7 dB | PASS | -19.8 / -37.3 dB | 0 (0) / 28, 38 | 22.3 / 29.6 | + +## vs SPICE per clip + +| Clip | 2x | +|---|---| +| CRUNCH GTR L | -46.2 dB | +| CRUNCH GTR LL | -45.7 dB | +| CRUNCH GTR R | -46.5 dB | +| CRUNCH GTR RR | -46.1 dB | +| DRIVE GTR L | -51.1 dB | +| DRIVE GTR LL | -52.1 dB | +| DRIVE GTR R | -51.9 dB | +| DRIVE GTR RR | -51.7 dB | +| HOT GTR L | -22.7 dB | +| HOT GTR LL | -23.5 dB | +| HOT GTR R | -23.7 dB | +| HOT GTR RR | -22.9 dB | + +## Aliasing (power outside the harmonics of f0, relative to the total; 16x for reference) + +Each cell: non-harmonic power, then Newton failures (not rescued) in that 2 s run. Non-converged samples add non-harmonic power, so a cell with many failures measures solver error as well as aliasing. + +| Preset | f0 | amplitude (FS) | 16x | 2x | +|---|---|---|---|---| +| CRUNCH | 5000 Hz | 0.1 | -25.3 dB, 0 (0) | -19.8 dB, 0 (0) | +| CRUNCH | 5000 Hz | 0.4 | -24.4 dB, 0 (0) | -16.7 dB, 0 (0) | +| CRUNCH | 9000 Hz | 0.1 | -40.9 dB, 0 (0) | -18.2 dB, 0 (0) | +| CRUNCH | 9000 Hz | 0.4 | -40.0 dB, 0 (0) | -14.7 dB, 0 (0) | +| HOT | 5000 Hz | 0.1 | -25.1 dB, 0 (0) | -13.1 dB, 0 (0) | +| HOT | 5000 Hz | 0.4 | -25.0 dB, 0 (0) | -13.2 dB, 0 (0) | +| HOT | 9000 Hz | 0.1 | -38.8 dB, 0 (0) | -14.6 dB, 0 (0) | +| HOT | 9000 Hz | 0.4 | -33.3 dB, 0 (0) | -15.9 dB, 0 (0) | + +## Cost + +| Rate | ns per engine sample (default / HOT) | % of one core at 48 kHz | +|---|---|---| +| 2x | 2318 / 3083 | 22.3 / 29.6 | diff --git a/harness/reports/M6/20261019_x2_rates_m7_hst300_full2x.md b/harness/reports/M6/20261019_x2_rates_m7_hst300_full2x.md new file mode 100644 index 0000000..c836c0a --- /dev/null +++ b/harness/reports/M6/20261019_x2_rates_m7_hst300_full2x.md @@ -0,0 +1,48 @@ +# M6 A1: full engine (exact J4) at lower internal rates, plugin model check + +- Date 2026-10-19; options: tol 0.0001, trigger 300.0; rates 48 kHz × [2]. +- Plugin model check (D-051): ESR ≤ -20 dB vs SPICE (cached 384000 Hz references, 48 kHz band, first 5 s of every DI track, first 0.1 s excluded). Unseen settings: the 16 X4 validation settings, 10 s, vs the engine at 16x (first 1 s excluded). Cost: ampwav --stats on 10 s of DI GTR L, one run at a time; oversampling filters not included. + +## Summary + +| Rate | vs SPICE worst (CRUNCH / DRIVE / HOT) | Check | Unseen settings vs 16x (worst / median) | Newton failures (not rescued) / runs, max iterations | Cost default / HOT (% of one core) | +|---|---|---|---|---|---| +| 2x (96 kHz) | -45.7 / -51.1 / -23.4 dB | PASS | -20.2 / -38.8 dB | 0 (0) / 28, 38 | 22.3 / 30.3 | + +## vs SPICE per clip + +| Clip | 2x | +|---|---| +| CRUNCH GTR L | -46.2 dB | +| CRUNCH GTR LL | -45.7 dB | +| CRUNCH GTR R | -46.5 dB | +| CRUNCH GTR RR | -46.1 dB | +| DRIVE GTR L | -51.1 dB | +| DRIVE GTR LL | -52.1 dB | +| DRIVE GTR R | -51.9 dB | +| DRIVE GTR RR | -51.7 dB | +| HOT GTR L | -23.4 dB | +| HOT GTR LL | -23.9 dB | +| HOT GTR R | -24.8 dB | +| HOT GTR RR | -23.5 dB | + +## Aliasing (power outside the harmonics of f0, relative to the total; 16x for reference) + +Each cell: non-harmonic power, then Newton failures (not rescued) in that 2 s run. Non-converged samples add non-harmonic power, so a cell with many failures measures solver error as well as aliasing. + +| Preset | f0 | amplitude (FS) | 16x | 2x | +|---|---|---|---|---| +| CRUNCH | 5000 Hz | 0.1 | -25.3 dB, 0 (0) | -19.8 dB, 0 (0) | +| CRUNCH | 5000 Hz | 0.4 | -24.4 dB, 0 (0) | -16.7 dB, 0 (0) | +| CRUNCH | 9000 Hz | 0.1 | -40.9 dB, 0 (0) | -18.2 dB, 0 (0) | +| CRUNCH | 9000 Hz | 0.4 | -40.0 dB, 0 (0) | -14.7 dB, 0 (0) | +| HOT | 5000 Hz | 0.1 | -25.1 dB, 0 (0) | -12.3 dB, 0 (0) | +| HOT | 5000 Hz | 0.4 | -25.0 dB, 0 (0) | -12.3 dB, 0 (0) | +| HOT | 9000 Hz | 0.1 | -38.8 dB, 0 (0) | -15.3 dB, 0 (0) | +| HOT | 9000 Hz | 0.4 | -33.3 dB, 0 (0) | -14.8 dB, 0 (0) | + +## Cost + +| Rate | ns per engine sample (default / HOT) | % of one core at 48 kHz | +|---|---|---| +| 2x | 2321 / 3152 | 22.3 / 30.3 | diff --git a/harness/reports/M6/20261019_x2_rates_m7_step1_full2x.md b/harness/reports/M6/20261019_x2_rates_m7_step1_full2x.md new file mode 100644 index 0000000..a4a12ee --- /dev/null +++ b/harness/reports/M6/20261019_x2_rates_m7_step1_full2x.md @@ -0,0 +1,48 @@ +# M6 A1: full engine (exact J4) at lower internal rates, plugin model check + +- Date 2026-10-19; options: tol 0.0001; rates 48 kHz × [2]. +- Plugin model check (D-051): ESR ≤ -20 dB vs SPICE (cached 384000 Hz references, 48 kHz band, first 5 s of every DI track, first 0.1 s excluded). Unseen settings: the 16 X4 validation settings, 10 s, vs the engine at 16x (first 1 s excluded). Cost: ampwav --stats on 10 s of DI GTR L, one run at a time; oversampling filters not included. + +## Summary + +| Rate | vs SPICE worst (CRUNCH / DRIVE / HOT) | Check | Unseen settings vs 16x (worst / median) | Newton failures (not rescued) / runs, max iterations | Cost default / HOT (% of one core) | +|---|---|---|---|---|---| +| 2x (96 kHz) | -45.3 / -48.5 / -24.0 dB | PASS | -20.8 / -39.0 dB | 0 (0) / 28, 38 | 22.2 / 31.7 | + +## vs SPICE per clip + +| Clip | 2x | +|---|---| +| CRUNCH GTR L | -45.8 dB | +| CRUNCH GTR LL | -45.3 dB | +| CRUNCH GTR R | -46.1 dB | +| CRUNCH GTR RR | -45.7 dB | +| DRIVE GTR L | -48.5 dB | +| DRIVE GTR LL | -49.7 dB | +| DRIVE GTR R | -49.5 dB | +| DRIVE GTR RR | -48.9 dB | +| HOT GTR L | -24.4 dB | +| HOT GTR LL | -25.2 dB | +| HOT GTR R | -25.2 dB | +| HOT GTR RR | -24.0 dB | + +## Aliasing (power outside the harmonics of f0, relative to the total; 16x for reference) + +Each cell: non-harmonic power, then Newton failures (not rescued) in that 2 s run. Non-converged samples add non-harmonic power, so a cell with many failures measures solver error as well as aliasing. + +| Preset | f0 | amplitude (FS) | 16x | 2x | +|---|---|---|---|---| +| CRUNCH | 5000 Hz | 0.1 | -25.3 dB, 0 (0) | -20.5 dB, 0 (0) | +| CRUNCH | 5000 Hz | 0.4 | -24.4 dB, 0 (0) | -17.3 dB, 0 (0) | +| CRUNCH | 9000 Hz | 0.1 | -40.9 dB, 0 (0) | -18.2 dB, 0 (0) | +| CRUNCH | 9000 Hz | 0.4 | -40.0 dB, 0 (0) | -14.9 dB, 0 (0) | +| HOT | 5000 Hz | 0.1 | -25.1 dB, 0 (0) | -16.5 dB, 0 (0) | +| HOT | 5000 Hz | 0.4 | -25.0 dB, 0 (0) | -15.2 dB, 0 (0) | +| HOT | 9000 Hz | 0.1 | -38.8 dB, 0 (0) | -16.1 dB, 0 (0) | +| HOT | 9000 Hz | 0.4 | -33.3 dB, 0 (0) | -15.0 dB, 0 (0) | + +## Cost + +| Rate | ns per engine sample (default / HOT) | % of one core at 48 kHz | +|---|---|---| +| 2x | 2311 / 3302 | 22.2 / 31.7 | diff --git a/harness/reports/M6/20261019_x2_rates_m7_step3a_full2x.md b/harness/reports/M6/20261019_x2_rates_m7_step3a_full2x.md new file mode 100644 index 0000000..af04741 --- /dev/null +++ b/harness/reports/M6/20261019_x2_rates_m7_step3a_full2x.md @@ -0,0 +1,48 @@ +# M6 A1: full engine (exact J4) at lower internal rates, plugin model check + +- Date 2026-10-19; options: tol 0.0001; rates 48 kHz × [2]. +- Plugin model check (D-051): ESR ≤ -20 dB vs SPICE (cached 384000 Hz references, 48 kHz band, first 5 s of every DI track, first 0.1 s excluded). Unseen settings: the 16 X4 validation settings, 10 s, vs the engine at 16x (first 1 s excluded). Cost: ampwav --stats on 10 s of DI GTR L, one run at a time; oversampling filters not included. + +## Summary + +| Rate | vs SPICE worst (CRUNCH / DRIVE / HOT) | Check | Unseen settings vs 16x (worst / median) | Newton failures (not rescued) / runs, max iterations | Cost default / HOT (% of one core) | +|---|---|---|---|---|---| +| 2x (96 kHz) | -45.3 / -48.5 / -24.0 dB | PASS | -20.8 / -39.0 dB | 0 (0) / 28, 38 | 21.5 / 29.5 | + +## vs SPICE per clip + +| Clip | 2x | +|---|---| +| CRUNCH GTR L | -45.8 dB | +| CRUNCH GTR LL | -45.3 dB | +| CRUNCH GTR R | -46.1 dB | +| CRUNCH GTR RR | -45.7 dB | +| DRIVE GTR L | -48.5 dB | +| DRIVE GTR LL | -49.7 dB | +| DRIVE GTR R | -49.5 dB | +| DRIVE GTR RR | -48.9 dB | +| HOT GTR L | -24.4 dB | +| HOT GTR LL | -25.2 dB | +| HOT GTR R | -25.2 dB | +| HOT GTR RR | -24.0 dB | + +## Aliasing (power outside the harmonics of f0, relative to the total; 16x for reference) + +Each cell: non-harmonic power, then Newton failures (not rescued) in that 2 s run. Non-converged samples add non-harmonic power, so a cell with many failures measures solver error as well as aliasing. + +| Preset | f0 | amplitude (FS) | 16x | 2x | +|---|---|---|---|---| +| CRUNCH | 5000 Hz | 0.1 | -25.3 dB, 0 (0) | -20.5 dB, 0 (0) | +| CRUNCH | 5000 Hz | 0.4 | -24.4 dB, 0 (0) | -17.3 dB, 0 (0) | +| CRUNCH | 9000 Hz | 0.1 | -40.9 dB, 0 (0) | -18.2 dB, 0 (0) | +| CRUNCH | 9000 Hz | 0.4 | -40.0 dB, 0 (0) | -14.9 dB, 0 (0) | +| HOT | 5000 Hz | 0.1 | -25.1 dB, 0 (0) | -16.5 dB, 0 (0) | +| HOT | 5000 Hz | 0.4 | -25.0 dB, 0 (0) | -15.2 dB, 0 (0) | +| HOT | 9000 Hz | 0.1 | -38.8 dB, 0 (0) | -16.1 dB, 0 (0) | +| HOT | 9000 Hz | 0.4 | -33.3 dB, 0 (0) | -15.0 dB, 0 (0) | + +## Cost + +| Rate | ns per engine sample (default / HOT) | % of one core at 48 kHz | +|---|---|---| +| 2x | 2235 / 3075 | 21.5 / 29.5 | diff --git a/harness/reports/M7/20261019_M7_report.md b/harness/reports/M7/20261019_M7_report.md new file mode 100644 index 0000000..f8a54da --- /dev/null +++ b/harness/reports/M7/20261019_M7_report.md @@ -0,0 +1,57 @@ +# M7 report — CPU optimisation + +Status: for owner review (2026-10-19). Scope (owner, 2026-10-19): lower the plugin's CPU at its defaults, above all at high +Master, toward a 20 % working budget of one core, without loosening a tolerance. Details: `docs/PROGRESS.md` (M7), DECISIONS +D-065 … D-070. + +## Outcome + +| Plugin CPU, 2x, 20 s DI GTR L, FIR (% of one core) | Start (Full) | M7 engine, Full | M7 default (Half, D-069) | +|---|---|---|---| +| Default knobs | 26.6 | 18.5 | 13.1 | +| NAM settings (Preamp 10, Treble 8, Middle 7, Bass 4, Master 10, Presence 8) | 42.7 | 31.1 | 21.6 | +| Every knob 10 | 49.7 | 34.5 | 24.0 | +| Worst knob automation (`M6/20261019_plugin_cpu.md`) | — | — | 24.6 | + +The engine changes (D-065 – D-068, D-070) leave the verified results unchanged (plugin model check at Full identical to +M6). The default Power Amp Rate is now Half (owner decision, D-069): it meets the budget at default knobs and is about 22 – 25 % +at high Master, with HOT at −18.9 dB against the −20 dB target (deviation accepted by the owner) and latency 49 → 58 +samples at 48 kHz. + +## Changes + +| Decision | Change | Effect | +|---|---|---| +| D-065 | Newton keeps its LU factors; Jacobian predictor from them; preamp J4 Thevenin resistance from them | iterations per solve at NAM 3.46 → 2.34 (power amp), 1.41 → 0.66 (preamp); plugin NAM 42.7 → 33.4 % | +| D-066 | fast Wright omega (series / table start + one step, ≤ 1e-14 of the reference), E1^ex via exp / log, no outputs for the preamp's trial solve | engine NAM −8 % | +| D-067 | preamp's second J4 solve → linear load correction when ≤ 10 mV (−115 / −126 dB vs the re-solve) | default knobs −11 % | +| D-068 | linear J4 port out of both Newton systems (9 → 8, 11 → 10 ports) | −2 … −3 % | +| D-069 | plugin default Power Amp Rate Half (Full selectable) | −31 % at NAM; HOT −18.9 dB | +| D-070 | power-amp maxIter 48 (was 32) | 3 Half automation failures after D-068 removed | + +Tried, not adopted: relaxed step halving, EL34 step limits, chord iterations, float LU (no gain), half-steps off / 300 V +(−2 / −1.4 points, HOT and unseen margins smaller), trend guards on the predictor (8 – 18 % slower). + +## Verification (2026-10-19) + +| Check | Result | +|---|---| +| M4 full acceptance 768 kHz; 384 kHz exact J4; HOT | 0 FAIL each | +| Block tests b1234 (M3), b56, b6, b5open (M4) | 0 FAIL (b6's D-043 Newton-median item now passes) | +| Knob jumps b1234 / b56 Presence | −39.1 … −55.1 dB / −62.9 dB | +| Plugin model check, 2x Full (`M6/20261019_x2_rates_m7_AB_full2x.md`) | CRUNCH / DRIVE / HOT −45.3 / −48.5 / −24.0 dB, unseen −20.8 dB, 0 failures: PASS | +| Plugin model check, 2x Half (`M6/20261019_x2_rates_m7_AB_half2x.md`) | −37.7 / −37.2 / −18.9 dB, unseen −16.6 dB, 0 failures: FAIL (accepted, D-069) | +| Automation 96 kHz exact J4 1e-4 V, Half and Full | 56 runs each, 0 non-finite, 0 failures | +| Plugin vs ampwav null test 2x Half and Full | 0 FAIL | +| pluginval strictness 5 (VST3, AU), auval | SUCCESS / SUCCESS / SUCCEEDED (`M6/20261019_*.log`) | +| Unit tests | 65 / 65 | + +Note: at every knob 1.0 the engine's output after ≈ 11 s of the DI clip depends on any perturbation (Newton tolerance +1e-8 vs 1e-10 V: −16 … −18 dB per second, spread around the 100 Hz harmonics; pre-M7 as well), so that setting is compared +by Newton failures and spectra, not sample-wise (D-065). + +## Open + +- High Master at Half is ≈ 22 – 25 %, Full ≈ 31 – 35 %; the remaining cost is mostly the power amp's Newton (≈ 2.3 + iterations per solve, half-steps on ≈ 15 % of samples at 2x). +- Listening check of Half after D-058 / D-059 at the NAM settings (the M6 Full / Half comparison predates them). diff --git a/harness/tolerances.py b/harness/tolerances.py index 76d7750..28d9774 100644 --- a/harness/tolerances.py +++ b/harness/tolerances.py @@ -73,7 +73,7 @@ # per-block overrides of a tolerance: the Newton maximum is "the configured limit", which is 32 for the two roots # of the full amp, the preamp B1234 and the power amp B56 (D-052; was 12 / 16, D-045); every other root has 12 -OVERRIDES = {(b, 'newton_max_iterations'): (32, 'max') for b in ('b56',)} +OVERRIDES = {(b, 'newton_max_iterations'): (48, 'max') for b in ('b56',)} # power amp 48 (D-070) OVERRIDES.update({(b, 'newton_max_iterations'): (64, 'max') for b in ('full', 'b1234', 'preamp')}) # preamp 64 (D-064) diff --git a/plugin/src/PluginProcessor.cpp b/plugin/src/PluginProcessor.cpp index f154ad8..f5485c0 100644 --- a/plugin/src/PluginProcessor.cpp +++ b/plugin/src/PluginProcessor.cpp @@ -47,9 +47,9 @@ juce::AudioProcessorValueTreeState::ParameterLayout Amp2203Processor::layout() { juce::StringArray{"2x", "4x", "8x", "16x"}, 0)); // 2x (D-054) l.add(std::make_unique(juce::ParameterID{"oversamplingFilter", 1}, "Oversampling Filter", juce::StringArray{"Linear phase (FIR)", "Low latency (IIR)"}, 0)); - // power amp at the oversampled rate or at half of it (M6, D-049, D-053; default Full, D-060) + // power amp at the oversampled rate or at half of it (M6, D-049, D-053; default Half, D-069, was Full, D-060) l.add(std::make_unique(juce::ParameterID{"powerAmpRate", 1}, "Power Amp Rate", - juce::StringArray{"Full", "Half"}, 0)); + juce::StringArray{"Full", "Half"}, 1)); return l; } diff --git a/tests/test_newton.cpp b/tests/test_newton.cpp index 73dd155..e1f7a1d 100644 --- a/tests/test_newton.cpp +++ b/tests/test_newton.cpp @@ -136,3 +136,31 @@ TEST_CASE("Newton statistics median", "[newton][m2]") { REQUIRE(s.failures == 1); REQUIRE(s.maxIterations == 12); } + +TEST_CASE("Kept LU factors solve a new right-hand side like a fresh elimination", "[newton][m7]") { + // a fixed, well-conditioned 6×6 matrix that needs row swaps (small leading entries) + Mat<6> A{}; + for (int r = 0; r < 6; ++r) + for (int c = 0; c < 6; ++c) + A[r][c] = std::sin(1.0 + 3.0 * r + 7.0 * c) + (r == (c + 2) % 6 ? 4.0 : 0.0); + amp2203::detail::LuFactors<6> lu; + lu.m = A; + Vec<6> r1{1.0, -2.0, 0.5, 3.0, -1.0, 2.0}; + REQUIRE(amp2203::solver::solveLinear<6>(lu.m, r1, lu.piv)); + lu.valid = true; + const Vec<6> b{0.3, 1.7, -2.2, 0.9, 4.1, -0.6}; + Vec<6> x = b; + amp2203::solver::solveFactored<6>(lu, x); + Mat<6> J = A; + Vec<6> y = b; + REQUIRE(amp2203::solver::solveLinear<6>(J, y)); + for (int k = 0; k < 6; ++k) + CHECK(std::fabs(x[k] - y[k]) <= 1e-13 * (1.0 + std::fabs(y[k]))); + // and A·x = b + for (int r = 0; r < 6; ++r) { + double s = 0.0; + for (int c = 0; c < 6; ++c) + s += A[r][c] * x[c]; + CHECK(std::fabs(s - b[r]) <= 1e-12); + } +} diff --git a/tests/test_triode.cpp b/tests/test_triode.cpp index e8fad96..4493ba2 100644 --- a/tests/test_triode.cpp +++ b/tests/test_triode.cpp @@ -66,7 +66,7 @@ bool jacobianMatches(double analytic, double fd) { TEST_CASE("Wright omega solves w + ln w = z", "[devices][m2]") { for (double z = -60.0; z <= 1e4; z = z < 10.0 ? z + 0.01 : z * 1.01) { - const double w = wrightOmega(z); + const double w = amp2203::devices::wrightOmegaReference(z); REQUIRE(w > 0.0); if (z < -40.0) REQUIRE(std::fabs(w - std::exp(z)) <= 1e-15 * w); @@ -77,6 +77,19 @@ TEST_CASE("Wright omega solves w + ln w = z", "[devices][m2]") { REQUIRE(std::fabs(wrightOmega(0.0) - 0.56714329040978387) < 1e-16); // Omega constant W(1) } +TEST_CASE("Fast Wright omega matches the reference", "[devices][m7]") { + // D-066: series below z = −10, table start + one step above; the residual allows 2 ulp of rounding in w + ln w + double worst = 0.0; + for (double z = -60.0; z <= 1e4; z = z < 50.0 ? z + 1e-3 : z * 1.001) { + const double ref = amp2203::devices::wrightOmegaReference(z); + const double w = wrightOmega(z); + worst = std::max(worst, std::fabs(w - ref) / ref); + if (z >= -10.0) + REQUIRE(std::fabs(w + std::log(w) - z) <= 8e-16 * std::max(1.0, std::fabs(z))); + } + REQUIRE(worst <= 1e-14); +} + TEST_CASE("12AX7 plate curves match SPICE", "[devices][m2]") { const auto rows = readSweep("12ax7_plate.csv"); REQUIRE(rows.size() == 7 * 81); diff --git a/tools/ampwav/src/Cli.cpp b/tools/ampwav/src/Cli.cpp index 66cac6b..280d7a5 100644 --- a/tools/ampwav/src/Cli.cpp +++ b/tools/ampwav/src/Cli.cpp @@ -41,6 +41,17 @@ bool splitPair(const std::string& s, std::string& key, std::string& value) { return true; } +/// Iteration histogram as a JSON array, trailing empty bins dropped. +std::string histogramJson(const amp2203::detail::NewtonStats& st) { + size_t n = st.histogram.size(); + while (n > 1 && st.histogram[n - 1] == 0) + --n; + std::string s = "["; + for (size_t k = 0; k < n; ++k) + s += (k ? ", " : "") + std::to_string(st.histogram[k]); + return s + "]"; +} + void writeStats(const std::string& path, const Options& o, double rate, size_t samples, double seconds, const amp2203::Amp2203::Stats& st) { std::ofstream f(path); @@ -61,6 +72,8 @@ void writeStats(const std::string& path, const Options& o, double rate, size_t s << " \"newton_failures_power\": " << st.powerAmp.failures << ",\n" << " \"max_iterations_preamp\": " << st.preamp.maxIterations << ",\n" << " \"max_iterations_power\": " << st.powerAmp.maxIterations << ",\n" + << " \"histogram_preamp\": " << histogramJson(st.preamp) << ",\n" + << " \"histogram_power\": " << histogramJson(st.powerAmp) << ",\n" << " \"power_rate_divisor\": " << o.powerRateDivisor << ",\n" << " \"newton_residual_tol\": " << o.newtonTol.value_or(1e-9) << ",\n" << " \"exact_j4\": " << (o.exactJ4 ? "true" : "false") << "\n"