Creative Coding

My creative coding work explores how data, interactivity, and visual systems can tell stories and provoke emotion. Using code, I translate conceptual ideas into dynamic, visual experiences

For this data visualization project, I explored my relationship with music and everyday listening habits. I turned to the dB levels recorded by my Apple earphones. I’m constantly notified to turn my volume down, which made me curious about how loud my listening actually is.

Inspired by sound waves and speaker vibrations, I experimented with FFT and CSV-driven visuals, eventually designing a series of animated rings that “wobble” in response to sound levels. What began as a graph evolved into an expressive, motion-based portrait translating invisible sound exposure into a visible, interactive experience.

Through this project, I discovered that I listen to music almost every day at volumes above healthy limits. Seeing this data made me reflect on how deeply sound shapes my routines and emotions, and how habits that bring comfort can also carry hidden harm.

const controls = {
  resolution: 240,
  noiseFactor: 6,
  hoverWobble: 5.0,
  noiseDetail: 0.01,
  speed: 0.02
};
let flowSpeed = 0.6;     
let waveWidth = 20;      
let waveFront = 0;
let table;
let size = 9;
let cx, cy;
let num;
let minR = 80;
let rings = [];
let palette = ["#4287f5", "#59B1C9", "#2b67af", "#62b6de", "#1B19A2", "#D0E5FF", "#587AD1", "#49C6FF"];

function preload() {
  table = loadTable("data.csv", "csv", "header");
}

function setup() {
  createCanvas(450, 450);
  cx = width / 2;
  cy = height / 2;
  num = table.getRows().length;

  for (let i = 0; i < num; i++) {
    let dataRow = table.getRow(i);  
    let date = dataRow.getString("Date");
    let AveragedB = dataRow.getString("Avg");
    let HighestdB = dataRow.getString("DB High");
    let r = minR + i * size;
    let c = random(palette);
    rings.push(new Ring(i, date, AveragedB, HighestdB, r, c));
  }
  waveFront = minR;
}

function draw() {
  background(60);
  waveFront += flowSpeed;
  const outerR = minR + (num - 1) * size;
  if (waveFront > outerR + waveWidth) {
    waveFront = minR - waveWidth;
  }

  for (let i = 0; i < num; i++) {
    rings[i].update();
    rings[i].display();
  }
}

class Ring {
  constructor(index, date, avg, high, r, c) {
    this.index = index;
    this.date = date;
    this.avg = float(avg);
    this.high = float(high);
    this.r = r;
    this.c = c;
    this.hoverFactor = 0;
  }

  update() {
    const d = abs(this.r - waveFront);
    const halfW = waveWidth * 0.5;
    const t = 1.0 - constrain((d - 0) / halfW, 0, 1);
    this.hoverFactor = t * t * (3 - 2 * t);
  }

  display() {
    noFill();
    const sw = lerp(2, 4, this.hoverFactor);
    strokeWeight(3);
    stroke(this.c);

    beginShape();
    const dataBoost = map(this.avg, 40, 100, 1.0, 3.5);  
    const wobbleBoost = lerp(1.0, controls.hoverWobble * 1.2, this.hoverFactor);
    const wobbleAmt = controls.noiseFactor * 1.5 * wobbleBoost * dataBoost;

    for (let i = 0; i <= controls.resolution; i++) {
      const a = map(i, 0, controls.resolution, 0, TWO_PI);
      const bx = cx + cos(a) * this.r;
      const by = cy + sin(a) * this.r;
      const n = noise(
        bx * controls.noiseDetail,
        by * controls.noiseDetail,
        frameCount * controls.speed
      );

      const offsetR = n * wobbleAmt;
      const x = cx + cos(a) * (this.r + offsetR);
      const y = cy + sin(a) * (this.r + offsetR);

      vertex(x, y);
    }

    endShape(CLOSE);
  }  
}

Inspired by the Spillmann illusion, I set out to recreate the sensation of motion within a still image using code. I built a dynamic checkerboard background through nested loops that generated alternating rectangular patterns, then layered a central circle composed of precisely placed lines that interact visually with the grid beneath it. Debugging the sequence of elements like ensuring the circle rendered above the stripes was key to achieving the illusion’s depth and rhythm.

The final piece simulates the hypnotic movement of the original optical illusion while existing entirely in code. I imagine it functioning as an interactive visual perhaps a responsive website background, a loading animation, or even a therapeutic visual tool that distorts and shifts with user interaction.

let rects = [];
let angle = 0; 

function setup() {
  createCanvas(680, 680);

  //create all rects
  for (let y = 0; y < height; y += 15) {
    let offset = (y / 15) % 2 === 0 ? 0 : 40;
    for (let x = 0; x < width; x += 80) {
      rects.push(new Rect(x + offset, y, 40, 15));
    }
  }
}

function draw() {
  background(255);

  //grid
  for (let i = 0; i < rects.length; i++) {
    rects[i].display();
  }
  
  
  let circleCenterX = width / 2;
  let circleCenterY = height / 2;
  let circleRadius = 190;

  //rotate stripes
  push();
  translate(circleCenterX, circleCenterY); 
  rotate(radians(angle)); 
  angle += 0.5; 
  noStroke();
  fill(0);
  

  //center
  const xStart = -circleRadius;
  const xEnd = circleRadius;

  for (let x = xStart; x <= xEnd; x += 40) {
    for (let y = -height / 2; y < height / 2; y += 5) {
      const d = dist(x + 10, y + 2.5, 0, 0);
      if (d <= circleRadius) {
        let stripeWidth = 20;
        if (x === xEnd) stripeWidth = 10;
        rect(x, y, stripeWidth, 5);
      }
    }
  }

  pop();
}

class Rect {
  constructor(x, y, w, h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
  }

  display() {
    fill(0);
    rect(this.x, this.y, this.w, this.h);
  }
}
Previous
Previous

Marketing and Branding

Next
Next

Fine Arts