# One Number Is Not Enough

*Task 2 of 5 · [The DFT, Honestly](https://gpu.rocks/learn/the-dft-7b1e3f9b.md) · GPU.js Learn*

Task 1's answer has a hole in it. Take a pure tone at bin 8 and correlate it
against a cosine at bin 8: you get 128, the whole amplitude. Now delay that identical tone
by 8 samples out of 256 — nothing about the sound has changed — and the same correlation
reads **0**. The tone is right there and the number says it is absent, because
a delay of 8 samples is a quarter turn at bin 8, and a cosine and a sine of the same
frequency are orthogonal.

The fix is to measure against *both*: a cosine and a sine. Two numbers per bin,
and the pair rotates as the signal shifts while its length — `√(re² + im²)` —
stays put. That pair is what "complex" means here. It is arithmetic on pairs of floats,
not a mysterious new number system: `re` is what the cosine measured,
`im` is what the sine measured (with a minus sign, from the standard
`e^(−2πi·ki/n)`), and nothing else about it needs believing.

**The convention this whole track uses.** gpu.js has no complex type, so a
complex result is **two planes** of floats: `output: [n, 2]`, read
as `result[p][i]` — plane `p = 0` is the real part, plane
`p = 1` the imaginary part, `i` the bin. Since
`output: [w, h]` is indexed `[y][x]`, `this.thread.y`
*is* the plane and `this.thread.x` is the bin. Here `n` is 1 —
one bin, two planes.

## Figures

- **the delay swings the pair around; the radius is the answer you wanted**

## Goal

**Goal:** compute both parts of bin 8 with one kernel —
`output: [1, 2]`, plane 0 the cosine correlation, plane 1 the sine correlation
with a minus sign — and log the real part, the imaginary part and the magnitude for both
`tone` and `shiftedTone`.

## Requirements

- `output: [1, 2]` — read back as `result[plane][0]`
- Branch on `this.thread.y`: plane 0 *adds* `signal[i] * Math.cos(angle)`, plane 1 *subtracts* `signal[i] * Math.sin(angle)`
- In JavaScript, take `Math.sqrt(re * re + im * im)` for each signal
- Log both magnitudes — both read `128`, while the real parts read `128` and `0`

## Hint 1 — one kernel, two jobs

Every thread runs the same body, so the plane has to be a branch inside it.
`this.thread.y` is 0 for the real plane and 1 for the imaginary one:

```js
if (this.thread.y === 0) acc += signal[i] * Math.cos(angle);
else acc -= signal[i] * Math.sin(angle);
```

## Hint 2 — why the minus sign

The forward transform correlates against `e^(−2πi·ki/n)`, and
`e^(−iθ) = cos(θ) − i·sin(θ)`. The minus rides on the sine, which is why
plane 1 subtracts. Getting that sign wrong mirrors the entire spectrum, so it is worth
fixing here where there is only one bin to look at.

## Hint 3 — reading the pair back

With `output: [1, 2]` the result is two rows of one value:

```js
const out = bin8(tone);
const re = out[0][0];
const im = out[1][0];
console.log('magnitude:', Math.sqrt(re * re + im * im));
```

## Same idea elsewhere

Interleaved or planar complex data is a decision every signal library makes:
cuFFT and rocFFT let you choose between `cufftComplex` (interleaved re/im pairs)
and planar layouts, VkFFT does the same, and WebGPU compute shaders usually reach for a
`vec2<f32>`. Planes suit gpu.js because an output axis is free; the
arithmetic underneath is identical whichever way the bytes are arranged.

## Starter code

```js
// Two planes, one bin: plane 0 real, plane 1 imaginary.
const gpu = new GPU({ mode });

const bin8 = gpu.createKernel(function (signal) {
  let acc = 0;
  for (let i = 0; i < this.constants.n; i++) {
    const angle = 2 * Math.PI * this.constants.k * i / this.constants.n;
    // TODO: branch on this.thread.y — plane 0 ADDS signal[i] * Math.cos(angle),
    // plane 1 SUBTRACTS signal[i] * Math.sin(angle).
    acc += signal[i] * Math.cos(angle);
  }
  return acc;
}, {
  output: [1, 2],
  constants: { n: 256, k: 8 },
});

function report(name, out) {
  const re = out[0][0];
  const im = out[1][0];
  // TODO: log the magnitude too — Math.sqrt(re * re + im * im)
  console.log(name, 'real:', re, 'imaginary:', im);
}

report('tone       ', bin8(tone));
report('shiftedTone', bin8(shiftedTone));
```

---

Interactive version: https://gpu.rocks/learn/the-dft-7b1e3f9b/2

[Previous task](https://gpu.rocks/learn/the-dft-7b1e3f9b/1.md) · [Next task](https://gpu.rocks/learn/the-dft-7b1e3f9b/3.md)
