Visual Operators
Beyond writing raw shaders (Audioreactive Shaders), std/visuals ships a set of ready-made operators modeled on TouchDesigner’s TOPs — gfx.Noise(), gfx.Blur(), gfx.Displace(), and more. Each one returns a Visual, so they wire together with the same >> and << you already use for shaders, into a node graph that renders as a multi-pass GPU pipeline.
use "std/visuals" as gfx;
let field = gfx.Noise(); // a generatorfield.param("period") << 2.0;
let soft = gfx.Blur(); // a filtersoft.param("radius") << 8.0;
field >> soft >> screen; // wire generator -> filter -> displayPLAY;Operators are per-node — every gfx.Blur() is its own object with its own uniforms — so you can drop as many of the same operator into one graph as you like and nothing collides.
The Wiring Protocol
Section titled “The Wiring Protocol”Every operator graph is built from exactly two operators, and the rule for each is uniform across all nodes.
>> — the primary input
Section titled “>> — the primary input”src >> op feeds src into op’s primary input — the shader’s iChannel0 — and, at the end of a chain, op >> screen routes the result to the display. >> is an expression: it wires its left side into its right and evaluates to the downstream node. So chains read left-to-right and compose inline:
use "std/visuals" as gfx;
let blurred = gfx.Noise() >> gfx.Blur(); // wires Noise -> Blur, returns the Blur nodeblurred >> screen; // ...which is reusable downstreamPLAY;Because each >> hands back the node it fed, a >> b >> c flows left-to-right and the whole chain is itself a value. >> never mutates the upstream node, so a node stays reusable for fan-out (below).
<< — named slots and uniforms
Section titled “<< — named slots and uniforms”op.param("name") << rhs binds a node’s input by name. It is type-dispatched on the right-hand side:
-
A node ⇒ a texture input edge — it wires that node into the operator’s named second input (the next
iChannel). This is how two-input operators get their other input:comp.param("b") << other; // Composite's second input Bdisp.param("by") << field; // Displace's displacement fieldlook.param("gradient") << ramp; // Lookup's colour gradient -
A constant, signal, meter feed, or 2–4-element array ⇒ a uniform. Scalars bind a
float; a#[x, y]/#[x, y, z]/#[x, y, z, w]array binds avec2/vec3/vec4:noise.param("period") << 2.0; // float constantedge.param("strength") << Sine(8).range(0.5, 2.0); // signalblur.param("radius") << drums.rms.smooth(80); // audio meter feedramp.param("color0") << #[0.1, 0.0, 0.2]; // vec3 from an array
Uniforms are sampled once per rendered frame, off the audio thread — binding a signal or meter never touches the real-time path.
Fan-out
Section titled “Fan-out”To use one node’s output in several places, bind it to a let and reference it from each consumer. The graph is de-duplicated by identity: the topo-sort sees the same node object and renders it as a single pass, sampled by every downstream consumer in the same frame. The graph is acyclic — within a frame, every edge points forward.
use "std/visuals" as gfx;
let field = gfx.Noise();field.param("period") << 2.0;
// Two independent blurs, both reading the SAME noise pass — one Noise render, sampled twice.let soft = gfx.Blur();soft.param("radius") << 6.0;let softer = gfx.Blur();softer.param("radius") << 24.0;
let comp = gfx.Composite();comp.param("operand") << 2.0; // subtract: A - Bfield >> soft >> comp; // soft is A (iChannel0)comp.param("b") << (field >> softer); // softer is B (iChannel1)
comp >> screen;PLAY;Here field is referenced three times but renders once; soft and softer each sample that one noise pass.
Feedback
Section titled “Feedback”A node graph is acyclic within a frame, so to read last frame’s output — a feedback loop — wrap the wiring in gfx.feedback(fn(prev) { … }). The closure is called with prev, the group’s previous-frame output as a sampleable visual; wire it into a slot to close the loop, and return the node that is the group’s output:
use "std/visuals" as gfx;
let look = gfx.feedback(fn(prev) { let field = gfx.Noise(); field.param("period") << 3.0;
// Displace this frame's noise BY last frame's output -> a flowing feedback trail. let disp = gfx.Displace(); disp.param("amount") << 0.1; disp.param("by") << prev; // close the loop with prev return field >> disp; // disp is the group output (prev tracks it)});
look >> screen;PLAY;The renderer realizes the loop as a ping-pong FBO pair: it renders into buffer A while prev reads buffer B, then swaps. That swap is the only thing feedback adds over fan-out — everything inside the closure is an ordinary acyclic graph.
Operator Reference
Section titled “Operator Reference”All operators come from use "std/visuals" as gfx;. Generators have no primary input; filters take one via src >> op; 2-in filters take a second via a named slot (op.param("slot") << node). Every pass clamps its output to [0, 1].
| Operator | Kind | Parameters |
|---|---|---|
gfx.Noise() | generator | period, harmonics, gain, exponent, offset |
gfx.Ramp() | generator | type, color0…color3 (vec3), pos1, pos2 |
gfx.Blur() | filter | radius |
gfx.Edge() | filter | strength |
gfx.Slope() | filter | strength, offset |
gfx.LumaLevel() | filter | brightness, black, gamma, invert |
gfx.RgbKey() | filter | key (vec3), threshold |
gfx.Transform() | filter | translate (vec2), rotate, scale (vec2), pivot (vec2), extend |
gfx.Composite() | filter, 2-in | operand; second input c.param("b") << node |
gfx.Displace() | filter, 2-in | amount; displacement field d.param("by") << node |
gfx.Lookup() | filter, 2-in | gradient l.param("gradient") << ramp |
Vector parameters (key, color0…, translate, scale, pivot) bind from a 2–4-element array, e.g. t.param("scale") << #[1.5, 1.5].
A few parameters are mode selectors, rounded to the nearest integer:
gfx.Composite()operand— the blend:0over,1add,2subtract,3multiply,4difference,5subtractive (A+B−1),6max/lighten,7min/darken,8screen.gfx.Transform()extend— how pixels outside the source are filled:0hold/clamp,1repeat,2mirror,3black.rotateis in degrees (positive is counter-clockwise) and aspect-corrected, so a wide canvas spins without shearing.gfx.Ramp()type— gradient orientation:0horizontal,1vertical,2radial. The default keys make a black→blue→light-blue→white palette, ready to feed agfx.Lookup().
A Full Patch
Section titled “A Full Patch”examples/05_visuals/04_voronoi_feedback.non rebuilds a complete TouchDesigner feedback-displacement network — pseudo-voronoi cells that swell, shimmer, and warp in time with a polyrhythm and an acid lead — node-for-node from these operators. It is the canonical worked example of fan-out, feedback, and audioreactive uniform binding together.
Next Steps
Section titled “Next Steps”- Audioreactive Shaders — writing raw fragment shaders, built-in uniforms, and the browser workflow
- Signals & Automation — the LFOs and envelopes you bind to operator uniforms
- Tracks & Master — where
.rms/.peakmeter feeds come from