Simulate String Vibration in Matter.js

This article explains how to model musical instrument string vibration in the 2D physics engine Matter.js by discretizing a continuous string into a series of linked micro-bodies. By connecting small rigid masses with elastic constraints between fixed boundaries, you can accurately approximate transverse wave propagation, fundamental frequencies, and harmonic damping directly in a browser environment.

Core Concept: Discretizing the String

A physical instrument string is a continuous medium governed by the 1D wave equation. To simulate this in a rigid-body physics engine like Matter.js, the string is discretized into a lumped-element model: a chain of \(N\) small, identical circular bodies (micro-bodies) interconnected by distance constraints that act as tension springs.

The physical fidelity of the simulation depends directly on:

Setting Up the Engine and Solver

Because musical strings vibrate at high frequencies and involve stiff constraints, standard physics engine settings will produce artificial sagging or solver instability. Increase the engine's iteration count to maintain constraint rigidity:

const engine = Matter.Engine.create({
  positionIterations: 10,
  velocityIterations: 10
});

Disable global gravity so the string maintains an equilibrium state determined solely by tension:

engine.gravity.y = 0;
engine.gravity.x = 0;

Constructing the Micro-Body Chain

Create two fixed boundary bodies representing the nut and the bridge of the instrument, then place \(N\) dynamic micro-bodies in a straight line between them.

const N = 30; // Number of micro-segments
const startX = 100;
const endX = 700;
const y = 300;
const segmentLength = (endX - startX) / (N + 1);
const radius = 2;

const bodies = [];

// Create particles
for (let i = 0; i <= N + 1; i++) {
  const x = startX + i * segmentLength;
  const isBoundary = (i === 0 || i === N + 1);

  const body = Matter.Bodies.circle(x, y, radius, {
    isStatic: isBoundary,
    mass: 0.1,
    frictionAir: 0.002, // Damping factor
    collisionFilter: { group: -1 } // Prevent self-collision
  });

  bodies.push(body);
}

Linking with Constraints

Connect each body to its neighbor using Matter.Constraint. To simulate tension, keep the resting length equal to or slightly less than the initial distance between adjacent bodies, and set the stiffness close to 1:

const constraints = [];

for (let i = 0; i < bodies.length - 1; i++) {
  const constraint = Matter.Constraint.create({
    bodyA: bodies[i],
    bodyB: bodies[i + 1],
    stiffness: 0.95,
    damping: 0.01,
    length: segmentLength * 0.98 // Slight pre-tension
  });

  constraints.push(constraint);
}

Matter.Composite.add(engine.world, [...bodies, ...constraints]);

Exciting the String (Plucking and Striking)

To generate vibrations, you must introduce initial energy into the system:

  1. Plucking (Displacement): Displace one or more interior nodes along the vertical axis (Y) before unfreezing them to let them oscillate from rest.
  2. Striking (Impulse): Apply an instantaneous impulse force to an interior body:
function strikeString(targetIndex, forceMagnitude) {
  const targetBody = bodies[targetIndex];
  Matter.Body.applyForce(targetBody, targetBody.position, {
    x: 0,
    y: forceMagnitude
  });
}

Managing Damping and Stability