WRITE
SHADERS.
LiveCode is a source module that lets you write Metal shaders inside Patchframe. You write a function that returns a color for every pixel — and the result appears in real time.
The mainImage function
Every LiveCode shader must define one function called mainImage.
The module calls it once per pixel, every frame.
float3 mainImage(float2 uv, constant Uniforms &u)
{
return float3(0.5, 0.5, 0.5); // grey
}
- float3Return value — the pixel color as (R, G, B), each 0.0–1.0.
- uvPixel coordinate.
uv.x0–1 left to right,uv.y0–1 bottom to top. - uUniforms struct — time, resolution, and eight CV inputs from the patch.
Built-in variables
The u struct gives you everything the running patch knows about the current frame.
| Variable | Type | Description |
|---|---|---|
u.resolution | float2 | Canvas size in pixels (width, height). |
u.time | float | Seconds since shader start. |
u.cv[0] … u.cv[7] | float | Eight CV inputs (0–1), wired from LFO, Audio Follow, Step Seq, or any CV source. |
Time animation
float3 mainImage(float2 uv, constant Uniforms &u)
{
float v = sin(uv.x * 10.0 + u.time);
v = v * 0.5 + 0.5; // remap -1..1 → 0..1
return float3(v, v, v);
}
CV control
Connect an LFO or other CV module to the LiveCode CV0 port,
then reference it as u.cv[0] in the shader. All eight inputs work the same way.
float3 mainImage(float2 uv, constant Uniforms &u)
{
float freq = 5.0 + u.cv[0] * 20.0; // CV0 drives frequency
float v = sin(uv.x * freq + u.time);
v = v * 0.5 + 0.5;
return float3(v, v * 0.5, 1.0 - v);
}
UV space
uv is normalized: (0, 0) is the bottom-left corner, (1, 1) is the top-right.
For effects that should look the same at any aspect ratio — circles, squares — correct for the canvas proportions:
float2 p = uv * 2.0 - 1.0; // center at (0,0), range -1..1
p.x *= u.resolution.x / u.resolution.y; // aspect correction
Color gradient
float3 mainImage(float2 uv, constant Uniforms &u)
{
return float3(uv.x, uv.y, 0.5);
}
Circle
float3 mainImage(float2 uv, constant Uniforms &u)
{
float2 p = uv * 2.0 - 1.0;
p.x *= u.resolution.x / u.resolution.y;
float d = length(p);
float circle = 1.0 - smoothstep(0.3, 0.35, d);
return float3(circle, circle, circle);
}
Polar coordinates
Convert to angle and radius for radial and spiral effects.
float3 mainImage(float2 uv, constant Uniforms &u)
{
float2 p = uv * 2.0 - 1.0;
p.x *= u.resolution.x / u.resolution.y;
float angle = atan2(p.y, p.x);
float radius = length(p);
float v = sin(radius * 8.0 - u.time * 2.0) * 0.5 + 0.5;
return float3(v, v * 0.5, 1.0 - v);
}
Useful built-ins
Metal Shading Language provides the full set of GPU math functions. These are the ones you will reach for most often in LiveCode:
| Function | What it does |
|---|---|
sin(x), cos(x) | Sine and cosine. |
floor(x), fract(x) | Integer part, fractional part. |
mix(a, b, t) | Linear interpolation: a + (b − a) * t. |
smoothstep(e0, e1, x) | Smooth 0→1 transition between edges. |
length(v) | Vector length. |
dot(a, b) | Dot product. |
atan2(y, x) | Angle in radians. |
min(a, b), max(a, b) | Minimum, maximum. |
clamp(x, lo, hi) | Clamp to range. |
abs(x) | Absolute value. |
pow(x, y) | Power — useful for gamma and contrast curves. |
step(edge, x) | Hard cutoff: 0 below edge, 1 at or above. |
You can also define #define PI 3.14159265359 and #define TWO_PI 6.28318530718 at the top of your shader. Some built-in snippets already include these.
From simple to complex
Sine stripe
float3 mainImage(float2 uv, constant Uniforms &u)
{
float v = sin((uv.x + u.cv[0]) * 10.0 + u.time);
v = v * 0.5 + 0.5;
return float3(v, v * v, 1.0 - v);
}
Plasma
float3 mainImage(float2 uv, constant Uniforms &u)
{
float2 p = uv * 10.0 - 5.0;
float v = sin(p.x + u.time + u.cv[0] * 3.0);
v += sin(p.y + u.time * 0.7 + u.cv[1] * 2.0);
v += sin(length(p) + u.time * 0.5);
v = v * 0.33 + 0.5;
return float3(v, v * 0.8 + 0.2, 1.0 - v * 0.5);
}
Pseudo-noise
float hash(float2 p) {
return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453);
}
float3 mainImage(float2 uv, constant Uniforms &u)
{
float2 p = uv * (10.0 + u.cv[0] * 10.0);
float n = hash(floor(p));
return float3(n, n, n);
}
Helper functions
You can define helper functions above mainImage.
If a helper needs u.time or u.cv, pass the whole struct:
float circle(float2 p, float r) {
return 1.0 - smoothstep(r - 0.02, r + 0.02, length(p));
}
float3 mainImage(float2 uv, constant Uniforms &u)
{
float2 p = uv * 2.0 - 1.0;
p.x *= u.resolution.x / u.resolution.y;
float c = circle(p, 0.3 + u.cv[0] * 0.4);
return float3(c, c * 0.5, 1.0 - c);
}
float myEffect(float2 p, constant Uniforms &u) {
return sin(length(p) * 5.0 + u.time + u.cv[0]);
}
float3 mainImage(float2 uv, constant Uniforms &u)
{
float2 p = uv * 2.0 - 1.0;
float v = myEffect(p, u) * 0.5 + 0.5;
return float3(v, v, v);
}
Using the editor
- 1Add LiveCode from Sources.
- 2Click the module and open the LiveCode editor window.
- 3Browse the snippet library in the left panel — Attractor, Plasma, Lava, Voronoi, and more. Load one to start from working code.
- 4Edit, then press Compile — or turn on Auto to compile after each pause.
- 5Wire LFO, Audio Follow, or any CV source into
CV0–CV7for live control. - 6Connect
Outto an effect or mixer chain, then to Output.
A failed compile keeps the previous successful output on screen, marks the editor red, and shows the compiler error. If no compile has succeeded yet, output is black.
LiveCode always has exactly eight CV ports. Editing the shader code does not change the port list —
it is not dynamic like ISF. Use whichever CV inputs you need inside mainImage and leave the rest unconnected.
Common errors
| Error | Cause | Fix |
|---|---|---|
use of undeclared identifier 'u' |
Helper function uses u without receiving it. |
Add constant Uniforms &u as a parameter. |
no matching constructor for float3 |
Wrong number of arguments. | float3 needs exactly 3 components: float3(r, g, b). |
unknown type name 'in' |
GLSL syntax pasted into Metal. | Remove in / out qualifiers from function parameters. |
| Distorted aspect ratio | Using raw uv for circles or squares. |
Multiply p.x by u.resolution.x / u.resolution.y. |
| Sluggish frame rate | Heavy per-pixel math or large loops. | Reduce iteration count, simplify expressions, or lower canvas resolution. |
Learn more
- →Snippet library — browse the built-in collection inside the LiveCode editor. Each snippet is a working shader you can modify.
- →The Book of Shaders — free online guide to fragment shaders. Written in GLSL, but the concepts translate directly to Metal.
- →Shadertoy — community gallery of GLSL shaders. Port ideas to Metal by replacing
fragCoord / iResolutionwithuvandiTimewithu.time. - →LiveCode module reference — ports, controls, and starting patch.