Skip to content

[RFC] Complete RTL-SDR receive DSP with multiple slices, independent zoom and stereo WFM #5468

Open

Description

@Ozy311

Current review scope — 2026-10-09

This RFC covers analog RTL reception. Optional Digital FM has moved to standalone RFC #6079 and remains deferred in draft PR #6002. No Digital FM dependency, worker, modulation, program metadata or approval is requested here.

I have separated the review decisions below in response to feedback that the accumulated amendments were making every PR wait on every other PR. This is a scope clarification, not a claim of new maintainer approval or permission to merge.

Existing decisions — do not request them again

Review map

PR Increment Decision/evidence owned here
#5924 Analog FM/FM-N capture/runtime, independent viewport, parked slices, shared-device controls Merged with RTL-only free-pan approval and final shared squelch/filter fixes. Preserve those changes in downstream increments.
#5973 Opt-in WDSP broadcast-FM recipe Merged into main on October 7, including the #5974 integration.
#5974 Analog WFM mono/stereo integration and WFM applet Merged through #5973 into main on October 7. Digital program controls remain separate.
#6082 Live per-device PPM adjustment Merged into main on October 7.
#6083 Analog multi-RX, SQL, meters, DAX/TCI/APRS routing and RTL packaging Review each changed path and qualification at the actual advertised capacity. Official builds require RTL. This increment has no Digital build switch or decoder dependency.
#6002 / #6079 Optional Digital FM Separate deferred feature and RFC. Its approval is not an analog RTL approval condition.

Current stack — 2026-10-09: main d6f6a4f0 → ready #6083 (a3458b09). #5924, #5973/#5974 and #6082 are already in main. Only the remaining analog increment was rebased. Deferred draft #6002 (a8112373) follows #6083; the dependency runs from Digital to analog.

The Digital option declaration, placeholder, explicit OFF release/build flags and matching build-option documentation have moved entirely to #6002 / RFC #6079. Analog #6083 keeps required RTL dependencies and adds an audit of the completed AppImage after its final deployment pass. Exact analog head a3458b09 passes Linux/macOS/Windows CI, static checks and docs; native Nobara application/daemon builds plus 53/53 selected tests; the full Linux suite (691 passed, five skipped, zero failures); AppImage x86-64/ARM, macOS DMG Apple Silicon/Intel and Windows installer/artifact workflows. Both final AppImage runtime audits pass; both DMGs pass signing/notarization. No release or Store publication occurred. Digital a8112373 separately passes focused Linux/GCC OFF 15/15 and ON 17/17 tests and remains draft. Exact workflow links and qualification boundaries are recorded in #6083 and #6002. Fresh maintainer review of #6083 remains required.

Previous October 6 checkpoint — historical evidence

Current rebased stack — 2026-10-06: main 5f09371e → #5973 (1398f3b9) → #5974 (ebb35cfd) → #6082 (c667b41c) → #6083 (ca3a36bd). Separate Digital draft #6002 follows at 5ae6b202; analog does not depend on it. Upstream window chrome and release debug-symbol changes are preserved.

Fresh rebase validation on analog ca3a36bd: native Apple Silicon / Qt 6.12 / RTL required / Digital OFF application build succeeds. 51/52 selected CTests pass in 226.55 seconds: all 47 RTL regressions and four additional UI checks pass. The new upstream unified_title_bar_test fails its Alt+F/menu-rounding assertions under Mac offscreen rendering; an independent rebuild and run on clean main 5f09371e reproduces the identical two failures. This is an inherited test failure, not a green full-suite claim. Static engine/routing/capability, generated manifests, registration/frozen-gate, theme and build-options checks pass; package-helper tests pass 9/9 and required-RTL configuration checks pass 16 combinations. The Digital increment and vendor trees are unchanged by replay; decoder-ON tests and release packaging were not rerun for these heads. Hosted checks are being refreshed by the push.

Previous 2026-10-05 checkpoint and validation — not new-head qualification

Published analog stack reconciled: #5924 has merged; the remaining order is main 035a8583 → #5973 (45028ceb) → #5974 (b8aba26e) → #6082 (1e0276fc) → #6083 (dd6be71e). Only the individual increments were replayed, preserving main’s final SQL/FM rules, typed receive routing and current architecture ratchets. The DSP foundation’s Linux/macOS/Windows CI passed. Focused Mac checkpoint tests passed for DSP (1/1), WFM/audio/filter integration (4/4) and PPM (3/3). The combined Mac application builds and 47/47 selected regressions pass. The Linux full suite passed with 659 passed, five skipped, zero failures; AppImage x86/ARM, macOS DMG Apple Silicon/Intel, and Windows packaging all passed. Runtime/package evidence is pinned to 50e2659e; final dd6be71e changes only BUILD-OPTIONS.md and its static gate passes. Artifacts retain their tested revision, and no new live RF or capacity qualification is claimed.

Separate Digital draft: #6002 is restacked at ac62e478 under RFC #6079 and remains draft. Nobara/GCC/Qt 6.12 focused checks pass with the decoder OFF (2/2) and ON (4/4). Tested d2c61b01 differs from final Digital only in BUILD-OPTIONS.md. These are focused offline checks, not full Digital release or live-audio qualification. Analog does not depend on Digital approval. Prior integration history remains on codex/rtl-digital-evaluation-preserved-20261001.

Free-pan decision settled: Jeremy approved this behavior as RTL-only in the linked ruling, and #5924 has merged. This does not extend that behavior to other radio families or raise qualified receiver capacity.

Current delivery boundary

The current submission is FM/FM-N and analog WFM mono/stereo, capture/display controls, selected-slice multi-RX and existing audio consumers. AM/SSB/CW are a later sprint; Digital AM, SDRplay/Airspy and AetherModem i-gate are future work. None is a completion or approval gate for these analog PRs. This does not claim those modes are implemented by the current submission.

Normal-launch admission currently remains one receiver; 2/4/8 are opt-in desktop evaluation. Raising normal admission needs the per-architecture qualification required by the original approval, including separate Linux aarch64 evidence. Successful operator multi-RX testing is recorded in #6083; no reviewer should infer a default change from that testing alone.

Release packaging is an infrastructure/code-owner review: build RTL and fail if its dependencies are missing, package matching runtime/source/notices, without introducing a Digital build switch. The optional decoder switch and explicit OFF publishing flags belong solely to #6002 / RFC #6079. It does not require approval of a new modulation. Required CI, accessibility, performance and hardware evidence still apply to each changed surface; moving scope does not waive them.

Retained analog amendments and original design

The original proposal, conditions, acceptance matrix and measurement appendices below are retained for traceability. This scope map governs what is requested in the current submission. Historical checkpoints are not current-head test claims. The removed Digital proposal and its unresolved historical observations are preserved in #6079.

Approved RTL-only amendment — 2026-09-25: free capture browsing and parked slices

Approved as RTL-only and merged in #5924. Jeremy's specific amendment ruling supplies this approval; it is separate from the original architecture approval. The original wording below remains for historical comparison. Revision-specific evidence and operator acceptance remain scoped to their recorded builds; this approval does not establish unrestricted multi-RX capacity or change other radios' browsing policy.

Approved RTL browsing behavior

The earlier rule kept every receiving slice inside capture and rejected a move when the complete set could not fit. The approved RTL behavior permits browsing beyond that capture window while preserving slice frequencies and settings.

  1. Pan within capture: when the requested viewport fits inside the current usable capture, move the view without retuning the hardware. Zoom remains independent of the hardware sample rate.
  2. Pan beyond capture: dragging beyond the current capture, including dragging at maximum zoom-out, moves the hardware capture within supported tuning limits. Do not clamp browsing solely to keep existing slices receiving. Publish the new spectrum geometry only after successful hardware readback and DSP adoption; failed requests retain or coherently restore the last valid state.
  3. Preserve configured slices: moving capture must not change another slice's absolute RF frequency, mode, complete filter, AGC, squelch, audio settings or stable identity. Configured slices and currently receiving DSP channels are distinct sets.
  4. Park outside capture: if a slice's complete required RF interval, including transition guards and mode-specific translation, no longer fits usable capture, keep it configured and visibly mark it OUT OF CAPTURE. Suspend its reception and delivery of audio/decoder samples. Partial overlap is insufficient; do not emit aliased or stale reception. Parking is separate from which slice is selected or focused, and must not overwrite its saved mute/volume settings.
  5. Resume automatically: when that complete interval fits again, resume reception using the preserved settings. Discard stale queued samples and obsolete spectrum frames across transitions. All slices may be parked while the spectrum and waterfall continue showing the current capture.
  6. Offscreen is not out of capture: a slice outside a zoomed viewport continues receiving while its required interval remains captured. Merely zooming or hiding its marker must not park it.
  7. Explicit tuning and rate changes: tuning a slice inside capture keeps the hardware center fixed. Tuning outside it may recenter to receive that slice while parking siblings; it must not retune their frequencies. A supported explicit capture-rate change likewise reevaluates receiving versus parked membership from actual readback. Reject malformed or unsupported requests and an interval that cannot fit any legal capture; a parked sibling alone is not a reason to reject an otherwise valid request.

Persistence, presentation and shared contracts

  • Amend §1.1 so valid configured slices outside the restored capture are retained as parked rather than omitted solely for being out of range. Restore the accepted capture context and recompute live availability; do not persist a stale boolean as authority. Keep existing schema/version validation, device scoping, atomic accepted-state writes, deterministic capacity limits and stable IDs. Do not silently move capture merely to bring every saved slice into range.
  • Provide an accessible out-of-capture reason in the slice presentation. Preserve frequency/settings controls needed to recover the slice. Shared UI consumes neutral observed availability rather than adding RTL family checks. Other backends keep their existing tuning behavior unless separately proposed and approved.
  • Apply this relationship per capture stream, not necessarily per physical device. SDRplay is the intended next family, with Airspy a possible follow-on; multiple antenna ports do not by themselves imply independently tunable streams. The SDRplay RSPduo has dual tuners, so future adapters must describe their actual stream topology and constraints. This amendment does not implement or qualify either new backend.

Following work retained in the RFC

  • Higher-resolution zoom (§5): retain wide capture while providing meaningful close-view frequency detail. Cropping or stretching the current FFT is insufficient. Choose and validate accumulation/larger FFT or filtered display decimation with truthful RF geometry, calibration and measured processing cost; the algorithm is not settled by this amendment.
  • Per-device RTL frequency correction: expose PPM correction in radio settings, apply it through the existing backend transaction, and persist only accepted correction under the appropriate device scope. Preserve requested RF frequencies and handle refusal/reconnect coherently. The operator reports roughly 8 kHz error near 460.3 MHz (about 17.4 ppm magnitude); this is not a calibrated value or an approved correction sign. Do not automatically apply that estimate or change everyone’s default.

Acceptance and approval boundary

Extend the shared-tuner acceptance row to distinguish configured slices from currently receiving slices. Demonstrate full-width capture dragging, interior viewport-only panning, exact full-passband boundaries, fixed slice settings, all-parked operation, automatic resumption, explicit tune/rate changes, parked-state restoration, rapid/coalesced requests, hardware-failure rollback and rejection of stale FFT/waterfall/audio/decoder delivery. Retain regression coverage for existing Flex, Icom and Hermes behavior. Multi-slice offline fixtures do not raise the production receiver limit without the existing hardware/performance qualification.

The evaluation build at 6ff4c07d passed 16 focused tests and all seven hosted checks. A Nobara RTL2838 receive-only test observed capture movement, fixed slice RF/settings, parking/resumption, continued waterfall updates and exact restoration of the starting state. This is scoped evidence, not operator acceptance, long-duration audio qualification, native Mac/Windows hardware testing or multi-receiver qualification. The shared changes must remain portable across Linux, macOS and Windows; only Nobara has this task's live hardware evidence.

Alternatives considered were the previous hard stop at capture boundaries and dragging slice frequencies along with capture. The proposed behavior preserves station intent while permitting free browsing, with explicit loss-of-capture indication instead of silent retuning.

Scope-specific review status: live PPM (#6082) and analog WFM (#5974 through #5973) have merged. The remaining analog delivery is #6083. This does not reopen the approved RTL-only free-pan amendment or earlier architecture/seam rulings. Ready status requests review; it does not supply maintainer approval.


Review-history correction: GitHub automatically marked original #6003 and #6077 merged into the Digital branch during ancestry rearrangement and deleted their heads. This did not merge them into main. The refs were restored, #6082/#6083 replace those reviews, and original discussion/evidence remains on the old PRs. #6082 has since merged into main; #6083 remains Ready and #6002 remains draft.

Original approved design, acceptance conditions and historical measurement appendices

Research date: 2026-09-06.
Related work: RTL backend issue #4797, merged experimental backend PR #4862, the existing Hermes-Lite/ANAN WDSP receive paths, and the aetherd backend boundary.
Evidence baseline: AetherSDR commit e60c3bea2ac8e5b762be9a3c6f52aa1bab8fb9ad. This is the tested 26.9.2-main portable-build source, not a claim that the tests ran on the release tag itself.

This proposal requires an RFC under GOVERNANCE.md because it changes the shared slice lifecycle, audio pipeline and receiver controls. Architecture approval was recorded on 2026-09-07 UTC. At the original proposal stage, completed work was source research, isolated feasibility probes and implementation planning. Current application checkpoints and their limited qualification are recorded above; the original acceptance requirements below remain the baseline.

Problem

The experimental RTL backend can receive WFM, but several advertised receive modes and controls are incomplete. In the reported case, selecting FM at 144.390 MHz after WFM left a 200 kHz passband displayed, and the panadapter could not zoom closely enough to inspect the channel. Source inspection and generated-IQ probes established that filter edges are stored but not applied by the current RTL demodulator, USB/LSB do not isolate a sideband, and CW lacks the required pitch offset. FM/AM do have processing branches; it would be inaccurate to describe the backend as WFM-only.

The required result is an RTL receiver that can place multiple independently demodulated RX slices across one captured IQ window, with working modes and filters, useful spectrum zoom, and broadcast-FM stereo. A user must be able to monitor several channels simultaneously without moving one receiver changing another's station or zooming the display reducing its available samples.

One RTL dongle provides one tunable RF capture. Software receivers may be separated as far as their complete passbands fit within that capture's usable bandwidth. They share the tuner center, hardware sample rate, RF gain, oscillator correction and front-end overload behavior. They do not provide reception across unrelated RF bands outside that window.

This builds on #4797 and the broader auxiliary-receiver request #4029. The decision here is how to complete the existing RTL backend with shared-window multi-RX, independent spectrum zoom and broadcast stereo.

Proposal

Feasibility evidence. A real RTL capture reached the existing WDSP stereo decoder offline. Generated stereo measured 42.30/48.43 dB separation; generated shared-IQ tests ran four chains at 7.20× real time with two tuning positions 2.1 MHz apart. These are component experiments; integrated multi-RX, live narrow-FM intelligibility and native Windows stereo remain unverified. Full measurements and limitations are retained in Appendix A.

Reuse the existing WdspChannel and r8brain infrastructure. Follow Hermes' established per-receiver ownership, stable IDs, mode/filter ordering, CW translation, DSP lifecycle and audio-mixing behavior. Add the RTL-specific shared-IQ channel extraction and shared-tuner rules described below.

Multiple RX belongs in the initial data model and processing topology. Do not replace the current demodulator with another hard-coded single-slice implementation and defer multi-RX to a later redesign.

The proposed initial acceptance target is four simultaneous RX slices on Nobara-Dell, with one visible panadapter and independently movable slice markers. The final advertised receiver limit is set per architecture from component probes and integrated performance/resource tests, including native Linux aarch64 on representative Raspberry Pi-class hardware. The x86-64 four-slice target is not an ARM default; macOS arm64 evidence does not establish Raspberry Pi performance. The eight-chain experiment below is feasibility evidence, not approval to advertise eight fully supported receivers.

The original approved analog proposal adds no third-party DSP dependency: WDSP 2.00 and r8brain are already in AetherSDR. Optional Digital FM and its additional dependencies are outside this RFC and tracked separately in #6079. RTL remains receive-only.

Capture-to-DSP boundary. The reusable software-receiver chain consumes normalized complex float IQ with explicit achieved sample rate, RF center, usable frequency interval, stable capture identity and discontinuity/reconfiguration information. Device sample conversion, hardware controls and callback lifetime remain in the RTL adapter. The chain must accept variable input block lengths and rates; the tested 2.4 MS/s setting and four-slice target are validation points, not constants in a shared DSP interface. Apply shared-tuner constraints per capture endpoint, rather than assuming every endpoint in a backend must share one center. This RFC still implements one RTL capture per session. Keep channel extraction replaceable if wider-rate measurements later justify staged decimation or a channelizer.

Related architecture issue #4955 proposes a backend-neutral IQ provider for external consumers, including canonical orientation, endpoint identity and bounded delivery. Align the internal capture contract with those concepts; this RFC does not implement its TCI subscription/export work. #3894 addresses standalone receive-session startup, a separate concern from completing this existing backend's DSP.

1. Separate capture, viewport and slice state

Maintain three distinct concepts:

  • Capture: actual hardware center and sample rate, with a documented usable frequency interval and margins.
  • Viewport: displayed spectrum center/span, entirely within the captured interval; changing it does not implicitly change the USB sample rate.
  • Slices: stable IDs and independent frequency, mode, filter, AGC, squelch, audio level/mute/balance and DSP state.

For slice i, let its required RF interval be [f_i + low_i, f_i + high_i]. The full interval, including required transition/guard margins, must fit within the usable capture interval. WFM requires its full channel width; checking only its center frequency is insufficient. CW's operator-facing carrier interval and internal BFO-shifted DSP interval must be translated consistently before validation.

Proposed tuning behavior:

  1. Keep the hardware center fixed for slice moves that fit the existing capture.
  2. If a requested move requires recentering, find a legal center that preserves every active slice's requested absolute RF interval. Retune once only if the full set fits and the hardware can accept it.
  3. If the set cannot fit, reject the requested move with a useful explanation; preserve the other slices and the prior valid state. Do not silently evict, retune or widen another receiver.
  4. Apply the same whole-set validation to explicit hardware sample-rate changes. Publish actual successful hardware readback; roll back a failed operation coherently.
  5. Keep spectrum-only zoom independent of capture and of receivers outside the currently visible subsection. Recenter/zoom UI details and refusal presentation require maintainer agreement.

The actual usable margin must be measured and specified. A 2.4 MS/s setting is not a promise of 2.4 MHz of uniformly clean usable bandwidth. RTL's current 0.45 × sampleRate center-only guard is not sufficient as the multi-slice policy.

1.1 Slice persistence and identity

RtlSliceSettings is the single owner of feature document RtlSlices, with kSchemaVersion = 1, following Hl2FilterBoardSettings. It receives the scope through RadioModel::settingsScope(). Consumers read the effective exact → family-wide → empty document; writers read-modify-write featureExact, preserve unrelated fields, refuse a newer schema, check the atomic write result and issue a qWarning on refusal. An existing empty document is a settled document, not permission to import old state again.

Use the reported serial as radioId only when it is non-empty and is not the synthesized USB-index fallback; otherwise use the RTL family-wide row (empty radioId). A connection locator such as rtl:<index> remains useful for this scan/connection, but is not persistent device identity. Preserve that provenance through discovery, connection metadata and the backend. Changing only RtlSdrBackend::m_serial is insufficient: RadioModel::settingsScope() currently reads m_lastInfo.serial, and restore is handed to the backend before connect. A genuine numeric serial must not be mistaken for the index merely because the strings match. Blank/index-fallback identities deliberately share the family-wide row. Duplicate nonempty factory serials can still collide under the stated rule; this revision does not claim per-dongle isolation or introduce another identity scheme.

The document shape is:

{
  "captureCenterHz": 100000000,
  "sampleRateHz": 2400000,
  "slices": {
    "0": {
      "freq": 100000000,
      "mode": "FM",
      "filterLow": -8000,
      "filterHigh": 8000,
      "agcMode": "med",
      "agcThreshold": 65,
      "squelch": { "enabled": false, "level": 20 },
      "audioGain": 50,
      "audioMute": false,
      "audioPan": 50
    }
  }
}

This is a schema example, not a new set of defaults. Frequencies and filter offsets in this document are in Hz; conversion to existing MHz model APIs occurs at an explicit boundary. Mode and AGC mode use normalized model strings; gain/pan are percentages and other values use the validated slice-model units. squelch contains both enablement and level, so restart does not lose half of that control. The WFM control PR adds the optional selected wfmDeemphasisUs to this same slice object, validates it against the supported capability values, and documents its reviewed runtime default; no new flat setting is introduced. Pilot/stereo detection is live state and is never restored from disk. slices is keyed by stable UI slot ID, not array position or WDSP channel number. Removing a middle slice never renumbers surviving slices. Reuse a free slot only after retirement, with a separate generation token to reject delayed work for its previous occupant.

Restore parses and validates the entire document before applying any slice. Against the current successfully established capture and its actual readback, restore the subset whose complete passbands fit, within the measured architecture limit; report each rejected slice with qWarning. Use ascending stable IDs to make capacity reduction deterministic. Never silently move the capture, reduce a slice's passband, or move a sibling to make saved slices fit. Saved capture center/rate record the saved set's context; they do not authorize a post-connect recenter to accommodate an otherwise refused restore. If no saved slice is acceptable, retain the session's valid initial state and report the refusal.

The existing RTL OperatingState currently owns tuning, passband and span. Transfer those overlapping fields to RtlSlices with a one-time, scope-safe claim migration; retain non-overlapping RF-gain ownership. Do not leave two writers or two restore paths for the same RTL slice/capture state. Do not import an index-keyed legacy row into an asserted per-device identity. A refused migration write retries later and never marks the migration complete. Persist only accepted state after successful preparation/hardware readback, not pending or rejected requests.

2. Build software receivers from one IQ stream

Conceptual receive path:

flowchart LR
    U[One RTL USB IQ stream] --> S[Shared capture block]
    S --> V[Viewport shift and filtered decimation]
    V --> F[FFT and waterfall]
    S --> A[RX A: shift, filter and resample]
    S --> B[RX B: shift, filter and resample]
    S --> N[Other bounded RX slices]
    A --> DA[Independent WDSP channel A]
    B --> DB[Independent WDSP channel B]
    N --> DN[Independent WDSP channels]
    DA --> PA[Per-slice audio]
    DB --> PB[Per-slice audio]
    DN --> PN[Per-slice audio]
    PA --> M[Aligned speaker mix]
    PB --> M
    PN --> M
Loading

Use a phase-continuous frequency shifter and identical, independent I/Q resamplers for each slice. AetherSDR's Resampler::processStereoToStereo() averages its inputs and is unsuitable for either complex IQ or preserving broadcast stereo; use the existing paired-instance pattern.

Proposed mode-rate policy follows the WDSP 2.00 manual: 192 kHz DSP for FM-family modes and 48 kHz for other analog modes. WFM specifically requires 192 kHz DSP and at least 192 kHz input. The tested path converts RTL IQ to 192 kHz before WFM; the final anti-alias specification must preserve the required channel. Some RTL rates do not satisfy WdspChannel's integral-rate/block contract directly, which is why the resampling stage is required.

Recover USB/LSB, AM/SAM, FM/narrow FM, CW/CWR and WFM through appropriate existing DSP facilities and explicit mode mappings. Preserve user-facing vocabulary where possible; resolve the current FMN/NFM and CWR/CWL differences deliberately. Apply mode changes before reapplying the corresponding passband, as Hermes does. Adopt a mode-appropriate filter on a genuine mode transition and preserve manual edits while that mode remains selected. Final preset widths and FM deviation values need reviewer agreement and test coverage.

CW requires the established BFO/filter translation; selecting a WDSP CW enum alone does not create the expected pitch. SAM must be validated for synchronous tracking, not just recovery of a centered AM carrier. FM and narrow FM must have meaningful, documented differences rather than identical branches.

Verify RTL IQ orientation independently. The generated WDSP probe selected negative mathematical complex frequency in USB; Hermes and ANAN document their own orientation handling. Tuning sign, spectrum direction, sideband selection and CW pitch must agree for RTL rather than inheriting a hardware-specific conjugation from another backend.

3. Reuse Hermes' lifecycle without copying its transport assumptions

Existing Hermes behavior Proposed RTL use
Receiver records survive DSP rebuilds Preserve each slice's requested/accepted state across reconnect and reconfiguration
UI IDs, hardware DDC indices and WDSP channel IDs are distinct Keep stable RTL slice IDs separate from container positions and shared WDSP pool IDs
Configured chains are published to the sample path; removed chains are withdrawn before destruction Prepare, publish, retire and destroy RTL chains in a defined order with no callback use-after-free
GUI-owned state is separate from the I/O-owned chain list Give the RTL processing context an explicitly owned active-chain snapshot
Mode before passband; carrier-relative CW helpers Follow those rules for every RTL slice
Pre-monitor per-slice audio plus separately controlled speaker mix Preserve decoder/routing audio independently of speaker mute, level and balance
Stereo-frame alignment and a stalled-receiver guard in the mixer Prevent one late or removed receiver from silencing or shifting the others

Hermes hardware sends separately tuned DDC streams, and adding a receiver changes its EP6 wire layout. RTL distributes one unchanged capture to multiple software extraction stages. Opening another RTL slice should not require restarting USB acquisition merely to change a receiver count.

Hermes currently creates a receiver through Add Panadapter, with one receiver per pan. The requested RTL interaction is several slices on one pan. RadioModel::addSlice()/addSliceOnPan() still construct Flex commands, and the neutral backend interface has pan lifecycle methods but lacks an equivalent slice-create/remove path. Add a narrow backend-neutral slice lifecycle seam that preserves existing Flex behavior and reports normalized slice events. Raising maxSlices alone is insufficient.

Do not copy Hermes' blocking Qt event-loop synchronization into librtlsdr's asynchronous read callback. The implementation design must specify off-callback DSP preparation, safe active-list publication, retirement acknowledgment and destruction, bounded pending work, and cancellation of stale connection/reconfiguration results. Keep FFTW planning and allocating control operations away from live sample processing. Reuse the existing RTL worker where practical; an extra Qt thread per slice is not assumed. WDSP already owns internal workers.

Cold-start strategy: use bounded background preparation on the existing Qt worker-pool infrastructure, with one executing RTL preparation job and one coalesced desired configuration across active and retiring connection generations. Retain the preparation controller until its work finishes, so repeated reconnects cannot enqueue unbounded obsolete FFTW jobs. Keep WDSP's existing global setup/planner serialization. Import production FFTW wisdom and prepare the requested receiver configurations off both the GUI thread and USB callback; cache successful production plans using the existing atomic wisdom writer. Publish only fully prepared chains at a sample-block boundary, acknowledge retirement, and destroy retired chains off the sample path. Coalesce pending control changes and reject stale connection/slice generations. Canceling a request invalidates its result; it does not assume a running FFTW call can be interrupted. Keep its owner alive until completion.

A cold first connection can report that receivers are being prepared while the UI remains responsive and cancellable. Adding a receiver must not stall existing reception for planning or restart USB merely because the receiver count changes. Plan construction, mode/filter reconfiguration, buffer allocation and destruction all remain off the callback. The experimental AETHER_WDSP_FFTW_TIMELIMIT=0.001 is not the shipping strategy. For cold/warm validation, use an explicit absolute task-owned AETHER_WDSP_WISDOM_DIR that persists across warm runs and verify the resolved path. PR #5417 has merged; follow its corrected docs/HERMES.md recipe rather than redirecting HOME and assuming the WDSP cache was isolated.

Any common helper extraction should be small, with behavior-preserving tests for Hermes. An RTL feature should not require a broad rewrite of working Hermes or ANAN backends.

4. Provide broadcast stereo and preserve independent audio routes

Use WDSP's existing WBFM stereo decoder, internal squelch and deemphasis. Expose stereo detection and regional deemphasis through AetherSDR's approved WDSP C boundary and wrapper, not by including vendor headers above the DSP boundary. Provide truthful stereo/mono indication and tested mono fallback after pilot loss. The placement and styling of new controls/indicators require maintainer review.

Approved audio target: 48 kHz stereo for WFM, preserving the normal broadcast audio band through 15 kHz. Current backend PCM is 24 kHz stereo: it can preserve left/right separation, as tested, but cannot carry audio through 15 kHz. Emitting 48 kHz bytes through an API that assumes 24 kHz would misinterpret timing.

Propose a rate-aware backend audio path with explicit frame metadata for sample rate and channel layout, preserving existing 24 kHz producers. All affected buffering, playback resampling, recording, DSP taps and supported downstream routes must interpret that metadata. Convert at explicit boundaries; do not globally change AudioEngine::DEFAULT_SAMPLE_RATE or silently bypass the normal engine. A mixed narrow-mode/WFM speaker mix must use one agreed output rate while retaining independent per-slice streams.

48 kHz is approved; do not ship a new 24 kHz interim milestone. Keep the existing WFM implementation usable during development until its 48 kHz replacement passes comparison tests.

The rate-aware PR must explicitly inventory all four AudioEngine.cpp sites Jeremy identified at baseline e60c3bea: the kTargetHz = 24000 at line 1774, the main RX L/R resamplers at 4328/4331, and the RADE resampler at 9444. m_rxOutputRate is the negotiated output-device rate, not the incoming backend producer rate. The main RX L/R resamplers must use the validated producer rate. The other two sites have different contracts: line 1774 adapts CW sidetone into the fixed-rate CW decoder, and line 9444 adapts fixed-rate decoded RADE speech. Keep those explicit fixed-rate boundaries and regression-test them; do not blindly replace all four literals with one variable.

Inventory queue/drain timing, client RX DSP preparation, pre/post-DSP taps, per-slice streams, TCI/DAX conversion, QSO recording/playback and clock/decoder consumers as well. Carry sample rate, channel layout, sample-frame count and session/format generation consistently across queued delivery. Preserve L/R with paired resamplers at stereo conversion boundaries. Existing fixed-rate consumers receive explicit conversion; 48 kHz must survive through the intended WFM speaker/recording/streaming paths, not be reduced to 24 kHz and relabeled later. Existing Flex 24 kHz and Hermes/ANAN behavior receive regression coverage in the same affected PRs.

Normalize output levels before the final limiter. Raw WDSP output in the recorded broadcast probe peaked at about 3.97, with 30.3% of samples above unity. Feeding that directly into RTL's current clamp would distort it. Gain, headroom, clipping behavior and mode-transition loudness need explicit validation. Preserve unmixed per-slice audio for supported decoder/streaming routes; adding a RadioReference upload service or a new network streaming protocol is outside this RFC.

5. Support narrow spectrum views independently

Provide filtered software decimation for the selected viewport while retaining the wide capture for all receivers. A proposed initial close-view target is 24 kHz or narrower, subject to reviewed UI limits and measured spectrum behavior.

For context, a 2048-bin FFT has about 1172 Hz grid spacing over 2.4 MHz and 11.7 Hz spacing over 24 kHz. These figures describe bin spacing, not effective window resolution. Merely stretching or cropping the current coarse FFT does not add frequency resolution. Preserve calibration, axis orientation and frame pacing through viewport changes.

6. Capability and accessibility contract

Proposed additions to RadioCapabilities, with conservative defaults:

Field Type/default Meaning
canCreateSlices bool = false Independent slices can be created/removed on an existing pan through the model lifecycle API, including its preserved Flex command-plane adapter; separate from maxSlices and pan creation
hasWfmStereo bool = false The implemented receive path supports broadcast stereo and truthful per-slice decoder detection
wfmDeemphasisUs QVector<int> = {} Supported regional deemphasis time constants; the completed RTL path advertises {50, 75}; empty hides the selector
hasIndependentViewportZoom bool = false Viewport center/span can change without changing capture rate or disturbing offscreen slices

Set every new field explicitly in all six backend capability constructors, add a row to docs/architecture/radio-capabilities-map.md, update the header's ADDING-A-FIELD obligations, and add behavioral gating tests including RTL → other-family → RTL swaps and disconnect. Inspect pending backend additions when the field lands. Flex advertises the lifecycle capability with its existing semantics; do not infer other capabilities from family names or maxSlices. RTL only raises each feature flag when the corresponding implementation is complete. The advertised maxSlices comes from the measured architecture profile, not from a new universal four/eight constant.

Capability is support, not current reception status. Actual pilot/stereo detection is live per-slice normalized state, cleared on leaving WFM, loss of pilot where appropriate, removal and family swap. Selected deemphasis is persistent slice configuration: preserve it across signal loss and mode round trips, while marking it inactive and hiding its control outside WFM. Removal/family swap withdraws the old slice's live controls rather than transferring its selection to an unrelated slice. UI consumes model/capability state, never a new vendor include. Placement and final preset/deemphasis defaults remain concrete PR-review decisions within the approved direction.

Read docs/a11y.md and docs/style/theme-style-guide.md before UI work. New stereo/pilot indication, deemphasis selection, per-slice audio controls and zoom must have accessible names; every new interactive control must expose settled value changes with QAccessibleValueChangeEvent. Provide keyboard operation and appropriate accessible state/name events for indicators, plus QAccessibleInterface for custom painting where needed. Do not add interactive QLabels. Verify these behaviors even though the existing accessibility lint is warning-only: accessibility is a release gate for this RFC.

Cross-platform impact

WDSP and r8brain already build on all three supported desktop platforms, and the stereo configuration was exercised on Linux and macOS. Windows still needs a native configuration probe and integrated validation. Native Linux aarch64/Raspberry Pi multi-chain measurements are also outstanding. Run at the intended 192 kHz FM-family DSP / 48 kHz stereo output, identify CPU/board/cooling and build settings, and measure separately from macOS arm64. If representative ARM hardware is unavailable, report that gap to the maintainer and obtain a tester; do not infer a supported limit from the desktop x86-64 result. The original analog scope proposes no new license-bearing dependency; optional Digital dependencies are outside this RFC in #6079. Any vendor-source change must follow the existing vendor patch/provenance process.

The RTL dependency remains optional and the receive backend must compile out cleanly. Linux packaging must address USB driver ownership: the tested auto-detach library setting is a candidate, with confirmed reattachment on clean close. macOS and Windows bundles still require their established RTL/USB and Qt runtime dependencies. Do not change drivers or system configuration as an automatic installation side effect without an approved platform-specific plan.

A rate-aware audio seam affects shared engine consumers even though only RTL initially needs this WFM configuration. Preserve old producer behavior and keep engine implementation below the GUI boundary. New controls use normal capability/model state and the theme guide. The proposal does not alter Flex protocol commands, RF transmit behavior, or existing radio default modes/rates.

Alternatives considered

Alternative Assessment
Continue expanding the current RTL boxcar demodulator Requires rebuilding filtering, sideband selection, CW, AGC and stereo work that WDSP already provides. Not recommended
Copy Hermes wholesale Receiver management and DSP patterns are valuable, but hardware DDC streams, EP6 layout changes, receiver/pan pairing and transmit state do not describe RTL. Reuse common components and behaviors selectively
Reuse the existing WfmDsp as the broadcast decoder It is a flat mono data-demodulation path with 48 kHz IQ processing and no broadcast deemphasis. Its NCO/paired-resampler pattern is useful; its output does not satisfy broadcast stereo
Add another DSP library No demonstrated need after the WDSP/r8brain probes; adds dependency, packaging and maintenance work
Ship single-RX improvements first and redesign for multi-RX later Does not meet the required use case and risks another lifecycle/state redesign. Use a multi-slice foundation from the start
Keep all audio at 24 kHz Preserves the current seam but cannot deliver the approved broadcast audio bandwidth. Rejected for the replacement; the existing implementation stays usable during development
Introduce a polyphase channelizer immediately Could improve scaling for many regularly spaced channels, but arbitrary-mode/frequency per-slice extraction already has measured headroom at the initial target. Reserve additional complexity for integrated evidence of a bottleneck

Implementation scope

The work touches src/core/backends/rtl/, src/core/dsp/WdspChannel and its approved WDSP C boundary, IRadioBackend, RadioModel/slice capability and lifecycle routing, AudioEngine and rate-sensitive consumers, spectrum processing, and the GUI connections for slice creation and controls. Tests belong in tests/ with registration in tests/tests.cmake; packaging changes are limited to the existing optional RTL dependency and its runtime requirements.

New dependencies: none proposed. The largest shared risks are audio-rate interpretation and slice lifecycle/tuning correctness. Existing Flex audio and Hermes/ANAN receive behavior need regression coverage. The following criteria and staged scope incorporate the maintainer's conditions; they are not evidence that the gates have passed.

Acceptance criteria and remaining validation

The following are required release gates, not claims of completed tests.

Area Required evidence
Slice lifecycle Create, select, tune and remove multiple slices on one RTL pan; remove a middle slice without redirecting surviving controls/audio; bound capacity; repeated churn/reconnect without leaked WDSP channels
Four-RX target Four independently tuned slices operate in the integrated app on Nobara; include distinct generated channels, mixed modes and a WFM-plus-narrow-mode case; measure sustained processing, memory, buffering and overruns with the GUI and output active
Shared tuner Every accepted passband fits; a refused move/rate change preserves current valid state; a valid shared recenter preserves other slices' absolute RF frequencies; failed USB controls and unplugging cannot leave false accepted state
Modes and filters Injected IQ proves the selected passband and sideband, FM/narrow-FM behavior, AM/SAM behavior, CW/CWR pitch and mode transitions; compare output against an independent reference where feasible
Wide separation Channels near opposite usable capture edges remain correctly tuned and independently filtered; quantify edge loss and adjacent-channel rejection instead of promising the nominal sample rate as useful bandwidth
WFM stereo Correct left/right orientation; at least 40 dB separation for the documented reference vector; truthful pilot indication; mono fallback; regional deemphasis; test signal-rate error, pilot offset and marginal signal conditions
Output quality Defined gain/headroom, no unintended hard clipping at normal reference levels, stable transition loudness, preserved stereo; verify the approved audio-bandwidth target through the actual output path
Zoom Narrow views have real frequency resolution and correct calibration; zoom/pan does not change USB rate or disturb hidden receivers; measured frame pacing
Routing Per-slice audio remains independent of speaker mute/balance; supported decoder and virtual-audio routes receive the intended slice and rate; no double feed or mixed-rate interpretation
Performance/lifetime A 30-minute initial soak at each architecture's candidate supported count (target four on Nobara), plus repeated mode/filter/zoom changes and USB removal/reconnect; record underruns and failures. Longer soak requirements may be set during review
Persistence/identity Restart/replug with real, blank, fallback and duplicate serial cases; no persistent identity from an index; checked atomic writes, newer-schema refusal, retryable one-time migration, stable IDs and fitting-subset restore without silent retune; no overlapping OperatingState/RtlSlices owner
Architecture limits Component probes and integrated runs on every advertised architecture, including native Linux aarch64/Raspberry Pi-class hardware; record hardware and set the supported receiver limit from that architecture's evidence
Integrated CPU/headroom Worst-case supported receiver/mode combination with USB, GUI, FFT/waterfall and speaker mixing all active. Predeclare a numeric CPU/headroom and block-deadline budget; report whole-app CPU, busiest processing thread, p95/p99 and maximum block time, dropped blocks, underruns, queue high-water marks, RSS and thermal/throttling state. A component throughput ratio is not this gate
Cold start/warm-up Empty-cache and warm-cache connection, add-slice and rate/mode changes with production planning settings; responsive UI, cancellation, bounded pending work, existing RX continuity, off-callback preparation/destruction and no stale-result adoption; verify the actual isolated wisdom path
Accessibility Accessible names and QAccessibleValueChangeEvent on every new interactive control; keyboard operation; accessible pilot/stereo state; no interactive QLabel; verify with bridge inspection, Qt accessibility events and native assistive technology
Bridge/radiocert Automation bridge verbs drive slice lifecycle, tuning, zoom and routing, then assert accepted normalized state and GUI convergence. Receive-only radiocert diagnostics establish real receive-path liveness using an appropriate known reference; extend its current slice-0/WWV assumptions before treating it as RTL multi-RX evidence
Regression/platforms Focused tests on Linux x86-64, Linux aarch64, macOS and Windows; preserve Flex 24 kHz, Hermes/ANAN and explicit CW/RADE fixed-rate domains; test RTL-disabled builds. Native Windows and representative Linux ARM stereo/multi-RX evidence remain outstanding

Use socket-free injected IQ and state/lifecycle tests for deterministic DSP and policy claims. Do not add a synthetic radio peer to the default test graph. Use the automation bridge for GUI/model convergence and receive-only radiocert for positive real-hardware receive liveness. Its current receive routine assumes slice 0, a WWV reference and specific modes; add capability-aware target/reference selection and avoid probe retunes that exceed the accepted shared window. Record unsupported or unobserved checks explicitly: radiocert is a diagnostic, not a pass/fail certificate, and a quiet RF interval cannot prove refusal, isolation or packet decoding. Apply sanitizer/lifetime coverage to receiver churn. Existing tests to study include hl2_receivers_test, hl2_receiver_churn_test and hl2_shift_test; their source was inspected, not rerun as part of this feasibility work.

Approved decisions and implementation sequence

Jeremy approved all six direction decisions in the linked comment, with the conditions incorporated above. No second architecture approval round is requested:

  1. Multiple independent RTL slices on one capture/pan, with four RXs as the initial Nobara target and bounded per-architecture limits based on integrated evidence.
  2. Reuse of WdspChannel, paired r8brain conversion and Hermes lifecycle/audio patterns, with minimal shared-helper extraction reviewed by Pat and the maintainer.
  3. A neutral slice lifecycle seam and the proposed full-passband shared-tuner acceptance/refusal behavior.
  4. Mode-specific DSP rates/presets and IQ/CW conventions, with the manual's 192 kHz FM guidance explicitly considered rather than blindly inheriting Hermes' current fixed 48 kHz configuration.
  5. A rate-aware 48 kHz WFM audio path, including mixed-rate per-slice routing/mixing. Skip the 24 kHz interim.
  6. Independent spectrum zoom, pilot indication/deemphasis UI, and the proposed validation gates.

Retain the approved four-stage sequence, split into one feature and its tests per PR:

  • Foundation: separate PRs for the neutral slice lifecycle (with Flex regression evidence), shared-window policy, identity/scoped persistence ownership, and testable capture/processing lifecycle. Do not combine neutral lifecycle and tuning policy into one PR.
  • First usable multi-RX build: working FM/channel filtering, independent audio controls/routes and bounded multi-RX integration, targeting four on Nobara and retaining the existing WFM path. The 48 kHz metadata/consumer foundations land before enabling a new producer; there is no replacement 24 kHz interim.
  • Mode/zoom completion: remaining analog modes, CW/SAM verification, independent high-resolution spectrum views and full persisted-state round trips through the foundation owner.
  • Stereo completion: verified 48 kHz end-to-end audio, stereo telemetry/fallback/deemphasis and output-level validation; accessibility, bridge/radiocert, all-platform packaging, architecture-specific CPU/headroom and soak gates.

Scope ends at a reliable, receive-only multi-slice RTL backend using one dongle/capture per session. Multiple dongle aggregation, coherent diversity, new transmit capabilities, new streaming services and unrelated backend rewrites are not proposed. Keep the existing working WFM path available during development until the replacement passes comparison tests.

The evidence supports feasibility; it does not establish a finished implementation or a delivery-time estimate. No fundamental hardware, algorithm, dependency or Nobara CPU blocker has been demonstrated. The largest remaining risks are shared audio-rate integration and lifecycle/tuning correctness under active multi-RX use.

Appendix A — Measurements supporting feasibility

These are completed experiments using unchanged application DSP sources in standalone probes. There is no integrated RTL-to-WDSP multi-slice application build yet.

Test environment and method

  • Application baseline: e60c3bea2ac8e5b762be9a3c6f52aa1bab8fb9ad.
  • WDSP vendor baseline: 2.00, upstream revision 584e8aca5ba1c4c6bc66fc0cc164ce567c8ba1e3.
  • Hardware host: Nobara-Dell, Nobara Linux 44, x86_64. One Realtek RTL2838UHIDIR with R820T tuner was assigned exclusively for receive development.
  • Cross-platform component check: macOS arm64, same application source and generated stereo vector. No new Windows stereo/multi-RX probe was run.
  • wdsp_probe.cpp compiles unchanged WdspChannel.cpp against the app's existing WDSP archive. resample_probe.cpp compiles unchanged Resampler.cpp. multirx_probe.cpp combines both with standalone per-RX software mixing.
  • Tests set AETHER_WDSP_FFTW_TIMELIMIT=0.001 and an isolated wisdom directory. This bounds FFTW planning and prevents writes of rushed plans to the user's normal cache. It is not a shipping-build configuration, and the recorded setup times do not establish production cold-start latency.
  • USB capture was sequential and bounded. Recorded RF was processed offline; no transmitter was operated and no production stream was interrupted.

RF acquisition and capture results

Test Result What it establishes
12-second RTL throughput check at 2.4 MS/s Reported minimum lost samples per million: 0 Bounded acquisition works; not a long soak
92.3 MHz broadcast capture 12 seconds, 2.4 MS/s, 28 dB gain; 0 ADC rail samples A usable physical RF recording
Pilot analysis of that recording Peak near 18,999 Hz, about 62.08 dB above the selected adjacent reference region Strong pilot evidence; not a calibrated receiver sensitivity measurement
Existing paired r8brain conversion 12 seconds converted to 192 kHz in 0.302 s, including file work Real-IQ rate conversion is practical
Existing WDSP decode after that conversion Eight seconds decoded in 0.616 s, 12.99× real time, stereo indicator = 1 Actual captured RF reaches a stereo decoder and produces distinct L/R offline
Raw decoded broadcast output L/R correlation 0.99099, difference RMS 0.14497, peak 3.97006, 30.31% above unity Channels differ, but level handling remains unfinished; unknown program content does not establish channel separation
144.390 MHz capture 20 seconds, no ADC rails; 50 ms channel-power bins: 5th percentile −56.8604, median −56.6608, 95th −56.4545, maximum −56.1105 dB in the analysis scale No clear strong burst in this interval; no confirmed packet, APRS decode or live narrow-FM intelligibility claim

Linux initially refused to claim the dongle because dvb_usb_rtl28xxu was active. A task-local RTL library rebuilt with DETACH_KERNEL_DRIVER=ON successfully detached the driver, opened the dongle and reattached it on close. No system blacklist, udev rule, EEPROM or bias-tee setting was changed. The existing portable package was not replaced during research.

Generated stereo, mode and filter results

The final stereo vector uses a 19 kHz pilot and a correctly phased 38 kHz suppressed subcarrier according to ITU-R BS.450-4 §2.2.2.5. It carries a 1 kHz left tone and a 2 kHz right tone. An earlier exploratory vector used the wrong quadrature relationship; its initial lack of separation was a test-vector error, and those results are superseded.

Test Measured result
Stereo at 192 kHz input/DSP, 48 kHz audio Indicator 1; left-channel 1 kHz amplitude 1.541755, unwanted 2 kHz 0.004698; right-channel 2 kHz 1.240370, unwanted 1 kHz 0.011832
Separation calculated from those tones 42.30 dB for the left tone and 48.43 dB for the right tone
No-pilot vector Indicator 0; left/right difference RMS 0
Pilot removed after four seconds Indicator 0; left/right difference RMS 0 in the final two-second measurement window
Stereo through 24 kHz output Indicator 1, with low-frequency separation retained; does not establish full broadcast audio bandwidth
FM Recovered the generated 1 kHz audio tone at both 48 kHz and 192 kHz DSP rates; rate choice still follows manual and quality testing
AM and SAM Recovered the generated AM tone; does not establish SAM tracking superiority or lock range
USB/LSB Selected opposite mathematical complex-frequency signs
USB passband moved from 300–2700 Hz to 3000–4000 Hz Test-tone output fell below printed precision; selected filtering is functional
WDSP processing status All recorded single-channel probe blocks succeeded, with zero changes to WDSP's tracked allocation sequence during processing

The allocation guard measures WDSP-tracked allocations, not all allocations in a future application pipeline. Centered-carrier AM/SAM similarity is not evidence that synchronous AM is fully validated. CW has not yet been demonstrated by this probe.

Processing times

Host / experiment Input duration Processing duration Input duration / processing duration
Nobara, generated stereo / 48 kHz output 8.000 s 0.363 s 22.02×
Nobara, generated mono WFM 8.000 s 0.351 s 22.81×
Nobara, pilot-dropout WFM 8.000 s 0.356 s 22.47×
Nobara, FM / 48 kHz DSP 8.000 s 0.074 s 108.31×
Nobara, FM / 192 kHz DSP 8.000 s 0.298 s 26.85×
Nobara, AM 8.000 s 0.056 s 142.62×
Nobara, SAM 8.000 s 0.062 s 129.36×
Nobara, generated stereo / 24 kHz output 8.000 s 0.380 s 21.03×
macOS arm64, generated stereo / 48 kHz output 8.000 s 0.488 s 16.39×

Ratios use unrounded internal timings and may differ slightly from division of the displayed rounded durations. These tests show component headroom, not full application CPU utilization or latency. The generated tests include signal-generation/measurement work; the recorded-IQ test includes file I/O. They exclude USB, full GUI rendering and speaker mixing.

Shared-IQ multiple-RX results

One generated 2.4 MS/s stream contains two equal-strength FM signals with different audio tones. Every receiver sees the same input block, shifts its selected channel to baseband, uses paired r8brain conversion and runs an independent WDSP FM channel at 192 kHz DSP / 24 kHz output.

Chains Test signal offsets Input duration Aggregate per-chain processing Speed
2 −800 / +800 kHz 3.9936 s 0.2744 s 14.55×
4 −800 / +800 kHz 3.9936 s 0.5425 s 7.36×
8 −800 / +800 kHz 3.9936 s 1.0811 s 3.69×
4 −1050 / +1050 kHz, 2.1 MHz separation 3.9936 s 0.5545 s 7.20×

Each chain emitted 95,744 output samples with zero failed WDSP blocks. Desired-tone versus other-channel-tone ratios were approximately 81.17 dB and 81.08 dB at the two tuning positions. The output count reflects finite buffering in the short probe; it is not a long-duration clock-drift result.

Four/eight chains duplicate the two tuning positions for load testing. These runs do not prove four/eight distinct physical signals, mixed-mode reception, live retuning, receiver churn or an integrated maximum receiver count. The measured section includes per-chain frequency mixing, resampling and demodulation; it excludes input generation, setup, USB, FFT/display work, speaker mixing and GUI activity. The current retained probe source uses the final ±1050 kHz case; reproducing the earlier table rows requires setting its two offsets back to ±800 kHz.

RTL-rate conversion coverage

The existing paired resampler was exercised at every advertised rate, converting to 192/48/24 kHz. The two identical input streams produced byte-identical outputs in every run; output duration matched input duration to displayed precision. These constant-input runs measure throughput and paired behavior, not all-rate RF rejection.

RTL input samples/sec To 192 kHz To 48 kHz To 24 kHz
225001 75.57× 239.27× 384.87×
250000 111.65× 275.43× 423.03×
300000 99.72× 261.56× 412.66×
1000000 70.31× 153.19× 206.06×
1536000 96.59× 142.03× 166.27×
1843200 60.89× 113.09× 135.80×
2000000 54.12× 101.05× 123.01×
2400000 49.77× 91.58× 108.21×
3000000 37.25× 70.52× 84.40×

Appendix B — Source and evidence index

Application source links below are pinned to the tested commit:

Companion evidence bundle contains FEASIBILITY.md, the three standalone C++ probes and CMake definition, analyze_captures.py, final result logs, USB/capture logs, RF survey, and the extracted upstream guide. Numeric results are included above so this draft remains understandable without private host access. Raw RF and intermediate audio are retained on the development host; no public attachment or upload is implied by this draft. Hashes and reproduction notes accompany the local bundle.


73, Ozy K6OZY · GPT-6 Astra-Ultra

Operator amendment: eight-receiver Nobara evaluation and SQL correction (2026-09-28)

The operator now requests eight simultaneous analog receivers in the isolated Nobara evaluation, replacing the earlier four-receiver evaluation target. Their approximately 3% laptop CPU observation with four is context, not a measured eight-receiver capacity or release qualification. This amendment is recorded for maintainer review; it does not claim maintainer approval, change the qualified/default capacity, authorize multiple digital decoders, or expand Mac/Windows/public distribution.

The immediate defect report is high manual SQL values needed to silence noise and non-working Auto SQL. Preserve the operator's before-state: A 460.300 MHz FM-N SQL 54; B 460.275 MHz FM SQL 51; C 460.1754 MHz FM SQL 51; filters ±8 kHz, all unmuted/volume 100, B selected, saved Auto off. The identified private instance was captured read-only, its database backed up consistently, and then closed cleanly with operator authorization. RF gain was 23 dB, capture 460.9 MHz / 2.4 MS/s; these are observed settings, not calibrated RF measurements.

Source diagnosis found that the 65536-bin display estimator drives a 2048-bin audio detector despite their different noise-bin power, and that a trimmed logarithmic noise average is biased low for a peak gate. The correction under focused validation moves RTL Auto measurement to each receiver's existing acquisition gate using its detector spectrum, a local noise estimate excluding the desired passband, and independent Auto/margin state. It uses typed model/backend control and accepted per-slice persistence. Manual -120 + 1.2 × level mapping is retained pending real station comparison; no guessed offset is applied. WFM keeps its separate decoder semantics. A threshold from a different measurement bandwidth must not be shown as an absolute line on the display spectrum.

Acceptance for this evaluation: eight stable receiver paths; selected-slice control and per-slice SQL/mute/frequency isolation; mixed analog WFM presentation; park/resume and persistence; a representative longer receive-only continuity run with actual faults, callback timing and thermal observations reported separately. Historical strict zero-timing/thermal screens are not automatic blockers to this operator evaluation. Development/testing is sequential and proportional; no new routine agent fan-out or broad unrelated suites.

TCI has an existing typed per-slice PCM consumer in source. Eight-client/external-streaming throughput remains to be measured. RTL does not provide Flex DAX/DAX-IQ streams, and no RTL DAX or RadioReference/Broadcastify publishing support is claimed by this amendment. Results and exact candidate identity will follow after focused validation; no merge is requested here.

Nobara SQL/eight evaluation results — 2026-09-28

Final local signed candidate: 48b9afeeb4ba75ec6899c0cfd460e95fa1c89225, tree 751ed3b0db0d392fe46c4de58d0471180419d56d (Ozy311 signature verified). Seven focused native tests passed, including generated-IQ SQL behavior, eight-slot model/restore checks and the 24-case RX applet suite; isolated package startup also passed. The detector-bypass mutation fails. A final fresh-process check found and fixed an additional UI manual-memory restore defect: accepted SQL54 was retained by the backend but the attached Auto UI remembered20. Its regression fails before the fix; final hardware Auto→Off→Manual returns54. Manual scale/defaults are unchanged.

Old/new three-station comparison retained the operator's460.300000FM-N/54,460.275000FM/51,460.175400FM/51 baseline. Old display Auto selected about39 and produced sustained audio; new engine Auto closed the final15-second comparison window on all three. Live RF was sequential and intermittent, not identical recorded IQ or acoustic proof. All eight controls were exercised; H's threshold/margin/tuning/mute changes preserved A–G recipes. Explicit target tuning parked A–G and restored all eight. Final fresh-process restoration retained all eight Auto/manual/filter/monitor recipes.

The ten-minute processing run on ancestor 3d7fea2 delivered28,827,648 frames on every receiver and the speaker mix with zero observed queue drops, mixer faults, withdrawals, nonfinite samples or PCM discontinuities. It recorded394 deadline overruns/175,951 callbacks; mean1.116ms. Process CPU averaged64.63% of one core (~3.23% across20 logical CPUs), RSS859.5→905.6MiB and package-temperature samples70–100°C. This establishes neither a memory plateau nor thermal qualification. The preceding Center-intent parking exercise left H at converter DC (captureDcClear=false), so this is processing continuity, not eight clean RF signals.

The final 48b9afe changes only model manual-memory adoption plus its regression test; detector/DSP/mixer code is unchanged. Its separate60.064-second live interval restored the original460.9MHz capture center with all passbands clear of DC:2,883,584 frames per path, zero observed continuity/fault deltas,392 deadline overruns/17,601 callbacks (mean1.174ms). Both timing results are retained. Both owned runs closed normally; normal profile/launcher hashes were unchanged. The delivery profile keeps only the operator's original three station recipes; capacity is eight and test stations stay in a separate profile.

The local sealed48b9afe package, bound launcher, source bundle and detailed evidence report are retained on Nobara. Bundle SHA-256:41591f452851e7bfc100888aadf8308f579029e3f08ad70750d3de87ebf09054 (base7911324a). This remains an isolated evaluation for maintainer review, with qualified default one. TCI's typed per-slice route was audited, not eight external clients; RTL DAX/DAX-IQ and RadioReference/Broadcastify publishing are not supplied. No PR, merge, public release or Mac/Windows qualification is requested or claimed here.

Activity

  1. self-assigned this
    on Sep 7, 2026
  2. aethersdr-agent commented on Sep 7, 2026

    @aethersdr-agent
    Contributor

    Thanks for filing this. This issue involves CI/CD workflows, build infrastructure, or release packaging — that is outside what I can help with, as I am restricted to source code changes in src/ and docs/.

    Jeremy will need to handle this one directly.

    — AetherClaude (automated agent for AetherSDR)


    🤖 aethersdr-agent · cost: $43.1456 · model: claude-opus-5

  3. Ozy311 commented on Sep 7, 2026

    @Ozy311
    ContributorAuthor

    @pcarff FYI and feedback.

  4. ten9876 commented on Sep 7, 2026

    @ten9876
    Collaborator

    Approved, conditionally — proceed on the sequence you proposed

    This is the best-evidenced RFC this project has received. Before the conditions, the part that earned the approval: every load-bearing source claim in here checks out against the tree. I verified eight of them independently.

    Your claim Verified
    "filter edges are stored but not applied" RtlSdrDdc.cpp:96-97 stores m_filterLowHz/m_filterHighHz; nothing reads them — those two stores are the only hits in the file
    "Resampler::processStereoToStereo() averages its inputs" Resampler.cpp:141-155 — downmix to mono, resample, duplicate back. Unusable for IQ or stereo, exactly as you say
    "the neutral backend interface has pan lifecycle methods but lacks an equivalent slice-create/remove path" IRadioBackend.h:329-330 has createPanadapter()/removePanadapter(); there is no createSlice/removeSlice
    "RTL's 0.45 × sampleRate center-only guard" RtlSdrBackend.cpp:446
    "Raising maxSlices alone is insufficient" RtlSdrBackend.cpp:123-124 — maxSlices = 1, maxPanadapters = 1
    "Current backend PCM is 24 kHz stereo" AudioEngine.h:88
    hl2_receivers_test, hl2_receiver_churn_test, hl2_shift_test as prior art All three registered in tests.cmake — worth noting given #5452 just retired eight tests
    WDSP's WBFM decoder is already vendored third_party/wdsp/upstream/wbfm.c; WdspChannel::Mode::Wbfm exists but carries no stereo/pilot/deemphasis surface, so your "new wrapper surface needed" framing is right

    The epistemic discipline is what makes this reviewable. Separating what you measured from what it establishes, disclosing the pilot-quadrature test-vector error as superseded rather than quietly dropping it, and refusing to advertise eight receivers off an eight-chain load test are all the opposite of how these usually arrive. You also volunteered the test-layer boundary rule without being asked. Thank you.

    The architecture is approved as proposed — shared capture / viewport / slice separation, multi-RX in the data model from the start rather than retrofitted, selective Hermes reuse rather than wholesale copying, no new dependencies, and the full-passband shared-tuner acceptance/refusal behavior including the whole-set validation on rate changes. Answers to your six decision questions: 1 yes, 2 yes, 3 yes, 4 yes, 5 yes to 48 kHz as the final target — skip the 24 kHz interim, 6 yes. Proceed on the four-stage sequence you laid out.

    Eight conditions. Six are revisions to the RFC body before the first PR; two are new release gates. None of them change the direction.


    1. Slice persistence — shape specified below, and there is a problem underneath it

    The RFC enumerates per-slice state (frequency, mode, filter, AGC, squelch, audio level/mute/balance) and then never says how any of it survives a restart. "Persistence" appears once, in the sequencing list. That is a Principle V design question, not an implementation detail, and I am specifying it rather than leaving it to PR review — because drafting it surfaced a blocker.

    RadioSettingsScope documents radioId as "the family's canonical identity — Flex serial, HL2 MAC, Kiwi profile UUID". RTL does not reliably have one:

    • RtlSdrBackend.cpp:252-260 — when rtlsdr_get_device_usb_strings() fails, m_serial = QString::number(idx), the USB enumeration index.
    • RtlSdrBackend.cpp:196 — the match path already concedes the empty-serial case, falling back to rtl:<index>.

    Most cheap R820T dongles ship with an empty or factory-identical serial. For a large share of users the RTL radioId is therefore USB port order, which changes on replug and when a second dongle appears. Scope slice state to that and a saved four-slice VHF set silently reattaches to the wrong dongle — and two identical dongles share one row.

    The shape:

    • Feature RtlSlices, kSchemaVersion = 1, written through RadioModel::settingsScope(), owned by a single RtlSliceSettings class in the shape of Hl2FilterBoardSettings — read-modify-write against featureExact, write result checked, qWarning on refusal.
    • Identity rule: use the reported serial as radioId only when it is non-empty and is not the index fallback; otherwise write the family-wide row (empty radioId). The scope already falls back exact → family-wide → empty, so this costs nothing structurally. It does need a small change near RtlSdrBackend.cpp:260 so a synthesised index stays distinguishable from a real serial instead of being laundered into one.
    • Document: { captureCenterHz, sampleRateHz, slices: { "<stableId>": { freq, mode, filterLow, filterHigh, agcMode, agcThreshold, squelch, audioGain, audioMute, audioPan } } }. Keyed sub-objects, not an array, so removing a middle slice does not renumber its siblings — your §3 already requires IDs stay stable through churn.
    • Restore rule, and this is where it meets your §1: validate the entire restored set against the current usable capture interval before applying any of it. Restore the subset that fits, drop the rest with a qWarning, and never silently retune to accommodate them. Restore is a move request with more slices, and it gets the same refusal policy you specified for live moves.

    2. Rate-aware audio — approved at 48 kHz, and here are the four sites

    Your instruction not to globally change AudioEngine::DEFAULT_SAMPLE_RATE is correct, but that constant is not where the risk lives. Four hardcoded 24000 literals in AudioEngine.cpp bypass it entirely:

    • 1774 — constexpr int kTargetHz = 24000;
    • 4328, 4331 — the L/R RX resamplers
    • 9444 — the RADE RX resampler

    All four construct Resampler(24000, m_rxOutputRate.load()). And m_rxOutputRate is the device rate, set from candidate.sampleRate() at 3970 — so the engine's existing rate awareness is on the wrong side of the seam from what you need. It adapts fixed 24 kHz producer PCM to whatever the sound card wants; you need the producer rate to vary. Anyone grepping for DEFAULT_SAMPLE_RATE to scope this work will miss three of the four sites.

    This is the single most likely source of a silent, hard-to-diagnose regression in the whole plan. Treat those four literals plus m_rxOutputRate's direction as the explicit inventory in the rate-aware seam PR, and carry regression coverage for existing Flex 24 kHz and Hermes/ANAN audio in the same PR. No 24 kHz interim milestone — you are right that it must not be described as complete WFM stereo, and the cleanest way to guarantee that is not to ship it.

    3. aarch64 needs a probe before the receiver limit is fixed

    The cross-platform section covers Linux/macOS/Windows desktop and never mentions ARM, but we ship an aarch64 AppImage and the README advertises Raspberry Pi. Four WDSP channels at 192 kHz DSP is a very different budget there. Rerun the multi-chain probe on aarch64 and set the advertised receiver limit per-architecture from the measurement. If you do not have ARM hardware, say so and we will find someone who does — do not guess, and do not let the x86-64 number become the advertised limit by default.

    4. Component ratios accepted; integrated CPU budget becomes a release gate

    The 7.20× and 3.69× figures are adequate evidence for approving a direction, so I am not holding the RFC on them. But they are aggregate per-chain DSP only — your own text excludes USB, FFT/waterfall, speaker mixing and GUI, and at 2.4 MS/s the display path is plausibly comparable in cost to the demod. Add to the acceptance table: worst-case CPU with GUI, FFT, USB and speaker mixing all active, at the target receiver count, on each architecture. A headroom budget, not another ratio.

    5. Cold start needs an engineered warm-up, not a measurement

    You correctly flag AETHER_WDSP_FFTW_TIMELIMIT=0.001 and the isolated wisdom directory as not a shipping configuration. Rather than just measuring the cold-cache cost, specify the strategy in the design: pre-planned or shipped FFTW wisdom, or background/off-thread planning, so first connect with four channels is not gated on synchronous planning. This interacts with #5417 — the isolation recipe there destroys the wisdom cache — so coordinate with it rather than around it.

    6. Name the capability fields in the RFC

    You are careful never to family-check, but the RFC never says how the GUI learns RTL has stereo, regional deemphasis, or independent viewport zoom. Add the proposed RadioCapabilities fields to the RFC body along with their ADDING-A-FIELD obligations — set explicitly in every backend, a row in docs/architecture/radio-capabilities-map.md, and a gating test that also asserts the flags clear on a family swap. Settling caps shape up front matters because it drives GUI structure, and late changes there are expensive.

    7. Accessibility is a release gate

    New stereo/pilot indicator, deemphasis selector, per-slice audio controls and zoom UI, with docs/a11y.md unmentioned. Add to the acceptance table: accessible names and QAccessibleValueChangeEvent on every new interactive control, no interactive QLabels. This is a commitment I have made in writing to a blind operator (#4896), it is cheap when planned from the start and expensive when retrofitted, and it is not negotiable for new UI.

    8. Route validation through the bridge and radiocert

    Your refusal to add a synthetic radio peer to the default test graph is correct and appreciated, as is routing positive reception to real hardware. But the acceptance table names neither the automation bridge nor radiocert, which are the mechanisms canon names for GUI convergence and receive-path liveness. Add both explicitly: bridge verbs for slice lifecycle, zoom and routing convergence, radiocert for the receive half. This matters more than usual here — it is what makes your gates checkable by someone who does not have your host and your dongle.


    What this means practically

    Revise the RFC body for 1, 2, 3, 5, 6 and 7, add the gates from 4, 7 and 8 to the acceptance table, and open the foundation PR. You do not need a second approval round; if the revisions match the above, the direction is settled and PR review takes it from there.

    Two notes on process. Keep PRs one-feature-and-its-tests rather than one-milestone — the foundation stage in particular will want splitting between the neutral slice lifecycle seam and the shared-window policy, since the former touches Flex behavior and deserves its own regression review. And follow your own instruction to keep the existing WFM path working until the replacement passes comparison tests; a period where RTL is worse than it is today is the one outcome that would make me regret approving this.

    The aethersdr-agent comment above is a triage misfire — this is not CI/CD work. Ignore it; I will look at the classifier.

  5. Ozy311 commented on Sep 7, 2026

    @Ozy311
    ContributorAuthor

    Updated the RFC body to incorporate the eight conditions in Jeremy's approval.

    • Added the RtlSlices persistence owner/schema, identity handling, and fitting-subset restore rules.
    • Made 48 kHz the required replacement audio target, removed the proposed 24 kHz interim, and documented the four cited audio sites and affected consumers.
    • Added native Linux aarch64 measurements, per-architecture receiver limits, and integrated CPU/headroom gates.
    • Specified bounded background DSP preparation and FFTW wisdom handling, including cancellation across reconnects.
    • Named the capability fields and their documentation/family-swap test obligations.
    • Added accessibility, automation-bridge convergence, and receive-only radiocert requirements to the acceptance criteria.
    • Retained the four-stage sequence, with separate foundation PRs for ordinary RX slice lifecycle and shared-window policy. Existing WFM stays usable until its replacement passes comparison tests.

    The original measurement/evidence appendices are unchanged. The new acceptance requirements are future validation gates; this amendment does not claim they have passed. Implementation details added to make the conditions concrete remain subject to normal PR review.

    Next is the ordinary RX slice lifecycle foundation change and its regression tests.

  6. 13 remaining items

  7. jensenpat commented on Oct 2, 2026

    @jensenpat
    Collaborator

    @Ozy311 @ten9876 — clarification requested during the review of #5924/#5919: this amendment needs to address the shared AetherSDR UX goal of the RadioModel/backend separation.

    The whole goal of that separation is to give operators one coherent AetherSDR experience across radios. The backend owns protocol details, capture topology and hardware constraints; RadioModel exposes neutral capabilities and observed state; the shared UI presents controls with consistent meanings. Operators should not have to learn a different meaning for the same control whenever they connect another radio. Different hardware capabilities are real, but the shared interaction contract must explain those limits and their consequences.

    The proposed change affects both UX and reception. Under the previously approved whole-set policy, a capture move that cannot retain the receiving slices is refused. Under the September 25 amendment, dragging the spectrum beyond the current RTL capture retunes that capture, preserves the slice's RF/settings, marks it OUT OF CAPTURE, and suspends its audio and decoder samples. Bringing its complete passband back into capture resumes reception. A gesture used to browse the spectrum therefore gains the ability to interrupt listening without changing the slice frequency or its mute setting.

    The amendment explicitly leaves other backends' existing tuning behavior unchanged. That makes the product question larger than whether RTL can implement parking correctly: does the same shared pan/browse control now carry a different reception promise depending on the connected backend? Neutral inCapture observations and capability-gated dispatch keep the code separated, but they do not by themselves establish a consistent UX.

    The existing Bandscope window, available on HL2 through the neutral wideband-converter capability, is relevant precedent: it displays a separate wideband snapshot while the listening receiver stays in place. It has no pan/click-to-tune interaction, so it is not an identical gesture implementation. It does show that viewing RF outside the listening slice and moving the listening capture are distinct operations. RTL's single capture cannot supply an independent listening stream just because the UI is shared; that physical limitation should be expressed through a common product contract rather than implicitly redefining browsing for RTL.

    Please make the RFC's decision explicit:

    • Define the common meaning of viewport browsing versus repositioning a receive capture, including whether an ordinary browse gesture is allowed to suspend reception.
    • State how a shared-capture limitation is communicated before or as the operator requests the move, alongside the observed parked state and a consistent recovery action. Define what happens when all slices would be parked.
    • Describe that contract by capture capabilities/topology, so any later backend with the same constraint follows it. Explain how the existing Flex, Icom and Hermes interactions fit the contract; preserving their current code alone does not answer the UX question.
    • If reception-interrupting free browsing is the intended shared behavior, approve it as that product decision and specify its applicability. If browsing is intended to preserve listening, explain how the capture-moving operation is distinguished or refused when hardware cannot provide it.

    This is a request to settle the common interaction contract, not to require identical hardware capabilities or force a particular implementation. The RTL capture/DSP work and already-ratified seam changes can be assessed separately from choosing this UX policy. HD Radio/consent is outside this question.

  8. Ozy311 commented on Oct 3, 2026

    @Ozy311
    ContributorAuthor

    @ten9876 @jensenpat — I have reconciled the analog stack with main 732b99a9 and addressed the small review fixes. The existing PRs remain #5924 → #5973 → #5974 → #6082 → #6083. Digital stays separately deferred in draft #6002 / RFC #6079. The rebuilt analog application and 43 selected Mac regressions pass; release packaging is being validated in #6083. No merge has been performed.

    The remaining product decision from Pat's comment is specifically the boundary between viewing and moving capture:

    • Moving a viewport inside captured RF, or zooming a slice offscreen, preserves reception.
    • My requested RTL behavior lets the same drag continue beyond capture by repositioning the shared capture. Slice frequency/settings stay parked; only complete-passband members receive. All slices may be parked while the spectrum continues. Revealing a parked slice moves capture back to its unchanged frequency and resumes it after adoption.
    • That reception consequence must be clear at the transition. The current implementation reports OUT OF CAPTURE after confirmed adoption; it does not yet provide a separate advance warning/confirmation for crossing the capture boundary. I am not presenting the parked label alone as proof that the advance-communication question is settled.

    The code comparison matters: Flex sends a pan-window command independent of slice tuning; HL2 moves the selected DDC and reapplies its slice shift, with radio-wide span; Icom distinguishes zoom from drag and retunes its VFO for a drag. HL2's separate Bandscope is a non-tuning snapshot. These are source observations at the reconciled head, not new hardware tests or a claim that those interactions already form one uniform contract.

    Please rule on whether ordinary drag may become a reception-interrupting capture move on shared-capture hardware, and whether that needs a visible boundary/advance notice or a distinct explicit capture-move action. I favor keeping the free drag with a clear capture-boundary/consequence indication. The rule should follow capture topology per stream, not a radio-family name; independent display windows retain their reception guarantee. Once that interaction is chosen, I can make the narrow UI/documentation adjustment and its regression test without reopening the already-ratified seam or the other increments.

    73, Ozy K6OZY · Codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions