Merges Renovate's kit 3 (PR #149) and adapter-static 4 (PR #148) bumps, plus the migration they need. The mechanical part is `sv migrate sveltekit-3`, run one task at a time and reviewed: - svelte.config.js is gone. Its options move into sveltekit() in vite.config.ts, exported as kitOptions so vitest.config.ts runs the same kit setup, including the $test-utils alias the tests import. - $lib becomes #lib through package.json "imports". There is no src/lib/index, so only the "#lib/*" entry is kept. - tsconfig extends $app/tsconfig. - Peer floors raised to kit 3's requirements: svelte ^5.57.1, vite ^8.0.12, svelte-check ^4.7.5. By hand, from the codemod's list of non-automated tasks: - goto's replaceState option is now replace; keepFocus becomes reset: false. For the search typeahead, reset: false also stops the scroll-to-top, which is wanted while typing. - The test setup mocks drop pushState/replaceState and $app/paths base/assets, which kit 3 removed, and mock refreshAll in place of invalidateAll. - The other flagged files only read page.url or goto internal routes, so they needed no change. TypeScript goes to ^6, not the ^7 Renovate offers: kit 3 declares typescript ^6 as a peer and svelte-check 4.7 accepts ^5 || ^6. Move to 7 once both accept it. With Tailwind 4 and kit 3 in, `npm audit` on the whole tree reports 0, so the web lane now audits every dependency rather than only what ships to browsers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
2.7 KiB
TypeScript
69 lines
2.7 KiB
TypeScript
// Loudness normalization math for the web player (M464 #4999). Pure, so the
|
|
// rules are tested without an <audio> element or an AudioContext.
|
|
|
|
import type { NormalizationPrefs, ReplayGain } from '#lib/api/normalization.js';
|
|
import type { TrackRef } from '#lib/api/types.js';
|
|
|
|
// The server's gains are to the ReplayGain 2.0 reference.
|
|
const REFERENCE_LUFS = -18;
|
|
// Headroom mode raises a quiet track only until its true peak reaches this.
|
|
export const PEAK_CEILING_DBTP = -1;
|
|
// No track is raised more than this, whatever its measurement says: a
|
|
// near-silent track would otherwise come out as amplified noise.
|
|
export const MAX_BOOST_DB = 12;
|
|
|
|
/**
|
|
* Whether the track at `index` is being played as part of its album, in
|
|
* order: a neighbour in the queue is from the same album and sits on the
|
|
* right side of it. That is when album gain keeps the album's own dynamics
|
|
* (a quiet intro stays quiet); anywhere else track gain levels the mix.
|
|
*/
|
|
export function playingAsAlbum(queue: TrackRef[], index: number): boolean {
|
|
const cur = queue[index];
|
|
const curOrder = cur ? order(cur) : null;
|
|
if (!cur || curOrder === null) return false;
|
|
const inOrder = (n: TrackRef | undefined, before: boolean): boolean => {
|
|
if (!n || n.album_id !== cur.album_id) return false;
|
|
const o = order(n);
|
|
if (o === null) return false;
|
|
return before ? o < curOrder : o > curOrder;
|
|
};
|
|
return inOrder(queue[index - 1], true) || inOrder(queue[index + 1], false);
|
|
}
|
|
|
|
// Disc-major track order. A track with no number has no place in the order
|
|
// and is never evidence of album play.
|
|
function order(t: TrackRef): number | null {
|
|
if (t.track_number == null) return null;
|
|
return (t.disc_number ?? 1) * 1000 + t.track_number;
|
|
}
|
|
|
|
/**
|
|
* The gain to apply, in dB, for one track. 0 when leveling is off or the
|
|
* track has not been measured yet: an unmeasured track plays as mastered.
|
|
*/
|
|
export function gainDb(
|
|
prefs: NormalizationPrefs,
|
|
g: ReplayGain | undefined,
|
|
asAlbum: boolean
|
|
): number {
|
|
if (prefs.mode === 'off' || !g) return 0;
|
|
const wantAlbum = prefs.mode === 'album' || (prefs.mode === 'auto' && asAlbum);
|
|
// Album gain falls back to track gain while the album is still being
|
|
// measured; track gain never falls back to album gain.
|
|
const useAlbum = wantAlbum && g.album_gain !== undefined;
|
|
const gain = useAlbum ? g.album_gain : g.track_gain;
|
|
const peak = useAlbum ? g.album_peak : g.track_peak;
|
|
if (gain === undefined) return 0;
|
|
|
|
let db = gain + (prefs.target_lufs - REFERENCE_LUFS);
|
|
if (prefs.boost === 'headroom' && peak !== undefined && peak > 0) {
|
|
db = Math.min(db, PEAK_CEILING_DBTP - 20 * Math.log10(peak));
|
|
}
|
|
return Math.min(db, MAX_BOOST_DB);
|
|
}
|
|
|
|
export function dbToLinear(db: number): number {
|
|
return Math.pow(10, db / 20);
|
|
}
|