← Back to projects

Audio

Tuner

A trumpet tuner that runs in the browser. It detects the note from the microphone using the McLeod Pitch Method (MPM) and explains the math behind it step by step.

No note detected

— Hz— cents

Turn on the microphone and play a note.

440 Hz
Notation

Waveform (time domain)

How it works: the math behind it

Here I walk through the whole path from sound to note, rigorously but without mystery. The formulas use real notation, but each one comes with the intuitive idea, a worked numerical example and, at several points, an interactive experiment you can play with and watch in action.

1. From sound to signal: sampling

Sound is a continuous variation of air pressure over time, a function p(t). The microphone measures that pressure many times per second. This rate is the sampling rate fs: in the browser, it is usually 44.1 kHz or 48 kHz, depending on the device (the tuner above shows yours while the microphone is on). The examples in this text use fs=44100 Hz. The result is a sequence of numbers x[n]:

x[n]=p(nfs)n=0,1,2,…,N−1

I work with blocks of N=2048 samples. At 44.1 kHz, each block spans N/fs≈46 ms of sound, long enough to hold several cycles even of the trumpet's lowest notes.

Why does this matter? A periodic sound satisfies x(t)=x(t+T), where T is the period (seconds per cycle). The frequency is just its inverse: f=1T. Finding the note means finding the period.

2. Frequency and period, in practice

The simplest wave is the sine x(t)=sin⁡(2πft). In words: the value oscillates between −1 and +1, and the term 2πf sets how fast. The larger f, the more cycles per second. The period is the duration of one cycle, T=1f. For example, a 440 Hz A has T=1/440≈2.3 ms per cycle.

In the experiment below, the time window is fixed at 20 ms, like an oscilloscope screen. So as you raise the frequency, more cycles get squeezed onto the screen and the highlighted band, which marks one period, shrinks. Lower it and the opposite happens. Try it:

220 Hz

Period T = 4.55 ms · note ≈ A3 · 4.4 cycles in 20 ms

3. Measuring how much the wave looks like itself

The signal is the list of samples x0,x1,x2,… The core idea of MPM is that an in-tune sound repeats. So if I take the signal and a shifted copy of it, offset by some number of samples, the two versions become nearly identical exactly when the shift equals one period. Finding the period means finding that shift.

Before the formulas, here is what each symbol means:

  • xj: the value of the j-th sample (the height of the wave at that instant, between −1 and +1).
  • j: the sample index (0, 1, 2, …), its position in time.
  • τ (tau): the shift in samples between the wave and the copy, also called the lag. This is what we're after.
  • xj+τ: the sample τ positions ahead of xj, the "shifted copy".
  • N: the block size (2048 samples).

The natural first measure of how different the wave and its copy are is the square difference function (SDF). For each sample, we take the difference xj−xj+τ, square it and add everything up:

d′(τ)=∑j=0N−1−τ(xj−xj+τ)2

Why square it? Two reasons. Squaring makes every difference positive (it doesn't matter whether the copy sits above or below: a mismatch counts as error either way) and penalizes big mismatches more. So d′(τ) is never negative and works as an error score: the smaller, the better the fit.

And why does the sum run from j=0 only up to N−1−τ, and not to N−1? Because xj+τ must exist: if j+τ ran past the end of the block, there would be no sample to compare against. An important consequence: the larger the shift τ, the fewer pairs remain to add up (only N−τ of them). Keep this in mind, because it comes back shortly.

When τ reaches the period, each xj meets an xj+τ that is almost equal, the differences drop to zero and d′(τ)≈0, forming a "valley". To move from the idea of difference to that of similarity, we expand the square (a−b)2=a2−2ab+b2 inside each term of the sum. Three pieces appear, and the third is the key one, the autocorrelation r′(τ):

d′(τ)=∑jxj2+∑jxj+τ2−2∑jxjxj+τwithr′(τ)=∑j=0N−1−τxjxj+τ

Look at the three pieces. The first two, ∑jxj2 and ∑jxj+τ2, are the energy of the two segments (the sum of the squared samples): they vary slowly and similarly to each other. All the action is in the third, r′(τ)=∑jxjxj+τ. Since it enters the difference with a negative sign, making d′(τ) small is the same as making r′(τ) large. In other words: smaller difference ⟺ larger autocorrelation.

Why does the autocorrelation grow at a good fit? It multiplies each sample by its copy. Two numbers with the same sign give a positive product (+×+ and −×−); with opposite signs, a negative one. When the copy is aligned, peaks meet peaks and valleys meet valleys: almost all the products are positive and the sum shoots up. Misaligned, it mixes positives and negatives that cancel out, and the sum falls close to zero.

A concrete mini-example. Take the little segment [1,−1,1,−1]. Comparing it with itself (τ=0): 1⋅1+(−1)(−1)+1⋅1+(−1)(−1)=4, all positive. Shift it by 1 sample and the pairs become 1⋅(−1)+(−1)⋅1+1⋅(−1)=−3: the opposite signs cancel. Aligned, large; misaligned, small.

This "raw" autocorrelation, however, has two flaws that keep us from using it directly as a tuning measure:

Flaw 1: it depends on volume. Play the same note louder and all the samples grow in the same proportion, say xj→2xj. Then each product xjxj+τ becomes 4 times larger (2⋅2=4), and so does r′(τ). The number changes just because the sound is louder, even though the fit is identical. A good similarity measure should not depend on volume.

Flaw 2: it shrinks as τ grows. Remember that the sum has only N−τ terms? With a larger τ, there are fewer products being added, so r′(τ) decreases on its own, even for a perfectly periodic sound. As a result, the peak at twice the period starts out smaller than the one at the period, forming a downward slope that can hide the right peak or favor a wrong one.

Both flaws share the same cure: normalizing, that is, dividing by the energy of the compared segments, so the result becomes a number between −1 and 1 that depends neither on volume nor on τ. That is exactly what the NSDF does, in the next step.

4. The NSDF: the heart of MPM

McLeod and Wyvill fix both flaws by dividing the autocorrelation by the energy of the compared segments. Defining

m′(τ)=∑j=0N−1−τ(xj2+xj+τ2)

we get the NSDF (normalized square difference function):

n′(τ)=2r′(τ)m′(τ)−1≤n′(τ)≤1

Dividing by the energy m′(τ) fixes both flaws from the previous step at once:

  • Volume: if the sound doubles (xj→2xj), the numerator 2r′(τ) grows 4 times, but so does the energy m′(τ), because both are built from products of the same samples. The two factors cancel out in the division and the result is unchanged.
  • Shrinking with τ: the numerator and the denominator have the same number of terms (N−τ). When τ grows and fewer terms remain, both shrink together, in the same proportion, so the ratio between them stays fair.

The result is dimensionless and always lies between −1 and +1: n′(τ)=1 is a perfect fit (an identical copy), n′(τ)=0 means there is no relation, and n′(τ)=−1 would be the exact opposite (a mirrored wave).

An important aside: what are harmonics? A pure sine is the "cleanest" sound there is (a flute comes close). But almost every real instrument produces, at the same time, the fundamental frequency f0 and integer multiples of it: 2f0, 3f0, 4f0… These extra frequencies are the harmonics, and their "recipe" (which ones are strong, which are weak) is what gives the timbre, what makes a trumpet sound different from a clarinet playing the same note. The signal becomes a sum of sines:

x(t)=sin⁡(2πf0t)+a2sin⁡(2π⋅2f0t)+a3sin⁡(2π⋅3f0t)+…

Here a2, a3, … are the amplitudes of each harmonic, that "recipe": the larger ak, the louder that harmonic sounds (the fundamental has amplitude 1). This mix is what defines the timbre.

The crucial detail: all these harmonics share the same period T=1/f0, because 2f0, 3f0… complete whole cycles within T. Adding them changes only the shape of each period (the wave gets spikier and full of detail), but does not change the period. That's why the pitch stays f0, regardless of timbre. In the experiment, switch between Pure sine and Trumpet: the shape of the wave changes a lot, but the NSDF peaks stay in the same places.

"So the repeated peaks are the harmonics?" No! This is the classic confusion. The NSDF peaks at τ=P,2P,3P,… appear simply because a signal that repeats every P samples also repeats every 2P, 3P…: shifting the copy by 2 periods realigns it just as well as by 1. The proof is that a pure sine, with no harmonics, has exactly the same repeated peaks. Select Pure sine and see! In other words, the repeated peaks are the signal repeating, not the harmonics. This is precisely what makes step 5 necessary: among several nearly perfect peaks, which one is the fundamental period (the first), and not twice it (an octave lower)?

Experiment: slide the copy and watch the NSDF

At the top, the original wave (solid line) and the copy shifted by τ (dashed line). At the bottom, the full curve n′(τ), with guides at the period and at its double. Drag τ: when it reaches the period, the copy overlaps the original and n′ rises to about 1; at the double, it rises again. Change the signal: changing the harmonics changes the shape of the wave, but not the position of the peaks. Even the pure sine has the same ones.

Original wave Copy shifted by τ
n′(τ) curve Current τ
0
n′(τ) = 1.00

5. Choosing the right peak (peak picking)

Step 4 produced the whole curve n′(τ), full of peaks. Now we need to choose one: the one matching the fundamental period. Taking the tallest isn't enough, and here's why, step by step:

  1. Ignore the trivial peak. At τ=0, the wave is compared with itself, so n′(0)=1 always, which says nothing about the period. We skip this first "hill" of the curve, until it crosses zero for the first time.
  2. Find the key maxima. The curve rises and falls, crossing zero several times. Between a rise that crosses zero and the next descent there is a "hump", and the highest point of each hump is a key maximum. Looking only at these points avoids mistaking small ripples for real peaks.
  3. Measure the tallest of all, nmax⁡. This value is also the clarity of the sound, from 0 to 1: near 1, the note is well defined; low, the sound is noisy or absent. The tuner uses this number to decide whether there is a note to show, and it's the "clarity" that appears in the tuner's messages.
  4. Choose the first peak that is good enough. We define a threshold k⋅nmax⁡, with k=0.9, and take, scanning from left to right, the first key maximum that exceeds it, and not the tallest.

Why the first, and not the tallest? Recall from step 4: the peaks repeat at τ=P,2P,3P,… and all have a similar height. The peak at 2P corresponds to a period twice as long, that is, half the frequency: one octave lower. If we took the tallest and it happened to be the one at 2P, the tuner would show the note an octave off. The first strong peak is always the fundamental period: the shortest one, the highest frequency, the right note.

Worked example, precisely the tricky case. Suppose key maxima with heights 0.93 (at τ=150), 0.97 (at τ=300) and 0.90 (at τ=450). The tallest is the one at τ=300, so nmax⁡=0.97 and the threshold is 0.9⋅0.97=0.873. If we took the tallest, we'd choose τ=300, twice the period and an octave lower: wrong! But, scanning from the left, the first to pass 0.873 is the one at τ=150 (0.93 ≥ 0.873), and we stop there. A period of 150 samples: the correct fundamental.

The τ of the chosen peak is our period, still a whole number of samples. In the next step, we refine it to fractions of a sample.

6. Sub-sample precision: parabolic interpolation

The NSDF is computed only at integer values of τ (sample by sample), but the real period almost never falls exactly on a sample: it lands between two points. At 44.1 kHz, a 440 Hz A has a period of τ=44100/440≈100.2 samples. Rounding to 100 would give 44100/100=441 Hz, about 4 cents of error from rounding alone. We need the peak with sub-sample precision.

Why a parabola, specifically? Two facts fit together:

  1. Every smooth peak, seen up close, is a parabola. This is what the Taylor series says: near a maximum x0, the slope is zero, so f(x)≈f(x0)+12f′′(x0)(x−x0)2, which is the equation of a parabola (opening downward, since at a maximum the curvature f′′ is negative).
  2. Three points define a unique parabola. The parabola y=ax2+bx+c has 3 unknowns, and we have 3 points: τ−1, τ and τ+1. There is one and only one, in closed form. A straight line wouldn't have a peak, and a higher degree could wiggle and invent false peaks.

The vertex of that parabola is the refined guess for the peak. Calling n−1, n0 and n+1 the three NSDF values, the vertex (at x=−b/2a) gives the offset δ:

δ=12⋅n−1−n+1n−1−2n0+n+1τ⋆=τ+δ

The formula tells a story. The denominator n−1−2n0+n+1 is the curvature, how sharp the peak is. The numerator n−1−n+1 measures the asymmetry: if the left neighbor is higher, the true peak pulls to the left (δ<0); if the neighbors are equal, δ=0 and the peak falls right on the sample.

The δ is a fraction of a sample, between −0.5 and +0.5. The frequency then comes out directly:

f=fsτ⋆

Interpolation in action

The real curve peaks at a fractional spot (the dashed vertical line), but we only measure the 3 integer points around it. The fitted parabola passes through those 3 points, and its vertex is the estimate. Drag the true period: notice how the vertex follows the true value, while plain rounding would miss.

Real curve Measured points Fitted parabola True peak Estimate (vertex)
100.27
peak at integer sample τ = 100parabola correction δ = +0.27estimate τ⋆ = τ + δ = 100.27 → f = 439.8 Hz plain rounding would give 441.0 Hz, an error of ~5 cents

Numerical example: with fs=44100 Hz, if the peak is at τ=100 and the parabola gives δ=−0.18, then τ⋆=99.82 and f=44100/99.82≈441.8 Hz. Without the δ, it would give 441.0 Hz, a difference of about 3 cents.

7. From frequency to note and cents

In equal temperament, the octave (doubling the frequency) is divided into 12 equal semitones on a logarithmic scale. Starting from the reference A4 fref, which you set with the tuner's Reference control (440 Hz, 442 Hz…), the frequency of the note n semitones away is:

f(n)=fref⋅2n/12

Inverting it with a base-2 logarithm tells us how many semitones away our frequency is:

n=12log2⁡(ffref)

Rounding n gives the nearest note. What's left, multiplied by 100, is the deviation in cents (100 cents = 1 semitone):

cents=(n−round⁡(n))⋅100=1200log2⁡(ffnearest)

Calculator: frequency → note

Type a frequency and a reference to see the formula being evaluated and the resulting note. Try 446 Hz with a 440 Hz reference: you get A4, +23 cents.

Hz
Hz
n = 12 · log₂(446.0 / 440) = 0.234 semitonesnearest note: A4deviation: +23 cents (sharp)

B♭ trumpet (transposition): when reading a written C, the instrument sounds a concert B♭, 2 semitones lower. To show the written note name, we add a whole-number offset to the note index, n+2. Since it's a whole number of semitones, the deviation in cents doesn't change. Only the note name does.

8. Stability: median and exponential filter

The analysis runs about 60 times per second, and each reading carries a small random error: the estimated frequency jitters by a few tenths of a Hz every frame. Wiring the needle straight to that would leave it vibrating. So I apply two filters in sequence, each solving a different problem.

First filter, the median: it removes outliers. I keep the last 7 frequencies and use their median, the middle value after sorting the list. The median is immune to a single absurd value, which an ordinary average is not.

Median or average, in practice. Suppose the readings [440,441,439,880,440], where 880 is a wrong reading (an octave jump in one frame). The average would give (440+441+439+880+440)/5=528 Hz, dragged by the error. The median (sorted: 439, 440, 440, 441, 880), however, is 440 Hz: the wrong reading becomes just "the largest in the list" and is discarded. The window is odd (7) precisely so there is always one element exactly in the middle.

Second filter, the exponential moving average: it smooths the remaining jitter. This one is applied to the needle's position itself, the deviation in cents. Instead of jumping to the new reading, I blend a fraction α of it with the value already on screen:

st=α⋅xt+(1−α)⋅st−1α=0.12

Reading the formula: the new displayed value st is α=0.12 (12%) of the new reading xt plus 88% of the previous value st−1. This is a low-pass filter: it lets slow changes through (you actually raising the note) and blocks fast ones (the frame-to-frame jitter). Since each reading nudges the needle only 12% toward the new value, it takes about 1/α≈8 frames (around 130 ms) to settle: fast for the eye, and slow enough not to tremble.

When the note changes, the average starts over. Otherwise, going from +49 cents on one note to −49 cents on the next, the needle would sweep through "in tune" on the way.

The α trade-off: a smaller value (e.g. 0.05) makes the needle calmer, but slower to react; a larger one (e.g. 0.30) makes it more responsive, but more nervous. 0.12 is a comfortable middle ground, and it's exactly the number I landed on when the needle was too nervous.

Putting it all together

This is the complete path, repeated about 60 times per second: the microphone becomes samples x[n] (steps 1 and 2); the NSDF measures how much the wave resembles a shifted copy of itself (steps 3 and 4); peak picking finds the fundamental period, dodging the octave error (step 5); the parabola refines the period to fractions of a sample (step 6); the frequency becomes a note name and cents (step 7); and the median and the exponential filter keep the needle stable (step 8). From air to a tuned note, in a few milliseconds.

Reference

  • Philip McLeod and Geoff Wyvill, A Smarter Way to Find Pitch, Proceedings of the International Computer Music Conference (ICMC), 2005.

© 2026 Giuliano Geiss Arnhold. All rights reserved.

Privacy

giuliano.codes