Follow-up from PR #2732 review (packet-loss concealment).
`PanadapterStream::applyConcealmentFade` is a pure function: input PCM (`QByteArray` float32 stereo) + `AudioPlcState` (tail samples, frame count, pending-missed count) → output PCM with cosine-faded silence prepended for the gap. The math is simple but easy to break (window-size mismatches, off-by-one in fade boundaries, wrong stereo interleave) and currently has zero test coverage.
Suggested cases
- No loss path: `pendingMissed = 0` → output equals input, tail/frame-count updated.
- First-packet path: `lastFrames = 0` (no prior good packet) → output equals input, no concealment prepended.
- One-packet loss: `pendingMissed = 1`, `lastFrames = 240` → output is 240 frames of concealment + 240 frames of input.
- Fade-down envelope: feed constant-1.0 stereo input with a known cached tail, verify the first 48 output frames decrease monotonically from `tail × 1.0` to `tail × 0.0`.
- Fade-up envelope: same setup, verify the last 48 frames of the concealment+input boundary fade from 0.0 to `input[48] × 1.0`.
- Multi-packet loss: `pendingMissed = 3`, verify `fillFrames = 3 × lastFrames` and zero-pad fills the middle correctly.
- Cap behavior: `pendingMissed = 99` doesn't actually happen (caller caps to `kMaxConcealPackets`), but worth a test that the function doesn't blow up if it does.
- Tail update: `plc.tailL` and `plc.tailR` end up as the last two samples of the function's output (not the input's last two if concealment fired — they should match the new input's last samples either way).
Why it's worth doing
The function is on the audio hot path. Future edits (adjusting fade length, adding configurable cap, swapping cosine for a different window) need a regression guard. Tests against canned float arrays are cheap.
Scope
New test executable `packet_loss_concealment_test` in `tests/`. Follows the existing pattern of `slice_label_test` etc. Pure C++, no Qt Widgets — just `Qt6::Core` + the math.
Wrinkle to handle
`applyConcealmentFade` is a private member function. Either:
- Make it package-private (move the body to a free function in an anonymous namespace, expose a thin wrapper for the test), or
- Make the test friend it, or
- Make it static/public.
The cleanest option for testability is option 1 — extract the pure math into a free function and have the member wrap it. Mirrors how `MeterSmoother` was structured.
🤖 Spotted by Claude reviewing PR #2732; carried forward at maintainer (KK7GWY) request.
Follow-up from PR #2732 review (packet-loss concealment).
`PanadapterStream::applyConcealmentFade` is a pure function: input PCM (`QByteArray` float32 stereo) + `AudioPlcState` (tail samples, frame count, pending-missed count) → output PCM with cosine-faded silence prepended for the gap. The math is simple but easy to break (window-size mismatches, off-by-one in fade boundaries, wrong stereo interleave) and currently has zero test coverage.
Suggested cases
Why it's worth doing
The function is on the audio hot path. Future edits (adjusting fade length, adding configurable cap, swapping cosine for a different window) need a regression guard. Tests against canned float arrays are cheap.
Scope
New test executable `packet_loss_concealment_test` in `tests/`. Follows the existing pattern of `slice_label_test` etc. Pure C++, no Qt Widgets — just `Qt6::Core` + the math.
Wrinkle to handle
`applyConcealmentFade` is a private member function. Either:
The cleanest option for testability is option 1 — extract the pure math into a free function and have the member wrap it. Mirrors how `MeterSmoother` was structured.
🤖 Spotted by Claude reviewing PR #2732; carried forward at maintainer (KK7GWY) request.