From baffe7a8e8d61d6553fe2058e4f44ae484898bcb Mon Sep 17 00:00:00 2001 From: Stephen Hicks Date: Fri, 8 Jan 2016 00:06:25 -0800 Subject: [PATCH] iterate --- Makefile | 4 ++-- apu/envelope.js | 18 ++++++++++++++---- apu/lengthcounter.js | 40 ++++++++++++++++++++++++++++++++++++++++ apu/pulse.js | 14 +++++++------- cpu.js | 40 ++++++++++++++++++++++++++++++++-------- mem.js | 9 +++++---- nsf.js | 1 + nsfplayer.js | 6 ++++-- 8 files changed, 105 insertions(+), 27 deletions(-) create mode 100644 apu/lengthcounter.js diff --git a/Makefile b/Makefile index f0eb0dc..c9ac706 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ test4.compiled.js: bufferedaudionode.js stepgeneratornode.js test4.js Makefile { java -jar ~/Downloads/compiler.jar --js=bufferedaudionode.js --js=stepgeneratornode.js --js=test4.js --language_in=ES6_STRICT --language_out=ES5_STRICT --create_source_map=test4.srcmap --jscomp_warning=checkTypes --jscomp_warning=checkVars; echo "//# sourceMappingURL=test4.srcmap"; } >| test4.compiled.js -nes.compiled.js: clock.js mem.js apu/apu.js apu/pulse.js apu/envelope.js cpu.js nsf.js stepgeneratornode.js bufferedaudionode.js nsfplayer.js Makefile - { java -jar ~/Downloads/compiler.jar --js=clock.js --js=mem.js --js=apu/apu.js --js=apu/pulse.js --js=apu/envelope.js --js=cpu.js --js=nsf.js --js=bufferedaudionode.js --js=stepgeneratornode.js --js=nsfplayer.js --language_in=ES6_STRICT --language_out=ES5_STRICT --create_source_map=nes.srcmap --jscomp_warning=checkTypes --jscomp_warning=checkVars; echo "//# sourceMappingURL=nes.srcmap"; } >| nes.compiled.js +nes.compiled.js: clock.js mem.js apu/lengthcounter.js apu/envelope.js apu/pulse.js apu/apu.js cpu.js nsf.js stepgeneratornode.js bufferedaudionode.js nsfplayer.js Makefile + { java -jar ~/Downloads/compiler.jar --js=clock.js --js=mem.js --js=apu/lengthcounter.js --js=apu/envelope.js --js=apu/pulse.js --js=apu/apu.js --js=cpu.js --js=nsf.js --js=bufferedaudionode.js --js=stepgeneratornode.js --js=nsfplayer.js --language_in=ES6_STRICT --language_out=ES5_STRICT --create_source_map=nes.srcmap --jscomp_warning=checkTypes --jscomp_warning=checkVars; echo "//# sourceMappingURL=nes.srcmap"; } >| nes.compiled.js diff --git a/apu/envelope.js b/apu/envelope.js index 6fdad79..8c55b66 100644 --- a/apu/envelope.js +++ b/apu/envelope.js @@ -1,4 +1,5 @@ import Memory from '../mem'; +import LengthCounter from './lengthcounter'; /** Envelope generator (TODO - inherit from LengthCounter?). */ export default class Envelope { @@ -13,13 +14,12 @@ export default class Envelope { this.constantVolume_ = mem.bool(base, 4); /** @private @const {!Memory.Register} */ this.loopFlag_ = mem.bool(base, 5); // TODO(sdh): also: length counter halt? - // /** @private @const {!Memory.Register} */ - // this.lengthCounter_ = mem.int(base + 3, 3, 5); mem.listen(base + 3, () => { // console.log('envelope start: ' + mem.get(base + 3)); // window.msg = true; this.start_ = true; + if (!this.loopFlag_.get()) this.lengthCounter_.start(); }); /** @private {boolean} */ @@ -28,6 +28,9 @@ export default class Envelope { this.divider_ = 0; /** @private {number} */ this.counter_ = 0; + + /** @private {!LengthCounter} */ + this.lengthCounter_ = new LengthCounter(mem, base); } print() { @@ -35,10 +38,14 @@ export default class Envelope { volumeEnvelope=${this.volumeEnvelope_.get()} constantVolume=${this.constantVolume_.get()} loopFlag=${this.loopFlag_.get()}`; + // TODO -include length counter } - /** Clocked by the frame counter. */ - clock() { + /** + * Clocked by the frame counter. + * @param {number} half Whether this is a half frame. + */ + clock(half) { if (!this.start_) { this.clockDivider_(); } else { @@ -46,6 +53,7 @@ export default class Envelope { this.counter_ = 15; this.reloadDivider_(); } + if (half && !this.loopFlag_.get()) this.lengthCounter_.clock(); } clockDivider_() { @@ -68,6 +76,8 @@ export default class Envelope { /** Returns the volume. */ volume() { + // First check the length counter + if (!this.loopFlag_.get() && !this.lengthCounter_.enabled()) return 0; if (this.constantVolume_.get()) { //console.log('constant volume: ' + this.volumeEnvelope_.get()); return this.volumeEnvelope_.get(); diff --git a/apu/lengthcounter.js b/apu/lengthcounter.js new file mode 100644 index 0000000..32de554 --- /dev/null +++ b/apu/lengthcounter.js @@ -0,0 +1,40 @@ +export default class LengthCounter { + constructor(mem, base) { + /** @private @const {!Memory.Register} */ + this.enabled_ = mem.bool(0x4015, (base >>> 2) & 7); + /** @private @const {!Memory.Register} */ + this.reload_ = mem.int(base + 3, 3, 5); + /** @private {number} */ + this.counter_ = 0; + + mem.listen(0x4015, () => { if (!this.enabled_.get()) this.disable(); }); + } + + clock() { + if (this.counter_ > 0) this.counter_--; + } + + start() { + if (this.enabled_.get()) { + this.counter_ = LengthCounter.LENGTHS[this.reload_.get()]; + } + } + + disable() { + this.counter_ = 0; + } + + /** @return {boolean} */ + enabled() { + return !!this.counter_; + } +} + + +/** + * List of lengths. + * @const {!Array} + */ +LengthCounter.LENGTHS = [ + 10,254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, + 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30]; diff --git a/apu/pulse.js b/apu/pulse.js index d43558f..d2f4c61 100644 --- a/apu/pulse.js +++ b/apu/pulse.js @@ -28,8 +28,6 @@ export default class ApuPulse { this.sweepEnabled_ = mem.bool(base, 15); /** @private @const {!Memory.Register} */ this.wavePeriod_ = mem.int(base, 16, 11); - /** @private @const {!Memory.Register} */ - this.lengthCounter_ = mem.int(base, 27, 5); /** @private {number} */ this.sweepDivider_ = 0; // TODO(sdh): use a Divider? @@ -59,8 +57,7 @@ pulse ${this.base_ - 0x4000}: silenced=${this.silenced_}, duty=${DUTY_CYCLE_LIST sweepNegate=${this.sweepNegate_.get()} sweepPeriod=${this.sweepPeriod_.get()} sweepEnabled=${this.sweepEnabled_.get()} - wavePeriod=${this.wavePeriod_.get()} - lengthCounter=${this.lengthCounter_.get()}` + this.envelope_.print()); + wavePeriod=${this.wavePeriod_.get()}` + this.envelope_.print()); } /** @@ -78,8 +75,11 @@ pulse ${this.base_ - 0x4000}: silenced=${this.silenced_}, duty=${DUTY_CYCLE_LIST return this.envelope_.volume(); } - /** Clocks the frame counter. */ - clockFrame() { + /** + * Clocks the frame counter. + * @param {number} quarter An integer from 0 to 3, indicating the quarter. + */ + clockFrame(quarter) { if (this.sweepDivider_ == 0 && this.sweepEnabled_.get()) { const target = this.sweepTarget_(); if (target > 0x7ff || target < 8) { @@ -94,7 +94,7 @@ pulse ${this.base_ - 0x4000}: silenced=${this.silenced_}, duty=${DUTY_CYCLE_LIST this.sweepDivider_ = this.sweepPeriod_.get(); this.sweepReload_ = false; } - this.envelope_.clock(); + this.envelope_.clock(quarter % 2); } /** Clocks the sequencer. */ diff --git a/cpu.js b/cpu.js index b29c8ea..7c741bc 100644 --- a/cpu.js +++ b/cpu.js @@ -61,7 +61,9 @@ export default class Cpu { try { this.opcode.op.call(this); } finally { - if (window.msg) { console.log(this.message); window.msg = false; } + if (window.msg) { + console.log(this.message); + window.msg = false; } } if (!this.opcode.extraCycles) this.wait = 0; @@ -85,10 +87,14 @@ export default class Cpu { disassemble(addr, count) { const result = []; - this.PC = addr - 1; + this.PC = --addr; while (count-- > 0) { this.loadOp(); - result.push(this.message); + let bytes = '\t\t\t'; + while (addr < this.PC) { + bytes += hex(this.mem_.get(++addr)).substring(1) + ' '; + } + result.push(this.message + bytes); } console.log(result.join('\n')); } @@ -323,7 +329,6 @@ export default class Cpu { BRK() { this.B = this.I = 1; } - get MP() { const addr = this.opcode.mode.func.call(this); if (addr == null || addr < 0) throw new Error('Jump to non-address.'); @@ -355,23 +360,29 @@ export default class Cpu { /** @param {number} value A one-byte integer. */ pushByte(value) { - this.mem_[this.SP--] = value; + this.mem_.set(this.SP--, value); + this.message += `\t\t(SP)=${hex(value)}, SP=${hex(this.SP,2)}`; } /** @param {number} value A two-byte integer. */ pushWord(value) { this.mem_.setWord(this.SP - 1, value); this.SP -= 2; + this.message += `\t\t(SP)=${hex(value, 2)}, SP=${hex(this.SP,2)}`; } /** @return {number} */ pullByte(value) { - return this.mem_[++this.SP]; + const result = this.mem_.get(++this.SP); + this.message += `\t\t${hex(result)}<-(SP), SP=${hex(this.SP,2)}`; + return result; } /** @return {number} */ pullWord(value) { - return this.mem_.getWord((this.SP += 2) - 1); + const result = this.mem_.getWord((this.SP += 2) - 1); + this.message += `\t\t${hex(result, 2)}<-(SP), SP=${hex(this.SP,2)}`; + return result; } /** @@ -406,7 +417,7 @@ export default class Cpu { * @private */ checkBranch_(addr) { - this.message += `\t\tPC=${hex(this.PC, 2)}`; + this.message += `\t\tPC=${hex(this.PC, 2)}->${hex(addr, 2)}`; this.wait = ((this.PC & 0xf000) == (addr & 0xf000)) ? 1 : 2; return addr; } @@ -563,7 +574,14 @@ function instructionTable() { op('NOP', Cpu.prototype.NOP); op('BRK', Cpu.prototype.BRK); // Illegal Opcodes + function combo(a, b) { + if (!a || !b) throw new Error('bad reference'); + return function() { a.call(this); b.call(this); }; + } op('KIL', Cpu.prototype.KIL); + op('SLO', combo(Cpu.prototype.ASL, Cpu.prototype.ORA)); + op('XAA', combo(Cpu.prototype.TXA, Cpu.prototype.AND)); + op('RLA', combo(Cpu.prototype.ROL, Cpu.prototype.AND)); // Addressing Modes mode('A', Cpu.prototype.accumulator); @@ -670,6 +688,7 @@ function instructionTable() { let illegal = false; if (split[0][3] == '!') { split[0] = split[0].replace('!', ''); + split[0] = split[0].replace('!', ''); // some have two illegal = true; } let op = ops[split[0]]; @@ -699,6 +718,11 @@ function instructionTable() { } function hex(num, opt_bytes, opt_signed) { + if (num == null) { + window.msg = true; + setTimeout(() => { throw 'NULL number'; }, 0); + return 'NUL'; + } let sign = ''; if (opt_signed) { sign = '+'; diff --git a/mem.js b/mem.js index 77afdbe..d8c5c44 100644 --- a/mem.js +++ b/mem.js @@ -48,7 +48,7 @@ export default class Memory { const reg = this.registers_[addr]; if (reg) reg.set(value); else this.data8_[addr] = value; - this.call_(addr); + this.call_(addr, value); } @@ -64,13 +64,14 @@ export default class Memory { /** * @param {number} addr + * @param {number} value * @private */ - call_(addr) { + call_(addr, value) { const cbs = this.callbacks_[addr]; if (cbs) { for (let cb of cbs) { - cb(); + cb(value); } } } @@ -78,7 +79,7 @@ export default class Memory { /** * @param {number} addr - * @param {function()} callback + * @param {function(number)} callback */ listen(addr, callback) { (this.callbacks_[addr] = this.callbacks_[addr] || []).push(callback); diff --git a/nsf.js b/nsf.js index acc740d..1729306 100644 --- a/nsf.js +++ b/nsf.js @@ -69,6 +69,7 @@ export default class Nsf { cpu.pushWord(this.playAddress_ - 1); cpu.PC = this.initAddress_ - 1; cpu.A = song || this.startSong_; + cpu.X = 0; // or PAL... } frame(cpu) { diff --git a/nsfplayer.js b/nsfplayer.js index ecd5c16..dec6ec1 100644 --- a/nsfplayer.js +++ b/nsfplayer.js @@ -26,7 +26,7 @@ export default class NsfPlayer { //yield []; let frameCounter = this.cyclesPerFrame; // console.log('Starting frame counter at ' + frameCounter); - for (let i = 0; i < 600 * this.cyclesPerFrame; i++) { + for (let i = 0; /*i < 6 * this.cyclesPerFrame*/; i++) { if (frameCounter != frameCounter) throw new Error('NaN'); if (--frameCounter <= 0) { frameCounter = this.cyclesPerFrame; @@ -35,7 +35,8 @@ export default class NsfPlayer { this.nsf.frame(this.cpu); } // Yield a single frame worth of steps - const data = this.apu.steps(); + let data = this.apu.steps(); + if (!data.length) data = [[this.clock.time, 0]]; // console.log('Yield data', data); yield data; } @@ -62,6 +63,7 @@ function startEmu(buf) { console.log(nsf + ''); const ac = new AudioContext(); const player = new NsfPlayer(ac, nsf); + window.PLAYER = player; let track = 1;