🎶 A lightweight TypeScript library for real-time spectrogram (waterfall) rendering.
Live Demo: https://anyshake.github.io/spectrogram-js/
spectrogram-js provides a simple, dependency-light way to generate FFT-based spectrograms and render them onto a <canvas>, suitable for signal processing, audio, sensor data, and scientific visualization use cases.
- Real-time spectrogram / waterfall rendering
- Configurable FFT window, overlap, and window functions
- Built-in colormaps (
jet,viridis, etc.) - Optional shared FFT executor for performance
- Designed for streaming / incremental data
- Zero framework dependency (plain Canvas API)
$ npm install spectrogram-jsor
$ pnpm add spectrogram-jsimport { Spectrogram } from 'spectrogram-js';
const canvas = document.querySelector('canvas')!;
const windowSize = 768;
const spectrogram = new Spectrogram({
windowType: 'hann', // hann, hamming, blackman, rectangular
overlap: Math.floor(windowSize * 0.86),
sampleRate: 250,
minDb: 110,
maxDb: 160,
windowSize
});
const init = async () => {
spectrogram.setColormap('jet');
await spectrogram.init();
};init() loads the necessary fonts, which is required for rendering the spectrogram.
spectrogram.render({
canvas,
width: 768,
height: 256,
timeRange: [0, 120], // seconds
freqRange: [0, 25] // Hz
});Call render() in a loop (requestAnimationFrame) for real-time updates.
You can customize appearance and visibility by setting xAxis and yAxis independently in render():
spectrogram.render({
canvas,
width: 768,
height: 256,
timeRange: [0, 120],
freqRange: [0, 25],
xAxis: {
visible: true,
color: '#2563eb',
lineWidth: 2,
labelStyle: {
color: '#1d4ed8',
fontSize: 14,
fontWeight: 600,
fontFamily: 'Arial, sans-serif'
}
},
yAxis: {
visible: false,
}
});All axis settings are optional. Defaults preserve the original appearance: visible axes,
#464646 lines at 1 px, and #252525 labels using normal-weight 11 px AxisFont.
labelStyle applies to tick labels and the frequency axis title. Sizes use CSS pixels;
fontWeight accepts a number or a CSS weight such as 'normal' or 'bold'.
Custom fonts must be available in the browser before rendering.
Setting visible: false hides that axis together with its ticks, labels, and title.
Margins adjust to label sizes and axis visibility. Omitted options use defaults on each
render() call. See example/index.ts for interactive controls for both axes.
Set xAxis.labelFormatter to control how time ticks are displayed:
spectrogram.render({
canvas,
width: 768,
height: 256,
timeRange: [0, 120],
xAxis: {
labelFormatter: (time, { offset, end, timestamped }) =>
timestamped
? new Date(time).toISOString().slice(11, 19)
: `${(end - offset).toFixed(0)}s`
}
});time is the tick timestamp in milliseconds for timestamped data, otherwise milliseconds
from the first sample. offset and end are seconds from the first sample to the tick and
to the end of the visible range. Margins and tick spacing follow the formatted label width.
Without a formatter, timestamped data shows local HH:mm:ss and other data shows seconds
before the end of the range.
Input data format:
Array<[timestamp_in_ms, value]>;For example:
spectrogram.setData([
[1765945224240, 80001],
[1765945224244, 27639],
[1765945224248, 57144],
[1765945224252, 29916],
[1765945224256, 66871]
// ...
]);You’re expected to manage buffering yourself (e.g. ring buffer).
By default, timestamped samples are placed back to back at sampleRate and only the first
timestamp is used. Set gapThreshold (milliseconds) to keep missing data visible:
const spectrogram = new Spectrogram({
windowType: 'hann',
overlap: Math.floor(windowSize * 0.86),
sampleRate: 250,
minDb: 110,
maxDb: 160,
windowSize,
gapThreshold: 500
});When two consecutive timestamps are further apart than gapThreshold, the missing time is
inserted and rendered blank (transparent), so the time axis stays aligned with the data.
Raw Float32Array / Float64Array data can mark gaps with NaN samples. Analysis windows
must contain windowSize valid samples; a column is left blank when its window contains a
missing sample or runs past the end of the data. gapThreshold applies to the next
setData() call.
Built-in colormaps include:
- viridis
- inferno
- grayscale
- jet
- hot
- cool
- spring
- summer
- autumn
- winter
- bone
See src/render/ColorMap.ts for the color definitions.
MIT License © 2025 AnyShake Project
