Documentation/Source example
Example source
samples/vis/JS - Spectrum.js
Referenced by:vis.Create
include('docs/Flags.js');
// JSplitter vis sample: Spectrum | JavaScript callback.
// Source: FFT frequency bands and held peaks; render options affect only the presentation.
// JSplitter vis: Spectrum rendered in JavaScript (works with either GDI or Direct2D).
// The callback below reproduces the built-in renderer defaults: automatic
// integer-pixel bar widths, 1 px gaps, UI text color and 1 px peak markers.
vis.UpdateIntervalMs = 16; // Default: 16 ms; allowed: unsigned integer 1..4294967295 ms.
const textColor = window.InstanceType === 1
? window.GetColourDUI(0) : window.GetColourCUI(0);
const backgroundColor = window.InstanceType === 1
? window.GetColourDUI(1) : window.GetColourCUI(1);
// Callback data (one value per frequency band):
// data.levels: Float32Array(Bands) - current band levels, normalized to 0..1.
// data.peaks: Float32Array(Bands) - held band peaks, normalized to 0..1.
// JSplitter owns both arrays, reuses them between frames and replaces them
// when Bands changes. Always read the arrays from the current callback data.
const spectrum = vis.Create(VisSourceType.Spectrum, {
channel: "mix", // Default: "mix"; also accepts a vis.AudioFormat channel name or unsigned PCM index.
fftSize: 4096, // Default: 4096; allowed: power of two, 256..16384.
bands: 64, // Default: 64; allowed: integer 4..4096.
minFrequency: 40, // Default: 40 Hz; allowed: 0 < value < maxFrequency.
maxFrequency: 23000, // Default: 23000 Hz; allowed: value > minFrequency. clamped to Nyquist.
minDb: -72, // Default: -72 dB; allowed: -160 <= value < maxDb.
maxDb: 0, // Default: 0 dB; allowed: minDb < value <= +24.
attackMs: 18, // Default: 18 ms; allowed: >= 0 ms.
releaseMs: 120, // Default: 120 ms; allowed: >= 0 ms.
peakHoldMs: 150, // Default: 150 ms; allowed: >= 0 ms.
peakGravity: 3, // Default: 3; allowed: >= 0.
gain: 1 // Default: 1; allowed: > 0.
}, (gr, x, y, w, h, data) => {
const { levels, peaks } = data;
if (!levels.length || peaks.length !== levels.length) return;
// Match the built-in renderer's default integer-pixel layout exactly.
const width = Math.max(0, Math.floor(w));
const height = Math.max(0, Math.floor(h));
if (!width || !height) return;
const gap = 1;
const fitCount = Math.floor((width + gap) / (1 + gap));
const barCount = Math.min(levels.length, fitCount);
if (!barCount) return;
const totalGap = gap * (barCount - 1);
const barArea = Math.max(0, width - totalGap);
const clipped = barCount < levels.length;
const baseWidth = clipped ? 1 : Math.floor(barArea / barCount);
const remainder = clipped ? 0 : barArea % barCount;
const alternatingExtraCount = Math.floor(barCount / 2);
const alternateWidth = alternatingExtraCount > 0 && remainder >= alternatingExtraCount;
let barX = Math.round(x);
const top = Math.round(y);
for (let i = 0; i < barCount; ++i) {
const barWidth = baseWidth + (alternateWidth && (i & 1) ? 1 : 0);
const level = Math.max(0, Math.min(1, levels[i]));
const peak = Math.max(0, Math.min(1, peaks[i]));
const barRise = level * height;
if (barRise > 0 && barWidth > 0) {
const topRow = Math.max(0, Math.min(height, Math.floor(height - barRise + 0.5)));
if (topRow < height)
gr.FillSolidRect(barX, top + topRow, barWidth, height - topRow, textColor);
}
const peakRise = peak * height;
if (peakRise >= 1 && barWidth > 0) {
const peakRow = Math.floor(height - peakRise);
if (peakRow >= 0 && peakRow < height)
gr.FillSolidRect(barX, top + peakRow, barWidth, 1, textColor);
}
barX += barWidth + gap;
}
});
function on_paint(gr) {
const w = window.Width, h = window.Height;
gr.FillSolidRect(0, 0, w, h, backgroundColor);
if (w > 0 && h > 0) spectrum.Draw(gr, 0, 0, w, h);
}
setInterval(() => window.Repaint(), 33);