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
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;
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;
rad*12— the12is a frequency in radians, not a count. Across aradof 0 to 1 it covers 12 ÷ 2π, about two full sine cycles, so about two rings between the center and the edge. Raise it to40and count again (about six).time*0.5inside the samesin(...)— slides the ring pattern over time. Because the time term is added, the rings drift inward; write-time*0.5and they travel outward, like ripples leaving a source.dris a magnitude — how far this pixel should be pushed this frame.cos(ang)andsin(-ang)turn that magnitude into a direction: push each point along its own radial line, not sideways. That's why the result is rings centered on the screen rather than a wave sliding across it.
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;
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;
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.