Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/DEVELOPER-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,5 @@ If your AI agent hits an issue requiring maintainer coordination, open a
GitHub issue with: your analysis, relevant log output, code references,
and proposed fix. The maintainer's Claude instance monitors issues and
will respond.

RTL receiver signal contracts: [SSB/CW](rtl-ssb-cw.md) and [AM/SAM](rtl-am-sam.md) describe native RF extraction, accepted controls and offline validation.
8 changes: 4 additions & 4 deletions docs/architecture/aetherd-touchpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ Burndown manifest for the engine/UI decoupling ([RFC](../aetherd-headless-engine
| `core/TailnetShimTokenStore.h` | 1 | peripheral(tailnet-shim) — OS-keychain storage of the tailnet shim's admin token, keyed by the Flex chassis serial (RFC #6271). Not SmartSDR wire: AetherSDR's own HTTP API to a separate service (the shim) that happens to run in the radio's container runtime, so a direct-transport peripheral, NOT behind the IRadioBackend radio seam (same shape as core/PgxlConnection.h). | unconverted |
| `core/TciServer.h` | 3 | mixed(flex) — TCI WebSocket server for WSJT-X et al: protocol surface is canonical radio state, but audio/IQ rides Flex DAX | unconverted |
| `core/TgxlConnection.h` | 3 | peripheral(4o3a) — Direct TCP client for the 4O3A Tuner Genius XL (port 9010, relay/autotune), reverse-engineered from the 4O3A management app — a standalone accessory transport, not SmartSDR. Not radio-family wire; a peripheral accessory, NOT behind the IRadioBackend radio seam (reclassified from vendor(flex), #4087 follow-up). | unconverted |
| `core/ThemeManager.h` | 179 | ui-support — Qt token-based theming singleton (colors/fonts/QSS, theme files, editor hooks) — pure client GUI plumbing, no radio state. | unconverted |
| `core/ThemeManager.h` | 180 | ui-support — Qt token-based theming singleton (colors/fonts/QSS, theme files, editor hooks) — pure client GUI plumbing, no radio state. | unconverted |
| `core/ThreadCpuRing.h` | 2 | ui-support — Short host-thread CPU history used by Runtime Monitor peak and sparkline presentation. Diagnostic UI support, not radio state. | unconverted |
| `core/TimeFrameVoter.h` | 1 | universal — Shared AetherClock time-frame types plus confidence-weighted cross-frame bit voting over a sliding window. Map-agnostic pure DSP/logic — no Qt, no GUI, no vendor ties. | unconverted |
| `core/TxKeyingMarker.h` | 16 | ui-support — QWidget property marker guarding TX-keying controls from the automation bridge; GUI-shell plumbing, no radio state. | unconverted |
Expand Down Expand Up @@ -242,7 +242,7 @@ Burndown manifest for the engine/UI decoupling ([RFC](../aetherd-headless-engine
| `models/DStarModel.h` | 1 | universal — D-STAR configuration and session state (callsigns, RPT1/RPT2 routing, modem/serial paths). Digital-voice state any backend carrying DV would present. | unconverted |
| `models/DaxIqModel.h` | 1 | vendor(flex) — Flex DAX IQ streams: dax_iq stream create/rate cmds, 4-ch DAX model, pipes to SDR apps — DAX is Flex-only | unconverted |
| `models/DaxReceiveModel.h` | 1 | universal — Owner-thread adapter from typed native receive PCM and stable TCI receiver bindings to the existing 24 kHz virtual audio bridges; no radio wire protocol. | unconverted |
| `models/DecoderAudioModel.h` | 1 | mixed(flex) — Selected-receiver fixed24 decoder PCM and bounded delivery, independent of speaker controls. Native ingress uses normalized typed slice frames; the compatibility DAX lane uses RadioModel's existing stream and holder registry. | unconverted |
| `models/DecoderAudioModel.h` | 3 | mixed(flex) — Selected-receiver fixed24 decoder PCM and bounded delivery, independent of speaker controls. Native ingress uses normalized typed slice frames; the compatibility DAX lane uses RadioModel's existing stream and holder registry. | unconverted |
| `models/DigitalVoiceWaveformHistory.h` | 1 | mixed(flex) — Rolling health history for the DV waveform path (sample rate, turnaround, deficits). Generic rate/latency telemetry fused with Flex VITA sequence-gap counters. | unconverted |
| `models/EqualizerModel.h` | 3 | universal — 8-band TX/RX audio EQ state (enable + band gains) — core capability; SmartSDR wire parsing/cmds move to flex adapter | unconverted |
| `models/FirmwareInstallModel.h` | 1 | peripheral(ctr2) — CTR2-Max firmware installer model behind the CTR2 window: the signed release catalog, which controller is connected and in what mode, and actions on it (EspFirmwareJob in ROM download mode, or Ctr2ProxyModel's updater over the USB link), each checked against the device the operator confirmed. Controller firmware tooling, no radio state; a peripheral, NOT behind the radio seam. | unconverted |
Expand All @@ -256,13 +256,13 @@ Burndown manifest for the engine/UI decoupling ([RFC](../aetherd-headless-engine
| `models/Nr2SettingsModel.h` | 5 | universal — Process-wide owner of client-side NR2 configuration as one versioned object (Principle V), so every editing surface shares state. Engine DSP configuration; radio-agnostic. | unconverted |
| `models/PanadapterModel.h` | 5 | mixed(flex) — Per-pan display state is core-profile; DAX IQ ch, client_handle, VITA stream IDs, SmartSDR kv parsing are flex. | unconverted |
| `models/ProfileLoadCommand.h` | 1 | vendor(flex) — Parses SmartSDR 'profile global/tx/mic load' wire command + recall-hold timing/suppression sentinels; Flex-only | unconverted |
| `models/RadioModel.h` | 53 | mixed(flex) — Central radio aggregate: core slice/pan/TX/meter/memory state fused with Flex protocol, DAX, SmartLink, Multi-Flex | unconverted |
| `models/RadioModel.h` | 54 | mixed(flex) — Central radio aggregate: core slice/pan/TX/meter/memory state fused with Flex protocol, DAX, SmartLink, Multi-Flex | unconverted |
| `models/RadioSession.h` | 1 | universal — Per-radio session aggregate: owns RadioModel + id/label; session concept is core-profile, no vendor surface | unconverted |
| `models/RadioStatusOwnership.h` | 1 | vendor(flex) — SmartSDR status parsing helpers: Flex hex handles, client_handle ownership, remote_audio_rx, interlock gate | unconverted |
| `models/ReceiverSlotCount.h` | 1 | universal — How many receiver letters (A, B, C, ...) the UI offers — the RX applet's slice tabs and the CAT applet's VFO targets — tracked from the connection/capability/slice edges. Family-agnostic: the backend's declared ceiling, floored by the slots live receivers occupy (#5775, #5776). | unconverted |
| `models/Rn2SettingsModel.h` | 1 | universal — Process-wide owner of client-side RN2 configuration as one versioned object (Principle V). Engine DSP configuration; radio-agnostic. | unconverted |
| `models/SliceLinkPolicy.h` | 2 | universal — Pure decision logic for Slice Link (cross-panadapter VFO link) — echo classification on integer Hz, no QObject, no I/O, no model access. Canonical slice state only. | unconverted |
| `models/SliceModel.h` | 36 | mixed(flex) — Slice state (freq/mode/filter/DSP) is core-profile; DAX, index_letter, SmartSDR status KVs are flex ext | unconverted |
| `models/SliceModel.h` | 37 | mixed(flex) — Slice state (freq/mode/filter/DSP) is core-profile; DAX, index_letter, SmartSDR status KVs are flex ext | unconverted |
| `models/SpotModel.h` | 1 | universal — Panadapter spot store (callsign/freq/mode/lifetime/priority) on canonical state; kv ingest is trivially generic | unconverted |
| `models/TnfModel.h` | 1 | universal — Tracking notch filter state (freq/width/depth/permanent, global enable) — generic DSP notch surface; kv parse is transport detail | unconverted |
| `models/TransmitModel.h` | 17 | mixed(flex) — TX state model: power/MOX/VOX/CW/filter are core-profile; ATU, DAX, APD, profiles, interlock are Flex. | unconverted |
Expand Down
38 changes: 38 additions & 0 deletions docs/architecture/radio-capabilities-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -996,3 +996,41 @@ that already holds it untouched; Flex keeps its wire route.
The wider per-backend declaration sweep lived in
`tests/radio_capability_gating_test.cpp` until #5452 removed it for an
ASan-flaky IC-9700 block; #5443 tracks recovering the rest of that coverage.


## Confirmed host receive controls

`receiveCwPitchControl` declares a session-wide receive BFO pitch range and step.
It uses the existing `setCwPitch` verb, with accepted pitch reported through
`TransmitDelta::cwPitch`. The record grants no transmit or microphone path;
`hostModulates` remains independent. A backend using confirmed receive controls
publishes pitch only after DSP adoption, and the shared model forwards operator
intent without optimistically changing the observed pitch or sending a raw Flex
command. An accepted pitch report is never routed back as another pitch request.
The backend owns restoration; connecting does not push the preceding session's
pitch into an engine declaring this record.

`receiveDspControl` declares the qualified modes and individually supported
signal controls (AGC, NB, NR, ANF, manual notch, APF and RIT). This can expose host
DSP without claiming `hasRadioSideDsp`. An absent record retains existing family
capability behavior; a present record makes unlisted features/modes unavailable.
The optional entries in `featureModes` restrict an individual feature to a
subset of those modes: RTL declares APF only for CW/CWR while its other receive
controls cover AM/SAM/USB/LSB/CW/CWR. A missing entry preserves the common mode
list; an empty entry makes the feature unavailable. A restriction cannot add a
mode or feature absent from the common lists.
The VFO and RX applet retain accepted state while a confirmed engine prepares a
request. AGC/DSP requests identify the changed field: a confirmed backend merges
that field into its pending recipe rather than overwriting independent pending
edits with companion fields from the last observation.

These desktop records do not themselves grant an aetherd control verb. Flex's
radio-authoritative command/status and persistence contracts remain unchanged.

Tracking-notch engines with `ReceiveControlPolicy::Confirmed` use the existing
`createNotch`, `setNotch`, `removeNotch` and `setNotchesEnabled` verbs. `TnfModel`
keeps moves, widths, removals and global bypass at accepted state until
`notchChanged`, `notchRemoved` or `RadioDelta::tnfEnabled` arrives. Legacy
optimistic sessions retain their existing behavior. Notch capability limits
remain the existing `maxNotchFilters`, width bounds and `notchHasDepth` fields;
a single relative MN control does not imply a tracking-notch bank.
2 changes: 1 addition & 1 deletion docs/automation-bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -1893,7 +1893,7 @@ re-poll `get slices`.
| `filter` | `<lowHz> <highHz>` e.g. `-3000 -150` | set the active slice passband through `SliceModel::setFilterWidth`, which emits a typed `receiveFilterRequested` with Operator origin and reaches `IRadioBackend::requestSliceFilter`. Mode normalization emits a separately tagged request: host DSP applies it, while Flex preserves its radio-owned mode-filter memory. Assert the passband before measuring the audio path. Returns requested edges and post-normalization `filterLow`/`filterHigh`; desktop model readback alone does not prove hardware application. Use `-4000 4000` for a carrier-straddling AM passband |
| `filterpreset` | `<FIL1\|FIL2\|FIL3>` | select a stable radio-owned RX filter slot without conflating it with a passband-width edit. Returns the requested slot; re-poll `get slice active filterPreset` and the filter edges for radio-authoritative readback |
| `agc` | `<off\|slow\|med\|fast> [threshold 0..100]` | set receive AGC through `SliceModel` operator setters and typed `receiveAgcRequested` requests. Applies threshold before mode; each changed field dispatches independently. Flex writes only that field; the default backend adapter passes the current mode/threshold pair to host DSP for either edit. HL2 maps this to WDSP RXA AGC mode and ceiling in dB. This is not an atomic paired command. Use `off` with a low threshold for a linear measurement path |
| `dsp` | `<nr\|nb\|nb2\|anf\|squelch> <on\|off> [level] [fill]` | drive the receive DSP controls an operator drives — noise blanker, noise reduction, auto-notch, and squelch (with an optional 0..100 level). `nb2` selects WDSP's second impulse blanker and takes an optional fill mode 0..4; at most one blanker runs, so `nb2 on` replaces `nb`. `slice dsp squelch` is the squelch path; there is deliberately no separate squelch verb (#5102) |
| `dsp` | `<nr\|nb\|nb2\|anf\|squelch> <on\|off> [level] [fill]` | drive the receive DSP controls an operator drives — noise blanker, noise reduction, auto-notch, and squelch (with an optional 0..100 level). `nb2` selects WDSP's second impulse blanker and takes an optional fill mode 0..4; at most one blanker runs, so `nb2 on` replaces `nb`. An explicit ANF level is forwarded like the NR level. Mode-dependent receive DSP rejects unsupported `on` requests but still accepts `off` intents for cleanup; command success is not a confirmed DSP readback. `slice dsp squelch` is the squelch path; there is deliberately no separate squelch verb (#5102) |
| `tone` | `<off\|ctcss_tx> [freq]` | set the FM CTCSS encode mode and tone. The value is applied before the mode, so enabling CTCSS never keys on the previous tone for a round trip. The mode pair is what a FlexRadio slice carries |
| `offset` | `<simplex\|up\|down> [mhz]` | set repeater duplex. The magnitude is unsigned (0..100 MHz — the GUI spinboxes' own bound); the direction carries the sign. Writes all three radio fields — `repeater_offset_dir`, `fm_repeater_offset_freq` **and** the signed `tx_offset_freq` that actually moves the transmitter — then reports `txOffsetFreq` so the applied split can be asserted rather than assumed |
| `diversity` | `<sliceId> <on\|off>` | enable or disable diversity through the slice model; re-poll `get slices` for parent/child state |
Expand Down
65 changes: 65 additions & 0 deletions docs/rtl-am-sam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# RTL AM and synchronous AM reception

AM and SAM use the existing shared-capture extractor and prepared WDSP
receiver bank at 48 kHz. AM invokes the envelope detector; SAM invokes the
vendored `amd.c` carrier PLL with dual-sideband detection and fade leveling.
The upstream PLL frequency bound is ±2 kHz. An abrupt carrier jump after a
dropout can require retuning to reset acquisition. No worker, decoder library,
transmit path or receiver-count default changes.

## RF and audio contract

RF cuts straddle the dial carrier with at least 100 Hz on each side, bounded
at ±12 kHz. Asymmetric cuts are supported. RTL analytic IQ is conjugated to
WDSP's receive convention, preserving the same signed RF meaning as USB/LSB.
RIT translates the extractor without moving the dial. The RF NBP receives
the signed cuts; the post-detector bandpass receives 50 Hz to the larger
absolute cut with 4096 taps, removing the demodulated carrier before the AGC and PCM taps.
This `WdspChannel::AmReceive` recipe is opt-in, so other WDSP owners retain
their existing AM behavior. The analytic post-filter outputs are averaged to cancel opposite Hilbert
components, yielding the original real audio waveform in both mono channels
before independent PCM delivery. SAM mode selection is real coherent detection,
not an envelope alias. There is no asserted lock indicator.

The existing transaction placement treats AM/SAM as converter-DC-sensitive.
Explicit follow tuning and recentering prefer a legal clear capture; free
browsing does not drag stations into capture or move them to compensate.
Converter DC suppression at the wanted RF carrier remains destructive and
is distinct from audio DC removal. A muted USB bootstrap preserves the saved
capture center and reserves the first restorable saved ID, including sparse
singleton IDs and invalid earlier records.

## Receiver controls and state

AGC mode/threshold/off gain, NB/NB2/fill, NR, ANF, MN, tracking notches and RIT
use the existing accepted recipe. MN maps the audio position from 300 Hz to
200 Hz below the wider RF cut (at least 300 Hz), with 400 Hz paired RF
notches; it does not place a notch on the carrier. Absolute tracking notches
keep their RF meaning, including a notch deliberately placed on a carrier.
APF remains CW-only. AM/SAM squelch is not exposed. Advanced radio-specific
algorithms remain unavailable.

Each slice remembers AM and SAM filter cuts separately, like its other modes.
A first entry uses the mode default; later entries recall only accepted cuts,
including after reconnecting. Graph changes,
parking, receiver recreation and mode transitions invalidate the matching
receiver/presentation epochs. Speaker monitor controls remain downstream of
independent TCI/DAX, selected CW-decoder and selected native-ASR routes.
Normal launches admit one receiver; 2/4/8 receivers remain process-only
evaluation with no sustained-capacity qualification implied.

## Validation

`rtl_am_sam_pipeline_test` drives analytic modulated IQ through production
RF extraction, WDSP and receiver PCM. Assertions cover actual modulation
level, harmonic distortion, DC suppression, carrier offsets, quadrature
rejection that distinguishes SAM from envelope AM, asymmetric RF cuts, RIT,
dropout/fade recovery, retune/park/recreate epochs and eight mixed
AM/SAM/USB/LSB/CW/CWR/FM receivers. Receiver gain/noise/notch controls are
measured on audio, not inferred from a successful setter.

`rtl_model_acceptance_test` drives public SliceModel controls and injected
USB through the production worker to verify adoption, refusal, sparse
restore, persistence and parking. Native ASR regressions include AM and SAM.
These are offline checks; a candidate still requires operator listening and
known RF stimulus before making a hardware intelligibility claim.
Loading
Loading