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