| src | |
| .gitignore | |
| index.html | |
| package-lock.json | |
| package.json | |
| README.md | |
| tsconfig.json |
double-pendulum#
Interactive double pendulum, step 1 of "Listening for chaos". Work in progress.
Current state: the pendulum hangs at rest until Strike, which starts it from hanging rest with the energy on the slider (default 50 J); Pause freezes the animation. The Ghost toggle adds a second pendulum (translucent orange) started from the same state with the outer arm's angle offset by 10^-12 to 10^-1 rad (default 10^-10), restarted on Strike or Reset, with a readout of the angular separation and the time since the ghost started: it grows exponentially when the motion is chaotic and stays tiny when it is regular. Charts "Separation" (ghost separation, log scale, last 30 s) and "Lyapunov" (largest Lyapunov exponent over a 10 s window and since the strike, last 30 s). Parameters (masses, lengths, g, decay time, presets) fold out underneath. Toggle chips choose the plots: a spectrogram of the selected signal (frequency axis auto-scaled, dashed lines at the normal modes) and live traces of energies, energy balance error, angles, momenta and bob positions. The sound card at the bottom has a Sound on/off toggle (with sound on, every Strike plays a note: a separate, sped-up copy of the motion from the same strike; a damped note ends once it has decayed by 60 dB), "Sound of" (the signal, also used by the animation spectrogram), volume, pitch (where the small-amplitude lower mode lands, 30 to 1000 Hz, default 100 Hz), and a spectrogram of the sound.
npm install
npm run dev
Model#
- Point masses on massless rods, absolute angles from the downward vertical, canonical momenta.
- SI units. m1, m2, l1, l2, g are set in the page (Apply restarts at the same energy relative to the both-inverted energy). Default 1 kg, 1 kg, 1 m, 1 m, 9.81 m/s^2, tau 540 s (normal modes 0.382 and 0.921 Hz, ratio 1 + sqrt(2)). Presets keep m1 = m2 = 1 kg, l1 = 1 m and vary l2; with equal masses the mode ratio is at least 1 + sqrt(2) (at l2 = l1) and is the same for l2/l1 and l1/l2. Presets: l2 = 1 (2.414), 1.5667 (5:2), 2 (golden ratio squared, 2.618), 2.1637 (8:3), 3.2476 (3:1), 6.886 (4:1). At a fixed lower-mode pitch only m2/m1, l2/l1 and the relative energy affect the sound.
- Energy is zero at hanging rest.
- Damping: linear drag on each bob, force -gamma m_i v_i (proportional to its mass; equal for equal bobs), so the generalized forces are -gamma p and every mode's amplitude decays with time constant tau = 2/gamma. Default tau = 540 s: about 10% amplitude per minute in the animation, about 2 s in the sound at x262. The state carries the dissipated energy W, so E + W is checked for conservation. With damping the energy slider sets the strike energy; with tau = 0 it sets the energy live.
- Live energy change: both momenta are scaled by one factor at fixed angles (a push along the current motion). A target below the current potential energy can't be reached at once; until it can, kinetic energy is removed at a rate of 6 per second.
- Lyapunov exponent: two-trajectory (Benettin) method in
src/lyapunov.ts. A hidden shadow pendulum is kept 1e-8 away (angles in rad, momenta in units of sqrt((m1 + m2) l1^2 E_top)), moved back after every step, and the log growth averaged. The shadow keeps its own energy (forcing it onto the pendulum's energy by rescaling momenta inflates the separation near turning points). Checked (no damping, 300 s): 2 J and 10 J fall toward 0 (0.01/s), 30 J ~1.0/s, 50 J ~1.4/s; unchanged for shadow distances 1e-6 and 1e-10. With damping (tau 540 s) a 50 J strike's 10 s value falls from ~1.4 to ~0.1/s by 300 s. - Integrator: 2-stage Gauss-Legendre (order 4, symplectic), fixed step h chosen per parameter set by a short calibration run so the relative energy error stays below 1e-6 up to the top of the energy slider (twice the both-inverted energy); 3.13 ms for the defaults, fixed-point stage solve. Angles are wrapped to (-pi, pi] after each step. The physics costs well under 1 ms of CPU per second; drawing dominates, so traces draw at most a min/max pair per pixel column and the spectrogram scrolls in new columns instead of redrawing.
Audio: a Web Worker (src/audio-worker.ts) integrates the audio copy with substeps of at most 3.2 ms of simulated time per sample, high-passes at 20 Hz, and streams 1024-sample chunks through a MessageChannel to an AudioWorklet (src/audio-worklet.ts) that plays them from a ring buffer, keeping about 0.25 s queued. The page shows how many times faster than real time the worker computes, and any dropouts. The worker also sends each chunk to the page, which feeds it to the audio spectrogram as the audio clock reaches it (2048-sample window, new column every 512 samples).
Code: src/pendulum.ts (physics), src/sim.ts (running simulation with energy target), src/audio.ts (audio setup), src/signals.ts (audible quantities), src/lyapunov.ts (Lyapunov exponent), src/traces.ts (strip charts), src/spectrogram.ts (spectrogram), src/main.ts (page).