diff --git a/doc/notes.txt b/doc/notes.txt index af1aa891..0a085cb6 100644 --- a/doc/notes.txt +++ b/doc/notes.txt @@ -549,6 +549,7 @@ Use tick() and refresh(), not callbacks Show current datum when using READ Use https://codemirror.net/doc/manual.html#markText Reset doesn't break @ start unless debugging tools expanded +Single-step can un-sync frame/scanline timing PORTING CC65 TO IDE diff --git a/src/common/baseplatform.ts b/src/common/baseplatform.ts index c0bc5a8e..06791e1d 100644 --- a/src/common/baseplatform.ts +++ b/src/common/baseplatform.ts @@ -282,7 +282,7 @@ export abstract class BaseDebugPlatform extends BasePlatform { setDebugCondition(debugCond : DebugCondition) { this.setBreakpoint('debug', debugCond); } - restartDebugging() { + resetDebugging() { if (this.debugSavedState) { this.loadState(this.debugSavedState); } else { @@ -291,6 +291,9 @@ export abstract class BaseDebugPlatform extends BasePlatform { this.debugClock = 0; this.debugCallback = this.getDebugCallback(); this.debugBreakState = null; + } + restartDebugging() { + this.resetDebugging(); this.resume(); } preFrame() { @@ -865,7 +868,8 @@ export abstract class BaseMachinePlatform extends BaseDebugPl } advance(novideo:boolean) { - var steps = this.machine.advanceFrame(this.getDebugCallback()); + let trap = this.getDebugCallback(); + var steps = this.machine.advanceFrame(trap); if (!novideo && this.video) this.video.updateFrame(); if (!novideo && this.serialVisualizer) this.serialVisualizer.refresh(); return steps; diff --git a/src/common/cpu/MOS6502.ts b/src/common/cpu/MOS6502.ts index 66aceb4b..6774f0c4 100644 --- a/src/common/cpu/MOS6502.ts +++ b/src/common/cpu/MOS6502.ts @@ -1887,14 +1887,17 @@ export var _MOS6502 = function() { PC = (PC-1) & 0xffff; } this.setIRQ = function() { - instruction = IRQ(); - T = 1; - PC = (PC-1) & 0xffff; + if (!I) { // only if not disabled + instruction = IRQ(); + T = 1; + PC = (PC-1) & 0xffff; + } } this.getSP = function() { return SP; } this.getPC = function() { return (PC-1) & 0xffff; } this.getT = function() { return T; } + this.isHalted = function() { return opcodes[opcode] == "uKIL"; } this.isPCStable = function() { return T == 0; @@ -1955,7 +1958,9 @@ export class MOS6502 implements CPU, ClockBased, SavesState, Inter this.interruptType = 0; } interrupt(itype:number) { - this.interruptType = itype; + if (this.interruptType != MOS6502Interrupts.NMI) { + this.interruptType = itype; + } } NMI() { this.interrupt(MOS6502Interrupts.NMI); @@ -1969,6 +1974,9 @@ export class MOS6502 implements CPU, ClockBased, SavesState, Inter getPC() { return this.cpu.getPC(); } + isHalted() { + return this.cpu.isHalted(); + } saveState() { var s = this.cpu.saveState(); s.it = this.interruptType; diff --git a/src/common/emu.ts b/src/common/emu.ts index 765804b8..53a09d5d 100644 --- a/src/common/emu.ts +++ b/src/common/emu.ts @@ -1,5 +1,5 @@ -import { hex, clamp, lpad } from "./util"; +import { hex, clamp, lpad, RGBA } from "./util"; import { SourceLocation } from "./workertypes"; import { VirtualList } from "./vlist" @@ -738,3 +738,20 @@ export class VirtualTextScroller { } } +// https://forums.atariage.com/topic/107853-need-the-256-colors/page/2/ +export function gtia_ntsc_to_rgb(val: number) { + const gamma = 0.9; + const bright = 1.1; + const color = 60; + let cr = (val >> 4) & 15; + let lm = val & 15; + let crlv = cr ? color : 0; + let phase = ((cr - 1) * 25 - 38) * (2 * Math.PI / 360); + let y = 256 * bright * Math.pow((lm + 1) / 16, gamma); + let i = crlv * Math.cos(phase); + let q = crlv * Math.sin(phase); + var r = y + 0.956 * i + 0.621 * q; + var g = y - 0.272 * i - 0.647 * q; + var b = y - 1.107 * i + 1.704 * q; + return RGBA(clamp(0,255,r), clamp(0,255,g), clamp(0,255,b)); +} diff --git a/src/machine/atari7800.ts b/src/machine/atari7800.ts index aecd5c17..4452ea7e 100644 --- a/src/machine/atari7800.ts +++ b/src/machine/atari7800.ts @@ -1,7 +1,7 @@ import { MOS6502, MOS6502State } from "../common/cpu/MOS6502"; import { BasicMachine, RasterFrameBased, Bus, ProbeAll } from "../common/devices"; -import { KeyFlags, newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler, EmuHalt, dumpRAM } from "../common/emu"; +import { KeyFlags, newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler, EmuHalt, dumpRAM, gtia_ntsc_to_rgb } from "../common/emu"; import { TssChannelAdapter, MasterAudio, POKEYDeviceChannel } from "../common/audio"; import { hex, rgb2bgr } from "../common/util"; @@ -525,141 +525,8 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased { /// -const ATARI_NTSC_RGB = [ - 0x000000, // 00 - 0x404040, // 02 - 0x6c6c6c, // 04 - 0x909090, // 06 - 0xb0b0b0, // 08 - 0xc8c8c8, // 0A - 0xdcdcdc, // 0C - 0xf4f4f4, // 0E - 0x004444, // 10 - 0x106464, // 12 - 0x248484, // 14 - 0x34a0a0, // 16 - 0x40b8b8, // 18 - 0x50d0d0, // 1A - 0x5ce8e8, // 1C - 0x68fcfc, // 1E - 0x002870, // 20 - 0x144484, // 22 - 0x285c98, // 24 - 0x3c78ac, // 26 - 0x4c8cbc, // 28 - 0x5ca0cc, // 2A - 0x68b4dc, // 2C - 0x78c8ec, // 2E - 0x001884, // 30 - 0x183498, // 32 - 0x3050ac, // 34 - 0x4868c0, // 36 - 0x5c80d0, // 38 - 0x7094e0, // 3A - 0x80a8ec, // 3C - 0x94bcfc, // 3E - 0x000088, // 40 - 0x20209c, // 42 - 0x3c3cb0, // 44 - 0x5858c0, // 46 - 0x7070d0, // 48 - 0x8888e0, // 4A - 0xa0a0ec, // 4C - 0xb4b4fc, // 4E - 0x5c0078, // 50 - 0x74208c, // 52 - 0x883ca0, // 54 - 0x9c58b0, // 56 - 0xb070c0, // 58 - 0xc084d0, // 5A - 0xd09cdc, // 5C - 0xe0b0ec, // 5E - 0x780048, // 60 - 0x902060, // 62 - 0xa43c78, // 64 - 0xb8588c, // 66 - 0xcc70a0, // 68 - 0xdc84b4, // 6A - 0xec9cc4, // 6C - 0xfcb0d4, // 6E - 0x840014, // 70 - 0x982030, // 72 - 0xac3c4c, // 74 - 0xc05868, // 76 - 0xd0707c, // 78 - 0xe08894, // 7A - 0xeca0a8, // 7C - 0xfcb4bc, // 7E - 0x880000, // 80 - 0x9c201c, // 82 - 0xb04038, // 84 - 0xc05c50, // 86 - 0xd07468, // 88 - 0xe08c7c, // 8A - 0xeca490, // 8C - 0xfcb8a4, // 8E - 0x7c1800, // 90 - 0x90381c, // 92 - 0xa85438, // 94 - 0xbc7050, // 96 - 0xcc8868, // 98 - 0xdc9c7c, // 9A - 0xecb490, // 9C - 0xfcc8a4, // 9E - 0x5c2c00, // A0 - 0x784c1c, // A2 - 0x906838, // A4 - 0xac8450, // A6 - 0xc09c68, // A8 - 0xd4b47c, // AA - 0xe8cc90, // AC - 0xfce0a4, // AE - 0x2c3c00, // B0 - 0x485c1c, // B2 - 0x647c38, // B4 - 0x809c50, // B6 - 0x94b468, // B8 - 0xacd07c, // BA - 0xc0e490, // BC - 0xd4fca4, // BE - 0x003c00, // C0 - 0x205c20, // C2 - 0x407c40, // C4 - 0x5c9c5c, // C6 - 0x74b474, // C8 - 0x8cd08c, // CA - 0xa4e4a4, // CC - 0xb8fcb8, // CE - 0x003814, // D0 - 0x1c5c34, // D2 - 0x387c50, // D4 - 0x50986c, // D6 - 0x68b484, // D8 - 0x7ccc9c, // DA - 0x90e4b4, // DC - 0xa4fcc8, // DE - 0x00302c, // E0 - 0x1c504c, // E2 - 0x347068, // E4 - 0x4c8c84, // E6 - 0x64a89c, // E8 - 0x78c0b4, // EA - 0x88d4cc, // EC - 0x9cece0, // EE - 0x002844, // F0 - 0x184864, // F2 - 0x306884, // F4 - 0x4484a0, // F6 - 0x589cb8, // F8 - 0x6cb4d0, // FA - 0x7ccce8, // FC - 0x8ce0fc // FE -]; - var COLORS_RGBA = new Uint32Array(256); -var COLORS_WEB = []; for (var i=0; i<256; i++) { - COLORS_RGBA[i] = ATARI_NTSC_RGB[i>>1] | 0xff000000; - COLORS_WEB[i] = "#"+hex(rgb2bgr(ATARI_NTSC_RGB[i>>1]),6); + COLORS_RGBA[i] = gtia_ntsc_to_rgb(i); } diff --git a/src/machine/atari8.ts b/src/machine/atari8.ts index f77678c8..6859a051 100644 --- a/src/machine/atari8.ts +++ b/src/machine/atari8.ts @@ -2,14 +2,15 @@ import { newPOKEYAudio, TssChannelAdapter } from "../common/audio"; import { EmuState, Machine } from "../common/baseplatform"; import { MOS6502 } from "../common/cpu/MOS6502"; import { AcceptsKeyInput, AcceptsPaddleInput, AcceptsROM, BasicScanlineMachine, FrameBased, Probeable, RasterFrameBased, TrapCondition, VideoSource } from "../common/devices"; -import { dumpRAM, KeyFlags, Keys, makeKeycodeMap, newAddressDecoder, newKeyboardHandler } from "../common/emu"; +import { dumpRAM, EmuHalt, KeyFlags, Keys, makeKeycodeMap, newAddressDecoder, newKeyboardHandler } from "../common/emu"; import { hex, lpad, lzgmini, rgb2bgr, safe_extend, stringToByteArray } from "../common/util"; import { BaseWASIMachine } from "../common/wasmplatform"; import { ANTIC, MODE_SHIFT } from "./chips/antic"; import { CONSOL, GTIA, TRIG0 } from "./chips/gtia"; +import { POKEY } from "./chips/pokey"; const ATARI8_KEYMATRIX_INTL_NOSHIFT = [ - Keys.VK_L, Keys.VK_J, Keys.VK_SEMICOLON, Keys.VK_F1, Keys.VK_F2, Keys.VK_K, Keys.VK_SLASH, Keys.VK_TILDE, + Keys.VK_L, Keys.VK_J, Keys.VK_SEMICOLON, Keys.VK_F1, Keys.VK_F2, Keys.VK_K, Keys.VK_BACK_SLASH, Keys.VK_TILDE, Keys.VK_O, null, Keys.VK_P, Keys.VK_U, Keys.VK_ENTER, Keys.VK_I, Keys.VK_MINUS, Keys.VK_EQUALS, Keys.VK_V, Keys.VK_F8, Keys.VK_C, Keys.VK_F3, Keys.VK_F4, Keys.VK_B, Keys.VK_X, Keys.VK_Z, Keys.VK_4, null, Keys.VK_3, Keys.VK_6, Keys.VK_ESCAPE, Keys.VK_5, Keys.VK_2, Keys.VK_1, @@ -62,15 +63,15 @@ export class Atari800 extends BasicScanlineMachine { rom: Uint8Array; bios: Uint8Array; bus; - pokey; + audio_pokey; audioadapter; antic: ANTIC; gtia: GTIA; + irq_pokey: POKEY; inputs = new Uint8Array(4); linergb = new Uint32Array(this.canvasWidth); lastdmabyte = 0; keycode = 0; - irqstatus = 0; cart_80 = false; cart_a0 = false; // TODO: save/load vars @@ -83,10 +84,11 @@ export class Atari800 extends BasicScanlineMachine { this.bus = this.newBus(); this.connectCPUMemoryBus(this.bus); // create support chips - this.antic = new ANTIC(this.readDMA.bind(this)); + this.antic = new ANTIC(this.readDMA.bind(this), this.antic_nmi.bind(this)); this.gtia = new GTIA(); - this.pokey = newPOKEYAudio(1); - this.audioadapter = new TssChannelAdapter(this.pokey.pokey1, this.audioOversample, this.sampleRate); + this.irq_pokey = new POKEY(this.pokey_irq.bind(this), () => this.antic.h); + this.audio_pokey = newPOKEYAudio(1); + this.audioadapter = new TssChannelAdapter(this.audio_pokey.pokey1, this.audioOversample, this.sampleRate); this.handler = newKeyboardHandler( this.inputs, ATARI8_KEYCODE_MAP, this.getKeyboardFunction(), true); } @@ -123,7 +125,6 @@ export class Atari800 extends BasicScanlineMachine { this.antic.reset(); this.gtia.reset(); this.keycode = 0; - this.irqstatus = 0; } read(a) { @@ -144,34 +145,35 @@ export class Atari800 extends BasicScanlineMachine { this.bus.write(a, v); } readPokey(a: number) { - //console.log(hex(a), hex(this.saveState().c.PC)); - switch (a) { + switch (a & 0xf) { case 9: // KBCODE return this.keycode & 0xff; - case 14: // IRQST - return this.irqstatus ^ 0xff; case 15: // SKSTAT return ((~this.keycode >> 6) & 0x4) | ((~this.keycode >> 3) & 0x8) | 0x12; default: - return 0xff; + return this.irq_pokey.read(a); } } readPIA(a: number) { if (a == 0 || a == 1) { return ~this.inputs[a]; } } writePokey(a, v) { - switch (a) { - //case 13: this.sendIRQ(0x18); break; // serial output ready IRQ (TODO) - case 14: this.irqstatus = 0; break; - } - this.pokey.pokey1.setRegister(a, v); + this.audio_pokey.pokey1.setRegister(a, v); + this.irq_pokey.write(a, v); } startScanline() { + // TODO: if (this.antic.h != 0) throw new Error(this.antic.h+""); + //if (this.cpu.isHalted()) throw new EmuHalt("CPU HALTED"); + // set GTIA switch inputs + this.gtia.sync(); for (let i = 0; i < 4; i++) this.gtia.readregs[TRIG0 + i] = (~this.inputs[2] >> i) & 1; this.gtia.readregs[CONSOL] = ~this.inputs[3] & this.gtia.regs[CONSOL]; + // advance POKEY audio this.audio && this.audioadapter.generate(this.audio); + // advance POKEY IRQ timers + this.irq_pokey.advanceScanline(); } drawScanline() { @@ -184,15 +186,10 @@ export class Atari800 extends BasicScanlineMachine { advanceCPU(): number { // update ANTIC if (this.antic.clockPulse()) { + // ANTIC DMA cycle, update GTIA + this.gtia.updateGfx(this.antic.h - 1, this.lastdmabyte); // HALT pin this.probe.logClocks(1); - // DMA cycle } else { - // update CPU, NMI? - if (this.antic.nmiPending) { - this.cpu.NMI(); - this.probe.logInterrupt(1); - this.antic.nmiPending = false; - } super.advanceCPU(); } // update GTIA @@ -204,7 +201,6 @@ export class Atari800 extends BasicScanlineMachine { this.gtia.clockPulse2(); this.linergb[xofs++] = this.gtia.rgb; } - this.gtia.updateGfx(this.antic.h - 1, this.lastdmabyte); let xofs = this.antic.h * 4 - this.firstVisibleClock; let bp = MODE_SHIFT[this.antic.mode]; if (bp < 8 || (xofs & 4) == 0) { this.gtia.an = this.antic.shiftout(); } @@ -223,10 +219,10 @@ export class Atari800 extends BasicScanlineMachine { this.ram.set(state.ram); this.antic.loadState(state.antic); this.gtia.loadState(state.gtia); + this.irq_pokey.loadState(state.pokey); this.loadControlsState(state); this.lastdmabyte = state.lastdmabyte; this.keycode = state.keycode; - this.irqstatus = state.irqstatus; } saveState() { return { @@ -234,10 +230,10 @@ export class Atari800 extends BasicScanlineMachine { ram: this.ram.slice(0), antic: this.antic.saveState(), gtia: this.gtia.saveState(), + pokey: this.irq_pokey.saveState(), inputs: this.inputs.slice(0), lastdmabyte: this.lastdmabyte, keycode: this.keycode, // TODO: inputs? - irqstatus: this.irqstatus, }; } loadControlsState(state) { @@ -258,12 +254,7 @@ export class Atari800 extends BasicScanlineMachine { switch (category) { case 'ANTIC': return ANTIC.stateToLongString(state.antic); case 'GTIA': return GTIA.stateToLongString(state.gtia); - case 'POKEY': { - let s = ''; - for (let i = 0; i < 16; i++) { s += hex(this.readPokey(i)) + ' '; } - s += "\nIRQ Status: " + hex(this.irqstatus) + "\n"; - return s; - } + case 'POKEY': return POKEY.stateToLongString(state.pokey); } } getKeyboardFunction() { @@ -271,7 +262,7 @@ export class Atari800 extends BasicScanlineMachine { if (flags & (KeyFlags.KeyDown | KeyFlags.KeyUp)) { var keymap = ATARI8_KEYMATRIX_INTL_NOSHIFT; if (key == Keys.VK_F9.c) { - this.sendIRQ(0x80); // break IRQ + this.irq_pokey.generateIRQ(0x80); // break IRQ return true; } for (var i = 0; i < keymap.length; i++) { @@ -281,7 +272,7 @@ export class Atari800 extends BasicScanlineMachine { if (flags & KeyFlags.Ctrl) { this.keycode |= 0x80; } if (flags & KeyFlags.KeyDown) { this.keycode |= 0x100; - this.sendIRQ(0x40); // key pressed IRQ + this.irq_pokey.generateIRQ(0x40); // key pressed IRQ console.log(o, key, code, flags, hex(this.keycode)); return true; } @@ -290,15 +281,15 @@ export class Atari800 extends BasicScanlineMachine { }; } } - sendIRQ(mask: number) { - // irq enabled? - if (this.pokey.pokey1.getRegister(0xe) & mask) { - this.irqstatus = mask; - this.cpu.IRQ(); - this.probe.logInterrupt(2); - // TODO? if (this.antic.h == 4) { console.log("NMI blocked!"); } - } + pokey_irq() { + this.cpu.IRQ(); + this.probe.logInterrupt(2); } + antic_nmi() { + this.cpu.NMI(); + this.probe.logInterrupt(1); + } + loadROM(rom: Uint8Array) { // TODO: support other than 8 KB carts // support 4/8/16/32 KB carts diff --git a/src/machine/chips/antic.ts b/src/machine/chips/antic.ts index fd075dd1..ea4c09e3 100644 --- a/src/machine/chips/antic.ts +++ b/src/machine/chips/antic.ts @@ -48,14 +48,13 @@ export const MODE_SHIFT = [0, 0, 1, 1, 2, 2, 2, 2, 8, 4, 4, 2, 2, 2, 2, 1]; export class ANTIC { read: (address: number) => number; // bus read function + nmi: () => void; // generate NMI regs = new Uint8Array(0x10); // registers - pfwidth: number; // playfield width left: number; right: number; // left/right clocks for mode - nmiPending: boolean = false; dma_enabled: boolean = false; dliop: number = 0; // dli operation mode: number = 0; // current mode @@ -78,9 +77,11 @@ export class ANTIC { dmaidx: number = 0; output: number = 0; dramrefresh = false; + vscroll = 0; - constructor(readfn) { + constructor(readfn, nmifn) { this.read = readfn; // bus read function + this.nmi = nmifn; // NMI function } reset() { this.regs.fill(0); @@ -103,24 +104,22 @@ export class ANTIC { static stateToLongString(state): string { let s = ""; s += "H: " + lpad(state.h, 3) + " V: " + lpad(state.v, 3) + "\n"; - s += "DLIOp: " + hex(state.dliop, 2) + " Lines: " + state.yofs + "/" + state.linesleft + "\n"; + s += "DLIOp: " + hex(state.dliop, 2) + " Lines: " + state.yofs + "/" + state.linesleft; + s += " DMA " + (state.dma_enabled ? "ON " : "off") + "\n"; s += "Addr: " + hex(state.scanaddr, 4) + "\n"; s += dumpRAM(state.regs, 0, 16).replace('$00', 'Regs'); return s; } setReg(a: number, v: number) { - this.regs[a] = v; switch (a) { case WSYNC: this.regs[WSYNC] = 0xff; - break; - case DMACTL: - this.pfwidth = this.regs[DMACTL] & 3; - break; + return; // this is readonly (we reset it) case NMIRES: this.regs[NMIST] = 0x1f; - break; + return; // this is readonly, don't mess with it } + this.regs[a] = v; } readReg(a: number) { switch (a) { @@ -144,7 +143,8 @@ export class ANTIC { this.mode = this.period = 0; // JVB (Jump and wait for Vertical Blank) if (this.dliop & 0x40) { - this.linesleft = (248 - this.v) & 0xff; // TODO? + this.linesleft = 1; //(248 - this.v) & 0xff; // TODO? + this.dma_enabled = false; } } else if (this.lms) { this.scanaddr = this.dlarg_lo + (this.dlarg_hi << 8); @@ -152,12 +152,24 @@ export class ANTIC { } this.startaddr = this.scanaddr; } + // horiz scroll // TODO: gtia fine scroll? - let pfwidth = this.pfwidth; + let effwidth = this.regs[DMACTL] & 3; let hscroll = (this.dliop & 0x10) ? (this.regs[HSCROL] & 15) >> 1 : 0; - if ((this.dliop & 0x10) && pfwidth < 3) pfwidth++; - this.left = PF_LEFT[pfwidth] + hscroll; - this.right = PF_RIGHT[pfwidth] + hscroll; + if ((this.dliop & 0x10) && effwidth < 3) effwidth++; + this.left = PF_LEFT[effwidth] + hscroll; + this.right = PF_RIGHT[effwidth] + hscroll; + // vertical scroll + let vscrol = this.regs[VSCROL] & 0xf; + if ((this.dliop & 0x20) ^ this.vscroll) { + if (this.vscroll) { + this.linesleft -= vscrol; + } else { + this.linesleft -= vscrol; + this.yofs += vscrol; + } + this.vscroll ^= 0x20; + } } nextLine() { @@ -171,10 +183,10 @@ export class ANTIC { } triggerNMI(mask: number) { - if (this.regs[NMIEN] & mask) { - this.nmiPending = true; - } this.regs[NMIST] = mask | 0x1f; + if (this.regs[NMIEN] & mask) { + this.nmi(); + } } nextInsn(): number { @@ -194,23 +206,22 @@ export class ANTIC { } dlDMAEnabled() { return this.regs[DMACTL] & 0b100000; } - pmDMAEnabled() { return this.regs[DMACTL] & 0b001100; } isVisibleScanline() { return this.v >= 8 && this.v < 248; } isPlayfieldDMAEnabled() { - return this.dlDMAEnabled() && !this.linesleft; + return this.dma_enabled && !this.linesleft; } isPlayerDMAEnabled() { - return this.regs[DMACTL] & 0b1000; + return this.dma_enabled && this.regs[DMACTL] & 0b1000; } isMissileDMAEnabled() { - return this.regs[DMACTL] & 0b1100; + return this.dma_enabled && this.regs[DMACTL] & 0b1100; } clockPulse(): boolean { - let dma = this.regs[WSYNC] != 0; + let did_dma = this.regs[WSYNC] != 0; if (!this.isVisibleScanline()) { this.doVBlank(); } else { @@ -218,7 +229,7 @@ export class ANTIC { case 0: if (this.isMissileDMAEnabled()) { this.doPlayerMissileDMA(3); - dma = true; + did_dma = true; } break; case 1: @@ -230,37 +241,38 @@ export class ANTIC { this.mode = op & 0xf; this.dliop = op; this.yofs = 0; - dma = true; + did_dma = true; } break; case 2: case 3: case 4: case 5: if (this.isPlayerDMAEnabled()) { this.doPlayerMissileDMA(this.h + 2); - dma = true; + did_dma = true; } break; case 6: case 7: - if (this.yofs == 0 && this.isPlayfieldDMAEnabled() && (this.jmp || this.lms)) { // read extra bytes? + if (this.isPlayfieldDMAEnabled() && this.yofs == 0 && (this.jmp || this.lms)) { if (this.h == 6) this.dlarg_lo = this.nextInsn(); if (this.h == 7) this.dlarg_hi = this.nextInsn(); - dma = true; - } - break; - case 9: - if (this.yofs == 0) { - this.processDLIEntry(); + did_dma = true; } break; case 8: + // TODO? is this at cycle 8? + if (this.yofs == 0) { + this.processDLIEntry(); + } if (this.dliop & 0x80) { // TODO: what if DLI disabled? if (this.linesleft == 1) { this.triggerNMI(0x80); // DLI interrupt } } break; + case 9: + break; case 111: - this.nextLine(); + if (this.dma_enabled) this.nextLine(); ++this.v; break; } @@ -276,24 +288,24 @@ export class ANTIC { if (this.dmaclock & 1) { if (this.mode < 8 && this.yofs == 0) { // only read chars on 1st line this.linebuf[this.dmaidx] = this.nextScreen(); // read char name - dma = candma; + did_dma = candma; } this.dmaidx++; } else if (this.dmaclock & 8) { this.ch = this.linebuf[this.dmaidx - 4 / this.period]; // latch char this.readBitmapData(); // read bitmap - dma = candma; + did_dma = candma; } this.output = this.h >= this.left + 3 && this.h <= this.right + 2 ? 4 : 0; } } if (this.h < 19 || this.h > 102) this.output = 2; this.incHorizCounter(); - if (!dma && this.dramrefresh) { + if (!did_dma && this.dramrefresh) { this.dramrefresh = false; - dma = true; + did_dma = true; } - return dma; + return did_dma; } incHorizCounter() { ++this.h; @@ -302,7 +314,7 @@ export class ANTIC { case 25 + 4 * 5: case 25 + 4 * 6: case 25 + 4 * 7: case 25 + 4 * 8: this.dramrefresh = true; break; - case 105: + case 103: this.regs[WSYNC] = 0; // TODO: dram refresh delay to 106? break; case 114: @@ -315,6 +327,9 @@ export class ANTIC { if (this.h == 111) { this.v++; } if (this.v == 248 && this.h == 0) { this.triggerNMI(0x40); } // VBI if (this.v == 262 && this.h == 112) { this.v = 0; } + if (this.v == 7 && this.h == 113) { + this.dma_enabled = this.dlDMAEnabled() != 0; + } this.output = 2; // blank } @@ -322,11 +337,11 @@ export class ANTIC { let oneline = this.regs[DMACTL] & 0x10; let pmaddr = this.regs[PMBASE] << 8; if (oneline) { - pmaddr &= 0b1111100000000000; + pmaddr &= 0xf800; pmaddr |= section << 8; pmaddr |= this.v & 0xff; } else { - pmaddr &= 0b111111000000000; + pmaddr &= 0xfc00; pmaddr |= section << 7; pmaddr |= this.v >> 1; } diff --git a/src/machine/chips/gtia.ts b/src/machine/chips/gtia.ts index 3172831b..547fe444 100644 --- a/src/machine/chips/gtia.ts +++ b/src/machine/chips/gtia.ts @@ -3,8 +3,8 @@ // https://user.xmission.com/~trevin/atari/gtia_regs.html // https://user.xmission.com/~trevin/atari/gtia_pinout.html -import { dumpRAM } from "../../common/emu"; -import { hex, rgb2bgr, safe_extend } from "../../common/util"; +import { dumpRAM, gtia_ntsc_to_rgb } from "../../common/emu"; +import { hex, lpad, safe_extend } from "../../common/util"; // write regs @@ -46,6 +46,7 @@ export class GTIA { reset() { this.regs.fill(0); this.readregs.fill(0); // TODO? + this.readregs[0x14] = 0xf; // NTSC this.count = 0; } saveState() { @@ -70,10 +71,12 @@ export class GTIA { readReg(a: number) { return this.readregs[a]; } + sync() { + this.count = 0; + } updateGfx(h: number, data: number) { switch (h) { case 0: - this.count = 0; if (this.regs[GRACTL] & 1) { this.regs[GRAFM] = data; } break; case 2: case 3: case 4: case 5: @@ -94,6 +97,21 @@ export class GTIA { return 0x100; // black } clockPulse1(): void { + this.processPlayerMissile(); + this.clockPulse2(); + this.count++; + } + clockPulse2(): void { + var col: number; + if (this.pmcol >= 0) { + col = this.pmcol; + } else { + let pf = this.getPlayfieldColor(); + col = pf & 0x100 ? pf & 0xff : this.regs[pf]; + } + this.rgb = COLORS_RGBA[col]; + } + processPlayerMissile() { let topobj = -1; let pfset = this.an - 4; // TODO? let ppmask = 0; @@ -101,7 +119,7 @@ export class GTIA { for (let i = 0; i < 4; i++) { let bit = this.shiftObject(i); if (bit) { - if (pfset >= 0) { + if (pfset >= 0) { // TODO: hires and GTIA modes this.readregs[P0PF + i] |= 1 << pfset; } ppmask |= 1 << i; @@ -124,21 +142,9 @@ export class GTIA { } } this.pmcol = topobj >= 0 ? this.getObjectColor(topobj) : -1; - this.count++; - this.clockPulse2(); - } - clockPulse2(): void { - var col: number; - if (this.pmcol >= 0) { - col = this.pmcol; - } else { - let pf = this.getPlayfieldColor(); - col = pf & 0x100 ? pf & 0xff : this.regs[pf]; - } - this.rgb = COLORS_RGBA[col]; } shiftObject(i: number) { - let bit = this.shiftregs[i] & 0x80000000; + let bit = (this.shiftregs[i] & 0x80000000) != 0; this.shiftregs[i] <<= 1; if (this.regs[HPOSP0 + i] - 7 == this.count) { this.triggerObject(i); @@ -169,6 +175,7 @@ export class GTIA { static stateToLongString(state): string { let s = '' + s += `X: ${lpad(state.count, 3)} ANTIC: ${hex(state.an, 1)} PM: ${hex(state.pmcol, 3)}\n`; s += "Write Registers:\n"; s += dumpRAM(state.regs, 0, 32); s += "Read Registers:\n"; @@ -185,141 +192,8 @@ function expandBits(x: number): number { return x | (x << 1); } -const ATARI_NTSC_RGB = [ - 0x000000, // 00 - 0x404040, // 02 - 0x6c6c6c, // 04 - 0x909090, // 06 - 0xb0b0b0, // 08 - 0xc8c8c8, // 0A - 0xdcdcdc, // 0C - 0xf4f4f4, // 0E - 0x004444, // 10 - 0x106464, // 12 - 0x248484, // 14 - 0x34a0a0, // 16 - 0x40b8b8, // 18 - 0x50d0d0, // 1A - 0x5ce8e8, // 1C - 0x68fcfc, // 1E - 0x002870, // 20 - 0x144484, // 22 - 0x285c98, // 24 - 0x3c78ac, // 26 - 0x4c8cbc, // 28 - 0x5ca0cc, // 2A - 0x68b4dc, // 2C - 0x78c8ec, // 2E - 0x001884, // 30 - 0x183498, // 32 - 0x3050ac, // 34 - 0x4868c0, // 36 - 0x5c80d0, // 38 - 0x7094e0, // 3A - 0x80a8ec, // 3C - 0x94bcfc, // 3E - 0x000088, // 40 - 0x20209c, // 42 - 0x3c3cb0, // 44 - 0x5858c0, // 46 - 0x7070d0, // 48 - 0x8888e0, // 4A - 0xa0a0ec, // 4C - 0xb4b4fc, // 4E - 0x5c0078, // 50 - 0x74208c, // 52 - 0x883ca0, // 54 - 0x9c58b0, // 56 - 0xb070c0, // 58 - 0xc084d0, // 5A - 0xd09cdc, // 5C - 0xe0b0ec, // 5E - 0x780048, // 60 - 0x902060, // 62 - 0xa43c78, // 64 - 0xb8588c, // 66 - 0xcc70a0, // 68 - 0xdc84b4, // 6A - 0xec9cc4, // 6C - 0xfcb0d4, // 6E - 0x840014, // 70 - 0x982030, // 72 - 0xac3c4c, // 74 - 0xc05868, // 76 - 0xd0707c, // 78 - 0xe08894, // 7A - 0xeca0a8, // 7C - 0xfcb4bc, // 7E - 0x880000, // 80 - 0x9c201c, // 82 - 0xb04038, // 84 - 0xc05c50, // 86 - 0xd07468, // 88 - 0xe08c7c, // 8A - 0xeca490, // 8C - 0xfcb8a4, // 8E - 0x7c1800, // 90 - 0x90381c, // 92 - 0xa85438, // 94 - 0xbc7050, // 96 - 0xcc8868, // 98 - 0xdc9c7c, // 9A - 0xecb490, // 9C - 0xfcc8a4, // 9E - 0x5c2c00, // A0 - 0x784c1c, // A2 - 0x906838, // A4 - 0xac8450, // A6 - 0xc09c68, // A8 - 0xd4b47c, // AA - 0xe8cc90, // AC - 0xfce0a4, // AE - 0x2c3c00, // B0 - 0x485c1c, // B2 - 0x647c38, // B4 - 0x809c50, // B6 - 0x94b468, // B8 - 0xacd07c, // BA - 0xc0e490, // BC - 0xd4fca4, // BE - 0x003c00, // C0 - 0x205c20, // C2 - 0x407c40, // C4 - 0x5c9c5c, // C6 - 0x74b474, // C8 - 0x8cd08c, // CA - 0xa4e4a4, // CC - 0xb8fcb8, // CE - 0x003814, // D0 - 0x1c5c34, // D2 - 0x387c50, // D4 - 0x50986c, // D6 - 0x68b484, // D8 - 0x7ccc9c, // DA - 0x90e4b4, // DC - 0xa4fcc8, // DE - 0x00302c, // E0 - 0x1c504c, // E2 - 0x347068, // E4 - 0x4c8c84, // E6 - 0x64a89c, // E8 - 0x78c0b4, // EA - 0x88d4cc, // EC - 0x9cece0, // EE - 0x002844, // F0 - 0x184864, // F2 - 0x306884, // F4 - 0x4484a0, // F6 - 0x589cb8, // F8 - 0x6cb4d0, // FA - 0x7ccce8, // FC - 0x8ce0fc // FE -]; - var COLORS_RGBA = new Uint32Array(256); -var COLORS_WEB = []; for (var i = 0; i < 256; i++) { - COLORS_RGBA[i] = ATARI_NTSC_RGB[i >> 1] | 0xff000000; - COLORS_WEB[i] = "#" + hex(rgb2bgr(ATARI_NTSC_RGB[i >> 1]), 6); + COLORS_RGBA[i] = gtia_ntsc_to_rgb(i); } diff --git a/src/machine/chips/pokey.ts b/src/machine/chips/pokey.ts new file mode 100644 index 00000000..fbff6e29 --- /dev/null +++ b/src/machine/chips/pokey.ts @@ -0,0 +1,474 @@ +/* + * pokey.c - POKEY sound chip emulation + * + * Copyright (C) 1995-1998 David Firth + * Copyright (C) 1998-2008 Atari800 development team (see DOC/CREDITS) + * + * This file is part of the Atari800 emulator project which emulates + * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers. + * + * Atari800 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 2 of the License, or + * (at your option) any later version. + * + * Atari800 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 Atari800; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +import { dumpRAM } from "../../common/emu" +import { hex, lpad, safe_extend } from "../../common/util" + +const AUDF1 = 0x00 +const AUDC1 = 0x01 +const AUDF2 = 0x02 +const AUDC2 = 0x03 +const AUDF3 = 0x04 +const AUDC3 = 0x05 +const AUDF4 = 0x06 +const AUDC4 = 0x07 +const AUDCTL = 0x08 +const STIMER = 0x09 +const SKRES = 0x0a +const POTGO = 0x0b +const SEROUT = 0x0d +const IRQEN = 0x0e +const SKCTL = 0x0f + +const POT0 = 0x00 +const POT1 = 0x01 +const POT2 = 0x02 +const POT3 = 0x03 +const POT4 = 0x04 +const POT5 = 0x05 +const POT6 = 0x06 +const POT7 = 0x07 +const ALLPOT = 0x08 +const KBCODE = 0x09 +const RANDOM = 0x0a +const SERIN = 0x0d +const IRQST = 0x0e +const SKSTAT = 0x0f + +/* definitions for AUDCx (D201, D203, D205, D207) */ +const NOTPOLY5 = 0x80 /* selects POLY5 or direct CLOCK */ +const POLY4 = 0x40 /* selects POLY4 or POLY17 */ +const PURETONE = 0x20 /* selects POLY4/17 or PURE tone */ +const VOL_ONLY = 0x10 /* selects VOLUME OUTPUT ONLY */ +const VOLUME_MASK = 0x0f /* volume mask */ + +/* definitions for AUDCTL (D208) */ +const POLY9 = 0x80 /* selects POLY9 or POLY17 */ +const CH1_179 = 0x40 /* selects 1.78979 MHz for Ch 1 */ +const CH3_179 = 0x20 /* selects 1.78979 MHz for Ch 3 */ +const CH1_CH2 = 0x10 /* clocks channel 1 w/channel 2 */ +const CH3_CH4 = 0x08 /* clocks channel 3 w/channel 4 */ +const CH1_FILTER = 0x04 /* selects channel 1 high pass filter */ +const CH2_FILTER = 0x02 /* selects channel 2 high pass filter */ +const CLOCK_15 = 0x01 /* selects 15.6999kHz or 63.9210kHz */ + +/* for accuracy, the 64kHz and 15kHz clocks are exact divisions of + the 1.79MHz clock */ +const DIV_64 = 28 /* divisor for 1.79MHz clock to 64 kHz */ +const DIV_15 = 114 /* divisor for 1.79MHz clock to 15 kHz */ + +/* the size (in entries) of the 4 polynomial tables */ +const POLY4_SIZE = 0x000f +const POLY5_SIZE = 0x001f +const POLY9_SIZE = 0x01ff +const POLY17_SIZE = 0x0001ffff + +const CHAN1 = 0 +const CHAN2 = 1 +const CHAN3 = 2 +const CHAN4 = 3 + +const ANTIC_LINE_C = 114 + +/* Some defines about the serial I/O timing. Currently fixed! */ +const SIO_XMTDONE_INTERVAL = 15 +const SIO_SERIN_INTERVAL = 8 +const SIO_SEROUT_INTERVAL = 8 +const SIO_ACK_INTERVAL = 36 + +var poly9: Uint8Array; +var poly17: Uint8Array; + +function initPolyTables() { + poly9 = new Uint8Array(511); + poly17 = new Uint8Array(16385); + /* initialise poly9_lookup */ + let reg = 0x1ff; + for (let i = 0; i < 511; i++) { + reg = ((((reg >> 5) ^ reg) & 1) << 8) + (reg >> 1); + poly9[i] = reg; + } + /* initialise poly17_lookup */ + reg = 0x1ffff; + for (let i = 0; i < 16385; i++) { + reg = ((((reg >> 5) ^ reg) & 0xff) << 9) + (reg >> 8); + poly17[i] = (reg >> 1); + } +} + +export class POKEY { + regs = new Uint8Array(16); + readregs = new Uint8Array(16); + divnirq = new Uint32Array(4); + divnmax = new Uint32Array(4); + pot_inputs = new Uint8Array(8); + basemult = 0; + pot_scanline = 0; + random_scanline_counter = 0; + kbcode = 0; + DELAYED_SERIN_IRQ = 0; + DELAYED_SEROUT_IRQ = 0; + DELAYED_XMTDONE_IRQ = 0; + + constructor( + public irq: () => void, + public antic_xpos: () => number, + ) { + this.init(); + } + + saveState() { + return safe_extend(0, {}, this); + } + loadState(s) { + safe_extend(0, this, s); + } + + init() { + /* Initialise Serial Port Interrupts */ + //DELAYED_SERIN_IRQ = 0; + //DELAYED_SEROUT_IRQ = 0; + //DELAYED_XMTDONE_IRQ = 0; + this.readregs.fill(0xff); + this.readregs[SKSTAT] = 0xef; + //SERIN = 0x00; /* or 0xff ? */ + //IRQEN = 0x00; + //SKCTL = 0x00; + this.basemult = DIV_64; + this.pot_inputs.fill(128); + initPolyTables(); + } + + + read(addr: number): number { + let byte = this.readregs[addr]; + addr &= 0xf; + switch (addr) { + case 0: case 1: case 2: case 3: + case 4: case 5: case 6: case 7: + byte = this.pot_inputs[addr]; + return (byte < this.pot_scanline) ? byte : this.pot_scanline; + case ALLPOT: + for (let i = 0; i < 8; i++) { + if (this.pot_inputs[i] <= this.pot_scanline) + byte &= ~(1 << i); // reset bit if pot value known + } + break; + case KBCODE: + return this.kbcode; + case SKSTAT: + byte = SKSTAT + (this.CASSETTE_IOLineStatus() << 4); + break; + case RANDOM: + if ((this.regs[SKCTL] & 0x03) != 0) { + let i = this.random_scanline_counter + this.antic_xpos(); + if (this.regs[AUDCTL] & POLY9) + byte = poly9[i % POLY9_SIZE]; + else { + i %= POLY17_SIZE; + let ptr = i >> 3; + i &= 7; + byte = (poly17[ptr] >> i) + (poly17[ptr + 1] << (8 - i)); + } + } + break; + } + return byte & 0xff; + } + + write(addr: number, byte: number): void { + addr &= 0x0f; + this.regs[addr] = byte; + switch (addr) { + case AUDCTL: + /* determine the base multiplier for the 'div by n' calculations */ + if (byte & CLOCK_15) + this.basemult = DIV_15; + else + this.basemult = DIV_64; + this.update_counter((1 << CHAN1) | (1 << CHAN2) | (1 << CHAN3) | (1 << CHAN4)); + break; + case AUDF1: + this.update_counter((this.regs[AUDCTL] & CH1_CH2) ? ((1 << CHAN2) | (1 << CHAN1)) : (1 << CHAN1)); + break; + case AUDF2: + this.update_counter(1 << CHAN2); + break; + case AUDF3: + this.update_counter((this.regs[AUDCTL] & CH3_CH4) ? ((1 << CHAN4) | (1 << CHAN3)) : (1 << CHAN3)); + break; + case AUDF4: + this.update_counter(1 << CHAN4); + break; + case IRQEN: + this.readregs[IRQST] |= ~byte & 0xf7; /* Reset disabled IRQs except XMTDONE */ + let mask = ~this.readregs[IRQST] & this.regs[IRQEN]; + if (mask) { + this.generateIRQ(this.readregs[IRQST]); + } + break; + case SKRES: + this.readregs[SKSTAT] |= 0xe0; + break; + case POTGO: + if (!(this.regs[SKCTL] & 4)) + this.pot_scanline = 0; /* slow pot mode */ + break; + case SEROUT: + if ((this.regs[SKCTL] & 0x70) == 0x20 && this.siocheck()) { + this.SIO_PutByte(byte); + } + // check if cassette 2-tone mode has been enabled + if ((this.regs[SKCTL] & 0x08) == 0x00) { + // intelligent device + this.DELAYED_SEROUT_IRQ = SIO_SEROUT_INTERVAL; + this.readregs[IRQST] |= 0x08; + this.DELAYED_XMTDONE_IRQ = SIO_XMTDONE_INTERVAL; + } + else { + // cassette + // some savers patch the cassette baud rate, so we evaluate it here + // scanlines per second*10 bit*audiofrequency/(1.79 MHz/2) + this.DELAYED_SEROUT_IRQ = 312 * 50 * 10 * (this.regs[AUDF3] + this.regs[AUDF4] * 0x100) / 895000; + // safety check + if (this.DELAYED_SEROUT_IRQ >= 3) { + this.readregs[IRQST] |= 0x08; + this.DELAYED_XMTDONE_IRQ = 2 * this.DELAYED_SEROUT_IRQ - 2; + } + else { + this.DELAYED_SEROUT_IRQ = 0; + this.DELAYED_XMTDONE_IRQ = 0; + } + }; + break; + case STIMER: + this.divnirq[CHAN1] = this.divnmax[CHAN1]; + this.divnirq[CHAN2] = this.divnmax[CHAN2]; + this.divnirq[CHAN3] = this.divnmax[CHAN3]; + this.divnirq[CHAN4] = this.divnmax[CHAN4]; + //POKEYSND_Update(STIMER, byte, 0, SOUND_GAIN); + break; + case SKCTL: + //VOICEBOX_SKCTLPutByte(byte); + //POKEYSND_Update(SKCTL, byte, 0, SOUND_GAIN); + if (byte & 4) + this.pot_scanline = 228; /* fast pot mode - return results immediately */ + if ((byte & 0x03) == 0) { + /* POKEY reset. */ + /* Stop serial IO. */ + this.DELAYED_SERIN_IRQ = 0; + this.DELAYED_SEROUT_IRQ = 0; + this.DELAYED_XMTDONE_IRQ = 0; + // TODO: CASSETTE_ResetPOKEY(); + /* TODO other registers should also be reset. */ + } + break; + } + this.snd_update(addr); + //POKEYSND_Update(AUDC1, byte, 0, SOUND_GAIN); + } + + /*****************************************************************************/ + /* Module: Update_Counter() */ + /* Purpose: To process the latest control values stored in the AUDF, AUDC, */ + /* and AUDCTL registers. It pre-calculates as much information as */ + /* possible for better performance. This routine has been added */ + /* here again as I need the precise frequency for the pokey timers */ + /* again. The pokey emulation is therefore somewhat sub-optimal */ + /* since the actual pokey emulation should grab the frequency values */ + /* directly from here instead of calculating them again. */ + /* */ + /* Author: Ron Fries,Thomas Richter */ + /* Date: March 27, 1998 */ + /* */ + /* Inputs: chan_mask: Channel mask, one bit per channel. */ + /* The channels that need to be updated */ + /* */ + /* Outputs: Adjusts local globals - no return value */ + /* */ + /*****************************************************************************/ + + update_counter(chan_mask: number): void { + + /************************************************************/ + /* As defined in the manual, the exact Div_n_cnt values are */ + /* different depending on the frequency and resolution: */ + /* 64 kHz or 15 kHz - AUDF + 1 */ + /* 1 MHz, 8-bit - AUDF + 4 */ + /* 1 MHz, 16-bit - AUDF[CHAN1]+256*AUDF[CHAN2] + 7 */ + /************************************************************/ + + /* only reset the channels that have changed */ + + if (chan_mask & (1 << CHAN1)) { + /* process channel 1 frequency */ + if (this.regs[AUDCTL] & CH1_179) + this.divnmax[CHAN1] = this.regs[AUDF1 + CHAN1] + 4; + else + this.divnmax[CHAN1] = (this.regs[AUDF1 + CHAN1] + 1) * this.basemult; + if (this.divnmax[CHAN1] < ANTIC_LINE_C) + this.divnmax[CHAN1] = ANTIC_LINE_C; + } + + if (chan_mask & (1 << CHAN2)) { + /* process channel 2 frequency */ + if (this.regs[AUDCTL] & CH1_CH2) { + if (this.regs[AUDCTL] & CH1_179) + this.divnmax[CHAN2] = this.regs[AUDF1 + CHAN2] * 256 + this.regs[AUDF1 + CHAN1] + 7; + else + this.divnmax[CHAN2] = (this.regs[AUDF1 + CHAN2] * 256 + this.regs[AUDF1 + CHAN1] + 1) * this.basemult; + } + else + this.divnmax[CHAN2] = (this.regs[AUDF1 + CHAN2] + 1) * this.basemult; + if (this.divnmax[CHAN2] < ANTIC_LINE_C) + this.divnmax[CHAN2] = ANTIC_LINE_C; + } + + if (chan_mask & (1 << CHAN4)) { + /* process channel 4 frequency */ + if (this.regs[AUDCTL] & CH3_CH4) { + if (this.regs[AUDCTL] & CH3_179) + this.divnmax[CHAN4] = this.regs[AUDF1 + CHAN4] * 256 + this.regs[AUDF1 + CHAN3] + 7; + else + this.divnmax[CHAN4] = (this.regs[AUDF1 + CHAN4] * 256 + this.regs[AUDF1 + CHAN3] + 1) * this.basemult; + } + else + this.divnmax[CHAN4] = (this.regs[AUDF1 + CHAN4] + 1) * this.basemult; + if (this.divnmax[CHAN4] < ANTIC_LINE_C) + this.divnmax[CHAN4] = ANTIC_LINE_C; + } + + //console.log(chan_mask, this.divnmax); + } + + snd_update(addr: number) { + + } + + advanceScanline() { + /*************************************************************************** + ** Generate POKEY Timer IRQs if required ** + ** called on a per-scanline basis, not very precise, but good enough ** + ** for most applications ** + ***************************************************************************/ + + + /* on nonpatched i/o-operation, enable the cassette timing */ + /* + if (!ESC_enable_sio_patch) { + if (CASSETTE_AddScanLine()) + DELAYED_SERIN_IRQ = 1; + } + */ + + if ((this.regs[SKCTL] & 0x03) == 0) + /* Don't process timers when POKEY is in reset mode. */ + return; + + if (this.pot_scanline < 228) + this.pot_scanline++; + + this.random_scanline_counter += ANTIC_LINE_C; + this.random_scanline_counter %= (this.regs[AUDCTL] & POLY9) ? POLY9_SIZE : POLY17_SIZE; + + if (this.DELAYED_SERIN_IRQ > 0) { + if (--this.DELAYED_SERIN_IRQ == 0) { + // Load a byte to SERIN - even when the IRQ is disabled. + this.readregs[SERIN] = this.SIO_GetByte(); + this.generateIRQ(0x20); + } + } + + if (this.DELAYED_SEROUT_IRQ > 0) { + if (--this.DELAYED_SEROUT_IRQ == 0) { + this.generateIRQ(0x10); + } + } + + if (this.DELAYED_XMTDONE_IRQ > 0) + if (--this.DELAYED_XMTDONE_IRQ == 0) { + this.generateIRQ(0x08); + } + + this.advanceIRQTimer(CHAN1, 0x1); + this.advanceIRQTimer(CHAN2, 0x2); + this.advanceIRQTimer(CHAN4, 0x4); + } + + advanceIRQTimer(chan: number, mask: number) { + if ((this.divnirq[chan] -= ANTIC_LINE_C) < 0) { + this.divnirq[chan] += this.divnmax[chan]; + this.generateIRQ(mask); + //console.log('irq', chan, this.divnirq[chan], this.divnmax[chan]) + } + } + + generateIRQ(mask: number) { + if (this.regs[IRQEN] & mask) { + this.irq(); + this.readregs[IRQST] &= ~mask; + } + } + + static stateToLongString(state): string { + let s = '' + s += "Write Registers:\n"; + s += dumpRAM(state.regs, 0, 16); + s += "Read Registers:\n"; + s += dumpRAM(state.readregs, 0, 16); + return s; + } + + CASSETTE_IOLineStatus() { + return 0; + } + + siocheck() { + return (((this.regs[AUDF1 + CHAN3] == 0x28 || this.regs[AUDF1 + CHAN3] == 0x10 + || this.regs[AUDF1 + CHAN3] == 0x08 || this.regs[AUDF1 + CHAN3] == 0x0a) + && this.regs[AUDF1 + CHAN4] == 0x00) // intelligent peripherals speeds + || (this.regs[SKCTL] & 0x78) == 0x28) // cassette save mode + && (this.regs[AUDCTL] & 0x28) == 0x28; + } + SIO_PutByte(byte: number) { + // TODO + console.log("SIO put byte", byte); + } + SIO_GetByte() { + return 0; // TODO + } + +} + + +//const SOUND_GAIN 4 +/* +void Frame(void) +{ + random_scanline_counter %= (this.regs[AUDCTL] & POLY9) ? POLY9_SIZE : POLY17_SIZE; +} +*/ + + +