broken-neees
Version:
A really broken NEEES emulator that introduces glitches and random bugs on purpose!
106 lines (99 loc) • 3.22 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _byte = _interopRequireDefault(require("../byte"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* DPCM (Δ-Modulation) channel handler.
* Manages sample playback: fetching bits, looping, and outputting variations.
*/
class DPCM {
constructor(dmcChannel) {
this.dmcChannel = dmcChannel;
this.startFlag = false;
this.isActive = false;
this.buffer = null;
this.cursorByte = 0;
this.cursorBit = 0;
this.dividerPeriod = 0;
this.dividerCount = 0;
this.sampleAddress = 0;
this.sampleLength = 0;
}
/**
* Called each APU cycle to handle DPCM timing, sample fetching,
* bit processing, output variation, and looping.
*/
update() {
if (this.startFlag) {
const dividerPeriod = dpcmPeriods[this.registers.control.dpcmPeriodId];
this.startFlag = false;
this.isActive = true;
this.cursorByte = -1;
this.cursorBit = 0;
this.dividerPeriod = dividerPeriod;
this.dividerCount = dividerPeriod - 1;
// sample address = %11AAAAAA.AA000000 = $C000 + (A * 64)
this.sampleAddress = 0xc000 + this.registers.sampleAddress.value * 64;
// sample length = %LLLL.LLLL0001 = (L * 16) + 1 bytes
this.sampleLength = this.registers.sampleLength.value * 16 + 1;
}
if (!this.isActive) return;
this.dividerCount++;
if (this.dividerCount >= this.dividerPeriod) this.dividerCount = 0;else return;
const needFetch = this.buffer === null || this.cursorBit === 8;
if (needFetch) {
const nextByte = this.cursorByte + 1;
if (nextByte >= this.sampleLength) {
this.isActive = false;
this.buffer = null;
if (this.registers.control.loop) this.start();
return;
}
this.cursorByte = nextByte;
this.cursorBit = 0;
let address = this.sampleAddress + this.cursorByte;
if (address > 0xffff) {
// (if it exceeds $FFFF, it is wrapped around to $8000)
address = 0x8000 + address % 0x10000;
}
this.buffer = this.cpu.memory.read(address);
}
const variation = _byte.default.getBit(this.buffer, this.cursorBit) ? 2 : -2;
const newSample = this.dmcChannel.outputSample + variation;
if (newSample >= 0 && newSample <= 127) this.dmcChannel.outputSample = newSample;
this.cursorBit++;
}
/**
* Sets the startFlag so playback begins on next update.
*/
start() {
this.startFlag = true;
}
/**
* Stops playback immediately by moving cursor to sample end.
*/
stop() {
this.cursorByte = this.sampleLength;
}
/**
* Returns remaining bytes in sample, or 0 if inactive.
*/
remainingBytes() {
if (!this.isActive) return 0;
return Math.max(0, this.sampleLength - (this.cursorByte + 1));
}
get cpu() {
return this.dmcChannel.cpu;
}
get registers() {
return this.dmcChannel.registers;
}
}
/**
* A list of all DPCM periods. The `DMCControl` register points to this table.
*/
exports.default = DPCM;
const dpcmPeriods = [214, 190, 170, 160, 143, 127, 113, 107, 95, 80, 71, 64, 53, 42, 36, 27];