Search guides and the APICtrl K
  1. API reference
  2. DSP

spectrum_feed.h

The spectrum a plugin's interface draws, measured where the audio is.

#include <sonore/spectrum_feed.h>
On this page

Design notes

One rule, wherever it is drawn

A Sonorie plugin's page mounts an analyzer with sonore.spectrum(), and the studio's browser preview feeds it from a Web Audio AnalyserNode. A shipped plugin feeds it from here. Both follow ONE rule, so a display written once looks the same in the studio and in a DAW:

  • mono, the average of the channels (an AnalyserNode's own down-mix);
  • the newest windowFor(rate) samples -- 8192 at 44.1 / 48 kHz, doubled per doubling of the rate, so every rate sees ~170 ms and the same hertz per bin -- under a Blackman window, the one an AnalyserNode uses;
  • |X[k]| / N in dB plus kCalibrationDb, so a full-scale sine centred on a bin reads 0 dBFS (a sine's energy is split between +f and -f, and 0.42 is the Blackman window's coherent gain);
  • resampled onto a FIXED grid of kPoints points, log-spaced kLowHz .. kHighHz. A point whose band (half a grid step each side) holds bin centres reads the LOUDEST of them, so a narrow peak is never averaged away between pixels; a point below the bin spacing (the bass) is interpolated in dB between its two neighbouring bins; above Nyquist, or quieter than kFloorDb, it reads kFloorDb.

The grid is the contract, not a picture size. The browser side is spectrum.ts in the Sonorie app; change one and change the other.

Two threads

push() is the audio thread's: it writes a lock-free ring with relaxed atomics, allocates nothing and does nothing at all until something asked (setActive). analyse() is the interface's: it copies the newest window, runs the transform and fills the grid. A torn read shows one slightly wrong frame for one tick, which nobody can see; a lock on the audio thread is a dropout. The same arrangement as AudioScopeBuffer, and for the same reason the feed is owned by whatever owns the audio, not by the window that shows it.

Nothing until asked

Every instance of every plugin carries two of these (its output and its input), and most never draw a spectrum. So the ring -- 128 KB, the longest window -- is made on the main thread the first time setActive(true) is called, published to the audio thread through an atomic pointer, and freed only with the feed. An unused feed is a few dozen bytes; the wrapper's Instance has a size budget, and two inline rings would have spent it five times over.

Types 1

A plugin's spectrum, for a display: fed by the audio thread, read on the interface's clock as kPoints calibrated dBFS values on a fixed log grid.

↑↓ to move↵ to openEvery public name in the headers, and every guide