[perf](trx-frontend-http): smooth SDR spectrum rendering

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-02-27 23:43:48 +01:00
parent e212283d3c
commit f77d0b0bb1
@@ -1601,7 +1601,7 @@ async function applyBandwidthFromInput() {
const clamped = Math.max(minBw, Math.min(maxBw, next)); const clamped = Math.max(minBw, Math.min(maxBw, next));
currentBandwidthHz = clamped; currentBandwidthHz = clamped;
syncBandwidthInput(clamped); syncBandwidthInput(clamped);
if (lastSpectrumData) drawSpectrum(lastSpectrumData); if (lastSpectrumData) scheduleSpectrumDraw();
try { await postPath(`/set_bandwidth?hz=${clamped}`); } catch (_) {} try { await postPath(`/set_bandwidth?hz=${clamped}`); } catch (_) {}
} }
@@ -2497,6 +2497,8 @@ const spectrumTooltip = document.getElementById("spectrum-tooltip");
let spectrumSource = null; let spectrumSource = null;
let spectrumReconnectTimer = null; let spectrumReconnectTimer = null;
let lastSpectrumData = null; let lastSpectrumData = null;
let spectrumDrawPending = false;
let spectrumAxisKey = "";
// Zoom / pan state. zoom >= 1; panFrac in [0,1] is the fraction of the full // Zoom / pan state. zoom >= 1; panFrac in [0,1] is the fraction of the full
// bandwidth at the centre of the visible window. // bandwidth at the centre of the visible window.
@@ -2567,7 +2569,7 @@ function startSpectrumStreaming() {
try { try {
lastSpectrumData = JSON.parse(evt.data); lastSpectrumData = JSON.parse(evt.data);
refreshCenterFreqDisplay(); refreshCenterFreqDisplay();
drawSpectrum(lastSpectrumData); scheduleSpectrumDraw();
} catch (_) {} } catch (_) {}
}; };
spectrumSource.onerror = () => { spectrumSource.onerror = () => {
@@ -2588,6 +2590,7 @@ function stopSpectrumStreaming() {
clearTimeout(spectrumReconnectTimer); clearTimeout(spectrumReconnectTimer);
spectrumReconnectTimer = null; spectrumReconnectTimer = null;
} }
spectrumDrawPending = false;
lastSpectrumData = null; lastSpectrumData = null;
clearSpectrumCanvas(); clearSpectrumCanvas();
} }
@@ -2600,6 +2603,15 @@ function clearSpectrumCanvas() {
ctx.fillRect(0, 0, spectrumCanvas.width, spectrumCanvas.height); ctx.fillRect(0, 0, spectrumCanvas.width, spectrumCanvas.height);
} }
function scheduleSpectrumDraw() {
if (spectrumDrawPending) return;
spectrumDrawPending = true;
requestAnimationFrame(() => {
spectrumDrawPending = false;
if (lastSpectrumData) drawSpectrum(lastSpectrumData);
});
}
function drawSpectrum(data) { function drawSpectrum(data) {
if (!spectrumCanvas) return; if (!spectrumCanvas) return;
@@ -2773,6 +2785,14 @@ function updateSpectrumFreqAxis(range) {
const ideal = spanHz / 5; const ideal = spanHz / 5;
const stepHz = targets.reduce((best, s) => const stepHz = targets.reduce((best, s) =>
Math.abs(s - ideal) < Math.abs(best - ideal) ? s : best, targets[0]); Math.abs(s - ideal) < Math.abs(best - ideal) ? s : best, targets[0]);
const axisKey = [
Math.round(range.visLoHz),
Math.round(range.visHiHz),
Math.round(stepHz),
spectrumFreqAxis.clientWidth || 0,
].join(":");
if (axisKey === spectrumAxisKey) return;
spectrumAxisKey = axisKey;
const firstHz = Math.ceil(range.visLoHz / stepHz) * stepHz; const firstHz = Math.ceil(range.visLoHz / stepHz) * stepHz;
spectrumFreqAxis.innerHTML = ""; spectrumFreqAxis.innerHTML = "";
@@ -2818,7 +2838,7 @@ if (spectrumCanvas) {
const cssX = e.clientX - rect.left; const cssX = e.clientX - rect.left;
const factor = e.deltaY < 0 ? 1.25 : 1 / 1.25; const factor = e.deltaY < 0 ? 1.25 : 1 / 1.25;
spectrumZoomAt(cssX, rect.width, lastSpectrumData, factor); spectrumZoomAt(cssX, rect.width, lastSpectrumData, factor);
drawSpectrum(lastSpectrumData); scheduleSpectrumDraw();
}, { passive: false }); }, { passive: false });
} }
@@ -2872,7 +2892,7 @@ if (spectrumCanvas) {
newBw = Math.round(Math.max(minBw, Math.min(maxBw, newBw))); newBw = Math.round(Math.max(minBw, Math.min(maxBw, newBw)));
currentBandwidthHz = newBw; currentBandwidthHz = newBw;
syncBandwidthInput(newBw); syncBandwidthInput(newBw);
drawSpectrum(lastSpectrumData); scheduleSpectrumDraw();
return; return;
} }
if (!_sDragStart || !lastSpectrumData) return; if (!_sDragStart || !lastSpectrumData) return;
@@ -2880,7 +2900,7 @@ if (spectrumCanvas) {
const dx = e.clientX - _sDragStart.clientX; const dx = e.clientX - _sDragStart.clientX;
if (Math.abs(dx) > 3) _sDragMoved = true; if (Math.abs(dx) > 3) _sDragMoved = true;
spectrumPanFrac = _sDragStart.panFrac - (dx / rect.width) / spectrumZoom; spectrumPanFrac = _sDragStart.panFrac - (dx / rect.width) / spectrumZoom;
drawSpectrum(lastSpectrumData); scheduleSpectrumDraw();
}); });
window.addEventListener("mouseup", async () => { window.addEventListener("mouseup", async () => {
@@ -2936,11 +2956,11 @@ if (spectrumCanvas) {
// Pan contribution from mid shift // Pan contribution from mid shift
const dxMid = newMidX - _sTouch.midX; const dxMid = newMidX - _sTouch.midX;
spectrumPanFrac -= (dxMid / rect.width) / spectrumZoom; spectrumPanFrac -= (dxMid / rect.width) / spectrumZoom;
drawSpectrum(lastSpectrumData); scheduleSpectrumDraw();
} else if (_sTouch.type === "pan" && e.touches.length === 1) { } else if (_sTouch.type === "pan" && e.touches.length === 1) {
const dx = e.touches[0].clientX - _sTouch.clientX; const dx = e.touches[0].clientX - _sTouch.clientX;
spectrumPanFrac = _sTouch.panFrac - (dx / rect.width) / spectrumZoom; spectrumPanFrac = _sTouch.panFrac - (dx / rect.width) / spectrumZoom;
drawSpectrum(lastSpectrumData); scheduleSpectrumDraw();
} }
}, { passive: false }); }, { passive: false });
@@ -2994,7 +3014,7 @@ if (spectrumCanvas) {
const v = Number(floorInput.value); const v = Number(floorInput.value);
if (!isNaN(v)) { if (!isNaN(v)) {
spectrumFloor = v; spectrumFloor = v;
if (lastSpectrumData) drawSpectrum(lastSpectrumData); if (lastSpectrumData) scheduleSpectrumDraw();
} }
}); });
} }
@@ -3009,7 +3029,7 @@ if (spectrumCanvas) {
spectrumFloor = Math.floor(noise / 10) * 10 - 10; spectrumFloor = Math.floor(noise / 10) * 10 - 10;
spectrumRange = Math.max(60, Math.ceil((peak - spectrumFloor) / 10) * 10 + 10); spectrumRange = Math.max(60, Math.ceil((peak - spectrumFloor) / 10) * 10 + 10);
if (floorInput) floorInput.value = spectrumFloor; if (floorInput) floorInput.value = spectrumFloor;
drawSpectrum(lastSpectrumData); scheduleSpectrumDraw();
}); });
} }
})(); })();