forked from Keelrleek/SoulStream
loudness: a first play starts three quarters of a decibel under the prefix
The prefix estimator is BIASED, not merely noisy, and the sign is known: a quiet opening under-reads the track, so the head asks for too much gain far more often than too little. Subtracting a constant moves the whole distribution toward the truth, so a first play begins nearer its real level and the correction that follows has less ground to cover. Measured by a new `head_start_offset_sweep` over 1046 tracks sampled across 182 artists — at most six per artist, spread across each discography rather than taken from the front, since openers are where intros are least typical and intros are the whole source of the bias. The cached corpus it replaces was a dozen artists and put the median at +3.48 dB. Sampled diversely it is +1.55, so the old number was overstating the bias by more than double. The size comes from the MARGINAL trade, not the smallest mean error: step loud fixed quiet created ratio 0.0 -> 0.5 -89 +41 2.2 : 1 0.5 -> 1.0 -83 +85 1.0 : 1 1.0 -> 1.5 -61 +132 0.5 : 1 Only the first step is clearly worth taking. mean|err| bottoms out at 1.5 dB instead and is the wrong objective: it weights an error that begins decaying at once the same as one that waits for Analysis::Complete, which on the streamed first plays this path exists for is download-bound and therefore late. A constant is deliberately a weak lever. The per-artist median error runs from -4.58 dB (Boards Of Canada) to +7.03 dB (Ponytail); no single number fits everyone, and an offset shifts that spread without narrowing it. The levers that would are letting the follow recover UPWARD, and predicting the offset per track from the prefix. Both still open. The overlay reports it as a MOVE — what the prefix asked for, where the track actually started, and a ghost tick on the dB scale at the prefix position, so the gap to the applied gain IS the head start. Gated on `analyzing`: at Analysis::Complete the player releases to the whole-track gain while loudness.gainDb goes on reporting the prefix value, which it keeps for the next play. Without that condition the line advertised a correction that had already been released.
This commit is contained in:
1 parent
4179247203
commit
52f5878297
2 files changed
+402
-6
No files matched your search
@@ -106,6 +106,25 @@
|
||||
);
|
||||
/** Position on a −15…+9 dB scale — the clamp `set_norm_gain_db` enforces. */
|
||||
const dbPct = (db) => Math.max(0, Math.min(100, ((db + 15) / 24) * 100));
|
||||
/**
|
||||
* The head start is only meaningful while a prefix-derived gain is the one
|
||||
* in force, and only visible as the gap between what the prefix asked for
|
||||
* and where the track actually started — so both ends are required before
|
||||
* it is drawn at all. A stale or absent value must read as "not applied"
|
||||
* rather than as zero decibels of correction.
|
||||
*
|
||||
* `analyzing` is the third condition and not an optional one. At
|
||||
* `Analysis::Complete` the backend retires the cut-only rule and slews to
|
||||
* the whole-track gain, so the head start stops being what the player is
|
||||
* doing — while `loudness.gainDb` still reports the prefix value, being
|
||||
* kept for the NEXT play. Without this the line goes on claiming a
|
||||
* correction that has already been released.
|
||||
*/
|
||||
const headStart = $derived(
|
||||
loud?.headStartDb > 0 && loud?.prefixGainDb != null && loud?.analyzing
|
||||
? { db: loud.headStartDb, from: loud.prefixGainDb }
|
||||
: null,
|
||||
);
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={onKeydown} />
|
||||
@@ -170,8 +189,28 @@
|
||||
<span class="dim">settled</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if headStart}
|
||||
<!-- The correction as a MOVE: where the prefix alone
|
||||
would have started this play, and where it actually
|
||||
did. A single number cannot show a bias correction
|
||||
working. -->
|
||||
<div class="dim">
|
||||
head start <span class="num hot"
|
||||
>−{headStart.db.toFixed(2)} dB</span
|
||||
>
|
||||
· prefix asked {fmtDb(headStart.from)} → started
|
||||
{fmtDb(headStart.from - headStart.db)}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="bar slew">
|
||||
<!-- −15…+9 dB is the clamp the player enforces. -->
|
||||
{#if headStart}
|
||||
<i
|
||||
class="prefix"
|
||||
style="left:{dbPct(headStart.from)}%"
|
||||
title="where the prefix alone would have started"
|
||||
></i>
|
||||
{/if}
|
||||
<i class="now" style="left:{dbPct(stats.normNowDb)}%"></i>
|
||||
<i class="tgt" style="left:{dbPct(stats.normTargetDb)}%"></i>
|
||||
<i class="zero" style="left:{dbPct(0)}%"></i>
|
||||
@@ -278,6 +317,14 @@
|
||||
}
|
||||
.bar.slew .now { background: #7fd0ff; }
|
||||
.bar.slew .tgt { background: #ffc46b; }
|
||||
/* Where the prefix alone would have started: a ghost, because it is the
|
||||
counterfactual rather than a gain anything is heading for. The distance
|
||||
from here to .now IS the head start. */
|
||||
.bar.slew .prefix {
|
||||
background: rgba(255, 255, 255, 0.28);
|
||||
height: 11px;
|
||||
top: -2px;
|
||||
}
|
||||
/* Unity, so a cut and a boost are told apart at a glance. */
|
||||
.bar.slew .zero {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
|
||||
Reference in new issue
Block a user