From 97b10b3ac9c7a7d65ed84ff567e5b33bf42c8268 Mon Sep 17 00:00:00 2001 From: Smittix Date: Thu, 26 Feb 2026 21:30:43 +0000 Subject: [PATCH] morse: fix SNR threshold for real CW and stop timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Widen noise detector offset from ±100Hz to ±200Hz to reduce spectral leakage into the noise reference, and scale threshold_multiplier for SNR space (2.8 → 1.54) so real CW signals reliably trigger tone detection instead of producing all-E's at 60 WPM. Fix misleading "decoder startup" timeout message on stop requests and increase stop timeout from 2.2s to 5s. Co-Authored-By: Claude Opus 4.6 --- static/js/modes/morse.js | 4 ++-- utils/morse.py | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/static/js/modes/morse.js b/static/js/modes/morse.js index a28ed9e..a1a5817 100644 --- a/static/js/modes/morse.js +++ b/static/js/modes/morse.js @@ -7,7 +7,7 @@ var MorseMode = (function () { var SETTINGS_KEY = 'intercept.morse.settings.v3'; var STATUS_POLL_MS = 5000; - var LOCAL_STOP_TIMEOUT_MS = 2200; + var LOCAL_STOP_TIMEOUT_MS = 5000; var START_TIMEOUT_MS = 60000; var state = { @@ -87,7 +87,7 @@ var MorseMode = (function () { }); }).catch(function (err) { if (err && err.name === 'AbortError') { - throw new Error('Request timed out while waiting for decoder startup'); + throw new Error('Request timed out'); } throw err; }).finally(function () { diff --git a/utils/morse.py b/utils/morse.py index 3f910f7..b920ceb 100644 --- a/utils/morse.py +++ b/utils/morse.py @@ -165,12 +165,12 @@ class MorseDecoder: self._detector = GoertzelFilter(self._active_tone_freq, self.sample_rate, self._block_size) self._noise_detector_low = GoertzelFilter( - _clamp(self._active_tone_freq - max(60.0, self.bandwidth_hz * 0.5), 150.0, 2000.0), + _clamp(self._active_tone_freq - max(150.0, self.bandwidth_hz), 150.0, 2000.0), self.sample_rate, self._block_size, ) self._noise_detector_high = GoertzelFilter( - _clamp(self._active_tone_freq + max(60.0, self.bandwidth_hz * 0.5), 150.0, 2000.0), + _clamp(self._active_tone_freq + max(150.0, self.bandwidth_hz), 150.0, 2000.0), self.sample_rate, self._block_size, ) @@ -249,7 +249,7 @@ class MorseDecoder: def _rebuild_detectors(self) -> None: """Rebuild target/noise Goertzel filters after tone updates.""" self._detector = GoertzelFilter(self._active_tone_freq, self.sample_rate, self._block_size) - ref_offset = max(60.0, self.bandwidth_hz * 0.5) + ref_offset = max(150.0, self.bandwidth_hz) self._noise_detector_low = GoertzelFilter( _clamp(self._active_tone_freq - ref_offset, 150.0, 2000.0), self.sample_rate, @@ -457,8 +457,9 @@ class MorseDecoder: # gain-invariant — fixes stuck-ON tone when AGC amplifies # inter-element silence above the raw magnitude threshold. snr = level / max(noise_ref, 1e-6) - snr_on = self.threshold_multiplier * (1.0 + self._hysteresis) - snr_off = self.threshold_multiplier * (1.0 - self._hysteresis) + snr_mult = max(1.3, self.threshold_multiplier * 0.55) + snr_on = snr_mult * (1.0 + self._hysteresis) + snr_off = snr_mult * (1.0 - self._hysteresis) if self._tone_on: tone_detected = gate_ok and snr >= snr_off