1// Physical and numerical parameters of the simulation, plus derived quantities.
3export interface PhysParams {
4 Lx: number; // plate width along x [m]
5 Ly: number; // plate height along y [m]
6 h: number; // thickness [m]
7 E: number; // Young's modulus [Pa]
8 nu: number; // Poisson ratio
9 rho: number; // density [kg/m^3]
10 sigma0: number; // frequency-independent loss [1/s]
11 sigma1: number; // frequency-dependent loss [m^2/s]
12 holeInset: number; // distance of the thread hole from the two edges of the top-left corner [m]
13 pushAmount: number; // corner displacement [m]
14 fingerRadius: number; // radius of the pushed patch [m]
15 rampTime: number; // duration of the push ramp [s]
16 holdMax: number; // maximum settle time before release [s]
17 settleSigma0: number; // artificial damping during push and settle [1/s]
18 settleKE: number; // kinetic energy threshold that counts as settled [J]
19 spacing: number; // requested grid spacing [m]
20 fs: number; // audio sample rate [Hz]
21 fps: number; // movie frame rate (also the batch rate) [Hz]
22 duration: number; // recorded duration after release [s]
23 listenerDist: number; // [m]
24 listenerAzimuth: number; // [deg], rotation about the plate's vertical axis (0 = on the normal)
25 listenerElevation: number; // [deg]
26 radiation: "rayleigh" | "dipole";
27}
29export const defaultParams: PhysParams = {
30 Lx: 18 * 0.0254,
31 Ly: 12 * 0.0254,
32 h: 0.79e-3,
33 E: 193e9,
34 nu: 0.29,
35 rho: 7900,
36 sigma0: 1.15,
37 sigma1: 1.08e-4,
38 holeInset: 0.012,
39 pushAmount: 0.01,
40 fingerRadius: 0.01,
41 rampTime: 0.1,
42 holdMax: 0.5,
43 settleSigma0: 300,
44 settleKE: 1e-8,
45 spacing: 0.004,
46 fs: 48000,
47 fps: 120,
48 duration: 4,
49 listenerDist: 1.0,
50 listenerAzimuth: 0,
51 listenerElevation: 0,
52 radiation: "rayleigh",
53};
55export const SPEED_OF_SOUND = 343; // [m/s]
56export const RHO_AIR = 1.2; // [kg/m^3]
57export const HIST_LEN = 256; // ring buffer length for the retarded-time audio sum [samples]
58export const RB_INTERVAL = 32; // steps between rigid-body projections while ringing
59export const RB_INTERVAL_HELD = 4; // while the finger constraint is active
61export interface Grid {
62 nx: number;
63 ny: number;
64 hx: number;
65 hy: number;
66 n: number;
67}
69export interface Material {
70 D: number; // bending stiffness [N m]
71 C: number; // membrane stiffness E h / (1 - nu^2) [N/m]
72 G: number; // membrane shear stiffness E h / (2 (1 + nu)) [N/m]
73 nu: number;
74 rhoh: number; // mass per unit area [kg/m^2]
75}
77export function makeGrid(p: PhysParams): Grid {
78 const nx = Math.max(6, Math.round(p.Lx / p.spacing) + 1);
79 const ny = Math.max(6, Math.round(p.Ly / p.spacing) + 1);
80 return { nx, ny, hx: p.Lx / (nx - 1), hy: p.Ly / (ny - 1), n: nx * ny };
81}
83export function makeMaterial(p: PhysParams): Material {
84 return {
85 D: (p.E * p.h ** 3) / (12 * (1 - p.nu * p.nu)),
86 C: (p.E * p.h) / (1 - p.nu * p.nu),
87 G: (p.E * p.h) / (2 * (1 + p.nu)),
88 nu: p.nu,
89 rhoh: p.rho * p.h,
90 };
91}
93/** Node coordinates relative to the plate center. */
94export function nodeX(g: Grid, i: number): number {
95 return (i - (g.nx - 1) / 2) * g.hx;
96}
97export function nodeY(g: Grid, j: number): number {
98 return (j - (g.ny - 1) / 2) * g.hy;
99}
101/** Trapezoidal weight of a node: 1 in the interior, 1/2 on an edge, 1/4 at a corner. */
102export function trapWeight(g: Grid, i: number, j: number): number {
103 const tx = i === 0 || i === g.nx - 1 ? 0.5 : 1;
104 const ty = j === 0 || j === g.ny - 1 ? 0.5 : 1;
105 return tx * ty;
106}
108/** Decay time T60 [s] of a mode with angular frequency omega under the two-parameter loss model. */
109export function t60(m: Material, p: PhysParams, omega: number): number {
110 const kappa = Math.sqrt(m.D / m.rhoh);
111 const k2 = omega / kappa;
112 const sigma = p.sigma0 + p.sigma1 * k2;
113 return (3 * Math.LN10) / sigma; // amplitude factor 1000 = 60 dB
114}