summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBen Firshman <ben@firshman.co.uk>2009-10-22 13:06:26 +0100
committerBen Firshman <ben@firshman.co.uk>2009-10-22 13:06:26 +0100
commit0f4519e8dadbcda6829790de46f5c96d359fb81f (patch)
treedbd8cde9b712a6b362a4dc59d2e0dec3d45291cb
parent81d7dd2ed44c613df1446ef2372b663afd7aae6e (diff)
downloadjsnes-0f4519e8dadbcda6829790de46f5c96d359fb81f.zip
jsnes-0f4519e8dadbcda6829790de46f5c96d359fb81f.tar.gz
jsnes-0f4519e8dadbcda6829790de46f5c96d359fb81f.tar.bz2
Various variable scope optimisations, split up PAPU code and added "enable sound" button.
-rw-r--r--channels.js685
-rw-r--r--cpu.js2
-rw-r--r--globals.js2
-rw-r--r--index.html23
-rw-r--r--jssound.swfbin1416 -> 5307 bytes
-rw-r--r--nes.js49
-rw-r--r--papu.js841
-rw-r--r--ppu.js169
8 files changed, 859 insertions, 912 deletions
diff --git a/channels.js b/channels.js
new file mode 100644
index 0000000..afcb3d7
--- /dev/null
+++ b/channels.js
@@ -0,0 +1,685 @@
+function ChannelDM(papu) {
+ this.papu = papu;
+
+ this.MODE_NORMAL = 0;
+ this.MODE_LOOP = 1;
+ this.MODE_IRQ = 2;
+
+ this.isEnabled = null;
+ this.hasSample = null;
+ this.irqGenerated=false;
+
+ this.playMode = null;
+ this.dmaFrequency = null;
+ this.dmaCounter = null;
+ this.deltaCounter = null;
+ this.playStartAddress = null;
+ this.playAddress = null;
+ this.playLength = null;
+ this.playLengthCounter = null;
+ this.shiftCounter = null;
+ this.reg4012 = null;
+ this.reg4013 = null;
+ this.sample = null;
+ this.dacLsb = null;
+ this.data = null;
+
+ this.reset();
+}
+
+ChannelDM.prototype.clockDmc = function(){
+
+ // Only alter DAC value if the sample buffer has data:
+ if(this.hasSample){
+
+ if((this.data&1)===0){
+
+ // Decrement delta:
+ if(this.deltaCounter>0) {
+ this.deltaCounter--;
+ }
+
+ }else{
+
+ // Increment delta:
+ if(this.deltaCounter<63) {
+ this.deltaCounter++;
+ }
+
+ }
+
+ // Update sample value:
+ this.sample = this.isEnabled ? (this.deltaCounter<<1)+this.dacLsb : 0;
+
+ // Update shift register:
+ this.data>>=1;
+
+ }
+
+ this.dmaCounter--;
+ if(this.dmaCounter <= 0){
+
+ // No more sample bits.
+ this.hasSample = false;
+ this.endOfSample();
+ this.dmaCounter = 8;
+
+ }
+
+ if(this.irqGenerated){
+ this.papu.nes.cpu.requestIrq(this.papu.nes.cpu.IRQ_NORMAL);
+ }
+
+};
+
+ChannelDM.prototype.endOfSample = function(){
+
+
+ if(this.playLengthCounter===0 && this.playMode==this.MODE_LOOP){
+
+ // Start from beginning of sample:
+ this.playAddress = this.playStartAddress;
+ this.playLengthCounter = this.playLength;
+
+ }
+
+ if(this.playLengthCounter > 0){
+
+ // Fetch next sample:
+ this.nextSample();
+
+ if(this.playLengthCounter == 0){
+
+ // Last byte of sample fetched, generate IRQ:
+ if(this.playMode == this.MODE_IRQ){
+
+ // Generate IRQ:
+ this.irqGenerated = true;
+
+ }
+
+ }
+
+ }
+
+};
+
+ChannelDM.prototype.nextSample = function(){
+
+ // Fetch byte:
+ this.data = this.papu.nes.memMapper.load(this.playAddress);
+ this.papu.nes.cpu.haltCycles(4);
+
+ this.playLengthCounter--;
+ this.playAddress++;
+ if(this.playAddress>0xFFFF){
+ this.playAddress = 0x8000;
+ }
+
+ this.hasSample = true;
+
+};
+
+ChannelDM.prototype.writeReg = function(address, value){
+
+ if(address == 0x4010){
+
+ // Play mode, DMA Frequency
+ if((value>>6)==0){
+ this.playMode = this.MODE_NORMAL;
+ }else if(((value>>6)&1)==1){
+ this.playMode = this.MODE_LOOP;
+ }else if((value>>6)==2){
+ this.playMode = this.MODE_IRQ;
+ }
+
+ if((value&0x80)==0){
+ this.irqGenerated = false;
+ }
+
+ this.dmaFrequency = this.papu.getDmcFrequency(value&0xF);
+
+ }else if(address == 0x4011){
+
+ // Delta counter load register:
+ this.deltaCounter = (value>>1)&63;
+ this.dacLsb = value&1;
+ this.sample = ((this.deltaCounter<<1)+this.dacLsb); // update sample value
+
+ }else if(address == 0x4012){
+
+ // DMA address load register
+ this.playStartAddress = (value<<6)|0x0C000;
+ this.playAddress = this.playStartAddress;
+ this.reg4012 = value;
+
+ }else if(address == 0x4013){
+
+ // Length of play code
+ this.playLength = (value<<4)+1;
+ this.playLengthCounter = this.playLength;
+ this.reg4013 = value;
+
+ }else if(address == 0x4015){
+
+ // DMC/IRQ Status
+ if(((value>>4)&1)==0){
+ // Disable:
+ this.playLengthCounter = 0;
+ }else{
+ // Restart:
+ this.playAddress = this.playStartAddress;
+ this.playLengthCounter = this.playLength;
+ }
+ this.irqGenerated = false;
+ }
+
+};
+
+ChannelDM.prototype.setEnabled = function(value){
+
+ if((!this.isEnabled) && value){
+ this.playLengthCounter = this.playLength;
+ }
+ this.isEnabled = value;
+
+};
+
+ChannelDM.prototype.getLengthStatus = function(){
+ return ((this.playLengthCounter==0 || !this.isEnabled)?0:1);
+};
+
+ChannelDM.prototype.getIrqStatus = function(){
+ return (this.irqGenerated?1:0);
+};
+
+ChannelDM.prototype.reset = function(){
+
+ this.isEnabled = false;
+ this.irqGenerated = false;
+ this.playMode = this.MODE_NORMAL;
+ this.dmaFrequency = 0;
+ this.dmaCounter = 0;
+ this.deltaCounter = 0;
+ this.playStartAddress = 0;
+ this.playAddress = 0;
+ this.playLength = 0;
+ this.playLengthCounter = 0;
+ this.sample = 0;
+ this.dacLsb = 0;
+ this.shiftCounter = 0;
+ this.reg4012 = 0;
+ this.reg4013 = 0;
+ this.data = 0;
+
+};
+
+
+function ChannelNoise(papu) {
+ this.papu = papu;
+
+ this.isEnabled = null;
+ this.envDecayDisable = null;
+ this.envDecayLoopEnable = null;
+ this.lengthCounterEnable = null;
+ this.envReset = null;
+ this.shiftNow = null;
+
+ this.lengthCounter = null;
+ this.progTimerCount = null;
+ this.progTimerMax = null;
+ this.envDecayRate = null;
+ this.envDecayCounter = null;
+ this.envVolume = null;
+ this.masterVolume = null;
+ this.shiftReg = 1<<14;
+ this.randomBit = null;
+ this.randomMode = null;
+ this.sampleValue = null;
+ this.accValue=0;
+ this.accCount=1;
+ this.tmp = null;
+
+ this.reset();
+}
+
+ChannelNoise.prototype.reset = function(){
+ this.progTimerCount = 0;
+ this.progTimerMax = 0;
+ this.isEnabled = false;
+ this.lengthCounter = 0;
+ this.lengthCounterEnable = false;
+ this.envDecayDisable = false;
+ this.envDecayLoopEnable = false;
+ this.shiftNow = false;
+ this.envDecayRate = 0;
+ this.envDecayCounter = 0;
+ this.envVolume = 0;
+ this.masterVolume = 0;
+ this.shiftReg = 1;
+ this.randomBit = 0;
+ this.randomMode = 0;
+ this.sampleValue = 0;
+ this.tmp = 0;
+};
+
+ChannelNoise.prototype.clockLengthCounter = function(){
+ if(this.lengthCounterEnable && this.lengthCounter>0){
+ this.lengthCounter--;
+ if(this.lengthCounter == 0) this.updateSampleValue();
+ }
+};
+
+ChannelNoise.prototype.clockEnvDecay = function(){
+
+ if(this.envReset){
+
+ // Reset envelope:
+ this.envReset = false;
+ this.envDecayCounter = this.envDecayRate + 1;
+ this.envVolume = 0xF;
+ }else if(--this.envDecayCounter <= 0){
+
+ // Normal handling:
+ this.envDecayCounter = this.envDecayRate + 1;
+ if(this.envVolume>0){
+ this.envVolume--;
+ }else{
+ this.envVolume = this.envDecayLoopEnable ? 0xF : 0;
+ }
+ }
+ this.masterVolume = this.envDecayDisable ? this.envDecayRate : this.envVolume;
+ this.updateSampleValue();
+};
+
+ChannelNoise.prototype.updateSampleValue = function(){
+ if(this.isEnabled && this.lengthCounter>0){
+ this.sampleValue = this.randomBit * this.masterVolume;
+ }
+};
+
+ChannelNoise.prototype.writeReg = function(address, value){
+
+ if(address == 0x400C){
+
+ // Volume/Envelope decay:
+ this.envDecayDisable = ((value&0x10)!=0);
+ this.envDecayRate = value&0xF;
+ this.envDecayLoopEnable = ((value&0x20)!=0);
+ this.lengthCounterEnable = ((value&0x20)==0);
+ this.masterVolume = this.envDecayDisable?this.envDecayRate:this.envVolume;
+
+ }else if(address == 0x400E){
+
+ // Programmable timer:
+ this.progTimerMax = this.papu.getNoiseWaveLength(value&0xF);
+ this.randomMode = value>>7;
+
+ }else if(address == 0x400F){
+
+ // Length counter
+ this.lengthCounter = this.papu.getLengthMax(value&248);
+ this.envReset = true;
+
+ }
+
+ // Update:
+ //updateSampleValue();
+
+};
+
+ChannelNoise.prototype.setEnabled = function(value){
+ this.isEnabled = value;
+ if(!value) this.lengthCounter = 0;
+ this.updateSampleValue();
+};
+
+ChannelNoise.prototype.getLengthStatus = function(){
+ return ((this.lengthCounter==0 || !this.isEnabled)?0:1);
+};
+
+
+function ChannelSquare(papu, square1){
+ this.papu = papu;
+
+ this.dutyLookup = [
+ 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 0, 0, 0,
+ 1, 0, 0, 1, 1, 1, 1, 1
+ ];
+ this.impLookup = [
+ 1,-1, 0, 0, 0, 0, 0, 0,
+ 1, 0,-1, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0,-1, 0, 0, 0,
+ -1, 0, 1, 0, 0, 0, 0, 0
+ ];
+
+ this.sqr1 = square1;
+ this.isEnabled = null;
+ this.lengthCounterEnable = null;
+ this.sweepActive = null;
+ this.envDecayDisable = null;
+ this.envDecayLoopEnable = null;
+ this.envReset = null;
+ this.sweepCarry = null;
+ this.updateSweepPeriod = null;
+
+ this.progTimerCount = null;
+ this.progTimerMax = null;
+ this.lengthCounter = null;
+ this.squareCounter = null;
+ this.sweepCounter = null;
+ this.sweepCounterMax = null;
+ this.sweepMode = null;
+ this.sweepShiftAmount = null;
+ this.envDecayRate = null;
+ this.envDecayCounter = null;
+ this.envVolume = null;
+ this.masterVolume = null;
+ this.dutyMode = null;
+ this.sweepResult = null;
+ this.sampleValue = null;
+ this.vol = null;
+
+ this.reset();
+}
+
+
+ChannelSquare.prototype.reset = function() {
+ this.progTimerCount = 0;
+ this.progTimerMax = 0;
+ this.lengthCounter = 0;
+ this.squareCounter = 0;
+ this.sweepCounter = 0;
+ this.sweepCounterMax = 0;
+ this.sweepMode = 0;
+ this.sweepShiftAmount = 0;
+ this.envDecayRate = 0;
+ this.envDecayCounter = 0;
+ this.envVolume = 0;
+ this.masterVolume = 0;
+ this.dutyMode = 0;
+ this.vol = 0;
+
+ this.isEnabled = false;
+ this.lengthCounterEnable = false;
+ this.sweepActive = false;
+ this.sweepCarry = false;
+ this.envDecayDisable = false;
+ this.envDecayLoopEnable = false;
+};
+
+ChannelSquare.prototype.clockLengthCounter = function(){
+
+ if(this.lengthCounterEnable && this.lengthCounter>0){
+ this.lengthCounter--;
+ if(this.lengthCounter==0) this.updateSampleValue();
+ }
+
+};
+
+ChannelSquare.prototype.clockEnvDecay = function() {
+
+ if(this.envReset){
+
+ // Reset envelope:
+ this.envReset = false;
+ this.envDecayCounter = this.envDecayRate + 1;
+ this.envVolume = 0xF;
+
+ }else if((--this.envDecayCounter) <= 0){
+
+ // Normal handling:
+ this.envDecayCounter = this.envDecayRate + 1;
+ if(this.envVolume>0){
+ this.envVolume--;
+ }else{
+ this.envVolume = this.envDecayLoopEnable ? 0xF : 0;
+ }
+
+ }
+
+ this.masterVolume = this.envDecayDisable ? this.envDecayRate : this.envVolume;
+ this.updateSampleValue();
+
+};
+
+ChannelSquare.prototype.clockSweep = function() {
+
+ if(--this.sweepCounter<=0){
+
+ this.sweepCounter = this.sweepCounterMax + 1;
+ if(this.sweepActive && this.sweepShiftAmount>0 && this.progTimerMax>7){
+
+ // Calculate result from shifter:
+ this.sweepCarry = false;
+ if(this.sweepMode==0){
+ this.progTimerMax += (this.progTimerMax>>this.sweepShiftAmount);
+ if(this.progTimerMax > 4095){
+ this.progTimerMax = 4095;
+ this.sweepCarry = true;
+ }
+ }else{
+ this.progTimerMax = this.progTimerMax - ((this.progTimerMax>>this.sweepShiftAmount)-(this.sqr1?1:0));
+ }
+
+ }
+
+ }
+
+ if(this.updateSweepPeriod){
+ this.updateSweepPeriod = false;
+ this.sweepCounter = this.sweepCounterMax + 1;
+ }
+
+};
+
+ChannelSquare.prototype.updateSampleValue = function() {
+
+ if(this.isEnabled && this.lengthCounter>0 && this.progTimerMax>7){
+
+ if(this.sweepMode==0 && (this.progTimerMax + (this.progTimerMax>>this.sweepShiftAmount)) > 4095){
+ //if(this.sweepCarry){
+ this.sampleValue = 0;
+ }else{
+ this.sampleValue = this.masterVolume*this.dutyLookup[(this.dutyMode<<3)+this.squareCounter];
+ }
+ }else{
+ this.sampleValue = 0;
+ }
+
+};
+
+ChannelSquare.prototype.writeReg = function(address, value){
+
+ var addrAdd = (this.sqr1?0:4);
+ if(address == 0x4000+addrAdd){
+
+ // Volume/Envelope decay:
+ this.envDecayDisable = ((value&0x10)!=0);
+ this.envDecayRate = value & 0xF;
+ this.envDecayLoopEnable = ((value&0x20)!=0);
+ this.dutyMode = (value>>6)&0x3;
+ this.lengthCounterEnable = ((value&0x20)==0);
+ this.masterVolume = this.envDecayDisable?this.envDecayRate:this.envVolume;
+ this.updateSampleValue();
+
+ }else if(address == 0x4001+addrAdd){
+
+ // Sweep:
+ this.sweepActive = ((value&0x80)!=0);
+ this.sweepCounterMax = ((value>>4)&7);
+ this.sweepMode = (value>>3)&1;
+ this.sweepShiftAmount = value&7;
+ this.updateSweepPeriod = true;
+
+ }else if(address == 0x4002+addrAdd){
+
+ // Programmable timer:
+ this.progTimerMax &= 0x700;
+ this.progTimerMax |= value;
+
+ }else if(address == 0x4003+addrAdd){
+
+ // Programmable timer, length counter
+ this.progTimerMax &= 0xFF;
+ this.progTimerMax |= ((value&0x7)<<8);
+
+ if(this.isEnabled){
+ this.lengthCounter = this.papu.getLengthMax(value&0xF8);
+ }
+
+ this.envReset = true;
+
+ }
+
+};
+
+ChannelSquare.prototype.setEnabled = function(value){
+ this.isEnabled = value;
+ if(!value) this.lengthCounter = 0;
+ this.updateSampleValue();
+};
+
+ChannelSquare.prototype.getLengthStatus = function() {
+ return ((this.lengthCounter==0 || !this.isEnabled)?0:1);
+};
+
+
+function ChannelTriangle(papu) {
+ this.papu = papu;
+
+ this.isEnabled = null;
+ this.sampleCondition = null;
+ this.lengthCounterEnable = null;
+ this.lcHalt = null;
+ this.lcControl = null;
+
+ this.progTimerCount = null;
+ this.progTimerMax = null;
+ this.triangleCounter = null;
+ this.lengthCounter = null;
+ this.linearCounter = null;
+ this.lcLoadValue = null;
+ this.sampleValue = null;
+ this.tmp = null;
+
+ this.reset();
+}
+
+ChannelTriangle.prototype.reset = function(){
+ this.progTimerCount = 0;
+ this.progTimerMax = 0;
+ this.triangleCounter = 0;
+ this.isEnabled = false;
+ this.sampleCondition = false;
+ this.lengthCounter = 0;
+ this.lengthCounterEnable = false;
+ this.linearCounter = 0;
+ this.lcLoadValue = 0;
+ this.lcHalt = true;
+ this.lcControl = false;
+ this.tmp = 0;
+ this.sampleValue = 0xF;
+};
+
+ChannelTriangle.prototype.clockLengthCounter = function(){
+ if(this.lengthCounterEnable && this.lengthCounter>0){
+ this.lengthCounter--;
+ if(this.lengthCounter==0){
+ this.updateSampleCondition();
+ }
+ }
+};
+
+ChannelTriangle.prototype.clockLinearCounter = function(){
+ if(this.lcHalt){
+ // Load:
+ this.linearCounter = this.lcLoadValue;
+ this.updateSampleCondition();
+ }else if(this.linearCounter > 0){
+ // Decrement:
+ this.linearCounter--;
+ this.updateSampleCondition();
+ }
+ if(!this.lcControl){
+ // Clear halt flag:
+ this.lcHalt = false;
+ }
+};
+
+ChannelTriangle.prototype.getLengthStatus = function(){
+ return ((this.lengthCounter === 0 || !this.isEnabled)?0:1);
+};
+
+ChannelTriangle.prototype.readReg = function(address){
+ return 0;
+};
+
+ChannelTriangle.prototype.writeReg = function(address, value){
+
+ if(address == 0x4008){
+
+ // New values for linear counter:
+ this.lcControl = (value&0x80)!==0;
+ this.lcLoadValue = value&0x7F;
+
+ // Length counter enable:
+ this.lengthCounterEnable = !this.lcControl;
+
+ }else if(address == 0x400A){
+
+ // Programmable timer:
+ this.progTimerMax &= 0x700;
+ this.progTimerMax |= value;
+
+ }else if(address == 0x400B){
+
+ // Programmable timer, length counter
+ this.progTimerMax &= 0xFF;
+ this.progTimerMax |= ((value&0x07)<<8);
+ this.lengthCounter = this.papu.getLengthMax(value&0xF8);
+ this.lcHalt = true;
+
+ }
+
+ this.updateSampleCondition();
+
+};
+
+ChannelTriangle.prototype.clockProgrammableTimer = function(nCycles){
+
+ if(this.progTimerMax>0){
+ this.progTimerCount += nCycles;
+ while(this.progTimerMax > 0 && this.progTimerCount >= this.progTimerMax){
+ this.progTimerCount-=this.progTimerMax;
+ if(this.isEnabled && this.lengthCounter>0 && this.linearCounter>0){
+ this.clockTriangleGenerator();
+ }
+ }
+ }
+
+};
+
+ChannelTriangle.prototype.clockTriangleGenerator = function(){
+ this.triangleCounter++;
+ this.triangleCounter &= 0x1F;
+};
+
+ChannelTriangle.prototype.setEnabled = function(value){
+ this.isEnabled = value;
+ if(!value) this.lengthCounter = 0;
+ this.updateSampleCondition();
+};
+
+ChannelTriangle.prototype.updateSampleCondition = function(){
+ this.sampleCondition =
+ this.isEnabled &&
+ this.progTimerMax>7 &&
+ this.linearCounter>0 &&
+ this.lengthCounter>0
+ ;
+};
+
+
diff --git a/cpu.js b/cpu.js
index f6cb793..df15c5c 100644
--- a/cpu.js
+++ b/cpu.js
@@ -100,7 +100,7 @@ function CPU(nes) {
if(this.irqRequested){
temp =
(this.F_CARRY)|
- ((this.F_ZERO==0?1:0)<<1)|
+ ((this.F_ZERO===0?1:0)<<1)|
(this.F_INTERRUPT<<2)|
(this.F_DECIMAL<<3)|
(this.F_BRK<<4)|
diff --git a/globals.js b/globals.js
index 5d786de..ecba551 100644
--- a/globals.js
+++ b/globals.js
@@ -8,7 +8,7 @@ var Globals = {
nes: null,
fpsInterval: 500, // Time between updating FPS in ms
- emulateSound: true,
+ emulateSound: false,
sampleRate: 44100, // Sound sample rate in hz
}
Globals.frameTime = 1000/Globals.preferredFrameRate;
diff --git a/index.html b/index.html
index b824be9..f872f90 100644
--- a/index.html
+++ b/index.html
@@ -12,6 +12,7 @@ charset="utf-8">
<script src="jquery-1.2.6.min.js" type="text/javascript" charset="utf-8"></script>
<script src="jquery.dimensions.min.js" type="text/javascript" charset="utf-8"></script>
<script src="utils.js" type="text/javascript" charset="utf-8"></script>
+ <script src="channels.js" type="text/javascript" charset="utf-8"></script>
<script src="cpu.js" type="text/javascript" charset="utf-8"></script>
<script src="cpuinfo.js" type="text/javascript" charset="utf-8"></script>
<script src="globals.js" type="text/javascript" charset="utf-8"></script>
@@ -55,6 +56,7 @@ charset="utf-8">
<select id="roms"></select>
<input type="button" value="pause" id="pause" disabled="disabled">
<input type="button" value="restart" id="restart" disabled="disabled">
+ <input type="button" value="enable sound" id="enablesound" disabled="disabled">
</div>
<p id="status">Loading...</p>
<script type="text/javascript">
@@ -144,6 +146,13 @@ digg_url = 'http://digg.com/playable_web_games/JSNES_A_NES_emulator_written_enti
function resetButtons() {
$("#pause").attr("disabled", null).attr("value", "pause");
$("#restart").attr("disabled", null);
+ $("#enablesound").attr("disabled", null);
+ if (Globals.emulateSound) {
+ $("#enablesound").attr("value", "disable sound");
+ }
+ else {
+ $("#enablesound").attr("value", "enable sound");
+ }
}
$("#roms").change(function() {
@@ -169,13 +178,25 @@ digg_url = 'http://digg.com/playable_web_games/JSNES_A_NES_emulator_written_enti
}
});
+ $("#enablesound").click(function() {
+ b = $("#enablesound");
+ if (b.attr("value") == "enable sound") {
+ Globals.emulateSound = true;
+ b.attr("value", "disable sound");
+ }
+ else {
+ Globals.emulateSound = false;
+ b.attr("value", "enable sound");
+ }
+ });
+
$("#restart").click(function() {
nes.reloadRom();
nes.start();
});
function readJSSoundBuffer() {
- return nes.papu.readBuffer();
+ return JSON.stringify(nes.papu.readBuffer());
}
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
diff --git a/jssound.swf b/jssound.swf
index cde545f..f2c6a18 100644
--- a/jssound.swf
+++ b/jssound.swf
Binary files differ
diff --git a/nes.js b/nes.js
index c606368..5ef45bd 100644
--- a/nes.js
+++ b/nes.js
@@ -66,54 +66,57 @@ function NES() {
this.ppu.startFrame();
var cycles = 0;
var emulateSound = Globals.emulateSound;
+ var cpu = this.cpu;
+ var ppu = this.ppu;
+ var papu = this.papu;
FRAMELOOP: for (;;) {
- if (this.cpu.cyclesToHalt == 0) {
+ if (cpu.cyclesToHalt == 0) {
// Execute a CPU instruction
- cycles = this.cpu.emulate();
- if(emulateSound) {
- this.papu.clockFrameCounter(cycles);
+ cycles = cpu.emulate();
+ if (emulateSound) {
+ papu.clockFrameCounter(cycles);
}
cycles *= 3;
}
else {
- if (this.cpu.cyclesToHalt > 8) {
+ if (cpu.cyclesToHalt > 8) {
cycles = 24;
if (emulateSound) {
- this.papu.clockFrameCounter(8);
+ papu.clockFrameCounter(8);
}
- this.cpu.cyclesToHalt -= 8;
+ cpu.cyclesToHalt -= 8;
}
else {
- cycles = this.cpu.cyclesToHalt * 3;
+ cycles = cpu.cyclesToHalt * 3;
if (emulateSound) {
- this.papu.clockFrameCounter(this.cpu.cyclesToHalt);
+ papu.clockFrameCounter(cpu.cyclesToHalt);
}
- this.cpu.cyclesToHalt = 0;
+ cpu.cyclesToHalt = 0;
}
}
for(;cycles>0;cycles--){
- if(this.ppu.curX == this.ppu.spr0HitX
- && this.ppu.f_spVisibility==1
- && this.ppu.scanline-21 == this.ppu.spr0HitY){
+ if(ppu.curX == ppu.spr0HitX
+ && ppu.f_spVisibility==1
+ && ppu.scanline-21 == ppu.spr0HitY){
// Set sprite 0 hit flag:
- this.ppu.setStatusFlag(this.ppu.STATUS_SPRITE0HIT,true);
+ ppu.setStatusFlag(ppu.STATUS_SPRITE0HIT,true);
}
- if(this.ppu.requestEndFrame){
- this.ppu.nmiCounter--;
- if(this.ppu.nmiCounter == 0){
- this.ppu.requestEndFrame = false;
- this.ppu.startVBlank();
+ if(ppu.requestEndFrame){
+ ppu.nmiCounter--;
+ if(ppu.nmiCounter == 0){
+ ppu.requestEndFrame = false;
+ ppu.startVBlank();
break FRAMELOOP;
}
}
- this.ppu.curX++;
- if(this.ppu.curX==341){
- this.ppu.curX = 0;
- this.ppu.endScanline();
+ ppu.curX++;
+ if(ppu.curX==341){
+ ppu.curX = 0;
+ ppu.endScanline();
}
}
diff --git a/papu.js b/papu.js
index a897b7c..1ec475e 100644
--- a/papu.js
+++ b/papu.js
@@ -1,691 +1,6 @@
-function ChannelDM(papu) {
- this.papu = papu;
-
- this.MODE_NORMAL = 0;
- this.MODE_LOOP = 1;
- this.MODE_IRQ = 2;
-
- this.isEnabled = null;
- this.hasSample = null;
- this.irqGenerated=false;
-
- this.playMode = null;
- this.dmaFrequency = null;
- this.dmaCounter = null;
- this.deltaCounter = null;
- this.playStartAddress = null;
- this.playAddress = null;
- this.playLength = null;
- this.playLengthCounter = null;
- this.shiftCounter = null;
- this.reg4012,reg4013 = null;
- this.status = null;
- this.sample = null;
- this.dacLsb = null;
- this.data = null;
-
- this.reset();
-}
-
-ChannelDM.prototype.clockDmc = function(){
-
- // Only alter DAC value if the sample buffer has data:
- if(this.hasSample){
-
- if((this.data&1)==0){
-
- // Decrement delta:
- if(this.deltaCounter>0) this.deltaCounter--;
-
- }else{
-
- // Increment delta:
- if(this.deltaCounter<63) this.deltaCounter++;
-
- }
-
- // Update sample value:
- this.sample = this.isEnabled ? (this.deltaCounter<<1)+this.dacLsb : 0;
-
- // Update shift register:
- this.data>>=1;
-
- }
-
- this.dmaCounter--;
- if(this.dmaCounter <= 0){
-
- // No more sample bits.
- this.hasSample = false;
- this.endOfSample();
- this.dmaCounter = 8;
-
- }
-
- if(this.irqGenerated){
- this.papu.nes.cpu.requestIrq(this.papu.nes.cpu.IRQ_NORMAL);
- }
-
-}
-
-ChannelDM.prototype.endOfSample = function(){
-
-
- if(this.playLengthCounter==0 && this.playMode==this.MODE_LOOP){
-
- // Start from beginning of sample:
- this.playAddress = this.playStartAddress;
- this.playLengthCounter = this.playLength;
-
- }
-
- if(this.playLengthCounter > 0){
-
- // Fetch next sample:
- this.nextSample();
-
- if(this.playLengthCounter == 0){
-
- // Last byte of sample fetched, generate IRQ:
- if(this.playMode == this.MODE_IRQ){
-
- // Generate IRQ:
- this.irqGenerated = true;
-
- }
-
- }
-
- }
-
-}
-
-ChannelDM.prototype.nextSample = function(){
-
- // Fetch byte:
- this.data = this.papu.nes.memMapper.load(this.playAddress);
- this.papu.nes.cpu.haltCycles(4);
-
- this.playLengthCounter--;
- this.playAddress++;
- if(this.playAddress>0xFFFF){
- this.playAddress = 0x8000;
- }
-
- this.hasSample = true;
-
-}
-
-ChannelDM.prototype.writeReg = function(address, value){
-
- if(address == 0x4010){
-
- // Play mode, DMA Frequency
- if((value>>6)==0){
- this.playMode = this.MODE_NORMAL;
- }else if(((value>>6)&1)==1){
- this.playMode = this.MODE_LOOP;
- }else if((value>>6)==2){
- this.playMode = this.MODE_IRQ;
- }
-
- if((value&0x80)==0){
- this.irqGenerated = false;
- }
-
- this.dmaFrequency = this.papu.getDmcFrequency(value&0xF);
-
- }else if(address == 0x4011){
-
- // Delta counter load register:
- this.deltaCounter = (value>>1)&63;
- this.dacLsb = value&1;
- this.sample = ((this.deltaCounter<<1)+this.dacLsb); // update sample value
-
- }else if(address == 0x4012){
-
- // DMA address load register
- this.playStartAddress = (value<<6)|0x0C000;
- this.playAddress = this.playStartAddress;
- this.reg4012 = value;
-
- }else if(address == 0x4013){
-
- // Length of play code
- this.playLength = (value<<4)+1;
- this.playLengthCounter = this.playLength;
- this.reg4013 = value;
-
- }else if(address == 0x4015){
-
- // DMC/IRQ Status
- if(((value>>4)&1)==0){
- // Disable:
- this.playLengthCounter = 0;
- }else{
- // Restart:
- this.playAddress = this.playStartAddress;
- this.playLengthCounter = this.playLength;
- }
- this.irqGenerated = false;
- }
-
-}
-
-ChannelDM.prototype.setEnabled = function(value){
-
- if((!this.isEnabled) && value){
- this.playLengthCounter = this.playLength;
- }
- this.isEnabled = value;
-
-}
-
-ChannelDM.prototype.getLengthStatus = function(){
- return ((this.playLengthCounter==0 || !this.isEnabled)?0:1);
-}
-
-ChannelDM.prototype.getIrqStatus = function(){
- return (this.irqGenerated?1:0);
-}
-
-ChannelDM.prototype.reset = function(){
-
- this.isEnabled = false;
- this.irqGenerated = false;
- this.playMode = this.MODE_NORMAL;
- this.dmaFrequency = 0;
- this.dmaCounter = 0;
- this.deltaCounter = 0;
- this.playStartAddress = 0;
- this.playAddress = 0;
- this.playLength = 0;
- this.playLengthCounter = 0;
- this.status = 0;
- this.sample = 0;
- this.dacLsb = 0;
- this.shiftCounter = 0;
- this.reg4012 = 0;
- this.reg4013 = 0;
- this.data = 0;
-
-}
-
-
-function ChannelNoise(papu) {
- this.papu = papu;
-
- this.isEnabled = null;
- this.envDecayDisable = null;
- this.envDecayLoopEnable = null;
- this.lengthCounterEnable = null;
- this.envReset = null;
- this.shiftNow = null;
-
- this.lengthCounter = null;
- this.progTimerCount = null;
- this.progTimerMax = null;
- this.envDecayRate = null;
- this.envDecayCounter = null;
- this.envVolume = null;
- this.masterVolume = null;
- this.shiftReg = 1<<14;
- this.randomBit = null;
- this.randomMode = null;
- this.sampleValue = null;
- this.accValue=0;
- this.accCount=1;
- this.tmp = null;
-
- this.reset();
-}
-
-ChannelNoise.prototype.reset = function(){
- this.progTimerCount = 0;
- this.progTimerMax = 0;
- this.isEnabled = false;
- this.lengthCounter = 0;
- this.lengthCounterEnable = false;
- this.envDecayDisable = false;
- this.envDecayLoopEnable = false;
- this.shiftNow = false;
- this.envDecayRate = 0;
- this.envDecayCounter = 0;
- this.envVolume = 0;
- this.masterVolume = 0;
- this.shiftReg = 1;
- this.randomBit = 0;
- this.randomMode = 0;
- this.sampleValue = 0;
- this.tmp = 0;
-}
-
-ChannelNoise.prototype.clockLengthCounter = function(){
- if(this.lengthCounterEnable && this.lengthCounter>0){
- this.lengthCounter--;
- if(this.lengthCounter == 0) this.updateSampleValue();
- }
-}
-
-ChannelNoise.prototype.clockEnvDecay = function(){
-
- if(this.envReset){
-
- // Reset envelope:
- this.envReset = false;
- this.envDecayCounter = this.envDecayRate + 1;
- this.envVolume = 0xF;
- }else if(--this.envDecayCounter <= 0){
-
- // Normal handling:
- this.envDecayCounter = this.envDecayRate + 1;
- if(this.envVolume>0){
- this.envVolume--;
- }else{
- this.envVolume = this.envDecayLoopEnable ? 0xF : 0;
- }
- }
- this.masterVolume = this.envDecayDisable ? this.envDecayRate : this.envVolume;
- this.updateSampleValue();
-}
-
-ChannelNoise.prototype.updateSampleValue = function(){
- if(this.isEnabled && this.lengthCounter>0){
- this.sampleValue = this.randomBit * this.masterVolume;
- }
-}
-
-ChannelNoise.prototype.writeReg = function(address, value){
-
- if(address == 0x400C){
-
- // Volume/Envelope decay:
- this.envDecayDisable = ((value&0x10)!=0);
- this.envDecayRate = value&0xF;
- this.envDecayLoopEnable = ((value&0x20)!=0);
- this.lengthCounterEnable = ((value&0x20)==0);
- this.masterVolume = this.envDecayDisable?this.envDecayRate:this.envVolume;
-
- }else if(address == 0x400E){
-
- // Programmable timer:
- this.progTimerMax = this.papu.getNoiseWaveLength(value&0xF);
- this.randomMode = value>>7;
-
- }else if(address == 0x400F){
-
- // Length counter
- this.lengthCounter = this.papu.getLengthMax(value&248);
- this.envReset = true;
-
- }
-
- // Update:
- //updateSampleValue();
-
-}
-
-ChannelNoise.prototype.setEnabled = function(value){
- this.isEnabled = value;
- if(!value) this.lengthCounter = 0;
- this.updateSampleValue();
-}
-
-ChannelNoise.prototype.getLengthStatus = function(){
- return ((this.lengthCounter==0 || !this.isEnabled)?0:1);
-}
-
-
-function ChannelSquare(papu, square1){
- this.papu = papu;
-
- this.dutyLookup = new Array(
- 0, 1, 0, 0, 0, 0, 0, 0,
- 0, 1, 1, 0, 0, 0, 0, 0,
- 0, 1, 1, 1, 1, 0, 0, 0,
- 1, 0, 0, 1, 1, 1, 1, 1
- );
- this.impLookup = new Array(
- 1,-1, 0, 0, 0, 0, 0, 0,
- 1, 0,-1, 0, 0, 0, 0, 0,
- 1, 0, 0, 0,-1, 0, 0, 0,
- -1, 0, 1, 0, 0, 0, 0, 0
- );
-
- this.sqr1 = square1;
- this.isEnabled = null;
- this.lengthCounterEnable = null;
- this.sweepActive = null;
- this.envDecayDisable = null;
- this.envDecayLoopEnable = null;
- this.envReset = null;
- this.sweepCarry = null;
- this.updateSweepPeriod = null;
-
- this.progTimerCount = null;
- this.progTimerMax = null;
- this.lengthCounter = null;
- this.squareCounter = null;
- this.sweepCounter = null;
- this.sweepCounterMax = null;
- this.sweepMode = null;
- this.sweepShiftAmount = null;
- this.envDecayRate = null;
- this.envDecayCounter = null;
- this.envVolume = null;
- this.masterVolume = null;
- this.dutyMode = null;
- this.sweepResult = null;
- this.sampleValue = null;
- this.vol = null;
-
- this.reset();
-}
-
-
-ChannelSquare.prototype.reset = function() {
- this.progTimerCount = 0;
- this.progTimerMax = 0;
- this.lengthCounter = 0;
- this.squareCounter = 0;
- this.sweepCounter = 0;
- this.sweepCounterMax = 0;
- this.sweepMode = 0;
- this.sweepShiftAmount = 0;
- this.envDecayRate = 0;
- this.envDecayCounter = 0;
- this.envVolume = 0;
- this.masterVolume = 0;
- this.dutyMode = 0;
- this.vol = 0;
-
- this.isEnabled = false;
- this.lengthCounterEnable = false;
- this.sweepActive = false;
- this.sweepCarry = false;
- this.envDecayDisable = false;
- this.envDecayLoopEnable = false;
-}
-
-ChannelSquare.prototype.clockLengthCounter = function(){
-
- if(this.lengthCounterEnable && this.lengthCounter>0){
- this.lengthCounter--;
- if(this.lengthCounter==0) this.updateSampleValue();
- }
-
-}
-
-ChannelSquare.prototype.clockEnvDecay = function() {
-
- if(this.envReset){
-
- // Reset envelope:
- this.envReset = false;
- this.envDecayCounter = this.envDecayRate + 1;
- this.envVolume = 0xF;
-
- }else if((--this.envDecayCounter) <= 0){
-
- // Normal handling:
- this.envDecayCounter = this.envDecayRate + 1;
- if(this.envVolume>0){
- this.envVolume--;
- }else{
- this.envVolume = this.envDecayLoopEnable ? 0xF : 0;
- }
-
- }
-
- this.masterVolume = this.envDecayDisable ? this.envDecayRate : this.envVolume;
- this.updateSampleValue();
-
-}
-
-ChannelSquare.prototype.clockSweep = function() {
-
- if(--this.sweepCounter<=0){
-
- this.sweepCounter = this.sweepCounterMax + 1;
- if(this.sweepActive && this.sweepShiftAmount>0 && this.progTimerMax>7){
-
- // Calculate result from shifter:
- this.sweepCarry = false;
- if(this.sweepMode==0){
- this.progTimerMax += (this.progTimerMax>>this.sweepShiftAmount);
- if(this.progTimerMax > 4095){
- this.progTimerMax = 4095;
- this.sweepCarry = true;
- }
- }else{
- this.progTimerMax = this.progTimerMax - ((this.progTimerMax>>this.sweepShiftAmount)-(this.sqr1?1:0));
- }
-
- }
-
- }
-
- if(this.updateSweepPeriod){
- this.updateSweepPeriod = false;
- this.sweepCounter = this.sweepCounterMax + 1;
- }
-
-}
-
-ChannelSquare.prototype.updateSampleValue = function() {
-
- if(this.isEnabled && this.lengthCounter>0 && this.progTimerMax>7){
-
- if(this.sweepMode==0 && (this.progTimerMax + (this.progTimerMax>>this.sweepShiftAmount)) > 4095){
- //if(this.sweepCarry){
- this.sampleValue = 0;
- }else{
- this.sampleValue = this.masterVolume*this.dutyLookup[(this.dutyMode<<3)+this.squareCounter];
- }
- }else{
- this.sampleValue = 0;
- }
-
-}
-
-ChannelSquare.prototype.writeReg = function(address, value){
-
- var addrAdd = (this.sqr1?0:4);
- if(address == 0x4000+addrAdd){
-
- // Volume/Envelope decay:
- this.envDecayDisable = ((value&0x10)!=0);
- this.envDecayRate = value & 0xF;
- this.envDecayLoopEnable = ((value&0x20)!=0);
- this.dutyMode = (value>>6)&0x3;
- this.lengthCounterEnable = ((value&0x20)==0);
- this.masterVolume = this.envDecayDisable?this.envDecayRate:this.envVolume;
- this.updateSampleValue();
-
- }else if(address == 0x4001+addrAdd){
-
- // Sweep:
- this.sweepActive = ((value&0x80)!=0);
- this.sweepCounterMax = ((value>>4)&7);
- this.sweepMode = (value>>3)&1;
- this.sweepShiftAmount = value&7;
- this.updateSweepPeriod = true;
-
- }else if(address == 0x4002+addrAdd){
-
- // Programmable timer:
- this.progTimerMax &= 0x700;
- this.progTimerMax |= value;
-
- }else if(address == 0x4003+addrAdd){
-
- // Programmable timer, length counter
- this.progTimerMax &= 0xFF;
- this.progTimerMax |= ((value&0x7)<<8);
-
- if(this.isEnabled){
- this.lengthCounter = this.papu.getLengthMax(value&0xF8);
- }
-
- this.envReset = true;
-
- }
-
-}
-
-ChannelSquare.prototype.setEnabled = function(value){
- this.isEnabled = value;
- if(!value) this.lengthCounter = 0;
- this.updateSampleValue();
-}
-
-ChannelSquare.prototype.getLengthStatus = function() {
- return ((this.lengthCounter==0 || !this.isEnabled)?0:1);
-}
-
-
-function ChannelTriangle(papu) {
- this.papu = papu;
-
- this.isEnabled = null;
- this.sampleCondition = null;
- this.lengthCounterEnable = null;
- this.lcHalt = null;
- this.lcControl = null;
-
- this.progTimerCount = null;
- this.progTimerMax = null;
- this.triangleCounter = null;
- this.lengthCounter = null;
- this.linearCounter = null;
- this.lcLoadValue = null;
- this.sampleValue = null;
- this.tmp = null;
-
- this.reset();
-}
-
-ChannelTriangle.prototype.reset = function(){
- this.progTimerCount = 0;
- this.progTimerMax = 0;
- this.triangleCounter = 0;
- this.isEnabled = false;
- this.sampleCondition = false;
- this.lengthCounter = 0;
- this.lengthCounterEnable = false;
- this.linearCounter = 0;
- this.lcLoadValue = 0;
- this.lcHalt = true;
- this.lcControl = false;
- this.tmp = 0;
- this.sampleValue = 0xF;
-}
-
-ChannelTriangle.prototype.clockLengthCounter = function(){
- if(this.lengthCounterEnable && this.lengthCounter>0){
- this.lengthCounter--;
- if(this.lengthCounter==0){
- this.updateSampleCondition();
- }
- }
-}
-
-ChannelTriangle.prototype.clockLinearCounter = function(){
- if(this.lcHalt){
- // Load:
- this.linearCounter = this.lcLoadValue;
- this.updateSampleCondition();
- }else if(this.linearCounter > 0){
- // Decrement:
- this.linearCounter--;
- this.updateSampleCondition();
- }
- if(!this.lcControl){
- // Clear halt flag:
- this.lcHalt = false;
- }
-}
-
-ChannelTriangle.prototype.getLengthStatus = function(){
- return ((this.lengthCounter==0 || !this.isEnabled)?0:1);
-}
-
-ChannelTriangle.prototype.readReg = function(address){
- return 0;
-}
-
-ChannelTriangle.prototype.writeReg = function(address, value){
-
- if(address == 0x4008){
-
- // New values for linear counter:
- this.lcControl = (value&0x80)!=0;
- this.lcLoadValue = value&0x7F;
-
- // Length counter enable:
- this.lengthCounterEnable = !this.lcControl;
-
- }else if(address == 0x400A){
-
- // Programmable timer:
- this.progTimerMax &= 0x700;
- this.progTimerMax |= value;
-
- }else if(address == 0x400B){
-
- // Programmable timer, length counter
- this.progTimerMax &= 0xFF;
- this.progTimerMax |= ((value&0x07)<<8);
- this.lengthCounter = this.papu.getLengthMax(value&0xF8);
- this.lcHalt = true;
-
- }
-
- this.updateSampleCondition();
-
-}
-
-ChannelTriangle.prototype.clockProgrammableTimer = function(nCycles){
-
- if(this.progTimerMax>0){
- this.progTimerCount += nCycles;
- while(this.progTimerMax > 0 && this.progTimerCount >= this.progTimerMax){
- this.progTimerCount-=this.progTimerMax;
- if(this.isEnabled && this.lengthCounter>0 && this.linearCounter>0){
- this.clockTriangleGenerator();
- }
- }
- }
-
-}
-
-ChannelTriangle.prototype.clockTriangleGenerator = function(){
- this.triangleCounter++;
- this.triangleCounter &= 0x1F;
-}
-
-ChannelTriangle.prototype.setEnabled = function(value){
- this.isEnabled = value;
- if(!value) this.lengthCounter = 0;
- this.updateSampleCondition();
-}
-
-ChannelTriangle.prototype.updateSampleCondition = function(){
- this.sampleCondition =
- this.isEnabled &&
- this.progTimerMax>7 &&
- this.linearCounter>0 &&
- this.lengthCounter>0
- ;
-}
-
-
-
function PAPU(nes) {
this.nes = nes;
-
- this.buffer = nes;
this.square1 = new ChannelSquare(this,true);
this.square2 = new ChannelSquare(this,false);
@@ -698,7 +13,7 @@ function PAPU(nes) {
this.initCounter = 2048;
this.channelEnableValue = null;
- this.bufferSize = 4096;
+ this.bufferSize = 8192;
this.bufferIndex = 0;
this.sampleRate = 44100;
@@ -707,7 +22,7 @@ function PAPU(nes) {
this.noiseWavelengthLookup = null;
this.square_table = null;
this.tnd_table = null;
- this.sampleBuffer = new Array(this.bufferSize*4);
+ this.sampleBuffer = new Array(this.bufferSize*2);
this.frameIrqEnabled = false;
this.frameIrqActive;
@@ -723,7 +38,6 @@ function PAPU(nes) {
this.frameTime = null;
this.sampleTimerMax = null;
this.sampleCount = null;
- this.triValue = 0;
this.smpSquare1 = null;
this.smpSquare2 = null;
@@ -758,7 +72,6 @@ function PAPU(nes) {
this.stereoPosRDMC = null;
this.extraCycles = null;
- this.maxCycles = null;
// Panning:
this.panning = new Array(
@@ -969,7 +282,7 @@ PAPU.prototype.clockFrameCounter = function(nCycles){
if(this.initCounter > 0){
if(this.initingHardware){
- this.initCounter-=nCycles;
+ this.initCounter -= nCycles;
if(this.initCounter<=0) this.initingHardware = false;
return;
}
@@ -977,10 +290,10 @@ PAPU.prototype.clockFrameCounter = function(nCycles){
// Don't process ticks beyond next sampling:
nCycles += this.extraCycles;
- this.maxCycles = this.sampleTimerMax-this.sampleTimer;
- if((nCycles<<10) > this.maxCycles){
+ var maxCycles = this.sampleTimerMax-this.sampleTimer;
+ if((nCycles<<10) > maxCycles){
- this.extraCycles = ((nCycles<<10) - this.maxCycles)>>10;
+ this.extraCycles = ((nCycles<<10) - maxCycles)>>10;
nCycles -= this.extraCycles;
}else{
@@ -989,38 +302,44 @@ PAPU.prototype.clockFrameCounter = function(nCycles){
}
+ var dmc = this.dmc;
+ var triangle = this.triangle;
+ var square1 = this.square1;
+ var square2 = this.square2;
+ var noise = this.noise;
+
// Clock DMC:
- if(this.dmc.isEnabled){
+ if(dmc.isEnabled){
- this.dmc.shiftCounter-=(nCycles<<3);
- while(this.dmc.shiftCounter<=0 && this.dmc.dmaFrequency>0){
- this.dmc.shiftCounter += this.dmc.dmaFrequency;
- this.dmc.clockDmc();
+ dmc.shiftCounter-=(nCycles<<3);
+ while(dmc.shiftCounter<=0 && dmc.dmaFrequency>0){
+ dmc.shiftCounter += dmc.dmaFrequency;
+ dmc.clockDmc();
}
}
// Clock Triangle channel Prog timer:
- if(this.triangle.progTimerMax>0){
+ if(triangle.progTimerMax>0){
- this.triangle.progTimerCount -= nCycles;
- while(this.triangle.progTimerCount <= 0){
+ triangle.progTimerCount -= nCycles;
+ while(triangle.progTimerCount <= 0){
- this.triangle.progTimerCount += this.triangle.progTimerMax+1;
- if(this.triangle.linearCounter>0 && this.triangle.lengthCounter>0){
+ triangle.progTimerCount += triangle.progTimerMax+1;
+ if(triangle.linearCounter>0 && triangle.lengthCounter>0){
- this.triangle.triangleCounter++;
- this.triangle.triangleCounter &= 0x1F;
+ triangle.triangleCounter++;
+ triangle.triangleCounter &= 0x1F;
- if(this.triangle.isEnabled){
- if(this.triangle.triangleCounter>=0x10){
+ if(triangle.isEnabled){
+ if(triangle.triangleCounter>=0x10){
// Normal value.
- this.triangle.sampleValue = (this.triangle.triangleCounter&0xF);
+ triangle.sampleValue = (triangle.triangleCounter&0xF);
}else{
// Inverted value.
- this.triangle.sampleValue = (0xF - (this.triangle.triangleCounter&0xF));
+ triangle.sampleValue = (0xF - (triangle.triangleCounter&0xF));
}
- this.triangle.sampleValue <<= 4;
+ triangle.sampleValue <<= 4;
}
}
@@ -1029,124 +348,117 @@ PAPU.prototype.clockFrameCounter = function(nCycles){
}
// Clock Square channel 1 Prog timer:
- this.square1.progTimerCount -= nCycles;
- if(this.square1.progTimerCount <= 0){
+ square1.progTimerCount -= nCycles;
+ if(square1.progTimerCount <= 0){
- this.square1.progTimerCount += (this.square1.progTimerMax+1)<<1;
+ square1.progTimerCount += (square1.progTimerMax+1)<<1;
- this.square1.squareCounter++;
- this.square1.squareCounter&=0x7;
- this.square1.updateSampleValue();
+ square1.squareCounter++;
+ square1.squareCounter&=0x7;
+ square1.updateSampleValue();
}
// Clock Square channel 2 Prog timer:
- this.square2.progTimerCount -= nCycles;
- if(this.square2.progTimerCount <= 0){
+ square2.progTimerCount -= nCycles;
+ if(square2.progTimerCount <= 0){
- this.square2.progTimerCount += (this.square2.progTimerMax+1)<<1;
+ square2.progTimerCount += (square2.progTimerMax+1)<<1;
- this.square2.squareCounter++;
- this.square2.squareCounter&=0x7;
- this.square2.updateSampleValue();
+ square2.squareCounter++;
+ square2.squareCounter&=0x7;
+ square2.updateSampleValue();
}
// Clock noise channel Prog timer:
var acc_c = nCycles;
- if(this.noise.progTimerCount-acc_c > 0){
+ if(noise.progTimerCount-acc_c > 0){
// Do all cycles at once:
- this.noise.progTimerCount -= acc_c;
- this.noise.accCount += acc_c;
- this.noise.accValue += acc_c * this.noise.sampleValue;
+ noise.progTimerCount -= acc_c;
+ noise.accCount += acc_c;
+ noise.accValue += acc_c * noise.sampleValue;
}else{
// Slow-step:
while((acc_c--) > 0){
- if(--this.noise.progTimerCount <= 0 && this.noise.progTimerMax>0){
+ if(--noise.progTimerCount <= 0 && noise.progTimerMax>0){
// Update noise shift register:
- this.noise.shiftReg <<= 1;
- this.noise.tmp = (((this.noise.shiftReg << (this.noise.randomMode==0?1:6)) ^ this.noise.shiftReg) & 0x8000 );
- if(this.noise.tmp!=0){
+ noise.shiftReg <<= 1;
+ noise.tmp = (((noise.shiftReg << (noise.randomMode==0?1:6)) ^ noise.shiftReg) & 0x8000 );
+ if(noise.tmp!=0){
// Sample value must be 0.
- this.noise.shiftReg |= 0x01;
- this.noise.randomBit = 0;
- this.noise.sampleValue = 0;
+ noise.shiftReg |= 0x01;
+ noise.randomBit = 0;
+ noise.sampleValue = 0;
}else{
// Find sample value:
- this.noise.randomBit = 1;
- if(this.noise.isEnabled && this.noise.lengthCounter>0){
- this.noise.sampleValue = this.noise.masterVolume;
+ noise.randomBit = 1;
+ if(noise.isEnabled && noise.lengthCounter>0){
+ noise.sampleValue = noise.masterVolume;
}else{
- this.noise.sampleValue = 0;
+ noise.sampleValue = 0;
}
}
- this.noise.progTimerCount += this.noise.progTimerMax;
+ noise.progTimerCount += noise.progTimerMax;
}
- this.noise.accValue += this.noise.sampleValue;
- this.noise.accCount++;
+ noise.accValue += noise.sampleValue;
+ noise.accCount++;
}
}
// Frame IRQ handling:
- if(this.frameIrqEnabled && this.frameIrqActive){
+ if (this.frameIrqEnabled && this.frameIrqActive){
this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NORMAL);
}
// Clock frame counter at double CPU speed:
- this.masterFrameCounter+=(nCycles<<1);
- if(this.masterFrameCounter>=this.frameTime){
-
+ this.masterFrameCounter += (nCycles<<1);
+ if (this.masterFrameCounter >= this.frameTime) {
// 240Hz tick:
this.masterFrameCounter -= this.frameTime;
this.frameCounterTick();
-
-
}
-
// Accumulate sample value:
this.accSample(nCycles);
-
// Clock sample timer:
this.sampleTimer += nCycles<<10;
if(this.sampleTimer>=this.sampleTimerMax){
-
// Sample channels:
this.sample();
this.sampleTimer -= this.sampleTimerMax;
-
}
-
}
PAPU.prototype.accSample = function(cycles){
+ var triangle = this.triangle;
// Special treatment for triangle channel - need to interpolate.
- if(this.triangle.sampleCondition){
+ if(triangle.sampleCondition){
- this.triValue = parseInt((this.triangle.progTimerCount<<4) / (this.triangle.progTimerMax+1));
- if(this.triValue>16) this.triValue = 16;
- if(this.triangle.triangleCounter >= 16){
- this.triValue = 16-this.triValue;
+ var triValue = parseInt((triangle.progTimerCount<<4) / (triangle.progTimerMax+1));
+ if(triValue>16) triValue = 16;
+ if(triangle.triangleCounter >= 16){
+ triValue = 16-triValue;
}
// Add non-interpolated sample value:
- this.triValue += this.triangle.sampleValue;
+ triValue += triangle.sampleValue;
}
@@ -1154,7 +466,7 @@ PAPU.prototype.accSample = function(cycles){
// Now sample normally:
if(cycles == 2){
- this.smpTriangle += this.triValue << 1;
+ this.smpTriangle += triValue << 1;
this.smpDmc += this.dmc.sample << 1;
this.smpSquare1 += this.square1.sampleValue << 1;
this.smpSquare2 += this.square2.sampleValue << 1;
@@ -1162,7 +474,7 @@ PAPU.prototype.accSample = function(cycles){
}else if(cycles == 4){
- this.smpTriangle += this.triValue << 2;
+ this.smpTriangle += triValue << 2;
this.smpDmc += this.dmc.sample << 2;
this.smpSquare1 += this.square1.sampleValue << 2;
this.smpSquare2 += this.square2.sampleValue << 2;
@@ -1170,7 +482,7 @@ PAPU.prototype.accSample = function(cycles){
}else{
- this.smpTriangle += cycles * this.triValue;
+ this.smpTriangle += cycles * triValue;
this.smpDmc += cycles * this.dmc.sample;
this.smpSquare1 += cycles * this.square1.sampleValue;
this.smpSquare2 += cycles * this.square2.sampleValue;
@@ -1284,7 +596,7 @@ PAPU.prototype.sample = function(){
sampleValueR = this.smpAccumR;
// Write:
- if(this.bufferIndex+4 < this.sampleBuffer.length){
+ if(this.bufferIndex+2 < this.sampleBuffer.length){
if (sampleValueL > this.maxSample) this.maxSample = sampleValueL;
if (sampleValueL < this.minSample) this.minSample = sampleValueL;
this.sampleBuffer[this.bufferIndex++] = (sampleValueL );
@@ -1301,12 +613,15 @@ PAPU.prototype.sample = function(){
PAPU.prototype.readBuffer = function() {
//console.debug(this.bufferIndex);
- if (this.bufferIndex+4 >= this.sampleBuffer.length) {
+ if (this.bufferIndex >= 2048) {
var b = this.sampleBuffer;
- this.sampleBuffer = new Array(this.bufferSize*4);
+ this.sampleBuffer = new Array(this.bufferSize*2);
this.bufferIndex = 0;
return b
}
+ else {
+ console.debug("Insufficient buffer: "+this.bufferIndex);
+ }
}
PAPU.prototype.getLengthMax = function(value){
diff --git a/ppu.js b/ppu.js
index e60ff20..f0c9a8d 100644
--- a/ppu.js
+++ b/ppu.js
@@ -43,7 +43,6 @@ function PPU(nes) {
this.vramMirrorTable = null; // Mirroring Lookup Table.
- this.i = null;
// SPR-RAM I/O:
this.sramAddress = null; // 8-bit only.
@@ -89,13 +88,12 @@ function PPU(nes) {
// Name table data:
this.ntable1 = new Array(4);
this.nameTable = null;
- this.currentMirroring=-1;
+ this.currentMirroring = -1;
// Palette data:
this.sprPalette = new Array(16);
this.imgPalette = new Array(16);
-
// Misc:
this.scanlineAlreadyRendered = null;
this.requestEndFrame = null;
@@ -104,14 +102,6 @@ function PPU(nes) {
this.tmp = null;
this.dummyCycleToggle = null;
- // Vars used when updating regs/address:
- this.address = null;
- this.b1 = null;
- this.b2 = null;
-
-
-
-
// Variables used when rendering:
this.attrib = new Array(32);
this.buffer = new Array(256*240);
@@ -120,38 +110,15 @@ function PPU(nes) {
this.pixrendered = new Array(256*240);
this.spr0dummybuffer = new Array(256*240);
this.dummyPixPriTable = new Array(256*240);
- this.oldFrame = new Array(256*240);
- this.tpix = null;
- this.scanlineChanged = Array(240);
- this.requestRenderAll=false;
this.validTileData = null;
- this.att = null;
this.scantile = new Array(32);
- this.t = null;
-
-
-
// These are temporary variables used in rendering and sound procedures.
// Their states outside of those procedures can be ignored.
+ // TODO: the use of this is a bit weird, investigate
this.curNt = null;
- this.destIndex = null;
- this.x = null;
- this.y = null;
- this.sx = null;
- this.si = null;
- this.ei = null;
- this.tile = null;
- this.col = null;
- this.baseTile = null;
- this.tscanoffset = null;
- this.srcy1 = null;
- this.srcy2 = null;
- this.bufferSize = null;
- this.available = null;
- this.scale = null;
this.init = function() {
// Get the memory:
@@ -196,10 +163,6 @@ function PPU(nes) {
this.lastRenderedScanline = -1;
this.curX = 0;
- // Initialize old frame buffer:
- for(var i=0;i<this.oldFrame.length;i++){
- this.oldFrame[i]=-1;
- }
}
@@ -813,59 +776,59 @@ function PPU(nes) {
// Updates the scroll registers from a new VRAM address.
this.regsFromAddress = function(){
- this.address = (this.vramTmpAddress>>8)&0xFF;
- this.regFV = (this.address>>4)&7;
- this.regV = (this.address>>3)&1;
- this.regH = (this.address>>2)&1;
- this.regVT = (this.regVT&7) | ((this.address&3)<<3);
+ var address = (this.vramTmpAddress>>8)&0xFF;
+ this.regFV = (address>>4)&7;
+ this.regV = (address>>3)&1;
+ this.regH = (address>>2)&1;
+ this.regVT = (this.regVT&7) | ((address&3)<<3);
- this.address = this.vramTmpAddress&0xFF;
- this.regVT = (this.regVT&24) | ((this.address>>5)&7);
- this.regHT = this.address&31;
+ address = this.vramTmpAddress&0xFF;
+ this.regVT = (this.regVT&24) | ((address>>5)&7);
+ this.regHT = address&31;
}
// Updates the scroll registers from a new VRAM address.
this.cntsFromAddress = function(){
- this.address = (this.vramAddress>>8)&0xFF;
- this.cntFV = (this.address>>4)&3;
- this.cntV = (this.address>>3)&1;
- this.cntH = (this.address>>2)&1;
- this.cntVT = (this.cntVT&7) | ((this.address&3)<<3);
+ var address = (this.vramAddress>>8)&0xFF;
+ this.cntFV = (address>>4)&3;
+ this.cntV = (address>>3)&1;
+ this.cntH = (address>>2)&1;
+ this.cntVT = (this.cntVT&7) | ((address&3)<<3);
- this.address = this.vramAddress&0xFF;
- this.cntVT = (this.cntVT&24) | ((this.address>>5)&7);
- this.cntHT = this.address&31;
+ address = this.vramAddress&0xFF;
+ this.cntVT = (this.cntVT&24) | ((address>>5)&7);
+ this.cntHT = address&31;
}
this.regsToAddress = function(){
- this.b1 = (this.regFV&7)<<4;
- this.b1 |= (this.regV&1)<<3;
- this.b1 |= (this.regH&1)<<2;
- this.b1 |= (this.regVT>>3)&3;
+ var b1 = (this.regFV&7)<<4;
+ b1 |= (this.regV&1)<<3;
+ b1 |= (this.regH&1)<<2;
+ b1 |= (this.regVT>>3)&3;
- this.b2 = (this.regVT&7)<<5;
- this.b2 |= this.regHT&31;
+ var b2 = (this.regVT&7)<<5;
+ b2 |= this.regHT&31;
- this.vramTmpAddress = ((this.b1<<8) | this.b2)&0x7FFF;
+ this.vramTmpAddress = ((b1<<8) | b2)&0x7FFF;
}
this.cntsToAddress = function(){
- this.b1 = (this.cntFV&7)<<4;
- this.b1 |= (this.cntV&1)<<3;
- this.b1 |= (this.cntH&1)<<2;
- this.b1 |= (this.cntVT>>3)&3;
+ var b1 = (this.cntFV&7)<<4;
+ b1 |= (this.cntV&1)<<3;
+ b1 |= (this.cntH&1)<<2;
+ b1 |= (this.cntVT>>3)&3;
- this.b2 = (this.cntVT&7)<<5;
- this.b2 |= this.cntHT&31;
+ var b2 = (this.cntVT&7)<<5;
+ b2 |= this.cntHT&31;
- this.vramAddress = ((this.b1<<8) | this.b2)&0x7FFF;
+ this.vramAddress = ((b1<<8) | b2)&0x7FFF;
}
this.incTileCounter = function(count){
- for(this.i=count;this.i!=0;this.i--){
+ for(var i=count; i!=0; i--){
this.cntHT++;
if(this.cntHT==32){
this.cntHT=0;
@@ -967,7 +930,6 @@ function PPU(nes) {
var pixrendered = this.pixrendered;
for(var destIndex=si;destIndex<ei;destIndex++){
if(pixrendered[destIndex]>0xFF){
- //console.log("Writing "+this.imgPalette[this.col+this.att].toString(16)+" to buffer at "+this.destIndex.toString(16));
buffer[destIndex] = bgbuffer[destIndex];
}
}
@@ -977,28 +939,6 @@ function PPU(nes) {
this.renderSpritesPartially(startScan,scanCount,false);
}
- /*BufferView screen = nes.getGui().getScreenView();
- if(screen.scalingEnabled() && !screen.useHWScaling() && !requestRenderAll){
-
- // Check which scanlines have changed, to try to
- // speed up scaling:
- int j,jmax;
- if(startScan+scanCount>240)scanCount=240-startScan;
- for(int i=startScan;i<startScan+scanCount;i++){
- scanlineChanged[i]=false;
- si = i<<8;
- jmax = si+256;
- for(j=si;j<jmax;j++){
- if(buffer[j]!=oldFrame[j]){
- scanlineChanged[i]=true;
- break;
- }
- oldFrame[j]=buffer[j];
- }
- System.arraycopy(buffer,j,oldFrame,j,jmax-j);
- }
-
- }*/
this.validTileData = false;
@@ -1067,7 +1007,6 @@ function PPU(nes) {
for(;sx<8;sx++){
col = tpix[tscanoffset+sx];
if(col != 0){
- //console.log("Writing "+this.imgPalette[this.col+this.att].toString(16)+" to buffer at "+this.destIndex.toString(16));
var pix = imgPalette[col+att];
targetBuffer[destIndex] = pix;
pixrendered[destIndex] |= 256;
@@ -1132,7 +1071,7 @@ function PPU(nes) {
this.srcy2 = 8;
if(this.sprY[i]<startscan){
- srcy1 = startscan - this.sprY[i]-1;
+ this.srcy1 = startscan - this.sprY[i]-1;
}
if(this.sprY[i]+8 > startscan+scancount){
@@ -1151,21 +1090,21 @@ function PPU(nes) {
top = this.sprTile[i]-1+256;
}
- this.srcy1 = 0;
- this.srcy2 = 8;
+ var srcy1 = 0;
+ var srcy2 = 8;
if(this.sprY[i]<startscan){
- this.srcy1 = startscan - this.sprY[i]-1;
+ srcy1 = startscan - this.sprY[i]-1;
}
if(this.sprY[i]+8 > startscan+scancount){
- this.srcy2 = startscan+scancount-this.sprY[i];
+ srcy2 = startscan+scancount-this.sprY[i];
}
this.ptTile[top+(this.vertFlip[i]?1:0)].render(0,
- this.srcy1,
+ srcy1,
8,
- this.srcy2,
+ srcy2,
this.sprX[i],
this.sprY[i]+1,
this.sprCol[i],
@@ -1176,22 +1115,22 @@ function PPU(nes) {
this.pixrendered
);
- this.srcy1 = 0;
- this.srcy2 = 8;
+ var srcy1 = 0;
+ var srcy2 = 8;
if(this.sprY[i]+8<startscan){
- this.srcy1 = startscan - (this.sprY[i]+8+1);
+ srcy1 = startscan - (this.sprY[i]+8+1);
}
if(this.sprY[i]+16 > startscan+scancount){
- this.srcy2 = startscan+scancount-(this.sprY[i]+8);
+ srcy2 = startscan+scancount-(this.sprY[i]+8);
}
this.ptTile[top+(this.vertFlip[i]?0:1)].render(
0,
- this.srcy1,
+ srcy1,
8,
- this.srcy2,
+ srcy2,
this.sprX[i],
this.sprY[i]+1+8,
this.sprCol[i],
@@ -1443,16 +1382,6 @@ function PPU(nes) {
leftOver-8, this.ppuMem[address-8], value);
}
}
-
- this.invalidateFrameCache = function(){
-
- // Clear the no-update scanline buffer:
- for(var i=0;i<240;i++) this.scanlineChanged[i]=true;
-
- for(var i=0;i<this.oldFrame.length;i++) this.oldFrame[i]=-1;
- this.requestRenderAll = true;
-
- }
// Updates the internal name table buffers
// with this new byte.
@@ -1533,8 +1462,6 @@ function PPU(nes) {
this.validTileData = false;
this.nmiCounter = 0;
this.tmp = 0;
- this.att = 0;
- this.i = 0;
// Control Flags Register 1:
this.f_nmiOnVblank = 0; // NMI on VBlank. 0=disable, 1=enable
@@ -1566,10 +1493,6 @@ function PPU(nes) {
this.regFH = 0;
this.regS = 0;
- for (var i=0; i<this.scanlineChanged.length;i++)
- this.scanlineChanged[i] = true;
- for (var i=0; i<this.oldFrame.length;i++)
- this.oldFrame[i] = -1;
// Initialize stuff:
this.init();