# Smooth a Signal

*Task 5 of 6 · [Thinking in Parallel](https://gpu.rocks/learn/thinking-in-parallel-c3876efb.md) · GPU.js Learn*

Time to combine everything: a **5-tap moving average**. Each output
cell is the mean of `signal[x−2 … x+2]` — a gather over a small
*window* of neighbors, with clamping where the window hangs off either end. This
shape — loop over a fixed window, clamp, accumulate — is called a
**stencil**, and it powers blurs, edge detectors, and physics simulations
alike.

Yes, a loop *inside* the kernel is fine: it's 5 iterations of private
arithmetic per thread, not a loop over the data. 128 threads each averaging 5 numbers
is still one parallel pass.

## Figures

- **read five, write one — always your own cell**

## Goal

**Goal:** each cell returns the average of the five values centered on
it, with window indexes clamped to `0 … n−1`.

## Requirements

- Loop over the window: `for (let d = 0; d < 5; d++)` with offset `d − 2`
- Clamp every read with `Math.max(0, Math.min(n − 1, …))`
- Return the sum divided by `5`

## Hint 1 — the window

The five indexes are `this.thread.x + d - 2` for
`d = 0…4`: two to the left, itself, two to the right.

## Hint 2 — clamp inside the loop

Each iteration:

```js
const j = Math.max(0, Math.min(this.constants.n - 1, this.thread.x + d - 2));
sum += signal[j];
```

## Hint 3 — sanity-check the edge

Cell 0's clamped window reads indexes `0, 0, 0, 1, 2` — so
`out[0]` should equal `(3·signal[0] + signal[1] + signal[2]) / 5`.

## Same idea elsewhere

Windowed sums over neighbors are stencil computations — the bread and butter of
scientific codes on CUDA and ROCm, where entire papers are devoted to tiling stencils into
shared memory so the window reads come from fast on-chip storage instead of DRAM.

## Starter code

```js
// A 5-tap stencil: mean of signal[x-2 ... x+2], edges clamped.
const gpu = new GPU({ mode });

const smooth = gpu.createKernel(function (signal) {
  let sum = 0;
  for (let d = 0; d < 5; d++) {
    // TODO: read the window neighbor at offset d - 2,
    // clamped to 0 ... this.constants.n - 1
    sum += signal[this.thread.x];
  }
  return sum / 5;
}, {
  output: [128],
  constants: { n: 128 },
});

const result = await smooth(signal);
console.log('raw:', signal[64], '→ smoothed:', result[64]);
```

---

Interactive version: https://gpu.rocks/learn/thinking-in-parallel-c3876efb/5

[Previous task](https://gpu.rocks/learn/thinking-in-parallel-c3876efb/4.md) · [Next task](https://gpu.rocks/learn/thinking-in-parallel-c3876efb/6.md)
