UNPKG

nes-js

Version:
1,451 lines (1,131 loc) 30.1 kB
import {Register8bit} from './Register.js'; /** * RP2A03(NTSC) * Audio Processing Unit implementation. Consists of * - Pulse 1/2 channel * - Triangle channel * - Noise channel * - DMC channel * * Refer to https://wiki.nesdev.com/w/index.php/APU */ function Apu() { // other devices this.cpu = null; // set by .setCpu() // this.audio = null; // set by .setAudio() // APU units, CPU memory address mapped registers this.pulse1 = new ApuPulse(true); // 0x4000 - 0x4003 this.pulse2 = new ApuPulse(false); // 0x4004 - 0x4007 this.triangle = new ApuTriangle(); // 0x4008 - 0x400B this.noise = new ApuNoise(); // 0x400C - 0x400F this.dmc = new ApuDmc(this); // 0x4010 - 0x4013 this.status = new ApuStatusRegister(); // 0x4015 this.frame = new ApuFrameRegister(); // 0x4017 // this.cycle = 0; this.step = 0; this.samplePeriod = 0; // set by .setAudio() // this.frameIrqActive = false; this.dmcIrqActive = false; } Object.assign(Apu.prototype, { isApu: true, // public methods /** * */ setCpu: function(cpu) { this.cpu = cpu; }, /** * */ setAudio: function(audio) { this.audio = audio; // CPU clock frequency 1.789773 MHz this.samplePeriod = (1789773 / this.audio.getSampleRate()) | 0; }, /** * */ bootup: function() { this.status.store(0x00); }, /** * */ reset: function() { }, /** * Expects being called with CPU clock */ runCycle: function() { this.cycle++; // Sampling at sample rate timing // @TODO: Fix me, more precise timing if((this.cycle % this.samplePeriod) === 0) this.sample(); // Timers // Clocked on every CPU cycles for triangle and 2CPU cycles for others if((this.cycle % 2) === 0) { this.pulse1.driveTimer(); this.pulse2.driveTimer(); this.noise.driveTimer(); this.dmc.driveTimer(); } this.triangle.driveTimer(); // 240Hz Frame sequencer // @TODO: Fix me, more precise timing if((this.cycle % 7457) === 0) { if(this.frame.isFiveStepMode() === true) { // Five-step sequence // // 0 1 2 3 4 function // ----------- ----------------------------- // - - - - - IRQ (if bit 6 is clear) // l - l - - Length counter and sweep // e e e e - Envelope and linear counter if(this.step < 4) { this.pulse1.driveEnvelope(); this.pulse2.driveEnvelope(); this.triangle.driveLinear(); this.noise.driveEnvelope(); } if(this.step === 0 || this.step === 2) { this.pulse1.driveLength(); this.pulse1.driveSweep(); this.pulse2.driveLength(); this.pulse2.driveSweep(); this.triangle.driveLength(); this.noise.driveLength(); } this.step = (this.step + 1) % 5; } else { // Four-step sequence // // 0 1 2 3 function // --------- ----------------------------- // - - - f IRQ (if bit 6 is clear) // - l - l Length counter and sweep // e e e e Envelope and linear counter this.pulse1.driveEnvelope(); this.pulse2.driveEnvelope(); this.triangle.driveLinear(); this.noise.driveEnvelope(); if(this.step === 1 || this.step === 3) { this.pulse1.driveLength(); this.pulse1.driveSweep(); this.pulse2.driveLength(); this.pulse2.driveSweep(); this.triangle.driveLength(); this.noise.driveLength(); } if(this.step === 3 && this.frame.disabledIrq() === false) this.frameIrqActive = true; // Seems like keep invoking IRQ once frame IRQ flag is on // until IRQ flag is cleared or it's disabled...? // @TODO: check sending IRQ timing if(this.frameIrqActive === true && this.frame.disabledIrq() === false) this.cpu.interrupt(this.cpu.INTERRUPTS.IRQ); this.step = (this.step + 1) % 4; } // @TODO: check sending IRQ timing if(this.dmcIrqActive === true) this.cpu.interrupt(this.cpu.INTERRUPTS.IRQ); } }, // load/store CPU memory address mapped register methods called by CPU /** * */ loadRegister: function(address) { switch(address) { case 0x4015: // Loading status register // // bit // 7: DMC interrupt // 6: Frame interrupt // 4: DMC remaining bytes > 0 // 3: Noise length counter > 0 // 2: Triangle length couter > 0 // 1: Pulse2 length counter > 0 // 0: Pulse1 length counter > 0 var value = 0; value |= (this.dmcIrqActive === true ? 1 : 0) << 7; value |= (this.frameIrqActive === true && this.frame.disabledIrq() === false ? 1 : 0) << 6; value |= (this.dmc.remainingBytes > 0 ? 1 : 0) << 4; value |= (this.noise.lengthCounter > 0 ? 1 : 0) << 3; value |= (this.triangle.lengthCounter > 0 ? 1 : 0) << 2; value |= (this.pulse2.lengthCounter > 0 ? 1 : 0) << 1; value |= (this.pulse1.lengthCounter > 0 ? 1 : 0) << 0; // Loading status register clears the frame IRQ flag this.frameIrqActive = false; return value; } return 0; }, /** * */ storeRegister: function(address, value) { switch(address) { case 0x4000: case 0x4001: case 0x4002: case 0x4003: this.pulse1.storeRegister(address, value); break; case 0x4004: case 0x4005: case 0x4006: case 0x4007: this.pulse2.storeRegister(address, value); break; case 0x4008: case 0x4009: case 0x400A: case 0x400B: this.triangle.storeRegister(address, value); break; case 0x400C: case 0x400D: case 0x400E: case 0x400F: this.noise.storeRegister(address, value); break; case 0x4010: case 0x4011: case 0x4012: case 0x4013: this.dmc.storeRegister(address, value); break; case 0x4015: // Storing status register // // bit: Enable(1) / Disable(0) // 4: DMC unit // 3: Noise unit // 2: Triangle unit // 1: Pulse2 unit // 0: Pulse1 unit // // Writing a zero to any of channel enables bits will // set its length counter/remaining bytes to zero. this.status.store(value); this.dmc.setEnable((value & 0x10) === 0x10); this.noise.setEnable((value & 0x8) === 0x8); this.triangle.setEnable((value & 0x4) === 0x4); this.pulse2.setEnable((value & 0x2) === 0x2); this.pulse1.setEnable((value & 0x1) === 0x1); // Storing status register clears the DMC interrupt flag this.dmcIrqActive = false; break; case 0x4017: // Storing frame counter register this.frame.store(value); // If interrupt inhibit flag is set, the frame IRQ flag is cleared. if(this.frame.disabledIrq() === true) this.frameIrqActive = false; break; } }, // private method /** * */ sample: function() { // Calculates the audio output within the range of 0.0 to 1.0. // Refer to https://wiki.nesdev.com/w/index.php/APU_Mixer var pulse1 = this.pulse1.output(); var pulse2 = this.pulse2.output(); var triangle = this.triangle.output(); var noise = this.noise.output(); var dmc = this.dmc.output(); var pulseOut = 0; var tndOut = 0; if(pulse1 !== 0 || pulse2 !== 0) pulseOut = 95.88 / ((8128 / (pulse1 + pulse2)) + 100); if(triangle !== 0 || noise !== 0 || dmc !== 0) tndOut = 159.79 / (1 / (triangle / 8227 + noise / 12241 + dmc / 22638) + 100); this.audio.push(pulseOut + tndOut); }, // dump /** * */ dump: function() { } }); /** * */ function ApuUnit(apu) { this.apu = apu; } // Refer to https://wiki.nesdev.com/w/index.php/APU_Length_Counter ApuUnit.LENGTH_TABLE = [ 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 ]; /** * Apu Pulse channel. Consists of * - Timer * - Length counter * - Envelope * - Sweep */ function ApuPulse(isChannel1) { this.isChannel1 = isChannel1; // CPU memory address mapped registers // 0x4000 / 0x4004 // // bit: // 7-6: Duty cycle // 5: Length counter halt // 4: Enable envelope // 3-0: Envelope divider period // 0x4001 / 0x4005 // // bit: // 7: Enable sweep // 6-4: Period // 3: Negate // 2-0: Shift amount per period // 0x4002 / 0x4006 // // bit: // 7-0: Timer low 8-bit // 0x4003 / 0x4007 // // bit: // 7-3: Length counter index // 2-0: Timer high 3-bit this.register0 = new Register8bit(); // 0x4000 / 0x4004 this.register1 = new Register8bit(); // 0x4001 / 0x4005 this.register2 = new Register8bit(); // 0x4002 / 0x4006 this.register3 = new Register8bit(); // 0x4003 / 0x4007 // this.enabled = false; // this.timerCounter = 0; this.timerPeriod = 0; this.timerSequence = 0; this.envelopeStartFlag = true; this.envelopeCounter = 0; this.envelopeDecayLevelCounter = 0; this.lengthCounter = 0; this.sweepReloadFlag = false; this.sweepCycle = 0; this.sweepCounter = 0; } Object.assign(ApuPulse.prototype, { isApuPulse: true, // LENGTH_TABLE: ApuUnit.LENGTH_TABLE, DUTY_TABLE: [ [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], ], // /** * */ storeRegister: function(address, value) { switch(address) { case 0x4000: case 0x4004: this.register0.store(value); break; case 0x4001: case 0x4005: this.register1.store(value); // Side effect // - Sets the sweep reload flag this.sweepReloadFlag = true; break; case 0x4002: case 0x4006: this.register2.store(value); this.timerPeriod = this.getTimer(); break; case 0x4003: case 0x4007: this.register3.store(value); // Side effects // - If the enabled flag is set, the length counter is reloaded // - The envelope is restarted // - The sequencer is immediately restarted at the first value of the current // sequence. The period divider is not reset. if(this.enabled === true) this.lengthCounter = this.LENGTH_TABLE[this.getLengthCounterIndex()]; this.timerPeriod = this.getTimer(); this.timerSequence = 0; this.envelopeStartFlag = true; break; } }, // /** * */ setEnable: function(enabled) { this.enabled = enabled; // When the enabled bit is cleared (via $4015), the length counter is forced to 0 if(enabled === false) this.lengthCounter = 0; }, /** * */ getDuty: function() { return this.register0.loadBits(6, 2); }, /** * */ enabledEnvelopeLoop: function() { return this.register0.isBitSet(5); }, /** * */ disabledEnvelope: function() { return this.register0.isBitSet(4); }, /** * */ getEnvelopePeriod: function() { return this.register0.loadBits(0, 4); }, /** * */ enabledSweep: function() { return this.register1.isBitSet(7); }, /** * */ getSweepPeriod: function() { return this.register1.loadBits(4, 3); }, /** * */ negatedSweep: function() { return this.register1.isBitSet(3); }, /** * */ getSweepShiftAmount: function() { return this.register1.loadBits(0, 3); }, /** * */ getTimerLow: function() { return this.register2.load(); }, /** * */ getTimerHigh: function() { return this.register3.loadBits(0, 3); }, /** * */ getTimer: function() { return ((this.getTimerHigh() << 8) | this.getTimerLow()); }, /** * */ getLengthCounterIndex: function() { return this.register3.loadBits(3, 5); }, // /** * */ driveTimer: function() { if(this.timerCounter > 0) { this.timerCounter--; } else { this.timerCounter = this.timerPeriod; this.timerSequence++; // 8-step sequencer if(this.timerSequence === 8) this.timerSequence = 0; } }, /** * */ driveLength: function() { if(this.disabledEnvelope() === false && this.lengthCounter > 0) this.lengthCounter--; }, /** * */ driveEnvelope: function() { if(this.envelopeStartFlag === true) { this.envelopeCounter = this.getEnvelopePeriod(); this.envelopeDecayLevelCounter = 0xF; this.envelopeStartFlag = false; return; } if(this.envelopeCounter > 0) { this.envelopeCounter--; } else { this.envelopeCounter = this.getEnvelopePeriod(); if(this.envelopeDecayLevelCounter > 0) this.envelopeDecayLevelCounter--; else if(this.envelopeDecayLevelCounter === 0 && this.enabledEnvelopeLoop() === true) this.envelopeDecayLevelCounter = 0xF; } }, /** * */ driveSweep: function() { if(this.sweepCounter === 0 && this.enabledSweep() === true && this.getSweepShiftAmount() !== 0 && this.timerPeriod >= 8 && this.timerPeriod <= 0x7FF) { var change = this.timerPeriod >> this.getSweepShiftAmount(); // In negated mode, Pulse 1 adds the ones' complement while // Pulse 2 adds the twos' complement if(this.negatedSweep() === true) { change = -change; if(this.isChannel1 === true) change--; } this.timerPeriod += change; } if(this.sweepReloadFlag === true || this.sweepCounter === 0) { this.sweepReloadFlag = false; this.sweepCounter = this.getSweepPeriod(); } else { this.sweepCounter--; } }, /** * */ output: function() { if(this.lengthCounter === 0 || this.timerPeriod < 8 || this.timerPeriod > 0x7FF || this.DUTY_TABLE[this.getDuty()][this.timerSequence] === 0) return 0; // 4-bit output return (this.disabledEnvelope() === true ? this.getEnvelopePeriod() : this.envelopeDecayLevelCounter) & 0xF; } }); /** * Apu Triangle channel. Consists of * - Timer * - Length counter * - Linear counter */ function ApuTriangle() { // CPU memory address mapped registers // 0x4008 // // bit: // 7: Length counter halt // 6-0: Linear counter period // 0x4009 // // Unused // 0x400A // // bit: // 7-0: Timer low 8-bit // 0x400B // // bit: // 7-3: Length counter index // 2-0: Timer high 3-bit this.register0 = new Register8bit(); // 0x4008 this.register1 = new Register8bit(); // 0x4009 this.register2 = new Register8bit(); // 0x400A this.register3 = new Register8bit(); // 0x400B // this.enabled = false; // this.timerCounter = 0; this.timerSequence = 0; this.lengthCounter = 0; this.linearReloadFlag = false; this.linearCounter = 0; } Object.assign(ApuTriangle.prototype, { isApuTriangle: true, // LENGTH_TABLE: ApuUnit.LENGTH_TABLE, SEQUENCE_TABLE: [ 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ], // /** * */ storeRegister: function(address, value) { switch(address) { case 0x4008: this.register0.store(value); break; case 0x4009: this.register1.store(value); break; case 0x400A: this.register2.store(value); break; case 0x400B: this.register3.store(value); // Side effects // - If the enabled flag is set, the length counter is reloaded // - Sets the linear counter reload flag // - The sequencer is immediately restarted at the first value of the current // sequence. The period divider is not reset. if(this.enabled === true) this.lengthCounter = this.LENGTH_TABLE[this.getLengthCounterIndex()]; this.timerSequence = 0; this.linearReloadFlag = true; break; } }, // /** * */ setEnable: function(enabled) { this.enabled = enabled; // When the enabled bit is cleared (via $4015), the length counter is forced to 0 if(enabled === false) this.lengthCounter = 0; }, /** * */ getLinearCounter: function() { return this.register0.loadBits(0, 7); }, /** * */ disabledLengthCounter: function() { return this.register0.isBitSet(7); }, /** * */ getLengthCounterIndex: function() { return this.register3.loadBits(3, 5); }, /** * */ getTimerLow: function() { return this.register2.load(); }, /** * */ getTimerHigh: function() { return this.register3.loadBits(0, 3); }, /** * */ getTimer: function() { return (this.getTimerHigh() << 8) | this.getTimerLow(); }, // /** * */ driveTimer: function() { if(this.timerCounter > 0) { this.timerCounter--; } else { this.timerCounter = this.getTimer(); // The sequencer is clocked by the timer as long as // both the linear counter and the length counter are nonzero. if(this.lengthCounter > 0 && this.linearCounter > 0) { this.timerSequence++; // 32-step sequencer if(this.timerSequence === 32) this.timerSequence = 0; } } }, /** * */ driveLinear: function() { if(this.linearReloadFlag === true) this.linearCounter = this.getLinearCounter(); else if(this.linearCounter > 0) this.linearCounter--; if(this.disabledLengthCounter() === false) this.linearReloadFlag = false; }, /** * */ driveLength: function() { if(this.disabledLengthCounter() === false && this.lengthCounter > 0) this.lengthCounter--; }, /** * */ output: function() { if(this.enabled === false || this.lengthCounter === 0 || this.linearCounter === 0 || this.getTimer() < 2) return 0; // 4-bit output return this.SEQUENCE_TABLE[this.timerSequence] & 0xF; } }); /** * Apu Noise channel. Consists of * - Timer * - Length counter * - Envelope * - Linear feedback shift register */ function ApuNoise() { // CPU memory address mapped registers // 0x400C // // bit: // 5: Length counter halt // 4: Disable envelope // 3-0: Envelope // 0x400D // // Unused // 0x400E // // bit: // 7: Loop noise // 3-0: Noise period // 0x400F // // bit: // 7-3: Length counter index this.register0 = new Register8bit(); // 0x400C this.register1 = new Register8bit(); // 0x400D this.register2 = new Register8bit(); // 0x400E this.register3 = new Register8bit(); // 0x400F // this.enabled = false; // this.timerCounter = 0; this.timerPeriod = 0; this.envelopeStartFlag = false; this.envelopeCounter = 0; this.envelopeDecayLevelCounter = 0; this.lengthCounter = 0; this.shiftRegister = 1; // 15-bit register } Object.assign(ApuNoise.prototype, { isApuNoise: true, // LENGTH_TABLE: ApuUnit.LENGTH_TABLE, TIMER_TABLE: [ 0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0, 0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4 ], // /** * */ storeRegister: function(address, value) { switch(address) { case 0x400C: this.register0.store(value); break; case 0x400D: this.register1.store(value); break; case 0x400E: this.register2.store(value); this.timerPeriod = this.TIMER_TABLE[this.getTimerIndex()] break; case 0x400F: this.register3.store(value); // Side effects // - If the enabled flag is set, the length counter is reloaded // - The envelope is restarted if(this.enabled === true) this.lengthCounter = this.LENGTH_TABLE[this.getLengthCounterIndex()]; this.envelopeStartFlag = true; break; } }, // /** * */ setEnable: function(enabled) { this.enabled = enabled; // When the enabled bit is cleared (via $4015), the length counter is forced to 0 if(enabled === false) this.lengthCounter = 0; }, /** * */ disabledLengthCounter: function() { return this.register0.isBitSet(5); }, /** * */ disabledEnvelope: function() { return this.register0.isBitSet(4); }, /** * */ getEnvelopePeriod: function() { return this.register0.loadBits(0, 4); }, /** * */ isRandom: function() { return this.register2.isBitSet(7); }, /** * */ getTimerIndex: function() { return this.register2.loadBits(0, 4); }, /** * */ getLengthCounterIndex: function() { return this.register3.loadBits(3, 5); }, // /** * */ driveTimer: function() { if(this.timerCounter > 0) { this.timerCounter--; } else { this.timerCounter = this.timerPeriod; // Feedback is calculated as the exclusive-OR of bit 0 // and another bit: bit 6 if Mode flag is set, otherwise bit 1. var feedback = (this.shiftRegister & 1) ^ ((this.shiftRegister >> (this.isRandom() === true ? 6 : 1)) & 1); this.shiftRegister = (feedback << 14) | (this.shiftRegister >> 1); } }, /** * */ driveEnvelope: function() { if(this.envelopeStartFlag === true) { this.envelopeCounter = this.getEnvelopePeriod(); this.envelopeDecayLevelCounter = 0xF; this.envelopeStartFlag = false; return; } if(this.envelopeCounter > 0) { this.envelopeCounter--; } else { this.envelopeCounter = this.getEnvelopePeriod(); if(this.envelopeDecayLevelCounter > 0) this.envelopeDecayLevelCounter--; else if(this.envelopeDecayLevelCounter === 0 && this.disabledLengthCounter() === true) this.envelopeDecayLevelCounter = 0xF; } }, /** * */ driveLength: function() { if(this.disabledLengthCounter() === false && this.lengthCounter > 0) this.lengthCounter--; }, /** * */ output: function() { if(this.lengthCounter === 0 || (this.shiftRegister & 1) === 1) return 0; // 4-bit output return (this.disabledEnvelope() === true ? this.getEnvelopePeriod() : this.envelopeDecayLevelCounter) & 0xF; } }); /** * Apu DMC channel. Consists of * - Timer * - Memory reader * - Sample buffer * - Output unit */ function ApuDmc(apu) { this.apu = apu; // 0x4010 // // bit: // 7: IRQ enable // 6: Loop // 3-0: Timer index // 0x4011 // // bit: // 6-0: Delta counter // Unused // 0x4012 // // bit: // 7-0: Sample address // 0x4013 // // bit: // 7-0: Sample length this.register0 = new Register8bit(); // 0x4010 this.register1 = new Register8bit(); // 0x4011 this.register2 = new Register8bit(); // 0x4012 this.register3 = new Register8bit(); // 0x4013 // this.enabled = false; // this.timerPeriod = 0; this.timerCounter = 0; this.deltaCounter = 0; this.addressCounter = 0; this.remainingBytesCounter = 0; this.sampleBuffer = 0; // 8-bit this.sampleBufferIsEmpty = true; this.shiftRegister = 0; this.remainingBitsCounter = 0; this.silenceFlag = true; } Object.assign(ApuDmc.prototype, { isApuDmc: true, // TIMER_TABLE: [ 0x1AC, 0x17C, 0x154, 0x140, 0x11E, 0x0FE, 0x0E2, 0x0D6, 0x0BE, 0x0A0, 0x08E, 0x080, 0x06A, 0x054, 0x048, 0x036 ], // /** * */ storeRegister: function(address, value) { switch(address) { case 0x4010: this.register0.store(value); this.timerPeriod = this.TIMER_TABLE[this.getTimerIndex()] >> 1; break; case 0x4011: this.register1.store(value); this.start(); break; case 0x4012: this.register2.store(value); this.start(); break; case 0x4013: this.register3.store(value); this.start(); break; } }, // /** * */ setEnable: function(enabled) { this.enabled = enabled; // If DMC enable flag is set via 0x4015, // the DMC sample will be restarted only if its remaining bytes is 0. if(enabled === true) { if(this.remainingBytesCounter === 0) this.start(); } else { this.remainingBytesCounter = 0; } }, /** * */ start: function() { this.deltaCounter = this.getDeltaCounter(); this.addressCounter = this.getSampleAddress() * 0x40 + 0xC000; this.remainingBytesCounter = this.getSampleLength() * 0x10 + 1; }, /** * */ enabledIrq: function() { return this.register0.isBitSet(7); }, /** * */ isLoop: function() { return this.register0.isBitSet(6); }, /** * */ getTimerIndex: function() { return this.register0.loadBits(0, 4); }, /** * */ getDeltaCounter: function() { return this.register1.loadBits(0, 7); }, /** * */ getSampleAddress: function() { return this.register2.load(); }, /** * */ getSampleLength: function() { return this.register3.load(); }, // /** * */ driveTimer: function() { if(this.timerCounter > 0) { this.timerCounter--; } else { this.timerCounter = this.timerPeriod; // Memory reader if(this.remainingBytesCounter > 0 && this.sampleBufferIsEmpty === true) { this.sampleBuffer = this.apu.cpu.load(this.sampleAddress++); this.sampleBufferIsEmpty = false; // if address exceeds 0xFFFF, it is wrapped around to 0x8000. if(this.sampleAddress > 0xFFFF) this.sampleAddress = 0x8000; this.remainingBytesCounter--; // If the bytes remaining counter becomes zero // - the sample is restarted if the loop flag is set // - otherwise, the interrupt flag is set if IRQ enabled flag is set if(this.remainingBytesCounter === 0) { if(this.isLoop() === true) { this.start(); } else { if(this.enabledIrq() === true) this.apu.dmcIrqActive = true; } } // The CPU is stalled for up to 4 CPU cycles this.apu.cpu.stallCycle += 4; } // Output unit if(this.remainingBitsCounter === 0) { this.remainingBitsCounter = 8; if(this.sampleBufferIsEmpty === true) { this.silenceFlag = true; } else { this.silenceFlag = false; this.sampleBufferIsEmpty = true; this.shiftRegister = this.sampleBuffer; this.sampleBuffer = 0; } } if(this.silenceFlag === false) { if((this.shiftRegister & 1) === 0) { if(this.deltaCounter > 1) this.deltaCounter -= 2; } else { if(this.deltaCounter < 126) this.deltaCounter += 2; } } this.shiftRegister = this.shiftRegister >> 1; this.remainingBitsCounter--; } }, /** * */ output: function() { // Seems like we should ignore enable bit set via 0x4015 // (or no enable bit in DMC unit?) //if(this.enabled === false) // return 0; if(this.silenceFlag === true) return 0; // 7-bit output return this.deltaCounter & 0x7F; } }); /** * */ function ApuStatusRegister() { Register8bit.call(this); } ApuStatusRegister.prototype = Object.assign(Object.create(Register8bit.prototype), { isApuStatusRegister: true, // ENABLE_DMC_BIT: 4, ENABLE_NOISE_BIT: 3, ENABLE_TRIANGLE_BIT: 2, ENABLE_PULSE2_BIT: 1, ENABLE_PULSE1_BIT: 0 }); /** * */ function ApuFrameRegister() { Register8bit.call(this); } ApuFrameRegister.prototype = Object.assign(Object.create(Register8bit.prototype), { isApuFrameRegister: true, // MODE_BIT: 7, IRQ_BIT: 6, // /** * */ isFiveStepMode: function() { return this.isBitSet(this.MODE_BIT); }, /** * */ disabledIrq: function() { return this.isBitSet(this.IRQ_BIT); } }); export {Apu};