Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix stuff #449

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 19 additions & 18 deletions example/nes-embed.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
<!DOCTYPE html>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Embedding Example</title>

<script type="text/javascript" src="https://unpkg.com/jsnes/dist/jsnes.min.js"></script>
<script type="text/javascript" src="nes-embed.js"></script>
<script>window.onload = function(){nes_load_url("nes-canvas", "InterglacticTransmissing.nes");}</script>
</head>
<body>
<div style="margin: auto; width: 75%;">
<canvas id="nes-canvas" width="256" height="240" style="width: 100%"/>
</div>
<p>DPad: Arrow keys<br/>Start: Return, Select: Tab<br/>A Button: A, B Button: S</p>
</body>
</html>
<!DOCTYPE html>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/>
<title>Embedding Example</title>

<script type="text/javascript" src="https://unpkg.com/jsnes/dist/jsnes.min.js"></script>
<script type="text/javascript" src="nes-embed.js"></script>
<script>window.onload = function(){nes_load_url("nes-canvas", "InterglacticTransmissing.nes");}</script>
</head>
<body>
<center>
<canvas id="nes-canvas" width="256" height="240" style="width: 512px; image-rendering: crisp-edges; image-rendering: pixelated; image-rendering: optimizespeed;"></canvas>
</center>
<p>DPad: Arrow keys<br/>Start: Return, Select: Shift<br/>A Button: X, B Button: Z</p>
</body>
</html>
89 changes: 58 additions & 31 deletions example/nes-embed.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var SCREEN_WIDTH = 256;
var SCREEN_HEIGHT = 240;
var FRAMEBUFFER_SIZE = SCREEN_WIDTH*SCREEN_HEIGHT;

var nes;
var canvas_ctx, image;
var framebuffer_u8, framebuffer_u32;

Expand All @@ -12,20 +13,9 @@ 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);
}
Expand All @@ -37,18 +27,18 @@ function audio_remain(){
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;
}

Expand All @@ -63,38 +53,54 @@ function keyboard(callback, event){
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
case 88: // A = X
callback(player, jsnes.Controller.BUTTON_A); break;
case 83: // 's' - qwerty, azerty
case 79: // 'o' - dvorak
case 90: // B = Z
callback(player, jsnes.Controller.BUTTON_B); break;
case 9: // Tab
case 16: // SELECT = Shift
callback(player, jsnes.Controller.BUTTON_SELECT); break;
case 13: // Return
case 13: // START = Return
callback(player, jsnes.Controller.BUTTON_START); break;
default: break;
}
}

function nes_init(canvas_id){
var audio_ctx = new window.AudioContext({
latencyHint: "interactive",
});

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;
},
sampleRate: audio_ctx.sampleRate,
});

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);

document.addEventListener('keydown', (event) => {keyboard(nes.buttonDown, event)});
document.addEventListener('keyup', (event) => {keyboard(nes.buttonUp, event)});
}

function nes_boot(rom_data){
Expand All @@ -109,24 +115,45 @@ function nes_load_data(canvas_id, 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);
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)});
function nes_load_nvram(item){
if (nes != null && nes.cpu != null && nes.cpu.mem != null && window.localStorage != null) {
let data = window.localStorage.getItem("jsnes-nvram-" + item);
if (data != null){
data = JSON.parse(data);
for (let i = 0; i < 8192; i++){
nes.cpu.mem[24576 + i] = data.mem[i]; //$6000-$7FFF (2K)
}
}
}
}

function nes_save_nvram(item){
if (nes != null && nes.cpu != null && nes.cpu.mem != null && window.localStorage != null) {
window.localStorage.setItem("jsnes-nvram-" + item, JSON.stringify({ mem: nes.cpu.mem.slice(24576, 32768)})); //$6000-$7FFF (2K)
}
}

function nes_volume(value){
if (nes != null && nes.papu != null) {
nes.papu.setMasterVolume(value);
}
}
16 changes: 3 additions & 13 deletions src/nes.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ var NES = function (opts) {
onStatusUpdate: function () {},
onBatteryRamWrite: function () {},

// FIXME: not actually used except for in PAPU
preferredFrameRate: 60,

emulateSound: true,
sampleRate: 48000, // Sound sample rate in hz
sampleRate: 48000,
};
if (typeof opts !== "undefined") {
var key;
Expand All @@ -26,8 +23,6 @@ var NES = function (opts) {
}
}

this.frameTime = 1000 / this.opts.preferredFrameRate;

this.ui = {
writeFrame: this.opts.onFrame,
updateStatus: this.opts.onStatusUpdate,
Expand Down Expand Up @@ -78,6 +73,7 @@ NES.prototype = {
},

frame: function () {
if (!this.mmap) return;
this.ppu.startFrame();
var cycles = 0;
var emulateSound = this.opts.emulateSound;
Expand Down Expand Up @@ -164,7 +160,7 @@ NES.prototype = {

getFPS: function () {
var now = +new Date();
var fps = null;
var fps = 0;
if (this.lastFpsTime) {
fps = this.fpsFrameCount / ((now - this.lastFpsTime) / 1000);
}
Expand Down Expand Up @@ -193,12 +189,6 @@ NES.prototype = {
this.romData = data;
},

setFramerate: function (rate) {
this.opts.preferredFrameRate = rate;
this.frameTime = 1000 / rate;
this.papu.setSampleRate(this.opts.sampleRate, false);
},

toJSON: function () {
return {
// romData: this.romData,
Expand Down
15 changes: 7 additions & 8 deletions src/papu.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ var utils = require("./utils");

var CPU_FREQ_NTSC = 1789772.5; //1789772.72727272d;
// var CPU_FREQ_PAL = 1773447.4;
var APU_TO_CPU_CYCLE_NTSC = 14915;
// var APU_TO_CPU_CYCLE_PAL = 16627;

var PAPU = function (nes) {
this.nes = nes;
Expand All @@ -17,7 +19,7 @@ var PAPU = function (nes) {
this.initCounter = 2048;
this.channelEnableValue = null;

this.sampleRate = 44100;
this.sampleRate = 48000;

this.lengthLookup = null;
this.dmcFreqLookup = null;
Expand Down Expand Up @@ -103,13 +105,10 @@ PAPU.prototype = {
reset: function () {
this.sampleRate = this.nes.opts.sampleRate;
this.sampleTimerMax = Math.floor(
(1024.0 * CPU_FREQ_NTSC * this.nes.opts.preferredFrameRate) /
(this.sampleRate * 60.0)
(1024.0 * CPU_FREQ_NTSC) / this.sampleRate
);

this.frameTime = Math.floor(
(14915.0 * this.nes.opts.preferredFrameRate) / 60.0
);
this.frameTime = APU_TO_CPU_CYCLE_NTSC;

this.sampleTimer = 0;

Expand Down Expand Up @@ -383,7 +382,7 @@ PAPU.prototype = {
// Clock frame counter at double CPU speed:
this.masterFrameCounter += nCycles << 1;
if (this.masterFrameCounter >= this.frameTime) {
// 240Hz tick:
// 240Hz (NTSC) tick:
this.masterFrameCounter -= this.frameTime;
this.frameCounterTick();
}
Expand Down Expand Up @@ -468,7 +467,7 @@ PAPU.prototype = {
this.frameIrqActive = true;
}

// End of 240Hz tick
// End of 240Hz (NSTC) tick
},

// Samples the channels, mixes the output together, then writes to buffer.
Expand Down