Remove js-emulator (that's on a separate branch now)

This commit is contained in:
Evan Barger
2020-07-05 10:54:47 -04:00
parent b380490b32
commit 8d3b2af195
17 changed files with 0 additions and 11805 deletions

View File

@@ -1,43 +0,0 @@
# JavaScript GameBoy Color Emulator
Fork of Grant Galitz's JavaScript GameBoy Color Emulator made for running a single ROM
when uploading homebrew games to services like Itch.io.
This version makes the following changes.
- Canvas fills browser window on desktop/tablet while keeping aspect ratio
- Touch controls displayed on mobile/tablet
- Using css `image-rendering: pixelated` rather than bilinear filtering
- Touch dpad controls for mobile using touch move with a deadzone
- Keyboard fix for iPad keyboard case that doesn't report keyup event keycode
- Wait for keyboard or touch input before starting AudioContext to fix issues in Chrome and iOS not playing Audio
- No ability to switch ROM, or save/load states, this version is intended for deploying a single game
## Usage
- Clone this repository
- Add your ROM file as `rom/game.gb` (or edit romPath in js/other/mobile.js to point to your ROM file)
- Upload to a webserver and visit index.html
## Keyboard Controls
Up - Up Arrow / W
Down - Down Arrow / S
Left - Left Arrow / A
Right - Right Arrow / D
A - Alt / Z / J
B - Ctrl / K / X
Start - Enter
Select - Shift
Edit by changing `bindKeyboard` in `js/other/controls.js`.
## License
**Copyright (C) 2010 - 2016 Grant Galitz**
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,317 +0,0 @@
body {
background: #031921;
color: #fff;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue",
Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 300;
margin: 0px;
padding: 0px;
touch-action: none;
text-align: center;
-webkit-touch-callout: none;
user-select: none;
overflow: hidden;
}
#game {
width: 100%;
touch-action: none;
text-align: center;
}
#game canvas {
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-crisp-edges;
image-rendering: pixelated;
image-rendering: crisp-edges;
}
#controller {
display: none;
position: fixed;
bottom: 0px;
height: 210px;
width: 100%;
touch-action: none;
opacity: 0.8;
}
#controller_dpad {
position: absolute;
bottom: 20px;
left: 0px;
width: 184px;
height: 184px;
}
#controller_dpad:before {
content: "";
display: block;
width: 48px;
height: 48px;
background: #5c5c5c;
background: radial-gradient(
ellipse at center,
#5c5c5c 0%,
#555 59%,
#5c5c5c 60%
);
position: absolute;
left: 68px;
top: 68px;
}
#controller_left {
position: absolute;
left: 20px;
top: 68px;
width: 48px;
height: 48px;
background: #666;
background: radial-gradient(ellipse at center, #666 0%, #5c5c5c 80%);
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
#controller_right {
position: absolute;
left: 116px;
top: 68px;
width: 48px;
height: 48px;
background: #666;
background: radial-gradient(ellipse at center, #666 0%, #5c5c5c 80%);
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
#controller_up {
position: absolute;
left: 68px;
top: 20px;
width: 48px;
height: 48px;
background: #666;
background: radial-gradient(ellipse at center, #666 0%, #5c5c5c 80%);
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
#controller_down {
position: absolute;
left: 68px;
top: 116px;
width: 48px;
height: 48px;
background: #666;
background: radial-gradient(ellipse at center, #666 0%, #5c5c5c 80%);
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
#controller_a {
position: absolute;
bottom: 110px;
right: 20px;
}
#controller_b {
position: absolute;
bottom: 80px;
right: 100px;
}
.roundBtn {
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
font-size: 32px;
color: #440f1f;
line-height: 64px;
width: 64px;
height: 64px;
border-radius: 64px;
background: #870a4c;
background: radial-gradient(ellipse at center, #ab1465 0%, #8b1e57 100%);
box-shadow: 0px 4px 5px rgba(0, 0, 0, 0.2);
}
.capsuleBtn {
font-weight: bold;
font-size: 10px;
color: #111;
display: flex;
justify-content: center;
align-items: center;
line-height: 40px;
text-transform: uppercase;
width: 64px;
height: 32px;
border-radius: 40px;
background: #222;
background: radial-gradient(ellipse at center, #666 0%, #555 100%);
box-shadow: 0px 4px 5px rgba(0, 0, 0, 0.2);
}
#controller_start {
position: absolute;
bottom: 20px;
right: 15px;
}
#controller_select {
position: absolute;
bottom: 20px;
right: 100px;
}
.btnPressed {
opacity: 0.5;
}
.spinner {
height: 50px;
width: 50px;
margin: 0px auto;
-webkit-animation: rotation 0.8s linear infinite;
-moz-animation: rotation 0.8s linear infinite;
-o-animation: rotation 0.8s linear infinite;
animation: rotation 0.8s linear infinite;
border-left: 10px solid #306850;
border-right: 10px solid #306850;
border-bottom: 10px solid #306850;
border-top: 10px solid #88c070;
border-radius: 100%;
background-color: #031921;
}
@-webkit-keyframes rotation {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
}
}
@-moz-keyframes rotation {
from {
-moz-transform: rotate(0deg);
}
to {
-moz-transform: rotate(360deg);
}
}
@-o-keyframes rotation {
from {
-o-transform: rotate(0deg);
}
to {
-o-transform: rotate(360deg);
}
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media only screen and (max-width: 640px) {
#game canvas {
margin-top: 0px;
width: 100%;
max-width: 480px;
border: 0px;
border-radius: 0px;
}
}
@media only screen and (max-device-width: 812px) and (orientation: portrait) {
body {
margin: 0;
}
#game {
width: 100%;
position: fixed;
touch-action: none;
}
#game canvas {
margin: 0;
display: block;
width: 100% !important;
height: auto !important;
}
}
@media only screen and (max-device-width: 320px) and (orientation: portrait) {
#controller_dpad {
left: -5px;
bottom: -5px;
}
#controller_a {
right: 5px;
bottom: 95px;
}
#controller_b {
right: 80px;
}
#controller_start {
right: 5px;
}
#controller_select {
right: 80px;
}
}
@media only screen and (max-width: 500px) and (max-height: 400px) {
#controller {
display: none;
}
}
/* Small devices in landscape */
@media only screen and (max-device-width: 300px) and (orientation: landscape) {
html,
body {
height: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
#game:after {
content: "PLEASE ROTATE ↻";
font-size: 24px;
font-weight: bold;
color: #fff;
}
#game canvas {
display: none;
max-width: 480px;
}
#controller {
display: none;
}
}
/* Devices large enough for landscape */
@media only screen and (min-width: 300px) and (orientation: landscape) {
#controller {
bottom: 50%;
transform: translateY(50%);
opacity: 0.5;
}
}

View File

@@ -1,44 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="css/style.css" />
<title>___PROJECT_NAME___</title>
<meta name="author" content="___AUTHOR___" />
___COLORS_HEAD___
___PROJECT_HEAD___
</head>
<body>
<div id="gameboy_shell">
<div id="game">
<canvas id="mainCanvas" width="160" height="144">No Canvas Support</canvas>
</div>
<div id="controller">
<div id="controller_dpad">
<div id="controller_left"></div>
<div id="controller_right"></div>
<div id="controller_up"></div>
<div id="controller_down"></div>
</div>
<div id="controller_select" class="capsuleBtn">Select</div>
<div id="controller_start" class="capsuleBtn">Start</div>
<div id="controller_b" class="roundBtn">B</div>
<div id="controller_a" class="roundBtn">A</div>
</div>
</div>
<script>
const customControls = ___CUSTOM_CONTROLS___
</script>
<script src="js/other/mobile.js"></script>
<script src="js/other/base64.js"></script>
<script src="js/other/swfobject.js"></script>
<script src="js/other/resampler.js"></script>
<script src="js/other/XAudioServer.js"></script>
<script src="js/other/controls.js"></script>
<script src="js/other/resize.js"></script>
<script src="js/GameBoyCore.js"></script>
<script src="js/GameBoyIO.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,417 +0,0 @@
"use strict";
var gameboy = null; //GameBoyCore object.
var gbRunInterval = null; //GameBoyCore Timer
var settings = [ //Some settings.
window.location.href.indexOf("audio=true") > -1, //Turn on sound.
true, //Boot with boot ROM first?
false, //Give priority to GameBoy mode
1, //Volume level set.
true, //Colorize GB mode?
false, //Disallow typed arrays?
8, //Interval for the emulator loop.
10, //Audio buffer minimum span amount over x interpreter iterations.
20, //Audio buffer maximum span amount over x interpreter iterations.
false, //Override to allow for MBC1 instead of ROM only (compatibility for broken 3rd-party cartridges).
false, //Override MBC RAM disabling and always allow reading and writing to the banks.
false, //Use the GameBoy boot ROM instead of the GameBoy Color boot ROM.
false, //Scale the canvas in JS, or let the browser scale the canvas?
true, //Use image smoothing based scaling?
[true, true, true, true] //User controlled channel enables.
];
function start(canvas, ROM) {
clearLastEmulation();
autoSave(); //If we are about to load a new game, then save the last one...
gameboy = new GameBoyCore(canvas, ROM);
gameboy.openMBC = openSRAM;
gameboy.openRTC = openRTC;
gameboy.start();
run();
}
function run() {
if (GameBoyEmulatorInitialized()) {
if (!GameBoyEmulatorPlaying()) {
gameboy.stopEmulator &= 1;
cout("Starting the iterator.", 0);
var dateObj = new Date();
gameboy.firstIteration = dateObj.getTime();
gameboy.iterations = 0;
gbRunInterval = setInterval(function () {
if (!document.hidden && !document.msHidden && !document.mozHidden && !document.webkitHidden) {
gameboy.run();
}
}, settings[6]);
}
else {
cout("The GameBoy core is already running.", 1);
}
}
else {
cout("GameBoy core cannot run while it has not been initialized.", 1);
}
}
function pause() {
if (GameBoyEmulatorInitialized()) {
if (GameBoyEmulatorPlaying()) {
autoSave();
clearLastEmulation();
}
else {
cout("GameBoy core has already been paused.", 1);
}
}
else {
cout("GameBoy core cannot be paused while it has not been initialized.", 1);
}
}
function clearLastEmulation() {
if (GameBoyEmulatorInitialized() && GameBoyEmulatorPlaying()) {
clearInterval(gbRunInterval);
gameboy.stopEmulator |= 2;
cout("The previous emulation has been cleared.", 0);
}
else {
cout("No previous emulation was found to be cleared.", 0);
}
}
function save() {
if (GameBoyEmulatorInitialized()) {
var state_suffix = 0;
while (findValue("FREEZE_" + gameboy.name + "_" + state_suffix) != null) {
state_suffix++;
}
saveState("FREEZE_" + gameboy.name + "_" + state_suffix);
}
else {
cout("GameBoy core cannot be saved while it has not been initialized.", 1);
}
}
function saveSRAM() {
if (GameBoyEmulatorInitialized()) {
if (gameboy.cBATT) {
try {
var sram = gameboy.saveSRAMState();
if (sram.length > 0) {
cout("Saving the SRAM...", 0);
if (findValue("SRAM_" + gameboy.name) != null) {
//Remove the outdated storage format save:
cout("Deleting the old SRAM save due to outdated format.", 0);
deleteValue("SRAM_" + gameboy.name);
}
setValue("B64_SRAM_" + gameboy.name, arrayToBase64(sram));
}
else {
cout("SRAM could not be saved because it was empty.", 1);
}
}
catch (error) {
cout("Could not save the current emulation state(\"" + error.message + "\").", 2);
}
}
else {
cout("Cannot save a game that does not have battery backed SRAM specified.", 1);
}
saveRTC();
}
else {
cout("GameBoy core cannot be saved while it has not been initialized.", 1);
}
}
function saveRTC() { //Execute this when SRAM is being saved as well.
if (GameBoyEmulatorInitialized()) {
if (gameboy.cTIMER) {
try {
cout("Saving the RTC...", 0);
setValue("RTC_" + gameboy.name, gameboy.saveRTCState());
}
catch (error) {
cout("Could not save the RTC of the current emulation state(\"" + error.message + "\").", 2);
}
}
}
else {
cout("GameBoy core cannot be saved while it has not been initialized.", 1);
}
}
function autoSave() {
if (GameBoyEmulatorInitialized()) {
cout("Automatically saving the SRAM.", 0);
saveSRAM();
saveRTC();
}
}
function openSRAM(filename) {
try {
if (findValue("B64_SRAM_" + filename) != null) {
cout("Found a previous SRAM state (Will attempt to load).", 0);
return base64ToArray(findValue("B64_SRAM_" + filename));
}
else if (findValue("SRAM_" + filename) != null) {
cout("Found a previous SRAM state (Will attempt to load).", 0);
return findValue("SRAM_" + filename);
}
else {
cout("Could not find any previous SRAM copy for the current ROM.", 0);
}
}
catch (error) {
cout("Could not open the SRAM of the saved emulation state.", 2);
}
return [];
}
function openRTC(filename) {
try {
if (findValue("RTC_" + filename) != null) {
cout("Found a previous RTC state (Will attempt to load).", 0);
return findValue("RTC_" + filename);
}
else {
cout("Could not find any previous RTC copy for the current ROM.", 0);
}
}
catch (error) {
cout("Could not open the RTC data of the saved emulation state.", 2);
}
return [];
}
function saveState(filename) {
if (GameBoyEmulatorInitialized()) {
try {
setValue(filename, gameboy.saveState());
cout("Saved the current state as: " + filename, 0);
}
catch (error) {
cout("Could not save the current emulation state(\"" + error.message + "\").", 2);
}
}
else {
cout("GameBoy core cannot be saved while it has not been initialized.", 1);
}
}
function openState(filename, canvas) {
try {
if (findValue(filename) != null) {
try {
clearLastEmulation();
cout("Attempting to run a saved emulation state.", 0);
gameboy = new GameBoyCore(canvas, "");
gameboy.savedStateFileName = filename;
gameboy.returnFromState(findValue(filename));
run();
}
catch (error) {
alert(error.message + " file: " + error.fileName + " line: " + error.lineNumber);
}
}
else {
cout("Could not find the save state " + filename + "\".", 2);
}
}
catch (error) {
cout("Could not open the saved emulation state.", 2);
}
}
function import_save(blobData) {
blobData = decodeBlob(blobData);
if (blobData && blobData.blobs) {
if (blobData.blobs.length > 0) {
for (var index = 0; index < blobData.blobs.length; ++index) {
cout("Importing blob \"" + blobData.blobs[index].blobID + "\"", 0);
if (blobData.blobs[index].blobContent) {
if (blobData.blobs[index].blobID.substring(0, 5) == "SRAM_") {
setValue("B64_" + blobData.blobs[index].blobID, base64(blobData.blobs[index].blobContent));
}
else {
setValue(blobData.blobs[index].blobID, JSON.parse(blobData.blobs[index].blobContent));
}
}
else if (blobData.blobs[index].blobID) {
cout("Save file imported had blob \"" + blobData.blobs[index].blobID + "\" with no blob data interpretable.", 2);
}
else {
cout("Blob chunk information missing completely.", 2);
}
}
}
else {
cout("Could not decode the imported file.", 2);
}
}
else {
cout("Could not decode the imported file.", 2);
}
}
function generateBlob(keyName, encodedData) {
//Append the file format prefix:
var saveString = "EMULATOR_DATA";
var consoleID = "GameBoy";
//Figure out the length:
var totalLength = (saveString.length + 4 + (1 + consoleID.length)) + ((1 + keyName.length) + (4 + encodedData.length));
//Append the total length in bytes:
saveString += to_little_endian_dword(totalLength);
//Append the console ID text's length:
saveString += to_byte(consoleID.length);
//Append the console ID text:
saveString += consoleID;
//Append the blob ID:
saveString += to_byte(keyName.length);
saveString += keyName;
//Now append the save data:
saveString += to_little_endian_dword(encodedData.length);
saveString += encodedData;
return saveString;
}
function generateMultiBlob(blobPairs) {
var consoleID = "GameBoy";
//Figure out the initial length:
var totalLength = 13 + 4 + 1 + consoleID.length;
//Append the console ID text's length:
var saveString = to_byte(consoleID.length);
//Append the console ID text:
saveString += consoleID;
var keyName = "";
var encodedData = "";
//Now append all the blobs:
for (var index = 0; index < blobPairs.length; ++index) {
keyName = blobPairs[index][0];
encodedData = blobPairs[index][1];
//Append the blob ID:
saveString += to_byte(keyName.length);
saveString += keyName;
//Now append the save data:
saveString += to_little_endian_dword(encodedData.length);
saveString += encodedData;
//Update the total length:
totalLength += 1 + keyName.length + 4 + encodedData.length;
}
//Now add the prefix:
saveString = "EMULATOR_DATA" + to_little_endian_dword(totalLength) + saveString;
return saveString;
}
function decodeBlob(blobData) {
/*Format is as follows:
- 13 byte string "EMULATOR_DATA"
- 4 byte total size (including these 4 bytes).
- 1 byte Console type ID length
- Console type ID text of 8 bit size
blobs {
- 1 byte blob ID length
- blob ID text (Used to say what the data is (SRAM/freeze state/etc...))
- 4 byte blob length
- blob length of 32 bit size
}
*/
var length = blobData.length;
var blobProperties = {};
blobProperties.consoleID = null;
var blobsCount = -1;
blobProperties.blobs = [];
if (length > 17) {
if (blobData.substring(0, 13) == "EMULATOR_DATA") {
var length = Math.min(((blobData.charCodeAt(16) & 0xFF) << 24) | ((blobData.charCodeAt(15) & 0xFF) << 16) | ((blobData.charCodeAt(14) & 0xFF) << 8) | (blobData.charCodeAt(13) & 0xFF), length);
var consoleIDLength = blobData.charCodeAt(17) & 0xFF;
if (length > 17 + consoleIDLength) {
blobProperties.consoleID = blobData.substring(18, 18 + consoleIDLength);
var blobIDLength = 0;
var blobLength = 0;
for (var index = 18 + consoleIDLength; index < length;) {
blobIDLength = blobData.charCodeAt(index++) & 0xFF;
if (index + blobIDLength < length) {
blobProperties.blobs[++blobsCount] = {};
blobProperties.blobs[blobsCount].blobID = blobData.substring(index, index + blobIDLength);
index += blobIDLength;
if (index + 4 < length) {
blobLength = ((blobData.charCodeAt(index + 3) & 0xFF) << 24) | ((blobData.charCodeAt(index + 2) & 0xFF) << 16) | ((blobData.charCodeAt(index + 1) & 0xFF) << 8) | (blobData.charCodeAt(index) & 0xFF);
index += 4;
if (index + blobLength <= length) {
blobProperties.blobs[blobsCount].blobContent = blobData.substring(index, index + blobLength);
index += blobLength;
}
else {
cout("Blob length check failed, blob determined to be incomplete.", 2);
break;
}
}
else {
cout("Blob was incomplete, bailing out.", 2);
break;
}
}
else {
cout("Blob was incomplete, bailing out.", 2);
break;
}
}
}
}
}
return blobProperties;
}
function matchKey(key) { //Maps a keyboard key to a gameboy key.
//Order: Right, Left, Up, Down, A, B, Select, Start
var keymap = ["right", "left", "up", "down", "a", "b", "select", "start"]; //Keyboard button map.
for (var index = 0; index < keymap.length; index++) {
if (keymap[index] == key) {
return index;
}
}
return -1;
}
function GameBoyEmulatorInitialized() {
return (typeof gameboy == "object" && gameboy != null);
}
function GameBoyEmulatorPlaying() {
return ((gameboy.stopEmulator & 2) == 0);
}
function GameBoyKeyDown(key) {
if (GameBoyEmulatorInitialized() && GameBoyEmulatorPlaying()) {
GameBoyJoyPadEvent(matchKey(key), true);
}
}
function GameBoyJoyPadEvent(keycode, down) {
if (GameBoyEmulatorInitialized() && GameBoyEmulatorPlaying()) {
if (keycode >= 0 && keycode < 8) {
gameboy.JoyPadEvent(keycode, down);
}
}
}
function GameBoyKeyUp(key) {
if (GameBoyEmulatorInitialized() && GameBoyEmulatorPlaying()) {
GameBoyJoyPadEvent(matchKey(key), false);
}
}
function GameBoyGyroSignalHandler(e) {
if (GameBoyEmulatorInitialized() && GameBoyEmulatorPlaying()) {
if (e.gamma || e.beta) {
gameboy.GyroEvent(e.gamma * Math.PI / 180, e.beta * Math.PI / 180);
}
else {
gameboy.GyroEvent(e.x, e.y);
}
try {
e.preventDefault();
}
catch (error) { }
}
}
//The emulator will call this to sort out the canvas properties for (re)initialization.
function initNewCanvas() {
if (GameBoyEmulatorInitialized()) {
gameboy.canvas.width = 160;
gameboy.canvas.height = 144;
}
}
//Call this when resizing the canvas:
function initNewCanvasSize() {
if (GameBoyEmulatorInitialized()) {
if (!settings[12]) {
if (gameboy.onscreenWidth != 160 || gameboy.onscreenHeight != 144) {
gameboy.initLCD();
}
}
else {
if (gameboy.onscreenWidth != gameboy.canvas.clientWidth || gameboy.onscreenHeight != gameboy.canvas.clientHeight) {
gameboy.initLCD();
}
}
}
}

Binary file not shown.

View File

@@ -1,537 +0,0 @@
//2010-2013 Grant Galitz - XAudioJS realtime audio output compatibility library:
var XAudioJSscriptsHandle = document.getElementsByTagName("script");
var XAudioJSsourceHandle = XAudioJSscriptsHandle[XAudioJSscriptsHandle.length-1].src;
function XAudioServer(channels, sampleRate, minBufferSize, maxBufferSize, underRunCallback, volume, failureCallback) {
XAudioJSChannelsAllocated = Math.max(channels, 1);
this.XAudioJSSampleRate = Math.abs(sampleRate);
XAudioJSMinBufferSize = (minBufferSize >= (XAudioJSSamplesPerCallback * XAudioJSChannelsAllocated) && minBufferSize < maxBufferSize) ? (minBufferSize & (-XAudioJSChannelsAllocated)) : (XAudioJSSamplesPerCallback * XAudioJSChannelsAllocated);
XAudioJSMaxBufferSize = (Math.floor(maxBufferSize) > XAudioJSMinBufferSize + XAudioJSChannelsAllocated) ? (maxBufferSize & (-XAudioJSChannelsAllocated)) : (XAudioJSMinBufferSize * XAudioJSChannelsAllocated);
this.underRunCallback = (typeof underRunCallback == "function") ? underRunCallback : function () {};
XAudioJSVolume = (volume >= 0 && volume <= 1) ? volume : 1;
this.failureCallback = (typeof failureCallback == "function") ? failureCallback : function () { throw(new Error("XAudioJS has encountered a fatal error.")); };
this.initializeAudio();
}
XAudioServer.prototype.MOZWriteAudioNoCallback = function (buffer) {
//Resample before passing to the moz audio api:
var bufferLength = buffer.length;
for (var bufferIndex = 0; bufferIndex < bufferLength;) {
var sliceLength = Math.min(bufferLength - bufferIndex, XAudioJSMaxBufferSize);
for (var sliceIndex = 0; sliceIndex < sliceLength; ++sliceIndex) {
XAudioJSAudioContextSampleBuffer[sliceIndex] = buffer[bufferIndex++];
}
var resampleLength = XAudioJSResampleControl.resampler(XAudioJSGetArraySlice(XAudioJSAudioContextSampleBuffer, sliceIndex));
if (resampleLength > 0) {
var resampledResult = XAudioJSResampleControl.outputBuffer;
var resampledBuffer = XAudioJSGetArraySlice(resampledResult, resampleLength);
this.samplesAlreadyWritten += this.audioHandleMoz.mozWriteAudio(resampledBuffer);
}
}
}
XAudioServer.prototype.callbackBasedWriteAudioNoCallback = function (buffer) {
//Callback-centered audio APIs:
var length = buffer.length;
for (var bufferCounter = 0; bufferCounter < length && XAudioJSAudioBufferSize < XAudioJSMaxBufferSize;) {
XAudioJSAudioContextSampleBuffer[XAudioJSAudioBufferSize++] = buffer[bufferCounter++];
}
}
/*Pass your samples into here!
Pack your samples as a one-dimenional array
With the channel samples packed uniformly.
examples:
mono - [left, left, left, left]
stereo - [left, right, left, right, left, right, left, right]
*/
XAudioServer.prototype.writeAudio = function (buffer) {
switch (this.audioType) {
case 0:
this.MOZWriteAudioNoCallback(buffer);
this.MOZExecuteCallback();
break;
case 2:
this.checkFlashInit();
case 1:
case 3:
this.callbackBasedWriteAudioNoCallback(buffer);
this.callbackBasedExecuteCallback();
break;
default:
this.failureCallback();
}
}
/*Pass your samples into here if you don't want automatic callback calling:
Pack your samples as a one-dimenional array
With the channel samples 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) {
switch (this.audioType) {
case 0:
this.MOZWriteAudioNoCallback(buffer);
break;
case 2:
this.checkFlashInit();
case 1:
case 3:
this.callbackBasedWriteAudioNoCallback(buffer);
break;
default:
this.failureCallback();
}
}
//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 null is returned, then that means metric could not be done.
XAudioServer.prototype.remainingBuffer = function () {
switch (this.audioType) {
case 0:
return Math.floor((this.samplesAlreadyWritten - this.audioHandleMoz.mozCurrentSampleOffset()) * XAudioJSResampleControl.ratioWeight / XAudioJSChannelsAllocated) * XAudioJSChannelsAllocated;
case 2:
this.checkFlashInit();
case 1:
case 3:
return (Math.floor((XAudioJSResampledSamplesLeft() * XAudioJSResampleControl.ratioWeight) / XAudioJSChannelsAllocated) * XAudioJSChannelsAllocated) + XAudioJSAudioBufferSize;
default:
this.failureCallback();
return null;
}
}
XAudioServer.prototype.MOZExecuteCallback = function () {
//mozAudio:
var samplesRequested = XAudioJSMinBufferSize - this.remainingBuffer();
if (samplesRequested > 0) {
this.MOZWriteAudioNoCallback(this.underRunCallback(samplesRequested));
}
}
XAudioServer.prototype.callbackBasedExecuteCallback = function () {
//WebKit /Flash Audio:
var samplesRequested = XAudioJSMinBufferSize - 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 () {
switch (this.audioType) {
case 0:
this.MOZExecuteCallback();
break;
case 2:
this.checkFlashInit();
case 1:
case 3:
this.callbackBasedExecuteCallback();
break;
default:
this.failureCallback();
}
}
//DO NOT CALL THIS, the lib calls this internally!
XAudioServer.prototype.initializeAudio = function () {
try {
this.initializeMozAudio();
}
catch (error) {
try {
this.initializeWebAudio();
}
catch (error) {
try {
this.initializeMediaStream();
}
catch (error) {
try {
this.initializeFlashAudio();
}
catch (error) {
this.audioType = -1;
this.failureCallback();
}
}
}
}
}
XAudioServer.prototype.initializeMediaStream = function () {
this.audioHandleMediaStream = new Audio();
this.resetCallbackAPIAudioBuffer(XAudioJSMediaStreamSampleRate);
if (XAudioJSMediaStreamWorker) {
//WebWorker is not GC'd, so manually collect it:
XAudioJSMediaStreamWorker.terminate();
}
XAudioJSMediaStreamWorker = new Worker(XAudioJSsourceHandle.substring(0, XAudioJSsourceHandle.length - 3) + "MediaStreamWorker.js");
this.audioHandleMediaStreamProcessing = new ProcessedMediaStream(XAudioJSMediaStreamWorker, XAudioJSMediaStreamSampleRate, XAudioJSChannelsAllocated);
this.audioHandleMediaStream.src = this.audioHandleMediaStreamProcessing;
this.audioHandleMediaStream.volume = XAudioJSVolume;
XAudioJSMediaStreamWorker.onmessage = XAudioJSMediaStreamPushAudio;
XAudioJSMediaStreamWorker.postMessage([1, XAudioJSResampleBufferSize, XAudioJSChannelsAllocated]);
this.audioHandleMediaStream.play();
this.audioType = 3;
}
XAudioServer.prototype.initializeMozAudio = function () {
this.audioHandleMoz = new Audio();
this.audioHandleMoz.mozSetup(XAudioJSChannelsAllocated, XAudioJSMozAudioSampleRate);
this.audioHandleMoz.volume = XAudioJSVolume;
this.samplesAlreadyWritten = 0;
this.audioType = 0;
//if (navigator.platform != "MacIntel" && navigator.platform != "MacPPC") {
//Add some additional buffering space to workaround a moz audio api issue:
var bufferAmount = (this.XAudioJSSampleRate * XAudioJSChannelsAllocated / 10) | 0;
bufferAmount -= bufferAmount % XAudioJSChannelsAllocated;
this.samplesAlreadyWritten -= bufferAmount;
//}
this.initializeResampler(XAudioJSMozAudioSampleRate);
}
XAudioServer.prototype.initializeWebAudio = function () {
if (!XAudioJSWebAudioLaunchedContext) {
try {
XAudioJSWebAudioContextHandle = new AudioContext(); //Create a system audio context.
}
catch (error) {
XAudioJSWebAudioContextHandle = new webkitAudioContext(); //Create a system audio context.
}
XAudioJSWebAudioLaunchedContext = true;
}
if (XAudioJSWebAudioAudioNode) {
XAudioJSWebAudioAudioNode.disconnect();
XAudioJSWebAudioAudioNode.onaudioprocess = null;
XAudioJSWebAudioAudioNode = null;
}
//Android chrome exception has stuttery audio, firefox android is fine with 2048
if (navigator.userAgent.includes("Android") && navigator.userAgent.includes("Chrome")) {
XAudioJSSamplesPerCallback = 4096;
}
try {
XAudioJSWebAudioAudioNode = XAudioJSWebAudioContextHandle.createScriptProcessor(XAudioJSSamplesPerCallback, 0, XAudioJSChannelsAllocated); //Create the js event node.
}
catch (error) {
XAudioJSWebAudioAudioNode = XAudioJSWebAudioContextHandle.createJavaScriptNode(XAudioJSSamplesPerCallback, 0, XAudioJSChannelsAllocated); //Create the js event node.
}
XAudioJSWebAudioAudioNode.onaudioprocess = XAudioJSWebAudioEvent;
var gainNode = XAudioJSWebAudioContextHandle.createGain();
XAudioJSWebAudioAudioNode.connect(gainNode); //Connect the audio processing event to a handling function so we can manipulate output
//Connect the audio processing event to a handling function so we can manipulate output
gainNode.connect(XAudioJSWebAudioContextHandle.destination);
// Ramp up gain to avoid pop when initialised, these can be relaxed/removed with the fixed dc offset in GameBoyCore.js no longer popping
gainNode.gain.value = 0.2;
gainNode.gain.exponentialRampToValueAtTime(
1,
XAudioJSWebAudioContextHandle.currentTime + 0.5
);
document.addEventListener("visibilitychange", function(event) {
if (document.hidden) {
// Ramp down gain to avoid pop when switching tab away
gainNode.gain.exponentialRampToValueAtTime(
0.0001,
XAudioJSWebAudioContextHandle.currentTime + 0.5
);
} else {
// Ramp up gain to avoid pop when switching back to tab
gainNode.gain.exponentialRampToValueAtTime(
1,
XAudioJSWebAudioContextHandle.currentTime + 0.5
);
}
});
this.resetCallbackAPIAudioBuffer(XAudioJSWebAudioContextHandle.sampleRate);
this.audioType = 1;
/*
Firefox has a bug in its web audio implementation...
The node may randomly stop playing on Mac OS X for no
good reason. Keep a watchdog timer to restart the failed
node if it glitches. Google Chrome never had this issue.
*/
XAudioJSWebAudioWatchDogLast = (new Date()).getTime();
if (navigator.userAgent.indexOf('Gecko/') > -1) {
if (XAudioJSWebAudioWatchDogTimer) {
clearInterval(XAudioJSWebAudioWatchDogTimer);
}
var parentObj = this;
XAudioJSWebAudioWatchDogTimer = setInterval(function () {
var timeDiff = (new Date()).getTime() - XAudioJSWebAudioWatchDogLast;
if (timeDiff > 500) {
parentObj.initializeWebAudio();
}
}, 500);
}
}
XAudioServer.prototype.initializeFlashAudio = function () {
var existingFlashload = document.getElementById("XAudioJS");
this.flashInitialized = false;
this.resetCallbackAPIAudioBuffer(44100);
switch (XAudioJSChannelsAllocated) {
case 1:
XAudioJSFlashTransportEncoder = XAudioJSGenerateFlashMonoString;
break;
case 2:
XAudioJSFlashTransportEncoder = XAudioJSGenerateFlashStereoString;
break;
default:
XAudioJSFlashTransportEncoder = XAudioJSGenerateFlashSurroundString;
}
if (existingFlashload == null) {
this.audioHandleFlash = 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(
XAudioJSsourceHandle.substring(0, XAudioJSsourceHandle.length - 9) + "JS.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;
thisObj.checkFlashInit();
}
else {
thisObj.failureCallback();
thisObj.audioType = -1;
}
}
);
}
else {
this.audioHandleFlash = existingFlashload;
this.checkFlashInit();
}
this.audioType = 2;
}
XAudioServer.prototype.changeVolume = function (newVolume) {
if (newVolume >= 0 && newVolume <= 1) {
XAudioJSVolume = newVolume;
switch (this.audioType) {
case 0:
this.audioHandleMoz.volume = XAudioJSVolume;
case 1:
break;
case 2:
if (this.flashInitialized) {
this.audioHandleFlash.changeVolume(XAudioJSVolume);
}
else {
this.checkFlashInit();
}
break;
case 3:
this.audioHandleMediaStream.volume = XAudioJSVolume;
break;
default:
this.failureCallback();
}
}
}
//Checks to see if the NPAPI Adobe Flash bridge is ready yet:
XAudioServer.prototype.checkFlashInit = function () {
if (!this.flashInitialized) {
try {
if (this.audioHandleFlash && this.audioHandleFlash.initialize) {
this.flashInitialized = true;
this.audioHandleFlash.initialize(XAudioJSChannelsAllocated, XAudioJSVolume);
}
}
catch (error) {
this.flashInitialized = false;
}
}
}
//Set up the resampling:
XAudioServer.prototype.resetCallbackAPIAudioBuffer = function (APISampleRate) {
XAudioJSAudioBufferSize = XAudioJSResampleBufferEnd = XAudioJSResampleBufferStart = 0;
this.initializeResampler(APISampleRate);
XAudioJSResampledBuffer = this.getFloat32(XAudioJSResampleBufferSize);
}
XAudioServer.prototype.initializeResampler = function (sampleRate) {
XAudioJSAudioContextSampleBuffer = this.getFloat32(XAudioJSMaxBufferSize);
XAudioJSResampleBufferSize = Math.max(XAudioJSMaxBufferSize * Math.ceil(sampleRate / this.XAudioJSSampleRate) + XAudioJSChannelsAllocated, XAudioJSSamplesPerCallback * XAudioJSChannelsAllocated);
XAudioJSResampleControl = new Resampler(this.XAudioJSSampleRate, sampleRate, XAudioJSChannelsAllocated, XAudioJSResampleBufferSize, true);
}
XAudioServer.prototype.getFloat32 = function (size) {
try {
return new Float32Array(size);
}
catch (error) {
return [];
}
}
function XAudioJSFlashAudioEvent() { //The callback that flash calls...
XAudioJSResampleRefill();
return XAudioJSFlashTransportEncoder();
}
function XAudioJSGenerateFlashSurroundString() { //Convert the arrays to one long string for speed.
var XAudioJSTotalSamples = XAudioJSSamplesPerCallback << 1;
if (XAudioJSBinaryString.length > XAudioJSTotalSamples) {
XAudioJSBinaryString = [];
}
XAudioJSTotalSamples = 0;
for (var index = 0; index < XAudioJSSamplesPerCallback && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd; ++index) {
//Sanitize the buffer:
XAudioJSBinaryString[XAudioJSTotalSamples++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000);
XAudioJSBinaryString[XAudioJSTotalSamples++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000);
XAudioJSResampleBufferStart += XAudioJSChannelsAllocated - 2;
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferStart = 0;
}
}
return XAudioJSBinaryString.join("");
}
function XAudioJSGenerateFlashStereoString() { //Convert the arrays to one long string for speed.
var XAudioJSTotalSamples = XAudioJSSamplesPerCallback << 1;
if (XAudioJSBinaryString.length > XAudioJSTotalSamples) {
XAudioJSBinaryString = [];
}
for (var index = 0; index < XAudioJSTotalSamples && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd;) {
//Sanitize the buffer:
XAudioJSBinaryString[index++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000);
XAudioJSBinaryString[index++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000);
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferStart = 0;
}
}
return XAudioJSBinaryString.join("");
}
function XAudioJSGenerateFlashMonoString() { //Convert the array to one long string for speed.
if (XAudioJSBinaryString.length > XAudioJSSamplesPerCallback) {
XAudioJSBinaryString = [];
}
for (var index = 0; index < XAudioJSSamplesPerCallback && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd;) {
//Sanitize the buffer:
XAudioJSBinaryString[index++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000);
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferStart = 0;
}
}
return XAudioJSBinaryString.join("");
}
//Some Required Globals:
var XAudioJSWebAudioContextHandle = null;
var XAudioJSWebAudioAudioNode = null;
var XAudioJSWebAudioWatchDogTimer = null;
var XAudioJSWebAudioWatchDogLast = false;
var XAudioJSWebAudioLaunchedContext = false;
var XAudioJSAudioContextSampleBuffer = [];
var XAudioJSResampledBuffer = [];
var XAudioJSMinBufferSize = 15000;
var XAudioJSMaxBufferSize = 25000;
var XAudioJSChannelsAllocated = 1;
var XAudioJSVolume = 1;
var XAudioJSResampleControl = null;
var XAudioJSAudioBufferSize = 0;
var XAudioJSResampleBufferStart = 0;
var XAudioJSResampleBufferEnd = 0;
var XAudioJSResampleBufferSize = 0;
var XAudioJSMediaStreamWorker = null;
var XAudioJSMediaStreamBuffer = [];
var XAudioJSMediaStreamSampleRate = 44100;
var XAudioJSMozAudioSampleRate = 44100;
var XAudioJSSamplesPerCallback = 2048; //Has to be between 2048 and 4096 (If over, then samples are ignored, if under then silence is added, 4096 fixes chrome android).
var XAudioJSFlashTransportEncoder = null;
var XAudioJSMediaStreamLengthAliasCounter = 0;
var XAudioJSBinaryString = [];
function XAudioJSWebAudioEvent(event) { //Web Audio API callback...
if (XAudioJSWebAudioWatchDogTimer) {
XAudioJSWebAudioWatchDogLast = (new Date()).getTime();
}
//Find all output channels:
for (var bufferCount = 0, buffers = []; bufferCount < XAudioJSChannelsAllocated; ++bufferCount) {
buffers[bufferCount] = event.outputBuffer.getChannelData(bufferCount);
}
//Make sure we have resampled samples ready:
XAudioJSResampleRefill();
//Copy samples from XAudioJS to the Web Audio API:
for (var index = 0; index < XAudioJSSamplesPerCallback && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd; ++index) {
for (bufferCount = 0; bufferCount < XAudioJSChannelsAllocated; ++bufferCount) {
buffers[bufferCount][index] = XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] * XAudioJSVolume;
}
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferStart = 0;
}
}
//Pad with silence if we're underrunning:
while (index < XAudioJSSamplesPerCallback) {
for (bufferCount = 0; bufferCount < XAudioJSChannelsAllocated; ++bufferCount) {
buffers[bufferCount][index] = 0;
}
++index;
}
}
//MediaStream API buffer push
function XAudioJSMediaStreamPushAudio(event) {
var index = 0;
var audioLengthRequested = event.data;
var samplesPerCallbackAll = XAudioJSSamplesPerCallback * XAudioJSChannelsAllocated;
var XAudioJSMediaStreamLengthAlias = audioLengthRequested % XAudioJSSamplesPerCallback;
audioLengthRequested = audioLengthRequested - (XAudioJSMediaStreamLengthAliasCounter - (XAudioJSMediaStreamLengthAliasCounter % XAudioJSSamplesPerCallback)) - XAudioJSMediaStreamLengthAlias + XAudioJSSamplesPerCallback;
XAudioJSMediaStreamLengthAliasCounter -= XAudioJSMediaStreamLengthAliasCounter - (XAudioJSMediaStreamLengthAliasCounter % XAudioJSSamplesPerCallback);
XAudioJSMediaStreamLengthAliasCounter += XAudioJSSamplesPerCallback - XAudioJSMediaStreamLengthAlias;
if (XAudioJSMediaStreamBuffer.length != samplesPerCallbackAll) {
XAudioJSMediaStreamBuffer = new Float32Array(samplesPerCallbackAll);
}
XAudioJSResampleRefill();
while (index < audioLengthRequested) {
var index2 = 0;
while (index2 < samplesPerCallbackAll && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd) {
XAudioJSMediaStreamBuffer[index2++] = XAudioJSResampledBuffer[XAudioJSResampleBufferStart++];
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferStart = 0;
}
}
XAudioJSMediaStreamWorker.postMessage([0, XAudioJSMediaStreamBuffer]);
index += XAudioJSSamplesPerCallback;
}
}
function XAudioJSResampleRefill() {
if (XAudioJSAudioBufferSize > 0) {
//Resample a chunk of audio:
var resampleLength = XAudioJSResampleControl.resampler(XAudioJSGetBufferSamples());
var resampledResult = XAudioJSResampleControl.outputBuffer;
for (var index2 = 0; index2 < resampleLength;) {
XAudioJSResampledBuffer[XAudioJSResampleBufferEnd++] = resampledResult[index2++];
if (XAudioJSResampleBufferEnd == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferEnd = 0;
}
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferEnd) {
XAudioJSResampleBufferStart += XAudioJSChannelsAllocated;
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferStart = 0;
}
}
}
XAudioJSAudioBufferSize = 0;
}
}
function XAudioJSResampledSamplesLeft() {
return ((XAudioJSResampleBufferStart <= XAudioJSResampleBufferEnd) ? 0 : XAudioJSResampleBufferSize) + XAudioJSResampleBufferEnd - XAudioJSResampleBufferStart;
}
function XAudioJSGetBufferSamples() {
return XAudioJSGetArraySlice(XAudioJSAudioContextSampleBuffer, XAudioJSAudioBufferSize);
}
function XAudioJSGetArraySlice(buffer, lengthOf) {
//Typed array and normal array buffer section referencing:
try {
return buffer.subarray(0, lengthOf);
}
catch (error) {
try {
//Regular array pass:
buffer.length = lengthOf;
return buffer;
}
catch (error) {
//Nightly Firefox 4 used to have the subarray function named as slice:
return buffer.slice(0, lengthOf);
}
}
}

View File

@@ -1,68 +0,0 @@
//This file is part of the XAudioJS library.
var XAudioJSResampledBuffer = [];
var XAudioJSOutputBuffer = [];
var XAudioJSResampleBufferStart = 0;
var XAudioJSResampleBufferEnd = 0;
var XAudioJSResampleBufferSize = 0;
var XAudioJSChannelsAllocated = 1;
//Message Receiver:
self.onmessage = function (event) {
var data = event.data;
switch (data[0]) {
case 0:
//Add new audio samples to our ring buffer:
var resampledResult = data[1];
var length = resampledResult.length;
for (var i = 0; i < length; ++i) {
XAudioJSResampledBuffer[XAudioJSResampleBufferEnd++] = resampledResult[i];
if (XAudioJSResampleBufferEnd == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferEnd = 0;
}
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferEnd) {
XAudioJSResampleBufferStart += XAudioJSChannelsAllocated;
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferStart = 0;
}
}
}
break;
case 1:
//Initialize:
XAudioJSResampleBufferSize = data[1];
XAudioJSChannelsAllocated = data[2];
XAudioJSResampledBuffer = new Float32Array(XAudioJSResampleBufferSize);
}
}
//MediaStream Polyfill Event:
self.onprocessmedia = function (event) {
//Get some buffer length computations:
var apiBufferLength = event.audioLength;
var apiBufferLengthAll = apiBufferLength * event.audioChannels;
if (apiBufferLengthAll > XAudioJSOutputBuffer.length) {
XAudioJSOutputBuffer = new Float32Array(apiBufferLengthAll);
}
//De-interleave the buffered audio while looping through our ring buffer:
var sampleFramesCount = Math.min(apiBufferLength, XAudioJSResampledSamplesLeft() / XAudioJSChannelsAllocated);
for (var sampleFramePosition = 0, channelOffset = 0; sampleFramePosition < sampleFramesCount; ++sampleFramePosition) {
for (channelOffset = sampleFramePosition; channelOffset < apiBufferLengthAll; channelOffset += apiBufferLength) {
XAudioJSOutputBuffer[channelOffset] = XAudioJSResampledBuffer[XAudioJSResampleBufferStart++];
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferStart = 0;
}
}
}
//Add some zero fill if we underran the required buffer fill amount:
while (sampleFramePosition < apiBufferLength) {
for (channelOffset = sampleFramePosition++; channelOffset < apiBufferLengthAll; channelOffset += apiBufferLength) {
XAudioJSOutputBuffer[channelOffset] = 0;
}
}
//Write some buffered audio:
event.writeAudio(XAudioJSOutputBuffer.subarray(0, apiBufferLengthAll));
//Request a buffer from the main thread:
self.postMessage(event.audioLength);
}
//Accessory function used to determine remaining samples in the ring buffer:
function XAudioJSResampledSamplesLeft() {
return ((XAudioJSResampleBufferStart <= XAudioJSResampleBufferEnd) ? 0 : XAudioJSResampleBufferSize) + XAudioJSResampleBufferEnd - XAudioJSResampleBufferStart;
}

View File

@@ -1,96 +0,0 @@
"use strict";
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 {
var base64 = window.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 {
var decode64 = window.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) + to_little_endian_word(str >> 16);
}
function to_little_endian_word(str) {
return to_byte(str) + to_byte(str >> 8);
}
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;
}

View File

@@ -1,399 +0,0 @@
const JS_KEY_ALT = 18;
const JS_KEY_CTRL = 17;
const DEADZONE = 0.1;
var defaultKeys = {
up: ["ArrowUp", "w", "W"],
down: ["ArrowDown", "s", "S"],
left: ["ArrowLeft", "a", "A"],
right: ["ArrowRight", "d", "D"],
a: ["Alt", "z", "j", "Z", "J"],
b: ["Control", "k", "x", "K", "X"],
start: ["Enter"],
select: ["Shift"]
};
// Build keybindings object using
// value from customControls if defined or defaultKeys if not
var keyBindings = {};
for (var key in defaultKeys) {
var keys = customControls[key] ? customControls[key] : defaultKeys[key];
for (var i = 0; i < keys.length; i++) {
keyBindings[keys[i]] = key;
keyBindings[String(keys[i]).toLowerCase()] = key;
keyBindings[String(keys[i]).toUpperCase()] = key;
}
}
var isTouchEnabled = "ontouchstart" in document.documentElement;
var controller = document.getElementById("controller");
var btnA = document.getElementById("controller_a");
var btnB = document.getElementById("controller_b");
var btnStart = document.getElementById("controller_start");
var btnSelect = document.getElementById("controller_select");
var dpad = document.getElementById("controller_dpad");
// HTML Gamepad API support
// Poll for gamepad input about ~4 times per gameboy frame (~240 times second)
const GAMEPAD_POLLING_INTERVAL = 1000 / 60 / 4;
const GAMEPAD_KEYMAP_STANDARD_STR = "standard"
// When gamepad.mapping reports "standard"
const GAMEPAD_KEYMAP_STANDARD = [
{gb_key: "b", gp_button: 0, type: "button"},
{gb_key: "a", gp_button: 1, type: "button"},
{gb_key: "select", gp_button: 8, type: "button"},
{gb_key: "start", gp_button: 9, type: "button"},
{gb_key: "up", gp_button: 12, type: "button"},
{gb_key: "down", gp_button: 13, type: "button"},
{gb_key: "left", gp_button: 14, type: "button"},
{gb_key: "right", gp_button: 15, type: "button"}
];
const GAMEPAD_KEYMAP_DEFAULT = [
{gb_key: "a", gp_button: 0, type: "button"},
{gb_key: "b", gp_button: 1, type: "button"},
{gb_key: "select", gp_button: 2, type: "button"},
{gb_key: "start", gp_button: 3, type: "button"},
{gb_key: "up", gp_button: 2, type: "axis"},
{gb_key: "down", gp_button: 3, type: "axis"},
{gb_key: "left", gp_button: 0, type: "axis"},
{gb_key: "right", gp_button: 1, type: "axis"}
];
// gamepad related vars
var gp = {
apiID: undefined,
timerID: undefined,
keybinds: undefined,
axes: {
last: undefined,
cur: [],
changed: [] },
buttons: {
last: undefined,
cur: [],
changed: [] }
};
function bindButton(el, code) {
el.addEventListener("touchstart", function(e) {
e.preventDefault();
e.stopPropagation();
e.currentTarget.className = e.currentTarget.className + " btnPressed";
GameBoyKeyDown(code);
});
el.addEventListener("touchend", function(e) {
e.preventDefault();
e.stopPropagation();
initSound();
e.currentTarget.className = e.currentTarget.className.replace(
/ btnPressed/,
""
);
GameBoyKeyUp(code);
});
}
function bindDpad(el) {
el.addEventListener("touchstart", function(e) {
e.preventDefault();
e.stopPropagation();
var rect = e.currentTarget.getBoundingClientRect();
var x = (2 * (e.targetTouches[0].clientX - rect.left)) / rect.width - 1;
var y = (2 * (e.targetTouches[0].clientY - rect.top)) / rect.height - 1;
move(x, y);
});
el.addEventListener("touchmove", function(e) {
e.preventDefault();
e.stopPropagation();
var rect = e.currentTarget.getBoundingClientRect();
var x = (2 * (e.targetTouches[0].clientX - rect.left)) / rect.width - 1;
var y = (2 * (e.targetTouches[0].clientY - rect.top)) / rect.height - 1;
move(x, y);
});
function move(x, y) {
if (x < -DEADZONE || x > DEADZONE) {
if (y > x && y < -x) {
GameBoyKeyUp("right");
GameBoyKeyDown("left");
} else if (y > -x && y < x) {
GameBoyKeyUp("left");
GameBoyKeyDown("right");
}
if (y > -DEADZONE && y < DEADZONE) {
GameBoyKeyUp("up");
GameBoyKeyUp("down");
}
}
if (y < -DEADZONE || y > DEADZONE) {
if (x > y && x < -y) {
GameBoyKeyUp("down");
GameBoyKeyDown("up");
} else if (x > -y && x < y) {
GameBoyKeyUp("up");
GameBoyKeyDown("down");
}
if (x > -DEADZONE && x < DEADZONE) {
GameBoyKeyUp("left");
GameBoyKeyUp("right");
}
}
}
el.addEventListener("touchend", function(e) {
e.preventDefault();
e.stopPropagation();
initSound();
GameBoyKeyUp("left");
GameBoyKeyUp("right");
GameBoyKeyUp("up");
GameBoyKeyUp("down");
});
}
function bindTouchRestore() {
window.addEventListener("touchstart", function(e) {
controller.style.display = "block";
isTouchEnabled = true;
})
}
function bindKeyboard() {
window.onkeydown = function(e) {
initSound();
if (isTouchEnabled) {
controller.style.display = "none";
isTouchEnabled = false;
}
if (
e.keyCode !== JS_KEY_CTRL &&
e.keyCode !== JS_KEY_ALT &&
(e.altKey || e.ctrlKey || e.metaKey)
) {
return;
}
if (keyBindings[e.key]) {
GameBoyKeyDown(keyBindings[e.key]);
}
e.preventDefault();
};
window.onkeyup = function(e) {
if (e.key === "Dead") {
// Ipad keyboard fix :-/
// Doesn't register which key was released, so release all of them
["right", "left", "up", "down", "a", "b", "select", "start"].forEach(
key => {
GameBoyKeyUp(key);
}
);
}
if (keyBindings[e.key]) {
GameBoyKeyUp(keyBindings[e.key]);
}
e.preventDefault();
};
}
function bindClick() {
window.addEventListener("click", function(e) {
initSound();
})
}
if (isTouchEnabled) {
controller.style.display = "block";
bindButton(btnA, "a");
bindButton(btnB, "b");
bindButton(btnStart, "start");
bindButton(btnSelect, "select");
bindDpad(dpad);
bindTouchRestore();
} else {
controller.style.display = "none";
}
bindKeyboard();
bindClick();
function resetKeys() {
for (var key in defaultKeys) {
GameBoyKeyUp(key);
}
}
window.addEventListener("focus", resetKeys);
window.addEventListener("blur", resetKeys);
// HTML Gamepad API Support
// Load a key map for gamepad-to-gameboy buttons
function gamepadBindKeys(strMapping) {
// Try to use the w3c "standard" gamepad mapping if available
// (Chrome/V8 seems to do that better than Firefox)
//
// Otherwise use a default mapping that assigns
// A/B/Select/Start to the first four buttons,
// and U/D/L/R to the first two axes.
if (strMapping === GAMEPAD_KEYMAP_STANDARD_STR)
gp.keybinds = GAMEPAD_KEYMAP_STANDARD;
else
gp.keybinds = GAMEPAD_KEYMAP_DEFAULT;
}
function gamepadCacheValues(gamepad) {
// Read Buttons
for(let k=0; k<gamepad.buttons.length; k++) {
// .value is for analog, .pressed is for boolean buttons
gp.buttons.cur[k] = (gamepad.buttons[k].value > 0 ||
gamepad.buttons[k].pressed == true);
// Update state changed if not on first input pass
if (gp.buttons.last !== undefined)
gp.buttons.changed[k] = (gp.buttons.cur[k] != gp.buttons.last[k]);
}
// Read Axes
for(let k=0; k<gamepad.axes.length; k++) {
// Decode each dpad axis into two buttons, one for each direction
gp.axes.cur[(k*2) ] = (gamepad.axes[k] < 0);
gp.axes.cur[(k*2)+1] = (gamepad.axes[k] > 0);
// Update state changed if not on first input pass
if (gp.axes.last !== undefined) {
gp.axes.changed[(k*2) ] = (gp.axes.cur[(k*2) ] != gp.axes.last[(k*2) ]);
gp.axes.changed[(k*2)+1] = (gp.axes.cur[(k*2)+1] != gp.axes.last[(k*2)+1]);
}
}
// Save current state for comparison on next input
gp.axes.last = gp.axes.cur.slice(0);
gp.buttons.last = gp.buttons.cur.slice(0);
}
function gamepadHandleButton(keyBind) {
var buttonCache;
// Select button / axis cache based on key bind type
if (keyBind.type === "button")
buttonCache = gp.buttons;
else if (keyBind.type === "axis")
buttonCache = gp.axes;
// Make sure the button exists in the cache array
if (keyBind.gp_button < buttonCache.changed.length) {
// Send the button state if it's changed
if (buttonCache.changed[keyBind.gp_button])
if (buttonCache.cur[keyBind.gp_button])
GameBoyKeyDown(keyBind.gb_key);
else
GameBoyKeyUp(keyBind.gb_key);
}
}
function gamepadGetCurrent() {
// Chrome requires retrieving a new gamepad object
// every time button state is queried (the existing object
// will have stale button state). Just do that for all browsers
var gamepad = navigator.getGamepads()[gp.apiID];
if (gamepad)
if (gamepad.connected)
return gamepad;
return undefined;
}
function gamepadUpdate() {
var gamepad = gamepadGetCurrent();
if (gamepad !== undefined) {
// Cache gamepad input values
gamepadCacheValues(gamepad);
// Loop through buttons and send changes if needed
for (let i=0; i<gp.keybinds.length; i++)
gamepadHandleButton(gp.keybinds[i]);
}
else {
// Gamepad is no longer present, disconnect
gamepadStop();
}
}
function gamepadStart(gamepad) {
// Make sure it has enough buttons and axes
if ((gamepad.mapping === GAMEPAD_KEYMAP_STANDARD_STR) ||
((gamepad.axes.length >= 2) && (gamepad.buttons.length >= 4))) {
// Save API index for polling (required by Chrome/V8)
gp.apiID = gamepad.index;
// Assign gameboy keys to the gamepad
gamepadBindKeys(gamepad.mapping);
// Start polling the gamepad for input
gp.timerID = setInterval( () => gamepadUpdate(), GAMEPAD_POLLING_INTERVAL);
}
}
function gamepadStop() {
// Stop polling the gamepad for input
if (gp.timerID !== undefined)
clearInterval(gp.timerID);
// Clear previous button history and controller info
gp.axes.last = undefined;
gp.buttons.last = undefined;
gp.keybinds = undefined;
gp.apiID = undefined;
}
function initGamePad()
{
// When a gamepad connects, start polling it for input
window.addEventListener("gamepadconnected", (event) => {
initSound();
gamepadStart( navigator.getGamepads()[event.gamepad.index] );
});
// When a gamepad disconnects, shut down polling for input
window.addEventListener("gamepaddisconnected", (event) => {
gamepadStop();
});
}
initGamePad();

View File

@@ -1,107 +0,0 @@
var romPath = "rom/game.gb";
var mainCanvas = null;
var soundReady = false;
var cout = console.log.bind(console);
function startGame(blob) {
var binaryHandle = new FileReader();
binaryHandle.onload = function() {
if (this.readyState === 2) {
try {
start(mainCanvas, this.result);
} catch (e) {
alert(e.message);
}
}
};
binaryHandle.readAsBinaryString(blob);
}
function loadViaXHR() {
var xhr = new XMLHttpRequest();
xhr.open("GET", romPath);
xhr.responseType = "blob";
xhr.onload = function() {
startGame(new Blob([this.response], { type: "text/plain" }));
};
xhr.send();
}
function windowingInitialize() {
cout("windowingInitialize() called.", 0);
mainCanvas = document.getElementById("mainCanvas");
window.onunload = autoSave;
loadViaXHR();
}
//Wrapper for localStorage getItem, so that data can be retrieved in various types.
function findValue(key) {
try {
if (window.localStorage.getItem(key) != null) {
return JSON.parse(window.localStorage.getItem(key));
}
} catch (error) {
//An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole):
if (window.globalStorage[location.hostname].getItem(key) != null) {
return JSON.parse(window.globalStorage[location.hostname].getItem(key));
}
}
return null;
}
//Wrapper for localStorage setItem, so that data can be set in various types.
function setValue(key, value) {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
//An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole):
window.globalStorage[location.hostname].setItem(key, JSON.stringify(value));
}
}
//Wrapper for localStorage removeItem, so that data can be set in various types.
function deleteValue(key) {
try {
window.localStorage.removeItem(key);
} catch (error) {
//An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole):
window.globalStorage[location.hostname].removeItem(key);
}
}
// Allow Audio context to be created in places which require
// user input first. Hook this up to keyboard and touch inputs
function initSound() {
if (!soundReady && !settings[0] && GameBoyEmulatorInitialized()) {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
window.audioContext = new AudioContext();
if (window.audioContext) {
// Create empty buffer
var buffer = window.audioContext.createBuffer(1, 1, 22050);
var source = window.audioContext.createBufferSource();
source.buffer = buffer;
// Connect to output (speakers)
source.connect(window.audioContext.destination);
// Play sound
if (source.start) {
source.start(0);
} else if (source.play) {
source.play(0);
} else if (source.noteOn) {
source.noteOn(0);
}
window.audioContext.resume().then(() => {
settings[0] = true;
gameboy.initSound();
soundReady = true;
});
}
}
}
var soundInitTimer = setInterval(function() {
if (GameBoyEmulatorInitialized()) {
initSound();
clearTimeout(soundInitTimer);
}
}, 16);
window.addEventListener("DOMContentLoaded", windowingInitialize);

View File

@@ -1,206 +0,0 @@
//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 {
this.ratioWeight = this.fromSampleRate / this.toSampleRate;
if (this.fromSampleRate < this.toSampleRate) {
/*
Use generic linear interpolation if upsampling,
as linear interpolation produces a gradient that we want
and works fine with two input sample points per output in this case.
*/
this.compileLinearInterpolationFunction();
this.lastWeight = 1;
}
else {
/*
Custom resampler I wrote that doesn't skip samples
like standard linear interpolation in high downsampling.
This is more accurate than linear interpolation on downsampling.
*/
this.compileMultiTapFunction();
this.tailExists = false;
this.lastWeight = 0;
}
this.initializeBuffers();
}
}
else {
throw(new Error("Invalid settings specified for the resampler."));
}
}
Resampler.prototype.compileLinearInterpolationFunction = function () {
var toCompile = "var bufferLength = buffer.length;\
var outLength = this.outputBufferSize;\
if ((bufferLength % " + this.channels + ") == 0) {\
if (bufferLength > 0) {\
var weight = this.lastWeight;\
var firstWeight = 0;\
var secondWeight = 0;\
var sourceOffset = 0;\
var outputOffset = 0;\
var outputBuffer = this.outputBuffer;\
for (; weight < 1; weight += " + this.ratioWeight + ") {\
secondWeight = weight % 1;\
firstWeight = 1 - secondWeight;";
for (var channel = 0; channel < this.channels; ++channel) {
toCompile += "outputBuffer[outputOffset++] = (this.lastOutput[" + channel + "] * firstWeight) + (buffer[" + channel + "] * secondWeight);";
}
toCompile += "}\
weight -= 1;\
for (bufferLength -= " + this.channels + ", sourceOffset = Math.floor(weight) * " + this.channels + "; outputOffset < outLength && sourceOffset < bufferLength;) {\
secondWeight = weight % 1;\
firstWeight = 1 - secondWeight;";
for (var channel = 0; channel < this.channels; ++channel) {
toCompile += "outputBuffer[outputOffset++] = (buffer[sourceOffset" + ((channel > 0) ? (" + " + channel) : "") + "] * firstWeight) + (buffer[sourceOffset + " + (this.channels + channel) + "] * secondWeight);";
}
toCompile += "weight += " + this.ratioWeight + ";\
sourceOffset = Math.floor(weight) * " + this.channels + ";\
}";
for (var channel = 0; channel < this.channels; ++channel) {
toCompile += "this.lastOutput[" + channel + "] = buffer[sourceOffset++];";
}
toCompile += "this.lastWeight = weight % 1;\
return this.bufferSlice(outputOffset);\
}\
else {\
return (this.noReturn) ? 0 : [];\
}\
}\
else {\
throw(new Error(\"Buffer was of incorrect sample length.\"));\
}";
this.resampler = Function("buffer", toCompile);
}
Resampler.prototype.compileMultiTapFunction = function () {
var toCompile = "var bufferLength = buffer.length;\
var outLength = this.outputBufferSize;\
if ((bufferLength % " + this.channels + ") == 0) {\
if (bufferLength > 0) {\
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 = " + this.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 + " / " + this.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 && outputOffset < outLength);\
return this.bufferSlice(outputOffset);\
}\
else {\
return (this.noReturn) ? 0 : [];\
}\
}\
else {\
throw(new Error(\"Buffer was of incorrect sample length.\"));\
}";
this.resampler = 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 = [];
}
}

View File

@@ -1,20 +0,0 @@
function bindResize() {
var canvas = document.getElementById("mainCanvas");
var gameRatio = 160 / 144;
function onResize() {
var windowRatio = window.innerWidth / window.innerHeight;
if (windowRatio < gameRatio) {
canvas.style.width = window.innerWidth + "px";
canvas.style.height = "auto";
} else {
canvas.style.height = window.innerHeight + "px";
canvas.style.width = "auto";
}
}
window.addEventListener("resize", onResize);
onResize();
}
bindResize();

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +0,0 @@
{
"name": "GAMEBOY",
"description": "GameBoy Online GAMEBOY Emulator",
"launch_path": "/index.html",
"fullscreen": true,
"icons": {
"128": "/images/128.png"
},
"orientation": ["portrait-primary"]
}

View File

@@ -1,2 +0,0 @@
Add your ROM here named as game.gb
or edit the romPath in js/other/mobile.js to point to your ROM file.

Binary file not shown.