Beginner friendly workshop exploring live coding visuals and audio. We'll use p5.js and Hydra for visuals (via P5 Live), and Strudel for live-coded music.
- What is Unsorted
- What is creative coding
- p5.js, Hydra, Strudel. What they are and why we use them
We'll use Strudel — a live coding environment for music, running entirely in the browser.
- Strudel tutorial interactive getting-started guide
- Strudel documentation full reference
- Explore patterns, samples, synths, and effects all from your browser
p5.js is a JavaScript library for creative coding, with a focus on making coding accessible for artists and beginners.
Every p5.js sketch has two main functions: setup() runs once at the start, and draw() runs in a loop, roughly 60 times per second.
function setup() {
createCanvas(windowWidth, windowHeight); // the canvas fills the whole window
background(20); // dark grey background
}
function draw() {
// everything you draw goes here
}p5.js gives you simple functions to draw shapes. Coordinates start at the top-left corner: x goes right, y goes down.
line(0, 0, width, height); // diagonal line corner to corner
rect(100, 100, 200, 150); // rectangle at x:100, y:100, 200 wide, 150 tall
// rectMode(CENTER); // uncomment to draw rectangles from their center
ellipse(width / 2, height / 2, 120, 120); // circle in the center of the canvas
triangle(300, 100, 250, 200, 350, 200); // triangle with three corner points
line()·rect()·ellipse()·triangle()·rectMode()
Control how shapes look with fill (inside color), stroke (outline color), and strokeWeight (outline thickness). Colors can be greyscale (one value), RGB (three values), or include alpha for transparency (four values).
fill(255, 0, 100); // bright pink fill
stroke(255); // white outline
strokeWeight(3); // 3px outline
rect(50, 50, 100, 100);
noFill(); // no fill, outline only
stroke(0, 200, 255); // cyan outline
ellipse(250, 150, 80, 80);
noStroke(); // no outline, fill only
fill(255, 200, 0, 150); // yellow, semi-transparent
ellipse(200, 120, 100, 100);
fill()·noFill()·stroke()·noStroke()·strokeWeight()
Use the mouse to make your sketches interactive. p5.js tracks the mouse position every frame.
// mouseX — current x position of the mouse
// mouseY — current y position of the mouse
// pmouseX, pmouseY — mouse position from the previous frame
line(mouseX, mouseY, pmouseX, pmouseY); // a simple drawing program!
// map() converts a value from one range to another
let brushSize = map(mouseX, 0, width, 2, 30);
strokeWeight(brushSize);
mouseX·mouseY·pmouseX·map()·strokeWeight()
Variables let you store and reuse values. Use let to declare them.
let speed = 3; // a whole number
let position = 12.5; // a decimal number
let label = "hello"; // text
let visible = true; // true or false — useful as a toggle
let points = [10, 20, 30, 40]; // an array — a list of values
let xPos = 0; // declared outside draw() so it persists between frames
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(0);
let size = 60; // declared inside draw() — resets every frame
rect(xPos, height / 2, size, size);
xPos += speed; // xPos increases each frame — the rectangle moves right
}noise() generates smooth, organic random values — great for natural-looking movement.
let t = 0;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(0, 20); // slight trail effect
let x = noise(t) * width;
let y = noise(t + 100) * height;
ellipse(x, y, 30, 30);
t += 0.01; // smaller increments = smoother movement
}Once you're comfortable with the basics, try these:
random() gives you a surprise value every time it's called. Think of it like rolling a die, but you get to pick the range. It's the easiest way to make your sketches feel alive and unpredictable, because no two frames will ever look the same.
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(0, 20); // slightly transparent black — leaves ghost trails
let x = random(width);
let y = random(height);
let size = random(5, 40);
fill(random(255), random(255), random(255), 180);
noStroke();
ellipse(x, y, size, size); // a new random circle every frame
}An if/else statement is like asking a question in your code: if something is true, do this, else, do that. It's how you make your sketch react to what's happening, like a ball bouncing off the edges of the canvas instead of flying off into the void.
let x = 200;
let speed = 3; // how many pixels the ball moves each frame
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(0);
fill(255);
noStroke();
ellipse(x, 200, 50, 50);
x = x + speed; // move the ball
// if the ball hits either edge, reverse direction
if (x > width || x < 0) {
speed = speed * -1; // this makes it bounce back
}
}A loop lets you do something many times without writing it out over and over. Instead of placing 100 circles by hand, you tell the computer "start here, end there, and repeat." Nest two loops together: one for rows, one for columns — and you get a grid with just a few lines of code.
function setup() {
createCanvas(windowWidth, windowHeight);
noLoop(); // only draw once — this is a static pattern
}
function draw() {
background(0);
noStroke();
let spacing = 40; // distance between each circle
for (let x = spacing / 2; x < width; x = x + spacing) {
for (let y = spacing / 2; y < height; y = y + spacing) {
let d = dist(x, y, width / 2, height / 2); // distance from center
let size = map(d, 0, 300, 30, 5); // bigger near the center
fill(map(d, 0, 300, 255, 50), 100, 200);
ellipse(x, y, size, size);
}
}
}Hydra is a live coding video synthesizer that runs in the browser. Think analog video synthesis, but with code.
- Sources
noise(),voronoi(),osc(), plus external sources (webcam, screen, p5 canvas)
noize(3,.5).out()
voronoi(3,.5).out()
osc(5,.5,1).out()- Geometry
rotate(),scale(),pixelate(),kaleid()to transform visuals
noize(3,.5)
.rotate(2,.3)
.out()
voronoi(3,.5)
.scale(-1.15)
.out()
noize(3,.5)
.pixelate(30,4)
.out()
osc(5,.5,1)
.kaleid(8)
.out()- Modulate
modulate(),modulateScale()use one texture to distort another
osc(3,.5,2)
.modulate(noize(3), .2)
.out(o0)
osc()
.modulateScalee(osc(3,.5,2), .2)
.out(o0)- Blend
blend(),add(),mult()combine layers together
//blend
osc(5,.5,1)
.blend(noize(3,.5), 2.2)
.out()
//add
noize(5,.5,1)
.add(osc(3,.5)1, 0.2)
.out()
//blend
osc(5,.5,1)
.mult(osc(5,.25,1),.3)
.out()- Color
contrast(),thresh()colorize and modulate
//contrast
osc(3,.5,2)
.contrast(5.4)
.out()
//thresh
noise(3,0.1)
.thresh(.05,0.04)
.out()- Audio
a.fft[]to make visuals react to sound input
fft = Array(0)
osc()
.modulate(noise(3),()=>a.fft[0])
.out(o0)-
p5 → Hydra feed your p5.js canvas into Hydra as a source for further processing
-
strudel → p5 feed your strudel composition to p5.js canvas for further processing
// strudel
$: sound("bd hh*8 bd*4 hh*4")
.dec(.2).delay(.4)
// .color("cyan magenta yellow")
.p5live(() => {
// console.log(hap)
// filter for bd and sd events
if(hap.s == 'bd') {
bd = 1
}
if(hap.s == 'hh') {
sd = 1
}
})
// hush() // silence everything above it
// strudel
// store some global vars
var bd = 0, sd = 0
function setup() {
createCanvas(windowWidth, windowHeight)
}
function draw() {
clear()
strudel.hide(1) // 0 to show
ellipse(width / 2 + 100, height / 2, bd * 200)
ellipse(width / 2 - 100, height / 2,sd * 200)
bd = ease(.2, bd, .02)
sd = ease(0, sd, .05)
}// sandbox
s0.initP5()
P5.hide()
// src(s0)
// .modulate(noize(3,.2))
// .out(o0)
noize(5,()=>effectH)
.thresh(0.5,.07)
.out(o0)
//()=>effectH
// sandbox
// strudel
$: sound("bd hh*2 bd*4 hh*2")
.dec(.2).delay(.4)
// .color("cyan magenta yellow")
.p5live(() => {
// console.log(hap)
// filter for bd and sd events
if(hap.s == 'bd') {
bd = 1
}
if(hap.s == 'hh') {
sd = 1
}
})
// hush() // silence everything above it
// strudel
// store some global vars
var bd = 0, sd = 0
let effectH
function setup() {
createCanvas(windowWidth, windowHeight)
}
function draw() {
clear()
strudel.hide(1) // 0 to show
ellipse(width / 2 + 100, height / 2, bd * 200)
ellipse(width / 2 - 100, height / 2,sd * 200)
bd = ease(.2, bd, .02)
sd = ease(0, sd, .05)
effectH = map(bd,0.6,1.3,0.01,0.03)
console.log(effectH)
}