feat: add OOK/AM envelope detection mode to Morse decoder

Re-implements envelope detection on top of the rewritten Morse decoder.
Addresses PR #160 review feedback:
- Rebase: rebuilt on current upstream/main (lifecycle state machine)
- Gap thresholds: 2.0/5.0 for envelope only; goertzel keeps 2.6/6.0
- Frequency validation: max_mhz=1766 for envelope, 30 for goertzel
- Tests: EnvelopeDetector unit tests + envelope-mode decoder test
- Envelope uses direct magnitude threshold (no SNR/noise ref)
- Goertzel path completely unchanged

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ribs
2026-02-27 10:35:56 -08:00
parent fb064a22fb
commit 377519fd95
5 changed files with 461 additions and 127 deletions

View File

@@ -3,21 +3,39 @@
<div class="section">
<h3>CW/Morse Decoder</h3>
<p class="info-text morse-mode-help">
Decode CW (continuous wave) Morse with USB demod + Goertzel tone detection.
Start with 700 Hz tone and 200 Hz bandwidth.
Decode CW (continuous wave) Morse code. Supports HF amateur bands (USB + Goertzel tone
detection) and ISM/UHF OOK signals (AM + envelope detection).
</p>
</div>
<div class="section">
<h3>Detection Mode</h3>
<div class="form-group">
<div style="display: flex; gap: 4px;">
<button class="preset-btn morseDetectBtn" id="morseDetectGoertzel"
onclick="MorseMode.setDetectMode('goertzel')"
style="flex: 1; background: var(--accent); color: #000;">CW Tone</button>
<button class="preset-btn morseDetectBtn" id="morseDetectEnvelope"
onclick="MorseMode.setDetectMode('envelope')"
style="flex: 1;">OOK Envelope</button>
</div>
<input type="hidden" id="morseDetectMode" value="goertzel">
<p id="morseDetectHint" class="info-text" style="font-size: 10px; color: var(--text-dim); margin-top: 4px;">
CW Tone: HF bands, USB demod, Goertzel filter. For amateur CW.
</p>
</div>
</div>
<div class="section">
<h3>Frequency</h3>
<div class="form-group">
<label>Frequency (MHz)</label>
<input type="number" id="morseFrequency" value="14.060" step="0.001" min="0.5" max="30" placeholder="e.g., 14.060">
<input type="number" id="morseFrequency" value="14.060" step="0.001" min="0.5" max="1766" placeholder="e.g., 14.060">
<span class="help-text morse-help-text">Enter CW center frequency in MHz (e.g., 7.030 for 40m).</span>
</div>
<div class="form-group">
<label>Band Presets</label>
<div class="morse-presets">
<div class="morse-presets" id="morseHFPresets">
<button class="preset-btn" onclick="MorseMode.setFreq(3.560)">80m</button>
<button class="preset-btn" onclick="MorseMode.setFreq(7.030)">40m</button>
<button class="preset-btn" onclick="MorseMode.setFreq(10.116)">30m</button>
@@ -27,6 +45,13 @@
<button class="preset-btn" onclick="MorseMode.setFreq(24.910)">12m</button>
<button class="preset-btn" onclick="MorseMode.setFreq(28.060)">10m</button>
</div>
<div class="morse-presets" id="morseISMPresets" style="display: none; flex-wrap: wrap; gap: 4px;">
<button class="preset-btn" onclick="MorseMode.setFreq(315.000)">315</button>
<button class="preset-btn" onclick="MorseMode.setFreq(433.300)">433.3</button>
<button class="preset-btn" onclick="MorseMode.setFreq(433.920)">433.9</button>
<button class="preset-btn" onclick="MorseMode.setFreq(868.000)">868</button>
<button class="preset-btn" onclick="MorseMode.setFreq(915.000)">915</button>
</div>
</div>
</div>
@@ -42,7 +67,7 @@
</div>
</div>
<div class="section">
<div class="section" id="morseToneFreqGroup">
<h3>CW Detector</h3>
<div class="form-group">
<label>Tone Frequency: <span id="morseToneFreqLabel">700</span> Hz</label>
@@ -154,12 +179,18 @@
</div>
</div>
<div class="section">
<div class="section" id="morseHFNote">
<p class="info-text morse-hf-note">
CW on HF (1-30 MHz) requires an HF-capable SDR path (direct sampling or upconverter)
and an appropriate antenna.
</p>
</div>
<div class="section" id="morseEnvelopeNote" style="display: none;">
<p class="info-text" style="font-size: 11px; color: #ffaa00; line-height: 1.5;">
OOK Envelope mode uses AM demodulation to detect carrier on/off keying.
Suitable for ISM-band (315/433/868/915 MHz) Morse transmitters.
</p>
</div>
<button class="run-btn" id="morseStartBtn" onclick="MorseMode.start()">Start Decoder</button>
<button class="stop-btn" id="morseStopBtn" onclick="MorseMode.stop()" style="display: none;">Stop Decoder</button>