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(maxClocks:number, 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; } // 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 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; } // TODO export interface AcceptsKeyInput { setKeyInput(key:number, code:number, flags:number) : void; } export interface Probeable { connectProbe(probe: ProbeAll) : void; } export function xorshift32(x : number) : number { x ^= x << 13; x ^= x >> 17; x ^= x << 5; return x; } /// HOOKS export interface Hook { //target : T; unhook(); } export class BusHook implements Hook { //target : Bus; constructor(bus : Bus, profiler : ProbeBus) { //this.target = bus; var oldread = bus.read.bind(bus); var oldwrite = bus.write.bind(bus); bus.read = (a:number):number => { var val = oldread(a); profiler.logRead(a,val); return val; } bus.write = (a:number,v:number) => { profiler.logWrite(a,v); oldwrite(a,v); } this.unhook = () => { bus.read = oldread; bus.write = oldwrite; } } unhook : () => void; } export class CPUClockHook implements Hook { //target : CPU&ClockBased; constructor(cpu : CPU&ClockBased, profiler : ProbeCPU) { //this.target = cpu; var oldclock = cpu.advanceClock.bind(cpu); cpu.advanceClock = () => { profiler.logExecute(cpu.getPC()); return oldclock(); } this.unhook = () => { cpu.advanceClock = oldclock; } } unhook : () => void; } export class CPUInsnHook implements Hook { //target : CPU&InstructionBased; constructor(cpu : CPU&InstructionBased, profiler : ProbeCPU) { //this.target = cpu; var oldinsn = cpu.advanceInsn.bind(cpu); cpu.advanceInsn = () => { profiler.logExecute(cpu.getPC()); return oldinsn(); } this.unhook = () => { cpu.advanceInsn = oldinsn; } } unhook : () => void; } /// PROFILER export interface ProbeTime { logClocks(clocks:number); logNewScanline(); logNewFrame(); } export interface ProbeCPU { logExecute(address:number); logInterrupt(type:number); } export interface ProbeBus { logRead(address:number, value:number); logWrite(address:number, value:number); } export interface ProbeIO { logIORead(address:number, value:number); logIOWrite(address:number, value:number); } export interface ProbeAll extends ProbeTime, ProbeCPU, ProbeBus, ProbeIO { } export class NullProbe implements ProbeAll { logClocks() {} logNewScanline() {} logNewFrame() {} logExecute() {} logInterrupt() {} logRead() {} logWrite() {} logIORead() {} logIOWrite() {} } /// CONVENIENCE export interface BasicMachineControlsState { inputs: Uint8Array; } export interface BasicMachineState extends BasicMachineControlsState { c: any; // TODO ram: Uint8Array; } export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, AcceptsROM, Probeable, SavesState, SavesInputState { abstract cpuFrequency : number; abstract canvasWidth : number; abstract numVisibleScanlines : number; abstract defaultROMSize : number; abstract sampleRate : number; overscan : boolean = false; rotate : number = 0; abstract cpu : CPU; abstract ram : Uint8Array; // TODO? abstract handler; // keyboard handler rom : Uint8Array; pixels : Uint32Array; audio : SampledAudioSink; inputs : Uint8Array = new Uint8Array(32); scanline : number; frameCycles : number; nullProbe = new NullProbe(); probe : ProbeAll = this.nullProbe; abstract read(a:number) : number; abstract write(a:number, v:number) : void; getAudioParams() : SampledAudioParams { return {sampleRate:this.sampleRate, stereo:false}; } connectAudio(audio : SampledAudioSink) : void { this.audio = audio; } getVideoParams() : VideoParams { return {width:this.canvasWidth, height:this.numVisibleScanlines, overscan:this.overscan, rotate:this.rotate}; } connectVideo(pixels:Uint32Array) : void { this.pixels = pixels; } 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) }; } advanceCPUMultiple(cycles : number) : number { for (var i=0; i { let val = membus.read(a); this.probe.logRead(a,val); return val; }, write: (a,v) => { this.probe.logWrite(a,v); membus.write(a,v); } }); } connectCPUIOBus(iobus:Bus) : void { this.cpu['connectIOBus']({ 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); } }); } } export abstract class BasicScanlineMachine extends BasicMachine implements RasterFrameBased { abstract numTotalScanlines : number; abstract cpuCyclesPerLine : number; abstract startScanline() : void; abstract drawScanline() : void; advanceFrame(maxClocks:number, trap) : number { this.preFrame(); var clock = 0; var endLineClock = 0; this.probe.logNewFrame(); for (var sl=0; sl