Randomness, Perlin noise, and the aesthetics of generative art
Learning objectives
- learner can articulate what generative art is, its order/chaos sweet spot, and chance as a deliberate tool
- learner can use pseudo-randomness, custom distributions, and Perlin noise to seed organic form
- learner can make generative sketches explorable and reproducible via parameterisation and random seeds
Capstone — one whole task that evidences the objectives
Design a parameterised, seed-reproducible generative sketch that sits in the order/chaos sweet spot: noise-driven organic structure with tunable named parameters and a saved seed that recreates any output.
Prerequisite modules
This module builds toward the signature act of the generative visual coder: a p5.js/Processing sketch that is neither a rigid pattern nor visual static, but a living system you can tune on stage or in the studio and recreate on demand. In live-visual and audio-reactive practice this pairing matters doubly — a VJ needs organic motion that never repeats yet stays on-brand, and a saved seed that brings back the exact frame a collaborator loved during last night’s set.
The arc starts with vocabulary and stance: what makes a system generative at all, why the order/chaos sweet spot (Pearson’s cultivated-garden metaphor) is the aesthetic target, and how chance operations make randomness a designed material rather than surrendered control. From there the learner works hands-on: the iterative random walk exercise contrasts memoryless jumps with cumulative drift, “random() vs noise()” and the Perlin noise atom supply the JIT how-to for smooth organic variation, and power-skewed distributions add visual gravity to otherwise flat randomness. The final stretch turns a working sketch into an instrument — extracting magic numbers into named parameters, bounding randomness with explicit ranges the way Reas parameterised the Chronograph fields, and pinning randomSeed() so any output can be saved, shared, and re-summoned.
Every required atom gates the capstone: the definitional and sweet-spot atoms justify the design brief, the noise/distribution/walk atoms produce the organic structure itself, and the parameterisation and seed atoms deliver tunability and reproducibility. Supporting atoms enrich the territory — 2D noise fields and grid displacement extend the technique to textures, multiplication and symmetry offer composition levers, and the collaboration framing and constraint-driven workflow deepen the craft mindset without being needed to finish the task.
Walkthrough
Generative art lives in the order/chaos sweet spot — not a rigid grid, not static. The tool that gets you there is noise(): randomness with memory, so values drift smoothly instead of jumping. Paste each sketch into the p5 web editor and press Run. Each is a complete, standalone sketch.
1 — random() vs noise(). The whole lesson in one image: random() scatters (memoryless, jagged); noise(x) flows (each value near the last). Smoothness is what reads as organic ([[processing-random-noise]], [[perlin-noise]]).
function setup() { createCanvas(400, 400); }
function draw() {
background(15);
stroke(255, 90, 90); // random — jagged
for (let x = 0; x < width; x++) point(x, 100 + random(-40, 40));
stroke(120, 200, 255); // noise — smooth
for (let x = 0; x < width; x++) point(x, 300 + (noise(x * 0.01) - 0.5) * 160);
}
2 — a noise field. Sample 2D noise across a grid and map it to brightness — a smooth, cloud-like terrain, the raw material of organic texture ([[two-d-noise-field]] if present).
function setup() { createCanvas(400, 400); noStroke(); }
function draw() {
background(0);
for (let y = 0; y < height; y += 8)
for (let x = 0; x < width; x += 8) {
let n = noise(x * 0.01, y * 0.01);
fill(n * 255);
rect(x, y, 8, 8);
}
}
3 — noise over time (a flow field). Let noise drive the direction of short strokes and advance a time input each frame — a living, drifting current that never quite repeats.
let t = 0;
function setup() { createCanvas(400, 400); }
function draw() {
background(15, 30);
stroke(180, 220, 255, 120);
for (let y = 20; y < height; y += 24)
for (let x = 20; x < width; x += 24) {
let a = noise(x * 0.005, y * 0.005, t) * TWO_PI * 2;
line(x, y, x + cos(a) * 12, y + sin(a) * 12);
}
t += 0.01;
}
4 — a seed makes it reproducible. noiseSeed(n) and randomSeed(n) pin the sequence so the same seed recreates the exact output — the “bring back last night’s frame” move ([[seed-reproducibility]] if present). Change the seed number to get a different, but repeatable, piece.
function setup() {
createCanvas(400, 400); noStroke();
noiseSeed(42); randomSeed(42); // change 42 → a different, repeatable output
background(12);
for (let i = 0; i < 400; i++) {
let x = random(width), y = random(height);
let n = noise(x * 0.008, y * 0.008);
fill(200 * n + 40, 120, 255 - 150 * n);
ellipse(x, y, 4 + n * 10);
}
}
function draw() {}
5 — parameters make it an instrument. Pull the magic numbers into named, bounded parameters at the top — now it’s tunable, not hard-coded. scale sets the zoom of the noise, count the density ([[parameterised-generative-system]] if present).
const P = { scale: 0.01, count: 500, hue: 200 };
function setup() {
createCanvas(400, 400); noStroke(); colorMode(HSB, 360, 100, 100);
noiseSeed(7); background(0, 0, 8);
for (let i = 0; i < P.count; i++) {
let x = random(width), y = random(height);
let n = noise(x * P.scale, y * P.scale);
fill((P.hue + n * 80) % 360, 70, 40 + n * 60);
ellipse(x, y, 3 + n * 12);
}
}
function draw() {}
6 — the sweet spot, seeded + tunable (the capstone). A noise-driven organic structure with named parameters and a fixed seed — order (the smooth field) and chaos (per-agent variation) in balance, reproducible on demand. Agents drift along the noise field, leaving trails:
const P = { scale: 0.006, agents: 300, speed: 1.4, seed: 11 };
let pts = [];
function setup() {
createCanvas(400, 400); noiseSeed(P.seed); randomSeed(P.seed);
background(10); colorMode(HSB, 360, 100, 100, 100);
for (let i = 0; i < P.agents; i++) pts.push({ x: random(width), y: random(height) });
}
function draw() {
noStroke();
for (let p of pts) {
let a = noise(p.x * P.scale, p.y * P.scale) * TWO_PI * 2;
p.x += cos(a) * P.speed; p.y += sin(a) * P.speed;
if (p.x < 0 || p.x > width || p.y < 0 || p.y > height) { p.x = random(width); p.y = random(height); }
fill((200 + noise(p.x * 0.01) * 100) % 360, 60, 90, 12);
ellipse(p.x, p.y, 3);
}
}
What good looks like. Structure that feels grown, not placed: coherent flow (the noise field organises everything) with enough per-element variation that it never looks like a grid or pure static. If it looks too random (TV snow), your noise scale is too large — zoom in (smaller scale); if it looks too regular, add variation or a second noise octave. The seed is your safety net: when you hit a frame you love, you can get it back. (Skill map: live-visualist Domain A4/F — generative systems and a personal library.)
Now make it yours. Change P.scale and watch order↔chaos slide. Colour agents by their heading instead of position. Add a second noise octave (noise(x*s) + 0.5*noise(x*s*2)) for finer detail. Fade the background less for longer trails. Save a seed that you like.
Runnable examples
Generated from the context/ instrument corpus by concept (redistributable idioms only). Do not edit — regenerate with gen-module-examples.mjs.
modulation-warp
osc(4).modulate(src(o0), 0.6).out(o0)
hydra-0022 · CC0-1.0
s0.initP5(); src(s0).modulate(noize(), 0.3).out()
p5live-0038 · CC0-1.0
feedback-trail
osc(4).modulate(src(o0), 0.6).out(o0)
hydra-0022 · CC0-1.0
function draw(){ fill(0, 20); rect(0, 0, width, height); circle(mouseX, mouseY, 40) }
p5live-0003 · CC0-1.0
noise-field
noise(4, 0.1).out()
hydra-0002 · CC0-1.0
float h21(vec2 p){return fract(sin(dot(p,vec2(12.9898,78.233)))*43758.5453);}
glsl-0013 · public-domain
oscillator-texture
osc(10).out(o1)
hydra-0039 · CC0-1.0
float v = sin((st.x + u_time*0.1) * 40.0) * 0.5 + 0.5;
glsl-0024 · public-domain
grain-glitch
col += (h21(st + fract(u_time)) - 0.5) * 0.15;
glsl-0026 · public-domain
voronoi-cells
voronoi(8, 0.3, 0.3).out()
hydra-0003 · CC0-1.0
float vor(vec2 p){vec2 g=floor(p),f=fract(p);float m=8.;for(int j=-1;j<=1;j++)for(int i=-1;i<=1;i++){vec2 o=vec2(i,j);vec2 r=o+vec2(h21(g+o))-f;m=min(m,dot(r,r));}return sqrt(m);}
glsl-0025 · public-domain
flow-field
let ang = noise(x*0.01, y*0.01) * TWO_PI
p5live-0008 · CC0-1.0
scanlines
osc (200*fy) * 0.3 + [0.1,0.1,0.1] >> add
punctual-0035 · CC0-1.0
col *= 0.8 + 0.2 * sin(gl_FragCoord.y * 3.14159);
glsl-0037 · public-domain
blur-soften
filter(BLUR, 4)
p5live-0073 · CC0-1.0
chromatic-aberration
col = vec3(texture2D(u_tex0,st+vec2(.005,0)).r, texture2D(u_tex0,st).g, texture2D(u_tex0,st-vec2(.005,0)).b);
glsl-0036 · public-domain
Atoms in this module
Required — these gate the capstone
Supporting — enrichment, not gating
Part of curricula
- Audio-Visual Performer — integrated, synced live AV — Make the image listen (audio-reactive show) recommended
- Live Visualist — zero to performing live-coded & generative visuals — Generative canvas — colour, motion, and Hydra live-coding required
- VJ — visual performance with projection, light & video — Generate & compose: build your own look recommended
Unlocks — modules that require this one