aethersdr / aethersdr/AetherSDR
Smart Squelch via libfvad — voice-aware discriminator layered on adaptive squelch
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 221
- Forks
- 117
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 299
Description
Background
Phase 2 of the client-side DSP roadmap. Tracks a focused implementation of voice-aware squelch as an opt-in layer above the existing power-based adaptive squelch.
The discriminator gap
Current state (SpectrumWidget.cpp:2683-2689) ships a power-based adaptive squelch: two-pass trimmed-mean noise-floor estimation + EWMA α=0.1 smoothing + configurable margin in dB. It correctly tracks moving band noise. It does not discriminate signal content — anything above the noise floor opens it.
Failure modes from #85 Phase 2 that the current squelch doesn't address:
| Trigger | Adaptive squelch (now) | Voice-aware squelch (proposed) |
|---|---|---|
| Steady carrier (CW unkey, AM dead air) | ❌ opens (above threshold) | ✅ stays closed |
| TX key-up noise on another channel | ❌ opens | ✅ stays closed |
| Data signals (FT8/RTTY mid-tone) | ❌ opens (treats as signal) | ✅ stays closed |
| Speech signal | ✅ opens | ✅ opens |
For SSB monitoring this is the kind of squelch behavior operators actually want — open on speech, stay closed on the rest.
Library choice — libfvad
The roadmap listed libfvad (BSD-3) and SpeexDSP (BSD-3) as candidates. Picking libfvad:
- Purpose-built standalone VAD extracted from WebRTC (Google's continuously-maintained codebase).
- WebRTC's VAD has been studied extensively and outperforms older Speex VAD on speech-detection accuracy.
- Small dependency footprint vs. SpeexDSP which bundles AEC + denoise + resampler + jitter buffer (none of which we need).
- Speex's VAD is from a codec largely superseded by Opus; less actively maintained.
- Same BSD-3 license as SpeexDSP, no licensing differential.
Repo: https://github.com/dpirch/libfvad
Frame sizes: 10, 20, or 30 ms. Sample rates: 8/16/32/48 kHz. Four aggressiveness modes (0-3 → least to most aggressive about declaring non-speech).
Implementation sketch
-
Vendor libfvad under
third_party/libfvad/(matches the project's vendor pattern — seethird_party/rnnoise/,third_party/r8brain/, etc.). CMake target via subdirectoryadd_subdirectory(third_party/libfvad). -
Wrap in
src/core/VoiceActivityDetector.{h,cpp}— thin RAII wrapper aroundFvad*handle. Process 10 ms frames at 48 kHz. Returns voice/no-voice + per-decision confidence (libfvad gives binary; we can smooth with hangover timer for hysteresis). -
Hook into the existing audio path in
AudioEngineafter demod, before the squelch gate. The decision feeds an additional gate that's AND'd with the existing power-based squelch. -
UX shape: extend the current squelch mode selector to Off / Adaptive / Adaptive + Voice. The voice layer is purely additive — when enabled, gate stays closed unless both the power-based gate AND the VAD agree.
-
Hangover timer (e.g., 300-500 ms hold-open after last VAD=true frame) — prevents the gate from chopping during inter-word silences in speech.
-
Aggressiveness setting exposed in Radio Setup → DSP tab (slider 0-3, with descriptive labels: "Permissive / Balanced / Aggressive / Strict").
Open questions for implementation PR
-
Per-slice VAD instances vs. one global VAD?
→ Per-slice is correct (different slices may have different speech vs. noise content), but the cost is N × Fvad state. libfvad state is tiny so per-slice is fine. -
Where exactly does this fit in the DSP chain order? Before/after NR2 + RN2?
→ After NR2/RN2/NR4, just before the final squelch gate. NR cleans the audio; VAD then sees a cleaner discriminator signal. -
Behavior on non-SSB modes (CW, AM, FM, FT8/data)?
→ Auto-disable the voice layer when slice mode is anything other than USB/LSB/AM. CW and data should not be gated by a speech VAD. -
Confidence smoothing: libfvad returns binary; pure binary will jitter at speech onsets. Hangover timer (option 5 above) is one approach; integrating decisions over a sliding window (e.g., 5-of-7 frames must say voice to open) is another. Start with hangover; evaluate during dogfood.
Acceptance criteria
- Squelch mode selector adds "Adaptive + Voice" option
- When enabled on an SSB slice with a steady tuned-up CW carrier, the squelch stays closed (current squelch opens)
- When enabled on an SSB slice with normal voice traffic, no audible chopping during words or short pauses
- Aggressiveness control exposed and persisted
- Auto-disable on CW/data/FM modes — voice layer becomes a no-op when slice mode isn't speech-bearing
- No new flat-key AppSettings (Principle V — settings nest under a
VoiceSquelchJSON block) - Cross-platform: libfvad builds on Linux/Windows/macOS via the existing third_party CMake pattern
Out of scope
- Replacing the existing adaptive squelch — the voice layer is purely additive
- PA3FWM spectral-tilt VAD — different approach, would be a separate issue if libfvad turns out to be inadequate in dogfood
- Acoustic echo cancellation (the SpeexDSP angle) — no current need
Parent
Tracks Phase 2 of #85.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading src/gui/SpectrumWidget.cpp around lines 2683-2689 and tracing the AudioEngine audio path and existing third_party CMake patterns. Review the proposed VoiceActivityDetector wrapper, squelch selector, and Radio Setup DSP settings before resolving the listed DSP-order and VAD-state questions. Done means the acceptance criteria pass across supported platforms and non-speech modes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cmake, cpp
- Domain
- audio-video-rtc, desktop
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100