diff --git a/.gitignore b/.gitignore
index 97f3520..45d20f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
*.xdc
-
+.nes
+.DS_Store
\ No newline at end of file
diff --git a/index.html b/index.html
index f50ead3..943ed9a 100644
--- a/index.html
+++ b/index.html
@@ -1,4 +1,23 @@
+
+
+
+
+ Embedding Example
+
+
+
+
+
+
+
+
+
+ DPad: Arrow keys
Start: Return, Select: Tab
A Button: A, B Button: S
+
+
+
+
\ No newline at end of file
diff --git a/nes-embed.js b/nes-embed.js
new file mode 100644
index 0000000..27aabd5
--- /dev/null
+++ b/nes-embed.js
@@ -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;
+}
+
+function keyboard(callback, event){
+ var player = 1;
+ switch(event.keyCode){
+ case 38: // UP
+ callback(player, jsnes.Controller.BUTTON_UP); break;
+ case 40: // Down
+ callback(player, jsnes.Controller.BUTTON_DOWN); break;
+ case 37: // Left
+ callback(player, jsnes.Controller.BUTTON_LEFT); break;
+ case 39: // Right
+ callback(player, jsnes.Controller.BUTTON_RIGHT); break;
+ case 65: // 'a' - qwerty, dvorak
+ case 81: // 'q' - azerty
+ callback(player, jsnes.Controller.BUTTON_A); break;
+ case 83: // 's' - qwerty, azerty
+ case 79: // 'o' - dvorak
+ callback(player, jsnes.Controller.BUTTON_B); break;
+ case 9: // Tab
+ callback(player, jsnes.Controller.BUTTON_SELECT); break;
+ case 13: // Return
+ callback(player, jsnes.Controller.BUTTON_START); break;
+ default: break;
+ }
+}
+
+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)});