Track 4 — Warp fields

Every knob in Tracks 1–3 applied to the whole screen at once: one zoom value, one rot value, the same for every pixel. per_pixel code breaks that — it runs once per mesh point, so the same knobs can take a different value depending on where the point is. This is how MilkDrop gets depth, tunnels, and ripples out of a flat feedback loop.

Lesson 1 · One value everywhere

The baseline, so the next lesson has something to compare against:

zoom=1.02

▶ Run uniform zoom

Every point on the mesh redraws 1.02× larger, center included. Flat, uniform outward push — the whole frame breathing as one rigid sheet.

Lesson 2 · rad — distance from center

per_pixel code gets two built-in reads that per_frame doesn't: rad, each mesh point's distance from the screen center (0 at dead center, ~1 at the edge), and ang, its angle around that center. Neither follows cx/cy: moving the pivot moves where zoom and rot act, not where rad is measured from. Pattern 3 from the coding guide:

per_pixel_1=zoom=(zoom-1)*rad+1;

▶ Run radial zoom

Read it at the two extremes. At rad=0: (zoom-1)*0+1 collapses to 1 — the center point is untouched, no matter what zoom was set to. At rad=1: (zoom-1)*1+1 simplifies back to zoom — the edge gets the full per-frame value. Everywhere in between is a smooth ramp. One line turns a flat push into something with a center of gravity — the difference between a screensaver and a tunnel.

This is the single most-copied line in the entire preset corpus for a reason: it's cheap, it's one line, and it instantly adds depth to any motion you already have.

Lesson 3 · ang — direction from center

Add ang and you can steer, not just scale. Pattern 4 from the coding guide:

per_pixel_1=dr=0.01+0.02*sin(rad*12+time*0.5);
per_pixel_2=dx=dx+dr*cos(ang)*0.6;
per_pixel_3=dy=dy+dr*sin(-ang)*0.6;

▶ Run the rings

dx/dy here work exactly like Track 2's screen-wide push — they're just being set to a different value per pixel instead of one constant.

Lesson 4 · Bridging per_frame and per_pixel with q-vars

per_frame runs once and can read audio; per_pixel runs per point and can read rad/ang — but it can't read audio directly, and it has no memory between frames. The bridge is a q-register, written once in per_frame and read by every point in per_pixel:

per_frame_1=ra=6/fps;
per_frame_2=bass_avg=bass_avg*(1-ra)+ra*bass;
per_frame_3=q8=bass_avg;
per_pixel_1=dr=0.01+0.05*q8*sin(rad*12+time*0.5);
per_pixel_2=dx=dx+dr*cos(ang)*0.6;
per_pixel_3=dy=dy+dr*sin(-ang)*0.6;

▶ Run the audio-driven rings

per_frame does the Track 3 work — RC-smooth the bass, once — and drops the result in q8. Every mesh point then reads the same q8 this frame, so the rings' amplitude breathes with the music while the per-pixel shape logic stays untouched. This is the general pattern: smooth and derive in per_frame, distribute in per_pixel. Reversing it — smoothing separately at every mesh point — would be redundant work, computed hundreds of times a frame instead of once.

Lesson 5 · Not everything has to be radial

rad/ang are convenient, not mandatory — x and y (each point's raw screen position, 0..1) are available too and let you build a current centered anywhere:

per_pixel_1=nx=x-0.75;
per_pixel_2=ny=y-0.3;
per_pixel_3=nrad=sqrt(nx*nx+ny*ny)+0.001;
per_pixel_4=dx=dx-0.02*ny/nrad;
per_pixel_5=dy=dy+0.02*nx/nrad;

▶ Run the off-center current

nx/ny recenter the coordinate system on an arbitrary point — here (0.75, 0.3), off in the upper right, independent of wherever cx/cy has the rotation pivot. nrad is a hand-built distance (the +0.001 avoids a division by exactly zero at the new center). The -ny/nrad, nx/nrad pair is a 90°-rotated unit vector — pushing perpendicular to the radius produces a swirl around the off-center point rather than a push toward or away from it. Whenever a preset has a distortion source that clearly isn't at screen center, this is usually how.

What you can now build

Anything that varies across the screen — tunnels, ripples, off-center vortices, audio-breathing rings — is per-pixel code doing exactly what you've just written: read rad/ang (or build your own coordinate system from x/y), turn that into a push, and optionally scale it by a q-var carried over from per_frame.

Next: Track 5 — Waves and shapes, where custom waves and shapes — the layer drawn on top of the warped feedback loop — get their own equations.