[feat](trx-frontend-http): add automatic bandwidth sizing

Add an Auto BW control that estimates a suitable\nreceive bandwidth from the live spectrum around the\ncurrently tuned peak and applies it to the server.\n\nCo-authored-by: Codex <codex@openai.com>

Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-02-28 09:09:39 +01:00
parent 454cf33d6b
commit e1ab2ae9dc
2 changed files with 75 additions and 0 deletions
@@ -1901,6 +1901,7 @@ function formatBwLabel(hz) {
let currentBandwidthHz = 3_000;
const spectrumBwInput = document.getElementById("spectrum-bw-input");
const spectrumBwSetBtn = document.getElementById("spectrum-bw-set-btn");
const spectrumBwAutoBtn = document.getElementById("spectrum-bw-auto-btn");
function formatBandwidthInputKhz(hz) {
const khz = hz / 1000;
@@ -1944,6 +1945,76 @@ async function applyBandwidthFromInput() {
try { await postPath(`/set_bandwidth?hz=${clamped}`); } catch (_) {}
}
function estimateBandwidthAroundPeak(data, centerHz) {
if (!data || !Array.isArray(data.bins) || data.bins.length < 3 || !Number.isFinite(centerHz)) {
return null;
}
const bins = data.bins;
const maxIdx = bins.length - 1;
const fullLoHz = data.center_hz - data.sample_rate / 2;
const centerIdx = Math.max(
1,
Math.min(maxIdx - 1, Math.round(((centerHz - fullLoHz) / data.sample_rate) * maxIdx)),
);
const searchRadius = Math.max(6, Math.min(120, Math.round(maxIdx * 0.03)));
const searchLo = Math.max(1, centerIdx - searchRadius);
const searchHi = Math.min(maxIdx - 1, centerIdx + searchRadius);
let peakIdx = centerIdx;
for (let i = searchLo; i <= searchHi; i++) {
if (bins[i] > bins[peakIdx]) peakIdx = i;
}
const sorted = [...bins].sort((a, b) => a - b);
const noise = sorted[Math.floor(sorted.length * 0.2)];
const peak = bins[peakIdx];
const threshold = Math.max(noise + 4, peak - Math.max(8, (peak - noise) * 0.35));
let left = peakIdx;
let right = peakIdx;
let belowCount = 0;
for (let i = peakIdx; i > 1; i--) {
if (bins[i] < threshold) belowCount += 1;
else belowCount = 0;
if (belowCount >= 2) break;
left = i;
}
belowCount = 0;
for (let i = peakIdx; i < maxIdx - 1; i++) {
if (bins[i] < threshold) belowCount += 1;
else belowCount = 0;
if (belowCount >= 2) break;
right = i;
}
const shoulderPad = Math.max(1, Math.round((right - left) * 0.08));
left = Math.max(0, left - shoulderPad);
right = Math.min(maxIdx, right + shoulderPad);
const hzPerBin = data.sample_rate / maxIdx;
const rawBw = Math.max(hzPerBin, (right - left) * hzPerBin);
const [, minBw, maxBw, stepBw] = mwDefaultsForMode(modeEl ? modeEl.value : "USB");
const clamped = Math.max(minBw, Math.min(maxBw, rawBw));
return Math.max(stepBw, Math.round(clamped / stepBw) * stepBw);
}
async function applyAutoBandwidth() {
if (!lastSpectrumData || lastFreqHz == null) return;
const estimated = estimateBandwidthAroundPeak(lastSpectrumData, lastFreqHz);
if (!Number.isFinite(estimated) || estimated <= 0) {
syncBandwidthInput(currentBandwidthHz);
return;
}
currentBandwidthHz = estimated;
syncBandwidthInput(estimated);
if (lastSpectrumData) scheduleSpectrumDraw();
try {
await postPath(`/set_bandwidth?hz=${estimated}`);
} catch (_) {}
}
if (spectrumBwInput) {
spectrumBwInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
@@ -1955,6 +2026,9 @@ if (spectrumBwInput) {
if (spectrumBwSetBtn) {
spectrumBwSetBtn.addEventListener("click", () => { applyBandwidthFromInput(); });
}
if (spectrumBwAutoBtn) {
spectrumBwAutoBtn.addEventListener("click", () => { applyAutoBandwidth(); });
}
// --- Tab navigation ---
document.querySelector(".tab-bar").addEventListener("click", (e) => {
@@ -80,6 +80,7 @@
<div id="spectrum-bw-row">
<label id="spectrum-bw-label">Bandwidth <input type="number" id="spectrum-bw-input" value="" step="0.1" min="0.1" /> kHz</label>
<button id="spectrum-bw-set-btn" type="button">Set</button>
<button id="spectrum-bw-auto-btn" type="button">Auto BW</button>
</div>
<div id="spectrum-level-row">
<label class="overview-control" id="spectrum-peak-hold-label">Peak Hold