Files
SoulStream/crates
KeelrLeek 1e35c5e039 loudness: correct the gain during the track instead of only guessing at its start
A streamed first play is gained from a prefix; every later play is gained
from the whole track. Measured across the 89 cached tracks, a 10s head asks
for a median 3.4 dB too much gain and is more than 1 dB hot on 68 of them —
that gap is the "first track is so much louder" complaint. The curve never
flattens either: even a 60s head is still ~1 dB out with a 6 dB worst case,
so no window size fixes it.

Live traces then showed something worse. The hard deadline was anchored to
the play REQUEST, so on a peer that took a moment to start sending, the 2.5s
ran out before a single gating block existed and the track began at unity —
no normalization at all, three plays out of four on one run. Releasing early
bought nothing in exchange: playback is blocked on the same bytes, so the
gate was discarding the measurement without making sound arrive sooner.

- The deadline now starts at the first byte, with an absolute 4s ceiling from
  the request so a slow peer cannot hold audio that is ready to play.
- The audio thread's slew drops 2.5 -> 0.25 dB/s. Mid-track correction was
  switched off in July because the glide was audible; at a tenth the rate it
  is not, and being 3 dB wrong for a whole track is the larger error.
- The correction follows the running measurement from its first publish and
  may only ratchet DOWN until the track completes. A convergence test lived
  here and was deleted: an integrated average over 30s cannot move much when
  one more second is added, so "3 publishes within 0.3 dB" was measuring the
  inertia of a long average, fired at its floor every time, and declared
  settled while 1.5 and 2.1 dB from truth. The protection was never the gate
  — it is the slew rate and the ratchet.
- Seeking lands the pending correction immediately. The slew is slow only
  because a gain change is audible against continuous music, and a seek is
  already a discontinuity.

Observability: SOULSTREAM_LOUDNESS_TRACE=1 reports every commit with the
seconds analyzed, which limit fired, the hold, and the bytes received; the
debug overlay gains a SLEW line showing applied gain, target, distance and
ETA, because one number cannot distinguish "settled" from "in flight".

Also adds an #[ignore] corpus harness that sweeps head-window bias in 30s.
2026-07-29 18:45:43 -05:00
..