Deep guide · v3.2.7

Curation Engine Guide

Each reusable element is broken into start, build, and end product. Every section includes a live animated preview and a full copyable block that you can paste into a p5 sketch with p5.waves loaded.

From One Wave To A Poster

The curation engine works when the page stops treating motion as decoration. Pools, samplers, modules, and readouts become one authored system.

Live assembly preview · ribbons + grid + title + atmosphere
start: pool -> role -> module
end product: one engine, many surfaces
start Stop thinking in single effects

The key shift is asking which family of wave behavior belongs to each layer, instead of asking how to animate one object.

build Assign roles, not values

Pools curate tone, samplers curate responsibility, and modules reveal the choices in public through layout, type, and proof panels.

end product One engine, many surfaces

The poster feels designed because motion, structure, and interface all come from the same logic instead of unrelated effects.

// the engine: pool -> role -> module
const sampler = Waves.createSampler({ group: ARRAY_GROUP, shift: true });
const grid    = Waves.createGrid(24, 12, { group: 'harsh', threshold: 0.12 });

// each module pulls from one source:
//   ribbons   - one sampler per row, shift-driven
//   panel     - grid.sample(t) as a binary readout
//   title     - typeSampler offsets each letter
//   ticker    - sampler.waveName / .targetName as labels

// one t drives them all - the composition reads as one piece

Curate The Pool

This is the editorial backbone. Instead of letting every layer access every wave, you decide which behavior family belongs to which job.

start Name the voices

gentle is readable, harsh ruptures, all stays broad, and the array pool is hand-picked for taste.

build Bind color to character

Each group gets a visible identity: a label, a pool, and a color. The selection logic becomes visual direction.

end product Rotation without chaos

The active pool can change over time, but the sketch still speaks in a controlled voice because each layer stays in its family.

preview: four curated families as live rows
result: variation with authorship
// group narrows the pool that shift/seed picks from
const sampler = Waves.createSampler({
  group: 'gentle',          // or 'harsh', 'all'
  shift: true,
  range: [-1, 1]
});

// or pass your own curated list:
//   group: ['classic sine', 'mountain peaks', 'meta sine']

// each pool has its own voice
const v = sampler.sample(i * 0.22, t);

Split The Responsibilities

Separate samplers are what make the composition feel layered. Motion, color, pulse, and type stop collapsing into one uniform behavior.

start Define layer jobs

List what the sketch needs: drift, pulse, color selection, and typographic wobble. Those are different roles.

build Tune each role separately

Readable motion uses gentle, pulse uses harsh, and typography uses the curated array. Each layer gets its own temperament.

end product Layered behavior

The page gains nuance because not everything responds the same way. The system reads as composed instead of globally animated.

preview: motion, color, pulse, type
result: four roles, four behaviors
// one sampler per role - motion, color, pulse, type
const motion = Waves.createSampler({ group: 'gentle', shift: true });
const color  = Waves.createSampler({ group: 'all',    shift: true, range: [0, 1] });
const pulse  = Waves.createSampler({ group: 'harsh',  shift: true });
const type   = Waves.createSampler({ group: ARRAY_GROUP, shift: true });

// each layer responds at its own tempo
const dx       = motion.sample(0, t) * 40;
const fillIdx  = floor(color.sample(0, t) * shades.length);
const power    = pulse.sample(i * 0.18, t);     // jagged
const wobbleY  = type.sample(i * 0.25, t) * 8;  // typographic drift

Turn Waves Into Material

A line describes. A ribbon divides space. This is where the library starts behaving like layout instead of motion garnish.

start Sample a line

Use Waves.wave() across the x-axis to get the upper contour of the future form.

build Close it as a band

Run the lower edge back with a vertical offset. The moment the shape closes, it becomes printable material.

end product Editorial strips

The final bands split the composition, carry tone, and push energy without abandoning the page structure.

preview: line -> ribbon -> band
result: wave as layout material
// wave forms the upper edge - close it back as a ribbon
fill('#174cffaa');
beginShape();
for (let x = 0; x <= width; x += 14) {
  vertex(x, y + Waves.wave(x * 0.015, {
    group: 'gentle', shift: true, t, amplitude: 22
  }));
}
for (let x = width; x >= 0; x -= 14) vertex(x, y + 26);  // bottom edge
endShape(CLOSE);

Show The System Thinking

The grid module is the proof panel. It gives the sketch a second language beside the hero layer and makes the internal logic visible.

start Create a binary field

Use createGrid() with a chosen group so the panel already speaks in the same temperament as the rest of the sketch.

build Frame it as a module

Render a bordered panel, draw the cells, and label the group. The output becomes a visible instrument instead of hidden logic.

end product Proof beside the hero

The panel stabilizes the poster by pairing expressive bands with a rational readout surface.

preview: binary field rendered as monitor panel
result: visual proof module
// createGrid - a binary 2D field driven by two waves
const g = Waves.createGrid(24, 12, {
  group: 'harsh',
  threshold: 0.12        // cells where waveRow(row) + waveCol(col) > 0.12 = on
});

// per frame: read all cells in one call
const cells = g.sample(t);
for (let row = 0; row < g.rows; row++) {
  for (let col = 0; col < g.cols; col++) {
    const on = cells[row * g.cols + col] === 1;
    fill(on ? '#ff3b2f' : '#ece5d7');
    rect(col * cw, row * ch, cw, ch);
  }
}

Make The Title Breathe

The type works because the movement is sampled and constrained. The word stays typographic first, live second.

start Keep the word intact

Draw the title letter by letter while preserving the baseline and spacing logic of the word.

build Sample each letter

Offset every glyph with the same sampler at a different sample position so the letters move as a family.

end product Alive, not unstable

The final word drifts just enough to show life without breaking its own legibility.

preview: one word, offset per letter
result: typography that breathes
// one sampler offsets each letter at a different sample position
const type = Waves.createSampler({ group: ARRAY_GROUP, shift: true });

textSize(110);
let cursor = x;
for (let i = 0; i < word.length; i++) {
  const wobble = type.sample(i * 0.24, t) * 6;
  text(word[i], cursor, y + wobble);
  cursor += textWidth(word[i]) * 0.97;
}
// the word stays typographic first, alive second

Expose The State

The strip turns the sketch into an instrument. It translates sampler state into a graphic surface with live labels and progress.

start Read the sampler

Sample first so waveName, targetName, mix, and shifting are current.

build Translate state into UI

Bar fill becomes mix, labels expose the current and target wave, and the group color keeps the strip tied to the active pool.

end product Readable instrument panel

The composition explains itself without becoming a dashboard. The logic is present, but still graphic.

preview: mix bar + wave labels + markers
result: state rendered as composition
// the sampler exposes its own state - render it as composition
sampler.sample(0, t);              // pump state
const mixBar = sampler.shifting ? sampler.mix : 0;

fill('#080808');                   // track
rect(44, 92, 560, 32);
fill(activeColor);                 // fill = morph progress
rect(44, 92, 560 * mixBar, 32);

text('GROUP / ' + sampler.waveName, 56, 113);
text('NEXT '   + sampler.targetName, 430, 113);

Let Vocabulary Become Atmosphere

The background field stays useful because it is still attached to the same engine as the hero layer. It adds context without turning generic.

start Choose a small vocabulary

Use words that belong to the library itself: wave names, behaviors, parameters, and descriptive tags.

build Drive drift and visibility

Motion moves the words, pulse decides whether they appear, and type drift keeps them from freezing in place.

end product Context without clutter

The words become ghost labels and marginalia behind the hero statement, not filler sprinkled on top.

preview: pulse-gated drifting vocabulary
result: ambient language field
// pulse gates visibility, motion drifts position, type wobbles
const a = pulse.sample(seed * 0.13, t);
if (a < 0.34) continue;          // below threshold = invisible this frame

const dx = motion.sample(seed * 0.31, t) * 22;
const dy = type.sample(seed * 0.19, t + 1.5) * 16;

fill(0, map(a, 0.34, 1, 40, 150));
text(word, x + dx, y + dy);

The Full Poster

The complete composition, live. Every module above runs together here.