p5.js addon library · v3.0.0

p5.waves Guide

You give it a number. It gives you a number back.
That's the whole idea. The rest is just picking which wave shape you want.

Install

Two script tags. p5.js first, then p5.waves. Done.

<script src="https://cdn.jsdelivr.net/npm/p5@2.2.2/lib/p5.js"></script> <script src="https://cdn.jsdelivr.net/gh/seb-prjcts-be/p5.waves@v3.0.0/p5.waves.js"></script> <script src="sketch.js"></script>

No npm. No build step. Just script tags.

Your First Sketch

Copy this into a new p5 sketch. You'll see a moving wave line.

function setup() { createCanvas(600, 400); } function draw() { background(245); noFill(); stroke(0); beginShape(); for (let x = 0; x <= width; x += 3) { let y = Waves.wave(x, { wave: 'mountain peaks', t: millis() / 1000, amplitude: 80 }); vertex(x, height / 2 + y); } endShape(); }

That's the whole API in action. Waves.wave() takes a position, returns a number. Use that number for y-position, size, color, rotation, opacity — anything that takes a number.

Now change 'mountain peaks' to 'batman'. Or 'wobble sine'. Or 'fuzzy pulse'. Each one has its own personality. There are 34 to explore.

Three Ways to Call It

From lazy to precise — all three return the same kind of number.

// Lazy — picks a random wave for you Waves.wave(x); // Quick — pick one by name Waves.wave(x, 'triangle'); // Full control — name, speed, size, everything Waves.wave(x, { wave: 'triangle', t: millis() / 1000, amplitude: 50 });

Start lazy. Add options when you need them.

What Can You Do With a Number?

Anything. Seriously. Here are some ideas:

// Move a circle up and down circle(width / 2, height / 2 + Waves.wave(frameCount, 'sine'), 20); // Control opacity let dotAlpha = Waves.wave(x, { wave: 'pulse', range: [30, 255] }); fill(0, dotAlpha); // Pick a color let wHue = Waves.wave(x + y, { wave: 'meta sine', range: [0, 360] }); fill(wHue, 80, 90); // Size each dot differently let dotSize = Waves.wave(i, { wave: 'sharp peaks', range: [2, 20] }); circle(x, y, dotSize);

If it accepts a number, a wave can drive it.

The Options

You don't need all of these. Start with the top three. Add more when you're curious.

Start here

OptionWhat it doesDefault
waveWhich shape. Pick a name like 'sine', a number like 9, or blend two: ['sine', 'triangle'].random
tTime. Makes it move. Pass millis() / 1000. No time = frozen wave.0
amplitudeHow big. Output swings from -amplitude to +amplitude.100

Level up

OptionWhat it doesDefault
rangeMap output to any range you want. [0, 255] = perfect for color values. Replaces amplitude.null
frequencySqueeze the wave tighter (high) or stretch it out (low).1
seedGive each object its own wave. Same seed always picks the same wave.0
shifttrue = auto-switch to a new random wave every few seconds. Smooth transitions. Different every page load.false

Go deeper

OptionWhat it doesDefault
phaseNudge the wave sideways.0
mode'stable' = clean. 'wild' = wobbly and unpredictable.'stable'
unpredictabilityChaos dial for wild mode. 0 = calm. 1 = full chaos.0
mixWhen blending two waves: 0 = all first wave, 1 = all second wave.0.5
shiftIntervalHow long to hold each wave before switching (seconds).3
shiftDurationHow long the smooth transition takes (seconds).1

All 34 Waves

Use the name or the number. Yes, there's one called batman. Try them all in the Wave Lab.

0 classic sine
1 sine
2 sharp peaks
3 square
4 pulse
5 stepped sine
6 mountain peaks
7 valleys
8 zig-zag sine
9 batman
10 offset sine
11 steps down
12 steps
13 squared sine
14 bumpy sine
15 wobble sine
16 up down noise
17 meta sine
18 triangle
19 ramp
20 saw down
21 saw up
22 fade out
23 grow random
24 noise
25 fuzzy pulse
26 up down pulse
27 bald patch
28 fuzzy peak sine
29 ramp up sine
30 triangle sine
31 round linked sine
32 half sine
33 smooth solid sine

Wave Shift — The Fun Part

This is the feature that makes people go "whoa". One flag, and your wave starts switching to random formulas on its own. Smooth morphs. Different sequence every time you reload.

let sampler = Waves.createSampler({ shift: true, // that's it. that's the flag. amplitude: 60, shiftInterval: 4, // hold each wave 4 seconds shiftDuration: 1.5 // morph over 1.5 seconds }); function draw() { background(245); let t = millis() / 1000; beginShape(); for (let y = 0; y <= height; y += 3) { vertex(width / 2 + sampler.sample(y, t), y); } endShape(); // Want to show what's playing? text(sampler.waveName, 10, 20); // "mountain peaks" text(sampler.shifting, 10, 40); // true while morphing text(sampler.targetName, 10, 60); // "batman" (what's coming next) text(sampler.mix, 10, 80); // 0.0 → 1.0 during morph }

Reload the page. Different waves. Every time. Your sketch is never the same twice.

Shift also works directly on Waves.wave() — just add shift: true to the options. But the sampler version is better: it caches everything and gives you the .waveName / .shifting / .mix getters for free.

Morph — Blend Two Waves

Pick two waves. Slide between them. Connect mix to your mouse and you get a live crossfader.

let y = Waves.wave(x, { wave: ['sine', 'batman'], // sine on the left, batman on the right mix: mouseX / width, // drag to blend t: millis() / 1000, amplitude: 40 });

mix: 0 = pure first wave. mix: 1 = pure second wave. Anything in between = a blend of both. Animate it for smooth shape transformations.

createSampler() — For Loops and Particles

If you're calling the same wave 200 times per frame (particles, grid cells, trail points), set up a sampler once and call .sample() instead. Same result, less repeated work.

let s = Waves.createSampler({ wave: 'triangle', range: [-80, 80] }); // Then in draw, call it as many times as you want: s.sample(y); // position only s.sample(y, t); // position + time s.sample(y, t, mix); // position + time + morph blend s.waveName; // "triangle"

Takes all the same options as Waves.wave(). Including shift: true.

Two samplers for independent axes

Want X and Y to move independently? Two samplers, two different seeds.

let sx = Waves.createSampler({ seed: 0, range: [-80, 80] }); let sz = Waves.createSampler({ seed: 1, range: [-80, 80] }); // Different seed = different wave = uncorrelated motion

createGrid() — Instant 2D Patterns

One call fills a 2D grid with wave values. Two waves combine — one runs along the rows, one along the columns. The result: animated black-and-white patterns.

let g = Waves.createGrid(14, 14, { waveRow: 'classic sine', waveCol: 'triangle', threshold: 0, // above 0 = on, below 0 = off speed: 1 }); function draw() { let cells = g.sample(frameCount * 0.02); // cells is a Uint8Array of 0s and 1s // cells[row * g.cols + col] }
OptionWhat it doesDefault
waveRowWave for the rows.random
waveColWave for the columns.random
seedAuto-picks two different waves for you.0
rangeContinuous output. Returns Float32Array.null
thresholdBinary on/off. Returns Uint8Array.null
speedHow fast the pattern moves.1

heads up The output array is reused between calls. If you need to keep a copy: new Float32Array(g.sample(t)).

Wild Mode — Controlled Chaos

Turn on mode: 'wild' and the wave gets wobbly. Frequency, phase, and amplitude all start drifting. It stays recognizable — just... unhinged.

let y = Waves.wave(x, { wave: 'pulse', t: millis() / 1000, mode: 'wild', unpredictability: 0.45, // 0 = calm, 1 = full chaos amplitude: 80 });

Start at 0.3 and work your way up. At 1.0 things get interesting. Wild mode is about 5x slower though — so don't use it in a 10,000-point loop unless you're ready for that.

Time — You Control It

There's no built-in clock. You pass t on every call. That means you're in charge.

// Tie time to the mouse — scrub through the wave let t = map(mouseX, 0, width, 0, 10); let y = Waves.wave(x, { wave: 'sine', t: t, amplitude: 50 });

Want to pause? Stop changing t. Want to rewind? Decrease it. Want slow motion? Use a tiny increment. Want to sync two sketches? Give them the same t.

Seed vs Index — Don't Mix Them Up

These look similar but do different things:

Waves.wave(x, 3) // seed 3 → hashed to SOME wave (not necessarily #3) Waves.wave(x, { wave: 3 }) // index 3 → exactly wave #3 (square)

Index = "I want that exact wave." Seed = "Give each of my 50 particles its own consistent wave — I don't care which."

Shorthand Names

Waves.* always works. But in a regular p5 sketch, shorter names are available too.

Always worksp5 global modep5 instance mode
Waves.wave(y, opts)waves(y, opts)p.waves(y, opts)
Waves.createSampler(opts)createWaveSampler(opts)p.createWaveSampler(opts)
Waves.createGrid(c, r, opts)createWaveGrid(c, r, opts)p.createWaveGrid(c, r, opts)

Extras: Waves.list() returns all 34 formulas. Waves.count = 34. Waves.data = the raw formula array.

Copy-Paste Starters

Grab one. Drop it in your sketch. Go.

Just a wave

let y = Waves.wave(x, 'mountain peaks');

Moving wave

let y = Waves.wave(x, { wave: 'mountain peaks', t: millis() / 1000, amplitude: 80 });

Wave as color

let dotAlpha = Waves.wave(x, { wave: 'pulse', t: millis() / 1000, range: [30, 255] }); fill(0, dotAlpha);

Auto-shifting

let s = Waves.createSampler({ shift: true, amplitude: 60 }); // in draw: s.sample(x, millis() / 1000);

Binary grid

let g = Waves.createGrid(20, 20, { threshold: 0, speed: 1 }); let cells = g.sample(t);

50 particles, each with its own wave

for (let i = 0; i < 50; i++) { let y = Waves.wave(frameCount, { seed: i, // each particle gets its own wave t: millis() / 1000, amplitude: 30 }); circle(i * 12, height / 2 + y, 6); }