1
0
mirror of https://github.com/pkimpel/retro-220.git synced 2026-03-09 20:18:35 +00:00
Files
pkimpel.retro-220/webUI/B220PaperTapeReader.js
Paul Kimpel 67958aa65a Commit emulator version 1.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.
2018-07-17 06:57:01 -07:00

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;
}
};