8bitworkshop/src/common/devices.ts

398 lines
9.1 KiB
TypeScript

export interface SavesState<S> {
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;
getRasterCanvasPosition?(): { x: number, y: 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<any> {
getPC(): number;
getSP(): number;
isStable(): boolean;
}
export interface HasCPU extends Resettable {
cpu: CPU;
}
export interface Interruptable<IT> {
interrupt(type: IT): void;
}
export interface SavesInputState<CS> {
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<BasicMachineState>, SavesInputState<BasicMachineControlsState> {
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 < this.numTotalScanlines; sl++) {
endLineClock += this.cpuCyclesPerLine; // could be fractional
this.scanline = sl;
this.startScanline();
while (this.frameCycles < endLineClock) {
if (trap && trap()) {
sl = 999;
break;
}
this.frameCycles += this.advanceCPU();
steps++;
}
this.drawScanline();
this.probe.logNewScanline();
this.probe.logClocks(Math.floor(this.frameCycles - endLineClock)); // remainder of prev. line
}
this.postFrame();
return steps; // TODO: return steps, not clock? for recorder
}
preFrame() { }
postFrame() { }
getRasterY() { return this.scanline; }
getRasterX() { return this.frameCycles % this.cpuCyclesPerLine; }
}