|
| 1 | +# Producer PCM contract (RFC #5468 A1) |
| 2 | + |
| 3 | +`PcmFrame` in `src/core/PcmFrame.h` is the in-process RX producer envelope. |
| 4 | +It is not an aetherd wire resource or a sound-device format. A1 keeps the current |
| 5 | +24 kHz stereo audio paths active. The new RTL WDSP 48 kHz producer remains disabled; |
| 6 | +A2–A5 must qualify playback, recording, TCI and fixed-rate decoders before M1/S2 |
| 7 | +activate it. No radio capability or default changes in this step. |
| 8 | + |
| 9 | +## Format, identity and ownership |
| 10 | + |
| 11 | +A frame carries owning, native-endian interleaved float32 samples, an immutable |
| 12 | +24/48 kHz mono/stereo format, and separate source, connection session, |
| 13 | +format-generation, receiver-instance and stable slice-slot identities. |
| 14 | +Speaker mixes and auxiliary sources have no slice slot. A slice stream accepts |
| 15 | +F4 `Handle::session`, `Handle::instance` and `Handle::slot` as separate values; |
| 16 | +legacy adapters use process-local identities until the native producer lands. |
| 17 | +These identifiers are not persistence keys. DAX channels and Kiwi source IDs |
| 18 | +travel alongside their frame and retain their existing meanings. |
| 19 | + |
| 20 | +`firstSample` counts sample frames (LR pairs for stereo), not floats or bytes. |
| 21 | +It advances only on accepted input. A start or accepted format change resets its |
| 22 | +origin and marks the first frame discontinuous. Backward positions, overflow and |
| 23 | +repeated or older explicit |
| 24 | +sessions are refused. Legacy adapters count accepted output samples; this does |
| 25 | +not reconstruct missing capture timestamps or establish multi-receiver alignment. |
| 26 | + |
| 27 | +Only `PcmProducer` constructs valid frames. It rejects empty, oversized (more than |
| 28 | +65,536 sample frames), incomplete-channel and nonfinite input. Finite values, |
| 29 | +including signed zero and peaks outside ±1, are preserved without clipping. |
| 30 | +Qt queued delivery copies the samples and metadata together with shared ownership; |
| 31 | +borrowed buffers are detached at publication. Consumers have no mutable access. |
| 32 | + |
| 33 | +An epoch token contains immutable metadata and an atomic live bit, with no radio, |
| 34 | +worker, DSP or QObject ownership. Stop, reconnect, format change and slice retirement |
| 35 | +revoke old tokens. A queued frame stays invalid even after the same slot or rate is |
| 36 | +reused. `PcmFrameGate` independently rejects stale, replayed and out-of-order frames |
| 37 | +for each consumer, with at most 32 live stream cursors. Inactive entries are reused. |
| 38 | + |
| 39 | +The gate is deliberately one-sided: it refuses a frame at or behind the cursor and |
| 40 | +admits one ahead of it. A forward gap means the consumer missed frames, which every |
| 41 | +consumer that can be detached from a running producer does legitimately — playback |
| 42 | +mute detaches the Flex speaker feed and short-circuits the seam-backend feed while |
| 43 | +the producer keeps counting. Because a cursor only advances on an accepted frame, |
| 44 | +refusing that gap would strand the consumer behind a live epoch with no way back, |
| 45 | +silencing RX until the next reconnect. Replay protection comes from the backward |
| 46 | +check plus epoch liveness, neither of which a forward gap weakens. |
| 47 | + |
| 48 | +Each producer and consumer gate has one execution context. Only token revocation |
| 49 | +may overlap production/delivery; start, format change and destruction remain |
| 50 | +serialized with the producer, and its caller joins it before destruction. A |
| 51 | +revocation rejects a later admission; it cannot retract samples already admitted |
| 52 | +into an existing AudioEngine processing/device queue. Queue flushing and rate-domain |
| 53 | +transitions are A2 work. A1 does not claim bounded end-to-end latency or lock-free |
| 54 | +real-time performance: it adds ownership copies and finite-sample validation. |
| 55 | + |
| 56 | +## Compatibility boundaries |
| 57 | + |
| 58 | +`legacyStereo24()` refuses revoked frames and every format except 24 kHz stereo. |
| 59 | +It copies sample bits unchanged; it never converts or relabels 48 kHz or mono input. |
| 60 | +Fixed-rate consumers unwrap at the receiving callback, after queued delivery. |
| 61 | + |
| 62 | +| Producer / route | A1 adapter and retained behavior | |
| 63 | +|---|---| |
| 64 | +| Flex LAN float/reduced-bandwidth/Opus | `PanadapterStream` publishes typed speaker PCM after the existing decode/concealment. Float alignment/finiteness is checked before concealment history. DAX registration owns revocable per-stream tokens. | |
| 65 | +| Hermes-Lite | The current DSP instance is checked before accepting queued worker output. Per-slice validation precedes the existing mixer. Its unity fast path, gain/balance, alignment and sum/clamp behavior stay unchanged. | |
| 66 | +| ANAN | `AnanRxDsp` tags its actual configured output rate before worker-to-owner delivery and revokes old tokens on channel installation. The production backend still requests 24 kHz, with the existing single-receiver speaker and slice outputs. | |
| 67 | +| Icom | The current session instance is checked; malformed/nonfinite mono input is refused before the existing 48 kHz mono to 24 kHz stereo converter. | |
| 68 | +| RTL | The current legacy worker is checked before publication. Existing DDC/demodulation, 24 kHz output, WFM behavior and slice-0 runtime remain in use. F4 preparation/registry is not activated as the audio producer. | |
| 69 | +| Simulator | Worker output has a connection epoch before queueing; the backend checks that epoch against its session before publishing. Existing pacing and speaker/slice samples remain unchanged. | |
| 70 | +| Kiwi | Existing resampling, silence, squelch and loss padding precede typed publication. Socket cleanup revokes queued audio; AudioEngine has an independent auxiliary ingress gate. | |
| 71 | + |
| 72 | +`IRadioBackend` publishes typed speaker and per-slice signals. Its compatibility |
| 73 | +publishers initialize on `connected`, revoke on `disconnected`/slice removal, and |
| 74 | +refuse further publication from retired streams. `RadioModel` retires them before |
| 75 | +backend teardown and uses the same guarded PCM bindings for production and test |
| 76 | +injection. No new backend-family decision is introduced. |
| 77 | + |
| 78 | +The existing single-producer choices remain: Flex stream playback, backend-owned |
| 79 | +speaker playback, and the simulator's existing direct speaker route. The normalized |
| 80 | +`rxDemodAudioReady` bus remains a separate subscriber for recording/CW/RTTY. Per-slice |
| 81 | +TCI/AetherClock, Flex DAX/RADE and concurrent Kiwi routes retain their attribution. |
| 82 | +Every production route uses the typed signals. `PanadapterStream`'s byte-valued |
| 83 | +`audioDataReady`/`daxAudioReady` are removed rather than retained: after the |
| 84 | +migration nothing in the tree connected to them, their arguments were still being |
| 85 | +deep-copied per audio block, and they bypassed the gate — so any later consumer |
| 86 | +wired to them would silently have skipped admission control. |
| 87 | + |
| 88 | +No A1 adapter sets `discontinuity` after the first frame of an epoch: each one |
| 89 | +publishes contiguous positions, so a lost Flex UDP audio packet is presented as |
| 90 | +continuous even though packet-loss concealment detected it. Propagating that would |
| 91 | +make AudioEngine retire and reset the chain on every lost packet, which is an |
| 92 | +audible-behaviour change rather than a wiring fix, so it belongs with the later |
| 93 | +milestones that own playback policy. Today the field is exercised by tests and by |
| 94 | +explicit-position producers only. |
| 95 | + |
| 96 | +AudioEngine's original byte APIs remain for legacy internal/playback callers. Its |
| 97 | +new typed entry points reject incompatible formats and duplicate/stale frames, |
| 98 | +then call the existing processing functions. The main RX L/R resamplers, TX CW |
| 99 | +sidetone adapter, decoded RADE speech adapter and device-rate state are unchanged. |
| 100 | + |
| 101 | +## Validation scope |
| 102 | + |
| 103 | +`pcm_frame_test` covers format, ownership, bounds, identity, continuity, queued |
| 104 | +revocation and replay/capacity rules without sockets or devices. |
| 105 | +`pcm_compatibility_test` injects the production backend/model/AudioEngine route, |
| 106 | +Flex decoders/DAX registration, and the Hermes mixer without a firmware peer or |
| 107 | +hardware connection. Existing simulator and ANAN DSP tests cover paired output |
| 108 | +and actual producer rates. Tests are registered in `tests/tests.cmake`; no frozen |
| 109 | +PR CI allow-list is expanded. Build, sanitizer and mutation results belong in the |
| 110 | +A1 execution report, with their host/configuration and hardware limits. |
0 commit comments