mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Convert more cards to typescript (#63)
Convert more cards to typescript
This commit is contained in:
parent
027b113cd4
commit
ab3e97e8d1
@ -148,6 +148,7 @@ export class Apple2 implements Restorable<State>, DebuggerContainer {
|
|||||||
const imageData = this.io.blit();
|
const imageData = this.io.blit();
|
||||||
if (imageData) {
|
if (imageData) {
|
||||||
this.vm.blit(imageData);
|
this.vm.blit(imageData);
|
||||||
|
this.stats.renderedFrames++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.vm.blit()) {
|
if (this.vm.blit()) {
|
||||||
|
@ -9,10 +9,9 @@
|
|||||||
* implied warranty.
|
* implied warranty.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { byte, memory, MemoryPages, rom } from './types';
|
import { byte, Color, memory, MemoryPages, rom } from './types';
|
||||||
import { allocMemPages } from './util';
|
import { allocMemPages } from './util';
|
||||||
import {
|
import {
|
||||||
Color,
|
|
||||||
GraphicsState,
|
GraphicsState,
|
||||||
HiresPage,
|
HiresPage,
|
||||||
LoresPage,
|
LoresPage,
|
||||||
|
@ -10,39 +10,49 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { debug } from '../util';
|
import { debug } from '../util';
|
||||||
|
import { Card, Restorable, byte } from '../types';
|
||||||
import { rom } from '../roms/cards/parallel';
|
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 = {
|
export default class Parallel implements Card, Restorable<ParallelState> {
|
||||||
IOREG: 0x80
|
constructor(private cbs: ParallelOptions) {
|
||||||
};
|
debug('Parallel card');
|
||||||
|
}
|
||||||
|
|
||||||
function _access(off, val) {
|
private access(off: byte, val?: byte) {
|
||||||
switch (off & 0x8f) {
|
switch (off & 0x8f) {
|
||||||
case LOC.IOREG:
|
case LOC.IOREG:
|
||||||
if (cbs.putChar && val) {
|
if (this.cbs.putChar && val) {
|
||||||
cbs.putChar(val);
|
this.cbs.putChar(val);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
debug('Parallel card unknown softswitch', off);
|
debug('Parallel card unknown softswitch', off);
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
ioSwitch(off: byte, val?: byte) {
|
||||||
ioSwitch: function (off, val) {
|
return this.access(off, val);
|
||||||
return _access(off, val);
|
}
|
||||||
},
|
|
||||||
read: function(page, off) {
|
read(_page: byte, off: byte) {
|
||||||
return rom[off];
|
return rom[off];
|
||||||
},
|
}
|
||||||
write: function() {},
|
|
||||||
getState() {
|
write() {}
|
||||||
return {};
|
|
||||||
},
|
getState() {
|
||||||
setState(_) {}
|
return {};
|
||||||
};
|
}
|
||||||
|
|
||||||
|
setState(_state: ParallelState) {}
|
||||||
}
|
}
|
@ -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
163
js/cards/ramfactor.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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
163
js/cards/thunderclock.ts
Normal 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() {}
|
||||||
|
}
|
@ -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
281
js/cards/videoterm.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
3
js/gl.ts
3
js/gl.ts
@ -9,12 +9,11 @@
|
|||||||
* implied warranty.
|
* implied warranty.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { byte, memory, MemoryPages, rom } from './types';
|
import { byte, Color, memory, MemoryPages, rom } from './types';
|
||||||
import { allocMemPages } from './util';
|
import { allocMemPages } from './util';
|
||||||
|
|
||||||
import { screenEmu } from 'apple2shader';
|
import { screenEmu } from 'apple2shader';
|
||||||
import {
|
import {
|
||||||
Color,
|
|
||||||
GraphicsState,
|
GraphicsState,
|
||||||
HiresPage,
|
HiresPage,
|
||||||
LoresPage,
|
LoresPage,
|
||||||
|
@ -77,11 +77,11 @@ var io = apple2.getIO();
|
|||||||
var printer = new Printer('#printer-modal .paper');
|
var printer = new Printer('#printer-modal .paper');
|
||||||
|
|
||||||
var lc = new LanguageCard(rom);
|
var lc = new LanguageCard(rom);
|
||||||
var parallel = new Parallel(io, printer);
|
var parallel = new Parallel(printer);
|
||||||
var videoTerm = new VideoTerm(io);
|
var videoTerm = new VideoTerm();
|
||||||
var slinky = new RAMFactor(io, 1024 * 1024);
|
var slinky = new RAMFactor(1024 * 1024);
|
||||||
var disk2 = new DiskII(io, driveLights, sectors);
|
var disk2 = new DiskII(io, driveLights, sectors);
|
||||||
var clock = new Thunderclock(io);
|
var clock = new Thunderclock();
|
||||||
var smartport = new SmartPort(cpu, { block: true });
|
var smartport = new SmartPort(cpu, { block: true });
|
||||||
|
|
||||||
initUI(apple2, disk2, smartport, printer, false);
|
initUI(apple2, disk2, smartport, printer, false);
|
||||||
|
@ -58,10 +58,10 @@ var cpu = apple2.getCPU();
|
|||||||
|
|
||||||
var printer = new Printer('#printer-modal .paper');
|
var printer = new Printer('#printer-modal .paper');
|
||||||
|
|
||||||
var parallel = new Parallel(io, printer);
|
var parallel = new Parallel(printer);
|
||||||
var slinky = new RAMFactor(io, 1024 * 1024);
|
var slinky = new RAMFactor(1024 * 1024);
|
||||||
var disk2 = new DiskII(io, driveLights);
|
var disk2 = new DiskII(io, driveLights);
|
||||||
var clock = new Thunderclock(io);
|
var clock = new Thunderclock();
|
||||||
var smartport = new SmartPort(cpu, { block: !enhanced });
|
var smartport = new SmartPort(cpu, { block: !enhanced });
|
||||||
|
|
||||||
initUI(apple2, disk2, smartport, printer, options.e);
|
initUI(apple2, disk2, smartport, printer, options.e);
|
||||||
|
@ -50,7 +50,7 @@ export interface Card extends Memory, Restorable {
|
|||||||
reset?(): void;
|
reset?(): void;
|
||||||
|
|
||||||
/* Draw card to canvas */
|
/* Draw card to canvas */
|
||||||
blit?(): ImageData;
|
blit?(): ImageData | undefined;
|
||||||
|
|
||||||
/* Process period events */
|
/* Process period events */
|
||||||
tick?(): void;
|
tick?(): void;
|
||||||
@ -100,3 +100,6 @@ export type TypedArrayMutableProperties = 'copyWithin' | 'fill' | 'reverse' | 's
|
|||||||
export interface ReadonlyUint8Array extends Omit<Uint8Array, TypedArrayMutableProperties> {
|
export interface ReadonlyUint8Array extends Omit<Uint8Array, TypedArrayMutableProperties> {
|
||||||
readonly [n: number]: number
|
readonly [n: number]: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Readonly RGB color value
|
||||||
|
export type Color = readonly [r: byte, g: byte, b: byte];
|
||||||
|
@ -3,12 +3,6 @@ import { MemoryPages, Restorable, byte, memory } from './types';
|
|||||||
export type bank = 0 | 1;
|
export type bank = 0 | 1;
|
||||||
export type pageNo = 1 | 2;
|
export type pageNo = 1 | 2;
|
||||||
|
|
||||||
export interface Color {
|
|
||||||
0: byte, // red
|
|
||||||
1: byte, // green
|
|
||||||
2: byte, // blue
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Region {
|
export interface Region {
|
||||||
top: number,
|
top: number,
|
||||||
bottom: number,
|
bottom: number,
|
||||||
|
Loading…
Reference in New Issue
Block a user