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
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
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
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
|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.
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
Implementation
Step 1: Create a Noisy Signal
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},SampleRatesr];
noisyAudio
Out[]=
Step 2: Estimate the Noise Profile
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
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
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},SampleRatesr];
wienerAudio
Out[]=
Compare: Noisy vs. Wiener-Filtered
Compare: Noisy vs. Wiener-Filtered
Waveform Comparison
Waveform Comparison
AudioPlotbird,
Out[]=
In[]:=
AudioPlotnoisyAudio,
Spectrogram Comparison
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
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
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
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
by Jana Rusrus
Wolfram Community, STAFF PICKS, May 13, 2026
https://community.wolfram.com/groups/-/m/t/3715438