fix unstable playback in browser runtime

This commit is contained in:
2022-05-04 00:32:41 +02:00
parent 7e203d93e6
commit a2714f25e4
2 changed files with 27 additions and 14 deletions

View File

@@ -3,13 +3,17 @@ class APU extends AudioWorkletProcessor {
constructor() { constructor() {
super(); super();
this.sampleIndex = 0; this.sampleIndex = 0;
this.currentTime = 0;
this.isFirstMessage = true;
this.pendingUpdates = [];
this.port.onmessage = (ev) => { this.port.onmessage = (ev) => {
if(this.memory) { if(this.memory) {
if(isNaN(ev.data)) { if(this.isFirstMessage)
U8(this.memory.buffer, 80, 32).set(U8(ev.data)); {
} else { this.currentTime += (ev.data.t - this.currentTime) / 8;
this.startTime = ev.data; this.isFirstMessage = false;
} }
this.pendingUpdates.push(ev.data);
} else { } else {
this.load(ev.data[0], ev.data[1]); this.load(ev.data[0], ev.data[1]);
} }
@@ -55,9 +59,13 @@ class APU extends AudioWorkletProcessor {
} }
process(inputs, outputs, parameters) { 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); let u32Mem = new Uint32Array(this.memory.buffer);
u32Mem[16] = Date.now() - this.startTime; u32Mem[16] = this.currentTime;
let channels = outputs[0]; let channels = outputs[0];
let index = this.sampleIndex; let index = this.sampleIndex;
let numSamples = channels[0].length; let numSamples = channels[0].length;
@@ -66,6 +74,7 @@ class APU extends AudioWorkletProcessor {
channels[1][i] = this.snd(index++); channels[1][i] = this.snd(index++);
} }
this.sampleIndex = index & 0xffffffff; this.sampleIndex = index & 0xffffffff;
this.currentTime += numSamples / 44.1;
} }
return true; return true;

View File

@@ -107,7 +107,7 @@ export default function MicroW8(screen, config = {}) {
audioContext.close(); audioContext.close();
keepRunning = false; keepRunning = false;
abortController.abort(); abortController.abort();
} };
let cartridgeSize = data.byteLength; let cartridgeSize = data.byteLength;
@@ -232,7 +232,6 @@ export default function MicroW8(screen, config = {}) {
let startTime = Date.now(); let startTime = Date.now();
const timePerFrame = 1000 / 60; const timePerFrame = 1000 / 60;
let nextFrame = startTime;
audioNode.connect(audioContext.destination); audioNode.connect(audioContext.destination);
@@ -244,18 +243,16 @@ export default function MicroW8(screen, config = {}) {
isPaused = false; isPaused = false;
audioContext.resume(); audioContext.resume();
startTime += now - pauseTime; startTime += now - pauseTime;
audioNode.port.postMessage(startTime);
} else { } else {
isPaused = true; isPaused = true;
audioContext.suspend(); audioContext.suspend();
pauseTime = now; pauseTime = now;
audioNode.port.postMessage(0);
} }
}; };
window.addEventListener('focus', () => updateVisibility(true), { signal: abortController.signal }); window.addEventListener('focus', () => updateVisibility(true), { signal: abortController.signal });
window.addEventListener('blur', () => updateVisibility(false), { signal: abortController.signal }); window.addEventListener('blur', () => updateVisibility(false), { signal: abortController.signal });
updateVisibility(document.hasFocus()); updateVisibility(document.hasFocus());
function mainloop() { function mainloop() {
if (!keepRunning) { if (!keepRunning) {
return; return;
@@ -263,6 +260,7 @@ export default function MicroW8(screen, config = {}) {
try { try {
let restart = false; let restart = false;
let thisFrame;
if (!isPaused) { if (!isPaused) {
let gamepads = navigator.getGamepads(); let gamepads = navigator.getGamepads();
let gamepad = 0; let gamepad = 0;
@@ -291,7 +289,8 @@ export default function MicroW8(screen, config = {}) {
} }
let u32Mem = U32(memory.buffer); let u32Mem = U32(memory.buffer);
u32Mem[16] = Date.now() - startTime; let time = Date.now() - startTime;
u32Mem[16] = time;
u32Mem[17] = pad | gamepad; u32Mem[17] = pad | gamepad;
if(instance.exports.upd) { if(instance.exports.upd) {
instance.exports.upd(); instance.exports.upd();
@@ -300,16 +299,21 @@ export default function MicroW8(screen, config = {}) {
let soundRegisters = new ArrayBuffer(32); let soundRegisters = new ArrayBuffer(32);
U8(soundRegisters).set(U8(memory.buffer, 80, 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); let palette = U32(memory.buffer, 0x13000, 1024);
for (let i = 0; i < 320 * 240; ++i) { for (let i = 0; i < 320 * 240; ++i) {
buffer[i] = palette[memU8[i + 120]] | 0xff000000; buffer[i] = palette[memU8[i + 120]] | 0xff000000;
} }
canvasCtx.putImageData(imageData, 0, 0); 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(); let now = Date.now();
nextFrame = Math.max(nextFrame + timePerFrame, now); let nextFrame = Math.max(thisFrame + timePerFrame, now);
if (restart) { if (restart) {
runModule(currentData); runModule(currentData);