Sadly, the can't-happen happened. Add some diagnostic info.
Use a single JSONEncoder for all emitted JSON.

This will help ensure consistency and save a few allocations, and may
be useful if we want custom serialization in the future.
Correct comment about spectrum texture height.
Correct alignment of FFT bins on display.

The previous implementation was under the incorrect impression that a
single FFT bin covered the frequency range (i, i+1)*f_s/N, whereas it
is actually (i-1/2, i+1/2)*f_s/N. Thus the displays were mispositioned
by one-half of a bin width.

We correct this by first removing the half-swapping from SpectrumCell,
and then changing the four spectrum displays ({2D, WebGL} ×
{WaterfallPlot, SpectrumPlot}) to use the correct coordinate system
and wrap around when looking up FFT values.

Unfortunately, because the WaterfallPlot uses a strategy of setting
its canvas width equal to the FFT width, it cannot correctly display
when zoomed in, so it remains incorrect by 1/2 bin width (and therefore
is now visibly mismatched with the SpectrumPlot).