[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:
@@ -565,7 +565,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="cw-tone-picker">
|
<div class="cw-tone-picker">
|
||||||
<div class="cw-tone-picker-head">
|
<div class="cw-tone-picker-head">
|
||||||
<span>CW Tone Picker</span>
|
<span>CW Audio Tone Picker</span>
|
||||||
<small id="cw-tone-range">--</small>
|
<small id="cw-tone-range">--</small>
|
||||||
</div>
|
</div>
|
||||||
<canvas id="cw-tone-waterfall" width="320" height="56" aria-label="CW tone selector"></canvas>
|
<canvas id="cw-tone-waterfall" width="320" height="56" aria-label="CW tone selector"></canvas>
|
||||||
|
|||||||
@@ -48,34 +48,60 @@ function clampCwTone(tone) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function currentCwToneRange() {
|
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;
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
const mode = String(document.getElementById("mode")?.value || "").toUpperCase();
|
const mode = String(document.getElementById("mode")?.value || "").toUpperCase();
|
||||||
const lowerSideband = mode === "CWR";
|
const lowerSideband = mode === "CWR";
|
||||||
const upperSideband = mode === "CW";
|
const upperSideband = mode === "CW";
|
||||||
if (!lowerSideband && !upperSideband) return null;
|
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 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) {
|
if (toneMaxHz < toneMinHz) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
lowHz,
|
tunedHz,
|
||||||
highHz,
|
|
||||||
centerHz,
|
|
||||||
bandwidthHz,
|
bandwidthHz,
|
||||||
toneMinHz,
|
toneMinHz,
|
||||||
toneMaxHz,
|
toneMaxHz,
|
||||||
|
toneSpanHz: Math.max(1, toneMaxHz - toneMinHz),
|
||||||
lowerSideband,
|
lowerSideband,
|
||||||
mode,
|
mode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cwToneToRfHz(range, toneHz) {
|
||||||
|
if (!range) return NaN;
|
||||||
|
return range.lowerSideband
|
||||||
|
? range.tunedHz - toneHz
|
||||||
|
: range.tunedHz + toneHz;
|
||||||
|
}
|
||||||
|
|
||||||
function toneClampForRange(tone, range) {
|
function toneClampForRange(tone, range) {
|
||||||
const clamped = clampCwTone(tone);
|
const clamped = clampCwTone(tone);
|
||||||
if (!range) return clamped;
|
if (!range) return clamped;
|
||||||
@@ -108,13 +134,15 @@ function drawCwTonePicker() {
|
|||||||
ctx.clearRect(0, 0, width, height);
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
const range = currentCwToneRange();
|
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) {
|
if (cwToneRangeEl) {
|
||||||
const mode = String(document.getElementById("mode")?.value || "").toUpperCase();
|
const mode = String(document.getElementById("mode")?.value || "").toUpperCase();
|
||||||
if (mode !== "CW" && mode !== "CWR") {
|
if (mode !== "CW" && mode !== "CWR") {
|
||||||
cwToneRangeEl.textContent = "CW/CWR mode required";
|
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";
|
cwToneRangeEl.textContent = "Waiting for spectrum";
|
||||||
|
} else {
|
||||||
|
cwToneRangeEl.textContent = "Audio tone window is outside spectrum";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.fillStyle = "rgba(130, 150, 165, 0.22)";
|
ctx.fillStyle = "rgba(130, 150, 165, 0.22)";
|
||||||
@@ -124,7 +152,7 @@ function drawCwTonePicker() {
|
|||||||
|
|
||||||
if (cwToneRangeEl) {
|
if (cwToneRangeEl) {
|
||||||
const side = range.lowerSideband ? "Lower side" : "Upper side";
|
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;
|
const bins = window.lastSpectrumData.bins;
|
||||||
@@ -137,8 +165,9 @@ function drawCwTonePicker() {
|
|||||||
let minPower = Number.POSITIVE_INFINITY;
|
let minPower = Number.POSITIVE_INFINITY;
|
||||||
for (let x = 0; x < width; x += 1) {
|
for (let x = 0; x < width; x += 1) {
|
||||||
const frac = width <= 1 ? 0 : x / (width - 1);
|
const frac = width <= 1 ? 0 : x / (width - 1);
|
||||||
const toneHz = range.lowHz + frac * (range.highHz - range.lowHz);
|
const toneHz = range.toneMinHz + frac * range.toneSpanHz;
|
||||||
const idx = Math.max(0, Math.min(maxIdx, Math.round((((toneHz - fullLoHz) / sampleRate) * maxIdx))));
|
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;
|
const power = Number.isFinite(Number(bins[idx])) ? Number(bins[idx]) : -140;
|
||||||
tones[x] = power;
|
tones[x] = power;
|
||||||
if (power > maxPower) maxPower = power;
|
if (power > maxPower) maxPower = power;
|
||||||
@@ -155,26 +184,11 @@ function drawCwTonePicker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentTone = toneClampForRange(cwToneInput ? cwToneInput.value : 700, range);
|
const currentTone = toneClampForRange(cwToneInput ? cwToneInput.value : 700, range);
|
||||||
const markerHz = range.lowerSideband
|
const markerFrac = (currentTone - range.toneMinHz) / range.toneSpanHz;
|
||||||
? range.centerHz - currentTone
|
|
||||||
: range.centerHz + currentTone;
|
|
||||||
const markerFrac = (markerHz - range.lowHz) / Math.max(1, (range.highHz - range.lowHz));
|
|
||||||
const markerX = Math.max(0, Math.min(width - 1, Math.round(markerFrac * (width - 1))));
|
const markerX = Math.max(0, Math.min(width - 1, Math.round(markerFrac * (width - 1))));
|
||||||
ctx.fillStyle = "rgba(255, 255, 255, 0.9)";
|
ctx.fillStyle = "rgba(255, 255, 255, 0.9)";
|
||||||
ctx.fillRect(markerX, 0, 2, height);
|
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) {
|
if (cwAutoInput?.checked) {
|
||||||
ctx.fillStyle = "rgba(0, 0, 0, 0.22)";
|
ctx.fillStyle = "rgba(0, 0, 0, 0.22)";
|
||||||
ctx.fillRect(0, 0, width, height);
|
ctx.fillRect(0, 0, width, height);
|
||||||
@@ -233,11 +247,7 @@ if (cwToneCanvas) {
|
|||||||
const range = currentCwToneRange();
|
const range = currentCwToneRange();
|
||||||
if (!range) return;
|
if (!range) return;
|
||||||
const frac = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));
|
const frac = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));
|
||||||
const rfHz = range.lowHz + frac * (range.highHz - range.lowHz);
|
const tone = range.toneMinHz + frac * range.toneSpanHz;
|
||||||
const signedOffsetHz = range.lowerSideband
|
|
||||||
? range.centerHz - rfHz
|
|
||||||
: rfHz - range.centerHz;
|
|
||||||
const tone = Math.max(range.toneMinHz, Math.min(range.toneMaxHz, signedOffsetHz));
|
|
||||||
await setCwTone(tone);
|
await setCwTone(tone);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2716,18 +2716,7 @@ button:focus-visible, input:focus-visible, select:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bm-add-btn {
|
.bm-add-btn {
|
||||||
background: var(--accent-green);
|
white-space: nowrap;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#bm-form-wrap {
|
#bm-form-wrap {
|
||||||
|
|||||||
Reference in New Issue
Block a user