[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:
@@ -1901,6 +1901,7 @@ function formatBwLabel(hz) {
|
|||||||
let currentBandwidthHz = 3_000;
|
let currentBandwidthHz = 3_000;
|
||||||
const spectrumBwInput = document.getElementById("spectrum-bw-input");
|
const spectrumBwInput = document.getElementById("spectrum-bw-input");
|
||||||
const spectrumBwSetBtn = document.getElementById("spectrum-bw-set-btn");
|
const spectrumBwSetBtn = document.getElementById("spectrum-bw-set-btn");
|
||||||
|
const spectrumBwAutoBtn = document.getElementById("spectrum-bw-auto-btn");
|
||||||
|
|
||||||
function formatBandwidthInputKhz(hz) {
|
function formatBandwidthInputKhz(hz) {
|
||||||
const khz = hz / 1000;
|
const khz = hz / 1000;
|
||||||
@@ -1944,6 +1945,76 @@ async function applyBandwidthFromInput() {
|
|||||||
try { await postPath(`/set_bandwidth?hz=${clamped}`); } catch (_) {}
|
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) {
|
if (spectrumBwInput) {
|
||||||
spectrumBwInput.addEventListener("keydown", (e) => {
|
spectrumBwInput.addEventListener("keydown", (e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
@@ -1955,6 +2026,9 @@ if (spectrumBwInput) {
|
|||||||
if (spectrumBwSetBtn) {
|
if (spectrumBwSetBtn) {
|
||||||
spectrumBwSetBtn.addEventListener("click", () => { applyBandwidthFromInput(); });
|
spectrumBwSetBtn.addEventListener("click", () => { applyBandwidthFromInput(); });
|
||||||
}
|
}
|
||||||
|
if (spectrumBwAutoBtn) {
|
||||||
|
spectrumBwAutoBtn.addEventListener("click", () => { applyAutoBandwidth(); });
|
||||||
|
}
|
||||||
|
|
||||||
// --- Tab navigation ---
|
// --- Tab navigation ---
|
||||||
document.querySelector(".tab-bar").addEventListener("click", (e) => {
|
document.querySelector(".tab-bar").addEventListener("click", (e) => {
|
||||||
|
|||||||
@@ -80,6 +80,7 @@
|
|||||||
<div id="spectrum-bw-row">
|
<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>
|
<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-set-btn" type="button">Set</button>
|
||||||
|
<button id="spectrum-bw-auto-btn" type="button">Auto BW</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="spectrum-level-row">
|
<div id="spectrum-level-row">
|
||||||
<label class="overview-control" id="spectrum-peak-hold-label">Peak Hold
|
<label class="overview-control" id="spectrum-peak-hold-label">Peak Hold
|
||||||
|
|||||||
Reference in New Issue
Block a user