// Portions copyright 2013 Google, Inc // Copyright (C) 2010 - 2012 Grant Galitz // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // The full license is available at http://www.gnu.org/licenses/gpl.html // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // The code has been adapted for use as a benchmark by Google. var GameboyBenchmark = new BenchmarkSuite('Gameboy', [26288412], [new Benchmark('Gameboy', false, false, 20, runGameboy, setupGameboy, tearDownGameboy, null, 4)]); var decoded_gameboy_rom = null; function setupGameboy() { // Check if all the types required by the code are supported. // If not, throw exception and quit. if (!(typeof Uint8Array != "undefined" && typeof Int8Array != "undefined" && typeof Float32Array != "undefined" && typeof Int32Array != "undefined") ) { throw "TypedArrayUnsupported"; } decoded_gameboy_rom = base64_decode(gameboy_rom); rom = null; } function runGameboy() { start(new GameBoyCanvas(), decoded_gameboy_rom); gameboy.instructions = 0; gameboy.totalInstructions = 250000; while (gameboy.instructions <= gameboy.totalInstructions) { gameboy.run(); GameBoyAudioNode.run(); } resetGlobalVariables(); } function tearDownGameboy() { decoded_gameboy_rom = null; expectedGameboyStateStr = null; } var expectedGameboyStateStr = '{"registerA":160,"registerB":255,"registerC":255,"registerE":11,' + '"registersHL":51600,"programCounter":24309,"stackPointer":49706,' + '"sumROM":10171578,"sumMemory":3435856,"sumMBCRam":234598,"sumVRam":0}'; // Start of browser emulation. var GameBoyWindow = { }; function GameBoyContext() { this.createBuffer = function() { return new Buffer(); } this.createImageData = function (w, h) { var result = {}; // The following line was updated since Octane 1.0 to avoid OOB access. result.data = new Uint8Array(w * h * 4); return result; } this.putImageData = function (buffer, x, y) { var sum = 0; for (var i = 0; i < buffer.data.length; i++) { sum += i * buffer.data[i]; sum = sum % 1000; } } this.drawImage = function () { } }; function GameBoyCanvas() { this.getContext = function() { return new GameBoyContext(); } this.width = 160; this.height = 144; this.style = { visibility: "visibile" }; } function cout(message, colorIndex) { } function clear_terminal() { } var GameBoyAudioNode = { bufferSize : 0, onaudioprocess : null , connect : function () {}, run: function() { var event = {outputBuffer : this.outputBuffer}; this.onaudioprocess(event); } }; function GameBoyAudioContext () { this.createBufferSource = function() { return { noteOn : function () {}, connect : function() {}}; } this.sampleRate = 48000; this.destination = {} this.createBuffer = function (channels, len, sampleRate) { return { gain : 1, numberOfChannels : 1, length : 1, duration : 0.000020833333110203966, sampleRate : 48000} } this.createJavaScriptNode = function (bufferSize, inputChannels, outputChannels) { GameBoyAudioNode.bufferSize = bufferSize; GameBoyAudioNode.outputBuffer = { getChannelData : function (i) {return this.channelData[i];}, channelData : [] }; for (var i = 0; i < outputChannels; i++) { GameBoyAudioNode.outputBuffer.channelData[i] = new Float32Array(bufferSize); } return GameBoyAudioNode; } } var mock_date_time_counter = 0; function new_Date() { return { getTime: function() { mock_date_time_counter += 16; return mock_date_time_counter; } }; } // End of browser emulation. // Start of helper functions. function checkFinalState() { function sum(a) { var result = 0; for (var i = 0; i < a.length; i++) { result += a[i]; } return result; } var state = { registerA: gameboy.registerA, registerB: gameboy.registerB, registerC: gameboy.registerC, registerE: gameboy.registerE, registerF: gameboy.registerF, registersHL: gameboy.registersHL, programCounter: gameboy.programCounter, stackPointer: gameboy.stackPointer, sumROM : sum(gameboy.fromTypedArray(gameboy.ROM)), sumMemory: sum(gameboy.fromTypedArray(gameboy.memory)), sumMBCRam: sum(gameboy.fromTypedArray(gameboy.MBCRam)), sumVRam: sum(gameboy.fromTypedArray(gameboy.VRam)) }; var expectedState = JSON.parse(expectedGameboyStateStr); for (var prop in expectedState) { if (state[prop] !== expectedState[prop]) { var stateStr = JSON.stringify(state); alert("Incorrect final state of processor:\n" + " actual " + stateStr + "\n" + " expected " + expectedGameboyStateStr); } } } function resetGlobalVariables () { //Audio API Event Handler: audioContextHandle = null; audioNode = null; audioSource = null; launchedContext = false; audioContextSampleBuffer = []; resampled = []; webAudioMinBufferSize = 15000; webAudioMaxBufferSize = 25000; webAudioActualSampleRate = 44100; XAudioJSSampleRate = 0; webAudioMono = false; XAudioJSVolume = 1; resampleControl = null; audioBufferSize = 0; resampleBufferStart = 0; resampleBufferEnd = 0; resampleBufferSize = 2; gameboy = null; //GameBoyCore object. gbRunInterval = null; //GameBoyCore Timer } // End of helper functions. // Original code from Grant Galitz follows. // Modifications by Google are marked in comments. // Start of js/other/base64.js file. var toBase64 = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+" , "/", "="]; var fromBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; function base64(data) { try { // The following line was modified for benchmarking: var base64 = GameBoyWindow.btoa(data); //Use this native function when it's available, as it's a magnitude faster than the non-native code below. } catch (error) { //Defaulting to non-native base64 encoding... var base64 = ""; var dataLength = data.length; if (dataLength > 0) { var bytes = [0, 0, 0]; var index = 0; var remainder = dataLength % 3; while (data.length % 3 > 0) { //Make sure we don't do fuzzy math in the next loop... data[data.length] = " "; } while (index < dataLength) { //Keep this loop small for speed. bytes = [data.charCodeAt(index++) & 0xFF, data.charCodeAt(index++) & 0xFF, data.charCodeAt(index++) & 0xFF]; base64 += toBase64[bytes[0] >> 2] + toBase64[((bytes[0] & 0x3) << 4) | (bytes[1] >> 4)] + toBase64[((bytes[1] & 0xF) << 2) | (bytes[2] >> 6)] + toBase64[bytes[2] & 0x3F]; } if (remainder > 0) { //Fill in the padding and recalulate the trailing six-bit group... base64[base64.length - 1] = "="; if (remainder == 2) { base64[base64.length - 2] = "="; base64[base64.length - 3] = toBase64[(bytes[0] & 0x3) << 4]; } else { base64[base64.length - 2] = toBase64[(bytes[1] & 0xF) << 2]; } } } } return base64; } function base64_decode(data) { try { // The following line was modified for benchmarking: var decode64 = GameBoyWindow.atob(data); //Use this native function when it's available, as it's a magnitude faster than the non-native code below. } catch (error) { //Defaulting to non-native base64 decoding... var decode64 = ""; var dataLength = data.length; if (dataLength > 3 && dataLength % 4 == 0) { var sixbits = [0, 0, 0, 0]; //Declare this out of the loop, to speed up the ops. var index = 0; while (index < dataLength) { //Keep this loop small for speed. sixbits = [fromBase64.indexOf(data.charAt(index++)), fromBase64.indexOf(data.charAt(index++)), fromBase64.indexOf(data.charAt(index++)), fromBase64.indexOf(data.charAt(index++))]; decode64 += String.fromCharCode((sixbits[0] << 2) | (sixbits[1] >> 4)) + String.fromCharCode(((sixbits[1] & 0x0F) << 4) | (sixbits[2] >> 2)) + String.fromCharCode(((sixbits[2] & 0x03) << 6) | sixbits[3]); } //Check for the '=' character after the loop, so we don't hose it up. if (sixbits[3] >= 0x40) { decode64.length -= 1; if (sixbits[2] >= 0x40) { decode64.length -= 1; } } } } return decode64; } function to_little_endian_dword(str) { return to_little_endian_word(str) + String.fromCharCode((str >> 16) & 0xFF, (str >> 24) & 0xFF); } function to_little_endian_word(str) { return to_byte(str) + String.fromCharCode((str >> 8) & 0xFF); } function to_byte(str) { return String.fromCharCode(str & 0xFF); } function arrayToBase64(arrayIn) { var binString = ""; var length = arrayIn.length; for (var index = 0; index < length; ++index) { if (typeof arrayIn[index] == "number") { binString += String.fromCharCode(arrayIn[index]); } } return base64(binString); } function base64ToArray(b64String) { var binString = base64_decode(b64String); var outArray = []; var length = binString.length; for (var index = 0; index < length;) { outArray.push(binString.charCodeAt(index++) & 0xFF); } return outArray; } // End of js/other/base64.js file. // Start of js/other/resampler.js file. //JavaScript Audio Resampler (c) 2011 - Grant Galitz function Resampler(fromSampleRate, toSampleRate, channels, outputBufferSize, noReturn) { this.fromSampleRate = fromSampleRate; this.toSampleRate = toSampleRate; this.channels = channels | 0; this.outputBufferSize = outputBufferSize; this.noReturn = !!noReturn; this.initialize(); } Resampler.prototype.initialize = function () { //Perform some checks: if (this.fromSampleRate > 0 && this.toSampleRate > 0 && this.channels > 0) { if (this.fromSampleRate == this.toSampleRate) { //Setup a resampler bypass: this.resampler = this.bypassResampler; //Resampler just returns what was passed through. this.ratioWeight = 1; } else { //Setup the interpolation resampler: this.compileInterpolationFunction(); this.resampler = this.interpolate; //Resampler is a custom quality interpolation algorithm. this.ratioWeight = this.fromSampleRate / this.toSampleRate; this.tailExists = false; this.lastWeight = 0; this.initializeBuffers(); } } else { throw(new Error("Invalid settings specified for the resampler.")); } } Resampler.prototype.compileInterpolationFunction = function () { var toCompile = "var bufferLength = Math.min(buffer.length, this.outputBufferSize);\ if ((bufferLength % " + this.channels + ") == 0) {\ if (bufferLength > 0) {\ var ratioWeight = this.ratioWeight;\ var weight = 0;"; for (var channel = 0; channel < this.channels; ++channel) { toCompile += "var output" + channel + " = 0;" } toCompile += "var actualPosition = 0;\ var amountToNext = 0;\ var alreadyProcessedTail = !this.tailExists;\ this.tailExists = false;\ var outputBuffer = this.outputBuffer;\ var outputOffset = 0;\ var currentPosition = 0;\ do {\ if (alreadyProcessedTail) {\ weight = ratioWeight;"; for (channel = 0; channel < this.channels; ++channel) { toCompile += "output" + channel + " = 0;" } toCompile += "}\ else {\ weight = this.lastWeight;"; for (channel = 0; channel < this.channels; ++channel) { toCompile += "output" + channel + " = this.lastOutput[" + channel + "];" } toCompile += "alreadyProcessedTail = true;\ }\ while (weight > 0 && actualPosition < bufferLength) {\ amountToNext = 1 + actualPosition - currentPosition;\ if (weight >= amountToNext) {"; for (channel = 0; channel < this.channels; ++channel) { toCompile += "output" + channel + " += buffer[actualPosition++] * amountToNext;" } toCompile += "currentPosition = actualPosition;\ weight -= amountToNext;\ }\ else {"; for (channel = 0; channel < this.channels; ++channel) { toCompile += "output" + channel + " += buffer[actualPosition" + ((channel > 0) ? (" + " + channel) : "") + "] * weight;" } toCompile += "currentPosition += weight;\ weight = 0;\ break;\ }\ }\ if (weight == 0) {"; for (channel = 0; channel < this.channels; ++channel) { toCompile += "outputBuffer[outputOffset++] = output" + channel + " / ratioWeight;" } toCompile += "}\ else {\ this.lastWeight = weight;"; for (channel = 0; channel < this.channels; ++channel) { toCompile += "this.lastOutput[" + channel + "] = output" + channel + ";" } toCompile += "this.tailExists = true;\ break;\ }\ } while (actualPosition < bufferLength);\ return this.bufferSlice(outputOffset);\ }\ else {\ return (this.noReturn) ? 0 : [];\ }\ }\ else {\ throw(new Error(\"Buffer was of incorrect sample length.\"));\ }"; this.interpolate = Function("buffer", toCompile); } Resampler.prototype.bypassResampler = function (buffer) { if (this.noReturn) { //Set the buffer passed as our own, as we don't need to resample it: this.outputBuffer = buffer; return buffer.length; } else { //Just return the buffer passsed: return buffer; } } Resampler.prototype.bufferSlice = function (sliceAmount) { if (this.noReturn) { //If we're going to access the properties directly from this object: return sliceAmount; } else { //Typed array and normal array buffer section referencing: try { return this.outputBuffer.subarray(0, sliceAmount); } catch (error) { try { //Regular array pass: this.outputBuffer.length = sliceAmount; return this.outputBuffer; } catch (error) { //Nightly Firefox 4 used to have the subarray function named as slice: return this.outputBuffer.slice(0, sliceAmount); } } } } Resampler.prototype.initializeBuffers = function () { //Initialize the internal buffer: try { this.outputBuffer = new Float32Array(this.outputBufferSize); this.lastOutput = new Float32Array(this.channels); } catch (error) { this.outputBuffer = []; this.lastOutput = []; } } // End of js/other/resampler.js file. // Start of js/other/XAudioServer.js file. /*Initialize here first: Example: Stereo audio with a sample rate of 70 khz, a minimum buffer of 15000 samples total, a maximum buffer of 25000 samples total and a starting volume level of 1. var parentObj = this; this.audioHandle = new XAudioServer(2, 70000, 15000, 25000, function (sampleCount) { return parentObj.audioUnderRun(sampleCount); }, 1); The callback is passed the number of samples requested, while it can return any number of samples it wants back. */ function XAudioServer(channels, sampleRate, minBufferSize, maxBufferSize, underRunCallback, volume) { this.audioChannels = (channels == 2) ? 2 : 1; webAudioMono = (this.audioChannels == 1); XAudioJSSampleRate = (sampleRate > 0 && sampleRate <= 0xFFFFFF) ? sampleRate : 44100; webAudioMinBufferSize = (minBufferSize >= (samplesPerCallback << 1) && minBufferSize < maxBufferSize) ? (minBufferSize & ((webAudioMono) ? 0xFFFFFFFF : 0xFFFFFFFE)) : (samplesPerCallback << 1); webAudioMaxBufferSize = (Math.floor(maxBufferSize) > webAudioMinBufferSize + this.audioChannels) ? (maxBufferSize & ((webAudioMono) ? 0xFFFFFFFF : 0xFFFFFFFE)) : (minBufferSize << 1); this.underRunCallback = (typeof underRunCallback == "function") ? underRunCallback : function () {}; XAudioJSVolume = (volume >= 0 && volume <= 1) ? volume : 1; this.audioType = -1; this.mozAudioTail = []; this.audioHandleMoz = null; this.audioHandleFlash = null; this.flashInitialized = false; this.mozAudioFound = false; this.initializeAudio(); } XAudioServer.prototype.MOZWriteAudio = function (buffer) { //mozAudio: this.MOZWriteAudioNoCallback(buffer); this.MOZExecuteCallback(); } XAudioServer.prototype.MOZWriteAudioNoCallback = function (buffer) { //mozAudio: this.writeMozAudio(buffer); } XAudioServer.prototype.callbackBasedWriteAudio = function (buffer) { //Callback-centered audio APIs: this.callbackBasedWriteAudioNoCallback(buffer); this.callbackBasedExecuteCallback(); } XAudioServer.prototype.callbackBasedWriteAudioNoCallback = function (buffer) { //Callback-centered audio APIs: var length = buffer.length; for (var bufferCounter = 0; bufferCounter < length && audioBufferSize < webAudioMaxBufferSize;) { audioContextSampleBuffer[audioBufferSize++] = buffer[bufferCounter++]; } } /*Pass your samples into here! Pack your samples as a one-dimenional array With the channel samplea packed uniformly. examples: mono - [left, left, left, left] stereo - [left, right, left, right, left, right, left, right] */ XAudioServer.prototype.writeAudio = function (buffer) { if (this.audioType == 0) { this.MOZWriteAudio(buffer); } else if (this.audioType == 1) { this.callbackBasedWriteAudio(buffer); } else if (this.audioType == 2) { if (this.checkFlashInit() || launchedContext) { this.callbackBasedWriteAudio(buffer); } else if (this.mozAudioFound) { this.MOZWriteAudio(buffer); } } } /*Pass your samples into here if you don't want automatic callback calling: Pack your samples as a one-dimenional array With the channel samplea packed uniformly. examples: mono - [left, left, left, left] stereo - [left, right, left, right, left, right, left, right] Useful in preventing infinite recursion issues with calling writeAudio inside your callback. */ XAudioServer.prototype.writeAudioNoCallback = function (buffer) { if (this.audioType == 0) { this.MOZWriteAudioNoCallback(buffer); } else if (this.audioType == 1) { this.callbackBasedWriteAudioNoCallback(buffer); } else if (this.audioType == 2) { if (this.checkFlashInit() || launchedContext) { this.callbackBasedWriteAudioNoCallback(buffer); } else if (this.mozAudioFound) { this.MOZWriteAudioNoCallback(buffer); } } } //Developer can use this to see how many samples to write (example: minimum buffer allotment minus remaining samples left returned from this function to make sure maximum buffering is done...) //If -1 is returned, then that means metric could not be done. XAudioServer.prototype.remainingBuffer = function () { if (this.audioType == 0) { //mozAudio: return this.samplesAlreadyWritten - this.audioHandleMoz.mozCurrentSampleOffset(); } else if (this.audioType == 1) { //WebKit Audio: return (((resampledSamplesLeft() * resampleControl.ratioWeight) >> (this.audioChannels - 1)) << (this.audioChannels - 1)) + audioBufferSize; } else if (this.audioType == 2) { if (this.checkFlashInit() || launchedContext) { //Webkit Audio / Flash Plugin Audio: return (((resampledSamplesLeft() * resampleControl.ratioWeight) >> (this.audioChannels - 1)) << (this.audioChannels - 1)) + audioBufferSize; } else if (this.mozAudioFound) { //mozAudio: return this.samplesAlreadyWritten - this.audioHandleMoz.mozCurrentSampleOffset(); } } //Default return: return 0; } XAudioServer.prototype.MOZExecuteCallback = function () { //mozAudio: var samplesRequested = webAudioMinBufferSize - this.remainingBuffer(); if (samplesRequested > 0) { this.writeMozAudio(this.underRunCallback(samplesRequested)); } } XAudioServer.prototype.callbackBasedExecuteCallback = function () { //WebKit /Flash Audio: var samplesRequested = webAudioMinBufferSize - this.remainingBuffer(); if (samplesRequested > 0) { this.callbackBasedWriteAudioNoCallback(this.underRunCallback(samplesRequested)); } } //If you just want your callback called for any possible refill (Execution of callback is still conditional): XAudioServer.prototype.executeCallback = function () { if (this.audioType == 0) { this.MOZExecuteCallback(); } else if (this.audioType == 1) { this.callbackBasedExecuteCallback(); } else if (this.audioType == 2) { if (this.checkFlashInit() || launchedContext) { this.callbackBasedExecuteCallback(); } else if (this.mozAudioFound) { this.MOZExecuteCallback(); } } } //DO NOT CALL THIS, the lib calls this internally! XAudioServer.prototype.initializeAudio = function () { try { throw (new Error("Select initializeWebAudio case")); // Line added for benchmarking. this.preInitializeMozAudio(); if (navigator.platform == "Linux i686") { //Block out mozaudio usage for Linux Firefox due to moz bugs: throw(new Error("")); } this.initializeMozAudio(); } catch (error) { try { this.initializeWebAudio(); } catch (error) { try { this.initializeFlashAudio(); } catch (error) { throw(new Error("Browser does not support real time audio output.")); } } } } XAudioServer.prototype.preInitializeMozAudio = function () { //mozAudio - Synchronous Audio API this.audioHandleMoz = new Audio(); this.audioHandleMoz.mozSetup(this.audioChannels, XAudioJSSampleRate); this.samplesAlreadyWritten = 0; var emptySampleFrame = (this.audioChannels == 2) ? [0, 0] : [0]; var prebufferAmount = 0; if (navigator.platform != "MacIntel" && navigator.platform != "MacPPC") { //Mac OS X doesn't experience this moz-bug! while (this.audioHandleMoz.mozCurrentSampleOffset() == 0) { //Mozilla Audio Bugginess Workaround (Firefox freaks out if we don't give it a prebuffer under certain OSes): prebufferAmount += this.audioHandleMoz.mozWriteAudio(emptySampleFrame); } var samplesToDoubleBuffer = prebufferAmount / this.audioChannels; //Double the prebuffering for windows: for (var index = 0; index < samplesToDoubleBuffer; index++) { this.samplesAlreadyWritten += this.audioHandleMoz.mozWriteAudio(emptySampleFrame); } } this.samplesAlreadyWritten += prebufferAmount; webAudioMinBufferSize += this.samplesAlreadyWritten; this.mozAudioFound = true; } XAudioServer.prototype.initializeMozAudio = function () { //Fill in our own buffering up to the minimum specified: this.writeMozAudio(getFloat32(webAudioMinBufferSize)); this.audioType = 0; } XAudioServer.prototype.initializeWebAudio = function () { if (launchedContext) { resetCallbackAPIAudioBuffer(webAudioActualSampleRate, samplesPerCallback); this.audioType = 1; } else { throw(new Error("")); } } XAudioServer.prototype.initializeFlashAudio = function () { var existingFlashload = document.getElementById("XAudioJS"); if (existingFlashload == null) { var thisObj = this; var mainContainerNode = document.createElement("div"); mainContainerNode.setAttribute("style", "position: fixed; bottom: 0px; right: 0px; margin: 0px; padding: 0px; border: none; width: 8px; height: 8px; overflow: hidden; z-index: -1000; "); var containerNode = document.createElement("div"); containerNode.setAttribute("style", "position: static; border: none; width: 0px; height: 0px; visibility: hidden; margin: 8px; padding: 0px;"); containerNode.setAttribute("id", "XAudioJS"); mainContainerNode.appendChild(containerNode); document.getElementsByTagName("body")[0].appendChild(mainContainerNode); swfobject.embedSWF( "XAudioJS.swf", "XAudioJS", "8", "8", "9.0.0", "", {}, {"allowscriptaccess":"always"}, {"style":"position: static; visibility: hidden; margin: 8px; padding: 0px; border: none"}, function (event) { if (event.success) { thisObj.audioHandleFlash = event.ref; } else { thisObj.audioType = 1; } } ); } else { this.audioHandleFlash = existingFlashload; } this.audioType = 2; } XAudioServer.prototype.changeVolume = function (newVolume) { if (newVolume >= 0 && newVolume <= 1) { XAudioJSVolume = newVolume; if (this.checkFlashInit()) { this.audioHandleFlash.changeVolume(XAudioJSVolume); } if (this.mozAudioFound) { this.audioHandleMoz.volume = XAudioJSVolume; } } } //Moz Audio Buffer Writing Handler: XAudioServer.prototype.writeMozAudio = function (buffer) { var length = this.mozAudioTail.length; if (length > 0) { var samplesAccepted = this.audioHandleMoz.mozWriteAudio(this.mozAudioTail); this.samplesAlreadyWritten += samplesAccepted; this.mozAudioTail.splice(0, samplesAccepted); } length = Math.min(buffer.length, webAudioMaxBufferSize - this.samplesAlreadyWritten + this.audioHandleMoz.mozCurrentSampleOffset()); var samplesAccepted = this.audioHandleMoz.mozWriteAudio(buffer); this.samplesAlreadyWritten += samplesAccepted; for (var index = 0; length > samplesAccepted; --length) { //Moz Audio wants us saving the tail: this.mozAudioTail.push(buffer[index++]); } } //Checks to see if the NPAPI Adobe Flash bridge is ready yet: XAudioServer.prototype.checkFlashInit = function () { if (!this.flashInitialized && this.audioHandleFlash && this.audioHandleFlash.initialize) { this.flashInitialized = true; this.audioHandleFlash.initialize(this.audioChannels, XAudioJSVolume); resetCallbackAPIAudioBuffer(44100, samplesPerCallback); } return this.flashInitialized; } /////////END LIB function getFloat32(size) { try { return new Float32Array(size); } catch (error) { return new Array(size); } } function getFloat32Flat(size) { try { var newBuffer = new Float32Array(size); } catch (error) { var newBuffer = new Array(size); var audioSampleIndice = 0; do { newBuffer[audioSampleIndice] = 0; } while (++audioSampleIndice < size); } return newBuffer; } //Flash NPAPI Event Handler: var samplesPerCallback = 2048; //Has to be between 2048 and 4096 (If over, then samples are ignored, if under then silence is added). var outputConvert = null; function audioOutputFlashEvent() { //The callback that flash calls... resampleRefill(); return outputConvert(); } function generateFlashStereoString() { //Convert the arrays to one long string for speed. var copyBinaryStringLeft = ""; var copyBinaryStringRight = ""; for (var index = 0; index < samplesPerCallback && resampleBufferStart != resampleBufferEnd; ++index) { //Sanitize the buffer: copyBinaryStringLeft += String.fromCharCode(((Math.min(Math.max(resampled[resampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000); copyBinaryStringRight += String.fromCharCode(((Math.min(Math.max(resampled[resampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000); if (resampleBufferStart == resampleBufferSize) { resampleBufferStart = 0; } } return copyBinaryStringLeft + copyBinaryStringRight; } function generateFlashMonoString() { //Convert the array to one long string for speed. var copyBinaryString = ""; for (var index = 0; index < samplesPerCallback && resampleBufferStart != resampleBufferEnd; ++index) { //Sanitize the buffer: copyBinaryString += String.fromCharCode(((Math.min(Math.max(resampled[resampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000); if (resampleBufferStart == resampleBufferSize) { resampleBufferStart = 0; } } return copyBinaryString; } //Audio API Event Handler: var audioContextHandle = null; var audioNode = null; var audioSource = null; var launchedContext = false; var audioContextSampleBuffer = []; var resampled = []; var webAudioMinBufferSize = 15000; var webAudioMaxBufferSize = 25000; var webAudioActualSampleRate = 44100; var XAudioJSSampleRate = 0; var webAudioMono = false; var XAudioJSVolume = 1; var resampleControl = null; var audioBufferSize = 0; var resampleBufferStart = 0; var resampleBufferEnd = 0; var resampleBufferSize = 2; function audioOutputEvent(event) { //Web Audio API callback... var index = 0; var buffer1 = event.outputBuffer.getChannelData(0); var buffer2 = event.outputBuffer.getChannelData(1); resampleRefill(); if (!webAudioMono) { //STEREO: while (index < samplesPerCallback && resampleBufferStart != resampleBufferEnd) { buffer1[index] = resampled[resampleBufferStart++] * XAudioJSVolume; buffer2[index++] = resampled[resampleBufferStart++] * XAudioJSVolume; if (resampleBufferStart == resampleBufferSize) { resampleBufferStart = 0; } } } else { //MONO: while (index < samplesPerCallback && resampleBufferStart != resampleBufferEnd) { buffer2[index] = buffer1[index] = resampled[resampleBufferStart++] * XAudioJSVolume; ++index; if (resampleBufferStart == resampleBufferSize) { resampleBufferStart = 0; } } } //Pad with silence if we're underrunning: while (index < samplesPerCallback) { buffer2[index] = buffer1[index] = 0; ++index; } } function resampleRefill() { if (audioBufferSize > 0) { //Resample a chunk of audio: var resampleLength = resampleControl.resampler(getBufferSamples()); var resampledResult = resampleControl.outputBuffer; for (var index2 = 0; index2 < resampleLength; ++index2) { resampled[resampleBufferEnd++] = resampledResult[index2]; if (resampleBufferEnd == resampleBufferSize) { resampleBufferEnd = 0; } if (resampleBufferStart == resampleBufferEnd) { ++resampleBufferStart; if (resampleBufferStart == resampleBufferSize) { resampleBufferStart = 0; } } } audioBufferSize = 0; } } function resampledSamplesLeft() { return ((resampleBufferStart <= resampleBufferEnd) ? 0 : resampleBufferSize) + resampleBufferEnd - resampleBufferStart; } function getBufferSamples() { //Typed array and normal array buffer section referencing: try { return audioContextSampleBuffer.subarray(0, audioBufferSize); } catch (error) { try { //Regular array pass: audioContextSampleBuffer.length = audioBufferSize; return audioContextSampleBuffer; } catch (error) { //Nightly Firefox 4 used to have the subarray function named as slice: return audioContextSampleBuffer.slice(0, audioBufferSize); } } } //Initialize WebKit Audio /Flash Audio Buffer: function resetCallbackAPIAudioBuffer(APISampleRate, bufferAlloc) { audioContextSampleBuffer = getFloat32(webAudioMaxBufferSize); audioBufferSize = webAudioMaxBufferSize; resampleBufferStart = 0; resampleBufferEnd = 0; resampleBufferSize = Math.max(webAudioMaxBufferSize * Math.ceil(XAudioJSSampleRate / APISampleRate), samplesPerCallback) << 1; if (webAudioMono) { //MONO Handling: resampled = getFloat32Flat(resampleBufferSize); resampleControl = new Resampler(XAudioJSSampleRate, APISampleRate, 1, resampleBufferSize, true); outputConvert = generateFlashMonoString; } else { //STEREO Handling: resampleBufferSize <<= 1; resampled = getFloat32Flat(resampleBufferSize); resampleControl = new Resampler(XAudioJSSampleRate, APISampleRate, 2, resampleBufferSize, true); outputConvert = generateFlashStereoString; } } //Initialize WebKit Audio: (function () { if (!launchedContext) { try { // The following line was modified for benchmarking: audioContextHandle = new GameBoyAudioContext(); //Create a system audio context. } catch (error) { try { audioContextHandle = new AudioContext(); //Create a system audio context. } catch (error) { return; } } try { audioSource = audioContextHandle.createBufferSource(); //We need to create a false input to get the chain started. audioSource.loop = false; //Keep this alive forever (Event handler will know when to ouput.) XAudioJSSampleRate = webAudioActualSampleRate = audioContextHandle.sampleRate; audioSource.buffer = audioContextHandle.createBuffer(1, 1, webAudioActualSampleRate); //Create a zero'd input buffer for the input to be valid. audioNode = audioContextHandle.createJavaScriptNode(samplesPerCallback, 1, 2); //Create 2 outputs and ignore the input buffer (Just copy buffer 1 over if mono) audioNode.onaudioprocess = audioOutputEvent; //Connect the audio processing event to a handling function so we can manipulate output audioSource.connect(audioNode); //Send and chain the input to the audio manipulation. audioNode.connect(audioContextHandle.destination); //Send and chain the output of the audio manipulation to the system audio output. audioSource.noteOn(0); //Start the loop! } catch (error) { return; } launchedContext = true; } })(); // End of js/other/XAudioServer.js file. // Start of js/other/resize.js file. //JavaScript Image Resizer (c) 2012 - Grant Galitz function Resize(widthOriginal, heightOriginal, targetWidth, targetHeight, blendAlpha, interpolationPass) { this.widthOriginal = Math.abs(parseInt(widthOriginal) || 0); this.heightOriginal = Math.abs(parseInt(heightOriginal) || 0); this.targetWidth = Math.abs(parseInt(targetWidth) || 0); this.targetHeight = Math.abs(parseInt(targetHeight) || 0); this.colorChannels = (!!blendAlpha) ? 4 : 3; this.interpolationPass = !!interpolationPass; this.targetWidthMultipliedByChannels = this.targetWidth * this.colorChannels; this.originalWidthMultipliedByChannels = this.widthOriginal * this.colorChannels; this.originalHeightMultipliedByChannels = this.heightOriginal * this.colorChannels; this.widthPassResultSize = this.targetWidthMultipliedByChannels * this.heightOriginal; this.finalResultSize = this.targetWidthMultipliedByChannels * this.targetHeight; this.initialize(); } Resize.prototype.initialize = function () { //Perform some checks: if (this.widthOriginal > 0 && this.heightOriginal > 0 && this.targetWidth > 0 && this.targetHeight > 0) { if (this.widthOriginal == this.targetWidth) { //Bypass the width resizer pass: this.resizeWidth = this.bypassResizer; } else { //Setup the width resizer pass: this.ratioWeightWidthPass = this.widthOriginal / this.targetWidth; if (this.ratioWeightWidthPass < 1 && this.interpolationPass) { this.initializeFirstPassBuffers(true); this.resizeWidth = (this.colorChannels == 4) ? this.resizeWidthInterpolatedRGBA : this.resizeWidthInterpolatedRGB; } else { this.initializeFirstPassBuffers(false); this.resizeWidth = (this.colorChannels == 4) ? this.resizeWidthRGBA : this.resizeWidthRGB; } } if (this.heightOriginal == this.targetHeight) { //Bypass the height resizer pass: this.resizeHeight = this.bypassResizer; } else { //Setup the height resizer pass: this.ratioWeightHeightPass = this.heightOriginal / this.targetHeight; if (this.ratioWeightHeightPass < 1 && this.interpolationPass) { this.initializeSecondPassBuffers(true); this.resizeHeight = this.resizeHeightInterpolated; } else { this.initializeSecondPassBuffers(false); this.resizeHeight = (this.colorChannels == 4) ? this.resizeHeightRGBA : this.resizeHeightRGB; } } } else { throw(new Error("Invalid settings specified for the resizer.")); } } Resize.prototype.resizeWidthRGB = function (buffer) { var ratioWeight = this.ratioWeightWidthPass; var weight = 0; var amountToNext = 0; var actualPosition = 0; var currentPosition = 0; var line = 0; var pixelOffset = 0; var outputOffset = 0; var nextLineOffsetOriginalWidth = this.originalWidthMultipliedByChannels - 2; var nextLineOffsetTargetWidth = this.targetWidthMultipliedByChannels - 2; var output = this.outputWidthWorkBench; var outputBuffer = this.widthBuffer; do { for (line = 0; line < this.originalHeightMultipliedByChannels;) { output[line++] = 0; output[line++] = 0; output[line++] = 0; } weight = ratioWeight; do { amountToNext = 1 + actualPosition - currentPosition; if (weight >= amountToNext) { for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) { output[line++] += buffer[pixelOffset++] * amountToNext; output[line++] += buffer[pixelOffset++] * amountToNext; output[line++] += buffer[pixelOffset] * amountToNext; } currentPosition = actualPosition = actualPosition + 3; weight -= amountToNext; } else { for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) { output[line++] += buffer[pixelOffset++] * weight; output[line++] += buffer[pixelOffset++] * weight; output[line++] += buffer[pixelOffset] * weight; } currentPosition += weight; break; } } while (weight > 0 && actualPosition < this.originalWidthMultipliedByChannels); for (line = 0, pixelOffset = outputOffset; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetTargetWidth) { outputBuffer[pixelOffset++] = output[line++] / ratioWeight; outputBuffer[pixelOffset++] = output[line++] / ratioWeight; outputBuffer[pixelOffset] = output[line++] / ratioWeight; } outputOffset += 3; } while (outputOffset < this.targetWidthMultipliedByChannels); return outputBuffer; } Resize.prototype.resizeWidthInterpolatedRGB = function (buffer) { var ratioWeight = (this.widthOriginal - 1) / this.targetWidth; var weight = 0; var finalOffset = 0; var pixelOffset = 0; var outputBuffer = this.widthBuffer; for (var targetPosition = 0; targetPosition < this.targetWidthMultipliedByChannels; targetPosition += 3, weight += ratioWeight) { //Calculate weightings: secondWeight = weight % 1; firstWeight = 1 - secondWeight; //Interpolate: for (finalOffset = targetPosition, pixelOffset = Math.floor(weight) * 3; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) { outputBuffer[finalOffset] = (buffer[pixelOffset] * firstWeight) + (buffer[pixelOffset + 3] * secondWeight); outputBuffer[finalOffset + 1] = (buffer[pixelOffset + 1] * firstWeight) + (buffer[pixelOffset + 4] * secondWeight); outputBuffer[finalOffset + 2] = (buffer[pixelOffset + 2] * firstWeight) + (buffer[pixelOffset + 5] * secondWeight); } } return outputBuffer; } Resize.prototype.resizeWidthRGBA = function (buffer) { var ratioWeight = this.ratioWeightWidthPass; var weight = 0; var amountToNext = 0; var actualPosition = 0; var currentPosition = 0; var line = 0; var pixelOffset = 0; var outputOffset = 0; var nextLineOffsetOriginalWidth = this.originalWidthMultipliedByChannels - 3; var nextLineOffsetTargetWidth = this.targetWidthMultipliedByChannels - 3; var output = this.outputWidthWorkBench; var outputBuffer = this.widthBuffer; do { for (line = 0; line < this.originalHeightMultipliedByChannels;) { output[line++] = 0; output[line++] = 0; output[line++] = 0; output[line++] = 0; } weight = ratioWeight; do { amountToNext = 1 + actualPosition - currentPosition; if (weight >= amountToNext) { for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) { output[line++] += buffer[pixelOffset++] * amountToNext; output[line++] += buffer[pixelOffset++] * amountToNext; output[line++] += buffer[pixelOffset++] * amountToNext; output[line++] += buffer[pixelOffset] * amountToNext; } currentPosition = actualPosition = actualPosition + 4; weight -= amountToNext; } else { for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) { output[line++] += buffer[pixelOffset++] * weight; output[line++] += buffer[pixelOffset++] * weight; output[line++] += buffer[pixelOffset++] * weight; output[line++] += buffer[pixelOffset] * weight; } currentPosition += weight; break; } } while (weight > 0 && actualPosition < this.originalWidthMultipliedByChannels); for (line = 0, pixelOffset = outputOffset; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetTargetWidth) { outputBuffer[pixelOffset++] = output[line++] / ratioWeight; outputBuffer[pixelOffset++] = output[line++] / ratioWeight; outputBuffer[pixelOffset++] = output[line++] / ratioWeight; outputBuffer[pixelOffset] = output[line++] / ratioWeight; } outputOffset += 4; } while (outputOffset < this.targetWidthMultipliedByChannels); return outputBuffer; } Resize.prototype.resizeWidthInterpolatedRGBA = function (buffer) { var ratioWeight = (this.widthOriginal - 1) / this.targetWidth; var weight = 0; var finalOffset = 0; var pixelOffset = 0; var outputBuffer = this.widthBuffer; for (var targetPosition = 0; targetPosition < this.targetWidthMultipliedByChannels; targetPosition += 4, weight += ratioWeight) { //Calculate weightings: secondWeight = weight % 1; firstWeight = 1 - secondWeight; //Interpolate: for (finalOffset = targetPosition, pixelOffset = Math.floor(weight) * 4; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) { outputBuffer[finalOffset] = (buffer[pixelOffset] * firstWeight) + (buffer[pixelOffset + 4] * secondWeight); outputBuffer[finalOffset + 1] = (buffer[pixelOffset + 1] * firstWeight) + (buffer[pixelOffset + 5] * secondWeight); outputBuffer[finalOffset + 2] = (buffer[pixelOffset + 2] * firstWeight) + (buffer[pixelOffset + 6] * secondWeight); outputBuffer[finalOffset + 3] = (buffer[pixelOffset + 3] * firstWeight) + (buffer[pixelOffset + 7] * secondWeight); } } return outputBuffer; } Resize.prototype.resizeHeightRGB = function (buffer) { var ratioWeight = this.ratioWeightHeightPass; var weight = 0; var amountToNext = 0; var actualPosition = 0; var currentPosition = 0; var pixelOffset = 0; var outputOffset = 0; var output = this.outputHeightWorkBench; var outputBuffer = this.heightBuffer; do { for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) { output[pixelOffset++] = 0; output[pixelOffset++] = 0; output[pixelOffset++] = 0; } weight = ratioWeight; do { amountToNext = 1 + actualPosition - currentPosition; if (weight >= amountToNext) { for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) { output[pixelOffset++] += buffer[actualPosition++] * amountToNext; output[pixelOffset++] += buffer[actualPosition++] * amountToNext; output[pixelOffset++] += buffer[actualPosition++] * amountToNext; } currentPosition = actualPosition; weight -= amountToNext; } else { for (pixelOffset = 0, amountToNext = actualPosition; pixelOffset < this.targetWidthMultipliedByChannels;) { output[pixelOffset++] += buffer[amountToNext++] * weight; output[pixelOffset++] += buffer[amountToNext++] * weight; output[pixelOffset++] += buffer[amountToNext++] * weight; } currentPosition += weight; break; } } while (weight > 0 && actualPosition < this.widthPassResultSize); for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) { outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] / ratioWeight); outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] / ratioWeight); outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] / ratioWeight); } } while (outputOffset < this.finalResultSize); return outputBuffer; } Resize.prototype.resizeHeightInterpolated = function (buffer) { var ratioWeight = (this.heightOriginal - 1) / this.targetHeight; var weight = 0; var finalOffset = 0; var pixelOffset = 0; var pixelOffsetAccumulated = 0; var pixelOffsetAccumulated2 = 0; var outputBuffer = this.heightBuffer; do { //Calculate weightings: secondWeight = weight % 1; firstWeight = 1 - secondWeight; //Interpolate: pixelOffsetAccumulated = Math.floor(weight) * this.targetWidthMultipliedByChannels; pixelOffsetAccumulated2 = pixelOffsetAccumulated + this.targetWidthMultipliedByChannels; for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels; ++pixelOffset) { outputBuffer[finalOffset++] = (buffer[pixelOffsetAccumulated + pixelOffset] * firstWeight) + (buffer[pixelOffsetAccumulated2 + pixelOffset] * secondWeight); } weight += ratioWeight; } while (finalOffset < this.finalResultSize); return outputBuffer; } Resize.prototype.resizeHeightRGBA = function (buffer) { var ratioWeight = this.ratioWeightHeightPass; var weight = 0; var amountToNext = 0; var actualPosition = 0; var currentPosition = 0; var pixelOffset = 0; var outputOffset = 0; var output = this.outputHeightWorkBench; var outputBuffer = this.heightBuffer; do { for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) { output[pixelOffset++] = 0; output[pixelOffset++] = 0; output[pixelOffset++] = 0; output[pixelOffset++] = 0; } weight = ratioWeight; do { amountToNext = 1 + actualPosition - currentPosition; if (weight >= amountToNext) { for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) { output[pixelOffset++] += buffer[actualPosition++] * amountToNext; output[pixelOffset++] += buffer[actualPosition++] * amountToNext; output[pixelOffset++] += buffer[actualPosition++] * amountToNext; output[pixelOffset++] += buffer[actualPosition++] * amountToNext; } currentPosition = actualPosition; weight -= amountToNext; } else { for (pixelOffset = 0, amountToNext = actualPosition; pixelOffset < this.targetWidthMultipliedByChannels;) { output[pixelOffset++] += buffer[amountToNext++] * weight; output[pixelOffset++] += buffer[amountToNext++] * weight; output[pixelOffset++] += buffer[amountToNext++] * weight; output[pixelOffset++] += buffer[amountToNext++] * weight; } currentPosition += weight; break; } } while (weight > 0 && actualPosition < this.widthPassResultSize); for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) { outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] / ratioWeight); outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] / ratioWeight); outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] / ratioWeight); outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] / ratioWeight); } } while (outputOffset < this.finalResultSize); return outputBuffer; } Resize.prototype.resizeHeightInterpolatedRGBA = function (buffer) { var ratioWeight = (this.heightOriginal - 1) / this.targetHeight; var weight = 0; var finalOffset = 0; var pixelOffset = 0; var outputBuffer = this.heightBuffer; while (pixelOffset < this.finalResultSize) { //Calculate weightings: secondWeight = weight % 1; firstWeight = 1 - secondWeight; //Interpolate: for (pixelOffset = Math.floor(weight) * 4; pixelOffset < this.targetWidthMultipliedByChannels; pixelOffset += 4) { outputBuffer[finalOffset++] = (buffer[pixelOffset] * firstWeight) + (buffer[pixelOffset + 4] * secondWeight); outputBuffer[finalOffset++] = (buffer[pixelOffset + 1] * firstWeight) + (buffer[pixelOffset + 5] * secondWeight); outputBuffer[finalOffset++] = (buffer[pixelOffset + 2] * firstWeight) + (buffer[pixelOffset + 6] * secondWeight); outputBuffer[finalOffset++] = (buffer[pixelOffset + 3] * firstWeight) + (buffer[pixelOffset + 7] * secondWeight); } weight += ratioWeight; } return outputBuffer; } Resize.prototype.resize = function (buffer) { return this.resizeHeight(this.resizeWidth(buffer)); } Resize.prototype.bypassResizer = function (buffer) { //Just return the buffer passsed: return buffer; } Resize.prototype.initializeFirstPassBuffers = function (BILINEARAlgo) { //Initialize the internal width pass buffers: this.widthBuffer = this.generateFloatBuffer(this.widthPassResultSize); if (!BILINEARAlgo) { this.outputWidthWorkBench = this.generateFloatBuffer(this.originalHeightMultipliedByChannels); } } Resize.prototype.initializeSecondPassBuffers = function (BILINEARAlgo) { //Initialize the internal height pass buffers: this.heightBuffer = this.generateUint8Buffer(this.finalResultSize); if (!BILINEARAlgo) { this.outputHeightWorkBench = this.generateFloatBuffer(this.targetWidthMultipliedByChannels); } } Resize.prototype.generateFloatBuffer = function (bufferLength) { //Generate a float32 typed array buffer: try { return new Float32Array(bufferLength); } catch (error) { return []; } } Resize.prototype.generateUint8Buffer = function (bufferLength) { //Generate a uint8 typed array buffer: try { return this.checkForOperaMathBug(new Uint8Array(bufferLength)); } catch (error) { return []; } } Resize.prototype.checkForOperaMathBug = function (typedArray) { typedArray[0] = -1; typedArray[0] >>= 0; if (typedArray[0] != 0xFF) { return []; } else { return typedArray; } } // End of js/other/resize.js file. // Remaining files are in gbemu-part2.js, since they run in strict mode.