Bunimovich Stadium

A funny synthesizer using a simulation of the Bunimovich Stadium, a famous chaotic dynamical system. The slider controls the "amount of chaos" in the system.

Log in to post a comment.

// === BUNIMOVICH STADIUM ===
// by Alexis THIBAULT

// The Bunimovich Stadium is a famous example of Dynamical Billiards, a mathematical problem where balls
// bounce indefinitely against the walls of a given shape.
// Here, the shape in question is a rectangle capped by two semicircles: it is one of the earliest examples
// of a convex shape that exhibits chaotic behavior.
// 
// A visualization of the bunimovich Stadium can be found here:
// https://cscheid.net/projects/bunimovich_stadium/
//
// In this ditty, we listen to the trajectory of a few balls as they bounce around.
// The speed of each ball is such that it usually bounces at a rate corresponding to its frequency.
// The "chaos" slider actually controls the width of the rectangle.

input.chaos = 0.3;

function sqNorm(x,y) {
    return x*x + y*y;
}

function reflect(vx, vy, nx, ny) {
    const dot = vx*nx + vy*ny;
    return [vx - 2*dot*nx, vy - 2*dot*ny];
}

const bunimovichStadium = synth.def(class {
    constructor(options) {
        this.x = 0;
        this.y = 0;
        let theta = 2 * Math.PI * Math.random();
        const freq = midi_to_hz(options.note);
        const spd = 4 * ditty.dt * freq;
        this.dx = spd * Math.cos(theta);
        this.dy = spd * Math.sin(theta);
    }
    process(note, tick, env, options) {
        const w = options.chaosAmount;
        this.x += this.dx;
        this.y += this.dy;
        if(this.x > w) {
            // Test whether we're in the circle of center (w,0), reflect if necessary
            let r2 = sqNorm(this.x-w, this.y);
            if(r2 > 1) {
                let r = Math.sqrt(r2);
                let xp = w + (this.x - w) / r;
                let yp = this.y / r;
                this.x = 2*xp - this.x;
                this.y = 2*yp - this.y;
                [this.dx, this.dy] = reflect(this.dx, this.dy, xp-w, yp);
            }
        } else if(this.x < -w) {
            // Test whether we're in the circle of center (-w,0), reflect if necessary
            let r2 = sqNorm(this.x+w, this.y);
            if(r2 > 1) {
                let r = Math.sqrt(r2);
                let xp = -w + (this.x + w) / r;
                let yp = this.y / r;
                this.x = 2*xp - this.x;
                this.y = 2*yp - this.y;
                [this.dx, this.dy] = reflect(this.dx, this.dy, xp+w, yp);
            }
        } else if (this.y > 1) {
            this.y = 1;
            this.dy *= -1;
        } else if (this.y < -1) {
            this.y = -1;
            this.dy *= -1;
        }
        const side = 0.2 * this.x / (1 + w);
        return [this.y + side, this.y - side];
    }
}, {env:one, amp:0.2, chaosAmount: () => 0.001 * 10000**input.chaos});

bunimovichStadium.play(c2, {pan:0});
bunimovichStadium.play(c3, {pan:-0.2});
bunimovichStadium.play(e4, {pan:0.2});
bunimovichStadium.play(g3, {pan:0.4});
bunimovichStadium.play(c4, {pan:-0.4});