Task 1 of 5
A transform sounds grand. One bin of it is not: pick a frequency, build a test wave at that frequency, multiply it against the signal sample by sample, and add the products up. One number falls out, and it answers exactly one question — how much of this frequency is in here?
That is a dot product, and it is the same instinct Template Matching used on images: slide a pattern over the data and let the sum of the products score the match. Here the pattern is a cosine and the data is a row of samples.
Start with one bin and one thread. output: [1] gives you a single thread
that walks the whole signal — the conceptual atom. Task 3 hands every bin a thread of its
own, which is where this stops being a loop and starts being a GPU workload.
signal against a cosine at bin
k — sum signal[i] × cos(2π·k·i/n) over all 256 samples — and
report bins 8, 20 and 13.output: [1]: one thread owns the one answerfor (let i = 0; i < this.constants.n; i++) — WebGL needs a compile-time boundsignal[i] * Math.cos(2 * Math.PI * k * i / this.constants.n)console.log bins 8, 20 and 13 — expect 128, 64 and 0Over the whole signal, the test wave for bin k must complete
exactly k turns. Term i is therefore a fraction
i / n of the way through k turns, and one turn is
2π radians:
const angle = 2 * Math.PI * k * i / this.constants.n;Same shape as any other loop-and-total kernel: declare
let acc = 0; before the loop, add one product per iteration, and
return acc; after it.
acc += signal[i] * Math.cos(angle);k is an ordinary kernel argument, so one kernel answers every
bin — call it again with a different number:
console.log('bin 8:', bin(signal, 8)[0]);
console.log('bin 20:', bin(signal, 20)[0]);This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.