Task 4 of 5

A Tone Buried in Noise

Noise is broadband; a tone is not. In the time domain they are hopelessly mixed — noisy looks like grass. In the frequency domain they come apart: three tones put nearly all their energy into three bins, while the noise spreads its energy thinly over all 256. Concentration versus dilution is the entire trick, and it is the best argument anyone has ever made for paying the cost of a transform.

The numbers here: the three tones land at magnitudes 124, 71 and 42, and the loudest noise bin reaches 19.5. Anything between about 22 and 40 separates them; the gate is set to 30, comfortably in the middle of that gap.

A convenience falls out of the conjugate symmetry that made task 2 painful. Bin k and bin n − k are conjugates, and conjugates have equal magnitude — so a gate that reads magnitude keeps or drops both mirrors together, automatically, and the result comes back real without you doing anything about it. Magnitude needs both planes though:

const mag = Math.sqrt(re * re + im * im);

The starter gates on Math.abs(re) alone, which throws away any tone whose energy happens to sit in the imaginary part. Run it and watch the SNR go the wrong way.

You are given clean as well as noisy — you would not have it in real life, and that is the point: it is here so the improvement can be a number instead of an impression.

Goal: finish gate so every bin quieter than this.constants.gate is zeroed on both planes, then read the two SNR figures off the console. Before is about 4.8 dB; after should be over 20 dB.

Requirements

Hint 1 — read both planes, decide once

Every thread of a bin has to reach the same verdict, so read both planes whichever one you are on:

const k = this.thread.x;
const re = spec[0][k];
const im = spec[1][k];
const mag = Math.sqrt(re * re + im * im);
if (mag < this.constants.gate) return 0;

Dropping the Math.sqrt and comparing re*re + im*im against 30 is not the same test — it is a gate at magnitude 5.5, and 171 of the 256 bins clear it.

Hint 2 — the SNR arithmetic

Signal-to-noise in dB is ten times the log of a power ratio: the clean signal's energy over the energy of whatever you got wrong.

let signal = 0;
let error = 0;
for (let i = 0; i < 256; i++) {
  signal += clean[i] * clean[i];
  error += (test[i] - clean[i]) * (test[i] - clean[i]);
}
const db = 10 * Math.log10(signal / error);

Same idea elsewhere

Spectral gating is the backbone of noise reduction: Audacity's Noise Reduction, RNNoise, and the "spectral subtraction" every phone applies to your voice are all this idea with a smarter threshold — estimated per band, from a stretch of silence, instead of one number. The same shape reappears as thresholding in wavelet denoising, and as top-k pruning of a weight matrix. Keeping the few coefficients that carry the energy is one idea wearing several hats.

All tasks in Filtering in the Frequency Domain

  1. Convolution Becomes Multiplication
  2. The Brick Wall Rings
  3. Trade the Ringing for a Softer Edge
  4. A Tone Buried in Noise
  5. Multiplying Spectra Convolves in a Circle

This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.