First sketches: the p5.js/Processing drawing core
Learning objectives
- learner can structure a sketch with setup/draw, coordinate system, and primitive shapes with fill/stroke state
- learner can drive repetition and branching with variables, for-loops, and if-else conditionals
- learner can specify colour in RGB and map values between ranges to parameterise a drawing
Capstone — one whole task that evidences the objectives
Build an animated generative composition from scratch that loops shapes across a coordinate grid, uses variables and conditionals to vary them, and colours them via map()-driven RGB — running smoothly in the draw loop.
Prerequisite modules
This module is where visuals for a live set stop being someone else’s demo and become something you can build on a blank canvas. In an audio-visual live-coding rig, p5.js or Processing is typically the projection layer: a sketch running fullscreen next to your sound engine, redrawing sixty times a second, its parameters ready to be wired to amplitude or MIDI later. Before any of that reactivity is possible, you need the drawing core — and that is the whole task here: an animated generative composition that fills the screen, varies itself, and never freezes the frame loop.
The arc starts fully supported. First exercises are static: place primitives on the canvas using the top-left-origin coordinate system, styling them with fill/stroke state (“Processing separates fill, stroke, and strokeWeight into independent state settings” is your JIT pointer when a shape inherits the wrong colour). Then motion enters via the two lifecycle functions — “setup() runs once and draw() runs every frame” — and a single variable nudged each frame. From there, scaffolding drops away: a for-loop turns one shape into a grid, conditionals make cells diverge, and “p5.js map() rescales a value from one numeric range into another” converts loop counters into RGB channels.
The required atoms gate the capstone directly: without setup/draw there is no animation, without loops no grid, without map() no parameterised colour. Supporting atoms enrich rather than gate — the frame-loop procedure atom deepens the same setup/draw material with frameRate() and trail effects, variable scope explains the classic frozen-animation bug, HSB colour modes and custom functions point toward cleaner, more expressive sketches, and the abstraction principle frames why one call can draw so much.
Walkthrough
p5.js is the other visual paradigm: where Hydra is signal-flow, p5 is imperative drawing — you place shapes, frame by frame. Paste each sketch into the p5 web editor and press Run (▶). Each step is a complete, standalone sketch.
1 — setup() and draw(). The two lifecycle functions: setup() runs once (make the canvas), draw() runs ~60×/second (paint a frame). background() clears each frame; coordinates start at the top-left, y going down ([[processing-setup-draw-loop]], [[processing-coordinate-system]]).
function setup() {
createCanvas(400, 400);
}
function draw() {
background(20);
noStroke();
fill(255, 140, 0);
ellipse(200, 200, 120, 120);
}
2 — shapes and fill/stroke state. fill, stroke, and strokeWeight are sticky state — every shape after a fill() uses it until you change it (the classic “why is my circle the wrong colour?” bug). Place a couple of primitives and style them independently ([[processing-shape-primitives]], [[processing-stroke-fill-attributes]]).
function setup() { createCanvas(400, 400); }
function draw() {
background(20);
noStroke();
fill(80, 180, 255);
rect(60, 60, 120, 120);
stroke(255);
strokeWeight(3);
fill(255, 90, 120);
ellipse(280, 280, 120, 120);
}
3 — motion from a variable. Declare a variable outside draw(), nudge it each frame, and the shape moves. A faint background alpha leaves trails. This is animation: state that persists across frames ([[processing-variables-data-types]], [[processing-frame-loop]]).
let x = 0;
function setup() { createCanvas(400, 400); }
function draw() {
background(20, 40);
noStroke();
fill(255, 140, 0);
ellipse(x, 200, 40, 40);
x = x + 3;
if (x > width) x = 0;
}
4 — a for-loop makes a grid. One shape becomes many. Nested for loops walk x and y across the canvas, drawing a shape at each cell — a whole composition from four lines ([[processing-for-loop]]).
function setup() { createCanvas(400, 400); }
function draw() {
background(20);
noStroke();
fill(120, 200, 255);
for (let gx = 40; gx < width; gx += 60) {
for (let gy = 40; gy < height; gy += 60) {
ellipse(gx, gy, 30, 30);
}
}
}
5 — conditionals make cells diverge. An if / else on each cell’s position breaks the uniform grid — some cells become squares, some circles. Rule-driven variation is the seed of generative art ([[processing-if-else-conditionals]]).
function setup() { createCanvas(400, 400); }
function draw() {
background(20);
noStroke();
for (let gx = 40; gx < width; gx += 60) {
for (let gy = 40; gy < height; gy += 60) {
if ((gx + gy) % 120 === 40) {
fill(255, 90, 120);
rect(gx - 15, gy - 15, 30, 30);
} else {
fill(120, 200, 255);
ellipse(gx, gy, 30, 30);
}
}
}
}
6 — map() for colour, animated (the capstone). map(v, a, b, c, d) rescales a value from one range to another — here a cell’s x/y into RGB channels, so the grid becomes a colour field. Drive size with sin(frameCount) and it breathes. This is the animated generative composition the capstone asks for ([[processing-rgb-color]], [[p5js-map-function]]):
function setup() { createCanvas(400, 400); }
function draw() {
background(15);
noStroke();
for (let gx = 40; gx < width; gx += 50) {
for (let gy = 40; gy < height; gy += 50) {
let r = map(gx, 0, width, 40, 255);
let b = map(gy, 0, height, 40, 255);
let s = 20 + 15 * sin(frameCount * 0.05 + gx * 0.02);
fill(r, 90, b);
ellipse(gx, gy, s, s);
}
}
}
What good looks like. A composition with a clear structure (the grid), an intentional colour relationship (the map() gradient, not random per-cell noise), and motion you can follow (the gentle sin breathing, not chaos). If it looks flat, widen the colour range or add a second varying channel; if it looks busy, slow the animation or reduce the grid density — negative space reads as intentional. (Skill map: live-visualist Domain B — composition, colour, readable motion.)
Now make it yours. Change the grid spacing (+= 50) for density. Swap ellipse for rect. Drive the green channel with sin too, for shifting colour. Make the size respond to distance from the centre with dist(gx, gy, 200, 200). Slow it all down with a smaller frameCount multiplier.
Atoms in this module
Required — these gate the capstone
Supporting — enrichment, not gating
Part of curricula
- Audio-Visual Performer — integrated, synced live AV — Pair sound and image (unsynced, side by side) recommended
- Live Visualist — zero to performing live-coded & generative visuals — See & sketch — training the eye and the first lines of code required
- VJ — visual performance with projection, light & video — Generate & compose: build your own look recommended
Unlocks — modules that require this one
- Colour, layers, and pixel-level image processing
- Debugging and panic recovery in P5LIVE
- Drawing with trigonometry, polar coordinates, and curves
- Grids, tiling, recursion, and fractal pattern
- Interaction, motion, and coordinate transforms in p5.js
- openFrameworks, audio/video I/O, and hardware bridges
- Randomness, Perlin noise, and the aesthetics of generative art