diff --git a/js/apple2.ts b/js/apple2.ts index 8e3cc8f..35ff4a9 100644 --- a/js/apple2.ts +++ b/js/apple2.ts @@ -148,6 +148,7 @@ export class Apple2 implements Restorable, DebuggerContainer { const imageData = this.io.blit(); if (imageData) { this.vm.blit(imageData); + this.stats.renderedFrames++; } } else { if (this.vm.blit()) { diff --git a/js/canvas.ts b/js/canvas.ts index d5e0f8f..22c74f1 100644 --- a/js/canvas.ts +++ b/js/canvas.ts @@ -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, diff --git a/js/cards/parallel.js b/js/cards/parallel.ts similarity index 51% rename from js/cards/parallel.js rename to js/cards/parallel.ts index fc9c1e6..d340e8f 100644 --- a/js/cards/parallel.js +++ b/js/cards/parallel.ts @@ -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 { + 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) {} } diff --git a/js/cards/ramfactor.js b/js/cards/ramfactor.js deleted file mode 100644 index 186328a..0000000 --- a/js/cards/ramfactor.js +++ /dev/null @@ -1,156 +0,0 @@ -/* Copyright 2010-2019 Will Scullin - * - * 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; - } - }; -} diff --git a/js/cards/ramfactor.ts b/js/cards/ramfactor.ts new file mode 100644 index 0000000..f9e42a7 --- /dev/null +++ b/js/cards/ramfactor.ts @@ -0,0 +1,163 @@ +/* Copyright 2010-2019 Will Scullin + * + * 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 { + 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; + } +} diff --git a/js/cards/thunderclock.js b/js/cards/thunderclock.js deleted file mode 100644 index 04ad21f..0000000 --- a/js/cards/thunderclock.js +++ /dev/null @@ -1,159 +0,0 @@ -/* Copyright 2010-2019 Will Scullin - * - * 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(_) {} - }; -} diff --git a/js/cards/thunderclock.ts b/js/cards/thunderclock.ts new file mode 100644 index 0000000..3244511 --- /dev/null +++ b/js/cards/thunderclock.ts @@ -0,0 +1,163 @@ +/* Copyright 2010-2019 Will Scullin + * + * 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 +{ + 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() {} +} diff --git a/js/cards/videoterm.js b/js/cards/videoterm.js deleted file mode 100644 index d3bcc50..0000000 --- a/js/cards/videoterm.js +++ /dev/null @@ -1,284 +0,0 @@ -/* Copyright 2017 Will Scullin - * - * 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; - } - }; -} diff --git a/js/cards/videoterm.ts b/js/cards/videoterm.ts new file mode 100644 index 0000000..4c3054c --- /dev/null +++ b/js/cards/videoterm.ts @@ -0,0 +1,281 @@ +/* Copyright 2017 Will Scullin + * + * 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 { + 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; + } +} diff --git a/js/gl.ts b/js/gl.ts index 310a70b..246d98c 100644 --- a/js/gl.ts +++ b/js/gl.ts @@ -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, diff --git a/js/main2.js b/js/main2.js index 9e6a894..5089dc0 100644 --- a/js/main2.js +++ b/js/main2.js @@ -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); diff --git a/js/main2e.js b/js/main2e.js index ccf0066..23d8950 100644 --- a/js/main2e.js +++ b/js/main2e.js @@ -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); diff --git a/js/types.ts b/js/types.ts index 9fac0b1..46fa1f4 100644 --- a/js/types.ts +++ b/js/types.ts @@ -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 { readonly [n: number]: number } + +// Readonly RGB color value +export type Color = readonly [r: byte, g: byte, b: byte]; diff --git a/js/videomodes.ts b/js/videomodes.ts index cd6ff3e..b6ef292 100644 --- a/js/videomodes.ts +++ b/js/videomodes.ts @@ -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,