mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
feat(playback): loudness bind rules, analysis seeding, and normalization UI
Resolve integrated loudness from SQLite only at decode bind; keep pre-trim until a row exists, then allow provisional gain from live updates. Pass DB-stable hints from the web app into play and gapless preload. Default pre-measurement attenuation -4.5 dB with an icon reset in Settings; drop redundant normalization copy and shorten pre-trim descriptions. analysis_cache seed_from_bytes returns an outcome and skips redundant waveform work on cache hits; wire callers and related frontend/backend glue.
This commit is contained in:
+41
-2
@@ -22,6 +22,27 @@ export type SeekbarStyle = 'waveform' | 'linedot' | 'bar' | 'thick' | 'segmented
|
||||
export type LoggingMode = 'off' | 'normal' | 'debug';
|
||||
export type NormalizationEngine = 'off' | 'replaygain' | 'loudness';
|
||||
|
||||
/** Integrated-loudness target presets (Settings + analysis). */
|
||||
export type LoudnessLufsPreset = -16 | -14 | -12 | -10;
|
||||
|
||||
const LOUDNESS_LUFS_PRESETS: LoudnessLufsPreset[] = [-16, -14, -12, -10];
|
||||
|
||||
/** Settings default + Rust engine cold default until `audio_set_normalization` runs. */
|
||||
export const DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB = -4.5;
|
||||
|
||||
function sanitizeLoudnessLufsPreset(v: unknown, fallback: LoudnessLufsPreset): LoudnessLufsPreset {
|
||||
return (LOUDNESS_LUFS_PRESETS as readonly number[]).includes(v as number)
|
||||
? (v as LoudnessLufsPreset)
|
||||
: fallback;
|
||||
}
|
||||
|
||||
function clampLoudnessPreAnalysisAttenuationDb(v: unknown): number {
|
||||
const n = typeof v === 'number' ? v : Number(v);
|
||||
if (!Number.isFinite(n)) return DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB;
|
||||
const stepped = Math.round(n * 2) / 2;
|
||||
return Math.max(-24, Math.min(0, stepped));
|
||||
}
|
||||
|
||||
export type LyricsSourceId = 'server' | 'lrclib' | 'netease';
|
||||
export interface LyricsSourceConfig { id: LyricsSourceId; enabled: boolean; }
|
||||
|
||||
@@ -51,7 +72,9 @@ interface AuthState {
|
||||
customGenreBlacklist: string[];
|
||||
replayGainEnabled: boolean;
|
||||
normalizationEngine: NormalizationEngine;
|
||||
loudnessTargetLufs: -16 | -14 | -12 | -10;
|
||||
loudnessTargetLufs: LoudnessLufsPreset;
|
||||
/** dB: extra quieting until loudness is saved; also seeds streaming rough-guess ramp. */
|
||||
loudnessPreAnalysisAttenuationDb: number;
|
||||
replayGainMode: 'track' | 'album' | 'auto';
|
||||
replayGainPreGainDb: number; // added to RG gain for tagged files (0…+6 dB)
|
||||
replayGainFallbackDb: number; // gain for untagged files / radio (-6…0 dB)
|
||||
@@ -212,7 +235,9 @@ interface AuthState {
|
||||
setCustomGenreBlacklist: (v: string[]) => void;
|
||||
setReplayGainEnabled: (v: boolean) => void;
|
||||
setNormalizationEngine: (v: NormalizationEngine) => void;
|
||||
setLoudnessTargetLufs: (v: -16 | -14 | -12 | -10) => void;
|
||||
setLoudnessTargetLufs: (v: LoudnessLufsPreset) => void;
|
||||
setLoudnessPreAnalysisAttenuationDb: (v: number) => void;
|
||||
resetLoudnessPreAnalysisAttenuationDbDefault: () => void;
|
||||
setReplayGainMode: (v: 'track' | 'album' | 'auto') => void;
|
||||
setReplayGainPreGainDb: (v: number) => void;
|
||||
setReplayGainFallbackDb: (v: number) => void;
|
||||
@@ -326,6 +351,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
replayGainEnabled: false,
|
||||
normalizationEngine: 'off',
|
||||
loudnessTargetLufs: -12,
|
||||
loudnessPreAnalysisAttenuationDb: DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB,
|
||||
replayGainMode: 'auto',
|
||||
replayGainPreGainDb: 0,
|
||||
replayGainFallbackDb: 0,
|
||||
@@ -462,6 +488,13 @@ export const useAuthStore = create<AuthState>()(
|
||||
set({ loudnessTargetLufs: v });
|
||||
usePlayerStore.getState().updateReplayGainForCurrentTrack();
|
||||
},
|
||||
setLoudnessPreAnalysisAttenuationDb: (v) => {
|
||||
set({ loudnessPreAnalysisAttenuationDb: clampLoudnessPreAnalysisAttenuationDb(v) });
|
||||
},
|
||||
resetLoudnessPreAnalysisAttenuationDbDefault: () => {
|
||||
set({ loudnessPreAnalysisAttenuationDb: DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB });
|
||||
usePlayerStore.getState().updateReplayGainForCurrentTrack();
|
||||
},
|
||||
setReplayGainMode: (v) => {
|
||||
set({ replayGainMode: v });
|
||||
usePlayerStore.getState().updateReplayGainForCurrentTrack();
|
||||
@@ -696,6 +729,10 @@ export const useAuthStore = create<AuthState>()(
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
const st = state as { loudnessTargetLufs?: unknown; loudnessPreAnalysisAttenuationDb?: unknown };
|
||||
const targetSan = sanitizeLoudnessLufsPreset(st.loudnessTargetLufs, -12);
|
||||
const preSan = clampLoudnessPreAnalysisAttenuationDb(st.loudnessPreAnalysisAttenuationDb);
|
||||
|
||||
useAuthStore.setState({
|
||||
mixMinRatingSong: clampMixFilterMinStars(state.mixMinRatingSong as number),
|
||||
mixMinRatingAlbum: clampMixFilterMinStars(state.mixMinRatingAlbum as number),
|
||||
@@ -703,6 +740,8 @@ export const useAuthStore = create<AuthState>()(
|
||||
skipStarManualSkipCountsByKey: sanitizeSkipStarCounts(
|
||||
(state as { skipStarManualSkipCountsByKey?: unknown }).skipStarManualSkipCountsByKey,
|
||||
),
|
||||
loudnessTargetLufs: targetSan,
|
||||
loudnessPreAnalysisAttenuationDb: preSan,
|
||||
...conflictingLegacyState,
|
||||
...lyricsSourcesMigrated,
|
||||
...wheelSmoothOneTime,
|
||||
|
||||
Reference in New Issue
Block a user