aethersdr / aethersdr/AetherSDR

[RFC] WDSP 2.10 refresh and Neural Noise Reduction (NNR) as a seventh ADSP method

Open
#5,684 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

audio dependencies New Feature priority: medium rfc RFC Approved
Dominant language
C++
Stars
221
Forks
117
Avg merge
2d 9h
Merged PRs (30d)
302

Description

Preflight
  • I have read GOVERNANCE.md and confirmed this change requires an RFC
  • I have searched existing issues and this RFC has not been proposed before
  • I have not opened a PR for this change yet
Problem

[!NOTE]
Approved 2026-09-14. Step 1 (the 2.10 refresh) merged as 85e4a1ff0
via #5686. The enabling half of step 2 is open as #5687. Decisions taken at
approval are recorded in §8.

Refresh the vendored WDSP snapshot from 2.00 to 2.10, and add 2.10's Neural
Noise Reduction as a seventh method in the existing client-side DSP suite.

Everything measured below was measured against a real build of WDSP 2.10 on a
development machine, not read out of the release notes. Where a number comes
from the WDSP Guide rather than from a measurement here, it says so.


1. Problem

1.1 What we ship today, and what none of it is

Noise reduction is not a gap — there are six methods already, mutually exclusive,
selected in AetherDspWidget (DspId { NR2, NR4, MNR, DFNR, RN2, BNR }) and run
by AudioEngine on the 24 kHz RX audio path:

Implementation Available
NR2 SpectralNR — a port of WDSP's own emnr.c: same OSMS/MMSE/NSTAT estimators, same Gaussian/Gamma gain processing, GG/GGS tables lifted from WDSP's calculus binary (see THIRD_PARTY_LICENSES §1) everywhere
RN2 RNNoise, third_party/rnnoise everywhere
NR4 libspecbleach, third_party/libspecbleach HAVE_SPECBLEACH
DFNR DeepFilterNet3, needs the prebuilt libdeepfilter fetched by scripts/setup/setup-deepfilter.sh HAVE_DFNR
MNR Apple Accelerate macOS only
BNR NVIDIA Maxine AFX NVIDIA GPU + accepted licence

Flex and Icom additionally have radio-side NR, reached through the slice model's
nrOn / nrLevel.

What none of the six is, is trained on HF. RNNoise learned office, street and
telephone noise. DeepFilterNet3 learned general speech enhancement. NR2 is a
statistical model that has to assume noise is roughly stationary, which HF noise
frequently is not. The two that were trained on anything at all were trained on
somebody else's problem.

1.2 WDSP 2.10 adds one that was trained on ours

Upstream b02d5bac, released 2026-09-09, GetWDSPVersion()210, adding
NNR: new TUs nnr.c/h, nnet.c/h, nnio.c/h, nnet_profile.h, and two
trained networks compiled in as nnr_model_0.c / nnr_model_1.c.

Architecture, read out of the source rather than the notes: 16 kHz internal rate,
512-point STFT, 50% overlap → 62.5 frames/s, 257 bins. Per frame a conditioning
block (|X|^0.3 compression, 2 s power normalisation) feeds 4 × Conv2D (2×5
kernel, stride 2 in frequency) + PReLU, then two dual-path RNN blocks (per-bin GRU
over time, bidirectional GRU over frequency), then a 4-stage transposed-conv
decoder, then a deep-filter head emitting complex coefficients over 5 taps
with 1 frame of lookahead. Complex coefficients mean phase is corrected, not just
magnitude; the 5-tap span means a bin is reconstructed from several frames, so it
can recover speech that is partly buried rather than only attenuating bins that
look noisy.

The training set is the reason to care (Guide §5.3.20): 100+ off-air HF noise
recordings, 85 used — atmospherics, static crashes, power-line and switching
hash, carriers, ordinary band congestion, none synthetic — and the speech was
pushed through a real SSB transmit chain, filtered and compressed, before
being mixed with it. The network learned speech as it arrives at a receiver
rather than speech as it sounds in a quiet room.

1.3 We already ship both of the things it was benchmarked against

Warren compared NNR against DeepFilterNet3 (official binary) and RNNoise
(released demo code) at release and reports it ahead on both objective measures
and listening. Those are DFNR and RN2 in the table above. Whatever else is
true, this is a claim we are unusually well placed to check rather than take on
faith — same audio path, same operator, one button apart. §5 makes that part of
the work rather than an afterthought.

1.4 The snapshot is a release behind

2.00 → 2.10 is 38 files changed, 10 added, 3 removed. It tunes PureSignal 3.0,
improves filter-generation performance, adds an auto-generated wdsp.h, and
retires one of our four vendor patches: 2.10's cfir.c is rebuilt around a
CFIRIMP object with a real teardown, so the leak that patch plugged no longer
exists. The gap only gets more expensive to close.


2. Proposal — Part A: refresh the vendor snapshot to 2.10

Standard refresh per third_party/wdsp/README.md. Patch status against 2.10,
verified by blob comparison against pristine 2.00 and 2.10 and by reading the
2.10 sources:

# AETHERSDR-PATCHES.md patch Status in 2.10 Action
1 nbp.c destroy_notchdb() object free Still leaks; file byte-identical to 2.00 Keep
2 nurbs.c destroy_nurbs() object free Still leaks; byte-identical Keep
3 cfir.c cfir_impulse() temp table free Fixed upstreamcfir.c was restructured around a CFIRIMP object with a real teardown freeing A, xistion, impulse and itself Drop
4 Worker exit handshake (channel.c/h, main.c, iobuffs.c/h) All five files unchanged from 2.00 Re-apply verbatim
new: fmd.c / emph.c a->pfcimp = build_fcimp(...) A use-after-free 2.10 introduces, on the channel-open path — see below Add

A diff of the current upstream/ against pristine 2.00 returns exactly those
eight files and nothing else, so the snapshot carries no undocumented edits.

2.10 introduces a use-after-free on a path every channel open takes, which
ASan caught on the first run of the refreshed snapshot. SetRXAFMNCde()
(fmd.c) and SetTXAFMEmphNC() (emph.c) both tear the filter-curve object
down and rebuild it:

teardown_fcimp (a->pfcimp);
build_fcimp (a->nc_de, a->wintype_de);   /* return value discarded */
exec_fcimp (a->pfcimp, ...);             /* a->pfcimp is dangling  */
setNc_fircore (a->pde, a->nc_de, get_pfcpulse (a->pfcimp));

build_fcimp() returns the new object and neither caller assigns it, so the two
statements that follow read and write through the pointer teardown_fcimp()
just freed, and the new object leaks. WdspChannel::open() calls RXASetNC()
calls SetRXAFMNCde(), so this is every channel this host opens, not a teardown
corner. It is new in 2.10 — 2.00 owned the impulse buffer directly and had no
object lifetime to get wrong — and the fix is to assign the result. Both copies
are fixed and recorded, reported upstream as
TAPR/OpenHPSDR-wdsp#2, and
dropped when a release carries the assignment.

One new porting break. 2.10's extrapolate.c, nurbs_fit.c and
nurbs_spline.c switched from malloc/free to _aligned_malloc/
_aligned_free without including comm.h. On MSVC those are CRT functions
and the omission is invisible; everywhere else they are macros in
port/include/wdsp_port.h, which those TUs never reach, and the build dies with
implicit declarations. Fixed in third_party/wdsp/CMakeLists.txt by
force-including the port header on non-Windows — symmetric with the alloc guard
Windows already force-includes, and therefore not a patch to the snapshot.

NR2 is unaffected. Nr2GammaTables.inc embeds values from WDSP's calculus
binary, and that binary is byte-identical between 2.00 and 2.10 (blob
7b51e4fd… in both). Only the reader, calculus.c, changed. The pinned SHA-256
in THIRD_PARTY_LICENSES stays valid and no NR2 regression is possible from the
refresh.

FFTW wisdom regenerates. NNR adds two 512-point FFTW_PATIENT plans per
instance; cold creation measured 1.15 s. The cache in WdspChannel.cpp absorbs
that, but the first run after the refresh re-measures.

Model weights ship embedded, as upstream ships them. nnr_model_0.c +
nnr_model_1.c = +34.7 MB of generated C (~8.1 MB packed) and +6.8 MB of
.rodata. Deliberate over sidecar .bin files (§6.2): the snapshot stays
byte-pristine, nothing is packaged per platform, and NNR cannot be broken by an
install that drops a file.

Part A is independently shippable and worth landing on its own. Status: in
review as #5686.
wdsp_channel_test and
wdsp_channel_reservation_test pass on Linux in RelWithDebInfo, under
ASan + UBSan with detect_leaks=1, and under TSan with zero warnings. macOS and
Windows remain.


3. Proposal — Part B: NNR as the seventh method

3.1 The two models

Both are compiled in and both are loaded per instance, so switching is immediate.
They are the same network at two widths — same depth, same 257 bins, same
deep-filter order 5, same 1 frame of lookahead, same training corpus.

Standard (slot 0) Premium (slot 1)
Encoder/decoder channels 16 / 32 / 48 / 64 24 / 48 / 72 / 96
DPRNN hidden size 32 48
Parameters 261,834 584,746
Weight blob 2.10 MB 4.68 MB
SDR improvement (Guide; 8 held-out recordings, −10…+15 dB SNR) +4.3 dB +4.9 dB
CPU, one hardware thread (Guide; 9950X3D, optimised build) ~10% ~32%
CPU measured here (i9-13980HX P-core, -O2, loaded box) ~13% ~29%
CPU measured here (same box, E-core) 29% 67%
Added delay (Guide and measured — identical) 51.17 ms 51.17 ms

Premium is larger but does not look further ahead, so it costs processor time
rather than delay; switching models never changes audio timing.

3.2 Measured cost
  • CPU: ~13% / ~29% of one modern x86 P-core, per instance. Plain scalar C,
    double precision, no SIMD, no threading.
  • Memory: ~9 MB RSS per instance. Every NNET copies the whole weight set
    into its own double blob and both slots are built per instance, so nothing is
    shared. One instance per RX audio source, not per slice, which is what keeps
    this affordable — see §3.4.
  • Latency: +51.17 ms. For scale, the Guide puts WDSP's EMNR — our NR2 — at
    64.00 ms, so this is less added delay than the method most operators use now.
3.3 It is a speech model, and CW proves it

Measured by pushing synthetic signals through the block at 48 kHz:

Input Output change
White noise only −28 dB
Voice-like harmonic content + noise −0.5 dB (passes)
Steady 700 Hz tone + noise −28 dB, tone amplitude 0.2000 → 0.0077

Both models behave identically. The network classifies an unmodulated carrier as
noise and deletes it. (Caveat: the CW case is a continuous unkeyed tone; a keyed
signal has onsets that may fare better. The steady-state result is not
ambiguous.)

So NNR is offered for voice modes only — SSB/AM/SAM/FM/DSB. In CW, CWL/CWU,
DIGU/DIGL and any data path it is not selectable, and a slice switching into one
of those with NNR active falls back to the previous method. A hard gate rather
than a warning, because an operator watching a CW signal vanish will not
attribute it to a noise reduction setting. Nothing on the data path — FT8, the
decoders, DAX — ever sees NNR audio.

This is not a new kind of constraint for the suite: RN2 and DFNR are speech
denoisers too. It is sharper here, and it is measured rather than assumed.

3.4 Placement: AudioEngine, beside DFNR and RN2

NNR becomes DspId::NNR, one more entry in the existing mutually-exclusive
selector, wrapped in an NnrFilter class shaped like DeepFilterFilter. That
gets it to every backend including Flex — where it is arguably most valuable,
since a Flex operator's alternative is the radio's own NR — and it inherits the
exclusion cascade, the ADSP UI, the settings plumbing and the per-source instance
model that already exist.

The one wrinkle: calc_nnr() requires the sample rate to be an integer multiple
of 16000
or it disables itself, and the RX NR path is 24 kHz
(docs/architecture/audio-pipeline.md). So processNnr() resamples 24 → 48 kHz
around the block and back, which is exactly what processBnr() already does for
the same reason. 24 kHz carries content to 12 kHz and NNR analyses to 8 kHz, so
the round trip costs nothing the model would have used.

3.5 It runs standalone — no RXA channel needed

create_nnr(), xnnr() and destroy_nnr() never touch ch[] or rxa[]; only
the SetRXANNR* property setters do. So NNR can be instantiated directly as a
host-side stage, which is the same pattern aether_wdsp.h already documents and
uses for the impulse noise blanker (create_anbEXT / xanbEXT). Buffers are
interleaved doubles with the signal in I, which the facade already has a
conversion path for.

One gap. The mask floor — the single control the Guide intends operators to
touch — is reachable only through SetRXANNRMaskFloor(channel, …), because
nnr->nets[] is private to nnr.c. Standalone use needs a five-line
setMaskFloor_nnr(NNR, double) beside the setModel_nnr() that is already
exported. Proposed: carry it as a documented fourth entry in
AETHERSDR-PATCHES.md and send it upstream, since it is the accessor the public
constructor argument already implies. Recreating the block to change one double
is not an option — that is two FFTW_PATIENT plans and two model loads.

3.6 Controls

The Guide documents exactly three console controls. All ten are exposed
(decision 4 in §8, taken after this section was first written): the three
documented ones plus alpha, alpha knee, tau, max gain and the two smoothing
times, which the Guide leaves undocumented. src/core/NnrControls.h fixes each
one's range to the clamp WDSP already enforces and marks WDSP's own starting
value, so an operator who wanders can see where home is. Test mode, cmode and
position stay internal — they are debug and structural rather than tuning.

  • On/off — the ADSP selector already is this.
  • Mask floor — how far any bin may be attenuated. −10 dB passes the most
    noise, −50 dB is maximum suppression, default −25. Mapped from the existing
    0..100 strength control: 0 → −10 dB, 50 → −25 dB, 100 → −50 dB. Note the
    slider runs opposite to the dB value, which is the right direction for an
    operator: more strength = more suppression. It is not a "more NR" knob in the
    usual sense — raising the floor leaves more of the genuine received noise in
    place, which is the correct response to a weak signal because it hands the
    speech/noise decision back to the listener.
  • Model — Standard/Premium, per radio rather than per slice: it is a
    CPU-budget decision about the machine, not about the signal.
    SetRXANNRModel() returns the slot actually in use, so the control is
    enabled or hidden on that return value rather than on a compile-time guess.

4. UI and settings

No new control surface: one more button in the ADSP row, one more tab, and the
existing exclusion cascade does the rest. The NNR tab holds the mask-floor
strength slider and the model selector, with the seven advanced controls of §3.6
below them; all persist through the existing DSP settings path. Each advanced
control is drawn with a marker at the value NnrControls.h records as WDSP's
default. The VfoWidget ADSP-launcher accent that already signals "some
client DSP is active" picks NNR up for free.

Mode gating (§3.3) is the only genuinely new UI behaviour: the NNR button
disables itself outside voice modes, with a tooltip that says why.


5. Validation

Beyond the usual tests, this RFC proposes one deliverable that is unusual and
cheap here: an A/B of NNR against DFNR and RN2 on recorded off-air HF audio,
run through the real audio path on the same recordings, reported on the issue.
We ship both of Warren's comparison baselines behind adjacent buttons; nobody
else is in a position to check the claim this directly, and the result is worth
having on the record whichever way it falls.

Unit tests mirror the existing filter tests: construction/teardown, flush,
bypass-equals-input, the 24↔48 resample round trip, and the mode gate. Declared
in tests/tests.cmake, per AGENTS.md.


6. Alternatives considered

6.1 Enable NNR inside the WDSP RXA chain instead. The block is already
instantiated in every channel we open and xrxa() already calls it, so this
looks like the cheap option — a few facade entries and no resampling, since
dsp_rate is 48 kHz. Rejected because it only reaches backends that demodulate
here (HL2, ANAN via #4970, RTL) and leaves Flex — the largest group of operators
— with nothing, while adding a second noise-reduction control surface that has to
be reconciled with the ADSP suite anyway.

6.2 Ship the models as sidecar wdsp_nnr_*.bin files. WDSP prefers them over
the built-ins if present, saving 34.7 MB of repo and 6.8 MB of binary. Rejected:
it needs stub TUs for the built-in extern symbols (a new vendor patch on files
that are pure generated data), per-platform packaging, and it introduces a
failure mode where a bad install silently produces a radio with no NNR. The file
mechanism stays available for what it is designed for — dropping an experimental
model in for a tester without a rebuild.

6.3 Standard model only. Saves 23 MB of source and 4.7 MB of binary but
requires patching out a file upstream ships, and SetRXANNRModel()'s return
contract exists precisely so a console can offer Premium where the CPU allows.
Embedding both means the desktop operator gets the better model and the operator
on modest hardware is never forced into it.

6.4 Also expose WDSP's ANR and EMNR. EMNR is already shipped as NR2 —
SpectralNR is a port of emnr.c down to the calculus tables — so exposing it
would be the same algorithm twice in different implementations. ANR (leaky LMS)
has no equivalent in the suite and is the one classic block that survives a
carrier, which makes it the CW answer NNR cannot be; but that is an argument for
its own RFC with its own evidence, not a rider on this one.

6.5 Patch WDSP to share model weights across instances. ~9 MB per instance is
real. Rejected as scope: a structural change to nnio/nnet lifetime, i.e. the
kind of vendor patch AETHERSDR-PATCHES.md exists to discourage. Worth raising
upstream. Per-source instancing keeps the practical count at one or two.

6.6 Fork the inference to float32 + SIMD. The inference is scalar
double-precision C; float32 with vectorisation would plausibly be several times
faster. Rejected outright: a numerical fork of a vendored library, re-validated
against trained weights, permanently off the refresh path. If CPU is the blocker,
the answer is the Standard model.

6.7 Refresh to 2.10 and stop there. Defensible — the leak fix and PureSignal
tuning have value — but it pays the 34.7 MB anyway, since the model TUs come with
the snapshot, and leaves the one HF-trained denoiser in the box unopened.


7. Risks

  1. CPU on modest hardware. Premium will not keep up on a Pi-class machine;
    even Standard is ~29% of one of this box's E-cores. Mitigation: Standard is
    the default, the model control reflects what the build actually has, and NNR
    is one of seven methods rather than the only one.
  2. The refresh is 38 changed files. Some structs moved out of headers into
    their TUs (cfir), so anything reaching into WDSP internals would break — our
    facade only calls PORT functions, so the blast radius should be nil. The gate
    is wdsp_channel_test under ASan and TSan on all three platforms, because
    patch 4 is a threading patch being re-applied to a moved target.
  3. Vendor patch count. The refresh drops one patch and adds one (the
    use-after-free above), and §3.5 would add another for setMaskFloor_nnr().
    Both are small and upstreamable, but the direction of travel on a vendored
    library should be downward and this RFC does not move it that way.
  4. Operator confusion with seven methods. Already true at six. NNR's
    mode gate at least makes one of them self-explanatory.

8. Decisions taken, and what is still open

Decided at approval (2026-09-14):

  1. All ten NNR controls are exposed, not three. §3.6 originally ruled the
    undocumented tuning controls stay at defaults; that was reversed before the
    accessors were written, and §3.6 and §4 are updated to match. The
    consequence is a larger vendored accessor set (patch 5) and a busier tab
    than the Guide's "minimal UI" implies — accepted knowingly. Caught in review
    of #5687, where the code had moved ahead of this document.

  2. Both models ship; Standard is the default. The model selector lives in the
    NNR tab and is driven off SetRXANNRModel()'s return value, so it hides
    itself in a build that has no Premium. Both sets of weights are embedded
    regardless, so offering the choice costs only the control, and nobody is
    pushed onto the extra ~16% of a core.

  3. Mask floor is a 0–100 strength slider, mapped linearly to −10…−50 dB, with
    the default marker at 50 (−25 dB) per src/core/NnrControls.h. Consistent
    with the strength control the other six ADSP methods already use. The
    direction still needs saying in the tooltip — more strength is more
    suppression, and on a weak signal that is not always better.

  4. The model-file lookup keeps upstream's behaviour. Every NNR construction
    looks for wdsp_nnr_0.bin / wdsp_nnr_1.bin in the process's working
    directory and prefers either over the built-in. Measured consequences, all
    four verified against a standalone NNR:

    Case Result
    no file built-ins load; −28.15 dB
    malformed file rejected, falls back to built-in; −28.15 dB
    another valid model under that name loads it — slot 0 ran the Premium network while a UI would still read "Standard"
    valid model, different dimensions "passing audio through" — NNR silently does nothing; −5.46 dB, which is only the resampler's band limit

    The last case is the one to know about: a well-formed model from a different
    WDSP release, left in the launch directory, disables NNR with no indication
    anywhere but stderr — and stderr is a terminal-launch-only channel here
    (OutputDebugStringAfputs(stderr) on Unix, the debugger channel on
    Windows). Accepted deliberately: it is how every other WDSP client behaves,
    it costs nothing to implement, and it is what §6.2 leans on when it argues for
    embedding the models rather than shipping sidecar files. Documented in
    third_party/wdsp/README.md so the next person reads it before diagnosing it.

Still open, none of them blocking step 2:

  1. Where NNR sits in the ADSP row — beside DFNR and RN2 as another neural
    method, or first as the recommended choice for voice. Cosmetic; proposing
    beside them and moving it if the A/B justifies the promotion.
  2. Apple Silicon CPU is unmeasured. Both figures in §3.1 are x86. A task
    rather than a decision, and worth doing on the M-series box before Premium is
    recommended there.
  3. Per-source instancing is what §3.4 proposes, matching DeepFilterFilter
    and RNNoiseFilter. Per-slice would multiply both the CPU and the ~9 MB;
    nothing has asked for it.
Cross-platform impact

Linux: primary development target; builds and runs as measured. The
_aligned_malloc fix in §2 is what makes 2.10 compile here at all.

macOS: same source, same port shims, no platform-specific code in any new TU
— NNR is plain C and FFTW, both already dependencies. Apple Silicon CPU cost is
unmeasured (open question 3).

Windows: the three TUs that break the Unix build are fine on MSVC by
construction, and the existing wdsp_alloc_guard.h force-include is untouched.
check-windows needs to pass with +6.8 MB of .rodata and ~35 MB more source in
the compile.

Unlike three of the six methods we ship — DFNR needs a prebuilt library, MNR is
macOS-only, BNR needs an NVIDIA GPU and an accepted licence — NNR is in-tree and
unconditional
. Every platform gets both models and the same behaviour; the only
variation is how much CPU headroom the machine has, which is the operator's
choice through the model control rather than a platform decision. That makes it
the only neural denoiser that is actually available to every operator on every
platform.

Implementation scope

Three steps, each separately shippable and testable:

  1. Refresh to 2.10. Snapshot, patch ledger per §2, CMake force-include,
    COMMIT / README / AETHERSDR-PATCHES.md / THIRD_PARTY_LICENSES updates,
    wisdom regeneration. No new facade surface.

    Gate: wdsp_channel_test and wdsp_channel_reservation_test green under
    ASan and TSan on Linux, and under the ordinary build-and-test lanes on
    macOS and Windows. An earlier draft of this line asked for sanitizers on all
    three platforms, which this project's CI cannot produce: sanitizers.yml is
    runs-on: ubuntu-latest for both its ASan and TSan entries, weekly plus
    manual dispatch. A gate that names a lane which does not exist is not a gate,
    it is a line nobody can close — so the Linux sanitizers are the real bar, and
    the other two platforms are covered by check-macos / check-windows. The
    WDSP code under test is identical on all three; what differs is the toolchain,
    which is what those build lanes exercise.

    Not "no behaviour change". Every RX channel open builds an NNR whether or
    not it processes audio, because create_rxa() calls create_nnr()
    unconditionally. Measured against pristine 2.00 with the same harness and the
    same WdspChannel defaults: +8.8 MB RSS per RX channel, plus ~5 MB once
    as the model .rodata is paged in, and 3 extra FFTW planner calls over 2 new
    distinct (kind, size) pairs. Ruled acceptable for a step that ships alone: the
    allocation is upstream's own chain, and removing it would mean null-guarding
    xnnr, flush, setBuffers, setSamplerate and setSize across the RXA
    lifecycle — a local opinion rather than a bug fix, and precisely the kind of
    vendor patch AETHERSDR-PATCHES.md exists to discourage. §6.5's shared-weight
    route is the principled fix, and it helps our own instance too.

  2. NnrFilter + facade. Standalone create_nnr/xnnr/destroy_nnr and the
    model/mask-floor accessors through aether_wdsp.h; the setMaskFloor_nnr
    patch; NnrFilter shaped like DeepFilterFilter; the 24↔48 kHz resample;
    unit tests.

  3. Wire it up. DspId::NNR, exclusion cascade, ADSP tab with mask floor and
    model, settings persistence, mode gating, and the §5 A/B.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

This RFC is already split across #5686 for the WDSP refresh and #5687 for NNR enablement. For context, read third_party/wdsp/README.md, AetherDspWidget, AudioEngine, WdspChannel.cpp, and wdsp_channel_test; completion is represented by those ongoing changes and their platform and DSP tests rather than a standalone contribution here.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, cmake, cpp
Domain
audio-video-rtc, desktop
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.