Introduction

When a cello and an oboe play the same note, you hear the same pitch — but you instantly know they are different instruments. The pitch is the same; the timbre (the tone color, the texture of the sound) is different. Those two pieces of information are tangled together inside every sound wave, and untangling them is one of the central tasks of audio analysis.

The surprising answer comes from a 1963 paper by Bogert, Healy, and Tukey. They coined the word cepstrum — an anagram of spectrum — for the result of a peculiar two-step operation: take the Fourier transform of a sound, take the logarithm of its magnitude, and then take the Fourier transform again.

That second transform turns the spectrum's repetitive harmonic pattern — the tell-tale sign of a pitched sound — into a single sharp spike. The spike's position on the new axis (which they playfully called the quefrency axis, an anagram of frequency) tells you the period of the original pitch, cleanly separated from the spectral envelope that carries timbre. Pitch and timbre, once hopelessly entangled in the waveform, now live in different regions of the cepstrum.

See the Pitch Peak

The demo below simulates a harmonic sound: a base pitch plus its overtones. Drag the slider to change the fundamental frequency and watch how a spike appears in the cepstrum at the quefrency corresponding to that pitch period.

<!-- Controls and two canvases: log-spectrum on the left, cepstrum on the right -->
<p class="hint">A harmonic sound has a pitch (fundamental frequency) and a timbre (spectral shape). The <b>cepstrum</b> — the spectrum of the log-spectrum — separates them: timbre stays near zero quefrency while a lone spike marks the pitch period.</p>
<div class="controls">
  <label for="pitchSlider">Pitch: <span id="pitchVal">220</span> Hz</label>
  <input id="pitchSlider" type="range" min="80" max="600" value="220" step="5">
</div>
<div class="panels">
  <div class="panel">
    <div class="panel-title">Log-spectrum</div>
    <canvas id="specCanvas" width="340" height="160"></canvas>
  </div>
  <div class="panel">
    <div class="panel-title">Cepstrum</div>
    <canvas id="cepCanvas" width="340" height="160"></canvas>
  </div>
</div>
<div class="status" id="status"></div>
/* Layout: hint, slider, two side-by-side canvas panels */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.controls { margin-bottom: .8rem; }
.controls label { font-size: .9rem; font-weight: 600; display: block; margin-bottom: .3rem; }
input[type=range] { width: 100%; accent-color: #1d3557; }
.panels { display: flex; gap: .8rem; flex-wrap: wrap; }
.panel { flex: 1 1 160px; min-width: 140px; }
.panel-title { font-size: .8rem; font-weight: 700; text-transform: uppercase;
               letter-spacing: .05em; color: #1d3557; margin-bottom: .3rem; }
canvas { border: 1px solid #cdd9e3; border-radius: 8px; background: #f5f8fb;
         display: block; width: 100%; max-width: 340px; }
.status { font-size: .9rem; font-weight: 600; color: #1d3557; min-height: 1.2em;
          margin-top: .6rem; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Cepstrum demo — build a harmonic signal, compute DFT, take log-magnitude, compute DFT again

const SAMPLE_RATE = 4000;  // Hz — enough for pitch 80-600 Hz with 8 harmonics
const N = 1024;            // DFT size — enough resolution without being too slow for a demo
const HARMONICS = 8;       // number of overtones to synthesize (amplitude 1/h)

// Synthesize a harmonic signal with the given fundamental frequency f0.
function buildSignal(f0) {
  const sig = new Float32Array(N);
  for (let h = 1; h <= HARMONICS; h++) {
    const amp = 1 / h;  // amplitude of h-th harmonic falls as 1/h (sawtooth-like spectrum)
    const freq = h * f0;
    if (freq >= SAMPLE_RATE / 2) break;
    for (let i = 0; i < N; i++) {
      sig[i] += amp * Math.sin(2 * Math.PI * freq * i / SAMPLE_RATE);
    }
  }
  return sig;
}

// Naive O(n^2) DFT — correct and simple enough for N=512 half-spectrum.
function dft(signal) {
  const n = signal.length;
  const half = n >> 1;
  const re = new Float32Array(half);
  const im = new Float32Array(half);
  for (let k = 0; k < half; k++) {
    let sr = 0, si = 0;
    const w = -2 * Math.PI * k / n;
    for (let t = 0; t < n; t++) {
      sr += signal[t] * Math.cos(w * t);
      si += signal[t] * Math.sin(w * t);
    }
    re[k] = sr;
    im[k] = si;
  }
  return { re, im };
}

// Magnitude spectrum from real and imaginary parts.
function magnitude(re, im) {
  return re.map((r, i) => Math.sqrt(r * r + im[i] * im[i]));
}

// Full cepstrum pipeline: signal -> DFT -> log-magnitude -> DFT again -> magnitude.
function computeCepstrum(f0) {
  const sig = buildSignal(f0);
  const half = N >> 1;

  // Step 1: magnitude spectrum of the signal.
  const { re, im } = dft(sig);
  const mag = magnitude(re, im);

  // Step 2: logarithm of the magnitude (turns multiplication into addition).
  const logMag = mag.map(v => Math.log(Math.max(v, 1e-9)));

  // Step 3: DFT of the log-magnitude = cepstrum.
  const { re: cre, im: cim } = dft(logMag);
  const cepstrum = magnitude(cre, cim);

  return { mag, logMag, cepstrum };
}

function drawBar(ctx, data, maxVal, color, highlight, highlightColor, w, h) {
  const n = data.length;
  const bw = w / n;
  ctx.clearRect(0, 0, w, h);
  for (let i = 0; i < n; i++) {
    const bh = (data[i] / maxVal) * (h - 4);
    ctx.fillStyle = (highlight !== null && i === highlight) ? highlightColor : color;
    ctx.fillRect(i * bw, h - bh, Math.max(bw - 0.5, 1), bh);
  }
}

// Find peak in a window around the expected quefrency bin — avoids the large DC bin 0.
function findPeakNear(arr, center, radius) {
  const lo = Math.max(1, center - radius);
  const hi = Math.min(arr.length - 1, center + radius);
  let best = 0, bestI = center;
  for (let i = lo; i <= hi; i++) {
    if (arr[i] > best) { best = arr[i]; bestI = i; }
  }
  return bestI;
}

const specCanvas = document.getElementById('specCanvas');
const cepCanvas  = document.getElementById('cepCanvas');
const sCtx = specCanvas.getContext('2d');
const cCtx = cepCanvas.getContext('2d');
const slider = document.getElementById('pitchSlider');
const pitchVal = document.getElementById('pitchVal');
const statusEl = document.getElementById('status');

function update() {
  const f0 = parseInt(slider.value, 10);
  pitchVal.textContent = f0;

  const { mag, cepstrum } = computeCepstrum(f0);

  // Show only the first 120 frequency bins (low-frequency part of the spectrum).
  const specShow = mag.slice(0, 120);
  const maxMag = Math.max(...specShow, 1e-9);
  drawBar(sCtx, specShow, maxMag, '#7da9c7', null, '#e63946',
          specCanvas.width, specCanvas.height);

  // Show bins 1..CEP_SHOW of the cepstrum; bin 0 is DC (always huge, skip it).
  // 36 bins covers quefrency 3-25, matching f0 range 80-600 Hz.
  const CEP_SHOW = 36;
  const cepShow = cepstrum.slice(1, CEP_SHOW + 1);
  const maxCep = Math.max(...cepShow, 1e-9);

  // Expected peak bin in the displayed cepstrum (offset by 1 since we sliced from bin 1).
  // Peak quefrency bin = SAMPLE_RATE/(2*f0): the DFT of the half-spectrum (N/2 pts) maps period p bins -> quefrency k = (N/2)/p = SR/(2*f0).
  const expectedQ = Math.round(SAMPLE_RATE / (2 * f0));
  const peakOffset = findPeakNear(cepstrum, expectedQ, 4) - 1;
  drawBar(cCtx, cepShow, maxCep, '#a8c5da', peakOffset, '#e63946',
          cepCanvas.width, cepCanvas.height);

  statusEl.textContent = i18nFmt('Pitch: %f0% Hz — expected cepstrum peak at quefrency ≈ %q% bins', { f0, q: expectedQ });
}

slider.addEventListener('input', update);
update();
Result
Console

Notice that the spike's position on the quefrency axis shifts when you change the pitch — while the smooth envelope of the spectrum (which encodes timbre) stays broad and spread out. The cepstrum does in one step what the ear does automatically: it pulls the periodicity of the harmonic series out of the messy spectrum and reports it as a single number.

How It Works

The elegance of the cepstrum comes from a chain of mathematical identities.

Step 1 — The source-filter model. A voiced sound is well modeled as a glottal pulse train (the pitch source, with fundamental frequency f0f_0) convolved with a vocal-tract filter (the timbre). Convolution in the time domain multiplies their Fourier transforms in the frequency domain:

X(f)=H(f)⋅G(f)X(f) = H(f) \cdot G(f)

where H(f)H(f) is the smooth spectral envelope (timbre) and G(f)G(f) is the comb-like harmonic series (pitch).

Step 2 — The logarithm turns multiplication into addition. Taking log⁡∣X(f)∣\log|X(f)| gives:

log⁡∣X(f)∣=log⁡∣H(f)∣+log⁡∣G(f)∣\log|X(f)| = \log|H(f)| + \log|G(f)|

The smooth envelope and the comb are now added, not multiplied.

Step 3 — A second Fourier transform separates them. log⁡∣H(f)∣\log|H(f)| varies slowly with frequency (it is the broad shape of the spectrum), while log⁡∣G(f)∣\log|G(f)| oscillates rapidly (one spike every 1/f01/f_0 Hz). After the second transform, slow variation maps to low quefrency and rapid oscillation maps to high quefrency — specifically to a spike at quefrency q=1/f0q = 1/f_0.

The result: timbre lives near the origin, pitch is a lone spike far out. A simple liftering step (the cepstrum's word for filtering in the quefrency domain) isolates whichever one you need. This clean separation is why cepstrum-based pitch detectors outperformed every earlier method when they appeared in the 1960s, and why the idea remains central to speech coding and music analysis today.

Where It Matters

The cepstrum unlocked a family of techniques that are now woven into nearly every corner of audio technology:

  • Pitch detection in speech and music: the cepstrum peak directly gives the fundamental frequency. It works even on noisy recordings where simple autocorrelation fails, because the log-spectrum averaging suppresses noise before the second transform.
  • Mel-Frequency Cepstral Coefficients (MFCCs): the standard feature vector for automatic speech recognition since the 1980s. MFCCs warp the quefrency axis to the mel scale (which mimics human hearing) and keep only the first dozen coefficients — the low-quefrency region that carries timbre, stripped of pitch. Every time you talk to a voice assistant, MFCCs are almost certainly in the pipeline.
  • Speaker recognition and verification: voices differ in their spectral envelopes; MFCCs capture exactly that. The low-quefrency cepstrum is a compact fingerprint of a speaker's vocal tract.
  • Echo and reverberation analysis: the cepstrum was originally invented for seismology — detecting delayed echoes from geological layers. A reflected signal produces a periodic ripple in the spectrum, which becomes a spike in the cepstrum at the delay time.
  • Music transcription and chord detection: the harmonic content of musical instruments maps cleanly onto the cepstrum. Automatic music transcription systems use cepstral features to separate multiple simultaneous pitches.

Understanding the cepstrum means understanding Fourier analysis at a deeper level — it is the idea that the spectrum itself can be treated as a signal and analyzed all over again.

Conclusion

The cepstrum is a perfect example of a simple idea with enormous reach. Bogert, Healy, and Tukey noticed that taking the spectrum of the log-spectrum untangles two kinds of information that are hopelessly mixed in a raw waveform: the pitch, encoded in the periodicity of the harmonic series, and the timbre, encoded in the smooth spectral envelope.

That single fold — log then re-transform — has powered pitch detectors, speaker recognizers, automatic speech transcription, and seismic echo finders for more than sixty years. The whimsical vocabulary they invented (cepstrum, quefrency, liftering) is still in use today, a reminder that the best ideas often arrive with a sense of play.

Next time you ask a voice assistant a question, remember: somewhere inside, the math is taking the spectrum of a spectrum, and a lone spike in quefrency space is quietly reporting your fundamental frequency.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/cepstrum-pitch/Content licensed under CC BY-NC 4.0.