Model Bell Chime Resonance Using Matter.js Springs
This article explains how to simulate the acoustic vibrations and exponential resonance decay of a bell chime by modeling it as a coupled mass-spring oscillator network in Matter.js. By discretizing a chime into a series of interconnected rigid bodies joined by tuned elastic constraints, you can reproduce high-frequency flexural standing waves and capture natural decay dynamics directly within a 2D physics engine.
The Physics of Coupled Oscillator Chimes
A tubular bell or chime vibrates through transverse flexural modes rather than purely rigid-body swing. To approximate continuous beam vibration in Matter.js:
- Discretization: The chime is represented as a 1D chain of small point-mass bodies.
- Coupling: Adjacent masses are linked using stiff
linear constraints (
Matter.Constraint) that act as restoring springs. - Cross-bracing (Shear Resistance): Secondary springs skipping one node (connecting mass \(i\) to \(i+2\)) simulate bending stiffness and beam elasticity.
- Decay (Damping): Internal friction is handled by constraint damping, while acoustic radiation and air resistance are handled by body-level air friction.
Step 1: Configuring Engine and World Parameters
Matter.js defaults to iterative relaxation designed for rigid bodies, which can prematurely bleed energy from small, high-frequency oscillations. To achieve stable, sustained resonance, increase solver iterations and configure an appropriate sub-stepping rate.
const { Engine, Render, Runner, Bodies, Composite, Constraint, Body, Vector } = Matter;
const engine = Engine.create({
positionIterations: 12,
velocityIterations: 12,
gravity: { x: 0, y: 0.5, scale: 0.001 } // Low gravity to emphasize vibrational dynamics
});Step 2: Building the Discretized Chime Structure
Create an array of small, uniform bodies aligned vertically. Anchor the top body to a fixed point to represent the chime's mounting string, leaving the lower nodes free to vibrate.
const nodes = [];
const nodeCount = 12;
const nodeRadius = 6;
const spacing = 18;
const startX = 400;
const startY = 100;
// Create mass nodes
for (let i = 0; i < nodeCount; i++) {
const node = Bodies.circle(startX, startY + i * spacing, nodeRadius, {
mass: 1.0,
frictionAir: 0.0005, // Controls global resonance decay rate
restitution: 0.95
});
nodes.push(node);
}
// Fixed suspension anchor for the top node
const anchor = Constraint.create({
pointA: { x: startX, y: startY - spacing },
bodyB: nodes[0],
pointB: { x: 0, y: 0 },
stiffness: 0.9,
damping: 0.01
});Step 3: Coupling Nodes with Tuned Springs
To model longitudinal tension and transverse flexural resistance, apply two sets of constraints:
- Primary Links (\(i \leftrightarrow i+1\)): High stiffness values to keep the chime cohesive.
- Flexural Links (\(i \leftrightarrow i+2\)): Medium stiffness values to provide the restoring force when the beam bends.
const springs = [];
for (let i = 0; i < nodeCount - 1; i++) {
// Nearest-neighbor constraint (axial stiffness)
springs.push(Constraint.create({
bodyA: nodes[i],
bodyB: nodes[i + 1],
stiffness: 0.85,
damping: 0.002 // Internal material damping
}));
// Next-nearest-neighbor constraint (bending stiffness)
if (i < nodeCount - 2) {
springs.push(Constraint.create({
bodyA: nodes[i],
bodyB: nodes[i + 2],
stiffness: 0.45,
damping: 0.005
}));
}
}
Composite.add(engine.world, [...nodes, anchor, ...springs]);Step 4: Exciting the Chime (Impact)
A physical strike imparts a localized transverse impulse to the chime. Target a node near the bottom or middle to generate a combination of fundamental and harmonic frequencies.
function strikeChime(intensity = 0.05) {
// Strike the third node from the bottom
const targetNode = nodes[nodes.length - 3];
Body.applyForce(targetNode, targetNode.position, { x: intensity, y: 0 });
}Step 5: Controlling Resonance Decay
Resonance decay in this coupled model follows an exponential decay envelope determined by two primary variables:
body.frictionAir: Represents ambient acoustic loss. Set between0.0001and0.001for a long metallic chime sustain. Increasing this parameter shortens the overall decay envelope without changing the frequency.constraint.damping: Represents viscoelastic dissipation within the chime material. Higher damping suppresses high-frequency harmonics faster than the fundamental mode, mimicking the spectral warmth that occurs as a real chime settles.
Step 6: Extracting the Vibrational Signal
To measure the vibration or synthesize audio from the physics model, track the horizontal displacement or velocity of the bottom-most antinode over successive engine ticks:
Matter.Events.on(engine, 'afterUpdate', () => {
const antinode = nodes[nodes.length - 1];
const displacementX = antinode.position.x - startX;
const velocityX = antinode.velocity.x;
// displacementX contains the instantaneous resonant wave amplitude
});By adjusting the stiffness ratio between axial and flexural constraints alongside node mass, you can precisely shape the natural frequencies and timbre of the chime.