CrosfieldTapeImpl.mesa
Copyright Ó 1988 by Xerox Corporation. All rights reserved.
Tim Diebert: March 11, 1988 8:35:43 am PST
Maureen Stone, June 4, 1989 12:51:31 pm PDT
DIRECTORY
AIS USING [CloseFile, CloseWindow, CreateFile, FRef, OpenWindow, Raster, RasterPart, WRef, WriteSample],
Basics USING [BITOR, BytePair],
RefText USING [New],
BasicTime USING [OutOfRange, GMT, nullGMT, Pack, Unpacked, Now, Unpack],
CountedVM USING [Handle, SimpleAllocate],
PrincOpsUtils USING [LongCopy],
Real USING [Round],
CedarProcess USING [CheckAbort],
Rope USING [Cat, Fetch, FromProc, Length, ROPE, Substr],
TapesCommon,
CrosfieldTape,
UnixTapeOps;
CrosfieldTapeImpl: CEDAR PROGRAM
IMPORTS AIS, Basics, BasicTime, PrincOpsUtils, Rope, UnixTapeOps, CedarProcess, Real, RefText
EXPORTS CrosfieldTape
~ BEGIN OPEN CrosfieldTape, TapesCommon;
DEC Tape conventions: All WORD quantities need to be ByteSwapped. DEC bit0=Cedar bit15, etc
However, numberic fields within this bit order are correct. Affects DateYMD and Color
15 14 13 12 11 10 9 8  7 6 5 4 3 2 1 0 --tape documentation
8 9 10 11 12 13 14 15  0 1 2 3 4 5 6 7 --Cedar bit addressing

0 1 2 3 4 5 6 7  8 9 10 11 12 13 14 15 --normal Cedar bit addressing
ROPE: TYPE ~ Rope.ROPE;
Identity: TYPE ~ REF IdentityRep;
IdentityRep: TYPE ~ RECORD [
tapeNumber: [1..999],
date: BasicTime.GMT,
name: ROPE
];
IdentityOnTape: TYPE ~ MACHINE DEPENDENT RECORD [
All quantities marked WORD are only accurate once byteswapped
checkWord1(0): WORD ← 0,
tapeID(1): WORD ← 0,
dateYMD(2): WORD ← 0,
dateHM(3): DateHM ← [0,0],
name(4:0..159): PACKED ARRAY [0..20) OF CHARALL [' ],
ws2(14): WORD ← 0,
checkWord2(15): WORD ← 0
];
nameLen: NAT ~ (159+1)/16*2;
Read Tape
Directory: TYPE ~ REF DirectoryRep;
DirectoryRep: TYPE ~ RECORD [
fileNumber: [1..70],
contone: BOOL,
color: Color,
hRes, vRes: PixelsPerInch,
pixelsPerLine: CARDINAL,
lines: CARDINAL,
createDate: BasicTime.GMT,
note: ROPE,
tapeNumber: [0..999],
linesPerCylinder, sectorsPerLine, cylinders, blocks: CARDINAL
];
DirectoryOnTape: TYPE ~ MACHINE DEPENDENT RECORD [
All quantities marked WORD are only accurate once byteswapped
idn(0): WORD ← 0, -- File number on tape (range 1- 70)
ws1(1): WORD ← 0,
ws3(2:0..10): [0..1024] ← 2,
att(2:11..11): BOOLFALSE, -- TRUE if contone, FALSE if screen
ws2(2:12..15): [0..15] ← 0,
color(3): Color ← [], 
ws4(4:0..31): CARD ← 0,
hRes(6): WORD ← 0,
vRes(7): WORD ← 0,
pixelsPerLine(8): WORD ← 0,
lines(9): WORD ← 0,
linesPerCylinder(10): WORD ← 0,
sectorsPerLine(11): WORD ← 0,
createTimeYMD(12): WORD ← 0,
createTimeHM(13): DateHM ← [0, 0],
ws5(14:0..31): CARD ← 0,
note(16:0..159): PACKED ARRAY [0..20) OF CHARALL[' ],
ws6(26:0..111): ARRAY [0..7) OF WORDALL[0],
cylinders(33): WORD ← 0,
blocks(34): WORD ← 0,
tapeID(35): WORD ← 0,
ws7(36:0..31): CARD ← 0,
ws8(38:0..31): CARD ← 0
];
noteLen: NAT ~ (159+1)/16*2;
Color: TYPE ~ MACHINE DEPENDENT RECORD [
cyan(0:7..7): BOOL ← TRUE,
magenta(0:6..6): BOOL ← TRUE,
yellow(0:5..5): BOOL ← TRUE,
black(0:4..4): BOOL ← TRUE,
red(0:3..3): BOOL ← FALSE,
green(0:2..2): BOOL ← FALSE,
blue(0:1..1): BOOL ← FALSE,
ws1(0:0..0): BOOLFALSE, -- filler
negativeImage(0:15..15): BOOL ← FALSE, -- low values represent dark print/film
ws2(0:14..14): BOOLFALSE, -- filler
bytesPerPixel(0:11..13): [0 .. 7) ← 4,
ws3(0:8..10): [0 .. 7) ← 0
];
Two separate records because we need to ByteSwap the words in DateYMD
DateYMD: TYPE ~ MACHINE DEPENDENT RECORD [
Apply ByteSwap before using
year(0:9..15): [0..127], -- Year-1900
month(0:5..8): [0..15],
day(0:0..4): [0..31]
];
DateHM: TYPE ~ MACHINE DEPENDENT RECORD [
minute(0:0..7): [0..255],
hour(0:8..15): [0..255]
];
State: TYPE ~ REF StateRep;
StateRep: TYPE ~ RECORD [
newState: BOOLEANTRUE,
tapeHandle: TapeHandle ← NIL,
dir: Directory,
text: REF TEXT, --one record (32 sectors) worth
atByte: INT ← -1,  -- the current byte offset in text
sector: [0..32) ← 31, --sector in the current record
recordsLeftInCylinder: [0..19] ← 0,
linesLeftInCylinder: NAT ← 0, --dir.linesPerCylinder
bytesLeftInSector: [0..512] ← 0,
linesLeftInFile: NAT ← 0,
mapPixel: PixelProc,
dirAsText: REF TEXT NIL
];
Pixel: TYPE ~ MACHINE DEPENDENT RECORD [
SELECT OVERLAID * FROM
cmky => [cyan, magenta, yellow, black: BYTE],
rgb => [red, green, blue, blank: BYTE],
ENDCASE
];
Colors: TYPE ~ {cyan, magenta, yellow, black};
AISInfo: TYPE ~ RECORD [fileName: ROPE, raster: AIS.Raster, fRef: AIS.FRef, wRef: AIS.WRef];
suffix: ARRAY Colors OF ROPE = ["cyan", "magenta", "yellow", "black"];
Write Tape
WriteTape: PUBLIC PROC [tapeHandle: TapeHandle, files: FileSpecList, tapeNumber: [1..999] ← 102, name: ROPE] RETURNS [FileHandleList] = {
Initializes the tape for writing. Writes the master directory. Returns a list of FileHandle.
Client rasterizes into each FileHandle in turn. newLine.eof is TRUE when tape has recorded nLines worth of rasters. Client can check newLine.eof to verify that the client and the tape are still in sync.
fileHandleList: FileHandleList ← NIL;
fileNumber: NAT ← 1;
reverse: PROC[files: FileHandleList] RETURNS[r: FileHandleList] = {
r ← NIL;
FOR f: FileHandleList ← files, f.rest UNTIL f=NIL DO
r ← CONS[f.first, r];
ENDLOOP;
};
InitializeTape[tapeHandle, files, tapeNumber, name];
Build the fileHandleList
FOR f: FileSpecList ← files, f.rest UNTIL f=NIL DO
fileSpec: FileSpec ← f.first;
fileHandleList ← CONS[FileHandleFromFileSpec[fileSpec, tapeHandle, fileNumber, tapeNumber], fileHandleList];
fileNumber ← fileNumber+1;
ENDLOOP;
Reverse the fileHandleList
fileHandleList ← reverse[fileHandleList];
RETURN[fileHandleList];
};
InitializeTape: PUBLIC PROC [tapeHandle: TapeHandle, files: FileSpecList, tapeNumber: [1..999] ← 102, name: ROPE] = {
Must include correct FileSpecs for every file that may appear on the tape.
identity: Identity ← NEW[IdentityRep ← [tapeNumber: tapeNumber, date: BasicTime.Now[], name: name]];
idAsText: REF TEXT;
masterDir: REF TEXT;
fileNumber: NAT ← 1;
Write the Identity block
idAsText ← TextFromIdentity[identity];
UnixTapeOps.Rewind[tapeHandle];
UnixTapeOps.WriteRecord[tapeHandle, idAsText];
Allocate and initialize the master directory
masterDir ← AllocateBuffer[sizeOfMasterDirectory];
FOR i: NAT IN [0..sizeOfMasterDirectory) DO
masterDir[i] ← VAL[377B]; --end of directories mark
ENDLOOP;
FOR f: FileSpecList ← files, f.rest UNTIL f=NIL DO
fileSpec: FileSpec ← f.first;
dirAsText: REF TEXT ← TextFromDirectory[DirectoryFromFileSpec[fileSpec, fileNumber, tapeNumber]];
offset: NAT ← (fileNumber-1)*bytesPerDirectory;
FOR i: NAT IN [0..bytesPerDirectory) DO --put directory into the master directory
masterDir[offset+i] ← dirAsText[i];
ENDLOOP;
fileNumber ← fileNumber+1;
ENDLOOP;
Write the master directory
UnixTapeOps.WriteRecord[tapeHandle, masterDir];
UnixTapeOps.WriteFileMark[tapeHandle];
};
AddFileToTape: PUBLIC PROC [tapeHandle: TapeHandle, file: FileSpec, fileNumber, tapeNumber: NAT, inPosition: BOOLEANFALSE] RETURNS [TapesCommon.FileHandle] = {
File numbers start at 1. Files must be added in the order specified in the master directory
IF rewind, will use the file number to position the tape.
Otherwise, will start writing at the current tape position.
IF NOT inPosition THEN {
UnixTapeOps.Rewind[tapeHandle];
UnixTapeOps.ForwardSpaceFile[tapeHandle]; --skip the master directory
FOR i: NAT IN [1..fileNumber) DO
UnixTapeOps.ForwardSpaceFile[tapeHandle];
ENDLOOP;
};
RETURN[FileHandleFromFileSpec[file,tapeHandle, fileNumber, tapeNumber]];
};
FileHandleFromFileSpec: PROC [fileSpec: FileSpec, tapeHandle: TapeHandle, fileNumber, tapeNumber: NAT] RETURNS [FileHandle] ~ {
dir: Directory ← DirectoryFromFileSpec[fileSpec, fileNumber, tapeNumber];
state: State;
linesPerCylinder, nCylinders, sectorsPerLine, nRecords: NAT;
[linesPerCylinder, nCylinders, sectorsPerLine, nRecords] ← FileStructures[fileSpec];
state ← NEW[StateRep ← [
newState: TRUE,
tapeHandle: tapeHandle,
dir: dir,
text: AllocateBuffer[bytesPerRecord],
atByte: 0,
sector: 0,
recordsLeftInCylinder: recordsPerCylinder,
linesLeftInCylinder: linesPerCylinder,
bytesLeftInSector: bytesPerSector,
linesLeftInFile: fileSpec.nLines,
mapPixel: fileSpec.mapPixel,
dirAsText: TextFromDirectory[dir]
]];
RETURN[NEW[FileHandleRep ← [newLine: WriteNewLine, data: state]]];
};
DirectoryFromFileSpec: PROC [fileSpec: FileSpec, fileNumber, tapeNumber: NAT] RETURNS [dir: Directory] ~ {
linesPerCylinder, nCylinders, sectorsPerLine, nRecords: NAT;
[linesPerCylinder, nCylinders, sectorsPerLine, nRecords] ← FileStructures[fileSpec];
dir ← NEW[DirectoryRep ← [
fileNumber: fileNumber,
contone: FALSE,
color: color, --it defaults correctly
hRes: fileSpec.pixelsPerInch,
vRes: fileSpec.pixelsPerInch,
pixelsPerLine: fileSpec.nPixels,
lines: fileSpec.nLines,
createDate: BasicTime.Now[],
note: fileSpec.note,
tapeNumber: tapeNumber,
linesPerCylinder: linesPerCylinder,
sectorsPerLine: sectorsPerLine,
cylinders: nCylinders,
blocks: nRecords
]];
};
Read Tape
ReadTapeIdentity: PUBLIC PROC [tapeHandle: TapeHandle] RETURNS [Identity] = {
leaves tape ready to read the master directory
tapeMark: BOOL;
text: REF TEXT;
UnixTapeOps.Rewind[tapeHandle];
The tape identity
[tapeMark, text] ← UnixTapeOps.ReadRecord[tapeHandle: tapeHandle, buffer: text];
IF tapeMark THEN ERROR; -- something wrong here
RETURN[IdentityFromText[text]];
};
ReadMasterDirectory: PUBLIC PROC[tapeHandle: TapeHandle] RETURNS [nFiles: NAT, masterDirectory: LIST OF Directory] = {
id: Identity ← ReadTapeIdentity[tapeHandle];
mDirAsText: REF TEXTNIL;
tapeMark: BOOL;
[tapeMark, mDirAsText] ← UnixTapeOps.ReadRecord[tapeHandle: tapeHandle, buffer: NIL];
IF tapeMark THEN ERROR; -- something wrong here
Read the tape mark
[tapeMark, ] ← UnixTapeOps.ReadRecord[tapeHandle: tapeHandle, buffer: NIL];
IF NOT tapeMark THEN ERROR; -- something wrong here
[nFiles, masterDirectory] ← ParseMasterDirectory[mDirAsText];
};
ParseMasterDirectory: PROC [dir: REF TEXT] RETURNS [nFiles: NAT, masterDirectory: LIST OF Directory] = {
directory: Directory;
list: LIST OF Directory;
start, nRecords: NAT ← 0;
reverse: PROC[l: LIST OF Directory] RETURNS[r: LIST OF Directory] = {
FOR d: LIST OF Directory ← l, d.rest UNTIL d=NIL DO
r ← CONS[d.first, r];
ENDLOOP;
};
nFiles ← 0;
DO
directory ← DirectoryFromText[text: dir, start: start];
IF directory=NIL THEN EXIT;
nFiles ← nFiles+1;
nRecords ← nRecords+directory.blocks;
start ← start+bytesPerDirectory;
list ← CONS[directory, list];
ENDLOOP;
list ← reverse[list];
RETURN[nFiles, list];
};
ReadFile: PUBLIC PROC [tapeHandle: TapeHandle, fileNumber: [1..70] ← 1] RETURNS[Directory] ~ {
stateProc: PROC[line: NAT, state: State] = {};
RETURN[ReadAndProcessFile[tapeHandle, fileNumber, stateProc]];
};
ReadAndProcessFile: PROC [tape: TapeHandle, fileNumber: [1..70] ← 1, stateProc: PROC[line: NAT, state: State]] RETURNS[Directory] ~ {
Will rewind the tape, read the identity and master directory, then find the file
state: State ← NEW[StateRep ← [tapeHandle: tape]];
nFiles: NAT;
tapeMark: BOOL;
[nFiles, ] ← ReadMasterDirectory[tape];
IF fileNumber > nFiles THEN ERROR;
FOR i: NAT IN [1..fileNumber) DO
UnixTapeOps.ForwardSpaceFile[tape]; --skip over the unwanted files
ENDLOOP;
Read the directory in front of file
[tapeMark, state.dirAsText] ← UnixTapeOps.ReadRecord[tapeHandle: tape, buffer: NIL];
IF tapeMark THEN ERROR; -- something wrong here
state.dir ← DirectoryFromText[text: state.dirAsText, start: 0];
state.linesLeftInFile ← state.dir.lines;
FOR line: NAT IN [0 .. state.dir.lines) DO
ReadNewLine[state];
stateProc[line, state];
CedarProcess.CheckAbort[];
ENDLOOP;
FOR i: NAT IN [0..state.recordsLeftInCylinder) DO
ReadNewRecord[state];
ENDLOOP;
[tapeMark, state.text] ← UnixTapeOps.ReadRecord[tapeHandle: tape, buffer: state.text];
IF NOT tapeMark THEN ERROR; -- something wrong here
RETURN[state.dir];
};
FileTo4AIS: PUBLIC PROC [tapeHandle: TapeHandle, fileNumber: [1..70] ← 1, aisRootName: ROPE, mapPixel: PixelProc, reduce: NAT ← 4] RETURNS [Directory] ~ {
AIS files are 1/reduce2 the size of the original (ie for reduce = 4, 300 spi => 75 spi)
ais: ARRAY Colors OF AISInfo;
dir: Directory;
Done: PROC [c: Colors] = {
AIS.CloseWindow[w: ais[c].wRef];
AIS.CloseFile[f: ais[c].fRef];
};
stateProc: PROC[line: NAT, state: State] = {
SetUp: PROC [c: Colors] = {
ais[c].raster ← NEW[AIS.RasterPart ← [scanCount: 1+state.dir.lines/reduce,
scanLength: 1+state.dir.pixelsPerLine/reduce, scanMode: rd, bitsPerPixel: 8, linesPerBlock: -1, paddingPerBlock: 0]];
ais[c].fileName ← Rope.Cat[r1: aisRootName, r2: "-", r3: suffix[c], r4: ".ais"];
ais[c].fRef ← AIS.CreateFile[name: ais[c].fileName, raster: ais[c].raster];
ais[c].wRef ← AIS.OpenWindow[f: ais[c].fRef];
MakeScreenToByte[mapPixel];
};
IF state.newState THEN {
SetUp[cyan]; SetUp[magenta]; SetUp[yellow]; SetUp[black];
state.newState ← FALSE;
};
FOR pixel: NAT IN [0 .. state.dir.pixelsPerLine) DO
p: Pixel ← ReadPixel[state];
store: PROC [c: Colors, data: BYTE] = {
lastPixel: NAT ← state.dir.pixelsPerLine-1;
data ← ScreenToByte[data];
AIS.WriteSample[w: ais[c].wRef, value: data, line: line/reduce, pixel: (lastPixel-pixel)/reduce];
};
IF line MOD reduce #0 OR pixel MOD reduce #0 THEN LOOP;
store[cyan, p.cyan];
store[magenta, p.magenta];
store[yellow, p.yellow];
store[black, p.black];
ENDLOOP;
};
dir ← ReadAndProcessFile[tapeHandle, fileNumber, stateProc];
Done[cyan]; Done[magenta]; Done[yellow]; Done[black];
RETURN[dir];
};
Raster handlers
bytesPerSector: NAT ~ 512;
bytesPerRecord: NAT ~ 16384;
sizeOfMasterDirectory: NAT ~5602;
bytesPerDirectory: NAT ~80;
recordsPerCylinder: NAT ~ 19;
sectorsPerRecord: NAT ~ 32;
lastSector: NAT ← sectorsPerRecord-1;
For tape file writing
WriteNewLine: PROC[self: FileHandle, line: PixelBuffer] RETURNS[eof: BOOLEAN]= {
state: State ← NARROW[self.data];
IF line.samplesPerPixel # 4 THEN ERROR;
IF state.newState THEN { --first time with this state
MakeByteToScreen[state.mapPixel]; --make the byte translation table
UnixTapeOps.WriteRecord[state.tapeHandle, state.dirAsText]; --write the directory
state.newState ← FALSE;
};
SELECT TRUE FROM
state.linesLeftInCylinder=0 => WriteNewCylinder[state];
state.atByte#0 AND state.linesLeftInCylinder#0 => WriteNewSector[state];
ENDCASE; --state.atByte=0 AND linesLeftInCylinder#0
FOR i: NAT DECREASING IN [0..line.length) DO
p: Pixel;
p.cyan ← ByteToScreen[line[0][i]];
p.magenta ← ByteToScreen[line[1][i]];
p.yellow ← ByteToScreen[line[2][i]];
p.black ← ByteToScreen[line[3][i]];
WritePixel[state,p];
ENDLOOP;
state.linesLeftInFile ← state.linesLeftInFile-1;
state.linesLeftInCylinder ← state.linesLeftInCylinder-1;
IF state.linesLeftInFile = 0 THEN { --write the rest of the records and the tape mark
UNTIL state.recordsLeftInCylinder=0 DO
WriteNewRecord[state];
ENDLOOP;
UnixTapeOps.WriteFileMark[state.tapeHandle];
eof ← TRUE;
}
ELSE eof ← FALSE;
};
WritePixel: PROC [state: State, p: Pixel] ~ {
IF state.bytesLeftInSector=0 THEN WriteNewSector[state];
state.text[state.atByte] ← VAL[p.cyan];
state.text[state.atByte+1] ← VAL[p.magenta];
state.text[state.atByte+2] ← VAL[p.yellow];
state.text[state.atByte+3] ← VAL[ p.black];
state.atByte←state.atByte+4;
state.bytesLeftInSector← state.bytesLeftInSector-4;
};
WriteNewSector: PROC [state: State] ~ {
IF state.sector>=lastSector THEN WriteNewRecord[state]
ELSE {state.sector ← state.sector+1; state.atByte←state.sector*bytesPerSector};
state.bytesLeftInSector ← bytesPerSector;
};
WriteNewCylinder: PROC [state: State] ~ {
UNTIL state.recordsLeftInCylinder=0 DO WriteNewRecord[state]; ENDLOOP;
state.recordsLeftInCylinder ← recordsPerCylinder;
state.atByte← 0;
state.sector ← 0;
state.bytesLeftInSector ← bytesPerSector;
state.linesLeftInCylinder ← state.dir.linesPerCylinder;
};
WriteNewRecord: PROC [state: State] ~ {
UnixTapeOps.WriteRecord[state.tapeHandle, state.text];
state.atByte← 0;
state.sector ← 0;
state.recordsLeftInCylinder ← state.recordsLeftInCylinder-1;
};
For tape file reading
ReadNewLine: PROC [state: State] ~ { --aligns state.atByte to start of next line
IF state.linesLeftInCylinder=0 THEN ReadNewCylinder[state] ELSE ReadNewSector[state];
state.linesLeftInCylinder ← state.linesLeftInCylinder-1;
state.linesLeftInFile ← state.linesLeftInFile-1;
};
ReadNewCylinder: PROC [state: State] ~ {
UNTIL state.recordsLeftInCylinder=0 DO ReadNewRecord[state]; ENDLOOP;
state.recordsLeftInCylinder ← recordsPerCylinder;
ReadNewRecord[state]; --sets atByte and sector
state.bytesLeftInSector ← bytesPerSector;
state.linesLeftInCylinder ← state.dir.linesPerCylinder;
};
ReadNewSector: PROC [state: State] ~ { --aligns state.atByte to start of next Sector
IF state.sector>=lastSector THEN ReadNewRecord[state]
ELSE {state.sector ← state.sector+1; state.atByte←state.sector*512};
state.bytesLeftInSector ← bytesPerSector;
};
ReadNewRecord: PROC [state: State] ~ { --reads a tape record
tapeMark: BOOL;
[tapeMark, state.text] ← UnixTapeOps.ReadRecord[state.tapeHandle, state.text];
IF tapeMark THEN ERROR; -- out of sync
state.atByte← 0;
state.sector ← 0;
state.recordsLeftInCylinder ← state.recordsLeftInCylinder-1;
};
ReadPixel: PROC [state: State] RETURNS [p: Pixel] ~ { --gets pixels out of the line
IF state.bytesLeftInSector=0 THEN ReadNewSector[state];
p.cyan ← ORD[state.text[state.atByte]];
p.magenta ← ORD[state.text[state.atByte+1]];
p.yellow ← ORD[state.text[state.atByte+2]];
p.black ← ORD[state.text[state.atByte+3]];
state.atByte←state.atByte+4;
state.bytesLeftInSector← state.bytesLeftInSector-4;
};
Convert from Tape to Cedar formats
Error: PUBLIC ERROR [r: ROPE] = CODE;
TextFromIdentity: PROC [identity: Identity] RETURNS [text: REF TEXT] ~ {
onTape: IdentityOnTape ← [];
size: NATSIZE[IdentityOnTape];
text ← AllocateBuffer[size*2];
onTape.checkWord1 ← ByteSwap[648Ch];
onTape.tapeID ← ByteSwap[identity.tapeNumber];
[onTape.dateYMD, onTape.dateHM] ← ToDate[identity.date];
onTape.name ← RopeToArray[identity.name];
onTape.checkWord2 ← ByteSwap[7D47h];
TRUSTED {
placeToPut: LONG POINTERLOOPHOLE[text, LONG POINTER] + SIZE[TEXT[0]];
placeToStart: LONG POINTER ← @onTape;
PrincOpsUtils.LongCopy[from: placeToStart, nwords: size, to: placeToPut];
};
};
IdentityFromText: PROC [text: REF TEXT] RETURNS [identity: Identity] ~ {
i: NAT ← 0;
P: PROC RETURNS [c: CHAR] = {c ← onTape.name[i]; i ← i + 1};
placeToStart: LONG POINTERLOOPHOLE[text, LONG POINTER] + SIZE[TEXT[0]];
onTape: IdentityOnTape;
TRUSTED {
placeToPut: LONG POINTER ← @onTape;
PrincOpsUtils.LongCopy[from: placeToStart, nwords: SIZE[IdentityOnTape], to: placeToPut];
};
IF ByteSwap[onTape.checkWord1] # 648Ch THEN ERROR Error["bad Identity checkword 1"];
IF ByteSwap[onTape.checkWord2] # 7D47h THEN ERROR Error["bad Identity checkword 2"];
identity ← NEW[IdentityRep];
identity.tapeNumber ← ByteSwap[onTape.tapeID];
identity.date ← FromDate[onTape.dateYMD, onTape.dateHM];
identity.name ← Rope.FromProc[nameLen, P];
identity.name ← StripRope[identity.name];
};
ValHRes: ARRAY PixelsPerInch OF WORD ← [ppi300: 006000B, ppi450: 010000B, ppi600: 014000B];
ValVRes: ARRAY PixelsPerInch OF WORD ← [ppi300: 005717B, ppi450: 010667B, ppi600: 013637B];
HResFromVal: PROC [val: WORD] RETURNS [ppi: PixelsPerInch] ~ {
ppi ← SELECT val FROM
006000B => ppi300, 010000B => ppi450, 014000B => ppi600, ENDCASE => ERROR;
};
VResFromVal: PROC [val: WORD] RETURNS [ppi: PixelsPerInch] ~ {
ppi ← SELECT val FROM
005717B => ppi300, 010667B => ppi450, 013637B => ppi600, ENDCASE => ERROR;
};
TextFromDirectory: PROC [directory: Directory] RETURNS [text: REF TEXT] ~ {
size: NATSIZE[DirectoryOnTape];
onTape: DirectoryOnTape ← [];
text ← AllocateBuffer[size*2];
onTape.idn ← ByteSwap[directory.fileNumber];
onTape.att ← directory.contone;
onTape.color ← directory.color;
onTape.hRes ← ByteSwap[ValHRes[directory.hRes]];
onTape.vRes ← ByteSwap[ValVRes[directory.vRes]];
onTape.pixelsPerLine ← ByteSwap[directory.pixelsPerLine];
onTape.lines ← ByteSwap[directory.lines];
[onTape.createTimeYMD, onTape.createTimeHM] ← ToDate[directory.createDate];
onTape.note ← RopeToArray[directory.note];
onTape.tapeID ← ByteSwap[directory.tapeNumber];
onTape.linesPerCylinder ← ByteSwap[directory.linesPerCylinder];
onTape.sectorsPerLine ← ByteSwap[directory.sectorsPerLine];
onTape.cylinders ← ByteSwap[directory.cylinders];
onTape.blocks ← ByteSwap[directory.blocks];
TRUSTED {
placeToPut: LONG POINTERLOOPHOLE[text, LONG POINTER] + SIZE[TEXT[0]];
placeToStart: LONG POINTER ← @onTape;
PrincOpsUtils.LongCopy[from: placeToStart, nwords: size, to: placeToPut];
};
};
DirectoryFromText: PROC [text: REF TEXT, start: CARD] RETURNS [directory: Directory] ~ {
placeToStart: LONG POINTERLOOPHOLE[text, LONG POINTER] + SIZE[TEXT[0]] + start/2;
i: NAT ← 0;
P: PROC RETURNS [c: CHAR] = {c ← onTape.note[i]; i ← i + 1};
onTape: DirectoryOnTape ← [];
TRUSTED {
placeToPut: LONG POINTER ← @onTape;
PrincOpsUtils.LongCopy[placeToStart, SIZE[DirectoryOnTape], placeToPut];
};
IF onTape.idn = 0FFFFh THEN RETURN [NIL]; -- The last one
directory ← NEW[DirectoryRep];
directory.fileNumber ← ByteSwap[onTape.idn];
directory.contone ← onTape.att;
directory.color ← onTape.color;
directory.hRes ← HResFromVal[ByteSwap[onTape.hRes]];
directory.vRes ← VResFromVal[ByteSwap[onTape.vRes]];
directory.pixelsPerLine ← ByteSwap[onTape.pixelsPerLine];
directory.lines ← ByteSwap[onTape.lines];
directory.createDate ← FromDate[onTape.createTimeYMD, onTape.createTimeHM];
directory.note ← Rope.FromProc[noteLen, P];
directory.note ← StripRope[directory.note];
directory.tapeNumber ← ByteSwap[onTape.tapeID];
directory.linesPerCylinder ← ByteSwap[onTape.linesPerCylinder];
directory.sectorsPerLine ← ByteSwap[onTape.sectorsPerLine];
directory.cylinders ← ByteSwap[onTape.cylinders];
directory.blocks ← ByteSwap[onTape.blocks];
};
ByteSwap: PROC [in: WORD] RETURNS [WORD] = INLINE {
RETURN[Basics.BITOR[LOOPHOLE[in, Basics.BytePair].low*256,
LOOPHOLE[in, Basics.BytePair].high]];
};
ByteSwapProc: PROC [in: WORD] RETURNS [out: WORD] = {
out ← Basics.BITOR[LOOPHOLE[in, Basics.BytePair].low*256,
LOOPHOLE[in, Basics.BytePair].high];
};
StripRope: PROC [rope: ROPE] RETURNS [ROPE] ~ {
last: INT ← Rope.Length[rope];
FOR i: INT DECREASING IN [0 .. Rope.Length[rope]) DO
IF Rope.Fetch[base: rope, index: i] = '\000 THEN last ← last - 1 ELSE EXIT;
ENDLOOP;
RETURN[Rope.Substr[base: rope, start: 0, len: last]];
};
RopeToArray: PROC [rope: ROPE] RETURNS [array: PACKED ARRAY [0..20) OF CHAR] ~ {
length: NAT ← Rope.Length[rope];
FOR i: NAT IN [0..length) DO
array[i] ← Rope.Fetch[base: rope, index: i];
ENDLOOP;
FOR i: NAT IN [length..20) DO
array[i] ← '\000;
ENDLOOP;
};
ToDate: PROC [t: BasicTime.GMT] RETURNS [rawYMD: WORD, dateHM: DateHM] ~ {
unpacked: BasicTime.Unpacked ← BasicTime.Unpack[t];
dateYMD: DateYMD;
dateYMD.year ← unpacked.year - 1900;
dateYMD.month ← SELECT unpacked.month FROM
January => 1, February=> 2, March=> 3, April=> 4, May => 5, June => 6, July=> 7,
August => 8, September => 9, October => 10, November => 11, December => 12,
ENDCASE => ERROR;
dateYMD.day ← unpacked.day;
dateHM.hour ← unpacked.hour;
dateHM.minute ← unpacked.minute;
rawYMD ← ByteSwapProc[LOOPHOLE[dateYMD]];
};
FromDate: PROC [rawYMD: WORD, dateHM: DateHM] RETURNS [t: BasicTime.GMT] ~ {
unpacked: BasicTime.Unpacked;
dateYMD: DateYMD ← LOOPHOLE[ByteSwapProc[rawYMD]];
unpacked.year ← dateYMD.year+1900;
unpacked.month ← SELECT dateYMD.month FROM
1 => January, 2=> February, 3=> March, 4=> April, 5 => May, 6 => June, 7=> July,
8 => August, 9 => September, 10 => October, 11 => November, 12 => December,
ENDCASE => unspecified;
IF dateYMD.day NOT IN [1..31] THEN dateYMD.day ← 1;
IF dateHM.hour NOT IN [0..12] THEN dateHM.hour ← 0;
IF dateHM.minute NOT IN [0..60] THEN dateHM.minute ← 0;
unpacked.day ← dateYMD.day;
unpacked.hour ← dateHM.hour;
unpacked.minute ← dateHM.minute;
t ← BasicTime.nullGMT;
t ← BasicTime.Pack[unpacked ! BasicTime.OutOfRange => CONTINUE];
RETURN[t];
};
ByteMapping, Pixel Procs, etc
ScreenToByte: ARRAY[0..255] OF BYTEALL[0];
ByteToScreen: ARRAY[0..255] OF BYTEALL[0];
MakeScreenToByte: PROC[pixelProc: PixelProc] = {
FOR i: NAT IN[0..255] DO
ScreenToByte[i] ← pixelProc[i];
ENDLOOP;
};
MakeByteToScreen: PROC[pixelProc: PixelProc] = {
FOR i: NAT IN[0..255] DO
ByteToScreen[i] ← pixelProc[i];
ENDLOOP;
};
CrosfieldToByte: PUBLIC PixelProc = {
In the Crosfield file, pixel = (DotPercent*2)+25
Except that 21=0 and 229=100 on some page makeup systems
SELECT TRUE FROM
pixel =21 => RETURN[0];
pixel =229 => RETURN[255];
pixel IN [25..225] => RETURN[ Real.Round[255*0.01* (pixel -25)/2.0]];
pixel < 25 => RETURN[0];
pixel > 225 => RETURN[255];
ENDCASE => ERROR;
};
KOToByte: PUBLIC PixelProc = {
data on tape runs from 6 to 245
dotPercent: REAL ← (pixel -6)*(100.0/239.0);
dotPercent ← MIN[MAX[dotPercent, 0], 255];
RETURN[Real.Round[dotPercent*2.55]];
};
ByteToCrosfield: PUBLIC PixelProc = {
SELECT TRUE FROM
pixel =0 => RETURN[21];
pixel =255 => RETURN[229];
pixel IN [1..254] => RETURN[ Real.Round[(100.0*pixel /255.0)*2+25]];
ENDCASE => ERROR;
};
IdentityByte: PUBLIC PixelProc = {RETURN[pixel]}; --use for read for KedieOrent tapes
AllocateBuffer: PROC[sizeInBytes: INT] RETURNS[t: REF TEXT] = {
t ← RefText.New[sizeInBytes];
t.length ← sizeInBytes;
};
FileStructures: PROC [fileSpec: FileSpec] RETURNS [linesPerCylinder, nCylinders, sectorsPerLine, nRecords: NAT] = {
sectorsPerLine ← Real.Round[0.5+(fileSpec.nPixels*4.0)/bytesPerSector];
linesPerCylinder ← (recordsPerCylinder*sectorsPerRecord)/sectorsPerLine;
nCylinders ← Real.Round[0.5+REAL[fileSpec.nLines]/linesPerCylinder];
nRecords ← nCylinders*recordsPerCylinder;
};
sizeRecordGap: REAL ← 0.8; --the inter-record gap, in inches
sizeFileGap: REAL ← 2; --the inter-file gap, in feet
SizeOfHeader: PUBLIC PROC [density: UnixTapeOps.Density ← GCR6250] RETURNS [sizeInFeet: REAL] = {
d: INTEGERSELECT density FROM GCR6250 => 6250, PE1600 => 1600, ENDCASE => ERROR;
id: REAL ← sizeRecordGap+ SIZE[IdentityOnTape]/d;
masterDir: REAL ← sizeRecordGap+ sizeOfMasterDirectory/d;
RETURN[sizeFileGap+(id+masterDir)/12.0];
};
SizeOfFile: PUBLIC PROC [fileSpec: FileSpec, density: UnixTapeOps.Density ← GCR6250] RETURNS [sizeInFeet: REAL] = {
d: INTEGERSELECT density FROM GCR6250 => 6250, PE1600 => 1600, ENDCASE => ERROR;
dir: REAL ← sizeRecordGap+ SIZE[DirectoryOnTape]/d;
records: REAL;
nCylinders: NAT;
[,nCylinders] ← FileStructures[fileSpec];
records ← recordsPerCylinder*nCylinders*(sizeRecordGap+REAL[bytesPerRecord]/d); 
RETURN[sizeFileGap+(dir+records)/12.0];
};
SizeOfList: PUBLIC PROC [files: FileSpecList, density: UnixTapeOps.Density ← GCR6250] RETURNS [sizeInFeet: REAL] = {
Assumes all files are on one tape
sizeInFeet ← SizeOfHeader[density];
FOR f: FileSpecList ← files, f.rest UNTIL f=NIL DO
sizeInFeet ← SizeOfFile[f.first, density]+sizeInFeet;
ENDLOOP;
};
END.