@designcombo/frames
Version:
1 lines • 624 kB
Source Map (JSON)
{"version":3,"file":"frames.umd.cjs","sources":["../src/dom-utils.ts","../node_modules/.pnpm/opfs-tools@0.6.1/node_modules/opfs-tools/dist/opfs-tools.js","../src/log.ts","../src/worker-timer.ts","../node_modules/.pnpm/wave-resampler@1.0.0/node_modules/wave-resampler/lib/interpolator.js","../node_modules/.pnpm/wave-resampler@1.0.0/node_modules/wave-resampler/lib/fir-lpf.js","../node_modules/.pnpm/wave-resampler@1.0.0/node_modules/wave-resampler/lib/butterworth-lpf.js","../node_modules/.pnpm/wave-resampler@1.0.0/node_modules/wave-resampler/index.js","../src/av-utils.ts","../node_modules/.pnpm/mp4box@0.5.2/node_modules/mp4box/dist/mp4box.all.js","../src/clips/iclip.ts","../src/mp4-utils/mp4box-utils.ts","../src/mp4-utils/sample-transform.ts","../src/clips/mp4-clip.ts","../src/event-tool.ts","../src/mp4-utils/meta-box.ts","../src/mp4-utils/index.ts"],"sourcesContent":["export function createEl(tagName: string): HTMLElement {\n return document.createElement(tagName);\n}\n\nexport function renderTxt2Img(txt: string, cssText: string): HTMLImageElement {\n const div = createEl(\"pre\");\n div.style.cssText = `margin: 0; ${cssText}; visibility: hidden; position: fixed;`;\n div.textContent = txt;\n document.body.appendChild(div);\n\n const { width, height } = div.getBoundingClientRect();\n div.remove();\n div.style.visibility = \"visible\";\n\n const img = new Image();\n img.width = width;\n img.height = height;\n const svgStr = `\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\">\n <foreignObject width=\"100%\" height=\"100%\">\n <div xmlns=\"http://www.w3.org/1999/xhtml\">${div.outerHTML}</div>\n </foreignObject>\n </svg>\n `\n .replace(/\\t/g, \"\")\n .replace(/#/g, \"%23\");\n\n img.src = `data:image/svg+xml;charset=utf-8,${svgStr}`;\n return img;\n}\n\nexport async function renderTxt2ImgBitmap(\n txt: string,\n cssText: string\n): Promise<ImageBitmap> {\n const imgEl = renderTxt2Img(txt, cssText);\n await new Promise((resolve) => {\n imgEl.onload = resolve;\n });\n const cvs = new OffscreenCanvas(imgEl.width, imgEl.height);\n const ctx = cvs.getContext(\"2d\");\n ctx?.drawImage(imgEl, 0, 0, imgEl.width, imgEl.height);\n return await createImageBitmap(cvs);\n}\n","var O = (t) => {\n throw TypeError(t);\n};\nvar E = (t, e, r) => e.has(t) || O(\"Cannot \" + r);\nvar s = (t, e, r) => (E(t, e, \"read from private field\"), r ? r.call(t) : e.get(t)), o = (t, e, r) => e.has(t) ? O(\"Cannot add the same private member more than once\") : e instanceof WeakSet ? e.add(t) : e.set(t, r), l = (t, e, r, a) => (E(t, e, \"write to private field\"), a ? a.call(t, r) : e.set(t, r), r);\nconst z = \"KGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2Z1bmN0aW9uIHUobil7aWYobj09PSIvIilyZXR1cm57cGFyZW50Om51bGwsbmFtZToiIn07Y29uc3QgZT1uLnNwbGl0KCIvIikuZmlsdGVyKGk9PmkubGVuZ3RoPjApO2lmKGUubGVuZ3RoPT09MCl0aHJvdyBFcnJvcigiSW52YWxpZCBwYXRoIik7Y29uc3QgYT1lW2UubGVuZ3RoLTFdLHI9Ii8iK2Uuc2xpY2UoMCwtMSkuam9pbigiLyIpO3JldHVybntuYW1lOmEscGFyZW50OnJ9fWFzeW5jIGZ1bmN0aW9uIHcobixlKXtjb25zdHtwYXJlbnQ6YSxuYW1lOnJ9PXUobik7aWYoYT09bnVsbClyZXR1cm4gYXdhaXQgbmF2aWdhdG9yLnN0b3JhZ2UuZ2V0RGlyZWN0b3J5KCk7Y29uc3QgaT1hLnNwbGl0KCIvIikuZmlsdGVyKHQ9PnQubGVuZ3RoPjApO3RyeXtsZXQgdD1hd2FpdCBuYXZpZ2F0b3Iuc3RvcmFnZS5nZXREaXJlY3RvcnkoKTtmb3IoY29uc3QgcyBvZiBpKXQ9YXdhaXQgdC5nZXREaXJlY3RvcnlIYW5kbGUocyx7Y3JlYXRlOmUuY3JlYXRlfSk7aWYoZS5pc0ZpbGUpcmV0dXJuIGF3YWl0IHQuZ2V0RmlsZUhhbmRsZShyLHtjcmVhdGU6ZS5jcmVhdGV9KX1jYXRjaCh0KXtpZih0Lm5hbWU9PT0iTm90Rm91bmRFcnJvciIpcmV0dXJuIG51bGw7dGhyb3cgdH19Y29uc3QgZj17fTtzZWxmLm9ubWVzc2FnZT1hc3luYyBuPT57dmFyIGk7Y29uc3R7ZXZ0VHlwZTplLGFyZ3M6YX09bi5kYXRhO2xldCByPWZbYS5maWxlSWRdO3RyeXtsZXQgdDtjb25zdCBzPVtdO2lmKGU9PT0icmVnaXN0ZXIiKXtjb25zdCBsPWF3YWl0IHcoYS5maWxlUGF0aCx7Y3JlYXRlOiEwLGlzRmlsZTohMH0pO2lmKGw9PW51bGwpdGhyb3cgRXJyb3IoYG5vdCBmb3VuZCBmaWxlOiAke2EuZmlsZUlkfWApO3I9YXdhaXQgbC5jcmVhdGVTeW5jQWNjZXNzSGFuZGxlKHttb2RlOmEubW9kZX0pLGZbYS5maWxlSWRdPXJ9ZWxzZSBpZihlPT09ImNsb3NlIilhd2FpdCByLmNsb3NlKCksZGVsZXRlIGZbYS5maWxlSWRdO2Vsc2UgaWYoZT09PSJ0cnVuY2F0ZSIpYXdhaXQgci50cnVuY2F0ZShhLm5ld1NpemUpO2Vsc2UgaWYoZT09PSJ3cml0ZSIpe2NvbnN0e2RhdGE6bCxvcHRzOm99PW4uZGF0YS5hcmdzO3Q9YXdhaXQgci53cml0ZShsLG8pfWVsc2UgaWYoZT09PSJyZWFkIil7Y29uc3R7b2Zmc2V0Omwsc2l6ZTpvfT1uLmRhdGEuYXJncyxnPW5ldyBVaW50OEFycmF5KG8pLGQ9YXdhaXQgci5yZWFkKGcse2F0Omx9KSxjPWcuYnVmZmVyO3Q9ZD09PW8/YzooKGk9Yy50cmFuc2Zlcik9PW51bGw/dm9pZCAwOmkuY2FsbChjLGQpKT8/Yy5zbGljZSgwLGQpLHMucHVzaCh0KX1lbHNlIGU9PT0iZ2V0U2l6ZSI/dD1hd2FpdCByLmdldFNpemUoKTplPT09ImZsdXNoIiYmYXdhaXQgci5mbHVzaCgpO3NlbGYucG9zdE1lc3NhZ2Uoe2V2dFR5cGU6ImNhbGxiYWNrIixjYklkOm4uZGF0YS5jYklkLHJldHVyblZhbDp0fSxzKX1jYXRjaCh0KXtjb25zdCBzPXQ7c2VsZi5wb3N0TWVzc2FnZSh7ZXZ0VHlwZToidGhyb3dFcnJvciIsY2JJZDpuLmRhdGEuY2JJZCxlcnJNc2c6cy5uYW1lKyI6ICIrcy5tZXNzYWdlK2AKYCtKU09OLnN0cmluZ2lmeShuLmRhdGEpfSl9fX0pKCk7Ci8vIyBzb3VyY2VNYXBwaW5nVVJMPW9wZnMtd29ya2VyLUY0UldscWNfLmpzLm1hcAo=\", B = (t) => Uint8Array.from(atob(t), (e) => e.charCodeAt(0)), U = typeof self < \"u\" && self.Blob && new Blob([B(z)], { type: \"text/javascript;charset=utf-8\" });\nfunction Q(t) {\n let e;\n try {\n if (e = U && (self.URL || self.webkitURL).createObjectURL(U), !e) throw \"\";\n const r = new Worker(e, {\n name: t == null ? void 0 : t.name\n });\n return r.addEventListener(\"error\", () => {\n (self.URL || self.webkitURL).revokeObjectURL(e);\n }), r;\n } catch {\n return new Worker(\n \"data:text/javascript;base64,\" + z,\n {\n name: t == null ? void 0 : t.name\n }\n );\n } finally {\n e && (self.URL || self.webkitURL).revokeObjectURL(e);\n }\n}\nasync function D(t, e, r) {\n const a = M();\n return await a(\"register\", { fileId: t, filePath: e, mode: r }), {\n read: async (i, n) => await a(\"read\", {\n fileId: t,\n offset: i,\n size: n\n }),\n write: async (i, n) => await a(\n \"write\",\n {\n fileId: t,\n data: i,\n opts: n\n },\n [ArrayBuffer.isView(i) ? i.buffer : i]\n ),\n close: async () => await a(\"close\", {\n fileId: t\n }),\n truncate: async (i) => await a(\"truncate\", {\n fileId: t,\n newSize: i\n }),\n getSize: async () => await a(\"getSize\", {\n fileId: t\n }),\n flush: async () => await a(\"flush\", {\n fileId: t\n })\n };\n}\nconst I = [];\nlet V = 0;\nfunction M() {\n if (I.length < 3) {\n const e = t();\n return I.push(e), e;\n } else {\n const e = I[V];\n return V = (V + 1) % I.length, e;\n }\n function t() {\n const e = new Q();\n let r = 0, a = {};\n return e.onmessage = ({\n data: i\n }) => {\n var n, c;\n i.evtType === \"callback\" ? (n = a[i.cbId]) == null || n.resolve(i.returnVal) : i.evtType === \"throwError\" && ((c = a[i.cbId]) == null || c.reject(Error(i.errMsg))), delete a[i.cbId];\n }, async function(n, c, u = []) {\n r += 1;\n const h = new Promise((b, T) => {\n a[r] = { resolve: b, reject: T };\n });\n return e.postMessage(\n {\n cbId: r,\n evtType: n,\n args: c\n },\n u\n ), h;\n };\n }\n}\nfunction v(t) {\n if (t === \"/\") return { parent: null, name: \"\" };\n const e = t.split(\"/\").filter((i) => i.length > 0);\n if (e.length === 0) throw Error(\"Invalid path\");\n const r = e[e.length - 1], a = \"/\" + e.slice(0, -1).join(\"/\");\n return { name: r, parent: a };\n}\nasync function d(t, e) {\n const { parent: r, name: a } = v(t);\n if (r == null) return await navigator.storage.getDirectory();\n const i = r.split(\"/\").filter((n) => n.length > 0);\n try {\n let n = await navigator.storage.getDirectory();\n for (const c of i)\n n = await n.getDirectoryHandle(c, {\n create: e.create\n });\n return e.isFile ? await n.getFileHandle(a, {\n create: e.create\n }) : await n.getDirectoryHandle(a, {\n create: e.create\n });\n } catch (n) {\n if (n.name === \"NotFoundError\")\n return null;\n throw n;\n }\n}\nasync function j(t) {\n const { parent: e, name: r } = v(t);\n if (e == null) {\n const i = await navigator.storage.getDirectory();\n for await (const n of i.keys())\n await i.removeEntry(n, { recursive: !0 });\n return;\n }\n const a = await d(e, {\n create: !1,\n isFile: !1\n });\n a != null && await a.removeEntry(r, { recursive: !0 });\n}\nfunction k(t, e) {\n return `${t}/${e}`.replace(\"//\", \"/\");\n}\nfunction g(t) {\n return new K(t);\n}\nvar w, S, p;\nclass K {\n constructor(e) {\n o(this, w);\n o(this, S);\n o(this, p);\n l(this, w, e);\n const { parent: r, name: a } = v(e);\n l(this, S, a), l(this, p, r);\n }\n get kind() {\n return \"dir\";\n }\n get name() {\n return s(this, S);\n }\n get path() {\n return s(this, w);\n }\n get parent() {\n return s(this, p) == null ? null : g(s(this, p));\n }\n /**\n * Creates the directory.\n * return A promise that resolves when the directory is created.\n */\n async create() {\n return await d(s(this, w), {\n create: !0,\n isFile: !1\n }), g(s(this, w));\n }\n /**\n * Checks if the directory exists.\n * return A promise that resolves to true if the directory exists, otherwise false.\n */\n async exists() {\n return await d(s(this, w), {\n create: !1,\n isFile: !1\n }) instanceof FileSystemDirectoryHandle;\n }\n /**\n * Removes the directory.\n * return A promise that resolves when the directory is removed.\n */\n async remove() {\n for (const e of await this.children())\n try {\n await e.remove();\n } catch (r) {\n console.warn(r);\n }\n try {\n await j(s(this, w));\n } catch (e) {\n console.warn(e);\n }\n }\n /**\n * Retrieves the children of the directory.\n * return A promise that resolves to an array of objects representing the children.\n */\n async children() {\n const e = await d(s(this, w), {\n create: !1,\n isFile: !1\n });\n if (e == null) return [];\n const r = [];\n for await (const a of e.values())\n r.push((a.kind === \"file\" ? m : g)(k(s(this, w), a.name)));\n return r;\n }\n /**\n * If the dest folder exists, copy the current directory into the dest folder;\n * if the dest folder does not exist, rename the current directory to dest name.\n */\n async copyTo(e) {\n if (!await this.exists())\n throw Error(`dir ${this.path} not exists`);\n const r = await e.exists() ? g(k(e.path, this.name)) : e;\n return await r.create(), await Promise.all((await this.children()).map((a) => a.copyTo(r))), r;\n }\n /**\n * move directory, copy then remove current\n */\n async moveTo(e) {\n const r = await this.copyTo(e);\n return await this.remove(), r;\n }\n}\nw = new WeakMap(), S = new WeakMap(), p = new WeakMap();\nconst C = /* @__PURE__ */ new Map();\nfunction m(t, e = \"rw\") {\n if (e === \"rw\") {\n const r = C.get(t) ?? new F(t, e);\n return C.set(t, r), r;\n }\n return new F(t, e);\n}\nasync function J(t, e, r = { overwrite: !0 }) {\n if (e instanceof F) {\n await J(t, await e.stream(), r);\n return;\n }\n const a = await (t instanceof F ? t : m(t, \"rw\")).createWriter();\n try {\n if (r.overwrite && await a.truncate(0), e instanceof ReadableStream) {\n const i = e.getReader();\n for (; ; ) {\n const { done: n, value: c } = await i.read();\n if (n) break;\n await a.write(c);\n }\n } else\n await a.write(e);\n } catch (i) {\n throw i;\n } finally {\n await a.close();\n }\n}\nlet _ = 0;\nconst A = () => ++_;\nvar f, Z, G, Y, X, y, R, W;\nconst P = class P {\n constructor(e, r) {\n o(this, f);\n o(this, Z);\n o(this, G);\n o(this, Y);\n o(this, X);\n o(this, y, 0);\n o(this, R, /* @__PURE__ */ (() => {\n let e = null;\n return () => (l(this, y, s(this, y) + 1), e ?? (e = new Promise(async (r, a) => {\n try {\n const i = await D(\n s(this, X),\n s(this, f),\n s(this, Y)\n );\n r([\n i,\n async () => {\n l(this, y, s(this, y) - 1), !(s(this, y) > 0) && (e = null, await i.close());\n }\n ]);\n } catch (i) {\n a(i);\n }\n })));\n })());\n o(this, W, !1);\n l(this, X, A()), l(this, f, e), l(this, Y, {\n r: \"read-only\",\n rw: \"readwrite\",\n \"rw-unsafe\": \"readwrite-unsafe\"\n }[r]);\n const { parent: a, name: i } = v(e);\n l(this, G, i), l(this, Z, a);\n }\n get kind() {\n return \"file\";\n }\n get path() {\n return s(this, f);\n }\n get name() {\n return s(this, G);\n }\n get parent() {\n return s(this, Z) == null ? null : g(s(this, Z));\n }\n /**\n * Random write to file\n */\n async createWriter() {\n if (s(this, Y) === \"read-only\") throw Error(\"file is read-only\");\n if (s(this, W)) throw Error(\"Other writer have not been closed\");\n l(this, W, !0);\n const e = new TextEncoder(), [r, a] = await s(this, R).call(this);\n let i = await r.getSize(), n = !1;\n return {\n write: async (c, u = {}) => {\n if (n) throw Error(\"Writer is closed\");\n const h = typeof c == \"string\" ? e.encode(c) : c, b = u.at ?? i, T = h.byteLength;\n return i = b + T, await r.write(h, { at: b });\n },\n truncate: async (c) => {\n if (n) throw Error(\"Writer is closed\");\n await r.truncate(c), i > c && (i = c);\n },\n flush: async () => {\n if (n) throw Error(\"Writer is closed\");\n await r.flush();\n },\n close: async () => {\n if (n) throw Error(\"Writer is closed\");\n n = !0, l(this, W, !1), await a();\n }\n };\n }\n /**\n * Random access to file\n */\n async createReader() {\n const [e, r] = await s(this, R).call(this);\n let a = !1, i = 0;\n return {\n read: async (n, c = {}) => {\n if (a) throw Error(\"Reader is closed\");\n const u = c.at ?? i, h = await e.read(u, n);\n return i = u + h.byteLength, h;\n },\n getSize: async () => {\n if (a) throw Error(\"Reader is closed\");\n return await e.getSize();\n },\n close: async () => {\n a || (a = !0, await r());\n }\n };\n }\n async text() {\n return new TextDecoder().decode(await this.arrayBuffer());\n }\n async arrayBuffer() {\n const e = await d(s(this, f), { create: !1, isFile: !0 });\n return e == null ? new ArrayBuffer(0) : (await e.getFile()).arrayBuffer();\n }\n async stream() {\n const e = await this.getOriginFile();\n return e == null ? new ReadableStream({\n pull: (r) => {\n r.close();\n }\n }) : e.stream();\n }\n async getOriginFile() {\n var e;\n return (e = await d(s(this, f), { create: !1, isFile: !0 })) == null ? void 0 : e.getFile();\n }\n async getSize() {\n const e = await d(s(this, f), { create: !1, isFile: !0 });\n return e == null ? 0 : (await e.getFile()).size;\n }\n async exists() {\n return await d(s(this, f), {\n create: !1,\n isFile: !0\n }) instanceof FileSystemFileHandle;\n }\n async remove() {\n if (s(this, y)) throw Error(\"exists unclosed reader/writer\");\n await j(s(this, f));\n }\n /**\n * If the target is a file, use current overwrite the target;\n * if the target is a folder, copy the current file into that folder.\n */\n async copyTo(e) {\n if (!await this.exists())\n throw Error(`file ${this.path} not exists`);\n if (e instanceof P)\n return m(e.path) === this ? this : (await J(e.path, this), m(e.path));\n if (e instanceof K)\n return await this.copyTo(m(k(e.path, this.name)));\n throw Error(\"Illegal target type\");\n }\n /**\n * move file, copy then remove current\n */\n async moveTo(e) {\n const r = await this.copyTo(e);\n return await this.remove(), r;\n }\n};\nf = new WeakMap(), Z = new WeakMap(), G = new WeakMap(), Y = new WeakMap(), X = new WeakMap(), y = new WeakMap(), R = new WeakMap(), W = new WeakMap();\nlet F = P;\nconst L = \"/.opfs-tools-temp-dir\";\nasync function N(t) {\n try {\n if (t.kind === \"file\") {\n if (!await t.exists()) return !0;\n const e = await t.createWriter();\n await e.truncate(0), await e.close(), await t.remove();\n } else\n await t.remove();\n return !0;\n } catch (e) {\n return console.warn(e), !1;\n }\n}\nfunction $() {\n setInterval(async () => {\n for (const e of await g(L).children()) {\n const r = /^\\d+-(\\d+)$/.exec(e.name);\n (r == null || Date.now() - Number(r[1]) > 2592e5) && await N(e);\n }\n }, 60 * 1e3);\n}\nconst x = [];\nlet H = !1;\nasync function q() {\n if (globalThis.localStorage == null) return;\n const t = \"OPFS_TOOLS_EXPIRES_TMP_FILES\";\n H || (H = !0, globalThis.addEventListener(\"unload\", () => {\n x.length !== 0 && localStorage.setItem(\n t,\n `${localStorage.getItem(t) ?? \"\"},${x.join(\",\")}`\n );\n }));\n let e = localStorage.getItem(t) ?? \"\";\n for (const r of e.split(\",\"))\n r.length !== 0 && await N(m(`${L}/${r}`)) && (e = e.replace(r, \"\"));\n localStorage.setItem(t, e.replace(/,{2,}/g, \",\"));\n}\n(async function() {\n var e;\n globalThis.__opfs_tools_tmpfile_init__ !== !0 && (globalThis.__opfs_tools_tmpfile_init__ = !0, !(globalThis.FileSystemDirectoryHandle == null || globalThis.FileSystemFileHandle == null || ((e = globalThis.navigator) == null ? void 0 : e.storage.getDirectory) == null) && ($(), await q()));\n})();\nfunction te() {\n const t = `${Math.random().toString().slice(2)}-${Date.now()}`;\n return x.push(t), m(`${L}/${t}`);\n}\nfunction re(t, e) {\n let r = m(t), a = 0, i = r.createWriter(), n = r.createReader();\n const c = async (u) => {\n const b = await (await n).read(a, { at: Math.round(a * 0.3) });\n a = await u.write(b, { at: 0 }), await u.truncate(a);\n };\n return {\n append: async (u) => {\n const h = await i;\n a += await h.write(u), a >= e && await c(h);\n },\n text: r.text.bind(r),\n remove: async () => {\n await (await n).close(), await (await i).close(), await r.remove();\n },\n getSize: async () => a\n };\n}\nexport {\n g as dir,\n m as file,\n re as rollfile,\n te as tmpfile,\n J as write\n};\n//# sourceMappingURL=opfs-tools.js.map\n","import { tmpfile } from \"opfs-tools\";\n\nfunction any2Str(val: any): string {\n if (val instanceof Error) return String(val);\n return typeof val === \"object\"\n ? JSON.stringify(val, (_, v) => (v instanceof Error ? String(v) : v))\n : String(val);\n}\n\nfunction getTimeStr() {\n const d = new Date();\n return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}.${d.getMilliseconds()}`;\n}\n\nlet THRESHOLD = 1;\n\nconst localFile = tmpfile();\n\nlet writer: Awaited<ReturnType<typeof localFile.createWriter>> | null = null;\n\ntype LvName = \"debug\" | \"info\" | \"warn\" | \"error\";\nconst lvHandler = [\"debug\", \"info\", \"warn\", \"error\"].reduce(\n (acc, lvName, lvThres) =>\n Object.assign(acc, {\n [lvName]: (...args: any[]) => {\n if (THRESHOLD <= lvThres) {\n console[lvName as LvName](...args);\n writer?.write(\n `[${lvName}][${getTimeStr()}] ${args\n .map((a) => any2Str(a))\n .join(\" \")}\\n`\n );\n }\n },\n }),\n {} as Record<LvName, typeof console.log>\n);\n\nconst map = new Map<Function, number>();\n\nexport const Log = {\n setLogLevel: <T extends Function>(logfn: T) => {\n THRESHOLD = map.get(logfn) ?? 1;\n },\n ...lvHandler,\n create: (tag: string) => {\n return Object.fromEntries(\n Object.entries(lvHandler).map(([k, h]) => [\n k,\n (...args: any[]) => h(tag, ...args),\n ])\n );\n },\n\n async dump() {\n await initPromise;\n await writer?.flush();\n return await localFile.text();\n },\n};\n\nmap.set(Log.debug, 0);\nmap.set(Log.info, 1);\nmap.set(Log.warn, 2);\nmap.set(Log.error, 3);\n\nasync function init() {\n try {\n writer = await localFile.createWriter();\n Log.info(navigator.userAgent);\n Log.info(\"date: \" + new Date().toLocaleDateString());\n } catch (err) {\n if (!(err instanceof Error)) throw err;\n if (err.message.includes(\"createSyncAccessHandle is not a function\")) {\n console.warn(err);\n } else {\n throw err;\n }\n }\n}\n\nconst initPromise = globalThis.navigator == null ? null : init();\n\nif (import.meta.env?.DEV) {\n Log.setLogLevel(Log.debug);\n}\n\nif (import.meta.env?.MODE === \"test\") {\n Log.setLogLevel(Log.warn);\n}\n","const setup = (): void => {\n let timerId: number;\n\n let interval: number = 16.6;\n\n self.onmessage = (e) => {\n if (e.data.event === \"start\") {\n self.clearInterval(timerId);\n timerId = self.setInterval(() => {\n self.postMessage({});\n }, interval);\n }\n\n if (e.data.event === \"stop\") {\n self.clearInterval(timerId);\n }\n };\n};\n\nconst createWorker = (): Worker => {\n const blob = new Blob([`(${setup.toString()})()`]);\n const url = URL.createObjectURL(blob);\n return new Worker(url);\n};\n\nconst handlerMap = new Map<number, Set<() => void>>();\nlet runCount = 1;\n\nlet worker: Worker | null = null;\nif (globalThis.Worker != null) {\n worker = createWorker();\n worker.onmessage = () => {\n runCount += 1;\n for (const [k, v] of handlerMap) {\n if (runCount % k === 0) for (const fn of v) fn();\n }\n };\n}\n\nexport const workerTimer = (\n handler: () => void,\n time: number\n): (() => void) => {\n const groupId = Math.round(time / 16.6);\n const fns = handlerMap.get(groupId) ?? new Set();\n fns.add(handler);\n handlerMap.set(groupId, fns);\n\n if (handlerMap.size === 1 && fns.size === 1) {\n worker?.postMessage({ event: \"start\" });\n }\n\n return () => {\n fns.delete(handler);\n if (fns.size === 0) handlerMap.delete(groupId);\n if (handlerMap.size === 0) {\n runCount = 0;\n worker?.postMessage({ event: \"stop\" });\n }\n };\n};\n","/*\r\n * Copyright (c) 2019 Rafael da Silva Rocha.\r\n * Copyright 2012 Spencer Cohen\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining\r\n * a copy of this software and associated documentation files (the\r\n * \"Software\"), to deal in the Software without restriction, including\r\n * without limitation the rights to use, copy, modify, merge, publish,\r\n * distribute, sublicense, and/or sell copies of the Software, and to\r\n * permit persons to whom the Software is furnished to do so, subject to\r\n * the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n *\r\n */\r\n\r\n/**\r\n * @fileoverview The Interpolator class. Based on Smooth.js by Spencer Cohen.\r\n * @see https://github.com/rochars/wave-resampler\r\n * @see https://github.com/osuushi/Smooth.js\r\n */\r\n\r\n/**\r\n * A class to get scaled values out of arrays.\r\n */\r\nexport class Interpolator {\r\n \r\n /**\r\n * @param {number} scaleFrom the length of the original array.\r\n * @param {number} scaleTo The length of the new array.\r\n * @param {?Object} details The extra configuration, if needed.\r\n */\r\n constructor(scaleFrom, scaleTo, details) {\r\n /**\r\n * The length of the original array.\r\n * @type {number}\r\n */\r\n this.length_ = scaleFrom;\r\n /**\r\n * The scaling factor.\r\n * @type {number}\r\n */\r\n this.scaleFactor_ = (scaleFrom - 1) / scaleTo;\r\n /**\r\n * The interpolation function.\r\n * @type {Function}\r\n */\r\n this.interpolate = this.cubic;\r\n if (details.method === 'point') {\r\n \tthis.interpolate = this.point;\r\n } else if(details.method === 'linear') {\r\n \tthis.interpolate = this.linear;\r\n } else if(details.method === 'sinc') {\r\n \tthis.interpolate = this.sinc;\r\n }\r\n /**\r\n * The tanget factor for cubic interpolation.\r\n * @type {number}\r\n */\r\n this.tangentFactor_ = 1 - Math.max(0, Math.min(1, details.tension || 0));\r\n // Configure the kernel for sinc\r\n /**\r\n * The sinc filter size.\r\n * @type {number}\r\n */\r\n this.sincFilterSize_ = details.sincFilterSize || 1;\r\n /**\r\n * The sinc kernel.\r\n * @type {Function}\r\n */\r\n this.kernel_ = sincKernel_(details.sincWindow || window_);\r\n }\r\n\r\n /**\r\n * @param {number} t The index to interpolate.\r\n * @param {Array|TypedArray} samples the original array.\r\n * @return {number} The interpolated value.\r\n */\r\n point(t, samples) {\r\n return this.getClippedInput_(Math.round(this.scaleFactor_ * t), samples);\r\n }\r\n\r\n /**\r\n * @param {number} t The index to interpolate.\r\n * @param {Array|TypedArray} samples the original array.\r\n * @return {number} The interpolated value.\r\n */\r\n linear(t, samples) {\r\n t = this.scaleFactor_ * t;\r\n let k = Math.floor(t);\r\n t -= k;\r\n return (1 - t) *\r\n \tthis.getClippedInput_(k, samples) + t *\r\n \tthis.getClippedInput_(k + 1, samples);\r\n }\r\n\r\n /**\r\n * @param {number} t The index to interpolate.\r\n * @param {Array|TypedArray} samples the original array.\r\n * @return {number} The interpolated value.\r\n */\r\n cubic(t, samples) {\r\n t = this.scaleFactor_ * t;\r\n let k = Math.floor(t);\r\n let m = [this.getTangent_(k, samples), this.getTangent_(k + 1, samples)];\r\n let p = [this.getClippedInput_(k, samples),\r\n this.getClippedInput_(k + 1, samples)];\r\n t -= k;\r\n let t2 = t * t;\r\n let t3 = t * t2;\r\n return (2 * t3 - 3 * t2 + 1) *\r\n p[0] + (t3 - 2 * t2 + t) *\r\n m[0] + (-2 * t3 + 3 * t2) *\r\n p[1] + (t3 - t2) * m[1];\r\n }\r\n\r\n /**\r\n * @param {number} t The index to interpolate.\r\n * @param {Array|TypedArray} samples the original array.\r\n * @return {number} The interpolated value.\r\n */\r\n sinc(t, samples) {\r\n t = this.scaleFactor_ * t;\r\n let k = Math.floor(t);\r\n let ref = k - this.sincFilterSize_ + 1;\r\n let ref1 = k + this.sincFilterSize_;\r\n let sum = 0;\r\n for (let n = ref; n <= ref1; n++) {\r\n sum += this.kernel_(t - n) * this.getClippedInput_(n, samples);\r\n }\r\n return sum;\r\n }\r\n\r\n /**\r\n * @param {number} k The scaled index to interpolate.\r\n * @param {Array|TypedArray} samples the original array.\r\n * @return {number} The tangent.\r\n * @private\r\n */\r\n getTangent_(k, samples) {\r\n return this.tangentFactor_ *\r\n (this.getClippedInput_(k + 1, samples) -\r\n this.getClippedInput_(k - 1, samples)) / 2;\r\n }\r\n\r\n /**\r\n * @param {number} t The scaled index to interpolate.\r\n * @param {Array|TypedArray} samples the original array.\r\n * @return {number} The interpolated value.\r\n * @private\r\n */\r\n getClippedInput_(t, samples) {\r\n if ((0 <= t && t < this.length_)) {\r\n return samples[t];\r\n }\r\n return 0;\r\n }\r\n}\r\n\r\n// Sinc functions\r\n\r\n/**\r\n * The default window function.\r\n * @param {number} x The sinc signal.\r\n * @return {number}\r\n * @private\r\n */\r\nfunction window_(x) {\r\n return Math.exp(-x / 2 * x / 2);\r\n}\r\n\r\n/**\r\n * @param {Function} window The window function.\r\n * @return {Function}\r\n * @private\r\n */\r\nfunction sincKernel_(window) {\r\n return function(x) { return sinc_(x) * window(x); };\r\n}\r\n\r\n/**\r\n * @param {number} x The sinc signal.\r\n * @return {number}\r\n * @private\r\n */\r\nfunction sinc_(x) {\r\n if (x === 0) {\r\n return 1;\r\n }\r\n return Math.sin(Math.PI * x) / (Math.PI * x);\r\n}\r\n\r\n","/*\r\n * Copyright (c) 2019 Rafael da Silva Rocha.\r\n * Copyright (c) 2014 Florian Markert\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining\r\n * a copy of this software and associated documentation files (the\r\n * \"Software\"), to deal in the Software without restriction, including\r\n * without limitation the rights to use, copy, modify, merge, publish,\r\n * distribute, sublicense, and/or sell copies of the Software, and to\r\n * permit persons to whom the Software is furnished to do so, subject to\r\n * the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n *\r\n */\r\n\r\n/**\r\n * @fileoverview FIR LPF. Based on the FIR LPF from Fili by Florian Markert.\r\n * @see https://github.com/rochars/wave-resampler\r\n * @see https://github.com/markert/fili.js\r\n */\r\n\r\n/**\r\n * A FIR low pass filter.\r\n */\r\nexport class FIRLPF {\r\n \r\n /**\r\n * @param {number} order The order of the filter.\r\n * @param {number} sampleRate The sample rate.\r\n * @param {number} cutOff The cut off frequency.\r\n */\r\n constructor(order, sampleRate, cutOff) {\r\n let omega = 2 * Math.PI * cutOff / sampleRate;\r\n let dc = 0;\r\n this.filters = [];\r\n for (let i = 0; i <= order; i++) {\r\n if (i - order / 2 === 0) {\r\n this.filters[i] = omega;\r\n } else {\r\n this.filters[i] = Math.sin(omega * (i - order / 2)) / (i - order / 2);\r\n // Hamming window\r\n this.filters[i] *= (0.54 - 0.46 * Math.cos(2 * Math.PI * i / order));\r\n }\r\n dc = dc + this.filters[i];\r\n }\r\n // normalize\r\n for (let i = 0; i <= order; i++) {\r\n this.filters[i] /= dc;\r\n }\r\n this.z = this.initZ_();\r\n }\r\n\r\n /**\r\n * @param {number} sample A sample of a sequence.\r\n * @return {number}\r\n */\r\n filter(sample) {\r\n this.z.buf[this.z.pointer] = sample;\r\n let out = 0;\r\n for (let i = 0, len = this.z.buf.length; i < len; i++) {\r\n out += (\r\n this.filters[i] * this.z.buf[(this.z.pointer + i) % this.z.buf.length]);\r\n }\r\n this.z.pointer = (this.z.pointer + 1) % (this.z.buf.length);\r\n return out;\r\n }\r\n\r\n /**\r\n * Reset the filter.\r\n */\r\n reset() {\r\n this.z = this.initZ_();\r\n }\r\n\r\n /**\r\n * Return the default value for z.\r\n * @private\r\n */\r\n initZ_() {\r\n let r = [];\r\n for (let i = 0; i < this.filters.length - 1; i++) {\r\n r.push(0);\r\n }\r\n return {\r\n buf: r,\r\n pointer: 0\r\n };\r\n }\r\n}\r\n","/*\r\n * Copyright (c) 2019 Rafael da Silva Rocha.\r\n * Copyright (c) 2014 Florian Markert\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining\r\n * a copy of this software and associated documentation files (the\r\n * \"Software\"), to deal in the Software without restriction, including\r\n * without limitation the rights to use, copy, modify, merge, publish,\r\n * distribute, sublicense, and/or sell copies of the Software, and to\r\n * permit persons to whom the Software is furnished to do so, subject to\r\n * the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n *\r\n */\r\n\r\n/**\r\n * @fileoverview Butterworth LPF. Based on the Butterworth LPF from Fili.js.\r\n * @see https://github.com/rochars/wave-resampler\r\n * @see https://github.com/markert/fili.js\r\n */\r\n\r\n/**\r\n * Butterworth LPF.\r\n */\r\nexport class ButterworthLPF {\r\n \r\n /**\r\n * @param {number} order The order of the filter.\r\n * @param {number} sampleRate The sample rate.\r\n * @param {number} cutOff The cut off frequency.\r\n */\r\n constructor(order, sampleRate, cutOff) {\r\n let filters = [];\r\n for (let i = 0; i < order; i++) {\r\n filters.push(this.getCoeffs_({\r\n Fs: sampleRate,\r\n Fc: cutOff,\r\n Q: 0.5 / (Math.sin((Math.PI / (order * 2)) * (i + 0.5)))\r\n }));\r\n }\r\n this.stages = [];\r\n for (let i = 0; i < filters.length; i++) {\r\n this.stages[i] = {\r\n b0 : filters[i].b[0],\r\n b1 : filters[i].b[1],\r\n b2 : filters[i].b[2],\r\n a1 : filters[i].a[0],\r\n a2 : filters[i].a[1],\r\n k : filters[i].k,\r\n z : [0, 0]\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * @param {number} sample A sample of a sequence.\r\n * @return {number}\r\n */\r\n filter(sample) {\r\n let out = sample;\r\n for (let i = 0, len = this.stages.length; i < len; i++) {\r\n out = this.runStage_(i, out);\r\n }\r\n return out;\r\n }\r\n\r\n getCoeffs_(params) {\r\n let coeffs = {};\r\n coeffs.z = [0, 0];\r\n coeffs.a = [];\r\n coeffs.b = [];\r\n let p = this.preCalc_(params, coeffs);\r\n coeffs.k = 1;\r\n coeffs.b.push((1 - p.cw) / (2 * p.a0));\r\n coeffs.b.push(2 * coeffs.b[0]);\r\n coeffs.b.push(coeffs.b[0]);\r\n return coeffs;\r\n }\r\n\r\n preCalc_(params, coeffs) {\r\n let pre = {};\r\n let w = 2 * Math.PI * params.Fc / params.Fs;\r\n pre.alpha = Math.sin(w) / (2 * params.Q);\r\n pre.cw = Math.cos(w);\r\n pre.a0 = 1 + pre.alpha;\r\n coeffs.a0 = pre.a0;\r\n coeffs.a.push((-2 * pre.cw) / pre.a0);\r\n coeffs.k = 1;\r\n coeffs.a.push((1 - pre.alpha) / pre.a0);\r\n return pre;\r\n }\r\n \r\n runStage_(i, input) {\r\n let temp =\r\n input * this.stages[i].k - this.stages[i].a1 * this.stages[i].z[0] -\r\n this.stages[i].a2 * this.stages[i].z[1];\r\n let out =\r\n this.stages[i].b0 * temp + this.stages[i].b1 * this.stages[i].z[0] +\r\n this.stages[i].b2 * this.stages[i].z[1];\r\n this.stages[i].z[1] = this.stages[i].z[0];\r\n this.stages[i].z[0] = temp;\r\n return out;\r\n }\r\n\r\n /**\r\n * Reset the filter.\r\n */\r\n reset() {\r\n for (let i = 0; i < this.stages.length; i++) {\r\n this.stages[i].z = [0, 0];\r\n }\r\n }\r\n}\r\n","/*\r\n * Copyright (c) 2019 Rafael da Silva Rocha.\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining\r\n * a copy of this software and associated documentation files (the\r\n * \"Software\"), to deal in the Software without restriction, including\r\n * without limitation the rights to use, copy, modify, merge, publish,\r\n * distribute, sublicense, and/or sell copies of the Software, and to\r\n * permit persons to whom the Software is furnished to do so, subject to\r\n * the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n *\r\n */\r\n\r\n/**\r\n * @fileoverview The resample function.\r\n * @see https://github.com/rochars/wave-resampler\r\n */\r\n\r\nimport { Interpolator } from './lib/interpolator';\r\nimport { FIRLPF } from './lib/fir-lpf';\r\nimport { ButterworthLPF } from './lib/butterworth-lpf';\r\n\r\n/**\r\n * Configures wich resampling method uses LPF by default.\r\n * @private\r\n */\r\nconst DEFAULT_LPF_USE = {\r\n 'point': false,\r\n 'linear': false,\r\n 'cubic': true,\r\n 'sinc': true\r\n};\r\n\r\n/**\r\n * The default orders for the LPF types.\r\n * @private\r\n */\r\nconst DEFAULT_LPF_ORDER = {\r\n 'IIR': 16,\r\n 'FIR': 71\r\n};\r\n\r\n/**\r\n * The classes to use with each LPF type.\r\n * @private\r\n */\r\nconst DEFAULT_LPF = {\r\n 'IIR': ButterworthLPF,\r\n 'FIR': FIRLPF\r\n};\r\n\r\n/**\r\n * Change the sample rate of the samples to a new sample rate.\r\n * @param {!Array|!TypedArray} samples The original samples.\r\n * @param {number} oldSampleRate The original sample rate.\r\n * @param {number} sampleRate The target sample rate.\r\n * @param {?Object} details The extra configuration, if needed.\r\n * @return {!Float64Array} the new samples.\r\n */\r\nexport function resample(samples, oldSampleRate, sampleRate, details={}) { \r\n // Make the new sample container\r\n let rate = ((sampleRate - oldSampleRate) / oldSampleRate) + 1;\r\n let newSamples = new Float64Array(samples.length * (rate));\r\n // Create the interpolator\r\n details.method = details.method || 'cubic';\r\n let interpolator = new Interpolator(\r\n samples.length,\r\n newSamples.length,\r\n {\r\n method: details.method,\r\n tension: details.tension || 0,\r\n sincFilterSize: details.sincFilterSize || 6,\r\n sincWindow: details.sincWindow || undefined\r\n });\r\n // Resample + LPF\r\n if (details.LPF === undefined) {\r\n details.LPF = DEFAULT_LPF_USE[details.method];\r\n } \r\n if (details.LPF) {\r\n details.LPFType = details.LPFType || 'IIR';\r\n const LPF = DEFAULT_LPF[details.LPFType];\r\n // Upsampling\r\n if (sampleRate > oldSampleRate) {\r\n let filter = new LPF(\r\n details.LPFOrder || DEFAULT_LPF_ORDER[details.LPFType],\r\n sampleRate,\r\n (oldSampleRate / 2));\r\n upsample_(\r\n samples, newSamples, interpolator, filter);\r\n // Downsampling\r\n } else {\r\n let filter = new LPF(\r\n details.LPFOrder || DEFAULT_LPF_ORDER[details.LPFType],\r\n oldSampleRate,\r\n sampleRate / 2);\r\n downsample_(\r\n samples, newSamples, interpolator, filter);\r\n }\r\n // Resample, no LPF\r\n } else {\r\n resample_(samples, newSamples, interpolator);\r\n }\r\n return newSamples;\r\n}\r\n\r\n/**\r\n * Resample.\r\n * @param {!Array|!TypedArray} samples The original samples.\r\n * @param {!Float64Array} newSamples The container for the new samples.\r\n * @param {Object} interpolator The interpolator.\r\n * @private\r\n */\r\nfunction resample_(samples, newSamples, interpolator) {\r\n // Resample\r\n for (let i = 0, len = newSamples.length; i < len; i++) {\r\n newSamples[i] = interpolator.interpolate(i, samples);\r\n }\r\n}\r\n\r\n/**\r\n * Upsample with LPF.\r\n * @param {!Array|!TypedArray} samples The original samples.\r\n * @param {!Float64Array} newSamples The container for the new samples.\r\n * @param {Object} interpolator The interpolator.\r\n * @param {Object} filter The LPF object.\r\n * @private\r\n */\r\nfunction upsample_(samples, newSamples, interpolator, filter) {\r\n // Resample and filter\r\n for (let i = 0, len = newSamples.length; i < len; i++) {\r\n newSamples[i] = filter.filter(interpolator.interpolate(i, samples));\r\n }\r\n // Reverse filter\r\n filter.reset();\r\n for (let i = newSamples.length - 1; i >= 0; i--) {\r\n newSamples[i] = filter.filter(newSamples[i]);\r\n }\r\n}\r\n\r\n/**\r\n * Downsample with LPF.\r\n * @param {!Array|!TypedArray} samples The original samples.\r\n * @param {!Float64Array} newSamples The container for the new samples.\r\n * @param {Object} interpolator The interpolator.\r\n * @param {Object} filter The LPF object.\r\n * @private\r\n */\r\nfunction downsample_(samples, newSamples, interpolator, filter) {\r\n // Filter\r\n for (let i = 0, len = samples.length; i < len; i++) {\r\n samples[i] = filter.filter(samples[i]);\r\n }\r\n // Reverse filter\r\n filter.reset();\r\n for (let i = samples.length - 1; i >= 0; i--) {\r\n samples[i] = filter.filter(samples[i]);\r\n }\r\n // Resample\r\n resample_(samples, newSamples, interpolator);\r\n}\r\n","import { Log } from \"./log\";\nimport { workerTimer } from \"./worker-timer\";\nimport { resample } from \"wave-resampler\";\n\nexport function concatFloat32Array(bufs: Float32Array[]): Float32Array {\n const rs = new Float32Array(\n bufs.map((buf) => buf.length).reduce((a, b) => a + b)\n );\n\n let offset = 0;\n for (const buf of bufs) {\n rs.set(buf, offset);\n offset += buf.length;\n }\n\n return rs;\n}\n\nexport function concatPCMFragments(\n fragments: Float32Array[][]\n): Float32Array[] {\n // fragments: [[chan0, chan1], [chan0, chan1]...]\n // chanListPCM: [[chan0, chan0...], [chan1, chan1...]]\n const chanListPCM: Float32Array[][] = [];\n for (let i = 0; i < fragments.length; i += 1) {\n for (let j = 0; j < fragments[i].length; j += 1) {\n if (chanListPCM[j] == null) chanListPCM[j] = [];\n chanListPCM[j].push(fragments[i][j]);\n }\n }\n // [bigChan0, bigChan1]\n return chanListPCM.map(concatFloat32Array);\n}\n\nexport function extractPCM4AudioData(ad: AudioData): Float32Array[] {\n if (ad.format === \"f32-planar\") {\n const rs = [];\n for (let idx = 0; idx < ad.numberOfChannels; idx += 1) {\n const chanBufSize = ad.allocationSize({ planeIndex: idx });\n const chanBuf = new ArrayBuffer(chanBufSize);\n ad.copyTo(chanBuf, { planeIndex: idx });\n rs.push(new Float32Array(chanBuf));\n }\n return rs;\n } else if (ad.format === \"f32\") {\n const buf = new ArrayBuffer(ad.allocationSize({ planeIndex: 0 }));\n ad.copyTo(buf, { planeIndex: 0 });\n return convertF32ToPlanar(new Float32Array(buf), ad.numberOfChannels);\n } else if (ad.format === \"s16\") {\n const buf = new ArrayBuffer(ad.allocationSize({ planeIndex: 0 }));\n ad.copyTo(buf, { planeIndex: 0 });\n return convertS16ToF32Planar(new Int16Array(buf), ad.numberOfChannels);\n }\n throw Error(\"Unsupported audio data format\");\n}\n\nfunction convertS16ToF32Planar(pcmS16Data: Int16Array, numChannels: number) {\n const numSamples = pcmS16Data.length / numChannels;\n const planarData = Array.from(\n { length: numChannels },\n () => new Float32Array(numSamples)\n );\n\n for (let i = 0; i < numSamples; i++) {\n for (let channel = 0; channel < numChannels; channel++) {\n const sample = pcmS16Data[i * numChannels + channel];\n planarData[channel][i] = sample / 32768; // Normalize to range [-1.0, 1.0]\n }\n }\n\n return planarData;\n}\n\nfunction convertF32ToPlanar(pcmF32Data: Float32Array, numChannels: number) {\n const numSamples = pcmF32Data.length / numChannels;\n const planarData = Array.from(\n { length: numChannels },\n () => new Float32Array(numSamples)\n );\n\n for (let i = 0; i < numSamples; i++) {\n for (let channel = 0; channel < numChannels; channel++) {\n planarData[channel][i] = pcmF32Data[i * numChannels + channel];\n }\n }\n\n return planarData;\n}\n\nexport function extractPCM4AudioBuffer(ab: AudioBuffer): Float32Array[] {\n return Array(ab.numberOfChannels)\n .fill(0)\n .map((_, idx) => {\n return ab.getChannelData(idx);\n });\n}\n\nexport function adjustAudioDataVolume(ad: AudioData, volume: number) {\n const data = new Float32Array(\n concatFloat32Array(extractPCM4AudioData(ad))\n ).map((v) => v * volume);\n const newAd = new AudioData({\n sampleRate: ad.sampleRate,\n numberOfChannels: ad.numberOfChannels,\n timestamp: ad.timestamp,\n format: ad.format,\n numberOfFrames: ad.numberOfFrames,\n data,\n });\n ad.close();\n return newAd;\n}\n\nexport async function decodeImg(\n stream: ReadableStream<Uint8Array>,\n type: string\n): Promise<VideoFrame[]> {\n const init = {\n type,\n data: stream,\n };\n const imageDecoder = new ImageDecoder(init);\n\n await Promise.all([imageDecoder.completed, imageDecoder.tracks.ready]);\n\n let frameCnt = imageDecoder.tracks.selectedTrack?.frameCount ?? 1;\n\n const rs: VideoFrame[] = [];\n for (let i = 0; i < frameCnt; i += 1) {\n rs.push((await imageDecoder.decode({ frameIndex: i })).image);\n }\n return rs;\n}\n\nexport function mixinPCM(audios: Float32Array[][]): Float32Array {\n const maxLen = Math.max(...audios.map((a) => a[0]?.length ?? 0));\n const data = new Float32Array(maxLen * 2);\n\n for (let bufIdx = 0; bufIdx < maxLen; bufIdx++) {\n let chan0 = 0;\n let chan1 = 0;\n for (let trackIdx = 0; trackIdx < audios.length; trackIdx++) {\n const _c0 = audios[trackIdx][0]?.[bufIdx] ?? 0;\n const _c1 = audios[trackIdx][1]?.[bufIdx] ?? _c0;\n chan0 += _c0;\n chan1 += _c1;\n }\n data[bufIdx] = chan0;\n data[bufIdx + maxLen] = chan1;\n }\n\n return data;\n}\n\nexport async function audioResample(\n pcmData: Float32Array[],\n curRate: number,\n target: {\n rate: number;\n chanCount: number;\n }\n): Promise<Float32Array[]> {\n const chanCnt = pcmData.length;\n const emptyPCM = Array(target.chanCount)\n .fill(0)\n .map(() => new Float32Array(0));\n if (chanCnt === 0) return emptyPCM;\n\n const len = Math.max(...pcmData.map((c) => c.length));\n if (len === 0) return emptyPCM;\n\n // The Worker scope does not have access to OfflineAudioContext\n if (globalThis.OfflineAudioContext == null) {\n return pcmData.map(\n (p) =>\n new Float32Array(\n resample(p, curRate, target.rate, { method: \"sinc\", LPF: false })\n )\n );\n }\n\n const ctx = new globalThis.OfflineAudioContext(\n target.chanCount,\n (len * target.rate) / curRate,\n target.rate\n );\n const abSource = ctx.createBufferSource();\n const ab = ctx.createBuffer(chanCnt, len, curRate);\n pcmData.forEach((d, idx) => ab.copyToChannel(d, idx));\n\n abSource.buffer = ab;\n abSource.connect(ctx.destination);\n abSource.start();\n\n return extractPCM4AudioBuffer(await ctx.startRendering());\n}\n\nexport function sleep(time: number): Promise<void> {\n return new Promise((resolve) => {\n const stop = workerTimer(() => {\n stop();\n resolve();\n }, time);\n });\n}\n\nexport function ringSliceFloat32Array(\n data: Float32Array,\n start: number,\n end: number\n): Float32Array {\n const cnt = end - start;\n const rs = new Float32Array(cnt);\n let i = 0;\n while (i < cnt) {\n rs[i] = data[(start + i) % data.length];\n i += 1;\n }\n return rs;\n}\n\nexport function autoReadStream<ST extends ReadableStream>(\n stream: ST,\n cbs: {\n onChunk: ST extends ReadableStream<infer DT>\n ? (chunk: DT) => Promise<void>\n : never;\n onDone: () => void;\n }\n) {\n let stoped = false;\n async function run() {\n const reader = stream.getReader();\n\n while (!stoped) {\n const { value, done } = await reader.read();\n if (done) {\n cbs.onDone();\n return;\n }\n await cbs.onChunk(value);\n }\n\n reader.releaseLock();\n await stream.cancel();\n }\n\n run().catch(Log.error);\n\n return () => {\n stoped = true;\n };\n}\n\nexport function throttle<F extends (...args: any[]) => any>(\n func: F,\n wait: number\n): (...rest: Parameters<F>) => undefined | ReturnType<F> {\n let lastTime: number;\n return function (this: any, ...rest) {\n if (lastTime == null || performance.now() - lastTime > wait) {\n lastTime = performance.now();\n return func.apply(this, rest);\n }\n };\n}\n\nexport function createGoPVideoDecoder(conf: VideoDecoderConfig) {\n type OutputHandle = (vf: VideoFrame | null, done: boolean) => void;\n\n let curCb: ((vf: VideoFrame) => void) | null = null;\n const vdec = new VideoDecoder({\n output: (vf) => {\n curCb?.(vf);\n },\n error: Log.error,\n });\n vdec.configure(conf);\n\n let tasks: Array<{ chunks: EncodedVideoChunk[]; cb: OutputHandle }> = [];\n\n async function run() {\n if (curCb != null) return;\n\n const t = tasks.shift();\n if (t == null) return;\n if (t.chunks.length <= 0) {\n t.cb(null, true);\n run().catch(Log.error);\n return;\n }\n\n let i = 0;\n curCb = (vf) => {\n i += 1;\n t.cb(vf, false);\n if (i >= t.chunks.length) {\n t.cb(null, true);\n curCb = null;\n run().catch(Log.error);\n }\n };\n for (const chunk of t.chunks) vdec.decode(chunk);\n await vdec.flush();\n }\n\n return {\n decode(chunks: EncodedVideoChunk[], cb: OutputHandle) {\n tasks.push({ chunks, cb }