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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Serve grouped decode history payloads and restore each decoder through
explicit history restore hooks instead of replaying a mixed message stream.
This reduces replay overhead further by removing type regrouping and keeping
history restoration on decoder-specific bulk paths.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Serve a dedicated decode-history worker and move compressed history fetch
and CBOR parsing into that worker.
The main thread now drains ready-made decode batches within a frame budget,
which further reduces UI disruption during large history restores.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Override the global button chrome on longest-QSO cards so they keep
the intended card layout instead of inheriting fixed control sizing.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replay decode history in decoder-specific batches instead of feeding every
message through the single-message path.
This reduces per-message array churn and UI scheduling during large history
loads while keeping the existing live decode behavior unchanged.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Serve decode history as gzipped CBOR and decode it in the frontend.
Defer map materialization until replay completes to avoid replay-time stutter,
and include the pending longest-QSO style adjustment.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Let users click longest-QSO cards to isolate a single contact path on the map and click again to restore all visible contact paths. Also remove the extra inner panel styling from decode map tooltips so the popup renders as a single container.
Verification: node --check src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js
Verification: git diff --check -- src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Keep map history data cached when the history window is reduced so older APRS, AIS, VDES, FT8, and WSPR items can be shown again when the user expands the window, and add a global decode-history replay overlay with progress updates across the UI. Also update the longest QSO summary to render bidirectional contacts with <-> labels.
Verification: node --check src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js
Verification: git diff --check -- src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add a map summary section below the map that lists the five longest directed FT8 and WSPR contacts in the current view, including distance, band, age, and locator details.
Verification: node --check src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js
Verification: git diff --check -- src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add a map filter-panel history picker with 15 minute through 24 hour retention options and prune dynamic APRS, AIS, VDES, FT8, and WSPR overlays to the selected age window.
Verification: node --check src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Write compressed audio-history replay directly into the local frontend history buffers so large APRS and AIS replays survive trx-client restart instead of overrunning the live decode broadcast channel.
Verification: cargo test -p trx-client --no-run
Verification: cargo test -p trx-frontend-http --no-run
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Automatically return control to the scheduler after using the Previous or Next entry controls so manual stepping does not leave the session latched in takeover mode.
Verification: node --check src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Apply scheduler-backed virtual channels as real manual selections so they take control, retune the rig, and restore bookmark decoder state including APRS/PKT. Also remove the inner border from the map decode locator tooltip.\n\nVerification: cargo test -p trx-frontend-http vchan\nVerification: node --check src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vchan.js\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Keep scheduler-managed virtual channels reconciled while\nclients remain connected, instead of only materializing\nthem during the initial connect path.\n\nVerification: cargo test -p trx-frontend-http vchan\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Materialize scheduler-managed virtual channels before the\ninitial channels SSE event when the scheduler currently\ncontrols the rig.\n\nVerification: cargo test -p trx-frontend-http vchan\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Use the currently tuned virtual channel for the website title\ninstead of always showing channel 0 metadata.\n\nVerification: node --check assets/web/app.js\nVerification: node --check assets/web/plugins/vchan.js\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Track the last applied scheduler entry so previous/next\ncycles correctly across active entries and resets the\ncountdown after manual entry changes.\n\nVerification: cargo test -p trx-frontend-http scheduler\nVerification: node --check assets/web/plugins/scheduler.js\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add previous/next scheduler entry controls for overlapping\ntime-span slots and fix interleave timing calculations so\nthe active slot and countdown align with the overlap window.\n\nVerification: cargo test -p trx-frontend-http scheduler\nVerification: node --check assets/web/plugins/scheduler.js\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Reduce main-thread stalls while decode history replays.\n\nCoalesce decoder list redraws and map maintenance so spectrum\nand controls stay responsive during history import.\n\nVerification: node --check on modified frontend JS files.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Treat the SPA entry routes as public so direct requests to /map,\n/decoders, /settings, and /about return the app shell and let\nthe frontend show the login screen instead of a 403.\n\nMove the map filter overlay to the bottom-right corner and color\ndecode contact paths by their decoded band so they match the band\nlegend and locator overlays.\n\nVerified with cargo test -p trx-frontend-http.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Append the APRS-IS position beacon comment as\n`trx-rs v<version> by SP2SJG` so IGate beacons identify\nthe running software version.\n\nUpdate the APRS beacon formatter tests to assert the exact\npayload including the generated version string.\n\nCo-Authored-By: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Skip decoded candidates where ftx_message_decode() returns a non-OK
status instead of forwarding a synthetic error string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Radio paths and decode contact paths now use the same color as the
marker they belong to, respecting the active filter mode:
- Band mode: color follows the band (golden-angle HSL hue)
- Mode/source mode: color follows the source type (FT8/WSPR/bookmark)
APRS, AIS, and VDES paths use their fixed source colors unchanged.
Decode contact paths sync color when the filter mode is switched.
CSS stroke/stroke-opacity removed from path classes so Leaflet's
color option takes effect; dasharray and flow animation are retained.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The score from ft8_lib is an averaged uint8 difference between Costas
sync tones and their neighbours (each unit = 0.5 dB). The previous
score * 0.5 gave the signal-above-adjacent-noise in dB relative to a
single 3.125 Hz waterfall bin, yielding values of +5 to +50 dB —
all wrong.
Subtract 10*log10(2500/3.125) ≈ 29 dB to normalise to the 2500 Hz
reference bandwidth used by WSJT-X and expected by PSKReporter:
snr = score * 0.5 - 29.0
This maps score 10 (minimum decodable) → -24 dB and score 60 → +1 dB,
matching typical WSJT-X SNR report ranges.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replace the two-character beacon_symbol string with separate
beacon_symbol_table (char) and beacon_symbol_code (char) fields to
avoid TOML backslash escaping issues with the alternate symbol table.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add periodic IGate position beacon support to the APRS-IS uplink.
New AprsFiConfig fields: beacon (bool), beacon_interval_secs (default
1200), beacon_symbol (default "/-"), latitude/longitude overrides.
A beacon is sent immediately on connect then every beacon_interval_secs.
Coordinates fall back from [aprsfi] to [general].latitude/longitude.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Allow specifying the IGate callsign directly in [aprsfi] instead of
relying on [general].callsign. The aprsfi-specific callsign takes
precedence; [general].callsign is used as fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Move aprsfi and pskreporter modules from trx-server into a new
standalone trx-reporting library crate. Config types (AprsFiConfig,
PskReporterConfig) move to trx-reporting and are re-exported from
trx-server::config for backwards compatibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
- Append mandatory q-construct (,qAR,<callsign>) to all forwarded
TNC2 packets via updated format_tnc2(pkt, igate_call)
- Add TCPIP/TCPXX loop-prevention check before forwarding
- Drain server-sent data in select! loop to prevent TCP backpressure
- Enable TCP_NODELAY for low-latency packet forwarding
- Guard against history replays: skip packets older than 2 minutes
- Use "trx-rs" in login string and keepalive comment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add [workspace.package] version = "0.1.0" to the root Cargo.toml and
switch all 21 member crates to version.workspace = true so the entire
workspace is versioned from a single place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Two bugs introduced by 60697bb:
1. dsp.rs passed `channel_idx == 0` as force_mono_pcm, which forced the
primary pipeline channel to output mono samples. The Opus encoder was
configured for stereo, so it received half the expected frame data,
causing distortion for all connected audio clients.
Fixed by passing `false` — hidden virtual channels already set
force_mono_pcm=true via set_force_mono_pcm() in vchan_impl.rs.
2. main.rs short-circuited channel conversion when no audio clients were
connected, sending raw frames to pcm_tx (decoders). When clients then
connected, decoders switched to receiving stereo-interleaved frames,
making decoder input format dependent on client presence.
Fixed by always performing the channel conversion before sending to
pcm_tx; the no-client skip now only bypasses Opus encode + rx_audio_tx.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Skip APRS packets whose ts_ms is older than 120 seconds. Live RF-decoded
packets arrive within milliseconds; history replay items can be up to 24
hours old and must not be re-uploaded to APRS-IS as live traffic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Rewrite the PSKReporter uplink to match the protocol spec exactly:
- Fix template FlowSetIDs: receiver uses 0x0003 (Options Template Set),
sender uses 0x0002 (Template Set); previously both used 0x9992/0x9993
- Add missing enterprise numbers (0x0000768F = 30351) to all enterprise
field specifiers in both template blocks
- Fix sender template field IDs: use correct attributes (senderCallsign
30351.1, frequency 30351.5, sNR 30351.6, iMD 30351.7, mode 30351.10,
informationSource 30351.11, senderLocator 30351.3, flowStartSeconds 150)
- Fix sender data field order to match the template declaration
- Add iMD byte (0) required by the 8-field template
- Add 4-byte null padding on receiver and sender data records
- Batch spots into one UDP packet per 5-minute window (spec requirement)
- Deduplicate by callsign within each window (keep most-recent spot)
- Send template descriptors only in first 3 packets then once per hour
- Increment sequence number by report count, not packet count
- Guard against history replays: drop any spot older than the flush
window (live FT8/WSPR is seconds old; history can be 24 h old)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Rebuild the visible-source chips when live APRS, AIS, or VDES markers are first added so the map filter list updates without a page refresh.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Use an unbounded virtual-channel command queue so background decode and scheduler transitions do not silently drop subscribe or remove commands.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Remove hidden background decode channels when the owning audio client disconnects to avoid stale DSP and decoder buildup.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Run FT8 and WSPR decode steps in blocking sections so the server listener stays responsive under decode load.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replace the misleading scheduler task countdown with the actual time-span interleave switch timing in the main controls row.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Raise remote spectrum polling from 100 ms to 50 ms while keeping the relaxed timeout and subscriber gating.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Relax the remote spectrum timeout, poll at the backend update cadence, and stop polling when no spectrum subscribers are connected.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add separate map path toggles, move scheduler handoff into the channels row, and show a live countdown to the next scheduler cycle.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Remove settings rig pickers, restore the last scheduler cycle on release, fix FT8 locator role parsing, and add toggleable decode contact paths on the map.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Rewrite the README, remove AI-generated planning docs, and regenerate the combined example config.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Move Scheduler under a new Settings tab in the HTTP frontend.
Add the virtual-channel audio implementation plan document.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Use the active channel frequency for spectrum bandwidth edge hit-testing.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Each RDS PS overlay item (position: absolute within the shared #rds-ps-overlay
container) now receives a z-index derived from its channel frequency: items are
sorted by freq_hz ascending so higher-frequency layers sit on top of
lower-frequency ones by default.
Hovering any layer temporarily assigns it the maximum z-index (entry count + 10)
to bring it to the front; mouseleave restores the frequency-derived default
stored in data-default-z.
Also reverts the incorrectly applied vchan picker layer changes from the
previous commit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Render virtual channels as absolutely-positioned layer strips inside a
shared relative container (#vchan-freq-layers). Layers are sorted by
frequency ascending so higher-frequency channels receive a higher z-index
and sit on top by default. Hovering any layer temporarily assigns it the
maximum z-index to bring it to the front; leaving restores the original
stacking order. Each layer is offset by 11 px vertically so all channels
remain visible as a staggered card stack.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Update test fixtures to include hf_aprs_decode_enabled and use the current spectrum watch sender type in remote client tests.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add client-side command plumbing, HTTP endpoint handling, and frontend interception so bandwidth changes are applied per active virtual channel and survive reconnects.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Handle AUDIO_MSG_VCHAN_BW in the audio server path and apply per-channel filter bandwidth through the SoapySDR virtual channel manager.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add virtual-channel bandwidth control to the shared core API and audio protocol constants for client/server coordination.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
- trx-server/rig_handle: remove dead vchan_manager field (was set but
never read after the virtual-channel refactor)
- trx-server/listener: remove now-missing vchan_manager initializer
- trx-server/main: remove vchan_manager_for_handle intermediates that
only fed the dropped field
- trx-server/audio: suppress too_many_arguments on run_audio_listener
- trx-frontend-http/server: suppress too_many_arguments on build_server
- trx-core/vchan: update module doc comment to not reference the
removed RigHandle::vchan_manager field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
MARINE was a composite mode that ran both AIS and VDES decoders
simultaneously. It is now fully replaced by allocating two virtual
channels — one tuned to the AIS frequencies and one to VDES — each
decoded independently.
- trx-core/state: remove RigMode::MARINE variant
- trx-protocol/codec: remove MARINE parse/serialize
- trx-backend-ft817: remove MARINE from unsupported-mode guard
- trx-backend-ft450d: remove MARINE from FM CAT code mapping
- trx-backend-soapysdr: remove MARINE from bandwidth table, supported
modes list, AIS channel activity check, parse_rig_mode, vchan_impl
bandwidth table, demod selection, dsp/channel bandwidth / sample-rate
/ IQ-tap guards
- trx-server/audio: remove MARINE from AIS and VDES decoder activation
- trx-server/rig_task: remove MARINE from audio-streaming mode list
- trx-server/main: remove MARINE from bandwidth table, mode parser,
VDES channel subscription match
- app.js: remove isMarineMode(), MARINE entry in MODE_BW_SPECS, MARINE
bandwidth specs block in visibleBandwidthSpecs(), MARINE from
decoder status mode lists, MARINE BW-edge drag guard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>