refactor(player): E.6 — extract deriveNormalizationSnapshot into module (#569)

`deriveNormalizationSnapshot` — the loudness / replaygain / off branch
that the runtime uses to compute the normalization fields on every track
switch and queue rewrite — moves into src/store/normalizationSnapshot.ts.
File-private, four internal call sites updated to import from the new
module. Adds 8 focused tests pinning each branch (off, loudness with
target LUFS, replaygain enabled with tag + pre-gain, replaygain enabled
with fallback) plus neighbour-track context for album-mode resolution.

playerStore 3470 → 3434 LOC.
This commit is contained in:
Frank Stellmacher
2026-05-12 12:21:51 +02:00
committed by GitHub
parent b68bddd034
commit 81b161a418
3 changed files with 189 additions and 37 deletions
+51
View File
@@ -0,0 +1,51 @@
import { useAuthStore } from './authStore';
import { resolveReplayGainDb } from '../utils/resolveReplayGainDb';
import type { PlayerState, Track } from './playerStore';
/**
* Compute the normalization fields that should land in the next state commit
* when the runtime switches tracks or rewrites the queue. Three branches:
*
* - **loudness** — clear the visible dB until the engine pushes a real
* `audio:normalization-state` event back; surface the user's target LUFS.
* - **replaygain (enabled)** — derive the dB from track/album tags via
* `resolveReplayGainDb`, add the pre-gain, fall back to the configured
* fallback when no tag is available.
* - **off** (or replaygain disabled) — everything null, engine `'off'`.
*/
export function deriveNormalizationSnapshot(
track: Track,
queue: Track[],
queueIndex: number,
): Pick<
PlayerState,
'normalizationNowDb' | 'normalizationTargetLufs' | 'normalizationEngineLive'
> {
const auth = useAuthStore.getState();
const engine = auth.normalizationEngine;
if (engine === 'loudness') {
const target = auth.loudnessTargetLufs;
return {
// Clears stale UI until `audio:normalization-state` / refresh catches up.
normalizationNowDb: null,
normalizationTargetLufs: target,
normalizationEngineLive: 'loudness',
};
}
if (engine === 'replaygain' && auth.replayGainEnabled) {
const prev = queueIndex > 0 ? queue[queueIndex - 1] : null;
const next = queueIndex + 1 < queue.length ? queue[queueIndex + 1] : null;
const resolved = resolveReplayGainDb(track, prev, next, true, auth.replayGainMode);
const nowDb = resolved != null ? (resolved + auth.replayGainPreGainDb) : auth.replayGainFallbackDb;
return {
normalizationNowDb: nowDb,
normalizationTargetLufs: null,
normalizationEngineLive: 'replaygain',
};
}
return {
normalizationNowDb: null,
normalizationTargetLufs: null,
normalizationEngineLive: 'off',
};
}