// Shared declarations, prepended to every kernel of the full simulation. struct Params { nx: u32, ny: u32, n: u32, histLen: u32, hx: f32, hy: f32, dt: f32, nu: f32, Db: f32, // E / (12 (1 - nu^2)): bending stiffness per h^3 [Pa] Cm: f32, // E / (1 - nu^2): membrane stiffness per h [Pa] Gm: f32, // E / (2 (1 + nu)): membrane shear stiffness per h [Pa] rho: f32, // density [kg/m^3] mass: f32, // total mass [kg] Ix: f32, // sum m y^2 Iy: f32, // sum m x^2 fs: f32, // audio sample rate [Hz] strikeX: f32, // drumstick contact point [m] strikeY: f32, strikeR2: f32, // squared contact radius [m^2] patchMass: f32, // mass of the contact patch [kg] strikeNode: u32, // plate node nearest the contact point K: f32, // Hertz contact stiffness [N/m^alpha] alpha: f32, // Hertz exponent stickMass: f32, // [kg] patchI0: u32, // index box enclosing the contact patch (inclusive) patchI1: u32, patchJ0: u32, patchJ1: u32, patchCX: f32, // mass-weighted center of the contact patch [m] patchCY: f32, standOn: u32, // 1 when the plate sits on a stand, 0 when it is free liftoff: u32, // 1 when the pads push only while compressed preloadTop: f32, // compression of the top pad at rest [m] (0 without a top pad) preloadBottom: f32, // compression of the bottom pad at rest [m] kInf: f32, // relaxed stiffness of the felt, per unit of its stiffness at the bounce frequency hasTop: u32, // 1 when there is a top pad nArms: u32, // arms of the felt's chain (at most MAX_ARMS) pad1: u32, pad2: u32, pad3: u32, // The chain of the felt (felt.ts), four arms per vec4: decay factors // exp(-dt / tau) in [0..2], growth factors (tau / dt)(1 - decay) in // [3..5], and arm stiffnesses per unit of pad stiffness in [6..8]. chain: array, }; const MAX_ARMS = 12u; fn armDecay(a: u32) -> f32 { return P.chain[a >> 2u][a & 3u]; } fn armGrowth(a: u32) -> f32 { return P.chain[3u + (a >> 2u)][a & 3u]; } fn armK(a: u32) -> f32 { return P.chain[6u + (a >> 2u)][a & 3u]; } // Per-step parameters, addressed with a dynamic uniform offset. struct StepParams { step: u32, flags: u32, // bit 0: drumstick active audioSlot: i32, // ring-buffer slot to record the velocity into (-1: none) audioOut: i32, // index of this sample within the batch's output sigma0: f32, sigma1: f32, sigmaIn: f32, // additional frequency-independent loss of the in-plane motion parity: u32, // which of the two stick-state slots holds the current state pad0: u32, pad1: u32, pad2: u32, pad3: u32, }; fn nodeX(i: i32) -> f32 { return (f32(i) - 0.5 * f32(P.nx - 1u)) * P.hx; } fn nodeY(j: i32) -> f32 { return (f32(j) - 0.5 * f32(P.ny - 1u)) * P.hy; } // Static per-node data, written once by the host: x = stress-free shape w0 [m], // y = quadrature and lumped-mass weight (the fraction of the node's four cells // that belong to the plate; zero outside), z = thickness [m], w = bottom-pad // stiffness at the node [N/m] (zero away from the pads; the top pad, when // present, has the same rate). @group(0) @binding(8) var nd: array; // Extensions of the arms of the felt's chain: padQ[(field * nArms + arm) * n + k] // with field 0, 1, 2 for u, v, w; zero is the relaxed felt. @group(0) @binding(9) var padQ: array; fn nodeWeight(i: i32, j: i32) -> f32 { if (i < 0 || i >= i32(P.nx) || j < 0 || j >= i32(P.ny)) { return 0.0; } return nd[u32(j) * P.nx + u32(i)].y; } // Lumped mass of a node [kg]. fn nodeMass(i: i32, j: i32) -> f32 { if (i < 0 || i >= i32(P.nx) || j < 0 || j >= i32(P.ny)) { return 0.0; } let k = u32(j) * P.nx + u32(i); return P.rho * nd[k].z * P.hx * P.hy * nd[k].y; }