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});