Skip to content

Add macOS MMSE-Wiener spectral NR (MNR) + 5 dark-theme quick-wins#1

Closed
M7HNF-Ian wants to merge 1 commit into
mainfrom
feat/mnr-and-quickfixes
Closed

Add macOS MMSE-Wiener spectral NR (MNR) + 5 dark-theme quick-wins#1
M7HNF-Ian wants to merge 1 commit into
mainfrom
feat/mnr-and-quickfixes

Conversation

@M7HNF-Ian

Copy link
Copy Markdown
Owner

Summary

MNR — macOS MMSE-Wiener Spectral Noise Reduction

Adds a new client-side noise reduction mode exclusive to macOS, accelerated by Apple's Accelerate framework (vDSP / AMX on Apple Silicon).

MNR uses a decision-directed MMSE-Wiener filter with minimum-statistics noise floor tracking. Unlike the radio-side NR (which applies a single scalar to the whole spectrum) or RNNoise (which is speech-optimised), MNR works per-bin in the frequency domain — it estimates the noise power at each of the 257 spectral bins independently and applies a customised Wiener gain to suppress only the bins that contain noise, leaving signal-bearing bins untouched.

Key characteristics:

  • 512-point real FFT at 24 kHz — 46.9 Hz/bin resolution, tight enough to spare CW sidebands while crushing between-signal hash
  • 50% overlap-add with sqrt-Hann window — perfect reconstruction, no blocking artefacts
  • 25-frame minimum-statistics noise estimator (~267 ms history) — tracks a slowly-varying noise floor without reacting to signal bursts
  • Temporal gain smoothing — suppresses musical-noise (the chirping/twittering artefact common in spectral NR) by preventing rapid frame-to-frame gain swings
  • Hardware accelerationvDSP_fft_zrip uses AMX co-processor on Apple Silicon; the whole pipeline runs in ~0.3 ms per hop on M-series chips
  • Exact byte-count output — no silence padding, no latency spikes; startup latency is one hop (≈10.7 ms)
  • Adjustable strength via right-click → DSP Settings → MNR tab (0 = transparent blend, 100 = maximum suppression)
  • Mutually exclusive with NR2/RN2/NR4/DFNR (only one client-side DSP path runs at a time — same as existing behaviour)

The MNR button appears in both the VFO widget DSP tab and the spectrum overlay DSP panel. On non-Apple builds it is hidden at compile time — zero overhead for Windows/Linux users. Right-clicking the MNR button opens the DSP Settings dialog directly on the MNR tab.

Quick-wins bundled in this PR

Test plan

  • macOS: MNR button visible in both VFO DSP tab and overlay DSP panel
  • macOS: Enable MNR on a noisy band — audible reduction in background hash
  • macOS: Right-click MNR → DSP Settings opens on MNR tab with strength slider
  • macOS: Strength slider (0–100%) changes suppression depth in real time
  • macOS: MNR button syncs between VFO and overlay panels
  • macOS: MNR state persists across app restart
  • macOS: Enabling DFNR does not activate MNR (cross-toggle regression check)
  • Windows/Linux: MNR button hidden, no link errors, no runtime overhead
  • All platforms: Diversity button present on Slice A at startup
  • All platforms: TNF/FDX indicators visible in active state
  • All platforms: Record/Playback buttons clearly visible in dark theme

🤖 Generated with Claude Code

MNR uses a decision-directed MMSE-Wiener filter with minimum-statistics
noise floor tracking, accelerated by Apple Accelerate (vDSP / AMX).
512-point FFT at 24 kHz, 50% overlap-add, per-bin gain with temporal
smoothing to suppress musical noise. Hardware-accelerated on Apple Silicon.
Right-click the MNR button to open strength controls in DSP Settings.

Quick-wins:
- aethersdr#1503: Diversity button missing on Slice A at startup
- aethersdr#1581: TNF/FDX indicators invisible in dark theme
- aethersdr#1583: Date/Time status bar labels too dim
- aethersdr#1576: Record/Playback buttons invisible in dark theme
- aethersdr#506:  Secondary text colour consistency (#506070 → #8aa8c0)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@M7HNF-Ian

Copy link
Copy Markdown
Owner Author

Closing — PR being submitted to upstream ten9876/AetherSDR instead.

@M7HNF-Ian M7HNF-Ian closed this Apr 19, 2026
M7HNF-Ian added a commit that referenced this pull request Apr 19, 2026
#1 (bug) — mutual exclusion: setMnrEnabled now disables all other NR modes
    before enabling MNR; every other NR setter (NR2/RN2/NR4/BNR/DFNR) now
    also calls setMnrEnabled(false) in its mutual-exclusion block.

aethersdr#2 (bug) — RADE guard: setMnrEnabled early-returns when m_radeMode is active,
    matching the pattern used by all other NR setters.

aethersdr#3 — raw pointer → std::unique_ptr<MacNRFilter>: AudioEngine.h gains a
    forward declaration (#ifdef __APPLE__) and unique_ptr member; AudioEngine.cpp
    already had the full include; new/delete replaced with make_unique/reset.
    std::atomic<float> m_mnrStrength replaces plain float.

aethersdr#5 — MacNRFilter.h: processing-chain comment corrected from
    "stereo int16 → … → stereo int16" to "stereo float32 → … → stereo float32";
    also fixes the process() doc-comment.

aethersdr#6 — MacNRFilter: m_strength changed to std::atomic<float>; setStrength/
    strength() use store/load; processFrame() reads m_strength.load().
    Allows AudioEngine to write strength from the audio thread while the
    DSP dialog reads it from the UI thread without a data race.

aethersdr#8 — strength persistence: setMnrEnabled(true) restores m_mnrStrength from
    AppSettings("MnrStrength"); setMnrStrength() now saves to AppSettings
    on every change, so the value survives restarts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
M7HNF-Ian added a commit that referenced this pull request Apr 19, 2026
    before enabling MNR; every other NR setter (NR2/RN2/NR4/BNR/DFNR) now
    also calls setMnrEnabled(false) in its mutual-exclusion block.

    matching the pattern used by all other NR setters.

    forward declaration (#ifdef __APPLE__) and unique_ptr member; AudioEngine.cpp
    already had the full include; new/delete replaced with make_unique/reset.
    std::atomic<float> m_mnrStrength replaces plain float.

    "stereo int16 → … → stereo int16" to "stereo float32 → … → stereo float32";
    also fixes the process() doc-comment.

    strength() use store/load; processFrame() reads m_strength.load().
    Allows AudioEngine to write strength from the audio thread while the
    DSP dialog reads it from the UI thread without a data race.

    AppSettings("MnrStrength"); setMnrStrength() now saves to AppSettings
    on every change, so the value survives restarts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant