From eb39f7549f1c4a68a53c0bb6c8370ab7e2265ad0 Mon Sep 17 00:00:00 2001 From: kwigbo Date: Mon, 11 May 2026 18:52:11 +0000 Subject: [PATCH] Audio: clean up event listeners and buffer view on destroy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Audio constructor binds three window-level event listeners (keydown / click / touchend) to drive startPlayback. They're only removed when startPlayback() fires; if the Emulator is destroyed before the user produces a gesture — or if an embedder recreates the Emulator multiple times before that happens — the listeners stayed attached to a stale Audio instance forever. Audio also holds a TypedArray view (this.buffer) into the wasm heap region returned by _get_audio_buffer_ptr. After Emulator destroy() calls _emulator_delete + _free, that backing memory is gone, and any held reference to the buffer view points at freed memory. Add Audio.destroy(): removes the gesture listeners and nulls out the buffer view. Call it from Emulator.destroy() before _emulator_delete so the cleanup happens while the wasm pointer is still valid. --- docs/simple.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/simple.js b/docs/simple.js index 968a766..04e8264 100644 --- a/docs/simple.js +++ b/docs/simple.js @@ -194,6 +194,7 @@ class Emulator { this.cancelAnimationFrame(); clearInterval(this.rewindIntervalId); this.rewind.destroy(); + this.audio.destroy(); this.module._emulator_delete(this.e); this.module._free(this.romDataPtr); } @@ -658,6 +659,17 @@ class Audio { if (!this.started) { return; } Audio.ctx.resume(); } + + destroy() { + if (this.boundStartPlayback) { + window.removeEventListener('keydown', this.boundStartPlayback, true); + window.removeEventListener('click', this.boundStartPlayback, true); + window.removeEventListener('touchend', this.boundStartPlayback, true); + this.boundStartPlayback = null; + } + this.buffer = null; + this.started = false; + } } Audio.ctx = new AudioContext;