// Predefined design scripts, selectable from the interface. Each is a plain // JavaScript script body that returns the design object; see design.ts for // the fields and the helpers in scope. export interface Preset { id: string; label: string; source: string; } const flatRectangle = `// A flat rectangular sheet of stainless steel, hanging freely (no mount). // Lengths are meters; inch and mm are conversion constants. // Every field can be a number or a function (x, y, r, theta) of the point, // measured from the center of the bounding box. return { name: "Flat steel rectangle", material: { E: 170e9, nu: 0.29, rho: 7900 }, bounds: { width: 12 * inch, height: 18 * inch }, thickness: 0.79 * mm, profile: 0, mount: null, loss: { sigma0: 1.15, sigma1: 5e-4, sigmaIn: 20 }, }; `; const bowedRectangle = `// The same steel sheet with a 3 mm parabolic bow across its width, the // kind of bow that rolling and handling leave in real sheet metal. The bow // makes the sheet a shallow shell, which oil-cans and wobbles when struck. const W = 12 * inch, H = 18 * inch; return { name: "Bowed steel rectangle", material: { E: 170e9, nu: 0.29, rho: 7900 }, bounds: { width: W, height: H }, thickness: 0.79 * mm, // bow(s, span, sag, offset): parabola of the given sag across the span; the // offset in -1..1 moves the crest toward one edge. profile: (x, y) => bow(x, W, 3 * mm, 0.3), mount: null, loss: { sigma0: 1.15, sigma1: 5e-4, sigmaIn: 20 }, }; `; const flatDisc = `// A flat 16 inch steel disc with a center hole, sitting on a stand: the // nodes within the mount radius are tied to the ground by a spring (the felt) // and a dashpot, spread over the mount area. const D = 16 * inch; return { name: "Flat steel disc", material: { E: 170e9, nu: 0.29, rho: 7900 }, bounds: { width: D, height: D }, inside: circle(D, 12 * mm), // diameter, hole diameter thickness: 1.0 * mm, profile: 0, mount: { radius: 20 * mm, stiffness: 2e4, damping: 5 }, loss: { sigma0: 1.15, sigma1: 5e-4, sigmaIn: 20 }, }; `; const crash16 = `// A 16 inch crash: B20 bronze, tapered from the bell to the edge, with a // shallow bow and a cosine bell. The shallow-shell model behind the // simulation is strained where the slope of the profile exceeds about 0.1 // to 0.2, so the bow and the bell are a little flatter than on a real crash. const D = 16 * inch, R = D / 2; const bellR = 60 * mm, bellH = 12 * mm; // base radius and height of the bell const bowH = 16 * mm; // rise from the edge to the foot of the bell return { name: "16 inch crash", material: { E: 110e9, nu: 0.34, rho: 8800 }, bounds: { width: D, height: D }, inside: circle(D, 12 * mm), // Taper: 1.4 mm at the bell, 0.8 mm at the edge. thickness: (x, y, r) => lerp(1.4 * mm, 0.8 * mm, clamp((r - bellR) / (R - bellR), 0, 1)), // A paraboloidal bow plus the bell; dome(d, radius, height) has zero slope at its rim. profile: (x, y, r) => bowH * (1 - (r / R) ** 2) + dome(r, bellR, bellH), mount: { radius: 20 * mm, stiffness: 2e4, damping: 5 }, loss: { sigma0: 0.6, sigma1: 2.5e-4, sigmaIn: 20 }, }; `; const crashDented = `// A 16 inch crash that has been through a lot: the same bronze, taper, // bow, and bell as the clean crash, plus a dozen dents scattered over the // bow and a faint hammering texture. The dents are Gaussian bumps of a few // centimeters (broad compared with the 8 mm grid) and a millimeter or two // in height, up or down; change the seed for a different set. Dents break // the symmetry of the disc, split the degenerate mode pairs, and give the // cymbal a less pure, more trashy tone. const D = 16 * inch, R = D / 2; const bellR = 60 * mm, bellH = 12 * mm, bowH = 16 * mm; // A small seeded generator, so the dents are the same every time. let seed = 7; const rnd = () => { seed = (seed * 1664525 + 1013904223) % 4294967296; return seed / 4294967296; }; const dents = []; for (let k = 0; k < 12; k++) { const rr = bellR + 25 * mm + (R - bellR - 45 * mm) * rnd(); // on the bow, clear of the bell and the edge const th = 2 * Math.PI * rnd(); dents.push({ x: rr * Math.cos(th), y: rr * Math.sin(th), radius: (18 + 14 * rnd()) * mm, height: (rnd() < 0.5 ? -1 : 1) * (0.8 + 1.4 * rnd()) * mm, }); } const hammer = noise2d(3, 40 * mm); // unit RMS, correlation length 40 mm return { name: "16 inch crash, dented", material: { E: 110e9, nu: 0.34, rho: 8800 }, bounds: { width: D, height: D }, inside: circle(D, 12 * mm), thickness: (x, y, r) => lerp(1.4 * mm, 0.8 * mm, clamp((r - bellR) / (R - bellR), 0, 1)), profile: (x, y, r) => { let z = bowH * (1 - (r / R) ** 2) + dome(r, bellR, bellH); for (const d of dents) z += bump(Math.hypot(x - d.x, y - d.y), d.radius, d.height); z += 0.3 * mm * hammer(x, y) * smoothstep(bellR, bellR + 30 * mm, r); // no hammering on the bell return z; }, mount: { radius: 20 * mm, stiffness: 2e4, damping: 5 }, loss: { sigma0: 0.6, sigma1: 2.5e-4, sigmaIn: 20 }, }; `; const ride20 = `// A 20 inch ride: heavier bronze, a larger bell, a flatter bow. The taper // is stronger than on the crash, and the mount is a little stiffer. const D = 20 * inch, R = D / 2; const bellR = 70 * mm, bellH = 14 * mm; const bowH = 15 * mm; return { name: "20 inch ride", material: { E: 110e9, nu: 0.34, rho: 8800 }, bounds: { width: D, height: D }, inside: circle(D, 12 * mm), thickness: (x, y, r) => lerp(2.0 * mm, 1.1 * mm, smoothstep(bellR, R, r)), profile: (x, y, r) => bowH * (1 - (r / R) ** 2) + dome(r, bellR, bellH), mount: { radius: 22 * mm, stiffness: 3e4, damping: 8 }, loss: { sigma0: 0.5, sigma1: 2e-4, sigmaIn: 20 }, }; `; const ellipseTwist = `// A steel ellipse with a saddle (twist) and a bow, hanging freely. The // twist raises two opposite quadrants and lowers the other two, which // breaks the symmetry of the disc modes. const W = 14 * inch, H = 20 * inch; return { name: "Steel ellipse with a twist", material: { E: 170e9, nu: 0.29, rho: 7900 }, bounds: { width: W, height: H }, inside: ellipse(W, H), thickness: 0.79 * mm, profile: (x, y) => { const X = (2 * x) / W, Y = (2 * y) / H; // -1..1 over the bounding box return 2 * mm * X * Y + bow(y, H, 2 * mm); }, mount: null, loss: { sigma0: 1.15, sigma1: 5e-4, sigmaIn: 20 }, }; `; const china18 = `// An 18 inch china: a conical bow that falls from the bell toward a flange, // and an upturned edge that rises again over the outer 45 mm. The bell is // square-topped (a plateau with rounded shoulders). The edge slope is well // beyond the shallow-shell range, so this preset is the least trustworthy. const D = 18 * inch, R = D / 2; const bellR = 65 * mm, bellH = 14 * mm; const flangeR = R - 45 * mm, bowH = 14 * mm, edgeH = 12 * mm; return { name: "18 inch china", material: { E: 110e9, nu: 0.34, rho: 8800 }, bounds: { width: D, height: D }, inside: circle(D, 12 * mm), thickness: (x, y, r) => lerp(1.6 * mm, 0.9 * mm, smoothstep(bellR, flangeR, r)), profile: (x, y, r) => bowH * Math.max(0, 1 - r / flangeR) // cone down to the flange + edgeH * smoothstep(flangeR, R, r) // upturned edge + bellH * (1 - smoothstep(bellR - 20 * mm, bellR, r)), // flat-topped bell mount: { radius: 20 * mm, stiffness: 2e4, damping: 5 }, loss: { sigma0: 0.7, sigma1: 3e-4, sigmaIn: 20 }, }; `; export const PRESETS: Preset[] = [ { id: "flat-rectangle", label: "Flat steel rectangle", source: flatRectangle }, { id: "bowed-rectangle", label: "Bowed steel rectangle", source: bowedRectangle }, { id: "flat-disc", label: "Flat steel disc on a stand", source: flatDisc }, { id: "crash-16", label: "16 inch crash", source: crash16 }, { id: "crash-16-dented", label: "16 inch crash, dented", source: crashDented }, { id: "ride-20", label: "20 inch ride", source: ride20 }, { id: "ellipse-twist", label: "Steel ellipse with a twist", source: ellipseTwist }, { id: "china-18", label: "18 inch china", source: china18 }, ]; export const DEFAULT_PRESET = PRESETS[0];