wave_shift
shift · one sampler · rangeFilled ribbons that auto-shift between random wave formulas. Colour flows from red to blue across the strips. The output of one shift-sampler drives the ribbon height per column.
// Wave Shift
// Filled ribbons that auto-shift between random wave formulas.
// Color flows from red to blue across the strips.
import waves.*;
final int STRIPS = 20;
Waves.WaveSampler sampler;
void setup() {
size(460, 460);
noStroke();
textFont(createFont("Consolas", 11));
sampler = Waves.createSampler(new WaveOpts()
.shift(true)
.amplitude(1)
.frequency(0.5f));
}
void draw() {
background(245);
float t = millis() / 1000.0f;
float sw = (float)width / STRIPS;
float cy = height / 2f;
float rowH = height * 0.42f;
for (int i = 0; i < STRIPS; i++) {
float frac = i / (float)(STRIPS - 1);
float r = 255 * (1 - frac);
float b = 255 * frac;
fill(r, 0, b);
float v = sampler.sample(i * 0.4f, t + i * 0.1f);
rect(i * sw, cy - v * rowH, sw - 1, v * rowH * 2);
}
fill(0);
textSize(11);
textAlign(LEFT);
text(sampler.waveName(), 8, 16);
}
morph_wave
morph · two waves · sweepA field of horizontal lines where each row blends two wave formulas. Top rows = pure wobble sine, bottom rows = pure meta sine. The blend point sweeps up and down over time, so you see the shape transform mid-field.
// Morph Wave
// A field of horizontal lines where each row blends two wave formulas.
// Top rows = pure waveA. Bottom rows = pure waveB. Middle = the morph.
// The blend sweeps up and down over time, you see the shape transform.
import waves.*;
final String WAVE_A = "wobble sine";
final String WAVE_B = "meta sine";
final int ROW_COUNT = 50;
float t = 0;
void setup() {
size(460, 460);
}
void draw() {
background(250);
t += 0.0375f;
float centre = (sin(t * 0.3f) + 1) * 0.5f;
float rowH = (float)height / ROW_COUNT;
for (int row = 0; row < ROW_COUNT; row++) {
float rowFrac = row / (float)(ROW_COUNT - 1);
float gap = abs(rowFrac - centre);
float morphMix = constrain(1 - gap * 3, 0, 1);
float yBase = row * rowH + rowH * 0.5f;
float r = lerp(0, 255, morphMix);
float b = lerp(255, 0, morphMix);
noFill();
stroke(r, 0, b);
strokeWeight(1.2f + morphMix * 1.8f);
beginShape();
WaveOpts o = new WaveOpts()
.wave(WAVE_A, WAVE_B)
.mix(morphMix)
.t(t + row * 0.06f)
.frequency(0.08f)
.amplitude(rowH * 2.5f);
for (int x = 0; x < width; x += 3) {
float waveY = Waves.wave(x, o);
vertex(x, yBase + constrain(waveY, -rowH * 0.7f, rowH * 0.7f));
}
endShape();
}
noStroke();
fill(0, 0, 255);
textSize(10);
textFont(createFont("Consolas", 10));
textAlign(LEFT);
text(WAVE_A, 8, 16);
fill(255, 0, 0);
textAlign(RIGHT);
text(WAVE_B, width - 8, height - 8);
}
flow_fields
ASCII · one sampler · directionA grid of ASCII characters forms a flow field. Each cell's direction comes from a single shift-sampler, like noise, but with structure. The wave shifts every few seconds, so the whole field's character keeps changing.
// Flow Fields
// A grid of ASCII characters forms a flow field.
// Each cell's direction comes from waves. Like noise, but with structure.
// The wave formula shifts automatically every few seconds.
import waves.*;
final int COLS = 30;
final int ROWS = 30;
final String[] DIRS = { "-", "/", "|", "\\" };
Waves.WaveSampler sampler;
void setup() {
size(460, 460);
textFont(createFont("Consolas", 14));
textAlign(CENTER, CENTER);
noStroke();
fill(0);
sampler = Waves.createSampler(new WaveOpts()
.shift(true)
.shiftInterval(4)
.shiftDuration(2)
.frequency(2)
.range(-1, 1));
}
void draw() {
background(255);
float t = millis() / 1000.0f;
float sz = (float)width / COLS;
textSize(sz * 0.9f);
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLS; col++) {
float val = sampler.sample(col * 0.5f, t + row * 0.4f);
int idx = constrain((int)map(val, -1, 1.001f, 0, 4), 0, 3);
text(DIRS[idx], col * sz + sz / 2, row * sz + sz / 2);
}
}
}
binary_field
two samplers · sum · thresholdTwo independent shift-samplers, summed per cell, thresholded into a 2D pattern. Both samplers shift on their own schedule, so the field's character keeps evolving: sometimes interference, sometimes stripes, sometimes checker.
// Binary Field
// Two samplers, summed per cell, thresholded into a 2D pattern.
// Both samplers shift independently, so the field's character keeps
// evolving. Sometimes interference, sometimes stripes, sometimes checker.
import waves.*;
final int COLS = 30;
final int ROWS = 20;
Waves.WaveSampler rowS, colS;
void setup() {
size(460, 460);
noStroke();
textFont(createFont("Consolas", 11));
rowS = Waves.createSampler(new WaveOpts()
.shift(true)
.shiftInterval(4)
.shiftDuration(1)
.range(-1, 1)
.seed(1));
colS = Waves.createSampler(new WaveOpts()
.shift(true)
.shiftInterval(4.5f)
.shiftDuration(1.2f)
.range(-1, 1)
.seed(2));
}
void draw() {
background(245);
float t = millis() / 1000.0f;
float labelH = 28;
float cw = (float)width / COLS;
float ch = (height - labelH) / ROWS;
float offset = t * 0.4f;
for (int row = 0; row < ROWS; row++) {
float rv = rowS.sample(row * 5 + offset, t);
for (int col = 0; col < COLS; col++) {
float cv = colS.sample(col * 2.5f - offset, t);
boolean on = (rv + cv) > 0;
fill(on ? 15 : 230);
rect(col * cw, labelH + row * ch, cw - 0.5f, ch - 0.5f);
}
}
fill(40);
textSize(11);
textAlign(LEFT, CENTER);
text(rowS.waveName() + " x " + colS.waveName(), 8, labelH / 2);
}
random_walker
wave as velocity · trails · PGraphicsNo angles, no steps. Wave output IS the velocity. Five trails ride two shift-samplers as raw displacement. When the formulas shift, the movement character transforms entirely. Trails draw onto a PGraphics with low-alpha fade.
// Not So Random Walker
// No angles, no steps. Wave output IS the velocity.
// Five trails ride two shift-samplers as raw displacement.
// When formulas shift, movement character transforms entirely.
import waves.*;
final int WALKERS = 5;
Waves.WaveSampler xWave, yWave;
float[] wx = new float[WALKERS], wy = new float[WALKERS];
float[] prevX = new float[WALKERS], prevY = new float[WALKERS];
float t = 0;
PGraphics trail;
// R, G, B, yellow, purple
final int[][] palette = {
{255, 60, 60},
{60, 220, 60},
{60, 100, 255},
{255, 220, 40},
{180, 60, 255}
};
void setup() {
size(460, 460);
trail = createGraphics(460, 460);
trail.beginDraw();
trail.background(15);
trail.endDraw();
xWave = Waves.createSampler(new WaveOpts()
.shift(true)
.shiftInterval(4)
.shiftDuration(1.5f)
.amplitude(2.5f)
.frequency(0.7f)
.seed(0));
yWave = Waves.createSampler(new WaveOpts()
.shift(true)
.shiftInterval(5)
.shiftDuration(1.2f)
.amplitude(2.5f)
.frequency(0.55f)
.seed(77));
for (int i = 0; i < WALKERS; i++) {
float a = TWO_PI * i / WALKERS;
wx[i] = width / 2f + cos(a) * 40;
wy[i] = height / 2f + sin(a) * 40;
prevX[i] = wx[i];
prevY[i] = wy[i];
}
}
void draw() {
trail.beginDraw();
trail.noStroke();
trail.fill(15, 15, 15, 8);
trail.rect(0, 0, trail.width, trail.height);
t += 0.025f;
for (int i = 0; i < WALKERS; i++) {
float phase = i * 6.7f;
float vx = xWave.sample(t * 1.8f + phase, t);
float vy = yWave.sample(t * 2.1f + phase * 1.3f, t);
prevX[i] = wx[i];
prevY[i] = wy[i];
wx[i] += vx;
wy[i] += vy;
if (wx[i] < 0) wx[i] += width;
if (wx[i] > width) wx[i] -= width;
if (wy[i] < 0) wy[i] += height;
if (wy[i] > height) wy[i] -= height;
if (abs(wx[i] - prevX[i]) > width / 2) continue;
if (abs(wy[i] - prevY[i]) > height / 2) continue;
int[] col = palette[i];
trail.stroke(col[0], col[1], col[2], 200);
trail.strokeWeight(2.5f);
trail.line(prevX[i], prevY[i], wx[i], wy[i]);
}
trail.endDraw();
image(trail, 0, 0);
noStroke();
fill(255, 255, 255, 120);
textSize(10);
textFont(createFont("Consolas", 10));
textAlign(LEFT);
text(xWave.waveName() + " x " + yWave.waveName(), 8, 16);
}