apple2js/js/apple2io.js

338 lines
11 KiB
JavaScript

/* -*- mode: JavaScript; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* Copyright 2010-2013 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.
*/
/*globals debug: false, toHex: false, each: false */
/*exported Apple2IO */
function Apple2IO(cpu, callbacks)
{
var SAMPLE_RATE = 64;
var _buffer = [];
var _key = 0;
var _keyDown = false;
var _button = [false, false, false];
var _paddle = [0.0, 0.0, 0.0, 0,0];
var _phase = -1;
var _sample = [];
var _oldSample = [];
var _sampleTime = 0;
var _high = "%A0";
var _low = "%60";
var _trigger = 0;
var LOC = {
KEYBOARD: 0x00, // keyboard data (latched) (Read),
CLR80VID: 0x0C, // clear 80 column mode
SET80VID: 0x0D, // set 80 column mode
CLRALTCH: 0x0E, // clear 80 column mode
SETALTCH: 0x0F, // set 80 column mode
STROBE: 0x10, // clear bit 7 of keyboard data ($C000)
RDTEXT: 0x1A, // using text mode
RDMIXED: 0x1B, // using mixed mode
RDPAGE2: 0x1C, // using text/graphics page2
RDHIRES: 0x1D, // using Hi-res graphics mode
RDALTCH: 0x1E, // using alternate character set
RD80VID: 0x1F, // using 80-column display mode
TAPEOUT: 0x20, // toggle the cassette output.
SPEAKER: 0x30, // toggle speaker diaphragm
CLRTEXT: 0x50, // display graphics
SETTEXT: 0x51, // display text
CLRMIXED: 0x52, // clear mixed mode- enable full graphics
SETMIXED: 0x53, // enable graphics/text mixed mode
PAGE1: 0x54, // select text/graphics page1
PAGE2: 0x55, // select text/graphics page2
CLRHIRES: 0x56, // select Lo-res
SETHIRES: 0x57, // select Hi-res
CLRAN0: 0x58, // Set annunciator-0 output to 0
SETAN0: 0x59, // Set annunciator-0 output to 1
CLRAN1: 0x5A, // Set annunciator-1 output to 0
SETAN1: 0x5B, // Set annunciator-1 output to 1
CLRAN2: 0x5C, // Set annunciator-2 output to 0
SETAN2: 0x5D, // Set annunciator-2 output to 1
CLRAN3: 0x5E, // Set annunciator-3 output to 0
SETAN3: 0x5F, // Set annunciator-3 output to 1
TAPEIN: 0x60, // bit 7: data from cassette
PB0: 0x61, // game Pushbutton 0 / open apple (command) key data
PB1: 0x62, // game Pushbutton 1 / closed apple (option) key data
PB2: 0x63, // game Pushbutton 2 (read)
PADDLE0: 0x64, // bit 7: status of pdl-0 timer (read)
PADDLE1: 0x65, // bit 7: status of pdl-1 timer (read)
PADDLE2: 0x66, // bit 7: status of pdl-2 timer (read)
PADDLE3: 0x67, // bit 7: status of pdl-3 timer (read)
PDLTRIG: 0x70, // trigger paddles
SETIOUDIS:0x7E, // Enable double hires
CLRIOUDIS:0x7F // Disable double hires
};
function _debug() {
// debug.apply(arguments);
}
var _locs = [];
function _access(off) {
var result = 0;
var now = cpu.cycles();
var delta = now - _trigger;
switch (off) {
case LOC.CLR80VID:
// _debug("80 Column Mode off");
if ('_80col' in callbacks) callbacks._80col(false);
break;
case LOC.SET80VID:
// _debug("80 Column Mode on");
if ('_80col' in callbacks) callbacks._80col(true);
break;
case LOC.CLRALTCH:
// _debug("Alt Char off");
if ('altchar' in callbacks) callbacks.altchar(false);
break;
case LOC.SETALTCH:
// _debug("Alt Char on");
if ('altchar' in callbacks) callbacks.altchar(true);
break;
case LOC.CLRTEXT:
_debug("Graphics Mode");
callbacks.text(false);
break;
case LOC.SETTEXT:
_debug("Text Mode");
callbacks.text(true);
break;
case LOC.CLRMIXED:
_debug("Mixed Mode off");
callbacks.mixed(false);
break;
case LOC.SETMIXED:
_debug("Mixed Mode on");
callbacks.mixed(true);
break;
case LOC.CLRHIRES:
_debug("LoRes Mode");
callbacks.hires(false);
break;
case LOC.SETHIRES:
_debug("HiRes Mode");
callbacks.hires(true);
break;
case LOC.PAGE1:
callbacks.page(1);
break;
case LOC.PAGE2:
callbacks.page(2);
break;
case LOC.RDTEXT:
if ('isText' in callbacks)
result = callbacks.isText() ? 0x80 : 0x0;
break;
case LOC.RDMIXED:
if ('isMixed' in callbacks)
result = callbacks.isMixed() ? 0x80 : 0x0;
break;
case LOC.RDPAGE2:
if ('isPage2' in callbacks)
result = callbacks.isPage2() ? 0x80 : 0x0;
break;
case LOC.RDHIRES:
if ('isHires' in callbacks)
result = callbacks.isHires() ? 0x80 : 0x0;
break;
case LOC.RD80VID:
if ('is80Col' in callbacks)
result = callbacks.is80Col() ? 0x80 : 0x0;
break;
case LOC.RDALTCH:
if ('isAltChar' in callbacks)
result = callbacks.isAltChar() ? 0x80 : 0x0;
break;
case LOC.SETAN0:
_debug('Annunciator 0 on');
if ('annunciator' in callbacks) callbacks.annunicator(0, true);
break;
case LOC.SETAN1:
_debug('Annunciator 1 on');
if ('annunciator' in callbacks) callbacks.annunicator(1, true);
break;
case LOC.SETAN2:
_debug('Annunciator 2 on');
if ('annunciator' in callbacks) callbacks.annunicator(2, true);
break;
case LOC.SETAN3:
_debug('Annunciator 3 on');
if ('annunciator' in callbacks) callbacks.annunicator(3, true);
if ('doublehires' in callbacks) callbacks.doublehires(false);
break;
case LOC.CLRAN0:
_debug('Annunciator 0 off');
if ('annunciator' in callbacks) callbacks.annunicator(0, false);
break;
case LOC.CLRAN1:
_debug('Annunciator 1 off');
if ('annunciator' in callbacks) callbacks.annunicator(1, false);
break;
case LOC.CLRAN2:
_debug('Annunciator 2 off');
if ('annunciator' in callbacks) callbacks.annunicator(2, false);
break;
case LOC.CLRAN3:
_debug('Annunciator 3 off');
if ('annunciator' in callbacks) callbacks.annunicator(3, false);
if ('doublehires' in callbacks) callbacks.doublehires(true);
break;
case LOC.SPEAKER:
if (_sampleTime) {
var phase = _phase > 0 ? _high : _low;
for (; _sampleTime < now; _sampleTime += SAMPLE_RATE) {
_sample.push(phase);
}
_phase = -_phase;
}
break;
case LOC.STROBE:
_key &= 0x7f;
if (_buffer.length > 0) {
_key = _buffer.shift().charCodeAt(0) | 0x80;
}
result = _keyDown ? 0x80 : 0x00;
break;
case LOC.KEYBOARD:
result = _key;
break;
case LOC.PB0:
result = _button[0] ? 0x80 : 0;
break;
case LOC.PB1:
result = _button[1] ? 0x80 : 0;
break;
case LOC.PB2:
result = _button[2] ? 0x80 : 0;
break;
case LOC.PADDLE0:
result = (delta < (_paddle[0] * 2560) ? 0x80 : 0x00);
break;
case LOC.PADDLE1:
result = (delta < (_paddle[1] * 2560) ? 0x80 : 0x00);
break;
case LOC.PADDLE2:
result = (delta < (_paddle[2] * 2560) ? 0x80 : 0x00);
break;
case LOC.PADDLE3:
result = (delta < (_paddle[3] * 2560) ? 0x80 : 0x00);
break;
case LOC.PDLTRIG:
_trigger = cpu.cycles();
break;
}
return result;
}
return {
registerSwitches: function apple2io_registerSwitches(a, locs) {
each(locs, function(key) {
var val = locs[key];
if (_locs[val]) {
debug('duplicate switch! ' + toHex(val));
}
_locs[val] = a;
});
},
start: function apple2io_start() {
this.registerSwitches(this, LOC);
return 0xc0;
},
end: function apple2io_end() {
return 0xc0;
},
read: function apple2io_read(page, off) {
var result = 0;
if (_locs[off]) {
result = _locs[off].ioSwitch(off);
} else {
debug("I/O read: C0" + toHex(off));
}
return result;
},
write: function apple2io_write(page, off, val) {
if (_locs[off]) {
_locs[off].ioSwitch(off, val);
} else {
debug("I/O write: C0" + toHex(off));
}
},
getState: function apple2io_getState() { return {}; },
setState: function apple2io_setState() { },
ioSwitch: function apple2io_ioSwitch(off, val) {
return _access(off, val);
},
keyDown: function apple2io_keyDown(ascii) {
_keyDown = true;
_key = ascii | 0x80;
},
keyUp: function apple2io_keyUp() {
_keyDown = false;
_key = 0;
},
buttonDown: function apple2io_buttonDown(b) {
_button[b] = true;
},
buttonUp: function apple2io_buttonUp(b) {
_button[b] = false;
},
paddle: function apple2io_paddle(p, v) {
_paddle[p] = v;
},
getSample: function apple2io_getSample(stop) {
var result = _sample;
var now = cpu.cycles();
//if (_sampleTime) {
// var phase = _phase > 0 ? _high : _low;
// for (; _sampleTime < now; _sampleTime += SAMPLE_RATE)
// _sample.push(phase);
//}
_sample = _oldSample;
_sample.length = 0;
_sampleTime = stop ? 0 : now;
_oldSample = result;
return result;
},
floatAudio: function apple2io_floatAudio(rate) {
_low = -0.5;
_high = 0.5;
SAMPLE_RATE = 1023000.0 / rate;
},
byteAudio: function apple2io_floatAudio(rate) {
_low = 0xa0;
_high = 0x60;
SAMPLE_RATE = 1023000.0 / rate;
},
setKeyBuffer: function apple2io_setKeyBuffer(buffer) {
_buffer = buffer.split("");
if (_buffer.length > 0) {
_keyDown = true;
_key = _buffer.shift().charCodeAt(0) | 0x80;
}
}
};
}