/ proof-of-concept / audio-spike-sorting
Sign in
proof-of-concept / audio-spike-sorting
audio-spike-sorting / index.html
159 lines · 8.0 KBBlameHistoryRaw
1<!doctype html>
2<html lang="en">
3 <head>
4 <meta charset="utf-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1" />
6 <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>&#128227;</text></svg>" />
7 <title>audio-spike-sorting</title>
8 <style>
9 :root {
10 color-scheme: light dark;
11 font-family: system-ui, sans-serif;
12 }
13 body {
14 margin: 0 auto;
15 max-width: 76rem;
16 padding: 1rem 1.5rem 4rem;
17 line-height: 1.45;
18 }
19 h1 { font-size: 1.3rem; margin: 0.3rem 0 0; }
20 h1 + p { margin-top: 0.2rem; opacity: 0.75; }
21 h2 { font-size: 0.95rem; margin: 1.5rem 0 0.4rem; }
22 .cost { font-weight: normal; opacity: 0.6; font-size: 0.8rem; }
23 .bar { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; margin: 0.5rem 0; }
24 button { font: inherit; padding: 0.3rem 0.8rem; }
25 button:disabled { opacity: 0.5; }
26 select, input[type='text'], input[type='number'] { font: inherit; }
27 input[type='number'] { width: 5.5rem; }
28 .type-row {
29 display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap;
30 padding: 0.3rem 0.4rem; border-radius: 6px; font-size: 0.9rem;
31 }
32 .type-row:nth-child(odd) { background: rgba(128, 128, 128, 0.09); }
33 .type-row .swatch { width: 0.8rem; height: 0.8rem; border-radius: 3px; flex: none; }
34 .type-row input.name { width: 9rem; border: none; background: transparent; font-weight: 600; }
35 .type-row .count { opacity: 0.65; font-size: 0.8rem; min-width: 11rem; }
36 .type-row .spacer { flex: 1; }
37 .rec { color: #d33; }
38 canvas.wave { width: 100%; height: 110px; border-radius: 6px; display: block; touch-action: none; }
39 canvas.explorer { width: 100%; border-radius: 6px; display: block; touch-action: none; cursor: crosshair; }
40 .meter { width: 7rem; height: 0.55rem; border-radius: 3px; background: rgba(128,128,128,0.25); overflow: hidden; }
41 .meter div { height: 100%; width: 0; background: #3dbb6e; }
42 .panel { border: 1px solid rgba(128,128,128,0.35); border-radius: 8px; padding: 0.6rem 0.8rem; margin-top: 0.8rem; }
43 #status { font-size: 0.85rem; opacity: 0.8; min-height: 1.3em; }
44 .hint { font-size: 0.8rem; opacity: 0.6; }
45 label { display: inline-flex; gap: 0.35rem; align-items: center; }
46 .config-grid {
47 display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
48 gap: 0.35rem 1.5rem; font-size: 0.85rem;
49 }
50 .config-grid label { display: flex; justify-content: space-between; }
51 .algo-row {
52 display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap;
53 padding: 0.25rem 0.4rem; border-radius: 6px; font-size: 0.9rem;
54 }
55 .algo-row:nth-child(odd) { background: rgba(128, 128, 128, 0.09); }
56 .algo-row .tag { font-size: 0.7rem; opacity: 0.55; border: 1px solid currentColor; border-radius: 3px; padding: 0 0.3rem; }
57 .algo-row .spacer { flex: 1; }
58 textarea.source {
59 width: 100%; min-height: 22rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
60 font-size: 0.8rem; line-height: 1.4; tab-size: 4; box-sizing: border-box;
61 }
62 table.results { border-collapse: collapse; font-size: 0.85rem; font-variant-numeric: tabular-nums; }
63 table.results th, table.results td { padding: 0.25rem 0.7rem; text-align: right; border-bottom: 1px solid rgba(128,128,128,0.25); }
64 table.results th:first-child, table.results td:first-child { text-align: left; }
65 table.confusion { border-collapse: collapse; font-size: 0.78rem; font-variant-numeric: tabular-nums; margin: 0.4rem 0 0.8rem; }
66 table.confusion th, table.confusion td { padding: 0.15rem 0.55rem; text-align: right; border: 1px solid rgba(128,128,128,0.2); }
67 table.confusion th { font-weight: 600; }
68 table.confusion td.diag { background: rgba(61, 187, 110, 0.15); }
69 table.confusion td.bad { background: rgba(228, 87, 61, 0.14); }
70 .err { color: #e4573d; font-size: 0.85rem; white-space: pre-wrap; }
71 progress { width: 14rem; }
72 </style>
73 </head>
74 <body>
75 <h1>audio-spike-sorting</h1>
76 <p>
77 Spike sorting for audio events: record examples of each event type,
78 synthesize recordings with known ground truth, run MATLAB-syntax
79 sorting algorithms in the browser via numbl, and score the errors.
80 </p>
82 <h2>Event types <span class="cost">record hundreds of examples of each; keep events at least 200 ms apart</span></h2>
83 <div id="type-list"></div>
84 <div class="bar">
85 <input id="type-name" type="text" placeholder="new event type (e.g. table tap)" />
86 <button id="add-type">Add event type</button>
87 <div class="meter" id="meter" hidden><div></div></div>
88 <span style="flex:1"></span>
89 <button id="load-demo" title="three synthetic event types, no microphone needed">Load demo data</button>
90 </div>
92 <div id="clip-editor" class="panel" hidden>
93 <div class="bar">
94 <strong id="clip-title"></strong>
95 <select id="clip-select"></select>
96 <span class="hint">click a marker to audition &middot; drag or shift-click to select several &middot; X toggles exclusion</span>
97 </div>
98 <canvas id="clip-wave" class="wave"></canvas>
99 <div class="bar">
100 <button id="clip-play">&#9654; Play</button>
101 <label>sensitivity <input id="clip-sens" type="range" min="0" max="1" step="0.01" value="0.6" /></label>
102 <button id="clip-exclude" disabled>Exclude</button>
103 <span id="clip-count" class="hint"></span>
104 <span style="flex:1"></span>
105 <button id="clip-delete">Delete clip</button>
106 <button id="clip-done">Done</button>
107 </div>
108 </div>
110 <h2>Benchmark configuration <span class="cost">each run synthesizes a fresh dataset from seed + run index</span></h2>
111 <div class="config-grid" id="config-grid"></div>
113 <h2>Algorithms <span class="cost">function [times, labels] = name(x, fs, training) &middot; MATLAB syntax, executed by numbl</span></h2>
114 <div id="algo-list"></div>
115 <div class="bar">
116 <button id="add-algo">Add algorithm</button>
117 </div>
119 <div id="algo-editor" class="panel" hidden>
120 <div class="bar">
121 <input id="algo-name" type="text" style="font-weight:600" />
122 <span class="hint">the file must define one function taking (x, fs, training) and returning [times, labels]</span>
123 <span style="flex:1"></span>
124 <button id="algo-reset" hidden>Reset to built-in</button>
125 <button id="algo-remove">Delete</button>
126 <button id="algo-close">Close</button>
127 </div>
128 <textarea id="algo-source" class="source" spellcheck="false"></textarea>
129 </div>
131 <h2>Run <span class="cost">every enabled algorithm on every synthesized dataset</span></h2>
132 <div class="bar">
133 <button id="run-bench">Run benchmark</button>
134 <button id="cancel-bench" hidden>Cancel</button>
135 <progress id="bench-progress" hidden></progress>
136 <span id="bench-label" class="hint"></span>
137 </div>
138 <div id="results"></div>
140 <h2>Explore <span class="cost">wheel zooms &middot; drag pans &middot; click auditions &middot; shift-drag selects an interval</span></h2>
141 <div class="bar">
142 <label>run <select id="explore-run"></select></label>
143 <label>algorithm <select id="explore-algo"></select></label>
144 <button id="explore-play" disabled>&#9654; Play</button>
145 <span id="explore-hover" class="hint"></span>
146 </div>
147 <canvas id="explorer" class="explorer"></canvas>
149 <div class="bar" style="margin-top:1.5rem">
150 <button id="export">Export project</button>
151 <button id="import">Import project</button>
152 <input id="import-file" type="file" accept=".json,application/json" hidden />
153 <span style="flex:1"></span>
154 <button id="reset-all">Forget everything</button>
155 </div>
156 <p id="status"></p>
157 <script type="module" src="./src/main.ts"></script>
158 </body>
159</html>
↑↓ move↵openescclose