In the previous project, we built a spectral subtraction noise remover — but it left behind "musical noise" artifacts and couldn't handle noise that changes over time. Now we'll build something smarter: a Wiener filter that computes the optimal gain for every frequency at every moment, adapting to the signal in real time.

Where Spectral Subtraction Falls Short

Spectral subtraction has one rule: subtract a fixed noise estimate from every frame. This creates two problems:
◼
  • Musical noise — when the noise estimate is slightly wrong at certain frequencies, the subtraction produces random tonal blips that sound like scattered piano notes.
  • ◼
  • Stationarity assumption — the noise profile is computed once from a silent segment and never updated. If the noise changes (traffic, wind, crowd chatter), the fixed estimate becomes wrong.
  • The Wiener filter solves both problems by replacing the hard subtraction with a soft, adaptive gain.

    The Key Idea: Gain Instead of Subtraction

    Spectral subtraction says: subtract the noise magnitude from each frequency bin.
    The Wiener filter says: multiply each frequency bin by a gain between 0 and 1, based on how much signal vs. noise is present at that frequency right now.

    The Math

    For each frequency bin k at time frame n, the Wiener filter computes:
    Signal-to-Noise Ratio (SNR)
    SNR(k) = |X(k)|² / |N(k)|² - 1
    Where:
    |X(k)|² = the power (magnitude squared) of the noisy signal at frequency bin k
    |N(k)|² = the estimated noise power at frequency bin k
    We subtract 1 because |X(k)|² contains both signal and noise: |X|² ≈ |S|² + |N|², so SNR = |S|²/|N|² = |X|²/|N|² - 1
    If this goes negative (more noise than expected), we clamp to 0
    ​
    Wiener Gain
    G(k) = SNR(k) / (SNR(k) + 1)​
    ​
    Cleaned Output
    ​
    ​Y(k) = G(k) × X(k)​
    ​
    We multiply the original complex spectrum (magnitude and phase) by the real-valued gain. The phase is preserved unchanged.

    Implementation

    Step 1: Create a Noisy Signal

    In[]:=
    bird=ExampleData[{"Audio","Bird"}];​​birdData=AudioData[bird][[1]];​​sr=QuantityMagnitude[AudioSampleRate[bird]];
    In[]:=
    (*Addwhitenoise*)​​noise=RandomReal[{-0.05,0.05},Length[birdData]];​​noisyData=birdData+noise;​​noisyAudio=Audio[{noisyData},SampleRatesr];
    noisyAudio
    Out[]=
    00:00
    00:02
    Data in Notebook

    Step 2: Estimate the Noise Profile

    In[]:=
    windowSize=1024;​​hopSize=256;​​windowFunc=HannWindow[Range[-0.5,0.5,1/(windowSize-1)]];
    In[]:=
    noiseSamples=Round[0.2*sr];​​noiseSegment=noisyData[[1;;noiseSamples]];​​noiseWindows=Floor[(Length[noiseSegment]-windowSize)/hopSize]+1;​​​​noiseSpectra=Table[​​Module[{chunk,windowed},​​chunk=noiseSegment[[i*hopSize+1;;i*hopSize+windowSize]];​​windowed=chunk*windowFunc;​​Abs[Fourier[windowed]][[1;;windowSize/2]]​​],​​{i,0,noiseWindows-1}​​];
    In[]:=
    noiseProfile=Mean[noiseSpectra];​​noisePower=noiseProfile^2;

    Step 3: Apply the Wiener Filter

    In[]:=
    nFullWindows=Floor[(Length[noisyData]-windowSize)/hopSize]+1;
    In[]:=
    wienerFrames=Table[​​Module[{chunk,windowed,ft,mag,phase,snr,gain,cleanMag,​​fullCleanMag,cleanFt},​​chunk=noisyData[[i*hopSize+1;;i*hopSize+windowSize]];​​windowed=chunk*windowFunc;​​ft=Fourier[windowed];​​mag=Abs[ft];​​phase=ft/(mag+10^-10);​​ (*SNRforeachfrequencybin*)​​snr=Table[Max[mag[[k]]^2/(noisePower[[k]]+10^-10)-1,0],​​{k,1,windowSize/2}​​];​​     (*Wienergain*)​​gain=snr/(snr+1);​​​​(*Applygaintomagnitude*)​​cleanMag=mag[[1;;windowSize/2]]*gain;​​​​(*Mirrorforsecondhalf*)​​fullCleanMag=Join[cleanMag,Reverse[cleanMag]];​​​​(*Reconstructwithoriginalphase*)​​cleanFt=fullCleanMag*phase;​​​​Re[InverseFourier[cleanFt]]​​],​​{i,0,nFullWindows-1}​​];

    Step 4: Overlap-Add Reconstruction

    In[]:=
    outputLength=(nFullWindows-1)*hopSize+windowSize;​​wienerOutput=ConstantArray[0.0,outputLength];​​wienerWindowSum=ConstantArray[0.0,outputLength];
    In[]:=
    Do[​​Module[{start},​​start=i*hopSize+1;​​wienerOutput[[start;;start+windowSize-1]]+=​​wienerFrames[[i+1]]*windowFunc;​​wienerWindowSum[[start;;start+windowSize-1]]+=windowFunc^2;​​],​​{i,0,nFullWindows-1}​​];
    In[]:=
    wienerData=wienerOutput/(wienerWindowSum+10^-10);​​wienerAudio=Audio[{wienerData},SampleRatesr];​​
    wienerAudio
    Out[]=
    00:00
    00:02
    Data in Notebook

    Compare: Noisy vs. Wiener-Filtered

    Waveform Comparison

    AudioPlotbird,
    
    Out[]=
    In[]:=
    ​
    AudioPlotnoisyAudio,
    

    Spectrogram Comparison

    In the noisy spectrogram, a uniform haze covers all frequencies. After Wiener filtering, the haze is gone and only the bird chirp patterns remain — clean diagonal sweeps against a dark background.

    Visualizing the Wiener Gain

    This plot reveals the filter's decision at each frequency:
    ◼
  • Gain near 1 (top) — the filter detected signal at this frequency and lets it pass
  • ◼
  • Gain near 0 (bottom) — the filter detected only noise at this frequency and suppresses it
  • ◼
  • Values in between — uncertain regions where signal and noise are mixed, partially attenuated
  • Compare this with spectral subtraction, which would either fully keep or fully remove each bin — the Wiener gain curve is much smoother.

    Summary

      The Wiener filter is one of the most important results in signal processing — it gives you the mathematically optimal linear estimate of a signal buried in noise, given knowledge of the noise statistics. Every time your phone cancels background noise on a call, a descendant of this algorithm is running.

    CITE THIS NOTEBOOK

    Wiener filter: adaptive noise removal​
    by Jana Rusrus​
    Wolfram Community, STAFF PICKS, May 13, 2026
    https://community.wolfram.com/groups/-/m/t/3715438