p5.js addon library · v3.6.0

p5.waves Examples

Each thumbnail is a live sketch. Click -> full example for the code and full-size canvas.

Wave Lab ->

All 35 waves. Morph, shift, wild mode. Try everything.

One Number

One call returns one number. Use it for size, position, color - anything.

// One number - use it for anything
const v = Waves.wave(i * 0.3, {
  wave: 'up down noise', t: t, range: [0, 1]
});

// Row 1: size
circle(x, y, v * 30);

// Row 2: position
circle(x, baseY + v * rowH * 0.8, 8);

// Row 3: color (pure red -> pure blue)
fill(255 - v * 255, 0, v * 255);
circle(x, y, 14);

Wave Shift

The wave changes automatically every few seconds. Smooth morph between random formulas. Watch the ribbons transform.

const sampler = Waves.createSampler({
  shift: true,
  amplitude: 1,
  frequency: 0.5
});

// Filled ribbons - color shifts with the wave
const t = millis() / 1000;
for (let i = 0; i < STRIPS; i++) {
  const frac = i / (STRIPS - 1);
  fill(255 - frac * 255, 0, frac * 255);
  const v = sampler.sample(i * 0.4, t + i * 0.1);
  rect(i * sw, cy - v * rowH, sw - 1, v * rowH * 2);
}

Shape Parameters

Move your mouse over the canvas. X = frequency, Y = amplitude. The formula shifts automatically - drag to feel the difference.

// mouseX = frequency, mouseY = amplitude
let freq = map(mouseX, 0, width, 0.15, 3.5);
let amp = map(mouseY, 0, height, 120, 8);

Waves.wave(x, {
  wave: waveName, t: t,
  amplitude: amp,
  frequency: freq * 0.01
});

Wild Mode

Left: order. Right: chaos. Same wave, one flag added. The difference speaks for itself.

// Left: stable circles. Right: wild circles.
// Stable - orderly pattern
const sz = Waves.wave(col + row * 0.5, {
  wave: waveIdx, t: t, range: [4, maxR]
});

// Wild - same wave, chaos added
const szW = Waves.wave(col + row * 0.5, {
  wave: waveIdx, t: t, range: [4, maxR],
  mode: 'wild', unpredictability: 0.7
});

Flow Fields

A grid of / - | \ characters. One sampler picks the direction per cell. Not unlike noise() and noiseSeed(), but with unexpected wave shifts every few seconds.

// ASCII flow field - sampler with shift
const DIRS = ['-', '/', '|', '\\'];
let sampler = createWaveSampler({
  shift: true,
  shiftInterval: 4,
  shiftDuration: 2,
  frequency: 2
});

// In draw():
for (let row = 0; row < ROWS; row++) {
  for (let col = 0; col < COLS; col++) {
    let val = sampler.sample(col * 0.5, t + row * 0.4);
    let idx = constrain(floor(map(val, -1, 1.001, 0, 4)), 0, 3);
    text(DIRS[idx], col * sz + sz / 2, row * sz + sz / 2);
  }
}

Time is a Number

No built-in clock. Pass any number as t. Move the mouse to scrub through time. Freeze it, rewind it, speed it up.

// No clock - t is whatever you pass
// Move mouse to scrub through time
const t = map(mouseX, 0, width, 0, 24);

for (let i = 0; i < 12; i++) {
  const layerT = t + (i / 12) * 6;
  beginShape();
  for (let x = 0; x <= width; x += 4) {
    const dy = Waves.wave(x * 0.15, {
      wave: strataWave, t: layerT,
      amplitude: 15
    });
    vertex(x, y0 + dy);
  }
  endShape();
}

Static Field

A wave-driven take on random(155) + 100. A slow base sampler sets a floor in [0, 155], a faster field sampler adds 0..100 per cell. Per channel.

// Slow base + fast field, per channel (R/G/B)
const baseR = Waves.createSampler({
  shift: true, group: 'gentle',
  range: [0, 155], frequency: 0.20,
  shiftInterval: 7, shiftDuration: 2
});
const fieldR = Waves.createSampler({
  shift: true,
  group: ['classic sine', 'triangle', 'bumpy sine',
          'mountain peaks', 'wobble sine'],
  range: [0, 1], frequency: 0.08,
  shiftInterval: 4, shiftDuration: 1.5
});
// ...baseG/fieldG, baseB/fieldB with own settings

// In draw: floor + field offset (0..100), per channel
const r0 = floor(baseR.sample(0, t));
fill(r0 + fieldR.sample(x + y*0.35, t) * 100,
     g0 + fieldG.sample(x*0.3 + y, t) * 100,
     b0 + fieldB.sample(x*0.7 - y*0.25, t) * 100);

Morph Wave

Blend two wave formulas together. mix: 0 = pure A, mix: 1 = pure B. Animate it and the shape smoothly transforms.

// Blend two formulas with mix
const wA = floor(random(35));
const wB = floor(random(35));

// mix: 0 = pure A, mix: 1 = pure B
const morphMix = (sin(t * 0.3) + 1) * 0.5;

const wv = Waves.wave(x, {
  wave: [wA, wB],    // morph between two waves
  mix: morphMix,
  t: t + row * 0.06,
  frequency: 0.08,
  amplitude: rowH * 0.8
});

Spiky Lissajous

Classic Lissajous with sin swapped for a spiky wave. It still closes cleanly because θ sweeps one full period and the ratio is integer.

// Swap sin() for a spiky wave - curve still closes.
// A pen sweeps one cycle, then lands back on the start dot.
const A = 3, B = 5;
const prog = (millis() % CYCLE_MS) / CYCLE_MS;
const drawnTo = floor(prog * N);

for (let i = 0; i <= N; i++) {
  const theta = (i / N) * wave.period;
  xs[i] = r * Waves.wave(A * theta, { wave: wave.name, amplitude: 1 });
  ys[i] = r * Waves.wave(B * theta + wave.period * 0.25, {
    wave: wave.name, amplitude: 1
  });
}

beginShape();
for (let i = 0; i <= drawnTo; i++) vertex(xs[i], ys[i]);
endShape();

// Home dot + pen tip. When drawnTo === N, they overlap exactly.
fill(255); circle(xs[0], ys[0], 10);
fill(18);  circle(xs[0], ys[0], 5);
fill(255); circle(xs[drawnTo], ys[drawnTo], 6);

Ghost Delay

A height wave y = f(x) can never loop - one x would need many y. But plot a single wave against a delayed copy of itself, x = wave(u) and y = wave(u + τ), and the pair closes into a loop ring. Sweep the delay and it breathes from ellipse to petals.

// The delayed wave IS a p5.waves sampler - shift morphs it
// through the 'ghost' pool, so the loop family evolves.
const s = Waves.createSampler({
  group: 'ghost',              // built-in pool tuned for this
  shift: true, range: [-1, 1]  // range:[-1,1] -> unit output (default is 100)
});

// The sampler reports its own period - no 2*PI/0.1 to hardcode.
const period = s.period;
const delay  = period * (0.5 + 0.35 * sin(t * 0.35));   // the ghost delay

for (let i = 0; i <= N; i++) {
  const u = (i / N) * period;          // one period -> ring closes
  xs[i] = r * s.sample(u, t);          // x = wave(u)
  ys[i] = r * s.sample(u + delay, t);  // y = same wave, delayed
}
beginShape();
for (let i = 0; i <= N; i++) vertex(xs[i], ys[i]);
endShape(CLOSE);

Not So Random Walker

No angles, no steps. The wave output is the velocity. Five trails ride two shift-samplers as raw displacement, so when the formulas shift the whole movement character transforms.

// Wave output IS the velocity - no angle, no step.
const xWave = Waves.createSampler({
  shift: true, shiftInterval: 4,
  amplitude: 2.5, frequency: 0.7, seed: 0
});
const yWave = Waves.createSampler({
  shift: true, shiftInterval: 5,
  amplitude: 2.5, frequency: 0.55, seed: 77
});

// Per walker, per frame: one sample = one step of displacement
for (let i = 0; i < WALKERS; i++) {
  const phase = i * 6.7;
  const vx = xWave.sample(t * 1.8 + phase, t);
  const vy = yWave.sample(t * 2.1 + phase * 1.3, t);
  wx[i] += vx;
  wy[i] += vy;
  trail.line(prevX[i], prevY[i], wx[i], wy[i]);
}

Binary Field

Two samplers - one for rows, one for columns. Sum them per cell. Threshold the result. The whole 2D pattern from one nested loop.

// Two samplers, one for each axis
const rowS = Waves.createSampler({ wave: 'smooth solid sine', range: [-1, 1] });
const colS = Waves.createSampler({ wave: 'ramp up sine',     range: [-1, 1] });

// Per frame: nested loop, sum, threshold
let t = millis() / 1000;
for (let row = 0; row < rows; row++) {
  let rv = rowS.sample(row * 5 + t);    // x-step ~ wave period
  for (let col = 0; col < cols; col++) {
    let cv = colS.sample(col * 2.5 - t);
    fill((rv + cv) > 0 ? 15 : 225);     // 1 = black, 0 = light
    rect(col * cw, row * ch, cw, ch);
  }
}

3D Wave Volume

Three samplers - one per axis - each shifting on its own cycle. Points near the surface glow.

// Three shift-samplers - one per axis
const sY = Waves.createSampler({
  shift: true, shiftInterval: 3,
  range: [-5, 5], frequency: 0.4
});
const sX = Waves.createSampler({
  shift: true, shiftInterval: 4,
  range: [-2.5, 2.5], frequency: 0.3
});
const sZ = Waves.createSampler({
  shift: true, shiftInterval: 5,
  range: [-2.5, 2.5], frequency: 0.35
});

// Points near the surface glow
const dy = sY.sample(xi * 0.9 + zi * 0.6, t);