/* -*- mode: JavaScript; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright 2010-2013 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. */ /*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; } } }; }