miniDSP
A small C library for audio DSP
Loading...
Searching...
No Matches
Window Functions

Window functions taper a finite signal block before an FFT so the block edges do not create a large discontinuity. That discontinuity causes spectral leakage: energy spreads into neighboring bins.

miniDSP provides five windows so you can compare the trade-off between main-lobe width (frequency resolution) and sidelobe level (leakage suppression).


Hanning window

The Hanning (Hann) window is a common default:

\[w[n] = 0.5 \left(1 - \cos\!\left(\frac{2\pi n}{N-1}\right)\right), \quad n = 0, 1, \ldots, N-1 \]

It tapers smoothly to zero at both ends and gives good all-around performance for FFT analysis.

Reading the formula in C:

// n -> N (window length), i -> n (sample index), out[i] -> w[n]
if (n == 1) {
out[0] = 1.0;
} else {
double n_minus_1 = (double)(n - 1);
for (unsigned i = 0; i < n; i++) {
out[i] = 0.5 * (1.0 - cos(2.0 * M_PI * (double)i / n_minus_1));
}
}

API:

void MD_Gen_Hann_Win(double *out, unsigned n);
void MD_Gen_Hann_Win(double *out, unsigned n)
Generate a Hanning (Hann) window of length n.

Visuals — window taps and magnitude response:

The smooth taper to zero at both ends reduces leakage compared with a rectangular window.

Quick example:

MD_Gen_Hann_Win(hann, N);

Hamming window

The Hamming window keeps a similar shape to Hanning, but with non-zero endpoints and a lower first sidelobe:

\[w[n] = 0.54 - 0.46 \cos\!\left(\frac{2\pi n}{N-1}\right) \]

Reading the formula in C:

// n -> N (window length), i -> n (sample index), out[i] -> w[n]
if (n == 1) {
out[0] = 1.0;
} else {
double n_minus_1 = (double)(n - 1);
for (unsigned i = 0; i < n; i++) {
out[i] = 0.54 - 0.46 * cos(2.0 * M_PI * (double)i / n_minus_1);
}
}

API:

void MD_Gen_Hamming_Win(double *out, unsigned n);
void MD_Gen_Hamming_Win(double *out, unsigned n)
Generate a Hamming window of length n.

Visuals — window taps and magnitude response:

Compared with Hanning, the non-zero endpoints and coefficients shift the sidelobe pattern while keeping a similar main-lobe width.

Quick example:

MD_Gen_Hamming_Win(hamming, N);

Blackman window

The Blackman window strongly suppresses sidelobes by adding another cosine term:

\[w[n] = 0.42 - 0.5 \cos\!\left(\frac{2\pi n}{N-1}\right) + 0.08 \cos\!\left(\frac{4\pi n}{N-1}\right) \]

Compared with Hanning/Hamming, it has much lower sidelobes but a wider main lobe.

Reading the formula in C:

// n -> N (window length), i -> n (sample index),
// p -> 2*pi*n/(N-1), out[i] -> w[n]
if (n == 1) {
out[0] = 1.0;
} else {
double n_minus_1 = (double)(n - 1);
for (unsigned i = 0; i < n; i++) {
double p = 2.0 * M_PI * (double)i / n_minus_1;
out[i] = 0.42 - 0.5 * cos(p) + 0.08 * cos(2.0 * p);
}
}

API:

void MD_Gen_Blackman_Win(double *out, unsigned n);
void MD_Gen_Blackman_Win(double *out, unsigned n)
Generate a Blackman window of length n.

Visuals — window taps and magnitude response:

You should see much lower sidelobes than Hanning/Hamming, with a wider main lobe in the response plot.

Quick example:

MD_Gen_Blackman_Win(blackman, N);

Rectangular window

The rectangular window is the no-taper baseline:

\[w[n] = 1 \]

It preserves the narrowest main lobe but has the highest sidelobes.

Reading the formula in C:

// i -> n (sample index), out[i] -> w[n]
for (unsigned i = 0; i < n; i++) {
out[i] = 1.0;
}

API:

void MD_Gen_Rect_Win(double *out, unsigned n);
void MD_Gen_Rect_Win(double *out, unsigned n)
Generate a rectangular window of length n (all ones).

Visuals — window taps and magnitude response:

As the no-taper baseline, rectangular gives the narrowest main lobe and the highest sidelobes.

Quick example:

MD_Gen_Rect_Win(rect, N);

Kaiser window

The Kaiser window uses the zeroth-order modified Bessel function \(I_0\) to provide continuous control over the sidelobe/mainlobe tradeoff via a single parameter \(\beta\):

\[w[n] = \frac{I_0\!\left(\beta\,\sqrt{1 - \left(\frac{2n}{N-1}-1\right)^2}\right)} {I_0(\beta)}, \quad n = 0, 1, \ldots, N-1 \]

Higher \(\beta\) values produce lower sidelobes (better leakage suppression) at the cost of a wider main lobe:

  • \(\beta \approx 5\) : ~45 dB stopband attenuation
  • \(\beta \approx 10\) : ~100 dB stopband attenuation
  • \(\beta \approx 14\) : ~120 dB stopband attenuation

Reading the formula in C:

// n -> N (window length), i -> n (sample index), out[i] -> w[n]
// beta -> shape parameter, inv_i0_beta -> 1/I₀(β)
if (n == 1) {
out[0] = 1.0;
} else {
double inv_i0_beta = 1.0 / MD_bessel_i0(beta);
double n_minus_1 = (double)(n - 1);
for (unsigned i = 0; i < n; i++) {
double t = 2.0 * (double)i / n_minus_1 - 1.0;
double arg = 1.0 - t * t;
if (arg < 0.0) arg = 0.0;
out[i] = MD_bessel_i0(beta * sqrt(arg)) * inv_i0_beta;
}
}
double MD_bessel_i0(double x)
Zeroth-order modified Bessel function of the first kind, .

API:

void MD_Gen_Kaiser_Win(double *out, unsigned n, double beta);
void MD_Gen_Kaiser_Win(double *out, unsigned n, double beta)
Generate a Kaiser window of length n with shape parameter beta.

Visuals — window taps and magnitude response ( \(\beta = 10\)):

With \(\beta = 10\), the Kaiser window achieves ~100 dB stopband attenuation — much deeper suppression than Blackman, with a comparable main lobe width.

Quick example:

MD_Gen_Kaiser_Win(kaiser, N, 10.0);

Quick comparison

Window Edge values Sidelobes Main lobe Tunable?
Rectangular 1.0 Highest Narrowest No
Hanning 0.0 Low Medium No
Hamming 0.08 Lower first sidelobe Medium No
Blackman 0.0 Very low Widest No
Kaiser > 0 Configurable via \(\beta\) Configurable Yes

If you are unsure where to start, Hanning is a good default. Use Blackman when leakage suppression matters more than peak sharpness. Use Kaiser when you need precise control over the sidelobe level (e.g., for FIR filter design or resampling). All response plots above use the same tap length and zero-padded FFT size, so sidelobe and main-lobe differences are directly comparable.

Further reading

API reference