Generative Art Systems
Design parameter spaces, manage output editions, build reproducible series, and think systematically about aesthetic rule-sets.
Thinking in parameter spaces
A generative system is a function: f(seed, params) → artwork. The interesting work is designing the space of possible outputs — the parameter space — so that every point in it produces something worth looking at.
Key design questions:
- Which parameters control the character of the piece (not just scale or colour)?
- Are all parameter combinations aesthetically valid, or are there constraints?
- How much variation should come from the seed vs the parameters?
Separating seed from parameters
const PARAMS = {
palette: 'cool', // 'warm' | 'cool' | 'mono'
density: 0.6, // 0–1
flowScale: 0.003,
curveCount: 200,
strokeAlpha: 40,
strokeWidth: 0.8
};
let seed = 42;
function setup() {
createCanvas(1200, 1200);
noLoop();
generate(seed, PARAMS);
}
function generate(s, p) {
randomSeed(s);
noiseSeed(s);
background(p.palette === 'cool' ? '#0d0d1a' : '#1a0a00');
drawFlowField(p);
}
function draw() {} // noLoop — draw happens in generate()
function keyPressed() {
if (key === 'n') {
seed++;
generate(seed, PARAMS);
print('Seed:', seed);
}
if (key === 's') {
saveCanvas(`piece-${seed}`, 'png');
}
}
Incrementing the seed lets you explore the series. Saving with the seed in the filename means you can regenerate any piece later.
Parameter ranges and validation
Define a schema that describes valid parameter space:
const SCHEMA = {
density: { min: 0.1, max: 1.0, default: 0.5, type: 'float' },
curveCount: { min: 50, max: 500, default: 200, type: 'int' },
palette: { options: ['cool', 'warm', 'mono', 'earth'], default: 'cool' }
};
function randomParams() {
let p = {};
for (let [key, def] of Object.entries(SCHEMA)) {
if (def.options) {
p[key] = random(def.options);
} else if (def.type === 'int') {
p[key] = floor(random(def.min, def.max));
} else {
p[key] = random(def.min, def.max);
}
}
return p;
}
Palette systems
Pre-defined palettes keep a series visually coherent:
const PALETTES = {
cool: {
bg: '#0a0a14',
strokes: ['#3b82f6', '#6366f1', '#8b5cf6', '#06b6d4', '#a78bfa']
},
warm: {
bg: '#140a00',
strokes: ['#f97316', '#ef4444', '#eab308', '#fb923c', '#fcd34d']
},
mono: {
bg: '#0a0a0a',
strokes: ['#ffffff', '#cccccc', '#888888', '#444444', '#1a1a1a']
},
earth: {
bg: '#1a1008',
strokes: ['#92400e', '#78350f', '#d97706', '#fbbf24', '#a16207']
}
};
function getStroke(paletteName, index) {
let palette = PALETTES[paletteName];
return palette.strokes[index % palette.strokes.length];
}
Edition management
For a series of N works from N seeds:
class Edition {
constructor(n, generatorFn) {
this.n = n;
this.generate = generatorFn;
this.current = 0;
this.hashes = {};
}
async exportAll(delay = 200) {
for (let i = 0; i < this.n; i++) {
this.current = i;
this.generate(i);
await new Promise(r => setTimeout(r, delay));
saveCanvas(`edition-${String(i).padStart(3, '0')}`, 'png');
}
}
preview(seed) {
this.generate(seed);
}
}
let edition = new Edition(100, s => generate(s, PARAMS));
function keyPressed() {
if (key === 'e') edition.exportAll(); // export all 100
}
Deterministic randomness — hash functions
Hash the seed + a counter for reproducible per-object randomness without calling random() in sequence:
function hash(seed, index) {
let h = (seed * 2654435761 + index * 40503) >>> 0;
h ^= h >>> 16;
h *= 0x45d9f3b;
h ^= h >>> 16;
return (h >>> 0) / 4294967296;
}
// Usage: deterministic random value for particle i
let x = hash(seed, i * 3) * width;
let y = hash(seed, i * 3 + 1) * height;
let r = hash(seed, i * 3 + 2) * 20 + 5;
This way you can change other aspects of the sketch without shifting every random value downstream.
Aesthetic constraint systems
The most interesting generative systems impose hard aesthetic constraints:
- Golden ratio compositions: all major proportions are φ ratios
- Colour harmony rules: only complementary or analogous palettes
- Spatial tension: no two major elements within N pixels of each other
- Gestalt grouping: elements cluster around attractors with Gaussian falloff
Example — ensuring no isolated points by grouping into clusters:
function generateClusters(numClusters, pointsPerCluster) {
let points = [];
for (let c = 0; c < numClusters; c++) {
let cx = random(100, width - 100);
let cy = random(100, height - 100);
let spread = random(30, 120);
for (let p = 0; p < pointsPerCluster; p++) {
points.push({
x: cx + randomGaussian(0, spread),
y: cy + randomGaussian(0, spread)
});
}
}
return points;
}
Key takeaways
- Separate
seed(which output) fromparams(what kind of output) — both are first-class randomSeed()+noiseSeed()make every aspect of the output reproducible from a single integer- Define parameter schemas with valid ranges; generate random valid parameter sets for exploration
- Pre-defined palettes enforce visual coherence across a series
- Hash functions provide deterministic per-object randomness that doesn’t depend on call order
- Aesthetic constraint systems are the difference between random noise and intentional generative work