diff --git a/README.md b/README.md index 2d8c565..b8381e9 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,15 @@ If i've messed up anywhere then results could be much worse. to judge yourself, these are the security measures i've taken: -- input code is ran in an audio worklet (note that the code has access to the global scope) +- input code is ran in an audio worklet (the code has access to the global scope) - most iterable global variables are deleted -- objects and prototypes of objects on the global scope are frozen -- variables remaining on the global scope are made unwritable and unconfigurable +- objects on the global scope and their prototypes are frozen +- objects on the global scope are made unwritable and unconfigurable +- proxies output by the bytebeat code are detected and never used for any operations +- data output by the bytebeat code is carefully converted to numbers with error handling +- output data is put into typed arrays and sent through a MessagePort +- data output from the audio worklet is treated as untrusted and parsed carefully +- audio worklets are never reused for new bytebeat code ## embedding diff --git a/src/audio-worklet/audio-worklet.ts b/src/audio-worklet/audio-worklet.ts index 5cafbf2..753f087 100644 --- a/src/audio-worklet/audio-worklet.ts +++ b/src/audio-worklet/audio-worklet.ts @@ -9,9 +9,8 @@ declare abstract class AudioWorkletProcessor { numberOfInputs?: number; numberOfOutputs?: number; outputChannelCount: number[]; - // i don't understand these ones - parameterData?: unknown; - processorOptions?: unknown; + parameterData?: Record; + processorOptions?: any; }); abstract process( _inputs: Float32Array[][], @@ -160,7 +159,7 @@ class BytebeatProcessor extends AudioWorkletProcessor { } refreshCode(code: string) { // code is already trimmed - // create shortened functions + // create shortened Math functions and other things for compatibility const params: string[] = Object.getOwnPropertyNames(Math); const values: any[] = params.map(k => Math[k as keyof Math]); params.push("int", "window"); diff --git a/src/bytebeat.ts b/src/bytebeat.ts index 0e486ca..cd960aa 100644 --- a/src/bytebeat.ts +++ b/src/bytebeat.ts @@ -26,13 +26,11 @@ const audioPromise = initAudioContext(); const codeEditor = new CodeEditor(refresh); const osc = new Oscillioscope(elements.canvas, elements.timeCursor); + if (globalThis.loadLibrary !== false) { import("./library/load.ts"); } -handleWindowResize(true); -addEventListener("resize", () => handleWindowResize(false)); - const { audioCtx, audioGain, audioWorklet } = await audioPromise; loadSettings(); @@ -89,6 +87,9 @@ function refresh() { audioWorklet.port.postMessage({ code: codeEditor.getCode().trim() }); } } +// TODO: bug, this calls refresh twice every time because +// setting codemirror programmatically also calls refresh +// the code text input might also have this issue? export function setSong(songData?: StrongPartial, play = true) { let code, sampleRate, mode; if (songData) { @@ -174,33 +175,6 @@ function handleMessage(e: MessageEvent) { } } -function handleWindowResize(force?: boolean) { - autoSizeCanvas(force); -} -export function autoSizeCanvas(force: boolean = false) { - if (!elements.canvas.dataset["forcedWidth"]) { - // 768 is halfway between 512 and 1024, 3 added for outline - if (innerWidth >= 768 + 3) { - let width = 1024; - while (innerWidth - 516 >= width * 2) { - // 516px = 4px (outline) + 512px (library) - width *= 2; - } - setCanvasWidth(width, force); - } else { - setCanvasWidth(512, force); - } - } -} -export function setCanvasWidth(width: number, force: boolean = false) { - if (elements.canvas) { - if (width !== elements.canvas.width || force) { - elements.canvas.width = width; - // TODO: see if it's possible to get rid of this - elements.main.style.maxWidth = `${width + 4}px`; - } - } -} function timeCursorShouldBeVisible(): boolean { return songData.sampleRate >> osc.settings.scale < 3950; } @@ -364,7 +338,7 @@ function setVolume(save = true, value?: number) { } volume = elements.volume.valueAsNumber; - audioGain.gain.value = volume * volume; + audioGain.gain.value = volume ** 2; if (save) { saveSettings(); @@ -441,3 +415,31 @@ Object.defineProperty(globalThis, "bytebeat", { refresh, }, }); + +// TODO: move this to a different file +autoSizeCanvas(true); +addEventListener("resize", () => autoSizeCanvas()); +export function autoSizeCanvas(force = false) { + if (!elements.canvas.dataset["forcedWidth"]) { + // 768 is halfway between 512 and 1024, 3 added for outline + if (innerWidth >= 768 + 3) { + let width = 1024; + while (innerWidth - 516 >= width * 2) { + // 516px = 4px (outline) + 512px (library) + width *= 2; + } + setCanvasWidth(width, force); + } else { + setCanvasWidth(512, force); + } + } +} +export function setCanvasWidth(width: number, force = false) { + if (elements.canvas) { + if (width !== elements.canvas.width || force) { + elements.canvas.width = width; + // TODO: see if it's possible to get rid of this + elements.main.style.maxWidth = `${width + 4}px`; + } + } +} diff --git a/src/code-editor/mod.ts b/src/code-editor/mod.ts index bb123a3..a46aadb 100644 --- a/src/code-editor/mod.ts +++ b/src/code-editor/mod.ts @@ -20,7 +20,7 @@ export class CodeEditor { this.setCode = v => // codemirror type is wrong // TODO pull request fix - // @ts-ignore + // @ts-expect-error codemirror.dispatch({ changes: { from: 0, to: codemirror.state.doc.length, insert: v }, }); diff --git a/src/common.ts b/src/common.ts index 6e016b0..048252e 100644 --- a/src/common.ts +++ b/src/common.ts @@ -1,5 +1,9 @@ export type StrongPartial = { [P in keyof T]?: T[P] | undefined }; +/// get a number with the least decimal digits that when rounded to +/// float32, will be the same as the value put in. +/// this is only used for displaying samplerates and probably doesn't +/// work outside of that one exact task. export function f32ToRound10(value: number): number { // this is stupid but it works for numbers less // than 10 digits on one side of the decimal point @@ -19,6 +23,8 @@ export function f32ToRound10(value: number): number { // 2629376 numbers needs precision 9 } +/// used to check if values from message passing are safe +/// can't detect proxies export function isPlainObject(value: unknown): value is object { if (typeof value === "object" && value) { const proto = Object.getPrototypeOf(value); diff --git a/src/oscillioscope.ts b/src/oscillioscope.ts index 5efa289..39bc553 100644 --- a/src/oscillioscope.ts +++ b/src/oscillioscope.ts @@ -16,10 +16,10 @@ export class Oscillioscope { this.timeCursor = timeCursor; } - getXpos(t: number): number { + private getXpos(t: number): number { return t / (1 << this.settings.scale); } - getTimeFromXpos(x: number): number { + private getTimeFromXpos(x: number): number { return x * (1 << this.settings.scale); } addToBuffer(data: DrawSample[], byteSample: number) { @@ -41,7 +41,7 @@ export class Oscillioscope { this.clearBuffer(); } } - clearBuffer() { + private clearBuffer() { this.drawBuffer = []; this.drawImageData = null; } @@ -226,6 +226,7 @@ export class Oscillioscope { this.drawBuffer = [{ t: endTime, value: this.drawBuffer[bufferLen - 1].value, carry: true }]; } + // TODO don't make this depend on play direction moveTimeCursor(time: number, playingForward: boolean) { if (!this.timeCursor.hasAttribute("disabled")) { const width = this.ctx.canvas.width;