/ proof-of-concept / instrawave-compose
Sign in
proof-of-concept / instrawave-compose
Classify takes with numbl algorithms by default (silence_gap_knn from audio-spike-sorting, noise-floor adapted), with a custom-algorithm editor and the feature kNN kept as an option
Jeremy Magland <jmagland@flatironinstitute.org> committed commit b11b069ec992 parent b2d9895 Browse files
17 changed files+2478−66
README.mdmodified+23−7View file
@@ -33,13 +33,29 @@ rate, attack and decay times. Loudness is deliberately kept out of this
3333 vector; it is expression, not identity, so playing softly does not change
3434 which voice a hit lands on.
3535
36-A take goes through the same detector, and each hit is classified by
37-distance-weighted nearest neighbours over the pooled training set,
38-z-scored per dimension. The timeline shows one lane per voice with a
39-marker per hit, bar height showing strength. The classifier will sometimes
40-be wrong, and the fix is one gesture: drag the marker to the right lane.
41-"Learn" folds the corrected hit back into that voice's training set, so
42-the labelling improves with use; there is no model to refit.
36+A take is detected and classified by a MATLAB-syntax algorithm executed
37+in the browser by [numbl](https://numbl.org), with the same interface the
38+[audio-spike-sorting](../audio-spike-sorting) benchmark uses
39+(`[times, labels] = name(x, fs, training)`, everything at 16 kHz), so an
40+algorithm developed and scored against that benchmark drops in here
41+unchanged. The default is silence_gap_knn from the benchmark, with one
42+adaptation: its segmentation threshold is raised from a fraction of the
43+recording peak to a robust noise-floor estimate, since a microphone's
44+background is never digitally silent. A classifier selector in the take
45+bar switches between the default (source editable, resettable), a custom
46+numbl algorithm pasted into the same editor, and the original in-app
47+method: spectral-flux onset detection plus distance-weighted nearest
48+neighbours over the pooled per-hit feature vectors. The training the
49+numbl algorithms see is the raw snippets cut from the voices' clips
50+(20 ms before each kept onset to 180 ms after); hits learned from takes
51+carry only features, so they inform the built-in kNN but not the numbl
52+path.
53+
54+The timeline shows one lane per voice with a marker per hit, bar height
55+showing strength. The classifier will sometimes be wrong, and the fix is
56+one gesture: drag the marker to the right lane. "Learn" folds the
57+corrected hit back into that voice's feature training set, so the
58+built-in kNN improves with use; there is no model to refit.
4359
4460 Everything persists in the browser: voices and their training, the take
4561 (audio and edited events), per-instrument geometry, slider positions, and
index.htmlmodified+25−1View file
@@ -43,6 +43,11 @@
4343 .voice-row progress { width: 6rem; }
4444 .voice-row input.vol { width: 5rem; }
4545 #iwedit { border: 1px solid rgba(128,128,128,0.35); border-radius: 8px; padding: 0.6rem 0.8rem; margin: 0.5rem 0; }
46+ #clf-editor { border: 1px solid rgba(128,128,128,0.35); border-radius: 8px; padding: 0.6rem 0.8rem; margin: 0.5rem 0; }
47+ #clf-editor textarea {
48+ width: 100%; min-height: 20rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
49+ font-size: 0.8rem; line-height: 1.4; tab-size: 4; box-sizing: border-box;
50+ }
4651 .iwedit-grid { display: grid; grid-template-columns: minmax(0, 1fr) 24rem; gap: 0 1.5rem; align-items: start; }
4752 @media (max-width: 56rem) { .iwedit-grid { grid-template-columns: 1fr; } }
4853 .params { display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: 0.3rem 1.5rem; }
@@ -102,12 +107,31 @@
102107 <div class="bar">
103108 <button id="take-record">&#9679; Record take</button>
104109 <div class="meter" id="meter" hidden><div></div></div>
105- <label>sensitivity <input id="take-sens" type="range" min="0" max="1" step="0.01" value="0.6" /></label>
110+ <label>classifier
111+ <select id="clf-method">
112+ <option value="builtin">silence_gap_knn (numbl)</option>
113+ <option value="custom">custom (numbl)</option>
114+ <option value="legacy">feature kNN (built-in)</option>
115+ </select>
116+ </label>
117+ <button id="clf-edit">algorithm&hellip;</button>
118+ <span id="take-sens-wrap"><label>sensitivity <input id="take-sens" type="range" min="0" max="1" step="0.01" value="0.6" /></label></span>
106119 <button id="take-redetect" disabled>Re-detect &amp; classify</button>
107120 <span style="flex:1"></span>
108121 <button id="take-play" disabled>Play</button>
109122 <button id="take-save" disabled>Save .wav</button>
110123 </div>
124+ <div id="clf-editor" hidden>
125+ <div class="bar">
126+ <strong id="clf-title"></strong>
127+ <span class="hint">function [times, labels] = name(x, fs, training) &middot; the audio-spike-sorting interface, so an algorithm developed there drops in unchanged</span>
128+ <span style="flex:1"></span>
129+ <button id="clf-reset" hidden>Reset to default</button>
130+ <button id="clf-close">Close</button>
131+ </div>
132+ <textarea id="clf-source" spellcheck="false"></textarea>
133+ </div>
134+
111135 <div id="iwedit" hidden>
112136 <div class="bar">
113137 <strong id="iwedit-title"></strong>
package-lock.jsonmodified+1807−48View file
@@ -9,7 +9,8 @@
99 "version": "0.1.0",
1010 "license": "Apache-2.0",
1111 "dependencies": {
12- "instrawave": "file:../instrawave"
12+ "instrawave": "file:../instrawave",
13+ "numbl": "^0.4.19"
1314 },
1415 "devDependencies": {
1516 "@types/node": "^26.1.1",
@@ -39,6 +40,15 @@
3940 "webgpu": "^0.4.0"
4041 }
4142 },
43+ "node_modules/@babel/runtime": {
44+ "version": "7.29.7",
45+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
46+ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
47+ "license": "MIT",
48+ "engines": {
49+ "node": ">=6.9.0"
50+ }
51+ },
4252 "node_modules/@esbuild/aix-ppc64": {
4353 "version": "0.21.5",
4454 "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -1129,11 +1139,52 @@
11291139 "win32"
11301140 ]
11311141 },
1142+ "node_modules/@types/debug": {
1143+ "version": "4.1.13",
1144+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
1145+ "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
1146+ "license": "MIT",
1147+ "dependencies": {
1148+ "@types/ms": "*"
1149+ }
1150+ },
11321151 "node_modules/@types/estree": {
11331152 "version": "1.0.9",
11341153 "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
11351154 "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
1136- "dev": true,
1155+ "license": "MIT"
1156+ },
1157+ "node_modules/@types/estree-jsx": {
1158+ "version": "1.0.5",
1159+ "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
1160+ "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
1161+ "license": "MIT",
1162+ "dependencies": {
1163+ "@types/estree": "*"
1164+ }
1165+ },
1166+ "node_modules/@types/hast": {
1167+ "version": "3.0.5",
1168+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz",
1169+ "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==",
1170+ "license": "MIT",
1171+ "dependencies": {
1172+ "@types/unist": "*"
1173+ }
1174+ },
1175+ "node_modules/@types/mdast": {
1176+ "version": "4.0.4",
1177+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
1178+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
1179+ "license": "MIT",
1180+ "dependencies": {
1181+ "@types/unist": "*"
1182+ }
1183+ },
1184+ "node_modules/@types/ms": {
1185+ "version": "2.1.0",
1186+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
1187+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
11371188 "license": "MIT"
11381189 },
11391190 "node_modules/@types/node": {
@@ -1146,6 +1197,34 @@
11461197 "undici-types": "~8.3.0"
11471198 }
11481199 },
1200+ "node_modules/@types/prismjs": {
1201+ "version": "1.26.6",
1202+ "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz",
1203+ "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==",
1204+ "license": "MIT"
1205+ },
1206+ "node_modules/@types/react": {
1207+ "version": "19.2.18",
1208+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
1209+ "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
1210+ "license": "MIT",
1211+ "peer": true,
1212+ "dependencies": {
1213+ "csstype": "^3.2.2"
1214+ }
1215+ },
1216+ "node_modules/@types/unist": {
1217+ "version": "3.0.3",
1218+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
1219+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
1220+ "license": "MIT"
1221+ },
1222+ "node_modules/@ungap/structured-clone": {
1223+ "version": "1.4.0",
1224+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz",
1225+ "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==",
1226+ "license": "ISC"
1227+ },
11491228 "node_modules/@webgpu/types": {
11501229 "version": "0.1.72",
11511230 "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.72.tgz",
@@ -1153,6 +1232,16 @@
11531232 "dev": true,
11541233 "license": "BSD-3-Clause"
11551234 },
1235+ "node_modules/bail": {
1236+ "version": "2.0.2",
1237+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
1238+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
1239+ "license": "MIT",
1240+ "funding": {
1241+ "type": "github",
1242+ "url": "https://github.com/sponsors/wooorm"
1243+ }
1244+ },
11561245 "node_modules/cac": {
11571246 "version": "7.0.0",
11581247 "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz",
@@ -1163,6 +1252,112 @@
11631252 "node": ">=20.19.0"
11641253 }
11651254 },
1255+ "node_modules/ccount": {
1256+ "version": "2.0.1",
1257+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
1258+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
1259+ "license": "MIT",
1260+ "funding": {
1261+ "type": "github",
1262+ "url": "https://github.com/sponsors/wooorm"
1263+ }
1264+ },
1265+ "node_modules/character-entities": {
1266+ "version": "2.0.2",
1267+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
1268+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
1269+ "license": "MIT",
1270+ "funding": {
1271+ "type": "github",
1272+ "url": "https://github.com/sponsors/wooorm"
1273+ }
1274+ },
1275+ "node_modules/character-entities-html4": {
1276+ "version": "2.1.0",
1277+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
1278+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
1279+ "license": "MIT",
1280+ "funding": {
1281+ "type": "github",
1282+ "url": "https://github.com/sponsors/wooorm"
1283+ }
1284+ },
1285+ "node_modules/character-entities-legacy": {
1286+ "version": "3.0.0",
1287+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
1288+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
1289+ "license": "MIT",
1290+ "funding": {
1291+ "type": "github",
1292+ "url": "https://github.com/sponsors/wooorm"
1293+ }
1294+ },
1295+ "node_modules/character-reference-invalid": {
1296+ "version": "2.0.1",
1297+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
1298+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
1299+ "license": "MIT",
1300+ "funding": {
1301+ "type": "github",
1302+ "url": "https://github.com/sponsors/wooorm"
1303+ }
1304+ },
1305+ "node_modules/comma-separated-tokens": {
1306+ "version": "2.0.3",
1307+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
1308+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
1309+ "license": "MIT",
1310+ "funding": {
1311+ "type": "github",
1312+ "url": "https://github.com/sponsors/wooorm"
1313+ }
1314+ },
1315+ "node_modules/csstype": {
1316+ "version": "3.2.3",
1317+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
1318+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
1319+ "license": "MIT",
1320+ "peer": true
1321+ },
1322+ "node_modules/debug": {
1323+ "version": "4.4.3",
1324+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
1325+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
1326+ "license": "MIT",
1327+ "dependencies": {
1328+ "ms": "^2.1.3"
1329+ },
1330+ "engines": {
1331+ "node": ">=6.0"
1332+ },
1333+ "peerDependenciesMeta": {
1334+ "supports-color": {
1335+ "optional": true
1336+ }
1337+ }
1338+ },
1339+ "node_modules/decode-named-character-reference": {
1340+ "version": "1.3.0",
1341+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
1342+ "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==",
1343+ "license": "MIT",
1344+ "dependencies": {
1345+ "character-entities": "^2.0.0"
1346+ },
1347+ "funding": {
1348+ "type": "github",
1349+ "url": "https://github.com/sponsors/wooorm"
1350+ }
1351+ },
1352+ "node_modules/dequal": {
1353+ "version": "2.0.3",
1354+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
1355+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
1356+ "license": "MIT",
1357+ "engines": {
1358+ "node": ">=6"
1359+ }
1360+ },
11661361 "node_modules/detect-libc": {
11671362 "version": "2.1.2",
11681363 "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -1173,6 +1368,19 @@
11731368 "node": ">=8"
11741369 }
11751370 },
1371+ "node_modules/devlop": {
1372+ "version": "1.1.0",
1373+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
1374+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
1375+ "license": "MIT",
1376+ "dependencies": {
1377+ "dequal": "^2.0.0"
1378+ },
1379+ "funding": {
1380+ "type": "github",
1381+ "url": "https://github.com/sponsors/wooorm"
1382+ }
1383+ },
11761384 "node_modules/es-module-lexer": {
11771385 "version": "2.3.2",
11781386 "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz",
@@ -1219,6 +1427,47 @@
12191427 "@esbuild/win32-x64": "0.21.5"
12201428 }
12211429 },
1430+ "node_modules/escape-string-regexp": {
1431+ "version": "5.0.0",
1432+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
1433+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
1434+ "license": "MIT",
1435+ "engines": {
1436+ "node": ">=12"
1437+ },
1438+ "funding": {
1439+ "url": "https://github.com/sponsors/sindresorhus"
1440+ }
1441+ },
1442+ "node_modules/estree-util-is-identifier-name": {
1443+ "version": "3.0.0",
1444+ "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
1445+ "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
1446+ "license": "MIT",
1447+ "funding": {
1448+ "type": "opencollective",
1449+ "url": "https://opencollective.com/unified"
1450+ }
1451+ },
1452+ "node_modules/extend": {
1453+ "version": "3.0.2",
1454+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
1455+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
1456+ "license": "MIT"
1457+ },
1458+ "node_modules/fault": {
1459+ "version": "1.0.4",
1460+ "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
1461+ "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
1462+ "license": "MIT",
1463+ "dependencies": {
1464+ "format": "^0.2.0"
1465+ },
1466+ "funding": {
1467+ "type": "github",
1468+ "url": "https://github.com/sponsors/wooorm"
1469+ }
1470+ },
12221471 "node_modules/fdir": {
12231472 "version": "6.5.0",
12241473 "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -1237,6 +1486,20 @@
12371486 }
12381487 }
12391488 },
1489+ "node_modules/fflate": {
1490+ "version": "0.8.3",
1491+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
1492+ "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
1493+ "license": "MIT"
1494+ },
1495+ "node_modules/format": {
1496+ "version": "0.2.2",
1497+ "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
1498+ "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
1499+ "engines": {
1500+ "node": ">=0.4.x"
1501+ }
1502+ },
12401503 "node_modules/fsevents": {
12411504 "version": "2.3.3",
12421505 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -1252,10 +1515,184 @@
12521515 "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
12531516 }
12541517 },
1518+ "node_modules/h5wasm": {
1519+ "version": "0.10.3",
1520+ "resolved": "https://registry.npmjs.org/h5wasm/-/h5wasm-0.10.3.tgz",
1521+ "integrity": "sha512-W4Jy5ExtX/VNbyD8GdOBckDuj6AL16TemppVNxZsV3rJZEWCv2sxlCzOttZLer3zkMttbDYsWHl0qt1z3Bln+Q==",
1522+ "license": "SEE LICENSE IN LICENSE.txt"
1523+ },
1524+ "node_modules/hast-util-parse-selector": {
1525+ "version": "4.0.0",
1526+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
1527+ "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
1528+ "license": "MIT",
1529+ "dependencies": {
1530+ "@types/hast": "^3.0.0"
1531+ },
1532+ "funding": {
1533+ "type": "opencollective",
1534+ "url": "https://opencollective.com/unified"
1535+ }
1536+ },
1537+ "node_modules/hast-util-to-jsx-runtime": {
1538+ "version": "2.3.6",
1539+ "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
1540+ "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
1541+ "license": "MIT",
1542+ "dependencies": {
1543+ "@types/estree": "^1.0.0",
1544+ "@types/hast": "^3.0.0",
1545+ "@types/unist": "^3.0.0",
1546+ "comma-separated-tokens": "^2.0.0",
1547+ "devlop": "^1.0.0",
1548+ "estree-util-is-identifier-name": "^3.0.0",
1549+ "hast-util-whitespace": "^3.0.0",
1550+ "mdast-util-mdx-expression": "^2.0.0",
1551+ "mdast-util-mdx-jsx": "^3.0.0",
1552+ "mdast-util-mdxjs-esm": "^2.0.0",
1553+ "property-information": "^7.0.0",
1554+ "space-separated-tokens": "^2.0.0",
1555+ "style-to-js": "^1.0.0",
1556+ "unist-util-position": "^5.0.0",
1557+ "vfile-message": "^4.0.0"
1558+ },
1559+ "funding": {
1560+ "type": "opencollective",
1561+ "url": "https://opencollective.com/unified"
1562+ }
1563+ },
1564+ "node_modules/hast-util-whitespace": {
1565+ "version": "3.0.0",
1566+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
1567+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
1568+ "license": "MIT",
1569+ "dependencies": {
1570+ "@types/hast": "^3.0.0"
1571+ },
1572+ "funding": {
1573+ "type": "opencollective",
1574+ "url": "https://opencollective.com/unified"
1575+ }
1576+ },
1577+ "node_modules/hastscript": {
1578+ "version": "9.0.1",
1579+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz",
1580+ "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==",
1581+ "license": "MIT",
1582+ "dependencies": {
1583+ "@types/hast": "^3.0.0",
1584+ "comma-separated-tokens": "^2.0.0",
1585+ "hast-util-parse-selector": "^4.0.0",
1586+ "property-information": "^7.0.0",
1587+ "space-separated-tokens": "^2.0.0"
1588+ },
1589+ "funding": {
1590+ "type": "opencollective",
1591+ "url": "https://opencollective.com/unified"
1592+ }
1593+ },
1594+ "node_modules/highlight.js": {
1595+ "version": "10.7.3",
1596+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
1597+ "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
1598+ "license": "BSD-3-Clause",
1599+ "engines": {
1600+ "node": "*"
1601+ }
1602+ },
1603+ "node_modules/highlightjs-vue": {
1604+ "version": "1.0.0",
1605+ "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz",
1606+ "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==",
1607+ "license": "CC0-1.0"
1608+ },
1609+ "node_modules/html-url-attributes": {
1610+ "version": "3.0.1",
1611+ "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
1612+ "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
1613+ "license": "MIT",
1614+ "funding": {
1615+ "type": "opencollective",
1616+ "url": "https://opencollective.com/unified"
1617+ }
1618+ },
1619+ "node_modules/inline-style-parser": {
1620+ "version": "0.2.7",
1621+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
1622+ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
1623+ "license": "MIT"
1624+ },
12551625 "node_modules/instrawave": {
12561626 "resolved": "../instrawave",
12571627 "link": true
12581628 },
1629+ "node_modules/is-alphabetical": {
1630+ "version": "2.0.1",
1631+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
1632+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
1633+ "license": "MIT",
1634+ "funding": {
1635+ "type": "github",
1636+ "url": "https://github.com/sponsors/wooorm"
1637+ }
1638+ },
1639+ "node_modules/is-alphanumerical": {
1640+ "version": "2.0.1",
1641+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
1642+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
1643+ "license": "MIT",
1644+ "dependencies": {
1645+ "is-alphabetical": "^2.0.0",
1646+ "is-decimal": "^2.0.0"
1647+ },
1648+ "funding": {
1649+ "type": "github",
1650+ "url": "https://github.com/sponsors/wooorm"
1651+ }
1652+ },
1653+ "node_modules/is-decimal": {
1654+ "version": "2.0.1",
1655+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
1656+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
1657+ "license": "MIT",
1658+ "funding": {
1659+ "type": "github",
1660+ "url": "https://github.com/sponsors/wooorm"
1661+ }
1662+ },
1663+ "node_modules/is-hexadecimal": {
1664+ "version": "2.0.1",
1665+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
1666+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
1667+ "license": "MIT",
1668+ "funding": {
1669+ "type": "github",
1670+ "url": "https://github.com/sponsors/wooorm"
1671+ }
1672+ },
1673+ "node_modules/is-plain-obj": {
1674+ "version": "4.1.0",
1675+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
1676+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
1677+ "license": "MIT",
1678+ "engines": {
1679+ "node": ">=12"
1680+ },
1681+ "funding": {
1682+ "url": "https://github.com/sponsors/sindresorhus"
1683+ }
1684+ },
1685+ "node_modules/koffi": {
1686+ "version": "2.16.3",
1687+ "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.3.tgz",
1688+ "integrity": "sha512-E9y1AsgYGlaxMhcZzHr8y96QF2U5XzA12GGVAfbWqIubTwPNMXQarfBzePNXHe0xtIEtNd6ifAv3GAKYGUeBAQ==",
1689+ "hasInstallScript": true,
1690+ "license": "MIT",
1691+ "optional": true,
1692+ "funding": {
1693+ "url": "https://liberapay.com/Koromix"
1694+ }
1695+ },
12591696 "node_modules/lightningcss": {
12601697 "version": "1.33.0",
12611698 "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
@@ -1529,64 +1966,1026 @@
15291966 "url": "https://opencollective.com/parcel"
15301967 }
15311968 },
1532- "node_modules/nanoid": {
1533- "version": "3.3.18",
1534- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
1535- "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
1536- "dev": true,
1537- "funding": [
1538- {
1539- "type": "github",
1540- "url": "https://github.com/sponsors/ai"
1541- }
1542- ],
1969+ "node_modules/longest-streak": {
1970+ "version": "3.1.0",
1971+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
1972+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
15431973 "license": "MIT",
1544- "bin": {
1545- "nanoid": "bin/nanoid.cjs"
1974+ "funding": {
1975+ "type": "github",
1976+ "url": "https://github.com/sponsors/wooorm"
1977+ }
1978+ },
1979+ "node_modules/lowlight": {
1980+ "version": "1.20.0",
1981+ "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
1982+ "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
1983+ "license": "MIT",
1984+ "dependencies": {
1985+ "fault": "^1.0.0",
1986+ "highlight.js": "~10.7.0"
15461987 },
1547- "engines": {
1548- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1988+ "funding": {
1989+ "type": "github",
1990+ "url": "https://github.com/sponsors/wooorm"
15491991 }
15501992 },
1551- "node_modules/obug": {
1552- "version": "2.1.4",
1553- "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
1554- "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==",
1555- "dev": true,
1556- "funding": [
1557- "https://github.com/sponsors/sxzz",
1558- "https://opencollective.com/debug"
1559- ],
1993+ "node_modules/markdown-table": {
1994+ "version": "3.0.4",
1995+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
1996+ "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
15601997 "license": "MIT",
1561- "engines": {
1562- "node": ">=12.20.0"
1998+ "funding": {
1999+ "type": "github",
2000+ "url": "https://github.com/sponsors/wooorm"
15632001 }
15642002 },
1565- "node_modules/pathe": {
1566- "version": "2.0.3",
1567- "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
1568- "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
1569- "dev": true,
1570- "license": "MIT"
2003+ "node_modules/mdast-util-find-and-replace": {
2004+ "version": "3.0.2",
2005+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
2006+ "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
2007+ "license": "MIT",
2008+ "dependencies": {
2009+ "@types/mdast": "^4.0.0",
2010+ "escape-string-regexp": "^5.0.0",
2011+ "unist-util-is": "^6.0.0",
2012+ "unist-util-visit-parents": "^6.0.0"
2013+ },
2014+ "funding": {
2015+ "type": "opencollective",
2016+ "url": "https://opencollective.com/unified"
2017+ }
15712018 },
1572- "node_modules/picocolors": {
1573- "version": "1.1.1",
1574- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1575- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1576- "dev": true,
1577- "license": "ISC"
2019+ "node_modules/mdast-util-from-markdown": {
2020+ "version": "2.0.3",
2021+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
2022+ "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
2023+ "license": "MIT",
2024+ "dependencies": {
2025+ "@types/mdast": "^4.0.0",
2026+ "@types/unist": "^3.0.0",
2027+ "decode-named-character-reference": "^1.0.0",
2028+ "devlop": "^1.0.0",
2029+ "mdast-util-to-string": "^4.0.0",
2030+ "micromark": "^4.0.0",
2031+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
2032+ "micromark-util-decode-string": "^2.0.0",
2033+ "micromark-util-normalize-identifier": "^2.0.0",
2034+ "micromark-util-symbol": "^2.0.0",
2035+ "micromark-util-types": "^2.0.0",
2036+ "unist-util-stringify-position": "^4.0.0"
2037+ },
2038+ "funding": {
2039+ "type": "opencollective",
2040+ "url": "https://opencollective.com/unified"
2041+ }
15782042 },
1579- "node_modules/picomatch": {
1580- "version": "4.0.7",
1581- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
1582- "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
1583- "dev": true,
2043+ "node_modules/mdast-util-gfm": {
2044+ "version": "3.1.0",
2045+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
2046+ "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
15842047 "license": "MIT",
1585- "engines": {
1586- "node": ">=12"
2048+ "dependencies": {
2049+ "mdast-util-from-markdown": "^2.0.0",
2050+ "mdast-util-gfm-autolink-literal": "^2.0.0",
2051+ "mdast-util-gfm-footnote": "^2.0.0",
2052+ "mdast-util-gfm-strikethrough": "^2.0.0",
2053+ "mdast-util-gfm-table": "^2.0.0",
2054+ "mdast-util-gfm-task-list-item": "^2.0.0",
2055+ "mdast-util-to-markdown": "^2.0.0"
15872056 },
15882057 "funding": {
1589- "url": "https://github.com/sponsors/jonschlinkert"
2058+ "type": "opencollective",
2059+ "url": "https://opencollective.com/unified"
2060+ }
2061+ },
2062+ "node_modules/mdast-util-gfm-autolink-literal": {
2063+ "version": "2.0.1",
2064+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
2065+ "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
2066+ "license": "MIT",
2067+ "dependencies": {
2068+ "@types/mdast": "^4.0.0",
2069+ "ccount": "^2.0.0",
2070+ "devlop": "^1.0.0",
2071+ "mdast-util-find-and-replace": "^3.0.0",
2072+ "micromark-util-character": "^2.0.0"
2073+ },
2074+ "funding": {
2075+ "type": "opencollective",
2076+ "url": "https://opencollective.com/unified"
2077+ }
2078+ },
2079+ "node_modules/mdast-util-gfm-footnote": {
2080+ "version": "2.1.0",
2081+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
2082+ "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
2083+ "license": "MIT",
2084+ "dependencies": {
2085+ "@types/mdast": "^4.0.0",
2086+ "devlop": "^1.1.0",
2087+ "mdast-util-from-markdown": "^2.0.0",
2088+ "mdast-util-to-markdown": "^2.0.0",
2089+ "micromark-util-normalize-identifier": "^2.0.0"
2090+ },
2091+ "funding": {
2092+ "type": "opencollective",
2093+ "url": "https://opencollective.com/unified"
2094+ }
2095+ },
2096+ "node_modules/mdast-util-gfm-strikethrough": {
2097+ "version": "2.0.0",
2098+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
2099+ "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
2100+ "license": "MIT",
2101+ "dependencies": {
2102+ "@types/mdast": "^4.0.0",
2103+ "mdast-util-from-markdown": "^2.0.0",
2104+ "mdast-util-to-markdown": "^2.0.0"
2105+ },
2106+ "funding": {
2107+ "type": "opencollective",
2108+ "url": "https://opencollective.com/unified"
2109+ }
2110+ },
2111+ "node_modules/mdast-util-gfm-table": {
2112+ "version": "2.0.0",
2113+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
2114+ "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
2115+ "license": "MIT",
2116+ "dependencies": {
2117+ "@types/mdast": "^4.0.0",
2118+ "devlop": "^1.0.0",
2119+ "markdown-table": "^3.0.0",
2120+ "mdast-util-from-markdown": "^2.0.0",
2121+ "mdast-util-to-markdown": "^2.0.0"
2122+ },
2123+ "funding": {
2124+ "type": "opencollective",
2125+ "url": "https://opencollective.com/unified"
2126+ }
2127+ },
2128+ "node_modules/mdast-util-gfm-task-list-item": {
2129+ "version": "2.0.0",
2130+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
2131+ "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
2132+ "license": "MIT",
2133+ "dependencies": {
2134+ "@types/mdast": "^4.0.0",
2135+ "devlop": "^1.0.0",
2136+ "mdast-util-from-markdown": "^2.0.0",
2137+ "mdast-util-to-markdown": "^2.0.0"
2138+ },
2139+ "funding": {
2140+ "type": "opencollective",
2141+ "url": "https://opencollective.com/unified"
2142+ }
2143+ },
2144+ "node_modules/mdast-util-mdx-expression": {
2145+ "version": "2.0.1",
2146+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
2147+ "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
2148+ "license": "MIT",
2149+ "dependencies": {
2150+ "@types/estree-jsx": "^1.0.0",
2151+ "@types/hast": "^3.0.0",
2152+ "@types/mdast": "^4.0.0",
2153+ "devlop": "^1.0.0",
2154+ "mdast-util-from-markdown": "^2.0.0",
2155+ "mdast-util-to-markdown": "^2.0.0"
2156+ },
2157+ "funding": {
2158+ "type": "opencollective",
2159+ "url": "https://opencollective.com/unified"
2160+ }
2161+ },
2162+ "node_modules/mdast-util-mdx-jsx": {
2163+ "version": "3.2.0",
2164+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
2165+ "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
2166+ "license": "MIT",
2167+ "dependencies": {
2168+ "@types/estree-jsx": "^1.0.0",
2169+ "@types/hast": "^3.0.0",
2170+ "@types/mdast": "^4.0.0",
2171+ "@types/unist": "^3.0.0",
2172+ "ccount": "^2.0.0",
2173+ "devlop": "^1.1.0",
2174+ "mdast-util-from-markdown": "^2.0.0",
2175+ "mdast-util-to-markdown": "^2.0.0",
2176+ "parse-entities": "^4.0.0",
2177+ "stringify-entities": "^4.0.0",
2178+ "unist-util-stringify-position": "^4.0.0",
2179+ "vfile-message": "^4.0.0"
2180+ },
2181+ "funding": {
2182+ "type": "opencollective",
2183+ "url": "https://opencollective.com/unified"
2184+ }
2185+ },
2186+ "node_modules/mdast-util-mdxjs-esm": {
2187+ "version": "2.0.1",
2188+ "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
2189+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
2190+ "license": "MIT",
2191+ "dependencies": {
2192+ "@types/estree-jsx": "^1.0.0",
2193+ "@types/hast": "^3.0.0",
2194+ "@types/mdast": "^4.0.0",
2195+ "devlop": "^1.0.0",
2196+ "mdast-util-from-markdown": "^2.0.0",
2197+ "mdast-util-to-markdown": "^2.0.0"
2198+ },
2199+ "funding": {
2200+ "type": "opencollective",
2201+ "url": "https://opencollective.com/unified"
2202+ }
2203+ },
2204+ "node_modules/mdast-util-phrasing": {
2205+ "version": "4.1.0",
2206+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
2207+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
2208+ "license": "MIT",
2209+ "dependencies": {
2210+ "@types/mdast": "^4.0.0",
2211+ "unist-util-is": "^6.0.0"
2212+ },
2213+ "funding": {
2214+ "type": "opencollective",
2215+ "url": "https://opencollective.com/unified"
2216+ }
2217+ },
2218+ "node_modules/mdast-util-to-hast": {
2219+ "version": "13.2.1",
2220+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
2221+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
2222+ "license": "MIT",
2223+ "dependencies": {
2224+ "@types/hast": "^3.0.0",
2225+ "@types/mdast": "^4.0.0",
2226+ "@ungap/structured-clone": "^1.0.0",
2227+ "devlop": "^1.0.0",
2228+ "micromark-util-sanitize-uri": "^2.0.0",
2229+ "trim-lines": "^3.0.0",
2230+ "unist-util-position": "^5.0.0",
2231+ "unist-util-visit": "^5.0.0",
2232+ "vfile": "^6.0.0"
2233+ },
2234+ "funding": {
2235+ "type": "opencollective",
2236+ "url": "https://opencollective.com/unified"
2237+ }
2238+ },
2239+ "node_modules/mdast-util-to-markdown": {
2240+ "version": "2.1.2",
2241+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
2242+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
2243+ "license": "MIT",
2244+ "dependencies": {
2245+ "@types/mdast": "^4.0.0",
2246+ "@types/unist": "^3.0.0",
2247+ "longest-streak": "^3.0.0",
2248+ "mdast-util-phrasing": "^4.0.0",
2249+ "mdast-util-to-string": "^4.0.0",
2250+ "micromark-util-classify-character": "^2.0.0",
2251+ "micromark-util-decode-string": "^2.0.0",
2252+ "unist-util-visit": "^5.0.0",
2253+ "zwitch": "^2.0.0"
2254+ },
2255+ "funding": {
2256+ "type": "opencollective",
2257+ "url": "https://opencollective.com/unified"
2258+ }
2259+ },
2260+ "node_modules/mdast-util-to-string": {
2261+ "version": "4.0.0",
2262+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
2263+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
2264+ "license": "MIT",
2265+ "dependencies": {
2266+ "@types/mdast": "^4.0.0"
2267+ },
2268+ "funding": {
2269+ "type": "opencollective",
2270+ "url": "https://opencollective.com/unified"
2271+ }
2272+ },
2273+ "node_modules/micromark": {
2274+ "version": "4.0.2",
2275+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
2276+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
2277+ "funding": [
2278+ {
2279+ "type": "GitHub Sponsors",
2280+ "url": "https://github.com/sponsors/unifiedjs"
2281+ },
2282+ {
2283+ "type": "OpenCollective",
2284+ "url": "https://opencollective.com/unified"
2285+ }
2286+ ],
2287+ "license": "MIT",
2288+ "dependencies": {
2289+ "@types/debug": "^4.0.0",
2290+ "debug": "^4.0.0",
2291+ "decode-named-character-reference": "^1.0.0",
2292+ "devlop": "^1.0.0",
2293+ "micromark-core-commonmark": "^2.0.0",
2294+ "micromark-factory-space": "^2.0.0",
2295+ "micromark-util-character": "^2.0.0",
2296+ "micromark-util-chunked": "^2.0.0",
2297+ "micromark-util-combine-extensions": "^2.0.0",
2298+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
2299+ "micromark-util-encode": "^2.0.0",
2300+ "micromark-util-normalize-identifier": "^2.0.0",
2301+ "micromark-util-resolve-all": "^2.0.0",
2302+ "micromark-util-sanitize-uri": "^2.0.0",
2303+ "micromark-util-subtokenize": "^2.0.0",
2304+ "micromark-util-symbol": "^2.0.0",
2305+ "micromark-util-types": "^2.0.0"
2306+ }
2307+ },
2308+ "node_modules/micromark-core-commonmark": {
2309+ "version": "2.0.3",
2310+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
2311+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
2312+ "funding": [
2313+ {
2314+ "type": "GitHub Sponsors",
2315+ "url": "https://github.com/sponsors/unifiedjs"
2316+ },
2317+ {
2318+ "type": "OpenCollective",
2319+ "url": "https://opencollective.com/unified"
2320+ }
2321+ ],
2322+ "license": "MIT",
2323+ "dependencies": {
2324+ "decode-named-character-reference": "^1.0.0",
2325+ "devlop": "^1.0.0",
2326+ "micromark-factory-destination": "^2.0.0",
2327+ "micromark-factory-label": "^2.0.0",
2328+ "micromark-factory-space": "^2.0.0",
2329+ "micromark-factory-title": "^2.0.0",
2330+ "micromark-factory-whitespace": "^2.0.0",
2331+ "micromark-util-character": "^2.0.0",
2332+ "micromark-util-chunked": "^2.0.0",
2333+ "micromark-util-classify-character": "^2.0.0",
2334+ "micromark-util-html-tag-name": "^2.0.0",
2335+ "micromark-util-normalize-identifier": "^2.0.0",
2336+ "micromark-util-resolve-all": "^2.0.0",
2337+ "micromark-util-subtokenize": "^2.0.0",
2338+ "micromark-util-symbol": "^2.0.0",
2339+ "micromark-util-types": "^2.0.0"
2340+ }
2341+ },
2342+ "node_modules/micromark-extension-gfm": {
2343+ "version": "3.0.0",
2344+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
2345+ "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
2346+ "license": "MIT",
2347+ "dependencies": {
2348+ "micromark-extension-gfm-autolink-literal": "^2.0.0",
2349+ "micromark-extension-gfm-footnote": "^2.0.0",
2350+ "micromark-extension-gfm-strikethrough": "^2.0.0",
2351+ "micromark-extension-gfm-table": "^2.0.0",
2352+ "micromark-extension-gfm-tagfilter": "^2.0.0",
2353+ "micromark-extension-gfm-task-list-item": "^2.0.0",
2354+ "micromark-util-combine-extensions": "^2.0.0",
2355+ "micromark-util-types": "^2.0.0"
2356+ },
2357+ "funding": {
2358+ "type": "opencollective",
2359+ "url": "https://opencollective.com/unified"
2360+ }
2361+ },
2362+ "node_modules/micromark-extension-gfm-autolink-literal": {
2363+ "version": "2.1.0",
2364+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
2365+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
2366+ "license": "MIT",
2367+ "dependencies": {
2368+ "micromark-util-character": "^2.0.0",
2369+ "micromark-util-sanitize-uri": "^2.0.0",
2370+ "micromark-util-symbol": "^2.0.0",
2371+ "micromark-util-types": "^2.0.0"
2372+ },
2373+ "funding": {
2374+ "type": "opencollective",
2375+ "url": "https://opencollective.com/unified"
2376+ }
2377+ },
2378+ "node_modules/micromark-extension-gfm-footnote": {
2379+ "version": "2.1.0",
2380+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
2381+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
2382+ "license": "MIT",
2383+ "dependencies": {
2384+ "devlop": "^1.0.0",
2385+ "micromark-core-commonmark": "^2.0.0",
2386+ "micromark-factory-space": "^2.0.0",
2387+ "micromark-util-character": "^2.0.0",
2388+ "micromark-util-normalize-identifier": "^2.0.0",
2389+ "micromark-util-sanitize-uri": "^2.0.0",
2390+ "micromark-util-symbol": "^2.0.0",
2391+ "micromark-util-types": "^2.0.0"
2392+ },
2393+ "funding": {
2394+ "type": "opencollective",
2395+ "url": "https://opencollective.com/unified"
2396+ }
2397+ },
2398+ "node_modules/micromark-extension-gfm-strikethrough": {
2399+ "version": "2.1.0",
2400+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
2401+ "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
2402+ "license": "MIT",
2403+ "dependencies": {
2404+ "devlop": "^1.0.0",
2405+ "micromark-util-chunked": "^2.0.0",
2406+ "micromark-util-classify-character": "^2.0.0",
2407+ "micromark-util-resolve-all": "^2.0.0",
2408+ "micromark-util-symbol": "^2.0.0",
2409+ "micromark-util-types": "^2.0.0"
2410+ },
2411+ "funding": {
2412+ "type": "opencollective",
2413+ "url": "https://opencollective.com/unified"
2414+ }
2415+ },
2416+ "node_modules/micromark-extension-gfm-table": {
2417+ "version": "2.1.1",
2418+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
2419+ "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
2420+ "license": "MIT",
2421+ "dependencies": {
2422+ "devlop": "^1.0.0",
2423+ "micromark-factory-space": "^2.0.0",
2424+ "micromark-util-character": "^2.0.0",
2425+ "micromark-util-symbol": "^2.0.0",
2426+ "micromark-util-types": "^2.0.0"
2427+ },
2428+ "funding": {
2429+ "type": "opencollective",
2430+ "url": "https://opencollective.com/unified"
2431+ }
2432+ },
2433+ "node_modules/micromark-extension-gfm-tagfilter": {
2434+ "version": "2.0.0",
2435+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
2436+ "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
2437+ "license": "MIT",
2438+ "dependencies": {
2439+ "micromark-util-types": "^2.0.0"
2440+ },
2441+ "funding": {
2442+ "type": "opencollective",
2443+ "url": "https://opencollective.com/unified"
2444+ }
2445+ },
2446+ "node_modules/micromark-extension-gfm-task-list-item": {
2447+ "version": "2.1.0",
2448+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
2449+ "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
2450+ "license": "MIT",
2451+ "dependencies": {
2452+ "devlop": "^1.0.0",
2453+ "micromark-factory-space": "^2.0.0",
2454+ "micromark-util-character": "^2.0.0",
2455+ "micromark-util-symbol": "^2.0.0",
2456+ "micromark-util-types": "^2.0.0"
2457+ },
2458+ "funding": {
2459+ "type": "opencollective",
2460+ "url": "https://opencollective.com/unified"
2461+ }
2462+ },
2463+ "node_modules/micromark-factory-destination": {
2464+ "version": "2.0.1",
2465+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
2466+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
2467+ "funding": [
2468+ {
2469+ "type": "GitHub Sponsors",
2470+ "url": "https://github.com/sponsors/unifiedjs"
2471+ },
2472+ {
2473+ "type": "OpenCollective",
2474+ "url": "https://opencollective.com/unified"
2475+ }
2476+ ],
2477+ "license": "MIT",
2478+ "dependencies": {
2479+ "micromark-util-character": "^2.0.0",
2480+ "micromark-util-symbol": "^2.0.0",
2481+ "micromark-util-types": "^2.0.0"
2482+ }
2483+ },
2484+ "node_modules/micromark-factory-label": {
2485+ "version": "2.0.1",
2486+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
2487+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
2488+ "funding": [
2489+ {
2490+ "type": "GitHub Sponsors",
2491+ "url": "https://github.com/sponsors/unifiedjs"
2492+ },
2493+ {
2494+ "type": "OpenCollective",
2495+ "url": "https://opencollective.com/unified"
2496+ }
2497+ ],
2498+ "license": "MIT",
2499+ "dependencies": {
2500+ "devlop": "^1.0.0",
2501+ "micromark-util-character": "^2.0.0",
2502+ "micromark-util-symbol": "^2.0.0",
2503+ "micromark-util-types": "^2.0.0"
2504+ }
2505+ },
2506+ "node_modules/micromark-factory-space": {
2507+ "version": "2.0.1",
2508+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
2509+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
2510+ "funding": [
2511+ {
2512+ "type": "GitHub Sponsors",
2513+ "url": "https://github.com/sponsors/unifiedjs"
2514+ },
2515+ {
2516+ "type": "OpenCollective",
2517+ "url": "https://opencollective.com/unified"
2518+ }
2519+ ],
2520+ "license": "MIT",
2521+ "dependencies": {
2522+ "micromark-util-character": "^2.0.0",
2523+ "micromark-util-types": "^2.0.0"
2524+ }
2525+ },
2526+ "node_modules/micromark-factory-title": {
2527+ "version": "2.0.1",
2528+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
2529+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
2530+ "funding": [
2531+ {
2532+ "type": "GitHub Sponsors",
2533+ "url": "https://github.com/sponsors/unifiedjs"
2534+ },
2535+ {
2536+ "type": "OpenCollective",
2537+ "url": "https://opencollective.com/unified"
2538+ }
2539+ ],
2540+ "license": "MIT",
2541+ "dependencies": {
2542+ "micromark-factory-space": "^2.0.0",
2543+ "micromark-util-character": "^2.0.0",
2544+ "micromark-util-symbol": "^2.0.0",
2545+ "micromark-util-types": "^2.0.0"
2546+ }
2547+ },
2548+ "node_modules/micromark-factory-whitespace": {
2549+ "version": "2.0.1",
2550+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
2551+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
2552+ "funding": [
2553+ {
2554+ "type": "GitHub Sponsors",
2555+ "url": "https://github.com/sponsors/unifiedjs"
2556+ },
2557+ {
2558+ "type": "OpenCollective",
2559+ "url": "https://opencollective.com/unified"
2560+ }
2561+ ],
2562+ "license": "MIT",
2563+ "dependencies": {
2564+ "micromark-factory-space": "^2.0.0",
2565+ "micromark-util-character": "^2.0.0",
2566+ "micromark-util-symbol": "^2.0.0",
2567+ "micromark-util-types": "^2.0.0"
2568+ }
2569+ },
2570+ "node_modules/micromark-util-character": {
2571+ "version": "2.1.1",
2572+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
2573+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
2574+ "funding": [
2575+ {
2576+ "type": "GitHub Sponsors",
2577+ "url": "https://github.com/sponsors/unifiedjs"
2578+ },
2579+ {
2580+ "type": "OpenCollective",
2581+ "url": "https://opencollective.com/unified"
2582+ }
2583+ ],
2584+ "license": "MIT",
2585+ "dependencies": {
2586+ "micromark-util-symbol": "^2.0.0",
2587+ "micromark-util-types": "^2.0.0"
2588+ }
2589+ },
2590+ "node_modules/micromark-util-chunked": {
2591+ "version": "2.0.1",
2592+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
2593+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
2594+ "funding": [
2595+ {
2596+ "type": "GitHub Sponsors",
2597+ "url": "https://github.com/sponsors/unifiedjs"
2598+ },
2599+ {
2600+ "type": "OpenCollective",
2601+ "url": "https://opencollective.com/unified"
2602+ }
2603+ ],
2604+ "license": "MIT",
2605+ "dependencies": {
2606+ "micromark-util-symbol": "^2.0.0"
2607+ }
2608+ },
2609+ "node_modules/micromark-util-classify-character": {
2610+ "version": "2.0.1",
2611+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
2612+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
2613+ "funding": [
2614+ {
2615+ "type": "GitHub Sponsors",
2616+ "url": "https://github.com/sponsors/unifiedjs"
2617+ },
2618+ {
2619+ "type": "OpenCollective",
2620+ "url": "https://opencollective.com/unified"
2621+ }
2622+ ],
2623+ "license": "MIT",
2624+ "dependencies": {
2625+ "micromark-util-character": "^2.0.0",
2626+ "micromark-util-symbol": "^2.0.0",
2627+ "micromark-util-types": "^2.0.0"
2628+ }
2629+ },
2630+ "node_modules/micromark-util-combine-extensions": {
2631+ "version": "2.0.1",
2632+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
2633+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
2634+ "funding": [
2635+ {
2636+ "type": "GitHub Sponsors",
2637+ "url": "https://github.com/sponsors/unifiedjs"
2638+ },
2639+ {
2640+ "type": "OpenCollective",
2641+ "url": "https://opencollective.com/unified"
2642+ }
2643+ ],
2644+ "license": "MIT",
2645+ "dependencies": {
2646+ "micromark-util-chunked": "^2.0.0",
2647+ "micromark-util-types": "^2.0.0"
2648+ }
2649+ },
2650+ "node_modules/micromark-util-decode-numeric-character-reference": {
2651+ "version": "2.0.2",
2652+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
2653+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
2654+ "funding": [
2655+ {
2656+ "type": "GitHub Sponsors",
2657+ "url": "https://github.com/sponsors/unifiedjs"
2658+ },
2659+ {
2660+ "type": "OpenCollective",
2661+ "url": "https://opencollective.com/unified"
2662+ }
2663+ ],
2664+ "license": "MIT",
2665+ "dependencies": {
2666+ "micromark-util-symbol": "^2.0.0"
2667+ }
2668+ },
2669+ "node_modules/micromark-util-decode-string": {
2670+ "version": "2.0.1",
2671+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
2672+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
2673+ "funding": [
2674+ {
2675+ "type": "GitHub Sponsors",
2676+ "url": "https://github.com/sponsors/unifiedjs"
2677+ },
2678+ {
2679+ "type": "OpenCollective",
2680+ "url": "https://opencollective.com/unified"
2681+ }
2682+ ],
2683+ "license": "MIT",
2684+ "dependencies": {
2685+ "decode-named-character-reference": "^1.0.0",
2686+ "micromark-util-character": "^2.0.0",
2687+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
2688+ "micromark-util-symbol": "^2.0.0"
2689+ }
2690+ },
2691+ "node_modules/micromark-util-encode": {
2692+ "version": "2.0.1",
2693+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
2694+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
2695+ "funding": [
2696+ {
2697+ "type": "GitHub Sponsors",
2698+ "url": "https://github.com/sponsors/unifiedjs"
2699+ },
2700+ {
2701+ "type": "OpenCollective",
2702+ "url": "https://opencollective.com/unified"
2703+ }
2704+ ],
2705+ "license": "MIT"
2706+ },
2707+ "node_modules/micromark-util-html-tag-name": {
2708+ "version": "2.0.1",
2709+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
2710+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
2711+ "funding": [
2712+ {
2713+ "type": "GitHub Sponsors",
2714+ "url": "https://github.com/sponsors/unifiedjs"
2715+ },
2716+ {
2717+ "type": "OpenCollective",
2718+ "url": "https://opencollective.com/unified"
2719+ }
2720+ ],
2721+ "license": "MIT"
2722+ },
2723+ "node_modules/micromark-util-normalize-identifier": {
2724+ "version": "2.0.1",
2725+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
2726+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
2727+ "funding": [
2728+ {
2729+ "type": "GitHub Sponsors",
2730+ "url": "https://github.com/sponsors/unifiedjs"
2731+ },
2732+ {
2733+ "type": "OpenCollective",
2734+ "url": "https://opencollective.com/unified"
2735+ }
2736+ ],
2737+ "license": "MIT",
2738+ "dependencies": {
2739+ "micromark-util-symbol": "^2.0.0"
2740+ }
2741+ },
2742+ "node_modules/micromark-util-resolve-all": {
2743+ "version": "2.0.1",
2744+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
2745+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
2746+ "funding": [
2747+ {
2748+ "type": "GitHub Sponsors",
2749+ "url": "https://github.com/sponsors/unifiedjs"
2750+ },
2751+ {
2752+ "type": "OpenCollective",
2753+ "url": "https://opencollective.com/unified"
2754+ }
2755+ ],
2756+ "license": "MIT",
2757+ "dependencies": {
2758+ "micromark-util-types": "^2.0.0"
2759+ }
2760+ },
2761+ "node_modules/micromark-util-sanitize-uri": {
2762+ "version": "2.0.1",
2763+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
2764+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
2765+ "funding": [
2766+ {
2767+ "type": "GitHub Sponsors",
2768+ "url": "https://github.com/sponsors/unifiedjs"
2769+ },
2770+ {
2771+ "type": "OpenCollective",
2772+ "url": "https://opencollective.com/unified"
2773+ }
2774+ ],
2775+ "license": "MIT",
2776+ "dependencies": {
2777+ "micromark-util-character": "^2.0.0",
2778+ "micromark-util-encode": "^2.0.0",
2779+ "micromark-util-symbol": "^2.0.0"
2780+ }
2781+ },
2782+ "node_modules/micromark-util-subtokenize": {
2783+ "version": "2.1.0",
2784+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
2785+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
2786+ "funding": [
2787+ {
2788+ "type": "GitHub Sponsors",
2789+ "url": "https://github.com/sponsors/unifiedjs"
2790+ },
2791+ {
2792+ "type": "OpenCollective",
2793+ "url": "https://opencollective.com/unified"
2794+ }
2795+ ],
2796+ "license": "MIT",
2797+ "dependencies": {
2798+ "devlop": "^1.0.0",
2799+ "micromark-util-chunked": "^2.0.0",
2800+ "micromark-util-symbol": "^2.0.0",
2801+ "micromark-util-types": "^2.0.0"
2802+ }
2803+ },
2804+ "node_modules/micromark-util-symbol": {
2805+ "version": "2.0.1",
2806+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
2807+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
2808+ "funding": [
2809+ {
2810+ "type": "GitHub Sponsors",
2811+ "url": "https://github.com/sponsors/unifiedjs"
2812+ },
2813+ {
2814+ "type": "OpenCollective",
2815+ "url": "https://opencollective.com/unified"
2816+ }
2817+ ],
2818+ "license": "MIT"
2819+ },
2820+ "node_modules/micromark-util-types": {
2821+ "version": "2.0.2",
2822+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
2823+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
2824+ "funding": [
2825+ {
2826+ "type": "GitHub Sponsors",
2827+ "url": "https://github.com/sponsors/unifiedjs"
2828+ },
2829+ {
2830+ "type": "OpenCollective",
2831+ "url": "https://opencollective.com/unified"
2832+ }
2833+ ],
2834+ "license": "MIT"
2835+ },
2836+ "node_modules/ms": {
2837+ "version": "2.1.3",
2838+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
2839+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
2840+ "license": "MIT"
2841+ },
2842+ "node_modules/nanoid": {
2843+ "version": "3.3.18",
2844+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
2845+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
2846+ "dev": true,
2847+ "funding": [
2848+ {
2849+ "type": "github",
2850+ "url": "https://github.com/sponsors/ai"
2851+ }
2852+ ],
2853+ "license": "MIT",
2854+ "bin": {
2855+ "nanoid": "bin/nanoid.cjs"
2856+ },
2857+ "engines": {
2858+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
2859+ }
2860+ },
2861+ "node_modules/node-addon-api": {
2862+ "version": "8.9.2",
2863+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.2.tgz",
2864+ "integrity": "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==",
2865+ "license": "MIT",
2866+ "engines": {
2867+ "node": "^18 || ^20 || >= 21"
2868+ }
2869+ },
2870+ "node_modules/numbl": {
2871+ "version": "0.4.19",
2872+ "resolved": "https://registry.npmjs.org/numbl/-/numbl-0.4.19.tgz",
2873+ "integrity": "sha512-wfTAhrdOSuzUoiglglTwykwEguU0Vl5qBJNhj2hyP8FzHACd/w1qX69OhEC7sbyE+gGEH9gjPjFsBkoi9UTGqw==",
2874+ "hasInstallScript": true,
2875+ "license": "Apache-2.0",
2876+ "dependencies": {
2877+ "fflate": "^0.8.2",
2878+ "h5wasm": "^0.10.3",
2879+ "node-addon-api": "^8.3.0",
2880+ "pako": "^2.1.0",
2881+ "qhull-wasm": "^0.0.1",
2882+ "react-markdown": "^10.1.0",
2883+ "react-syntax-highlighter": "^16.1.1",
2884+ "remark-gfm": "^4.0.1",
2885+ "three": "^0.183.2"
2886+ },
2887+ "bin": {
2888+ "numbl": "dist-cli/cli.js"
2889+ },
2890+ "engines": {
2891+ "node": ">=18"
2892+ },
2893+ "optionalDependencies": {
2894+ "koffi": "^2.15.2"
2895+ },
2896+ "peerDependencies": {
2897+ "react": ">=18",
2898+ "react-dom": ">=18"
2899+ },
2900+ "peerDependenciesMeta": {
2901+ "react": {
2902+ "optional": true
2903+ },
2904+ "react-dom": {
2905+ "optional": true
2906+ }
2907+ }
2908+ },
2909+ "node_modules/obug": {
2910+ "version": "2.1.4",
2911+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
2912+ "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==",
2913+ "dev": true,
2914+ "funding": [
2915+ "https://github.com/sponsors/sxzz",
2916+ "https://opencollective.com/debug"
2917+ ],
2918+ "license": "MIT",
2919+ "engines": {
2920+ "node": ">=12.20.0"
2921+ }
2922+ },
2923+ "node_modules/pako": {
2924+ "version": "2.2.0",
2925+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.2.0.tgz",
2926+ "integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==",
2927+ "funding": [
2928+ {
2929+ "type": "github",
2930+ "url": "https://github.com/sponsors/puzrin"
2931+ },
2932+ {
2933+ "type": "github",
2934+ "url": "https://github.com/sponsors/nodeca"
2935+ }
2936+ ],
2937+ "license": "(MIT AND Zlib)"
2938+ },
2939+ "node_modules/parse-entities": {
2940+ "version": "4.0.2",
2941+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
2942+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
2943+ "license": "MIT",
2944+ "dependencies": {
2945+ "@types/unist": "^2.0.0",
2946+ "character-entities-legacy": "^3.0.0",
2947+ "character-reference-invalid": "^2.0.0",
2948+ "decode-named-character-reference": "^1.0.0",
2949+ "is-alphanumerical": "^2.0.0",
2950+ "is-decimal": "^2.0.0",
2951+ "is-hexadecimal": "^2.0.0"
2952+ },
2953+ "funding": {
2954+ "type": "github",
2955+ "url": "https://github.com/sponsors/wooorm"
2956+ }
2957+ },
2958+ "node_modules/parse-entities/node_modules/@types/unist": {
2959+ "version": "2.0.11",
2960+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
2961+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
2962+ "license": "MIT"
2963+ },
2964+ "node_modules/pathe": {
2965+ "version": "2.0.3",
2966+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
2967+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
2968+ "dev": true,
2969+ "license": "MIT"
2970+ },
2971+ "node_modules/picocolors": {
2972+ "version": "1.1.1",
2973+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
2974+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
2975+ "dev": true,
2976+ "license": "ISC"
2977+ },
2978+ "node_modules/picomatch": {
2979+ "version": "4.0.7",
2980+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
2981+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
2982+ "dev": true,
2983+ "license": "MIT",
2984+ "engines": {
2985+ "node": ">=12"
2986+ },
2987+ "funding": {
2988+ "url": "https://github.com/sponsors/jonschlinkert"
15902989 }
15912990 },
15922991 "node_modules/postcss": {
@@ -1618,6 +3017,173 @@
16183017 "node": "^10 || ^12 || >=14"
16193018 }
16203019 },
3020+ "node_modules/prismjs": {
3021+ "version": "1.30.0",
3022+ "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
3023+ "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
3024+ "license": "MIT",
3025+ "engines": {
3026+ "node": ">=6"
3027+ }
3028+ },
3029+ "node_modules/property-information": {
3030+ "version": "7.2.0",
3031+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz",
3032+ "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==",
3033+ "license": "MIT",
3034+ "funding": {
3035+ "type": "github",
3036+ "url": "https://github.com/sponsors/wooorm"
3037+ }
3038+ },
3039+ "node_modules/qhull-wasm": {
3040+ "version": "0.0.1",
3041+ "resolved": "https://registry.npmjs.org/qhull-wasm/-/qhull-wasm-0.0.1.tgz",
3042+ "integrity": "sha512-A09kumKhdRIX0YNR+Y2G0bEuqCJ3xFOH61+x6uajHqDyvRydlZKWhH1AGGsM4PCyGm/cPFVbqOUrPJpq5n8HeA==",
3043+ "license": "(MIT AND Qhull)",
3044+ "engines": {
3045+ "node": ">=18"
3046+ }
3047+ },
3048+ "node_modules/react": {
3049+ "version": "19.2.8",
3050+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
3051+ "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
3052+ "license": "MIT",
3053+ "peer": true,
3054+ "engines": {
3055+ "node": ">=0.10.0"
3056+ }
3057+ },
3058+ "node_modules/react-markdown": {
3059+ "version": "10.1.0",
3060+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
3061+ "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==",
3062+ "license": "MIT",
3063+ "dependencies": {
3064+ "@types/hast": "^3.0.0",
3065+ "@types/mdast": "^4.0.0",
3066+ "devlop": "^1.0.0",
3067+ "hast-util-to-jsx-runtime": "^2.0.0",
3068+ "html-url-attributes": "^3.0.0",
3069+ "mdast-util-to-hast": "^13.0.0",
3070+ "remark-parse": "^11.0.0",
3071+ "remark-rehype": "^11.0.0",
3072+ "unified": "^11.0.0",
3073+ "unist-util-visit": "^5.0.0",
3074+ "vfile": "^6.0.0"
3075+ },
3076+ "funding": {
3077+ "type": "opencollective",
3078+ "url": "https://opencollective.com/unified"
3079+ },
3080+ "peerDependencies": {
3081+ "@types/react": ">=18",
3082+ "react": ">=18"
3083+ }
3084+ },
3085+ "node_modules/react-syntax-highlighter": {
3086+ "version": "16.1.1",
3087+ "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.1.tgz",
3088+ "integrity": "sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==",
3089+ "license": "MIT",
3090+ "dependencies": {
3091+ "@babel/runtime": "^7.28.4",
3092+ "highlight.js": "^10.4.1",
3093+ "highlightjs-vue": "^1.0.0",
3094+ "lowlight": "^1.17.0",
3095+ "prismjs": "^1.30.0",
3096+ "refractor": "^5.0.0"
3097+ },
3098+ "engines": {
3099+ "node": ">= 16.20.2"
3100+ },
3101+ "peerDependencies": {
3102+ "react": ">= 0.14.0"
3103+ }
3104+ },
3105+ "node_modules/refractor": {
3106+ "version": "5.0.0",
3107+ "resolved": "https://registry.npmjs.org/refractor/-/refractor-5.0.0.tgz",
3108+ "integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==",
3109+ "license": "MIT",
3110+ "dependencies": {
3111+ "@types/hast": "^3.0.0",
3112+ "@types/prismjs": "^1.0.0",
3113+ "hastscript": "^9.0.0",
3114+ "parse-entities": "^4.0.0"
3115+ },
3116+ "funding": {
3117+ "type": "github",
3118+ "url": "https://github.com/sponsors/wooorm"
3119+ }
3120+ },
3121+ "node_modules/remark-gfm": {
3122+ "version": "4.0.1",
3123+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
3124+ "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
3125+ "license": "MIT",
3126+ "dependencies": {
3127+ "@types/mdast": "^4.0.0",
3128+ "mdast-util-gfm": "^3.0.0",
3129+ "micromark-extension-gfm": "^3.0.0",
3130+ "remark-parse": "^11.0.0",
3131+ "remark-stringify": "^11.0.0",
3132+ "unified": "^11.0.0"
3133+ },
3134+ "funding": {
3135+ "type": "opencollective",
3136+ "url": "https://opencollective.com/unified"
3137+ }
3138+ },
3139+ "node_modules/remark-parse": {
3140+ "version": "11.0.0",
3141+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
3142+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
3143+ "license": "MIT",
3144+ "dependencies": {
3145+ "@types/mdast": "^4.0.0",
3146+ "mdast-util-from-markdown": "^2.0.0",
3147+ "micromark-util-types": "^2.0.0",
3148+ "unified": "^11.0.0"
3149+ },
3150+ "funding": {
3151+ "type": "opencollective",
3152+ "url": "https://opencollective.com/unified"
3153+ }
3154+ },
3155+ "node_modules/remark-rehype": {
3156+ "version": "11.1.2",
3157+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz",
3158+ "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
3159+ "license": "MIT",
3160+ "dependencies": {
3161+ "@types/hast": "^3.0.0",
3162+ "@types/mdast": "^4.0.0",
3163+ "mdast-util-to-hast": "^13.0.0",
3164+ "unified": "^11.0.0",
3165+ "vfile": "^6.0.0"
3166+ },
3167+ "funding": {
3168+ "type": "opencollective",
3169+ "url": "https://opencollective.com/unified"
3170+ }
3171+ },
3172+ "node_modules/remark-stringify": {
3173+ "version": "11.0.0",
3174+ "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
3175+ "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
3176+ "license": "MIT",
3177+ "dependencies": {
3178+ "@types/mdast": "^4.0.0",
3179+ "mdast-util-to-markdown": "^2.0.0",
3180+ "unified": "^11.0.0"
3181+ },
3182+ "funding": {
3183+ "type": "opencollective",
3184+ "url": "https://opencollective.com/unified"
3185+ }
3186+ },
16213187 "node_modules/rolldown": {
16223188 "version": "1.2.6",
16233189 "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.6.tgz",
@@ -1708,6 +3274,54 @@
17083274 "node": ">=0.10.0"
17093275 }
17103276 },
3277+ "node_modules/space-separated-tokens": {
3278+ "version": "2.0.2",
3279+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
3280+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
3281+ "license": "MIT",
3282+ "funding": {
3283+ "type": "github",
3284+ "url": "https://github.com/sponsors/wooorm"
3285+ }
3286+ },
3287+ "node_modules/stringify-entities": {
3288+ "version": "4.0.4",
3289+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
3290+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
3291+ "license": "MIT",
3292+ "dependencies": {
3293+ "character-entities-html4": "^2.0.0",
3294+ "character-entities-legacy": "^3.0.0"
3295+ },
3296+ "funding": {
3297+ "type": "github",
3298+ "url": "https://github.com/sponsors/wooorm"
3299+ }
3300+ },
3301+ "node_modules/style-to-js": {
3302+ "version": "1.1.21",
3303+ "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
3304+ "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
3305+ "license": "MIT",
3306+ "dependencies": {
3307+ "style-to-object": "1.0.14"
3308+ }
3309+ },
3310+ "node_modules/style-to-object": {
3311+ "version": "1.0.14",
3312+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz",
3313+ "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
3314+ "license": "MIT",
3315+ "dependencies": {
3316+ "inline-style-parser": "0.2.7"
3317+ }
3318+ },
3319+ "node_modules/three": {
3320+ "version": "0.183.2",
3321+ "resolved": "https://registry.npmjs.org/three/-/three-0.183.2.tgz",
3322+ "integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==",
3323+ "license": "MIT"
3324+ },
17113325 "node_modules/tinyglobby": {
17123326 "version": "0.2.17",
17133327 "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
@@ -1725,6 +3339,26 @@
17253339 "url": "https://github.com/sponsors/SuperchupuDev"
17263340 }
17273341 },
3342+ "node_modules/trim-lines": {
3343+ "version": "3.0.1",
3344+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
3345+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
3346+ "license": "MIT",
3347+ "funding": {
3348+ "type": "github",
3349+ "url": "https://github.com/sponsors/wooorm"
3350+ }
3351+ },
3352+ "node_modules/trough": {
3353+ "version": "2.2.0",
3354+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
3355+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
3356+ "license": "MIT",
3357+ "funding": {
3358+ "type": "github",
3359+ "url": "https://github.com/sponsors/wooorm"
3360+ }
3361+ },
17283362 "node_modules/typescript": {
17293363 "version": "5.9.3",
17303364 "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -1746,6 +3380,121 @@
17463380 "dev": true,
17473381 "license": "MIT"
17483382 },
3383+ "node_modules/unified": {
3384+ "version": "11.0.5",
3385+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
3386+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
3387+ "license": "MIT",
3388+ "dependencies": {
3389+ "@types/unist": "^3.0.0",
3390+ "bail": "^2.0.0",
3391+ "devlop": "^1.0.0",
3392+ "extend": "^3.0.0",
3393+ "is-plain-obj": "^4.0.0",
3394+ "trough": "^2.0.0",
3395+ "vfile": "^6.0.0"
3396+ },
3397+ "funding": {
3398+ "type": "opencollective",
3399+ "url": "https://opencollective.com/unified"
3400+ }
3401+ },
3402+ "node_modules/unist-util-is": {
3403+ "version": "6.0.1",
3404+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
3405+ "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
3406+ "license": "MIT",
3407+ "dependencies": {
3408+ "@types/unist": "^3.0.0"
3409+ },
3410+ "funding": {
3411+ "type": "opencollective",
3412+ "url": "https://opencollective.com/unified"
3413+ }
3414+ },
3415+ "node_modules/unist-util-position": {
3416+ "version": "5.0.0",
3417+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
3418+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
3419+ "license": "MIT",
3420+ "dependencies": {
3421+ "@types/unist": "^3.0.0"
3422+ },
3423+ "funding": {
3424+ "type": "opencollective",
3425+ "url": "https://opencollective.com/unified"
3426+ }
3427+ },
3428+ "node_modules/unist-util-stringify-position": {
3429+ "version": "4.0.0",
3430+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
3431+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
3432+ "license": "MIT",
3433+ "dependencies": {
3434+ "@types/unist": "^3.0.0"
3435+ },
3436+ "funding": {
3437+ "type": "opencollective",
3438+ "url": "https://opencollective.com/unified"
3439+ }
3440+ },
3441+ "node_modules/unist-util-visit": {
3442+ "version": "5.1.0",
3443+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz",
3444+ "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
3445+ "license": "MIT",
3446+ "dependencies": {
3447+ "@types/unist": "^3.0.0",
3448+ "unist-util-is": "^6.0.0",
3449+ "unist-util-visit-parents": "^6.0.0"
3450+ },
3451+ "funding": {
3452+ "type": "opencollective",
3453+ "url": "https://opencollective.com/unified"
3454+ }
3455+ },
3456+ "node_modules/unist-util-visit-parents": {
3457+ "version": "6.0.2",
3458+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
3459+ "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
3460+ "license": "MIT",
3461+ "dependencies": {
3462+ "@types/unist": "^3.0.0",
3463+ "unist-util-is": "^6.0.0"
3464+ },
3465+ "funding": {
3466+ "type": "opencollective",
3467+ "url": "https://opencollective.com/unified"
3468+ }
3469+ },
3470+ "node_modules/vfile": {
3471+ "version": "6.0.3",
3472+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
3473+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
3474+ "license": "MIT",
3475+ "dependencies": {
3476+ "@types/unist": "^3.0.0",
3477+ "vfile-message": "^4.0.0"
3478+ },
3479+ "funding": {
3480+ "type": "opencollective",
3481+ "url": "https://opencollective.com/unified"
3482+ }
3483+ },
3484+ "node_modules/vfile-message": {
3485+ "version": "4.0.3",
3486+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
3487+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
3488+ "license": "MIT",
3489+ "dependencies": {
3490+ "@types/unist": "^3.0.0",
3491+ "unist-util-stringify-position": "^4.0.0"
3492+ },
3493+ "funding": {
3494+ "type": "opencollective",
3495+ "url": "https://opencollective.com/unified"
3496+ }
3497+ },
17493498 "node_modules/vite": {
17503499 "version": "5.4.21",
17513500 "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
@@ -1906,6 +3655,16 @@
19063655 "optional": true
19073656 }
19083657 }
3658+ },
3659+ "node_modules/zwitch": {
3660+ "version": "2.0.4",
3661+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
3662+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
3663+ "license": "MIT",
3664+ "funding": {
3665+ "type": "github",
3666+ "url": "https://github.com/sponsors/wooorm"
3667+ }
19093668 }
19103669 }
19113670 }
package.jsonmodified+5−4View file
@@ -13,13 +13,14 @@
1313 "test": "vite-node scripts/test-node.ts"
1414 },
1515 "dependencies": {
16- "instrawave": "file:../instrawave"
16+ "instrawave": "file:../instrawave",
17+ "numbl": "^0.4.19"
1718 },
1819 "devDependencies": {
1920 "@types/node": "^26.1.1",
21+ "@webgpu/types": "^0.1.44",
2022 "typescript": "^5.5.0",
2123 "vite": "^5.4.0",
22- "vite-node": "^6.0.0",
23- "@webgpu/types": "^0.1.44"
24+ "vite-node": "^6.0.0"
2425 }
25-}
\ No newline at end of file
26+}
scripts/test-node.tsmodified+68−0View file
@@ -123,5 +123,73 @@ function burst(x: Float32Array, t0: number, freq: number, decay: number, amp: nu
123123 check(v.getInt16(50, true) === 32767, 'wav: over-range clips to full scale');
124124 }
125125
126+// --- numbl classifier path ---------------------------------------------------------
127+// The default silence_gap_knn (with its noise-floor threshold adaptation)
128+// run over the same pipeline the app uses: training snippets cut from
129+// voice clips at 16 kHz, the take reduced to 16 kHz, detections mapped
130+// back to voices. The take carries a realistic microphone noise floor,
131+// which the original benchmark algorithm's silence assumption would not
132+// survive; this is exactly what the adaptation is for.
133+
134+const { buildVoiceTraining } = await import('../src/numbl/snippets.ts');
135+const { runAlgorithm } = await import('../src/numbl/execute.ts');
136+const { downsampleBox } = await import('../src/numbl/dsp.ts');
137+const { DEFAULT_CLASSIFIER_SOURCE } = await import('../src/numbl/index.ts');
138+const { CLF_FS } = await import('../src/numbl/types.ts');
139+
140+{
141+ const noiseInto = (x: Float32Array, amp: number, seed: number) => {
142+ let s = seed;
143+ for (let i = 0; i < x.length; i++) {
144+ s = (s * 1103515245 + 12345) & 0x7fffffff;
145+ x[i] += (s / 0x3fffffff - 1) * amp;
146+ }
147+ };
148+
149+ // one training clip per voice: 8 well-separated bursts at known times
150+ const clipAudio = new Map<string, { samples: Float32Array; sr: number }>();
151+ const mkVoice = (id: string, freq: number, decay: number, seed: number) => {
152+ const times = Array.from({ length: 8 }, (_, i) => 0.3 + i * 0.35);
153+ const samples = new Float32Array(Math.floor(3.2 * SR));
154+ noiseInto(samples, 5e-4, seed);
155+ times.forEach((t, i) => burst(samples, t, freq, decay, 0.5, seed + i));
156+ clipAudio.set(`clip-${id}`, { samples, sr: SR });
157+ return {
158+ id, name: id, color: '#888',
159+ binding: { kind: 'synth', preset: 'kick' } as const,
160+ volume: 1,
161+ clips: [{ id: `clip-${id}`, sr: SR, sens: 0.6, onsets: times, excluded: [], hits: [] }],
162+ loose: [],
163+ };
164+ };
165+ const testVoices = [mkVoice('thump', 200, 0.05, 11), mkVoice('tick', 1500, 0.015, 77)];
166+
167+ const { training, voiceIds, skipped } = await buildVoiceTraining(
168+ testVoices,
169+ (id) => Promise.resolve(clipAudio.get(id) ?? null),
170+ );
171+ check(voiceIds.length === 2 && skipped.length === 0,
172+ `numbl training: 2 voices, ${training.snips.map((s) => s.count).join('+')} snippets`);
173+
174+ // a take with a mic-like noise floor and 6 alternating hits
175+ const takeTimes = [0.4, 0.8, 1.2, 1.6, 2.0, 2.4];
176+ const takeSamples = new Float32Array(Math.floor(3 * SR));
177+ noiseInto(takeSamples, 5e-4, 999);
178+ takeTimes.forEach((t, i) => burst(takeSamples, t, i % 2 === 0 ? 200 : 1500, i % 2 === 0 ? 0.05 : 0.015, 0.4, 300 + i));
179+
180+ const x16 = downsampleBox(takeSamples, SR, CLF_FS);
181+ const { detections } = runAlgorithm(DEFAULT_CLASSIFIER_SOURCE, x16, CLF_FS, training);
182+ check(detections.length === takeTimes.length,
183+ `numbl classify: ${detections.length} of ${takeTimes.length} hits found on a noisy take`);
184+ let timesOk = true, labelsOk = true;
185+ detections.forEach((d, i) => {
186+ if (i >= takeTimes.length) return;
187+ if (Math.abs(d.t - takeTimes[i]) > 0.03) timesOk = false;
188+ if (d.typeIndex !== i % 2) labelsOk = false;
189+ });
190+ check(timesOk, 'numbl classify: every hit within 30 ms of the truth');
191+ check(labelsOk, 'numbl classify: thump/tick labels all correct');
192+}
193+
126194 console.log(failures ? `\n${failures} failure(s)` : '\nall checks passed');
127195 process.exit(failures ? 1 : 0);
src/m-raw.d.tsadded+1−0View file
@@ -0,0 +1 @@
1+declare module "*.m?raw" { const src: string; export default src; }
src/main.tsmodified+102−5View file
@@ -20,11 +20,17 @@ import { IwEngine, parseBake, PhaseCancelled, type StimulusEvent } from './rende
2020 import { Viewer, type Camera } from 'instrawave/src/host/view.ts';
2121 import {
2222 clearBulk, clearStore, deleteClipAudio, getBakeBytes, getBakeKey, getClipAudio, getTakeAudio,
23- deleteRender, getRender, loadIwOverrides, loadTakeHits, loadUi, loadVoices, putBake, putClipAudio,
23+ deleteRender, getRender, loadClassifier, loadIwOverrides, loadTakeHits, loadUi, loadVoices,
24+ putBake, putClipAudio,
2425 putRender, putTakeAudio,
25- saveIwOverrides, saveTakeHits, saveUi, saveVoices, trainPool,
26+ saveClassifier, saveIwOverrides, saveTakeHits, saveUi, saveVoices, trainPool,
2627 type TakeHit, type TrainClip, type Voice,
2728 } from './store.ts';
29+import { CUSTOM_CLASSIFIER_TEMPLATE, DEFAULT_CLASSIFIER_NAME, DEFAULT_CLASSIFIER_SOURCE } from './numbl/index.ts';
30+import { buildVoiceTraining } from './numbl/snippets.ts';
31+import { classifyInWorker } from './numbl/client.ts';
32+import { downsampleBox } from './numbl/dsp.ts';
33+import { CLF_FS } from './numbl/types.ts';
2834 import { WaveView, type Marker } from './ui/waveform.ts';
2935 import { Timeline } from './ui/timeline.ts';
3036
@@ -676,10 +682,56 @@ async function startBake(name: string): Promise<void> {
676682
677683 const timeline = new Timeline($('timeline') as HTMLCanvasElement);
678684
679-function processTake(): void {
685+const clf = loadClassifier();
686+const activeClfSource = (): string =>
687+ clf.method === 'custom' ? (clf.customSrc ?? CUSTOM_CLASSIFIER_TEMPLATE)
688+ : (clf.builtinSrc ?? DEFAULT_CLASSIFIER_SOURCE);
689+
690+async function processTake(): Promise<void> {
680691 if (!take.clip) return;
681692 stopPlayback();
682693 const { samples, sr } = take.clip;
694+ if (clf.method === 'legacy') { processTakeLegacy(samples, sr); return; }
695+
696+ const btn = $('take-redetect') as HTMLButtonElement;
697+ btn.disabled = true;
698+ status('classifying take with numbl...');
699+ try {
700+ const { training, voiceIds, skipped } = await buildVoiceTraining(voices, getClipAudio);
701+ if (voiceIds.length === 0) {
702+ status('no voice has training clips with audio; record examples first, or switch the classifier to the built-in feature kNN');
703+ return;
704+ }
705+ const x16 = downsampleBox(samples, sr, CLF_FS);
706+ const { detections } = await classifyInWorker(activeClfSource(), x16, CLF_FS, training);
707+
708+ const hits: TakeHit[] = [];
709+ let refDb = -160;
710+ for (let i = 0; i < detections.length; i++) {
711+ const det = detections[i];
712+ if (det.typeIndex < 0 || det.typeIndex >= voiceIds.length) continue;
713+ const s0 = Math.max(0, Math.min(samples.length - 1, Math.floor(det.t * sr)));
714+ const s1 = i + 1 < detections.length ? Math.floor(detections[i + 1].t * sr) : samples.length;
715+ const f = extractHit(samples, sr, s0, Math.max(s1, s0 + 1));
716+ if (f.peakDb > refDb) refDb = f.peakDb;
717+ hits.push({ id: uid(), t: det.t, voiceId: voiceIds[det.typeIndex], gain: 0, peakDb: f.peakDb, vec: f.vec, s0, s1 });
718+ }
719+ for (const h of hits) h.gain = gainFromPeakDb(h.peakDb, refDb);
720+ take.hits = hits;
721+ saveTakeHits(take.hits);
722+ refreshTimeline();
723+ const algoName = clf.method === 'builtin' ? DEFAULT_CLASSIFIER_NAME : 'the custom algorithm';
724+ const skippedNote = skipped.length ? `; no training audio for ${skipped.join(', ')}` : '';
725+ status(`${hits.length} hits classified by ${algoName} across ${voiceIds.length} trained voice${voiceIds.length === 1 ? '' : 's'}${skippedNote}`);
726+ } catch (err) {
727+ status(`classifier failed: ${err instanceof Error ? err.message : err}; previous hits kept`);
728+ } finally {
729+ btn.disabled = take.clip === null;
730+ syncTakeBar();
731+ }
732+}
733+
734+function processTakeLegacy(samples: Float32Array, sr: number): void {
683735 const sens = Number(($('take-sens') as HTMLInputElement).value);
684736 const onsets = detectOnsets(samples, sr, sens);
685737 const trained = voices.filter((v) => trainPool(v).length > 0);
@@ -746,11 +798,55 @@ $('take-record').addEventListener('click', () => {
746798 else void startRecording({ kind: 'take' });
747799 });
748800
749-$('take-redetect').addEventListener('click', processTake);
801+$('take-redetect').addEventListener('click', () => void processTake());
750802 $('take-sens').addEventListener('change', () => {
751803 ui.takeSens = Number(($('take-sens') as HTMLInputElement).value);
752804 saveUi(ui);
753- if (take.clip) processTake();
805+ if (take.clip) void processTake();
806+});
807+
808+// ---- classifier selection and editor ---------------------------------------
809+
810+const clfMethodSel = $('clf-method') as HTMLSelectElement;
811+
812+function syncClfBar(): void {
813+ clfMethodSel.value = clf.method;
814+ ($('take-sens-wrap') as HTMLElement).hidden = clf.method !== 'legacy';
815+ ($('clf-edit') as HTMLButtonElement).disabled = clf.method === 'legacy';
816+}
817+
818+function syncClfEditor(): void {
819+ if ($('clf-editor').hidden) return;
820+ $('clf-title').textContent = clf.method === 'custom' ? 'custom classifier' : DEFAULT_CLASSIFIER_NAME;
821+ ($('clf-source') as HTMLTextAreaElement).value = activeClfSource();
822+ ($('clf-reset') as HTMLButtonElement).hidden = clf.method !== 'builtin' || clf.builtinSrc === null;
823+}
824+
825+clfMethodSel.addEventListener('change', () => {
826+ clf.method = clfMethodSel.value as typeof clf.method;
827+ saveClassifier(clf);
828+ syncClfBar();
829+ if (clf.method === 'legacy') $('clf-editor').hidden = true;
830+ else syncClfEditor();
831+ if (take.clip) void processTake();
832+});
833+
834+$('clf-edit').addEventListener('click', () => {
835+ $('clf-editor').hidden = false;
836+ syncClfEditor();
837+});
838+$('clf-close').addEventListener('click', () => { $('clf-editor').hidden = true; });
839+($('clf-source') as HTMLTextAreaElement).addEventListener('change', () => {
840+ const src = ($('clf-source') as HTMLTextAreaElement).value;
841+ if (clf.method === 'custom') clf.customSrc = src;
842+ else clf.builtinSrc = src === DEFAULT_CLASSIFIER_SOURCE ? null : src;
843+ saveClassifier(clf);
844+ syncClfEditor();
845+});
846+$('clf-reset').addEventListener('click', () => {
847+ clf.builtinSrc = null;
848+ saveClassifier(clf);
849+ syncClfEditor();
754850 });
755851
756852 timeline.onSelect = (id) => {
@@ -1113,6 +1209,7 @@ $('iwedit-done').addEventListener('click', closeIwEditor);
11131209
11141210 ($('capture-sens') as HTMLInputElement).value = String(ui.captureSens);
11151211 ($('take-sens') as HTMLInputElement).value = String(ui.takeSens);
1212+syncClfBar();
11161213 take.hits = loadTakeHits() ?? [];
11171214 void getTakeAudio().then((a) => {
11181215 if (a) {
src/numbl/algo.worker.tsadded+36−0View file
@@ -0,0 +1,36 @@
1+/// <reference lib="webworker" />
2+/** Runs the classifier off the main thread so a slow or broken custom
3+ * algorithm cannot freeze the page. One request, one response. */
4+import { runAlgorithm } from './execute.ts';
5+import type { Detection, TrainingArrays } from './types.ts';
6+
7+export interface ClfRequest {
8+ id: number;
9+ source: string;
10+ x: Float32Array;
11+ fs: number;
12+ training: TrainingArrays;
13+}
14+
15+export interface ClfResponse {
16+ id: number;
17+ ok: boolean;
18+ detections?: Detection[];
19+ logs: string[];
20+ error?: string;
21+}
22+
23+self.onmessage = (e: MessageEvent<ClfRequest>) => {
24+ const { id, source, x, fs, training } = e.data;
25+ try {
26+ const out = runAlgorithm(source, x, fs, training);
27+ const resp: ClfResponse = { id, ok: true, ...out };
28+ self.postMessage(resp);
29+ } catch (err) {
30+ const resp: ClfResponse = {
31+ id, ok: false, logs: [],
32+ error: err instanceof Error ? err.message : String(err),
33+ };
34+ self.postMessage(resp);
35+ }
36+};
src/numbl/client.tsadded+36−0View file
@@ -0,0 +1,36 @@
1+/** Main-thread side of the classifier worker: one call per take, a fresh
2+ * worker each time so a hung custom algorithm dies with its worker when
3+ * the next classification starts. */
4+import type { ClfRequest, ClfResponse } from './algo.worker.ts';
5+import type { Detection, TrainingArrays } from './types.ts';
6+
7+let current: Worker | null = null;
8+let nextId = 1;
9+
10+export function classifyInWorker(
11+ source: string,
12+ x: Float32Array,
13+ fs: number,
14+ training: TrainingArrays,
15+): Promise<{ detections: Detection[]; logs: string[] }> {
16+ current?.terminate();
17+ const worker = new Worker(new URL('./algo.worker.ts', import.meta.url), { type: 'module' });
18+ current = worker;
19+ const id = nextId++;
20+ const req: ClfRequest = { id, source, x, fs, training };
21+ return new Promise((resolve, reject) => {
22+ worker.onmessage = (e: MessageEvent<ClfResponse>) => {
23+ if (e.data.id !== id) return;
24+ worker.terminate();
25+ if (current === worker) current = null;
26+ if (e.data.ok && e.data.detections) resolve({ detections: e.data.detections, logs: e.data.logs });
27+ else reject(new Error(e.data.error ?? 'classifier failed'));
28+ };
29+ worker.onerror = (e) => {
30+ worker.terminate();
31+ if (current === worker) current = null;
32+ reject(new Error(e.message || 'classifier worker failed'));
33+ };
34+ worker.postMessage(req);
35+ });
36+}
src/numbl/dsp.tsadded+52−0View file
@@ -0,0 +1,52 @@
1+/**
2+ * Rate reduction and onset refinement for the classifier path, both
3+ * carried over from audio-spike-sorting so snippets cut here match the
4+ * ones the benchmark cuts.
5+ */
6+
7+/** Downsample by box-averaging the input samples covered by each output
8+ * sample; a crude but adequate anti-alias for percussive material. */
9+export function downsampleBox(x: Float32Array, srFrom: number, srTo: number): Float32Array {
10+ if (srFrom === srTo) return x;
11+ const n = Math.floor((x.length * srTo) / srFrom);
12+ const out = new Float32Array(n);
13+ for (let i = 0; i < n; i++) {
14+ const a = Math.floor((i * srFrom) / srTo);
15+ const b = Math.max(a + 1, Math.floor(((i + 1) * srFrom) / srTo));
16+ let s = 0;
17+ for (let j = a; j < b && j < x.length; j++) s += x[j];
18+ out[i] = s / (b - a);
19+ }
20+ return out;
21+}
22+
23+/** Refine a coarse onset to the foot of the energy rise: find the
24+ * short-term energy peak within a window after the coarse time, then
25+ * walk back to where the envelope first falls below a small fraction of
26+ * that peak. */
27+export function refineOnset(x: Float32Array, sr: number, tCoarse: number): number {
28+ const w = Math.max(1, Math.round(0.002 * sr));
29+ const from = Math.max(0, Math.round((tCoarse - 0.03) * sr));
30+ const to = Math.min(x.length - w, Math.round((tCoarse + 0.05) * sr));
31+ if (to <= from) return Math.round(tCoarse * sr);
32+
33+ const env = (s: number) => {
34+ let e = 0;
35+ for (let i = 0; i < w; i++) e += x[s + i] * x[s + i];
36+ return e;
37+ };
38+ let peakS = from, peakE = 0;
39+ for (let s = from; s <= to; s += w) {
40+ const e = env(s);
41+ if (e > peakE) { peakE = e; peakS = s; }
42+ }
43+ if (peakE === 0) return Math.round(tCoarse * sr);
44+ let s = peakS;
45+ while (s - w >= from) {
46+ const e = env(s - w);
47+ if (e < 0.02 * peakE) break;
48+ if (e > env(s)) break; // rising again: an earlier, separate bump
49+ s -= w;
50+ }
51+ return s;
52+}
src/numbl/execute.tsadded+68−0View file
@@ -0,0 +1,68 @@
1+/**
2+ * Runs a MATLAB-syntax classifier through numbl, with the same interface
3+ * the audio-spike-sorting benchmark uses, so an algorithm developed
4+ * there drops in here unchanged:
5+ *
6+ * function [times, labels] = anyname(x, fs, training)
7+ *
8+ * x is the take (column vector), fs its rate, and training a struct with
9+ * snips ({1 x nVoices} cell of [snipLen x nSnips] matrices), names, and
10+ * pre (samples before the hit onset within each snippet). It returns
11+ * event times in seconds and 1-based voice indices. Imported only by the
12+ * worker and the node test, never by the page, so numbl stays out of the
13+ * main bundle.
14+ */
15+import { executeCode, RTV } from 'numbl';
16+import type { RuntimeValue } from 'numbl';
17+import { extractFunctionName } from './source.ts';
18+import type { Detection, TrainingArrays } from './types.ts';
19+
20+export function runAlgorithm(
21+ source: string,
22+ x: Float32Array | Float64Array,
23+ fs: number,
24+ training: TrainingArrays,
25+): { detections: Detection[]; logs: string[] } {
26+ const fnName = extractFunctionName(source);
27+ if (!fnName) throw new Error('the classifier must define a function [times, labels] = name(x, fs, training)');
28+
29+ const xd = x instanceof Float64Array ? x : Float64Array.from(x);
30+ const trainingStruct = RTV.struct({
31+ snips: RTV.cell(
32+ training.snips.map((s) => RTV.tensor(Float64Array.from(s.data), [s.snipLen, Math.max(s.count, 0)])),
33+ [1, training.snips.length],
34+ ),
35+ names: RTV.cell(training.names.map((n) => RTV.char(n)), [1, training.names.length]),
36+ pre: RTV.num(training.pre),
37+ });
38+
39+ const logs: string[] = [];
40+ const result = executeCode(
41+ `[times__, labels__] = ${fnName}(x, fs, training);\ntimes__ = times__(:); labels__ = labels__(:);`,
42+ {
43+ initialVariableValues: { x: RTV.tensor(xd, [xd.length, 1]), fs: RTV.num(fs), training: trainingStruct },
44+ optimization: '1',
45+ displayResults: false,
46+ onOutput: (text: string) => logs.push(text),
47+ },
48+ [{ name: `${fnName}.m`, source }],
49+ 'main.m',
50+ );
51+
52+ const times = toVector(result.variableValues.times__);
53+ const labels = toVector(result.variableValues.labels__);
54+ if (times.length !== labels.length) {
55+ throw new Error(`times (${times.length}) and labels (${labels.length}) differ in length`);
56+ }
57+ const detections: Detection[] = times.map((t, i) => ({ t, typeIndex: Math.round(labels[i]) - 1 }));
58+ detections.sort((a, b) => a.t - b.t);
59+ return { detections, logs };
60+}
61+
62+function toVector(v: RuntimeValue | undefined): number[] {
63+ if (v === undefined || v === null) throw new Error('the classifier did not return times/labels');
64+ if (typeof v === 'number') return [v];
65+ const t = v as { data?: Float64Array };
66+ if (t.data instanceof Float64Array) return Array.from(t.data);
67+ throw new Error('times/labels must be numeric vectors');
68+}
src/numbl/index.tsadded+18−0View file
@@ -0,0 +1,18 @@
1+/** The shipped classifier source and the starting point for a custom one. */
2+import silenceGapKnnSrc from './silence_gap_knn.m?raw';
3+
4+export const DEFAULT_CLASSIFIER_NAME = 'silence_gap_knn';
5+export const DEFAULT_CLASSIFIER_SOURCE = silenceGapKnnSrc;
6+
7+export const CUSTOM_CLASSIFIER_TEMPLATE = `function [times, labels] = my_classifier(x, fs, training)
8+% x the take, column vector at fs Hz (16 kHz)
9+% training struct: snips {1 x nVoices} cell of [snipLen x nSnips]
10+% matrices (columns are training snippets), names {1 x nVoices},
11+% pre (samples before the hit onset within each snippet)
12+% Return column vectors: hit times in seconds and 1-based voice indices.
13+% The same interface as the audio-spike-sorting benchmark, so an
14+% algorithm developed there drops in here unchanged.
15+times = zeros(0, 1);
16+labels = zeros(0, 1);
17+end
18+`;
src/numbl/silence_gap_knn.madded+131−0View file
@@ -0,0 +1,131 @@
1+function [times, labels] = silence_gap_knn(x, fs, training)
2+% Segment on quiet gaps, then classify with kNN over spectral shape.
3+%
4+% Ported from the audio-spike-sorting benchmark, where it was developed
5+% against synthesized recordings whose background is digitally silent.
6+% One adaptation for live microphone takes: the segmentation threshold
7+% is raised from a fraction of the recording peak to a robust noise
8+% floor estimate (a multiple of the median absolute sample), since a
9+% mic's background is never exactly zero. On digitally silent audio the
10+% median is zero and the original threshold takes over, so benchmark
11+% behavior is unchanged.
12+%
13+% Detection: samples above the threshold are grouped into regions, gaps
14+% of a few milliseconds inside a region are closed, sub-10 ms glitches
15+% are dropped, and a region much longer than one snippet is split into
16+% snippet-length events, which handles back-to-back hits. The event time
17+% is the region start plus the pre-onset offset. Each event is then
18+% classified by distance-weighted k-nearest-neighbours over
19+% level-invariant spectral features of the post-onset segment.
20+%
21+% Overlapping events remain the limitation: simultaneous hits merge into
22+% one region and only the length-based split separates them.
23+nTypes = numel(training.snips);
24+L = size(training.snips{1}, 1);
25+pre = training.pre;
26+n = numel(x);
27+
28+% ---- detection: contiguous above-noise regions ---------------------------
29+eps0 = max(1e-4 * max(abs(x)), 6 * median(abs(x)));
30+gapClose = round(0.002 * fs);
31+minDur = round(0.010 * fs);
32+
33+starts = zeros(0, 1);
34+ends = zeros(0, 1);
35+i = 1;
36+while i <= n
37+ if abs(x(i)) > eps0
38+ s = i;
39+ e = i;
40+ j = i + 1;
41+ quiet = 0;
42+ while j <= n && quiet <= gapClose
43+ if abs(x(j)) > eps0
44+ e = j;
45+ quiet = 0;
46+ else
47+ quiet = quiet + 1;
48+ end
49+ j = j + 1;
50+ end
51+ if e - s + 1 >= minDur
52+ starts(end+1, 1) = s;
53+ ends(end+1, 1) = e;
54+ end
55+ i = e + gapClose + 1;
56+ else
57+ i = i + 1;
58+ end
59+end
60+
61+% split regions covering several back-to-back snippets
62+ev = zeros(0, 1);
63+for r = 1:numel(starts)
64+ nev = max(1, round((ends(r) - starts(r) + 1) / L));
65+ for q = 1:nev
66+ ev(end+1, 1) = starts(r) + (q - 1) * L;
67+ end
68+end
69+
70+% ---- training features --------------------------------------------------
71+nTrain = 0;
72+for k = 1:nTypes
73+ nTrain = nTrain + size(training.snips{k}, 2);
74+end
75+F = zeros(nTrain, 7);
76+Fl = zeros(nTrain, 1);
77+r = 0;
78+for k = 1:nTypes
79+ S = training.snips{k};
80+ for j = 1:size(S, 2)
81+ r = r + 1;
82+ F(r, :) = spectral_features(S(pre+1:end, j), fs);
83+ Fl(r) = k;
84+ end
85+end
86+mu = mean(F, 1);
87+sd = std(F, 0, 1) + 1e-9;
88+Fz = (F - repmat(mu, nTrain, 1)) ./ repmat(sd, nTrain, 1);
89+
90+% ---- classify each event ------------------------------------------------
91+kNN = min(5, nTrain);
92+times = zeros(0, 1);
93+labels = zeros(0, 1);
94+for e = 1:numel(ev)
95+ s = ev(e);
96+ seg = x(min(n, s + pre):min(n, s + L - 1));
97+ f = (spectral_features(seg, fs) - mu) ./ sd;
98+ d = sqrt(sum((Fz - repmat(f, nTrain, 1)).^2, 2));
99+ [ds, di] = sort(d);
100+ votes = zeros(nTypes, 1);
101+ for v = 1:kNN
102+ votes(Fl(di(v))) = votes(Fl(di(v))) + 1 / (ds(v) + 1e-6);
103+ end
104+ [~, lab] = max(votes);
105+ times(end+1, 1) = (s - 1 + pre) / fs;
106+ labels(end+1, 1) = lab;
107+end
108+end
109+
110+function f = spectral_features(seg, fs)
111+% Six log band-energy ratios (mean removed, so absolute level cancels)
112+% and the log spectral centroid.
113+nfft = 2048;
114+m = min(numel(seg), nfft);
115+s2 = seg(1:m);
116+win = 0.5 - 0.5 * cos(2 * pi * (0:m-1)' / max(m - 1, 1));
117+S = fft(s2 .* win, nfft);
118+p = abs(S(1:nfft/2+1)).^2;
119+hz = fs / nfft;
120+edges = [0 200 500 1000 2000 4000 8000];
121+b = zeros(1, 6);
122+for q = 1:6
123+ k0 = max(2, floor(edges(q) / hz) + 1);
124+ k1 = min(nfft/2 + 1, floor(edges(q+1) / hz));
125+ b(q) = log10(sum(p(k0:k1)) + 1e-12);
126+end
127+b = b - mean(b);
128+kv = (0:numel(p)-1)' * hz;
129+cen = sum(p .* kv) / (sum(p) + 1e-12);
130+f = [b, log10(cen + 1)];
131+end
src/numbl/snippets.tsadded+61−0View file
@@ -0,0 +1,61 @@
1+/**
2+ * Turning the voices' training clips into the snippet matrices a numbl
3+ * classifier takes. Each clip is reduced to the classifier rate once,
4+ * each included onset is refined to the foot of its energy rise, and a
5+ * fixed window is cut, exactly as the audio-spike-sorting benchmark cuts
6+ * its snippets. Voices whose clips have no usable audio contribute
7+ * nothing and are reported, since the classifier cannot land hits on
8+ * them; hits learned from takes (which store features, not audio) do not
9+ * reach this path.
10+ */
11+import type { Voice } from '../store.ts';
12+import { downsampleBox, refineOnset } from './dsp.ts';
13+import { CLF_FS, CLF_PRE, CLF_POST, type TrainingArrays } from './types.ts';
14+
15+export interface VoiceTraining {
16+ training: TrainingArrays;
17+ /** Voice id per training type index (label k maps to voiceIds[k-1]). */
18+ voiceIds: string[];
19+ /** Names of voices that contributed no snippets. */
20+ skipped: string[];
21+}
22+
23+export async function buildVoiceTraining(
24+ voices: Voice[],
25+ getClipAudio: (id: string) => Promise<{ samples: Float32Array; sr: number } | null>,
26+): Promise<VoiceTraining> {
27+ const snipLen = CLF_PRE + CLF_POST;
28+ const perVoice: { voice: Voice; snips: Float32Array[] }[] = [];
29+ for (const voice of voices) {
30+ const snips: Float32Array[] = [];
31+ for (const clip of voice.clips) {
32+ const audio = await getClipAudio(clip.id);
33+ if (!audio) continue;
34+ const x = downsampleBox(audio.samples, audio.sr, CLF_FS);
35+ const excluded = new Set(clip.excluded);
36+ clip.onsets.forEach((t, i) => {
37+ if (excluded.has(i)) return;
38+ const s = refineOnset(x, CLF_FS, t);
39+ if (s - CLF_PRE < 0 || s + CLF_POST > x.length) return;
40+ snips.push(x.slice(s - CLF_PRE, s + CLF_POST));
41+ });
42+ }
43+ perVoice.push({ voice, snips });
44+ }
45+
46+ const contributing = perVoice.filter((p) => p.snips.length > 0);
47+ const training: TrainingArrays = {
48+ names: contributing.map((p) => p.voice.name),
49+ pre: CLF_PRE,
50+ snips: contributing.map((p) => {
51+ const data = new Float32Array(snipLen * p.snips.length);
52+ p.snips.forEach((s, j) => data.set(s, j * snipLen));
53+ return { data, snipLen, count: p.snips.length };
54+ }),
55+ };
56+ return {
57+ training,
58+ voiceIds: contributing.map((p) => p.voice.id),
59+ skipped: perVoice.filter((p) => p.snips.length === 0).map((p) => p.voice.name),
60+ };
61+}
src/numbl/source.tsadded+5−0View file
@@ -0,0 +1,5 @@
1+/** Name of the first function defined in a .m source, or null for a plain script. */
2+export function extractFunctionName(source: string): string | null {
3+ const m = source.match(/^\s*function\s+(?:\[[^\]]*\]\s*=\s*|\w+\s*=\s*)?([A-Za-z]\w*)\s*\(/m);
4+ return m ? m[1] : null;
5+}
src/numbl/types.tsadded+23−0View file
@@ -0,0 +1,23 @@
1+/** Shared classifier types, free of any numbl import. */
2+
3+/** The internal rate classification runs at; the take and the training
4+ * snippets are both reduced to this, matching the audio-spike-sorting
5+ * benchmark, so algorithms transfer between the two apps unchanged. */
6+export const CLF_FS = 16000;
7+/** Snippet window around each training onset at CLF_FS (20 ms / 180 ms). */
8+export const CLF_PRE = Math.round(0.02 * CLF_FS);
9+export const CLF_POST = Math.round(0.18 * CLF_FS);
10+
11+export interface Detection {
12+ /** Event time in seconds. */
13+ t: number;
14+ /** 0-based index into the trained-voice list. */
15+ typeIndex: number;
16+}
17+
18+export interface TrainingArrays {
19+ names: string[];
20+ pre: number;
21+ /** Per voice: column-major [snipLen x count] snippet matrix. */
22+ snips: { data: Float32Array; snipLen: number; count: number }[];
23+}
src/store.tsmodified+17−1View file
@@ -103,8 +103,24 @@ export function saveUi(ui: UiState): void {
103103 localStorage.setItem(KEY_UI, JSON.stringify(ui));
104104 }
105105
106+/** How takes are classified: the shipped numbl algorithm (default, its
107+ * source editable), a custom numbl algorithm, or the original TS
108+ * feature-kNN. Null sources mean "the shipped default" / "the template". */
109+export interface ClassifierState {
110+ method: 'builtin' | 'custom' | 'legacy';
111+ builtinSrc: string | null;
112+ customSrc: string | null;
113+}
114+const KEY_CLF = 'instrawave-compose/classifier';
115+export function loadClassifier(): ClassifierState {
116+ return read(KEY_CLF) ?? { method: 'builtin', builtinSrc: null, customSrc: null };
117+}
118+export function saveClassifier(c: ClassifierState): void {
119+ localStorage.setItem(KEY_CLF, JSON.stringify(c));
120+}
121+
106122 export function clearStore(): void {
107- for (const k of [KEY, KEY_IW, KEY_TAKE, KEY_UI]) localStorage.removeItem(k);
123+ for (const k of [KEY, KEY_IW, KEY_TAKE, KEY_UI, KEY_CLF]) localStorage.removeItem(k);
108124 }
109125
110126 // ---- bulk data in IndexedDB ---------------------------------------------------
↑↓ move↵openescclose