BackgroundDecodeManager.send_audio_cmd used the global active_rig_id()
to route virtual channel commands. During a rig switch, Remove commands
for the old rig's channels were sent to the new rig's audio pipeline,
leaving orphaned virtual channels on the previous rig's server.
Replace send_audio_cmd with send_audio_cmd_to_rig that takes an explicit
rig_id, derived from the channel's own rig_id field. Both Remove and
SubscribeBackground commands now reach the correct rig.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replace per-sample circular-buffer processing with block-based linear
buffers in the FM discriminator and polyphase resampler. This eliminates
modular indexing in FIR inner loops, enabling compiler auto-vectorisation.
Also fix O(n²) drain pattern in the line slicer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Move default output directories from $XDG_DATA_HOME to $XDG_CACHE_HOME
so all runtime data lives under ~/.cache/trx-rs/ consistently.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Restructure the WEFAX tab to match the SAT/LRPT pattern with a
view switcher bar. Live view shows decoder description, live canvas,
and latest image card. History view adds a filterable, sortable table
of all decoded images with Clear All button.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
DIG mode provides the same SSB audio as USB, so WEFAX reception works
there. Added DIG to both the decoder registry active_modes and the
server-side mode gate.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The WEFAX button had id="subtab-wefax" which duplicated the panel's id,
causing querySelector to match the button instead of the panel on click.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
After fetching /decoders, hide sub-tab buttons, panels, overview
descriptions, about-status rows, and settings clear-history buttons
for decoders the server doesn't advertise. This makes feature-gated
decoders like FT2 fully invisible in the UI when disabled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The inline row editor rendered extra channels as read-only text and
never saved changes to bookmark_ids. Add a dropdown + chip UI matching
the form modal pattern so users can add/remove extra channels inline.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
FT2 decoder implementation, protocol constants, server decoder tasks,
background decode, and registry entry are now conditional on the ft2
feature. Lightweight types (enum variants, commands, state fields) remain
unconditional to avoid cascading cfg noise in macros and serde.
Enable with: cargo build -p trx-server --features ft2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
sat.js was only in the 'map' plugin group but the SAT subtab lives
under digital-modes. History/Predictions buttons had no click handlers
until the map tab was visited. The loaded Set prevents double-loading.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Wrap unit labels (dBm, dBf, dBFS, S, dB) in a .sig-unit span styled
with the system monospace stack, keeping numeric values in DSEG14.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The Download button is an <a> tag which inherits default link styles
(underline, mismatched font/sizing). Added text-decoration, display,
font-family, and line-height to normalize both <a> and <button> elements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The @font-face unicode-range only included digits and punctuation, so
letter characters in RDS station names fell back to generic monospace.
Expanded to U+0020-007E (full printable ASCII) matching all glyphs in
the font.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Function was defined in map-core.js but not exported, causing a TypeError
when app.js called window.trx.map.reverseGeocodeLocation().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Same issue as Leaflet — content blockers block the jsdelivr CDN request,
causing the seven-segment font to fail loading and fall back to monospace.
Also replace preload-to-stylesheet swap with media="print" onload swap
for themes.css and leaflet.css to eliminate Safari preload warnings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The .rds-ps class was missing font-family after JS refactoring, causing it
to inherit the generic monospace stack from .rds-value instead of using the
seven-segment DSEG14 Classic font.
Fixes#141
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add markDecodeMapSyncPending, decodeHistoryMapRenderingDeferred,
decodeHistoryReplayActive, decodeMapSyncPending, updateDocumentTitle,
activeChannelRds, _activeTab, and locationSubtitle to window.trx so
map-core.js can access them via the T alias.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
navigateToTab now calls loadPluginsForTab to ensure map-core.js is
injected on initial page load from /map URL. map-core.js auto-inits the
map if the map tab is already visible when the module loads.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Bundle Leaflet JS, CSS, and marker/layer images as embedded assets served
under /vendor/ instead of loading from unpkg.com, which content blockers
(e.g. Safari) prevent.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add download/remove buttons per file, filename filter, sort dropdown, and paginated file list. Restore header REC toggle button. Add GET /api/recorder/download/{filename} and DELETE /api/recorder/files/{filename} endpoints with path traversal protection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Record Opus audio streams to OGG files on the client. Includes manual start/stop via HTTP API, scheduler-driven auto-recording per schedule entry, and a header REC button in the web UI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Show S0–S9 as whole units and S9+xdB in 10dB steps instead of fractional S-unit values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Signal strength now refreshes every 100ms for SDR backends using the cached DSP value, keeping the S-meter responsive at half the spectrum redraw rate.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Fetch /decoders on page load and use the registry to drive all
decoder-related UI instead of hardcoded lists:
- bookmarks.js: bmReadDecoders/bmWriteDecoders and bookmark form
checkboxes generated from registry; bmApply() decoder toggle gate
uses registry active_modes instead of hardcoded DIG/FM check
- background-decode.js: delete SUPPORTED_DECODERS constant, derive
bookmarkDecoderKinds() from registry
- app.js: _decoderToggles and SSE status sync built from registry;
updateDecodeStatus() and setModeBoundDecodeStatus() driven by
registry mode_bound/toggle entries
- index.html: replace 8 hardcoded decoder checkboxes with dynamic
container populated from registry
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add DECODER_REGISTRY in trx-protocol::decoders as the single source of
truth for all decoder metadata (activation mode, supported rig modes,
background-decode capability). Replace duplicated resolver functions in
background_decode.rs and sse.rs with shared resolve_bookmark_decoders().
Add GET /decoders endpoint to expose the registry to the frontend.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Previously R would retune even when the frequency was already aligned
to the jog step boundary. Now it shows "Already on step" and sends no
command. Also remove the stale "retune" label from the shortcut help.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Remove the wxsat/NOAA APT checkbox from bookmark decoder form and all
JS references — the APT decoder no longer exists.
Fix LRPT decoder not activating when an FM-mode bookmark is applied:
bmApply() gated decoder toggles on DIG mode only, so LRPT bookmarks
(which use FM) never triggered SetLrptDecodeEnabled. Gate on DIG or FM.
Wire satellite pass scheduling into the scheduler loop: check configured
satellite entries against live pass predictions, activate the satellite's
bookmark (enabling LRPT decoder) when a pass is active, and expose
active_satellite in SchedulerStatus for the frontend.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Combine round (R) and retune (T) into a single R hotkey that rounds to
the nearest jog step boundary, or retunes if already rounded. Update F
hotkey description to "Pick frequency" in the F1 help overlay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Visual 24h timeline bar, inline entry editor, interleave progress ring, filterable checkbox list for bookmarks, status cards moved to top, SVG dot state badges.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Audit codebase against previous improvement list — all P0/P1/P2 items from
the prior review are now resolved or dropped. Restructured document with
resolved items in a collapsed section and identified new areas: decoder task
duplication, missing tests, decode log error handling, api.rs size, and others.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Fixes dead_code warning on HttpAuthConfig::session_ttl().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Show the interactive setup wizard as the primary way to generate
trx-server.toml and trx-client.toml, with --print-config as an alternative.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
JSON-TCP frontend is for debugging only, not worth showing in the overview.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replace single-server Mermaid diagram with two trx-servers: one with two
SDRs, the other with an SDR and FT-817, both feeding a single trx-client.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
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>
Streamline README with centered header, feature summary table, collapsible
install commands, compact data-flow diagram, and documentation table linking
to wiki pages instead of duplicating content.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add per-library descriptions, platform audio table, and concrete install
commands for Debian/Ubuntu, Fedora, Arch Linux, and macOS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
compute_upcoming_passes requires the TLE store to be populated by
CelesTrak fetches. If a client requests passes before the async NOAA
group fetch completes, NOAA-15/18/19 are missing from predictions.
Seed the store with hardcoded fallback TLEs synchronously in
spawn_tle_refresh_task before spawning the async fetch. CelesTrak
data overwrites these entries once fetched. Also adds pass sanity
tests for NOAA-15 and NOAA-18.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
CelesTrak GROUP=weather does not include legacy NOAA POES satellites.
Added GROUP=noaa fetch so NOAA-15/18/19 appear in predictions. Moved
GetSatPasses to a dedicated TCP connection (client) and spawn_blocking
(server) so pass computation never blocks state polling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
GetSatPasses responses with 100+ satellites easily exceed the previous
16KB limit, causing the remote client to disconnect.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
TLE refresh now happens only on trx-server (once at startup, then every
24h). Client fetches satellite predictions from server via new
GetSatPasses fast-path command and caches them locally, refreshing
every 5 minutes. Removes spawn_tle_refresh_task from trx-client.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add category selector (All/Weather/Ham Radio/Other) to predictions panel.
Split predictions into currently receivable passes with live countdown
timer and upcoming passes table. Add SatCategory enum to geo types
for CelesTrak group classification.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The TLE store is process-local; only the server was fetching TLEs from CelesTrak, leaving the client store empty and predictions always unavailable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Hardcoded fallback TLEs had approximate orbital elements (round numbers for RAAN, arg of perigee, mean anomaly) producing pass times hours off. Return empty predictions with a clear error when CelesTrak data is not yet available. Add TLE source and satellite count to the API response.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Iterate all TLE store entries (weather + amateur) for pass predictions instead of a hardcoded list. Add name/elevation filter bar to the predictions UI. Fix pre-existing missing fields in remote_client test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
- Add NOAA-15/18/19 and Meteor-M N2-3/N2-4 to predictions list
- Rename PREDICTION_SATS (was HAM_SATS) to include weather + ham sats
- Rename all wxsat identifiers to sat throughout JS/HTML/CSS/Rust:
wxsat.js → sat.js, WXSAT_JS → SAT_JS, /wxsat.js route → /sat.js,
all #wxsat-* element IDs, .wxsat-* CSS classes, window.addWxsat* →
window.addSat*, window.onServerWxsatImage → window.onServerSatImage,
etc. (backend protocol strings unchanged)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
- Rename "Weather Satellites" sub-tab to "SAT"
- Add "Predictions" view: next 24 h flyby table for 13 ham sats
(ISS, AO-91, AO-92, SO-50, AO-73, JO-97, PO-101, LilacSat-2,
CAS-4B, EO-88, RS-44, SALSAT, GREENCUBE)
- trx-core/geo: add PassPrediction, HAM_SATS, compute_upcoming_passes(),
find_passes_for_sat(), compute_az_el() helpers; spawn_tle_refresh_task
now also fetches CelesTrak amateur group on startup and every 24 h
- trx-frontend-http: add GET /sat_passes endpoint
- app.js: locator tooltips now accumulate all receivers per station
via remotes Set; _detailPassesRigFilter checks the Set
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
- Add noaa_decode_enabled to the fallback RigSnapshot initializer in api.rs
- Add NoaaImage arm (no-op) to the DecodedMessage match in audio.rs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
- Remove unused chrono::Local import (use fully-qualified path)
- Drop watch::Ref before .await in state-change branch to satisfy Send
- Remove unused pass_start_ms parameter from finalize_noaa_pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
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>
- 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>
- 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>
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>
- 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>
Track per-rig server connection state in `rig_server_connected` so that when
one trx-server drops, only the rig(s) it serves are marked disconnected. Other
rigs with active connections remain fully interactive. The SSE `server_connected`
field is now resolved from the per-rig map for the session's active rig, falling
back to the global flag for backward compatibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
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>
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>