Record what the radio was doing, and turn PTT into a recording - #4
Merged
Conversation
The recorder was already ported - WAV, continuous record and the replay ring, wired and unit-tested. What was missing is that a recording carried no provenance: filename in local time, no frequency, no mode. Every log worth matching against is UTC, so nothing downstream could join on it and "who was that" was unanswerable by construction. - Every recording now writes a .json sidecar: UTC start/end, frequency at start and end, mode, and the operator's overs. Manual, replay and auto alike. Provenance is pushed from the poll loop rather than pulled, because pulling took the poller's lock while holding the recorder's, from the poll thread. A replay's start time is derived from the sample count - the ring holds what happened before the press, so stamping it "now" would match it to the wrong contact. "overs": null means not tracked; an empty array would claim the operator never transmitted. - QsoRecorder ports the C# PTT auto-record: start on the rising edge, each over pushes the idle deadline out, stop on idle or a QSY measured from where the QSO started. It must not start on a tune, and a tune must not move the remembered PTT state. Off by default - it records whoever the operator is talking to, unasked. - RadioPoller::OnPoll is one fan-out point for everything that reacts to rig state, rather than a second loop competing for the CAT port. The UTC assertion was blind: this box runs UTC, so localtime_r and gmtime_r agree and the test passed against a local-time stamp. It now pins TZ to a POSIX string glibc parses without tzdata, asserts the two clocks actually differ, and compares the filename's local hour to the sidecar's UTC hour. Only then does localtime_r fail it. Same for the other three: each bug was reintroduced and watched to fail. Proven on the built host against the simulated rig, not only in tests: keyed, unkeyed, waited out the timer, got a 3.0 s WAV with rms 5656 and a sidecar carrying both UTC times, the frequency, the mode and one closed over. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EqubrbjLXwz1GrVgmTcrf9
…me claim Takes a recording's sidecar and reports LOGGED (a fact - the operator wrote the callsign down) separately from ON THE NET (candidates - a check-in says present, not that they were the voice on the tape). Those must never be presented as the same kind of answer. The net name turns out to be in the log already, so this layer needs no API and works retroactively over the whole log: NetLogger-sourced QSOs carry it in COL_COMMENT in the two encodings qsl-queue measured over 29,573 rows. Bracketed text is not always a net, so only net-shaped tokens are taken as one - checked against the [New call sign May 2025] rows that would otherwise be reported as a net name. Matching is on time alone and the band is shown rather than filtered on: filtering would silently drop true matches whenever the sidecar's frequency is unreliable, and a dropped true match is invisible in a way a flagged odd one is not. A QSO on another band in the same window is marked, never hidden. A logged QSO is an instant, not a span - COL_TIME_OFF equals COL_TIME_ON on every row - so the timestamp is when it was logged, usually the end of the exchange. Hence the pad, and hence every match prints its offset into the recording so an edge match reads as one. "Nothing found" is never printed as "nobody". An unlogged QSO and a station heard but not worked look identical from here. Verified against the real log: a 26-minute window over the 07/26 80m net returned three callsigns with their offsets and the net name; the same window with the band claim changed flagged both rows as a different band; a window with no QSOs said nothing was found and said why that is not an absence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EqubrbjLXwz1GrVgmTcrf9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reopens the work from #2, which GitHub auto-closed when its base branch (
macos-bundle-identity) was deleted on merge. Rebased ontomain; the two commits are unchanged.The premise was wrong, and checking cost less than building
"Port recording like the C# has" — it was already ported.
src/recorder.cppdoes WAV writing, continuous record and the replay ring, wired atmain.cpp:213with unit tests, andAUDIT-CSHARP.md:42has had it as ✅ the whole time.The real gap: a recording carried no provenance.
hamdeck-rec-<local time>.wavand nothing else — no frequency, no mode, and its one timestamp in local time while every log worth matching against is UTC.What changed
.jsonsidecar beside every recording — UTC start/end, frequency at start and end, mode, and the operator's overs. Manual, replay and auto alike. Provenance is pushed from the poll loop, not pulled (pulling took the poller's lock while holding the recorder's, from the poll thread). A replay'sstarted_utcis derived from the sample count, because the ring holds what happened before the press."overs": null≠"overs": [].QsoRecorder— the C# PTT auto-record (Views/MainWindow.xaml.cs:420), ported rather than invented. Won't start on a tune. Off by default.tools/identify_recording.py— answers "who was that" from the log, reporting LOGGED (a fact) separately from ON THE NET (candidates). The net name is already inCOL_COMMENT, so this works retroactively over the whole log with no API.Gates, each proven to fail first
NoteOverbefore the file opens ·localtime_rfor the sidecar · replay stamped when saved · auto-record starting on a tune.localtime_randgmtime_ragree. It now pinsTZto a POSIX string and asserts the two clocks differ before testing anything.Proven on the built host
Simulated rig, keyed, unkeyed, waited out the timer → a 3.0 s WAV (rms 5656, real audio) beside a correct sidecar. And
identify_recording.pyagainst the real log returned N4GTO / N4TTU / W4ETA with their offsets and the net name.🤖 Generated with Claude Code
https://claude.ai/code/session_01EqubrbjLXwz1GrVgmTcrf9