AI Noise reduction feature. - #448
Open
vis4573 wants to merge 13 commits into
Open
Conversation
- src/sbitx.c: the spectral subtraction sigmoid used a hardcoded 0.5 midpoint; scaled_noise_threshold was computed from the UI slider but never referenced anywhere. Wired it in as the sigmoid midpoint so the DSP noise threshold field actually affects noise reduction aggressiveness. - src/sbitx_gtk.c: rescaled scaleNoiseThreshold()'s output range from 0.001-0.01 to 0.1-2.0 so it's large enough to meaningfully shift the sigmoid midpoint (old range was ~50x too small to matter against typical SNR values). Verified numerically that the new range produces a real spread of reduction factors across the slider's full travel. - docs/rnnoise-integration-scope.md: scoping notes only, no code, for evaluating RNNoise as a future NR mode alongside the existing spectral subtraction / Wiener ANR stages. Not build-tested end to end (wiringPi/GTK deps unavailable in this environment) - change is small/localized and was verified numerically in isolation. Recommend testing on actual hardware before merging.
Builds natively on ubuntu-24.04-arm (matches Pi 4/5 architecture, no cross-compilation/emulation) on push to main/feature branches and PRs to main. Installs build deps via apt (all available in Ubuntu 24.04 universe, including libwiringpi-dev), builds ft8_lib then sbitx, verifies the binary exists, and uploads it as a downloadable artifact. Verified locally on x86_64 with the same dependency set (this repo's Makefile has no arch-specific flags) - full build succeeds including linking; the only step that fails here is the Makefile's 'sudo setcap' call, because sudo isn't installed in this sandbox. That will work normally on actual GitHub-hosted runners.
New RNN toggle (Menu 1, line 1, next to the other NR controls) applies RNNoise - a small recurrent neural network trained for real-time speech denoising - to the RX speaker audio in voice modes. Selectable independently of the existing DSP (spectral subtraction) and ANR (Wiener) stages; help text advises using one NR method at a time. Implementation: - rnnoise/: vendored xiph/rnnoise v0.1.1 source (BSD-licensed, COPYING included). This version embeds the trained model in rnn_data.c, so no network access is needed at build time. Its FFT symbols are prefixed rnn_fft_*, so no collision with ft8_lib's kiss_fft. - src/sbitx_rnn.c/h: wrapper handling 96k<->48k rate conversion (plain 2:1 decimation - audio is band-limited far below 24 kHz by the RX bandpass; linear interpolation back up), amplitude scaling between sbitx's int32 speaker range and RNNoise's int16-float range, and ring buffering into RNNoise's fixed 480-sample frames. ~10-20 ms latency. - src/sbitx.c: rnn_enabled global; hook in the voice-mode post-processing path after modem_rx (decoders see unprocessed audio) and before the RX EQ. State is reset when toggled off. - src/sbitx_gtk.c: #rnn_plugin toggle field, check_plugin_controls wiring, and menu placement, following the ANR pattern. Works from the console as \rnn on / \rnn off via the generic field handler. - Makefile: rnnoise sources and include path added; ./build sbitx works unchanged since it wraps make. Verified: full build succeeds; functional test feeding 2s of 1 kHz tone + white noise at speaker scale through the wrapper passes signal at matching amplitude (in 1.061e8 RMS vs out 1.063e8) with no overflow or silence. Audio quality needs on-air evaluation on real hardware.
Full-strength RNNoise gating swings band gains between ~0 and 1 as its speech confidence changes, which is audible as volume pumping/breathing, especially on fading (QSB) SSB signals - reported in on-air testing. Standard mitigation: blend the denoised (wet) output with an identically delayed copy of the original (dry) signal. At strength S the output can never drop below (1 - S/100) of the input, bounding the gain swings that cause pumping while retaining most of the noise reduction. - src/sbitx_rnn.c: parallel dry ring buffer kept in lockstep with the denoised output; per-block wet/dry mix from new rnn_strength global (0-100, default 80). - src/sbitx_gtk.c: #rnn_strength number field (RNNS, 0-100 step 5) read periodically in check_plugin_controls so \rnns console edits apply within a second; placed in Menu 1 line 2 under the RNN toggle. - help_commands.txt: RNNS documented with tuning guidance (70-85 for SSB, lower if pumping persists). Verified: build succeeds; noise-only test shows correct monotonic behavior - strength 0 passes input (2.51e7 RMS), 50 halves it (1.26e7), 100 gives ~23x suppression (1.09e6).
- #rnn_plugin now a COMMON_CONTROL at (370,5), directly left of REC on the main screen top row, always visible - no need to open Menu 1. Also positioned in the scaled-UI layout branch. - #rnn_strength (RNNS) appears at (330,5), immediately left of RNN, only while RNN is ON; parked off-screen otherwise. Visibility handled in check_plugin_controls (periodic timer, reacts within ~1s of toggling). Both fields are COMMON_CONTROL so the generic layout hide-loop leaves them alone and check_plugin_controls is the single visibility authority. - Removed the Menu 1 placements (RNN/RNNS no longer live there). - Help text updated with the new location. Build verified (binary links; only the sandbox's missing sudo fails at the setcap step). Touch layout needs eyes-on verification on the 7-inch screen: confirm RNN doesn't overlap REC and RNNS appears/disappears with the toggle.
UI labels and help text only; internal field cmds (#rnn_plugin, #rnn_strength) and code identifiers unchanged. Console commands become \ainr on/off and \ainrs <0-100> via the generic label-matched handler.
rnnoise_process_frame() returns the network's per-frame voice-activity probability, previously discarded. On weak/fading signals that confidence sits in the uncertain middle and the band gains chatter, eating fragile speech. Now the wet mix is relaxed as the smoothed VAD rises: effective_wet = AINRS_mix * (1 - 0.5 * vad_smooth) - Frames the network is confident are noise: full configured suppression (silence stays silent). - Any speech evidence, even weak: suppression depth backs off by up to half, protecting the speech at the cost of a little more noise around it. - VAD smoothing is fast-attack (~30 ms, catches syllable onsets) and slow-release (~0.5 s, rides through inter-word gaps without flutter). - vad_ring kept sample-aligned with the wet/dry rings across block boundaries; state cleared in rnn_reset(). Verified: synthetic speech-like harmonic content + noise shows ~2x more residual noise immediately after speech (relaxed) vs settled noise-only (full suppression), with strength=100. Attack/release/relax constants are first-pass values pending on-air feedback.
On-air testing reported: noise halo between words on weak signals strong enough to feel like AINR was off, plus overall slow fading in/out. Root causes at v1 constants: VAD_RELAX 0.5 blended up to half raw noisy signal in around speech (on weak signals that is mostly noise), and VAD_RELEASE 0.02 (~1s+ practical decay) held the relaxed mix through entire inter-word gaps - producing the halo and a slow pump. v2: VAD_RELAX 0.5 -> 0.25 (shallower dry blend, halo ~halved) and VAD_RELEASE 0.02 -> 0.065 (~150 ms time constant; suppression recovers inside a word gap instead of after it). Attack unchanged. Synthetic verification: residual noise just after speech <2x settled floor and returns to settled level within ~200 ms (was >1 s).
Constants tuning by proxy has hit its limit; the relax depth is now a UI-tunable field so it can be swept live on real weak signals. - rnn_relax global (0-50 percent, default 25) replaces the fixed VAD_RELAX constant; clamped in the mix loop. - AINRV number field (0-50, step 5) appears at (290,5) beside AINRS while AINR is ON, hidden otherwise; read periodically in check_plugin_controls like AINRS. Console: \ainrv <0-50>. - AINRV 0 disables adaptation entirely: with AINRS 100 that reproduces the original full-gate behavior from the first well-received test. - Documented in help_commands.txt. Once a preferred value is found on-air, it can be hardcoded as the default (keeping or removing the control as desired).
On-air observation: AINR performed noticeably better at high IF values. Cause: the wrapper's fixed /16384 scaling. At low IF gain the samples reaching RNNoise are far below the amplitudes the model was trained on (speech at int16-style levels), starving its band-energy and pitch features and degrading its gain/VAD decisions. Fix: track the running RMS of the input (~100 ms smoothing), apply makeup gain toward a 3000-RMS target (clamped 0.25x-64x) before the network, and divide the identical per-sample gain back out after the wet/dry mix - output volume is untouched, only what the network sees is leveled. Gain stored in a ring aligned with wet/dry/vad; state reset in rnn_reset(). Verified: at 100x lower input level, noise suppression now matches or exceeds the high-level case (3.0x vs 1.8x; previously degraded), with tone passthrough exactly 1.00 at both levels. Recommended operating point after this change: IF back at 20-25 for front-end headroom; AINR quality is now consistent across IF.
Feature/noise reduction improvements
Owner
|
Hello. Please rebase and submit to the dev branch. Also, please join us on Discord so we can communicate how changes are approved, tested, etc. Thanks |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I've been experimenting with adding neural-network noise reduction (AINR) to the sBitx firmware — it uses Xiph.org's RNNoise, a small AI model trained on thousands of hours of speech, so instead of just filtering frequencies it actually recognizes what a human voice sounds like and mutes everything else.
Results on 40m SSB have been impressive — band hiss between words drops to near silence while the voice stays clean. Works alongside the existing DSP/ANR options (use one at a time).
Two on-screen controls: AINR (on/off, main screen next to REC) and strength/weak-signal tuning. Runs fine on the stock Pi, ~15ms latency, voice modes only (won't touch your FT8).
Code is on my fork of the drexjj 64-bit firmware if anyone wants to try:
github.com/vis4573/sbitx (branch: main)
Build with the usual ./build sbitx. Feedback welcome — especially reports on weak-signal behavior!
73, VU3UBP
vu3ubp@gmail.com