mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
86b13dd4d0
`invokeAudioSetNormalizationDeduped` (450 ms window for `audio_set_normalization`) and `invokeAudioUpdateReplayGainDeduped` (250 ms window for `audio_update_replay_gain`, with LUFS-target / pre-trim implicitly contributing to the dedupe key) move into `src/store/normalizationIpcDedupe.ts` along with their four "last-invoked" mutables. File-private throughout; three internal call sites become plain imports. 11 focused tests cover the window-boundary behaviour, the payload-field sensitivity, the engine-mode key contribution (re-fires when LUFS target changes even with identical gain), and the null / NaN serialization. playerStore 3369 → 3302 LOC.
93 lines
3.3 KiB
TypeScript
93 lines
3.3 KiB
TypeScript
import { invoke } from '@tauri-apps/api/core';
|
|
import { useAuthStore } from './authStore';
|
|
import { effectiveLoudnessPreAnalysisAttenuationDb } from '../utils/loudnessPreAnalysisSlider';
|
|
|
|
/**
|
|
* Two IPC entry points to the Rust normalization pipeline that get hammered
|
|
* by analysis ticks, queue rewrites, and React-StrictMode double mounts —
|
|
* each carries its own time-bounded de-duplicator so the same payload sent
|
|
* within a short window collapses into a single `invoke`.
|
|
*
|
|
* - `invokeAudioSetNormalizationDeduped` — `audio_set_normalization`
|
|
* (engine + target + pre-attenuation). 450 ms window.
|
|
* - `invokeAudioUpdateReplayGainDeduped` — `audio_update_replay_gain`
|
|
* (per-track gain + peak). 250 ms window. The dedupe key picks up the
|
|
* LUFS target / pre-trim implicitly so Rust still recomputes when the
|
|
* user changes the target even if JS happens to forward the same dB.
|
|
*/
|
|
|
|
let lastNormAudioInvokeKey = '';
|
|
let lastNormAudioInvokeAtMs = 0;
|
|
|
|
const NORMALIZATION_DEDUPE_WINDOW_MS = 450;
|
|
|
|
export function invokeAudioSetNormalizationDeduped(payload: {
|
|
engine: string;
|
|
targetLufs: number;
|
|
preAnalysisAttenuationDb: number;
|
|
}): void {
|
|
const key = `${payload.engine}|${payload.targetLufs}|${payload.preAnalysisAttenuationDb}`;
|
|
const now = Date.now();
|
|
if (key === lastNormAudioInvokeKey && now - lastNormAudioInvokeAtMs < NORMALIZATION_DEDUPE_WINDOW_MS) {
|
|
return;
|
|
}
|
|
lastNormAudioInvokeKey = key;
|
|
lastNormAudioInvokeAtMs = now;
|
|
void invoke('audio_set_normalization', payload).catch(() => {});
|
|
}
|
|
|
|
let lastRgInvokeKey = '';
|
|
let lastRgInvokeAtMs = 0;
|
|
|
|
const REPLAY_GAIN_DEDUPE_WINDOW_MS = 250;
|
|
|
|
export function invokeAudioUpdateReplayGainDeduped(payload: {
|
|
volume: number;
|
|
replayGainDb: number | null;
|
|
replayGainPeak: number | null;
|
|
loudnessGainDb: number | null;
|
|
preGainDb: number;
|
|
fallbackDb: number;
|
|
}): void {
|
|
const auth = useAuthStore.getState();
|
|
/** Must vary when LUFS target / pre-trim changes: Rust recomputes in `audio_update_replay_gain` even if JS still sends the same cached dB. */
|
|
const preEff =
|
|
auth.normalizationEngine === 'loudness'
|
|
? effectiveLoudnessPreAnalysisAttenuationDb(
|
|
auth.loudnessPreAnalysisAttenuationDb,
|
|
auth.loudnessTargetLufs,
|
|
)
|
|
: auth.loudnessPreAnalysisAttenuationDb;
|
|
const normDedupeKey =
|
|
auth.normalizationEngine === 'loudness'
|
|
? `loudness|tgt=${auth.loudnessTargetLufs}|pre=${preEff.toFixed(2)}`
|
|
: auth.normalizationEngine === 'replaygain'
|
|
? 'replaygain'
|
|
: 'off';
|
|
const fmt = (v: number | null) => (v == null || !Number.isFinite(v) ? 'null' : v.toFixed(3));
|
|
const key = [
|
|
normDedupeKey,
|
|
payload.volume.toFixed(4),
|
|
fmt(payload.replayGainDb),
|
|
fmt(payload.replayGainPeak),
|
|
fmt(payload.loudnessGainDb),
|
|
payload.preGainDb.toFixed(2),
|
|
payload.fallbackDb.toFixed(2),
|
|
].join('|');
|
|
const now = Date.now();
|
|
if (key === lastRgInvokeKey && now - lastRgInvokeAtMs < REPLAY_GAIN_DEDUPE_WINDOW_MS) {
|
|
return;
|
|
}
|
|
lastRgInvokeKey = key;
|
|
lastRgInvokeAtMs = now;
|
|
invoke('audio_update_replay_gain', payload).catch(console.error);
|
|
}
|
|
|
|
/** Test-only: clear the cached dedupe state so each spec starts fresh. */
|
|
export function _resetNormalizationIpcDedupeForTest(): void {
|
|
lastNormAudioInvokeKey = '';
|
|
lastNormAudioInvokeAtMs = 0;
|
|
lastRgInvokeKey = '';
|
|
lastRgInvokeAtMs = 0;
|
|
}
|