Record what the radio was doing, and turn PTT into a recording - #2
Closed
jwussler wants to merge 2 commits into
Closed
Record what the radio was doing, and turn PTT into a recording#2jwussler wants to merge 2 commits into
jwussler wants to merge 2 commits into
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.
Stacked on #1 — review that first; the diff here is just this change.
The premise was wrong, and checking cost less than building
"Port recording like the C# has" — it's 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. "Who was that" was unanswerable by construction.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.started_utcis derived from the sample count — the ring holds what happened before the press, so stamping it "now" matches the audio to the wrong contact."overs": null≠"overs": []. Null is "not tracked"; an empty array would claim the operator never transmitted.QsoRecorder— the C# PTT auto-record (Views/MainWindow.xaml.cs:420), ported rather than invented: start on the rising edge, each over pushes the idle deadline, stop on idle or a QSY measured from where the QSO started. Must not start on a tune, and a tune must not move the remembered PTT state. Off by default — it records whoever you're talking to, unasked.RadioPoller::OnPoll— one fan-out point for anything reacting to rig state, instead of a second loop competing for the CAT port.The gates, each proven to fail
NoteOverbefore the file opensovers == 2— the over that starts each QSO went unlistedlocaltime_rfor the sidecarage >= 19 && age < 60!q.active()localtime_randgmtime_ragree and the assertion held just as happily against a local-time stamp. It now pinsTZto a POSIX string (glibc parses it with no tzdata, so a bare container can't silently drop back to UTC), asserts the two clocks actually differ, and compares the filename's local hour to the sidecar's UTC hour. Only then doeslocaltime_rfail it.Proven on the built host, not only in tests
Simulated rig,
ptt_record_seconds: 3, keyed via/api/ptt/on, unkeyed, waited out the timer:Not in this PR
The identification half, and a re-measurement of MONI —
CARRYOVER.md:207says it can't be captured,WsAudioClient.cs:205says the operator hears themselves through it. Both describe the same station. Details inWIP.md.🤖 Generated with Claude Code
https://claude.ai/code/session_01EqubrbjLXwz1GrVgmTcrf9