Gallery: setup diagram at the top of the page
2 changed files+91−0
docs/gallery/index.htmlmodified+38−0View file
@@ -11,8 +11,46 @@ p { max-width: 900px; } .dim { color: #9aa0a8; font-size: 13px; } a { color: #6f
1111 .card audio { width: 100%; } .label { font-weight: 600; }
1212 .metrics { font-size: 12px; color: #c8cbd0; } .metrics div { padding: 1px 0; }
1313 ul.toc { columns: 2; }
14+.setup-wrap { background: #1c1e22; border: 1px solid #2c3036; border-radius: 8px; padding: 8px; display: inline-block; max-width: 100%; overflow-x: auto; }
15+svg.setup { display: block; max-width: 100%; height: auto; }
1416 </style></head><body>
1517 <h1>Steel sheet gong: how the parameters shape the sound</h1>
18+<div class="setup-wrap"><svg class="setup" viewBox="0 0 880 358.6" width="880" height="358.6" role="img" aria-label="Physical setup">
19+ <defs><marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" fill="#f0b429"/></marker></defs>
20+ <g font-family="system-ui, sans-serif" font-size="12" fill="#c8cbd0">
21+ <text x="60" y="38" font-weight="600">Front view</text>
22+ <text x="60" y="56" fill="#9aa0a8">12 x 18 in, 0.79 mm stainless steel</text>
23+ <!-- thread and hanging sheet -->
24+ <line x1="70" y1="72" x2="70" y2="90" stroke="#c8cbd0" stroke-width="1" stroke-dasharray="3 2"/>
25+ <rect x="60" y="80" width="152.39999999999998" height="228.6" fill="#2a3340" stroke="#c8cbd0" stroke-width="1.5"/>
26+ <circle cx="70" cy="90" r="3" fill="#15171a" stroke="#c8cbd0"/>
27+ <text x="80" y="106" fill="#9aa0a8">hangs from a thread through a hole</text>
28+ <!-- strike point -->
29+ <circle cx="166.67999999999998" cy="228.59" r="6" fill="none" stroke="#6fa8ea" stroke-width="2"/>
30+ <line x1="173.67999999999998" y1="221.59" x2="224.67999999999998" y2="170.59" stroke="#b08050" stroke-width="5" stroke-linecap="round"/>
31+ <text x="234.67999999999998" y="168.59" fill="#6fa8ea">drumstick strike</text>
32+ <text x="234.67999999999998" y="182.59" fill="#9aa0a8">70% of width, 35% of height</text>
33+ <!-- pushed corner -->
34+ <circle cx="212.39999999999998" cy="308.6" r="7" fill="none" stroke="#f0b429" stroke-width="2"/>
35+ <text x="60" y="332.6" fill="#f0b429">finger pushes this corner 10 mm toward the listener,</text>
36+ <text x="60" y="346.6" fill="#9aa0a8">holds it until the sheet settles, then lets go</text>
37+ <!-- top view -->
38+ <text x="430" y="38" font-weight="600">Top view</text>
39+ <text x="430" y="56" fill="#9aa0a8">the sheet seen from above</text>
40+ <path d="M 430 90 Q 506.2 110 582.4 90" fill="none" stroke="#c8cbd0" stroke-width="3"/>
41+ <text x="594.4" y="94" fill="#9aa0a8">bow of 3 mm across the width</text>
42+ <text x="594.4" y="108" fill="#9aa0a8">(exaggerated), bulging toward the listener</text>
43+ <line x1="582.4" y1="96" x2="582.4" y2="134" stroke="#f0b429" stroke-width="2" marker-end="url(#arrow)"/>
44+ <text x="594.4" y="136" fill="#f0b429">push</text>
45+ <line x1="506.2" y1="108" x2="506.2" y2="276" stroke="#9aa0a8" stroke-width="1" stroke-dasharray="4 3"/>
46+ <text x="514.2" y="200" fill="#9aa0a8">1 m</text>
47+ <circle cx="506.2" cy="290" r="8" fill="none" stroke="#6fa8ea" stroke-width="2"/>
48+ <circle cx="506.2" cy="290" r="2" fill="#6fa8ea"/>
49+ <text x="522.2" y="286" fill="#6fa8ea">listener</text>
50+ <text x="522.2" y="302" fill="#9aa0a8">on the normal through the center</text>
51+ </g>
52+</svg></div>
53+<p class="dim">The setup: the sheet hangs from a thread through a hole near one corner. It is either pushed at the opposite corner by a fingertip, held until it settles, and released, or struck with a wooden drumstick. It has a slight bow, and the sound is computed 1 m in front of its center.</p>
1654 <p>Each section varies one property of the simulated sheet or of the excitation while everything else stays at the simulator defaults: a 12 by 18 inch, 22 gauge (0.79 mm) stainless steel sheet hanging in portrait orientation with a 3 mm bow across its width, heard 1 m in front of its center, recorded for 4 s. The clips are computed with the simulator's default numerical settings (8 mm grid), so the 'Open in simulator' links reproduce them exactly. Click a player to hear a clip. Every clip is rendered at the simulator's default playback level (+20 dB relative to 1 Pa at full scale, with the same soft clipper), so loudness differences within and between sections are the ones the model predicts, and a clip sounds as it does in the app. The numbers under each clip are the peak pressure and sound pressure level at the listener, the RMS pressure over the clip, the spectral centroid of the sound between 0.2 and 1.2 s (a measure of brightness), and fitted 60 dB decay times of a low and a high band.</p>
1755 <ul class="toc"><li><a href="#strike-speed">Drumstick: stick speed</a></li><li><a href="#strike-mass">Drumstick: stick mass</a></li><li><a href="#strike-stiffness">Drumstick: tip hardness</a></li><li><a href="#strike-position">Drumstick: strike position</a></li><li><a href="#push-amount">Push and release: push amount</a></li><li><a href="#push-sign">Push and release: with or against the bow</a></li><li><a href="#push-release">Push and release: release time</a></li><li><a href="#bow-amplitude">Sheet: initial bow (push and release)</a></li><li><a href="#bow-amplitude-strike">Sheet: initial bow (drumstick strike)</a></li><li><a href="#bow-shape">Sheet: shape of the bow (push and release)</a></li><li><a href="#bow-shape-strike">Sheet: shape of the bow (drumstick strike)</a></li><li><a href="#thickness">Sheet: thickness (push and release)</a></li><li><a href="#thickness-strike">Sheet: thickness (drumstick strike)</a></li></ul>
1856 <section id="strike-speed">
src/cli/main.tsmodified+53−0View file
@@ -202,6 +202,55 @@ function fmtNum(v: number | null, digits = 2): string {
202202 return v.toFixed(digits);
203203 }
204204
205+/** Inline SVG of the physical setup, drawn from the default parameters. */
206+function setupDiagram(): string {
207+ const p = defaultParams;
208+ const k = 0.5; // px per mm
209+ const x0 = 60, y0 = 80, W = p.Lx * 1e3 * k, H = p.Ly * 1e3 * k;
210+ const hole = [x0 + p.holeInset * 1e3 * k + 4, y0 + p.holeInset * 1e3 * k + 4];
211+ const push = [x0 + W, y0 + H];
212+ const strike = [x0 + p.strikeFracX * W, y0 + (1 - p.strikeFracY) * H];
213+ const gray = "#c8cbd0", dim = "#9aa0a8", accent = "#f0b429", blue = "#6fa8ea";
214+ const tx = 430, ty = y0 + 10, tw = W, sag = Math.max(10, p.bowAmplitude * 1e3 * k * 6);
215+ const listenerY = ty + 200;
216+ const width = 880, height = y0 + H + 50;
217+ return `<svg class="setup" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}" role="img" aria-label="Physical setup">
218+ <defs><marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" fill="${accent}"/></marker></defs>
219+ <g font-family="system-ui, sans-serif" font-size="12" fill="${gray}">
220+ <text x="${x0}" y="${y0 - 42}" font-weight="600">Front view</text>
221+ <text x="${x0}" y="${y0 - 24}" fill="${dim}">${(p.Lx / 0.0254).toFixed(0)} x ${(p.Ly / 0.0254).toFixed(0)} in, ${(p.h * 1e3).toFixed(2)} mm stainless steel</text>
222+ <!-- thread and hanging sheet -->
223+ <line x1="${hole[0]}" y1="${y0 - 8}" x2="${hole[0]}" y2="${hole[1]}" stroke="${gray}" stroke-width="1" stroke-dasharray="3 2"/>
224+ <rect x="${x0}" y="${y0}" width="${W}" height="${H}" fill="#2a3340" stroke="${gray}" stroke-width="1.5"/>
225+ <circle cx="${hole[0]}" cy="${hole[1]}" r="3" fill="#15171a" stroke="${gray}"/>
226+ <text x="${hole[0] + 10}" y="${hole[1] + 16}" fill="${dim}">hangs from a thread through a hole</text>
227+ <!-- strike point -->
228+ <circle cx="${strike[0]}" cy="${strike[1]}" r="6" fill="none" stroke="${blue}" stroke-width="2"/>
229+ <line x1="${strike[0] + 7}" y1="${strike[1] - 7}" x2="${strike[0] + 58}" y2="${strike[1] - 58}" stroke="#b08050" stroke-width="5" stroke-linecap="round"/>
230+ <text x="${strike[0] + 68}" y="${strike[1] - 60}" fill="${blue}">drumstick strike</text>
231+ <text x="${strike[0] + 68}" y="${strike[1] - 46}" fill="${dim}">${(p.strikeFracX * 100).toFixed(0)}% of width, ${(p.strikeFracY * 100).toFixed(0)}% of height</text>
232+ <!-- pushed corner -->
233+ <circle cx="${push[0]}" cy="${push[1]}" r="7" fill="none" stroke="${accent}" stroke-width="2"/>
234+ <text x="${x0}" y="${push[1] + 24}" fill="${accent}">finger pushes this corner ${(p.pushAmount * 1e3).toFixed(0)} mm toward the listener,</text>
235+ <text x="${x0}" y="${push[1] + 38}" fill="${dim}">holds it until the sheet settles, then lets go</text>
236+ <!-- top view -->
237+ <text x="${tx}" y="${y0 - 42}" font-weight="600">Top view</text>
238+ <text x="${tx}" y="${y0 - 24}" fill="${dim}">the sheet seen from above</text>
239+ <path d="M ${tx} ${ty} Q ${tx + tw / 2} ${ty + 2 * sag} ${tx + tw} ${ty}" fill="none" stroke="${gray}" stroke-width="3"/>
240+ <text x="${tx + tw + 12}" y="${ty + 4}" fill="${dim}">bow of ${(p.bowAmplitude * 1e3).toFixed(0)} mm across the width</text>
241+ <text x="${tx + tw + 12}" y="${ty + 18}" fill="${dim}">(exaggerated), bulging toward the listener</text>
242+ <line x1="${tx + tw}" y1="${ty + 6}" x2="${tx + tw}" y2="${ty + 44}" stroke="${accent}" stroke-width="2" marker-end="url(#arrow)"/>
243+ <text x="${tx + tw + 12}" y="${ty + 46}" fill="${accent}">push</text>
244+ <line x1="${tx + tw / 2}" y1="${ty + sag + 8}" x2="${tx + tw / 2}" y2="${listenerY - 14}" stroke="${dim}" stroke-width="1" stroke-dasharray="4 3"/>
245+ <text x="${tx + tw / 2 + 8}" y="${(ty + listenerY) / 2 + 10}" fill="${dim}">${p.listenerDist.toFixed(0)} m</text>
246+ <circle cx="${tx + tw / 2}" cy="${listenerY}" r="8" fill="none" stroke="${blue}" stroke-width="2"/>
247+ <circle cx="${tx + tw / 2}" cy="${listenerY}" r="2" fill="${blue}"/>
248+ <text x="${tx + tw / 2 + 16}" y="${listenerY - 4}" fill="${blue}">listener</text>
249+ <text x="${tx + tw / 2 + 16}" y="${listenerY + 12}" fill="${dim}">on the normal through the center</text>
250+ </g>
251+</svg>`;
252+}
253+
205254 function writeHtml(manifest: Manifest, dir: string): void {
206255 const sections: string[] = [];
207256 for (const sw of manifest.sweeps) {
@@ -249,8 +298,12 @@ p { max-width: 900px; } .dim { color: #9aa0a8; font-size: 13px; } a { color: #6f
249298 .card audio { width: 100%; } .label { font-weight: 600; }
250299 .metrics { font-size: 12px; color: #c8cbd0; } .metrics div { padding: 1px 0; }
251300 ul.toc { columns: 2; }
301+.setup-wrap { background: #1c1e22; border: 1px solid #2c3036; border-radius: 8px; padding: 8px; display: inline-block; max-width: 100%; overflow-x: auto; }
302+svg.setup { display: block; max-width: 100%; height: auto; }
252303 </style></head><body>
253304 <h1>${esc(manifest.title)}</h1>
305+<div class="setup-wrap">${setupDiagram()}</div>
306+<p class="dim">The setup: the sheet hangs from a thread through a hole near one corner. It is either pushed at the opposite corner by a fingertip, held until it settles, and released, or struck with a wooden drumstick. It has a slight bow, and the sound is computed 1 m in front of its center.</p>
254307 <p>${esc(manifest.intro)}</p>
255308 <ul class="toc">${toc}</ul>
256309 ${sections.join("\n")}