# From Lag to Pitch

*Task 2 of 5 · [Autocorrelation & Pitch](https://gpu.rocks/learn/autocorrelation-b159433f.md) · GPU.js Learn*

A lag is a count of samples; a pitch is a count of hertz. One division bridges
them, and it is easy to write upside down. The peak lag *is* the period, so the
period in seconds is `lag / sampleRate` and the frequency is its reciprocal:
**`sampleRate / lag`**. Put those the wrong way round and the
answer is out by four or five orders of magnitude, which at least fails loudly.

Now the real problem. Lags are integers. At 8192 Hz, lag 18 means 455.1 Hz,
lag 19 means 431.2 Hz and lag 20 means 409.6 Hz — steps of more than
20 Hz, which up here is nearly a semitone. The signal below is concert A, 440 Hz,
whose true period is 18.618 samples. The best integer lag is 19, and a tuner built on it
would report 431 Hz: **35 cents flat**, audibly wrong, and no amount of
GPU makes the grid any finer.

Three numbers fix it. Near its top the correlation curve is smooth, so fit a parabola
through the peak sample and its two neighbours and take the apex — the peak *between*
the samples. This is the one part of the module that belongs on the CPU: it is three
numbers and a divide, done once.

## Figures

- **the tallest sample is not the peak — the peak is between two of them**

## Goal

**Goal:** find the peak lag, refine it to a fraction of a sample with
parabolic interpolation, convert it to hertz, and log both.

## Requirements

- Scan from lag `8` for the peak lag `p` (the kernel is already written for you)
- Interpolate: `δ = ½(r[p−1] − r[p+1]) / (r[p−1] − 2·r[p] + r[p+1])`, and the refined lag is `p + δ`
- Log the refined lag: `console.log('refined lag:', p + delta)`
- Log the pitch: `console.log('pitch:', sampleRate / (p + delta), 'Hz')`

## Hint 1 — the parabola, in one line

```js
const a = r[p - 1];
const b = r[p];
const c = r[p + 1];
const delta = (0.5 * (a - c)) / (a - 2 * b + c);
```

`delta` always lands between −0.5 and +0.5 — it is a nudge, not a jump.

## Hint 2 — which way does it move?

Here the true peak is to the *left* of the sampled one, so
`delta` comes out **negative** and the refined lag is smaller
than `p`. If your refinement pushes the answer further from 440 Hz than
the raw integer lag did, the two terms in the numerator are the wrong way round: it is
`r[p - 1] - r[p + 1]`, left minus right.

## Hint 3 — "but I want to hum into my laptop"

So would we. In a browser page the wiring is short:

```js
const mic = await navigator.mediaDevices
  .getUserMedia({ audio: true });
const audio = new AudioContext({ sampleRate: 8192 });
const analyser = audio.createAnalyser();
analyser.fftSize = 512;
audio.createMediaStreamSource(mic).connect(analyser);

const frame = new Float32Array(analyser.fftSize);
analyser.getFloatTimeDomainData(frame);
const r = correlate(frame);   // the same kernel, real audio
```

None of that runs here, and not for a policy reason: your code executes inside a Web
Worker, and a Worker has no `AudioContext`, no
`OfflineAudioContext` and no `navigator.mediaDevices`. So the
signals in this module are synthesised in the course's own source, which has the
consolation that every expected answer is exact.

## Same idea elsewhere

Sub-sample peak refinement is everywhere in GPU work under other names: it is
the same quadratic fit stereo matchers use to get sub-pixel disparity, that keypoint
detectors use to place a corner between pixels, and that time-of-flight sensors use
between range bins. The pattern is identical — the grid is coarse, the underlying function
is smooth, so fit and solve rather than sample harder.

## Starter code

```js
// The correlation kernel is done. The arithmetic after it is the lesson.
const gpu = new GPU({ mode });

const correlate = gpu.createKernel(function (signal) {
  const lag = this.thread.x;
  let sum = 0;
  for (let i = 0; i < this.constants.n; i++) {
    if (i + lag < this.constants.n) {
      sum += signal[i] * signal[i + lag];
    }
  }
  return sum;
}, {
  output: [256],
  constants: { n: 512 },
});

const r = correlate(signal);

let p = 8;
for (let lag = 8; lag < 255; lag++) {
  if (r[lag] > r[p]) p = lag;
}
console.log('peak lag:', p, '->', sampleRate / p, 'Hz (integer lags only)');

// TODO: fit a parabola through r[p - 1], r[p], r[p + 1] and take its apex.
//   const delta = ...
// Then log the refined lag and the pitch:
//   console.log('refined lag:', p + delta);
//   console.log('pitch:', sampleRate / (p + delta), 'Hz');
```

---

Interactive version: https://gpu.rocks/learn/autocorrelation-b159433f/2

[Previous task](https://gpu.rocks/learn/autocorrelation-b159433f/1.md) · [Next task](https://gpu.rocks/learn/autocorrelation-b159433f/3.md)
