Level 2

Creative Coding — Level 2: Intermediate

Take this course — FREEGuided training — PAIDFree to read + exercises. For mentoring (workshop / masterclass), switch to the guided format.
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:

  1. Use (applying) advanced Perlin noise (multi-octave, domain warping) to generate complex organic textures.
  2. Build (creating) an object-oriented Particle class with force accumulation.
  3. Construct (creating) a vector flow field driven by Perlin noise.
  4. Load (applying) CSV or JSON data and map it to visual variables.
  5. Animate (applying) text as a visual form (generative typography).
  6. Connect (applying) p5.sound FFT to visual parameters for audio-reactive sketches.
  7. Produce (creating) a clean, high-resolution export workflow (PNG, SVG, PDF).
  8. 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:

  1. Create a 600×600 canvas
  2. For each pixel, calculate a domain-warped noise value
  3. Map the value to a color palette (HSB: fixed saturation, varying hue/brightness)
  4. Compare: noise without warping vs. 1 warp pass vs. 2 warp passes
  5. 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:

  1. Implement the Particle class from the template above
  2. Add a gravity force applied each frame
  3. Add a wind force activated on mouse pressed
  4. Limit to 200 particles max (create one per frame, remove dead ones)
  5. Experiment: what happens if lifespan decreases faster? If maxSpeed is 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:

  1. Implement FlowField with resolution 20
  2. Draw the field: for each cell, a short arrow line() in the field direction
  3. Add 200 particles following the field (with the Particle class from Module 2)
  4. Experiment: what happens if the noise evolves over time (t = frameCount * 0.003)?
  5. 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:

  1. Download an open CSV dataset (ideas: World Bank data, Moroccan statistics, city temperature)
  2. Load it into p5.js
  3. Create a visualization that transforms at least 2 data columns into visual variables (x, y, size, color, angle)
  4. Add a title and axis labels with text()
  5. 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:

  1. Load a free font (Google Fonts: Rubik, Cairo for Arabic, etc.)
  2. Display a word with animated size and color (with sin)
  3. Modify the font weight progressively: small with noise, deformed with displacement
  4. With mouseX, control the level of distortion
  5. 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:

  1. Connect the microphone (p5.AudioIn)
  2. Use bass to control circle size, treble to control color
  3. Visualize the full spectrum in a bar chart
  4. Add the waveform as a line (beginShape / vertex)
  5. 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:

  1. Take any sketch from Modules 1–6
  2. Parametrize it with a SEED variable
  3. Fix randomSeed(SEED) and noiseSeed(SEED) at the start of draw()
  4. Generate and save 10 variations (seeds 0–9)
  5. 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:

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:

  1. Implement the Turtle class
  2. Apply the F → F[+F]F[-F]F rule on 4 iterations
  3. Draw the L-system on a 600×600 canvas
  4. Make angleStep vary with mouseX (map from 5° to 40°)
  5. 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

Deliverables

  1. Source sketch (well-commented code + documentation)
  2. 10 PNG exports at 2000×2000px minimum, named serie-01.pngserie-10.png
  3. Series documentation (2 pages): concept, generative system, creative choices per variation, seed table
  4. Presentation (10 min): show all 10 works, explain the system and 3 creative decisions

Evaluation Rubric

CriterionInsufficient (1)Satisfactory (2)Good (3)Excellent (4)
Technical masteryOOP or flow field not functionalOne technique masteredTwo techniques integrated and functionalMultiple techniques combined, original implementation
Series coherenceNo visible relationship between the 10 worksCommon visual elementRecognizable coherent identityStrong visual system, each work a distinct variation
Conceptual depthNo intentionTheme statedTheme developed through visual choicesWorks create meaning as a body — not just aesthetic
ReproducibilitySeeds not documentedSeeds notedSeeds + parameters tableSeeds + reproducibility protocol + documented variations
Documentation qualityAbsent or summaryTechnical descriptionConcept + choices + seed tableReflection on the generative system as authorial tool

Readings & Resources


Next level: Creative Coding — Level 3Custom workshop for your school or team: Book a 30-min call — free

Custom training

Intensive workshop (1–2 days) for schools, studios, museums — condensed theory, guided labs, project mentoring.

Book a 30-min call (free) →