Docs · Custom effects
Custom effects
Write a fragment shader, get a real effect. It has its own layer slot, an inspector full of controls, and every one of those controls binds to the audio. Or build the same thing visually, without writing GLSL.
What you get
You write GLSL with a sprinkle of // @param comments. The
editor turns that same source into a fully integrated effect: it appears
in the Add gallery under Custom, gets an auto-generated
parameter panel, saves inside your project file, and exports on its own
as a .zestial-fx.json you can share.
Nothing about a custom effect is second-class. Its parameters bind to audio exactly like a built-in effect's, and it renders in the export the same way it renders in the preview.
The editor
- Live preview. A canvas under the parameter list compiles what you're typing and runs it against real audio features, so you see the effect react as you write it.
- Validation as you go. The editor compiles two seconds after you stop typing, or immediately when you press Validate. Errors flag their line in the gutter.
- Nothing breaks mid-edit. While a shader is invalid, the composition keeps rendering the last version that compiled, and the badge tells you that's what you're looking at.
- Scene or Post. Scene draws a full-screen quad; Post
reads the scene so far from
tDiffuseand transforms it.
The @param directive
Put a // @param line directly above a uniform and it
becomes a control in the inspector:
// @param min=0 max=4 step=0.01 default=1.5 label="Speed"
uniform float uSpeed;
// @param default=#ff5fa2 label="Color A"
uniform vec3 uColorA;
// @param default=true label="Sharpen"
uniform bool uSharpen;
// @param options=soft,hard default=soft label="Mode"
uniform int uMode;
// @param min=0 max=2 default=0.5 bindable=false label="Aspect"
uniform float uAspect; The control you get is inferred from the uniform's type:
float, int | Number slider, or a select if you give it options= |
vec3 | Colour swatch |
bool | Checkbox |
int with options= | Dropdown |
Number parameters are bindable to audio by default. Add
bindable=false for the ones that shouldn't be. An aspect
correction, say, that has a correct value rather than an expressive one.
Built-in uniforms
These are supplied for you every frame. Declare one and use it, but
don't give it a @param, which the parser rejects:
uTime | Seconds since the effect started |
uResolution | Canvas size in pixels |
uRms, uPeak | Overall level, smoothed and instantaneous |
uBeat, uBeatPhase, uBpm | Beat pulse, position within the beat, detected tempo |
uProgress | Position through the track, 0 to 1 |
uBandSub … uBandAir | Seven frequency bands, sub-bass through air |
tDiffuse | The scene so far, post effects only |
Spectrum textures
For anything that needs more than seven bands, two textures carry the full spectrum. Declare them only if you use them:
uniform sampler2D uSpectrum; // 64x1, this frame
uniform sampler2D uSpectrumHistory; // 64x64 waterfall
// x in 0..1 reads low -> high frequency
float mag = texture2D(uSpectrum, vec2(x, 0.5)).r; uSpectrum is 64 log-spaced magnitudes from 30 Hz to
16 kHz for the current frame. uSpectrumHistory stacks
those rows into a 64×64 waterfall. v=0 is now,
v=1 is about sixty-four frames ago. That's where scrolling
spectrograms and trails come from. Both use linear filtering, so
fractional coordinates interpolate for free. Both are shared too, so
sampling them from ten effects costs the same as sampling them from one.
Building one without code
Every custom effect can also be built as a node graph, which compiles to exactly the same GLSL. Reach it from Add → Custom, either with the Code | Graph toggle or the New from graph starter… tile, which opens straight into a working graph you can take apart.
- Press to search the node palette. It holds sources, maths, vector ops, procedural patterns and post nodes, grouped by category.
- Click a socket, then another, to wire them. Zestial refuses connections that can't type-check or would make a cycle, and shows you why rather than failing silently. Esc cancels a half-drawn wire.
- You edit node parameters inline, in the node body. Numbers, colours, and which audio band a source follows.
- Ctrl+Z / Ctrl+Shift+Z undo and redo inside the editor, Ctrl+D duplicates, and F zooms to fit. Your pan and zoom are remembered with the graph.
Audio nodes follow the same features the shader runtime exposes: overall level, beat, tempo, and the seven bands.
One-way door worth knowing about. A graph can be converted to code. You get the generated GLSL and the graph is discarded, behind a confirmation. There's no route back: Zestial doesn't parse hand-written GLSL into a graph. So the Graph toggle is disabled once you've written your own code, rather than offering to destroy it.
Where custom effects live
Custom effects travel inside your .zestial.json project, so
opening a project on another machine brings its effects with it. To move
a single effect between projects, or send one to someone else, export
it as a .zestial-fx.json, which is self-contained and needs
no project context to import.