Learn Creative Coding (#23) - What Makes Art "Generative"?

Welcome to Phase 4. We've spent 22 episodes building a toolkit -- particles, noise, physics, shaders, audio reactivity, recording, exporting. All of that has been creative coding. But starting now, we're stepping into a specific branch of it: generative art. And before we write our next line of JavaScript, I want to talk about what that actually means. Because understanding the "why" changes how you approach the "how."
We've been making generative things this whole series, honestly. That galaxy in episode 15? Generative. The flow of Perlin noise in episode 12? Generative. The particle bursts in our audio visualizer last episode? Also generative. But we never stopped to think about what ties those things together, or what separates "code that draws stuff" from "generative art" as a practice. This episode is more conceptual than technical. We'll still write code -- this isn't a philosophy lecture -- but the focus is on the ideas behind the code. The artist-system relationship. The spectrum between order and chaos. The role of curation. And a bit of history, because this stuff is older than you'd think :-)
Where this all started
Generative art didn't start with computers. The Dadaists in the 1920s used chance operations -- Tristan Tzara's cut-up technique pulled random words from a hat to compose poems. John Cage used the I Ching to determine musical compositions. Sol LeWitt wrote textual instructions that other people executed on gallery walls -- the instructions were the art, not any specific rendering. The idea of designing a system and then letting the system create the output has been around for over a century.
But computers changed everything. In the 1960s, Georg Nees and Frieder Nake created some of the first computer-generated artworks. Plotted line drawings where algorithms determined the composition. Vera Molnar, working in Paris, used early mainframes to explore systematic variations of geometric forms. She'd write a program, generate hundreds of outputs, then select the ones that spoke to her as an artist. The computer proposed, the artist curated.
This artist-as-curator model is still how most generative art works today. You write the system, you generate outputs, you select. The system does the exploration. You do the taste. Neither can do the other's job. A computer can generate a million compositions in an hour, but it can't tell which ones are beautiful. A human can recognize beauty instantly, but couldn't generate a million unique compositions by hand.
By the 1990s, Casey Reas and Ben Fry made creative coding accessible with Processing. The creative coding community exploded. Artists like Manolo Gamboa Naranjo, Tyler Hobbs, and Dmitri Cherniak built entire reputations on algorithmic work. Then in 2021, the NFT wave hit. Art Blocks launched on Ethereum -- a platform where generative art scripts live on the blockchain and produce unique outputs at the moment of minting. Tyler Hobbs's Fidenza collection (essentially a sophisticated flow field, like the Perlin-based motion we explored in episode 12) sold for millions. Suddenly generative art wasn't a niche. It was a market.
The hype has cooled since then. What's left are artists who genuinely care about the craft. The ones making work that would be interesting even without a blockchain attached. That's the space I want us to occupy.
The core idea
The artist writes the system. The system makes the art.
That's it. That's the fundamental distinction. A painter decides every brushstroke. A generative artist decides the rules, the constraints, the palette, the composition logic -- and then the system produces individual pieces within those rules.
You're not drawing a picture. You're designing a process that draws pictures.
It sounds like giving up control, but it's actually the opposite. You're controlling at a higher level of abstraction. Instead of choosing where one line goes, you're choosing the principles that govern where ALL lines go. Think back to our particle systems from episode 11. You didn't place each particle by hand. You wrote the rules -- spawn here, move at this speed, fade over this lifetime -- and the system generated the particles. The rules were your creative decision. The specific particles were the system's output.
Same with the Perlin noise sketches in episode 12. You didn't design each noise pattern. You chose the scale, the octaves, the mapping. The noise function filled in the details. That's the generative art relationship in miniature.
The spectrum: order to chaos
Generative art exists on a spectrum. On one end: pure determinism -- every element precisely placed, the same output every time. A fractal with fixed parameters. Mathematical art. Beautiful, but static. On the other end: pure randomness. Math.random() for everything, no structure, complete chaos. Looks like TV static. Not art.
The interesting space is in between. Rules + randomness. Structure + surprise.
// too deterministic -- every output identical
function setup() {
createCanvas(600, 400);
background(20);
stroke(200);
noFill();
for (let i = 0; i < 100; i++) {
let x = i * 6;
let y = sin(i * 0.1) * 100 + 200;
ellipse(x, y, 5);
}
}
This draws the same sine wave every time. Pretty, but not generative. There's no possibility space -- just one fixed outcome.
// too random -- no structure at all
function setup() {
createCanvas(600, 400);
background(20);
noStroke();
for (let i = 0; i < 100; i++) {
fill(random(255), random(255), random(255), random(100, 200));
ellipse(random(width), random(height), random(5, 40));
}
}
This is different every time but there's no design intention. No visual coherence. Just noise.
// the sweet spot -- structure with variation
function setup() {
createCanvas(600, 400);
background(20);
noStroke();
for (let i = 0; i < 100; i++) {
let x = i * 6;
let y = sin(i * 0.1) * 100 + 200;
y += random(-15, 15); // controlled randomness
let size = 3 + noise(i * 0.2) * 8; // noise-driven variation
fill(100, 180 + noise(i * 0.3) * 75, 220, 150);
ellipse(x, y, size);
}
}
The third version has the sine wave as structure and noise as personality. Every run looks different, but they're all clearly siblings -- same DNA, different expressions. That's generative art.
Most compelling generative work lives at about 70-80% deterministic, 20-30% random. Enough structure that it's clearly designed. Enough randomness that each output surprises even the artist. Where exactly you land on that spectrum is a personal artistic choice. Some artists lean toward tight control (strict geometry, precise palettes). Others lean toward chaos (loose flow fields, wild color ranges). Neither approach is better -- it's about your relationship with the system.
Thinking in layers
One framework I find useful: think about your generative system as having three layers. Each layer can have its own balance of control versus randomness.
Structural layer -- the overall composition. Where things go, how big they are, the spatial relationships. Maybe this is tightly controlled: a grid, a golden ratio layout, a rule-of-thirds division. Or maybe it's loose: random placement, scatter distribution.
Detail layer -- the textures, colors, small variations within the structure. This is where noise shines. Even if your structural layer is a rigid grid, noise-driven size and color variation in the detail layer keeps it interesting.
Temporal layer -- how things change over time, if your piece is animated. The evolution of motion, the rhythm of change.
// structural layer: hexagonal grid (deterministic)
// detail layer: noise-driven color and size (random)
// result: orderly composition with organic feel
function setup() {
createCanvas(600, 400);
background(245, 240, 235);
noStroke();
colorMode(HSB, 360, 100, 100, 100);
let spacing = 30;
let cols = ceil(width / spacing) + 1;
let rows = ceil(height / (spacing * 0.866)) + 1;
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
let x = col * spacing + (row % 2) * spacing * 0.5;
let y = row * spacing * 0.866;
// detail layer: noise drives everything visual
let n = noise(x * 0.01, y * 0.01);
let size = 4 + n * 18;
let hue = 180 + n * 60; // teal to blue range
let brightness = 40 + n * 50;
fill(hue, 60, brightness, 70);
ellipse(x, y, size);
}
}
}
The hex grid is fully deterministic -- every dot is in a mathematically precise position. But the noise-driven sizing and coloring makes it feel organic. The structure is controlled, the details are loose. Mix those levels differently and you get a completley different feel. Random placement with precise colors. Grid layout with chaotic sizing. Each combination produces a distinct visual personality.
Here's the same system with a temporal layer added -- using the t * TWO_PI looping trick from episode 20:
// add animation: the noise offset drifts over time
function draw() {
background(245, 240, 235);
colorMode(HSB, 360, 100, 100, 100);
noStroke();
let t = frameCount * 0.005; // slow drift
let spacing = 30;
let cols = ceil(width / spacing) + 1;
let rows = ceil(height / (spacing * 0.866)) + 1;
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
let x = col * spacing + (row % 2) * spacing * 0.5;
let y = row * spacing * 0.866;
let n = noise(x * 0.01 + t, y * 0.01 + t * 0.7);
let size = 4 + n * 18;
let hue = 180 + n * 60;
fill(hue, 60, 40 + n * 50, 70);
ellipse(x, y, size);
}
}
}
Same structural layer, same detail layer logic, but now the noise field slowly shifts over time. The dots breathe and pulse as different areas of the noise space flow through the grid. The structure stays perfectly rigid. The details are alive. Three layers, each with its own character.
Constraints as creativity
Here's something I find beautiful about generative art: the constraints ARE the creative act.
"Use only circles" is a constraint. "Only blue and gold" is a constraint. "Every element must connect to the previous one" is a constraint. "Lines can only be horizontal or vertical" is a constraint.
Constraints aren't limitations -- they're the artist's signature. When you look at a Fidenza, you know it's a Fidenza. Not because of any single specific piece, but because of the consistent visual language across all outputs. The algorithm IS the artwork. The constraints define the visual vocabulary, and every output is a sentence written in that vocabulary.
// constraint: only horizontal and vertical lines
// constraint: lines must connect to previous endpoint
// constraint: palette limited to 4 colors
// constraint: line weight varies with noise
let palette = ['#264653', '#2a9d8f', '#e9c46a', '#e76f51'];
let x, y;
function setup() {
createCanvas(400, 400);
background(253, 249, 241);
x = width / 2;
y = height / 2;
for (let i = 0; i < 300; i++) {
let length = random(10, 80);
let horizontal = random() > 0.5;
let nx = horizontal ? x + (random() > 0.5 ? length : -length) : x;
let ny = horizontal ? y : y + (random() > 0.5 ? length : -length);
stroke(random(palette));
strokeWeight(1 + noise(i * 0.1) * 3);
line(x, y, nx, ny);
x = constrain(nx, 20, width - 20);
y = constrain(ny, 20, height - 20);
}
}
Four constraints. Simple rules. But run this 50 times and you'll get 50 compositions that are clearly related -- a visual family. That's an artistic voice expressed through code. The randomness explores different paths through the possibility space. The constraints keep every path recognizably "yours."
This connects directly to what we did in episode 14 with Bezier curves, actually. The curves themselves had fixed control point structures, but randomizing the control point positions within bounds created endless organic shape variations. Same principle. Structure defines the possibility space, randomness explores it.
Seeds: taming the randomness
If generative art is about randomness within constraints, you need a way to control that randomness. That's what seeds do.
A seed is a number that initializes the random number generator. Same seed, same sequence of "random" numbers, same output. Change the seed, different output. But always reproducible.
function setup() {
createCanvas(400, 400);
// try different seeds: 42, 43, 100, 7777
randomSeed(42);
noiseSeed(42);
background(20);
noStroke();
for (let i = 0; i < 500; i++) {
let x = random(width);
let y = random(height);
let size = random(5, 30);
fill(random(100, 255), random(50, 150), random(100, 200), 100);
ellipse(x, y, size);
}
}
Run this with seed 42 and you get the exact same image every time. Change to seed 43 and you get a different image -- but still from the same system. The system is constant. The seed is the variable.
This is how editions work. Seed 42 is edition #42. Seed 43 is edition #43. Same artist, same algorithm, different output. Like prints from the same printing press but each one unique. The algorithm is the press. The seed is the ink configuration.
We'll go much deeper into seed-based art next episode -- how to build systems that are both reproducible and consistently beautiful across hundreds of different seeds. For now, the key insight is: randomSeed() and noiseSeed() let you freeze randomness. They turn "random" into "deterministic with a knob." And that knob is what makes generative art distributable.
Remember in episode 20 when I said to save your random seed alongside every export? This is why. The seed IS the identity of that specific output. Lose the seed, lose the ability to reproduce it.
Editions and the open edition problem
In the NFT world, this seed concept drives three different distribution models:
1/1 (unique): one seed, one output, manually curated by the artist. The artist runs the algorithm hundreds of times, picks the best one, mints it. Maximum artistic control, maximum scarcity.
Curated edition of N: the artist writes the algorithm, runs it with many seeds, picks N that produce good results. Maybe 50 out of 500. The artist curates the collection.
Open edition: anyone can mint, each mint gets a new seed generated at mint time. The artist doesn't control which outputs exist. The algorithm has to be good enough that ALL outputs are interesting.
The open edition is the hardest and most impressive. Your algorithm needs to produce consistently interesting results across thousands of random seeds. No duds, no broken compositions, no ugly color combinations. This forces you to really understand your system.
// a system that works across many seeds
// structure is strong enough that randomness can't break it
function generatePiece(seed) {
randomSeed(seed);
noiseSeed(seed);
let bgHue = random(360);
let accentHue = (bgHue + 120 + random(-30, 30)) % 360;
background(20);
colorMode(HSB, 360, 100, 100, 100);
// structural constraint: concentric rings
let rings = floor(random(5, 12));
for (let r = 0; r < rings; r++) {
let radius = map(r, 0, rings - 1, 40, min(width, height) * 0.45);
let count = floor(10 + r * 8);
let dotSize = map(r, 0, rings - 1, 8, 3);
for (let i = 0; i < count; i++) {
let angle = (i / count) * TWO_PI;
// noise offsets each dot slightly from its perfect position
let offset = noise(r * 0.5, i * 0.3) * 15 - 7.5;
let px = width/2 + cos(angle) * (radius + offset);
let py = height/2 + sin(angle) * (radius + offset);
let hue = lerp(bgHue, accentHue, r / rings) % 360;
fill(hue, 50 + random(30), 60 + random(30), 70);
noStroke();
ellipse(px, py, dotSize + noise(i * 0.2) * 4);
}
}
}
The structural constraint (concentric rings) is strong enough that no seed can produce a broken layout. The rings are always there, always centered, always nested. But within that structure, noise offsets, random color shifts, and varying dot counts create genuine variety. Run this with seed 1 through seed 1000 and every output is recognizably from the same system, but no two are identical. That's the goal.
Notice the color logic: bgHue is random, accentHue is always roughly 120 degrees away on the color wheel (a triadic harmony) with some random drift. This guarantees the palette always works because the constraint is based on color theory, not on specific colors. No matter what seed you get, the hue relationship is harmonious. Constraints protecting quality across all possible outputs. That's the open edition mindset.
The role of curation
Here's something that suprises people: most generative artists don't show every output their system produces. They curate. They run the algorithm hundreds or thousands of times and select the outputs that resonate with them artistically. The algorithm generates candidates. The human selects the winners.
This isn't "cheating." It's the same thing a photographer does (take 500 shots, show 20) or a writer does (write 10 drafts, publish 1). The creative act includes both generation AND selection. The algorithm extends your generative capacity -- you can explore more possibilities than you could ever create by hand. Your taste narrows it back down to the ones worth sharing.
// a simple curation workflow
let currentSeed = 0;
let savedSeeds = [];
function setup() {
createCanvas(400, 400);
generateWithSeed(currentSeed);
}
function generateWithSeed(seed) {
randomSeed(seed);
noiseSeed(seed);
background(20);
// your generative algorithm here
for (let i = 0; i < 200; i++) {
let x = noise(i * 0.05) * width;
let y = noise(i * 0.05 + 100) * height;
let size = noise(i * 0.1 + 200) * 20;
fill(noise(i * 0.02 + 300) * 255, 150, 200, 80);
noStroke();
ellipse(x, y, size);
}
}
function keyPressed() {
if (key === ' ') {
// next seed -- explore
currentSeed++;
generateWithSeed(currentSeed);
}
if (key === 's') {
// save this seed -- curate
savedSeeds.push(currentSeed);
console.log('Saved seeds:', savedSeeds);
}
if (key === 'b') {
// go back
currentSeed = max(0, currentSeed - 1);
generateWithSeed(currentSeed);
}
}
Space bar to generate the next seed. S to save the ones you like. B to go back. Simple curation tool. I use something like this constantly when developing a new system -- flip through seeds rapidly, save the good ones, then study what makes the good ones good. Often you find patterns. "Seeds that produce more elements in the top-left feel more balanced." "Seeds where the color range stays narrow look better than wide-range ones." Those observations feed back into your algorithm as new constraints.
For on-chain generative art (open editions on Art Blocks and similar platforms), you can't curate individual outputs because the buyer picks the seed by minting. This makes the parameter-tuning phase critical -- every possible output needs to meet the quality bar. It's a fundamentally different creative constraint compared to curated editions. Some artists prefer curated series (more control, higher average quality). Others prefer the on-chain model (every output is the art, surprises included). Both are valid.
The "is it art?" question
The question "is it art?" comes up a lot with generative work. My answer: the art is in the decisions. Which algorithm, which parameters, which palette, which composition rules, which constraints, which outputs to show. The computer executes, but every execution reflects hundreds of artistic choices the creator made.
A brush doesn't make art either -- the artist holding it does. Code is just a different kind of brush.
The tools have changed (JavaScript instead of FORTRAN, GPUs instead of pen plotters, NFTs instead of gallery walls), but the fundamental creative dynamic hasn't changed since Vera Molnar in the 1960s. You design the possibility space. The machine explores it. You curate the results. The art lives in all three of those activities, not just the execution.
Building a tiny generative system
Let's put these ideas together. Here's a complete generative system that demonstrates the concepts: structure, randomness, constraints, seed-based reproducibility, and curation.
// "connected clusters" -- a simple generative system
let seed;
function setup() {
createCanvas(600, 400);
seed = floor(random(10000));
generate();
}
function generate() {
randomSeed(seed);
noiseSeed(seed);
background(245, 240, 235);
// constraint: 3-6 clusters
let numClusters = floor(random(3, 7));
let clusters = [];
// place cluster centers with minimum spacing
for (let i = 0; i < numClusters; i++) {
let cx = random(80, width - 80);
let cy = random(80, height - 80);
let radius = random(30, 80);
let count = floor(random(8, 25));
let hue = random(360);
clusters.push({ cx, cy, radius, count, hue });
}
colorMode(HSB, 360, 100, 100, 100);
// draw connections between nearby cluster centers
stroke(0, 0, 70, 15);
strokeWeight(1);
for (let i = 0; i < clusters.length; i++) {
for (let j = i + 1; j < clusters.length; j++) {
let d = dist(clusters[i].cx, clusters[i].cy,
clusters[j].cx, clusters[j].cy);
if (d < 250) {
line(clusters[i].cx, clusters[i].cy,
clusters[j].cx, clusters[j].cy);
}
}
}
// draw dots in each cluster
noStroke();
for (let c of clusters) {
for (let i = 0; i < c.count; i++) {
let angle = random(TWO_PI);
let r = random(c.radius);
let px = c.cx + cos(angle) * r;
let py = c.cy + sin(angle) * r;
let size = 3 + noise(px * 0.02, py * 0.02) * 10;
fill(c.hue, 40 + random(30), 50 + random(40), 60 + random(30));
ellipse(px, py, size);
}
}
// display seed number
fill(0, 0, 50);
textSize(10);
textAlign(LEFT, BOTTOM);
text('seed: ' + seed, 10, height - 10);
}
function keyPressed() {
if (key === ' ') {
seed++;
generate();
}
if (key === 's') {
saveCanvas('clusters_seed_' + seed, 'png');
}
}
The constraints: clusters of dots, connected by faint lines when close enough, warm palette per cluster. The randomness: cluster positions, sizes, dot counts, colors. The structure is strong enough that every seed produces something coherent, but loose enough that every seed looks different. Space bar to explore. S to save the ones you like.
This is a tiny system -- 60 lines. But it has all the pieces. Constraints define the visual language. Randomness fills in the specifics. Seeds make it reproducible. And you, the artist, decide which seeds to show the world. That's the generative art workflow in a nutshell.
Why this matters for us
Everything from here through the end of Phase 4 builds on these ideas. We'll learn about seed-based reproducibility in detail, composition algorithms that generate balanced layouts, generative typography, texture techniques, color palette generation, and eventually building full generative systems ready for blockchain minting.
But the foundation is what we covered today: a generative artist designs systems, not images. The system is the artwork. The outputs are expressions of it. Your creative decisions live in the constraints, the parameter ranges, the composition rules. The code is your brush. Randomness is your collaborator. Curation is your editor.
If you've made it through 22 episodes of this series, you already have the technical skills. The particles, the noise, the trig, the physics, the shaders -- those are all tools for building generative systems. Now we're learning to think about them that way. Not "what does this code draw?" but "what range of things can this system produce, and is every part of that range interesting?"
That shift in thinking is what turns a creative coder into a generative artist.
't Komt erop neer...
- Generative art = the artist designs the system, the system produces the art
- The sweet spot is 70-80% structure, 20-30% randomness -- enough design to be intentional, enough chance to be surprising
- Think in three layers: structural (composition), detail (texture/color), temporal (animation)
- Seeds make randomness reproducible -- same seed, same output, every time
- Editions are different seeds through the same algorithm
- Constraints aren't limitations -- they're your artistic signature, the visual vocabulary every output shares
- Open editions are the hardest: the algorithm must produce interesting results for ANY seed
- Curation is part of the art -- generate many, show the best, study why the best ones work
- Generative art has a 60+ year history, but the tools and distribution have never been better
- Everything from Vera Molnar to Art Blocks follows the same fundamental dynamic: design the possibility space, let the machine explore it, curate the results
We've laid the conceptual foundation. Next episode, we go deep on the seed mechanic -- how pseudo-random number generators actually work under the hood, how to build systems where every seed produces beautiful output, and how to use seed space as a creative tool. It's going to get practical fast :-)
Sallukes! Thanks for reading.
X