Improve phase trail and correlation controls
This commit is contained in:
+1
-1
@@ -17,7 +17,7 @@
|
||||
<li><strong>Hardwareparameter:</strong> Tatsächlich von ALSA ausgehandelte Samplerate, Perioden- und Puffergröße werden nun durch DSP, Aufnahme, Status und Browser verwendet; die feste 48-kHz-Annahme wurde entfernt.</li>
|
||||
<li><strong>Goniometer:</strong> Nur neue XY-Samplepaare werden mit einer periodengrößenunabhängigen mittleren Rate von 60 Hz übertragen. Punktbegrenzung wirkt bereits auf den Transport; begrenzte und wiederverwendbare Spurpuffer reduzieren Speicher- und Zeichenlast.</li>
|
||||
<li><strong>Goniometer-Persistenz:</strong> Reproduzierbare Fast-, Medium- und Slow-Profile sowie ein freies Phoenix-Fade ergänzt. Künstliche Bézier-Verformung der Messspur entfernt; AGC und Silence-Gate arbeiten zeit- beziehungsweise fensterbasiert.</li>
|
||||
<li><strong>Korrelation:</strong> Kontinuierliche DSP-Messung aus L², R² und L·R mit wählbaren 1,0/2,5 Sekunden ergänzt. Bildratenabhängige Doppelglättung entfernt; Negative-Peak-Memory, Marker und manueller Reset hinzugefügt.</li>
|
||||
<li><strong>Korrelation:</strong> Kontinuierliche DSP-Messung aus L², R² und L·R mit wählbaren 0,5/1,0/2,5 Sekunden und konfigurierbarem Mono-RMS-Silence-Threshold ergänzt. Bildratenabhängige Doppelglättung entfernt; Negative-Peak-Memory, Marker und manueller Reset hinzugefügt.</li>
|
||||
<li><strong>Phasenrad:</strong> Bandpass, Hilbert-Transformation und energiegewichtete L/R-Phasenmittelung laufen nun samplekontinuierlich im Audiokern statt auf ausgedünnten Browser-XY-Punkten. Paketgrenzen beeinflussen den Winkel nicht mehr; Glättung und AGC arbeiten zeitbasiert, und die doppelte AGC-Verstärkung wurde entfernt.</li>
|
||||
<li><strong>True Peak, RMS und VU:</strong> True Peak verwendet den vierphasigen Referenz-FIR aus ITU-R BS.1770, besteht die EBU-Testfälle 15 bis 19 und zeigt Übersteuerungen in allen Ansichten bis +6 dBTP an. True RMS integriert samplekontinuierlich im Leistungsbereich mit Fast (125 ms), Slow (1 s) oder einem gleitenden 300-ms-Fenster; Browser-Doppelglättung und der RMS-fremde Impulse-Modus wurden entfernt. VU verwendet ein Moving-Coil-Modell mit 300-ms-Sprungantwort und 1 bis 1,5 % Überschwingen; der fälschliche Standardoffset von +1,92 dB wurde auf 0 dB korrigiert.</li>
|
||||
<li><strong>Qualitätssicherung:</strong> Automatische Regressionstests für PPM, RTA, A/C/Z, mehrere Sampleraten, Korrelationssignale, Goniometertaktung, Binärprotokolle sowie Spektrogramm-Zeitbasis und Langlauf ergänzt.</li>
|
||||
|
||||
+10
-2
@@ -1,7 +1,12 @@
|
||||
// core/audio.js — Phoenix Audio-Init, Metrics-WS, Waveform/Spectrogram buffers
|
||||
// Wird von main.js mit `initAudio(env)` gestartet. Nutzt env.config (CONFIG) und env.meters.
|
||||
|
||||
import { applyPhoenixGlobalConfig, buildPhoenixGlobalConfigPayload, saveConfig } from './config.js';
|
||||
import {
|
||||
applyPhoenixGlobalConfig,
|
||||
buildPhoenixGlobalConfigPayload,
|
||||
normalizeCorrelationResponseSeconds,
|
||||
saveConfig,
|
||||
} from './config.js';
|
||||
import { decodePhoenixSpectroBuffer, decodePhoenixVisualsBuffer } from './binary_protocol.js';
|
||||
import { getRtwCenters } from './rtw_centers.js';
|
||||
|
||||
@@ -943,7 +948,10 @@ export function buildRtaRuntimeConfig(CONFIG = {}) {
|
||||
ppmEbuDecayDbPerS: 24 / 2.8,
|
||||
lufsIWindowMin: Number.isFinite(CONFIG.LUFS_I_WINDOW_MIN) ? CONFIG.LUFS_I_WINDOW_MIN : 4,
|
||||
lufsINormEnabled: !!CONFIG.LUFS_I_NORM_ENABLED,
|
||||
correlationResponseS: Number(CONFIG.CORR_RESPONSE_S) >= 1.75 ? 2.5 : 1.0,
|
||||
correlationResponseS: normalizeCorrelationResponseSeconds(CONFIG.CORR_RESPONSE_S),
|
||||
correlationSilenceThresholdRmsDbfs: Number.isFinite(CONFIG.CORR_SILENCE_THRESHOLD_RMS_DBFS)
|
||||
? Math.max(-90, Math.min(-40, CONFIG.CORR_SILENCE_THRESHOLD_RMS_DBFS))
|
||||
: -75,
|
||||
correlationResetToken: Math.max(0, Math.floor(Number(CONFIG.CORR_RESET_TOKEN) || 0)),
|
||||
xyPoints: [128, 256, 512, 1024, 2048].includes(Number(CONFIG.XY_POINTS))
|
||||
? Number(CONFIG.XY_POINTS)
|
||||
|
||||
+9
-1
@@ -1,5 +1,13 @@
|
||||
// core/config.js — zentrale CONFIG + Presets + Persistenz
|
||||
|
||||
export function normalizeCorrelationResponseSeconds(value) {
|
||||
const seconds = Number(value);
|
||||
if (!Number.isFinite(seconds) || seconds <= 0) return 1.0;
|
||||
if (seconds < 0.75) return 0.5;
|
||||
if (seconds < 1.75) return 1.0;
|
||||
return 2.5;
|
||||
}
|
||||
|
||||
function isLoopbackHost(host) {
|
||||
const h = String(host || '').trim().toLowerCase();
|
||||
return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '[::1]';
|
||||
@@ -971,7 +979,7 @@ function loadConfig(opts = {}) {
|
||||
const fallback = Number.isFinite(CONFIG.GONIO_DISPLAY_GAIN_DB) ? CONFIG.GONIO_DISPLAY_GAIN_DB : 0;
|
||||
CONFIG.GONIO_MANUAL_GAIN_DB = Math.max(-35, Math.min(35, Math.round(fallback / 5) * 5));
|
||||
}
|
||||
CONFIG.CORR_RESPONSE_S = Number(CONFIG.CORR_RESPONSE_S) >= 1.75 ? 2.5 : 1.0;
|
||||
CONFIG.CORR_RESPONSE_S = normalizeCorrelationResponseSeconds(CONFIG.CORR_RESPONSE_S);
|
||||
const corrNegativeMarkerMode = String(CONFIG.CORR_NEGATIVE_MARKER_MODE || 'memory').toLowerCase();
|
||||
CONFIG.CORR_NEGATIVE_MARKER_MODE = ['memory', 'hold', 'off'].includes(corrNegativeMarkerMode)
|
||||
? corrNegativeMarkerMode
|
||||
|
||||
+2
-1
@@ -521,7 +521,7 @@
|
||||
<input id="opt_xySilenceGate" type="checkbox" checked>
|
||||
Silence-Gate aktivieren (RMS-basiert)
|
||||
</label>
|
||||
<small>Blendet nur die Goniometer-Spur bei Stille aus; die Korrelation wird unabhängig kontinuierlich gemessen.</small>
|
||||
<small>Blendet die Goniometer-Spur bei Stille aus; der Threshold setzt außerdem die Korrelation auf Neutralstellung.</small>
|
||||
</div>
|
||||
<div class="opt">
|
||||
<label>Silence-Threshold [dBFS RMS]</label>
|
||||
@@ -530,6 +530,7 @@
|
||||
</div>
|
||||
<div class="opt"><label>Korrelation Ansprechzeit</label>
|
||||
<select id="opt_corrResponse">
|
||||
<option value="0.5">0,5 s</option>
|
||||
<option value="1">1,0 s</option>
|
||||
<option value="2.5">2,5 s</option>
|
||||
</select>
|
||||
|
||||
+5
-4
@@ -1,7 +1,7 @@
|
||||
// ui/options.js — bindet das Options-Panel an CONFIG (lesen/schreiben), Presets, Export/Import
|
||||
// Erwartet vorhandenes DOM aus index.html. Nutzt localStorage-Key 'analyzer_config'.
|
||||
|
||||
import { CONFIG, saveConfig, loadConfig, applyAlignmentProfile, applyRtaBpoSelection, applySoftwarePreset, loadSoftwarePreset, saveSoftwarePreset, loadLayoutPreset, saveLayoutPreset, exportSoftwarePreset, importSoftwarePreset, updateVuReference, resetConfigToDefaults, updateInputOffsetGain } from '../core/config.js';
|
||||
import { CONFIG, saveConfig, loadConfig, applyAlignmentProfile, applyRtaBpoSelection, applySoftwarePreset, loadSoftwarePreset, saveSoftwarePreset, loadLayoutPreset, saveLayoutPreset, exportSoftwarePreset, importSoftwarePreset, updateVuReference, resetConfigToDefaults, updateInputOffsetGain, normalizeCorrelationResponseSeconds } from '../core/config.js';
|
||||
import { clamp, clampPow2 } from '../core/utils.js';
|
||||
import { bindOptionsViewportAssist } from './options_viewport.js';
|
||||
import { setupLazyChangelog } from './changelog.js';
|
||||
@@ -184,7 +184,7 @@ function syncUI() {
|
||||
['opt_rmsColNorm', CONFIG.RMS_COLOR_NORMAL],
|
||||
['opt_rmsColWarn', CONFIG.RMS_COLOR_WARN],
|
||||
|
||||
['opt_corrResponse', String(Number(CONFIG.CORR_RESPONSE_S) >= 1.75 ? 2.5 : 1)],
|
||||
['opt_corrResponse', String(normalizeCorrelationResponseSeconds(CONFIG.CORR_RESPONSE_S))],
|
||||
['opt_corrNegativeMarkerMode', CONFIG.CORR_NEGATIVE_MARKER_MODE || 'memory'],
|
||||
['opt_xyPoints', String(CONFIG.XY_POINTS)],
|
||||
['opt_xyStyle', CONFIG.XY_STYLE],
|
||||
@@ -457,7 +457,8 @@ function wireHandlers(env) {
|
||||
if (!el) return;
|
||||
const notifyRta = /^opt_rta/i.test(id)
|
||||
|| id === 'opt_lufsIWindowMin'
|
||||
|| id === 'opt_lufsINorm';
|
||||
|| id === 'opt_lufsINorm'
|
||||
|| id === 'opt_xySilenceThr';
|
||||
const inputType = String(el.type || '').toLowerCase();
|
||||
const commitOnInput = isCheckbox || inputType === 'range' || inputType === 'color';
|
||||
|
||||
@@ -813,7 +814,7 @@ function wireHandlers(env) {
|
||||
});
|
||||
|
||||
h('opt_corrResponse', v => {
|
||||
CONFIG.CORR_RESPONSE_S = Number(v) >= 1.75 ? 2.5 : 1.0;
|
||||
CONFIG.CORR_RESPONSE_S = normalizeCorrelationResponseSeconds(v);
|
||||
try { env?.notifyRtaConfig?.(); } catch (_) {}
|
||||
return String(CONFIG.CORR_RESPONSE_S);
|
||||
});
|
||||
|
||||
+50
-12
@@ -29,6 +29,8 @@ const PHASE_RADIUS_TAU_S = 0.085;
|
||||
const PHASE_TRAIL_FADE_MS = 2000;
|
||||
const PHASE_TRAIL_MIN_STEP_MS = 1000 / 45;
|
||||
const PHASE_TRAIL_MIN_DIST_PX = 1.5;
|
||||
const PHASE_TRAIL_MAX_ANGLE_STEP_RAD = (2 * Math.PI) / 180;
|
||||
const PHASE_TRAIL_MAX_POINTS = 1024;
|
||||
const PHASE_SECTORS = [
|
||||
{ startDeg: -30, endDeg: 30, color: 'rgba(72,210,150,0.3)' },
|
||||
{ startDeg: 30, endDeg: 60, color: 'rgba(255,214,120,0.25)' },
|
||||
@@ -517,31 +519,64 @@ function updatePhaseTrail(state, wheel, CONFIG, now) {
|
||||
}
|
||||
const radiusNorm = Math.max(0, Number(state?.smoothRadius) || 0);
|
||||
if (radiusNorm > 0.002 && Number.isFinite(state?.smoothPhase)) {
|
||||
const length = radiusNorm * wheel.radius;
|
||||
const x = wheel.cx + Math.cos(state.smoothPhase) * length;
|
||||
const y = wheel.cy + Math.sin(state.smoothPhase) * length;
|
||||
const color = trailColorForAngle(state.smoothPhase);
|
||||
const phase = wrapAngle(state.smoothPhase);
|
||||
const point = createPhaseTrailPoint(wheel, phase, radiusNorm, now);
|
||||
const last = state.phaseTrail.length ? state.phaseTrail[state.phaseTrail.length - 1] : null;
|
||||
if (!last) {
|
||||
state.phaseTrail.push({ x, y, t: now, color });
|
||||
state.phaseTrail.push(point);
|
||||
} else {
|
||||
const dt = now - last.t;
|
||||
const dx = x - last.x;
|
||||
const dy = y - last.y;
|
||||
const dx = point.x - last.x;
|
||||
const dy = point.y - last.y;
|
||||
const dist2 = dx * dx + dy * dy;
|
||||
if (dt >= PHASE_TRAIL_MIN_STEP_MS || dist2 >= PHASE_TRAIL_MIN_DIST_PX * PHASE_TRAIL_MIN_DIST_PX || last.color !== color) {
|
||||
state.phaseTrail.push({ x, y, t: now, color });
|
||||
if (dt >= PHASE_TRAIL_MIN_STEP_MS || dist2 >= PHASE_TRAIL_MIN_DIST_PX * PHASE_TRAIL_MIN_DIST_PX || last.color !== point.color) {
|
||||
appendPhaseTrailSegment(state.phaseTrail, wheel, last, phase, radiusNorm, now);
|
||||
} else {
|
||||
last.x = x;
|
||||
last.y = y;
|
||||
last.x = point.x;
|
||||
last.y = point.y;
|
||||
last.t = now;
|
||||
last.color = color;
|
||||
last.color = point.color;
|
||||
last.phase = phase;
|
||||
last.radiusNorm = radiusNorm;
|
||||
}
|
||||
}
|
||||
}
|
||||
trimPhaseTrail(state, CONFIG, now);
|
||||
}
|
||||
|
||||
function createPhaseTrailPoint(wheel, phase, radiusNorm, timestamp) {
|
||||
const length = radiusNorm * wheel.radius;
|
||||
return {
|
||||
x: wheel.cx + Math.cos(phase) * length,
|
||||
y: wheel.cy + Math.sin(phase) * length,
|
||||
t: timestamp,
|
||||
color: trailColorForAngle(phase),
|
||||
phase,
|
||||
radiusNorm,
|
||||
};
|
||||
}
|
||||
|
||||
function appendPhaseTrailSegment(trail, wheel, last, phase, radiusNorm, timestamp) {
|
||||
const startPhase = Number.isFinite(last?.phase) ? last.phase : phase;
|
||||
const startRadius = Number.isFinite(last?.radiusNorm) ? last.radiusNorm : radiusNorm;
|
||||
const phaseDelta = wrapAngle(phase - startPhase);
|
||||
const steps = Math.max(1, Math.ceil(Math.abs(phaseDelta) / PHASE_TRAIL_MAX_ANGLE_STEP_RAD));
|
||||
const startTime = Number.isFinite(last?.t) ? last.t : timestamp;
|
||||
|
||||
for (let step = 1; step <= steps; step++) {
|
||||
const fraction = step / steps;
|
||||
const interpolatedPhase = wrapAngle(startPhase + phaseDelta * fraction);
|
||||
const interpolatedRadius = lerp(startRadius, radiusNorm, fraction);
|
||||
const interpolatedTime = lerp(startTime, timestamp, fraction);
|
||||
trail.push(createPhaseTrailPoint(
|
||||
wheel,
|
||||
interpolatedPhase,
|
||||
interpolatedRadius,
|
||||
interpolatedTime,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
function trimPhaseTrail(state, CONFIG, now) {
|
||||
if (!state.phaseTrail) state.phaseTrail = [];
|
||||
if (!CONFIG?.PHASE_TRAIL_ENABLED) {
|
||||
@@ -557,6 +592,9 @@ function trimPhaseTrail(state, CONFIG, now) {
|
||||
}
|
||||
}
|
||||
state.phaseTrail.length = write;
|
||||
if (state.phaseTrail.length > PHASE_TRAIL_MAX_POINTS) {
|
||||
state.phaseTrail.splice(0, state.phaseTrail.length - PHASE_TRAIL_MAX_POINTS);
|
||||
}
|
||||
}
|
||||
|
||||
function drawPhaseTrail(g, wheel, state, CONFIG, now) {
|
||||
|
||||
Reference in New Issue
Block a user