fix(loudness): target sync, effective pre-analysis trim, and queue/settings copy (#333)

* fix(loudness): target sync, -14 pre-analysis ref, queue UI, and reseed

- Front: coalesce loudness refresh by target LUFS; replay-gain IPC dedupe keys
  include norm target and effective pre-attenuation so TGT changes apply.
- Rust: placeholder gain before integrated LUFS uses pivot at -14 LUFS; UI gain
  from effective trim; reseed loudness after delete when waveform cache would skip.
- Pre-analysis: store attenuation relative to -14 LUFS; engine and UI use an
  offset for other targets; migrate legacy absolute values on rehydrate.
- Queue/Settings: Loudness/TGT labels vs value buttons; styles; i18n for help.

* fix(i18n): simplify loudness pre-analysis helper copy

Remove reference-target wording from loudness pre-analysis helper text and keep
only the effective adjustment shown for the current LUFS target in all locales.
This commit is contained in:
cucadmuh
2026-04-27 02:54:58 +03:00
committed by GitHub
parent cf09fd4bd3
commit 87373edb17
18 changed files with 299 additions and 69 deletions
+22 -5
View File
@@ -29,6 +29,8 @@ import NowPlayingInfo from './NowPlayingInfo';
import { TFunction } from 'i18next';
import OverlayScrollArea from './OverlayScrollArea';
import { useLuckyMixStore } from '../store/luckyMixStore';
import { loudnessGainPlaceholderUntilCacheDb } from '../utils/loudnessPlaceholder';
import { effectiveLoudnessPreAnalysisAttenuationDb } from '../utils/loudnessPreAnalysisSlider';
function formatTime(seconds: number): string {
if (!seconds || isNaN(seconds)) return '0:00';
@@ -343,6 +345,7 @@ function QueuePanelHostOrSolo() {
const reanalyzeLoudnessForTrack = usePlayerStore(s => s.reanalyzeLoudnessForTrack);
const authLoudnessTargetLufs = useAuthStore(s => s.loudnessTargetLufs);
const setLoudnessTargetLufs = useAuthStore(s => s.setLoudnessTargetLufs);
const loudnessPreAnalysisAttenuationDb = useAuthStore(s => s.loudnessPreAnalysisAttenuationDb);
useEffect(() => {
if (!showCrossfadePopover) return;
@@ -627,9 +630,23 @@ function QueuePanelHostOrSolo() {
const baseLine = baseParts.join(' · ');
const rgLine = rgParts.join(' · ');
const isLoudnessActive = normalizationEngine === 'loudness' || normalizationEngineLive === 'loudness';
const liveGainLabel = normalizationNowDb != null
? `${normalizationNowDb >= 0 ? '+' : ''}${normalizationNowDb.toFixed(2)} dB`
: '';
const liveGainLabel = (() => {
if (normalizationNowDb != null && Number.isFinite(normalizationNowDb)) {
return `${normalizationNowDb >= 0 ? '+' : ''}${normalizationNowDb.toFixed(2)} dB`;
}
if (isLoudnessActive && Number.isFinite(loudnessPreAnalysisAttenuationDb)) {
const preEff = effectiveLoudnessPreAnalysisAttenuationDb(
loudnessPreAnalysisAttenuationDb,
authLoudnessTargetLufs,
);
const ph = loudnessGainPlaceholderUntilCacheDb(
authLoudnessTargetLufs,
preEff,
);
return `${ph >= 0 ? '+' : ''}${ph.toFixed(2)} dB`;
}
return '—';
})();
const tgtNum = normalizationTargetLufs ?? authLoudnessTargetLufs;
const targetLabel = `${tgtNum} LUFS`;
if (!baseLine && !rgLine && !playbackSource) return null;
@@ -696,13 +713,14 @@ function QueuePanelHostOrSolo() {
{' · '}
<button
type="button"
className="queue-current-tech-metric"
className="queue-current-tech-metric queue-current-tech-metric--lufs-reanalyze"
onClick={e => {
e.stopPropagation();
setLufsTgtOpen(false);
void reanalyzeLoudnessForTrack(currentTrack.id);
}}
data-tooltip="Clear cached loudness and re-analyze this track"
aria-label="Clear cached loudness and re-analyze this track"
>
{liveGainLabel}
</button>
@@ -749,7 +767,6 @@ function QueuePanelHostOrSolo() {
e.stopPropagation();
if (v !== authLoudnessTargetLufs) {
setLoudnessTargetLufs(v);
void reanalyzeLoudnessForTrack(currentTrack.id);
}
setLufsTgtOpen(false);
}}