← All tutorials

Power Spectral Density

How a signal’s power spreads over frequency, and how to measure it.

On this page

1 Power per hertz 2 The periodogram is noisy 3 Welch averages short windowed pieces 4 Autocorrelation and filters 5 Equalization matching 6 In practice 7 When a PSD looks wrong 8 References

Tape hiss and mains hum are both noise, but hiss spreads its power evenly over every frequency while hum puts nearly all of it at 60 Hz (50 Hz in most of the world) and its multiples. The power spectral density (PSD) tells them apart. It is one curve, the signal’s power at each frequency per hertz, measured once per clip, and it is the right picture for noise, which has no fixed waveform but a steady spread of power. One spectrum of the whole recording, the periodogram, gives a very jumpy curve; Welch’s method averages the spectra of many short windowed segments, which steadies the curve at the cost of frequency detail.

a recordingperiodogramWelch averagefilter and match
Figure 1. 1Recording. 2Jumpy periodogram. 3Welch average. 4Filter and match.

1 Power per hertz

The PSD Px(f) says how much of a signal’s power sits near each frequency f. It is measured with the FFT (fast Fourier transform), the fast way to compute the DFT (discrete Fourier transform). The FFT splits N samples taken at rate fs into frequency bins fs/N apart, the bin width. Dividing each bin’s power by that width makes it a density, so the same sound gives the same curve whatever N or fs you measure it with.

Tape hiss is close to white noise, the same power at every frequency. Pink noise loses 3 dB, half its power per hertz, every octave, each doubling of frequency, so it sounds deeper. Hum is a row of spikes.

white noise, flatpink noise, 3 dB less per octavea pure tone at 1 kHz, one spike60 Hz hum, spikes at 60, 120, 180 Hz201001k4 kHzpower per Hz (dB)
Figure 2. 1White noise, flat. 2Pink noise. 3A 1 kHz tone. 460 Hz hum and its multiples.

For example, take a microphone preamp’s hiss, recorded at 8 kHz as white noise of power 1 (illustrative). It spreads that power evenly from 0 to 4 kHz, so its PSD sits at 0.00025 per hertz, −36 dB/Hz, and the area 0.00025 × 4,000 gives back the power of 1.

The area under the PSD is the signal’s power.
∫ Px(f) df = 1N Σ x[n]²
Added up over every frequency from 0 to fs2 on linear axes, the power per hertz gives back the power, the mean of the N squared samples x[n]. This is Parseval’s theorem, which says power is the same counted in time or in frequency (Fourier Transform and the FFT). The unit of Px(f) is power per hertz, V²/Hz (volts squared per hertz, for a signal in volts), or dB/Hz on a log axis. It builds on the Fourier transform, and the periodogram card of Section 2 is scaled so that its area obeys it.

The PSD gives a converter’s noise floor (quantization noise is flat, ADC Fundamentals), the signal-to-noise ratio (SNR) in each band and where hum sits.

2 The periodogram is noisy

The simplest estimate takes one FFT of the whole recording of N samples, squares the magnitude and scales it.

For example, take one second of illustrative fan noise at 16 kHz: white noise through a one-pole low-pass, a filter with one feedback term that dulls the highs. The table measures its first 256 and its first 4,096 samples.

Samples NBinsBin widthSpread over level
25612962.5 Hz1.06
4,0962,0493.9 Hz0.97

Spread over level is how far each bin scatters around the true PSD, divided by it.

The periodogram is one FFT, squared, per hertz.
P̂x(fk) = 2 |X[k]|²fs N
The hat marks an estimate; Px without it is the true PSD being estimated. |X[k]|² is the power the FFT finds at bin k, whose frequency is fk = k fsN.
Dividing by fs N, the sampling rate times the number of samples, turns it into power per hertz, so the level does not depend on N.
The 2 makes it one-sided, from 0 to fs2. A real signal’s negative frequencies mirror its positive ones, so each bin also carries its mirror’s power. Bin 0 and bin N2 have no mirror and are not doubled. It builds on the DFT and is used by every segment in Section 3.

For noise this estimate is ragged, as the table shows. Each value jumps by about its own size, and a longer recording only gives more bins, each just as jumpy. Welch (1967) shows that for Gaussian (bell-curve) noise the variance of each value, its average squared jump, is about Px², whatever the length. Figure 3 shows the same on white noise at 8 kHz.

512 samples (64 ms)−60−402,048 samples (256 ms)01k2k3k4 kHz−60−40dB per Hz
Figure 3. 1512 samples. 22,048, more bins, same jitter around the red true level.

3 Welch averages short windowed pieces

Welch’s fix cuts the recording into K segments, windows each with a Hann window, a smooth bump that fades the segment to zero at both ends, and averages their periodograms.

K = 4 windowed segmentsone jagged periodogram per segmenttheir average, smoother
Figure 4. 1K = 4 windowed segments. 2One periodogram each. 3Their smoother average.

For example, the equalization match of Section 5 needs the PSD of a phone recording. Take 256 ms of it at 8 kHz, 2,048 samples (illustrative). Cut into K = 4 segments, it gives L = 512, bins 15.6 Hz wide instead of 3.9 Hz, and a noise spread about half as large, since the square root of 4 is 2.

One segment’s windowed periodogram; the N samples are cut into K segments of L = NK samples, so the FFT length is L.
vu[n] = w[n] xu[n]
xu[n] is segment u, samples n = 0 to L − 1, faded by the window w[n], so the cut edges do not spray energy across far bins.
Xu[k] = Σn vu[n] e−j2πkn/L
The FFT of the faded segment vu, with L bins. The factor e−j2πkn/L is basis wave k, a complex sine that turns k full circles over the L samples. Here j is the imaginary unit, the square root of −1, and 2π is one full circle. Summing vu[n] times that wave measures how much of it the segment holds (Fourier Transform and the FFT, section 3).
P̂u(fk) = 2 |Xu[k]|²fs Σn w[n]²
The bottom replaces fs N, because the window takes energy out; with w[n] = 1 it is fs L, the plain periodogram of the segment. The 2 is the same one-sided fold, again except bins 0 and L2. It builds on the periodogram card of Section 2 and is used by Welch’s average.
Averaging the K segment periodograms keeps the true shape and shrinks the jitter about √K times.
P̂x(f) = 1K Σu=1K P̂u(f)
The estimate is the average of the K segment periodograms; u counts segments. Each segment sees the same true PSD with its own random jitter, so the jitter averages out while the true shape stays.
spread of P̂x ≈ Px√K
K independent copies, each as jumpy as Px itself, average to a curve about 1√K as jumpy (Welch’s method gives variance Px²K for segments that do not overlap).
Each segment has only L samples, so its bins are fsL wide, K times wider, and close peaks blur together (Figure 6). It is the same trade-off as the window length in The STFT and Spectrograms. It builds on the segment periodogram card above; Figure 5 draws it, and the match gain of Section 5 uses it.

Figure 5 runs Welch on 2,048 samples at 8 kHz: tones at 1,000 and 1,200 Hz in white noise through a one-pole low-pass.

8

Figure 5. 1Two tones. More pieces steady the noise but merge the tones.

1416641/641/81864segments Krelative to K = 1bin width, ×Knoise spread, 1/√Ktone peak, 1/K
Figure 6. 1Bin width grows K times. 2Noise spread falls as 1/√K. 3Tone peak falls as 1/K.

4 Autocorrelation and filters

The autocorrelation measures how much a signal looks like itself shifted by a lag τ. A pure 60 Hz hum repeats 60 times a second, every 16.7 ms, so it matches itself at every multiple of that lag. The PSD is the Fourier transform of the autocorrelation (Wiener 1930, Khinchin 1934).

autocorrelation of the hum against lag τ016.7 ms33.3 ms50 msits PSD against frequency (Hz)60 Hz060120180240300
Figure 7. A pure 60 Hz hum. 1It repeats every 16.7 ms. 2Its PSD is one spike at 60 Hz.

A filter scales each sine (LTI Systems and Convolution, section 7), so passing a signal through it multiplies its PSD by |H(f)|², the squared magnitude response from How FIR and IIR Filters Shape Magnitude and Phase. That is why the noise in Figure 5 slopes down like the one-pole low-pass that made it.

5 Equalization matching

Equalization (EQ) turns bands of frequencies up or down. A 2016 thesis on example-based audio editing describes a phone recording made “facing away from the sound source” on an unknown EQ setting (Anushiravani 2016). It sounds “muffled or squeaky”, as if passed through a band-pass filter. An example recording of the same kind of sound was made with professional equipment, and the goal is to make the phone recording sound like it.

Section 4 says a filter multiplies the PSD by |H(f)|². Solved for that factor, it gives the gain that turns the phone recording’s PSD into the example’s, held back by a floor β where the phone recording is nearly empty. Figure 8 runs it on four bands, with the phone too loud in the mids as in the thesis’s toy example.

phoneexamplematched+60−6−15dB125 Hz500 Hz1 kHz4 kHzfloor β
Figure 8. Illustrative. 1Loud mids come down. 2Near-empty 4 kHz rises part way.
The match gain, one power gain per frequency bin.
M[k] = Pex[k]Pin[k] + β
Pin[k] and Pex[k] are the Welch PSDs of Section 3 for the phone recording and the example at bin k. M[k] is the power gain that turns one into the other; the thesis calls it E, renamed here so it is not read as an average. β is that floor, a power. It builds on the filter rule of Section 4 and is used by the fixed EQ card.
β = c · mean(Pin)
The floor is a small share c of the phone recording’s average power per bin. Tying it to the input keeps the floor the same size relative to the signal, however loud the recording is. The thesis calls β only a regularization parameter and gives no value, so this rule is the page’s own choice.

The table gives the four bands of Figure 8 at c = 0.05 and c = 0.5.

Band (Hz)1255001,0004,000
Phone Pin1440.05
Example Pex1110.5
M, c = 0.050.900.240.243.07
M in dB−0.5−6.1−6.1+4.9
Matched0.900.970.970.15
M, c = 0.50.470.200.200.42
M, no floor10.250.2510

Illustrative band powers; matched is the phone power times M at c = 0.05.

With no floor the 4 kHz gain would be 10, lifting a band that holds mostly noise. A larger β lifts that band less and pulls every gain down, and a very large one fades the output toward silence. The mids match and the empty band rises part way, by design.

One fixed EQ curve on every frame, then back to sound.
|Y[t,k]|² = M[k] · |Xin[t,k]|²
Xin[t,k] is the STFT (short-time Fourier transform, one FFT per short windowed frame) of the phone recording at frame t and bin k, and Y[t,k] the matched STFT. The same M[k] scales bin k in every frame, so it acts as one fixed EQ curve.
Y[t,k] = √M[k] · Xin[t,k]
M is a power gain and Xin holds amplitudes, so each bin is scaled by the square root of M; a power gain of 4 is an amplitude gain of 2. Each bin keeps the phone’s phase; only its magnitude changes. The inverse STFT turns Y back into sound. It builds on the match gain and is used again by late-reverb suppression on the Reverberation page and by room matching (Non-negative Matrix Factorization, section 7.4).

It works when the example holds the same kind of sound, speech for speech. Setting M[k] = 1 outside chosen bands matches only those bands. Both recordings must first share a sampling rate (Aliasing and Anti-Aliasing Filters, section 5), or the same bin k means two different frequencies.

The same gain matches background noise, so two scenes cut together sound as if recorded in one place. In two film scenes, one with air-conditioner hiss and the next with a faint band of noise near 3 to 5 kHz, first remove each scene’s own noise with a gain per bin, as in Spectral Subtraction for Denoising. Then match one scene’s background noise to the other’s, and add it back under the cleaned speech at that scene’s own SNR.

This code matches the phone recording to the example.

import numpy as np
from scipy import signal

# phone: the muffled phone recording; example: the studio recording, same rate
FS, NPS, FLOOR_C = 16000, 512, 0.05
f, P_in = signal.welch(phone, fs=FS, nperseg=NPS)
f, P_ex = signal.welch(example, fs=FS, nperseg=NPS)
M = P_ex / (P_in + FLOOR_C * P_in.mean())
f, t, X = signal.stft(phone, fs=FS, nperseg=NPS)
Y = np.sqrt(M)[:, None] * X
t, matched = signal.istft(Y, fs=FS, nperseg=NPS)
  1. 1Welch PSDs of both recordings with the STFT’s segment length, so M has one value per STFT bin (eq cards in Section 3).
  2. 2The match gain, with FLOOR_C as c (match gain card in Section 5, lines 1 and 2).
  3. 3The STFT of the phone recording, every frame scaled by √M per bin (fixed EQ card in Section 5, line 2).
  4. 4The inverse STFT, overlap-add back to sound.

signal.stft and signal.istft are taught on The STFT and Spectrograms, section 5.

6 In practice

In practice a PSD reads a noise floor, finds hum, matches the tone of two recordings, or learns the noise that the denoising pages remove.

StepHow often, on what dataState it keepsCost
Periodogramonce per clip, on the whole clipnoneone FFT
Welch PSDonce per clip, averaging its segments, on overlapped windowed segmentsa running sum of periodogramsone short FFT per segment
EQ matchonce per recording, offline, on the whole recordingthe gain M[k]one Welch pass per recording, then one multiply per STFT bin

Rates, state and cost come from the sources in References.

Welch's estimate and the plain periodogram of the same noise; the last line prints each curve's spread relative to its level.

import numpy as np
from scipy import signal

FS, L = 16000, 512
x = np.random.default_rng(0).standard_normal(2 * FS)
f, P_welch = signal.welch(x, fs=FS, nperseg=L)
f1, P_one = signal.periodogram(x, fs=FS)
print(P_welch.std() / P_welch.mean(), P_one.std() / P_one.mean())
  1. 1Two seconds of white noise at 16 kHz.
  2. 2Welch's estimate with 512-sample segments (eq cards in Section 3).
  3. 3The plain periodogram (eq card in Section 2).
  4. 4Spread over level for each, about 0.11 against about 1 (Welch average card in Section 3, line 2).
Knob, where it entersRaise itLower itDefault
Segment length L, the FFT length per segmentFiner bins, fewer segments, a noisier curve; L = N is the plain periodogramMore segments and a smoother curve, blurrier bins; close peaks merge once fs/L exceeds their gap256 samples (scipy.signal.welch)
Segment count K, the average in the Welch cardSpread falls roughly as 1/√KK = 1 is the plain periodogram, as jumpy as one FFTNot set directly; it follows from the recording length, L and the overlap
Overlap between segmentsMore segments from the same data; gains shrink as segments repeat each otherAt 0, segments are independent but fewerHalf a segment (scipy.signal.welch)

SciPy documents scipy.signal.welch (v1.11.4). Its other defaults are a periodic Hann window, scaling='density' and average='mean'.

6.1 Gotchas

7 When a PSD looks wrong

What you seeLikely causeWhat to do
Levels off by a constant factorDensity (V²/Hz) and spectrum (V²) scaling mixed upSciPy defaults to scaling='density'; use 'spectrum' only to read a tone’s power
Everything off by a factor of 2 (3 dB)One-sided and two-sided spectra comparedOne-sided doubles every bin except 0 and N/2; compare like with like
A changing sound blurred into one curveThe PSD assumes the statistics stay putUse a spectrogram (The STFT and Spectrograms)
Bin 0 towers over the restA constant offset (DC) in the signalDetrend first; welch does detrend='constant' by default
Still raggedToo few segmentsMore data, or 50% overlap

If a fan’s PSD stays put, can it be learned during the pauses and take that much away from every frame? That is Spectral Subtraction for Denoising.

8 References