@soapbox.pub/wasmboy
Version:
Soapbox fork of Wasmboy.
254 lines (203 loc) • 7.88 kB
JavaScript
(function () {
'use strict';
function getEventData(event) {
if (event.data) {
return event.data;
}
return event;
}
const isInBrowser = typeof self !== 'undefined'; // Function to read a base64 string as a buffer
// Isomorphic worker api to be imported by web workers
let parentPort;
if (!isInBrowser) {
parentPort = require('worker_threads').parentPort;
} // https://nodejs.org/api/worker_threads.html#worker_threads_worker_postmessage_value_transferlist
// https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage
function postMessage(message, transferArray) {
// Can't bind parentPort.postMessage, so we need to kinda copy code here :p
if (isInBrowser) {
self.postMessage(message, transferArray);
} else {
parentPort.postMessage(message, transferArray);
}
} // https://nodejs.org/api/worker_threads.html#worker_threads_worker_parentport
// https://developer.mozilla.org/en-US/docs/Web/API/Worker/onmessage
function onMessage(callback, port) {
if (!callback) {
console.error('workerapi: No callback was provided to onMessage!');
} // If we passed a port, use that
if (port) {
if (isInBrowser) {
// We are in the browser
port.onmessage = callback;
} else {
// We are in Node
port.on('message', callback);
}
return;
}
if (isInBrowser) {
// We are in the browser
self.onmessage = callback;
} else {
// We are in Node
parentPort.on('message', callback);
}
}
// Smarter workers.
let idCounter = 0;
const generateId = () => {
const randomId = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(2, 10);
idCounter++;
const id = `${randomId}-${idCounter}`;
if (idCounter > 100000) {
idCounter = 0;
}
return id;
};
function getSmartWorkerMessage(message, messageId, workerId) {
if (!messageId) {
messageId = generateId();
}
return {
workerId,
messageId,
message
};
}
const WORKER_MESSAGE_TYPE = {
CONNECT: 'CONNECT',
INSTANTIATE_WASM: 'INSTANTIATE_WASM',
CLEAR_MEMORY: 'CLEAR_MEMORY',
CLEAR_MEMORY_DONE: 'CLEAR_MEMORY_DONE',
GET_MEMORY: 'GET_MEMORY',
SET_MEMORY: 'SET_MEMORY',
SET_MEMORY_DONE: 'SET_MEMORY_DONE',
GET_CONSTANTS: 'GET_CONSTANTS',
GET_CONSTANTS_DONE: 'GET_CONSTANTS_DONE',
CONFIG: 'CONFIG',
RESET_AUDIO_QUEUE: 'RESET_AUDIO_QUEUE',
PLAY: 'PLAY',
BREAKPOINT: 'BREAKPOINT',
PAUSE: 'PAUSE',
UPDATED: 'UPDATED',
CRASHED: 'CRASHED',
SET_JOYPAD_STATE: 'SET_JOYPAD_STATE',
AUDIO_LATENCY: 'AUDIO_LATENCY',
RUN_WASM_EXPORT: 'RUN_WASM_EXPORT',
GET_WASM_MEMORY_SECTION: 'GET_WASM_MEMORY_SECTION',
GET_WASM_CONSTANT: 'GET_WASM_CONSTANT',
FORCE_OUTPUT_FRAME: 'FORCE_OUTPUT_FRAME',
SET_SPEED: 'SET_SPEED',
IS_GBC: 'IS_GBC'
};
// Web worker for wasmboy lib
const getUnsignedAudioSampleAsFloat = audioSample => {
// Subtract 1 as it is added so the value is not empty
audioSample -= 1; // Divide by 127 to get back to our float scale
audioSample = audioSample / 127; // Subtract 1 to regain our sign
audioSample -= 1; // Because of the innacuracy of converting an unsigned int to a signed float
// We will have some leftovers when doing the conversion.
// When testing with Pokemon blue, when it is supposed to be complete silence in the intro,
// It shows 0.007874015748031482, meaning we want to cut our values lower than this
if (Math.abs(audioSample) < 0.008) {
audioSample = 0;
} // Return, but divide by lower volume, PCM is loouuuuddd
return audioSample / 2.5;
};
const getAudioChannelBuffersFromBuffer = (audioBuffer, numberOfSamples) => {
// Create our buffers as Float 32 Array
// https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer/getChannelData
// Number of samples divided by two, since we split into each channel
const leftChannelBuffer = new Float32Array(numberOfSamples);
const rightChannelBuffer = new Float32Array(numberOfSamples); // Our index on our left/right buffers
let bufferIndex = 0; // Our total number of stereo samples
let numberOfSamplesForStereo = numberOfSamples * 2; // Left Channel
for (let i = 0; i < numberOfSamplesForStereo; i = i + 2) {
leftChannelBuffer[bufferIndex] = getUnsignedAudioSampleAsFloat(audioBuffer[i]);
bufferIndex++;
} // Reset the buffer index
bufferIndex = 0; // Right Channel
for (let i = 1; i < numberOfSamplesForStereo; i = i + 2) {
rightChannelBuffer[bufferIndex] = getUnsignedAudioSampleAsFloat(audioBuffer[i]);
bufferIndex++;
}
return {
left: leftChannelBuffer.buffer,
right: rightChannelBuffer.buffer
};
}; // Worker port for the lib
let libWorkerPort;
const libMessageHandler = event => {
const eventData = getEventData(event); // Handle update method transfrables
if (!eventData.message) {
return;
} // Handle our messages from the lib thread
switch (eventData.message.type) {
case WORKER_MESSAGE_TYPE.GET_CONSTANTS_DONE:
{
postMessage(getSmartWorkerMessage(eventData.message, eventData.messageId));
return;
}
case WORKER_MESSAGE_TYPE.UPDATED:
{
// Process the memory buffer and pass back to the main thread
// For Each Possible Buffer
const message = {
type: WORKER_MESSAGE_TYPE.UPDATED,
numberOfSamples: eventData.message.numberOfSamples,
fps: eventData.message.fps,
allowFastSpeedStretching: eventData.message.allowFastSpeedStretching
};
const messageTransferables = [];
const audioDebuggingChannelBufferKeys = ['audioBuffer', 'channel1Buffer', 'channel2Buffer', 'channel3Buffer', 'channel4Buffer'];
audioDebuggingChannelBufferKeys.forEach(channelBufferKey => {
if (!eventData.message[channelBufferKey]) {
return;
}
const audioBufferAsArray = new Uint8Array(eventData.message[channelBufferKey]);
const audioChannelBuffers = getAudioChannelBuffersFromBuffer(audioBufferAsArray, eventData.message.numberOfSamples);
message[channelBufferKey] = {};
message[channelBufferKey].left = audioChannelBuffers.left;
message[channelBufferKey].right = audioChannelBuffers.right;
messageTransferables.push(audioChannelBuffers.left);
messageTransferables.push(audioChannelBuffers.right);
});
postMessage(getSmartWorkerMessage(message), messageTransferables);
return;
}
}
};
const messageHandler = event => {
// Handle our messages from the main thread
const eventData = getEventData(event);
switch (eventData.message.type) {
case WORKER_MESSAGE_TYPE.CONNECT:
{
// Set our lib port
libWorkerPort = eventData.message.ports[0];
onMessage(libMessageHandler, libWorkerPort); // Simply post back that we are ready
postMessage(getSmartWorkerMessage(undefined, eventData.messageId));
return;
}
case WORKER_MESSAGE_TYPE.GET_CONSTANTS:
{
// Forward to our lib worker
libWorkerPort.postMessage(getSmartWorkerMessage(eventData.message, eventData.messageId));
return;
}
case WORKER_MESSAGE_TYPE.AUDIO_LATENCY:
{
// Forward to our lib worker
libWorkerPort.postMessage(getSmartWorkerMessage(eventData.message, eventData.messageId));
return;
}
default:
{
//handle other messages from main
console.log(eventData);
}
}
};
onMessage(messageHandler);
}());