From 6c51a5803ba8ab8033c853100dcbff968bd64e78 Mon Sep 17 00:00:00 2001 From: Paul Kimpel Date: Sun, 19 Nov 2017 17:56:09 -0800 Subject: [PATCH] Commit 220 emulator version 0.4: 1. Minor improvements to magnetic tape implementation and rearrangement of code. 2. Correct Processor CAD/CAA/CSU/CSA operation for operand signs other than 0 or 1. 3. Correct Processor LDB to load only the low-order four digits of the operand into B. 4. Correct Processor SLS shift count; improve mechanization of other shift-left operations. 5. Fix beforeunload event registration for Cardatron and Console devices. 6. Implement "Whippet" mode for Console TTY devices to print at 200 CPS instead of 10 CPS. 7. Expand B220PaperTapePunch translate table to cover all 256 8-bit codes; scroll end of punched data into view. 8. Minor code cleanup in B220PaperTapeReader. --- emulator/B220Processor.js | 138 ++++++++----- index.html | 6 +- webUI/B220CardatronControl.js | 6 +- webUI/B220CardatronInput.js | 18 +- webUI/B220CardatronOutput.js | 6 +- webUI/B220ConsolePrinter.css | 14 ++ webUI/B220ConsolePrinter.html | 3 + webUI/B220ConsolePrinter.js | 47 +++-- webUI/B220MagTapeControl.js | 368 +++++++++++++++++----------------- webUI/B220MagTapeDrive.js | 362 +++++++++++++++++---------------- webUI/B220Manifest.appcache | 2 +- webUI/B220PaperTapePunch.js | 27 ++- webUI/B220PaperTapeReader.js | 50 ++--- webUI/B220SystemConfig.js | 3 +- 14 files changed, 567 insertions(+), 483 deletions(-) diff --git a/emulator/B220Processor.js b/emulator/B220Processor.js index f19d47c..a47edca 100644 --- a/emulator/B220Processor.js +++ b/emulator/B220Processor.js @@ -256,7 +256,7 @@ function B220Processor(config, devices) { * Global Constants * ***********************************************************************/ -B220Processor.version = "0.03c"; +B220Processor.version = "0.04"; B220Processor.tick = 1000/200000; // milliseconds per clock cycle (200KHz) B220Processor.cyclesPerMilli = 1/B220Processor.tick; @@ -1166,6 +1166,41 @@ B220Processor.prototype.bcdAdd = function bcdAdd(a, d, digits, complement, initi return am; }; +/**************************************/ +B220Processor.prototype.clearAdd = function clearAdd(absolute) { + /* After accessing memory, algebraically add the addend (IB) to zero. If + "absolute" is true, then the sign-bit of the word from memory is forced to the + subtract toggle. All values are BCD with the sign in the 11th digit position. + Sets the Digit Check alarm as necessary */ + var am = 0; // augend mantissa + var dm; // addend mantissa + var dSign; // addend sign + + this.opTime = 0.095; + this.E.set(this.CADDR); + this.readMemory(); + if (this.MET.value) { // invalid address + this.A.set(am); // sign is zero + return; // exit to Operation Complete + } + + dm = this.IB.value % 0x10000000000; + dSign = ((this.IB.value - dm)/0x10000000000); + if (absolute) { // force sign bit to SUT + dSign = (dSign & 0x0E) | this.SUT.value; + } else if (this.SUT.value) { // complement the sign bit + dSign = dSign ^ 0x01; + } + + am = this.bcdAdd(am, dm, 11); + + // Set toggles for display purposes and return the result + this.DST.set(dSign%2); + this.SGT.set(dSign%2); + this.D.set(dSign*0x10000000000 + dm); + this.A.set(dSign*0x10000000000 + am); +}; + /**************************************/ B220Processor.prototype.integerAdd = function integerAdd(absolute, toD) { /* After accessing memory, algebraically add the addend (IB) to the augend (A). @@ -3142,15 +3177,13 @@ B220Processor.prototype.execute = function execute() { case 0x10: //--------------------- CAD/CAA Clear add/add absolute this.SUT.set(0); - this.A.value = this.IB.value - this.IB.value%0x10000000000; // 0 with sign of IB - this.integerAdd(this.CCONTROL % 0x10 == 1, false); + this.clearAdd(this.CCONTROL % 0x10 == 1); this.operationComplete(); break; case 0x11: //--------------------- CSU/CSA Clear subtract/subtract absolute this.SUT.set(1); - this.A.value = this.IB.value - this.IB.value%0x10000000000; // 0 with sign of IB - this.integerAdd(this.CCONTROL % 0x10 == 1, false); + this.clearAdd(this.CCONTROL % 0x10 == 1); this.operationComplete(); break; @@ -3451,7 +3484,7 @@ B220Processor.prototype.execute = function execute() { if (this.CCONTROL%0x10 == 1) { // Load B complement this.B.set(this.bcdAdd(this.IB.value, 0, 4, 1, 1)); } else { // Load B - this.B.set(this.IB.value); + this.B.set(this.IB.value%0x10000); } } this.operationComplete(); @@ -3538,9 +3571,14 @@ B220Processor.prototype.execute = function execute() { case 0x49: //--------------------- SL* Shift (rotate) left A/A and R/A with sign switch (this.CCONTROL%0x10) { case 1: // SLT: Shift Left A and R - x = B220Processor.bcdBinary(this.CADDR % 0x20); - this.opTime = 0.210 - x*0.005; - this.DC.set(B220Processor.binaryBCD(x)); + x = this.CADDR % 0x20; + if (x < 0x10) { + this.opTime = 0.210 - x*0.005; + } else { + this.opTime = 0.160 - (x-0x10)*0.005; + } + + this.DC.set(x); w = this.R.value % 0x10000000000; // R sign is not affected this.A.value %= 0x10000000000; // discard the A sign while (this.DC.value < 0x20) { @@ -3553,10 +3591,12 @@ B220Processor.prototype.execute = function execute() { this.R.set(this.R.value - this.R.value%0x10000000000 + w); // restore the R sign break; case 2: // SLS: Shift Left A with Sign - x = B220Processor.bcdBinary(this.CADDR % 0x10); + x = this.CADDR % 0x10; this.opTime = 0.160 - x*0.005; - this.DC.set(B220Processor.binaryBCD(10+x)); + this.DC.set(0x10+x); w = this.A.value % 0x100000000000; // A sign is included + d = w % 0x10; // do one more rotate right + w = (w-d)/0x10 + d*0x10000000000; // than the count calls for while (this.DC.value < 0x20) { d = w % 0x10; w = (w-d)/0x10 + d*0x10000000000; @@ -3565,9 +3605,9 @@ B220Processor.prototype.execute = function execute() { this.A.set(w); break; default: // SLA: Shift Left A - x = B220Processor.bcdBinary(this.CADDR % 0x10); + x = this.CADDR % 0x10; this.opTime = 0.160 - x*0.005; - this.DC.set(B220Processor.binaryBCD(10+x)); + this.DC.set(0x10+x); w = this.A.value % 0x10000000000; // A sign is not affected while (this.DC.value < 0x20) { d = w % 0x10; @@ -3721,7 +3761,7 @@ B220Processor.prototype.execute = function execute() { } else if (this.magTape.controlBusy) { this.opTime = 0.01; } else { - opTime = 0.14; + this.opTime = 0.14; if (this.CCONTROL%0x10 == 1) { // MIE if (this.magTape.testUnitAtEOT(this.D.value)) { this.P.set(this.CADDR); @@ -4251,41 +4291,6 @@ B220Processor.prototype.powerDown = function powerDown() { B220Processor.prototype.loadDefaultProgram = function loadDefaultProgram() { /* Loads a set of default demo programs to the memory drum */ - // TEMP // Tape tests - this.MM[ 0] = 0x1008500000; // MRW 1 - this.MM[ 1] = 0x1002580000; // MPE 1 - this.MM[ 2] = 0x1000540000; // MIW 0,1,10,100 - this.MM[ 3] = 0x1750540100; // MIW 100,1,7,50 - this.MM[ 4] = 0x1500550079; // MIR 79,1,5,00 - this.MM[ 5] = 0x1101542000; // MIW 2000,1,1,1 // write an EOT block - - this.MM[ 6] = 0x1008500000; // MRW 1 - this.MM[ 7] = 0x1000560000; // MOW 0,1,10,100 - this.MM[ 8] = 0x1750560100; // MOW 100,1,7,50 - this.MM[ 9] = 0x1500570079; // MOR 79,1,5,00 - //this.MM[ 10] = 0x1101562000; // MOW 2000,1,1,1 - this.MM[ 10] = 0x1110562000; // MOW 2000,1,1,10 // TEMP: block-length=10, should fire EOT control word - - this.MM[ 11] = 0x1008500000; // MRW 1 - this.MM[ 12] = 0x1000523000; // MRD 3000,1,10,0 - this.MM[ 13] = 0x1700524000; // MRD 4000,1,7,0 - this.MM[ 14] = 0x1500534350; // MRR 4350,1,5,0 - this.MM[ 15] = 0x1100534800; // MRR 4800,1,1,0 // should be an EOT block - - this.MM[ 16] = 0x1009500000; // MDA 1 - this.MM[ 17] = 0x7777009999; // HLT 9999,7777 - - this.MM[ 79] = 0x1900000000; // preface for 19 words, 80-98 - this.MM[ 99] = 0x4000000000; // preface for 40 words, 100-139 - this.MM[ 140] = 0x5800000000; // preface for 58 words, 141-198 - this.MM[ 199] = 0x9900000000; // preface for 99 words, 200-298 - this.MM[ 299] = 0x0000000000; // preface for 100 words, 300-399 - - this.MM[2000] = 0x9920012002; // end-of-tape control word - this.MM[2001] = 0x9999999999; // storage for end-of-tape block state - this.MM[2002] = 0x9999008421; // HLT: target for end-of-tape control branch - this.MM[2003] = 0x0000300011; // branch to read test sequence - // Simple counter speed test this.MM[ 80] = 0x0000120082; // ADD 82 this.MM[ 81] = 0x0000300080; // BUN 80 @@ -4469,4 +4474,39 @@ B220Processor.prototype.loadDefaultProgram = function loadDefaultProgram() { this.MM[ 377]= 0x20202021616; // CR CNST 20202021616 NEWLINES this.MM[1000]= 0x00000000000; // F DEFN * ARRAY F[2800] + + // TEMP // Tape tests + this.MM[ 400] = 0x1008500000; // MRW 1 + this.MM[ 401] = 0x1002580000; // MPE 1 + this.MM[ 402] = 0x1000540000; // MIW 0,1,10,100 + this.MM[ 403] = 0x1750540100; // MIW 100,1,7,50 + this.MM[ 404] = 0x1500550079; // MIR 79,1,5,00 + this.MM[ 405] = 0x1101542000; // MIW 2000,1,1,1 // write an EOT block + + this.MM[ 406] = 0x1008500000; // MRW 1 + this.MM[ 407] = 0x1000560000; // MOW 0,1,10,100 + this.MM[ 408] = 0x1750560100; // MOW 100,1,7,50 + this.MM[ 409] = 0x1500570079; // MOR 79,1,5,00 + //this.MM[ 410] = 0x1101562000; // MOW 2000,1,1,1 + this.MM[ 410] = 0x1110562000; // MOW 2000,1,1,10 // TEMP: block-length=10, should fire EOT control word + + this.MM[ 411] = 0x1008500000; // MRW 1 + this.MM[ 412] = 0x1000523000; // MRD 3000,1,10,0 + this.MM[ 413] = 0x1700524000; // MRD 4000,1,7,0 + this.MM[ 414] = 0x1500534350; // MRR 4350,1,5,0 + this.MM[ 415] = 0x1100534800; // MRR 4800,1,1,0 // should be an EOT block + + this.MM[ 416] = 0x1009500000; // MDA 1 + this.MM[ 417] = 0x7777009999; // HLT 9999,7777 + + this.MM[ 79] = 0x1900000000; // preface for 19 words, 80-98 + this.MM[ 99] = 0x4000000000; // preface for 40 words, 100-139 + this.MM[ 140] = 0x5800000000; // preface for 58 words, 141-198 + this.MM[ 199] = 0x9900000000; // preface for 99 words, 200-298 + this.MM[ 299] = 0x0000000000; // preface for 100 words, 300-399 + + this.MM[2000] = 0x9920012002; // end-of-tape control word + this.MM[2001] = 0x9999999999; // storage for end-of-tape block state + this.MM[2002] = 0x9999008421; // HLT: target for end-of-tape control branch + this.MM[2003] = 0x0000300411; // branch to read test sequence }; \ No newline at end of file diff --git a/index.html b/index.html index 91fe71c..85d939e 100644 --- a/index.html +++ b/index.html @@ -18,10 +18,6 @@ Burroughs 220 Emulator – Hosting Site Home -

- (Under Construction) -

-

This site hosts the current version of the retro-220 emulator, an implementation of the Burroughs 220 computer system that runs in a web browser.

@@ -69,7 +65,7 @@ Copyright (c) 2017, Paul Kimpel • Licensed under the MIT License

Revised - 2016-12-25 + 2017-11-19

diff --git a/webUI/B220CardatronControl.js b/webUI/B220CardatronControl.js index aa48b4b..176112c 100644 --- a/webUI/B220CardatronControl.js +++ b/webUI/B220CardatronControl.js @@ -164,9 +164,9 @@ B220CardatronControl.prototype.cardatronOnLoad = function cardatronOnLoad() { // Events - this.window.addEventListener("beforeunload", B220CardatronControl.prototype.beforeUnload); + this.window.addEventListener("beforeunload", B220CardatronControl.prototype.beforeUnload, false); this.$$("ClearBtn").addEventListener("click", - B220Util.bindMethod(this, B220CardatronControl.prototype.ClearBtn_onClick)); + B220Util.bindMethod(this, B220CardatronControl.prototype.ClearBtn_onClick), false); this.clear(); @@ -305,6 +305,6 @@ B220CardatronControl.prototype.shutDown = function shutDown() { } } - this.window.removeEventListener("beforeunload", B220CardatronControl.prototype.beforeUnload); + this.window.removeEventListener("beforeunload", B220CardatronControl.prototype.beforeUnload, false); this.window.close(); }; \ No newline at end of file diff --git a/webUI/B220CardatronInput.js b/webUI/B220CardatronInput.js index 96e8424..bdcc53e 100644 --- a/webUI/B220CardatronInput.js +++ b/webUI/B220CardatronInput.js @@ -615,21 +615,21 @@ B220CardatronInput.prototype.readerOnLoad = function readerOnLoad() { this.clearUnit(); // will actually set the state and lamps correctly this.window.addEventListener("beforeunload", - B220CardatronInput.prototype.beforeUnload); + B220CardatronInput.prototype.beforeUnload, false); this.$$("CIFileSelector").addEventListener("change", - B220Util.bindMethod(this, B220CardatronInput.prototype.fileSelector_onChange)); + B220Util.bindMethod(this, B220CardatronInput.prototype.fileSelector_onChange), false); this.$$("FormatColumn").addEventListener("change", - B220Util.bindMethod(this, B220CardatronInput.prototype.format_onChange)); + B220Util.bindMethod(this, B220CardatronInput.prototype.format_onChange), false); this.$$("FormatSelect").addEventListener("change", - B220Util.bindMethod(this, B220CardatronInput.prototype.format_onChange)); + B220Util.bindMethod(this, B220CardatronInput.prototype.format_onChange), false); this.$$("CIStartBtn").addEventListener("click", - B220Util.bindMethod(this, B220CardatronInput.prototype.CIStartBtn_onClick)); + B220Util.bindMethod(this, B220CardatronInput.prototype.CIStartBtn_onClick), false); this.$$("CIStopBtn").addEventListener("click", - B220Util.bindMethod(this, B220CardatronInput.prototype.CIStopBtn_onClick)); + B220Util.bindMethod(this, B220CardatronInput.prototype.CIStopBtn_onClick), false); this.$$("ClearBtn").addEventListener("click", - B220Util.bindMethod(this, B220CardatronInput.prototype.ClearBtn_onClick)); + B220Util.bindMethod(this, B220CardatronInput.prototype.ClearBtn_onClick), false); this.hopperBar.addEventListener("click", - B220Util.bindMethod(this, B220CardatronInput.prototype.CIHopperBar_onClick)); + B220Util.bindMethod(this, B220CardatronInput.prototype.CIHopperBar_onClick), false); this.window.resizeBy(de.scrollWidth - this.window.innerWidth + 4, // kludge for right-padding/margin de.scrollHeight - this.window.innerHeight); @@ -896,6 +896,6 @@ B220CardatronInput.prototype.shutDown = function shutDown() { if (this.timer) { clearCallback(this.timer); } - this.window.removeEventListener("beforeunload", B220CardatronInput.prototype.beforeUnload); + this.window.removeEventListener("beforeunload", B220CardatronInput.prototype.beforeUnload, false); this.window.close(); }; diff --git a/webUI/B220CardatronOutput.js b/webUI/B220CardatronOutput.js index 3bd8dd3..a4fb77f 100644 --- a/webUI/B220CardatronOutput.js +++ b/webUI/B220CardatronOutput.js @@ -758,7 +758,7 @@ B220CardatronOutput.prototype.deviceOnLoad = function deviceOnLoad() { this.window.addEventListener("beforeunload", B220CardatronOutput.prototype.beforeUnload, false); this.supply.addEventListener("dblclick", - B220Util.bindMethod(this, B220CardatronOutput.prototype.copySupply)); + B220Util.bindMethod(this, B220CardatronOutput.prototype.copySupply), false); this.$$("COStopBtn").addEventListener("click", B220Util.bindMethod(this, B220CardatronOutput.prototype.COStopBtn_onClick), false); this.$$("COStartBtn").addEventListener("click", @@ -772,7 +772,7 @@ B220CardatronOutput.prototype.deviceOnLoad = function deviceOnLoad() { this.$$("COSetZSBtn").addEventListener("click", B220Util.bindMethod(this, B220CardatronOutput.prototype.COSetZSBtn_onClick), false); this.$$("ClearBtn").addEventListener("click", - B220Util.bindMethod(this, B220CardatronOutput.prototype.ClearBtn_onClick)); + B220Util.bindMethod(this, B220CardatronOutput.prototype.ClearBtn_onClick), false); if (!this.isPrinter) { this.$$("COEndOfSupplyBtn").innerHTML = "OUT OF
CARDS"; @@ -1031,7 +1031,7 @@ B220CardatronOutput.prototype.shutDown = function shutDown() { if (this.timer) { clearCallback(this.timer); } - this.window.removeEventListener("beforeunload", B220CardatronOutput.prototype.beforeUnload); + this.window.removeEventListener("beforeunload", B220CardatronOutput.prototype.beforeUnload, false); this.window.close(); if (this.zsWindow && !this.zsWindow.closed) { this.zsWindow.close(); diff --git a/webUI/B220ConsolePrinter.css b/webUI/B220ConsolePrinter.css index a1f4190..ce3c45d 100644 --- a/webUI/B220ConsolePrinter.css +++ b/webUI/B220ConsolePrinter.css @@ -185,6 +185,20 @@ top: 66px; width: 220px} +#SpeedSwitch { + position: absolute; + left: 60px; + top: 160px; + width: 24px} +#WhippetSpeedCaption { + left: 48px; + top: 146px; + width: 48px} +#TTYSpeedCaption { + left: 48px; + top: 190px; + width: 48px} + #UnitSwitch0 { position: absolute; left: 134px; diff --git a/webUI/B220ConsolePrinter.html b/webUI/B220ConsolePrinter.html index d63703d..09eedbe 100644 --- a/webUI/B220ConsolePrinter.html +++ b/webUI/B220ConsolePrinter.html @@ -63,6 +63,9 @@
TAB STOPS
+
SPEED
WHIPPET
+
TTY
+
SPO
1
2
diff --git a/webUI/B220ConsolePrinter.js b/webUI/B220ConsolePrinter.js index 22f4d23..b07a15a 100644 --- a/webUI/B220ConsolePrinter.js +++ b/webUI/B220ConsolePrinter.js @@ -32,12 +32,13 @@ function B220ConsolePrinter(mnemonic, unitIndex, config) { this.unitSwitch = new Array(11); // unit selection switch objects this.tabStop = []; // 0-relative tab stop positions this.zeroSuppress = 0; // zero-suppression switch setting + this.charPeriod = 0; // printer speed, ms/char - this.boundButton_Click = B220Util.bindMethod(this, B220ConsolePrinter.prototype.button_Click); - this.boundText_OnChange = B220Util.bindMethod(this, B220ConsolePrinter.prototype.text_OnChange); - this.boundFlipSwitch = B220Util.bindMethod(this, B220ConsolePrinter.prototype.flipSwitch); - this.boundReceiveSign = B220Util.bindMethod(this, B220ConsolePrinter.prototype.receiveSign); - this.boundReceiveChar = B220Util.bindMethod(this, B220ConsolePrinter.prototype.receiveChar); + this.boundButton_Click = B220ConsolePrinter.prototype.button_Click.bind(this); + this.boundText_OnChange = B220ConsolePrinter.prototype.text_OnChange.bind(this); + this.boundFlipSwitch = B220ConsolePrinter.prototype.flipSwitch.bind(this); + this.boundReceiveSign = B220ConsolePrinter.prototype.receiveSign.bind(this); + this.boundReceiveChar = B220ConsolePrinter.prototype.receiveChar.bind(this); this.clear(); @@ -58,8 +59,8 @@ function B220ConsolePrinter(mnemonic, unitIndex, config) { B220ConsolePrinter.offSwitchImage = "./resources/ToggleDown.png"; B220ConsolePrinter.onSwitchImage = "./resources/ToggleUp.png"; -B220ConsolePrinter.charsPerSecond = 10; // Printer speed -B220ConsolePrinter.charPeriod = 1000/B220ConsolePrinter.charsPerSecond; +B220ConsolePrinter.ttySpeed = 10; // TTY printer speed, char/sec +B220ConsolePrinter.whippetSpeed = 200; // Whippet printer speed, char/sec // Inter-character period, ms B220ConsolePrinter.pageSize = 66; // lines/page for form-feed B220ConsolePrinter.maxScrollLines = 15000; @@ -264,7 +265,7 @@ B220ConsolePrinter.prototype.flipSwitch = function flipSwitch(ev) { break; case "MapMemorySwitch": this.mapMemorySwitch.flip(); - prefs.mapMemory, this.mapMemory = this.mapMemorySwitch.state; + prefs.mapMemory = this.mapMemory = this.mapMemorySwitch.state; break; case "RemoteKnob": this.remoteKnob.step(); @@ -276,6 +277,15 @@ B220ConsolePrinter.prototype.flipSwitch = function flipSwitch(ev) { prefs.format = this.formatKnob.position; this.format = this.formatKnob.position; break; + case "SpeedSwitch": + this.speedSwitch.flip(); + prefs.printerSpeed = this.speedSwitch.state; + if (this.speedSwitch.state) { + this.charPeriod = 1000/B220ConsolePrinter.whippetSpeed; + } else { + this.charPeriod = 1000/B220ConsolePrinter.ttySpeed; + } + break; default: x = id.indexOf("UnitSwitch"); if (x == 0) { @@ -394,6 +404,14 @@ B220ConsolePrinter.prototype.printerOnLoad = function printerOnLoad() { B220ConsolePrinter.offSwitchImage, B220ConsolePrinter.onSwitchImage); this.mapMemorySwitch.set(prefs.mapMemory); this.mapMemory = this.mapMemorySwitch.state; + this.speedSwitch = new ToggleSwitch(body, null, null, "SpeedSwitch", + B220ConsolePrinter.offSwitchImage, B220ConsolePrinter.onSwitchImage); + this.speedSwitch.set(prefs.printerSpeed); + if (this.speedSwitch.state) { + this.charPeriod = 1000/B220ConsolePrinter.whippetSpeed; + } else { + this.charPeriod = 1000/B220ConsolePrinter.ttySpeed; + } mask = 0x001; this.unitMask = prefs.unitMask; @@ -421,11 +439,11 @@ B220ConsolePrinter.prototype.printerOnLoad = function printerOnLoad() { // Events this.window.addEventListener("beforeunload", - B220ConsolePrinter.prototype.beforeUnload); + B220ConsolePrinter.prototype.beforeUnload, false); this.window.addEventListener("resize", - B220Util.bindMethod(this, B220ConsolePrinter.prototype.resizeWindow)); + B220Util.bindMethod(this, B220ConsolePrinter.prototype.resizeWindow), false); this.paper.addEventListener("dblclick", - B220Util.bindMethod(this, B220ConsolePrinter.prototype.copyPaper)); + B220Util.bindMethod(this, B220ConsolePrinter.prototype.copyPaper), false); this.$$("OpenPanelBtn").addEventListener("click", this.boundButton_Click); this.$$("ClosePanelBtn").addEventListener("click", this.boundButton_Click); @@ -434,6 +452,7 @@ B220ConsolePrinter.prototype.printerOnLoad = function printerOnLoad() { this.zeroSuppressSwitch.addEventListener("click", this.boundFlipSwitch); this.mapMemorySwitch.addEventListener("click", this.boundFlipSwitch); + this.speedSwitch.addEventListener("click", this.boundFlipSwitch); this.remoteKnob.addEventListener("click", this.boundFlipSwitch); this.formatKnob.addEventListener("click", this.boundFlipSwitch); this.$$("Columns").addEventListener("change", this.boundText_OnChange); @@ -459,7 +478,7 @@ B220ConsolePrinter.prototype.receiveSign = function receiveSign(char, successor) /* Receives the sign character from the processor and handles it according to the value of the sign and the setting of the Map Memory and LZ Suppress switches */ - var delay = B220ConsolePrinter.charPeriod; // default character delay + var delay = this.charPeriod; // default character delay var stamp = performance.now(); // current time switch (true) { @@ -506,7 +525,7 @@ B220ConsolePrinter.prototype.receiveSign = function receiveSign(char, successor) B220ConsolePrinter.prototype.receiveChar = function receiveChar(char, successor) { /* Receives a non-sign character from the processor and outputs it. Special handling is provided for tabs, carriage returns, form feeds, and end-of-word characters */ - var delay = B220ConsolePrinter.charPeriod; // default character delay + var delay = this.charPeriod; // default character delay var nextReceiver = this.boundReceiveChar; // default routine to receive next char var stamp = performance.now(); // current time @@ -572,7 +591,7 @@ B220ConsolePrinter.prototype.shutDown = function shutDown() { } if (this.window) { - this.window.removeEventListener("beforeunload", B220ConsolePrinter.prototype.beforeUnload); + this.window.removeEventListener("beforeunload", B220ConsolePrinter.prototype.beforeUnload, false); this.window.close(); this.window = null; } diff --git a/webUI/B220MagTapeControl.js b/webUI/B220MagTapeControl.js index 324e8d6..b167c13 100644 --- a/webUI/B220MagTapeControl.js +++ b/webUI/B220MagTapeControl.js @@ -126,30 +126,15 @@ B220MagTapeControl.prototype.clear = function clear() { }; /**************************************/ -B220MagTapeControl.prototype.findDesignate = function findDesignate(u) { - /* Searches this.tapeUnit[] to find the internal index of the unit that is - designated as "u". If found, returns the internal index; if not found, - returns -1. If more than one ready unit with the same designate is found, - returns -2 */ - var index = -1; - var unit; - var x; +B220MagTapeControl.prototype.clearMisc = function clearMisc() { + /* Resets this.regMisc and the individual lamps for that register */ + var bitNr; + var m = this.regMisc; - for (x=this.tapeUnit.length-1; x>=0; --x) { - unit = this.tapeUnit[x]; - if (unit && unit.ready) { - if (unit.unitDesignate == u) { - if (index == -1) { - index = x; - } else { - index = -2; - break; // out of for loop - } - } - } - } // for x - - return index; + m.update(0); + for (bitNr=m.bits-1; bitNr>= 0; --bitNr) { + m.lamps[bitNr].set(0); + } }; /**************************************/ @@ -175,126 +160,73 @@ B220MagTapeControl.prototype.storeWord = function storeWord(initialStore, word) }; /**************************************/ -B220MagTapeControl.prototype.queuePendingOperation = function queuePendingOperation(callee, args) { - /* Queues a pending tape operation */ +B220MagTapeControl.prototype.reportStatus = function reportStatus(state) { + /* Sets bits in the MISC register to indicate various drive and control unit + status and error conditions */ - //console.log(this.mnemonic + " queuePendingOperation: " + args[0].toString(16)); - if (this.pendingCallee !== null) { - throw new Error("More than one pending tape control operation"); - } - - this.pendingCallee = callee; - this.pendingArgs = args; -}; - -/**************************************/ -B220MagTapeControl.prototype.dequeuePendingOperation = function dequeuePendingOperation() { - /* Dequeues and reinitiates a pending tape operation */ - var args = this.pendingArgs; // pending Arguments object - var callee = this.pendingCallee; // pending method to call - - this.pendingCallee = this.pendingArgs = null; - callee.apply(this, args); -}; - -/**************************************/ -B220MagTapeControl.prototype.loadCommand = function loadCommand(dReg, callee, args) { - /* If the control unit or the tape unit addressed by the unit field in dReg - are currently busy, queues the args parameter (an Arguments object) in - this.pendingCallee and -Args, and returns false. If the control is idle but - the tape unit is not ready or not present, or two units have the same designate, - calls this.releaseProcessor and returns false. If the control and tape unit - are ready for their next operation, loads the contents of the processor's - D register passed to the operation routines into the T, C, and MISC registers. - Sets this.unitNr, and this.unitIndex from the digits in T. Sets this. - currentUnit to the current tape unit object. Then returns true to inidicate - the I/O can proceed */ - var c; // scratch - var proceed = false; // return value: true => proceed with I/O - var t = dReg%0x10000000000; // temp to partition fields of Processor's D register - var ux; // internal unit index - - //console.log(this.mnemonic + " loadCommand: " + dReg.toString(16)); - if (this.controlBusy) { - this.queuePendingOperation(callee, args); - } else { - this.T = t; - this.regT.update(this.T); - this.unitNr = (t - t%0x1000000000)/0x1000000000; - t = (t - t%0x10000)/0x10000; - c = t%0x10; // low-order digit of op code - t = (t - t%0x100)/0x100; // control digits from instruction - this.C = this.unitNr*0x100000 + t*0x10 + c; - this.regC.update(this.C); + switch (state) { + case this.driveState.driveNoError: this.clearMisc(); - this.unitIndex = ux = this.findDesignate(this.unitNr); - if (ux < 0) { - this.reportStatus(this.driveState.driveNotReady); // drive not ready, not present - this.releaseProcessor(false, 0); - } else { - this.currentUnit = this.tapeUnit[ux]; - if (this.currentUnit.busy || this.currentUnit.rewindLock) { - this.queuePendingOperation(callee, args); - } else { - proceed = true; - this.driveState.startTime = performance.now(); - this.driveState.completionDelay = 0; - this.driveState.state = this.driveState.driveNoError; + break; + case this.driveState.driveNotReady: + this.TX2Lamp.set(1); + this.TX10Lamp.set(1); + break; + case this.driveState.drivePrefaceCheck: + this.p.setMagneticTapeCheck(1); + this.TPCLamp.set(1); + break; + case this.driveState.drivePrefaceMismatch: + this.p.setMagneticTapeCheck(1); + this.TCFLamp.set(1); + this.C = (this.C & 0x00FFFF) | 0xFF0000; + this.regC.update(this.C); + break; + case this.driveState.driveReadCheck: + this.p.setMagneticTapeCheck(1); + this.TYC1Lamp.set(1); + this.TYC2Lamp.set(1); + this.C = (this.C & 0xFFF00F) | 0x000F90; + this.regC.update(this.C); + break; + case this.driveState.driveInvalidBlockLength: + this.p.setMagneticTapeCheck(1); + this.TX2Lamp.set(1); + this.TX4Lamp.set(1); + this.C = (this.C & 0x000F0F) | 0xB010F0; + this.regC.update(this.C); + break; + case this.driveState.driveNotEditedTape: + this.p.setMagneticTapeCheck(1); + break; + } // switch code +}; + +/**************************************/ +B220MagTapeControl.prototype.findDesignate = function findDesignate(u) { + /* Searches this.tapeUnit[] to find the internal index of the unit that is + designated as "u". If found, returns the internal index; if not found, + returns -1. If more than one ready unit with the same designate is found, + returns -2 */ + var index = -1; + var unit; + var x; + + for (x=this.tapeUnit.length-1; x>=0; --x) { + unit = this.tapeUnit[x]; + if (unit && unit.ready) { + if (unit.unitDesignate == u) { + if (index == -1) { + index = x; + } else { + index = -2; + break; // out of for loop + } } } - } + } // for x - return proceed; -}; - -/**************************************/ -B220MagTapeControl.prototype.releaseControl = function releaseControl(param) { - /* Releases the busy status of the control. If an error is present, sets the - bits in the MISC register and the Processor's Magnetic Tape Check alarm, as - appropriate. If another operation is pending, initiates that operation. - Returns but does not use its parameter so that it can be used with - Promise.then() */ - - this.TFLamp.set(0); - this.TBLamp.set(0); - this.controlBusy = false; - if (this.driveState.state != this.driveState.driveNoError) { - this.currentUnit.releaseUnit(this.driveState); - this.reportStatus(this.driveState.state); - } - - if (this.pendingCallee !== null) { - this.dequeuePendingOperation(); - } - - return param; -}; - -/**************************************/ -B220MagTapeControl.prototype.cancelIO = function cancelIO(param) { - /* Terminates the current I/O operation by releasing the Processor, tape - unit, and tape control unit. Returns but does not use its parameter so it - can be used with Promise.then() */ - - this.releaseProcessor(false, 0); - this.currentUnit.releaseUnit(); - this.releaseControl(); - return param; -}; - -/**************************************/ -B220MagTapeControl.prototype.tapeUnitFinished = function tapeUnitFinished(param) { - /* Call-back function passed to tape unit methods to signal when the unit has - completed its asynchronous operation. Returns but does not use "param", so - that it can be used with Promise.then() */ - - if (!this.controlBusy) { // if the control unit is currently idle... - if (this.pendingCallee !== null) { - this.dequeuePendingOperation(); - } - } - - return param; + return index; }; /**************************************/ @@ -436,58 +368,126 @@ B220MagTapeControl.prototype.compareKeywordField = function compareKeywordField( }; /**************************************/ -B220MagTapeControl.prototype.clearMisc = function clearMisc() { - /* Resets this.regMisc and the individual lamps for that register */ - var bitNr; - var m = this.regMisc; +B220MagTapeControl.prototype.queuePendingOperation = function queuePendingOperation(callee, args) { + /* Queues a pending tape operation */ - m.update(0); - for (bitNr=m.bits-1; bitNr>= 0; --bitNr) { - m.lamps[bitNr].set(0); + //console.log(this.mnemonic + " queuePendingOperation: " + args[0].toString(16)); + if (this.pendingCallee !== null) { + throw new Error("More than one pending tape control operation"); } + + this.pendingCallee = callee; + this.pendingArgs = args; }; /**************************************/ -B220MagTapeControl.prototype.reportStatus = function reportStatus(state) { - /* Sets bits in the MISC register to indicate various drive and control unit - status and error conditions */ +B220MagTapeControl.prototype.dequeuePendingOperation = function dequeuePendingOperation() { + /* Dequeues and reinitiates a pending tape operation */ + var args = this.pendingArgs; // pending Arguments object + var callee = this.pendingCallee; // pending method to call - switch (state) { - case this.driveState.driveNoError: + this.pendingCallee = this.pendingArgs = null; + callee.apply(this, args); +}; + +/**************************************/ +B220MagTapeControl.prototype.loadCommand = function loadCommand(dReg, callee, args) { + /* If the control unit or the tape unit addressed by the unit field in dReg + are currently busy, queues the args parameter (an Arguments object) in + this.pendingCallee and -Args, and returns false. If the control is idle but + the tape unit is not ready or not present, or two units have the same designate, + calls this.releaseProcessor and returns false. If the control and tape unit + are ready for their next operation, loads the contents of the processor's + D register passed to the operation routines into the T, C, and MISC registers. + Sets this.unitNr, and this.unitIndex from the digits in T. Sets this. + currentUnit to the current tape unit object. Then returns true to inidicate + the I/O can proceed */ + var c; // scratch + var proceed = false; // return value: true => proceed with I/O + var t = dReg%0x10000000000; // temp to partition fields of Processor's D register + var ux; // internal unit index + + //console.log(this.mnemonic + " loadCommand: " + dReg.toString(16)); + if (this.controlBusy) { + this.queuePendingOperation(callee, args); + } else { + this.T = t; + this.regT.update(this.T); + this.unitNr = (t - t%0x1000000000)/0x1000000000; + t = (t - t%0x10000)/0x10000; + c = t%0x10; // low-order digit of op code + t = (t - t%0x100)/0x100; // control digits from instruction + this.C = this.unitNr*0x100000 + t*0x10 + c; + this.regC.update(this.C); this.clearMisc(); - break; - case this.driveState.driveNotReady: - this.TX2Lamp.set(1); - this.TX10Lamp.set(1); - break; - case this.driveState.drivePrefaceCheck: - this.p.setMagneticTapeCheck(1); - this.TPCLamp.set(1); - break; - case this.driveState.drivePrefaceMismatch: - this.p.setMagneticTapeCheck(1); - this.TCFLamp.set(1); - this.C = (this.C & 0x00FFFF) | 0xFF0000; - this.regC.update(this.C); - break; - case this.driveState.driveReadCheck: - this.p.setMagneticTapeCheck(1); - this.TYC1Lamp.set(1); - this.TYC2Lamp.set(1); - this.C = (this.C & 0xFFF00F) | 0x000F90; - this.regC.update(this.C); - break; - case this.driveState.driveInvalidBlockLength: - this.p.setMagneticTapeCheck(1); - this.TX2Lamp.set(1); - this.TX4Lamp.set(1); - this.C = (this.C & 0x000F0F) | 0xB010F0; - this.regC.update(this.C); - break; - case this.driveState.driveNotEditedTape: - this.p.setMagneticTapeCheck(1); - break; - } // switch code + this.unitIndex = ux = this.findDesignate(this.unitNr); + if (ux < 0) { + this.reportStatus(this.driveState.driveNotReady); // drive not ready, not present + this.releaseProcessor(false, 0); + } else { + this.currentUnit = this.tapeUnit[ux]; + if (this.currentUnit.busy || this.currentUnit.rewindLock) { + this.queuePendingOperation(callee, args); + } else { + proceed = true; + this.driveState.startTime = performance.now(); + this.driveState.completionDelay = 0; + this.driveState.state = this.driveState.driveNoError; + } + } + } + + return proceed; +}; + +/**************************************/ +B220MagTapeControl.prototype.releaseControl = function releaseControl(param) { + /* Releases the busy status of the control. If an error is present, sets the + bits in the MISC register and the Processor's Magnetic Tape Check alarm, as + appropriate. If another operation is pending, initiates that operation. + Returns but does not use its parameter so that it can be used with + Promise.then() */ + + this.TFLamp.set(0); + this.TBLamp.set(0); + this.controlBusy = false; + if (this.driveState.state != this.driveState.driveNoError) { + this.currentUnit.releaseUnit(this.driveState); + this.reportStatus(this.driveState.state); + } + + if (this.pendingCallee !== null) { + this.dequeuePendingOperation(); + } + + return param; +}; + +/**************************************/ +B220MagTapeControl.prototype.cancelIO = function cancelIO(param) { + /* Terminates the current I/O operation by releasing the Processor, tape + unit, and tape control unit. Returns but does not use its parameter so it + can be used with Promise.then() */ + + this.releaseProcessor(false, 0); + this.currentUnit.releaseUnit(); + this.releaseControl(); + return param; +}; + +/**************************************/ +B220MagTapeControl.prototype.tapeUnitFinished = function tapeUnitFinished(param) { + /* Call-back function passed to tape unit methods to signal when the unit has + completed its asynchronous operation. Returns but does not use "param", so + that it can be used with Promise.then() */ + + if (!this.controlBusy) { // if the control unit is currently idle... + if (this.pendingCallee !== null) { + this.dequeuePendingOperation(); + } + } + + return param; }; /**************************************/ @@ -568,7 +568,7 @@ B220MagTapeControl.prototype.magTapeOnLoad = function magTapeOnLoad() { // Events - this.window.addEventListener("beforeunload", B220MagTapeControl.prototype.beforeUnload); + this.window.addEventListener("beforeunload", B220MagTapeControl.prototype.beforeUnload, false); this.$$("ClearBtn").addEventListener("click", this.boundSwitch_Click, false); this.regMisc.rightClearBar.addEventListener("click", this.boundSwitch_Click, false); this.regC.rightClearBar.addEventListener("click", this.boundSwitch_Click, false); diff --git a/webUI/B220MagTapeDrive.js b/webUI/B220MagTapeDrive.js index 05fb1cc..3760860 100644 --- a/webUI/B220MagTapeDrive.js +++ b/webUI/B220MagTapeDrive.js @@ -209,13 +209,6 @@ B220MagTapeDrive.prototype.clear = function clear() { this.tapeState = this.tapeUnloaded; // tape drive state }; -/**************************************/ -B220MagTapeDrive.prototype.nil = function nil() { - /* An empty function that just returns */ - - return; -}; - /**************************************/ B220MagTapeDrive.prototype.releaseUnit = function releaseUnit(param) { /* Releases the busy status of the unit. Returns but does not use its @@ -255,105 +248,6 @@ B220MagTapeDrive.prototype.setUnitDesignate = function setUnitDesignate(index) { } }; -/**************************************/ -B220MagTapeDrive.prototype.spinReel = function spinReel(inches) { - /* Rotates the reel image icon an appropriate amount based on the "inches" - of tape to be moved. The rotation is limited to this.maxSpinAngle degrees - in either direction so that movement remains apparent to the viewer */ - var circumference = this.reelCircumference*(1 - this.tapeInches/this.maxTapeInches/2); - var degrees = inches/circumference*360; - - if (degrees > this.maxSpinAngle) { - degrees = this.maxSpinAngle; - } else if (degrees < -this.maxSpinAngle) { - degrees = -this.maxSpinAngle; - } - - this.reelAngle = (this.reelAngle + degrees)%360; - this.reelIcon.style.transform = "rotate(" + this.reelAngle.toFixed(0) + "deg)"; - - this.tapeInches += inches; - if (this.tapeInches < this.maxTapeInches) { - this.reelBar.value = this.maxTapeInches - this.tapeInches; - } else { - this.reelBar.value = 0; - } -}; - -/**************************************/ -B220MagTapeDrive.prototype.moveTape = function moveTape(inches, delay, successor, param) { - /* Delays the I/O during tape motion, during which it animates the reel image - icon. At the completion of the "delay" time in milliseconds, "successor" is - called with "param" as a parameter */ - var delayLeft = Math.abs(delay); // milliseconds left to delay - var direction = (inches < 0 ? -1 : 1); - var inchesLeft = inches; // inches left to move tape - var initiallyReady = this.ready; // remember initial ready state to detect change - var lastStamp = performance.now(); // last timestamp for spinDelay - - function spinFinish() { - this.timer = 0; - if (inchesLeft != 0) { - this.spinReel(inchesLeft); - } - successor.call(this, param); - } - - function spinDelay() { - var motion; - var stamp = performance.now(); - var interval = stamp - lastStamp; - - if (interval <= 0) { - interval = this.spinUpdateInterval/2; - if (interval > delayLeft) { - interval = delayLeft; - } - } - - if (initiallyReady && !this.ready) { // drive went not ready - inchesLeft = 0; - this.timer = setCallback(this.mnemonic, this, this.spinUpdateInterval, spinFinish); - } else { - delayLeft -= interval; - if (delayLeft > this.spinUpdateInterval) { - lastStamp = stamp; - this.timer = setCallback(this.mnemonic, this, this.spinUpdateInterval, spinDelay); - } else { - this.timer = setCallback(this.mnemonic, this, delayLeft, spinFinish); - } - - motion = inchesLeft*interval/delayLeft; - if (inchesLeft*direction <= 0) { // inchesLeft crossed zero - motion = inchesLeft = 0; - } else if (motion*direction <= inchesLeft*direction) { - inchesLeft -= motion; - } else { - motion = inchesLeft; - inchesLeft = 0; - } - - this.spinReel(motion); - } - } - - spinDelay.call(this); -}; - -/**************************************/ -B220MagTapeDrive.prototype.moveTapeTo = function moveTapeTo(index, result) { - /* Advances the tape to the specified image index and returns a Promise - that will resolve when tape motion completes */ - - return new Promise((resolve, reject) => { - var len = index - this.imgIndex; // number of words passed - var delay = len*this.millisPerWord; // amount of tape spin time - - this.imgIndex = index; - this.moveTape(len*this.inchesPerWord, delay, resolve, result); - }); -}; - /**************************************/ B220MagTapeDrive.prototype.setAtBOT = function setAtBOT(atBOT) { /* Controls the at-Beginning-of-Tape state of the tape drive */ @@ -478,6 +372,165 @@ B220MagTapeDrive.prototype.setTapeUnloaded = function setTapeUnloaded() { } }; +/**************************************/ +B220MagTapeDrive.prototype.tapeRewind = function tapeRewind(laneNr, lockout) { + /* Rewinds the tape. Makes the drive not-ready and delays for an appropriate + amount of time depending on how far up-tape we are. Readies the unit again + when the rewind is complete unless lockout is truthy. Returns a Promise that + resolves when the rewind completes */ + + return new Promise((resolve, reject) => { + var lastStamp; + + function rewindFinish() { + this.timer = 0; + this.tapeState = this.tapeLocal; + B220Util.removeClass(this.$$("MTRewindingLight"), "annunciatorLit"); + this.rewindLock = (lockout ? true : false); + this.rwlLamp.set(this.rewindLock ? 1 : 0); + this.setTapeReady(!this.rewindLock); + resolve(this.setLane(laneNr, null)); + } + + function rewindDelay() { + var inches; + var stamp = performance.now(); + var interval = stamp - lastStamp; + + if (interval <= 0) { + interval = this.spinUpdateInterval/2; + } + if (this.tapeInches <= 0) { + this.setAtBOT(true); + this.timer = setCallback(this.mnemonic, this, 1000, rewindFinish); + } else { + inches = interval*this.rewindSpeed; + lastStamp = stamp; + this.timer = setCallback(this.mnemonic, this, this.spinUpdateInterval, rewindDelay); + this.spinReel(-inches); + } + } + + function rewindStart() { + this.designatedLamp.set(0); + lastStamp = performance.now(); + this.timer = setCallback(this.mnemonic, this, this.spinUpdateInterval, rewindDelay); + } + + if (this.timer) { + clearCallback(this.timer); + this.timer = 0; + } + + if (this.tapeState != this.tapeUnloaded && this.tapeState != this.tapeRewinding) { + this.busy = true; + this.tapeState = this.tapeRewinding; + this.setAtEOT(false); + B220Util.addClass(this.$$("MTRewindingLight"), "annunciatorLit"); + this.timer = setCallback(this.mnemonic, this, 1000, rewindStart); + } + }); +}; + +/**************************************/ +B220MagTapeDrive.prototype.spinReel = function spinReel(inches) { + /* Rotates the reel image icon an appropriate amount based on the "inches" + of tape to be moved. The rotation is limited to this.maxSpinAngle degrees + in either direction so that movement remains apparent to the viewer */ + var circumference = this.reelCircumference*(1 - this.tapeInches/this.maxTapeInches/2); + var degrees = inches/circumference*360; + + if (degrees > this.maxSpinAngle) { + degrees = this.maxSpinAngle; + } else if (degrees < -this.maxSpinAngle) { + degrees = -this.maxSpinAngle; + } + + this.reelAngle = (this.reelAngle + degrees)%360; + this.reelIcon.style.transform = "rotate(" + this.reelAngle.toFixed(0) + "deg)"; + + this.tapeInches += inches; + if (this.tapeInches < this.maxTapeInches) { + this.reelBar.value = this.maxTapeInches - this.tapeInches; + } else { + this.reelBar.value = 0; + } +}; + +/**************************************/ +B220MagTapeDrive.prototype.moveTape = function moveTape(inches, delay, successor, param) { + /* Delays the I/O during tape motion, during which it animates the reel image + icon. At the completion of the "delay" time in milliseconds, "successor" is + called with "param" as a parameter */ + var delayLeft = Math.abs(delay); // milliseconds left to delay + var direction = (inches < 0 ? -1 : 1); + var inchesLeft = inches; // inches left to move tape + var initiallyReady = this.ready; // remember initial ready state to detect change + var lastStamp = performance.now(); // last timestamp for spinDelay + + function spinFinish() { + this.timer = 0; + if (inchesLeft != 0) { + this.spinReel(inchesLeft); + } + successor.call(this, param); + } + + function spinDelay() { + var motion; + var stamp = performance.now(); + var interval = stamp - lastStamp; + + if (interval <= 0) { + interval = this.spinUpdateInterval/2; + if (interval > delayLeft) { + interval = delayLeft; + } + } + + if (initiallyReady && !this.ready) { // drive went not ready + inchesLeft = 0; + this.timer = setCallback(this.mnemonic, this, this.spinUpdateInterval, spinFinish); + } else { + delayLeft -= interval; + if (delayLeft > this.spinUpdateInterval) { + lastStamp = stamp; + this.timer = setCallback(this.mnemonic, this, this.spinUpdateInterval, spinDelay); + } else { + this.timer = setCallback(this.mnemonic, this, delayLeft, spinFinish); + } + + motion = inchesLeft*interval/delayLeft; + if (inchesLeft*direction <= 0) { // inchesLeft crossed zero + motion = inchesLeft = 0; + } else if (motion*direction <= inchesLeft*direction) { + inchesLeft -= motion; + } else { + motion = inchesLeft; + inchesLeft = 0; + } + + this.spinReel(motion); + } + } + + spinDelay.call(this); +}; + +/**************************************/ +B220MagTapeDrive.prototype.moveTapeTo = function moveTapeTo(index, result) { + /* Advances the tape to the specified image index and returns a Promise + that will resolve when tape motion completes */ + + return new Promise((resolve, reject) => { + var len = index - this.imgIndex; // number of words passed + var delay = len*this.millisPerWord; // amount of tape spin time + + this.imgIndex = index; + this.moveTape(len*this.inchesPerWord, delay, resolve, result); + }); +}; + /**************************************/ B220MagTapeDrive.prototype.loadTape = function loadTape() { /* Loads a tape into memory based on selections in the MTLoad window */ @@ -967,66 +1020,6 @@ B220MagTapeDrive.prototype.unloadTape = function unloadTape() { win.addEventListener("load", unloadSetup, false); }; -/**************************************/ -B220MagTapeDrive.prototype.tapeRewind = function tapeRewind(laneNr, lockout) { - /* Rewinds the tape. Makes the drive not-ready and delays for an appropriate - amount of time depending on how far up-tape we are. Readies the unit again - when the rewind is complete unless lockout is truthy. Returns a Promise that - resolves when the rewind completes */ - - return new Promise((resolve, reject) => { - var lastStamp; - - function rewindFinish() { - this.timer = 0; - this.tapeState = this.tapeLocal; - B220Util.removeClass(this.$$("MTRewindingLight"), "annunciatorLit"); - this.rewindLock = (lockout ? true : false); - this.rwlLamp.set(this.rewindLock ? 1 : 0); - this.setTapeReady(!this.rewindLock); - resolve(this.setLane(laneNr, null)); - } - - function rewindDelay() { - var inches; - var stamp = performance.now(); - var interval = stamp - lastStamp; - - if (interval <= 0) { - interval = this.spinUpdateInterval/2; - } - if (this.tapeInches <= 0) { - this.setAtBOT(true); - this.timer = setCallback(this.mnemonic, this, 1000, rewindFinish); - } else { - inches = interval*this.rewindSpeed; - lastStamp = stamp; - this.timer = setCallback(this.mnemonic, this, this.spinUpdateInterval, rewindDelay); - this.spinReel(-inches); - } - } - - function rewindStart() { - this.designatedLamp.set(0); - lastStamp = performance.now(); - this.timer = setCallback(this.mnemonic, this, this.spinUpdateInterval, rewindDelay); - } - - if (this.timer) { - clearCallback(this.timer); - this.timer = 0; - } - - if (this.tapeState != this.tapeUnloaded && this.tapeState != this.tapeRewinding) { - this.busy = true; - this.tapeState = this.tapeRewinding; - this.setAtEOT(false); - B220Util.addClass(this.$$("MTRewindingLight"), "annunciatorLit"); - this.timer = setCallback(this.mnemonic, this, 1000, rewindStart); - } - }); -}; - /**************************************/ B220MagTapeDrive.prototype.LoadBtn_onclick = function LoadBtn_onclick(ev) { /* Handle the click event for the LOAD button */ @@ -1217,6 +1210,16 @@ B220MagTapeDrive.prototype.startUpBackward = function startUpBackward(driveState } }; +/**************************************/ +B220MagTapeDrive.prototype.reverseDirection = function reverseDirection(driveState) { + /* Generates a delay to allow the drive to stop and reverse direction. + Returns a Promise that resolves when the delay is complete */ + + return new Promise((resolve, reject) => { + setCallback(this.mnemonic, this, this.turnaroundTime, resolve, driveState); + }); +}; + /**************************************/ B220MagTapeDrive.prototype.reposition = function reposition(driveState) { /* Reverses tape direction after a forward tape operation and repositions @@ -1245,6 +1248,7 @@ B220MagTapeDrive.prototype.reposition = function reposition(driveState) { switch (state) { case 1: // initial state: skip backwards until erase-gap or BOT flaw-marker words if (lane[x] == this.markerEOB) { + --x; state = 2; } else if (lane[x] == this.markerFlaw) { state = 0; @@ -1278,16 +1282,6 @@ B220MagTapeDrive.prototype.reposition = function reposition(driveState) { }); }; -/**************************************/ -B220MagTapeDrive.prototype.reverseDirection = function reverseDirection(driveState) { - /* Generates a delay to allow the drive to stop and reverse direction. - Returns a Promise that resolves when the delay is complete */ - - return new Promise((resolve, reject) => { - setCallback(this.mnemonic, this, this.turnaroundTime, resolve, driveState); - }); -}; - /**************************************/ B220MagTapeDrive.prototype.scanBlock = function scanBlock(driveState, wordIndex) { /* Scans one block in a forward direction. Terminates with either the control @@ -1318,6 +1312,7 @@ B220MagTapeDrive.prototype.scanBlock = function scanBlock(driveState, wordIndex) switch (state) { case 1: // initial state: skip over flaw and intra-block words if (w == this.markerGap) { + ++x; state = 2; } else { ++x; @@ -1397,6 +1392,7 @@ B220MagTapeDrive.prototype.scanBlock = function scanBlock(driveState, wordIndex) case 7: // step through remaining words in the block until normal EOB if (w == this.markerEOB) { + ++x; state = 8; } else { ++x; @@ -1452,6 +1448,7 @@ B220MagTapeDrive.prototype.searchForwardBlock = function searchForwardBlock(driv switch (state) { case 1: // initial state: skip over flaw and intra-block words if (w == this.markerGap) { + ++x; state = 2; } else { ++x; @@ -1553,6 +1550,7 @@ B220MagTapeDrive.prototype.searchBackwardBlock = function searchBackwardBlock(dr switch (state) { case 1: // initial state: skip over flaw and magnetic EOT words if (w == this.markerGap) { + --x; state = 2; } else if (w == this.markerFlaw) { --x; @@ -1573,6 +1571,7 @@ B220MagTapeDrive.prototype.searchBackwardBlock = function searchBackwardBlock(dr case 3: // search for start of block (first prior inter-block gap word) if (w == this.markerGap) { + --x; state = 4; } else if (w < 0) { count = 0; @@ -1650,6 +1649,7 @@ B220MagTapeDrive.prototype.readNextBlock = function readNextBlock(driveState, re switch (state) { case 1: // initial state: skip over flaw and intra-block words if (w == this.markerGap) { + ++x; state = 2; } else { ++x; @@ -1774,6 +1774,7 @@ B220MagTapeDrive.prototype.readNextBlock = function readNextBlock(driveState, re case 7: // check for proper end-of-block if (w == this.markerEOB) { + ++x; state = 9; } else { state = 0; // block was longer than preface indicated @@ -1784,6 +1785,7 @@ B220MagTapeDrive.prototype.readNextBlock = function readNextBlock(driveState, re case 8: // step through remaining words in the block until normal EOB if (w == this.markerEOB) { + ++x; state = 9; } else { ++x; @@ -1801,6 +1803,7 @@ B220MagTapeDrive.prototype.readNextBlock = function readNextBlock(driveState, re case 10: // step through remaining words in the block until EOB for error if (w == this.markerEOB) { + ++x; state = 11; } else { ++x; @@ -1856,6 +1859,7 @@ B220MagTapeDrive.prototype.overwriteBlock = function overwriteBlock(driveState, switch (state) { case 1: // initial state: skip over flaw and intra-block words if (w == this.markerGap) { + ++x; state = 2; } else { ++x; @@ -1945,6 +1949,7 @@ B220MagTapeDrive.prototype.overwriteBlock = function overwriteBlock(driveState, case 6: // step through remaining words in the block until normal EOB if (w == this.markerEOB) { + ++x; state = 7; } else { ++x; @@ -1962,6 +1967,7 @@ B220MagTapeDrive.prototype.overwriteBlock = function overwriteBlock(driveState, case 8: // step through remaining words in the block until EOB for error if (w == this.markerEOB) { + ++x; state = 9; } else { ++x; @@ -2168,6 +2174,7 @@ B220MagTapeDrive.prototype.spaceForwardBlock = function spaceForwardBlock(driveS switch (state) { case 1: // initial state: skip over flaw and intra-block words if (w == this.markerGap) { + ++x; state = 2; } else { ++x; @@ -2184,6 +2191,7 @@ B220MagTapeDrive.prototype.spaceForwardBlock = function spaceForwardBlock(driveS case 3: // found preface: search for end of block (next erase-gap word) if (w == this.markerEOB) { + ++x; state = 4; } else { ++x; @@ -2238,6 +2246,7 @@ B220MagTapeDrive.prototype.spaceBackwardBlock = function spaceBackwardBlock(driv switch (state) { case 1: // initial state: skip over flaw and magnetic EOT words if (w == this.markerGap) { + --x; state = 2; } else if (w == this.markerFlaw) { --x; @@ -2342,6 +2351,7 @@ B220MagTapeDrive.prototype.spaceEOIBlock = function spaceEOIBlock(driveState) { case 3: // search for end of block (next erase-gap word) if (w == this.markerEOB) { + ++x; state = 4; } else { ++x; diff --git a/webUI/B220Manifest.appcache b/webUI/B220Manifest.appcache index e249001..5f86e9f 100644 --- a/webUI/B220Manifest.appcache +++ b/webUI/B220Manifest.appcache @@ -1,5 +1,5 @@ CACHE MANIFEST -# retro-220 emulator 0.03c, 2017-11-17 06:15 +# retro-220 emulator 0.04, 2017-11-19 15:00 CACHE: ../emulator/B220Processor.js B220.css diff --git a/webUI/B220PaperTapePunch.js b/webUI/B220PaperTapePunch.js index 59eca68..39e1d35 100644 --- a/webUI/B220PaperTapePunch.js +++ b/webUI/B220PaperTapePunch.js @@ -58,16 +58,22 @@ B220PaperTapePunch.codeXlate = [ // translate internal B220 code to ANSI // so B220 carriage-return (16) translates to "|". To avoid space-expansion // of tabs (26), they are translated to "~". The 02 "blank" code is "_". // Form-feed (15) translates to "^". - " ", "?", "_", ".", "\u00A4", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 00-0F - "&", "?", "?", "$", "*", "^", "|", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 10-1F - "-", "/", "?", ",", "%", "?", "~", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 20-2F - "?", "?", "?", "#", "@", "!", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 30-3F - "?", "A", "B", "C", "D", "E", "F", "G", "H", "I", "?", "?", "?", "?", "?", "?", // 40-4F - "?", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "?", "?", "?", "?", "?", "?", // 50-5F - "?", "?", "S", "T", "U", "V", "W", "X", "Y", "Z", "?", "?", "?", "?", "?", "?", // 60-6F - "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 70-7F - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "?", "?", "?", "?", "?", "?", // 80-8F - "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?"]; // 90-9F + " ", "?", "_", ".", "\u00A4", "?", "?", "?", "?", "?", "!", "!", "!", "!", "!", "!", // 00-0F + "&", "?", "?", "$", "*", "^", "|", "?", "?", "?", "!", "!", "!", "!", "!", "!", // 10-1F + "-", "/", "?", ",", "%", "?", "~", "?", "?", "?", "!", "!", "!", "!", "!", "!", // 20-2F + "?", "?", "?", "#", "@", "\\", "?", "?", "?", "?", "!", "!", "!", "!", "!", "!", // 30-3F + "?", "A", "B", "C", "D", "E", "F", "G", "H", "I", "!", "!", "!", "!", "!", "!", // 40-4F + "?", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "!", "!", "!", "!", "!", "!", // 50-5F + "?", "?", "S", "T", "U", "V", "W", "X", "Y", "Z", "!", "!", "!", "!", "!", "!", // 60-6F + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "!", "!", "!", "!", "!", "!", // 70-7F + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "!", "!", "!", "!", "!", // 80-8F + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "!", "!", "!", "!", "!", "!", // 90-9F + "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", // A0-AF + "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", // B0-BF + "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", // C0-CF + "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", // D0-DF + "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", // E0-EF + "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!"]; // F0-FF /**************************************/ @@ -105,6 +111,7 @@ B220PaperTapePunch.prototype.punchEmptyLine = function punchEmptyLine(text) { } paper.lastChild.nodeValue += "\n"; // newline paper.appendChild(this.doc.createTextNode(line)); + this.punchEOP.scrollIntoView(); }; /**************************************/ diff --git a/webUI/B220PaperTapeReader.js b/webUI/B220PaperTapeReader.js index 9b2b3a8..0d89235 100644 --- a/webUI/B220PaperTapeReader.js +++ b/webUI/B220PaperTapeReader.js @@ -247,7 +247,7 @@ B220PaperTapeReader.prototype.readerOnload = function readerOnload() { body = this.$$("PaperTapeReader") this.remoteSwitch = new ToggleSwitch(body, null, null, "RemoteSwitch", B220PaperTapeReader.offSwitchImage, B220PaperTapeReader.onSwitchImage); - this.remoteSwitch.set(prefs.remote); + 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); @@ -360,36 +360,30 @@ B220PaperTapeReader.prototype.readTapeChar = function readTapeChar(receiver) { this.window.focus(); // call attention to the tape reader } else { this.busy = false; - do { - if (x >= bufLength) { // end of buffer -- send finish - this.sendTapeChar(0x20, 0x35, receiver); - this.setReaderEmpty(); - break; // out of do loop - } else { - c = this.buffer.charCodeAt(x) % 0x100; - if (c == 0x0D) { // carriage return -- send EOW and check for LF - if (++x < bufLength && this.buffer.charCodeAt(x) == 0x0A) { - ++x; - } - this.sendTapeChar(0x20, 0x35, receiver); - if (x >= bufLength) { - this.setReaderEmpty(); - } - break; // out of do loop - } else if (c == 0x0A) { // line feed -- send EOW + if (x >= bufLength) { // end of buffer -- send finish + this.sendTapeChar(0x20, 0x35, receiver); + this.setReaderEmpty(); + } else { + c = this.buffer.charCodeAt(x) % 0x100; + if (c == 0x0D) { // carriage return -- send EOW and check for LF + if (++x < bufLength && this.buffer.charCodeAt(x) == 0x0A) { ++x; - this.sendTapeChar(0x20, 0x35, receiver); - if (x >= bufLength) { - this.setReaderEmpty(); - } - break; // out of do loop - } else { // translate character and send its code - ++x; - this.sendTapeChar(c, B220PaperTapeReader.xlate220[c], receiver); - break; // out of do loop } + this.sendTapeChar(0x20, 0x35, receiver); + if (x >= bufLength) { + this.setReaderEmpty(); + } + } else if (c == 0x0A) { // line feed -- send EOW + ++x; + this.sendTapeChar(0x20, 0x35, receiver); + if (x >= bufLength) { + this.setReaderEmpty(); + } + } else { // translate character and send its code + ++x; + this.sendTapeChar(c, B220PaperTapeReader.xlate220[c], receiver); } - } while (true); + } this.tapeSupplyBar.value = bufLength-x; this.bufIndex = x; diff --git a/webUI/B220SystemConfig.js b/webUI/B220SystemConfig.js index 537d6a1..4d4ba00 100644 --- a/webUI/B220SystemConfig.js +++ b/webUI/B220SystemConfig.js @@ -76,7 +76,7 @@ B220SystemConfig.defaultConfig = { ConsoleOutput: { units: [ - {type: "TTYA", unitMask: 0x001, remote: 1, format: 0, zeroSuppress: 0, mapMemory: 0, + {type: "TTYA", unitMask: 0x001, remote: 1, format: 0, zeroSuppress: 0, mapMemory: 0, printerSpeed: 0, columns: 72, tabs: "9,17,25,33,41,49,57,65,73,81"}, {type: "NONE"}, {type: "NONE"}, @@ -460,6 +460,7 @@ B220SystemConfig.prototype.saveConfigDialog = function saveConfigDialog() { unit.remote = (unit.remote || 0); unit.zeroSuppress = (unit.zeroSuppress || 0); unit.mapMemory = (unit.mapMemory || 0); + unit.printerSpeed = (unit.printerSpeed || 0); e = this.$$(prefix + "Format"); unit.format = (e.selectedIndex < 0 ? "NONE" : e.options[e.selectedIndex].value); unit.columns = (unit.columns ? unit.columns : 72);