Never call a string of Morse tones "words"
A repeater identifying itself in CW over an FM carrier came back from the recogniser as "2-2-2-3-3-5-2-7-0-5-9-7-0-8-1-0" -- one digit per tone, sixteen characters of nothing, which cleared the five-character bar and cost the capture its waterfall. The rule now lives in one place, waterfall.is_readable, shared by the scanner and the waterfall command: voice, no Morse, and more than a handful of characters. bandsaunter waterfall --check-morse runs the CW decoder over the recordings a sidecar calls readable, for sidecars written before the decoder could hear an ident over an FM carrier, and draws -- and records the ident in -- the ones that have one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PsWPTweCT6pwxKngvVxcg
This commit is contained in:
parent
dee262e130
commit
a8a8548369
9 changed files with 190 additions and 29 deletions
|
|
@ -376,3 +376,91 @@ def test_the_command_notes_the_picture_in_the_sidecar(tmp_path, monkeypatch):
|
|||
assert _waterfall_command(str(tmp_path)) == 0
|
||||
hit = json.loads((tmp_path / "0146.940000MHz--b-ook.json").read_text())["hit"]
|
||||
assert Path(hit["waterfall_path"]).is_file()
|
||||
|
||||
|
||||
# -- Morse is never words ----------------------------------------------------
|
||||
|
||||
def test_a_transcript_of_morse_tones_is_not_words():
|
||||
"""Whisper renders a CW ident as one digit per tone: sixteen characters
|
||||
of nothing, which clears the bar and means nothing."""
|
||||
hit = {"category": "voice", "morse_complete": "KSQ330"}
|
||||
assert not wf.is_readable(hit, "2-2-2-3-3-5-2-7-0-5-9-7-0-8-1-0", 5)
|
||||
|
||||
|
||||
def test_words_over_the_top_of_an_ident_are_still_words():
|
||||
assert wf.is_readable({"category": "voice"},
|
||||
"net control this is W1AW standing by", 5)
|
||||
|
||||
|
||||
def test_the_partial_morse_text_counts_as_much_as_the_complete_one():
|
||||
"""A keyed carrier whose timing only half resolved is still a keyed
|
||||
carrier, and the transcript of it is still digits."""
|
||||
hit = {"category": "voice", "morse_text": "K SQ 3 30"}
|
||||
assert not wf.is_readable(hit, "3-3-0-5-9-7-0-8", 5)
|
||||
|
||||
|
||||
def test_a_capture_that_identified_itself_in_morse_is_drawn(tmp_path,
|
||||
recogniser,
|
||||
monkeypatch):
|
||||
"""The whole point: an FM carrier with a CW ident on the end of it gets
|
||||
a picture, however long a string of digits the recogniser made of it."""
|
||||
from bandsaunter.scanner import Scanner
|
||||
|
||||
class _Ident:
|
||||
is_morse = True
|
||||
text = "KSQ330"
|
||||
complete_text = "KSQ330"
|
||||
wpm = 20.0
|
||||
confidence = 0.9
|
||||
|
||||
monkeypatch.setattr(Scanner, "_morse_from_recording",
|
||||
lambda self, rec, morse: _Ident())
|
||||
recogniser["text"] = "2-2-2-3-3-5-2-7-0-5-9-7-0-8-1-0"
|
||||
scanner = _scan(tmp_path, recogniser["text"])
|
||||
assert scanner.stats.recordings >= 1
|
||||
assert len(drawings(tmp_path)) == scanner.stats.recordings
|
||||
for meta in tmp_path.glob("*.json"):
|
||||
if meta.name.startswith("scan_log"):
|
||||
continue
|
||||
assert json.loads(meta.read_text())["hit"]["morse_complete"] == "KSQ330"
|
||||
|
||||
|
||||
def test_the_command_draws_an_ident_it_finds_in_the_sidecar(tmp_path,
|
||||
monkeypatch):
|
||||
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "cfg"))
|
||||
_recording(tmp_path, "0154.369068MHz--a-nfm", tone(1200.0),
|
||||
hit={"category": "voice", "frequency": 154.369068e6,
|
||||
"mode": "nfm", "morse_complete": "KSQ330"},
|
||||
transcript="2-2-2-3-3-5-2-7-0-5-9-7-0-8-1-0")
|
||||
assert _waterfall_command(str(tmp_path)) == 0
|
||||
assert len(drawings(tmp_path)) == 1
|
||||
|
||||
|
||||
def test_check_morse_listens_to_what_the_sidecar_calls_readable(tmp_path,
|
||||
monkeypatch):
|
||||
"""A sidecar written before the CW decoder could hear an ident over an FM
|
||||
carrier calls a repeater readable. Listening again finds it."""
|
||||
from morse_gen import morse_audio
|
||||
|
||||
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "cfg"))
|
||||
ident = morse_audio("DE KSQ330 KSQ330", wpm=20, fs=FS, snr_db=25)
|
||||
_recording(tmp_path, "0154.369068MHz--a-nfm", ident,
|
||||
hit={"category": "voice", "frequency": 154.369068e6,
|
||||
"mode": "nfm", "morse_text": "", "morse_complete": ""},
|
||||
transcript="2-2-2-3-3-5-2-7-0-5-9-7-0-8-1-0")
|
||||
assert _waterfall_command(str(tmp_path)) == 0
|
||||
assert drawings(tmp_path) == [] # the sidecar was believed
|
||||
assert _waterfall_command(str(tmp_path), "--check-morse") == 0
|
||||
assert len(drawings(tmp_path)) == 1
|
||||
hit = json.loads((tmp_path / "0154.369068MHz--a-nfm.json").read_text())["hit"]
|
||||
assert "KSQ330" in hit["morse_complete"]
|
||||
|
||||
|
||||
def test_check_morse_leaves_a_conversation_alone(tmp_path, monkeypatch):
|
||||
"""It has to be able to say no, or it is just --all with a long wait."""
|
||||
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "cfg"))
|
||||
_recording(tmp_path, "0146.520000MHz--a-nfm", tone(1200.0),
|
||||
hit={"category": "voice", "frequency": 146.52e6},
|
||||
transcript="net control this is W1AW standing by")
|
||||
assert _waterfall_command(str(tmp_path), "--check-morse") == 0
|
||||
assert drawings(tmp_path) == []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue