Correct audio metering and realtime displays

This commit is contained in:
Mikei386
2026-07-22 10:50:15 +02:00
parent 97352ecfce
commit 2e868bfa3a
26 changed files with 999 additions and 903 deletions
+13 -1
View File
@@ -444,6 +444,11 @@ async function applyIncomingAudioPacket(env, packet, CONFIG, sampleTs = performa
if (typeof d.correlationNegativePeak === 'number') {
env.audio.correlationNegativePeak = d.correlationNegativePeak;
}
env.audio.phaseAngleRad = typeof d.phaseAngleRad === 'number' ? d.phaseAngleRad : null;
env.audio.phaseSeq = Number.isFinite(seq) ? seq : ((env.audio.phaseSeq || 0) + 1);
if (typeof d.phaseCoherence === 'number') env.audio.phaseCoherence = d.phaseCoherence;
if (typeof d.phaseLevel === 'number') env.audio.phaseLevel = d.phaseLevel;
if (typeof d.phasePeak === 'number') env.audio.phasePeak = d.phasePeak;
if (d.waveL && env.audio.pushWaveSamples) {
const channelCount = d.waveChannels || (d.waveR ? 2 : 1);
env.audio.sampleRate = d.sampleRate || env.audio.sampleRate || 48000;
@@ -502,6 +507,10 @@ function buildPhoenixMeterPacket(frame) {
const ppmBoxR = Number(frame?.ppm_box_r);
const correlation = Number(frame?.correlation);
const correlationNegativePeak = Number(frame?.correlation_negative_peak);
const phaseAngleRad = Number(frame?.phase_angle_rad);
const phaseCoherence = Number(frame?.phase_coherence);
const phaseLevel = Number(frame?.phase_level);
const phasePeak = Number(frame?.phase_peak);
const xyL = Array.isArray(frame?.xy_l) ? frame.xy_l : null;
const xyR = Array.isArray(frame?.xy_r) ? frame.xy_r : null;
const waveL = Array.isArray(frame?.wave_l) && frame.wave_l.length ? frame.wave_l : null;
@@ -515,6 +524,10 @@ function buildPhoenixMeterPacket(frame) {
correlationNegativePeak: Number.isFinite(correlationNegativePeak)
? Math.max(-1, Math.min(1, correlationNegativePeak))
: 0,
phaseAngleRad: Number.isFinite(phaseAngleRad) ? phaseAngleRad : null,
phaseCoherence: Number.isFinite(phaseCoherence) ? Math.max(0, Math.min(1, phaseCoherence)) : 0,
phaseLevel: Number.isFinite(phaseLevel) ? Math.max(0, phaseLevel) : 0,
phasePeak: Number.isFinite(phasePeak) ? Math.max(0, phasePeak) : 0,
rmsL: Number.isFinite(rmsL) ? rmsL : -120,
rmsR: Number.isFinite(rmsR) ? rmsR : -120,
tpL: Number.isFinite(tpL) ? tpL : -120,
@@ -926,7 +939,6 @@ export function buildRtaRuntimeConfig(CONFIG = {}) {
inputOffsetDbR: Number.isFinite(CONFIG.INPUT_OFFSET_DB_R) ? CONFIG.INPUT_OFFSET_DB_R : -5,
ppmDinAttackMs: 10,
ppmDinDecayDbPerS: 20 / 1.5,
ppmDinFastAttack: !!CONFIG.PPM_DIN_FAST_ATTACK,
ppmEbuAttackMs: 10,
ppmEbuDecayDbPerS: 24 / 2.8,
lufsIWindowMin: Number.isFinite(CONFIG.LUFS_I_WINDOW_MIN) ? CONFIG.LUFS_I_WINDOW_MIN : 4,
+28 -20
View File
@@ -106,7 +106,7 @@ const CONFIG = {
VU_REF_IS_PLUS4: true,
// 0 VU soll +4 dBu entsprechen → DIGITAL_REF + 4 dB.
VU_DBFS_REF: -14.0,
VU_OFFSET_DB: 1.92,
VU_OFFSET_DB: 0.0,
VU_HEADER_SHOW_VALUE: false,
TP_OFFSET_DB: 0.0,
TP_HEADER_SHOW_VALUE: false,
@@ -119,8 +119,9 @@ const CONFIG = {
PPM_RED_BAR_ONLY: true,
TP_RED_BAR_ONLY: true,
RMS_RED_BAR_ONLY: true,
RMS_TC_MODE: 'fast', // 'impulse' (35 ms), 'fast' (125 ms), 'slow' (1000 ms), 'none'
RMS_TC_MS: null, // optional override in ms (falls gesetzt, überschreibt den Modus)
// True-RMS integration is performed sample-continuously on linear power in
// the backend. "window" is a rectangular sliding 300-ms measurement.
RMS_TC_MODE: 'fast', // 'fast', 'slow', 'window'
VU_COLOR_NORMAL: '#ffe066',
VU_COLOR_WARN: '#ff3b3b',
PPM_DIN_COLOR_NORMAL: '#ffe066',
@@ -161,9 +162,6 @@ const CONFIG = {
PPM_DIN_RED_START: 0,
// Fixed RTW/DIN profile: 10 ms integration and 20 dB return in 1.5 s.
PPM_DIN_ATTACK_MS: 10,
// Non-normative: when enabled, PPM DIN attack becomes instant (no integration).
// Useful to compensate perceived meter lag caused by device / pipeline latency.
PPM_DIN_FAST_ATTACK: false,
PPM_DIN_DECAY_DB_PER_S: 20 / 1.5,
// Hold time for DIN PPM: verlängert auf 1000 ms (1 s), damit der PeakHold
// auch bei kurzen Transienten deutlich sichtbar bleibt. Gemäß
@@ -589,9 +587,9 @@ const PHOENIX_GLOBAL_OPTION_KEYS = Object.freeze([
'LR_FRACTIONAL_DELAY_SAMPLES',
'PPM_DIN_ATTACK_MS',
'PPM_DIN_DECAY_DB_PER_S',
'PPM_DIN_FAST_ATTACK',
'PPM_EBU_ATTACK_MS',
'PPM_EBU_DECAY_DB_PER_S',
'RMS_TC_MODE',
'LUFS_I_WINDOW_MIN',
'LUFS_I_NORM_ENABLED',
'PPM_DIN_LOUDNESS_BOXES',
@@ -679,8 +677,8 @@ const BROADCAST_STANDARDS = {
};
const STORAGE_KEY = 'analyzer_config';
const VU_OFFSET_ALIGNMENT_DB = 1.92;
const VU_OFFSET_ALIGNMENT_MIGRATION_KEY = 'migrate_vu_offset_alignment_plus2_v1';
const VU_OFFSET_DEFAULT_DB = 0.0;
const VU_OFFSET_ZERO_MIGRATION_KEY = 'migrate_vu_offset_default_zero_v2';
const INPUT_OFFSET_DB_MIN = -60;
const INPUT_OFFSET_DB_MAX = 20;
const LR_DELAY_ZERO_MIGRATION_KEY = 'migrate_lr_delay_zero_to_0_05_v1';
@@ -845,21 +843,23 @@ function loadConfig(opts = {}) {
if (!Number.isFinite(CONFIG.PPM_DIN_LOUDNESS_OFFSET_DB)) CONFIG.PPM_DIN_LOUDNESS_OFFSET_DB = 0;
CONFIG.PPM_DIN_LOUDNESS_OFFSET_DB = Math.max(-7, Math.min(7, CONFIG.PPM_DIN_LOUDNESS_OFFSET_DB));
try {
const needsVuMigration = !localStorage.getItem(VU_OFFSET_ALIGNMENT_MIGRATION_KEY);
const needsVuMigration = !localStorage.getItem(VU_OFFSET_ZERO_MIGRATION_KEY);
const savedVuOffset = Number(saved?.VU_OFFSET_DB);
const oldDefaultOffset = Number(CONFIG_DEFAULTS?.VU_OFFSET_DB);
const looksLikeOldDefault = !Number.isFinite(savedVuOffset)
|| Math.abs(savedVuOffset - oldDefaultOffset) < 0.02
const looksLikeOldAutomaticOffset = !Number.isFinite(savedVuOffset)
|| Math.abs(savedVuOffset - 1.92) < 0.02
|| Math.abs(savedVuOffset - (-0.08)) < 0.02
|| Math.abs(savedVuOffset - (-0.05)) < 0.02;
if (needsVuMigration && looksLikeOldDefault) {
CONFIG.VU_OFFSET_DB = VU_OFFSET_ALIGNMENT_DB;
if (needsVuMigration && looksLikeOldAutomaticOffset) {
CONFIG.VU_OFFSET_DB = VU_OFFSET_DEFAULT_DB;
}
if (needsVuMigration) localStorage.setItem(VU_OFFSET_ALIGNMENT_MIGRATION_KEY, '1');
if (needsVuMigration) localStorage.setItem(VU_OFFSET_ZERO_MIGRATION_KEY, '1');
} catch (_) {}
if (!Number.isFinite(CONFIG.LUFS_I_WINDOW_MIN)) CONFIG.LUFS_I_WINDOW_MIN = CONFIG_DEFAULTS.LUFS_I_WINDOW_MIN ?? 4;
CONFIG.LUFS_I_WINDOW_MIN = Math.max(1, Math.min(10, Math.round(CONFIG.LUFS_I_WINDOW_MIN)));
CONFIG.RMS_TC_MODE = (CONFIG.RMS_TC_MODE === 'slow' || CONFIG.RMS_TC_MODE === 'window')
? CONFIG.RMS_TC_MODE
: 'fast';
CONFIG.LUFS_I_NORM_ENABLED = !!CONFIG.LUFS_I_NORM_ENABLED;
CONFIG.STOPWATCH_DISPLAY_STYLE = (CONFIG.STOPWATCH_DISPLAY_STYLE === 'seven') ? 'seven' : 'mono';
@@ -876,7 +876,6 @@ function loadConfig(opts = {}) {
if (!Number.isFinite(CONFIG.LR_FRACTIONAL_DELAY_SAMPLES)) CONFIG.LR_FRACTIONAL_DELAY_SAMPLES = CONFIG_DEFAULTS.LR_FRACTIONAL_DELAY_SAMPLES;
CONFIG.LR_FRACTIONAL_DELAY_SAMPLES = Math.max(-1.5, Math.min(1.5, Number(CONFIG.LR_FRACTIONAL_DELAY_SAMPLES)));
CONFIG.PHOENIX_BASE_URL = normalizePhoenixBaseUrlForCurrentClient(CONFIG.PHOENIX_BASE_URL || CONFIG_DEFAULTS.PHOENIX_BASE_URL || DEFAULT_PHOENIX_BASE_URL);
CONFIG.PPM_DIN_FAST_ATTACK = !!CONFIG.PPM_DIN_FAST_ATTACK;
// Migrate stale profiles: detector ballistics are standards profiles,
// not user-adjustable display preferences.
CONFIG.PPM_DIN_ATTACK_MS = 10;
@@ -1055,6 +1054,12 @@ function buildPhoenixGlobalConfigPayload() {
return (mode === '1_3' || mode === '1_6' || mode === '1_12') ? mode : '1_6';
};
const phaseAmplitudeMode = normalizePhaseAmplitudeMode(CONFIG.PHASE_AMPLITUDE_MODE);
const rmsIntegration = (() => {
const mode = String(CONFIG.RMS_TC_MODE || '').trim().toLowerCase();
if (mode === 'slow' || mode === 'window') return mode;
if (mode === 'none' || mode === 'window300') return 'window';
return 'fast';
})();
return {
fftSize,
rtaBpoMode: normalizeRtaBpoMode(CONFIG.RTA_BPO_MODE),
@@ -1065,9 +1070,9 @@ function buildPhoenixGlobalConfigPayload() {
lrFractionalDelaySamples: Number.isFinite(CONFIG.LR_FRACTIONAL_DELAY_SAMPLES) ? CONFIG.LR_FRACTIONAL_DELAY_SAMPLES : (CONFIG_DEFAULTS.LR_FRACTIONAL_DELAY_SAMPLES ?? 0.996),
ppmDinAttackMs: 10,
ppmDinDecayDbPerS: 20 / 1.5,
ppmDinFastAttack: !!CONFIG.PPM_DIN_FAST_ATTACK,
ppmEbuAttackMs: 10,
ppmEbuDecayDbPerS: 24 / 2.8,
rmsIntegration,
lufsIWindowMin: Number.isFinite(CONFIG.LUFS_I_WINDOW_MIN) ? CONFIG.LUFS_I_WINDOW_MIN : (CONFIG_DEFAULTS.LUFS_I_WINDOW_MIN ?? 4),
lufsINormEnabled: !!CONFIG.LUFS_I_NORM_ENABLED,
ppmDinLoudnessBoxes: !!CONFIG.PPM_DIN_LOUDNESS_BOXES,
@@ -1162,9 +1167,12 @@ function applyPhoenixGlobalConfig(payload = {}) {
if ('spectroScrollMode' in payload) CONFIG.SPECTRO_SCROLL_MODE = normalizeSpectroScroll(payload.spectroScrollMode);
CONFIG.PPM_DIN_ATTACK_MS = 10;
CONFIG.PPM_DIN_DECAY_DB_PER_S = 20 / 1.5;
if ('ppmDinFastAttack' in payload) CONFIG.PPM_DIN_FAST_ATTACK = !!payload.ppmDinFastAttack;
CONFIG.PPM_EBU_ATTACK_MS = 10;
CONFIG.PPM_EBU_DECAY_DB_PER_S = 24 / 2.8;
if ('rmsIntegration' in payload) {
const mode = String(payload.rmsIntegration || '').trim().toLowerCase();
CONFIG.RMS_TC_MODE = (mode === 'slow' || mode === 'window') ? mode : 'fast';
}
const lufsWin = Number(payload.lufsIWindowMin);
if (Number.isFinite(lufsWin)) CONFIG.LUFS_I_WINDOW_MIN = Math.max(1, Math.min(10, Math.round(lufsWin)));
if ('lufsINormEnabled' in payload) CONFIG.LUFS_I_NORM_ENABLED = !!payload.lufsINormEnabled;
@@ -1297,7 +1305,7 @@ function applyBroadcastStandard(name) {
CONFIG.VU_RED_START = p.VU_RED_START;
// Offsets zurücksetzen (wie bisher)
CONFIG.VU_OFFSET_DB = VU_OFFSET_ALIGNMENT_DB;
CONFIG.VU_OFFSET_DB = VU_OFFSET_DEFAULT_DB;
CONFIG.TP_OFFSET_DB = 0;
CONFIG.RMS_OFFSET_DB = 0;