diff --git a/emulator/B220Processor.js b/emulator/B220Processor.js index 30d539f..9964ff4 100644 --- a/emulator/B220Processor.js +++ b/emulator/B220Processor.js @@ -224,9 +224,6 @@ function B220Processor(config, devices) { this.TAT = new B220Processor.FlipFlop(this, false); // magnetic tape alarm toggle this.UET = new B220Processor.FlipFlop(this, false); // unequal comparison toggle (HIT=UET=0 => off) - // Mag-Tape Control Unit switch - this.tswSuppressB = 0; // Suppress B-register modification on input - // Left/Right Maintenance Panel this.leftPanelOpen = false; this.rightPanelOpen = false; @@ -245,10 +242,9 @@ function B220Processor(config, devices) { this.boundCardatronOutputFinished = B220Processor.bindMethod(this, B220Processor.prototype.cardatronOutputFinished); this.boundCardatronReceiveWord = B220Processor.bindMethod(this, B220Processor.prototype.cardatronReceiveWord); + this.boundMagTapeComplete = B220Processor.bindMethod(this, B220Processor.prototype.magTapeComplete); this.boundMagTapeReceiveBlock = B220Processor.bindMethod(this, B220Processor.prototype.magTapeReceiveBlock); - this.boundMagTapeInitiateSend = B220Processor.bindMethod(this, B220Processor.prototype.magTapeInitiateSend); this.boundMagTapeSendBlock = B220Processor.bindMethod(this, B220Processor.prototype.magTapeSendBlock); - this.boundMagTapeTerminateSend = B220Processor.bindMethod(this, B220Processor.prototype.magTapeTerminateSend); this.clear(); // Create and initialize the processor state @@ -260,7 +256,7 @@ function B220Processor(config, devices) { * Global Constants * ***********************************************************************/ -B220Processor.version = "0.03"; +B220Processor.version = "0.03a"; B220Processor.tick = 1000/200000; // milliseconds per clock cycle (200KHz) B220Processor.cyclesPerMilli = 1/B220Processor.tick; @@ -1056,7 +1052,7 @@ B220Processor.prototype.setPaperTapeCheck = function setPaperTapeCheck(value) { /**************************************/ B220Processor.prototype.setHighSpeedPrinterCheck = function setHighSpeedPrinterCheck(value) { - /* Sets the Cardatron Check alarm */ + /* Sets the High Speed Printer Check alarm */ if (!this.ALARMSW) { this.HAT.set(value); @@ -2755,11 +2751,11 @@ B220Processor.prototype.cardatronOutputFinished = function cardatronOutputFinish /**************************************/ B220Processor.prototype.cardatronReceiveWord = function cardatronReceiveWord(word) { /* Handles a word coming from the Cardatron input unit. Negative values for - the word indicates this is the last word and the I/O is finished. The word - is stored into the D register and is handled according to the sign digit in - the D register. The last word received (typically a "pusher" word of zeroes) - is abandoned and not acted upon. Returns -1 if further data transfer is to - be terminated, 0 otherwise */ + the word indicates this is the last word and the I/O is finished. Otherwise, + the word is stored into the D register and is handled according to the sign + digit in the D register. The last word received (typically a "pusher" word + of zeroes) is abandoned and not acted upon. Returns -1 if further data + transfer is to be terminated, 0 otherwise */ var returnCode = 0; // default is to continue receiving var sign; // D-register sign digit @@ -2836,95 +2832,70 @@ B220Processor.prototype.cardatronReceiveWord = function cardatronReceiveWord(wor ***********************************************************************/ /**************************************/ -B220Processor.prototype.magTapeInitiateSend = function magTapeInitiateSend(writeInitiate) { - /* Performs the initial memory block-to-loop operation after the tape control - unit has determined the drive is ready and not busy. Once the initial loop is - loaded, calls "writeInitiate" to start tape motion, which in turn will cause - the control to call this.magTapeSendBlock to pass the loop data to the drive - and initiate loading of the alternate loop buffer */ - var that = this; +B220Processor.prototype.magTapeComplete = function magTapeComplete(alarm, control, word) { + /* Call-back routine to signal completion of a magnetic tape operation. If + "alarm" is true, the Magnetic Tape Alarm will be set. If "control" is true, + the contents of "word" are processed as a tape control word and an appropriate + branch is set up. Unconditionally terminates the tape I/O instruction */ + var aaaa; + var bbbb; - if (this.togMT3P) { // if false, we've probably been cleared - if (this.CADDR >= 0x8000) { - writeInitiate(this.boundMagTapeSendBlock, this.boundMagTapeTerminateSend); - } else { - this.execTime += performance.now()*B220Processor.wordsPerMilli; // restore time after I/O - this.blockToLoop((this.togMT1BV4 ? 4 : 5), function initialBlockComplete() { - that.execTime -= performance.now()*B220Processor.wordsPerMilli; // suspend time during I/O - writeInitiate(that.boundMagTapeSendBlock, that.boundMagTapeTerminateSend); - }); + if (alarm) { + this.setMagneticTapeCheck(true); + } else if (control) { + this.D.set(word); + bbbb = word%0x10000; + aaaa = ((word - bbbb)/0x10000)%0x10000; + if ((word - word%0x10000000000)%2) { // if sign bit is 1, + bbbb = this.bcdAdd(bbbb, this.B.value, 4); // B-adjust the low-order 4 digits + } + + this.E.set(aaaa); + this.readMemory(); + if (!this.MET.value) { + this.IB.set(this.IB.value - this.IB.value%0x100000000 + + (this.C.value%0x10000)*0x10000 + this.P.value%0x10000); + this.writeMemory(); + this.P.set(bbbb); } } + + this.ioComplete(true); }; /**************************************/ -B220Processor.prototype.magTapeSendBlock = function magTapeSendBlock(lastBlock) { - /* Sends a block of data from a loop buffer to the tape control unit and - initiates the load of the alternate loop buffer. this.togMT1BV4 and - this.togMT1BV5 control alternation of the loop buffers. "lastBlock" indicates - this will be the last block requested by the control unit and no further - blocks should be buffered. If the C-register address is 8000 or higher, the - loop is not loaded from main memory, and the current contents of the loop - are written to tape. Since tape block writes take 46 ms, they are much - longer than any memory-to-loop transfer, so this routine simply exits after - the next blockToLoop is initiated, and the processor then waits for the tape - control unit to request the next block, by which time the blockToLoop will - have completed. Returns null if the processor has been cleared and the I/O - must be aborted */ - var loop; - var that = this; +B220Processor.prototype.magTapeSendBlock = function magTapeSendBlock(buffer, words) { + /* Sends a block of data from memory to the tape control unit. "buffer" is an + array of words to receive the data to be written to tape. "words" is the number + of words to place in the buffer, starting at the current operand address in the + C register. Returns true if the processor has been cleared or a memory address + error occurs, and the I/O must be aborted */ + var result = false; // return value + var that = this; // local context + var x = 0; // buffer index - function blockFetchComplete() { - that.execTime -= performance.now()*B220Processor.wordsPerMilli; // suspend time again during I/O - } + //console.log("TSU " + this.selectedUnit + " W, Len " + words + + // ", ADDR=" + this.CADDR.toString(16)); - //console.log("TSU " + this.selectedUnit + " W, L" + (this.togMT1BV4 ? 4 : 5) + - // ", ADDR=" + this.CADDR.toString(16) + - // " : " + block[0].toString(16) + ", " + block[19].toString(16)); - - if (!this.togMT3P) { - loop = null; + if (!this.AST.value) { + result = true; } else { - // Select the appropriate loop to send data to the drive - if (this.togMT1BV4) { - loop = this.L4; - this.toggleGlow.glowL4 = 1; // turn on the lamp and let normal decay work - } else { - loop = this.L5; - this.toggleGlow.glowL5 = 1; - } - - if (!lastBlock) { - this.execTime += performance.now()*B220Processor.wordsPerMilli; // restore time after I/O - // Flip the loop-buffer toggles - this.togMT1BV5 = this.togMT1BV4; - this.togMT1BV4 = 1-this.togMT1BV4; - // Block the loop buffer from main memory if appropriate - if (this.CADDR < 0x8000) { - this.blockToLoop((this.togMT1BV4 ? 4 : 5), blockFetchComplete); + while (x < words) { + this.E.set(this.CADDR); + this.CADDR = this.bcdAdd(this.CADDR, 1, 4); + this.readMemory(); + if (this.MET.value) { // invalid address + result = true; + break; // out of do-loop } else { - blockFetchComplete(); + buffer[x] = this.IB.value; + ++x; } } - - this.A.set(loop[loop.length-1]); // for display only - this.D.set(0); // for display only } - return loop; // give the loop data to the control unit -}; - -/**************************************/ -B220Processor.prototype.magTapeTerminateSend = function magTapeTerminateSend() { - /* Called by the tape control unit after the last block has been completely - written to tape. Terminates the write instruction */ - - if (this.togMT3P) { // if false, we've probably been cleared - this.togMT3P = 0; - this.togMT1BV4 = this.togMT1BV5 = 0; - this.execTime += performance.now()*B220Processor.wordsPerMilli; // restore time after I/O - this.schedule(); - } + this.C.set(this.C.value - this.C.value%0x10000 + this.CADDR); + return result; }; /**************************************/ @@ -3676,9 +3647,32 @@ B220Processor.prototype.execute = function execute() { this.operationComplete(); break; - case 0x50: //--------------------- MT* Magnetic tape search/field search/lane select/rewind - this.setProgramCheck(1); - this.operationComplete(); + case 0x50: //--------------------- MTS/MFS/MLS/MRW/MDA Magnetic tape search/field search/lane select/rewind + this.opTime = 0.160; + if (!this.magTape) { + this.setMagneticTapeCheck(true); // no tape control + this.operationComplete(); + } else { + this.selectedUnit = (this.CCONTROL >>> 12) & 0x0F; + switch (this.CCONTROL%0x10) { + case 0: case 1: case 2: case 3: // MTS/MFS: search or field search + this.setProgramCheck(true); // TEMP // + this.operationComplete(); + break; + case 4: case 5: case 6: case 7: // MLS: lane select + this.ioInitiate(); + this.magTape.laneSelect(this.D.value, this.boundMagTapeComplete); + break; + case 8: case 9: // MRW/MDA: rewind, with or without lockout + this.ioInitiate(); + this.magTape.rewind(this.D.value, this.boundMagTapeComplete); + break; + default: // should never happen + this.setProgramCheck(true); + this.operationComplete(); + break; + } // switch on operation variant + } break; case 0x51: //--------------------- MTC/MFC Magnetic tape scan/field scan @@ -3697,13 +3691,31 @@ B220Processor.prototype.execute = function execute() { break; case 0x54: //--------------------- MIW Magnetic tape initial write - this.setProgramCheck(1); - this.operationComplete(); + this.opTime = 0.160; + if (!this.magTape) { + this.setMagneticTapeCheck(true); // no tape control + this.operationComplete(); + } else { + this.selectedUnit = (this.CCONTROL >>> 12) & 0x0F; + this.CCONTROL = this.CADDR; // copy C address into control digits + this.C.set((this.CCONTROL*0x100 + this.COP)*0x10000 + this.CADDR); + this.ioInitiate(); + this.magTape.initialWrite(this.D.value, this.boundMagTapeComplete, this.boundMagTapeSendBlock); + } break; case 0x55: //--------------------- MIR Magnetic tape initial write, record - this.setProgramCheck(1); - this.operationComplete(); + this.opTime = 0.160; + if (!this.magTape) { + this.setMagneticTapeCheck(true); // no tape control + this.operationComplete(); + } else { + this.selectedUnit = (this.CCONTROL >>> 12) & 0x0F; + this.CCONTROL = this.CADDR; // copy C address into control digits + this.C.set((this.CCONTROL*0x100 + this.COP)*0x10000 + this.CADDR); + this.ioInitiate(); + this.magTape.initialWriteRecord(this.D.value, this.boundMagTapeComplete, this.boundMagTapeSendBlock); + } break; case 0x56: //--------------------- MOW Magnetic tape overwrite @@ -3716,13 +3728,47 @@ B220Processor.prototype.execute = function execute() { this.operationComplete(); break; - case 0x58: //--------------------- MPF/MPB Magnetic tape position forward/backward/at end - this.setProgramCheck(1); - this.operationComplete(); + case 0x58: //--------------------- MPF/MPB/MIE Magnetic tape position forward/backward/at end + this.opTime = 0.130; + if (!this.magTape) { + this.setMagneticTapeCheck(true); // no tape control + this.operationComplete(); + } else { + this.selectedUnit = (this.CCONTROL >>> 12) & 0x0F; + this.ioInitiate(); + switch (this.CCONTROL%0x10) { + case 1: // MPB: position tape backward + this.magTape.positionBackward(this.D.value, this.boundMagTapeComplete); + break; + case 2: // MPE: position tape at end + this.magTape.positionAtEnd(this.D.value, this.boundMagTapeComplete); + break; + default: // MPF: position tape forward + this.magTape.positionForward(this.D.value, this.boundMagTapeComplete); + break; + } // switch on operation variant + } break; case 0x59: //--------------------- MIB/MIE Magnetic tape interrogate, branch/end of tape, branch - this.setProgramCheck(1); + if (!this.magTape) { + this.opTime = 0.01; + } else if (this.magTape.controlBusy) { + this.opTime = 0.01; + } else { + opTime = 0.14; + if (this.CCONTROL%0x10 == 1) { // MIE + if (this.magTape.testUnitAtMagneticEOT(this.D.value)) { + this.P.set(this.CADDR); + this.opTime += 0.020; + } + } else { // MIB + if (this.magTape.testUnitReady(this.D.value)) { + this.P.set(this.CADDR); + this.opTime += 0.020; + } + } + } this.operationComplete(); break; @@ -3913,14 +3959,6 @@ B220Processor.prototype.execute = function execute() { } break; - case 0x52: //---------------- MTRW Magnetic Tape Rewind - if (this.magTape) { - this.selectedUnit = (this.CCONTROL >>> 4) & 0x0F; - if (this.magTape.rewind(this.selectedUnit)) { - this.OFT.set(1); // control or tape unit busy/not-ready - } - } - break; default: //---------------- (unimplemented instruction -- alarm) break; } // switch this.COP @@ -4132,6 +4170,7 @@ B220Processor.prototype.stop = function stop() { this.execLimit = 0; // kill the time slice this.SST.set(0); this.RUT.set(0); + this.AST.set(0); // Stop the timers this.clockIn(); @@ -4169,7 +4208,11 @@ B220Processor.prototype.setStop = function setStop() { the end of the Execute cycle, then stop */ if (this.poweredOn) { - this.RUT.set(0); + if (this.RUT.value) { + this.RUT.set(0); + } else { + this.stop(); + } } }; @@ -4250,7 +4293,9 @@ B220Processor.prototype.tcuClear = function tcuClear() { /* Clears the Tape Control Unit */ if (this.poweredOn) { - //?? TBD ?? + if (this.magTape) { + this.magTape.clearUnit(); + } } }; @@ -4297,6 +4342,22 @@ 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] = 0x1101540200; // MIW 200,1,1,1 + this.MM[ 6] = 0x1009500000; // MDA 1 + this.MM[ 7] = 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 + // Simple counter speed test this.MM[ 80] = 0x0000120082; // ADD 82 this.MM[ 81] = 0x0000300080; // BUN 80 diff --git a/webUI/B220.html b/webUI/B220.html index 8e08fd4..5adf915 100644 --- a/webUI/B220.html +++ b/webUI/B220.html @@ -36,14 +36,15 @@ ---> + diff --git a/webUI/B220.js b/webUI/B220.js index 7558892..da697e2 100644 --- a/webUI/B220.js +++ b/webUI/B220.js @@ -60,13 +60,11 @@ window.addEventListener("load", function() { devices.CardatronControl = null; } - /******************** if (config.getNode("MagTape.hasMagTape")) { devices.MagTapeControl = new B220MagTapeControl(processor); } else { devices.MagTapeControl = null; } - ********************/ // Control Console must be instantiated last devices.ControlConsole = new B220ControlConsole(processor, systemShutDown); diff --git a/webUI/B220CardatronControl.css b/webUI/B220CardatronControl.css index 3d9b443..5147d3c 100644 --- a/webUI/B220CardatronControl.css +++ b/webUI/B220CardatronControl.css @@ -38,7 +38,7 @@ #ClearBtnCaption { bottom: 4px; right: 4px; - width: 40px} + width: 32px} #OutputUnitLamp { top: 8px; diff --git a/webUI/B220Common.css b/webUI/B220Common.css index 7c8f08e..8f0b9e7 100644 --- a/webUI/B220Common.css +++ b/webUI/B220Common.css @@ -445,7 +445,7 @@ DIV.redButton1 { width: 14px; height: 14px; font-size: 4px; - background-image: radial-gradient(circle, #C00, #F00); + background-image: radial-gradient(circle, #C00, #E00, #F00); border-radius: 50%; border: 10px solid #CCC} DIV.redButton2 { @@ -453,7 +453,7 @@ DIV.redButton2 { width: 8px; height: 8px; font-size: 4px; - background-image: radial-gradient(circle, #C00, #F00); + background-image: radial-gradient(circle, #C00, #E00, #F00); border-radius: 50%; border: 6px solid #CCC} DIV.redButton3 { @@ -461,7 +461,7 @@ DIV.redButton3 { width: 20px; height: 20px; font-size: 4px; - background-image: radial-gradient(circle, #C00, #F00); + background-image: radial-gradient(circle, #C00, #E00, #F00); border-radius: 50%; border: 2px solid #CCC} DIV.blackButton1 { @@ -469,7 +469,7 @@ DIV.blackButton1 { width: 14px; height: 14px; font-size: 4px; - background-image: radial-gradient(circle, #000, #333); + background-image: radial-gradient(circle, #333, #111, #000); border-radius: 50%; border: 10px solid #CCC} DIV.blackButton3 { @@ -477,7 +477,7 @@ DIV.blackButton3 { width: 20px; height: 20px; font-size: 4px; - background-image: radial-gradient(circle, #000, #333); + background-image: radial-gradient(circle, #333, #111, #000); border-radius: 50%; border: 2px solid #CCC} diff --git a/webUI/B220ConsolePrinter.css b/webUI/B220ConsolePrinter.css index 9dcadd1..a1f4190 100644 --- a/webUI/B220ConsolePrinter.css +++ b/webUI/B220ConsolePrinter.css @@ -84,12 +84,14 @@ #EndOfPaper.hidden { display: none} +/***** TTY Control Panel *****/ + #FormatControlsDiv { position: absolute; overflow: visible; display: none; - left: calc(50% - 312px); - top: 48px; + left: 2px; + top: 4px; z-index: 10; height: 216px; width: 624px; diff --git a/webUI/B220ConsolePrinter.js b/webUI/B220ConsolePrinter.js index cb61868..6b8aea2 100644 --- a/webUI/B220ConsolePrinter.js +++ b/webUI/B220ConsolePrinter.js @@ -48,7 +48,7 @@ function B220ConsolePrinter(mnemonic, unitIndex, config) { this.printerLine = 0; this.printerCol = 0; this.window = window.open("../webUI/B220ConsolePrinter.html", mnemonic, - "location=no,scrollbars=no,resizable,width=640,height=300," + + "location=no,scrollbars=no,resizable,width=640,height=240," + "left=" + left + ",top=" + top); this.window.addEventListener("load", B220Util.bindMethod(this, B220ConsolePrinter.prototype.printerOnLoad)); diff --git a/webUI/B220MagTapeControl.css b/webUI/B220MagTapeControl.css new file mode 100644 index 0000000..c1ef99d --- /dev/null +++ b/webUI/B220MagTapeControl.css @@ -0,0 +1,57 @@ +/*********************************************************************** +* retro-220/webUI B220MagTapeControl.css +************************************************************************ +* Copyright (c) 2017, Paul Kimpel. +* Licensed under the MIT License, see +* http://www.opensource.org/licenses/mit-license.php +************************************************************************ +* Burroughs 220 emulator Magnetic Tape Control style sheet. +************************************************************************ +* 2017-07-09 P.Kimpel +* Original version, from retro-205 D205MagTapeControl.css. +***********************************************************************/ + +DIV.panelRegCaption { /* Overrides class in B220Common.css */ + border-top: none} + +#mtControlBody { + height: 100%; + min-height: 100%; + overflow: hidden; + padding: 0} + +#PanelSurface { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: 0} + +#ClearBtn { + bottom: 60px; + left: 8px; + border: 10px solid #CCC; + box-shadow: 3px 3px 2px #999} +#ClearBtnCaption { + bottom: 48px; + left: 8px; + width: 32px} + +#MiscRegPanel { + left: 52px; + top: 4px; + width: 144px; + height: 128px} + +#CRegPanel { + left: 208px; + top: 4px; + width: 192px; + height: 128px} + +#TRegPanel { + left: 412px; + top: 4px; + width: 288px; + height: 128px} diff --git a/webUI/B220MagTapeControl.html b/webUI/B220MagTapeControl.html new file mode 100644 index 0000000..fb4bf59 --- /dev/null +++ b/webUI/B220MagTapeControl.html @@ -0,0 +1,39 @@ + + +
+ +
+
+ content
+ tape.removeChild(tape.firstChild);
+ }
+
+ for (lx=0; lx<2; ++lx) {
+ mt.laneNr = lx;
+ lane = mt.image[lx];
+ state = 1;
+ x = 0;
+ while (x < imgLength) {
+ switch (state) {
+ case 1: // Search for start of block
+ nzw = 0;
+ mt.imgIndex = x;
+ w = mt.findBlockStart();
+ if (w < 0) { // done with this lane
+ x = imgLength; // kill the loop
+ } else { // format the preface word
+ buf = lx.toString(10) + "," + w.toString(10);
+ x = mt.imgIndex;
+ state = 2;
+ }
+ break;
+ case 2: // Record the block data words
+ w = lane[x++];
+ if (w == 0) {
+ ++nzw; // suppress trailing zero words
+ } else if (w >= 0) {
+ while (nzw > 0) {
+ --nzw; // restore non-trailing zero words
+ buf += ",0";
+ }
+ buf += "," + w.toString(16);
+ } else {
+ tape.appendChild(doc.createTextNode(buf + "\n"));
+ state = 1; // reset for next block
+ }
+ break;
+ default:
+ x = imgLength; // kill the loop
+ throw new Error("Invalid state: B220MagTapeDrive.unloadTape, " + state);
+ break;
+ } // switch state
+ } // while not at end of lane
+ } // for lx
+
+ mt.setTapeUnloaded();
+ }
+
+ function unloadSetup() {
+ /* Loads a status message into the "paper" rendering area, then calls
+ unloadDriver after a short wait to allow the message to appear */
+
+ win.document.getElementById("Paper").appendChild(
+ win.document.createTextNode("Rendering tape image... please wait..."));
+ setTimeout(unloadDriver, 50);
+ }
+
+ // Outer block of unloadTape
+ win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
+ win.focus();
+ win.addEventListener("load", unloadSetup, false);
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.tapeRewind = function tapeRewind(laneNr, lockout, unitFinished) {
+ /* 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 */
+ var lastStamp;
+
+ function rewindFinish() {
+ this.timer = 0;
+ this.tapeState = this.tapeLocal;
+ B220Util.removeClass(this.$$("MTRewindingLight"), "annunciatorLit");
+ this.laneNr = laneNr%2;
+ this.rewindLock = (lockout ? true : false);
+ this.rwlLamp.set(this.rewindLock ? 1 : 0);
+ this.setTapeReady(!this.rewindLock);
+ this.releaseUnit(unitFinished, false);
+ }
+
+ 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.tapeReposition = function tapeReposition(successor) {
+ /* Reverses tape direction after a forward tape operation and repositions
+ the head two words from the end of the block just passed, giving room for
+ startup of the next forward operation. At completion, calls the successor
+ function, passing false.
+
+ A real 220 drive repositioned tape about 60 digits (five words) from the end
+ of the data portion of the block, but that was to allow for tape acceleration
+ of about 3ms, at which point it took about 2ms (50 digits, or just over four
+ words) to reach the end of the erase gap and start of the inter-block gap
+ for the next block. this.repositionWords is sized to approximate that 2ms
+ delay */
+
+ this.imgIndex -= this.repositionWords;
+ this.moveTape(-this.repositionWords*this.inchesPerWord, this.turnaroundTime, successor);
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.LoadBtn_onclick = function LoadBtn_onclick(ev) {
+ /* Handle the click event for the LOAD button */
+
+ if (!this.busy && !this.powerOn && this.tapeState == this.tapeUnloaded) {
+ this.loadTape();
+ }
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.UnloadBtn_onclick = function UnloadBtn_onclick(ev) {
+ /* Handle the click event for the UNLOAD button */
+
+ if (!this.busy && this.atBOT && !this.powerOn && this.tapeState == this.tapeLocal) {
+ if (this.imgWritten && this.window.confirm(
+ "Do you want to save the tape image data?\n(CANCEL discards the image)")) {
+ this.unloadTape(); // it will do setTapeUnloaded() afterwards
+ } else {
+ this.setTapeUnloaded();
+ }
+ }
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.RewindBtn_onclick = function RewindBtn_onclick(ev) {
+ /* Handle the click event for the REWIND button */
+
+ if (!this.busy && !this.powerOn && this.tapeState != this.tapeUnloaded) {
+ this.tapeRewind(this.laneNr, this.rewindLock, null);
+ }
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.UnitDesignate_onchange = function UnitDesignate_onchange(ev) {
+ /* Handle the change event for the UNIT DESIGNATE select list */
+ var prefs = this.config.getNode("MagTape.units", this.unitIndex);
+ var sx = ev.target.selectedIndex;
+
+ this.setUnitDesignate(sx);
+ prefs.designate = sx;
+ this.setTapeReady(this.remote);
+ this.config.putNode("MagTape.units", prefs, this.unitIndex);
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.RWLRBtn_onclick = function RWLRBtn_onclick(ev) {
+ /* Handle the click event for the RWLR (Rewind-Lock-Release) button */
+
+ this.rewindLock = false;
+ this.rwlLamp.set(0);
+ this.setTapeReady(true);
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.WriteBtn_onclick = function WriteBtn_onclick(ev) {
+ /* Handle the click event for the WRITE and NOT WRITE buttons */
+
+ this.notWrite = (ev.target.id == "NotWriteBtn");
+ this.notWriteLamp.set(this.notWrite ? 1 : 0);
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.TransportOnBtn_onclick = function TransportOnBtn_onclick(ev) {
+ /* Handle the click event for the TRANSPORT POWER ON and STANDBY buttons */
+
+ this.powerOn = (ev.target.id == "TransportOnBtn") && (this.tapeState != this.tapeUnloaded);
+ this.transportOnLamp.set(this.powerOn ? 1 : 0);
+ this.transportStandbyLamp.set(this.powerOn ? 0 : 1);
+ this.setTapeReady(this.powerOn);
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.beforeUnload = function beforeUnload(ev) {
+ var msg = "Closing this window will make the device unusable.\n" +
+ "Suggest you stay on the page and minimize this window instead";
+
+ ev.preventDefault();
+ ev.returnValue = msg;
+ return msg;
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.tapeDriveOnload = function tapeDriveOnload() {
+ /* Initializes the reader window and user interface */
+ var body;
+ var prefs = this.config.getNode("MagTape.units", this.unitIndex);
+
+ this.doc = this.window.document;
+ this.doc.title = "retro-220 Tape Drive " + this.mnemonic;
+
+ body = this.$$("MTDiv");
+ this.reelBar = this.$$("MTReelBar");
+ this.reelIcon = this.$$("MTReel");
+
+ this.rwlLamp = new ColoredLamp(body, null, null, "RWLLamp", "blueLamp lampCollar", "blueLit");
+ this.notWriteLamp = new ColoredLamp(body, null, null, "NotWriteLamp", "blueLamp lampCollar", "blueLit");
+ this.notReadyLamp = new ColoredLamp(body, null, null, "NotReadyLamp", "blueLamp lampCollar", "blueLit");
+ this.designatedLamp = new ColoredLamp(body, null, null, "DesignatedLamp", "blueLamp lampCollar", "blueLit");
+ this.transportOnLamp = new ColoredLamp(body, null, null, "TransportOnLamp", "blueLamp lampCollar", "blueLit");
+ this.transportStandbyLamp = new ColoredLamp(body, null, null, "TransportStandbyLamp", "blueLamp lampCollar", "blueLit");
+
+ this.transportStandbyLamp.set(1);
+
+ this.unitDesignateList = this.$$("UnitDesignate");
+ this.unitDesignateList.selectedIndex = prefs.designate;
+ this.setUnitDesignate(prefs.designate);
+
+ this.tapeState = this.tapeLocal; // setTapeUnloaded() requires it to be in local
+ this.atBOT = true; // and also at BOT
+ this.setTapeUnloaded();
+
+ this.window.addEventListener("beforeunload",
+ B220MagTapeDrive.prototype.beforeUnload, false);
+ this.$$("LoadBtn").addEventListener("click",
+ B220Util.bindMethod(this, B220MagTapeDrive.prototype.LoadBtn_onclick), false);
+ this.$$("UnloadBtn").addEventListener("click",
+ B220Util.bindMethod(this, B220MagTapeDrive.prototype.UnloadBtn_onclick), false);
+ this.$$("RewindBtn").addEventListener("click",
+ B220Util.bindMethod(this, B220MagTapeDrive.prototype.RewindBtn_onclick), false);
+ this.unitDesignateList.addEventListener("change",
+ B220Util.bindMethod(this, B220MagTapeDrive.prototype.UnitDesignate_onchange), false);
+ this.$$("RWLRBtn").addEventListener("click",
+ B220Util.bindMethod(this, B220MagTapeDrive.prototype.RWLRBtn_onclick), false);
+ this.$$("WriteBtn").addEventListener("click",
+ B220Util.bindMethod(this, B220MagTapeDrive.prototype.WriteBtn_onclick), false);
+ this.$$("NotWriteBtn").addEventListener("click",
+ B220Util.bindMethod(this, B220MagTapeDrive.prototype.WriteBtn_onclick), false);
+ this.$$("TransportOnBtn").addEventListener("click",
+ B220Util.bindMethod(this, B220MagTapeDrive.prototype.TransportOnBtn_onclick), false);
+ this.$$("TransportStandbyBtn").addEventListener("click",
+ B220Util.bindMethod(this, B220MagTapeDrive.prototype.TransportOnBtn_onclick), false);
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.readBlock = function readBlock(receiveBlock) {
+ /* Reads the next block from the tape. If at EOT, makes the drive not ready
+ and terminates the read. After delaying for tape motion, calls the receiveBlock
+ callback function to pass the block to the control unit and thence the processor */
+ var wx = this.blockNr*this.blockWords;
+
+ if (this.blockNr >= this.imgLength) {
+ this.blockNr = this.imgLength;
+ this.setTapeReady(false);
+ this.setAtEOT(true);
+ this.designatedLamp.set(0);
+ this.busy = false;
+ receiveBlock(null, true); // terminate the I/O
+ } else {
+ if (this.atBOT) {
+ this.setAtBOT(false);
+ }
+ ++this.blockNr;
+ this.moveTape(this.blockLength, this.blockLength/this.tapeSpeed, function readBlockComplete() {
+ receiveBlock(this.image[this.laneNr].subarray(wx, wx+this.blockWords), false);
+ });
+ }
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.readTerminate = function readTerminate() {
+ /* Terminates a read operation on the tape drive and makes it not-busy */
+
+ this.designatedLamp.set(0);
+ this.busy = false;
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.readInitiate = function readInitiate(receiveBlock) {
+ /* Initiates a read operation on the unit. If the drive is busy or not ready,
+ returns true. Otherwise, delays for the start-up time of the drive and calls
+ readBlock() to read the next block and send it to the Processor */
+ var result = false;
+
+ if (this.busy) {
+ result = true; // report unit busy
+ } else if (!this.ready) {
+ result = true; // report unit not ready
+ } else {
+ this.busy = true;
+ this.designatedLamp.set(1);
+ setCallback(this.mnemonic, this, this.startTime, this.readBlock, receiveBlock);
+ }
+ //console.log(this.mnemonic + " read: result=" + result.toString());
+
+ return result;
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.writeBlock = function writeBlock(block, sendBlock, lastBlock) {
+ /* Writes the next block to the tape. If at EOT, makes the drive not ready
+ and terminates the write. Calls the sendBlock callback function to request the
+ next block from the processor */
+ var lane = this.image[this.laneNr];
+ var wx = this.blockNr*this.blockWords;
+ var x;
+
+ if (this.blockNr >= this.imgLength) {
+ this.blockNr = this.imgLength;
+ this.setTapeReady(false);
+ this.setAtEOT(true);
+ this.designatedLamp.set(0);
+ this.busy = false;
+ sendBlock(true); // terminate the I/O
+ } else {
+ if (this.atBOT) {
+ this.setAtBOT(false);
+ }
+
+ // If the NOT WRITE switch is on, do not copy the block data to the tape image
+ if (!this.notWrite) {
+ this.imgWritten = true;
+ if (this.blockNr > this.imgTopWordNr) {
+ this.imgTopWordNr = this.blockNr;
+ }
+ for (x=0; x= this.imgLength) {
+ this.blockNr = this.imgLength;
+ this.setTapeReady(false);
+ this.setAtEOT(true);
+ }
+ complete(this.blockNr == targetBlock);
+ }
+
+ function searchBackward() {
+ /* While the tape is beyond the target block, moves backward one block and
+ is called by moveTape to evaluate the new block position */
+
+ --this.blockNr;
+ if (this.atEOT) {
+ this.setAtEOT(false);
+ }
+
+ if (testDisabled()) {
+ finishSearch.call(this);
+ } else if (this.blockNr > targetBlock) {
+ this.moveTape(-this.blockLength, delay, searchBackward);
+ } else {
+ finishSearch.call(this);
+ }
+ }
+
+ function searchForward() {
+ /* Searches forward until the tape is beyond the target block, then
+ reverses direction and does a backward search */
+
+ ++this.blockNr;
+ if (this.atBOT) {
+ this.setAtBOT(false);
+ }
+
+ if (testDisabled()) {
+ finishSearch.call(this);
+ } else if (this.blockNr >= this.imgLength) {
+ this.blockNr = this.imgLength;
+ finishSearch.call(this);
+ } else if (this.blockNr <= targetBlock) {
+ this.moveTape(this.blockLength, delay, searchForward);
+ } else {
+ // We have searched forward (or are already forward) of the target block.
+ // Now change direction and search backwards to the target.
+ lampSet(false); // indicate backward motion
+ this.moveTape(-this.blockLength, this.turnaroundTime, searchBackward);
+ }
+ }
+
+ if (this.busy) {
+ result = true; // report unit busy
+ } else if (!this.ready) {
+ result = true; // report unit not ready
+ } else {
+ this.busy = true;
+ this.laneNr = laneNr & 0x01; // TSU uses only low-order lane bit
+ this.designatedLamp.set(1);
+
+ // Begin by searching forward until we are past the target block
+ lampSet(true); // indicate forward motion
+ this.moveTape(this.blockLength, delay+this.startTime, searchForward);
+ }
+
+ //console.log(this.mnemonic + " search: result=" + result.toString());
+ if (result) {
+ complete(false);
+ }
+ return result;
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.initialWriteBlock = function initialWriteBlock(blockReady, controlFinished) {
+ /* Writes one block on edited (blank) tape. "blockReady" is a function
+ to be called after the block is written. "releaseControl" is a function
+ to call after the control signals completion (see release). This routine
+ is used for both MIW and MIR, as block lengths are determined by the
+ tape control unit */
+ var imgLength = this.imgLength; // physical end of tape index
+ var lane = this.image[this.laneNr]; // image data for current lane
+ var result = false;
+ var startIndex = this.imgIndex; // initial index into lane buffer
+ var that = this;
+
+ function release() {
+ /* Releases the unit and control */
+
+ that.releaseUnit(controlFinished, false);
+ }
+
+ function finish() {
+ /* Wraps up the positioning and delays before releasing the unit and control */
+ var delay = 20 - that.startTime - that.repositionTime;
+
+ setCallback(that.mnemonic, that, delay, release);
+ }
+
+ function turnaround() {
+ /* Repositions the tape after the last block is written */
+
+ that.tapeReposition(finish);
+ }
+
+ function finalizeWrite() {
+ /* Write a longer inter-block gap so that MPE will work at magnetic
+ end-of-tape, check for end-of-tape, and initiate repositioning prior
+ to terminating the I/O */
+ var count = that.startOfBlockWords*2; // number of gap words
+ var done = false; // loop control
+ var x = that.imgIndex; // lane image word index
+
+ do {
+ if (x >= imgLength) {
+ done = true; // at end-of-tape
+ that.setAtEOT(true);
+ advance.call(that, x, finish);
+ } else if (count > 0) {
+ --count; // write extra inter-block gap word
+ lane[x] = that.markerGap;
+ ++x;
+ } else if (lane[x] == that.markerMagEOT) {
+ ++x; // space over magnetic EOT words
+ } else {
+ done = true; // finish write and initiate repositioning
+ advance.call(that, x, turnaround);
+ }
+ } while(!done);
+ }
+
+ function finalizeBlock() {
+ /* Returns to the tape control after completion of one block */
+
+ blockReady(false, writeBlock, finalizeWrite);
+ }
+
+ function abort() {
+ /* Aborts the I/O due to some error */
+
+ blockReady(true, null, release);
+ }
+
+ function setEOT() {
+ /* Sets EOT status after positioning to end-of-tape. Does not release
+ the control unit */
+
+ that.setAtEOT(true);
+ that.releaseUnit(null, false);
+ }
+
+ function advance (index, successor) {
+ /* Advances the tape after a block is passed, then calls the successor */
+ var len = index - that.imgIndex; // number of words passed
+ var delay = len*that.millisPerWord; // amount of tape spin time
+
+ that.imgIndex = index;
+ if (index > that.imgTopWordNr) {
+ that.imgTopWordNr = index;
+ }
+ that.moveTape(len*that.inchesPerWord, delay, successor);
+ }
+
+ function writeBlock(block, words) {
+ /* Writes the next block from the words in "block" */
+ var count = 0; // word counter
+ var done = false; // completion flag
+ var gapCount = 0; // count of consecutive inter-block gap words
+ var state = 1; // FSA state variable
+ var x = that.imgIndex; // lane image word index
+
+ if (!that.ready) {
+ done = true;
+ advance.call(that, x, abort); // drive went non-ready
+ } else {
+ do {
+ if (x >= imgLength) {
+ done = true; // at EOT: just exit and leave everybody hanging...
+ that.busy = false;
+ advance.call(that, x, setEOT);
+ } else {
+ switch (state) {
+ case 1: // initial state: skip over flaw and intra-block words
+ if (lane[x] == that.markerGap) {
+ state = 2;
+ } else {
+ ++x;
+ }
+ break;
+ case 2: // count 3 consecutive inter-block gap words
+ if (lane[x] == that.markerGap) {
+ ++gapCount;
+ if (gapCount < that.startOfBlockWords) {
+ ++x;
+ } else {
+ state = 3;
+ }
+ } else if (lane[x] == that.markerMagEOT) {
+ ++x;
+ } else {
+ done = true;
+ that.imgIndex = x;
+ advance.call(that, x, abort); // not an edited tape
+ }
+ break;
+ case 3: // write the preface
+ lane[x] = words;
+ ++x;
+ state = 4;
+ break;
+ case 4: // write the block words
+ if (count < words) {
+ lane[x] = block[count];
+ ++x;
+ ++count;
+ } else if (count < that.minBlockWords) {
+ lane[x] = 0;
+ ++x;
+ ++count;
+ } else {
+ count = 0;
+ state = 5;
+ }
+ break;
+ case 5: // write the erase gap
+ if (count < that.endOfBlockWords) {
+ lane[x] = that.markerEOB;
+ ++x;
+ ++count;
+ } else {
+ done = true;
+ advance.call(that, x, finalizeBlock);
+ }
+ break;
+ } // switch state
+ }
+ } while (!done);
+ }
+ }
+
+ function firstBlock() {
+ /* Called after the startTime delay to signal the control unit we are
+ ready for the first block of data */
+
+ blockReady(false, writeBlock, release);
+ }
+
+ if (this.busy) {
+ result = true; // unit busy
+ } else if (!this.ready) {
+ result = true; // unit not ready
+ } else if (this.notWrite) {
+ result = true; // tape in not-write status
+ } else if (this.atEOT) {
+ result = true; // tape at EOT
+ } else {
+ this.busy = true;
+ this.imgWritten = true;
+ this.designatedLamp.set(1);
+ this.setAtBOT(false);
+
+ // Begin with a delay for start-up time
+ setCallback(this.mnemonic, this, this.startTime, firstBlock);
+ }
+
+ //console.log(this.mnemonic + " initialWriteBlock result=" + result.toString());
+ return result;
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.positionForward = function positionForward(blockFinished, releaseControl) {
+ /* Initiates the positioning of tape in a forward direction and spaces the
+ first block. "blockFinished" is a function to be called after the block is
+ spaced. "releaseControl" is a function to call after the last block is spaced
+ (see spaceForward and turnaround) */
+ var lane = this.image[this.laneNr]; // image data for current lane
+ var result = false;
+ var that = this;
+
+ function release() {
+ /* Releases the unit and control */
+
+ that.releaseUnit(releaseControl, false);
+ }
+
+ function finish() {
+ /* Wraps up the positioning and delays before releasing the unit and control */
+ var delay = 16 - that.startTime - that.repositionTime;
+
+ setCallback(that.mnemonic, that, delay, release);
+ }
+
+ function turnaround() {
+ /* Repositions the tape after the last block is spaced over */
+
+ that.tapeReposition(finish);
+ }
+
+ function setEOT() {
+ /* Sets EOT status after positioning to end-of-tape. Does not release
+ the control unit */
+
+ that.setAtEOT(true);
+ that.relaseUnit(null, false);
+ }
+
+ function advance (index, successor) {
+ /* Advances the tape after a block is passed, then calls the successor */
+ var len = index - that.imgIndex; // number of words passed
+ var delay = len*that.millisPerWord; // amount of tape spin time
+
+ that.imgIndex = index;
+ that.moveTape(len*that.inchesPerWord, delay, successor);
+ }
+
+ function spaceForward(blockFinished) {
+ /* Spaces over the next block */
+ var done = false; // completion flag
+ var imgLength = that.imgLength; // physical end of tape index
+ var state = 1; // FSA state variable
+ var w; // current image word
+ var x = that.imgIndex; // lane image word index
+
+ do {
+ if (x >= imgLength) {
+ done = true; // just exit and leave everybody hanging...
+ that.busy = false;
+ advance.call(that, x, setEOT);
+ } else if (!that.ready) {
+ done = true;
+ that.busy = false;
+ that.releaseUnit(releaseControl, true);
+ } else {
+ w = lane[x];
+ switch (state) {
+ case 1: // initial state: skip over flaw and intra-block words
+ if (w == that.markerGap) {
+ state = 2;
+ } else {
+ ++x;
+ }
+ break;
+ case 2: // skip inter-block gap words
+ if (w != that.markerGap) {
+ state = 3;
+ } else {
+ ++x;
+ }
+ break;
+ case 3: // search for end of block (next inter-block gap word)
+ if (w != that.markerGap) {
+ ++x;
+ } else {
+ done = true;
+ advance.call(that, x, function cb() {blockFinished(spaceForward, turnaround)});
+ }
+ break;
+ } // switch state
+ }
+ } while (!done);
+
+ }
+
+ if (this.busy) {
+ result = true; // unit busy
+ } else if (!this.ready) {
+ result = true; // unit not ready
+ } else if (this.atEOT) {
+ result = true; // tape at EOT, leave control hung
+ } else {
+ this.busy = true;
+ this.designatedLamp.set(1);
+ this.setAtBOT(false);
+
+ // Begin with a delay for start-up time
+ setCallback(this.mnemonic, this, this.startTime, spaceForward, blockFinished);
+ }
+
+ //console.log(this.mnemonic + " positionForward result=" + result.toString());
+ return result;
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.positionBackward = function positionBackward(blockFinished, releaseControl) {
+ /* Initiates the positioning of tape in a backward direction and spaces the
+ first block. "blockFinished" is a function to be called after the block is
+ spaced. "releaseControl" is a function to call after the last block is spaced
+ (see spaceBackward and finish) */
+ var lane = this.image[this.laneNr]; // image data for current lane
+ var result = false;
+ var that = this;
+
+ function release() {
+ /* Releases the unit and control */
+
+ that.releaseUnit(releaseControl, false);
+ }
+
+ function finish() {
+ /* Wraps up the positioning and delays before releasing the unit and control */
+ var delay = 6 - that.startTime;
+
+ setCallback(that.mnemonic, that, delay, release);
+ }
+
+ function setBOT() {
+ /* Sets BOT status after positioning to beginning-of-tape. Does not
+ release the control unit */
+
+ that.setAtBOT(true);
+ that.releaseUnit(null, false);
+ }
+
+ function advance (index, successor) {
+ /* Advances the tape after a block is passed, then calls the successor */
+ var len = index - that.imgIndex; // number of words passed
+ var delay = len*that.millisPerWord; // amount of tape spin time
+
+ that.imgIndex = index;
+ that.moveTape(len*that.inchesPerWord, delay, successor);
+ }
+
+ function spaceBackward(blockFinished) {
+ /* Spaces over the current or prior block until the inter-block gap */
+ var done = false; // completion flag
+ var imgLength = that.imgLength; // physical end of tape index
+ var state = 1; // FSA state variable
+ var w; // current image word
+ var x = that.imgIndex; // lane image word index
+
+ do {
+ if (x <= 0) {
+ done = true; // just exit and leave everybody hanging...
+ that.busy = false;
+ advance.call(that, x, setBOT);
+ } else if (!that.ready) {
+ done = true;
+ that.busy = false;
+ that.releaseUnit(releaseControl, true);
+ } else {
+ w = lane[x];
+ switch (state) {
+ case 1: // initial state: skip over flaw words
+ if (w == that.markerGap) {
+ state = 2;
+ } else if (w != that.markerFlaw) {
+ state = 3;
+ } else {
+ --x;
+ }
+ break;
+ case 2: // skip initial inter-block gap words
+ if (w != that.markerGap) {
+ state = 3;
+ } else {
+ --x;
+ }
+ break;
+ case 3: // search for start of block (first prior inter-block gap word)
+ if (w != that.markerGap) {
+ --x;
+ } else {
+ state = 4;
+ }
+ break;
+ case 4: // skip this block's inter-block gap words
+ if (w != that.markerGap) {
+ done = true;
+ x -= that.repositionWords; // position into end of prior block, as usual
+ advance.call(that, x, function cb() {blockFinished(spaceBackward, finish)});
+ } else {
+ --x;
+ }
+ break;
+ } // switch state
+ }
+ } while (!done);
+ }
+
+ if (this.busy) {
+ result = true; // unit busy
+ } else if (!this.ready) {
+ result = true; // unit not ready
+ } else if (this.atBOT) {
+ result = true; // tape at BOT, leave control hung
+ } else {
+ this.busy = true;
+ this.designatedLamp.set(1);
+ this.setAtEOT(false);
+
+ // Begin with a delay for start-up time
+ setCallback(this.mnemonic, this, this.startTime, spaceBackward, blockFinished);
+ }
+
+ //console.log(this.mnemonic + " positionBackward result=" + result.toString());
+ return result;
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.positionAtEnd = function positionAtEnd(releaseControl) {
+ /* Positions the tape to the end of recorded information (i.e., gap longer
+ and inter-block gap */
+ var lane = this.image[this.laneNr]; // image data for current lane
+ var result = false;
+ var that = this;
+
+ function release() {
+ /* Releases the unit and control */
+
+ that.releaseUnit(releaseControl, false);
+ }
+
+ function finish() {
+ /* Wraps up the positioning and delays before releasing the unit and control */
+ var delay = 23 - that.startTime - that.repositionTime;
+
+ setCallback(that.mnemonic, that, delay, release);
+ }
+
+ function turnaround() {
+ /* Repositions the tape after finding end-of-info */
+
+ that.tapeReposition(finish);
+ }
+
+ function setEOT() {
+ /* Sets EOT status after positioning to end-of-tape. Does not release
+ the control unit */
+
+ that.setAtEOT(true);
+ that.relaseUnit(null, false);
+ }
+
+ function advance (index, successor) {
+ /* Advances the tape after a block is passed, then calls the successor */
+ var len = index - that.imgIndex; // number of words passed
+ var delay = len*that.millisPerWord; // amount of tape spin time
+
+ that.imgIndex = index;
+ that.moveTape(len*that.inchesPerWord, delay, successor);
+ }
+
+ function spaceForward() {
+ /* Spaces over the next block, unless a long gap or physical EOT is
+ encountered */
+ var done = false; // completion flag
+ var gapCount = 0; // number of consecutive erase-gap words
+ var imgLength = that.imgLength; // physical end of tape index
+ var state = 1; // FSA state variable
+ var w; // current image word
+ var x = that.imgIndex; // lane image word index
+
+ do {
+ if (x >= imgLength) {
+ done = true; // just exit and leave everybody hanging...
+ that.busy = false;
+ advance.call(that, x, setEOT);
+ } else if (!that.ready) {
+ done = true;
+ that.busy = false;
+ that.releaseUnit(releaseControl, true);
+ } else {
+ w = lane[x];
+ switch (state) {
+ case 1: // initial state: skip over flaw words
+ if (w == that.markerGap) {
+ state = 2;
+ } else if (w != that.markerFlaw) {
+ state = 3;
+ } else {
+ ++x;
+ }
+ break;
+ case 2: // count inter-block gap words
+ if (w != that.markerGap) {
+ state = 3;
+ } else {
+ ++x;
+ ++gapCount;
+ if (gapCount > that.startOfBlockWords) {
+ done = true;
+ advance.call(that, x, turnaround);
+ }
+ }
+ break;
+ case 3: // search for end of block (next inter-block gap word)
+ if (w != that.markerGap) {
+ ++x;
+ } else {
+ done = true;
+ advance.call(that, x, spaceForward);
+ }
+ break;
+ } // switch state
+ }
+ } while (!done);
+
+ }
+
+ if (this.busy) {
+ result = true; // unit busy
+ } else if (!this.ready) {
+ result = true; // unit not ready
+ } else if (this.atEOT) {
+ result = true; // tape at EOT, leave control hung
+ } else {
+ this.busy = true;
+ this.designatedLamp.set(1);
+ this.setAtBOT(false);
+
+ // Begin with a delay for start-up time
+ setCallback(this.mnemonic, this, this.startTime, spaceForward);
+ }
+
+ //console.log(this.mnemonic + " positionAtEnd: result=" + result.toString());
+ return result;
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.laneSelect = function laneSelect(laneNr, releaseControl) {
+ /* Selects the tape lane on the unit. If the drive is busy or not ready,
+ returns true */
+ delay = 3; // ms for a no-lane change
+ var lane = laneNr%2;
+ var result = false;
+
+ if (this.busy) {
+ result = true; // unit busy
+ } else if (!this.ready) {
+ result = true; // unit not ready
+ } else {
+ this.busy = true;
+ this.designatedLamp.set(1);
+ if (this.laneNr != lane) {
+ delay += 70; // additional time (ms) if the lane changes
+ this.laneNr = laneNr%2;
+ }
+
+ setCallback(this.mnemonic, this, delay, this.releaseUnit, releaseControl);
+ }
+
+ //console.log(this.mnemonic + " lane-select: lane=" + laneNr + ", result=" + result.toString());
+ return result;
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.rewind = function rewind(laneNr, lockout, unitFinished) {
+ /* Initiates a rewind operation on the unit. If the drive is busy or not ready,
+ returns true. Otherwise, makes the drive not-ready, delays for an
+ appropriate amount of time depending on how far up-tape we are, then readies the
+ unit again */
+ var result = false;
+
+ if (this.busy) {
+ result = true; // unit busy
+ } else if (!this.ready) {
+ result = true; // unit not ready
+ } else {
+ this.designatedLamp.set(1);
+ this.tapeRewind(laneNr, lockout, unitFinished);
+ }
+
+ //console.log(this.mnemonic + " rewind: lane=" + laneNr + ", lockout=" + lockout + ", result=" + result.toString());
+ return result;
+};
+
+/**************************************/
+B220MagTapeDrive.prototype.shutDown = function shutDown() {
+ /* Shuts down the device */
+
+ if (this.timer) {
+ clearCallback(this.timer);
+ }
+ this.window.removeEventListener("beforeunload", B220MagTapeDrive.prototype.beforeUnload, false);
+ this.window.close();
+ if (this.loadWindow && !this.loadWindow.closed) {
+ this.loadWindow.close();
+ }
+};
diff --git a/webUI/B220MagTapeLoadPanel.html b/webUI/B220MagTapeLoadPanel.html
new file mode 100644
index 0000000..7fba338
--- /dev/null
+++ b/webUI/B220MagTapeLoadPanel.html
@@ -0,0 +1,69 @@
+
+
+
+
+220 Emulator Magnetic Tape Loader
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Format
+
+
+
+
+
+
+
+ To load a blank tape, simply click OK; otherwise
+ select a tape-image file before clicking OK.
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webUI/B220Manifest.appcache b/webUI/B220Manifest.appcache
index 60efdb1..b8f7909 100644
--- a/webUI/B220Manifest.appcache
+++ b/webUI/B220Manifest.appcache
@@ -1,5 +1,5 @@
CACHE MANIFEST
-# retro-220 emulator 0.03, 2017-05-29 13:30
+# retro-220 emulator 0.03a, 2017-10-19 08:45
CACHE:
../emulator/B220Processor.js
B220.css
@@ -30,13 +30,13 @@ B220ControlConsole.js
#B220DataFile.js
B220DiagMonitor.html
B220FramePaper.html
-#B220MagTapeControl.css
-#B220MagTapeControl.html
-#B220MagTapeControl.js
-#B220MagTapeDrive.css
-#B220MagTapeDrive.html
-#B220MagTapeDrive.js
-#B220MagTapeLoadPanel.html
+B220MagTapeControl.css
+B220MagTapeControl.html
+B220MagTapeControl.js
+B220MagTapeDrive.css
+B220MagTapeDrive.html
+B220MagTapeDrive.js
+B220MagTapeLoadPanel.html
B220PanelUtil.js
B220PaperTapePunch.css
B220PaperTapePunch.html
@@ -60,7 +60,7 @@ resources/DejaVuSans-webfont.ttf
resources/DejaVuSans-webfont.woff
resources/DejaVuSansMono-webfont.ttf
resources/DejaVuSansMono-webfont.woff
-#resources/MagTapeReel.jpg
+resources/MagTapeReel.jpg
resources/Organ-Switch-Down.png
resources/Organ-Switch-Up.png
resources/retro-220-Logo.png
diff --git a/webUI/B220PanelUtil.js b/webUI/B220PanelUtil.js
index e448014..bba0047 100644
--- a/webUI/B220PanelUtil.js
+++ b/webUI/B220PanelUtil.js
@@ -824,7 +824,6 @@ PanelRegister.prototype.drawBox = function drawBox(col, lamps, rows, leftStyle,
leftStyle and rightStyle specify the left and right borders of the box using
standard CSS border syntax. Returns the box element */
var box = document.createElement("div");
- var rightBias = (rightStyle ? 1 : 0);
box.style.position = "absolute";
box.style.left = (this.xCoord(col) - (PanelRegister.hSpacing-PanelRegister.lampDiameter)/2 + 1).toString() + "px";
diff --git a/webUI/B220PaperTapePunch.css b/webUI/B220PaperTapePunch.css
index 8ece032..b61a94a 100644
--- a/webUI/B220PaperTapePunch.css
+++ b/webUI/B220PaperTapePunch.css
@@ -44,7 +44,8 @@
position: absolute;
width: 24px;
left: 56px;
- top: 14px}
+ top: 14px;
+ box-shadow: 3px 3px 2px #999}
#ReadyLampCaption {
width: 36px;
left: 52px;
@@ -57,7 +58,8 @@
right: 8px;
top: 18px;
color: white;
- background-color: #333}
+ background-color: #333;
+ box-shadow: 3px 3px 2px #999}
#UnitDesignateKnobCaption {
width: 64px;
right: 8px;
diff --git a/webUI/B220PaperTapePunch.js b/webUI/B220PaperTapePunch.js
index 6e3d48c..59eca68 100644
--- a/webUI/B220PaperTapePunch.js
+++ b/webUI/B220PaperTapePunch.js
@@ -15,7 +15,7 @@
/**************************************/
function B220PaperTapePunch(mnemonic, unitIndex, config) {
/* Constructor for the Console Paper Tape Punch object */
- var top = unitIndex*32 + 360;
+ var top = unitIndex*32 + 264;
var left = (unitIndex-1)*32;
this.config = config; // System configuration object
diff --git a/webUI/B220PaperTapeReader.css b/webUI/B220PaperTapeReader.css
index 765b553..fe5cc28 100644
--- a/webUI/B220PaperTapeReader.css
+++ b/webUI/B220PaperTapeReader.css
@@ -44,7 +44,8 @@
position: absolute;
width: 24px;
left: 56px;
- top: 14px}
+ top: 14px;
+ box-shadow: 3px 3px 2px #999}
#ReadyLampCaption {
width: 36px;
left: 52px;
@@ -75,7 +76,8 @@
right: 8px;
top: 18px;
color: white;
- background-color: #333}
+ background-color: #333;
+ box-shadow: 3px 3px 2px #999}
#UnitDesignateKnobCaption {
width: 64px;
right: 8px;
diff --git a/webUI/B220PaperTapeReader.js b/webUI/B220PaperTapeReader.js
index ec6ba5c..9b2b3a8 100644
--- a/webUI/B220PaperTapeReader.js
+++ b/webUI/B220PaperTapeReader.js
@@ -18,7 +18,7 @@
/**************************************/
function B220PaperTapeReader(mnemonic, unitIndex, config) {
/* Constructor for the PaperTapeReader object */
- var top = unitIndex*32 + 360;
+ var top = unitIndex*32 + 264;
var left = (unitIndex-1)*32 + 250;
this.config = config; // System configuration object
diff --git a/webUI/B220SetCallback.js b/webUI/B220SetCallback.js
index 8fde89b..aa4870d 100644
--- a/webUI/B220SetCallback.js
+++ b/webUI/B220SetCallback.js
@@ -18,8 +18,8 @@
* minimum they use, and their precision in activating the call-back function
* once the actual delay is established varies even more. This module will use
* setTimeout() if the requested delay time is above a certain threshold, and
-* a setImmediate-like mechanism (based on window.postMessage) if the requested
-* delay is below that threshold.
+* a setImmediate-like mechanism (based on Promise) if the requested delay is
+* below that threshold.
*
* To help compensate for the fact that the call-back function may be called
* sooner than requested, and that due either to other activity or to browser
@@ -43,9 +43,9 @@
* earlier or later than the specified delay. The string "category" (which
* may be empty, null, or undefined) defines the category under which the
* average delay difference will be maintained. setCallBack returns a
-* numeric token identifying the call-back event, which can be used
-* with clearCallback(). Note that passing a string in lieu of a function
-* object is not permitted.
+* numeric token identifying the call-back event, which can be used with
+* clearCallback() to cancel the callback. Note that passing a string in
+* lieu of a function object is not permitted.
*
* clearCallBack(token)
*
@@ -77,6 +77,8 @@
* Original version, cloned from retro-205 emulator D205SetCallback.js.
* 2017-02-18 P.Kimpel
* Redesign yet again the delay adjustment mechanism -- from 205 project.
+* 2017-10-16 P.Kimpel
+* Replace window.postMessage yield mechanism with one based on Promise().
***********************************************************************/
"use strict";
@@ -89,7 +91,6 @@
var perf = global.performance; // cached window.performance object
var pool = []; // pool of reusable callback objects
var poolLength = 0; // length of active entries in pool
- var secretPrefix = "retro-205.webUI." + Date.now().toString(16);
/**************************************/
function activateCallback(token) {
@@ -140,7 +141,7 @@
/* Sets up and schedules a callback for function "fcn", called with context
"context", after a delay of "delay" ms. An optional "arg" value will be passed
to "fcn". If the delay is less than "minTimeout", a setImmediate-like mechanism
- based on window.postsMessage() will be used; otherwise the environment's standard
+ based on DOM Promise() will be used; otherwise the environment's standard
setTimeout mechanism will be used */
var adj = 0; // adjustment to delay and delayDev[]
var categoryName = (category || "NUL").toString();
@@ -213,25 +214,12 @@
thisCallback.cancelToken = global.setTimeout(activateCallback, delay, token);
} else {
thisCallback.cancelToken = 0;
- global.postMessage(secretPrefix + tokenName, "*");
+ Promise.resolve(token).then(activateCallback);
}
return token;
}
- /**************************************/
- function onMessage(ev) {
- /* Handler for the global.onmessage event. Activates the callback */
- var payload;
-
- if (ev.source === global) {
- payload = ev.data.toString();
- if (payload.substring(0, secretPrefix.length) === secretPrefix) {
- activateCallback(payload.substring(secretPrefix.length));
- }
- }
- }
-
/**************************************/
function getCallbackState(optionMask) {
/* Diagnostic function. Returns an object that, depending upon bits in
@@ -270,7 +258,7 @@
}
/********** Outer block of anonymous closure **********/
- if (!global.setCallback && global.postMessage && !global.importScripts) {
+ if (!global.setCallback && !global.importScripts) {
// Attach to the prototype of global, if possible, otherwise to global itself
var attachee = global;
@@ -282,7 +270,6 @@
}
*****/
- global.addEventListener("message", onMessage, false);
attachee.setCallback = setCallback;
attachee.clearCallback = clearCallback;
attachee.getCallbackState = getCallbackState;
diff --git a/webUI/B220SystemConfig.css b/webUI/B220SystemConfig.css
index 1ac94cf..31a7bb1 100644
--- a/webUI/B220SystemConfig.css
+++ b/webUI/B220SystemConfig.css
@@ -89,7 +89,6 @@
#ConsoleInputTable,
#ConsoleOutputTable,
#CardatronTable,
-#MagTapeOptionsTable,
#MagTapeTable {
border-spacing: 0;
border-collapse: collapse}
@@ -100,8 +99,6 @@
#ConsoleOutputTable TD,
#CardatronTable TH,
#CardatronTable TD,
-#MagTapeOptionsTable TH,
-#MagTapeOptionsTable TD,
#MagTapeTable TH,
#MagTapeTable TD {
padding-left: 4px;
diff --git a/webUI/B220SystemConfig.html b/webUI/B220SystemConfig.html
index 423fe7c..b5c964e 100644
--- a/webUI/B220SystemConfig.html
+++ b/webUI/B220SystemConfig.html
@@ -925,19 +925,10 @@
Magnetic Tape Unit Selection:
-
-
-
-
-
-
-
-
- Unit Type Designate Remote Rewind-Ready Not-Write
+ Unit Type Designate
A
@@ -959,12 +950,6 @@
diff --git a/webUI/B220SystemConfig.js b/webUI/B220SystemConfig.js
index 3594e81..537d6a1 100644
--- a/webUI/B220SystemConfig.js
+++ b/webUI/B220SystemConfig.js
@@ -107,11 +107,11 @@ B220SystemConfig.defaultConfig = {
hasMagTape: true,
units: [
null, // unit[0] not used
- {type: "MTA", designate: 0, remoteSwitch: false, rewindReadySwitch: true, notWriteSwitch: false},
+ {type: "MTA", designate: 1},
+ {type: "MTB", designate: 2},
+ {type: "NONE"},
{type: "NONE"},
{type: "NONE"},
- {type: "MTD", designate: 3, remoteSwitch: false, rewindReadySwitch: true, notWriteSwitch: false},
- {type: "MTE", designate: 4, remoteSwitch: false, rewindReadySwitch: true, notWriteSwitch: false},
{type: "NONE"},
{type: "NONE"},
{type: "NONE"},
@@ -371,15 +371,11 @@ B220SystemConfig.prototype.loadConfigDialog = function loadConfigDialog() {
cd.MagTape = B220SystemConfig.defaultConfig.MagTape;
}
- this.$$("SuppressBMod").checked = !cd.MagTape.suppressBSwitch;
for (x=1; x<=10; ++x) {
unit = cd.MagTape.units[x];
prefix = "MagTape" + x;
this.setListValue(prefix + "Type", unit.type);
- this.$$(prefix + "Designate").selectedIndex = unit.designate;
- this.$$(prefix + "Remote").checked = unit.remoteSwitch;
- this.$$(prefix + "RewindReady").checked = unit.rewindReadySwitch;
- this.$$(prefix + "NotWrite").checked = unit.notWriteSwitch;
+ this.$$(prefix + "Designate").selectedIndex = unit.designate-1;
} // for x
this.$$("MessageArea").textContent = "220 System Configuration loaded.";
@@ -509,17 +505,13 @@ B220SystemConfig.prototype.saveConfigDialog = function saveConfigDialog() {
// Magnetic Tape units
cd.MagTape.hasMagTape = false;
- cd.MagTape.suppressBSwitch = !this.$$("SuppressBMod").checked;
for (x=1; x<=10; ++x) {
unit = cd.MagTape.units[x];
prefix = "MagTape" + x;
e = this.$$(prefix + "Type");
unit.type = (e.selectedIndex < 0 ? "NONE" : e.options[e.selectedIndex].value);
- unit.designate = this.$$(prefix + "Designate").selectedIndex;
- unit.remoteSwitch = this.$$(prefix + "Remote").checked;
- unit.rewindReadySwitch = this.$$(prefix + "RewindReady").checked;
- unit.notWriteSwitch = this.$$(prefix + "NotWrite").checked;
+ unit.designate = this.$$(prefix + "Designate").selectedIndex+1;
if (unit.type != "NONE") {
cd.MagTape.hasMagTape = true;
}