broken-neees
Version:
A really broken NEEES emulator that introduces glitches and random bugs on purpose!
268 lines (267 loc) • 8.53 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _InMemoryRegister = _interopRequireDefault(require("../lib/InMemoryRegister"));
var _noteLengths = _interopRequireDefault(require("../lib/apu/noteLengths"));
var _byte = _interopRequireDefault(require("../lib/byte"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
class PulseControl extends _InMemoryRegister.default.APU {
onLoad() {
this.addField("volumeOrEnvelopePeriod", 0, 4).addField("constantVolume", 4).addField("envelopeLoopOrLengthCounterHalt", 5).addField("dutyCycleId", 6, 2);
}
onWrite(value) {
this.setValue(value);
}
}
class PulseSweep extends _InMemoryRegister.default.APU {
onLoad() {
this.addField("shiftCount", 0, 3).addField("negateFlag", 3).addField("dividerPeriodMinusOne", 4, 3).addField("enabledFlag", 7);
}
onWrite(value) {
this.setValue(value);
const channel = this.apu.channels.pulses[this.id];
channel.frequencySweep.startFlag = true;
}
}
class PulseTimerLow extends _InMemoryRegister.default.APU {
onWrite(value) {
this.setValue(value);
const channel = this.apu.channels.pulses[this.id];
channel.updateTimer();
}
}
class PulseTimerHighLCL extends _InMemoryRegister.default.APU {
onLoad() {
this.addField("timerHigh", 0, 3).addField("lengthCounterLoad", 3, 5);
}
onWrite(value) {
this.setValue(value);
const channel = this.apu.channels.pulses[this.id];
channel.updateTimer();
channel.lengthCounter.counter = _noteLengths.default[this.lengthCounterLoad];
channel.volumeEnvelope.startFlag = true;
}
}
class TriangleLengthControl extends _InMemoryRegister.default.APU {
onLoad() {
this.addField("linearCounterReload", 0, 7).addField("halt", 7, 1);
}
onWrite(value) {
this.setValue(value);
this.apu.channels.triangle.linearLengthCounter.reload = this.linearCounterReload;
}
}
class TriangleTimerLow extends _InMemoryRegister.default.APU {
onWrite(value) {
this.setValue(value);
}
}
class TriangleTimerHighLCL extends _InMemoryRegister.default.APU {
onLoad() {
this.addField("timerHigh", 0, 3).addField("lengthCounterLoad", 3, 5);
}
onWrite(value) {
this.setValue(value);
const triangle = this.apu.channels.triangle;
triangle.lengthCounter.counter = _noteLengths.default[this.lengthCounterLoad];
triangle.linearLengthCounter.reloadFlag = true;
}
}
class NoiseControl extends _InMemoryRegister.default.APU {
onLoad() {
this.addField("volumeOrEnvelopePeriod", 0, 4).addField("constantVolume", 4).addField("envelopeLoopOrLengthCounterHalt", 5);
}
onWrite(value) {
this.setValue(value);
}
}
class NoiseForm extends _InMemoryRegister.default.APU {
onLoad() {
this.addField("periodId", 0, 4).addField("mode", 7);
}
onWrite(value) {
this.setValue(value);
}
}
class NoiseLCL extends _InMemoryRegister.default.APU {
onLoad() {
this.addField("lengthCounterLoad", 3, 5);
}
onWrite(value) {
this.setValue(value);
const channel = this.apu.channels.noise;
channel.lengthCounter.counter = _noteLengths.default[this.lengthCounterLoad];
channel.volumeEnvelope.startFlag = true;
}
}
class DMCControl extends _InMemoryRegister.default.APU {
onLoad() {
this.addField("dpcmPeriodId", 0, 4).addField("loop", 6);
}
onWrite(value) {
this.setValue(value);
}
}
class DMCLoad extends _InMemoryRegister.default.APU {
onLoad() {
this.addField("directLoad", 0, 7);
}
onWrite(value) {
this.setValue(value);
const {
apu
} = this;
apu.channels.dmc.outputSample = this.directLoad;
}
}
class DMCSampleAddress extends _InMemoryRegister.default.APU {
onWrite(value) {
this.setValue(value);
}
}
class DMCSampleLength extends _InMemoryRegister.default.APU {
onWrite(value) {
this.setValue(value);
}
}
class APUStatus extends _InMemoryRegister.default.APU {
onRead() {
const {
apu
} = this;
const channels = apu.channels;
return _byte.default.bitfield(+(channels.pulses[0].lengthCounter.counter > 0), +(channels.pulses[1].lengthCounter.counter > 0), +(channels.triangle.lengthCounter.counter > 0), +(channels.noise.lengthCounter.counter > 0), channels.dmc.dpcm.remainingBytes() > 0, 0, 0, 0);
}
}
class APUControl extends _InMemoryRegister.default.APU {
onLoad() {
this.addField("enablePulse1", 0).addField("enablePulse2", 1).addField("enableTriangle", 2).addField("enableNoise", 3).addField("enableDMC", 4);
}
onWrite(value) {
const {
channels
} = this.apu;
this.setValue(value);
const {
enablePulse1,
enablePulse2,
enableTriangle,
enableNoise,
enableDMC
} = this;
if (!enablePulse1) channels.pulses[0].lengthCounter.reset();
if (!enablePulse2) channels.pulses[1].lengthCounter.reset();
if (!enableTriangle) {
channels.triangle.lengthCounter.reset();
channels.triangle.linearLengthCounter.fullReset();
}
if (!enableNoise) channels.noise.lengthCounter.reset();
if (!enableDMC) channels.dmc.dpcm.stop();else if (channels.dmc.dpcm.remainingBytes() === 0) channels.dmc.dpcm.start();
}
}
class APUFrameCounter extends _InMemoryRegister.default.APU {
onLoad() {
this.addField("use5StepSequencer", 7);
}
onWrite(value) {
this.setValue(value);
this.apu.frameSequencer.reset();
this.apu.onQuarterFrameClock();
this.apu.onHalfFrameClock();
}
}
class AudioRegisters {
constructor(apu) {
this.pulses = [0, 1].map(id => ({
control: new PulseControl(apu),
// $4000/$4004
sweep: new PulseSweep(apu, id),
// $4001/$4005
timerLow: new PulseTimerLow(apu, id),
// $4002/$4006
timerHighLCL: new PulseTimerHighLCL(apu, id) // $4003/$4007
}));
this.triangle = {
lengthControl: new TriangleLengthControl(apu),
// $4008
timerLow: new TriangleTimerLow(apu),
// $400A
timerHighLCL: new TriangleTimerHighLCL(apu) // $400B
};
this.noise = {
control: new NoiseControl(apu),
// $400C
form: new NoiseForm(apu),
// $400E
lcl: new NoiseLCL(apu) // $400F
};
this.dmc = {
control: new DMCControl(apu),
// $4010
load: new DMCLoad(apu),
// $4011
sampleAddress: new DMCSampleAddress(apu),
// $4012
sampleLength: new DMCSampleLength(apu) // $4013
};
this.apuStatus = new APUStatus(apu); // $4015 (read)
this.apuControl = new APUControl(apu); // $4015 (write)
this.apuFrameCounter = new APUFrameCounter(apu); // $4017
}
read(address) {
var _this$_getRegister;
if (address === 0x4015) return this.apuStatus.onRead();
return (_this$_getRegister = this._getRegister(address)) === null || _this$_getRegister === void 0 ? void 0 : _this$_getRegister.onRead();
}
write(address, value) {
var _this$_getRegister2;
if (address === 0x4015) return this.apuControl.onWrite(value);
(_this$_getRegister2 = this._getRegister(address)) === null || _this$_getRegister2 === void 0 || _this$_getRegister2.onWrite(value);
}
_getRegister(address) {
switch (address) {
case 0x4000:
return this.pulses[0].control;
case 0x4001:
return this.pulses[0].sweep;
case 0x4002:
return this.pulses[0].timerLow;
case 0x4003:
return this.pulses[0].timerHighLCL;
case 0x4004:
return this.pulses[1].control;
case 0x4005:
return this.pulses[1].sweep;
case 0x4006:
return this.pulses[1].timerLow;
case 0x4007:
return this.pulses[1].timerHighLCL;
case 0x4008:
return this.triangle.lengthControl;
case 0x400a:
return this.triangle.timerLow;
case 0x400b:
return this.triangle.timerHighLCL;
case 0x400c:
return this.noise.control;
case 0x400e:
return this.noise.form;
case 0x400f:
return this.noise.lcl;
case 0x4010:
return this.dmc.control;
case 0x4011:
return this.dmc.load;
case 0x4012:
return this.dmc.sampleAddress;
case 0x4013:
return this.dmc.sampleLength;
case 0x4017:
return this.apuFrameCounter;
default:
}
}
}
exports.default = AudioRegisters;