Correct goniometer transport and correlation meter
This commit is contained in:
@@ -351,6 +351,7 @@ function applyVisualsBuffer(env, buffer) {
|
||||
env.audio.xyL = left;
|
||||
env.audio.xyR = right;
|
||||
env.audio.xySeq = seq;
|
||||
env.audio.xyLastSampleTs = performance.now();
|
||||
}
|
||||
if (wave) updateWaveformEnvelopeStore(env.audio, wave);
|
||||
env.audio.phoenixVisualsSeq = seq;
|
||||
@@ -409,6 +410,7 @@ async function applyIncomingAudioPacket(env, packet, CONFIG, sampleTs = performa
|
||||
env.audio.xyL = d.xyL;
|
||||
env.audio.xyR = d.xyR;
|
||||
env.audio.xySeq = Number.isFinite(d.seq) ? d.seq : (env.audio.xySeq || 0);
|
||||
env.audio.xyLastSampleTs = sampleTs;
|
||||
}
|
||||
if (typeof d.correlation === 'number') env.audio.correlation = d.correlation;
|
||||
if (typeof d.correlationNegativePeak === 'number') {
|
||||
|
||||
@@ -934,6 +934,13 @@ 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_RESET_TOKEN = Math.max(0, Math.floor(Number(CONFIG.CORR_RESET_TOKEN) || 0));
|
||||
const persistenceMode = String(CONFIG.GONIO_PERSISTENCE_MODE || 'fast').toLowerCase();
|
||||
CONFIG.GONIO_PERSISTENCE_MODE = ['fast', 'medium', 'slow', 'custom'].includes(persistenceMode)
|
||||
? persistenceMode
|
||||
: 'fast';
|
||||
CONFIG.GONIO_LINE_FADE_MS = Math.max(0, Math.min(600, Number(CONFIG.GONIO_LINE_FADE_MS) || 0));
|
||||
} catch (e) {
|
||||
console.warn('Config load error:', e);
|
||||
}
|
||||
|
||||
+20
-11
@@ -507,23 +507,23 @@
|
||||
<input id="opt_xySilenceGate" type="checkbox" checked>
|
||||
Silence-Gate aktivieren (RMS-basiert)
|
||||
</label>
|
||||
<small>Gatet Goniometer & Korrelation bei Pegeln unterhalb der Schwelle</small>
|
||||
<small>Blendet nur die Goniometer-Spur bei Stille aus; die Korrelation wird unabhängig kontinuierlich gemessen.</small>
|
||||
</div>
|
||||
<div class="opt">
|
||||
<label>Silence-Threshold [dBFS RMS]</label>
|
||||
<input id="opt_xySilenceThr" type="number" min="-90" max="-40" step="1" style="width:120px">
|
||||
<small>Default: -75 dBFS RMS (Mono)</small>
|
||||
</div>
|
||||
<div class="opt"><label>Korrelation Glättung</label>
|
||||
<input id="opt_corrSmooth" type="range" min="0.5" max="0.98" step="0.01"><span id="val_corrSmooth"></span>
|
||||
<small>Glättet die Korrelation für ruhigere Anzeige</small>
|
||||
<div class="opt"><label>Korrelation Ansprechzeit</label>
|
||||
<select id="opt_corrResponse">
|
||||
<option value="1">1,0 s</option>
|
||||
<option value="2.5">2,5 s</option>
|
||||
</select>
|
||||
<small>Kontinuierliche DSP-Integration; unabhängig von Bildrate und ALSA-Periode.</small>
|
||||
</div>
|
||||
<div class="opt">
|
||||
<label class="row" style="align-items:center;gap:8px">
|
||||
<input id="opt_corrZeroOnSilence" type="checkbox">
|
||||
Sofortige Nullstellung wenn kein Pegel
|
||||
</label>
|
||||
<small>Setzt den Korrelations‑Würfel bei Stille sofort auf 0 (ohne Hold/Drift).</small>
|
||||
<button id="opt_corrResetPeak" type="button">Negativspitze zurücksetzen</button>
|
||||
<small>Setzt den Negative-Peak-Memory auf 0 zurück; der rote Marker verschwindet.</small>
|
||||
</div>
|
||||
<div class="opt"><label>Goniometer Display Gain (dB)</label>
|
||||
<input id="opt_goniGain" type="range" min="-35" max="35" step="5"><span id="val_goniGain"></span>
|
||||
@@ -536,9 +536,18 @@
|
||||
<input id="opt_goniGap" type="range" min="2" max="24" step="1"><span id="val_goniGap"></span>
|
||||
<small>Abstand zwischen den XY-Meter-Slots</small>
|
||||
</div>
|
||||
<div class="opt"><label>Goniometer Fade-Out</label>
|
||||
<div class="opt"><label>Goniometer-Persistenz</label>
|
||||
<select id="opt_goniPersistence">
|
||||
<option value="fast">Fast (50 ms)</option>
|
||||
<option value="medium">Medium (150 ms)</option>
|
||||
<option value="slow">Slow (300 ms)</option>
|
||||
<option value="custom">Phoenix – frei</option>
|
||||
</select>
|
||||
<small>Fast/Medium/Slow sind reproduzierbare Profile; die endgültige RTW-Abstimmung erfolgt am Referenzgerät.</small>
|
||||
</div>
|
||||
<div class="opt"><label>Freies Fade-Out</label>
|
||||
<input id="opt_goniFade" type="range" min="0" max="600" step="10"><span id="val_goniFade"></span>
|
||||
<small>Nachleuchten der Spur (ms)</small>
|
||||
<small>Wird beim Verschieben automatisch als Phoenix-Profil aktiviert.</small>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
+24
-8
@@ -270,8 +270,7 @@ function syncUI() {
|
||||
['opt_rmsColNorm', CONFIG.RMS_COLOR_NORMAL],
|
||||
['opt_rmsColWarn', CONFIG.RMS_COLOR_WARN],
|
||||
|
||||
['opt_corrSmooth', CONFIG.CORR_SMOOTH, 'val_corrSmooth', (v)=>Number(v).toFixed(2)],
|
||||
['opt_corrZeroOnSilence', CONFIG.CORR_ZERO_ON_SILENCE, null, null, 'checkbox'],
|
||||
['opt_corrResponse', String(Number(CONFIG.CORR_RESPONSE_S) >= 1.75 ? 2.5 : 1)],
|
||||
['opt_xyPoints', String(CONFIG.XY_POINTS)],
|
||||
['opt_xyStyle', CONFIG.XY_STYLE],
|
||||
['opt_xySilenceGate', CONFIG.XY_SILENCE_GATE_ENABLED, null, null, 'checkbox'],
|
||||
@@ -316,6 +315,7 @@ function syncUI() {
|
||||
['opt_phaseTrail', CONFIG.PHASE_TRAIL_ENABLED, null, null, 'checkbox'],
|
||||
['opt_goniGap', CONFIG.GONI_METER_GAP, 'val_goniGap', (v)=>`${v} px`],
|
||||
['opt_goniFade', CONFIG.GONIO_LINE_FADE_MS, 'val_goniFade', (v)=>`${Math.round(v)} ms`],
|
||||
['opt_goniPersistence', CONFIG.GONIO_PERSISTENCE_MODE || 'fast'],
|
||||
['opt_panelDividers', CONFIG.PANEL_DIVIDERS_ENABLED, null, null, 'checkbox'],
|
||||
['opt_lufsRedThr', CONFIG.LUFS_RED_START],
|
||||
['opt_lufsYellowThr', CONFIG.LUFS_YELLOW_START],
|
||||
@@ -916,11 +916,17 @@ function wireHandlers(env) {
|
||||
notifyPhoenixGlobalConfig();
|
||||
});
|
||||
|
||||
h('opt_corrSmooth', v => CONFIG.CORR_SMOOTH = clamp(+v, 0.5, 0.98), 'val_corrSmooth', v=>Number(v).toFixed(2));
|
||||
h('opt_corrZeroOnSilence', v => {
|
||||
CONFIG.CORR_ZERO_ON_SILENCE = !!v;
|
||||
return CONFIG.CORR_ZERO_ON_SILENCE;
|
||||
}, null, null, true);
|
||||
h('opt_corrResponse', v => {
|
||||
CONFIG.CORR_RESPONSE_S = Number(v) >= 1.75 ? 2.5 : 1.0;
|
||||
try { env?.notifyRtaConfig?.(); } catch (_) {}
|
||||
return String(CONFIG.CORR_RESPONSE_S);
|
||||
});
|
||||
const corrResetPeak = E('opt_corrResetPeak');
|
||||
if (corrResetPeak) corrResetPeak.onclick = () => {
|
||||
CONFIG.CORR_RESET_TOKEN = Math.max(0, Math.floor(Number(CONFIG.CORR_RESET_TOKEN) || 0)) + 1;
|
||||
saveConfig();
|
||||
try { env?.notifyRtaConfig?.(); } catch (_) {}
|
||||
};
|
||||
h('opt_xyPoints', v => {
|
||||
const raw = Number(v);
|
||||
CONFIG.XY_POINTS = [128, 256, 512, 1024, 2048].includes(raw)
|
||||
@@ -1047,7 +1053,17 @@ function wireHandlers(env) {
|
||||
notifyPhoenixGlobalConfig();
|
||||
}, null, null, true);
|
||||
h('opt_goniGap', v => CONFIG.GONI_METER_GAP = clamp(+v, 2, 24), 'val_goniGap', v=>`${v} px`);
|
||||
h('opt_goniFade', v => { CONFIG.GONIO_LINE_FADE_MS = clamp(+v, 0, 600); }, 'val_goniFade', v=>`${Math.round(v)} ms`);
|
||||
h('opt_goniPersistence', v => {
|
||||
const mode = String(v).toLowerCase();
|
||||
CONFIG.GONIO_PERSISTENCE_MODE = ['fast', 'medium', 'slow', 'custom'].includes(mode) ? mode : 'fast';
|
||||
return CONFIG.GONIO_PERSISTENCE_MODE;
|
||||
});
|
||||
h('opt_goniFade', v => {
|
||||
CONFIG.GONIO_LINE_FADE_MS = clamp(+v, 0, 600);
|
||||
CONFIG.GONIO_PERSISTENCE_MODE = 'custom';
|
||||
const persistence = E('opt_goniPersistence');
|
||||
if (persistence) persistence.value = 'custom';
|
||||
}, 'val_goniFade', v=>`${Math.round(v)} ms`);
|
||||
h('opt_panelDividers', v => {
|
||||
CONFIG.PANEL_DIVIDERS_ENABLED = !!v;
|
||||
return CONFIG.PANEL_DIVIDERS_ENABLED;
|
||||
|
||||
+95
-31
@@ -41,6 +41,7 @@ export function init() {
|
||||
agcEnv: 1e-3,
|
||||
agcGainDb: 0,
|
||||
agcLastTs: 0,
|
||||
agcLastXySeq: 0,
|
||||
traceBuffer: new Float32Array(0),
|
||||
lineTrails: [],
|
||||
trailPool: [],
|
||||
@@ -51,7 +52,12 @@ export function init() {
|
||||
}
|
||||
|
||||
export function resize() {}
|
||||
export function destroy() {}
|
||||
export function destroy(state) {
|
||||
if (!state) return;
|
||||
clearTrails(state);
|
||||
state.trailPool.length = 0;
|
||||
state.traceBuffer = new Float32Array(0);
|
||||
}
|
||||
|
||||
export async function render(env, state) {
|
||||
const { ctx: g, rect, config: CONFIG, audio, utils, meters } = env;
|
||||
@@ -68,12 +74,14 @@ export async function render(env, state) {
|
||||
const layout = computeGoniometerLayout(rect, CONFIG, slots.length, topInset);
|
||||
const settings = resolveScopeSettings(CONFIG);
|
||||
const style = CONFIG.XY_STYLE === 'points' ? 'points' : 'lines';
|
||||
const gate = resolveSilenceGate(env, CONFIG, state);
|
||||
|
||||
drawStaticLayer(g, state, rect, layout, CONFIG, slots.length);
|
||||
|
||||
const xyData = extractXYData(audio);
|
||||
const lastSampleTs = Number.isFinite(env?.audio?.lastSampleTs) ? Number(env.audio.lastSampleTs) : 0;
|
||||
const gate = resolveSilenceGate(CONFIG, state, xyData);
|
||||
const lastSampleTs = Number.isFinite(env?.audio?.xyLastSampleTs)
|
||||
? Number(env.audio.xyLastSampleTs)
|
||||
: 0;
|
||||
const audioAgeMs = lastSampleTs ? (frameNow - lastSampleTs) : Infinity;
|
||||
const audioFresh = Number.isFinite(audioAgeMs) && audioAgeMs >= 0 && audioAgeMs <= 250;
|
||||
const xyReady = xyData.ready && audioFresh;
|
||||
@@ -396,6 +404,7 @@ function extractXYData(audio) {
|
||||
ready,
|
||||
xyL,
|
||||
xyR,
|
||||
seq: Number(audio?.xySeq) || 0,
|
||||
length: ready ? Math.min(xyL.length, xyR.length) : 0,
|
||||
};
|
||||
}
|
||||
@@ -403,7 +412,7 @@ function extractXYData(audio) {
|
||||
function resolveScopeSettings(CONFIG) {
|
||||
let gainDb = Number.isFinite(CONFIG?.GONIO_DISPLAY_GAIN_DB) ? CONFIG.GONIO_DISPLAY_GAIN_DB : 0;
|
||||
gainDb = Math.max(GONIO_GAIN_MIN_DB, Math.min(GONIO_GAIN_MAX_DB, Math.round(gainDb / 5) * 5));
|
||||
const lineFadeMs = Number.isFinite(CONFIG?.GONIO_LINE_FADE_MS) ? CONFIG.GONIO_LINE_FADE_MS : 300;
|
||||
const lineFadeMs = resolveGoniometerPersistenceMs(CONFIG);
|
||||
const rtwClassic = true;
|
||||
return {
|
||||
gainDb,
|
||||
@@ -417,16 +426,37 @@ function resolveScopeSettings(CONFIG) {
|
||||
pointBaseAlpha: rtwClassic ? 0.14 : 0.16,
|
||||
pointBoostAlpha: rtwClassic ? 0.34 : 0.36,
|
||||
pointSize: rtwClassic ? 1.4 : 2.2,
|
||||
curveSmoothing: rtwClassic,
|
||||
// Do not bend the measured M/S sample path with cosmetic Bézier curves.
|
||||
curveSmoothing: false,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveSilenceGate(env, CONFIG, state) {
|
||||
export function resolveGoniometerPersistenceMs(CONFIG = {}) {
|
||||
const mode = String(CONFIG.GONIO_PERSISTENCE_MODE || 'fast').toLowerCase();
|
||||
if (mode === 'medium') return 150;
|
||||
if (mode === 'slow') return 300;
|
||||
if (mode === 'custom') {
|
||||
const custom = Number(CONFIG.GONIO_LINE_FADE_MS);
|
||||
return Number.isFinite(custom) ? Math.max(0, Math.min(600, custom)) : 50;
|
||||
}
|
||||
return 50;
|
||||
}
|
||||
|
||||
function resolveSilenceGate(CONFIG, state, xyData) {
|
||||
const enabled = CONFIG?.XY_SILENCE_GATE_ENABLED !== false;
|
||||
const threshold = Number.isFinite(CONFIG?.XY_SILENCE_THRESHOLD_RMS_DBFS)
|
||||
? CONFIG.XY_SILENCE_THRESHOLD_RMS_DBFS
|
||||
: CORR_SILENCE_THRESHOLD_DEFAULT;
|
||||
const rmsMono = Number.isFinite(env.audio?.rmsDb?.mono) ? env.audio.rmsDb.mono : -120;
|
||||
let power = 0;
|
||||
if (xyData?.ready && xyData.length > 0) {
|
||||
for (let index = 0; index < xyData.length; index++) {
|
||||
const left = Number(xyData.xyL[index]) || 0;
|
||||
const right = Number(xyData.xyR[index]) || 0;
|
||||
power += left * left + right * right;
|
||||
}
|
||||
power /= 2 * xyData.length;
|
||||
}
|
||||
const rmsMono = power > 1e-12 ? 10 * Math.log10(power) : -120;
|
||||
const targetActive = enabled ? (rmsMono >= threshold) : true;
|
||||
const now = getNow();
|
||||
const dt = state.gateLastTs ? Math.max(0, (now - state.gateLastTs) / 1000) : 0;
|
||||
@@ -450,7 +480,8 @@ function buildTrace(state, xyData, scope, scale, CONFIG) {
|
||||
if (targetPoints <= 1 || total <= 1) {
|
||||
return { buffer: null, count: 0 };
|
||||
}
|
||||
const sampleCount = Math.max(2, Math.min(targetPoints, MAX_TRACE_POINTS));
|
||||
// The browser may reduce density but must never invent interpolated samples.
|
||||
const sampleCount = Math.max(2, Math.min(targetPoints, total, MAX_TRACE_POINTS));
|
||||
if (sampleCount <= 1) {
|
||||
return { buffer: null, count: 0 };
|
||||
}
|
||||
@@ -497,7 +528,7 @@ function renderTrace(g, state, trace, scope, style, xyReady, settings) {
|
||||
|
||||
if (style === 'lines') {
|
||||
if (lineFadeMs <= 0) {
|
||||
trails.length = 0;
|
||||
clearTrails(state);
|
||||
if (hasTrace && trace.count > 1) {
|
||||
drawImmediateLine(g, trace, scope, settings);
|
||||
} else if (!xyReady) {
|
||||
@@ -505,14 +536,14 @@ function renderTrace(g, state, trace, scope, style, xyReady, settings) {
|
||||
}
|
||||
} else {
|
||||
if (hasTrace && trace.count > 1) {
|
||||
addLineTrail(trails, trace, now);
|
||||
addLineTrail(state, trace, now);
|
||||
}
|
||||
const rendered = drawLineTrails(g, trails, scope, now, lineFadeMs, settings);
|
||||
const rendered = drawLineTrails(g, trails, state.trailPool, scope, now, lineFadeMs, settings);
|
||||
if (!rendered && !xyReady) drawIdleMessage(g, scope);
|
||||
}
|
||||
} else {
|
||||
if (lineFadeMs <= 0) {
|
||||
trails.length = 0;
|
||||
clearTrails(state);
|
||||
if (hasTrace) {
|
||||
drawPointTrace(g, trace, scope, settings);
|
||||
} else if (!xyReady) {
|
||||
@@ -520,9 +551,9 @@ function renderTrace(g, state, trace, scope, style, xyReady, settings) {
|
||||
}
|
||||
} else {
|
||||
if (hasTrace && trace.count > 0) {
|
||||
addLineTrail(trails, trace, now);
|
||||
addLineTrail(state, trace, now);
|
||||
}
|
||||
const rendered = drawPointTrails(g, trails, scope, now, lineFadeMs, settings);
|
||||
const rendered = drawPointTrails(g, trails, state.trailPool, scope, now, lineFadeMs, settings);
|
||||
if (!rendered && !xyReady) {
|
||||
drawIdleMessage(g, scope);
|
||||
}
|
||||
@@ -530,17 +561,36 @@ function renderTrace(g, state, trace, scope, style, xyReady, settings) {
|
||||
}
|
||||
}
|
||||
|
||||
function addLineTrail(trails, trace, timestamp) {
|
||||
const coords = new Float32Array(trace.count * 2);
|
||||
function addLineTrail(state, trace, timestamp) {
|
||||
const trails = state.lineTrails || (state.lineTrails = []);
|
||||
const pool = state.trailPool || (state.trailPool = []);
|
||||
while (trails.length >= MAX_TRAIL_FRAMES) releaseTrail(pool, trails.shift());
|
||||
let coords = pool.pop();
|
||||
if (!(coords instanceof Float32Array) || coords.length < trace.count * 2) {
|
||||
coords = new Float32Array(trace.count * 2);
|
||||
}
|
||||
coords.set(trace.buffer.subarray(0, trace.count * 2));
|
||||
trails.push({ coords, count: trace.count, time: timestamp });
|
||||
}
|
||||
|
||||
function drawLineTrails(g, trails, scope, now, fadeMs, settings) {
|
||||
function releaseTrail(pool, trail) {
|
||||
if (trail?.coords instanceof Float32Array && pool.length < MAX_TRAIL_FRAMES) {
|
||||
pool.push(trail.coords);
|
||||
}
|
||||
}
|
||||
|
||||
function clearTrails(state) {
|
||||
const trails = state.lineTrails || [];
|
||||
const pool = state.trailPool || (state.trailPool = []);
|
||||
for (const trail of trails) releaseTrail(pool, trail);
|
||||
trails.length = 0;
|
||||
}
|
||||
|
||||
function drawLineTrails(g, trails, pool, scope, now, fadeMs, settings) {
|
||||
if (!trails.length) return false;
|
||||
|
||||
const cutoff = now - fadeMs;
|
||||
const keep = [];
|
||||
let keepCount = 0;
|
||||
let drawn = false;
|
||||
|
||||
g.save();
|
||||
@@ -549,13 +599,19 @@ function drawLineTrails(g, trails, scope, now, fadeMs, settings) {
|
||||
g.clip();
|
||||
|
||||
for (const trail of trails) {
|
||||
if (trail.time < cutoff) continue;
|
||||
if (trail.time < cutoff) {
|
||||
releaseTrail(pool, trail);
|
||||
continue;
|
||||
}
|
||||
const age = now - trail.time;
|
||||
const fade = Math.max(0, 1 - age / fadeMs);
|
||||
if (fade <= 0) continue;
|
||||
if (fade <= 0) {
|
||||
releaseTrail(pool, trail);
|
||||
continue;
|
||||
}
|
||||
|
||||
drawn = true;
|
||||
keep.push(trail);
|
||||
trails[keepCount++] = trail;
|
||||
|
||||
g.save();
|
||||
g.globalAlpha = (settings?.trailBaseAlpha ?? 0.35) + (settings?.trailBoostAlpha ?? 0.65) * fade;
|
||||
@@ -568,8 +624,7 @@ function drawLineTrails(g, trails, scope, now, fadeMs, settings) {
|
||||
|
||||
g.restore();
|
||||
|
||||
trails.length = 0;
|
||||
Array.prototype.push.apply(trails, keep);
|
||||
trails.length = keepCount;
|
||||
|
||||
return drawn;
|
||||
}
|
||||
@@ -611,11 +666,11 @@ function beginTracePath(g, coords, count, settings) {
|
||||
g.quadraticCurveTo(coords[prev], coords[prev + 1], coords[last], coords[last + 1]);
|
||||
}
|
||||
|
||||
function drawPointTrails(g, trails, scope, now, fadeMs, settings) {
|
||||
function drawPointTrails(g, trails, pool, scope, now, fadeMs, settings) {
|
||||
if (!trails.length) return false;
|
||||
|
||||
const cutoff = now - fadeMs;
|
||||
const keep = [];
|
||||
let keepCount = 0;
|
||||
let drawn = false;
|
||||
const pointSize = settings?.pointSize ?? 1.4;
|
||||
const half = pointSize / 2;
|
||||
@@ -626,13 +681,19 @@ function drawPointTrails(g, trails, scope, now, fadeMs, settings) {
|
||||
g.clip();
|
||||
|
||||
for (const trail of trails) {
|
||||
if (trail.time < cutoff) continue;
|
||||
if (trail.time < cutoff) {
|
||||
releaseTrail(pool, trail);
|
||||
continue;
|
||||
}
|
||||
const age = now - trail.time;
|
||||
const fade = Math.max(0, 1 - age / fadeMs);
|
||||
if (fade <= 0) continue;
|
||||
if (fade <= 0) {
|
||||
releaseTrail(pool, trail);
|
||||
continue;
|
||||
}
|
||||
|
||||
drawn = true;
|
||||
keep.push(trail);
|
||||
trails[keepCount++] = trail;
|
||||
|
||||
for (let i = 0; i < trail.count; i++) {
|
||||
const idx = i * 2;
|
||||
@@ -645,8 +706,7 @@ function drawPointTrails(g, trails, scope, now, fadeMs, settings) {
|
||||
|
||||
g.restore();
|
||||
|
||||
trails.length = 0;
|
||||
Array.prototype.push.apply(trails, keep);
|
||||
trails.length = keepCount;
|
||||
|
||||
return drawn;
|
||||
}
|
||||
@@ -703,6 +763,10 @@ function getNow() {
|
||||
function computeAgcGain(state, xyData, nowTs) {
|
||||
const attackTau = 0.001; // 1 ms
|
||||
const releaseDbPerS = 10;
|
||||
if (xyData.seq > 0 && xyData.seq === state.agcLastXySeq) {
|
||||
return { gainDb: state.agcGainDb, gain: dbToLinear(state.agcGainDb) };
|
||||
}
|
||||
state.agcLastXySeq = xyData.seq;
|
||||
const dt = state.agcLastTs ? Math.max(0, (nowTs - state.agcLastTs) / 1000) : 0;
|
||||
state.agcLastTs = nowTs;
|
||||
|
||||
@@ -785,7 +849,7 @@ function drawCorrelationBar(g, centerX, y, w, h, val, negativePeak) {
|
||||
g.lineWidth = 1;
|
||||
g.strokeRect(cubeX, cubeY, cubeSize, cubeSize);
|
||||
g.beginPath(); g.moveTo(mid, y); g.lineTo(mid, y + h); g.stroke();
|
||||
if (Number.isFinite(negativePeak) && negativePeak < 0.999) {
|
||||
if (Number.isFinite(negativePeak) && negativePeak < -0.001) {
|
||||
const markerX = clampCenter(mid + (clamp1(negativePeak) * 0.5) * w);
|
||||
g.fillStyle = WARN_COLOR;
|
||||
g.beginPath();
|
||||
|
||||
Reference in New Issue
Block a user