<!doctype html>

<html>
  <head>
    <link rel="stylesheet" href="style.css">
  </head>

  <body>

    <button id="mobile_mode_button" type="button" class="cellphone_btn" onclick="onMoblieModeClick()"></button>
    

    <div id="mainview" class="">


    <div class="canvasHolder">  
      <div id="canvasAligner" class="pageHolder">
        <canvas id="cavasDisplay" width="640" height="320" style=""></canvas>
      </div>

      <div id="mobile_holder" class="mobile_holder">
      <div id="mini_keyboard_full" class="mini_keyboard_full_container">
        <button type="button" class="mini_keyboard_full_key" value="1" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">1</button>
        <button type="button" class="mini_keyboard_full_key" value="2" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">2</button>
        <button type="button" class="mini_keyboard_full_key" value="3" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">3</button>
        <button type="button" class="mini_keyboard_full_key" value="4" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">4</button>
        </br>
        <button type="button" class="mini_keyboard_full_key" value="q" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">Q</button>
        <button type="button" class="mini_keyboard_full_key" value="w" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">W</button>
        <button type="button" class="mini_keyboard_full_key" value="e" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">E</button>
        <button type="button" class="mini_keyboard_full_key" value="r" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">R</button>
        </br>
        <button type="button" class="mini_keyboard_full_key" value="a" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">A</button>
        <button type="button" class="mini_keyboard_full_key" value="s" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">S</button>
        <button type="button" class="mini_keyboard_full_key" value="d" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">D</button>
        <button type="button" class="mini_keyboard_full_key" value="f" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">F</button>
        </br>
        <button type="button" class="mini_keyboard_full_key" value="z" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">Z</button>
        <button type="button" class="mini_keyboard_full_key" value="x" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">X</button>
        <button type="button" class="mini_keyboard_full_key" value="c" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">C</button>
        <button type="button" class="mini_keyboard_full_key" value="d" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">D</button>
      </div>

      <div id="mini_keyboard_small" class="mini_keyboard_full_container">
        <button type="button" class="mini_keyboard_full_key" value="1" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">1</button>
        </br>
        <button type="button" class="mini_keyboard_full_key" value="q" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">Q</button>
        <button type="button" class="mini_keyboard_full_key" value="w" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">W</button>
        <button type="button" class="mini_keyboard_full_key" value="e" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">E</button>
        </br>
        <button type="button" class="mini_keyboard_full_key" value="a" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">A</button>
        <button type="button" class="mini_keyboard_full_key" value="s" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">S</button>
        <button type="button" class="mini_keyboard_full_key" value="d" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">D</button>
      </div>
      
      <div id="mini_keyboard_wasd" class="mini_keyboard_full_container">
        <button type="button" class="mini_keyboard_full_blank"></button>
        <button type="button" class="mini_keyboard_full_key" value="w" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">W</button>
        </br>
        <button type="button" class="mini_keyboard_full_key" value="a" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">A</button>
        <button type="button" class="mini_keyboard_full_key" value="s" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">S</button>
        <button type="button" class="mini_keyboard_full_key" value="d" onpointerdown="onMobileKeyDown(value)" onpointerup="onMobileKeyUp(value)" onpointerout="onMobileKeyUp(value)">D</button>
      </div>
    </div>
    </div>

    <div id="mainview" class="ghostHolder">
    <div class="pageHolder">

    
    
    <div class="rowLoad">
      <div class="columnLoad">
        
        <div class="columnLoadHeader">
          <span>Load Built In ROM:</span>
        </div>
        
        <p>
          <select id="ROMS" name="ROMS" onchange="loadROMSelection()">
            <option value="chip8_rom" selected>Startup Intro</option>
            <option value="invaders_rom">Space Invaders</option>
            <option value="danm8ku_rom">Danm8ku</option>
            <option value="pong_rom">Pong</option>
            <option value="snake_rom">Snake</option>
          </select>
        </p>

      </div>
      <div class="columnLoad">

        <div class="columnLoadHeader">
          <span>Load ROM From File:</span>
        </div>

        <p>
          <input type="file" onchange="showFileBrowser(this)">
        </p>
      </div>

      <div class="columnLoad">
        <div class="columnLoadHeader">
          <span>Quick Settings:</span>
        </div>
        <div style="padding-top:10px">
          <input type="checkbox" id="soundMute" name="soundMute" value="mute" onclick="onMuteClick(this)">
          <label for="soundMute">Mute Sound</label>&nbsp;&nbsp;
          <input type="button" class="defualtBtn" value="Reset" onclick="htmlButtonReset()">
          </br>
          </br>
          <label for="soundGainSlider">Volume: </label>
          <input type="range" min="0.0" max="1.0" value="0.5" step="0.05" class="slider" id="soundGainSlider" onchange="setAudioVolume(value)">
        </div>
      </div>
    </div>
    
    <button type="button" class="collapsible" id="ROMDiscriptionCollapsible" onclick="onCollapsibleClick(this)">ROM Description</button>
    <div class="content">
      <p style="font-size: 16px;" id="htmlDescription"></p>
    </div>

    <button type="button" class="collapsible" onclick="onCollapsibleClick(this)">Emulator Options</button>
    <div class="content">
      <form>

        <p>Display Wrapping:&nbsp;&nbsp;
        <input type="checkbox" id="wrapX" name="wrapX" onclick="wrapXCheckboxClick()"
         checked>
        <label for="wrapX">Wrap X</label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
        <input type="checkbox" id="wrapY" name="wrapY" onclick="wrapYCheckboxClick()"
         checked>
        <label for="wrapY">Wrap Y</label>
        </p>
        
        <p>
          Shift Compatability:&nbsp;&nbsp;
          <input type="radio" id="shiftVy" name="userShiftCompatability" value="shiftVy" checked>
          <label for="shiftVy">Shift Vy and store in Vx</label>
          &nbsp;&nbsp;
          <input type="radio" id="shiftVx" name="userShiftCompatability" value="shiftVx">
          <label for="shiftVx">Shift Vx and store in Vx</label>
        </p>

        <label for="emuTargetFreq">Emulation Cycle Rate:</label>
          
        <input type="number" id="emuTargetFreq" name="targetFreq"
       min="1" max="2000" value="8" onchange="onOptionCycleRateChange(value)">
       <label for="emuTargetFreq"> Instructions Every Draw Frame</label>
      </form>
    </div>

    <button type="button" class="collapsible" onclick="onCollapsibleClick(this); onEmulatorInternalsClick()">Emulator Internals</button>
    <div class="content">
      <div style="text-align: center;">
        <p style="white-space: pre;" id="state-msg"></p>
      </div>
    <form>
      <table>
        <tr>
          <td colspan="2" style="text-align: center;">
            <input type="radio" id="bin" name="registerBase" value="2" onchange="updateOutputRegisterBase(value)">
            <label for="bin">binary</label>&nbsp;&nbsp;
            <input type="radio" id="hex" name="registerBase" value="16" onchange="updateOutputRegisterBase(value)"" checked>
            <label for="hex">hexadecimal</label>&nbsp;&nbsp;
            <input type="radio" id="dec" name="registerBase" value="10" onchange="updateOutputRegisterBase(value)">
            <label for="dec">decimal</label>
          </td>
        </tr>
        <tr>
          <td colspan="2">
            <input type="button" class="defualtBtn" value="Step" onclick="runPC()">
            <input type="button" class="defualtBtn" value="Run" onclick="runningSwitcher()">
            <input type="button" class="defualtBtn" value="Reset" onclick="htmlButtonReset()">
          </td>
        </tr>
        <tr>
          <td>
            <label for="tbPC">PC:</label>
            <input type="text" id="tbPC" name="tbPC" size="16">
            
            <br>
          </td>
          <td>
            <label for="tbI">I:</label>
            <input type="text" id="tbI" name="tbI" size="17"><br>
          </td>
        </tr>
        <tr>
          <td>
            <label for="tbV0">V0:</label>
            <input type="text" id="tbV0" name="tbV0" size="8"><br>
          </td>
          <td>
            <label for="tbV8">V8:</label>
            <input type="text" id="tbV8" name="tbV8" size="8"><br>
          </td>
        </tr>
        <tr>
          <td>
            <label for="tbV1">V1:</label>
            <input type="text" id="tbV1" name="tbV1" size="8"><br>
          </td>
          <td>
            <label for="tbV9">V9:</label>
            <input type="text" id="tbV9" name="tbV9" size="8"><br>
          </td>
        </tr>
        <tr>
          <td>
            <label for="tbV2">V2:</label>
            <input type="text" id="tbV2" name="tbV2" size="8"><br>
          </td>
          <td>
            <label for="tbVA">VA:</label>
            <input type="text" id="tbVA" name="tbVA" size="8"><br>
          </td>
        </tr>
        <tr>
          <td>
            <label for="tbV3">V3:</label>
            <input type="text" id="tbV3" name="tbV3" size="8"><br>
          </td>
          <td>
            <label for="tbVB">VB:</label>
            <input type="text" id="tbVB" name="tbVB" size="8"><br>
          </td>
        </tr>
        <tr>
          <td>
            <label for="tbV4">V4:</label>
            <input type="text" id="tbV4" name="tbV4" size="8"><br>
          </td>
          <td>
            <label for="tbVC">VC:</label>
            <input type="text" id="tbVC" name="tbVC" size="8"><br>
          </td>
        </tr>
        <tr>
          <td>
            <label for="tbV5">V5:</label>
            <input type="text" id="tbV5" name="tbV5" size="8"><br>
          </td>
          <td>
            <label for="tbVD">VD:</label>
            <input type="text" id="tbVD" name="tbVD" size="8"><br>
          </td>
        </tr>
        <tr>
          <td>
            <label for="tbV6">V6:</label>
            <input type="text" id="tbV6" name="tbV6" size="8"><br>
          </td>
          <td>
            <label for="tbVE">VE:</label>
            <input type="text" id="tbVE" name="tbVE" size="8"><br>
          </td>
        </tr>
        <tr>
          <td>
            <label for="tbV7">V7:</label>
            <input type="text" id="tbV7" name="tbV7" size="8"><br>
          </td>
          <td>
            <label for="tbVF">VF:</label>
            <input type="text" id="tbVF" name="tbVF" size="8"><br>
          </td>
        </tr>
        <tr>
          <td>
            <label for="tbDT">DT:</label>
            <input type="text" id="tbDT" name="tbDT" size="8"><br>
          </td>
          <td>
            <label for="tbST">ST:</label>
            <input type="text" id="tbST" name="tbST" size="8"><br>
          </td>
        </tr>
      </table>
    </form>
    </br>
    
    </div>
      </br>
      </br>
      <a href="https://github.com/EricLju/CHIP8" >Click to check out the GitHub Page.</a>
    </div>
    </div>
    
    <table id="memdump"></table>

    <script src="c8/c8_globals.js"></script>
    <script src="c8/c8_compatability_list.js"></script>
    <script src="c8/c8_audio.js"></script>
    <script src="c8/c8_input.js"></script>
    <script src="c8/c8_helpers.js"></script>
    <script src="c8/c8_instructions.js"></script>
    <script src="c8/c8_data.js"></script>
    <script src="c8/c8_active_html_handler.js"></script>
    <script src="c8/c8_main.js"></script>

  </body>
</html>
//c8_main.js - Main file

//Notes:
//https://en.wikipedia.org/wiki/CHIP-8
//https://github.com/dmatlack/chip8/tree/master/roms
//http://mattmik.com/files/chip8/mastering/chip8.html

//For Debugging
//Dump the memory to a table at the bottom of the page.
function outputMemory(){
  //Number of columns wide for the output table.
  var outputCols = 64;
  var table = document.getElementById("memdump");

  //Clear the current table spot just in case there is currenty a table there.
  table.innerHTML = "";

  for(var j = 0; j < (C8_MEMORY_SIZE / outputCols); j ++){
    //New row in table.
    var curRow = table.insertRow(j);
    for(var i = 0; i < outputCols; i++){
      //New cell in current row.
      var curCell = curRow.insertCell(i);
      //Convert XY position to linear array position
      var curMem = i+(j*outputCols);
      //Jam in the memory address and contents at that address in the current cell.
      curCell.innerHTML = "<p style=\"font-size:10px;margin-top:0em;margin-bottom:0em\">" + curMem.toString(16).padStart(3, "0").toUpperCase() + "</p><p style=\"font-size:18px;margin-top:0em;margin-bottom:0em\">" + c8Memory_U8View[curMem].toString(16).padStart(2, "0").toUpperCase() + "</p>";
    }
  }
}

//Load a ROM that is stored as JS array of 16 bit numbers into a new buffer
//AKA the ROMs stored in c8_data.js
//return the buffer
function loadProgramFrom16Array(array){
  var bufferByteLength = array.length * 2;
  var newBuffer = new ArrayBuffer(bufferByteLength);
  var newBuffer_View = new DataView(newBuffer);
  
  for(var i = 0; i < array.length; i++){
    newBuffer_View.setUint16(i*2, array[i], false);
  }

  return newBuffer;
}

//Load the defualt font into CHIP-8 memory
function loadFontIntoMemory(font){
  //For each letter in the font
  for(var i = 0; i < font.length; i++){
    //Each character is a sprite that is 8 bits wide by 5 bits high.
    loadSpriteArrayIntoMemory(c8FontStartAddress + (i * 5), font[i], 5)
  }
}

//Load a sprite that is in a byte array form into CHIP-8 memory
//Sprites are always 8 bits wide
function loadSpriteArrayIntoMemory(memoryLocation, spriteArray, height){
  var currentMemoryLocation = 0;

  //For each row in the sprite
  for(var i = 0; i < height; i++){
    currentMemoryLocation = memoryLocation + i;
    c8Memory_View.setUint8(currentMemoryLocation, spriteArray[i]);
  }
}

//Clear the canvas.
function clearDisplayCanvas(){
  //Clear the canvas by drawing a filled rectangle over the whole thing
  contextDisplay.fillStyle = "rgb(" + backgroundColor.r + ", " + backgroundColor.g + ", " + backgroundColor.b + ")";
	contextDisplay.fillRect(0, 0, canvasDisplay.width, canvasDisplay.height);
};

//Clear the display memory buffer by filling it with 0 values.
function clearDisplayBuffer(){
  c8DisplayBuffer.fill(0);
}

//Clear the display memory and the canvas.
function clearDisplay(){
  clearDisplayBuffer();
  clearDisplayCanvas();
}

//Draw a pixel on the canvas. Grayscale transition for position is handled
//by setting the alpha value of the pixel.
function setDisplayPixel(x, y, persistance=1){
  //Calculate the actual canvas pixel location by accounting for pixel scale
  var realX = x * c8DisplayPixelWidth;
  var realY = y * c8DisplayPixelHeight;
  //TODO: It would probably be best to generate a look up table of color values
  contextDisplay.fillStyle = "rgba(" + forgroundColor.r + ", " + forgroundColor.g + ", " + forgroundColor.b + ", " + persistance + ")";
  contextDisplay.fillRect(realX, realY, c8DisplayPixelWidth, c8DisplayPixelHeight);
}

//Take the data in the display buffer and draw it on the canvas. Update pixel
//persistance data in the process.
function drawDisplayBuffer(){
  var currentBufferIndex;
  var curBufferValue;
  var curPixelValue;
  //For each row of the CHIP-8 display.
  for(var row = 0; row < c8DisplayHeight; row++){
    //For each column in the current row.
    for(var col = 0; col < c8DisplayWidth; col++){
      //Calculator the linear array position from the XY position of the pixel.
      currentBufferIndex = (row * c8DisplayWidth) + col;
      //Current pixel value.
      curBufferValue = c8DisplayBuffer[currentBufferIndex];
      //Decay the persistance value of the current pixel.
      c8DisplayPersistance[currentBufferIndex] -= displayDecayRate;
      //Make sure the persistance value doesn't fall below 0
      if(c8DisplayPersistance[currentBufferIndex] < 0.0){
        c8DisplayPersistance[currentBufferIndex] = 0.0;
      }
      //If the pixel is set then there is no decay.
      if(curBufferValue == 1){
        c8DisplayPersistance[currentBufferIndex] = curBufferValue;
      }
      //Draw the pixel using the current persistance decay value.
      curPixelValue = c8DisplayPersistance[currentBufferIndex];
      if(curPixelValue > 0.0){
        setDisplayPixel(col, row, curPixelValue);
      }
    }
  }
}

//Set all registers to a zero value.
function clearAllRegisters(){
  //For each register in the register array
  for(var i = 0; i < c8RegisterArray.length; i++){
    c8RegisterArray[i] = 0;
  }
}

//Fill the entire CHIP-8 memory with zero values.
function clearMemory(){
  //For each byte in the CHIP-8 memory buffer.
  for(var i = 0; i < c8Memory_View.byteLength; i++){
    c8Memory_View.setUint8(i, 0);
  }
}

//Copy an ArrayBuffer into CHIP-8 program memory.
function copyArrayBufferToProgramMemory(buffer){
  var currentMemoryLocation = 0;
  var buffer_view = new DataView(buffer);

  //For each byte in the buffer.
  for(var i = 0; i < buffer.byteLength; i++){
    currentMemoryLocation = c8ProgramStartAddress + i;
    c8Memory_View.setUint8(currentMemoryLocation, buffer_view.getUint8(i));
  }
}

//Reload the currently loaded ROMs buffer into CHIP-8 memory
function reloadCurrentROMIntoMemory(){
  copyArrayBufferToProgramMemory(currentROMBuffer);
}

//Restart the currently selected ROM
function resetEmulator(){
  clearDisplay();
  clearAllRegisters();
  clearMemory();
  ioBlocking = false;
  c8RegisterArray[PC] = 0x200;
  c8StackCurrentFreeAddress = c8StackStartAddress;
  loadFontIntoMemory(font);
  reloadCurrentROMIntoMemory();
  outputAllRegisters(outputRegisterBase);
}

//Run on page load. Initialize the emulator and page into a defualt known state.
function init(){
  //InputHandler handles key events
  inputHandler = new InputHandler();
  //The browser (at least firefox) doesn't reset this on page refresh. Set this
  //back to the defualt on startup.
  selectBuiltInRoms.value = "chip8_rom"; 
  document.getElementById('ROMDiscriptionCollapsible').click();
  dbgMsg.textContent = "";
  //Add a listener for window resizes and run the function windowResize() if
  //the size of the window changes.
  window.addEventListener('resize', windowResize);
  //Run the window resizing function on startup to make sure the canvas is 
  //displaying correctly.
  windowResize();
  //Load the "boot ROM" into memory
  currentROMBuffer = loadProgramFrom16Array(startup_rom);
  //My "boot rom" is index 0 in the compatability list.
  setEmulatorToCompatabilitySettings(5);

  resetEmulator();

  //Set the settings HTML elements to their JavaScript variable counterpart
  //values.
  updateAllHTMLOptionElements();

  //Set the Emulator Internals table to some defualt values
  outputAllRegisters(outputRegisterBase);

  running = true;
}

//Update a timer register by subtracting the time since the last animation 
//frame in "target" increments. The CHIP-8 timer registers are decremented at a
//60Hz interval. So in that case would be decrement every time 16.67ms has past.
//returns the remaining time if it could not be evenly divided in "target" increments
function timerFrame(timer, remainder, target){
  var tempDTVal = c8RegisterArray[timer];

  if(tempDTVal >= 0){
    //Integer division to figure out how many times dT goes into the target delay
    var tempTotalTime = deltaTime + remainder;
    var tempDiv = tempTotalTime / target;
    var newRemainder = 0;
    tempDiv = Math.trunc(tempDiv);

    //if greater than 1 target increment
    if(tempDiv >= 1){
      tempDTVal = tempDTVal - tempDiv;
      if(tempDTVal < 0){
        tempDTVal = 0;
        newRemainder = 0;
      } else {
        newRemainder = tempTotalTime - (tempDiv * target);
      }
      c8RegisterArray[timer] = tempDTVal;
      return newRemainder;
    } else {
      return tempTotalTime;
    }
  }
}

//Take the instruction and determine what fuction to run.
function decodeOperation(opcode){
  var tempNibble0 = opcode & 0x000F; //Lower 4 bits
  var tempNibble3 = opcode & 0xF000; //Upper 4 bits
  var tempByte0 = opcode & 0x00FF; //Lower byte

  //CHIP-8 doesn't have very many instructions. Use a large switch tree to
  //determine what the instructions function is.
  switch(tempNibble3){
    case 0x0000:
      switch(tempByte0){
        case 0xE0: clearDisplay(); break;
        case 0xEE: instructionReturnSubroutine(opcode); break;
        default: instructionCallExternal(opcode);
      }
    break;
    case 0x1000: instructionJump(opcode); break;
    case 0x2000: instructionCallSubroutine(opcode); break;
    case 0x3000: instructionIfConstant(opcode); break;
    case 0x4000: instructionIfNotConstant(opcode); break;
    case 0x5000: instructionIfRegister(opcode); break;
    case 0x6000: instructionLoad(opcode); break;
    case 0x7000: instructionAddConstant(opcode); break;
    case 0x8000:
      switch(tempNibble0){
        case 0x0: instructionCopy(opcode); break;
        case 0x1: instructionOr(opcode); break;
        case 0x2: instructionAnd(opcode); break;
        case 0x3: instructionXor(opcode); break;
        case 0x4: instructionAddRegisters(opcode); break;
        case 0x5: instructionSubtractRegisters1(opcode); break;
        case 0x6: instructionRightShift(opcode); break;
        case 0x7: instructionSubtractRegisters2(opcode); break;
        case 0xE: instructionLeftShift(opcode); break;
      }
    break;
    case 0x9000: instructionIfNotegister(opcode); break;
    case 0xA000: instructionSetI(opcode); break;
    case 0xB000: instructionJumpOffset(opcode); break;
    case 0xC000: instructionRand(opcode); break;
    case 0xD000: instructionDraw(opcode); break;
    case 0xE000:
      switch(tempByte0){
        case 0x9E: instructionIfKeyPressed(opcode); break;
        case 0xA1: instructionIfKeyNotPressed(opcode); break;
      }
    break;
    case 0xF000:
      switch(tempByte0){
        case 0x07: instructionGetDelayTimer(opcode); break;
        case 0x0A: instructionWaitForKeyPress(opcode); break;
        case 0x15: instructionSetDelayTimer(opcode); break;
        case 0x18: instructionSetSoundTimer(opcode); break;
        case 0x1E: instructionSetDisplacedI(opcode); break;
        case 0x29: instructionFontAddress(opcode); break;
        case 0x33: instructionBCD(opcode); break;
        case 0x55: instructionSaveRegisters(opcode); break;
        case 0x65: instructionLoadRegisters(opcode); break;
      }
    break;
    default:
      console.log("Unknown Instruction: " + c8RegisterArray[PC] + " / " + opcode);
  }
}

//Run the emulator one cycle. Run the instruction at the current PC address.
function runPC(){
  //Get the instruction at address PC from CHIP-8 memory.
  var currentPC = c8RegisterArray[PC];
  var currentInstruction = c8Memory_View.getUint16(currentPC, false);

  //Run the instruction
  decodeOperation(currentInstruction);
  //If the debugging collapsable is open then update the register table.
  if(isDisplayingDebugOutput == true){
    outputAllRegisters(outputRegisterBase);
  }
  //If the instruction didn't modify PC then increment PC to the next instruction.
  if(skipPCIncrement == false){
    c8RegisterArray[PC] += 2
  } else {
    skipPCIncrement = false;
  }
}

//Main runner function. Called every animation frame.
function mainLoop(){
  mainFrameCount++;
  //Date.now() returns number of milliseconds since UNIX epoch
  currentTime = Date.now();
  //Number of milliseconds since last frame. Divide by 1000 to convert deltaTime
  //to seconds.
  deltaTime = currentTime - lastTime;
  lastTime = currentTime;
  totalTime += deltaTime;


  if(isDisplayingDebugOutput == true){
    dbgMsg.textContent = "";
    dbgMsg.textContent += "dt: " + deltaTime.toString() + "ms";
    dbgMsg.textContent += "  ";
    dbgMsg.textContent += "fps: " + (1/(deltaTime/1000)).toFixed(0);
    //dbgMsg.textContent += "  ";
    //dbgMsg.textContent += "mf: " + mainFrameCount;
    //dbgMsg.textContent += "  ";
    //dbgMsg.textContent += "df: " + framecountDebug;
    //dbgMsg.textContent += "  ";
    //dbgMsg.textContent += "tt: " + totalTime;
    //dbgMsg.textContent += "  ";
    //dbgMsg.textContent += "fi: " + numberOfInstructionThisFrame;
  }

  //Update the timer registers.
  //16.67ms * 60 ~= 1 second -> ~60Hz
  remainderDT = timerFrame(DT,remainderDT, 16.67);
  remainderST = timerFrame(ST,remainderST, 16.67);
  

  //Play that high definition audio if the sound timer register has a value.
  if(c8RegisterArray[ST] > 0){
    playTone();
  } else {
    stopTone();
  }

  //Allow for pausing if running is false, else run as normal
  if(running == true){
    //Run the CHIP-8 emulator for numberOfInstructionThisFrame cycles on this
    //frame.
    for(var i = 0; i < numberOfInstructionThisFrame; i++){
      //If ioBlocking then we are waiting for user input else then run as normal.
      if(ioBlocking == false){
        runPC();
      } else {
        //Wait for user input
        var tempIOTest = inputHandler.isAnyKeyDown();
        if(tempIOTest != -1){
          inputHandler.c8Keylist[tempIOTest] = false;
          c8RegisterArray[ioBlockDestinationRegister] = tempIOTest;
          ioBlocking = false;
        }
      }
    }
  }

  //To handle persistance the canvas is redrawn every frame rather then just
  //when the CHIP-8 display function is called.
  //Clear the display on each draw frame
  clearDisplayCanvas();
  //Redraw the display for this frame
  drawDisplayBuffer();

  //Rerun this function on the next animation frame.
  requestAnimationFrame(mainLoop);
}


//Get things ready
init();

//need to start the loop.
mainLoop();
//c8_helpers.js - General purpose functions

//Generate random float value from min to max. Includes min, excludes max.
//min - minimum value
//max - maximum value
//returns float value between min and max. Includes min, excludes max.
function randomFloat(min, max) {
  return Math.random() * (max - min) + min;
}

//Generate random integer value from min to max. Includes min, includes max.
function randomInt(min, max) {
  return Math.floor(randomFloat(min, max + 1));
}

//JavaScript doesn't do % modulo like other programming languages.
//https://web.archive.org/web/20090717035140if_/javascript.about.com/od/problemsolving/a/modulobug.htm
function programmerModulo(val, mod){
  return ((val % mod)+ mod) % mod;
}

//Retruns the selected 4 bits from a 16 bit value.
function getNibbleFromOpcode(opcode, index){
  var tempMask = 0x000F;
  //Number of right shift operations to move the nibble into the LSB spot
  var tempShift = 0;
  var tempNibble = 0;

  switch(index){
    case 1:
      tempMask = 0x00F0;
      tempShift = 4;
      break;
    case 2:
      tempMask = 0x0F00;
      tempShift = 8;
      break;
    case 3:
      tempMask = 0xF000;
      tempShift = 12;
      break;
    default:
  }
  //Make all other bits 0 except for the nibble we want.
  tempNibble = opcode & tempMask;
  //Shift the nibble into the LSB position
  tempNibble = tempNibble >>> tempShift; //zero fill shift

  return tempNibble;
}

//Compute an sdbm hash of a given ArrayBuffer.
function sdbmHash(buffer){
  var hash = 0;
  var buffer_view = new DataView(buffer);
  //For each byte in the buffer
  for(var i = 0; i < buffer.byteLength; i++){
    //http://www.cse.yorku.ca/~oz/hash.html
    hash = buffer_view.getUint8(i) + (hash << 6) + (hash << 16) - hash;
  }
  return hash;
}
//c8_instructions.js - CHIP-8 emulator instructions.  

//0NNN - Calls RCA 1802 program at address NNN. Not necessary for most ROMs. 
function instructionCallExternal(opcode){
  //Not implemented.
  console.log(c8RegisterArray[PC].toString(16) + ": Calls to external programs are not supported.");
}

//used for instructionReturnSubroutine(). Pops a return address off the stack.
function stackPop(){
  var curTopOfStack = c8StackCurrentFreeAddress - 2;

  //Check stack bounds.
  if(curTopOfStack < c8StackStartAddress){
    curTopOfStack = c8StackStartAddress;
    console.log("Warning: Tried to pop data from empty stack.");
  }
  
  c8StackCurrentFreeAddress = curTopOfStack;
  //Get the value from CHIP-8 memory at the top of the stack area.
  return c8Memory_View.getUint16(curTopOfStack, false);
}

//00EE - Returns from a subroutine. 
function instructionReturnSubroutine(opcode){
  var returnPC;
  
  //Handling PC on our own. Skip normal flow.
  skipPCIncrement = true;
  returnPC = stackPop();
  //console.log("return " + returnPC.toString(16));
  c8RegisterArray[PC] = returnPC;
}

//1NNN - Jumps to address NNN. 
function instructionJump(opcode){
  var tempAddress = opcode & 0x0FFF;
  //console.log("jump");
  skipPCIncrement = true;
  c8RegisterArray[PC] = tempAddress;
}

//used for instructionCallSubroutine(). Pushes a return address off the stack.
function stackPush(val){

  //Check stack bounds
  if(c8StackCurrentFreeAddress > c8StackMaxAddress){
    console.log("Warning: Stack larger than allocated memory space.");
  }

  //Put the given value in CHIP-8 Memory and increment the stack pointer.
  c8Memory_View.setUint16(c8StackCurrentFreeAddress, val, false);
  c8StackCurrentFreeAddress += 2;
}

//2NNN - Calls subroutine at NNN. 
function instructionCallSubroutine(opcode){
  var tempAddress;
  var returnPC;
  
  //Address of subroutine is the last 3 nibbles of the opcode.
  tempAddress = opcode & 0x0FFF;
  //We want to return to the instruction right after this one.
  returnPC = c8RegisterArray[PC] + 2;
  //Push the return address on to the stack to save for the return call.
  stackPush(returnPC);
  //Handling PC on our own. Skip normal flow.
  skipPCIncrement = true;
  //console.log(c8RegisterArray[PC].toString(16) + " call: " + tempAddress.toString(16) + " return: " + returnPC.toString(16));
  //Set PC to the subroutine address.
  c8RegisterArray[PC] = tempAddress;
}

//3XNN - if(Vx==NN) - Skips the next instruction if VX equals NN.
function instructionIfConstant(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempRegisterXData = c8RegisterArray[tempRegisterX];
  var tempConstant = opcode & 0x00FF;
  //console.log("If const: " + tempRegisterXData + " C: " + tempConstant);
  //Check condition
  if(tempRegisterXData == tempConstant){
    //skip setting PC based on normal flow. Skip the next instruction.
    skipPCIncrement = true;
    c8RegisterArray[PC] += 4;
  }
  //If condition is not met then execution continues as normal
}

//4XNN - if(Vx!=NN) - Skips the next instruction if VX doesn't equal NN.
function instructionIfNotConstant(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempRegisterXData = c8RegisterArray[tempRegisterX];
  var tempConstant = opcode & 0x00FF;
  //console.log(c8RegisterArray[PC].toString(16) + " If Not Const: " + tempRegisterXData + " C: " + tempConstant);
  //Check condition
  if(tempRegisterXData != tempConstant){
    //Skip setting PC based on normal flow.
    skipPCIncrement = true;
    //Skip the next instruction.
    c8RegisterArray[PC] += 4;
  }
  //If condition is not met then execution continues as normal
}

//5XY0 - if(Vx==Vy) - Skips the next instruction if VX equals VY. (Usually the next instruction is a jump to skip a code block) 
function instructionIfRegister(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempRegisterXData = c8RegisterArray[tempRegisterX];
  var tempRegisterY = getNibbleFromOpcode(opcode, 1);
  var tempRegisterYData = c8RegisterArray[tempRegisterY];
  //console.log("IfREG: Vx" + tempRegisterXData + " Vy: " + tempRegisterYData);
  //Check condition
  if(tempRegisterXData == tempRegisterYData){
    //Skip setting PC based on normal flow. 
    skipPCIncrement = true;
    //Skip the next instruction.
    c8RegisterArray[PC] += 4;
  }
}

//6XNN - Vx = NN - Sets VX to NN. 
function instructionLoad(opcode){ 
  var tempRegister = getNibbleFromOpcode(opcode, 2);
  var tempData = opcode & 0x00FF;
  //console.log("load reg: V" + tempRegister.toString(16) + " = " + tempData);
  c8RegisterArray[tempRegister] = tempData;
}

//8XY0 - Vx=Vy - Sets VX to the value of VY. 
function instructionCopy(opcode){ 
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempRegisterY = getNibbleFromOpcode(opcode, 1);
  //console.log("copy reg: " + tempRegisterX.toString(16) + " = " + tempRegisterY.toString(16));
  c8RegisterArray[tempRegisterX] = c8RegisterArray[tempRegisterY];
}

//7XNN - Vx += NN - Adds NN to VX. (Carry flag is not changed) 
function instructionAddConstant(opcode){
  var tempData = 0;
  var tempRegisterX = 0;
  var tempResult = 0;

  tempData = opcode & 0x00FF;
  tempRegisterX = getNibbleFromOpcode(opcode, 2);
  tempResult = c8RegisterArray[tempRegisterX] + tempData;
  tempResult = tempResult & 0xFF;
  //console.log(opcode.toString(16) + " add const: " + c8RegisterArray[tempRegisterX] + " + " + tempData + " = " + tempResult);
  c8RegisterArray[tempRegisterX] = tempResult;
  
}

//8XY1 - Vx=Vx|Vy - Sets VX to VX or VY. (Bitwise OR operation) 
function instructionOr(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempRegisterY = getNibbleFromOpcode(opcode, 1);
  //console.log("OR");
  c8RegisterArray[tempRegisterX] = c8RegisterArray[tempRegisterX] | c8RegisterArray[tempRegisterY];
  
  if(compatabilityLogicClearsVF == true){
    c8RegisterArray[VF] = 0;
  }
}

//8XY2 - Vx= Vx & Vy - Sets VX to VX and VY. (Bitwise AND operation) 
function instructionAnd(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempRegisterY = getNibbleFromOpcode(opcode, 1);
  var tempResult;
  tempResult = c8RegisterArray[tempRegisterX] & c8RegisterArray[tempRegisterY];

  //console.log("AND: " + c8RegisterArray[tempRegisterX] + " & " + c8RegisterArray[tempRegisterY] + " = " + tempResult);
  c8RegisterArray[tempRegisterX] = tempResult
  
  if(compatabilityLogicClearsVF == true){
    c8RegisterArray[VF] = 0;
  }
}

//8XY3 - Vx=Vx^Vy - Sets VX to VX xor VY. 
function instructionXor(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempRegisterY = getNibbleFromOpcode(opcode, 1);
  //console.log("XOR");
  c8RegisterArray[tempRegisterX] = c8RegisterArray[tempRegisterX] ^ c8RegisterArray[tempRegisterY];
  
  if(compatabilityLogicClearsVF == true){
    c8RegisterArray[VF] = 0;
  }
}

//8XY4 - Vx = Vx + Vy - Adds VY to VX. VF is set to 1 when there's a carry, and to 0 when there isn't. 
function instructionAddRegisters(opcode){
  var tempData = 0;
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempRegisterY = getNibbleFromOpcode(opcode, 1);
  //console.log("Add Vx + Vy with carry");
  tempData = c8RegisterArray[tempRegisterX] + c8RegisterArray[tempRegisterY];

  //Mask off any bits greater than 0xFF;
  c8RegisterArray[tempRegisterX] = tempData & 0xFF;

  if(tempData > 0xFF){
    //tempData = programmerModulo(tempData, 256);
    c8RegisterArray[VF] = 0x01;
  } else {
    c8RegisterArray[VF] = 0x00;
  }
}

//8XY5 - Vx = Vx - Vy -> VY is subtracted from VX. VF is set to 0 when there's a borrow, and 1 when there isn't. 
function instructionSubtractRegisters1(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempRegisterY = getNibbleFromOpcode(opcode, 1);
  var tempRegisterXData = c8RegisterArray[tempRegisterX];
  var tempRegisterYData = c8RegisterArray[tempRegisterY];
  
  var tempData = tempRegisterXData - tempRegisterYData;

  //Mask off any bits greater than 0xFF;
  tempData = tempData & 0xFF

  c8RegisterArray[tempRegisterX] = tempData;

  //if Vx is greater that Vy then there is no borrow
  if(tempRegisterXData >= tempRegisterYData){
    c8RegisterArray[VF] = 0x01;
  } else {
    c8RegisterArray[VF] = 0x00;
  }
  //console.log("subract 1 Vx - Vy: " + tempRegisterXData + " - " + tempRegisterYData + " = " + test + " => " + c8RegisterArray[tempRegisterX]);
}

//8XY7 - Vx=Vy-Vx - Sets VX to VY minus VX. VF is set to 0 when there's a borrow, and 1 when there isn't. 
function instructionSubtractRegisters2(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempRegisterY = getNibbleFromOpcode(opcode, 1);
  var tempRegisterXData = c8RegisterArray[tempRegisterX];
  var tempRegisterYData = c8RegisterArray[tempRegisterY];

  var tempData = tempRegisterYData - tempRegisterXData;

  //Mask off any bits greater than 0xFF;
  tempData = tempData & 0xFF;

  c8RegisterArray[tempRegisterX] = tempData;

  //if Vy is greater that Vx then there is no borrow
  if(tempRegisterYData >= tempRegisterXData){
    c8RegisterArray[VF] = 0x01;
  } else {
    c8RegisterArray[VF] = 0x00;
  }
  //console.log("subract 2 Vy-Vx: " + tempRegisterYData + " - " + tempRegisterXData + " = " + c8RegisterArray[tempRegisterX]);
}

//8XY6 - Vx>>=1 - Stores the least significant bit of VX in VF and then shifts VX to the right by 1.
//Wiki: CHIP-8's opcodes 8XY6 and 8XYE (the bit shift instructions), which were in fact undocumented opcodes in the original interpreter, shifted the value in the register VY and stored the result in VX. The CHIP-48 and SCHIP implementations instead ignored VY, and simply shifted VX.
function instructionRightShift(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempRegisterY = getNibbleFromOpcode(opcode, 1);
  var tempData;
  //console.log("right shift");
  if(compatabilityBitShift == true){
    tempData = c8RegisterArray[tempRegisterX];
  } else {
    tempData = c8RegisterArray[tempRegisterY];
  }
  
  var tempLSB = tempData & 0x01;

  tempData = tempData >>> 1; //zero fill right shift

  c8RegisterArray[tempRegisterX] = tempData;
  c8RegisterArray[VF] = tempLSB;
}

//8XYE - Vx<<=1 - Stores the most significant bit of VX in VF and then shifts VX to the left by 1.
//Wiki: CHIP-8's opcodes 8XY6 and 8XYE (the bit shift instructions), which were in fact undocumented opcodes in the original interpreter, shifted the value in the register VY and stored the result in VX. The CHIP-48 and SCHIP implementations instead ignored VY, and simply shifted VX.
function instructionLeftShift(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempRegisterY = getNibbleFromOpcode(opcode, 1);
  var tempData;
  //console.log("left shift");
  if(compatabilityBitShift == true){
    tempData = c8RegisterArray[tempRegisterX];
  } else {
    tempData = c8RegisterArray[tempRegisterY];
  }
  var tempMSB = tempData >>> 7;

  tempData = tempData << 1; //left shift
  //clamp variable size to one byte
  tempData = tempData & 0xFF;

  c8RegisterArray[tempRegisterX] = tempData;
  c8RegisterArray[VF] = tempMSB;
}

//9XY0 - if(Vx!=Vy) - Skips the next instruction if VX doesn't equal VY. (Usually the next instruction is a jump to skip a code block) 
function instructionIfNotegister(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempRegisterXData = c8RegisterArray[tempRegisterX];
  var tempRegisterY = getNibbleFromOpcode(opcode, 1);
  var tempRegisterYData = c8RegisterArray[tempRegisterY];
  //console.log("If not registers");
  //Check condition
  if(tempRegisterXData != tempRegisterYData){
    //Skip setting PC based on normal flow. 
    skipPCIncrement = true;
    //Skip the next instruction.
    c8RegisterArray[PC] += 4;
  }
}

//ANNN - I = NNN - Sets I to the address NNN. 
function instructionSetI(opcode){
  var tempData = 0;
  //console.log("Set I");
  tempData = opcode & 0x0FFF;
  c8RegisterArray[I] = tempData;
}

//BNNN- PC=V0+NNN - Jumps to the address NNN plus V0. 
function instructionJumpOffset(opcode){
  var tempAddress = opcode & 0x0FFF;
  var tempV0Data = c8RegisterArray[V0];
  //console.log("register ofset jump");
  c8RegisterArray[PC] = tempAddress + tempV0Data;
}

//CXNN - Vx=rand()&NN - Sets VX to the result of a bitwise and operation on a random number (Typically: 0 to 255) and NN. 
function instructionRand(opcode){
  var tempData = opcode & 0x00FF;
  var tempRand = randomInt(0, 255);
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  
  c8RegisterArray[tempRegisterX] = tempRand & tempData;
  //console.log("rand: " + tempRand + " & " + tempData.toString(2) + " = 0b" + c8RegisterArray[tempRegisterX]);
}

//DXYN - Draws a sprite at coordinate (VX, VY) that has a width of 8 pixels and a height of N pixels. Each row of 8 pixels is read as bit-coded starting from memory location I; I value doesn’t change after the execution of this instruction. As described above, VF is set to 1 if any screen pixels are flipped from set to unset when the sprite is drawn, and to 0 if that doesn’t happen 
function instructionDraw(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempXCoordinate = c8RegisterArray[tempRegisterX];
  var tempRegisterY = getNibbleFromOpcode(opcode, 1);
  var tempYCoordinate = c8RegisterArray[tempRegisterY];
  var tempSpriteHeight = getNibbleFromOpcode(opcode, 0);
  var tempCurrentI = c8RegisterArray[I];
  var mask;
  var curSpriteByte;
  var curBit;
  var curX = 0;
  var curY = 0;
  var oldval;
  //console.log(opcode.toString(16) + " draw X: " + tempXCoordinate + " Y: " + tempYCoordinate + " N: " + tempSpriteHeight);

  c8RegisterArray[VF] = 0x00;

  //For each row in the sprite.
  for(var curSpriteY = 0; curSpriteY < tempSpriteHeight; curSpriteY++){
    //Get the sprite data of the current row from CHIP-8 memory
    curSpriteByte = c8Memory_View.getUint8(tempCurrentI + curSpriteY);
    //Current pixel Y coordinate.
    curY = tempYCoordinate + curSpriteY;
    //If the pixel is off the screen then wrap it around to the other side if
    //wrapping is enabled or drop it if not.
    if(wrapDisplayY == true){
      curY = programmerModulo(curY, c8DisplayHeight);
    } else {
      if(curY >= c8DisplayHeight){
        break;
      }
    }
    
    //For each pixel in the current row starting at the last pixel
    for(var curSpriteX = 7; curSpriteX >= 0; curSpriteX--){
      //Current pixel value
      mask = 0x1 << curSpriteX;
      curBit = curSpriteByte & mask;
      curBit = curBit >>> curSpriteX;
      curX = tempXCoordinate + (7 - curSpriteX);

      //If the pixel is off the screen then wrap it around to the other side if
      //wrapping is enabled or drop it if not.
      if(wrapDisplayX == true){
        curX = programmerModulo(curX, c8DisplayWidth);
      } else {
        if(curX >= c8DisplayWidth){
          break;
        }
      }

      //Pixel setting is handled by XORing the curent value with the last value
      //that was in the display buffer
      oldval = c8DisplayBuffer[(curY * c8DisplayWidth) + curX];
      if(oldval == 1 && curBit == 1){
        c8RegisterArray[VF] = 0x01;
      }
      curBit = curBit ^ oldval;
      c8DisplayBuffer[(curY * c8DisplayWidth) + curX] = curBit;
    }
  }
}

//EX9E - Skips the next instruction if the key stored in VX is pressed. 
function instructionIfKeyPressed(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var keyStoredInX = c8RegisterArray[tempRegisterX];
  //console.log("skip if key pressed");
  if(inputHandler.isDown(keyStoredInX) == true){
    
    skipPCIncrement = true;
    //Skip the next instruction.
    c8RegisterArray[PC] += 4;
  }
}

//EXA1 - Skips the next instruction if the key stored in VX isn't pressed.
function instructionIfKeyNotPressed(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var keyStoredInX = c8RegisterArray[tempRegisterX];
  //console.log(opcode.toString(16) + " PC: " + c8RegisterArray[PC].toString(16) +" skip if not key pressed: " + keyStoredInX + " " + inputHandler.isDown(keyStoredInX));
  if(inputHandler.isDown(keyStoredInX) == false){
    skipPCIncrement = true;
    //Skip the next instruction.
    c8RegisterArray[PC] += 4;
  }
}

//FX07 - Vx = get_delay() - Sets VX to the value of the delay timer. 
function instructionGetDelayTimer(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  //console.log("get delay");
  c8RegisterArray[tempRegisterX] = c8RegisterArray[DT];
}

//FX0A - A key press is awaited, and then stored in VX. (Blocking Operation. All instruction halted until next key event) 
function instructionWaitForKeyPress(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  //console.log("wait for key press");
  ioBlocking = true;
  ioBlockDestinationRegister = tempRegisterX;
}


//FX15 - delay_timer(Vx) - Sets the delay timer to VX. 
function instructionSetDelayTimer(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  //console.log("set delay");
  c8RegisterArray[DT] = c8RegisterArray[tempRegisterX];
}

//FX18 - sound_timer(Vx) - Sets the sound timer to VX. 
function instructionSetSoundTimer(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  //console.log("set sound");
  c8RegisterArray[ST] = c8RegisterArray[tempRegisterX];
}

//FX1E - I +=Vx - Adds VX to I. VF is set to 1 when there is a range overflow (I+VX>0xFFF), and to 0 when there isn't.[c] 
function instructionSetDisplacedI(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempXData = c8RegisterArray[tempRegisterX];
  var tempCurrentI = c8RegisterArray[I];
  //console.log("offset I by reg");
  //Add Vx to I
  tempCurrentI += tempXData;

  //check for overflow
  if(tempCurrentI > 0xFFF){
    //On overflow set VF to 1.
    c8RegisterArray[VF] = 0x01;

    tempCurrentI = tempCurrentI & 0xFFF;
  } else {
    //No overflow
    c8RegisterArray[VF] = 0x00;
  }

  c8RegisterArray[I] = tempCurrentI;
}

//FX29 - I=sprite_addr[Vx] - Sets I to the location of the sprite for the character in VX. Characters 0-F (in hexadecimal) are represented by a 4x5 font. 
function instructionFontAddress(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempXData = c8RegisterArray[tempRegisterX];
  //console.log("font adress");
  tempXData = tempXData & 0x0F;
  c8RegisterArray[I] = c8FontStartAddress + (tempXData * 5);
}

//FX33 - Stores the binary-coded decimal representation of VX, with the most significant of three digits at the address in I, the middle digit at I plus 1, and the least significant digit at I plus 2. (In other words, take the decimal representation of VX, place the hundreds digit in memory at location in I, the tens digit at location I+1, and the ones digit at location I+2.) 
function instructionBCD(opcode){
  var tempRegisterX = getNibbleFromOpcode(opcode, 2);
  var tempXData = c8RegisterArray[tempRegisterX];
  var tempCurrentI = c8RegisterArray[I];

  var hundredsDigit = Math.trunc(tempXData / 100);
  var tensDigit = Math.trunc((tempXData / 10) % 10);
  var onesDigit = tempXData % 10;

  //console.log("store bcd");
  c8Memory_View.setUint8(tempCurrentI + 0, hundredsDigit);
  c8Memory_View.setUint8(tempCurrentI + 1, tensDigit);
  c8Memory_View.setUint8(tempCurrentI + 2, onesDigit);
}

//FX55 - Stores V0 to VX (including VX) in memory starting at address I. The offset from I is increased by 1 for each value written, but I itself is left unmodified.[d] 
//Wiki Note: In the original CHIP-8 implementation, and also in CHIP-48, I is left incremented after this instruction had been executed. In SCHIP, I is left unmodified.
function instructionSaveRegisters(opcode){
  var tempXEndRegister = getNibbleFromOpcode(opcode, 2);
  var tempCurrentI = c8RegisterArray[I];
  var currentRegisterData = 0;
  //console.log("Save Registers called");
  //From 0 to the value in register Vx
  for(var i = 0; i <= tempXEndRegister; i++){
    currentRegisterData = c8RegisterArray[i];
    c8Memory_View.setUint8(tempCurrentI + i, currentRegisterData);
  }
  if(compatabilitySaveLoad == false){
    c8RegisterArray[I] = tempCurrentI + tempXEndRegister + 1;
  }
}

//FX65 - Fills V0 to VX (including VX) with values from memory starting at address I. The offset from I is increased by 1 for each value written, but I itself is left unmodified.[d]
//Wiki Note: In the original CHIP-8 implementation, and also in CHIP-48, I is left incremented after this instruction had been executed. In SCHIP, I is left unmodified.
function instructionLoadRegisters(opcode){
  var tempXEndRegister = getNibbleFromOpcode(opcode, 2);
  var tempCurrentI = c8RegisterArray[I];
  //console.log("Load Registers called: " + tempCurrentI);
  //From 0 to the value in register Vx
  for(var i = 0; i <= tempXEndRegister; i++){
    c8RegisterArray[i] = c8Memory_View.getUint8(tempCurrentI + i);
  }
  if(compatabilitySaveLoad == false){
    c8RegisterArray[I] = tempCurrentI + tempXEndRegister + 1;
  }
}
//c8_data.js - ROMs and Font data in the form of Arrays.

//The EricLj boot ROM
var startup_rom = [
  0x12E2, 0xA2EA, 0x6104, 0x6203, 0x6305, 0xD125, 0x7108, 0xF31E, 0x6F3B, 0x8F17, 0x4F00, 0x120A, 0x00EE, 0xA30D, 0x611D, 0x620A, 0xD126, 0x00EE, 0xA313, 0x6116, 0x6212, 0x6309, 0xD129, 
  0x7108, 0xF31E, 0x6F2B, 0x8F17, 0x4F00, 0x122C, 0x00EE, 0x00EE, 0xA32E, 0x3701, 0x125A, 0x3500, 0xD651, 0x7501, 0x351F, 0x1258, 0x6500, 0x8A60, 0x6B01, 0xC63F, 0x125A, 0xD651, 0x00EE, 
  0x4B00, 0x128E, 0x3B01, 0x1268, 0x2290, 0x6B02, 0x3B02, 0x1272, 0x2290, 0x229C, 0x6B03, 0x3B03, 0x127C, 0x229C, 0x22A8, 0x6B04, 0x3B04, 0x1286, 0x22A8, 0x22B4, 0x6B05, 0x3B05, 0x128E, 
  0x22B4, 0x6B00, 0x00EE, 0xA32F, 0x81A0, 0x71FF, 0x621E, 0xD121, 0x00EE, 0xA330, 0x81A0, 0x71FE, 0x621D, 0xD122, 0x00EE, 0xA332, 0x81A0, 0x71FD, 0x621C, 0xD122, 0x00EE, 0xA334, 0x81A0, 
  0x71FD, 0x621C, 0xD121, 0x00EE, 0x6100, 0x621F, 0xF129, 0xD121, 0x7104, 0x6F40, 0x8F17, 0x4F00, 0x12C6, 0x00EE, 0x6701, 0x6500, 0x22C0, 0x2202, 0x221A, 0x2224, 0x00EE, 0x22D4, 0x223E, 
  0x225C, 0x12E4, 0xE989, 0x8F89, 0xE977, 0x2527, 0x2474, 0x0E0A, 0x6E0A, 0x0E38, 0x2033, 0x223A, 0x0000, 0xEAAA, 0xAE80, 0xB9AB, 0xB9A9, 0x0000, 0xBB2A, 0xBA80, 0x80EA, 0xAAE4, 0x08E0, 
  0x8080, 0xCC8A, 0x88E8, 0x0000, 0x0282, 0x02BA, 0xA2A2, 0xBB00, 0x0000, 0x0000, 0x7020, 0x20A0, 0x20E0, 0x80A0, 0x8850, 0x8244, 0x8200 ];

//CHIP-8 image ROM
var chip8_rom = [
  0x00E0, 0xA248, 0x6000, 0x611E, 0x6200, 0xD202, 0xD212, 0x7208, 0x3240, 0x120A, 0x6000, 0x613E, 0x6202, 0xA24A, 0xD02E, 0xD12E, 0x720E, 0xD02E, 0xD12E, 0xA258, 0x600B, 0x6108, 0xD01F, 0x700A, 0xA267,
  0xD01F, 0x700A, 0xA276, 0xD01F, 0x7003, 0xA285, 0xD01F, 0x700A, 0xA294, 0xD01F, 0x1246, 0xFFFF, 0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xFF80, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080,
  0x8080, 0xFF81, 0x8181, 0x8181, 0x8181, 0xFF81, 0x8181, 0x8181, 0x8181, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x80FF, 0x8181, 0x8181, 0x8181, 0xFF80, 0x8080, 0x8080, 0x8080, 0xFF81,
  0x8181, 0x8181, 0x81FF, 0x8181, 0x8181, 0x8181, 0xFFFF];


//Space invaders ROM
var invaders_rom = [
  0x1225, 0x5350, 0x4143, 0x4520, 0x494E, 0x5641, 0x4445, 0x5253, 0x2030, 0x2E39, 0x3120, 0x4279, 0x2044, 0x6176, 0x6964, 0x2057, 0x494E, 0x5445, 0x5260, 0x0061, 0x0062, 0x08A3, 0xDDD0, 
  0x1871, 0x08F2, 0x1E31, 0x2012, 0x2D70, 0x0861, 0x0030, 0x4012, 0x2D69, 0x056C, 0x156E, 0x0023, 0x9160, 0x0AF0, 0x15F0, 0x0730, 0x0012, 0x4B23, 0x917E, 0x0112, 0x4566, 0x0068, 0x1C69, 
  0x006A, 0x046B, 0x0A6C, 0x046D, 0x3C6E, 0x0F00, 0xE023, 0x7523, 0x51FD, 0x1560, 0x04E0, 0x9E12, 0x7D23, 0x7538, 0x0078, 0xFF23, 0x7560, 0x06E0, 0x9E12, 0x8B23, 0x7538, 0x3978, 0x0123, 
  0x7536, 0x0012, 0x9F60, 0x05E0, 0x9E12, 0xE966, 0x0165, 0x1B84, 0x80A3, 0xD9D4, 0x51A3, 0xD9D4, 0x5175, 0xFF35, 0xFF12, 0xAD66, 0x0012, 0xE9D4, 0x513F, 0x0112, 0xE9D4, 0x5166, 0x0083, 
  0x4073, 0x0383, 0xB562, 0xF883, 0x2262, 0x0833, 0x0012, 0xC923, 0x7D82, 0x0643, 0x0812, 0xD333, 0x1012, 0xD523, 0x7D82, 0x0633, 0x1812, 0xDD23, 0x7D82, 0x0643, 0x2012, 0xE733, 0x2812, 
  0xE923, 0x7D3E, 0x0013, 0x0779, 0x0649, 0x1869, 0x006A, 0x046B, 0x0A6C, 0x047D, 0xF46E, 0x0F00, 0xE023, 0x5123, 0x75FD, 0x1512, 0x6FF7, 0x0737, 0x0012, 0x6FFD, 0x1523, 0x518B, 0xA43B, 
  0x1213, 0x1B7C, 0x026A, 0xFC3B, 0x0213, 0x237C, 0x026A, 0x0423, 0x513C, 0x1812, 0x6F00, 0xE0A4, 0xDD60, 0x1461, 0x0862, 0x0FD0, 0x1F70, 0x08F2, 0x1E30, 0x2C13, 0x3360, 0xFFF0, 0x15F0, 
  0x0730, 0x0013, 0x41F0, 0x0A00, 0xE0A7, 0x06FE, 0x6512, 0x25A3, 0xC1F9, 0x1E61, 0x0823, 0x6981, 0x0623, 0x6981, 0x0623, 0x6981, 0x0623, 0x697B, 0xD000, 0xEE80, 0xE080, 0x1230, 0x00DB, 
  0xC67B, 0x0C00, 0xEEA3, 0xD960, 0x1CD8, 0x0400, 0xEE23, 0x518E, 0x2323, 0x5160, 0x05F0, 0x18F0, 0x15F0, 0x0730, 0x0013, 0x8900, 0xEE6A, 0x008D, 0xE06B, 0x04E9, 0xA112, 0x57A6, 0x0CFD, 
  0x1EF0, 0x6530, 0xFF13, 0xAF6A, 0x006B, 0x046D, 0x016E, 0x0113, 0x97A5, 0x0AF0, 0x1EDB, 0xC67B, 0x087D, 0x017A, 0x013A, 0x0713, 0x9700, 0xEE3C, 0x7EFF, 0xFF99, 0x997E, 0xFFFF, 0x2424, 
  0xE77E, 0xFF3C, 0x3C7E, 0xDB81, 0x423C, 0x7EFF, 0xDB10, 0x387C, 0xFE00, 0x007F, 0x003F, 0x007F, 0x0000, 0x0001, 0x0101, 0x0303, 0x0303, 0x0000, 0x3F20, 0x2020, 0x2020, 0x2020, 0x203F, 
  0x0808, 0xFF00, 0x00FE, 0x00FC, 0x00FE, 0x0000, 0x007E, 0x4242, 0x6262, 0x6262, 0x0000, 0xFF00, 0x0000, 0x0000, 0x0000, 0x00FF, 0x0000, 0xFF00, 0x7D00, 0x417D, 0x057D, 0x7D00, 0x00C2, 
  0xC2C6, 0x446C, 0x2838, 0x0000, 0xFF00, 0x0000, 0x0000, 0x0000, 0x00FF, 0x0000, 0xFF00, 0xF710, 0x14F7, 0xF704, 0x0400, 0x007C, 0x44FE, 0xC2C2, 0xC2C2, 0x0000, 0xFF00, 0x0000, 0x0000, 
  0x0000, 0x00FF, 0x0000, 0xFF00, 0xEF20, 0x28E8, 0xE82F, 0x2F00, 0x00F9, 0x85C5, 0xC5C5, 0xC5F9, 0x0000, 0xFF00, 0x0000, 0x0000, 0x0000, 0x00FF, 0x0000, 0xFF00, 0xBE00, 0x2030, 0x20BE, 
  0xBE00, 0x00F7, 0x04E7, 0x8585, 0x84F4, 0x0000, 0xFF00, 0x0000, 0x0000, 0x0000, 0x00FF, 0x0000, 0xFF00, 0x007F, 0x003F, 0x007F, 0x0000, 0x00EF, 0x28EF, 0x00E0, 0x606F, 0x0000, 0xFF00, 
  0x0000, 0x0000, 0x0000, 0x00FF, 0x0000, 0xFF00, 0x00FE, 0x00FC, 0x00FE, 0x0000, 0x00C0, 0x00C0, 0xC0C0, 0xC0C0, 0x0000, 0xFC04, 0x0404, 0x0404, 0x0404, 0x04FC, 0x1010, 0xFFF9, 0x81B9, 
  0x8B9A, 0x9AFA, 0x00FA, 0x8A9A, 0x9A9B, 0x99F8, 0xE625, 0x25F4, 0x3434, 0x3400, 0x1714, 0x3437, 0x3626, 0xC7DF, 0x5050, 0x5CD8, 0xD8DF, 0x00DF, 0x111F, 0x121B, 0x19D9, 0x7C44, 0xFE86, 
  0x8686, 0xFC84, 0xFE82, 0x82FE, 0xFE80, 0xC0C0, 0xC0FE, 0xFC82, 0xC2C2, 0xC2FC, 0xFE80, 0xF8C0, 0xC0FE, 0xFE80, 0xF0C0, 0xC0C0, 0xFE80, 0xBE86, 0x86FE, 0x8686, 0xFE86, 0x8686, 0x1010, 
  0x1010, 0x1010, 0x1818, 0x1848, 0x4878, 0x9C90, 0xB0C0, 0xB09C, 0x8080, 0xC0C0, 0xC0FE, 0xEE92, 0x9286, 0x8686, 0xFE82, 0x8686, 0x8686, 0x7C82, 0x8686, 0x867C, 0xFE82, 0xFEC0, 0xC0C0, 
  0x7C82, 0xC2CA, 0xC47A, 0xFE86, 0xFE90, 0x9C84, 0xFEC0, 0xFE02, 0x02FE, 0xFE10, 0x3030, 0x3030, 0x8282, 0xC2C2, 0xC2FE, 0x8282, 0x82EE, 0x3810, 0x8686, 0x9692, 0x92EE, 0x8244, 0x3838, 
  0x4482, 0x8282, 0xFE30, 0x3030, 0xFE02, 0x1EF0, 0x80FE, 0x0000, 0x0000, 0x0606, 0x0000, 0x0060, 0x60C0, 0x0000, 0x0000, 0x0000, 0x1818, 0x1818, 0x0018, 0x7CC6, 0x0C18, 0x0018, 0x0000, 
  0xFEFE, 0x0000, 0xFE82, 0x8686, 0x86FE, 0x0808, 0x0818, 0x1818, 0xFE02, 0xFEC0, 0xC0FE, 0xFE02, 0x1E06, 0x06FE, 0x84C4, 0xC4FE, 0x0404, 0xFE80, 0xFE06, 0x06FE, 0xC0C0, 0xC0FE, 0x82FE, 
  0xFE02, 0x0206, 0x0606, 0x7C44, 0xFE86, 0x86FE, 0xFE82, 0xFE06, 0x0606, 0x44FE, 0x4444, 0xFE44, 0xA8A8, 0xA8A8, 0xA8A8, 0xA86C, 0x5A00, 0x0C18, 0xA830, 0x4E7E, 0x0012, 0x1866, 0x6CA8, 
  0x5A66, 0x5424, 0x6600, 0x4848, 0x1812, 0xA806, 0x90A8, 0x1200, 0x7E30, 0x12A8, 0x8430, 0x4E72, 0x1866, 0xA8A8, 0xA8A8, 0xA8A8, 0x9054, 0x78A8, 0x4878, 0x6C72, 0xA812, 0x186C, 0x7266, 
  0x5490, 0xA872, 0x2A18, 0xA830, 0x4E7E, 0x0012, 0x1866, 0x6CA8, 0x7254, 0xA85A, 0x6618, 0x7E18, 0x4E72, 0xA872, 0x2A18, 0x3066, 0xA830, 0x4E7E, 0x006C, 0x3054, 0x4E9C, 0xA8A8, 0xA8A8, 
  0xA8A8, 0xA848, 0x547E, 0x18A8, 0x9054, 0x7866, 0xA86C, 0x2A30, 0x5AA8, 0x8430, 0x722A, 0xA8D8, 0xA800, 0x4E12, 0xA8E4, 0xA2A8, 0x004E, 0x12A8, 0x6C2A, 0x5454, 0x72A8, 0x8430, 0x722A, 
  0xA8DE, 0x9CA8, 0x722A, 0x18A8, 0x0C54, 0x485A, 0x7872, 0x1866, 0xA866, 0x185A, 0x5466, 0x726C, 0xA872, 0x2A00, 0x72A8, 0x722A, 0x18A8, 0x304E, 0x7E00, 0x1218, 0x666C, 0xA800, 0x6618, 
  0xA830, 0x4E0C, 0x6618, 0x006C, 0x304E, 0x24A8, 0x722A, 0x1830, 0x66A8, 0x1E54, 0x660C, 0x189C, 0xA824, 0x5454, 0x12A8, 0x4278, 0x0C3C, 0xA8AE, 0xA8A8, 0xA8A8, 0xA8A8, 0xA8FF, 0x0000, 
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000];

var submarine_rom = [
  0xA2CD, 0x6938, 0x6A1E, 0xD9A2, 0xA2D0, 0x6B00, 0x6C1A, 0xDBC2, 0xA2D4, 0x643C, 0x6606, 0xD463, 0x6700, 0x6819, 0x22A2, 0x22AC, 0x4800, 0x12D8, 0x6509, 0xA2D7, 0x6300, 0x6D05, 0xEDA1, 
  0x6301, 0x8E40, 0xEDA1, 0xDE51, 0x123C, 0xEDA1, 0x22D8, 0xA2D4, 0xD463, 0x1242, 0x74FF, 0xD463, 0xA2D0, 0xDBC2, 0xCD04, 0x8BD4, 0xDBC2, 0x3F00, 0x1292, 0xA2CD, 0xD9A2, 0xCD07, 0x4D00, 
  0x7903, 0x79FD, 0xD9A2, 0x3F00, 0x128C, 0x4300, 0x122A, 0xA2D7, 0xDE51, 0x451F, 0x1286, 0x7502, 0xF318, 0xDE51, 0x3F01, 0x123C, 0x6D1F, 0x8D52, 0x4D1F, 0x128C, 0x1292, 0x22AC, 0x78FF, 
  0x121E, 0x22A2, 0x7705, 0x1296, 0x22A2, 0x770A, 0x22A2, 0x6D03, 0xFD18, 0xA2D7, 0xDE51, 0x1286, 0xA2F8, 0xF733, 0x6300, 0x22B6, 0x00EE, 0xA2F8, 0xF833, 0x6332, 0x22B6, 0x00EE, 0x6D00, 
  0xF265, 0xF029, 0xD3D5, 0x7305, 0xF129, 0xD3D5, 0x7305, 0xF229, 0xD3D5, 0x00EE, 0x0108, 0x7F7C, 0x083E, 0x6008, 0x183C, 0xFF08, 0xA300, 0x6311, 0x6D0B, 0xD3D5, 0xA305, 0x6319, 0xD3D5, 
  0xA30A, 0x6323, 0xD3D5, 0xA30F, 0x632B, 0xD3D5, 0x6300, 0x12F4, 0x6D0B, 0x0001, 0x0400, 0x00EE, 0x0001, 0xEE8A, 0x8AAA, 0xEEEF, 0xA5A5, 0xA5EF, 0x7A2A, 0x3B29, 0x79BA, 0xA2B2, 0x203A, 
  0x343A, 0x3CD6, 0x541C, 0x0C40, 0x9E25, 0x680C ];

var danm8ku_rom = [
  0x14E2, 0xE000, 0x8079, 0x0060, 0x0C0C, 0x3E0C, 0x9BFD, 0x2063, 0x1C0C, 0x3E0C, 0xFBCD, 0x70E7, 0x1C1E, 0x230C, 0xF3FD, 0xF8CF, 0x1E1F, 0x210C, 0xC3FD, 0xD8CD, 0x1E9B, 0x211C, 0xE3CD, 
  0xDDDD, 0x9EBF, 0x2198, 0xF38D, 0xCDD9, 0x9F3F, 0x23F8, 0xF99D, 0xCDD9, 0xDB71, 0x3FF0, 0x98F9, 0xCDD9, 0xD960, 0x3C00, 0x00F8, 0x0000, 0x0000, 0x001E, 0x1010, 0x101E, 0x1010, 0x10EA, 
  0xAAAA, 0xAAEE, 0xEA8A, 0x8E8A, 0xEAAE, 0xA84C, 0x484E, 0xE484, 0xE420, 0xE4FF, 0x0000, 0x0000, 0x0000, 0x00A0, 0xA000, 0x0050, 0x5000, 0x0060, 0x6000, 0x6000, 0x6000, 0xC0A0, 0x6000, 
  0x3050, 0x6000, 0x0005, 0x0F00, 0x0060, 0x0060, 0x0060, 0xA0C0, 0x0060, 0x5030, 0x0060, 0x6000, 0x00C0, 0xC000, 0x0030, 0x3000, 0xC060, 0xC000, 0x6060, 0x0000, 0xC0C0, 0x0000, 0x3030, 
  0x0000, 0xA040, 0xA000, 0x0000, 0x6060, 0x0000, 0xC0C0, 0x0000, 0x3030, 0x00E0, 0x6A38, 0x6B0B, 0xA203, 0xDAB1, 0xF065, 0x7AF8, 0x4AF8, 0x7B01, 0x4AF8, 0x6A38, 0x3B15, 0x12D2, 0x6405, 
  0xE4A1, 0x00EE, 0x6407, 0xE4A1, 0x00EE, 0x6408, 0xE4A1, 0x00EE, 0x6409, 0xE4A1, 0x00EE, 0xA202, 0x6000, 0xC11F, 0x71F6, 0x6F0B, 0x8F17, 0x4F00, 0x12E4, 0x710A, 0x623C, 0x4020, 0x12E4, 
  0xD012, 0xD212, 0x3F01, 0x12E4, 0x7004, 0x72FC, 0x1310, 0x00E0, 0x631F, 0x653F, 0xA265, 0x601A, 0x610E, 0xD015, 0x7008, 0xA26A, 0xD015, 0xA000, 0x6003, 0x6101, 0x6220, 0x6620, 0x6420, 
  0x8204, 0x8605, 0x8415, 0x8720, 0x8B10, 0x2366, 0x8760, 0x2366, 0x8B40, 0x2366, 0x8720, 0x2366, 0x7001, 0x7101, 0x4020, 0x7001, 0x8052, 0x133C, 0x8B32, 0x8752, 0x6C00, 0x6F15, 0x8F77, 
  0x4F00, 0x7C01, 0x6F29, 0x8F75, 0x4F00, 0x7C01, 0x6F0A, 0x8FB7, 0x4F00, 0x7C01, 0x6F13, 0x8FB5, 0x4F00, 0x7C01, 0x6F00, 0x8FC5, 0x4F00, 0xD7B4, 0x00EE, 0x00E0, 0xA25B, 0x6019, 0x610D, 
  0xD015, 0xA260, 0x7008, 0xD015, 0xF00A, 0xF00A, 0x00EE, 0x60FF, 0x2420, 0xA29E, 0x833E, 0x833E, 0xF31E, 0xD454, 0x00EE, 0x8C00, 0x800E, 0x8AF0, 0x8604, 0x80C0, 0x8AF2, 0x88F0, 0x88A5, 
  0x88A5, 0x8484, 0x8C10, 0x811E, 0x8AF0, 0x8714, 0x81C0, 0x8AF2, 0x8DF0, 0x8DA5, 0x8DA5, 0x85D4, 0x6FFA, 0x8F07, 0x3F00, 0x13F2, 0x8024, 0x3F00, 0x8025, 0x8380, 0x6F01, 0x8F85, 0x4F00, 
  0x6302, 0x6F01, 0x8FD5, 0x4F00, 0x6D02, 0x8DDE, 0x8DDE, 0x83D1, 0x6F3E, 0x8F45, 0x3F00, 0x1416, 0x23AC, 0x141E, 0x6F1D, 0x8F55, 0x4F00, 0x23AC, 0x00EE, 0xABB8, 0xFB1E, 0xF755, 0x00EE, 
  0x6B00, 0xABB8, 0xFB1E, 0xF765, 0x40FF, 0x143E, 0xA272, 0x833E, 0x833E, 0xF31E, 0xD454, 0x7B08, 0x3B00, 0x142A, 0x00EE, 0x6B00, 0xABB8, 0xFB1E, 0xF765, 0x30FF, 0x23BC, 0x30FF, 0x2420, 
  0x7B08, 0x3B00, 0x1448, 0x00EE, 0xAB54, 0xF755, 0xA28E, 0xF065, 0xABB8, 0xF01E, 0xF765, 0x6800, 0x30FF, 0x1480, 0xA28E, 0xF065, 0x8A00, 0x7008, 0xA28E, 0xF055, 0x6801, 0xAB54, 0xF765, 
  0x6300, 0xABB8, 0xFA1E, 0x00EE, 0xFF07, 0x3F00, 0x148C, 0x6F01, 0xFF15, 0x00EE, 0xA28F, 0xF165, 0x8316, 0x8206, 0xA2AA, 0xD233, 0x6205, 0xE29E, 0x14B0, 0x71FF, 0x4101, 0x6102, 0x6207, 
  0xE29E, 0x14BC, 0x70FF, 0x4001, 0x6002, 0x6209, 0xE29E, 0x14C8, 0x7001, 0x4023, 0x6022, 0x6208, 0xE29E, 0x14D4, 0x7101, 0x413A, 0x6139, 0xA28F, 0xF155, 0x8316, 0x8206, 0xA2AA, 0xD233, 
  0x00EE, 0x22CA, 0x6900, 0x6000, 0xA2AD, 0xF055, 0x6B00, 0xA26F, 0xF765, 0xABB8, 0xF755, 0x7B01, 0x3B20, 0x14F4, 0x00E0, 0xA253, 0x6039, 0x6100, 0xD018, 0x7108, 0x6F20, 0x8F17, 0x4F00, 
  0x1504, 0xA28F, 0xF165, 0x8316, 0x8206, 0xA2AA, 0xD233, 0x2428, 0x2498, 0x4F01, 0x1534, 0x2446, 0x8D90, 0x255C, 0x4DFF, 0x2538, 0x7901, 0x248C, 0x151C, 0x2396, 0x14E2, 0xA2AD, 0xF065, 
  0x7001, 0xA2AD, 0xF055, 0x623D, 0x6101, 0x8006, 0x3F00, 0x155A, 0x4001, 0x1556, 0x7104, 0x70FF, 0x154C, 0xA2BA, 0xD213, 0x00EE, 0xA2AD, 0xF065, 0x6F01, 0x80F2, 0x3001, 0x156E, 0x4D8C, 
  0x69FE, 0x1574, 0xA2AD, 0xF065, 0xB715, 0x00EE, 0xF755, 0xA27E, 0xD454, 0x00EE, 0x6007, 0x80D2, 0x3007, 0x1596, 0xCC1F, 0x245E, 0x6202, 0xC03F, 0x70AA, 0x6437, 0x85C0, 0x2576, 0x1574, 
  0x6003, 0x80D2, 0x3003, 0x15CC, 0xCC1F, 0x245E, 0x3801, 0x15B6, 0xC07F, 0x707F, 0x6439, 0x85C0, 0x6123, 0x6200, 0x2576, 0xCC1F, 0x245E, 0x3801, 0x15CC, 0xC07F, 0x707F, 0x6439, 0x85C0, 
  0x619B, 0x6200, 0x2576, 0x1574, 0x0000, 0x00CE, 0x32D2, 0x2ED6, 0x2ADA, 0x26DE, 0x22E2, 0x1EE6, 0x1AEA, 0x16EE, 0x12F2, 0x0EF6, 0x0AFA, 0x06FE, 0x02FA, 0x82F6, 0x86F2, 0x8AEE, 0x8EEA, 
  0x92E6, 0x96E2, 0x9ADE, 0x9EDA, 0xA2D6, 0xA6D2, 0xAACE, 0xAEA5, 0xCEFD, 0x33A5, 0xCEF2, 0x6532, 0x0016, 0x2540, 0x0171, 0x0A40, 0x0271, 0x1481, 0x1EF1, 0x1EF1, 0x6524, 0x5E64, 0x3C65, 
  0x1025, 0x7615, 0x7460, 0x0F80, 0xD230, 0x0F16, 0x55CC, 0x1F64, 0x3C85, 0xC060, 0x9661, 0x0024, 0x5E62, 0x0825, 0x7624, 0x5E62, 0x0425, 0x7664, 0x2D60, 0x4B24, 0x5E61, 0x8725, 0x7624, 
  0x5E61, 0x0A25, 0x7615, 0x74CC, 0x1F24, 0x5E64, 0x3285, 0xC060, 0xE661, 0x0025, 0x7624, 0x5E64, 0x3625, 0x7624, 0x5E64, 0x3A25, 0x7600, 0xEE60, 0x1F80, 0xD230, 0x1F16, 0x91CC, 0x1F24, 
  0x5E64, 0x2385, 0xC060, 0x4261, 0x0C62, 0x0225, 0x7624, 0x5E61, 0x8225, 0x764D, 0x4026, 0x574D, 0x8026, 0x574D, 0xC026, 0x5715, 0x7460, 0x0780, 0xD230, 0x0716, 0xC360, 0x2080, 0xD240, 
  0x0016, 0xB565, 0x1C61, 0xA016, 0xB965, 0x0061, 0x1EC4, 0x1F74, 0x1F60, 0xBE24, 0x5E25, 0x7615, 0x7460, 0x0780, 0xD230, 0x0716, 0xE760, 0x1080, 0xD265, 0x1C61, 0xA030, 0x0016, 0xDD65, 
  0x0061, 0x1E60, 0x0062, 0x00C4, 0x1F24, 0x5E25, 0x7615, 0x7460, 0x0F80, 0xD230, 0x0F17, 0x0F85, 0xD685, 0x5685, 0x5664, 0x3260, 0x5F61, 0x0062, 0x0524, 0x5E25, 0x7661, 0x1424, 0x5E25, 
  0x7661, 0x9424, 0x5E25, 0x7615, 0x7423, 0x2214, 0xE215, 0x7E15, 0x9816, 0x0316, 0x2716, 0x7316, 0x9F16, 0xC516, 0xE917, 0x1100 ];

var pong_rom = [
  0x6A02, 0x6B0C, 0x6C3F, 0x6D0C, 0xA2EA, 0xDAB6, 0xDCD6, 0x6E00, 0x22D4, 0x6603, 0x6802, 0x6060, 0xF015, 0xF007, 0x3000, 0x121A, 0xC717, 0x7708, 0x69FF, 0xA2F0, 0xD671, 0xA2EA, 0xDAB6, 
  0xDCD6, 0x6001, 0xE0A1, 0x7BFE, 0x6004, 0xE0A1, 0x7B02, 0x601F, 0x8B02, 0xDAB6, 0x8D70, 0xC00A, 0x7DFE, 0x4000, 0x7D02, 0x6000, 0x601F, 0x8D02, 0xDCD6, 0xA2F0, 0xD671, 0x8684, 0x8794, 
  0x603F, 0x8602, 0x611F, 0x8712, 0x4602, 0x1278, 0x463F, 0x1282, 0x471F, 0x69FF, 0x4700, 0x6901, 0xD671, 0x122A, 0x6802, 0x6301, 0x8070, 0x80B5, 0x128A, 0x68FE, 0x630A, 0x8070, 0x80D5, 
  0x3F01, 0x12A2, 0x6102, 0x8015, 0x3F01, 0x12BA, 0x8015, 0x3F01, 0x12C8, 0x8015, 0x3F01, 0x12C2, 0x6020, 0xF018, 0x22D4, 0x8E34, 0x22D4, 0x663E, 0x3301, 0x6603, 0x68FE, 0x3301, 0x6802, 
  0x1216, 0x79FF, 0x49FE, 0x69FF, 0x12C8, 0x7901, 0x4902, 0x6901, 0x6004, 0xF018, 0x7601, 0x4640, 0x76FE, 0x126C, 0xA2F2, 0xFE33, 0xF265, 0xF129, 0x6414, 0x6500, 0xD455, 0x7415, 0xF229, 
  0xD455, 0x00EE, 0x8080, 0x8080, 0x8080, 0x8000, 0x0000, 0x0000 ];

var snake_rom = [
  0x16D6, 0x8000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6090, 0x90F0, 0x90E0, 0x90E0, 0x90E0, 0x6080, 0x8080, 0x60E0, 0x9090, 0x90E0, 0xE080, 0xE080, 0xE0E0, 0x80E0, 0x8080, 0x7080, 0xB090, 0x7090, 
  0x90F0, 0x9090, 0xE040, 0x4040, 0xE030, 0x1010, 0x9060, 0x90A0, 0xC0A0, 0x9080, 0x8080, 0x80E0, 0x88D8, 0xA888, 0x8890, 0xD0B0, 0x9090, 0x6090, 0x9090, 0x60E0, 0x9090, 0xE080, 0x6090, 
  0x90B0, 0x60E0, 0x9090, 0xE090, 0x7080, 0x6010, 0xE0E0, 0x4040, 0x4040, 0x9090, 0x9090, 0x6090, 0x90A0, 0xA040, 0x88A8, 0xA8A8, 0x5090, 0x9060, 0x9090, 0x9090, 0x7010, 0x60E0, 0x2040, 
  0x80E0, 0x0070, 0x9090, 0x7080, 0xE090, 0x90E0, 0x0060, 0x8080, 0x6010, 0x7090, 0x9070, 0x0060, 0xB0C0, 0x6020, 0x40E0, 0x4040, 0x7090, 0xF010, 0x6080, 0xE090, 0x9090, 0x4000, 0x4040, 
  0x4040, 0x0040, 0x4080, 0x8090, 0xA0E0, 0x9040, 0x4040, 0x4040, 0x00F0, 0xA8A8, 0xA800, 0xE090, 0x9090, 0x0060, 0x9090, 0x60E0, 0x90E0, 0x8080, 0x7090, 0xF010, 0x1000, 0xA0C0, 0x8080, 
  0x0070, 0xC030, 0xE040, 0xE040, 0x4020, 0x0090, 0x9090, 0x7000, 0x9090, 0xA040, 0x00A8, 0xA850, 0x5000, 0xA040, 0x40A0, 0x0090, 0x7010, 0x6000, 0xF020, 0x40F0, 0x0000, 0x0000, 0x8080, 
  0x8080, 0x0080, 0xE010, 0x6000, 0x4000, 0x00E0, 0x0000, 0x0080, 0x0080, 0x0050, 0xF850, 0xF850, 0x4080, 0x8080, 0x4080, 0x4040, 0x4080, 0x00A0, 0x40A0, 0x0000, 0x40E0, 0x4000, 0x0000, 
  0x0000, 0x000F, 0x3E72, 0xF81C, 0x0402, 0x0241, 0x7EFC, 0x0040, 0x4178, 0x7C6E, 0x6361, 0x7030, 0x3038, 0x00C0, 0xC0E1, 0xE163, 0x6366, 0xC747, 0x468E, 0x0241, 0xC1E1, 0xF131, 0x3919, 
  0xE989, 0x0808, 0x080C, 0x0E0C, 0x8CB8, 0xB0F0, 0xD8CE, 0xCFE6, 0x003F, 0x4742, 0x4062, 0x7E64, 0x6071, 0x3F36, 0x000F, 0x2B1E, 0x2C2C, 0x3E16, 0x0012, 0x032D, 0x283E, 0x2C2D, 0x1A2B, 
  0x2D06, 0x1A26, 0x1E3E, 0x282F, 0x1E2B, 0x3512, 0x1C28, 0x2B1E, 0x3811, 0x1E1C, 0x282B, 0x1D38, 0x0000, 0xA5BC, 0xF155, 0x8150, 0x8260, 0x6300, 0x0000, 0xF31E, 0xF065, 0x8500, 0xA404, 
  0x6E05, 0x3000, 0x25E8, 0xD125, 0x7105, 0x4516, 0x7101, 0x4526, 0x7101, 0x452B, 0x71FF, 0x452D, 0x71FF, 0x7301, 0x5340, 0x15BC, 0x00EE, 0xFE1E, 0x70FF, 0x3000, 0x15E8, 0x00EE, 0x7901, 
  0xA204, 0xF91E, 0xF91E, 0x80A0, 0x81B0, 0xF155, 0x00EE, 0x8290, 0x82D5, 0xA204, 0xF21E, 0xF21E, 0xF165, 0x00EE, 0x7D01, 0xC73F, 0xC81F, 0x00EE, 0x6100, 0x6200, 0x6300, 0x4000, 0x163A, 
  0x70FF, 0x7101, 0x310A, 0x1636, 0x6100, 0x7201, 0x320A, 0x1636, 0x6200, 0x7301, 0x3000, 0x1622, 0xF129, 0x6A37, 0xDAB5, 0xF229, 0x6A31, 0xDAB5, 0xF329, 0x6A2B, 0xDAB5, 0x00EE, 0x00E0, 
  0x7DFB, 0xF085, 0x8AD0, 0x8B00, 0x4A00, 0x166A, 0x4B00, 0x1666, 0x7AFF, 0x7BFF, 0x1658, 0x80D0, 0xF075, 0x8C00, 0x60A5, 0x6199, 0x640A, 0x6508, 0x6603, 0x25B2, 0x60A5, 0x61A3, 0x6406, 
  0x6505, 0x660E, 0x25B2, 0x80D0, 0x6B0E, 0x2618, 0x60A5, 0x61A9, 0x6407, 0x6505, 0x6616, 0x25B2, 0x80C0, 0x6B16, 0x2618, 0xFF0A, 0x00E0, 0x6000, 0x6100, 0x6200, 0x6300, 0x6400, 0x6500, 
  0x6600, 0x6700, 0x6F40, 0xA204, 0xF755, 0x7FFF, 0x3F00, 0x16B4, 0x16D6, 0x4101, 0xD781, 0x2610, 0x1798, 0x6C04, 0x1758, 0x6C03, 0x1758, 0x6C02, 0x1758, 0x6C01, 0x1758, 0x6C00, 0x6D04, 
  0x6A20, 0x6B10, 0x6900, 0x2610, 0x6004, 0x6106, 0x620C, 0x6304, 0xA53F, 0xD30C, 0x71FF, 0x7308, 0xF21E, 0x3100, 0x16EC, 0x60A5, 0x6187, 0x640A, 0x6504, 0x6612, 0x25B2, 0x60A5, 0x6191, 
  0x6408, 0x651C, 0x6618, 0x25B2, 0xFF0A, 0x4F05, 0x6C04, 0x4F07, 0x6C03, 0x4F08, 0x6C02, 0x4F09, 0x6C01, 0x6000, 0x00E0, 0x4002, 0x1730, 0x2602, 0xA202, 0xD011, 0x6F05, 0x4C02, 0x173A, 
  0xEFA1, 0x16C6, 0x6F07, 0x4C01, 0x1744, 0xEFA1, 0x16CA, 0x6F08, 0x4C04, 0x174E, 0xEFA1, 0x16CE, 0x6F09, 0x4C03, 0x1758, 0xEFA1, 0x16D2, 0x4C01, 0x7A01, 0x4C02, 0x7B01, 0x4C03, 0x7AFF, 
  0x4C04, 0x7BFF, 0x4A40, 0x6A00, 0x4B20, 0x6B00, 0x4AFF, 0x6A3F, 0x4BFF, 0x6B1F, 0x25F2, 0x6100, 0x6F00, 0xA202, 0xDAB1, 0x4F01, 0x6101, 0x6000, 0x9A70, 0x7001, 0x9B80, 0x7001, 0x4002, 
  0x16BE, 0x4101, 0x264E, 0xA202, 0xD781, 0x1726 ];

//Built in font. Characters are 4 bits wide but padded out to 8 bits.
var font = [
  [0xF0, 0x90, 0x90, 0x90, 0xF0],//0
  [0x20, 0x60, 0x20, 0x20, 0x70],//1
  [0xF0, 0x10, 0xF0, 0x80, 0xF0],//2
  [0xF0, 0x10, 0xF0, 0x10, 0xF0],//3
  [0x90, 0x90, 0xF0, 0x10, 0x10],//4
  [0xF0, 0x80, 0xF0,0x10,0xF0],//5
  [0xF0, 0x80, 0xF0,0x90,0xF0],//6
  [0xF0, 0x10, 0x20,0x40,0x40],//7
  [0xF0, 0x90, 0xF0,0x90,0xF0],//8
  [0xF0, 0x90, 0xF0,0x10,0xF0],//9
  [0xF0, 0x90, 0xF0,0x90,0x90],//A
  [0xE0, 0x90, 0xE0,0x90,0xE0],//B
  [0xF0, 0x80, 0x80,0x80,0xF0],//C
  [0xE0, 0x90, 0x90,0x90,0xE0],//D
  [0xF0, 0x80, 0xF0,0x80,0xF0],//E
  [0xF0, 0x80, 0xF0,0x80,0x80],//F
  ];
//c8_audio.js - Uses WebAudio api to generate sound for the emulator.

//New Web Audio API context. Reduce the sample rate to 16000, going lower
//negatively effects the sound of generated audio.
var audioContext = new window.AudioContext({sampleRate:16000});

//WebAudio expects we keep track of sound nodes.
var currentTone = null;

//CHIP-8 only specifies playing a simple tone. Use WebAudio to generate a 
//very simple triangle wave oscillator. I thought the triangle wave sounded the
//best compared to sine, square, and sawtooth.
function playTone(){
  //If there isn't already a tone playing. If there is then we don't have to do
  //anything
  if(currentTone == null){
    //WebAudio implementaitions have issues with 0 values for gain
    if(audioGlobalGain <= 0.0){
      audioGlobalGain = 0.001;
    }
    //If muted don't play the tone.
    if(muteAudio == true){
      return;
    }
    //current Web Audio API time
    var now = audioContext.currentTime; 
    //oscillatorNode-->gainNode-->destination
    var gainNode = audioContext.createGain();
    var oscillatorNode = audioContext.createOscillator();
    
    oscillatorNode.type = 'triangle';
    //note: As of writing this, firefox doesn't set with .value as it seems it should. Using setValueAtTime works
    //I thought 1000Hz sounded good
    oscillatorNode.frequency.setValueAtTime(1000, now);
    
    oscillatorNode.connect(gainNode);
    
    gainNode.gain.setValueAtTime(audioGlobalGain, now);
    
    gainNode.connect(audioContext.destination);

    oscillatorNode.start(now);
    //Save the oscilatorNode so that we can stop the sound later.
    currentTone = oscillatorNode;
  }
}

//Stop any playing sound.
function stopTone(){
  if(currentTone != null){
    currentTone.stop(audioContext.currentTime);
    currentTone = null;
  }
}
//c8_active_html_handler.js - Functions related to handling or updating anything
//HTML related on index.html.

//Window resize handler
//On window resize, make sure the canvas is resized correctly.
function windowResize(){
  //Check the aspect ratio of the space provided for the canvas renderer. If
  //the aspect ratio is greater than about two then we have to change the way
  //we size the canvas so that it remains in the screen and doesn't stretch 
  //oddly.
  var aspectRatio = canvasDisplay.parentElement.clientWidth / window.innerHeight;
  //Actual scale to fit transition value would be 2 but I think 1.96 provides
  //a bit better looking transition.
  if(aspectRatio > 1.96){ 
    //The screen is wide but very short.
    canvasDisplay.style.width = "auto";
    //Only 95% the viewport height so that there is a small space below the
    //canvas that matches the padding above the canvas.
    canvasDisplay.style.height = "95vh";
  } else {
    //Go back to normal sizing. The border pushes the canvas 5 pixels to the
    //right. Subract 5 pixels from the size of the canvas to keep HTML elements
    //lined up.
    canvasDisplay.style.width = "calc(100% - 5px)";
    canvasDisplay.style.height = "auto";
  }
}

//Switch to mobile mode. Mobile mode makes the cavas take up the entire screen
//and adds an on screen keyboard
function activateMobileMode(){
  //Fullscreen to get as much space as possible for the canvas. Hides the 
  //address bar.
  //.catch to keep the console happy.
  document.documentElement.requestFullscreen().catch(function() {}); 
  //Landscape mode since the CHIP-8 display is landscape shaped.
  screen.orientation.lock("landscape").catch(function() {});
  //Scroll the canvas back into view.
  window.scrollTo({top: 3, left: 0, behavior: 'auto'});
  //Switch the mobile mode button to a style that is less visible.
  buttonMobileMode.className = "cellphone_btn_activated";
  //Dark background so that we can see the white key outlines.
  document.body.style.backgroundColor = "#3E252B";
}

//Exit mobile mode. Revert the page back to it's defualt look.
function deactivateMobileMode(){
  //Check to see if we are in fullscreen mode. If not then .unlock() will
  //throw an error.
  if(document.fullscreenElement != null){
    document.exitFullscreen(); 
    screen.orientation.unlock();
  }
  //Return to defualt background color.
  document.body.style.backgroundColor = "#CEE5D0";
  //Return the mobile mode button back to its defualt styling.
  buttonMobileMode.className = "cellphone_btn";
}

//Mobile mode button click handler
//Enter mobile mode on first click. Switch modes on other clicks. 
//Lastly close mobile mode on last click.
function onMoblieModeClick(){
  //Switch mode on click.
  mobileMode++;
  switch(mobileMode){
    case 1:
      //Enter full screen mobile mode.
      activateMobileMode();
      //Show full CHIP-8 keyboard
      buttonMobileMode.innerText = "1";
      divFullMobileKeyboard.style.display = "block";
    break;
    case 2:
      //Show the reduced keyboard
      buttonMobileMode.innerText = "2";
      divFullMobileKeyboard.style.display = "none";
      divMiniMobileKeyboard.style.display = "block";
    break;
    case 3:
      //Show the WASD only keyboard
      buttonMobileMode.innerText = "3";
      divMiniMobileKeyboard.style.display = "none";
      divWASDMobileKeyboard.style.display = "block";
    break;
    default:
      //Leave mobile mode
      deactivateMobileMode();
      //Close all mobile keyboards.
      buttonMobileMode.innerText = "";
      divFullMobileKeyboard.style.display = "none";
      divMiniMobileKeyboard.style.display = "none";
      divWASDMobileKeyboard.style.display = "none";
      mobileMode = 0;
  }
}

//Mobile mode key down handler
function onMobileKeyDown(value){
  //Pass the key value back to the InputHander to treat it as a regular
  //keyboard press.
  inputHandler.onPress({key: value});
}

//Mobile mode key up handler
function onMobileKeyUp(value){
  //Pass the key value back to the InputHander to treat it as a regular
  //keyboard key release.
  inputHandler.onRelease({key: value});
}

//On mute checkbox click handler
function onMuteClick(element){
  //element.checked returns true/false. Pass the value to the global mute var.
  muteAudio = element.checked;
}

//Volume slider change handler
function setAudioVolume(value){
  //HTML slider passes a string, convert to a Float.
  audioGlobalGain = parseFloat(value);
}

//On collapsible button click handler.
//Collapsables are the div's that expand accordian style to show hidden info. 
function onCollapsibleClick(element){
  element.classList.toggle("active");
  var content = element.nextElementSibling;
  if (content.style.display == "block") {
    //hide content div
    content.style.display = "none";
  } else {
    //Show content div
    content.style.display = "block";
  }
}

//An extra handler for the "Emulator Internals" collapsible.
//The fields that are updated every frame can take up a fair bit of processing
//power. Only update those fields if they are actually visible.
function onEmulatorInternalsClick(){
  isDisplayingDebugOutput = !isDisplayingDebugOutput;
}

//Reset button click handler
function htmlButtonReset(){
  resetEmulator();
}

//Update the "ROM Discription" collapsible with info about the current ROM
function setROMDescription(content){
  document.getElementById('htmlDescription').innerHTML = content;
}

//Show the file browser. Allows the user to load a ROM located on their system
function showFileBrowser(input) {
  var calcedHash = 0;
  var file = input.files[0];
  var reader = new FileReader();
  
  currentROMName = file.name;
  
  //Inline function to run after file load.
  reader.onload = function(e) {
    //File loaded by FileReader as ArrayBuffer.
    var arrayBuffer1 = reader.result;
    //Set the CHIP-8 ROM to the loaded file.
    currentROMBuffer = arrayBuffer1;
    //Reset will reload with the new ROM.
    resetEmulator();
    //Calculate a hash to see if it matches any ROMs in the compatibility list.
    calcedHash = sdbmHash(arrayBuffer1);
    console.log("Calculated hash: " + calcedHash);
    var listIndex = isInCompatabilityListByHash(calcedHash);
    //if(listIndex != 0){
      setEmulatorToCompatabilitySettings(listIndex);
      //Update HTML elements with the new settings
      updateAllHTMLOptionElements();
    //}
    console.log("File name: " + file.name + " / " + arrayBuffer1.byteLength);
  }

  reader.readAsArrayBuffer(file);
}

//Run/Stop button click handler
function runningSwitcher(){
  running = !running;
}

//Compatibility X display wrapping checkbox click handler
function wrapXCheckboxClick(){
  wrapDisplayX = checkboxWrapX.checked;
}

//Compatibility Y display wrapping checkbox click handler
function wrapYCheckboxClick(){
  wrapDisplayY = checkboxWrapY.checked;
}

//Cycle rate Number field change handler
function onOptionCycleRateChange(value){
  numberOfInstructionThisFrame = value;
}

//Built in ROM selection list handler
function loadROMSelection(){
  var currentSelection = selectBuiltInRoms.value;
  var settingsIndex = 0;

  switch (currentSelection){
    case "invaders_rom":
      currentROMBuffer = loadProgramFrom16Array(invaders_rom);
      settingsIndex = 1;
    break;
    case "pong_rom":
      currentROMBuffer = loadProgramFrom16Array(pong_rom);
      settingsIndex = 2;
    break;
    case "danm8ku_rom":
      currentROMBuffer = loadProgramFrom16Array(danm8ku_rom);
      settingsIndex = 3;
    break;
    case "snake_rom":
      currentROMBuffer = loadProgramFrom16Array(snake_rom);
      settingsIndex = 4;
    break;
    default:
      currentROMBuffer = loadProgramFrom16Array(startup_rom);
      settingsIndex = 5;
  }
  //Reset will load the new ROM
  resetEmulator();

  setEmulatorToCompatabilitySettings(settingsIndex);
  //Update HTML elements with the new settings
  updateAllHTMLOptionElements();
  //Automatically scroll to the canvas so that the user doesn't have to.
  canvasDisplay.scrollIntoView({behavior: "smooth", block: "center", inline: "center"});
}

//Update settings HTML elements with the internal variable values.
function updateAllHTMLOptionElements(){
  //Audio mute
  checkboxMuteSound.checked = muteAudio;
  //Audio volume
  sliderVolume.value = audioGlobalGain;
  //Display wrapping x
  checkboxWrapX.checked = wrapDisplayX;
  //Display wrapping y
  checkboxWrapY.checked = wrapDisplayY;
  //Shift vy vx
  checkboxShiftVy.checked = !compatabilityBitShift;
  //Shift vx vx
  checkboxShiftVx.checked = compatabilityBitShift;
  //Cycle rate
  numberboxTargetRate.value = numberOfInstructionThisFrame;
}

//Debugging output number base radio button handler
function updateOutputRegisterBase(val){
  outputRegisterBase = val;
}

//Update a debugging register textbox with it's corresponding internal variable
//value.
function outputSingleRegister(register, base=10){
  var tempOutput = "";
  var tempPad = 8;
  //Add a certain number of leading zeros depending on what base the number
  //will be displayed as. I think it's easier to read if the leading zeros
  //are included.
  if(base == 2){ //Binary
    //I and PC are 16 bit. Pad them out 16 places. The rest are padded 8 places.
    tempPad = (register==I||register==PC)? 16 : 8;
    tempOutput = c8RegisterArray[register].toString(base).padStart(tempPad, "0");
  } else if (base == 16){ //Hexadecimal
    tempPad = (register==I||register==PC)? 4 : 2;
    tempOutput = c8RegisterArray[register].toString(base).padStart(tempPad, "0").toUpperCase();
  } else { //Decimal
    //No padding for decimal
    tempOutput = c8RegisterArray[register].toString(base);
  }
  //Update the HTML textbox with the generated string.
  textboxArray[register].value = tempOutput;
}

//Update all textbox fields in the Emulator Internals table 
function outputAllRegisters(base=10){
  //For each register in the emulator
  for(var i = 0; i < c8RegisterArray.length; i++){
    outputSingleRegister(i, base);
  }
}
//c8_input.js - User input handling.
//InputHandler object adds keyboard event listeners. Only one InputHandler 
//is created for the page.

function InputHandler(){  
  this.c8Keylist = new Array(16).fill(false);
  //map of keys currently being pressed.
  this.mapCurrentKeys = new Map();

  this.initDefualtKeys();

  //keyboard event listeners.
  window.addEventListener('keyup', this.onRelease.bind(this), false);
  window.addEventListener('keydown', this.onPress.bind(this), false);
};

//CHIP-8 uses a hexidecimal keypad input. Map some qwert keyboard keys to match.
InputHandler.prototype.initDefualtKeys = function(){
  this.mapCurrentKeys.set("1", 0x1);
  this.mapCurrentKeys.set("2", 0x2);
  this.mapCurrentKeys.set("3", 0x3);
  this.mapCurrentKeys.set("4", 0xC);
  this.mapCurrentKeys.set("q", 0x4);
  this.mapCurrentKeys.set("w", 0x5);
  this.mapCurrentKeys.set("e", 0x6);
  this.mapCurrentKeys.set("r", 0xD);
  this.mapCurrentKeys.set("a", 0x7);
  this.mapCurrentKeys.set("s", 0x8);
  this.mapCurrentKeys.set("d", 0x9);
  this.mapCurrentKeys.set("f", 0xE);
  this.mapCurrentKeys.set("z", 0xA);
  this.mapCurrentKeys.set("x", 0x0);
  this.mapCurrentKeys.set("c", 0xB);
  this.mapCurrentKeys.set("v", 0xF);
};

//Is the passed key currently being pressed.
//key - DOMString designation or name alias of the key
//returns boolean - true if the key is currently down, false if it is not.
InputHandler.prototype.isDown = function(c8KeyValue){
  //If the key is found in the mapCurrentKeys map then it is being pressed.
  return this.c8Keylist[c8KeyValue];
};

//Check if any CHIP-8 key is being pressed.
//Returns the key value if a key is being pressed. If no key is pressed, return -1.
InputHandler.prototype.isAnyKeyDown = function(){
  //From 0x0 to 0xF
  for(var i = 0; i < 16; i++){
    if(this.c8Keylist[i] == true){
      return i;
    }
  }
  //If the key is found in the mapCurrentKeys map then it is being pressed.
  return -1;
};

//Function passed to the EventListener. Handles keyboard keydown events.
//event - KeyboardEvent
InputHandler.prototype.onPress = function(event){
  //Add the key to the mapCurrentKeys map. If it's already in the map it will be
  //updated, if it's not then it will be added. If the key is in the
  //mapCurrentKeys map then it is being pressed.
  var tempCurrentKeyValue = this.mapCurrentKeys.get(event.key)
  if(tempCurrentKeyValue != undefined){
    this.c8Keylist[tempCurrentKeyValue] = true;
  }
};

//Function passed to the EventListener. Handles keyboard keyup events.
//event - KeyboardEvent
InputHandler.prototype.onRelease = function(event){
  //keyup is only triggered once on release. Just have to delete the value from
  //the mapCurrentKeys map to recognize that it is no longer being pressed.
  var tempCurrentKeyValue = this.mapCurrentKeys.get(event.key)
  if(tempCurrentKeyValue != undefined){
    this.c8Keylist[tempCurrentKeyValue] = false;
  }
};
//c8_compatability_list.js - Handles setting compatibitly settings.

//A big ole array of ROMs and their settings. Also include a HTML text description.
var programCompatabilityList = [
  {names:["default"], hash:[0],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"No ROM description found.</br>Using defualt settings.</br><b>The defualt input keys for the emulator are:</br><code>[Keyboard Key / CHIP-8 Key]</br></br>[1 / 1] [2 / 2] [3 / 3] [4 / C]</br>[Q / 4] [W / 5] [E / 6] [R / D]</br>[A / 7] [S / 8] [D / 9] [F / E]</br>[Z / A] [X / 0] [C / B] [V / F]</code></b>"},

  {names:["Space Invaders [David Winter].ch8","invaders_rom", "INVADERS"], hash:[35982051930, -35714285786, 15947312129],
    compCycleRate:8, compPersistance:0.1, compShift:true, compWrapX:true, compWrapY:true, compSaveLoad:false,
    note:"A CHIP-8 version of the game Space Invaders. </br>Use Q and E to move and W to fire."},

  {names:["Pong.ch8","pong_rom", "Pong (1 player).ch8"], hash:[78274310905],
    compCycleRate:8, compPersistance:0.1, compShift:true, compWrapX:true, compWrapY:true, compSaveLoad:false,
    note:"A CHIP-8 single player version of the game Pong. </br>Use 1 to move the paddle up and Q to move the paddle down."},

  {names:["Danm8ku.ch8","danm8ku_rom"], hash:[-85303660676, 86935269764,44312548685],
    compCycleRate:1000, compPersistance:1, compShift:false, compWrapX:true, compWrapY:true, compSaveLoad:false,
    note:"A bullet hell game for CHIP-8. Avoid getting hit by the bullets.  </br>WASD controls to move your ship. </br></br><a href=\"https://github.com/buffis/danm8ku\" target=\"_blank\">Author: Björn Kempen</a>."},

  {names:["Snake.ch8","snake_rom"], hash:[34481992282],
    compCycleRate:20, compPersistance:0.2, compShift:false, compWrapX:true, compWrapY:true, compSaveLoad:false,
    note:"A CHIP-8 version of the game Snake but without walls. </br>Use WASD to change the direction of the snake.</br></br><a href=\"https://boringreallife.com\" target=\"_blank\">Author: TimoTriisa</a>."},
    
  {names:["EricLj.ch8"], hash:[0],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"The \"boot ROM\" for my emulator.</br>Displays the text \"CHIP-8 Emulator by EricLj\" with a water dripping animation.</br><b>The defualt input keys for the emulator are:</br><code>[Keyboard Key / CHIP-8 Key]</br></br>[1 / 1] [2 / 2] [3 / 3] [4 / C]</br>[Q / 4] [W / 5] [E / 6] [R / D]</br>[A / 7] [S / 8] [D / 9] [F / E]</br>[Z / A] [X / 0] [C / B] [V / F]</code></b>"},

  {names:["15PUZZLE"], hash:[-4719195035],
    compCycleRate:20, compPersistance:0.2, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A CHIP-8 version of the 15-Puzzle challenge. The numbers in this ROMs case are 1-15 in Hexadecimal i.e. 1-F.</br>The goal is to move the blank spot around until all numbers are in order.</br>Use 2 to move up. S to move down. E to move left. A to move right."},

  {names:["15 Puzzle [Roger Ivie].ch8", "PUZZLE"], hash:[-23327605029],
    compCycleRate:20, compPersistance:0.2, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A CHIP-8 version of the 15-Puzzle challenge. The numbers in this ROMs case are 1-15 in Hexadecimal i.e. 1-F.</br>The goal is to move the blank spot around until all numbers are in order. The game does not check for a winning condition and leaves that up to the user to decide.</br>Keys on the keyboard directly corrispond to their place on the screen. Use a key to move the blank spot to that keys position on screen."},

    {names:["15 Puzzle [Roger Ivie] (alt).ch8"], hash:[-21370680314],
    compCycleRate:20, compPersistance:0.2, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:true,
    note:"A CHIP-8 version of the 15-Puzzle challenge. The numbers in this ROMs case are 1-15 in Hexadecimal i.e. 1-F.</br>The goal is to move the blank spot around until all numbers are in order. The game does not check for a winning condition and leaves that up to the user to decide.</br>Keys on the keyboard directly corrispond to their place on the screen. Use a key to move the blank spot to that keys position on screen."},

  {names:["Blinky [Hans Christian Egeberg, 1991].ch8", "BLINKY"], hash:[66773178927],
    compCycleRate:20, compPersistance:0.15, compShift:true, compWrapX:true, compWrapY:true, compSaveLoad:true,
    note:"A CHIP-8 version of the game Pac-Man.</br>Use 3 to move up and E to move down. A to move left and S to move right.</br>V to restart."},

  {names:["Blinky [Hans Christian Egeberg] (alt).ch8"], hash:[-48999211666],
    compCycleRate:20, compPersistance:0.15, compShift:true, compWrapX:true, compWrapY:false, compSaveLoad:true,
    note:"A CHIP-8 version of the game Pac-Man.</br>Use 2 to move up and S to move down. Q to move left and E to move right. V to restart."},

  {names:["BLITZ"], hash:[-6168681364],
    compCycleRate:10, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"Drop bombs on buildings to destory them before you plane gets too low and hits them.</br>Use W to drop a bomb."},

  {names:["BRIX"], hash:[-5590339823],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:true, compWrapY:false, compSaveLoad:false,
    note:"A CHIP-8 version of the game Breakout.</br>Use Q to move the paddle left and E to move the paddle right"},

  {names:["CONNECT4"], hash:[11200752502],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:true,
    note:"A CHIP-8 version of the game Connect 4.</br>A two player game.</br>The game uses solid and non-solid chips to differentiate players. The player with non-solid chips goes first.</br>The game does not check for a winning condition and leaves that up to the users to decide.</br>Use Q and E to move the selector left and right. Use W to drop a chip in the current slot."},

  {names:["Guess [David Winter].ch8", "Guess [David Winter] (alt).ch8", "GUESS"], hash:[1641579044, -19862749679],
    compCycleRate:20, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"Player picks a random number between 0 and 62. The game will output a list of numbers. If the players number is on that list then click the W key. If not, click any other CHIP-8 key. After a few screens the game will guess the number the player picked."},

  {names:["HIDDEN"], hash:[39694540825],
    compCycleRate:20, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false, 
    note:"Players flips two cards at a time in an attempt to match up corresponding symbols. </br>Use 2QSE to move the selector. Use W to select a card."},

  {names:["Kaleidoscope [Joseph Weisbecker, 1978].ch8", "KALEID"], hash:[20519594308, 19613075268],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"Draws Kaleidoscope style designs.</br>Use 2 to move outward vertically. Q to move outward horizontally. E to move inward horizontally. S to move inward vertically. Press X to make the ROM automatically repeat the patten. Any other key inverts the current pixel."},

  {names:["MAZE", "Maze (alt) [David Winter, 199x].ch8", "Maze [David Winter, 199x].ch8"], hash:[-8225291698, 14084734049],
    compCycleRate:20, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false, 
    note:"Draws a maze-like pattern. Non-interactive."},

    {names:["MERLIN"], hash:[18420513284],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false, 
    note:"A CHIP-8 version of the game Simon.</br>The keys QWAS match their corresponding position on screen."},

  {names:["MISSILE"], hash:[-360583139],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false, 
    note:"Try to shoot as many circles you can in 10 shots. Everytime you shoot the player platform moves faster.</br>Use the S key to shoot."},

  {names:["Pong [Paul Vervalin, 1990].ch8", "Pong (alt).ch8", "PONG", "PONG2", "Pong 2 (Pong hack) [David Winter, 1997].ch8"], hash:[2425556103, 48609632143, -45535810107],
    compCycleRate:8, compPersistance:0.05, compShift:false, compWrapX:false, compWrapY:true, compSaveLoad:false, 
    note:"A CHIP-8 version of Pong. This is the two player version.</br>The Left Player uses the 1 and Q keys. The Right Player uses the 4 and R keys."},

  {names:["SYZYGY"], hash:[47570497584],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:true, compWrapY:true, compSaveLoad:true, 
    note:"Only way I can describe this is it's like an odd game of Snake.</br>To start a new game with boarders use V. Without boarders use F. When in play use 3 and E to move up and down. Use A and S to move left and right."},

  {names:["TANK"], hash:[-29421901875],
    compCycleRate:16, compPersistance:0.1, compShift:false, compWrapX:true, compWrapY:true, compSaveLoad:false,
    note:"Player controls a tank and tries to shoot the moving target. You have 25 shots to gain as many points as possible. You also lose 5 shots if the target and the tank touch.</br>Use 2QSE to move the tank. Use W to shoot. The up and down controls are reversed."},

  {names:["TETRIS"], hash:[-28536509524],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A CHIP-8 version of Tetris.</br>Use W and E to move the piece left and right. Q to rotate the piece. A to drop the piece."},

  {names:["UFO"], hash:[-2357304686],
    compCycleRate:8, compPersistance:0.2, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"Launch your missiles at the passing UFO. Hitting the Large UFO gives 5 points and the small UFO gives 15 points. You have 15 missiles to start with.</br>Use W to launch the missile straight and Q and E to launch diagonally."},

  {names:["Addition Problems [Paul C. Moews].ch8"], hash:[-24553814736],
    compCycleRate:8, compPersistance:0.2, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"Solve the adition problem presented on the screen.</br>The keys are as folows: <br>1=1<br>2=2<br>3=3<br>4=Q<br>5=W<br>6=E<br>7=A<br>8=S<br>9=D<br>0=X"},

  //TODO: VBRIX sometimes at the start the ball doesnt spawn. is this a compatability issue?
  {names:["VBRIX"], hash:[-36749216140],
    compCycleRate:10, compPersistance:0.1, compShift:true, compWrapX:false, compWrapY:false, compSaveLoad:true,
    note:"A sort of sideways version of the game Breakout.</br>Use 1 and Q to move the paddle up and down. A to start."},

  {names:["VERS"], hash:[-19226769104],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:true, compWrapY:true, compSaveLoad:false,
    note:"A two player Tron like game. The first person to collide with a wall or trail loses the round.</br>The left player uses A and Z for up and down and 1 to turn left. The left player cannot turn right. The right player uses 4 and R for up and down and V to turn right. The right player cannot turn left."},

  {names:["WIPEOFF"], hash:[1999573643],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A Breakout type clone with small squares replacing the horizontal bricks of the origonal.</br>Use Q and E to move the paddle left and right."},

  {names:["Particle Demo [zeroZshadow, 2008].ch8"], hash:[-41166046015],
    compCycleRate:50, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A partical system demo. Non-interactive."},

  {names:["Sierpinski [Sergey Naydenov, 2010].ch8", "Sirpinski [Sergey Naydenov, 2010].ch8"], hash:[-14004650155],
    compCycleRate:80, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"Draws a Sierpiński triangle. Non-interactive."},

  {names:["Stars [Sergey Naydenov, 2010].ch8"], hash:[42084579263],
    compCycleRate:30, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:true,
    note:"Draws a a star or firework type animation. Non-interactive."},

  {names:["Trip8 Demo (2008) [Revival Studios].ch8"], hash:[86763486488],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A sprite animation demo. Non-interactive."},

  {names:["Zero Demo [zeroZshadow, 2007].ch8"], hash:[-24968834668],
    compCycleRate:8, compPersistance:0.2, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A sprite animation demo. Non-interactive."},

  {names:["Airplane.ch8"], hash:[21303099779],
    compCycleRate:9, compPersistance:0.12, compShift:false, compWrapX:true, compWrapY:false, compSaveLoad:false,
    note:"Drop your bombs without hitting any of the other airplanes.</br>Use S to drop a bomb."},
  
  {names:["Animal Race [Brian Astle].ch8"], hash:[13099740275],
    compCycleRate:20, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A race betting game. Choose an animal to bet for and a bet of $1 to $9. Reaching a winnings of $256 wins the game.</br>Use the full key map for inputs. See the Keyboard layout for corresponding keys. A=Z B=C C=4 D=R F=E"},

  {names:["Astro Dodge [Revival Studios, 2008].ch8"], hash:[-65305044081],
    compCycleRate:10, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:true,
    note:"Dodge the astroids.</br>W to start. Q and E to move Left and Right"},

  {names:["Biorhythm [Jef Winsor].ch8"], hash:[22589932603],
    compCycleRate:10, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"Program to chart and idea called Biorhythms. Use the keyboard to enter a birth date and a starting date both in MM-DD-YYYY format. Then use C and V to move the starting date of the charts.</br>See a copy of \"Biorhythm [Jef Winsor].txt\" for more information."},

  {names:["Bowling [Gooitzen van der Wal].ch8"], hash:[15503104560],
    compCycleRate:10, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A bowling simulator. Can be played as single player or multiplayer.</br>For mor information see a copy of \"Bowling [Gooitzen van der Wal].txt\" for more information."},

  {names:["Breakout (Brix hack) [David Winter, 1997].ch8"], hash:[-6366733567],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A Breakout type clone but with no gaps horizontally between bricks.</br>Use Q and E to move the paddle left and right."},

  {names:["Breakout [Carmelo Cortez, 1979].ch8"], hash:[15464462430],
    compCycleRate:7, compPersistance:0.1, compShift:false, compWrapX:true, compWrapY:false, compSaveLoad:false,
    note:"A Breakout type clone but the goal is to simply reach the other side. The bricks have a strange breaking pattern but I am not sure if that is intentional or a compatability issue.</br>Use Q and E to move the paddle left and right."},

  {names:["Brick (Brix hack, 1990).ch8"], hash:[-71103311806],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:true, compWrapY:false, compSaveLoad:false,
    note:"A Breakout type clone but with no gaps between bricks.</br>Use Q and E to move the paddle left and right."},

  {names:["Cave.ch8"], hash:[35925833237],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"Guide the square through the cave without hitting the walls. Once the square starts moving it will not stop moving and only change directions.</br>Use V to start and 2QSE to change direction"},

  {names:["Coin Flipping [Carmelo Cortez, 1978].ch8"], hash:[6336881799],
    compCycleRate:20, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A \"Coin-flipping\" program. Generates a random flip and counts how many \"Heads\" or \"Tails\" are generated"},

  {names:["Craps [Camerlo Cortez, 1978].ch8"], hash:[2390296288],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A demo/unfinished version of the game Craps. Press any key to roll the dice. Each number represents a single die."},

  {names:["Deflection [John Fort].ch8"], hash:[-17275152630],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A two player game where each player is given a turn to place mirrors to deflect a ball into a target. The more deflections in a turn the higher the score in that round.</br>This game has a complicated control scheme. See \"Deflection [John Fort].txt\" for more details."},

  {names:["Figures.ch8"], hash:[82683060516],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A tetris like game. Not sure exactly how to play this one but it seems the goal is to simply pack as many falling numbers in the given space as possible.</br>Use Q and E to move the falling piece left and right."},

  {names:["Hi-Lo [Jef Winsor, 1978].ch8"], hash:[-12395040894],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:true, compWrapY:false, compSaveLoad:false,
    note:"The computer generates a random number between 0 and 99 and you have to guess what it is. Use the keypad to enter a number and the computer will tell you if the number you guessed is too high or too low. You have 10 chances to guess the number."},

  {names:["Filter.ch8"], hash:[16056497905],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:true, compWrapY:false, compSaveLoad:false,
    note:"Catch as many falling squares as possible. Everytime you catch a square you gain a point. Everytime you let a square fall past your paddle you lose a life.</br>Use Q and E to move the paddle left and right."},

  {names:["Landing.ch8"], hash:[-32827587628],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"Drop bombs on the towers to make them smaller. Your plane will slowly lose altitude. The round ends when you hit a tower.</br>Use S to drop a bomb."},

  {names:["Lunar Lander (Udo Pernisz, 1979).ch8"], hash:[-146373874615],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:true, compSaveLoad:false,
    note:"Land your lunar lander on the surface of the moon.</br>Use 2 for upwards thrust. Q and E for thrust left and right."},

  {names:["Mastermind FourRow (Robert Lindley, 1978).ch8"], hash:[29535845786],
    compCycleRate:8, compPersistance:0.1, compShift:true, compWrapX:false, compWrapY:true, compSaveLoad:false,
    note:"The computer generates a random 4-digit code with the numbers 1-6. The players goal is to try to guess that code within 10 tries. Each column of 4 dashes represents a try. After each column of numbers are entered, a score made up of symbols is assigned to that column. Two dots means that a number exactly matches what is in the code. A solid line means that a number exists in the code but it is currently in the wrong place.</br>Use the keys 1 2 3 Q W E to enter numbers. V clears the current try."},

  {names:["Most Dangerous Game [Peter Maruhnic].ch8"], hash:[12249434154],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A two player game where one player hunts the other. Each round takes place in a maze in which you can't see the walls until you interact with them.</br>See the file \"Most Dangerous Game [Peter Maruhnic].txt\" for a more in depth explanation and game controls."},

  {names:["Nim [Carmelo Cortez, 1978].ch8"], hash:[-7715248324],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A single player game against the computer. The player and computer take turns subracting either 1, 2, or 3 from the number displayed on screen. The goal is to not be the last one to subract the number to zero.</br>Use the 1, 2, and 3 keys to subract that number."},

  {names:["Paddles.ch8"], hash:[-36940725723],
    compCycleRate:9, compPersistance:0.05, compShift:true, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A pong or table tennis like game. Can be played signle player or two player.</br>To start press V for single player or F for two player. In single player use Q and E to move the player paddle. In two player the top player uses Q and E to move and the bottom player uses A and D."},

  {names:["Programmable Spacefighters [Jef Winsor].ch8"], hash:[15474757539],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A multiplayer game in which players program spacefighters with commands that are executed during a round.</br>This game has complicated gameplay and many inputs. See \"Programmable Spacefighters [Jef Winsor].txt\" for more details on how to play."},

  {names:["Reversi [Philip Baltzer].ch8"], hash:[11625064035],
    compCycleRate:10, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A two player CHIP-8 version of the game Reversi.</br>Use 2QSE to move the cursor and W to place the current piece. Use V to skip the current players turn."},

  {names:["Rocket [Joseph Weisbecker, 1978].ch8"], hash:[-17138497400],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"Luanch your rocket and try to hit the passing plane.</br>Use V to launch the rocket."},

  {names:["Rocket Launch [Jonas Lindstedt].ch8"], hash:[23940779256],
    compCycleRate:10, compPersistance:0.2, compShift:false, compWrapX:false, compWrapY:true, compSaveLoad:true,
    note:"A cave flying type game in which you try to fly your rocket without touching the walls.</br>Use C to start. Use Q and E to move left and right."},

  {names:["Rocket Launcher.ch8"], hash:[-8795680451],
    compCycleRate:8, compPersistance:0.1, compShift:true, compWrapX:false, compWrapY:false, compSaveLoad:true,
    note:"A demo program.</br>Use V to launch the rocket."},

  //TODO: seems to be a bug in my code. Rush Hour [Hap, 2006] (alt) works in some other emulators.
  {names:["Rush Hour [Hap, 2006] (alt).ch8"], hash:[-12875744074],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"NOTE: I could not get this version to work correctly. Use the non-(alt) version</br>A CHIP-8 version of the board game Rush Hour."},

  {names:["Rush Hour [Hap, 2006].ch8"], hash:[78973603451],
    compCycleRate:8, compPersistance:0.1, compShift:false, compWrapX:false, compWrapY:false, compSaveLoad:false,
    note:"A CHIP-8 version of the board game Rush Hour. Move the block containing the arrow to the exit point.</br>Use WASD to move to cursor. Use Z to select"},
];

//Check if the ROM name is in the compatibility list. I would assume that people
//wouldn't be naming their ROM the same as something that's already out there.
//Returns the index position of the ROM in the compatibility list if found.
//Returns 0 if not found.
function isInCompatabilityListByName(name){
  //For each ROM in the combatibility list
  for(var i = 1; i < programCompatabilityList.length; i++){
    //For each name at the currently checking ROM.
    for(var j = 0; j < programCompatabilityList[i].names.length; j++){
      if(programCompatabilityList[i].names[j] == name){
        console.log("Match by name: " + i);
        return i;
      }
    }
  }
  return 0;
}

//Check to see if the ROM is in the compatibility list by file hash.
//Returns the index position of the ROM in the compatibility list if found.
//Returns 0 if not found.
function isInCompatabilityListByHash(hash){
  //For each ROM in the combatibility list
  for(var i = 1; i < programCompatabilityList.length; i++){
    //For each hash value at the currently checking ROM. Some ROMs will be
    //functionally the same but have different hashs.
    for(var j = 0; j < programCompatabilityList[i].hash.length; j++){
      if(programCompatabilityList[i].hash[j] == hash){
        console.log("Match by hash: " + i);
        return i;
      }
    }
  }
  return 0;
}

//Change internal emulator settings to match the values in the compatibility
//list.
function setEmulatorToCompatabilitySettings(settingsListIndex){
  var currentSettings = programCompatabilityList[settingsListIndex];
  var defaultSettings = programCompatabilityList[0];
  
  //Instructions per draw frame
  if(currentSettings.compCycleRate != undefined){
    numberOfInstructionThisFrame = currentSettings.compCycleRate;
  } else {
    numberOfInstructionThisFrame = defaultSettings.compCycleRate;
  }

  //Display persistance
  if(currentSettings.compPersistance != undefined){
    displayDecayRate = currentSettings.compPersistance;
  } else {
    displayDecayRate = defaultSettings.compPersistance;
  }

  //Shift instructions
  if(currentSettings.compShift != undefined){
    compatabilityBitShift = currentSettings.compShift;
  } else {
    compatabilityBitShift = defaultSettings.compShift;
  }

  //Display wrapping on the X-Axis
  if(currentSettings.compWrapX != undefined){
    wrapDisplayX = currentSettings.compWrapX;
  } else {
    wrapDisplayX = defaultSettings.compWrapX;
  }

  //Display wrapping on the Y-Axis
  if(currentSettings.compWrapY != undefined){
    wrapDisplayY = currentSettings.compWrapY;
  } else {
    wrapDisplayY = defaultSettings.compWrapY;
  }

  //Save and Load instructions
  if(currentSettings.compSaveLoad != undefined){
    compatabilitySaveLoad = currentSettings.compSaveLoad;
  } else {
    compatabilitySaveLoad = defaultSettings.compSaveLoad;
  }

  setROMDescription(currentSettings.note);
}


function setSettingsByName(name){
  var listIndex;

  listIndex = isInCompatabilityListByName(name);
  setEmulatorToCompatabilitySettings(listIndex);
}

<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path fill="#5E454B" d="M16 1H8C6.34 1 5 2.34 5 4v16c0 1.66 1.34 3 3 3h8c1.66 0 3-1.34 3-3V4c0-1.66-1.34-3-3-3zm-2.5 20h-3c-.28 0-.5-.22-.5-.5s.22-.5.5-.5h3c.28 0 .5.22.5.5s-.22.5-.5.5zm3.5-3H7V4h10v14z"/></svg>
//c8_globals.js - global/window variables

//TODO: This is a bit of a mess. Needs clean up.

//----------------------CHIP-8 Emulator variables-------------------------------
var C8_MEMORY_SIZE = 0x2000;
var c8Memory = new ArrayBuffer(C8_MEMORY_SIZE); //CHIP-8 memory space
var c8Memory_View = new DataView(c8Memory);
var c8Memory_U8View = new Uint8Array(c8Memory);
//Going to store the display in its own buffer as some documentation states
//that program memory can go from 0x200 to 0xFFF
var c8Display = new ArrayBuffer(0x100); //display buffer
var c8FontStartAddress = 0x000;
var c8ProgramStartAddress = 0x200;
var c8DisplayStartAdrdess = 0xF00;
var c8StackStartAddress = 0x050;
var c8StackCurrentFreeAddress = c8StackStartAddress; //stack pointer
var c8StackMaxAddress = 0x0B0;
var c8RegisterArray = new Array(20).fill(0); //CHIP-8 registers
var V0 = 0, V1=1, V2=2, V3=3, V4=4, V5=5, V6=6, V7=7, V8=8, V9=9, VA=10, VB=11, VC=12, VD=13, VE=14, VF=15, I=16, PC=17, DT = 18, ST = 19;
var ioBlocking = false; //is emulator waiting on user input
var ioBlockDestinationRegister = 0; //register to load once user input is detected
var currentROMName = "chip8_rom";
var numberOfInstructionThisFrame = 8; //number of emulator cycles to run on each animation frame
var c8DisplayWidth = 64;
var c8DisplayHeight = 32;
var c8DisplayBufferSize = c8DisplayHeight * c8DisplayWidth;
var c8DisplayBuffer = [];
c8DisplayBuffer.length = c8DisplayBufferSize;
var c8DisplayPersistance = []; //secondary display buffer holding persistance decay values
c8DisplayPersistance.length = c8DisplayBufferSize;
var displayDecayRate = 0.1; //rate at which a pixel decays after being turned off
var running = false; //is the emulator running, false=paused
var currentROMBuffer; //points to the ArrayBuffer of the currently loaded ROM
var isCurrentROMBuiltIn = true;
var skipPCIncrement = false; //should the emulator increment PC after the current instruction is ran
var forgroundColor = {r:243, g:240, b:215}; //canvas pixel color
var backgroundColor = {r:34, g:19, b:25}; //canvas bacground color
var remainderST = 0; //carryover remainder after calculating ST register timing
var remainderDT = 0; //carryover remainder after calculating DT register timing

//----------------------Compatibility variables---------------------------------
var wrapDisplayX = true;
var wrapDisplayY = true;
var compatabilityBitShift = false;
var compatabilitySaveLoad = true;
var compatabilityLogicClearsVF = false;
var compatabilitySpeedUpLoadInstruction = 0x000;
var compatabilitySpeedUpLoadRate = 50;


//----------------------Animation Frame variables-------------------------------
var lastTime = Date.now(); //last animation frame time
var currentTime = 0; //current animation frame time
var deltaTime = 0; //time in milliseconds since last frame
var totalTime = 0;
var framecountDebug = 0;
var targetRemainder = 0;
var mainFrameCount = 0;


//----------------------Audio variables-----------------------------------------
//audioGlobalGain acts as a global volume control.
var audioGlobalGain = 0.5;
//Handle muting with a boolean value. This allows audioGlobalGain to save the
//volume value before muting rather then just setting gain to 0.
var muteAudio = false;


//----------------------Other variables-----------------------------------------
var inputHandler = null;
var mobileMode = 0; //current mobile mode state

//----------------------HTML Elements-------------------------------------------
var isDisplayingDebugOutput = false;
var canvasDisplay = document.getElementById('cavasDisplay');
var contextDisplay = canvasDisplay.getContext('2d', {alpha: false});
var dbgMsg = document.getElementById('state-msg');
var checkboxWrapX = document.getElementById("wrapX");
var checkboxWrapY = document.getElementById("wrapY");
var checkboxShiftVy = document.getElementById("shiftVy");
var checkboxShiftVx = document.getElementById("shiftVx");
var numberboxTargetRate = document.getElementById("emuTargetFreq");
var checkboxMuteSound = document.getElementById("soundMute");
var buttonMobileMode = document.getElementById("mobile_mode_button");
var divMobileHolder = document.getElementById("mobile_holder");
var divFullMobileKeyboard = document.getElementById("mini_keyboard_full");
var divMiniMobileKeyboard = document.getElementById("mini_keyboard_small");
var divWASDMobileKeyboard = document.getElementById("mini_keyboard_wasd");
var divCanvasAligner = document.getElementById("canvasAligner");
var selectBuiltInRoms = document.getElementById("ROMS");
var sliderVolume = document.getElementById("soundGainSlider");
var textboxArray = [];
textboxArray[V0] = document.getElementById('tbV0');
textboxArray[V1] = document.getElementById('tbV1');
textboxArray[V2] = document.getElementById('tbV2');
textboxArray[V3] = document.getElementById('tbV3');
textboxArray[V4] = document.getElementById('tbV4');
textboxArray[V5] = document.getElementById('tbV5');
textboxArray[V6] = document.getElementById('tbV6');
textboxArray[V7] = document.getElementById('tbV7');
textboxArray[V8] = document.getElementById('tbV8');
textboxArray[V9] = document.getElementById('tbV9');
textboxArray[VA] = document.getElementById('tbVA');
textboxArray[VB] = document.getElementById('tbVB');
textboxArray[VC] = document.getElementById('tbVC');
textboxArray[VD] = document.getElementById('tbVD');
textboxArray[VE] = document.getElementById('tbVE');
textboxArray[VF] = document.getElementById('tbVF');
textboxArray[I] =  document.getElementById('tbI');
textboxArray[PC] = document.getElementById('tbPC');
textboxArray[DT] = document.getElementById('tbDT');
textboxArray[ST] = document.getElementById('tbST');
var outputRegisterBase = 16;

var c8DisplayPixelWidth = canvasDisplay.width / c8DisplayWidth;
var c8DisplayPixelHeight = canvasDisplay.height / c8DisplayHeight;


h1 {
  text-align: center;
  font-size: 42px;
  text-decoration: underline dotted;
}

h2 {
  text-align: center;
}

body {
  font-family: Helvetica;
  color: #5E454B;
  background-color: #CEE5D0;
  font-size:16px;
  margin: 0px;
}

select {
  width: 90%;
  text-align: center;
  font-size:20px;
  color: #5E454B;
  min-width: 15ch;
  max-width: 30ch;
  border: 2px solid #5E454B;
  background-color: #fff;
  border-radius: 5px;
  cursor: pointer;
}

input[type=file] {
  width: 90%;
  color: #5E454B;
  background-color: #fff;
  border: 2px solid #5E454B;
  border-radius: 5px;
  font-size:20px;
  cursor: pointer;
  padding-top: 5px;
}

canvas {
  border:5px solid #5E454B; 
  width: calc(100% - 5px);
  max-width: 1000px;
  margin: auto; 
  border-radius: 10px;
  border-style: none none solid solid;
  z-index: 0;
}

table {
  
	border:1px solid #5E454B;
	border-collapse:collapse;
	padding:5px;
  margin-left: auto;
  margin-right: auto;
}

table td {
  width: 50%;
	border:1px solid #5E454B;
	text-align:center;
	padding:5px;
	color: #5E454B;
}

input[type=file]::file-selector-button {
  box-shadow: -2px 2px 0px 0px #5E454B;
	background-color:#F3C16D;
	border-radius:6px;
	border:none;
	display:inline-block;
	cursor:pointer;
	color:#5E454B;
	font-size:15px;
	font-weight:bold;
	padding:6px 20px;
  /*button border messes up text alignment. Use padding to push text and button 
  down and translate button back up*/
  transform: translateY(-2px); 
  margin: 0px 10px 3px 3px;
	text-decoration:none;
}

input[type=file]::file-selector-button:hover {
  background-color:#5E454B;
  color:#CEE5D0;
}

input[type=file]::file-selector-button:active {
	position:relative;
	top:1px;
}

/* file-selector-button is somewhat new. Get compatability with a few more 
browsers. AKA my computer at work.*/
input[type=file]::-webkit-file-upload-button {
  box-shadow: -2px 2px 0px 0px #5E454B;
	background-color:#F3C16D;
	border-radius:6px;
	border:none;
	display:inline-block;
	cursor:pointer;
	color:#5E454B;
	font-size:15px;
	font-weight:bold;
	padding:6px 20px;
  transform: translateY(-2px); 
  margin: 0px 3px 3px 3px;
	text-decoration:none;
}
input[type=file]::-webkit-file-upload-button:hover {
  background-color:#5E454B;
  color:#CEE5D0;
}
input[type=file]::-webkit-file-upload-button:active {
	position:relative;
	top:1px;
}

.pageHolder{
  width: 95%;
  max-width: 1000px; 
  margin: auto;
  padding-top: 5px;
  text-align: center;
}

.ghostHolder{
  display: flex;
  align-items: center;
}

.canvasHolder{

  position: relative;
  width: 100%;
}

.defualtBtn {
	box-shadow: -2px 2px 0px 0px #5E454B;
	background-color:#F3C16D;
	border-radius:6px;
	border:none;
	display:inline-block;
	cursor:pointer;
	color:#5E454B;
	font-size:15px;
	font-weight:bold;
	padding:6px 20px;
	text-decoration:none;
}

.defualtBtn:hover {
	background-color:#5E454B;
  color:#CEE5D0;
}

.defualtBtn:active {
	position:relative;
	top:1px;
}


.rowLoad {
  position:relative;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  grid-gap: 15px;
  z-index: 2;
  
}

.columnLoad {
  flex-grow: 1;
  text-align: center;
  background-color: #F3F0D7;
  border:5px  #5E454B;
  border-style: none none solid solid;
  border-radius: 10px;
  margin-bottom: 15px;


}
.columnLoad p {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 60px;
  width: 100%;
}


.columnLoadHeader {
  left:-5px;
  border-radius: 7px 10px 0px 0px;
  padding: 15px;
  font-size: 15px;
  font-weight: bold;
  text-align: left;
  background-color: #F3C16D;
}



/* Style the button that is used to open and close the collapsible content */
.collapsible {
  position: relative;
  background-color: #F3C16D;
  color: #5E454B;
  cursor: pointer;
  padding: 15px;
  width: 100%;
  text-align: left;
  outline: none;
  font-size: 15px;
  font-weight: bold;
  border:5px  #5E454B;
  border-style: none none solid solid;
  border-radius: 10px 10px 10px 10px;
  margin-bottom: 15px;
  z-index: 2;
}

/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.active {
  border-style: none none none solid;
  border-radius: 10px 10px 0px 0px;
  margin-bottom: 0px;
}

.collapsible:hover {
  background-color: #5E454B;
  color: #CEE5D0;
}

.content {
  position: relative;
  padding: 0px 15px 15px 15px;
  display: none;
  overflow: hidden;
  background: #F3F0D7;   
  border:5px  #5E454B;
  border-style: none none solid solid;
  border-radius: 0px 0px 10px 10px;
  margin-bottom: 15px;
  z-index: 2;
}

.collapsible:after {
  content: '+';
  font-size: 20px;
  font-weight:bold;
  float: right;
}

.active:after {
  content: "-"; 
}

.mobile_holder {
  position: absolute;
  left: 1%;
  bottom: 1%;
  z-index: 1;
}

.mini_keyboard_full_container {
  display:none;
  touch-action: none;
  margin-top: 10px;
}

.mini_keyboard_full_key, .mini_keyboard_full_blank {
  user-select: none;
  -webkit-touch-callout: none;
  color: #ffffff;
  background: transparent;
  border:2px  #ffffff;
  border-style: dotted;
  border-radius: 50px;
  margin: 5px;
  width: 50px;
  height: 50px;
  opacity: 0.8;
}

.mini_keyboard_full_blank {
  color: transparent;
  background: transparent;
  border:none;
}

.mini_keyboard_full_key:active {
  background: rgba(230,230,230,0.8);
  margin: 5px;
  width: 50px;
  height: 50px;
}

.cellphone_btn, .cellphone_btn_activated {
  box-shadow: -2px 2px 0px 0px #5E454B;
	background-color:#F3C16D;
	border-radius: 50px;
	border:2px solid #5E454B;
	cursor:pointer;
	color:#5E454B;
	font-weight:bold;
	text-decoration:none;
  width: 50px;
  height: 50px;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 80%;
  background-image: url("c8/phone_icon.svg");
  position: fixed;
  left: 3%;
  bottom: 3%;
  z-index: 3;
}

.cellphone_btn_activated {
  box-shadow: none;
  border:2px  #fff;
  border-style: dotted;
  border-radius: 50px;
	background-color:transparent;
  filter: brightness(400%);
  opacity: 0.8;
  left: 1%;
  top: 1%;
}

.cellphone_btn:active {
  box-shadow:none;
  transform: translate(-2px, 2px);
}