← Blog
PhysicsSimulationCanvas APIMath

Visualizing Wave Equations in the Browser — Interference, Diffraction, and Resonance

Updated May 27, 20269 min read

Go deeper on this topic

Creative Coding

Mathematical simulations, nature visualization, and generative art in the browser

Article 5 of 12 in this series.

View topic page

Use the topic hub as a map, then continue to the next article in the series. New here? Start at the home hub →

Discretizing the Wave Equation

The continuous wave equation d^2u/dt^2 = c^2 nabla^2 u is discretized using the finite difference method. Time and space are divided into a grid, and derivatives are approximated with differences.

1D Wave Equation

const N = 500
const c = 2.0 // wave propagation speed
const dt = 0.01
const dx = 1.0

const u = new Float32Array(N)     // current
const uPrev = new Float32Array(N) // one step back
const uNext = new Float32Array(N) // next step

function step1D() {
  const r = (c * dt / dx) ** 2

  for (let i = 1; i < N - 1; i++) {
    uNext[i] = 2 * u[i] - uPrev[i] + r * (u[i + 1] - 2 * u[i] + u[i - 1])
  }

  // Boundary conditions (fixed ends)
  uNext[0] = 0
  uNext[N - 1] = 0

  // Buffer swap
  uPrev.set(u)
  u.set(uNext)
}

r = (c*dt/dx)^2 is the CFL Courant number. The solution becomes numerically unstable (diverges) if r > 1.

2D Wave Equation

Used in WaveEquation and ChladniPlate:

const W = 200
const H = 200

const u2d = new Float32Array(W * H)
const u2dPrev = new Float32Array(W * H)
const u2dNext = new Float32Array(W * H)
const damping = 0.999

function step2D() {
  const r = (c * dt / dx) ** 2

  for (let y = 1; y < H - 1; y++) {
    for (let x = 1; x < W - 1; x++) {
      const idx = y * W + x
      const laplacian =
        u2d[idx + 1] + u2d[idx - 1] +
        u2d[idx + W] + u2d[idx - W] -
        4 * u2d[idx]

      u2dNext[idx] = (2 * u2d[idx] - u2dPrev[idx] + r * laplacian) * damping
    }
  }

  u2dPrev.set(u2d)
  u2d.set(u2dNext)
}

damping = 0.999 provides energy dissipation. Without it, waves would reflect endlessly and become chaotic.

WaveInterference -- Interference Patterns

Two wave sources emit concentric waves, and the interference pattern is visualized:

function addSource(u: Float32Array, x: number, y: number, amplitude: number, t: number) {
  const freq = 5.0
  const idx = y * W + x
  u[idx] += amplitude * Math.sin(2 * Math.PI * freq * t * dt)
}

Placing two sources close together reveals constructive and destructive interference stripes (Young's interference fringes). The fringe spacing is determined by the ratio of the distance between sources to the wavelength.

ChladniPlate -- Resonance Patterns

Chladni figures visualize the resonance modes of thin plates. Driving a 2D wave equation at specific frequencies reveals lines of zero amplitude (nodal lines):

function renderChladni(ctx: CanvasRenderingContext2D, u: Float32Array) {
  const imageData = ctx.createImageData(W, H)

  for (let i = 0; i < W * H; i++) {
    const amplitude = Math.abs(u[i])
    // Nodal lines (near-zero amplitude) are white, antinodes are dark
    const brightness = amplitude < 0.01 ? 255 : Math.max(0, 255 - amplitude * 500)
    const px = i * 4
    imageData.data[px] = brightness
    imageData.data[px + 1] = brightness
    imageData.data[px + 2] = brightness
    imageData.data[px + 3] = 255
  }

  ctx.putImageData(imageData, 0, 0)
}

In physical Chladni experiments, sand is sprinkled on a metal plate and the plate is bowed. The sand collects along nodal lines, revealing beautiful geometric patterns. This is the browser reproduction of that phenomenon.

Relationship with SoundWaves

The SoundWaves component uses Web Audio API's AnalyserNode to capture and render real-time waveform data. Unlike wave equation simulation, this is visualization of actual audio signals:

function drawWaveform(ctx: CanvasRenderingContext2D, dataArray: Uint8Array) {
  ctx.beginPath()
  const sliceWidth = ctx.canvas.width / dataArray.length

  for (let i = 0; i < dataArray.length; i++) {
    const v = dataArray[i] / 128.0
    const y = (v * ctx.canvas.height) / 2
    if (i === 0) ctx.moveTo(0, y)
    else ctx.lineTo(i * sliceWidth, y)
  }

  ctx.stroke()
}

Even under the shared theme of "waves," physics simulation (WaveEquation) and signal processing (SoundWaves) have entirely different implementations.

Performance Optimization

A 200x200 grid 2D simulation updates 40,000 cells per frame. Why Float32Array is used:

  1. Memory contiguity: TypedArray has the same memory layout as C arrays, yielding higher cache hit rates
  2. Fixed numeric type: The JIT compiler can skip type inference
  3. No GC: Fixed size means no garbage collection runs

Loop processing can be 2-3x faster compared to regular number[] in some cases.

Summary: Technologies and Tools Used for Wave Equations

For understanding the mathematical background of wave phenomena, the books in the toolshelf are helpful. Stability conditions for difference methods and the CFL condition are essential knowledge for simulations in general.