From a2714f25e4fb74b43fdb120aa7e1377809b8672b Mon Sep 17 00:00:00 2001 From: Dennis Ranke Date: Wed, 4 May 2022 00:32:41 +0200 Subject: [PATCH] fix unstable playback in browser runtime --- web/src/audiolet.js | 21 +++++++++++++++------ web/src/microw8.js | 20 ++++++++++++-------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/web/src/audiolet.js b/web/src/audiolet.js index 58133c6..4f8bb2b 100644 --- a/web/src/audiolet.js +++ b/web/src/audiolet.js @@ -3,13 +3,17 @@ class APU extends AudioWorkletProcessor { constructor() { super(); this.sampleIndex = 0; + this.currentTime = 0; + this.isFirstMessage = true; + this.pendingUpdates = []; this.port.onmessage = (ev) => { if(this.memory) { - if(isNaN(ev.data)) { - U8(this.memory.buffer, 80, 32).set(U8(ev.data)); - } else { - this.startTime = ev.data; + if(this.isFirstMessage) + { + this.currentTime += (ev.data.t - this.currentTime) / 8; + this.isFirstMessage = false; } + this.pendingUpdates.push(ev.data); } else { this.load(ev.data[0], ev.data[1]); } @@ -55,9 +59,13 @@ class APU extends AudioWorkletProcessor { } process(inputs, outputs, parameters) { - if(this.snd && this.startTime) { + this.isFirstMessage = true; + if(this.snd) { + while(this.pendingUpdates.length > 0 && this.pendingUpdates[0].t <= this.currentTime) { + U8(this.memory.buffer, 80, 32).set(U8(this.pendingUpdates.shift().r)); + } let u32Mem = new Uint32Array(this.memory.buffer); - u32Mem[16] = Date.now() - this.startTime; + u32Mem[16] = this.currentTime; let channels = outputs[0]; let index = this.sampleIndex; let numSamples = channels[0].length; @@ -66,6 +74,7 @@ class APU extends AudioWorkletProcessor { channels[1][i] = this.snd(index++); } this.sampleIndex = index & 0xffffffff; + this.currentTime += numSamples / 44.1; } return true; diff --git a/web/src/microw8.js b/web/src/microw8.js index 08e42e0..6156809 100644 --- a/web/src/microw8.js +++ b/web/src/microw8.js @@ -107,7 +107,7 @@ export default function MicroW8(screen, config = {}) { audioContext.close(); keepRunning = false; abortController.abort(); - } + }; let cartridgeSize = data.byteLength; @@ -232,7 +232,6 @@ export default function MicroW8(screen, config = {}) { let startTime = Date.now(); const timePerFrame = 1000 / 60; - let nextFrame = startTime; audioNode.connect(audioContext.destination); @@ -244,18 +243,16 @@ export default function MicroW8(screen, config = {}) { isPaused = false; audioContext.resume(); startTime += now - pauseTime; - audioNode.port.postMessage(startTime); } else { isPaused = true; audioContext.suspend(); pauseTime = now; - audioNode.port.postMessage(0); } }; window.addEventListener('focus', () => updateVisibility(true), { signal: abortController.signal }); window.addEventListener('blur', () => updateVisibility(false), { signal: abortController.signal }); updateVisibility(document.hasFocus()); - + function mainloop() { if (!keepRunning) { return; @@ -263,6 +260,7 @@ export default function MicroW8(screen, config = {}) { try { let restart = false; + let thisFrame; if (!isPaused) { let gamepads = navigator.getGamepads(); let gamepad = 0; @@ -291,7 +289,8 @@ export default function MicroW8(screen, config = {}) { } let u32Mem = U32(memory.buffer); - u32Mem[16] = Date.now() - startTime; + let time = Date.now() - startTime; + u32Mem[16] = time; u32Mem[17] = pad | gamepad; if(instance.exports.upd) { instance.exports.upd(); @@ -300,16 +299,21 @@ export default function MicroW8(screen, config = {}) { let soundRegisters = new ArrayBuffer(32); U8(soundRegisters).set(U8(memory.buffer, 80, 32)); - audioNode.port.postMessage(soundRegisters, [soundRegisters]); + audioNode.port.postMessage({t: time, r: soundRegisters}, [soundRegisters]); let palette = U32(memory.buffer, 0x13000, 1024); for (let i = 0; i < 320 * 240; ++i) { buffer[i] = palette[memU8[i + 120]] | 0xff000000; } canvasCtx.putImageData(imageData, 0, 0); + + let timeOffset = ((time * 6) % 100 - 50) / 6; + thisFrame = startTime + time - timeOffset / 2; + } else { + thisFrame = Date.now(); } let now = Date.now(); - nextFrame = Math.max(nextFrame + timePerFrame, now); + let nextFrame = Math.max(thisFrame + timePerFrame, now); if (restart) { runModule(currentData);