export interface SavesState { saveState() : S; loadState(state:S) : void; } export interface Bus { read(a:number) : number; write(a:number, v:number) : void; readConst?(a:number) : number; } export interface ClockBased { advanceClock() : void; } export interface InstructionBased { advanceInsn() : number; } export type TrapCondition = () => boolean; export interface FrameBased { advanceFrame(trap:TrapCondition) : number; } export interface VideoSource { getVideoParams() : VideoParams; connectVideo(pixels:Uint32Array) : void; } export interface RasterFrameBased extends FrameBased, VideoSource { getRasterY() : number; getRasterX() : number; } export interface VideoParams { width : number; height : number; overscan? : boolean; rotate? : number; videoFrequency? : number; // default = 60 aspect? : number; } // TODO: frame buffer optimization (apple2, etc) export interface SampledAudioParams { sampleRate : number; stereo : boolean; } export interface SampledAudioSink { feedSample(value:number, count:number) : void; //sendAudioFrame(samples:Uint16Array) : void; } export interface SampledAudioSource { getAudioParams() : SampledAudioParams; connectAudio(audio : SampledAudioSink) : void; } export interface AcceptsROM { loadROM(data:Uint8Array, title?:string) : void; } export interface AcceptsBIOS { loadBIOS(data:Uint8Array, title?:string) : void; } export interface Resettable { reset() : void; } export interface MemoryBusConnected { connectMemoryBus(bus:Bus) : void; } export interface IOBusConnected { connectIOBus(bus:Bus) : void; } export interface CPU extends MemoryBusConnected, Resettable, SavesState { getPC() : number; getSP() : number; isStable() : boolean; } export interface HasCPU extends Resettable { cpu : CPU; } export interface Interruptable { interrupt(type:IT) : void; } export interface SavesInputState { loadControlsState(cs:CS) : void; saveControlsState() : CS; } export interface AcceptsKeyInput { setKeyInput(key:number, code:number, flags:number) : void; } export interface AcceptsPaddleInput { setPaddleInput(controller:number, value:number) : void; } // TODO: interface not yet used (setKeyInput() handles joystick) export interface AcceptsJoyInput { setJoyInput(joy:number, bitmask:number) : void; } // SERIAL I/O export interface SerialEvent { op: 'read' | 'write'; value: number; nbits: number; } // TODO: all these needed? export interface SerialIOInterface { // from machine to platform clearToSend() : boolean; sendByte(b : number); // from platform to machine byteAvailable() : boolean; recvByte() : number; // implement these too reset() : void; advance(clocks: number) : void; // refresh() : void; } export interface HasSerialIO { connectSerialIO(serial: SerialIOInterface); serialOut?: SerialEvent[]; // outgoing event log serialIn?: SerialEvent[]; // incoming queue } /// PROFILER export interface Probeable { connectProbe(probe: ProbeAll) : void; } export interface ProbeTime { logClocks(clocks:number); logNewScanline(); logNewFrame(); } export interface ProbeCPU { logExecute(address:number, SP:number); logInterrupt(type:number); logIllegal(address:number); logWait(address:number); } export interface ProbeBus { logRead(address:number, value:number); logWrite(address:number, value:number); logDMARead(address:number, value:number); logDMAWrite(address:number, value:number); } export interface ProbeIO { logIORead(address:number, value:number); logIOWrite(address:number, value:number); } export interface ProbeVRAM { logVRAMRead(address:number, value:number); logVRAMWrite(address:number, value:number); } export interface ProbeAll extends ProbeTime, ProbeCPU, ProbeBus, ProbeIO, ProbeVRAM { logData(data:number); // entire 32 bits addLogBuffer(src: Uint32Array); } export class NullProbe implements ProbeAll { logClocks() {} logNewScanline() {} logNewFrame() {} logExecute() {} logInterrupt() {} logRead() {} logWrite() {} logIORead() {} logIOWrite() {} logVRAMRead() {} logVRAMWrite() {} logIllegal() {} logWait() {} logDMARead() {} logDMAWrite() {} logData() {} addLogBuffer(src: Uint32Array) {} } /// CONVENIENCE export interface BasicMachineControlsState { inputs: Uint8Array; } export interface BasicMachineState extends BasicMachineControlsState { c: any; // TODO ram: Uint8Array; } export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, Probeable, SavesState, SavesInputState { abstract cpuFrequency : number; abstract defaultROMSize : number; abstract cpu : CPU; abstract ram : Uint8Array; rom : Uint8Array; inputs : Uint8Array = new Uint8Array(32); handler : (key,code,flags) => void; // keyboard handler nullProbe = new NullProbe(); probe : ProbeAll = this.nullProbe; abstract read(a:number) : number; abstract write(a:number, v:number) : void; setKeyInput(key:number, code:number, flags:number) : void { this.handler && this.handler(key,code,flags); } connectProbe(probe: ProbeAll) : void { this.probe = probe || this.nullProbe; } reset() { this.cpu.reset(); } loadROM(data:Uint8Array, title?:string) : void { if (!this.rom) this.rom = new Uint8Array(this.defaultROMSize); this.rom.set(data); } loadState(state) { this.cpu.loadState(state.c); this.ram.set(state.ram); this.inputs.set(state.inputs); } saveState() { return { c:this.cpu.saveState(), ram:this.ram.slice(0), inputs:this.inputs.slice(0), }; } loadControlsState(state) { this.inputs.set(state.inputs); } saveControlsState() { return { inputs:this.inputs.slice(0) }; } advanceCPU() { var c = this.cpu as any; var n = 1; if (this.cpu.isStable()) { this.probe.logExecute(this.cpu.getPC(), this.cpu.getSP()); } if (c.advanceClock) { c.advanceClock(); } else if (c.advanceInsn) { n = c.advanceInsn(1); } this.probe.logClocks(n); return n; } probeMemoryBus(membus:Bus) : Bus { return { read: (a) => { let val = membus.read(a); this.probe.logRead(a,val); return val; }, write: (a,v) => { this.probe.logWrite(a,v); membus.write(a,v); } }; } connectCPUMemoryBus(membus:Bus) : void { this.cpu.connectMemoryBus(this.probeMemoryBus(membus)); } probeIOBus(iobus:Bus) : Bus { return { read: (a) => { let val = iobus.read(a); this.probe.logIORead(a,val); return val; }, write: (a,v) => { this.probe.logIOWrite(a,v); iobus.write(a,v); } }; } probeDMABus(iobus:Bus) : Bus { return { read: (a) => { let val = iobus.read(a); this.probe.logDMARead(a,val); return val; }, write: (a,v) => { this.probe.logDMAWrite(a,v); iobus.write(a,v); } }; } connectCPUIOBus(iobus:Bus) : void { this.cpu['connectIOBus'](this.probeIOBus(iobus)); } } export abstract class BasicMachine extends BasicHeadlessMachine implements SampledAudioSource { abstract canvasWidth : number; abstract numVisibleScanlines : number; abstract sampleRate : number; overscan : boolean = false; rotate : number = 0; aspectRatio : number; pixels : Uint32Array; audio : SampledAudioSink; scanline : number; getAudioParams() : SampledAudioParams { return {sampleRate:this.sampleRate, stereo:false}; } connectAudio(audio : SampledAudioSink) : void { this.audio = audio; } getVideoParams() : VideoParams { return {width:this.canvasWidth, height:this.numVisibleScanlines, aspect:this.aspectRatio, overscan:this.overscan, rotate:this.rotate}; } connectVideo(pixels:Uint32Array) : void { this.pixels = pixels; } } export abstract class BasicScanlineMachine extends BasicMachine implements RasterFrameBased { abstract numTotalScanlines : number; abstract cpuCyclesPerLine : number; abstract startScanline() : void; abstract drawScanline() : void; frameCycles : number; advanceFrame(trap: TrapCondition) : number { this.preFrame(); var endLineClock = 0; var steps = 0; this.probe.logNewFrame(); this.frameCycles = 0; for (var sl=0; sl