/ proof-of-concept / steelsim31
Sign in
proof-of-concept / steelsim31
steelsim31 / scripts / gallery.html
132 lines · 6.6 KBCodeBlameHistory
2<meta charset="utf-8">
3<title>steelsim31 dataset</title>
4<style>
5 body { font: 14px system-ui, sans-serif; margin: 16px; color: #222; }
6 h1 { font-size: 18px; margin: 0 0 8px; }
7 #summary { margin-bottom: 12px; color: #444; }
8 table { border-collapse: collapse; width: 100%; }
9 th, td { padding: 4px 8px; border-bottom: 1px solid #ddd; text-align: right; white-space: nowrap; vertical-align: middle; }
10 th { position: sticky; top: 0; background: #f4f4f4; cursor: pointer; }
11 td.l, th.l { text-align: left; }
12 audio { height: 28px; width: 220px; }
13 canvas { display: block; }
14 .feat { max-width: 420px; overflow: hidden; text-overflow: ellipsis; font-family: monospace; font-size: 11px; color: #555; white-space: nowrap; }
15 .ctl { margin-bottom: 8px; display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
16</style>
17<h1>steelsim31 dataset</h1>
18<div id="summary">loading…</div>
19<div class="ctl">
20 <label><input type="checkbox" id="auto" checked> reload every 30 s</label>
21 <label>show <input type="number" id="limit" value="60" min="10" step="10" style="width:5em"> newest</label>
22 <label><input type="checkbox" id="specs"> spectrograms</label>
23 <button id="reload">reload</button>
24 <a href="ml/report.html">training report (linear f)</a> <a href="ml/report-logf.html">training report (log f)</a> <a href="ml/report-specnet.html">training report (SpecNet)</a>
25</div>
26<table>
27 <thead><tr>
28 <th class="l">id</th><th class="l">audio</th><th>E GPa</th><th>ρ kg/m³</th><th>h mm</th><th>W×H in</th>
29 <th>v m/s</th><th>strike</th><th>σ0</th><th>σ1·10⁴</th><th>listener</th><th>shape p-p mm</th><th class="l">features</th>
30 <th>peak Pa</th><th>grid</th><th>wall s</th>
31 </tr></thead>
32 <tbody id="rows"></tbody>
33</table>
34<script>
35const $ = (id) => document.getElementById(id);
36let recs = [];
37async function load() {
38 const r = await fetch("index.jsonl", { cache: "no-store" });
39 const text = await r.text();
40 recs = text.split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
41 render();
43function fmt(x, d) { return x == null ? "" : Number(x).toFixed(d); }
44function render() {
45 const n = recs.length;
46 if (!n) { $("summary").textContent = "no clips yet"; return; }
47 const first = new Date(recs[0].finished).getTime(), last = new Date(recs[n - 1].finished).getTime();
48 const hours = Math.max(1e-6, (last - first) / 3.6e6);
49 const rate = n > 1 ? ((n - 1) / hours).toFixed(0) : "?";
50 const wall = recs.reduce((s, r) => s + r.wallSeconds, 0) / n;
51 $("summary").textContent = `${n} clips, ${rate} clips/hour, mean ${wall.toFixed(0)} s wall per clip, last ${new Date(recs[n - 1].finished).toLocaleTimeString()}`;
52 const limit = parseInt($("limit").value) || 60;
53 const rows = recs.slice(-limit).reverse();
54 const tb = $("rows");
55 tb.innerHTML = "";
56 for (const r of rows) {
57 const tr = document.createElement("tr");
58 const feats = r.features === "none" ? "flat" : r.features.split(";").map((f) => f.split(":")[0]).join(", ") + " (" + r.features + ")";
59 tr.innerHTML = `<td class="l">${r.id}</td><td class="l"><audio controls preload="none" src="clips/${r.id}.wav"></audio></td>` +
60 `<td>${fmt(r.E / 1e9, 0)}</td><td>${fmt(r.rho, 0)}</td><td>${fmt(r.h * 1e3, 2)}</td><td>${fmt(r.Lx / 0.0254, 1)}×${fmt(r.Ly / 0.0254, 1)}</td>` +
61 `<td>${fmt(r.strikeVelocity, 2)}</td><td>${fmt(r.strikeFracX, 2)}, ${fmt(r.strikeFracY, 2)}</td><td>${fmt(r.sigma0, 2)}</td><td>${fmt(r.sigma1 * 1e4, 1)}</td>` +
62 `<td>${fmt(r.listenerAzimuth, 0)}°, ${fmt(r.listenerElevation, 0)}°</td><td>${fmt(r.peakToPeak * 1e3, 1)}</td><td class="l"><div class="feat" title="${r.features}">${feats}</div></td>` +
63 `<td>${fmt(r.peakPa, 2)}</td><td>${r.nx}×${r.ny}</td><td>${fmt(r.wallSeconds, 0)}</td>`;
64 tb.appendChild(tr);
65 if ($("specs").checked) {
66 const tr2 = document.createElement("tr");
67 const td = document.createElement("td"); td.colSpan = 16; td.className = "l";
68 const c = document.createElement("canvas"); c.width = 900; c.height = 120; td.appendChild(c); tr2.appendChild(td); tb.appendChild(tr2);
69 drawSpec(c, `clips/${r.id}.wav`);
70 }
71 }
73// Spectrogram: 2048-point Hann windows, hop 512, 0 to 5 kHz, 80 dB range.
74let actx = null;
75async function drawSpec(canvas, url) {
76 actx = actx || new (window.OfflineAudioContext || window.webkitOfflineAudioContext)(1, 48000, 48000);
77 const buf = await (await fetch(url)).arrayBuffer();
78 const audio = await actx.decodeAudioData(buf);
79 const x = audio.getChannelData(0), fs = audio.sampleRate;
80 const N = 2048, hop = 512, fmax = 5000, kmax = Math.floor((fmax / fs) * N);
81 const frames = Math.floor((x.length - N) / hop);
82 const g = canvas.getContext("2d"), W = canvas.width, H = canvas.height;
83 const img = g.createImageData(W, H);
84 const win = new Float32Array(N); for (let i = 0; i < N; i++) win[i] = 0.5 - 0.5 * Math.cos((2 * Math.PI * i) / N);
85 const re = new Float32Array(N), im = new Float32Array(N);
86 const cols = new Array(frames);
87 let peak = -Infinity;
88 for (let f = 0; f < frames; f++) {
89 for (let i = 0; i < N; i++) { re[i] = x[f * hop + i] * win[i]; im[i] = 0; }
90 fft(re, im);
91 const col = new Float32Array(kmax);
92 for (let k = 0; k < kmax; k++) { col[k] = 10 * Math.log10(re[k] * re[k] + im[k] * im[k] + 1e-20); peak = Math.max(peak, col[k]); }
93 cols[f] = col;
94 }
95 for (let px = 0; px < W; px++) {
96 const f = Math.min(frames - 1, Math.floor((px / W) * frames));
97 for (let py = 0; py < H; py++) {
98 const k = Math.floor(((H - 1 - py) / H) * kmax);
99 const v = Math.max(0, Math.min(1, (cols[f][k] - peak + 80) / 80));
100 const o = 4 * (py * W + px);
101 img.data[o] = 255 * v; img.data[o + 1] = 255 * v * v; img.data[o + 2] = 255 * (0.2 + 0.8 * v * v * v) * (1 - v) + 255 * v * v * v; img.data[o + 3] = 255;
102 }
103 }
104 g.putImageData(img, 0, 0);
106function fft(re, im) {
107 const n = re.length;
108 for (let i = 1, j = 0; i < n; i++) {
109 let bit = n >> 1;
110 for (; j & bit; bit >>= 1) j ^= bit;
111 j ^= bit;
112 if (i < j) { [re[i], re[j]] = [re[j], re[i]]; [im[i], im[j]] = [im[j], im[i]]; }
113 }
114 for (let len = 2; len <= n; len <<= 1) {
115 const ang = (-2 * Math.PI) / len, wr = Math.cos(ang), wi = Math.sin(ang);
116 for (let i = 0; i < n; i += len) {
117 let cr = 1, ci = 0;
118 for (let k = 0; k < len / 2; k++) {
119 const a = i + k, b = a + len / 2;
120 const tr = re[b] * cr - im[b] * ci, ti = re[b] * ci + im[b] * cr;
121 re[b] = re[a] - tr; im[b] = im[a] - ti; re[a] += tr; im[a] += ti;
122 const ncr = cr * wr - ci * wi; ci = cr * wi + ci * wr; cr = ncr;
123 }
124 }
125 }
127$("reload").onclick = load;
128$("limit").onchange = render;
129$("specs").onchange = render;
130setInterval(() => { if ($("auto").checked) load(); }, 30000);
131load();
132</script>
↑↓ move↵openescclose