Documentation/Source example
Example source

samples/vis/Shader - Spectrum.js

Referenced by:vis.CreateAsync
include('docs/Flags.js');
window.DrawMode = 1;
// JSplitter vis sample: Spectrum | custom HLSL.
// Source: FFT frequency bands and held peaks; render options affect only the presentation.
// Native SpectrumSource supplies already normalized levels and held peaks.
// t0 = levels, t1 = peaks: R32F, one texel per frequency band (0..1).
// b0 = float2 Draw size, float elapsed time, float sourceCount.
// The shader 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);

function hlslColor(argb) {
    argb >>>= 0;
    return [
        ((argb >>> 16) & 0xff) / 255,
        ((argb >>> 8) & 0xff) / 255,
        (argb & 0xff) / 255,
        ((argb >>> 24) & 0xff) / 255
    ].map(value => value.toFixed(8)).join(', ');
}

const shader = `
Texture2D<float> Levels : register(t0);
Texture2D<float> Peaks  : register(t1);

cbuffer VisConstants : register(b0)
{
    float2 viewportSize;
    float time;
    float sourceCount;
};

struct VS_OUTPUT
{
    float4 clipSpacePos : SV_POSITION;
    float4 sceneSpacePos : SCENE_POS;
};

static const float4 SpectrumColor = float4(${hlslColor(textColor)});
static const int Gap = 1;

int VisibleBarCount(int widthPixels, int count)
{
    // Built-in default: barWidth=0, gap=1. Preserve the gap and clip excess
    // source bands on the right if even 1 px bars cannot all fit.
    int fitCount = (widthPixels + Gap) / (1 + Gap);
    return min(count, fitCount);
}

int BaseBarWidth(int widthPixels, int count, int visible)
{
    if (visible <= 0) return 0;
    if (visible < count) return 1;
    int barArea = max(0, widthPixels - Gap * (visible - 1));
    return barArea / visible;
}

bool AlternateBarWidth(int widthPixels, int count, int visible)
{
    if (visible <= 0 || visible < count) return false;
    int barArea = max(0, widthPixels - Gap * (visible - 1));
    int remainder = barArea % visible;
    int extraCount = visible / 2;
    return extraCount > 0 && remainder >= extraCount;
}

int BarLeft(int index, int baseWidth, bool alternate)
{
    int extraBefore = alternate ? index / 2 : 0;
    return index * (baseWidth + Gap) + extraBefore;
}

int BarWidth(int index, int baseWidth, bool alternate)
{
    return baseWidth + (alternate && (index & 1) != 0 ? 1 : 0);
}

int ReadBarIndex(int x, int count, int baseWidth, bool alternate)
{
    int lo = 0;
    int hi = count;
    [loop]
    for (int step = 0; step < 16 && lo < hi; ++step)
    {
        int mid = lo + (hi - lo) / 2;
        if (BarLeft(mid, baseWidth, alternate) <= x)
            lo = mid + 1;
        else
            hi = mid;
    }

    int index = lo - 1;
    if (index < 0 || index >= count) return -1;
    int left = BarLeft(index, baseWidth, alternate);
    return x >= left && x < left + BarWidth(index, baseWidth, alternate) ? index : -1;
}

float4 Premultiply(float4 color, float coverage)
{
    float alpha = color.a * saturate(coverage);
    return float4(color.rgb * alpha, alpha);
}

float4 CompositeOver(float4 foreground, float4 background)
{
    return foreground + background * (1.0 - foreground.a);
}

float4 main(VS_OUTPUT input) : SV_Target
{
    int widthPixels = max(1, (int)floor(viewportSize.x));
    int heightPixels = max(1, (int)floor(viewportSize.y));
    int count = max(0, (int)sourceCount);
    int visible = VisibleBarCount(widthPixels, count);
    if (visible <= 0) return float4(0.0, 0.0, 0.0, 0.0);

    int baseWidth = BaseBarWidth(widthPixels, count, visible);
    bool alternate = AlternateBarWidth(widthPixels, count, visible);
    int pixelX = (int)floor(input.sceneSpacePos.x);
    int index = ReadBarIndex(pixelX, visible, baseWidth, alternate);
    if (index < 0) return float4(0.0, 0.0, 0.0, 0.0);

    float level = saturate(Levels.Load(int3(index, 0, 0)).r);
    float peak = saturate(Peaks.Load(int3(index, 0, 0)).r);
    float barRise = level * viewportSize.y;
    float peakRise = peak * viewportSize.y;
    int pixelRow = (int)floor(input.sceneSpacePos.y);

    float barCoverage = 0.0;
    if (barRise > 0.0)
    {
        int topRow = (int)floor(viewportSize.y - barRise + 0.5);
        if (pixelRow >= topRow)
            barCoverage = 1.0;
    }

    float peakCoverage = 0.0;
    if (peakRise >= 1.0)
    {
        int peakRow = (int)floor(viewportSize.y - peakRise);
        if (pixelRow == peakRow)
            peakCoverage = 1.0;
    }

    float4 barPixel = Premultiply(SpectrumColor, barCoverage);
    float4 peakPixel = Premultiply(SpectrumColor, peakCoverage);
    return CompositeOver(peakPixel, barPixel);
}
`;

const source = {
    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 (clamped to Nyquist); allowed: value > minFrequency.
    minDb: -72, // Default: -72 dB; allowed: -160 <= value < maxDb.
    maxDb: 0, // Default: 0 dB; allowed: minDb < value <= +24.
    gain: 1, // Default: 1; allowed: > 0.
    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.
};

let visual = null;
vis.CreateAsync(VisSourceType.Spectrum, source, { shader })
    .then(value => {
        visual = value;
        window.Repaint();
    })
    .catch(error => console.log("Spectrum HLSL: " + error));

function on_paint(gr) {
    const w = window.Width, h = window.Height;
    gr.FillSolidRect(0, 0, w, h, backgroundColor);
    if (visual && w > 0 && h > 0) visual.Draw(gr, 0, 0, w, h);
}

setInterval(() => window.Repaint(), 33);