Skip to content

Instantly share code, notes, and snippets.

@Gro-Tsen
Created May 28, 2021 21:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Gro-Tsen/9a8a410aff7198be1bd9c7ab6574af2a to your computer and use it in GitHub Desktop.
Save Gro-Tsen/9a8a410aff7198be1bd9c7ab6574af2a to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="utf-8" />
<title>Audio Double Pendulum</title>
</head>
<body>
<h1>Audio Double Pendulum</h1>
<script type="text/javascript">
// <![CDATA[
"use strict";
// Notational conventions are the same as in
// <URL: https://scienceworld.wolfram.com/physics/DoublePendulum.html >
var m1 = 1; // First mass
var m2 = 1; // Second mass
var l1 = 1; // First arm length (btw pivot and first mass)
var l2 = 1; // Second arm length (btw first and second masses)
var gacc = (220.*2*Math.PI)*(220.*2*Math.PI); // Acceleration of gravity
function double_pendulum_equation(pos, vel) {
// Return second derivative of (θ₁,θ₂) in function of their value
// and first derivative.
"use strict";
var θ1 = pos[0];
var θ2 = pos[1];
var θd1 = vel[0];
var θd2 = vel[1];
var sindiff = Math.sin(θ1 - θ2);
var denom = (m1 + m2*sindiff*sindiff);
var θdd1 = -(gacc*(2*m1+m2)*Math.sin(θ1) + gacc*m2*Math.sin(θ1-2*θ2) + 2*m2*(l2*θd2*θd2 + l1*θd1*θd1*Math.cos(θ1-θ2))*sindiff)/(2*l1*denom);
var θdd2 = (((m1+m2)*(l1*θd1*θd1 + gacc*Math.cos(θ1)) + l2*m2*θd2*θd2*Math.cos(θ1-θ2))*sindiff)/(l2*denom);
return [ θdd1, θdd2 ];
}
function total_energy(pos, vel) {
"use strict";
var θ1 = pos[0];
var θ2 = pos[1];
var θd1 = vel[0];
var θd2 = vel[1];
return (l1*l1*m1*θd1*θd1 + l1*l1*m2*θd1*θd1 + l2*l2*m2*θd2*θd2 +
2*l1*l2*m2*θd1*θd2*Math.cos(θ1-θ2)
- 2*gacc*l1*(m1+m2)*Math.cos(θ1) - 2*gacc*l2*m2*Math.cos(θ2))/2;
}
var time; // Time (in real-time seconds) of current state represented below
var points_pos = new Array(2); // θ₁ and θ₂ of pendulum's position (radians)
var points_vel = new Array(2); // Derivatives of θ₁ and θ₂ (rad/s)
function runge_kutta_evolve(step) {
// Fourth-order Runge-Kutta: update time, points_pos and
// points_vel by travelling forward step in time.
"use strict";
var points_pos_1 = points_pos;
var points_vel_1 = points_vel;
var points_acc_1 = double_pendulum_equation(points_pos_1, points_vel_1);
var points_pos_2 = new Array(2);
var points_vel_2 = new Array(2);
for ( var i=0 ; i<2 ; i++ ) {
points_pos_2[i] = points_pos[i] + points_vel_1[i]*step/2;
points_vel_2[i] = points_vel[i] + points_acc_1[i]*step/2;
}
var points_acc_2 = double_pendulum_equation(points_pos_2, points_vel_2);
var points_pos_3 = new Array(2);
var points_vel_3 = new Array(2);
for ( var i=0 ; i<2 ; i++ ) {
points_pos_3[i] = points_pos[i] + points_vel_2[i]*step/2;
points_vel_3[i] = points_vel[i] + points_acc_2[i]*step/2;
}
var points_acc_3 = double_pendulum_equation(points_pos_3, points_vel_3);
var points_pos_4 = new Array(2);
var points_vel_4 = new Array(2);
for ( var i=0 ; i<2 ; i++ ) {
points_pos_4[i] = points_pos[i] + points_vel_3[i]*step;
points_vel_4[i] = points_vel[i] + points_acc_3[i]*step;
}
var points_acc_4 = double_pendulum_equation(points_pos_4, points_vel_4);
var new_points_pos = new Array(2);
var new_points_vel = new Array(2);
for ( var i=0 ; i<2 ; i++ ) {
new_points_pos[i] = points_pos[i] + (points_vel_1[i]+points_vel_2[i]*2+points_vel_3[i]*2+points_vel_4[i])*step/6;
new_points_vel[i] = points_vel[i] + (points_acc_1[i]+points_acc_2[i]*2+points_acc_3[i]*2+points_acc_4[i])*step/6;
}
time += step;
for ( var i=0 ; i<2 ; i++ ) {
points_pos[i] = new_points_pos[i];
points_vel[i] = new_points_vel[i];
}
}
// Initial conditions are HERE:
var iθ1 = 0.5 * Math.PI;
var iθ2 = 0;
time = 0;
points_pos[0] = iθ1;
points_pos[1] = iθ2;
points_vel[0] = 0;
points_vel[1] = 0;
var initial_energy = total_energy(points_pos, points_vel);
var audioCtx = new window.AudioContext();
var sampleRate = audioCtx.sampleRate;
var basicStep = 1./sampleRate; // Audio sample time step
var bufferSampleCount = Math.floor(sampleRate * 0.5);
var stepDivision = 2; // Number of R-K steps per audio sample
var subStep = basicStep/stepDivision;
var audioTimeZero = undefined; // Audio context time for time=0
function computeBuffer() {
// Compute in one audio buffer's worth of data (bufferSampleCount
// samples).
"use strict";
var audioBuffer = audioCtx.createBuffer(2, bufferSampleCount, sampleRate);
var audioBufferData0 = audioBuffer.getChannelData(0);
var audioBufferData1 = audioBuffer.getChannelData(1);
for ( var i=0 ; i<bufferSampleCount ; i++ ) {
audioBufferData0[i] = Math.sin(points_pos[0]);
audioBufferData1[i] = Math.sin(points_pos[1]);
for ( var j=0 ; j<stepDivision ; j++ )
runge_kutta_evolve(subStep);
}
return audioBuffer;
}
var audioSource;
function enqueueBuffer() {
// Compute and enqueue one audio buffer's worth of data.
"use strict";
var time0 = time;
if ( audioTimeZero === undefined )
audioTimeZero = audioCtx.currentTime - time0;
audioSource = audioCtx.createBufferSource();
audioSource.buffer = computeBuffer();
audioSource.loop = false;
audioSource.connect(audioCtx.destination);
audioSource.start(audioTimeZero + time0);
}
var aheadMargin = 1; // How many seconds of audio to enqueue (at least)
function maybeEnqueueBuffer() {
// Compute and enqueue as manay audio buffers as required to have
// at least aheadMargin seconds of audio.
"use strict";
while ( audioTimeZero !== undefined
&& audioTimeZero + time < audioCtx.currentTime + aheadMargin ) {
// console.log("audioTimeZero+time="+audioTimeZero+"+"+time+"="+(audioTimeZero+time)+"; audioCtx.currentTime+aheadMargin="+audioCtx.currentTime+"+"+aheadMargin+"="+(audioCtx.currentTime+aheadMargin)+": enqueuing new buffer");
enqueueBuffer();
}
}
function pauseButton() {
"use strict";
if ( audioCtx.state === "running" ) {
document.getElementById("pause-button").value = "▶\ufe0f Resume";
audioCtx.suspend();
} else if ( audioCtx.state === "suspended" ) {
document.getElementById("pause-button").value = "⏸\ufe0f Pause";
audioCtx.resume();
}
}
audioCtx.suspend();
enqueueBuffer();
function scheduler() {
"use strict";
maybeEnqueueBuffer();
window.setTimeout(scheduler, 125); // DO NOT assume this is accurate
}
scheduler();
function write_string_in_id (id, str) {
"use strict";
// Write a string in a DOM node.
var node;
node = document.getElementById(id);
while ( node.firstChild )
node.removeChild(node.firstChild);
node.appendChild(document.createTextNode(str));
}
document.addEventListener("DOMContentLoaded", function(e) {
write_string_in_id("fill-me-m1", m1);
write_string_in_id("fill-me-m2", m2);
write_string_in_id("fill-me-l1", l1);
write_string_in_id("fill-me-l2", l2);
write_string_in_id("fill-me-iθ1", iθ1/Math.PI*180);
write_string_in_id("fill-me-iθ2", iθ2/Math.PI*180);
});
// ]]>
</script>
<p><input type="button" value="▶&#xfe0f; Play" onclick="pauseButton()" id="pause-button"
/></p>
<p>This web page simulates
a <a href="https://scienceworld.wolfram.com/physics/DoublePendulum.html">double
pendulum</a>, a physical system consisting of two physical masses with
values <var>m</var>₁=<span id="fill-me-m1">###</span>
and <var>m</var>₂=<span id="fill-me-m2">###</span> that are connected
with massless rigid rods: the point <var>P</var>₁ with
mass <var>m</var>₁ is connected to the fixed pivot point <var>O</var>
by a rod of length <var>ℓ</var>₁=<span id="fill-me-l1">###</span> and
the <var>P</var>₂ with mass <var>m</var>₂ is connected to the
point <var>P</var>₁ by a rod of
length <var>ℓ</var>₂=<span id="fill-me-l2">###</span>; the system is
otherwise free to move, in a vertical plane under the action of
gravity. The system starts (at rest) with the angle <var>θ</var>₁
of <var>O</var><var>P</var>₁ with the downward vertical axis
being <span id="fill-me-iθ1">###</span>° and with the
angle <var>θ</var>₂ of <var>P</var>₁<var>P</var>₂ with the downward
vertical axis being <span id="fill-me-iθ2">###</span>°, and evolves.
This evolution is chaotic, and is rendered as audio: the left audio
channel represents the horizontal displacement of <var>P</var>₁ with
respect to the vertical axis through <var>O</var> (i.e.,
sin(<var>θ</var>₁)), and the right audio channel represents the
horizontal displacement of <var>P</var>₂ with respect to the vertical
axis through <var>P</var>₁ (i.e., sin(<var>θ</var>₂)).</p>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment