[feat](trx-frontend-http): mark spectrum peaks

Draw small peak markers on strong visible spectrum maxima\nso snap-tune targets are easier to spot.\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:25:56 +01:00
parent ff8625f04e
commit 88e0be0cdd
@@ -3028,6 +3028,45 @@ function nearestSpectrumPeakHz(cssX, cssW, data) {
return nearestSpectrumPeak(cssX, cssW, data)?.hz ?? null;
}
function visibleSpectrumPeakIndices(data, limit = 24) {
if (!data || !Array.isArray(data.bins) || data.bins.length < 3) {
return [];
}
const bins = data.bins;
const maxIdx = bins.length - 1;
const range = spectrumVisibleRange(data);
const fullLoHz = data.center_hz - data.sample_rate / 2;
const visStartIdx = Math.max(
1,
Math.min(maxIdx - 1, Math.floor(((range.visLoHz - fullLoHz) / data.sample_rate) * maxIdx)),
);
const visEndIdx = Math.max(
visStartIdx,
Math.min(maxIdx - 1, Math.ceil(((range.visHiHz - fullLoHz) / data.sample_rate) * maxIdx)),
);
const peaks = [];
for (let i = visStartIdx; i <= visEndIdx; i++) {
const v = bins[i];
if (v >= bins[i - 1] && v >= bins[i + 1]) {
peaks.push(i);
}
}
if (peaks.length === 0) {
return [];
}
const peakValues = peaks.map((i) => bins[i]).sort((a, b) => a - b);
const cutoff = peakValues[Math.max(0, Math.floor(peakValues.length * 0.7))];
return peaks
.filter((i) => bins[i] >= cutoff)
.sort((a, b) => bins[b] - bins[a])
.slice(0, limit)
.sort((a, b) => a - b);
}
// Format a frequency according to the current jog-step unit.
function formatSpectrumFreq(hz) {
if (jogStep >= 1_000_000) return (hz / 1e6).toFixed(3) + " MHz";
@@ -3305,6 +3344,25 @@ function drawSpectrum(data) {
ctx.stroke();
ctx.restore();
// Peak markers for easier snap-tune targeting.
const markerPeaks = visibleSpectrumPeakIndices(data);
if (markerPeaks.length > 0) {
ctx.save();
ctx.fillStyle = pal.waveformPeak;
ctx.strokeStyle = pal.bg;
ctx.lineWidth = Math.max(1, dpr * 0.75);
const radius = Math.max(2, dpr * 1.6);
for (const idx of markerPeaks) {
const x = binX(idx);
const y = binY(idx);
ctx.beginPath();
ctx.arc(x, y - radius * 0.35, radius, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
ctx.restore();
}
// ── Tuned-frequency marker ────────────────────────────────────────────────
if (lastFreqHz != null) {
const xf = hzToX(lastFreqHz);