adapt simple embedding example
(bring your own smb.nes)
This commit is contained in:
parent
34e0367dff
commit
c29f364957
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
.direnv
|
||||
.direnv
|
||||
smb.nes
|
18
index.html
Normal file
18
index.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Embedding Example</title>
|
||||
|
||||
<script type="text/javascript" src="jsnes/dist/jsnes.js"></script>
|
||||
<script type="text/javascript" src="nes-embed.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="margin: auto; max-height: 75vh; aspect-ratio: 4 / 3;">
|
||||
<canvas id="nes-canvas" width="256" height="240" style="width: 100%; max-height: 75vh; image-rendering: pixelated; aspect-ratio: 4 / 3;"/>
|
||||
</div>
|
||||
<button onclick="nes_load_url('nes-canvas', 'smb.nes')">Start!</button>
|
||||
<p>DPad: Arrow keys<br/>B button: Z, A button: X, Select: Tab / C, Start: Return / V</p>
|
||||
</body>
|
||||
</html>
|
132
nes-embed.js
Normal file
132
nes-embed.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
var SCREEN_WIDTH = 256;
|
||||
var SCREEN_HEIGHT = 240;
|
||||
var FRAMEBUFFER_SIZE = SCREEN_WIDTH*SCREEN_HEIGHT;
|
||||
|
||||
var canvas_ctx, image;
|
||||
var framebuffer_u8, framebuffer_u32;
|
||||
|
||||
var AUDIO_BUFFERING = 512;
|
||||
var SAMPLE_COUNT = 4*1024;
|
||||
var SAMPLE_MASK = SAMPLE_COUNT - 1;
|
||||
var audio_samples_L = new Float32Array(SAMPLE_COUNT);
|
||||
var audio_samples_R = new Float32Array(SAMPLE_COUNT);
|
||||
var audio_write_cursor = 0, audio_read_cursor = 0;
|
||||
|
||||
var nes = new jsnes.NES({
|
||||
onFrame: function(framebuffer_24){
|
||||
for(var i = 0; i < FRAMEBUFFER_SIZE; i++) framebuffer_u32[i] = 0xFF000000 | framebuffer_24[i];
|
||||
},
|
||||
onAudioSample: function(l, r){
|
||||
audio_samples_L[audio_write_cursor] = l;
|
||||
audio_samples_R[audio_write_cursor] = r;
|
||||
audio_write_cursor = (audio_write_cursor + 1) & SAMPLE_MASK;
|
||||
},
|
||||
});
|
||||
|
||||
function onAnimationFrame(){
|
||||
window.requestAnimationFrame(onAnimationFrame);
|
||||
|
||||
image.data.set(framebuffer_u8);
|
||||
canvas_ctx.putImageData(image, 0, 0);
|
||||
}
|
||||
|
||||
function audio_remain(){
|
||||
return (audio_write_cursor - audio_read_cursor) & SAMPLE_MASK;
|
||||
}
|
||||
|
||||
function audio_callback(event){
|
||||
var dst = event.outputBuffer;
|
||||
var len = dst.length;
|
||||
|
||||
// Attempt to avoid buffer underruns.
|
||||
if(audio_remain() < AUDIO_BUFFERING) nes.frame();
|
||||
|
||||
var dst_l = dst.getChannelData(0);
|
||||
var dst_r = dst.getChannelData(1);
|
||||
for(var i = 0; i < len; i++){
|
||||
var src_idx = (audio_read_cursor + i) & SAMPLE_MASK;
|
||||
dst_l[i] = audio_samples_L[src_idx];
|
||||
dst_r[i] = audio_samples_R[src_idx];
|
||||
}
|
||||
|
||||
audio_read_cursor = (audio_read_cursor + len) & SAMPLE_MASK;
|
||||
}
|
||||
|
||||
const KEY_MAPPINGS = [{
|
||||
"ArrowUp": "BUTTON_UP",
|
||||
"ArrowDown": "BUTTON_DOWN",
|
||||
"ArrowLeft": "BUTTON_LEFT",
|
||||
"ArrowRight": "BUTTON_RIGHT",
|
||||
"KeyZ": "BUTTON_B",
|
||||
"KeyX": "BUTTON_A",
|
||||
"KeyC": "BUTTON_SELECT",
|
||||
"KeyV": "BUTTON_START",
|
||||
"Enter": "BUTTON_START",
|
||||
"Tab": "BUTTON_SELECT"
|
||||
}]
|
||||
|
||||
function keyboard(callback, event){
|
||||
var player = 1;
|
||||
for (const mapping of KEY_MAPPINGS) {
|
||||
const button = mapping[event.code];
|
||||
if (button) {
|
||||
callback(player, jsnes.Controller[button]);
|
||||
event.preventDefault();
|
||||
}
|
||||
player ++;
|
||||
}
|
||||
}
|
||||
|
||||
function nes_init(canvas_id){
|
||||
var canvas = document.getElementById(canvas_id);
|
||||
canvas_ctx = canvas.getContext("2d");
|
||||
image = canvas_ctx.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
|
||||
canvas_ctx.fillStyle = "black";
|
||||
canvas_ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
|
||||
// Allocate framebuffer array.
|
||||
var buffer = new ArrayBuffer(image.data.length);
|
||||
framebuffer_u8 = new Uint8ClampedArray(buffer);
|
||||
framebuffer_u32 = new Uint32Array(buffer);
|
||||
|
||||
// Setup audio.
|
||||
var audio_ctx = new window.AudioContext();
|
||||
var script_processor = audio_ctx.createScriptProcessor(AUDIO_BUFFERING, 0, 2);
|
||||
script_processor.onaudioprocess = audio_callback;
|
||||
script_processor.connect(audio_ctx.destination);
|
||||
}
|
||||
|
||||
function nes_boot(rom_data){
|
||||
nes.loadROM(rom_data);
|
||||
window.requestAnimationFrame(onAnimationFrame);
|
||||
}
|
||||
|
||||
function nes_load_data(canvas_id, rom_data){
|
||||
nes_init(canvas_id);
|
||||
nes_boot(rom_data);
|
||||
}
|
||||
|
||||
function nes_load_url(canvas_id, path){
|
||||
nes_init(canvas_id);
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", path);
|
||||
req.overrideMimeType("text/plain; charset=x-user-defined");
|
||||
req.onerror = () => console.log(`Error loading ${path}: ${req.statusText}`);
|
||||
|
||||
req.onload = function() {
|
||||
if (this.status === 200) {
|
||||
nes_boot(this.responseText);
|
||||
} else if (this.status === 0) {
|
||||
// Aborted, so ignore error
|
||||
} else {
|
||||
req.onerror();
|
||||
}
|
||||
};
|
||||
|
||||
req.send();
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (event) => {keyboard(nes.buttonDown, event)});
|
||||
document.addEventListener('keyup', (event) => {keyboard(nes.buttonUp, event)});
|
Loading…
Reference in a new issue