1import { midiHz, midiName } from '../params';
2import type { RenderResponse } from '../worker/renderWorker';
4export default function DiagnosticsPanel({ result }: { result: RenderResponse }) {
5 const d = result.diag;
6 const pitches = Array.from(result.noteMidi).map((m) => midiName(m));
7 const shown = pitches.slice(0, 12);
8 const cents = 1200 * Math.log2(d.fundHz / midiHz(result.noteMidi[0]));
9 return (
10 <div className="diag">
11 <table>
12 <tbody>
13 <tr>
14 <th>Notes</th>
15 <td className="partials">
16 {shown.join(', ')}
17 {pitches.length > shown.length ? `, … (${pitches.length} total)` : ''}
18 {` · mean pluck ${d.meanForce.toFixed(1)} N`}
19 </td>
20 </tr>
21 <tr>
22 <th>Fundamental</th>
23 <td>
24 {d.fundHz.toFixed(2)} Hz ({cents >= 0 ? '+' : ''}{cents.toFixed(0)} cents) · T60{' '}
25 {d.fundT60.toFixed(1)} s
26 </td>
27 </tr>
28 <tr>
29 <th>Modes</th>
30 <td>
31 {d.modesNote1} coupled (note 1) · {d.modesTotal} across {d.fingerings} fingering
32 {d.fingerings === 1 ? '' : 's'}
33 </td>
34 </tr>
35 <tr>
36 <th>Body</th>
37 <td>
38 A0 {result.bodyF[0]?.toFixed(1)} Hz · {result.bodyF.length} modes to{' '}
39 {result.bodyF[result.bodyF.length - 1]?.toFixed(0)} Hz
40 </td>
41 </tr>
42 <tr>
43 <th>Listener</th>
44 <td>{(d.listenerDistM * 100).toFixed(0)} cm from the bridge</td>
45 </tr>
46 <tr>
47 <th>Peak pressure</th>
48 <td>
49 {d.peakPa.toFixed(3)} Pa ({d.peakSplDb.toFixed(1)} dB SPL)
50 </td>
51 </tr>
52 <tr>
53 <th>Length</th>
54 <td>
55 {d.durationS.toFixed(2)} s · {result.quality} · oversample {d.oversample}
56 </td>
57 </tr>
58 </tbody>
59 </table>
60 <div className="cachenote">
61 eigensolves: {d.solveCached ? 'cached' : `${(d.solveMs / 1000).toFixed(2)} s`} · radiation:{' '}
62 {d.radiateCached ? 'cached' : `${(d.radiateMs / 1000).toFixed(2)} s`}
63 </div>
64 {d.warnings.map((w, i) => (
65 <div key={i} className="warning">⚠ {w}</div>
66 ))}
67 </div>
68 );
69}