Abstract: This intermediate creative coding course consolidates the foundations from Level 1 and introduces more complex structures and creative domains. The course covers advanced Perlin noise (multi-octave, 3D noise field), OOP (Particle class, inheritance), vector flow fields, data art with CSV/JSON, generative typography, audio-reactive (p5.sound FFT), clean export workflow and L-systems. The capstone project is a generative series of 10 works forming a coherent body of work. Outline: Noise advanced → OOP Particles → Vector fields → Data art → Typography → Audio-reactive → Export → L-systems.
Learning Objectives
By the end of this course, students will be able to:
- Use (applying) advanced Perlin noise (multi-octave, domain warping) to generate complex organic textures.
- Build (creating) an object-oriented
Particleclass with force accumulation. - Construct (creating) a vector flow field driven by Perlin noise.
- Load (applying) CSV or JSON data and map it to visual variables.
- Animate (applying) text as a visual form (generative typography).
- Connect (applying) p5.sound FFT to visual parameters for audio-reactive sketches.
- Produce (creating) a clean, high-resolution export workflow (PNG, SVG, PDF).
- Implement (creating) an L-system with a graphical Turtle class.
Module 1 — Advanced Perlin Noise (2h)
Concept
The basic noise(x) function returns values between 0 and 1. More powerful uses:
Multi-octave noise (fBm — Fractional Brownian Motion): javascript // Manual fBm: sum of noise at different scales function fbm(x, y, octaves) { let value = 0; let amplitude = 0.5; let frequency = 1.0; for (let i = 0; i < octaves; i++) { value += amplitude * noise(x * frequency, y * frequency); amplitude *= 0.5; // each octave: half the amplitude frequency *= 2.0; // double the frequency } return value; }
3D noise field (time-varying): javascript // 3D noise: x, y as position, frameCount * 0.005 as "time" let n = noise(x * 0.003, y * 0.003, frameCount * 0.005); let angle = n * TWO_PI * 2;
Domain warping: javascript // Distort the coordinates before sampling noise let qx = noise(px * 0.004, py * 0.004); let qy = noise(px * 0.004 + 5.2, py * 0.004 + 1.3); let value = noise(px * 0.004 + 4.0 * qx, py * 0.004 + 4.0 * qy); Result: turbulent, folded textures reminiscent of geological layers.
Lab 1.1 — Organic Texture with Domain Warping
Steps:
- Create a 600×600 canvas
- For each pixel, calculate a domain-warped noise value
- Map the value to a color palette (HSB: fixed saturation, varying hue/brightness)
- Compare: noise without warping vs. 1 warp pass vs. 2 warp passes
- Save the best result
Module 2 — OOP: Particle Class (2h)
Concept
Object-Oriented Programming allows encapsulating state and behaviors in reusable classes. A Particle is the canonical creative coding OOP example.
`javascript class Particle { constructor(x, y) { this.pos = createVector(x, y); this.vel = p5.Vector.random2D().mult(random(0.5, 2)); this.acc = createVector(0, 0); this.maxSpeed = 3; this.lifespan = 255; this.history = []; }
applyForce(force) { this.acc.add(force); }
update() { this.vel.add(this.acc); this.vel.limit(this.maxSpeed); this.pos.add(this.vel); this.acc.mult(0); // reset acc each frame this.lifespan -= 1.5; this.history.push(this.pos.copy()); if (this.history.length > 30) this.history.shift(); }
display() { for (let i = 0; i < this.history.length; i++) { let alpha = map(i, 0, this.history.length, 0, this.lifespan); stroke(255, alpha); let p = this.history[i]; if (i > 0) line(this.history[i-1].x, this.history[i-1].y, p.x, p.y); } }
isDead() { return this.lifespan <= 0; } } `
Managing a particle array: `javascript let particles = [];
function draw() { // add particles.push(new Particle(mouseX, mouseY)); // update + display for (let p of particles) { p.update(); p.display(); } // remove dead particles (reverse loop to not skip elements) for (let i = particles.length - 1; i >= 0; i--) { if (particles[i].isDead()) particles.splice(i, 1); } } `
Lab 2.1 — Particle System with Gravity
Steps:
- Implement the
Particleclass from the template above - Add a
gravityforce applied each frame - Add a
windforce activated on mouse pressed - Limit to 200 particles max (create one per frame, remove dead ones)
- Experiment: what happens if
lifespandecreases faster? IfmaxSpeedis higher?
Module 3 — Vector Flow Fields (2h)
Concept
A vector flow field is a structure that stores a direction for each zone of space. Each particle queries the field at its position to determine its direction of movement.
`javascript class FlowField { constructor(resolution) { this.resolution = resolution; this.cols = floor(width / resolution); this.rows = floor(height / resolution); this.field = new Array(this.cols * this.rows); this.update(); }
update() { let t = frameCount 0.003; for (let col = 0; col < this.cols; col++) { for (let row = 0; row < this.rows; row++) { let angle = noise(col 0.12, row 0.12, t) TWO_PI 2; let v = p5.Vector.fromAngle(angle); this.field[col + row this.cols] = v; } } }
localDirection(pos) { let col = floor(pos.x / this.resolution); let row = floor(pos.y / this.resolution); col = constrain(col, 0, this.cols - 1); row = constrain(row, 0, this.rows - 1); return this.field[col + row * this.cols].copy(); } } `
Connecting a Particle to the field: javascript // In Particle.update(): let force = flowField.localDirection(this.pos); force.mult(0.5); // field strength this.applyForce(force);
Lab 3.1 — Flow Field Visualization
Steps:
- Implement
FlowFieldwith resolution 20 - Draw the field: for each cell, a short arrow
line()in the field direction - Add 200 particles following the field (with the Particle class from Module 2)
- Experiment: what happens if the noise evolves over time (
t = frameCount * 0.003)? - Try to make the field interactive: direction shifts if the mouse is nearby
Module 4 — Data Art (CSV/JSON) (2h)
Concept
p5.js can load CSV and JSON files to transform real data into visual forms.
Loading CSV: `javascript let table; function preload() { table = loadTable('data.csv', 'csv', 'header'); }
function setup() { // table.getRowCount() — number of rows // table.getString(row, 'column_name') — value of cell for (let i = 0; i < table.getRowCount(); i++) { let value = float(table.getString(i, 'value')); let label = table.getString(i, 'name'); //... } } `
Loading JSON: javascript let data; function preload() { data = loadJSON('data.json'); } function setup() { // data.key, data.array[i].field }
Mapping data to visual parameters: `javascript // Map a value [min,max] to a position [margin, width-margin] let x = map(value, dataMin, dataMax, margin, width - margin);
// Map to a color colorMode(HSB, 360, 100, 100); let hue = map(value, dataMin, dataMax, 0, 240); fill(hue, 80, 90); `
Lab 4.1 — Open Data Visualization
Steps:
- Download an open CSV dataset (ideas: World Bank data, Moroccan statistics, city temperature)
- Load it into p5.js
- Create a visualization that transforms at least 2 data columns into visual variables (x, y, size, color, angle)
- Add a title and axis labels with
text() - Ask yourself: what does the visual form reveal that a spreadsheet doesn't?
Module 5 — Generative Typography (2h)
Concept
In p5.js, loadFont() loads an OpenType font (.otf,.ttf). textToPoints() (with the opentype.js library or p5.Font) extracts the polygon points of a character.
Basics of animated text: `javascript let myFont; function preload() { myFont = loadFont('path/to/font.otf'); }
function setup() { createCanvas(800, 400); textFont(myFont); textSize(200); textAlign(CENTER, CENTER); }
function draw() { background(0); // Animated color let hue = (frameCount * 0.5) % 360; fill(hue, 80, 90); text('TYPE', width/2, height/2); } `
Text as mask (createGraphics): javascript // Text as a clipping mask for a particle system // 1. Draw particles on a buffer // 2. Draw text with blendMode(MULTIPLY) on the buffer // → particles appear only through the letters
textToPoints (with typr.js / p5.Font): javascript // Extract points from an outline let pts = myFont.textToPoints('CODE', x, y, size, { sampleFactor: 0.1, simplifyThreshold: 0 }); // pts: array of {x, y, alpha} objects
Lab 5.1 — Animated Text
Steps:
- Load a free font (Google Fonts: Rubik, Cairo for Arabic, etc.)
- Display a word with animated size and color (with
sin) - Modify the font weight progressively: small with
noise, deformed with displacement - With
mouseX, control the level of distortion - Export a static high-resolution frame
Module 6 — Audio-Reactive (p5.sound + FFT) (2h)
Concept
p5.sound is the sound extension of p5.js. It allows loading files, capturing the microphone and analyzing frequencies in real time.
`javascript // Add in index.html: <script src="p5.sound.min.js"></script> let mic, fft;
function setup() { createCanvas(800, 400); mic = new p5.AudioIn(); mic.start(); fft = new p5.FFT(0.8, 1024); // smoothing, number of bins fft.setInput(mic); }
function draw() { background(0); let spectrum = fft.analyze(); // FFT spectrum: 1024 values 0-255 let waveform = fft.waveform(); // Waveform: values between -1 and 1
// Visualization: spectrum bar by bar for (let i = 0; i < spectrum.length; i++) { let h = map(spectrum[i], 0, 255, 0, height); fill(map(i, 0, spectrum.length, 0, 360), 80, 90); noStroke(); rect(map(i, 0, spectrum.length, 0, width), height - h, width / spectrum.length, h); }
// Dominant energy by frequency band let bass = fft.getEnergy('bass'); // 20–140 Hz let mid = fft.getEnergy('mid'); // 400–2600 Hz let treble = fft.getEnergy('treble'); // 5200–14000 Hz } `
Lab 6.1 — Audio-Reactive Composition
Steps:
- Connect the microphone (
p5.AudioIn) - Use bass to control circle size, treble to control color
- Visualize the full spectrum in a bar chart
- Add the waveform as a line (
beginShape/vertex) - With a sound file (
p5.SoundFile): load and play a track, make the visuals react
Module 7 — Clean Export Workflow (2h)
Concept
Pixel resolution: javascript // High-resolution raster export function setup() { pixelDensity(3); // 3× screen → 2400px for a 800px canvas createCanvas(800, 800); noLoop(); } function keyPressed() { if (key === 's') saveCanvas('export', 'png'); }
SVG export with p5.svg: SVG export requires the p5.svg library. javascript // With p5.svg loaded: function setup() { createCanvas(800, 800, SVG); // SVG renderer instead of Canvas } function keyPressed() { if (key === 's') save('export.svg'); }
Reproducibility: javascript // Fix the seeds at the beginning of setup() randomSeed(137); noiseSeed(137);
Series: generate N variations: javascript // Generate 10 variations with different seeds, save all async function saveSeries(n) { for (let i = 0; i < n; i++) { clear(); randomSeed(i * 100); noiseSeed(i * 100); redraw(); await new Promise(r => setTimeout(r, 100)); // wait for rendering saveCanvas('series-' + nf(i, 2), 'png'); } }
Lab 7.1 — Generating a Series
Steps:
- Take any sketch from Modules 1–6
- Parametrize it with a
SEEDvariable - Fix
randomSeed(SEED)andnoiseSeed(SEED)at the start ofdraw() - Generate and save 10 variations (seeds 0–9)
- Choose the 3 most interesting: what makes them stand out from the others?
Module 8 — L-Systems and Growth (2h)
Concept
L-systems are rewriting grammars that model growth. Introduced by biologist Aristid Lindenmayer (1968), they apply to branching plants, corals, snowflakes.
The principle:
- Axiom: starting string (
F) - Rules: substitution rules (
F → F[+F]F[-F]F) - Interpretation: turtle graphics
Implementation: `javascript class Turtle { constructor() { this.reset(); } reset() { this.x = width / 2; this.y = height - 50; this.angle = -HALF_PI; // pointing upward this.stack = []; this.stepSize = 4; this.angleStep = radians(25); } forward() { let x2 = this.x + cos(this.angle) this.stepSize; let y2 = this.y + sin(this.angle) this.stepSize; stroke(40, 100, 60, 180); strokeWeight(1); line(this.x, this.y, x2, y2); this.x = x2; this.y = y2; } turn(sign) { this.angle += sign * this.angleStep; } push() { this.stack.push({x: this.x, y: this.y, angle: this.angle}); } pop() { let s = this.stack.pop(); this.x = s.x; this.y = s.y; this.angle = s.angle; } }
function applyRules(str) { let result = ''; for (let c of str) { if (c === 'F') result += 'F[+F]F[-F]F'; else result += c; } return result; }
function drawLSystem(str, turtle) { turtle.reset(); for (let c of str) { if (c === 'F') turtle.forward(); else if (c === '+') turtle.turn(1); else if (c === '-') turtle.turn(-1); else if (c === '[') turtle.push(); else if (c === ']') turtle.pop(); } } `
Lab 8.1 — Animated L-System
Steps:
- Implement the
Turtleclass - Apply the
F → F[+F]F[-F]Frule on 4 iterations - Draw the L-system on a 600×600 canvas
- Make
angleStepvary withmouseX(map from 5° to 40°) - Animate the growth: draw character by character, with a delay between each step
Capstone Project — "Generative Series"
Brief
Produce a series of 10 coherent generative works exploring a theme (cultural, social, scientific or personal) through variation on a single generative system.
Constraints
- All works from the same source sketch
- Use at least 2 techniques from Modules 1–8
- Fixed seed for each work (reproducible)
- Consistent visual identity across the series
Deliverables
- Source sketch (well-commented code + documentation)
- 10 PNG exports at 2000×2000px minimum, named
serie-01.png→serie-10.png - Series documentation (2 pages): concept, generative system, creative choices per variation, seed table
- Presentation (10 min): show all 10 works, explain the system and 3 creative decisions
Evaluation Rubric
| Criterion | Insufficient (1) | Satisfactory (2) | Good (3) | Excellent (4) |
|---|---|---|---|---|
| Technical mastery | OOP or flow field not functional | One technique mastered | Two techniques integrated and functional | Multiple techniques combined, original implementation |
| Series coherence | No visible relationship between the 10 works | Common visual element | Recognizable coherent identity | Strong visual system, each work a distinct variation |
| Conceptual depth | No intention | Theme stated | Theme developed through visual choices | Works create meaning as a body — not just aesthetic |
| Reproducibility | Seeds not documented | Seeds noted | Seeds + parameters table | Seeds + reproducibility protocol + documented variations |
| Documentation quality | Absent or summary | Technical description | Concept + choices + seed table | Reflection on the generative system as authorial tool |
Readings & Resources
- The Nature of Code (Daniel Shiffman): https://natureofcode.com
- p5.js + p5.sound reference: https://p5js.org/reference/
- L-systems reference: https://en.wikipedia.org/wiki/L-system
- openFrameworks (C++ alternative): https://openframeworks.cc
- OpenProcessing galleries: https://openprocessing.org
→ Next level: Creative Coding — Level 3 → Custom workshop for your school or team: Book a 30-min call — free
Intensive workshop (1–2 days) for schools, studios, museums — condensed theory, guided labs, project mentoring.
Book a 30-min call (free) →