Beam defaults: 16 masses, frequency-dependent loss on (τ top 200 s), mallet strike (M 1 kg, K 1000)
3 changed files+12−8
src/beam/README.mdmodified+2−1View file
@@ -2,7 +2,7 @@
22
33 Struck beam with bending stiffness, step 3 of "Listening for chaos" (after double-pendulum and fput-chain). Work in progress: step 1 (physics, animation, sped-up note with spectrogram), step 2 (Lyapunov exponent), step 3 (mode bars and mode histories), step 4 (ghost), step 5 (λ vs E), curvature (rise h of a curved rest shape), frequency-dependent loss (τ top), Masses as resolution (refine).
44
5-Current state: N masses (2 to 64, default 8) on vertical rails, moving sideways only, with tension k, bending κ and a stretching nonlinearity k3 (below). Ends (a select next to Masses; changing it returns the beam to rest): clamped–clamped (default), pinned–pinned, or cantilever (clamped at the left, free at the right). Strike gives energy E (log slider, 10 µJ to 100 J, default 2.51 J) as velocity, as a hammer blow at x (m from the left support, default 1.0 m, where mass 2 sits at every N; `strikeAt`) or in the shape of one normal mode. The blow is a triangular velocity bump of half-width one spacing of the 8-mass beam (0.5 m; at least one spacing), so at N = 8 on a mass only that mass moves (bit-identical to the old mass strike) and at larger N the same blow spreads over the neighbours. The card draws the beam from the side, at rest shape plus displacement, scaled to fit (scale given; the rest shape is a faint line), with rails as faint lines, a hatched wall for a clamp and a triangle for a pin, plus the time, energy and relative energy error. The Ghost chip (as in fput-chain; controls in `../common/ghost.ts`) adds a second beam started from the same state with every mass moved by 10^k / sqrt(N) m (total offset 10^k m, k = -12 to -1, default -10), run in lockstep without renormalization and drawn in translucent orange underneath; restarted on Strike, on "Restart ghost", when switched on or when the offset changes; a readout gives the separation (root sum of squared displacement differences, m) and the time since the ghost started. Default strike: the separation stays near 1e-10 m for ~10 s, then grows to 1.7e-8 m by 39.5 s, e^5.1, matching λ since the strike (0.13 /s × 39.5 s). A "Real time" card on the right has chips: Mode bars (on; the current harmonic energy of each mode relative to the strike, log scale over 12 decades, with the number of modes sharing the energy, 1 / sum (E_n / sum E)^2, and the mode frequency range; `../common/modebars.ts`), Mode history (off; each mode's share of the harmonic energy over the last 300 s as a heat map, mode 1 at the bottom; `LiveModeHistory` in `../common/modehistory.ts`), and charts (`../common/traces.ts`): Lyapunov (on; the largest Lyapunov exponent over the last 10 s and since the strike, last 60 s shown, the first 5 s after a strike left out of the y range) Mode energies (off; log, last 60 s, values below 1e-12 of the strike left out), Energy error (off) and Separation (log, last 60 s; offered only with the ghost on). The exponent is the two-trajectory method of `../common/lyapunov.ts` with the beam's shadow in `lyapunov.ts`: a hidden second beam 1e-8 away, displacements in units of A / sqrt(N) and momenta in units of m omega_1 A / sqrt(N), A the mode-1 amplitude at the nonlinear scale. Speed 0.1x to 20x real time; Pause or space. Parameters (m, k, κ, k3, rise h, a, τ, τ top, stretching form) fold out with derived values (mode frequencies, ratios to mode 1, the nonlinear scale, integrator step); Apply returns the beam to rest.
5+Current state: N masses (2 to 64, default 16; 8 until 2026-10-09) on vertical rails, moving sideways only, with tension k, bending κ and a stretching nonlinearity k3 (below). Ends (a select next to Masses; changing it returns the beam to rest): clamped–clamped (default), pinned–pinned, or cantilever (clamped at the left, free at the right). Strike gives energy E (log slider, 10 µJ to 100 J, default 2.51 J) as velocity, as a hammer blow at x (m from the left support, default 1.0 m, where mass 2 sits at every N; `strikeAt`) or in the shape of one normal mode. The blow is a triangular velocity bump of half-width one spacing of the 8-mass beam (0.5 m; at least one spacing), so at N = 8 on a mass only that mass moves (bit-identical to the old mass strike) and at larger N the same blow spreads over the neighbours. The card draws the beam from the side, at rest shape plus displacement, scaled to fit (scale given; the rest shape is a faint line), with rails as faint lines, a hatched wall for a clamp and a triangle for a pin, plus the time, energy and relative energy error. The Ghost chip (as in fput-chain; controls in `../common/ghost.ts`) adds a second beam started from the same state with every mass moved by 10^k / sqrt(N) m (total offset 10^k m, k = -12 to -1, default -10), run in lockstep without renormalization and drawn in translucent orange underneath; restarted on Strike, on "Restart ghost", when switched on or when the offset changes; a readout gives the separation (root sum of squared displacement differences, m) and the time since the ghost started. Default strike: the separation stays near 1e-10 m for ~10 s, then grows to 1.7e-8 m by 39.5 s, e^5.1, matching λ since the strike (0.13 /s × 39.5 s). A "Real time" card on the right has chips: Mode bars (on; the current harmonic energy of each mode relative to the strike, log scale over 12 decades, with the number of modes sharing the energy, 1 / sum (E_n / sum E)^2, and the mode frequency range; `../common/modebars.ts`), Mode history (off; each mode's share of the harmonic energy over the last 300 s as a heat map, mode 1 at the bottom; `LiveModeHistory` in `../common/modehistory.ts`), and charts (`../common/traces.ts`): Lyapunov (on; the largest Lyapunov exponent over the last 10 s and since the strike, last 60 s shown, the first 5 s after a strike left out of the y range) Mode energies (off; log, last 60 s, values below 1e-12 of the strike left out), Energy error (off) and Separation (log, last 60 s; offered only with the ghost on). The exponent is the two-trajectory method of `../common/lyapunov.ts` with the beam's shadow in `lyapunov.ts`: a hidden second beam 1e-8 away, displacements in units of A / sqrt(N) and momenta in units of m omega_1 A / sqrt(N), A the mode-1 amplitude at the nonlinear scale. Speed 0.1x to 20x real time; Pause or space. Parameters (m, k, κ, k3, rise h, a, τ, τ top, stretching form) fold out with derived values (mode frequencies, ratios to mode 1, the nonlinear scale, integrator step); Apply returns the beam to rest.
66
77 "Sped up · listen" (as in fput-chain): each Strike renders the same strike in a worker (`render-worker.ts`), sped up so the lowest mode sounds at the Pitch (5 to 1000 Hz, default 100), and plays it; the page starts muted. A damped note is the whole decay to -60 dB. Notes are anti-alias filtered (`../common/antialias.ts`, in the shared render loop): the signal is read 4 times per audio sample, low-passed by a 143-tap Kaiser FIR (passband to 0.42 fs, 20.2 kHz at 48 kHz, ripple 0.0003 dB; at least 87 dB down from 0.58 fs, so what folds lands above the passband) and decimated, centred so samples stay aligned with the diagnostics. A note shows when the top mode is above the passband (those modes are filtered out). Checked (Node, one trajectory recorded at 32x or 64x, against an FFT brick-wall reference at 20 kHz, error below 18 kHz): default N = 8 -153 dB before, -107 dB now (filter and float32 floor); clamped N = 32, 25 J -46 -> -105 dB; cantilever N = 8, 2.51 J -95 -> -111 dB; cantilever N = 16, 2.51 J -20 -> -104 dB. Cost: the 4 points per sample force at least 4 steps per sample, so the default N = 8 note now takes ~0.9 s to compute (0.3 s before; 1.49 M steps instead of 0.57 M); N = 32 9.9 s (7.9 s). Signals (`signals.ts`): velocity (default), displacement or acceleration at x (m from the left support, default 1.0 m; linear between the two nearest masses, a support counting as 0; `signalAt`; exactly mass 2 at N = 8), or the sum of accelerations (the net force of the supports). Replay, Save WAV (named e.g. beam-clamped-local-N8-mass2-2.51J-v2-100Hz.wav), spectrogram over the whole note with dashed lines at the mode pitches. Chips: Spectrogram (on; frequency axis logarithmic by default, from a round number at or below half the lowest mode pitch, with a "log f" chip to switch to linear from 0; each pixel row shows the loudest FFT bin in its band; mode lines each drawn only while at least 6 px from their neighbours: at the default note the six lowest are drawn, modes 7 and 8 are 2 px apart; on the old linear axis to 10 kHz the modes filled the bottom eighth), Modes (off; the note's mode-energy shares over the whole note, one column per 256 samples, from the worker; `ModeHistory` in `../common/modehistory.ts`), Lyapunov (on; the note's exponent over the last 10 s of physical time, from a shadow run in the worker; the first 5 s of physical time left out of the y range; the shadow runs only while this chart or λ vs E is shown, as it doubles the cost) Energy (log) and λ vs E (off; `../common/scatter.ts` as in fput-chain: one point per 256 samples, accumulated over notes, log energy axis, colored by time into the note, earlier notes faded, hover for values, Clear; not cleared when parameters change). All but λ vs E span the whole note and share one cursor (playhead while playing; hover, click to pin). The render loop is shared with fput-chain (`../common/note-render.ts`). The status line gives the render time (default note 1.0 to 1.4 s with the shadow, 0.6 s without).
88
@@ -10,6 +10,7 @@ Live site: https://chaos-apps--listening-for-chaos.vault1-sites.magland.org/beam
1010
1111 ## Model
1212
13+- Defaults since 2026-10-09 (chosen for a pleasant sound, see the mallet notes): 16 masses, τ top 200 s, strike "mallet at" 1.0 m (M 1 kg, K 1000), 2.51 J: contact 152 ms, 93 % into the beam, 5.3 s note computed in 1.8 s in the pane; spectral centroid 1050 / 690 / 480 / 250 Hz at 0.05 / 0.3 / 1 / 2 s of sound (old default N 8 kick without loss: 935 / 934 / 725 / 600 Hz; N 16 kick without loss 1.3 to 1.8 kHz). Earlier numbers in this README are for the old defaults unless stated.
1314 - SI units. Defaults m = 1 kg, k = 0, κ = 20 N/m, k3 = 4000 N/m^3, a = 0.5 m (drawing only), τ = 540 s, local stretching, clamped ends.
1415 - Resolution: the Parameters values are for the beam with 8 masses (REF_N); Masses (2 to 64) refines the same beam (`refine` in `beam.ts`): same length, rise, drag, strike energies; with spacing ratio r = span(8) / span(N) (span N + 1 spacings between two supports, N for a cantilever) m x r, k / r, κ / r^3, k3 / r^3, a x r, and the loss dashpots / r (τ top is converted so a wave of given length decays alike at any N). N = 8 returns the parameters unchanged (checked, all ends). The derived values show the scaled constants when N is not 8. Before this (to 2026-10-09) more masses made the beam longer at fixed spacing: mode 1 fell as ~1/N^2 (0.211 Hz at N = 8, 0.015 Hz at 32), so at fixed τ the note shrank (7.9 s at N = 8, 0.6 s at 32, 100 Hz pitch). Now (clamped, local, τ 540, τ top 50): f1 0.207 / 0.211 / 0.206 / 0.202 / 0.200 Hz at N = 4 / 8 / 16 / 32 / 64; f2/f1 2.24 / 2.59 / 2.71 / 2.75 / 2.754 and f3/f1 3.37 / 4.68 / 5.20 / 5.35 / 5.39 (beam theory 2.757, 5.404); pinned 3.998, 8.986 at 64 (4, 9); cantilever 6.265, 17.53 (6.27, 17.55); mode-1 decay ~375 s, mode 3 ~122 s; nonlinear scale 176 / 167 / 161 / 158 mJ at 8 / 16 / 32 / 64; default note 7.9 / 7.7 / 7.5 s. Cost: the step falls ~1/N^2 (0.58 ms at 8, 0.087 ms at 32 for 2.51 J), so the default note (with the Lyapunov shadow) took ~1 s to compute at N = 8, 8.2 s at 16 and 55 s at 32 in the pane at step 0.03; at 0.2, 0.3 s, 1.5 s and 7.9 s. Strike and listening are at positions (m), so they mean the same at any N. Default blow at 1.0 m, 2.51 J, clamped (mode-energy shares just after the strike, modes 1 / 2 / 3, and above mode 8; λ over 0 to 300 s with τ 540 s): N = 8 5.1 / 18.8 / 26.2 %, 0 %, λ 0.13 to 0.15 /s; N = 16 8.4 / 26.5 / 30.8 %, 2.7 %, 0.12 to 0.13; N = 32 8.4 / 26.2 / 30.6 %, 2.9 %, 0.068; N = 64 8.7 / 26.8 / 30.3 %, 2.8 %, 0.071. N = 8 differs because the bump covers a single mass there. The old blow on mass 2 (to 2026-10-09) moved up the spectrum with N: above mode 8 38 % at N = 16, 89 % at 32, 99 % at 64; λ 0.09, 0.024, 0.011.
1516 - Mallet (strike "mallet at"; `../common/mallet.ts`, beam only): mass M (default 1 kg) arriving with energy E presses into the beam through a Hertz contact, force K δ^1.5 (K default 1000 N/m^1.5), δ the mallet face's depth past the weighted mean displacement under the hammer bump (weights the bump normalized to sum 1; force distributed by the same weights). The contact phase is integrated at a fine step (bounded by the rigid-wall contact time / 400 and the struck masses' contact frequency) until lift-off; drag and loss are left out during it; the note and animation start at lift-off; the readout gives contact time, energy share delivered, peak force. Scan (2.51 J at 1.0 m, M = 1 kg): K 1e2 / 1e3 / 1e4 / 1e5: contact 470 / 150 / 55 / 22 ms (N = 8 and 32 alike), energy in 78-83 / 92-100 / 95-100 / 96-100 %; at N = 32 brightness (energy-weighted mode frequency / f1) just after lift-off 5.4 / 6.7 / 8.5 / 9.3 against 9.7 for the kick (the kick is the hard-mallet limit). Light mallets bounce off (M = 0.25 kg: 40 to 64 % in). In the sound at 2.51 J the difference is small: the chaotic phase redistributes the energy over the modes within a fraction of a second (spectral centroid at 1 s of sound, N = 32: kick 4.3 kHz, mallets 4.0 to 4.8 kHz; with τ top 200 s: kick 363 Hz, mallets 300 to 400 Hz).
src/beam/index.htmlmodified+3−3View file
@@ -16,8 +16,8 @@
1616 <button id="strike" class="primary">Strike</button>
1717 <select id="strike-kind">
1818 <option value="mode">mode</option>
19- <option value="at" selected>kick at</option>
20- <option value="mallet">mallet at</option>
19+ <option value="at">kick at</option>
20+ <option value="mallet" selected>mallet at</option>
2121 </select>
2222 <input id="strike-index" type="number" min="1" value="2" step="1" hidden />
2323 <span id="strike-at"><input id="strike-x" type="number" min="0" value="1" step="0.05" /> <span class="unit">m</span></span>
@@ -32,7 +32,7 @@
3232 <div class="row"><span id="strike-info" class="muted"></span></div>
3333 <div class="row">
3434 <span class="muted">Masses</span>
35- <input id="n" type="number" min="2" max="64" step="1" value="8" />
35+ <input id="n" type="number" min="2" max="64" step="1" value="16" />
3636 <span class="muted" style="margin-left: 12px">Ends</span>
3737 <select id="ends">
3838 <option value="clamped" selected title="clamped at both ends">clamped</option>
src/beam/main.tsmodified+7−4View file
@@ -42,11 +42,11 @@ const pauseButton = document.getElementById("pause") as HTMLButtonElement;
4242
4343 // Parameters. No tension by default: a pure beam (k = 0, bending kappa = 20),
4444 // clamped at both ends, with the FPUT chain's stretching coefficient.
45-// tau: amplitude decay time from drag; 540 s makes the default strike ring
46-// down in about 8 s of sound at the default pitch.
45+// tau: amplitude decay time from drag (540 s); tauTop: the frequency-dependent
46+// loss (200 s); together the default strike rings down in ~5 s of sound.
4747 // base: the page's parameters, for the beam at REF_N masses; P: the same beam
4848 // refined to the chosen number of masses (see refine).
49-let base: Params = { N: REF_N, m: 1, k: 0, kappa: 20, k3: 4000, stretch: "local", ends: "clamped", rise: 0, a: 0.5, tau: 540, tauTop: 0 };
49+let base: Params = { N: REF_N, m: 1, k: 0, kappa: 20, k3: 4000, stretch: "local", ends: "clamped", rise: 0, a: 0.5, tau: 540, tauTop: 200 };
5050 let P: Params = base;
5151 // The mallet (for "mallet at" strikes): a physical object, not refined with N.
5252 let mallet: MalletParams = { M: 1, K: 1000 };
@@ -514,7 +514,10 @@ function setParams(next: Params, N: number) {
514514 showPitch();
515515 showParams();
516516 }
517-setParams(base, REF_N);
517+// Defaults (2026-10-09, chosen for a pleasant sound): 16 masses (the strike's
518+// mode spectrum has converged there, and notes render in ~1 to 2 s), the
519+// loss on (τ top 200 s), a mallet strike (M 1 kg, K 1000: 150 ms contact).
520+setParams(base, 16);
518521
519522 nInput.addEventListener("change", () => {
520523 const N = Math.min(64, Math.max(2, Math.round(Number(nInput.value) || P.N)));