Commit Graph
100 Commits
Author SHA1 Message Date
sjgandOpenAI Codex 32e1618384 [fix](trx-frontend-http): drop rig_id http aliases
Remove the remaining legacy rig_id aliases across the HTTP frontend and use remote consistently for scheduler and audio requests.

Disable browser caching for the HTML, CSS, and JS assets so clients pick up the parameter rename immediately instead of running stale frontend code.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-23 22:49:01 +01:00
sjgandOpenAI Codex 7d76606927 [fix](trx-frontend-http): accept remote on audio endpoint
Parse the renamed `remote` query parameter on `/audio` while keeping
`rig_id` as a compatibility alias, and use per-rig stream info for
rig-scoped audio subscriptions.

Add tests covering both query names.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-23 22:31:04 +01:00
sjgandOpenAI Codex b8ce05d41e [fix](trx-client): isolate selected state per server group
Keep global state and spectrum updates scoped to the server group that
owns the selected short name, so other servers cannot overwrite the UI
or clear the active spectrum stream.

Add regression tests for cross-server selection and spectrum ownership.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-23 22:23:22 +01:00
sjgandOpenAI Codex fad63be247 [fix](trx-client): keep multi-server rigs in audio cache
Merge per-server GetRigs updates into the shared remote rig cache
instead of replacing it, so audio tasks from other servers are not
torn down on each poll cycle.

Add a regression test covering the multi-server cache update path.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-23 22:18:50 +01:00
sjgandOpenAI Codex e09f14d2d3 [feat](trx-client): support audio URL overrides
Use full audio endpoint URLs for trx-server audio connections while
preserving server-advertised ports and legacy port-based fallback for
backward compatibility.

Add `server_url` and per-remote `rig_urls` config entries, plus
validation and tests for audio URL parsing and address resolution.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-23 22:04:14 +01:00
sjgandOpenAI Codex d8444f35f6 [refactor](trx-frontend-http): rename rig_id API fields to remote
Rename HTTP query params, JSON fields, and scheduler payloads to
use remote names consistently while still accepting legacy `rig_id`
inputs through serde aliases.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-23 21:54:45 +01:00
sjgandOpenAI Codex 2db13da706 [fix](trx-client): use default remote name in config
Use the remote short name for the HTTP frontend default selection and
keep `default_rig_id` as a serde alias for backward compatibility.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-23 21:54:37 +01:00
sjgandClaude Opus 4.6 9cd172ce64 [feat](trx-client): support multiple trx-servers from a single client
Introduce [[remotes]] config array where each entry maps a user-chosen
short name to a (server URL, rig_id) pair. Short names replace raw
rig_ids as the universal key throughout frontends, audio routing, and
state management, allowing rig_ids to safely collide across servers.

Entries sharing the same server URL and token share a single TCP
connection. A request routing dispatcher forwards frontend commands to
the correct per-server channel based on the short name.

Legacy [remote] config and CLI --url are preserved via automatic
fallback to a single-entry remotes list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-23 21:00:31 +01:00
sjgandClaude Opus 4.6 1e438acaf7 [feat](trx-frontend-http): show rig name and location in page title
Include active rig display name and nearest city in the browser tab
title: "freq - rig name - city, country - trx-rs v<version>".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 14:20:09 +01:00
sjg 6c8d294f6a [chore](docs): upgrade docs
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 14:15:51 +01:00
sjg eb97d8379d [chore](trx-rs): remove MANUAL.md - moved to docs/ (wiki submodule)
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 14:15:25 +01:00
sjgandClaude Opus 4.6 182240faa8 [docs](trx-rs): add threshold selection guide to MANUAL.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 14:14:04 +01:00
sjgandClaude Opus 4.6 a5a654d508 [docs](trx-rs): add noise blanker section to MANUAL.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 14:11:26 +01:00
sjgandClaude Opus 4.6 1f6e3bb142 [feat](trx-frontend-http): add noise blanker controls to SDR settings UI
Add checkbox to enable/disable NB and number input for threshold (1-100).
Controls are hidden by default and shown when the server reports NB support
via SSE filter state updates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 14:05:14 +01:00
sjgandClaude Opus 4.6 189d27bac8 [feat](trx-rs): add configurable noise blanker for SoapySDR backend
IQ-domain impulse noise blanker using exponential-smoothing RMS tracker. Samples exceeding threshold × running RMS are replaced with the last clean sample. Configurable via [sdr.noise_blanker] in TOML config and runtime via POST /set_sdr_noise_blanker API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 13:54:17 +01:00
sjgandClaude Opus 4.6 01a6b331f6 [fix](trx-frontend-http): invalidate bookmark colors on style change too
The bookmark color palette is derived from CSS variables (--accent-yellow,
--accent-green, etc.) which change on both dark/light theme toggle AND
palette style change (arctic, lime, etc.). The previous fix only covered
setTheme; extract invalidateBookmarkColors() and call it from setStyle
as well so bookmark chips recolour on any visual change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 12:12:09 +01:00
sjgandClaude Opus 4.6 f9f06a1db1 [fix](trx-frontend-http): force style recalc before reading bookmark theme colors
getComputedStyle may return stale CSS variable values if the browser
has not flushed the style recalculation after changing data-theme. Force
a recalc by reading a property value first. Also clear cached bookmark
DOM keys so the next draw pass rebuilds chips from scratch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 09:42:41 +01:00
sjgandClaude Opus 4.6 2d1f635019 [fix](trx-frontend-http): directly re-apply bookmark chip colors on theme change
Bumping bmRevision and scheduling a spectrum draw was not enough because
the spectrum draw path only runs when spectrum data is present. Instead,
directly update --bm-cat-bg/--bm-cat-fg on all existing bookmark chips
from the new theme palette so colors update immediately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 09:29:50 +01:00
sjgandClaude Opus 4.6 4d09636793 [fix](trx-frontend-http): guard scheduleSpectrumDraw from TDZ during init
scheduleSpectrumDraw references a let-bound variable that hasn't been
initialized yet when setTheme runs at startup. Wrap in try/catch so the
early call is silently skipped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 09:25:38 +01:00
sjgandClaude Opus 4.6 84259a01f4 [fix](trx-frontend-http): redraw bookmark colors on theme change
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 09:20:32 +01:00
sjgandClaude Opus 4.6 4ad5bf863d [fix](trx-client): update per-rig state channel on command response
send_command only updated the global state_tx watch channel, but SSE
sessions subscribe to per-rig rig_states channels. Per-rig channels were
only updated during the poll cycle (default 750ms). Now send_command also
pushes state to the per-rig channel immediately, eliminating up to 750ms
of latency for confirmed frequency/mode/state changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 09:09:59 +01:00
sjgandClaude Opus 4.6 3ad5f7a3b7 [fix](trx-frontend-http): make vchan wrapper fire-and-forget on freq change
The vchan setRigFrequency wrapper was awaiting vchanTakeSchedulerControl()
(HTTP PUT to /scheduler-control) and vchanSetChannelFreq() (HTTP PUT to
channel freq endpoint) before calling the original setRigFrequency. This
added a full HTTP round-trip of latency to every frequency change. Make
both fire-and-forget: optimistic local update happens first, network calls
run in background.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 08:57:50 +01:00
sjgandClaude Opus 4.6 faf86faff9 [fix](trx-frontend-http): make setRigFrequency fire-and-forget
The HTTP round-trip for set_freq blocks on the server processing the
command (mpsc → TCP → rig hardware → response). With optimistic local
updates, CSS overlay, and SSE snap-back guard already in place, there is
no reason to await the network call. All callers (jog, freq input, spectrum
click, RDS AF tune) now return immediately after the optimistic update.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 08:31:01 +01:00
sjgandClaude Opus 4.6 7f9ecad34c [fix](trx-frontend-http): prevent SSE from snapping freq back during optimistic update
When the user changes frequency, applyLocalTunedFrequency sets lastFreqHz
optimistically. But the SSE state stream could push back the old server
frequency before set_freq completes, causing the marker to snap back then
forward. Add a sequence-guarded _freqOptimisticHz that suppresses stale
SSE frequency updates while a set_freq is in flight.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 08:20:58 +01:00
sjgandClaude Opus 4.6 54df7cf0f9 [feat](trx-frontend-http): GPU-composited CSS overlay for instant freq/BW updates
Replace synchronous drawSignalOverlay() calls in freq/BW change handlers
with lightweight CSS div elements repositioned via transform: translateX().
This is GPU-composited with zero layout/paint cost, making frequency and
bandwidth changes appear instantaneous. The full WebGL overlay catches up
on the next requestAnimationFrame.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 08:09:36 +01:00
sjgandClaude Opus 4.6 dc2c8b6eb1 [fix](trx-frontend-http): instant spectrum overlay on freq/bw changes
Call drawSignalOverlay() synchronously on frequency and bandwidth changes instead of deferring entirely to requestAnimationFrame. Also make bookmark apply fire-and-forget so the click handler returns immediately after optimistic UI updates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 07:51:30 +01:00
sjgandClaude Opus 4.6 5821531a93 [feat](trx-client): per-rig audio TCP connections
Replace single-connection relay with per-rig audio manager that spawns independent TCP connections for each rig. Each rig gets its own broadcast channel, stream info, and vchan command routing. Selected rig mirrors to global channels for backward compat. Also fix bookmark apply to update spectrum marker instantly and fire all requests in parallel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 07:37:59 +01:00
sjgandClaude Opus 4.6 5d905bff87 [fix](trx-frontend-http): stop per-rig audio fallback to global broadcast
When ?rig_id= is specified on /audio, don't fall back to the global broadcast (which carries whichever rig is connected). Return 404 for rigs without an active audio connection instead of silently delivering the wrong rig's audio. Also create per-rig audio channels for all known rigs eagerly so connected rigs are instantly subscribable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 07:26:26 +01:00
sjgandClaude Opus 4.6 bf754f573a [fix](trx-frontend-http): filter SSE channel events by rig and fix audio hang
Channel SSE events were broadcast to all tabs regardless of rig, causing tabs to display wrong rig's channels. Per-rig audio info_rx override caused WebSocket to hang waiting for stream info that never arrives.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 07:17:26 +01:00
sjgandClaude Opus 4.6 9900314c8c [feat](trx-client): add per-rig spectrum and audio streams
Each browser tab can now subscribe to a specific rig's spectrum and
audio independently via ?rig_id= query params on /spectrum and /audio.
The remote client polls spectrum for all rigs with active subscribers
and routes responses to per-rig watch channels. Virtual channel
commands are routed through per-rig senders with global fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 06:59:54 +01:00
sjgandClaude Opus 4.6 6fb7b61c1c [feat](trx-frontend-http): auto-subscribe new sessions to primary channel
When a new tab connects and receives the initial channels event,
automatically subscribe to channel 0 (primary) so the session joins
the same tuned channel as other users on that rig. Uses a lightweight
auto-join that skips scheduler control takeover since audio isn't
started yet at this point.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 06:15:47 +01:00
sjgandClaude Opus 4.6 0e195bd3c6 [fix](trx-frontend-http): remove global state mutation from select_rig
select_rig no longer mutates remote_active_rig_id — only the per-session
mapping is updated. This eliminates cross-tab leaking entirely: each tab
carries its own rig_id via the session manager, /events?rig_id, and
/status?rig_id query params.

The global remote_active_rig_id now only serves as the startup default
for brand-new sessions that have no rig_id yet.

Also fix the About section to show the per-tab lastActiveRigId instead
of the server's global active_rig_id.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 00:20:34 +01:00
sjgandClaude Opus 4.6 f3d8d349b1 [fix](trx-frontend-http): make /status respect per-tab rig selection
pollFreshSnapshot() fetches GET /status on every SSE connect/reconnect,
but /status always returned the global selected rig's state, overwriting
the per-tab display with whichever rig was last switched to globally.

Now pollFreshSnapshot passes rig_id as a query param and the /status
endpoint uses the per-rig watch channel when provided, matching the
/events behavior for true per-tab rig isolation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 00:14:43 +01:00
sjgandClaude Opus 4.6 0836c815e4 [fix](trx-frontend-http): restore global rig update in select_rig
The previous commit conditionally skipped updating remote_active_rig_id
when session_id was provided, but the remote client reads the global to
route commands to the correct rig on trx-server. Restore the
unconditional global update; cross-tab SSE isolation is handled by the
rig_id query param on /events and the JS-side guard in applyRigList.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 00:08:41 +01:00
sjgandClaude Opus 4.6 c332172900 [fix](trx-frontend-http): pass rig_id to SSE endpoint on reconnect
After select_rig stopped mutating the global remote_active_rig_id for
session-aware clients, SSE reconnects would fall back to the old global
default instead of the newly selected rig. Now connect() passes
lastActiveRigId as a rig_id query param to /events, and the server
prefers it over the global default when present.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 00:04:36 +01:00
sjgandClaude Opus 4.6 b9e1601730 [fix](trx-frontend-http): use green color for active header play button
The .audio-active state was using --accent-green which is actually
orange (#c24b1a). Match the regular play button's hardcoded #00d17f
green so the header button visually matches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 00:02:25 +01:00
sjgandClaude Opus 4.6 7f13cdf08a [fix](trx-frontend-http): prevent rig switch from leaking across tabs
select_rig was unconditionally updating the global remote_active_rig_id,
causing all SSE sessions to see the changed rig. Now only the per-session
mapping is updated when session_id is provided; the global default is
only changed for non-session-aware clients.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 00:00:49 +01:00
sjgandClaude Opus 4.6 12a3c62917 [fix](trx-frontend-http): fix play button icon not filling header button
Bump selector specificity to .header-bar-btn.header-audio-btn so the
padding: 0 rule wins over the generic .header-bar-btn padding, and
switch the SVG to width/height: 100% so it expands to fill the button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-22 00:00:44 +01:00
sjgandClaude Opus 4.6 f78097b0ed [feat](trx-rs): per-rig watch channels for independent SSE streams
Add per-rig watch::Sender<RigState> map to FrontendRuntimeContext,
populated by refresh_remote_snapshot for every rig returned by GetRigs.
The SSE /events endpoint now subscribes to the session's rig-specific
watch channel instead of the single global one, allowing different
browser tabs to independently view different rigs. The JS frontend
reconnects SSE on rig switch to pick up the new channel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 23:55:55 +01:00
sjgandClaude Opus 4.6 f023369c7d [style](trx-frontend-http): enlarge play button icon to fill header button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 23:40:56 +01:00
sjgandClaude Opus 4.6 ea362c9cdd [feat](trx-frontend-http): per-session SSE rig selection
Add SessionRigManager to track per-SSE-session rig_id so different
browser tabs can independently select rigs without interfering.
The /events SSE stream filters state updates by session rig (falling
back to the global active rig), and /select_rig accepts an optional
session_id to update the per-session mapping.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 23:38:24 +01:00
sjgandClaude Opus 4.6 dffaed6216 [fix](trx-frontend-http): restore select_rig call and simplify play button icon
Rig switch needs the server call so SSE/audio follow the selected rig. Play button now uses a fixed triangle icon sized to match header controls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 23:17:17 +01:00
sjgandClaude Opus 4.6 730dbcc20d [feat](trx-frontend-http): add audio play/stop toggle button in header
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 23:13:01 +01:00
sjgandClaude Opus 4.6 5a12a321b2 [fix](trx-frontend-http): make rig switch purely client-side, no /select_rig call
The /select_rig endpoint sets global server state which affects all tabs. Since postPath() already sends rig_id with every command, the rig picker now just sets the local lastActiveRigId variable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 23:07:36 +01:00
sjgandClaude Opus 4.6 3412827704 [fix](trx-frontend-http): skip auto-appending rig_id when already in URL
postPath() was duplicating rig_id on /select_rig calls, causing deserialization failure and silently dropping the rig switch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 22:59:48 +01:00
sjgandClaude Opus 4.6 38f4a71c97 [feat](trx-frontend-http): per-tab rig selection via rig_id on all commands
All POST command endpoints now accept an optional ?rig_id= parameter so each browser tab can independently target a specific rig. The JS frontend tracks the active rig per-tab and auto-appends rig_id to every request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 22:34:37 +01:00
sjgandClaude Opus 4.6 6079407257 [fix](trx-frontend-http): widen card bookmark gutter so side stacks are not clipped
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 21:58:49 +01:00
sjgandClaude Opus 4.6 bc596dd9a1 [feat](trx-server): make VFO priming optional via behavior.vfo_prime config
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 21:45:59 +01:00
sjgandClaude Opus 4.6 2a720573f4 [fix](trx-frontend-http): show side bookmark stacks on desktop, hide only on mobile
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 21:27:27 +01:00
sjgandClaude Opus 4.6 cd80954767 [fix](trx-frontend-http): refresh spectrum bookmark markers on edit and delete
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 21:17:20 +01:00
sjgandClaude Opus 4.6 a814e7ec5b [fix](trx-frontend-http): widen spectrum shift button hit target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 21:04:57 +01:00
sjgandClaude Opus 4.6 013badd081 [fix](trx-frontend-http): preserve location city name across freq changes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 21:04:52 +01:00
sjgandClaude Opus 4.6 e2e1aaebe2 [feat](trx-frontend-http): add double-click to reset contrast slider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 18:33:40 +01:00
sjgandClaude Opus 4.6 9c1c7b9ff1 [feat](trx-frontend-http): add rig source filter to map view
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 18:22:55 +01:00
sjgandClaude Opus 4.6 1078fb23ed [feat](trx-frontend-http): add waterfall contrast gamma curve slider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 18:11:39 +01:00
sjgandClaude Opus 4.6 6a7c3b5bbb [feat](trx-frontend-http): add multi-bookmark selection for batch deletion
Add checkbox column to bookmark table with select-all support and a
Delete Selected button for batch removal. New POST /bookmarks/batch_delete
API endpoint accepts an array of IDs and removes them in one request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 18:01:10 +01:00
sjgandClaude Opus 4.6 72465d14b3 [fix](trx-frontend-http): reset decoder state and virtual channels on rig switch
Broadcast virtual channel list for newly selected rig from select_rig so SSE
clients receive correct channels immediately. Detect rig changes in render()
and reset stale decoder state (RDS, spectrum, decoder status indicators) from
the previous rig.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 15:58:20 +01:00
sjgandClaude Opus 4.6 49d9756fd1 [feat](trx-frontend-http): show location grid and city/country in header
Add Maidenhead locator and reverse-geocoded city/country to the header.
Uses Nominatim API to resolve nearest city asynchronously.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 15:32:52 +01:00
sjgandClaude Opus 4.6 7898c3d61a [fix](trx-backend-soapysdr): enumerate available devices on match failure for diagnostics
When Device::new(args) fails, enumerate all available SoapySDR devices and
include them in the error message. Also hint that args are case-sensitive.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 15:32:46 +01:00
sjgandClaude Opus 4.6 d74f77537a [fix](trx-backend-soapysdr): remove silent fallback to first device when args are specified
When specific SoapySDR device args are provided (e.g. with a serial number),
fail hard instead of silently falling back to Device::new("") which opens
the first available device. This caused multi-device setups to bind both
rig instances to the same physical device.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 15:23:06 +01:00
sjgandClaude Opus 4.6 e6c34bb695 [feat](trx-frontend-http): add weakest decoded signal stats and clickable tiles
Add weakest decoded signal panel showing top 5 weakest SNR signals. Make all
stat tiles (longest decode, strongest signal, weakest signal) clickable to
highlight the corresponding locator on the map.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-21 08:46:40 +01:00
sjgandClaude Opus 4.6 3877de3c4f [fix](trx-wspr): rewrite decoder with soft-decision Fano from WSJT-X reference
Replace broken hard-decision pipeline with proper soft-decision decoding
matching the WSJT-X wsprd reference implementation. Key fixes: soft-symbol
demodulation using amplitude differences, soft-decision Fano decoder with
Es/No=6dB metric table and delta=60 threshold, deinterleave preserving soft
values instead of extracting hard bits, convolutional tail constraint, and
normalized sync correlation scoring.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-20 08:16:33 +01:00
sjgandClaude Opus 4.6 11097a5133 [docs](trx-ftx): update README with new module architecture
Reflect the common/ft8/ft4/ft2 directory reorganization in the
architecture diagram, file tree, and signal flow description.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-20 00:08:38 +01:00
sjgandClaude Opus 4.6 ab8425c85c [refactor](trx-ftx): move ft2_encode to ft2 module, remove all allow clauses
Move ft2_encode from ft4/ to ft2/ where it belongs. Remove all
module-level #[allow] suppressions and fix the underlying issues:
- Remove dead code: wf_mag_at, xor_rows, unused Monitor IFFT fields, OsdBox.size
- Gate encode174_to_bits with #[cfg(test)] (only used in tests)
- Convert 40+ C-style index loops to idiomatic iterators
- Add targeted #[allow(clippy::too_many_arguments)] on two OSD functions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-20 00:05:11 +01:00
sjgandClaude Opus 4.6 bb18d90cbe [refactor](trx-ftx): reorganize into common/, ft8/, ft4/, ft2/ modules
Split flat src/ layout into protocol-oriented directory structure:
- common/: shared types, constants, LDPC/OSD decoders, monitor, message, CRC
- ft8/: FT8-specific sync scoring, likelihood extraction, tone encoding
- ft4/: FT4-specific sync scoring, likelihood extraction, tone encoding
- ft2/: FT2 pipeline, waterfall decode, bitmetrics, downsample, sync
- Top-level: lib.rs (mod declarations) and decoder.rs (public API)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 23:51:17 +01:00
sjgandClaude Opus 4.6 de0bc89705 [refactor](trx-ftx): flatten ft2/ submodules into top-level src/
Move ft2/osd.rs, ft2/bitmetrics.rs, ft2/downsample.rs, ft2/sync.rs
out of the ft2/ directory into src/ as top-level modules. Convert
ft2/mod.rs to ft2.rs. Update all imports from super:: to crate::ft2::.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 23:31:19 +01:00
sjgandClaude Opus 4.6 2da749b978 [refactor](trx-ftx): optimize hot paths and deduplicate decoder internals
- Cache generator matrix with OnceLock (P0.1)
- Store raw complex in WfElem, eliminate powf round-trip (P0.2)
- Reuse FFT planners across decode cycles in Ft2Pipeline (P0.3)
- Deduplicate fast_atanh/ldpc_check into ldpc.rs (P1.1)
- Gate unused sum-product ldpc_decode behind #[cfg(test)] (P1.2)
- Eliminate double pack_bits in verify_crc_and_build_message (P1.3)
- Remove unnecessary unsafe impl Send for Ft8Decoder (P1.4)
- Convert key loops to iterator/zip patterns (P2.1)
- Remove resolved clippy::manual_memcpy suppressions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 23:22:58 +01:00
sjgandClaude Opus 4.6 3dc6918082 [docs](trx-ftx): add README with attribution and architecture diagram
Replace FTX_CRATE.md with README.md documenting upstream origins
(kgoba/ft8_lib for FT8/FT4, iu8lmc/Decodium-3.0 for FT2) and a
Mermaid diagram of the crate architecture.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 20:01:29 +01:00
sjgandClaude Opus 4.6 1fe63257a1 [fix](trx-ftx): align FT2 decoder with Fortran reference thresholds
Remove normalize_llr which was undoing the scalefac=2.83 scaling,
causing LLRs to be 2.83x too small for the BP+OSD decoder. Align
sync thresholds with reference: coarse 0.50->0.40, decode 0.65->0.55,
sync quality 10->9, maxosd 3->4. Revert norm_sqr back to norm in
bitmetrics since the metric difference is nonlinear.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 19:59:52 +01:00
sjgandClaude Opus 4.6 9c9026e7ca [refactor](trx-ftx): eliminate heap allocations in LDPC and OSD decoders
Replace Vec<Vec<f32>> with flat stack arrays in ldpc_decode (~114KB),
convert 19+ Vec allocations to stack arrays in osd174_91, eliminate
per-call temp Vec in nextpat91 via in-place mutation, and replace
norm() with norm_sqr() in bitmetrics hot loop (~5.4M calls/frame).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 19:41:34 +01:00
sjgandClaude Opus 4.6 9b49b41fb3 [refactor](trx-ftx): consolidate FT2 decoder with shared FT8 code
Eliminate duplicated code between FT2 and FT8/shared modules:
- Share parity8() from encode.rs, remove copies in ft2/mod.rs and osd.rs
- Share pack_bits() from decode.rs, remove pack_bits91() from osd.rs
- Add verify_crc_and_build_message() to decode.rs, used by both FT8 and FT2
- Add normalize_llr() to decode.rs, replacing per-module normalization
- Make encode174() pub(crate), add encode174_to_bits() for bit-array output
- Wire FT2 decode_hit to use full BP+OSD decoder from osd.rs instead of
  separate BP + sum-product + OSD-lite flow
- Align LLR scale factor to 2.83 matching reference implementation

Net -178 lines removed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 19:15:46 +01:00
sjgandClaude Sonnet 4.6 4c728bd8da [fix](trx-ftx): fix infinite loop in callsign hash table when full
add() and lookup() had no wrap-around guard in their linear-probe loops.
Once 256 unique callsigns filled the table, any subsequent add or lookup
for an absent hash would cycle through all 256 slots forever, hanging the
FT8 decoder task permanently inside block_in_place. On a busy band this
could happen within a few minutes of operation.

- add(): evict the probe-start slot when a full cycle completes
- lookup(): return None after a full probe cycle
- reset(): call cleanup(10) each slot boundary to age out stale entries
- Add regression tests for both infinite-loop scenarios

Also includes cargo fmt reformatting of pre-existing style issues.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 18:38:27 +01:00
sjgandOpenAI Codex f4bfaa70d2 [style](trx-frontend-http): update tab bar icons
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 00:51:41 +01:00
sjgandOpenAI Codex f81213e35d [style](trx-frontend-http): simplify spectrum scale arrows
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 00:47:13 +01:00
sjgandOpenAI Codex f1fd667660 [fix](trx-frontend-http): preserve scale arrows on axis redraw
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 00:41:33 +01:00
sjgandOpenAI Codex 5d0794924b [fix](trx-frontend-http): move spectrum shift arrows onto scale
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 00:38:31 +01:00
sjgandOpenAI Codex 9c6ccd626d [fix](trx-frontend-http): move spectrum shift arrows into overlay
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 00:35:03 +01:00
sjgandOpenAI Codex f85e24a92f [fix](trx-frontend-http): restore full-width spectrum layout
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-19 00:00:28 +01:00
sjgandOpenAI Codex fb6a27068b [fix](trx-frontend-http): fit spectrum bookmarks on smaller displays
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-18 23:54:15 +01:00
sjgandOpenAI Codex 43eb9a2292 [fix](trx-server): scope decoder resets by mode
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-18 23:31:55 +01:00
sjg 0b28900082 [refactor](trx-ftx): optimize ft2 decode hot paths
Reuse FT2 downsample and bitmetric work buffers, speed up\nsync2d_score with precomputed references, and cache peak-search\nFFT state on the pipeline.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>

Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-18 23:08:42 +01:00
sjgandOpenAI Codex 7d20058c03 [chore](trx-rs): remove unused external ft8_lib
Delete the obsolete ft8_lib submodule and update documentation to point at the pure Rust trx-ftx decoder.

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-18 22:44:35 +01:00
sjgandOpenAI Codex ab30270a63 [chore](trx-rs): update SPDX copyright headers
Normalize tracked SPDX headers to the 2026 Stan Grams identity.

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-18 22:39:06 +01:00
sjgandOpenAI Codex 2609ce668a [fix](trx-ftx): clear decoder warnings
Quiet compiler and clippy warnings in the translated decoder modules.

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-18 22:34:55 +01:00
sjgandOpenAI Codex 7cc8490024 [fix](trx-wspr): clear decoder warnings
Keep protocol items before tests and rewrite warning-triggering loops.

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-18 22:34:43 +01:00
sjgandClaude Opus 4.6 5dabe32106 [refactor](trx-rs): remove trx-ft8 C FFI crate, use trx-ftx directly
Delete trx-ft8 (C wrapper around ft8_lib + ft2_ldpc) and update
trx-server to depend on trx-ftx (pure Rust) directly. Removes
~2,900 lines of C code and all unsafe FFI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-18 22:22:20 +01:00
sjgandClaude Opus 4.6 de79e8a1e6 [feat](trx-ftx): add pure Rust FTx decoder crate
Replace the C FFI-based trx-ft8 with a pure Rust implementation
supporting FT8, FT4, and FT2 protocols. Eliminates cc/libc build
dependencies and all unsafe FFI code while providing the same
Ft8Decoder/Ft8DecodeResult public API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-18 22:21:12 +01:00
sjgandClaude Opus 4.6 974b9fa9ed [refactor](trx-rs): convert external/ft8_lib to git submodule
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-18 21:44:07 +01:00
sjgandClaude Sonnet 4.6 22311eb01c [fix](trx-frontend-http): drop bidirectional requirement for decode paths
The bidirectional check required both A→B and B→A directed messages to
draw a contact line, which was too strict — the receiver may not hear
both sides of a QSO. Now a decode path is drawn whenever a directed
message is decoded and the target's locator is known from any message
in the 24 h history window.

Also rename "Longest QSOs" → "Longest decode paths" and update
related UI labels to better reflect what is actually shown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-15 17:09:12 +01:00
sjgandClaude Sonnet 4.6 8cb713b3b1 [fix](trx-frontend-http): require bidirectional decodes for map QSO contact paths
A QSO requires both parties to hear each other. Previously the map drew
contact paths whenever A sent a directed message to B and B's locator
was known from any message. Now paths only appear when both A→B and B→A
directed messages are present in the decoded history.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-15 16:53:46 +01:00
sjgandClaude Sonnet 4.6 3d99cac03b [feat](trx-backend-soapysdr): add hardware AGC toggle and SDR settings UI row
Add hardware AGC on/off control for SoapySDR backend, wired through the
full stack from RigCommand to the web UI:

- RigCommand::SetSdrAgc(bool) + ClientCommand::SetSdrAgc in protocol
- set_sdr_agc() on RigCat trait (not-supported default)
- SoapySdrRig: agc_enabled field, set_sdr_agc() via pipeline agc_cmd,
  sdr_agc_enabled in filter_state(); removes the "not yet implemented"
  warning — gain_mode="auto" now properly enables hardware AGC via
  SoapySDR set_gain_mode()
- IqSource::set_gain_mode() trait method; RealIqSource implements it
- SdrPipeline: agc_cmd channel, read loop applies it each iteration
- POST /set_sdr_agc endpoint in trx-frontend-http
- New "SDR settings" full-row in index.html with Hardware AGC checkbox
  and RF Gain (moved out of WFM controls); row hidden when
  show_sdr_gain_control is false
- app.js: AGC checkbox handler, disables RF gain input while AGC is on,
  syncs checkbox state from filter.sdr_agc_enabled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-15 09:44:46 +01:00
sjgandClaude Sonnet 4.6 262e78e72b [fix](trx-backend-soapysdr): fix AM demodulation DC blocker, AGC, and add IQ AGC
Three issues caused audible distortion on AM reception:

1. DC blocker shared r=0.9999 (τ≈1.25 s at 8 kHz) across all modes.
   For AM the envelope detector outputs A_c+m(t) — always positive —
   so the blocker needs to track the carrier bias quickly.  AM now uses
   r=0.999 (τ≈125 ms), 10× faster, while keeping the highpass cutoff
   below 2 Hz so speech is unaffected.

2. Audio AGC time constants were inverted relative to good AM AGC design:
   attack=200 ms (should be fast to prevent overload) and
   release=3500 ms (unreasonably sluggish).  Changed to attack=5 ms /
   release=200 ms, target=0.5, max_gain=36 dB.

3. No IQ AGC before envelope detection meant carrier amplitude variation
   went directly into the audio chain, forcing the slow audio AGC to
   handle both RF level and audio level simultaneously.  Added an AM IQ
   AGC (attack=0.5 ms, release=50 ms, target=0.7, max=30 dB) that
   normalizes carrier power before demod_am, so the DC blocker always
   sees the same steady-state bias regardless of signal strength.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-15 09:28:59 +01:00
sjgandClaude Sonnet 4.6 f78232562d [fix](trx-frontend-http): add 200-row DOM cap notice to FT8/FT4/FT2 tabs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-15 08:50:30 +01:00
sjgandClaude Sonnet 4.6 b1f52bbfa5 [fix](trx-frontend-http): fix FT4/FT2/WSPR message container styling and cap DOM rows
Apply #ft8-messages container style (border, rounded corners, monospace
font, max-height with scroll) to #ft4-messages, #ft2-messages, and
#wspr-messages which were missing it.

Add #ft4-decode-toggle-btn and #ft2-decode-toggle-btn to the narrow-
screen white-space:nowrap media query rule alongside FT8/WSPR.

Cap DOM rows rendered per history view to 200 (FT8_MAX_DOM_ROWS,
FT4_MAX_DOM_ROWS, FT2_MAX_DOM_ROWS). Full history is retained in
memory; only the DOM representation is bounded. This prevents tab
switching from becoming sluggish after a long decode session where
thousands of rows accumulate in the DOM.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-15 08:46:14 +01:00
ab3bf9120e [fix](trx-backend-soapysdr): fix SSB demodulation with asymmetric complex BPF
Three bugs caused USB/LSB to sound like AM:

1. The IQ low-pass filter was symmetric (passband ±BW/2), so both
   sidebands were passed equally — taking .re then produced DSB-SC
   rather than SSB audio.

2. cutoff_hz was computed as bandwidth_hz/2, halving the usable audio
   bandwidth (1500 Hz for a 3 kHz USB channel).

3. demod_lsb claimed spectrum inversion was "handled upstream by
   negating channel_if_hz", but that negation was never applied; USB
   and LSB were functionally identical.

Fix: add a shift_norm parameter to build_fir_kernel / BlockFirFilterPair
that complex-modulates the time-domain FIR coefficients by
e^{j·2π·shift_norm·n}, shifting the passband in the frequency domain.
A new ssb_shift_norm() helper returns +cutoff_norm for USB/CW/DIG
([0, BW] Hz passband) and -cutoff_norm for LSB/CWR ([-BW, 0] Hz
passband); all other modes get 0.0 (symmetric LPF unchanged).

After the one-sided filter, taking .re correctly reconstructs the
selected sideband. No IF negation is needed for LSB.

Also fix two unit tests missing the force_mono_pcm argument introduced
after they were last updated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-15 08:35:33 +01:00
sjgandClaude Sonnet 4.6 342adf476c [fix](trx-ft8): limit FT2 OSD-lite to prevent false decodes
The OSD-3 (triples) path over 5 LLR passes was doing ~11,600 CRC checks
per candidate. With a 14-bit CRC this gives ~0.7 expected false positives
per candidate — far too high.

Remove OSD-3 entirely. Cap max_candidates at 16 for OSD-1/OSD-2, giving
136 CRC checks per pass (680 total). Gate OSD-lite behind a check that
LDPC reached within 6 parity errors of converging, so it only fires when
the LLRs are already trustworthy. Combined false-positive rate drops to
~0.04 per near-miss candidate.

Also remove the now-unused ft2_osd_decode and ft2_codeword_distance
functions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-15 08:14:22 +01:00
sjgandClaude Sonnet 4.6 eac2efdb35 [fix](trx-ft8): increase FT2 LDPC iterations and improve OSD fallback
Increase BP/SP iteration count from 30 to 50 to match WSJT-X reference
and give belief propagation more opportunities to converge near-threshold
candidates.

Replace the parity-based OSD-1/OSD-2 fallback (which required LDPC to
have nearly converged) with ft2_osd_lite_decode applied to all five LLR
combination passes. The CRC-based decoder works directly from raw LLRs
without depending on LDPC convergence, searching the 24 least-reliable
systematic bits for up to three bit errors via OSD-3.

Also increase max_candidates in ft2_osd_lite_decode from 12 to 24 for
broader coverage of likely error positions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-15 08:06:19 +01:00
sjgandClaude Sonnet 4.6 3b28281684 [fix](trx-ft8): add OSD-1/OSD-2 to FT2 LDPC decode path
Diagnostic logging showed the FT2 BP/SP decoders consistently reach
1-8 residual parity errors rather than zero — the LLRs are correct
in direction but LDPC belief propagation stalls just short of
convergence.

Add ft2_osd_decode() implementing Ordered Statistics Decoding orders
1 and 2: after the five-pass BP/SP loop fails, sort the 174 codeword
bits by |LLR| ascending and trial-flip single bits (OSD-1, always)
or all pairs of the 50 least-reliable bits (OSD-2, when the remaining
error count is <= 4).  Each trial costs one O(83) parity check;
worst-case overhead is ~1300 checks per candidate, negligible next to
the 5 x 30-iteration BP/SP passes already performed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-15 07:30:17 +01:00
sjgandClaude Sonnet 4.6 0d2eb7adcd [fix](trx-ft8): log minimum LDPC parity errors per pass in FT2 window
Add err=N/N/N/N/N to the FT2 window diagnostic log line, showing the
minimum number of unsatisfied parity equations across all candidates
for each of the five LLR passes. This makes it possible to distinguish
between a signal-quality-limited failure (small error count) and a
systematic decoder bug (large error count), which is the key unknown
in diagnosing the current FT2 LDPC non-convergence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-15 07:21:38 +01:00
sjg 188fa38f1b [fix](trx-ft8): improve FT2 decode fallback
Normalize FT2 log-likelihoods before LDPC and fall back to\nthe standard waterfall candidate decoder when the raw FT2\npath produces no decodes.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>

Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-14 22:39:07 +01:00