/* JSNES, based on Jamie Sanders' vNES Copyright (C) 2010 Ben Firshman This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ JSNES.PAPU = function(nes) { this.nes = nes; this.square1 = new JSNES.PAPU.ChannelSquare(this, true); this.square2 = new JSNES.PAPU.ChannelSquare(this, false); this.triangle = new JSNES.PAPU.ChannelTriangle(this); this.noise = new JSNES.PAPU.ChannelNoise(this); this.dmc = new JSNES.PAPU.ChannelDM(this); this.frameIrqCounter = null; this.frameIrqCounterMax = 4; this.initCounter = 2048; this.channelEnableValue = null; this.bufferSize = 8192; this.bufferIndex = 0; this.sampleRate = 44100; this.lengthLookup = null; this.dmcFreqLookup = null; this.noiseWavelengthLookup = null; this.square_table = null; this.tnd_table = null; this.sampleBuffer = new Array(this.bufferSize*2); this.frameIrqEnabled = false; this.frameIrqActive = null; this.frameClockNow = null; this.startedPlaying=false; this.recordOutput = false; this.initingHardware = false; this.masterFrameCounter = null; this.derivedFrameCounter = null; this.countSequence = null; this.sampleTimer = null; this.frameTime = null; this.sampleTimerMax = null; this.sampleCount = null; this.triValue = 0; this.smpSquare1 = null; this.smpSquare2 = null; this.smpTriangle = null; this.smpDmc = null; this.accCount = null; // DC removal vars: this.prevSampleL = 0; this.prevSampleR = 0; this.smpAccumL = 0; this.smpAccumR = 0; // DAC range: this.dacRange = 0; this.dcValue = 0; // Master volume: this.masterVolume = 256; // Stereo positioning: this.stereoPosLSquare1 = null; this.stereoPosLSquare2 = null; this.stereoPosLTriangle = null; this.stereoPosLNoise = null; this.stereoPosLDMC = null; this.stereoPosRSquare1 = null; this.stereoPosRSquare2 = null; this.stereoPosRTriangle = null; this.stereoPosRNoise = null; this.stereoPosRDMC = null; this.extraCycles = null; this.maxSample = null; this.minSample = null; // Panning: this.panning = [80, 170, 100, 150, 128]; this.setPanning(this.panning); // Initialize lookup tables: this.initLengthLookup(); this.initDmcFrequencyLookup(); this.initNoiseWavelengthLookup(); this.initDACtables(); // Init sound registers: for (var i = 0; i < 0x14; i++) { if (i === 0x10){ this.writeReg(0x4010, 0x10); } else { this.writeReg(0x4000 + i, 0); } } this.reset(); }; JSNES.PAPU.prototype = { reset: function() { this.sampleRate = this.nes.opts.sampleRate; this.sampleTimerMax = Math.floor( (1024.0 * this.nes.opts.CPU_FREQ_NTSC * this.nes.opts.preferredFrameRate) / (this.sampleRate * 60.0) ); this.frameTime = Math.floor( (14915.0 * this.nes.opts.preferredFrameRate) / 60.0 ); this.sampleTimer = 0; this.bufferIndex = 0; this.updateChannelEnable(0); this.masterFrameCounter = 0; this.derivedFrameCounter = 0; this.countSequence = 0; this.sampleCount = 0; this.initCounter = 2048; this.frameIrqEnabled = false; this.initingHardware = false; this.resetCounter(); this.square1.reset(); this.square2.reset(); this.triangle.reset(); this.noise.reset(); this.dmc.reset(); this.bufferIndex = 0; this.accCount = 0; this.smpSquare1 = 0; this.smpSquare2 = 0; this.smpTriangle = 0; this.smpDmc = 0; this.frameIrqEnabled = false; this.frameIrqCounterMax = 4; this.channelEnableValue = 0xFF; this.startedPlaying = false; this.prevSampleL = 0; this.prevSampleR = 0; this.smpAccumL = 0; this.smpAccumR = 0; this.maxSample = -500000; this.minSample = 500000; }, readReg: function(address){ // Read 0x4015: var tmp = 0; tmp |= (this.square1.getLengthStatus() ); tmp |= (this.square2.getLengthStatus() <<1); tmp |= (this.triangle.getLengthStatus()<<2); tmp |= (this.noise.getLengthStatus() <<3); tmp |= (this.dmc.getLengthStatus() <<4); tmp |= (((this.frameIrqActive && this.frameIrqEnabled)? 1 : 0) << 6); tmp |= (this.dmc.getIrqStatus() <<7); this.frameIrqActive = false; this.dmc.irqGenerated = false; return tmp & 0xFFFF; }, writeReg: function(address, value){ if (address >= 0x4000 && address < 0x4004) { // Square Wave 1 Control this.square1.writeReg(address, value); ////System.out.println("Square Write"); } else if (address >= 0x4004 && address < 0x4008) { // Square 2 Control this.square2.writeReg(address, value); } else if (address >= 0x4008 && address < 0x400C) { // Triangle Control this.triangle.writeReg(address, value); } else if (address >= 0x400C && address <= 0x400F) { // Noise Control this.noise.writeReg(address, value); } else if (address === 0x4010){ // DMC Play mode & DMA frequency this.dmc.writeReg(address, value); } else if (address === 0x4011){ // DMC Delta Counter this.dmc.writeReg(address, value); } else if (address === 0x4012){ // DMC Play code starting address this.dmc.writeReg(address, value); } else if (address === 0x4013){ // DMC Play code length this.dmc.writeReg(address, value); } else if (address === 0x4015){ // Channel enable this.updateChannelEnable(value); if (value !== 0 && this.initCounter > 0) { // Start hardware initialization this.initingHardware = true; } // DMC/IRQ Status this.dmc.writeReg(address, value); } else if (address === 0x4017) { // Frame counter control this.countSequence = (value>>7)&1; this.masterFrameCounter = 0; this.frameIrqActive = false; if (((value>>6)&0x1)===0){ this.frameIrqEnabled = true; } else { this.frameIrqEnabled = false; } if (this.countSequence === 0) { // NTSC: this.frameIrqCounterMax = 4; this.derivedFrameCounter = 4; } else { // PAL: this.frameIrqCounterMax = 5; this.derivedFrameCounter = 0; this.frameCounterTick(); } } }, resetCounter: function(){ if (this.countSequence === 0) { this.derivedFrameCounter = 4; }else{ this.derivedFrameCounter = 0; } }, // Updates channel enable status. // This is done on writes to the // channel enable register (0x4015), // and when the user enables/disables channels // in the GUI. updateChannelEnable: function(value){ this.channelEnableValue = value&0xFFFF; this.square1.setEnabled((value&1) !== 0); this.square2.setEnabled((value&2) !== 0); this.triangle.setEnabled((value&4) !== 0); this.noise.setEnabled((value&8) !== 0); this.dmc.setEnabled((value&16) !== 0); }, // Clocks the frame counter. It should be clocked at // twice the cpu speed, so the cycles will be // divided by 2 for those counters that are // clocked at cpu speed. clockFrameCounter: function(nCycles){ if (this.initCounter > 0) { if (this.initingHardware) { this.initCounter -= nCycles; if (this.initCounter <= 0) { this.initingHardware = false; } return; } } // Don't process ticks beyond next sampling: nCycles += this.extraCycles; var maxCycles = this.sampleTimerMax-this.sampleTimer; if ((nCycles<<10) > maxCycles) { this.extraCycles = ((nCycles<<10) - maxCycles)>>10; nCycles -= this.extraCycles; }else{ this.extraCycles = 0; } var dmc = this.dmc; var triangle = this.triangle; var square1 = this.square1; var square2 = this.square2; var noise = this.noise; // Clock DMC: if (dmc.isEnabled) { dmc.shiftCounter-=(nCycles<<3); while(dmc.shiftCounter<=0 && dmc.dmaFrequency>0){ dmc.shiftCounter += dmc.dmaFrequency; dmc.clockDmc(); } } // Clock Triangle channel Prog timer: if (triangle.progTimerMax>0) { triangle.progTimerCount -= nCycles; while(triangle.progTimerCount <= 0){ triangle.progTimerCount += triangle.progTimerMax+1; if (triangle.linearCounter>0 && triangle.lengthCounter>0) { triangle.triangleCounter++; triangle.triangleCounter &= 0x1F; if (triangle.isEnabled) { if (triangle.triangleCounter>=0x10) { // Normal value. triangle.sampleValue = (triangle.triangleCounter&0xF); }else{ // Inverted value. triangle.sampleValue = (0xF - (triangle.triangleCounter&0xF)); } triangle.sampleValue <<= 4; } } } } // Clock Square channel 1 Prog timer: square1.progTimerCount -= nCycles; if (square1.progTimerCount <= 0) { square1.progTimerCount += (square1.progTimerMax+1)<<1; square1.squareCounter++; square1.squareCounter&=0x7; square1.updateSampleValue(); } // Clock Square channel 2 Prog timer: square2.progTimerCount -= nCycles; if (square2.progTimerCount <= 0) { square2.progTimerCount += (square2.progTimerMax+1)<<1; square2.squareCounter++; square2.squareCounter&=0x7; square2.updateSampleValue(); } // Clock noise channel Prog timer: var acc_c = nCycles; if (noise.progTimerCount-acc_c > 0) { // Do all cycles at once: noise.progTimerCount -= acc_c; noise.accCount += acc_c; noise.accValue += acc_c * noise.sampleValue; }else{ // Slow-step: while((acc_c--) > 0){ if (--noise.progTimerCount <= 0 && noise.progTimerMax>0) { // Update noise shift register: noise.shiftReg <<= 1; noise.tmp = (((noise.shiftReg << (noise.randomMode===0?1:6)) ^ noise.shiftReg) & 0x8000 ); if (noise.tmp !== 0) { // Sample value must be 0. noise.shiftReg |= 0x01; noise.randomBit = 0; noise.sampleValue = 0; }else{ // Find sample value: noise.randomBit = 1; if (noise.isEnabled && noise.lengthCounter>0) { noise.sampleValue = noise.masterVolume; }else{ noise.sampleValue = 0; } } noise.progTimerCount += noise.progTimerMax; } noise.accValue += noise.sampleValue; noise.accCount++; } } // Frame IRQ handling: 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) { // 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; } }, accSample: function(cycles) { // Special treatment for triangle channel - need to interpolate. if (this.triangle.sampleCondition) { this.triValue = Math.floor((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; } // Add non-interpolated sample value: this.triValue += this.triangle.sampleValue; } // Now sample normally: if (cycles === 2) { this.smpTriangle += this.triValue << 1; this.smpDmc += this.dmc.sample << 1; this.smpSquare1 += this.square1.sampleValue << 1; this.smpSquare2 += this.square2.sampleValue << 1; this.accCount += 2; }else if (cycles === 4) { this.smpTriangle += this.triValue << 2; this.smpDmc += this.dmc.sample << 2; this.smpSquare1 += this.square1.sampleValue << 2; this.smpSquare2 += this.square2.sampleValue << 2; this.accCount += 4; }else{ this.smpTriangle += cycles * this.triValue; this.smpDmc += cycles * this.dmc.sample; this.smpSquare1 += cycles * this.square1.sampleValue; this.smpSquare2 += cycles * this.square2.sampleValue; this.accCount += cycles; } }, frameCounterTick: function(){ this.derivedFrameCounter++; if (this.derivedFrameCounter >= this.frameIrqCounterMax) { this.derivedFrameCounter = 0; } if (this.derivedFrameCounter===1 || this.derivedFrameCounter===3) { // Clock length & sweep: this.triangle.clockLengthCounter(); this.square1.clockLengthCounter(); this.square2.clockLengthCounter(); this.noise.clockLengthCounter(); this.square1.clockSweep(); this.square2.clockSweep(); } if (this.derivedFrameCounter >= 0 && this.derivedFrameCounter < 4) { // Clock linear & decay: this.square1.clockEnvDecay(); this.square2.clockEnvDecay(); this.noise.clockEnvDecay(); this.triangle.clockLinearCounter(); } if (this.derivedFrameCounter === 3 && this.countSequence===0) { // Enable IRQ: this.frameIrqActive = true; } // End of 240Hz tick }, // Samples the channels, mixes the output together, // writes to buffer and (if enabled) file. sample: function(){ var sq_index, tnd_index; if (this.accCount > 0) { this.smpSquare1 <<= 4; this.smpSquare1 = Math.floor(this.smpSquare1 / this.accCount); this.smpSquare2 <<= 4; this.smpSquare2 = Math.floor(this.smpSquare2 / this.accCount); this.smpTriangle = Math.floor(this.smpTriangle / this.accCount); this.smpDmc <<= 4; this.smpDmc = Math.floor(this.smpDmc / this.accCount); this.accCount = 0; } else { this.smpSquare1 = this.square1.sampleValue << 4; this.smpSquare2 = this.square2.sampleValue << 4; this.smpTriangle = this.triangle.sampleValue; this.smpDmc = this.dmc.sample << 4; } var smpNoise = Math.floor((this.noise.accValue << 4) / this.noise.accCount); this.noise.accValue = smpNoise >> 4; this.noise.accCount = 1; // Stereo sound. // Left channel: sq_index = ( this.smpSquare1 * this.stereoPosLSquare1 + this.smpSquare2 * this.stereoPosLSquare2 ) >> 8; tnd_index = ( 3 * this.smpTriangle * this.stereoPosLTriangle + (smpNoise<<1) * this.stereoPosLNoise + this.smpDmc * this.stereoPosLDMC ) >> 8; if (sq_index >= this.square_table.length) { sq_index = this.square_table.length-1; } if (tnd_index >= this.tnd_table.length) { tnd_index = this.tnd_table.length - 1; } var sampleValueL = this.square_table[sq_index] + this.tnd_table[tnd_index] - this.dcValue; // Right channel: sq_index = (this.smpSquare1 * this.stereoPosRSquare1 + this.smpSquare2 * this.stereoPosRSquare2 ) >> 8; tnd_index = (3 * this.smpTriangle * this.stereoPosRTriangle + (smpNoise << 1) * this.stereoPosRNoise + this.smpDmc * this.stereoPosRDMC ) >> 8; if (sq_index >= this.square_table.length) { sq_index = this.square_table.length - 1; } if (tnd_index >= this.tnd_table.length) { tnd_index = this.tnd_table.length - 1; } var sampleValueR = this.square_table[sq_index] + this.tnd_table[tnd_index] - this.dcValue; // Remove DC from left channel: var smpDiffL = sampleValueL - this.prevSampleL; this.prevSampleL += smpDiffL; this.smpAccumL += smpDiffL - (this.smpAccumL >> 10); sampleValueL = this.smpAccumL; // Remove DC from right channel: var smpDiffR = sampleValueR - this.prevSampleR; this.prevSampleR += smpDiffR; this.smpAccumR += smpDiffR - (this.smpAccumR >> 10); sampleValueR = this.smpAccumR; // Write: if (sampleValueL > this.maxSample) { this.maxSample = sampleValueL; } if (sampleValueL < this.minSample) { this.minSample = sampleValueL; } this.sampleBuffer[this.bufferIndex++] = sampleValueL; this.sampleBuffer[this.bufferIndex++] = sampleValueR; // Write full buffer if (this.bufferIndex === this.sampleBuffer.length) { this.nes.ui.writeAudio(this.sampleBuffer); this.sampleBuffer = new Array(this.bufferSize*2); this.bufferIndex = 0; } // Reset sampled values: this.smpSquare1 = 0; this.smpSquare2 = 0; this.smpTriangle = 0; this.smpDmc = 0; }, getLengthMax: function(value){ return this.lengthLookup[value >> 3]; }, getDmcFrequency: function(value){ if (value >= 0 && value < 0x10) { return this.dmcFreqLookup[value]; } return 0; }, getNoiseWaveLength: function(value){ if (value >= 0 && value < 0x10) { return this.noiseWavelengthLookup[value]; } return 0; }, setPanning: function(pos){ for (var i = 0; i < 5; i++) { this.panning[i] = pos[i]; } this.updateStereoPos(); }, setMasterVolume: function(value){ if (value < 0) { value = 0; } if (value > 256) { value = 256; } this.masterVolume = value; this.updateStereoPos(); }, updateStereoPos: function(){ this.stereoPosLSquare1 = (this.panning[0] * this.masterVolume) >> 8; this.stereoPosLSquare2 = (this.panning[1] * this.masterVolume) >> 8; this.stereoPosLTriangle = (this.panning[2] * this.masterVolume) >> 8; this.stereoPosLNoise = (this.panning[3] * this.masterVolume) >> 8; this.stereoPosLDMC = (this.panning[4] * this.masterVolume) >> 8; this.stereoPosRSquare1 = this.masterVolume - this.stereoPosLSquare1; this.stereoPosRSquare2 = this.masterVolume - this.stereoPosLSquare2; this.stereoPosRTriangle = this.masterVolume - this.stereoPosLTriangle; this.stereoPosRNoise = this.masterVolume - this.stereoPosLNoise; this.stereoPosRDMC = this.masterVolume - this.stereoPosLDMC; }, initLengthLookup: function(){ this.lengthLookup = [ 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E ]; }, initDmcFrequencyLookup: function(){ this.dmcFreqLookup = new Array(16); this.dmcFreqLookup[0x0] = 0xD60; this.dmcFreqLookup[0x1] = 0xBE0; this.dmcFreqLookup[0x2] = 0xAA0; this.dmcFreqLookup[0x3] = 0xA00; this.dmcFreqLookup[0x4] = 0x8F0; this.dmcFreqLookup[0x5] = 0x7F0; this.dmcFreqLookup[0x6] = 0x710; this.dmcFreqLookup[0x7] = 0x6B0; this.dmcFreqLookup[0x8] = 0x5F0; this.dmcFreqLookup[0x9] = 0x500; this.dmcFreqLookup[0xA] = 0x470; this.dmcFreqLookup[0xB] = 0x400; this.dmcFreqLookup[0xC] = 0x350; this.dmcFreqLookup[0xD] = 0x2A0; this.dmcFreqLookup[0xE] = 0x240; this.dmcFreqLookup[0xF] = 0x1B0; //for(int i=0;i<16;i++)dmcFreqLookup[i]/=8; }, initNoiseWavelengthLookup: function(){ this.noiseWavelengthLookup = new Array(16); this.noiseWavelengthLookup[0x0] = 0x004; this.noiseWavelengthLookup[0x1] = 0x008; this.noiseWavelengthLookup[0x2] = 0x010; this.noiseWavelengthLookup[0x3] = 0x020; this.noiseWavelengthLookup[0x4] = 0x040; this.noiseWavelengthLookup[0x5] = 0x060; this.noiseWavelengthLookup[0x6] = 0x080; this.noiseWavelengthLookup[0x7] = 0x0A0; this.noiseWavelengthLookup[0x8] = 0x0CA; this.noiseWavelengthLookup[0x9] = 0x0FE; this.noiseWavelengthLookup[0xA] = 0x17C; this.noiseWavelengthLookup[0xB] = 0x1FC; this.noiseWavelengthLookup[0xC] = 0x2FA; this.noiseWavelengthLookup[0xD] = 0x3F8; this.noiseWavelengthLookup[0xE] = 0x7F2; this.noiseWavelengthLookup[0xF] = 0xFE4; }, initDACtables: function(){ var value, ival, i; var max_sqr = 0; var max_tnd = 0; this.square_table = new Array(32*16); this.tnd_table = new Array(204*16); for (i = 0; i < 32 * 16; i++) { value = 95.52 / (8128.0 / (i/16.0) + 100.0); value *= 0.98411; value *= 50000.0; ival = Math.floor(value); this.square_table[i] = ival; if (ival > max_sqr) { max_sqr = ival; } } for (i = 0; i < 204 * 16; i++) { value = 163.67 / (24329.0 / (i/16.0) + 100.0); value *= 0.98411; value *= 50000.0; ival = Math.floor(value); this.tnd_table[i] = ival; if (ival > max_tnd) { max_tnd = ival; } } this.dacRange = max_sqr+max_tnd; this.dcValue = this.dacRange/2; } }; JSNES.PAPU.ChannelDM = function(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(); }; JSNES.PAPU.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); } }, 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; } } } }, nextSample: function() { // Fetch byte: this.data = this.papu.nes.mmap.load(this.playAddress); this.papu.nes.cpu.haltCycles(4); this.playLengthCounter--; this.playAddress++; if (this.playAddress > 0xFFFF) { this.playAddress = 0x8000; } this.hasSample = true; }, 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; } }, setEnabled: function(value) { if ((!this.isEnabled) && value) { this.playLengthCounter = this.playLength; } this.isEnabled = value; }, getLengthStatus: function(){ return ((this.playLengthCounter === 0 || !this.isEnabled) ? 0 : 1); }, getIrqStatus: function(){ return (this.irqGenerated ? 1 : 0); }, 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; } }; JSNES.PAPU.ChannelNoise = function(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(); }; JSNES.PAPU.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; }, clockLengthCounter: function(){ if (this.lengthCounterEnable && this.lengthCounter>0){ this.lengthCounter--; if (this.lengthCounter === 0) { this.updateSampleValue(); } } }, 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(); }, updateSampleValue: function() { if (this.isEnabled && this.lengthCounter>0) { this.sampleValue = this.randomBit * this.masterVolume; } }, 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(); }, setEnabled: function(value){ this.isEnabled = value; if (!value) { this.lengthCounter = 0; } this.updateSampleValue(); }, getLengthStatus: function() { return ((this.lengthCounter===0 || !this.isEnabled)?0:1); } }; JSNES.PAPU.ChannelSquare = function(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(); }; JSNES.PAPU.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; }, clockLengthCounter: function() { if (this.lengthCounterEnable && this.lengthCounter > 0){ this.lengthCounter--; if (this.lengthCounter === 0) { this.updateSampleValue(); } } }, 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(); }, 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; } }, 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; } }, 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; } }, setEnabled: function(value) { this.isEnabled = value; if (!value) { this.lengthCounter = 0; } this.updateSampleValue(); }, getLengthStatus: function() { return ((this.lengthCounter === 0 || !this.isEnabled) ? 0 : 1); } }; JSNES.PAPU.ChannelTriangle = function(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(); }; JSNES.PAPU.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; }, clockLengthCounter: function(){ if (this.lengthCounterEnable && this.lengthCounter>0) { this.lengthCounter--; if (this.lengthCounter===0) { this.updateSampleCondition(); } } }, 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; } }, getLengthStatus: function(){ return ((this.lengthCounter === 0 || !this.isEnabled)?0:1); }, readReg: function(address){ return 0; }, 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(); }, 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(); } } } }, clockTriangleGenerator: function() { this.triangleCounter++; this.triangleCounter &= 0x1F; }, setEnabled: function(value) { this.isEnabled = value; if(!value) { this.lengthCounter = 0; } this.updateSampleCondition(); }, updateSampleCondition: function() { this.sampleCondition = this.isEnabled && this.progTimerMax > 7 && this.linearCounter > 0 && this.lengthCounter > 0; } };