mirror of
https://github.com/pkimpel/retro-220.git
synced 2026-03-09 20:18:35 +00:00
1. Rework Processor internal timing and throttling mechanism during I/O. 2. Revise Console statistics display, add instruction counter. 3. Correct (again) CFA/CFR instruction when sign is included in field. 4. Correct B-register modification of words during Cardatron and magnetic tape input. 5. Clear Processor alarms on Reset/Transfer. 6. Add links to wiki on index and home pages. 7. Eliminate B220Util CSS class functions in favor of DOM classList methods. 8. Attempt to reproduce "Sundland Beige" color for the panels. 9. Correct formatting of tab stops for B220ConsolePrinter. 10. Reduce Whippet printer speed from 5000 to 1000 cps. 11. Reduce Console update frequency from every 50 to 100 ms; increase lamp glow update factor from 0.25 to 0.75. 12. Allow click of white button below console register lamps in addition to clicking the lamps themselves to toggle the lamp state. 13. Rework the way that white vertical bars are drawn on registers. 14. Allow B220PaperTapeReader to properly send sign-2 alphanumeric words with trailing spaces if the tape image file has been space-trimmed on the right. 15. Clear the paper tape reader view window when loading new tapes. 16. Revise yet again the setCallback() delay deviation adjustment algorithm.
446 lines
19 KiB
JavaScript
446 lines
19 KiB
JavaScript
/***********************************************************************
|
|
* retro-220/webUI B220PaperTapeReader.js
|
|
************************************************************************
|
|
* Copyright (c) 2017, Paul Kimpel.
|
|
* Licensed under the MIT License, see
|
|
* http://www.opensource.org/licenses/mit-license.php
|
|
************************************************************************
|
|
* Burroughs 220 Paper Tape Reader module.
|
|
*
|
|
* Defines the paper tape input devices.
|
|
*
|
|
************************************************************************
|
|
* 2017-05-27 P.Kimpel
|
|
* Original version, from retro-205 D205ConsoleInput.js.
|
|
***********************************************************************/
|
|
"use strict";
|
|
|
|
/**************************************/
|
|
function B220PaperTapeReader(mnemonic, unitIndex, config) {
|
|
/* Constructor for the PaperTapeReader object */
|
|
var top = unitIndex*32 + 264;
|
|
var left = (unitIndex-1)*32 + 250;
|
|
|
|
this.config = config; // System configuration object
|
|
this.mnemonic = mnemonic; // Unit mnemonic
|
|
this.unitIndex = unitIndex; // Unit index into console output units
|
|
this.inTimer = 0; // input setCallback() token
|
|
|
|
this.charPeriod = B220PaperTapeReader.highSpeedPeriod;
|
|
// paper-tape reader speed [ms/char]
|
|
this.nextCharTime = 0; // next time a character can be read
|
|
this.unitMask = 0; // unit selection mask
|
|
|
|
this.boundFlipSwitch = B220PaperTapeReader.prototype.flipSwitch.bind(this);
|
|
this.boundReadTapeChar = B220PaperTapeReader.prototype.readTapeChar.bind(this);
|
|
|
|
this.readResult = { // object passed back to Processor for each read
|
|
code: 0, // 220 char code sent to processor
|
|
signDigit: 0, // copy of first digit read for word
|
|
frameCount: 0, // count of tape frames read for word
|
|
readChar: this.boundReadTapeChar // callback function for next char
|
|
};
|
|
|
|
this.clear();
|
|
|
|
// Create the reader window and onload event
|
|
this.doc = null;
|
|
this.window = null;
|
|
this.tapeSupplyBar = null;
|
|
this.tapeView = null;
|
|
B220Util.openPopup(window, "../webUI/B220PaperTapeReader.html", mnemonic,
|
|
"location=no,scrollbars=no,resizable,width=420,height=160,left=" + left + ",top=" + top,
|
|
this, B220PaperTapeReader.prototype.readerOnload);
|
|
}
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.offSwitchImage = "./resources/ToggleDown.png";
|
|
B220PaperTapeReader.onSwitchImage = "./resources/ToggleUp.png";
|
|
|
|
B220PaperTapeReader.lowSpeedPeriod = 1000/500;
|
|
// low reader speed: 500 cps
|
|
B220PaperTapeReader.highSpeedPeriod = 1000/1000;
|
|
// high reader speed: 1000 cps
|
|
B220PaperTapeReader.startupTime = 5; // reader start-up delay, ms
|
|
B220PaperTapeReader.idleTime = 50; // idle time before reader requires start-up delay, ms
|
|
|
|
// Translate ANSI character codes to B220 charater codes.
|
|
// Note that ANSI new-line sequences are used for end-of-word characters,
|
|
// so "|" translates to B220 carriage-return (16). To avoid space-expansion
|
|
// of tabs, "~" translates to tab (26). "_" translates to the "blank" code (02).
|
|
// "^" translates to form-feed (15).
|
|
B220PaperTapeReader.xlate220 = [
|
|
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
|
0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x26, 0x16, 0x17, 0x15, 0x16, 0x17, 0x17, // 00-0F
|
|
0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, // 10-1F
|
|
0x00, 0x17, 0x17, 0x33, 0x13, 0x24, 0x10, 0x34, 0x24, 0x04, 0x14, 0x10, 0x23, 0x20, 0x03, 0x21, // 20-2F
|
|
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x17, 0x13, 0x04, 0x33, 0x17, 0x17, // 30-3F
|
|
0x34, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, // 40-4F
|
|
0x57, 0x58, 0x59, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x17, 0x17, 0x17, 0x15, 0x02, // 50-5F
|
|
0x17, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, // 60-6F
|
|
0x57, 0x58, 0x59, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x17, 0x16, 0x17, 0x26, 0x17, // 70-7F
|
|
0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, // 80-8F
|
|
0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, // 90-9F
|
|
0x17, 0x17, 0x17, 0x17, 0x04, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, // A0-AF
|
|
0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, // B0-BF
|
|
0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, // C0-CF
|
|
0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, // D0-DF
|
|
0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, // E0-EF
|
|
0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17];// F0-FF
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.clear = function clear() {
|
|
/* Initializes (and if necessary, creates) the reader unit state */
|
|
|
|
this.ready = false; // a tape has been loaded into the reader
|
|
this.busy = false; // a request for a digit is outstanding
|
|
this.pendingReceiver = null; // stashed digit-receiver function
|
|
|
|
this.buffer = ""; // reader input buffer (paper-tape reel)
|
|
this.bufLength = 0; // current input buffer length (characters)
|
|
this.bufIndex = 0; // 0-relative offset to next character to be read
|
|
};
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.$$ = function $$(e) {
|
|
return this.doc.getElementById(e);
|
|
};
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.PRTapeSupplyBar_onclick = function PRTapeSupplyBar_onclick(ev) {
|
|
/* Handle the click event for the "input hopper" meter bar */
|
|
|
|
if (!this.ready) {
|
|
if (this.window.confirm((this.bufLength-this.bufIndex).toString() + " of " + this.bufLength.toString() +
|
|
" characters remaining to read.\nDo you want to clear the reader input?")) {
|
|
this.tapeView.value = "";
|
|
this.setReaderEmpty();
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.fileSelector_onChange = function fileSelector_onChange(ev) {
|
|
/* Handle the <input type=file> onchange event when files are selected. For each
|
|
file, load it and add it to the input buffer of the reader */
|
|
var tape;
|
|
var f = ev.target.files;
|
|
var that = this;
|
|
var x;
|
|
|
|
function fileLoader_onLoad(ev) {
|
|
/* Handle the onload event for a Text FileReader */
|
|
|
|
if (that.bufIndex >= that.bufLength) {
|
|
that.buffer = ev.target.result;
|
|
} else {
|
|
switch (that.buffer.charAt(that.buffer.length-1)) {
|
|
case "\r":
|
|
case "\n":
|
|
break; // do nothing -- the last word has a delimiter
|
|
default:
|
|
that.buffer += "\n"; // so the next tape starts on a new line
|
|
break;
|
|
}
|
|
that.buffer = that.buffer.substring(that.bufIndex) + ev.target.result;
|
|
}
|
|
|
|
that.bufIndex = 0;
|
|
that.bufLength = that.buffer.length;
|
|
that.$$("PRTapeSupplyBar").value = that.bufLength;
|
|
that.$$("PRTapeSupplyBar").max = that.bufLength;
|
|
that.setReaderReady(true);
|
|
if (that.busy && that.ready) { // reinitiate the pending read
|
|
that.readTapeChar(that.pendingReceiver);
|
|
that.receiver = null;
|
|
}
|
|
}
|
|
|
|
this.tapeView.value = "";
|
|
for (x=f.length-1; x>=0; x--) {
|
|
tape = new FileReader();
|
|
tape.onload = fileLoader_onLoad;
|
|
tape.readAsText(f[x]);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.setReaderReady = function setReaderReady(ready) {
|
|
/* Sets the reader to a ready or not-ready status */
|
|
|
|
this.ready = ready && this.remoteSwitch.state && (this.bufIndex < this.bufLength);
|
|
this.readyLamp.set(this.ready ? 1 : 0);
|
|
};
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.setReaderEmpty = function setReaderEmpty() {
|
|
/* Sets the reader to a not-ready status and empties the buffer */
|
|
|
|
this.setReaderReady(false);
|
|
this.tapeSupplyBar.value = 0;
|
|
this.buffer = ""; // discard the input buffer
|
|
this.bufLength = 0;
|
|
this.bufIndex = 0;
|
|
this.fileSelector.value = null; // reset the control so the same file can be reloaded
|
|
};
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.beforeUnload = function beforeUnload(ev) {
|
|
var msg = "Closing this window will make the device unusable.\n" +
|
|
"Suggest you stay on the page and minimize this window instead";
|
|
|
|
ev.preventDefault();
|
|
ev.returnValue = msg;
|
|
return msg;
|
|
};
|
|
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.flipSwitch = function flipSwitch(ev) {
|
|
/* Handler for switch clicks */
|
|
var id = ev.target.id;
|
|
var prefs = this.config.getNode("ConsoleInput.units", this.unitIndex);
|
|
var x = 0;
|
|
|
|
switch (id) {
|
|
case "RemoteSwitch":
|
|
this.remoteSwitch.flip();
|
|
prefs.remote = this.remoteSwitch.state;
|
|
this.setReaderReady(this.remoteSwitch.state != 0);
|
|
this.fileSelector.disabled = (this.remoteSwitch.state != 0);
|
|
break;
|
|
case "SpeedSwitch":
|
|
this.speedSwitch.flip();
|
|
prefs.speed = this.speedSwitch.state;
|
|
this.charPeriod = (this.speedSwitch.state ? B220PaperTapeReader.highSpeedPeriod : B220PaperTapeReader.lowSpeedPeriod);
|
|
break;
|
|
case "UnitDesignateKnob":
|
|
x = this.unitDesignateKnob.selectedIndex;
|
|
if (x < 0) {
|
|
this.unitMask = 0;
|
|
} else {
|
|
this.unitMask = B220Processor.pow2[x+1];
|
|
}
|
|
|
|
prefs.unitMask = this.unitMask;
|
|
break;
|
|
}
|
|
|
|
this.config.putNode("ConsoleInput.units", prefs, this.unitIndex);
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
};
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.readerOnload = function readerOnload(ev) {
|
|
/* Initializes the reader window and user interface */
|
|
var body;
|
|
var mask;
|
|
var prefs = this.config.getNode("ConsoleInput.units", this.unitIndex);
|
|
var x;
|
|
|
|
this.doc = ev.target;
|
|
this.window = this.doc.defaultView;
|
|
//de = this.doc.documentElement;
|
|
this.doc.title = "retro-220 - Reader - " + this.mnemonic;
|
|
|
|
this.fileSelector = this.$$("PRFileSelector");
|
|
this.tapeSupplyBar = this.$$("PRTapeSupplyBar");
|
|
this.tapeView = this.$$("PRTapeView");
|
|
|
|
body = this.$$("PaperTapeReader")
|
|
this.remoteSwitch = new ToggleSwitch(body, null, null, "RemoteSwitch",
|
|
B220PaperTapeReader.offSwitchImage, B220PaperTapeReader.onSwitchImage);
|
|
this.remoteSwitch.set(0); // ignore prefs.remote, always initialize as LOCAL
|
|
|
|
this.readyLamp = new ColoredLamp(body, null, null, "ReadyLamp", "blueLamp lampCollar", "blueLit");
|
|
this.setReaderReady(this.remoteSwitch.state != 0);
|
|
|
|
this.speedSwitch = new ToggleSwitch(body, null, null, "SpeedSwitch",
|
|
B220PaperTapeReader.offSwitchImage, B220PaperTapeReader.onSwitchImage);
|
|
this.speedSwitch.set(prefs.speed);
|
|
this.charPeriod = (this.speedSwitch.state ? B220PaperTapeReader.highSpeedPeriod : B220PaperTapeReader.lowSpeedPeriod);
|
|
|
|
this.unitDesignateKnob = this.$$("UnitDesignateKnob");
|
|
this.unitMask = prefs.unitMask;
|
|
if (this.unitMask == 0) {
|
|
this.unitDesignateKnob.selectedIndex = this.unitDesignateKnob.length-1; // OFF
|
|
} else {
|
|
mask = 0x002; // ignore the 1-bit (used for SPO on output devices)
|
|
for (x=0; x<this.unitDesignateKnob.length; ++x) {
|
|
if (this.unitMask & mask) {
|
|
this.unitDesignateKnob.selectedIndex = x;
|
|
break; // out of for loop
|
|
} else {
|
|
mask <<= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.fileSelector.disabled = (this.remoteSwitch.state != 0);
|
|
|
|
// Events
|
|
this.window.addEventListener("beforeunload",
|
|
B220PaperTapeReader.prototype.beforeUnload, false);
|
|
this.fileSelector.addEventListener("change",
|
|
B220PaperTapeReader.prototype.fileSelector_onChange.bind(this), false);
|
|
this.tapeSupplyBar.addEventListener("click",
|
|
B220PaperTapeReader.prototype.PRTapeSupplyBar_onclick.bind(this), false);
|
|
this.remoteSwitch.addEventListener("click", this.boundFlipSwitch);
|
|
this.speedSwitch.addEventListener("click", this.boundFlipSwitch);
|
|
this.unitDesignateKnob.addEventListener("change", this.boundFlipSwitch);
|
|
|
|
//this.window.resizeBy(de.scrollWidth - this.window.innerWidth + 4, // kludge for right-padding/margin
|
|
// de.scrollHeight - this.window.innerHeight);
|
|
};
|
|
|
|
/***********************************************************************
|
|
* Input Entry Points *
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.initiateInput = function initiateInput(successor) {
|
|
/* Initiates input to the reader. This simply calls this.readTapeChar(),
|
|
which returns the first tape character (which should be the sign digit) to
|
|
the processor and gets the ball rolling */
|
|
var stamp = performance.now();
|
|
|
|
this.readResult.code = 0;
|
|
this.readResult.signDigit = 0;
|
|
this.readResult.frameCount = 0;
|
|
if (stamp-this.nextCharTime < B220PaperTapeReader.idleTime) {
|
|
this.readTapeChar(successor);
|
|
} else {
|
|
setCallback(this.mnemonic, this, B220PaperTapeReader.startupTime, this.readTapeChar, successor);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.sendTapeChar = function sendTapeChar(c, code, receiver) {
|
|
/* Sends the character or EOW code read from the tape back to the
|
|
Processor after an appropriate delay. Updates the TapeView display with the
|
|
character sent */
|
|
var delay;
|
|
var length;
|
|
var stamp = performance.now();
|
|
var text = this.tapeView.value;
|
|
|
|
this.readResult.code = code;
|
|
++this.readResult.frameCount;
|
|
if (this.readResult.frameCount == 1) {
|
|
this.readResult.signDigit = code;
|
|
}
|
|
|
|
if (this.nextCharTime < stamp) {
|
|
delay = 0;
|
|
this.nextCharTime = stamp + this.charPeriod;
|
|
} else {
|
|
delay = this.nextCharTime - stamp;
|
|
this.nextCharTime += this.charPeriod;
|
|
}
|
|
|
|
this.inTimer = setCallback(this.mnemonic, this, delay, receiver, this.readResult);
|
|
length = text.length;
|
|
if (length < 120) {
|
|
this.tapeView.value = text + String.fromCharCode(c);
|
|
++length;
|
|
} else {
|
|
this.tapeView.value = text.substring(length-119) + String.fromCharCode(c);
|
|
}
|
|
this.tapeView.setSelectionRange(length-1, length);
|
|
};
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.conditionalSendEOW = function conditionalSendEOW(receiver) {
|
|
/* Checks for the special case of sending an alphanumeric word (sign=2) with
|
|
less than five alpha characters. Since text editors may trim trailing blanks
|
|
from the end of lines, alpha words with trailing blanks may be trimmed in the
|
|
tape image file. If the sign digit was 2 and fewer than six characters (sign
|
|
plus five alpha codes) have been sent to the processor, sends a space code to
|
|
pad the alpha word and returns false. Otherwise, sends an End-of-Word code and
|
|
returns true */
|
|
var code = 0x35; // EOW code
|
|
var result = true;
|
|
|
|
if (this.readResult.signDigit == 0x82) { // sign digit was a "2"
|
|
if (this.readResult.frameCount < 6) {
|
|
code = 0x00; // space code
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
this.sendTapeChar(0x20, code, receiver);
|
|
return result;
|
|
};
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.readTapeChar = function readTapeChar(receiver) {
|
|
/* Reads one character frame from the paper-tape buffer and passes it to the
|
|
"receiver" function. If at end-of-line, passes an end-of-word (0x35) code;
|
|
an error or invalid character is passed as a 0x17 code. Otherwise, the ANSI
|
|
character is translated to B220 code and that code is passed.
|
|
If the buffer is empty (this.ready=false) or we are at end of buffer,
|
|
simply exits after stashing a reference to the receiver function in
|
|
this.pendingReceiver. This will leave the processor hanging until more tape
|
|
is loaded into the reader or the processor is cleared. Once more tape is
|
|
loaded, the fileSelector_onChange event will set ready, notice the hanging
|
|
read (this.busy=true) and restart the read */
|
|
var bufLength = this.bufLength; // current buffer length
|
|
var c = 0; // current character ANSI code
|
|
var x = this.bufIndex; // current buffer index
|
|
|
|
if (!this.ready) {
|
|
this.busy = true;
|
|
this.pendingReceiver = receiver;// stash the receiver until tape is loaded
|
|
this.window.focus(); // call attention to the tape reader
|
|
} else {
|
|
this.busy = false;
|
|
if (x >= bufLength) { // end of buffer -- send finish
|
|
if (this.conditionalSendEOW(receiver)) {
|
|
this.setReaderEmpty();
|
|
}
|
|
} else {
|
|
c = this.buffer.charCodeAt(x) % 0x100;
|
|
if (c == 0x0D) { // carriage return -- send EOW and check for LF
|
|
if (this.conditionalSendEOW(receiver)) {
|
|
if (++x < bufLength && this.buffer.charCodeAt(x) == 0x0A) {
|
|
++x;
|
|
}
|
|
if (x >= bufLength) {
|
|
this.setReaderEmpty();
|
|
}
|
|
}
|
|
} else if (c == 0x0A) { // line feed -- send EOW
|
|
if (this.conditionalSendEOW(receiver)) {
|
|
++x;
|
|
if (x >= bufLength) {
|
|
this.setReaderEmpty();
|
|
}
|
|
}
|
|
} else { // translate character and send its code
|
|
++x;
|
|
this.sendTapeChar(c, B220PaperTapeReader.xlate220[c], receiver);
|
|
}
|
|
}
|
|
|
|
this.bufIndex = x;
|
|
this.tapeSupplyBar.value = bufLength-x;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220PaperTapeReader.prototype.shutDown = function shutDown() {
|
|
/* Shuts down the device */
|
|
|
|
if (this.inTimer) {
|
|
clearCallback(this.inTimer);
|
|
}
|
|
|
|
if (this.window) {
|
|
this.window.removeEventListener("beforeunload",
|
|
B220PaperTapeReader.prototype.beforeUnload, false);
|
|
this.window.close();
|
|
this.window = null;
|
|
}
|
|
};
|