Add configurable peak hold grace time for VU meter

This commit is contained in:
Armin 2026-07-08 18:26:26 +02:00
commit 34c0a4ccf4

View file

@ -219,6 +219,15 @@ footer .footer-track { flex: 1; min-width: 0; white-space: nowrap; overflow: hid
<option value="0.9">Fast</option>
<option value="0.8">Very Fast</option>
</select>
<span style="font-size:11px;color:#6b7089;margin-left:8px">Peak Hold:</span>
<select id="vuPeakHoldSelect">
<option value="0" selected>Off</option>
<option value="6">100ms</option>
<option value="12">200ms</option>
<option value="30">500ms</option>
<option value="60">1s</option>
<option value="120">2s</option>
</select>
<span style="font-size:11px;color:#6b7089;margin-left:8px">Crossfade:</span>
<select id="crossfadeSelect">
<option value="0" selected>Off</option>
@ -355,6 +364,8 @@ let peakData = [];
let smoothedData = [];
let vuPeakL = 0, vuPeakR = 0;
let vuSmoothL = 0, vuSmoothR = 0;
let vuPeakHoldL = 0, vuPeakHoldR = 0;
let vuPeakGrace = 0;
function bessi0(x) {
let s = 1, t = 1;
for (let k = 1; k <= 20; k++) {
@ -572,10 +583,11 @@ function drawSpectrum() {
if (levelR > vuSmoothR) vuSmoothR = levelR; else vuSmoothR = vuSmoothR * vuSmooth + levelR * (1 - vuSmooth);
levelL = vuSmoothL;
levelR = vuSmoothR;
function drawVuRow(y, level, peak) {
function drawVuRow(y, level, peak, hold) {
const lit = Math.round(level * segCount);
if (level > peak) peak = level;
else peak *= 0.990;
if (level > peak) { peak = level; hold = 0; }
else if (hold < vuPeakGrace) { hold++; }
else { peak *= 0.990; }
for (let i = 0; i < segCount; i++) {
const sx = segLeft + i * (segW + segG);
const on = i < lit;
@ -594,10 +606,12 @@ function drawSpectrum() {
vuctx.fillRect(sx, y, segW, rowH);
}
}
return peak;
return [peak, hold];
}
vuPeakL = drawVuRow(0, levelL, vuPeakL);
vuPeakR = drawVuRow(gap + rowH, levelR, vuPeakR);
const rL = drawVuRow(0, levelL, vuPeakL, vuPeakHoldL);
vuPeakL = rL[0]; vuPeakHoldL = rL[1];
const rR = drawVuRow(gap + rowH, levelR, vuPeakR, vuPeakHoldR);
vuPeakR = rR[0]; vuPeakHoldR = rR[1];
}
animId = requestAnimationFrame(drawSpectrum);
}
@ -723,6 +737,7 @@ const errorEl = document.getElementById('error');
const windowSelect = document.getElementById('windowSelect');
const decaySelect = document.getElementById('decaySelect');
const barDecaySelect = document.getElementById('barDecaySelect');
const vuPeakHoldSelect = document.getElementById('vuPeakHoldSelect');
const crossfadeSelect = document.getElementById('crossfadeSelect');
const infoBtn = document.getElementById('infoBtn');
const tagEditorOverlay = document.getElementById('tagEditorOverlay');
@ -1685,6 +1700,7 @@ sortSelect.addEventListener('change', () => {
windowSelect.addEventListener('change', () => { windowType = windowSelect.value; });
decaySelect.addEventListener('change', () => { peakDecay = parseFloat(decaySelect.value); });
barDecaySelect.addEventListener('change', () => { barDecay = parseFloat(barDecaySelect.value); });
vuPeakHoldSelect.addEventListener('change', () => { vuPeakGrace = parseInt(vuPeakHoldSelect.value, 10); });
crossfadeSelect.addEventListener('change', () => { crossfadeDuration = parseFloat(crossfadeSelect.value); });
crossfadeDuration = parseFloat(crossfadeSelect.value);
repeatSelect.addEventListener('change', () => { repeatMode = repeatSelect.value; });