// The bead system as the solver sees it. Indices are 0-based here; the // script uses 1-based indices and parse.ts converts them. export type Method = "verlet" | "yoshida4"; export interface Hit { t: number; // start time (s) beads: Int32Array; // Impulse: velocity added to each bead at time t (m/s). Used when duration == 0. velocity: [number, number, number]; // Force pulse: raised-cosine force on each bead, peak `force` (N), lasting `duration` (s). force: [number, number, number]; duration: number; } export interface BeadSystem { n: number; pos0: Float64Array; // 3n, interleaved x,y,z (m) mass: Float64Array; // n (kg) pinned: Uint8Array; // n, 1 = held fixed // Springs: f = k e + k3 e^3, e = L - L0 si: Int32Array; sj: Int32Array; sk: Float64Array; sL0: Float64Array; sk3: Float64Array; // Dashpots on relative velocity: F_i = c (v_j - v_i) di: Int32Array; dj: Int32Array; dc: Float64Array; friction: Float64Array; // n, F_i = -c0_i v_i (N s/m) load: Float64Array | null; // 3n, constant force on each bead (N), e.g. air pressure or gravity relax: boolean; // move the beads to static equilibrium (springs + load) before simulating hits: Hit[]; listenBeads: Int32Array; listenDir: [number, number, number]; // unit vector duration: number; // s sampleRate: number; // Hz, audio output dt: number; // s; 0 = automatic safety: number; // fraction of the stability limit used when dt is automatic method: Method; frameRate: number; // Hz, snapshots for animation and mode energies modes: boolean; // compute linear transverse modes and track their energies } export interface SimProgress { t: number; duration: number; } export interface Modes { freqs: Float64Array; // Hz, ascending // psi: orthonormal eigenvectors of M^{-1/2} K M^{-1/2} over the degrees of // freedom in dof, column-major (dof.length x nModes) psi: Float64Array; dof: Int32Array; // index into the 3n coordinates (3*bead + component) of each row of psi kind: "transverse" | "full"; // z motion of a flat net only, or all motion } export interface SimResult { audio: Float32Array; // listener displacement at sampleRate (m) sampleRate: number; dt: number; substeps: number; // solver steps per audio sample omegaMax: number; // rad/s, highest linearized frequency frameTimes: Float64Array; // times of the energy records (s) energy: Float64Array; // mechanical energy above the rest state at each frame (J) animTimes: Float64Array; // times of the stored displacement snapshots (s) animFrames: Float32Array[]; // displacement (3n) at each snapshot (m) dissipated: Float64Array; // cumulative energy removed by damping (J) // mode energies (nModes per frame, frame-major), present when modes were computed modeEnergy: Float32Array | null; residualForce: number; // max |F| on free beads at rest (N) maxTension: number; // max spring force at rest (N) energyDrift: number; // max relative change in E + dissipated after the last excitation freqErrors: { hz: number; cents: number }[]; // integrator frequency error at a few frequencies elapsedMs: number; aborted: string | null; // reason if the run stopped early warnings: string[]; }