[fix](trx-frontend): fix CW audio picker and bookmark add button

Use an audio-window tone picker for CW with exact click-to-tone mapping.
Make + Add Bookmark inherit the shared button style.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-04 22:46:08 +01:00
parent 2a9cd5ed0e
commit 1ebe65e0f2
3 changed files with 46 additions and 47 deletions
@@ -565,7 +565,7 @@
</div>
<div class="cw-tone-picker">
<div class="cw-tone-picker-head">
<span>CW Tone Picker</span>
<span>CW Audio Tone Picker</span>
<small id="cw-tone-range">--</small>
</div>
<canvas id="cw-tone-waterfall" width="320" height="56" aria-label="CW tone selector"></canvas>
@@ -48,34 +48,60 @@ function clampCwTone(tone) {
}
function currentCwToneRange() {
const centerHz = Number.isFinite(window.lastFreqHz) ? Number(window.lastFreqHz) : NaN;
const tunedHz = Number.isFinite(window.lastFreqHz) ? Number(window.lastFreqHz) : NaN;
const bandwidthHz = Number.isFinite(window.currentBandwidthHz) ? Number(window.currentBandwidthHz) : NaN;
if (!Number.isFinite(centerHz) || !Number.isFinite(bandwidthHz) || bandwidthHz <= 0) {
if (!Number.isFinite(tunedHz) || !Number.isFinite(bandwidthHz) || bandwidthHz <= 0) {
return null;
}
const mode = String(document.getElementById("mode")?.value || "").toUpperCase();
const lowerSideband = mode === "CWR";
const upperSideband = mode === "CW";
if (!lowerSideband && !upperSideband) return null;
const lowHz = lowerSideband ? centerHz - bandwidthHz : centerHz;
const highHz = lowerSideband ? centerHz : centerHz + bandwidthHz;
const spectrumSampleRateHz = Number(window.lastSpectrumData?.sample_rate);
const spectrumCenterHz = Number(window.lastSpectrumData?.center_hz);
let maxToneFromSpectrumHz = Number.POSITIVE_INFINITY;
if (
Number.isFinite(spectrumSampleRateHz)
&& spectrumSampleRateHz > 0
&& Number.isFinite(spectrumCenterHz)
) {
const spectrumLoHz = spectrumCenterHz - spectrumSampleRateHz / 2;
const spectrumHiHz = spectrumCenterHz + spectrumSampleRateHz / 2;
maxToneFromSpectrumHz = lowerSideband
? tunedHz - spectrumLoHz
: spectrumHiHz - tunedHz;
}
const toneMinHz = CW_TONE_MIN_HZ;
const toneMaxHz = Math.min(CW_TONE_MAX_HZ, Math.round(bandwidthHz));
const toneMaxHz = Math.min(
CW_TONE_MAX_HZ,
Math.round(bandwidthHz),
Number.isFinite(maxToneFromSpectrumHz)
? Math.floor(maxToneFromSpectrumHz)
: CW_TONE_MAX_HZ,
);
if (toneMaxHz < toneMinHz) {
return null;
}
return {
lowHz,
highHz,
centerHz,
tunedHz,
bandwidthHz,
toneMinHz,
toneMaxHz,
toneSpanHz: Math.max(1, toneMaxHz - toneMinHz),
lowerSideband,
mode,
};
}
function cwToneToRfHz(range, toneHz) {
if (!range) return NaN;
return range.lowerSideband
? range.tunedHz - toneHz
: range.tunedHz + toneHz;
}
function toneClampForRange(tone, range) {
const clamped = clampCwTone(tone);
if (!range) return clamped;
@@ -108,13 +134,15 @@ function drawCwTonePicker() {
ctx.clearRect(0, 0, width, height);
const range = currentCwToneRange();
if (!range || !window.lastSpectrumData || !Array.isArray(window.lastSpectrumData.bins) || !window.lastSpectrumData.bins.length) {
if (!window.lastSpectrumData || !Array.isArray(window.lastSpectrumData.bins) || !window.lastSpectrumData.bins.length || !range) {
if (cwToneRangeEl) {
const mode = String(document.getElementById("mode")?.value || "").toUpperCase();
if (mode !== "CW" && mode !== "CWR") {
cwToneRangeEl.textContent = "CW/CWR mode required";
} else {
} else if (!window.lastSpectrumData || !Array.isArray(window.lastSpectrumData.bins) || !window.lastSpectrumData.bins.length) {
cwToneRangeEl.textContent = "Waiting for spectrum";
} else {
cwToneRangeEl.textContent = "Audio tone window is outside spectrum";
}
}
ctx.fillStyle = "rgba(130, 150, 165, 0.22)";
@@ -124,7 +152,7 @@ function drawCwTonePicker() {
if (cwToneRangeEl) {
const side = range.lowerSideband ? "Lower side" : "Upper side";
cwToneRangeEl.textContent = `${side} · Tone ${range.toneMinHz}-${range.toneMaxHz} Hz`;
cwToneRangeEl.textContent = `Audio ${range.toneMinHz}-${range.toneMaxHz} Hz · ${side}`;
}
const bins = window.lastSpectrumData.bins;
@@ -137,8 +165,9 @@ function drawCwTonePicker() {
let minPower = Number.POSITIVE_INFINITY;
for (let x = 0; x < width; x += 1) {
const frac = width <= 1 ? 0 : x / (width - 1);
const toneHz = range.lowHz + frac * (range.highHz - range.lowHz);
const idx = Math.max(0, Math.min(maxIdx, Math.round((((toneHz - fullLoHz) / sampleRate) * maxIdx))));
const toneHz = range.toneMinHz + frac * range.toneSpanHz;
const rfHz = cwToneToRfHz(range, toneHz);
const idx = Math.max(0, Math.min(maxIdx, Math.round((((rfHz - fullLoHz) / sampleRate) * maxIdx))));
const power = Number.isFinite(Number(bins[idx])) ? Number(bins[idx]) : -140;
tones[x] = power;
if (power > maxPower) maxPower = power;
@@ -155,26 +184,11 @@ function drawCwTonePicker() {
}
const currentTone = toneClampForRange(cwToneInput ? cwToneInput.value : 700, range);
const markerHz = range.lowerSideband
? range.centerHz - currentTone
: range.centerHz + currentTone;
const markerFrac = (markerHz - range.lowHz) / Math.max(1, (range.highHz - range.lowHz));
const markerFrac = (currentTone - range.toneMinHz) / range.toneSpanHz;
const markerX = Math.max(0, Math.min(width - 1, Math.round(markerFrac * (width - 1))));
ctx.fillStyle = "rgba(255, 255, 255, 0.9)";
ctx.fillRect(markerX, 0, 2, height);
const lowLimitHz = range.lowerSideband
? range.centerHz - range.toneMaxHz
: range.centerHz + range.toneMinHz;
const highLimitHz = range.lowerSideband
? range.centerHz - range.toneMinHz
: range.centerHz + range.toneMaxHz;
const limitLowX = Math.max(0, Math.min(width - 1, Math.round(((lowLimitHz - range.lowHz) / Math.max(1, range.highHz - range.lowHz)) * (width - 1))));
const limitHighX = Math.max(0, Math.min(width - 1, Math.round(((highLimitHz - range.lowHz) / Math.max(1, range.highHz - range.lowHz)) * (width - 1))));
ctx.fillStyle = "rgba(255, 255, 255, 0.22)";
ctx.fillRect(limitLowX, 0, 1, height);
ctx.fillRect(limitHighX, 0, 1, height);
if (cwAutoInput?.checked) {
ctx.fillStyle = "rgba(0, 0, 0, 0.22)";
ctx.fillRect(0, 0, width, height);
@@ -233,11 +247,7 @@ if (cwToneCanvas) {
const range = currentCwToneRange();
if (!range) return;
const frac = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));
const rfHz = range.lowHz + frac * (range.highHz - range.lowHz);
const signedOffsetHz = range.lowerSideband
? range.centerHz - rfHz
: rfHz - range.centerHz;
const tone = Math.max(range.toneMinHz, Math.min(range.toneMaxHz, signedOffsetHz));
const tone = range.toneMinHz + frac * range.toneSpanHz;
await setCwTone(tone);
});
}
@@ -2716,18 +2716,7 @@ button:focus-visible, input:focus-visible, select:focus-visible {
}
.bm-add-btn {
background: var(--accent-green);
color: #fff;
border: none;
border-radius: 0.35rem;
padding: 0.4rem 0.85rem;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
}
.bm-add-btn:hover {
opacity: 0.88;
white-space: nowrap;
}
#bm-form-wrap {