Commit Graph

366 Commits

Author SHA1 Message Date
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
Claude 0905360ba5 [fix](trx-backend-soapysdr): remove noise/pilot dependency from WFM signal strength
The noise floor subtraction was over-aggressive: the bandwidth ratio
scaling between the 67 kHz baseband probe and the IQ domain amplified
the noise estimate excessively, causing weak stations to be subtracted
to nothing.  The pilot-referenced correction only worked for stereo
stations.

Strip the signal strength path back to what actually works universally:
mean IQ envelope power with asymmetric attack/decay smoothing.  This
always produces a reading for any FM signal — mono, stereo, with or
without RDS.

The baseband noise probe, CNR estimation, and pilot metrics remain in
the WfmStereoDecoder for their existing uses (RDS quality weighting,
CCI/ACI estimation) but no longer feed into the S-meter.

https://claude.ai/code/session_017URSDqSJ8TyZpDhV2vKZUe
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 20:50:06 +01:00
Claude 41fa9dc242 [feat](trx-backend-soapysdr): proper WFM signal strength algorithm
Replace the simple IQ power averaging with a proper WFM signal
strength measurement algorithm based on established RF engineering
practice:

1. Asymmetric attack/decay smoothing (τ_attack=2ms, τ_decay=300ms)
   per IARU Region 1 Technical Recommendation R.1 for professional
   S-meter behaviour.  Fast attack catches signal increases
   immediately; slow decay provides stable, readable meter movement.

2. Baseband noise floor estimation via a 67 kHz probe in the
   demodulated FM baseband.  FM demodulation noise follows an f²
   spectral shape, so energy above the useful baseband (audio +
   RDS ≤ 57 kHz) is dominated by channel noise and independent of
   program content.  Subtracting this noise estimate in the linear
   domain reveals the carrier-only power, preventing the meter from
   reading the noise floor on empty/weak channels.

3. Pilot-referenced quality correction.  The 19 kHz stereo pilot
   has a known fixed amplitude at the transmitter (±7.5 kHz
   deviation, 10% of ±75 kHz).  Near the FM threshold (~10 dB CNR)
   where noise dominates the IQ reading, the pilot tone power
   provides an independent quality-weighted correction.  The blend
   factor scales from 0.3 at low CNR down to 0 at high CNR where
   the raw IQ measurement is already accurate.

4. CNR estimation from the ratio of total baseband power to the
   above-band noise probe, enabling adaptive pilot correction and
   providing a signal quality metric for future use.

https://claude.ai/code/session_017URSDqSJ8TyZpDhV2vKZUe
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 20:33:09 +01:00
sjg 0efdb5e360 [fix](trx-rs): show signal strength with decimal precision
Change RigRxStatus.sig from i32 to f64 and add get_signal_strength_db
to RigCat trait so SDR backends can bypass the coarse 0..15 quantisation.
Compensate for decimation processing gain so the meter matches the
spectrum peak. Display with one decimal place in all units.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-27 20:06:36 +01:00
sjg d468a96448 [fix](trx-backend-soapysdr): stabilize WFM signal strength and speed up SDR polling
Smooth envelope power (I²+Q²) instead of filtering I/Q components
separately — eliminates ~6 dB modulation-dependent fluctuation caused
by FM carrier rotation in the IQ plane. Reset signal strength on
frequency change. Reduce SDR poll interval from 500ms to 100ms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-27 18:52:12 +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 3ca3836702 [fix](trx-backend-soapysdr): measure WFM signal strength in IQ domain, not power domain
The previous carrier power IIR filtered |IQ|² (power), which only smoothed
temporal fluctuations but still integrated noise across the full 180 kHz WFM
channel bandwidth. This caused background noise to read ~-78 dBFS instead of
the expected ~-110 dBFS (~32 dB too high ≈ 10·log₁₀(180kHz/500Hz)).

Move the single-pole IIR lowpass to the IQ domain (filter I and Q separately
at ~500 Hz cutoff), then compute power from the filtered output. This rejects
out-of-band noise before the power measurement, so the meter reads true
carrier level rather than total wideband noise.

https://claude.ai/code/session_01W4WPMB2Lg3hgaY6opsk25f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 18:28:35 +01:00
Claude 0ad3440a2d [fix](trx-backend-soapysdr): use narrow carrier IIR for WFM signal strength
Replace peak |IQ|² measurement with a per-sample single-pole IIR lowpass
on the instantaneous power (~500 Hz cutoff).  FM has constant envelope so
the IIR converges to the true carrier power A², rejecting wideband noise
that previously inflated the peak reading and masked actual signal level.

Other modes keep the existing peak + EMA approach.

https://claude.ai/code/session_01X6tedMVpjX3DEqLFDBR7FK
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 17:35:08 +01:00
Claude a387047464 [fix](trx-backend-soapysdr): widen RDS bandpass Q from 5.0 to 3.5 to reduce in-band distortion
The 8th-order (4×biquad) RDS bandpass at Q=5 per stage produced a
composite −3 dB bandwidth of ±2480 Hz, but the steep 8th-order roll-off
tapered the RDS signal edges (±1544 Hz at α=0.30) by −1.2 dB.  This
distorted the RRC matched filter's expected flat spectrum, causing ISI
and degrading soft-decision confidence — directly hurting PS/RT decode
on weak signals.

Q=3.5 widens the composite passband to ±3560 Hz, reducing band-edge
attenuation to −0.59 dB while still providing ≈−4 dB rejection at the
stereo difference signal edge (53 kHz) and steep 8th-order far-out
roll-off.

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
sjg 56c19ae15d [fix](trx-backend-soapysdr): measure signal strength from peak filtered IQ before AGC
Replace the DC-component approach (which underreads FM due to carrier deviation) with peak |s|² on the filtered+decimated IQ before AGC is applied. Works correctly for both constant-envelope FM and narrowband modes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-27 15:57:38 +01:00
sjg 6440f67d94 [fix](trx-rs): add EMA smoothing to signal strength and fix freq input clobbering
EMA (α=0.4) smooths the carrier power estimate across DSP blocks. Custom PartialEq on VchanRdsEntry excludes signal_db so rapidly-changing levels do not trigger main state SSE updates that overwrite the frequency input.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-27 15:24:18 +01:00
sjg 5220e606c1 [fix](trx-backend-soapysdr): measure carrier power from mixed-signal DC for signal strength
Use the DC component of the baseband-mixed IQ (before LPF/decimation) as a narrow-band carrier power estimate. This correlates with the spectrum FFT peak instead of measuring wideband channel power which inflates the reading for WFM.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-27 15:00:17 +01:00
sjg 7ffd1ccd6a [fix](trx-backend-soapysdr): use peak IQ magnitude for signal strength display
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-27 14:48:03 +01:00
sjg 4fc32f0e90 [fix](trx-rs): wire DSP signal strength to Signal strength field and per-vchan SSE
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-27 14:24:44 +01:00
Claude e44e616ab8 [fix](trx-backend-soapysdr): fix ACI/CCI always reading 0% in WFM
ACI: the hard limiter in channel.rs normalised IQ samples to unit
magnitude *before* the CMA equalizer, making the signal perfectly
constant-modulus so the CMA never adapted and tap deviation stayed
at zero.  Fix by moving the hard limiter inside process_iq (after
the CMA) and replacing the CMA-based metric with IQ envelope
coefficient of variation, computed on the raw samples.

CCI: the pilot coherence has a theoretical maximum of π/4 ≈ 0.785
(not 1.0), so coherence_penalty was always ~0.215 even for a clean
signal.  The Q/I ratio also depended on the arbitrary NCO-pilot
phase offset rather than actual interference.  Fix by normalising
coherence by its theoretical max and dropping the phase-dependent
Q/I ratio.  Gate CCI on pilot detection so mono signals read 0%.

https://claude.ai/code/session_01PUXWNMRGfrWYH56k2DLmen
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 11:38:03 +01:00
Claude 4da4d8ec66 [feat](trx-rs): add CCI/ACI bars to WFM panel with RDS mitigation
Estimate Co-Channel Interference (CCI) from pilot tone quadrature
leakage and coherence degradation.  Estimate Adjacent Channel
Interference (ACI) from CMA equalizer tap deviation from identity.
Both metrics (0-100 scale) are surfaced through RigFilterState and
displayed as colour-coded bars in the WFM control panel.

The RDS decoder quality parameter is now adaptively penalised when
CCI/ACI levels are elevated, reducing block-error rate under
interference conditions.

https://claude.ai/code/session_016EKzep42RCvE4GxvvRaCwu
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 09:44:28 +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 8b310c184b [fix](trx-backend-soapysdr): lower pilot lock threshold for weak-signal RDS
Lower PILOT_LOCK_THRESHOLD 0.5 -> 0.25 so the accurate 57 kHz pilot-derived
carrier reference is handed to the RDS decoder even with a weaker pilot tone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-27 00:01:10 +01:00
sjg 32e090f927 [fix](trx-backend-soapysdr): preserve RDS subcarrier with narrow WFM bandwidth
The IQ prefilter cutoff was audio_bandwidth_hz/2, so any setting below
~120 kHz would cut off the 57 kHz RDS subcarrier before FM demod.

- Clamp IQ prefilter cutoff to >= 60 kHz for WFM in both new() and
  rebuild_filters() — audio quality is unaffected since WfmStereoDecoder
  applies its own 18 kHz lowpass internally
- Ensure pipeline target rate >= 120 kHz for WFM so the decimated IQ
  sample rate can represent the 60 kHz cutoff

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-26 23:31:54 +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
sjg 27489c3745 [feat](trx-rs): rename AMC (AM C-QUAM) to SAM (Stereo AM) with stereo width and carrier sync controls
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-26 21:50:53 +01:00
sjg 999d2c0436 [fix](trx-server): prevent capacity overflow panic in audio history replay
Use saturating CAS loop in adjust_total_count to prevent AtomicUsize underflow, and cap history estimate at 500k entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-26 21:20:15 +01:00
sjg c8de54d85e [feat](trx-rs): show audio bitrate and active stream count on About page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-26 20:18:19 +01:00
sjg 10b1512d75 [style](trx-backend): fix trailing blank line in dummy.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-26 18:57:34 +01:00
sjg 7b07feb725 [fix](trx-backend): remove stale filter_state test from DummyRig
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-26 18:36:02 +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 a63f27971d fix(trx-server): remove unused RigSdr import in rig_task.rs
Also run cargo fmt to fix formatting issues across trx-server,
trx-frontend-http, and trx-configurator.

https://claude.ai/code/session_01RsHUyVz2wjQjsEsxJo5owt
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 14:01:44 +01:00
Claude c8f33b8939 [refactor](trx-rs): remove shared-library plugin system
Drop the plugin loading infrastructure (libloading-based dynamic .so/.dylib/.dll
loading) from both trx-server and trx-client. The feature was unused and posed an
unnecessary security risk by executing arbitrary native code from disk.

Removed:
- src/trx-app/src/plugins.rs (plugin discovery, validation, FFI registration)
- examples/trx-plugin-example/ (cdylib example plugin)
- libloading dependency from trx-app
- load_backend_plugins / load_frontend_plugins calls from server and client
- Plugin documentation from README.md and CLAUDE.md

https://claude.ai/code/session_01DTEUpz3XPUeWmz74NeaFgb
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 12:44:56 +01:00
Claude 3e0169b91f [refactor](trx-backend-soapysdr): implement RigSdr trait for SoapySdrRig
Move 13 SDR-specific methods from impl RigCat into a new impl RigSdr
block. Add as_sdr()/as_sdr_ref() overrides returning Some(self) so the
SDR extension is accessible via the RigCat trait object.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 08:47:17 +01:00
Claude 748d26f47d [refactor](trx-server): route SDR commands through RigCat::as_sdr()
Update all SDR command handlers in rig_task to access SDR methods via
ctx.rig.as_sdr() instead of calling them directly on RigCat. Query-only
SDR operations (filter_state, get_spectrum, get_vchan_rds) use
as_sdr_ref(). Non-SDR rigs now get proper not_supported errors.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 08:47:17 +01:00
Claude aa3ed81786 [fix](trx-server): replace all history mutex .expect() with poison recovery
Replace 25 .expect("X history mutex poisoned") calls in DecoderHistories
with .unwrap_or_else(|e| e.into_inner()) to gracefully recover from
poisoned locks instead of crashing the server.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 08:47:17 +01:00
Claude 337cb72974 [refactor](trx-server): use state data constructors for pub(crate) fields
Migrate ready_data_from_state and transmitting_data_from_state to use
the new ReadyStateData::new() and TransmittingStateData::new()
constructors instead of direct struct field initialization.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
Claude c5320ca2fb [refactor](trx-server): add AtomicUsize counter and recover from poisoned locks
Add an AtomicUsize total_count field to DecoderHistories, maintained by
record/prune/clear methods, so estimated_total_count() avoids 9 separate
mutex acquisitions. Also replace audio ring buffer .unwrap() calls with
.unwrap_or_else(|e| e.into_inner()) to recover from poisoned locks.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
Claude 22ad90b2ba [fix](trx-server): release mutex before serialization in flush_all
Clone history data out under the lock, then drop the guard before
calling save_key, so serialization never blocks concurrent readers.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
Claude c299e9a2d2 [fix](trx-server): truncate raw JSON in error logs to 128 chars
Prevent potential information disclosure by truncating raw client input
in log messages instead of logging the full payload.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00
sjg 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
sjg 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
sjg 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
sjg 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
sjg 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 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 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 7cf829ef52 [feat](trx-rs): display APRS-IS connection status on About page
Thread aprs_is_status through RigState, RigSnapshot, and the protocol
layer following the same pattern as pskreporter_status. Show the
connection target and callsign when enabled, or "Disabled" otherwise.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
2026-03-17 23:20:43 +01:00
sjg 9019acee0e [feat](trx-backend-soapysdr): enable hardware AGC by default if available
Query the device for AGC support via has_gain_mode and enable it
automatically at startup. Devices without hardware AGC fall back to
manual gain as before.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
2026-03-17 22:57:20 +01:00
sjg b533d704a1 [style](trx-rs): reformat codebase
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
2026-03-17 22:36:11 +01:00
sjg a823e66816 [fix](trx-server): drop in-flight decodes on main retune
Invalidate main-decoder windows whenever the rig's main frequency
changes, including direct SetFreq, scheduler-driven retunes, and
external CAT retunes observed during polling.

The decoder loops now resubscribe their PCM receivers on reset and drop
results that finish after the reset sequence advances, preventing false
decodes from stale pre-retune audio.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
2026-03-17 22:28:31 +01:00
sjg bff3be38be [feat](trx-backend-soapysdr): implement ARM NEON optimizations
Add NEON (aarch64) vectorized paths mirroring the existing AVX2 paths:

- demod/math_arm.rs: replace the no-op placeholder with a full NEON FM
  discriminator that processes 4 samples per iteration using a 7th-order
  minimax atan polynomial and branchless atan2 with argument reduction,
  matching the accuracy of the AVX2 path (max error ~2.4e-7 rad).
  32-bit ARM retains the scalar fallback.

- dsp/filter.rs: add mul_freq_domain_neon() that deinterleaves 4 complex
  pairs via vuzpq/vzipq, performs complex multiply with vmulq/vaddq/vsubq,
  then reinterleaves. On aarch64 this path is always taken (NEON is
  mandatory); scalar fallback remains for other targets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
2026-03-17 01:22:56 +01:00
sjg 7a144375f2 [fix](trx-backend-soapysdr): enforce 9 kHz minimum bandwidth for AMC mode
clamp_bandwidth_for_mode now floors AMC bandwidth at 9 kHz so that both
the sum (L+R) and difference (L−R) sidebands are always captured,
regardless of what the user or a set_filter call requests.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
2026-03-17 00:19:35 +01:00
sjg e337870d53 [fix](trx-backend-soapysdr): add AMC to supported_modes capabilities list
RigMode::AMC was implemented but omitted from the supported_modes vec,
so the HTTP frontend never received it in capabilities and the mode
selector did not show AMC-QUAM.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
2026-03-17 00:15:47 +01:00
sjg 016d4ddfd7 [fix](trx-rs): resolve all clippy warnings
- remote_client tests: add missing server_connected field and import
  AtomicBool in the test module
- pskreporter: replace map_or(true, …) with is_none_or and
  repeat(x).take(n) with repeat_n(x, n)
- dsp.rs, scheduler.rs: suppress intentional too_many_arguments with
  #[allow(clippy::too_many_arguments)]

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
2026-03-17 00:15:03 +01:00