Skip to content

Divide the sum loss before smoothing it, not after - #76

Merged
DIMOSUS merged 2 commits into
mainfrom
sum-loss-smooth-the-ratio
Aug 12, 2026
Merged

Divide the sum loss before smoothing it, not after#76
DIMOSUS merged 2 commits into
mainfrom
sum-loss-smooth-the-ratio

Conversation

@DIMOSUS

@DIMOSUS DIMOSUS commented Aug 12, 2026

Copy link
Copy Markdown
Owner

The Virtual DSP "Sum loss" curve drew dips at 75 Hz and 170 Hz in the
psychoacoustic mode that the unsmoothed curve does not contain. Both sat on
crossover corners (70 Hz sub/bass, 180 Hz bass/mid), and the 75 Hz one is
entirely manufactured: the same session measures −0.19 dB of true loss there.

Why

The loss is 20log10(|ΣH|) − 20log10(Σ|H|), and both operands were display-
smoothed before the subtraction. That does not commute. A fractional-octave
window straddling a steep skirt pulls the rolling-off channel up toward its own
passband — several dB on a 36 dB/octave slope, and more again under the
psychoacoustic mode's peak-weighted cubic mean — while the flat sum barely
moves. Measured on the same session, at the 70 Hz corner the bass channel reads
−21.6 dB unsmoothed and −16.4 dB smoothed (+5.2 dB) against +1.1 dB for the sum,
so Σ|H| inflates faster than |ΣH| and the difference is drawn as a dip.

The plain 1/N modes hide it in the bass only because their window clamps to the
two-bin minimum down there and effectively does nothing; the psychoacoustic mode
is 1/3 octave below 100 Hz, which is where the artifact appears.

What changed

  • SumLossCurve takes the smoothing width and applies it to the finished ratio;
    its operands must now be unsmoothed, and the doc says why.
  • New DataHelper.SmoothRatioLevels: a plain arithmetic mean of decibels on the
    display grid (box for a fixed width, Gaussian for the psychoacoustic one). The
    psychoacoustic mode keeps its frequency-dependent WIDTH and loses only the
    cubic magnitude weighting, which biases a ratio. Non-finite points stay gaps
    and are excluded from their neighbours' means.
  • AverageSumLossDb / MinimumSumLossDb read a curve that was already built
    instead of deriving their own, so the read-out and the drawn trace are the
    same numbers by construction, smoothing included.
  • GetGatedPrimarySpectrumPair returns the display and unsmoothed curves from
    one gate and one FFT (the same instance when smoothing is off), so the honest
    order costs one extra resample per curve, not a second gated FFT.
  • Same defect, same fix in the Compare view's Complex Sum Loss and in the
    overlay slot that stores it.

Effect on the reported session (psychoacoustic mode)

f, Hz before after unsmoothed
75 −1.63 −0.22 −0.19
170 −2.16 −0.73 −1.25
180 −1.60 −0.74 −0.27
1500 −1.51 −0.80 −0.01

Per-junction read-outs: sub/bass avg −0.70 → −0.24, dip −1.63 → −0.67;
bass/mid avg −0.83 → −0.46, dip −2.19 → −0.74. The real 170 Hz cancellation
survives, widened and shallowed by the smoothing as a narrow notch should be;
the invented corner dips are gone. The smoothing selector no longer moves the
measured loss by half a dB, so it can no longer disagree with Auto delay's own
prediction, which is computed on a fixed internal grid.

Tests

  • SumLossCurve_SmoothsTheRatio_NotTheOperands — an ideal complementary 70 Hz /
    48 dB/octave crossover, in phase, whose sum is flat 0 dB: the honest answer is
    0 dB at every frequency. The shipped order returns it; the operand-first order
    invents a −0.87 dB dip at the corner, so the test fails on the old code.
  • SmoothRatioLevels_AveragesDecibelsWithoutTheMagnitudeBias — a −6 dB step
    smooths toward the arithmetic middle (a power mean would read ≈ −2.0 dB), and
    gaps neither spread nor fill.
  • Full solution suite green (2079 tests).

Verified end to end against the reporting session's own measurements through a
scratchpad harness that replays the panel's pipeline (chain application, shared
anchor, 5/50/20 ms fixed gate) on the four left-side channels.

🤖 Generated with Claude Code

DIMOSUS and others added 2 commits August 12, 2026 10:04
The Virtual DSP and Compare sum-loss curves smoothed the channel and sum
magnitudes and then subtracted them. That order does not commute: a
fractional-octave window straddling a steep crossover skirt pulls that channel
up toward its own passband — several dB on a 36 dB/octave slope, more under the
psychoacoustic cubic mean — while the flat sum barely moves, so the magnitude
sum inflates faster than the complex sum and the curve draws a dip at every
corner. A measured 70 Hz junction losing a true 0.2 dB read as 1.6 dB.

Build the loss out of the unsmoothed pair (both widths now come from one gate
and one FFT) and smooth the finished ratio with a plain mean of decibels — the
psychoacoustic width is kept, its magnitude weighting dropped, since a ratio is
not a level. The per-junction avg/dip read-outs consume that same curve instead
of re-deriving it, so the label and the trace cannot disagree. Same fix for the
Compare view's Complex Sum Loss and its overlay slot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Complex Sum Loss overlay slot smoothed twice: the pipeline baked the FR
plot's width into the ratio, then the slot smoothed the result again with its
own. Switching the main plot's smoothing moved a slot set to Off, and 1/6 on
both meant two 1/6 passes. The slot's pass also had to run with the magnitude
weighting disabled — correct for a ratio, but that switch turns the whole
psychoacoustic mode off inside the overlay smoother, collapsing its 1/3-octave
bass window to a fixed 1/6 and its Gaussian to a cosine, so the overlay drew a
different bass than the Compare curve it mirrors.

Hand the slot's width down to TryBuildComplexSumLossCurve instead (the Compare
view keeps passing its own, as its default) and drop the second pass: the ratio
is smoothed once, by SmoothRatioLevels, at the width whose control the user
just moved. The complex SUM overlay is a magnitude curve and keeps inheriting
the plot's pipeline as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@DIMOSUS

DIMOSUS commented Aug 12, 2026

Copy link
Copy Markdown
Owner Author

Both findings confirmed against the code and fixed in 0748705.

1. Double smoothing / inherited plot width — real. TryBuildComplexSumLossCurve baked frequencyResponseOptions.SmoothingInverseOctaves into the ratio and BuildComplexSumPoints then ran SmoothByOctaves with the slot's width on top. (The inheritance predates this PR — the operands were built with the plot's options before — but the PR was the moment to stop paying for it.)

2. Psychoacoustic width collapsing to a fixed 1/6 — real. psychoacousticMagnitude: false does not just drop the cubic weighting inside OverlayMath.SmoothByOctaves; it sets psychoacoustic false outright, so PsychoacousticCode decodes to the 1/6 base width and the kernel becomes the cosine window. The overlay's bass would have diverged from the Compare curve it mirrors.

Fixed exactly as suggested: TryBuildComplexSumLossCurve takes double? smoothingInverseOctaves (null = the plot's own, which is what the Compare curve wants), BuildComplexSumOverlayPoints passes it through, and the overlay hands down settings.SmoothingInverseOctaves and no longer re-smooths the loss. One pass, through SmoothRatioLevels, at the width whose control the user moved — variable bandwidth and Gaussian kernel included. The complex SUM overlay is a magnitude curve and keeps inheriting the plot's pipeline as before; only the ratio changes hands.

Tests added, both as requested:

  • ComplexSumLoss_WithAnExplicitWidth_IgnoresThePlotsOwnSmoothing — with the slot asking for Off, flipping the plot between psychoacoustic and 1/6 leaves the curve identical to 12 decimals (this catches a regression on either side: a re-baked ratio, or operands built smoothed again), while the width that IS asked for still visibly acts.
  • SmoothRatioLevels_KeepsThePsychoacousticBandwidth — a one-point notch at 50 Hz smooths measurably shallower under the psychoacoustic mode than under a fixed 1/6 octave, and the two agree at 4 kHz where the schedule has converged.

Full solution suite green: 2081 tests.

@DIMOSUS
DIMOSUS merged commit c7960bb into main Aug 12, 2026
1 check passed
@DIMOSUS
DIMOSUS deleted the sum-loss-smooth-the-ratio branch August 12, 2026 07:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant