Commit Graph

134 Commits

Author SHA1 Message Date
Claude f2469fee12 [fix](trx-wxsat): fix LRPT decoder always-enabled bug and implement image decoding
The LRPT decoder task was missing mode checks, processing audio in any
rig mode once toggled on. Now it only activates in FM mode, matching
the decoder registry descriptor. Also corrects active_modes from
DIG/USB to FM.

Replaces the MCU stub (which treated compressed JPEG data as raw
pixels) with proper Huffman + inverse-DCT decompression, CCSDS packet
reassembly from MPDUs, and CCSDS derandomization in the CADU framer.

https://claude.ai/code/session_0135LuveBndEiZHkU2jsKPB9
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-31 14:58:15 +02:00
sjg 083caf412f [style](trx-rs): apply rustfmt formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-29 22:59:43 +02:00
Claude c041ac83f3 [refactor](trx-rs): resolve all improvement areas (P1–P3)
P1 — High:
- Merge duplicate APRS/HF-APRS decoder tasks into parameterised inner fn
- Merge duplicate FT8/FT4 decoder tasks into shared ftx inner fn
- Add multi-rig state isolation and command routing tests (listener.rs)
- Add background decode evaluate_bookmark unit tests

P2 — Medium:
- Fix decode-log silent flush errors and rotation failure fallback
- Split api.rs (2,831 LOC) into 7 logical modules (decoder, rig, vchan,
  sse, bookmarks, assets, mod)
- Extract background decode decision cascade into pure evaluate_bookmark()
  function with ChannelAction enum
- Relax actix-web pin from =4.4.1 to 4.4
- Replace VDES magic numbers with named constants

P3 — Low:
- Add doc comments to AisDecoder, VdesDecoder, RdsDecoder
- Add debug_assert on turbo decoder interleaver/deinterleaver lengths
- Add tracing info_span! to all 10 decoder block_in_place calls
- Optimize hot-path string cloning in remote_client spectrum loop

https://claude.ai/code/session_01Y3G65hrfsRRjwyBF2qbBmc
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-29 19:29:17 +02:00
Claude d512268526 [feat](trx-vdes): implement Turbo FEC, CRC-16, and link-layer parsing
Add the three missing VDES decoder components per ITU-R M.2092-1:

- turbo.rs: Turbo FEC decoder with dual 8-state RSC constituent
  encoders, BCJR/MAP iterative decoding (8 iterations), QPP
  interleaver, and rate-1/2 depuncturing
- crc.rs: CRC-16-CCITT validation (poly 0x1021, init 0xFFFF) for
  decoded link-layer frames
- link_layer.rs: Structured parsing of M.2092-1 link-layer frames
  (Messages 0-6) including station addressing, ASM identification,
  geographic bounding boxes, and ACK/NACK reporting

The main decode pipeline now attempts turbo decoding first with CRC
validation, falls back to Viterbi when turbo fails, and reports
crc_ok=true when either path validates. 27 tests covering all new
modules.

https://claude.ai/code/session_01SJSN7cv3zoL1xNcb8ex2zY
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-29 14:50:42 +02:00
sjg 83c23401fc [docs](trx-rs): replace all ASCII diagrams with Mermaid
Convert ASCII art and box-drawing diagrams to Mermaid fenced code blocks
across README.md, CLAUDE.md, Architecture.md, Wxsat-Map-Overlay.md, and
trx-wxsat/README.md. Add Mermaid-only policy to CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-29 12:29:12 +02:00
sjg 0a60684e28 [feat](trx-rs): remove NOAA APT decoder
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-28 23:32:10 +01:00
Claude 3ff848a715 [docs](trx-wxsat): add README with architecture and API documentation
https://claude.ai/code/session_01Cm1JpWMDZanjwKg3r2S3VR
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 11:23:33 +01:00
Claude 384e1597f6 [style](trx-wxsat): apply cargo fmt formatting
https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
Claude 4d40c29e49 [refactor](trx-wxsat): unify image encoding to shared PNG module
Extract common image_enc module at crate root with encode_grayscale_png
and encode_rgb_png helpers. Both NOAA APT and Meteor-M LRPT now use PNG
as the output format through the shared encoder. Drop jpeg image feature
dependency.

https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
Claude 1a3b815ed8 [feat](trx-wxsat): add Meteor-M LRPT decoder and Weather Satellites frontend panel
Restructure trx-wxsat into noaa/ (APT) and lrpt/ (Meteor-M LRPT) submodules
with shared crate base. Add QPSK demodulator, CCSDS CADU framer, MCU channel
assembler for LRPT. Wire LRPT through full stack (core types, protocol, server
decoder task, client). Add Weather Satellites sub-tab in Digital Modes with
toggle buttons for NOAA APT and Meteor LRPT, descriptions, and image history.

https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
Claude d26ef6ca81 [feat](trx-wxsat): rename trx-noaa to trx-wxsat with full NOAA APT decode
Rename the crate from trx-noaa to trx-wxsat (weather satellite) across
the entire workspace. Add full NOAA satellite decode support:

- Telemetry frame parsing: extract 16-wedge calibration data from the
  128-line telemetry frames embedded in APT lines
- Radiometric calibration: piecewise-linear LUT built from wedges 1-8
  to correct pixel values against known reference levels
- Channel identification: detect AVHRR sensor channels (VIS, NIR, MIR,
  TIR) from wedge 9 values per APT sub-channel
- Satellite identification: heuristic NOAA-15/18/19 detection from
  channel A/B sensor pairings
- Histogram equalisation: per-channel contrast enhancement for improved
  image output
- WxsatImage now carries satellite name and channel labels in decoded
  message broadcasts

https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
sjg 4b40d44814 [feat](trx-noaa): add NOAA APT satellite image decoder
New trx-noaa crate: FFT-based Hilbert transform (rustfft) for 2400 Hz
AM demodulation, sync A detection via cross-correlation, line assembly
at 4160 Hz, and JPEG output via the image crate.

- trx-core: NoaaImage type, DecodedMessage::NoaaImage variant,
  noaa_decode_enabled/noaa_decode_reset_seq on RigState/RigSnapshot,
  AUDIO_MSG_NOAA_IMAGE = 0x16
- trx-server: DecoderHistories::noaa, run_noaa_decoder task (activates
  on noaa_decode_enabled, auto-finalises after 30 s silence), saves
  JPEGs to ~/.cache/trx-rs/noaa/<YYYY-MM-DD_HH-MM-SS>.jpg, forwards
  events over TCP audio channel and history replay

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-28 07:00:24 +01:00
sjg 07cb8818f5 [style](trx-rs): cargo fmt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-27 18:52:05 +01:00
Claude ec0e41fe29 [refactor](trx-rds): remove Gardner TED to fix decoder freeze
The closed-loop Gardner Timing Error Detector was causing decoder
freezes under real-world conditions.  Remove all TED state and logic,
reverting to the simpler open-loop fixed clock_inc approach.  The
8-candidate parallel architecture already provides adequate timing
coverage via phase offsets without needing closed-loop tracking.

All other improvements (adaptive Costas bandwidth, syndrome-based OSD,
OSD(3/4), PI LLR accumulation) are retained.

https://claude.ai/code/session_01FsK5hZWGpAaaCpmWupN5AD
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 16:44:11 +01:00
Claude dd1af5aa94 [fix](trx-rds): revert RRC span to 10 chips to restore stopband rejection
The span-5 reduction passed synthetic tests because both the TX and RX
filters used the same truncated pulse shape (perfect matched filtering).
On real signals, the transmitter uses a full RRC pulse, and our truncated
RX filter couldn't match it — the weaker stopband rejection (~25% less
than pre-TED at α=0.30) allowed adjacent-channel interference through,
degrading soft confidence values and block decode rate, which caused
poor PS accumulation.

Span 10 at α=0.30 gives 50% better stopband rejection than the pre-TED
α=0.50/span=4 configuration, at the cost of 2048 vs 1024 FFT size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>

https://claude.ai/code/session_01Sw9esAuic8KHP1t8nZgvH2
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 16:28:39 +01:00
Claude c92e53c3d3 [fix](trx-rds): restore pre-TED decode quality with tighter OSD, higher TED gate, and syndrome-based search
Three root causes for the post-TED decode quality regression:

1. OSD(4) at cost ceiling 0.60 produced excessive false positives at
   marginal SNR.  Tightened to OSD(2)/0.45 baseline, OSD(3)/0.50 only
   after 2+ successful groups.

2. Gardner TED activated after just 1 group (score >= 1), but a single
   false OSD match could trigger timing adjustments that injected jitter
   into soft values.  Raised lock gate to score >= 3 so the TED only
   engages after the candidate has proven itself on a real signal.

3. RRC filter span of 10 chips doubled FFT size to 2048 with negligible
   sensitivity gain over span 5 at α=0.30 (sidelobes beyond ±2.5 chips
   contribute <5% energy).  Reduced to span 5 → FFT 1024, matching
   pre-TED efficiency.

Additional optimizations (no quality impact):
- Syndrome-based OSD: replaces per-trial CRC recomputation with a single
  XOR per trial (CRC linearity), and sorts bit positions by ascending
  soft confidence so inner loops break early instead of continuing.
- Pre-allocated FFT scratch buffer: eliminates ~234 heap allocations/sec
  in the overlap-save convolution.
- PI_ACC_THRESHOLD reduced from 8 to 5 for faster acquisition while
  retaining reliable majority voting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>

https://claude.ai/code/session_01Sw9esAuic8KHP1t8nZgvH2
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 16:02:45 +01:00
Claude 54ca86a93d [fix](trx-rds): stabilise Gardner TED to fix PI fluctuation and weak-signal regression
The Gardner TED (Tech 11) caused PI instability and worse weak-signal
pickup due to three issues:

1. Loop gains too aggressive: noise×noise error products at low SNR
   injected sub-chip jitter that degraded OSD soft confidence and PI
   LLR accumulation.  Reduced Kp from 4e-4→1.5e-4, Ki from 8e-8→2e-8
   (loop BW 0.11→0.053 Hz).

2. TED active during acquisition: before any group is decoded, the
   error signal is unreliable.  Now lock-gated (score >= 1) so the
   TED only engages after the first successful group decode, when
   timing is already close.  During acquisition, the 8-candidate
   architecture with fixed clocks provides adequate timing coverage.

3. Slow power estimate convergence: ted_power_est took ~420 ms to
   settle (0.999 alpha), causing the TED to over-steer during startup.
   Now uses 0.995 alpha (~84 ms convergence).

Additionally, when TED is gated off, the integrator decays toward zero
so stale corrections from a previous strong-signal period don't persist.

https://claude.ai/code/session_01KcVUcQQXrFyFA9NEjLhr9J
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 11:51:08 +01:00
Claude d4d852456f [fix](trx-rds): stay locked between groups to prevent decoder freeze
After completing a group (Block D), the decoder dropped lock and
reverted to search mode which only uses hard CRC. On weak signals,
Block A frequently has bit errors that OSD could correct but hard
decode cannot, causing the decoder to freeze after 2-3 successful
groups. Stay locked with ExpectBlock::A so the next Block A benefits
from OSD soft decoding.

https://claude.ai/code/session_015Ds9dxpeyFimYHySBuzbFw
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 10:41:56 +01:00
Claude 6b33550116 [fix](trx-rds): add staleness timeout to prevent decoder freeze
If the incumbent candidate has not produced a state update in 2 seconds,
clear its score advantage so any candidate can take over.  This prevents
the decoder from "freezing" on stale data when the incumbent's timing or
carrier tracking degrades — particularly important for dynamic PS where
the station rotates program service text.

Signed-off-by: Claude <noreply@anthropic.com>

https://claude.ai/code/session_0136sPdLUpYgvskrzbi2Epkv
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 08:46:29 +01:00
Claude 104629e373 [feat](trx-rds): push RDS decoding to 5 dB SNR
Fix Gardner TED loop structure bug (type-3 → type-2 PLL) and tune
gains for ζ=0.707 damping.  Add adaptive Costas loop bandwidth that
narrows from ~22 Hz to ~5.5 Hz once carrier is locked, reducing phase
noise at low SNR.  Narrow RRC matched filter (α=0.30, span=10 chips)
for ~0.6 dB noise BW gain.  Add OSD(4) for locked-mode blocks after
first successful group, and increase PI accumulation threshold to 8.

TED bug details: the original code used `clock_inc += correction`
which added the full integrator value at every chip, creating an
extra integration (type-3 loop) that is unconditionally unstable.
Fixed to `clock_inc = nominal + correction` (standard type-2 PLL).
Gains retuned: Kp=4e-4, Ki=8e-8 for ζ≈0.707 and loop BW≈0.11 Hz.

Signed-off-by: Claude <noreply@anthropic.com>

https://claude.ai/code/session_0136sPdLUpYgvskrzbi2Epkv
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 08:28:39 +01:00
sjg 9fc469aad1 [feat](trx-rds): improve low-SNR sensitivity with RRC, OSD(3), and Gardner TED
- RRC span 4→6 chips: better ISI rejection and pulse energy capture
- PI_ACC_THRESHOLD 3→5: more Block A votes before committing PI at weak signal
- OSD(3): add C(26,3)=2600 triple-bit search under same cost gate as OSD(2)
- Tech 11 Gardner TED: closed-loop symbol timing PI loop per Candidate;
  replaces open-loop NCO with mid-chip capture, power-normalised error signal,
  anti-windup integrator, and ±1% pull-in range (±23.75 Hz at 2375 chips/s)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-27 08:09:02 +01:00
Claude 5d6b9a4d94 [fix](trx-rds): reduce false decodes with OSD cost ceiling and PI consistency
Add OSD_MAX_FLIP_COST (0.45) to reject OSD corrections where the flipped
bits had high confidence — a strong false-decode indicator. Genuine errors
at 9-10 dB SNR have cost ≲0.3; noise matches cost 0.6-1.2.

Add PI consistency gate in process_group: reject groups whose Block A PI
differs from the candidate's established PI, preventing noise from
polluting accumulated PS/RT/PTYN text fields.

Raise PI_ACC_THRESHOLD from 2 to 3 for stronger PI voting.

Extend noise rejection test from 0.5s to 2s. Add 9 dB SNR sensitivity
test (all 16 tests pass).

https://claude.ai/code/session_01GYax4BQ9ZV9ZZfMjmmzgbh
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 07:20:22 +01:00
Claude 54b1f20ea4 [fix](trx-wspr): reduce false positives in WSPR decoder
The WSPR decoder was producing many false positive decodes due to
several overly permissive thresholds that allowed noise to reach the
Fano sequential decoder, which could then converge on random data:

- Raise normalized sync score threshold from 0.10 to 0.20 to reject
  noise candidates before attempting expensive Fano decoding
- Add minimum SNR gate (-20 dB) to skip candidates where the signal
  is indistinguishable from noise
- Return and check the Fano decoder's cumulative path metric, rejecting
  low-confidence decodes (metric < 20) that are likely noise artifacts
- Raise RMS threshold from 0.0005 to 0.005 to reject near-silent audio
- Add near-frequency deduplication to prevent the same signal decoded
  at slightly different (freq, dt) offsets from appearing multiple times
- Add noise-only regression test to verify no false positives on random
  input

https://claude.ai/code/session_01HTBoEsD1hp99TiYMSaHMVG
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 02:06:56 +01:00
Claude 06796342e7 [fix](trx-rds): apply RRC pulse shaping in test signal generator
The `chips_to_rds_signal` test helper was generating rectangular chip
pulses, but the receiver expects RRC-shaped transmit pulses so that
RRC(tx) × RRC(rx) = raised cosine with zero ISI. The rectangular
pulses caused ISI that drifted the symbol clock sampling point,
consistently skipping PS segment 2 in the end-to-end test.

Replace rectangular pulses with an impulse train convolved with the
same RRC taps used by the receiver. All 15 tests now pass including
`end_to_end_clean_signal_decodes_ps`.

https://claude.ai/code/session_01N2UcGaLDzYiM3gNrZ6kFBj
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 01:53:29 +01:00
sjg 57e88b3590 [fix](trx-rds): tune RDS parameters for maximum sensitivity
- RRC_ALPHA 0.75→0.50: narrower noise BW, ~0.6 dB SNR gain
- COSTAS_KI 3.5e-7: maintain ζ≈0.68 (1e-6 caused loop instability)
- Soft confidence: use biphase_i.abs() instead of full vector magnitude
  so OSD confidence is aligned with bit-decision sign; suppresses
  false groups under noise with residual Costas phase error
- OSD(2) in locked mode: corrects ≤2-bit errors after block sync
- Search mode: hard decode only for Block A; OSD(1) in search yielded
  ~13% false Block A rate per bit, letting wrong clock candidates
  accumulate false groups as fast as the correct candidate
- Incumbent candidate tracking (best_candidate_idx): the winning
  candidate updates best_state at equal score; challengers need strictly
  higher score; best_score tracks incumbent even on no-state-change
  groups so challengers can't leapfrog on a single false group
- blocks_to_chips: add NRZI (NRZ-Mark) pre-encoding so the differential
  biphase decoder recovers actual data bits rather than XOR-of-pairs
- Add blocks_to_chips_round_trips_all_groups test: verifies all 16 blocks
  across 4 PS segments round-trip correctly without BPSK modulation

[fix](trx-backend-soapysdr): lower pilot lock threshold for weak-signal RDS

- PILOT_LOCK_THRESHOLD 0.25→0.20, add PILOT_LOCK_ONSET=0.30 constant
- Pilot reference engages at coherence ≥0.36 (was ≥0.45)

WIP: end_to_end_clean_signal_decodes_ps still failing (13/15 pass).
Decoder skips segment 2 due to ISI from rectangular test chips through
RRC receive filter. chips_to_rds_signal needs RRC pulse shaping.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-27 01:46:01 +01:00
sjg 42b7dcaa42 [fix](trx-rds): improve weak-signal sensitivity
- Add single-bit flip fallback in search mode (push_bit_soft) so Block A
  can be acquired with one bit error, matching locked-mode OSD(1) behaviour
- Lower MIN_PUBLISH_QUALITY 0.38 -> 0.20 for earlier publish on noisy signals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-27 00:01:04 +01:00
sjg d1703f6b0a [fix](trx-rds): replace ring-buffer FIR with FFT overlap-save, tune constants
- Replace FirFilter (ring-buffer FIR) with FftRrcFilter using overlap-save
  FFT convolution; I and Q are processed together as a single complex FFT,
  halving filter cost (~10x fewer operations than direct convolution)
- Reduce PHASE_CANDIDATES 16 -> 8 (reasonable, double the original)
- Lower MIN_PUBLISH_QUALITY 0.55 -> 0.38 (more permissive acquisition)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-26 23:53:53 +01:00
sjg 032ff2a735 [fix](trx-rds): tune acquisition speed vs false-positive tradeoff
- Increase phase candidates 4 -> 16 for faster clock-phase lock
- Lower MIN_PUBLISH_QUALITY 0.65 -> 0.55 for earlier decode on weaker signals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-26 23:36:45 +01:00
sjg 1994534b4d [fix](trx-rds): reduce false positives and cpu usage
- Downgrade OSD from distance-2 to distance-1 (removes 325-iteration
  double-bit flip loop per block, main source of both false positives
  and excess CPU)
- Reduce phase candidates from 8 to 4 (halves per-sample work)
- Raise MIN_PUBLISH_QUALITY from 0.45 to 0.65 (requires stronger
  signal confidence before emitting decoded state)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-26 23:31:45 +01:00
sjg ba2fbed7c3 [feat](trx-rds): improve RDS robustness with 9 DSP techniques
Tech 1: replace one-pole baseband LPF with FIR RRC matched filter
(alpha=0.75, 4-chip span) — largest single measured improvement per
empirical comparison (gr-rds RRC vs plain FIR: 32/38 vs 18/38 stations).
Tech 2: 19 kHz pilot x3 -> 57 kHz coherent carrier reference via the
triple-angle formula; fed from the WFM pilot Costas PLL when
pilot_lock_level > 0.5, clearing to NCO fallback otherwise.
Tech 3/7/8: OSD(2) soft-decision block decoder replaces hard CRC check.
Per-bit soft magnitudes accumulated in Candidate::block_soft[26].
decode_block_soft() searches Hamming distance 0/1/2 (352 trials total)
and returns the minimum Euclidean-cost valid codeword; ~2-3 dB gain.
Tech 4: 8th-order 57 kHz BPF (4 cascaded biquads at Q=5) in wfm.rs
replaces the previous single Q=10 biquad; ~6x steeper ACI stopband.
Tech 5: Costas loop with tanh soft phase detector drives the RDS carrier
NCO when no pilot reference is available (P+I, B_L ~20 Hz).
Tech 6: Block A PI field LLR accumulation — signed per-bit LLR summed
over 3 independent Block A observations before committing the PI value,
correcting weak-signal false locks without delaying strong-signal lock.
Tech 9: 8-tap complex CMA blind equalizer applied to IQ samples before
FM discrimination; constant-modulus error (|y|^2 - R^2) drives tap
adaptation without a training sequence, suppressing adjacent-channel
interference at the source.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-26 23:10:42 +01:00
Claude 8e6623b39e [fix](trx-rs): use per-message rig_id for map marker tagging
The map module was tagging all decode markers (APRS, AIS, VDES,
FT8/FT4/FT2/WSPR locators) with the global rig picker's active rig
instead of the actual source rig. This made the map's own rig filter
dropdown ineffective in multi-rig setups.

- Add rig_id field to all decode message structs (AisMessage,
  VdesMessage, AprsPacket, CwEvent, Ft8Message, WsprMessage)
- Set rig_id on messages in audio_client before broadcasting, using
  the actual rig connection identifier
- Update history collector to prefer message rig_id over the global
  active rig fallback
- Pass rig_id through plugin normalize functions (AIS, APRS, VDES,
  HF-APRS) so it reaches the map add functions
- Update all map marker functions (aprsMapAddStation, aisMapAddVessel,
  vdesMapAddPoint, mapAddLocator) to use the message's rig_id with
  fallback to the global picker for backward compatibility

https://claude.ai/code/session_015gC7axHk2jmp7HbFPdbivN
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 16:29:53 +01:00
Claude 449b877694 [refactor](trx-ftx): use HashSet for candidate deduplication
Replace Vec::contains() with HashSet::insert() for O(1) dedup lookups
instead of O(n), significantly reducing comparisons during decode.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
sjg 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
sjg 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
sjg 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
sjg 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
sjg 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
sjg 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
sjg 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
sjg 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
sjg 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
sjg 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
sjg 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
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
sjg 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
sjg 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
sjg 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
sjg 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
sjg 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
sjg 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