Created
May 28, 2021 21:27
-
-
Save Gro-Tsen/9a8a410aff7198be1bd9c7ab6574af2a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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="▶️ 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