[feat](trx-frontend-http): set the squelch on the spectrum, in dB
The threshold is in dB, and since the squelch fix that is the scale the spectrum axis and the S-meter are labelled in — so the control belongs on the plot, at the level it gates. A dashed line spans the spectrum at its threshold with a grip that reads it out, dragged like the bandwidth edges, green while the signal is above it and amber while it gates. Arrow keys move it a dB at a time for anyone not using a mouse. The audio row keeps a compact version: the dB, an indicator lit from the same meter the DSP compares against, Auto, and an enable toggle that no longer doubles as the level. The slider ran 0-100% over that dB range, which gave the operator a number with nothing on screen to relate it to, and zero meant "disabled", so turning the squelch off to listen threw the threshold away. Auto now says which level it picked. Two things the browser could only show once it was on the plot: the grip landed underneath the split control at the right edge, which swallowed its pointer, and dragging to the foot of the axis hid the line — and the grip with it — instead of pinning it where it could be dragged back. The fixture could not exercise any of this: /audio answered 404, which hides the audio row and the control inside it, and the status carried no filter block, which is what tells the client the rig has a squelch at all. Both now look like an SDR, and the spectrum test drives the line. Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -2244,6 +2244,7 @@ function formatSigStrength(dbm) {
|
||||
return `${dbfs.toFixed(1)} ${sigUnit("dBFS")}`;
|
||||
}
|
||||
function refreshSigStrengthDisplay() {
|
||||
renderSdrSquelch();
|
||||
if (!sigStrengthEl) return;
|
||||
sigStrengthEl.innerHTML = formatSigStrength(sigLastDbm);
|
||||
}
|
||||
@@ -6209,11 +6210,14 @@ var modeControlsRow = document.getElementById("mode-controls-row");
|
||||
var samStereoWidthEl = document.getElementById("sam-stereo-width");
|
||||
var samCarrierSyncEl = document.getElementById("sam-carrier-sync");
|
||||
var sdrSquelchWrapEl = document.getElementById("sdr-squelch-wrap");
|
||||
var sdrSquelchEl = document.getElementById("sdr-squelch");
|
||||
var sdrSquelchPctEl = document.getElementById("sdr-squelch-pct");
|
||||
var sdrSquelchDbEl = document.getElementById("sdr-squelch-db");
|
||||
var sdrSquelchStateEl = document.getElementById("sdr-squelch-state");
|
||||
var sdrSquelchToggleBtn = document.getElementById("sdr-squelch-toggle");
|
||||
var squelchLineEl = document.getElementById("spectrum-squelch-line");
|
||||
var squelchGripEl = document.getElementById("spectrum-squelch-grip");
|
||||
var squelchLabelEl = document.getElementById("spectrum-squelch-label");
|
||||
var SDR_SQUELCH_MIN_DB = -120;
|
||||
var SDR_SQUELCH_MAX_DB = -30;
|
||||
var syncFromServerSdrSquelch = false;
|
||||
var sdrNbWrapEl = document.getElementById("sdr-nb-wrap");
|
||||
var sdrNbEnabledEl = document.getElementById("sdr-nb-enabled");
|
||||
var sdrNbThresholdControlsEl = document.getElementById("sdr-nb-threshold-controls");
|
||||
@@ -6302,89 +6306,162 @@ function normalizeWfmDenoiseLevel(value) {
|
||||
if (next === "off" || next === "auto" || next === "low" || next === "medium" || next === "high") return next;
|
||||
return "auto";
|
||||
}
|
||||
function clampSdrSquelchPercent(value) {
|
||||
if (!isFiniteNumber(value)) return 0;
|
||||
return Math.max(0, Math.min(100, Math.round(value)));
|
||||
var sdrSquelchEnabled = loadSetting("sdrSquelchEnabled", false);
|
||||
var sdrSquelchThresholdDb = clampSdrSquelchDb(Number(loadSetting("sdrSquelchThresholdDb", -95)));
|
||||
function clampSdrSquelchDb(value) {
|
||||
if (!isFiniteNumber(value)) return SDR_SQUELCH_MIN_DB;
|
||||
return Math.max(SDR_SQUELCH_MIN_DB, Math.min(SDR_SQUELCH_MAX_DB, Math.round(value)));
|
||||
}
|
||||
function sdrSquelchPercentToServer(percent) {
|
||||
const pct = clampSdrSquelchPercent(percent);
|
||||
if (pct <= 0) {
|
||||
return { enabled: false, thresholdDb: SDR_SQUELCH_MIN_DB };
|
||||
function sdrSquelchIsPassing() {
|
||||
if (!sdrSquelchEnabled) return true;
|
||||
if (!isFiniteNumber(sigLastDbm)) return false;
|
||||
return sigLastDbm >= sdrSquelchThresholdDb;
|
||||
}
|
||||
function renderSdrSquelch() {
|
||||
if (sdrSquelchDbEl && document.activeElement !== sdrSquelchDbEl) {
|
||||
sdrSquelchDbEl.value = String(sdrSquelchThresholdDb);
|
||||
}
|
||||
const ratio = pct / 100;
|
||||
const thresholdDb = SDR_SQUELCH_MIN_DB + ratio * (SDR_SQUELCH_MAX_DB - SDR_SQUELCH_MIN_DB);
|
||||
return { enabled: true, thresholdDb };
|
||||
if (sdrSquelchDbEl) sdrSquelchDbEl.disabled = !sdrSquelchSupported;
|
||||
if (sdrSquelchToggleBtn) {
|
||||
sdrSquelchToggleBtn.textContent = sdrSquelchEnabled ? "On" : "Off";
|
||||
sdrSquelchToggleBtn.setAttribute("aria-pressed", String(sdrSquelchEnabled));
|
||||
}
|
||||
const state = !sdrSquelchEnabled ? "off" : sdrSquelchIsPassing() ? "open" : "closed";
|
||||
if (sdrSquelchStateEl) {
|
||||
sdrSquelchStateEl.dataset.state = state;
|
||||
sdrSquelchStateEl.setAttribute(
|
||||
"aria-label",
|
||||
state === "off" ? "Squelch off" : state === "open" ? "Squelch open" : "Squelch closed"
|
||||
);
|
||||
}
|
||||
if (squelchLabelEl) squelchLabelEl.textContent = String(sdrSquelchThresholdDb);
|
||||
if (squelchGripEl) squelchGripEl.setAttribute("aria-valuenow", String(sdrSquelchThresholdDb));
|
||||
if (squelchLineEl) squelchLineEl.dataset.state = state === "open" ? "open" : "closed";
|
||||
positionSquelchLine();
|
||||
}
|
||||
function sdrSquelchServerToPercent(enabled, thresholdDb) {
|
||||
if (!enabled) return 0;
|
||||
if (!isFiniteNumber(thresholdDb)) return 0;
|
||||
const ratio = (thresholdDb - SDR_SQUELCH_MIN_DB) / (SDR_SQUELCH_MAX_DB - SDR_SQUELCH_MIN_DB);
|
||||
return clampSdrSquelchPercent(ratio * 100);
|
||||
function positionSquelchLine() {
|
||||
if (!squelchLineEl) return;
|
||||
const mode = (modeEl && modeEl.value ? modeEl.value : "").toUpperCase();
|
||||
const canvas = document.getElementById("spectrum-canvas");
|
||||
const visible = sdrSquelchSupported && sdrSquelchEnabled && mode !== "WFM" && !!canvas && canvas.clientHeight > 0 && getComputedStyle(requiredElement("spectrum-panel")).display !== "none";
|
||||
if (!visible) {
|
||||
squelchLineEl.style.display = "none";
|
||||
return;
|
||||
}
|
||||
const dbMin = spectrumFloor;
|
||||
const dbMax = spectrumFloor + spectrumRange;
|
||||
const frac = Math.max(0, Math.min(1, (sdrSquelchThresholdDb - dbMin) / Math.max(1, dbMax - dbMin)));
|
||||
squelchLineEl.style.display = "";
|
||||
squelchLineEl.style.top = `${canvas.offsetTop + canvas.clientHeight * (1 - frac)}px`;
|
||||
}
|
||||
function updateSdrSquelchPctLabel() {
|
||||
if (!sdrSquelchEl || !sdrSquelchPctEl) return;
|
||||
const pct = clampSdrSquelchPercent(Number(sdrSquelchEl.value));
|
||||
sdrSquelchPctEl.textContent = pct <= 0 ? "Open" : `${pct}%`;
|
||||
function submitSdrSquelch() {
|
||||
if (!sdrSquelchSupported) return;
|
||||
saveSetting("sdrSquelchEnabled", sdrSquelchEnabled);
|
||||
saveSetting("sdrSquelchThresholdDb", sdrSquelchThresholdDb);
|
||||
postPath(
|
||||
`/set_sdr_squelch?enabled=${sdrSquelchEnabled ? "true" : "false"}&threshold_db=${encodeURIComponent(sdrSquelchThresholdDb.toFixed(2))}`
|
||||
).catch(() => {
|
||||
});
|
||||
}
|
||||
function setSdrSquelch(thresholdDb, enabled, options = {}) {
|
||||
sdrSquelchThresholdDb = clampSdrSquelchDb(thresholdDb);
|
||||
sdrSquelchEnabled = enabled;
|
||||
renderSdrSquelch();
|
||||
if (options.submit !== false) submitSdrSquelch();
|
||||
}
|
||||
function autoSquelchThresholdDb() {
|
||||
const data = lastSpectrumData || window.lastSpectrumData;
|
||||
if (!data || !isNumericBins(data.bins) || data.bins.length === 0) return null;
|
||||
const noiseDb = estimateNoiseFloorDb(data.bins);
|
||||
if (noiseDb == null || !isFiniteNumber(noiseDb)) return null;
|
||||
return clampSdrSquelchDb(noiseDb + 6);
|
||||
}
|
||||
function updateSdrSquelchControlVisibility() {
|
||||
if (!sdrSquelchWrapEl) return;
|
||||
const mode = (modeEl && modeEl.value ? modeEl.value : "").toUpperCase();
|
||||
sdrSquelchWrapEl.style.display = sdrSquelchSupported && mode !== "WFM" ? "" : "none";
|
||||
renderSdrSquelch();
|
||||
}
|
||||
function syncSdrSquelchFromServer(enabled, thresholdDb) {
|
||||
if (!sdrSquelchEl) return;
|
||||
if (document.activeElement === sdrSquelchEl) return;
|
||||
const pct = sdrSquelchServerToPercent(enabled, thresholdDb);
|
||||
syncFromServerSdrSquelch = true;
|
||||
sdrSquelchEl.value = String(pct);
|
||||
updateSdrSquelchPctLabel();
|
||||
syncFromServerSdrSquelch = false;
|
||||
saveSetting("sdrSquelchPct", pct);
|
||||
if (squelchDragPointerId !== null) return;
|
||||
if (sdrSquelchDbEl && document.activeElement === sdrSquelchDbEl) return;
|
||||
sdrSquelchEnabled = enabled;
|
||||
if (isFiniteNumber(thresholdDb)) sdrSquelchThresholdDb = clampSdrSquelchDb(thresholdDb);
|
||||
saveSetting("sdrSquelchEnabled", sdrSquelchEnabled);
|
||||
saveSetting("sdrSquelchThresholdDb", sdrSquelchThresholdDb);
|
||||
renderSdrSquelch();
|
||||
}
|
||||
function submitSdrSquelchPercent(percent) {
|
||||
if (!sdrSquelchSupported) return;
|
||||
const { enabled, thresholdDb } = sdrSquelchPercentToServer(percent);
|
||||
postPath(
|
||||
`/set_sdr_squelch?enabled=${enabled ? "true" : "false"}&threshold_db=${encodeURIComponent(thresholdDb.toFixed(2))}`
|
||||
).catch(() => {
|
||||
var squelchDragPointerId = null;
|
||||
var squelchDragSubmitAt = 0;
|
||||
if (sdrSquelchDbEl) {
|
||||
sdrSquelchDbEl.addEventListener("change", () => {
|
||||
setSdrSquelch(Number(sdrSquelchDbEl.value), sdrSquelchEnabled);
|
||||
});
|
||||
sdrSquelchDbEl.addEventListener("blur", () => {
|
||||
renderSdrSquelch();
|
||||
});
|
||||
}
|
||||
if (sdrSquelchEl) {
|
||||
const savedPct = clampSdrSquelchPercent(Number(loadSetting("sdrSquelchPct", 0)));
|
||||
sdrSquelchEl.value = String(savedPct);
|
||||
updateSdrSquelchPctLabel();
|
||||
sdrSquelchEl.addEventListener("input", () => {
|
||||
const pct = clampSdrSquelchPercent(Number(sdrSquelchEl.value));
|
||||
sdrSquelchEl.value = String(pct);
|
||||
updateSdrSquelchPctLabel();
|
||||
saveSetting("sdrSquelchPct", pct);
|
||||
if (!syncFromServerSdrSquelch) {
|
||||
submitSdrSquelchPercent(pct);
|
||||
}
|
||||
if (sdrSquelchToggleBtn) {
|
||||
sdrSquelchToggleBtn.addEventListener("click", () => {
|
||||
if (!sdrSquelchSupported) return;
|
||||
setSdrSquelch(sdrSquelchThresholdDb, !sdrSquelchEnabled);
|
||||
});
|
||||
}
|
||||
var sdrSquelchAutoBtn = document.getElementById("sdr-squelch-auto");
|
||||
if (sdrSquelchAutoBtn) {
|
||||
sdrSquelchAutoBtn.addEventListener("click", () => {
|
||||
if (!sdrSquelchSupported) return;
|
||||
let pct = 0;
|
||||
const data = lastSpectrumData || window.lastSpectrumData;
|
||||
if (data && isNumericBins(data.bins) && data.bins.length > 0) {
|
||||
const noiseDb = estimateNoiseFloorDb(data.bins);
|
||||
if (noiseDb != null && isFiniteNumber(noiseDb)) {
|
||||
const thresholdDb = noiseDb + 6;
|
||||
const clamped = Math.max(SDR_SQUELCH_MIN_DB, Math.min(SDR_SQUELCH_MAX_DB, thresholdDb));
|
||||
pct = clampSdrSquelchPercent(
|
||||
(clamped - SDR_SQUELCH_MIN_DB) / (SDR_SQUELCH_MAX_DB - SDR_SQUELCH_MIN_DB) * 100
|
||||
);
|
||||
}
|
||||
const threshold = autoSquelchThresholdDb();
|
||||
if (threshold == null) {
|
||||
showHint("No spectrum to measure the noise from", 1800);
|
||||
return;
|
||||
}
|
||||
if (sdrSquelchEl) {
|
||||
sdrSquelchEl.value = String(pct);
|
||||
updateSdrSquelchPctLabel();
|
||||
saveSetting("sdrSquelchPct", pct);
|
||||
setSdrSquelch(threshold, true);
|
||||
showHint(`Squelch ${threshold} dB`, 1500);
|
||||
});
|
||||
}
|
||||
if (squelchGripEl) {
|
||||
const dbFromClientY = (clientY) => {
|
||||
const canvas = document.getElementById("spectrum-canvas");
|
||||
if (!canvas || !canvas.clientHeight) return sdrSquelchThresholdDb;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const frac = 1 - (clientY - rect.top) / rect.height;
|
||||
return clampSdrSquelchDb(spectrumFloor + frac * spectrumRange);
|
||||
};
|
||||
squelchGripEl.addEventListener("pointerdown", (event) => {
|
||||
if (!sdrSquelchSupported) return;
|
||||
squelchDragPointerId = event.pointerId;
|
||||
squelchGripEl.setPointerCapture(event.pointerId);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
});
|
||||
squelchGripEl.addEventListener("pointermove", (event) => {
|
||||
if (squelchDragPointerId !== event.pointerId) return;
|
||||
sdrSquelchThresholdDb = dbFromClientY(event.clientY);
|
||||
renderSdrSquelch();
|
||||
const now = Date.now();
|
||||
if (now - squelchDragSubmitAt > 200) {
|
||||
squelchDragSubmitAt = now;
|
||||
submitSdrSquelch();
|
||||
}
|
||||
submitSdrSquelchPercent(pct);
|
||||
});
|
||||
const endDrag = (event) => {
|
||||
if (squelchDragPointerId !== event.pointerId) return;
|
||||
squelchDragPointerId = null;
|
||||
submitSdrSquelch();
|
||||
};
|
||||
squelchGripEl.addEventListener("pointerup", endDrag);
|
||||
squelchGripEl.addEventListener("pointercancel", endDrag);
|
||||
squelchGripEl.addEventListener("keydown", (event) => {
|
||||
const step = event.shiftKey ? 10 : 1;
|
||||
if (event.key === "ArrowUp" || event.key === "ArrowRight") {
|
||||
setSdrSquelch(sdrSquelchThresholdDb + step, sdrSquelchEnabled);
|
||||
} else if (event.key === "ArrowDown" || event.key === "ArrowLeft") {
|
||||
setSdrSquelch(sdrSquelchThresholdDb - step, sdrSquelchEnabled);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
if (wfmAudioModeEl) {
|
||||
@@ -7244,15 +7321,10 @@ function volWheel(slider, pctEl, getGain, storageKey) {
|
||||
}
|
||||
volWheel(rxVolSlider, rxVolPct, () => rxGainNode, "rxVol");
|
||||
volWheel(txVolSlider, txVolPct, () => txGainNode, "txVol");
|
||||
if (sdrSquelchEl) {
|
||||
sdrSquelchEl.addEventListener("wheel", (e) => {
|
||||
e.preventDefault();
|
||||
const step = e.deltaY < 0 ? 2 : -2;
|
||||
const next = clampSdrSquelchPercent(Number(sdrSquelchEl.value) + step);
|
||||
sdrSquelchEl.value = String(next);
|
||||
updateSdrSquelchPctLabel();
|
||||
saveSetting("sdrSquelchPct", next);
|
||||
submitSdrSquelchPercent(next);
|
||||
if (sdrSquelchDbEl) {
|
||||
sdrSquelchDbEl.addEventListener("wheel", (event) => {
|
||||
event.preventDefault();
|
||||
setSdrSquelch(sdrSquelchThresholdDb + (event.deltaY < 0 ? 1 : -1), sdrSquelchEnabled);
|
||||
}, { passive: false });
|
||||
}
|
||||
requiredElement("copyright-year").textContent = String((/* @__PURE__ */ new Date()).getFullYear());
|
||||
@@ -8299,6 +8371,7 @@ function drawSpectrum(data) {
|
||||
}
|
||||
spectrumGl.drawSegments(spectrumTmpGridSegments, cssColorToRgba(pal.spectrumGrid), 1);
|
||||
updateSpectrumDbAxis(DB_MIN, DB_MAX, gridStep, H, dpr);
|
||||
positionSquelchLine();
|
||||
function hzToX(hz) {
|
||||
return (hz - range.visLoHz) / range.visSpanHz * W;
|
||||
}
|
||||
@@ -8927,31 +9000,15 @@ window.addEventListener("keydown", (event) => {
|
||||
}
|
||||
if (key === "q") {
|
||||
event.preventDefault();
|
||||
if (sdrSquelchSupported && sdrSquelchEl) {
|
||||
const current = clampSdrSquelchPercent(Number(sdrSquelchEl.value));
|
||||
let nextPct;
|
||||
if (current > 0) {
|
||||
nextPct = 0;
|
||||
if (sdrSquelchSupported) {
|
||||
if (sdrSquelchEnabled) {
|
||||
setSdrSquelch(sdrSquelchThresholdDb, false);
|
||||
showHint("Squelch off", 1200);
|
||||
} else {
|
||||
let auto = 30;
|
||||
const data = lastSpectrumData || window.lastSpectrumData;
|
||||
if (data && isNumericBins(data.bins) && data.bins.length > 0) {
|
||||
const noiseDb = estimateNoiseFloorDb(data.bins);
|
||||
if (noiseDb != null && isFiniteNumber(noiseDb)) {
|
||||
const thresholdDb = noiseDb + 6;
|
||||
const clamped = Math.max(SDR_SQUELCH_MIN_DB, Math.min(SDR_SQUELCH_MAX_DB, thresholdDb));
|
||||
auto = clampSdrSquelchPercent(
|
||||
(clamped - SDR_SQUELCH_MIN_DB) / (SDR_SQUELCH_MAX_DB - SDR_SQUELCH_MIN_DB) * 100
|
||||
);
|
||||
}
|
||||
}
|
||||
nextPct = auto;
|
||||
const threshold = sdrSquelchThresholdDb > SDR_SQUELCH_MIN_DB ? sdrSquelchThresholdDb : autoSquelchThresholdDb() ?? SDR_SQUELCH_MIN_DB + 25;
|
||||
setSdrSquelch(threshold, true);
|
||||
showHint(`Squelch ${clampSdrSquelchDb(threshold)} dB`, 1200);
|
||||
}
|
||||
sdrSquelchEl.value = String(nextPct);
|
||||
updateSdrSquelchPctLabel();
|
||||
saveSetting("sdrSquelchPct", nextPct);
|
||||
submitSdrSquelchPercent(nextPct);
|
||||
showHint(nextPct > 0 ? `Squelch ${nextPct}%` : "Squelch Off", 1200);
|
||||
} else {
|
||||
showHint("Squelch N/A", 1200);
|
||||
}
|
||||
|
||||
@@ -142,6 +142,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<div id="spectrum-bookmark-side-left" class="spectrum-bookmark-side spectrum-bookmark-side-left" aria-hidden="true"></div>
|
||||
<canvas id="spectrum-canvas" tabindex="0" role="img" aria-label="Spectrum display" aria-describedby="spectrum-text-summary"></canvas>
|
||||
<p id="spectrum-text-summary" class="visually-hidden">Spectrum data is waiting for the receiver.</p>
|
||||
<div id="spectrum-squelch-line" class="spectrum-squelch-line" style="display:none;" data-state="closed">
|
||||
<span class="spectrum-squelch-grip" id="spectrum-squelch-grip" role="slider" tabindex="0"
|
||||
aria-label="Squelch threshold" aria-valuemin="-120" aria-valuemax="-30" aria-valuenow="-95">SQL <span id="spectrum-squelch-label">-95</span> dB</span>
|
||||
</div>
|
||||
<div id="spectrum-zoom-indicator" aria-hidden="true"></div>
|
||||
<div id="spectrum-minimap" aria-hidden="true"><div class="minimap-view"></div></div>
|
||||
<div id="spectrum-db-axis" aria-hidden="true"></div>
|
||||
@@ -411,7 +415,13 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<button id="tx-audio-btn" type="button">Transmit Audio</button>
|
||||
<label class="vol-label">RX<input type="range" id="rx-vol" min="0" max="100" value="80" class="vol-slider" /><small class="vol-pct" id="rx-vol-pct">80%</small></label>
|
||||
<label class="vol-label">TX<input type="range" id="tx-vol" min="0" max="100" value="80" class="vol-slider" /><small class="vol-pct" id="tx-vol-pct">80%</small></label>
|
||||
<label class="vol-label" id="sdr-squelch-wrap" style="display:none;">SQL<input type="range" id="sdr-squelch" min="0" max="100" value="0" class="vol-slider" /><small class="vol-pct" id="sdr-squelch-pct">Open</small><button id="sdr-squelch-auto" type="button" class="sql-auto-btn" title="Set squelch to current noise level">Auto</button></label>
|
||||
<span class="sql-control" id="sdr-squelch-wrap" style="display:none;">
|
||||
<span class="sql-title">SQL</span>
|
||||
<span class="sql-state" id="sdr-squelch-state" data-state="off" role="img" aria-label="Squelch state"></span>
|
||||
<label class="sql-db"><input type="number" id="sdr-squelch-db" min="-120" max="-30" step="1" value="-95" inputmode="numeric" aria-label="Squelch threshold in dB" /><span class="sql-db-unit">dB</span></label>
|
||||
<button id="sdr-squelch-auto" type="button" class="sql-auto-btn" title="Set the threshold just above the noise floor">Auto</button>
|
||||
<button id="sdr-squelch-toggle" type="button" class="sql-auto-btn" aria-pressed="false" title="Enable or disable the squelch">Off</button>
|
||||
</span>
|
||||
<div id="audio-level">
|
||||
<div id="audio-level-fill"></div>
|
||||
</div>
|
||||
|
||||
@@ -1845,6 +1845,106 @@ small { color: var(--text-muted); }
|
||||
color: var(--text-muted);
|
||||
line-height: 1;
|
||||
}
|
||||
/* Squelch: a threshold in dB, the unit the spectrum's own axis is labelled in,
|
||||
with a dot showing whether the gate is passing audio right now. The old
|
||||
percentage was a number with nothing on screen to relate it to. */
|
||||
.sql-control {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.82rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.sql-title {
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.sql-state {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
background: color-mix(in srgb, var(--text-muted) 45%, transparent);
|
||||
transition: background var(--dur-fast) var(--ease-standard),
|
||||
box-shadow var(--dur-fast) var(--ease-standard);
|
||||
}
|
||||
.sql-state[data-state="open"] {
|
||||
background: var(--status-ok);
|
||||
box-shadow: 0 0 0 0.14rem color-mix(in srgb, var(--status-ok) 25%, transparent);
|
||||
}
|
||||
.sql-state[data-state="closed"] {
|
||||
background: var(--accent-yellow);
|
||||
box-shadow: 0 0 0 0.14rem color-mix(in srgb, var(--accent-yellow) 22%, transparent);
|
||||
}
|
||||
.sql-db {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
.sql-db input {
|
||||
width: 3.4rem;
|
||||
height: 1.5rem;
|
||||
min-height: 0;
|
||||
padding: 0 0.3rem;
|
||||
font-size: 0.78rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-align: right;
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 4px;
|
||||
background: var(--input-bg);
|
||||
color: var(--text);
|
||||
}
|
||||
.sql-db-unit {
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
.sql-control button[aria-pressed="true"] {
|
||||
border-color: color-mix(in srgb, var(--accent-green) 55%, var(--border-light));
|
||||
color: var(--accent-text);
|
||||
}
|
||||
/* The line is the control: it spans the plot at its threshold and carries its
|
||||
own grip, so the level is set where the noise it has to clear is visible. */
|
||||
.spectrum-squelch-line {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 0;
|
||||
/* Above the overlays that share the plot: the split control sits at the
|
||||
right edge on z-index 9 and would otherwise swallow the grip's pointer. */
|
||||
z-index: 10;
|
||||
border-top: 1px dashed color-mix(in srgb, var(--accent-yellow) 75%, transparent);
|
||||
pointer-events: none;
|
||||
}
|
||||
.spectrum-squelch-line[data-state="open"] {
|
||||
border-top-color: color-mix(in srgb, var(--status-ok) 80%, transparent);
|
||||
}
|
||||
.spectrum-squelch-grip {
|
||||
position: absolute;
|
||||
/* Clear of the split control's column so the two never sit on top of each
|
||||
other, whatever height the threshold is at. */
|
||||
right: 3.6rem;
|
||||
top: -0.72rem;
|
||||
padding: 0.05rem 0.35rem;
|
||||
border-radius: 3px;
|
||||
border: 1px solid color-mix(in srgb, var(--accent-yellow) 60%, var(--border-light));
|
||||
background: color-mix(in srgb, var(--card-bg) 88%, transparent);
|
||||
color: var(--text);
|
||||
font-size: 0.62rem;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1.3;
|
||||
cursor: ns-resize;
|
||||
pointer-events: auto;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.spectrum-squelch-line[data-state="open"] .spectrum-squelch-grip {
|
||||
border-color: color-mix(in srgb, var(--status-ok) 60%, var(--border-light));
|
||||
}
|
||||
.spectrum-squelch-grip:focus-visible {
|
||||
outline: 2px solid var(--accent-text);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.sql-auto-btn {
|
||||
font-size: 0.68rem;
|
||||
padding: 0 5px;
|
||||
|
||||
Reference in New Issue
Block a user