mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
4d80f9f28e
Initial checkin, still pretty rough around the edges.
338 lines
11 KiB
JavaScript
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;
|
|
}
|
|
}
|
|
};
|
|
}
|