Convert more cards to typescript (#63)

Convert more cards to typescript
This commit is contained in:
Will Scullin 2021-03-15 12:51:40 -07:00 committed by GitHub
parent 027b113cd4
commit ab3e97e8d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 652 additions and 638 deletions

View File

@ -148,6 +148,7 @@ export class Apple2 implements Restorable<State>, DebuggerContainer {
const imageData = this.io.blit();
if (imageData) {
this.vm.blit(imageData);
this.stats.renderedFrames++;
}
} else {
if (this.vm.blit()) {

View File

@ -9,10 +9,9 @@
* implied warranty.
*/
import { byte, memory, MemoryPages, rom } from './types';
import { byte, Color, memory, MemoryPages, rom } from './types';
import { allocMemPages } from './util';
import {
Color,
GraphicsState,
HiresPage,
LoresPage,

View File

@ -10,39 +10,49 @@
*/
import { debug } from '../util';
import { Card, Restorable, byte } from '../types';
import { rom } from '../roms/cards/parallel';
export default function Parallel(io, cbs) {
const LOC = {
IOREG: 0x80
} as const;
debug('Parallel card');
export interface ParallelState {}
export interface ParallelOptions {
putChar: (val: byte) => void;
}
var LOC = {
IOREG: 0x80
};
export default class Parallel implements Card, Restorable<ParallelState> {
constructor(private cbs: ParallelOptions) {
debug('Parallel card');
}
function _access(off, val) {
private access(off: byte, val?: byte) {
switch (off & 0x8f) {
case LOC.IOREG:
if (cbs.putChar && val) {
cbs.putChar(val);
if (this.cbs.putChar && val) {
this.cbs.putChar(val);
}
break;
default:
debug('Parallel card unknown softswitch', off);
}
return 0;
}
return {
ioSwitch: function (off, val) {
return _access(off, val);
},
read: function(page, off) {
return rom[off];
},
write: function() {},
getState() {
return {};
},
setState(_) {}
};
ioSwitch(off: byte, val?: byte) {
return this.access(off, val);
}
read(_page: byte, off: byte) {
return rom[off];
}
write() {}
getState() {
return {};
}
setState(_state: ParallelState) {}
}

View File

@ -1,156 +0,0 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { allocMem, debug } from '../util';
import { rom } from '../roms/cards/ramfactor';
export default function RAMFactor(io, size) {
var mem = [];
var _firmware = 0;
var _ramlo = 0;
var _rammid = 0;
var _ramhi = 0;
var _loc = 0;
var LOC = {
// Disk II Stuff
RAMLO: 0x80,
RAMMID: 0x81,
RAMHI: 0x82,
RAMDATA: 0x83,
_RAMLO: 0x84,
_RAMMID: 0x85,
_RAMHI: 0x86,
_RAMDATA: 0x87,
BANK: 0x8F
};
function _init() {
debug('RAMFactor card');
mem = allocMem(size);
for (var off = 0; off < size; off++) {
mem[off] = 0;
}
}
function _sethi(val) {
_ramhi = (val & 0xff);
}
function _setmid(val) {
if (((_rammid & 0x80) !== 0) && ((val & 0x80) === 0)) {
_sethi(_ramhi + 1);
}
_rammid = (val & 0xff);
}
function _setlo(val) {
if (((_ramlo & 0x80) !== 0) && ((val & 0x80) === 0)) {
_setmid(_rammid + 1);
}
_ramlo = (val & 0xff);
}
function _access(off, val) {
var result = 0;
switch (off & 0x8f) {
case LOC.RAMLO:
case LOC._RAMLO:
if (val !== undefined) {
_setlo(val);
} else {
result = _ramlo;
}
break;
case LOC.RAMMID:
case LOC._RAMMID:
if (val !== undefined) {
_setmid(val);
} else {
result = _rammid;
}
break;
case LOC.RAMHI:
case LOC._RAMHI:
if (val !== undefined) {
_sethi(val);
} else {
result = _ramhi;
result |= 0xf0;
}
break;
case LOC.RAMDATA:
case LOC._RAMDATA:
if (val !== undefined) {
mem[_loc % mem.length] = val;
} else {
result = mem[_loc % mem.length];
}
_setlo(_ramlo + 1);
break;
case LOC.BANK:
if (val !== undefined) {
_firmware = val & 0x01;
} else {
result = _firmware;
}
break;
default:
break;
}
_loc = (_ramhi << 16) | (_rammid << 8) | (_ramlo);
/*
if (val === undefined) {
debug("Read: " + toHex(result) + " from " + toHex(off) + " (loc = " + _loc + ")");
} else {
debug("Wrote: " + toHex(val) + " to " + toHex(off) + " (loc = " + _loc + ")");
}
*/
return result;
}
_init();
return {
ioSwitch: function (off, val) {
return _access(off, val);
},
read: function ramfactor_read(page, off) {
return rom[_firmware << 12 | (page - 0xC0) << 8 | off];
},
write: function ramfactor_write() {},
reset: function ramfactor_reset() {
_firmware = 0;
},
getState: function() {
return {
loc: _loc,
firmware: _firmware,
mem: new Uint8Array(mem)
};
},
setState: function(state) {
_loc = state.loc;
_firmware = state.firmware;
mem = new Uint8Array(state.mem);
_ramhi = (_loc >> 16) & 0xff;
_rammid = (_loc >> 8) & 0xff;
_ramlo = (_loc) & 0xff;
}
};
}

163
js/cards/ramfactor.ts Normal file
View File

@ -0,0 +1,163 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { allocMem, debug } from '../util';
import { Card, Restorable, byte, memory } from '../types';
import { rom } from '../roms/cards/ramfactor';
const LOC = {
// Disk II Stuff
RAMLO: 0x80,
RAMMID: 0x81,
RAMHI: 0x82,
RAMDATA: 0x83,
_RAMLO: 0x84,
_RAMMID: 0x85,
_RAMHI: 0x86,
_RAMDATA: 0x87,
BANK: 0x8F
} as const;
export class RAMFactorState {
loc: number
firmware: byte
mem: memory
}
export default class RAMFactor implements Card, Restorable<RAMFactorState> {
private mem: memory;
private firmware = 0;
private ramlo = 0;
private rammid = 0;
private ramhi = 0;
private loc = 0;
constructor(size: number) {
debug('RAMFactor card');
this.mem = allocMem(size);
for (let off = 0; off < size; off++) {
this.mem[off] = 0;
}
}
private sethi(val: byte) {
this.ramhi = (val & 0xff);
}
private setmid(val: byte) {
if (((this.rammid & 0x80) !== 0) && ((val & 0x80) === 0)) {
this.sethi(this.ramhi + 1);
}
this.rammid = (val & 0xff);
}
private setlo(val: byte) {
if (((this.ramlo & 0x80) !== 0) && ((val & 0x80) === 0)) {
this.setmid(this.rammid + 1);
}
this.ramlo = (val & 0xff);
}
private access(off: byte, val: byte) {
let result = 0;
switch (off & 0x8f) {
case LOC.RAMLO:
case LOC._RAMLO:
if (val !== undefined) {
this.setlo(val);
} else {
result = this.ramlo;
}
break;
case LOC.RAMMID:
case LOC._RAMMID:
if (val !== undefined) {
this.setmid(val);
} else {
result = this.rammid;
}
break;
case LOC.RAMHI:
case LOC._RAMHI:
if (val !== undefined) {
this.sethi(val);
} else {
result = this.ramhi;
result |= 0xf0;
}
break;
case LOC.RAMDATA:
case LOC._RAMDATA:
if (val !== undefined) {
this.mem[this.loc % this.mem.length] = val;
} else {
result = this.mem[this.loc % this.mem.length];
}
this.setlo(this.ramlo + 1);
break;
case LOC.BANK:
if (val !== undefined) {
this.firmware = val & 0x01;
} else {
result = this.firmware;
}
break;
default:
break;
}
this.loc = (this.ramhi << 16) | (this.rammid << 8) | (this.ramlo);
/*
if (val === undefined) {
debug("Read: " + toHex(result) + " from " + toHex(off) + " (loc = " + _loc + ")");
} else {
debug("Wrote: " + toHex(val) + " to " + toHex(off) + " (loc = " + _loc + ")");
}
*/
return result;
}
ioSwitch(off: byte, val: byte) {
return this.access(off, val);
}
read(page: byte, off: byte) {
return rom[this.firmware << 12 | (page - 0xC0) << 8 | off];
}
write() {}
reset() {
this.firmware = 0;
}
getState() {
return {
loc: this.loc,
firmware: this.firmware,
mem: new Uint8Array(this.mem)
};
}
setState(state: RAMFactorState) {
this.loc = state.loc;
this.firmware = state.firmware;
this.mem = new Uint8Array(state.mem);
this.ramhi = (this.loc >> 16) & 0xff;
this.rammid = (this.loc >> 8) & 0xff;
this.ramlo = (this.loc) & 0xff;
}
}

View File

@ -1,159 +0,0 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { debug, toHex } from '../util';
import { rom } from '../roms/cards/thunderclock';
export default function Thunderclock()
{
var LOC = {
CONTROL: 0x80,
AUX: 0x88
};
var COMMANDS = {
MASK: 0x18,
REGHOLD: 0x00,
REGSHIFT: 0x08,
TIMED: 0x18
};
var FLAGS = {
DATA: 0x01,
CLOCK: 0x02,
STROBE: 0x04
};
function _init() {
debug('Thunderclock');
}
var _clock = false;
var _strobe = false;
var _shiftMode = false;
var _register = 0;
var _bits = [];
var _command = COMMANDS.HOLD;
function _debug() {
// debug.apply(this, arguments);
}
function _calcBits() {
function shift(val) {
for (var idx = 0; idx < 4; idx++) {
_bits.push((val & 0x08) !== 0);
val <<= 1;
}
}
function shiftBCD(val) {
shift(parseInt(val / 10, 10));
shift(parseInt(val % 10, 10));
}
var now = new Date();
var day = now.getDate();
var weekday = now.getDay();
var month = now.getMonth() + 1;
var hour = now.getHours();
var minutes = now.getMinutes();
var seconds = now.getSeconds();
_bits = [];
shift(month);
shift(weekday);
shiftBCD(day);
shiftBCD(hour);
shiftBCD(minutes);
shiftBCD(seconds);
}
function _shift() {
if (_shiftMode) {
if (_bits.pop()) {
_debug('shifting 1');
_register |= 0x80;
} else {
_debug('shifting 0');
_register &= 0x7f;
}
}
}
function _access(off, val) {
switch (off & 0x8F) {
case LOC.CONTROL:
if (val !== undefined) {
var strobe = val & FLAGS.STROBE ? true : false;
if (strobe !== _strobe) {
_debug('strobe', _strobe ? 'high' : 'low');
if (strobe) {
_command = val & COMMANDS.MASK;
switch (_command) {
case COMMANDS.TIMED:
_debug('TIMED');
_calcBits();
break;
case COMMANDS.REGSHIFT:
_debug('REGSHIFT');
_shiftMode = true;
_shift();
break;
case COMMANDS.REGHOLD:
_debug('REGHOLD');
_shiftMode = false;
break;
default:
_debug('Unknown command', toHex(_command));
}
}
}
var clock = val & FLAGS.CLOCK ? true : false;
if (clock !== _clock) {
_clock = clock;
_debug('clock', _clock ? 'high' : 'low');
if (clock) {
_shift();
}
}
}
break;
case LOC.AUX:
break;
}
return _register;
}
_init();
return {
read: function thunderclock_read(page, off) {
var result;
if (page < 0xc8) {
result = rom[off];
} else {
result = rom[(page - 0xc8) << 8 | off];
}
return result;
},
write: function thunderclock_write() {
},
ioSwitch: function thunderclock_ioSwitch(off, val) {
return _access(off, val);
},
getState() {
return {};
},
setState(_) {}
};
}

163
js/cards/thunderclock.ts Normal file
View File

@ -0,0 +1,163 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { debug, toHex } from '../util';
import { Card, Restorable, byte } from '../types';
import { rom } from '../roms/cards/thunderclock';
const LOC = {
CONTROL: 0x80,
AUX: 0x88
} as const;
const COMMANDS = {
MASK: 0x18,
REGHOLD: 0x00,
REGSHIFT: 0x08,
TIMED: 0x18
} as const;
const FLAGS = {
DATA: 0x01,
CLOCK: 0x02,
STROBE: 0x04
} as const;
export interface ThunderclockState {}
export default class Thunderclock implements Card, Restorable<ThunderclockState>
{
constructor() {
debug('Thunderclock');
}
private clock = false;
private strobe = false;
private shiftMode = false;
private register = 0;
private bits: boolean[] = [];
private command: byte = COMMANDS.REGHOLD;
private debug(..._args: any[]) {
// debug.apply(this, arguments);
}
private calcBits() {
const shift = (val: byte) => {
for (let idx = 0; idx < 4; idx++) {
this.bits.push((val & 0x08) !== 0);
val <<= 1;
}
};
const shiftBCD = (val: byte) => {
shift(Math.floor(val / 10));
shift(val % 10);
};
const now = new Date();
const day = now.getDate();
const weekday = now.getDay();
const month = now.getMonth() + 1;
const hour = now.getHours();
const minutes = now.getMinutes();
const seconds = now.getSeconds();
this.bits = [];
shift(month);
shift(weekday);
shiftBCD(day);
shiftBCD(hour);
shiftBCD(minutes);
shiftBCD(seconds);
}
private shift() {
if (this.shiftMode) {
if (this.bits.pop()) {
this.debug('shifting 1');
this.register |= 0x80;
} else {
this.debug('shifting 0');
this.register &= 0x7f;
}
}
}
private access(off: byte, val?: byte) {
switch (off & 0x8F) {
case LOC.CONTROL:
if (val !== undefined) {
const strobe = val & FLAGS.STROBE ? true : false;
if (strobe !== this.strobe) {
this.debug('strobe', this.strobe ? 'high' : 'low');
if (strobe) {
this.command = val & COMMANDS.MASK;
switch (this.command) {
case COMMANDS.TIMED:
this.debug('TIMED');
this.calcBits();
break;
case COMMANDS.REGSHIFT:
this.debug('REGSHIFT');
this.shiftMode = true;
this.shift();
break;
case COMMANDS.REGHOLD:
this.debug('REGHOLD');
this.shiftMode = false;
break;
default:
this.debug('Unknown command', toHex(this.command));
}
}
}
const clock = val & FLAGS.CLOCK ? true : false;
if (clock !== this.clock) {
this.clock = clock;
this.debug('clock', this.clock ? 'high' : 'low');
if (clock) {
this.shift();
}
}
}
break;
case LOC.AUX:
break;
}
return this.register;
}
read(page: byte, off: byte) {
let result;
if (page < 0xc8) {
result = rom[off];
} else {
result = rom[(page - 0xc8) << 8 | off];
}
return result;
}
write() {
}
ioSwitch(off: byte, val?: byte) {
return this.access(off, val);
}
getState() {
return {};
}
setState() {}
}

View File

@ -1,284 +0,0 @@
/* Copyright 2017 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { allocMemPages, debug } from '../util';
import { ROM, VIDEO_ROM } from '../roms/cards/videoterm';
export default function Videoterm(_io) {
debug('Videx Videoterm');
var LOC = {
IOREG: 0x80,
IOVAL: 0x81
};
var REGS = {
CURSOR_UPPER: 0x0A,
CURSOR_LOWER: 0x0B,
STARTPOS_HI: 0x0C,
STARTPOS_LO: 0x0D,
CURSOR_HI: 0x0E,
CURSOR_LO: 0x0F,
LIGHTPEN_HI: 0x10,
LIGHTPEN_LO: 0x11
};
var CURSOR_MODES = {
SOLID: 0x00,
HIDDEN: 0x01,
BLINK: 0x10,
FAST_BLINK: 0x11
};
var _regs = [
0x7b, // 00 - Horiz. total
0x50, // 01 - Horiz. displayed
0x62, // 02 - Horiz. sync pos
0x29, // 03 - Horiz. sync width
0x1b, // 04 - Vert. total
0x08, // 05 - Vert. adjust
0x18, // 06 - Vert. displayed
0x19, // 07 - Vert. sync pos
0x00, // 08 - Interlaced
0x08, // 09 - Max. scan line
0xc0, // 0A - Cursor upper
0x08, // 0B - Cursor lower
0x00, // 0C - Startpos Hi
0x00, // 0D - Startpos Lo
0x00, // 0E - Cursor Hi
0x00, // 0F - Cursor Lo
0x00, // 10 - Lightpen Hi
0x00 // 11 - Lightpen Lo
];
var _blink = false;
var _curReg = 0;
var _startPos;
var _cursorPos;
var _shouldRefresh;
// var _cursor = 0;
var _bank = 0;
var _buffer = allocMemPages(8);
var _imageData;
var _dirty = false;
var _black = [0x00, 0x00, 0x00];
var _white = [0xff, 0xff, 0xff];
function _init() {
var idx;
_imageData = new ImageData(560, 384);
for (idx = 0; idx < 560 * 384 * 4; idx++) {
_imageData.data[idx] = 0xff;
}
for (idx = 0; idx < 0x800; idx++) {
_buffer[idx] = idx & 0xff;
}
_refresh();
setInterval(function() {
_blink = !_blink;
_refreshCursor();
}, 300);
}
function _updateBuffer(addr, val) {
_buffer[addr] = val;
val &= 0x7f; // XXX temp
var saddr = (0x800 + addr - _startPos) & 0x7ff;
var data = _imageData.data;
var row = (saddr / 80) & 0xff;
var col = saddr % 80;
var x = col * 7;
var y = row << 4;
var c = val << 4;
var color;
if (row < 25) {
_dirty = true;
for (var idx = 0; idx < 8; idx++) {
var cdata = VIDEO_ROM[c + idx];
for (var jdx = 0; jdx < 7; jdx++) {
if (cdata & 0x80) {
color = _white;
} else {
color = _black;
}
data[(y + idx * 2) * 560 * 4 + (x + jdx) * 4] = color[0];
data[(y + idx * 2) * 560 * 4 + (x + jdx) * 4 + 1] = color[1];
data[(y + idx * 2) * 560 * 4 + (x + jdx) * 4 + 2] = color[2];
data[(y + idx * 2 + 1) * 560 * 4 + (x + jdx) * 4] = color[0];
data[(y + idx * 2 + 1) * 560 * 4 + (x + jdx) * 4 + 1] = color[1];
data[(y + idx * 2 + 1) * 560 * 4 + (x + jdx) * 4 + 2] = color[2];
cdata <<= 1;
}
}
}
}
function _refreshCursor(fromRegs) {
var addr = _regs[REGS.CURSOR_HI] << 8 | _regs[REGS.CURSOR_LO];
var saddr = (0x800 + addr - _startPos) & 0x7ff;
var data = _imageData.data;
var row = (saddr / 80) & 0xff;
var col = saddr % 80;
var x = col * 7;
var y = row * 16;
var blinkmode = (_regs[REGS.CURSOR_UPPER] & 0x60) >> 5;
if (fromRegs) {
if (addr !== _cursorPos) {
var caddr = (0x800 + _cursorPos - _startPos) & 0x7ff;
_updateBuffer(caddr, _buffer[caddr]);
_cursorPos = addr;
}
}
_updateBuffer(addr, _buffer[addr]);
if (blinkmode === CURSOR_MODES.HIDDEN) {
return;
}
if (_blink || (blinkmode === CURSOR_MODES.SOLID)) {
_dirty = true;
for (var idx = 0; idx < 8; idx++) {
var color = _white;
if (idx >= (_regs[REGS.CURSOR_UPPER] & 0x1f) &&
idx <= (_regs[REGS.CURSOR_LOWER] & 0x1f)) {
for (var jdx = 0; jdx < 7; jdx++) {
data[(y + idx * 2) * 560 * 4 + (x + jdx) * 4] = color[0];
data[(y + idx * 2) * 560 * 4 + (x + jdx) * 4 + 1] = color[1];
data[(y + idx * 2) * 560 * 4 + (x + jdx) * 4 + 2] = color[2];
data[(y + idx * 2 + 1) * 560 * 4 + (x + jdx) * 4] = color[0];
data[(y + idx * 2 + 1) * 560 * 4 + (x + jdx) * 4 + 1] = color[1];
data[(y + idx * 2 + 1) * 560 * 4 + (x + jdx) * 4 + 2] = color[2];
}
}
}
}
}
function _updateStartPos() {
var startPos =
_regs[REGS.STARTPOS_HI] << 8 |
_regs[REGS.STARTPOS_LO];
if (_startPos != startPos) {
_startPos = startPos;
_shouldRefresh = true;
}
}
function _refresh() {
for (var idx = 0; idx < 0x800; idx++) {
_updateBuffer(idx, _buffer[idx]);
}
}
function _access(off, val) {
var writeMode = val !== undefined;
var result = undefined;
switch (off & 0x81) {
case LOC.IOREG:
if (writeMode) {
_curReg = val;
} else {
result = _curReg;
}
break;
case LOC.IOVAL:
if (writeMode) {
_regs[_curReg] = val;
switch (_curReg) {
case REGS.CURSOR_UPPER:
case REGS.CURSOR_LOWER:
_refreshCursor(true);
break;
case REGS.CURSOR_HI:
case REGS.CURSOR_LO:
_refreshCursor(true);
break;
case REGS.STARTPOS_HI:
case REGS.STARTPOS_LO:
_updateStartPos();
break;
}
} else {
result = _regs[_curReg];
}
break;
}
_bank = (off & 0x0C) >> 2;
return result;
}
_init();
return {
ioSwitch: function (off, val) {
return _access(off, val);
},
read: function(page, off) {
if (page < 0xcc) {
return ROM[(page & 0x03) << 8 | off];
} else if (page < 0xce){
var addr = ((page & 0x01) + (_bank << 1)) << 8 | off;
return _buffer[addr];
}
},
write: function(page, off, val) {
if (page > 0xcb && page < 0xce) {
var addr = ((page & 0x01) + (_bank << 1)) << 8 | off;
_updateBuffer(addr, val);
}
},
blit: function() {
if (_shouldRefresh) {
_refresh();
_shouldRefresh = false;
}
if (_dirty) {
_dirty = false;
return _imageData;
}
return;
},
getState() {
return {
curReg: _curReg,
startPos: _startPos,
cursorPos: _cursorPos,
bank: _bank,
buffer: new Uint8Array(_buffer),
regs: [..._regs],
};
},
setState(state) {
_curReg = state.curReg;
_startPos = state.startPos;
_cursorPos = state.cursorPos;
_bank = state.bank;
_buffer = new Uint8Array(_buffer);
_regs = [...state.regs];
_shouldRefresh = true;
_dirty = true;
}
};
}

281
js/cards/videoterm.ts Normal file
View File

@ -0,0 +1,281 @@
/* Copyright 2017 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { allocMemPages, debug } from '../util';
import { Card, Restorable, byte, Color, memory, word } from '../types';
import { ROM, VIDEO_ROM } from '../roms/cards/videoterm';
interface VideotermState {
curReg: byte,
startPos: word,
cursorPos: word,
bank: byte,
buffer: memory,
regs: byte[],
}
const LOC = {
IOREG: 0x80,
IOVAL: 0x81
} as const;
const REGS = {
CURSOR_UPPER: 0x0A,
CURSOR_LOWER: 0x0B,
STARTPOS_HI: 0x0C,
STARTPOS_LO: 0x0D,
CURSOR_HI: 0x0E,
CURSOR_LO: 0x0F,
LIGHTPEN_HI: 0x10,
LIGHTPEN_LO: 0x11
} as const;
const CURSOR_MODES = {
SOLID: 0x00,
HIDDEN: 0x01,
BLINK: 0x10,
FAST_BLINK: 0x11
} as const;
const BLACK: Color = [0x00, 0x00, 0x00];
const WHITE: Color = [0xff, 0xff, 0xff];
export default class Videoterm implements Card, Restorable<VideotermState> {
private regs = [
0x7b, // 00 - Horiz. total
0x50, // 01 - Horiz. displayed
0x62, // 02 - Horiz. sync pos
0x29, // 03 - Horiz. sync width
0x1b, // 04 - Vert. total
0x08, // 05 - Vert. adjust
0x18, // 06 - Vert. displayed
0x19, // 07 - Vert. sync pos
0x00, // 08 - Interlaced
0x08, // 09 - Max. scan line
0xc0, // 0A - Cursor upper
0x08, // 0B - Cursor lower
0x00, // 0C - Startpos Hi
0x00, // 0D - Startpos Lo
0x00, // 0E - Cursor Hi
0x00, // 0F - Cursor Lo
0x00, // 10 - Lightpen Hi
0x00 // 11 - Lightpen Lo
];
private blink = false;
private curReg = 0;
private startPos: word;
private cursorPos: word;
private shouldRefresh: boolean;
// private cursor = 0;
private bank = 0;
private buffer = allocMemPages(8);
private imageData;
private dirty = false;
constructor() {
debug('Videx Videoterm');
this.imageData = new ImageData(560, 192);
for (let idx = 0; idx < 560 * 192 * 4; idx++) {
this.imageData.data[idx] = 0xff;
}
for (let idx = 0; idx < 0x800; idx++) {
this.buffer[idx] = idx & 0xff;
}
this.refresh();
setInterval(() => {
this.blink = !this.blink;
this.refreshCursor(false);
}, 300);
}
private updateBuffer(addr: word, val: byte) {
this.buffer[addr] = val;
val &= 0x7f; // XXX temp
const saddr = (0x800 + addr - this.startPos) & 0x7ff;
const data = this.imageData.data;
const row = (saddr / 80) & 0xff;
const col = saddr % 80;
const x = col * 7;
const y = row << 3;
const c = val << 4;
let color;
if (row < 25) {
this.dirty = true;
for (let idx = 0; idx < 8; idx++) {
let cdata = VIDEO_ROM[c + idx];
for (let jdx = 0; jdx < 7; jdx++) {
if (cdata & 0x80) {
color = WHITE;
} else {
color = BLACK;
}
data[(y + idx) * 560 * 4 + (x + jdx) * 4] = color[0];
data[(y + idx) * 560 * 4 + (x + jdx) * 4 + 1] = color[1];
data[(y + idx) * 560 * 4 + (x + jdx) * 4 + 2] = color[2];
cdata <<= 1;
}
}
}
}
private refreshCursor(fromRegs: boolean) {
const addr = this.regs[REGS.CURSOR_HI] << 8 | this.regs[REGS.CURSOR_LO];
const saddr = (0x800 + addr - this.startPos) & 0x7ff;
const data = this.imageData.data;
const row = (saddr / 80) & 0xff;
const col = saddr % 80;
const x = col * 7;
const y = row << 3;
const blinkmode = (this.regs[REGS.CURSOR_UPPER] & 0x60) >> 5;
if (fromRegs) {
if (addr !== this.cursorPos) {
const caddr = (0x800 + this.cursorPos - this.startPos) & 0x7ff;
this.updateBuffer(caddr, this.buffer[caddr]);
this.cursorPos = addr;
}
}
this.updateBuffer(addr, this.buffer[addr]);
if (blinkmode === CURSOR_MODES.HIDDEN) {
return;
}
if (this.blink || (blinkmode === CURSOR_MODES.SOLID)) {
this.dirty = true;
for (let idx = 0; idx < 8; idx++) {
const color = WHITE;
if (idx >= (this.regs[REGS.CURSOR_UPPER] & 0x1f) &&
idx <= (this.regs[REGS.CURSOR_LOWER] & 0x1f)) {
for (let jdx = 0; jdx < 7; jdx++) {
data[(y + idx) * 560 * 4 + (x + jdx) * 4] = color[0];
data[(y + idx) * 560 * 4 + (x + jdx) * 4 + 1] = color[1];
data[(y + idx) * 560 * 4 + (x + jdx) * 4 + 2] = color[2];
}
}
}
}
}
private updateStartPos() {
const startPos =
this.regs[REGS.STARTPOS_HI] << 8 |
this.regs[REGS.STARTPOS_LO];
if (this.startPos != startPos) {
this.startPos = startPos;
this.shouldRefresh = true;
}
}
private refresh() {
for (let idx = 0; idx < 0x800; idx++) {
this.updateBuffer(idx, this.buffer[idx]);
}
}
private access(off: byte, val?: byte) {
let result = undefined;
switch (off & 0x81) {
case LOC.IOREG:
if (val !== undefined) {
this.curReg = val;
} else {
result = this.curReg;
}
break;
case LOC.IOVAL:
if (val !== undefined) {
this.regs[this.curReg] = val;
switch (this.curReg) {
case REGS.CURSOR_UPPER:
case REGS.CURSOR_LOWER:
this.refreshCursor(true);
break;
case REGS.CURSOR_HI:
case REGS.CURSOR_LO:
this.refreshCursor(true);
break;
case REGS.STARTPOS_HI:
case REGS.STARTPOS_LO:
this.updateStartPos();
break;
}
} else {
result = this.regs[this.curReg];
}
break;
}
this.bank = (off & 0x0C) >> 2;
return result;
}
ioSwitch(off: byte, val?: byte) {
return this.access(off, val);
}
read(page: byte, off: byte) {
if (page < 0xcc) {
return ROM[(page & 0x03) << 8 | off];
} else if (page < 0xce){
const addr = ((page & 0x01) + (this.bank << 1)) << 8 | off;
return this.buffer[addr];
}
return 0;
}
write(page: byte, off: byte, val: byte) {
if (page > 0xcb && page < 0xce) {
const addr = ((page & 0x01) + (this.bank << 1)) << 8 | off;
this.updateBuffer(addr, val);
}
}
blit() {
if (this.shouldRefresh) {
this.refresh();
this.shouldRefresh = false;
}
if (this.dirty) {
this.dirty = false;
return this.imageData;
}
return;
}
getState() {
return {
curReg: this.curReg,
startPos: this.startPos,
cursorPos: this.cursorPos,
bank: this.bank,
buffer: new Uint8Array(this.buffer),
regs: [...this.regs],
};
}
setState(state: VideotermState) {
this.curReg = state.curReg;
this.startPos = state.startPos;
this.cursorPos = state.cursorPos;
this.bank = state.bank;
this.buffer = new Uint8Array(this.buffer);
this.regs = [...state.regs];
this.shouldRefresh = true;
this.dirty = true;
}
}

View File

@ -9,12 +9,11 @@
* implied warranty.
*/
import { byte, memory, MemoryPages, rom } from './types';
import { byte, Color, memory, MemoryPages, rom } from './types';
import { allocMemPages } from './util';
import { screenEmu } from 'apple2shader';
import {
Color,
GraphicsState,
HiresPage,
LoresPage,

View File

@ -77,11 +77,11 @@ var io = apple2.getIO();
var printer = new Printer('#printer-modal .paper');
var lc = new LanguageCard(rom);
var parallel = new Parallel(io, printer);
var videoTerm = new VideoTerm(io);
var slinky = new RAMFactor(io, 1024 * 1024);
var parallel = new Parallel(printer);
var videoTerm = new VideoTerm();
var slinky = new RAMFactor(1024 * 1024);
var disk2 = new DiskII(io, driveLights, sectors);
var clock = new Thunderclock(io);
var clock = new Thunderclock();
var smartport = new SmartPort(cpu, { block: true });
initUI(apple2, disk2, smartport, printer, false);

View File

@ -58,10 +58,10 @@ var cpu = apple2.getCPU();
var printer = new Printer('#printer-modal .paper');
var parallel = new Parallel(io, printer);
var slinky = new RAMFactor(io, 1024 * 1024);
var parallel = new Parallel(printer);
var slinky = new RAMFactor(1024 * 1024);
var disk2 = new DiskII(io, driveLights);
var clock = new Thunderclock(io);
var clock = new Thunderclock();
var smartport = new SmartPort(cpu, { block: !enhanced });
initUI(apple2, disk2, smartport, printer, options.e);

View File

@ -50,7 +50,7 @@ export interface Card extends Memory, Restorable {
reset?(): void;
/* Draw card to canvas */
blit?(): ImageData;
blit?(): ImageData | undefined;
/* Process period events */
tick?(): void;
@ -100,3 +100,6 @@ export type TypedArrayMutableProperties = 'copyWithin' | 'fill' | 'reverse' | 's
export interface ReadonlyUint8Array extends Omit<Uint8Array, TypedArrayMutableProperties> {
readonly [n: number]: number
}
// Readonly RGB color value
export type Color = readonly [r: byte, g: byte, b: byte];

View File

@ -3,12 +3,6 @@ import { MemoryPages, Restorable, byte, memory } from './types';
export type bank = 0 | 1;
export type pageNo = 1 | 2;
export interface Color {
0: byte, // red
1: byte, // green
2: byte, // blue
}
export interface Region {
top: number,
bottom: number,