Task 3 of 5

Fine in Time, or Fine in Frequency

Shorten the window and the time axis sharpens. Lengthen it and the frequency axis does. You cannot have both, and this is not a gpu.js limitation or a numerical one — it is a theorem. A window w samples long cannot tell two frequencies apart unless they differ by roughly 2 · SR / w, and it cannot place an event in time to better than w samples. Multiply those two together and the window length cancels: the product is a constant, and picking a window is choosing which end of it to spend.

This signal has both kinds of detail. Two steady tones, 512 Hz and 640 Hz, run the whole way through; and at 0.254 s there is a single sample driven hard — a click, the sharpest event a sampled signal can hold. Build the same spectrogram twice: same hop, same 64 frames, one with a 64-sample window and one with a 256-sample window. Then measure both, on both axes. The two measurements are already written; do not take anyone's word for which window wins, because neither of them does.

One kernel source, two compilations. this.constants.win is fixed when a kernel is created — which is precisely why it is legal as a loop bound — so handing createKernel a different constants object turns the same function body into a different kernel. Same trick a CUDA template parameter plays.

sharpen one axis and you have spent the other — there is no third option
Goal: compile the spectrogram twice — once with a 64-sample window and once with 256 — and let the two measurements say what changed.

Requirements

Hint 1 — how many bins?

A window of w real samples has w/2 bins below Nyquist, each SR / w hertz wide. A 64-sample window gives 32 bins 64 Hz apart; a 256-sample window gives 128 bins 16 Hz apart.

Hint 2 — the two calls
const shortSpec = makeSpectrogram(64, 32);
const longSpec = makeSpectrogram(256, 128);

makeSpectrogram is plain JavaScript — win and bins reach the kernel through its settings object, never through a closure, which is why this compiles at all.

Hint 3 — what you should see

The short window finds one peak where there are two tones, and pins the click to 2 frames. The long window resolves two peaks and smears the same click across 6. Neither is the right answer; they are the same information spent differently.

Same idea elsewhere

Window length is the first knob in every audio pipeline, and the choice is always this trade: a speech recogniser takes 25 ms windows with a 10 ms hop because phonemes move fast, a music transcriber takes 100 ms because it needs to tell a semitone from its neighbour, and a vibration monitor looking for a bearing fault takes seconds. Recompiling one kernel source against different compile-time constants is equally universal — template<int WIN> in CUDA, override constants in WGSL, function constants in Metal. The specialisation is what lets the compiler unroll the loop.

All tasks in Spectrograms

  1. One Spectrum, No Clock
  2. Slice, Window, Transform
  3. Fine in Time, or Fine in Frequency
  4. Decibels, Then Colour
  5. Payoff: Read the Sweep Off the Picture

This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.