-- ColorDisplayHeadDorado.mesa
-- Last edited by Doug Wyatt on 31-Mar-82 23:12:06

DIRECTORY
  ColorDisplayFace USING [Color, Mode],
  DeviceCleanup USING [Await, Item, Reason],
  DoradoInputOutput USING [InputNoPE, RWMufMan],
  Environment USING [Base, bitsPerWord, first64K, PageCount, PageNumber, wordsPerPage],
  HeadStartChain USING [Start],
  Inline USING [BITAND, BITOR, BITSHIFT, LongDiv, LongDivMod, LongMult],
  ProcessorFace USING [GetClockPulses, microsecondsPerHundredPulses],
  RuntimeInternal USING [WorryCallDebugger];

ColorDisplayHeadDorado: PROGRAM
IMPORTS DeviceCleanup, DoradoInputOutput, RemainingHeads: HeadStartChain,
  Inline, ProcessorFace, RuntimeInternal
EXPORTS ColorDisplayFace, HeadStartChain = {

ErrorHalt: PROC = {RuntimeInternal.WorryCallDebugger["Error in ColorDisplayHeadDorado"L]};

RPtr: TYPE = Environment.Base RELATIVE POINTER; -- relative to absolute address 0
rpNIL: RPtr = LOOPHOLE[0];

Mode: TYPE = ColorDisplayFace.Mode;
Color: TYPE = ColorDisplayFace.Color;

CSBState: TYPE = MACHINE DEPENDENT RECORD [mcb: MCBPtr];
csb: LONG POINTER TO CSBState = LOOPHOLE[LONG[177414B]];

-- Monitor control block
MCBPtr: TYPE = Environment.Base RELATIVE POINTER TO MCB; -- in first64k
MCB: TYPE = MACHINE DEPENDENT RECORD [
  seal: CARDINAL ← mcbSeal,
  flags: MCBFlags,
  achanCB: ChanCBPtr,
  bchanCB: ChanCBPtr,
  colorCB: ColorCBPtr
  ];

mcbSeal: CARDINAL = 177456B;

Flag: TYPE = BOOLEAN ← FALSE;

MCBFlags: TYPE = MACHINE DEPENDENT RECORD [
  unused: [0..777B] ← 0,
  m: Flag,  -- minimixer (unimplemented)
  vc: Flag,  -- vertical control
  hc: Flag,  -- horizontal control
  clk: Flag,  -- clock
  a: Flag,  -- A table
  b: Flag,  -- B table
  c: Flag  -- C table
  ];

-- Channel control block

ChanCBPtr: TYPE = Environment.Base RELATIVE POINTER TO ChanCB; -- in first64k
ChanCB: TYPE = RECORD [
  link: ChanCBPtr ← rpNIL, -- next ChanCB in chain, or NIL
  wordsPerLine: CARDINAL,  -- words per bitmap scanline
  bitmap: LONG POINTER ← NIL,  -- pointer to bitmap bits (must be even)
  linesPerField: CARDINAL,  -- scan lines per field (bitmap height / 2)
  pixelsPerLine: CARDINAL,  -- visible pixels/scanline + 377B
  leftMargin: CARDINAL,  -- left margin + marginOffset[monitortype]
  scan: ScanControl  -- scan control word (see below)
  ];
pplOffset: CARDINAL = 377B; -- offset for pixelsPerLine

-- Margin offsets for various monitors
--  AltoTerminal: 107B, Ramtek525: 66B, Ramtek1000: 0,
--  ConracRQB525: 66B, ConracRQB875: 107B, ConracRQB1225: 0,

-- Scan control word

ScanControl: TYPE = MACHINE DEPENDENT RECORD [
  unused: [0..77B] ← 0,
  mode24: BOOLEAN, -- 24 bit per pixel mode
  aChannelOnly: BOOLEAN, -- if TRUE, disable B channel
  bBypass: BOOLEAN, -- B channel bypassed to BTable
  a8b2: BOOLEAN, -- 8 bits from A, 2 bits from B (a6b4 if FALSE)
  res: ScanRes ← full, -- resolution
  size: [0..17B] ← 1 -- bits per pixel; must be 1, 2, 4, or 8
  ];
ScanRes: TYPE = MACHINE DEPENDENT{quarter(0), half(2), full(3)};

-- Color control block

ColorCBPtr: TYPE = Environment.Base RELATIVE POINTER TO ColorCB; -- in first64k
ColorCB: TYPE = MACHINE DEPENDENT RECORD [
  aTable(0:0..31): LONG POINTER TO ATableImage,  -- pointer to color table A
  bTable(2:0..31): LONG POINTER TO BCTableImage, -- pointer to color table B
  cTable(4:0..31): LONG POINTER TO BCTableImage, -- pointer to color table C
  miniMixer(6:0..31): LONG POINTER TO MiniMixerImage ← NIL, -- pointer to MiniMixer table
  vcontrol(10B:0..47): VControl, -- vertical control
  hcontrol(13B:0..47): HControl, -- horizontal control
  ccontrol(16B:0..15): CControl, -- clock control
  reserved(17B:0..15): CARDINAL ← 0
  ];

-- Vertical control information (loaded via vc flag)
VControl: TYPE = MACHINE DEPENDENT RECORD [
  VBtoVS: [0..377B], VStoVS: [0..377B],
  VStoVB: CARDINAL,
  VisibleLines: CARDINAL];
-- Horizontal control information (loaded via hc flag)
HControl: TYPE = MACHINE DEPENDENT RECORD [
  HRamMaxAddr: CARDINAL,
  HBLeadLength: [0..377B], HSTrailAddr: [0..377B],
  HBTrailLength: CARDINAL];
-- Clock control information (loaded via clk flag)
CControl: TYPE = MACHINE DEPENDENT RECORD [
  zero: [0..17B] ← 0, mul: [0..377B], div: [0..17B]];

ATableIndex: TYPE = [0..1024);
ATableImage: TYPE = ARRAY ATableIndex OF ATableEntry;
BCTableIndex: TYPE = [0..256);
BCTableImage: TYPE = ARRAY BCTableIndex OF BCTableEntry;
MiniMixerIndex: TYPE = [0..256);
MiniMixerImage: TYPE = ARRAY MiniMixerIndex OF MiniMixerEntry;

ATableEntry: TYPE = MACHINE DEPENDENT RECORD [
  zeroL(0:0..3): [0..17B] ← 0,  -- always zero
  redL(0:4..7): [0..17B],  -- low order 4 bits of red value
  blue(0:8..15): [0..377B],  -- blue value
  zeroH(1:0..3): [0..17B] ← 0,  -- always zero
  green(1:4..11): [0..377B],  -- green value
  redH(1:12..15): [0..17B]  -- high order 4 bits of red value
  ];

BCTableEntry: TYPE = MACHINE DEPENDENT RECORD [
  zero(0:0..7): [0..377B] ← 0,  -- always zero
  value(0:8..15): [0..377B]  -- table value
  ];

MiniMixerEntry: TYPE = MACHINE DEPENDENT RECORD [
  address(0:0..7): [0..377B], data(0:8..15): [0..377B]];

-- Interface variables

-- The following are valid as soon as the module start code has run (see Start)

globalStateSize: PUBLIC CARDINAL ← SIZE[MCB]+3*SIZE[ChanCB]+SIZE[ColorCB]; -- for Initialize
displayType: PUBLIC CARDINAL ← 0; -- display type; 0 means display not available
oldRev: BOOLEAN ← FALSE; -- TRUE if color board has an old revision level (less than Cj)

Standard525: CARDINAL = 1; -- displayType for Ramtek or Hitachi 525 line monitor
Hitachi3619: CARDINAL = 2; -- displayType for Hitachi UHR monitor

pixelsPerInch: PUBLIC CARDINAL ← 0; -- Size of a pixel
mixedRG: PUBLIC BOOLEAN ← TRUE; -- in fullmode, red and green alternate in A bitmap

width,height: PUBLIC CARDINAL ← 0; -- Dimensions of current raster in pixels
baseA,baseB,baseC: PUBLIC LONG POINTER ← NIL; -- bitmap addresses
bplA,bplB,bplC: PUBLIC CARDINAL ← 0; -- bitmap bits per line

-- Internal globals

screenwidth,screenheight: CARDINAL; -- screen dimensions in pixels

initialized: BOOLEAN ← FALSE; -- control blocks are allocated
connected: BOOLEAN ← FALSE; -- bitmaps and tables are allocated, globals are set
turnedon: BOOLEAN ← FALSE; -- display is on, bitmaps and tables are resident
fullmode: BOOLEAN ← FALSE; -- connected in 24 bit per pixel mode
mapmode: BOOLEAN ← FALSE; -- connected in a mapped mode

-- The following globals are valid when initialized=TRUE (see Initialize)
-- first64k-relative pointers to global state:
rpMCB: MCBPtr ← rpNIL;
rpTChan: ChanCBPtr ← rpNIL;
rpAChan: ChanCBPtr ← rpNIL;
rpBChan: ChanCBPtr ← rpNIL;
rpColor: ColorCBPtr ← rpNIL;
-- long pointers to same:
mcb: LONG POINTER TO MCB ← NIL;
tchan: LONG POINTER TO ChanCB ← NIL;
achan: LONG POINTER TO ChanCB ← NIL;
bchan: LONG POINTER TO ChanCB ← NIL;
color: LONG POINTER TO ColorCB ← NIL;

-- The following globals are valid when connected=TRUE (see Connect)

bppA,bppB: [0..LAST[INTEGER]] ← 0; -- bits per pixel
showA,showB: BOOLEAN ← FALSE; -- to be shown

atable: LONG POINTER TO ATableImage ← NIL;
btable,ctable: LONG POINTER TO BCTableImage ← NIL; -- used only in fullmode

ashift,bshift: INTEGER ← 0;
amask,bmask: WORD ← 0;

Initialize: PUBLIC PROC [globalState: RPtr] = {
  alloc: RPtr ← globalState;
  end: RPtr ← globalState+globalStateSize;
  Alloc: PROC[words: CARDINAL] RETURNS[RPtr] = INLINE {
    p: RPtr ← alloc; alloc ← alloc + words; RETURN[p] };
  IF initialized THEN ErrorHalt[];
  -- Allocate from globalState block:
  mcb ← @Environment.first64K[rpMCB ← Alloc[SIZE[MCB]]];
  tchan ← @Environment.first64K[rpTChan ← Alloc[SIZE[ChanCB]]];
  achan ← @Environment.first64K[rpAChan ← Alloc[SIZE[ChanCB]]];
  bchan ← @Environment.first64K[rpBChan ← Alloc[SIZE[ChanCB]]];
  color ← @Environment.first64K[rpColor ← Alloc[SIZE[ColorCB]]];
  IF LOOPHOLE[alloc,CARDINAL]>LOOPHOLE[end,CARDINAL] THEN ErrorHalt[];
  tchan↑ ← [link: rpNIL, wordsPerLine: 0, bitmap: NIL, linesPerField: 0,
    pixelsPerLine: pplOffset, leftMargin: 0, scan: ];
  color↑ ← [aTable: NIL, bTable: NIL, cTable: NIL, vcontrol: , hcontrol: , ccontrol: ];
  mcb↑ ← [flags: [], achanCB: rpNIL, bchanCB: rpNIL, colorCB: rpColor];
  initialized ← TRUE;
  };

Lg: TYPE = [0..4); -- logarithm base 2
lg1: Lg = 0; lg2: Lg = 1; lg4: Lg = 2; lg8: Lg = 3;
Exp: PROC[lg: Lg] RETURNS[CARDINAL] = INLINE { RETURN[Inline.BITSHIFT[1,lg]] }; -- 2↑lg

PagesForWords: PROC[words: LONG CARDINAL] RETURNS[CARDINAL] = INLINE {
  q,r: CARDINAL; [q,r] ← Inline.LongDivMod[words,Environment.wordsPerPage];
  RETURN[IF r>0 THEN q+1 ELSE q] };

WordsForPages: PROC[pages: CARDINAL] RETURNS[LONG CARDINAL] = INLINE {
  RETURN[Inline.LongMult[pages,Environment.wordsPerPage]] };

LPFromPage: PROC[page: Environment.PageNumber] RETURNS[LONG POINTER] = INLINE {
  RETURN[LOOPHOLE[Inline.LongMult[page, Environment.wordsPerPage]]] };

-- words per line, guaranteed to be even; resulting width in pixels >= w
Wpl: PROC[w: CARDINAL, lbpp: Lg] RETURNS[CARDINAL] = INLINE {
  bits: CARDINAL ← w*Exp[lbpp]; RETURN[((bits+31)/32)*2] };

WordsForBitmap: PROC[w,h: CARDINAL, lbpp: Lg] RETURNS[LONG CARDINAL] = INLINE {
  RETURN[Inline.LongMult[Wpl[w,lbpp],h]] };

HasMode: PUBLIC PROC[mode: Mode] RETURNS[BOOLEAN] = {
  IF displayType=0 THEN RETURN[FALSE]; -- no display
  -- old revision boards can't run both channels at the same time
  IF oldRev AND (mode.full OR (mode.useA AND mode.useB)) THEN RETURN[FALSE];
  IF mode.full THEN {
    SELECT displayType FROM
      Standard525 => RETURN[TRUE];
      Hitachi3619 => RETURN[FALSE];
      ENDCASE; -- drop through
    }
  ELSE {
    a,b: CARDINAL ← 0; -- bits per pixel
    IF mode.useA THEN a ← Exp[mode.lgBitsPerPixelA];
    IF mode.useB THEN b ← Exp[mode.lgBitsPerPixelB];
    IF a>8 OR b>4 OR (a+b)>10 THEN RETURN[FALSE]; -- limits of Dorado hardware
    SELECT displayType FROM
      Standard525 => RETURN[TRUE]; -- all possible modes supported
      Hitachi3619 => RETURN[a<8 OR b=0]; -- can't use B channel if a=8
      ENDCASE; -- drop through
    };
  ErrorHalt[]; -- bogus displayType ("can't happen")
  RETURN[FALSE];
  };

PagesForMode: PUBLIC PROC[mode: Mode] RETURNS[Environment.PageCount] = {
  a,b: CARDINAL ← 0; -- bits per pixel
  words: LONG CARDINAL ← 0;
  IF NOT HasMode[mode] THEN RETURN[0];
  words ← SIZE[ATableImage]; -- atable always required
  IF mode.full THEN {
    a ← 16; b ← 8; -- bitmap A has double pixels
    words ← words + 2*SIZE[BCTableImage]; -- need 2 more color tables
    }
  ELSE {
    IF mode.useA THEN a ← Exp[mode.lgBitsPerPixelA]; -- bitmap A
    IF mode.useB THEN b ← Exp[mode.lgBitsPerPixelB]; -- bitmap B
    };
  words ← words + Inline.LongMult[(a+b)*screenwidth/Environment.bitsPerWord,screenheight];
  RETURN[PagesForWords[words]];
  };

MapIndex: PROC[a,b: CARDINAL] RETURNS[ATableIndex] = INLINE { OPEN Inline;
  RETURN[BITOR[BITAND[BITSHIFT[a,ashift],amask],BITAND[BITSHIFT[b,bshift],bmask]]] };

SetSize: PROC[mode: Mode, pages: Environment.PageCount] RETURNS[BOOLEAN] = {
  w: CARDINAL ← screenwidth;
  h: CARDINAL ← screenheight;
  avail: LONG CARDINAL ← 0;
  extra: CARDINAL ← 0;
  IF pages>=PagesForMode[mode] THEN { width ← w; height ← h; RETURN[TRUE] };
  IF NOT mode.full THEN RETURN[FALSE];
  avail ← WordsForPages[pages]; -- available words
  extra ← SIZE[ATableImage] + 2*SIZE[BCTableImage]; -- overhead for tables
  IF avail>extra THEN avail ← avail - extra ELSE RETURN[FALSE];
  -- Shrink the raster width, observing necessary constraints.
  -- Bitmaps must have an even number of words per line, so width must shrink
  -- in steps of 4 pixels. Each 4 pixels of width require 6*h words.
  w ← 4*Inline.LongDiv[avail,6*h];
  IF w<screenwidth/2 THEN {
    w ← screenwidth/2;
    -- Shrink the raster height. Bitmaps must have an even number of lines,
    -- so height must shrink in steps of 2. Each 2 lines require 3*w words.
    h ← 2*Inline.LongDiv[avail,3*w];
    };
  IF h>0 THEN { width ← w; height ← h; RETURN[TRUE] } ELSE RETURN[FALSE];
  };

Connect: PUBLIC PROC [mode: Mode,
  firstPage: Environment.PageNumber, nPages: Environment.PageCount] = {
  allocBegin: LONG POINTER ← LPFromPage[firstPage]; -- beginning address
  allocLimit: LONG CARDINAL = WordsForPages[nPages]; -- maximum words available
  allocCount: LONG CARDINAL ← 0; -- words allocated so far
  Alloc: PROC[words: LONG CARDINAL] RETURNS[LONG POINTER] = INLINE {
    p: LONG POINTER ← allocBegin + allocCount; allocCount ← allocCount + words;
    IF allocCount>allocLimit THEN ErrorHalt[]; RETURN[p] };
  AllocBitmap: PROC[bpl: CARDINAL] RETURNS [LONG POINTER] = INLINE {
    RETURN[Alloc[Inline.LongMult[bpl/Environment.bitsPerWord,height]]] };

  IF NOT initialized OR NOT HasMode[mode] OR connected THEN ErrorHalt[];
  IF NOT SetSize[mode,nPages] THEN ErrorHalt[];
  SELECT displayType FROM
    Standard525 => {
      color.vcontrol ← [VBtoVS: 3B, VStoVS: 3B, VStoVB: 20B, VisibleLines: 240];
      color.hcontrol ← [HRamMaxAddr: 379, HBLeadLength: 6, HSTrailAddr: 28, HBTrailLength: 25];
      color.ccontrol ← [mul: 130B, div: 14B];
      tchan.leftMargin ← 65B };
    Hitachi3619 => {
      color.vcontrol ← [VBtoVS: 3B, VStoVS: 3B, VStoVB: 14B, VisibleLines: 512];
      color.hcontrol ← [HRamMaxAddr: 629, HBLeadLength: 6, HSTrailAddr: 61, HBTrailLength: 49];
      color.ccontrol ← [mul: 163B, div: 17B];
      tchan.leftMargin ← 172 -- a guess -- };
    ENDCASE => ErrorHalt[];
  -- Allocate bitmaps first; they must begin at even addresses
  IF mode.full THEN {
    -- double the pixel clock rate
    SELECT displayType FROM
      Standard525 => color.ccontrol ← [mul: 130B, div: 16B];
      ENDCASE => ErrorHalt[];
    tchan.scan ← [mode24: TRUE, aChannelOnly: FALSE, bBypass: TRUE, a8b2: TRUE];
    -- adjust leftMargin to compensate for the faster pixel clock
    tchan.leftMargin ← 2*tchan.leftMargin + 40B--HWindow-- + 40B--marginCounter--;
    baseA ← AllocBitmap[bplA ← 2*8*width]; -- double pixels in bitmap A
    baseB ← AllocBitmap[bplB ← 8*width];
    bppA ← bppB ← 8; showA ← showB ← TRUE;
    color.aTable ← atable ← Alloc[SIZE[ATableImage]];
    color.bTable ← btable ← Alloc[SIZE[BCTableImage]];
    color.cTable ← ctable ← Alloc[SIZE[BCTableImage]];
    atable↑ ← ALL[[redH: 0, redL: 0, blue: 0, green: 0]];
    btable↑ ← ALL[[value: 0]]; ctable↑ ← ALL[[value: 0]];
    fullmode ← TRUE }
  ELSE {
    tchan.scan ← [mode24: FALSE, aChannelOnly: ~mode.useB, bBypass: FALSE, a8b2: bppB<=2];
    IF mode.useA THEN {
      bppA ← Exp[mode.lgBitsPerPixelA];
      bplA ← width*bppA;
      baseA ← AllocBitmap[bplA];
      showA ← TRUE };
    IF mode.useB THEN {
      bppB ← Exp[mode.lgBitsPerPixelB];
      bplB ← width*bppB;
      baseB ← AllocBitmap[bplB];
      showB ← TRUE };
    color.aTable ← atable ← Alloc[SIZE[ATableImage]];
    atable↑ ← ALL[[redH: 0, redL: 0, blue: 0, green: 0]];
    mapmode ← TRUE };
  -- set up for MapIndex
  IF tchan.scan.a8b2 THEN { ashift ← 10-bppA; amask ← 1774B; bshift ← 2-bppB; bmask ← 0003B }
  ELSE {     -- a6b4 --     ashift ← 10-bppA; amask ← 1760B; bshift ← 4-bppB; bmask ← 0017B };
  -- set up channel control blocks; use tchan for a template
  tchan.pixelsPerLine ← width+pplOffset; tchan.linesPerField ← height/2;
  IF bppA>0 THEN { achan↑ ← tchan↑; achan.bitmap ← baseA;
    achan.wordsPerLine ← bplA/Environment.bitsPerWord; achan.scan.size ← bppA };
  IF bppB>0 THEN { bchan↑ ← tchan↑; bchan.bitmap ← baseB;
    bchan.wordsPerLine ← bplB/Environment.bitsPerWord; bchan.scan.size ← bppB };
  tchan.pixelsPerLine ← 0 + pplOffset; -- now make tchan a dummy control block
  IF fullmode THEN {
    -- achan fetches two bitmap pixels per screen pixel
    -- bchan runs at half resolution: 2 pixel clocks for each pixel from the bitmap
    achan.pixelsPerLine ← bchan.pixelsPerLine ← 2*width+pplOffset;
    bchan.scan.res ← half;
    -- horizontal control is measured in pixel clock periods
    color.hcontrol.HRamMaxAddr ← 2*color.hcontrol.HRamMaxAddr;
    color.hcontrol.HBLeadLength ← 2*color.hcontrol.HBLeadLength;
    color.hcontrol.HSTrailAddr ← 2*color.hcontrol.HSTrailAddr;
    color.hcontrol.HBTrailLength ← 2*color.hcontrol.HBTrailLength;
    };
  connected ← TRUE;
  };

Disconnect: PUBLIC PROC = {
  IF NOT connected THEN ErrorHalt[];
  IF turnedon THEN TurnOff[];
  color.aTable ← NIL; color.bTable ← color.cTable ← NIL;
  achan.bitmap ← bchan.bitmap ← NIL;
  width ← height ← 0;
  baseA ← baseB ← NIL; bplA ← bplB ← 0; bppA ← bppB ← 0;
  showA ← showB ← FALSE;
  atable ← NIL; btable ← ctable ← NIL;
  fullmode ← mapmode ← connected ← FALSE;
  };

TurnOn: PUBLIC PROC = {
  IF (NOT connected) OR turnedon THEN RETURN;
  mcb.flags ← [vc: TRUE, hc: TRUE, clk: TRUE, a: TRUE];
  mcb.achanCB ← rpTChan; -- plug in dummy control block to establish scan mode
  csb.mcb ← rpMCB;
  WHILE mcb.flags.a DO ENDLOOP; -- wait for microcode to notice everything
  IF fullmode THEN mcb.flags ← [b: TRUE, c: TRUE];
  mcb.achanCB ← (IF showA THEN rpAChan ELSE rpNIL); -- show bitmap A
  mcb.bchanCB ← (IF showB THEN rpBChan ELSE rpNIL); -- show bitmap B
  turnedon ← TRUE;
  };

TurnOff: PUBLIC PROC = {
  asave: ATableEntry;
  bsave,csave: BCTableEntry;
  IF NOT turnedon THEN RETURN;
  mcb.achanCB ← mcb.bchanCB ← rpNIL; -- disable bitmaps
  asave ← atable[0];
  atable[0] ← [redH: 0, redL: 0, green: 0, blue: 0]; -- black
  mcb.flags.a ← TRUE;
  IF fullmode THEN {
    bsave ← btable[0]; csave ← ctable[0];
    btable[0] ← ctable[0] ← [value: 0];
    mcb.flags.b ← mcb.flags.c ← TRUE;
    };
  Wait[]; -- be sure the microcode is no longer touching bitmaps or color tables
  csb.mcb ← rpNIL;
  atable[0] ← asave;
  IF fullmode THEN { btable[0] ← bsave; ctable[0] ← csave };
  turnedon ← FALSE;
  };


Show: PUBLIC PROC[a,b,c: BOOLEAN] = {
  IF bppA>0 THEN showA ← a;
  IF bppB>0 THEN showB ← b;
  IF turnedon THEN {
    mcb.achanCB ← (IF showA THEN rpAChan ELSE rpNIL);
    mcb.bchanCB ← (IF showB THEN rpBChan ELSE rpNIL);
    };
  };

GetColor: PUBLIC PROC[pixelA,pixelB: CARDINAL] RETURNS[r,g,b: Color] = {
  IF mapmode THEN {
    index: ATableIndex ← MapIndex[pixelA,pixelB];
    entry: ATableEntry ← atable[index];
    RETURN[r: entry.redH*16+entry.redL, g: entry.green, b: entry.blue];
    }
  ELSE RETURN[0,0,0];
  };

SetColor: PUBLIC PROC[pixelA,pixelB: CARDINAL, r,g,b: Color] = {
  IF mapmode THEN {
    index: ATableIndex ← MapIndex[pixelA,pixelB];
    atable[index] ← [redH: r/16, redL: r MOD 16, blue: b, green: g];
    IF turnedon THEN mcb.flags.a ← TRUE;
    };
  };

GetRedMap: PUBLIC PROC[in: Color] RETURNS[out: Color] = {
  IF fullmode THEN {
    index: ATableIndex ← MapIndex[in,0];
    entry: ATableEntry ← atable[index];
    RETURN[entry.redH*16+entry.redL] }
  ELSE RETURN[0];
  };
GetGreenMap: PUBLIC PROC[in: Color] RETURNS[out: Color] = {
  IF fullmode THEN RETURN[ctable[in].value] ELSE RETURN[0];
  };
GetBlueMap: PUBLIC PROC[in: Color] RETURNS[out: Color] = {
  IF fullmode THEN RETURN[btable[in].value] ELSE RETURN[0];
  };

SetRedMap: PUBLIC PROC[in,out: Color] = {
  IF fullmode THEN {
    index: ATableIndex ← MapIndex[in,0];
    atable[index] ← [redH: out/16, redL: out MOD 16, green: 0, blue: 0];
    IF turnedon THEN mcb.flags.a ← TRUE; };
  };
SetGreenMap: PUBLIC PROC[in,out: Color] = {
  IF fullmode THEN { ctable[in] ← [value: out];
    IF turnedon THEN mcb.flags.c ← TRUE; };
  };
SetBlueMap: PUBLIC PROC[in,out: Color] = {
  IF fullmode THEN { btable[in] ← [value: out];
    IF turnedon THEN mcb.flags.b ← TRUE; };
  };

-- Initialization

minFPS: CARDINAL = 30; -- minimum number of frames per second
maxPulsesPerRefresh: LONG CARDINAL = 2*  --for safety--
  (LONG[100]* --pulsesPerHundredPulses-- 1000000  --microsecondsPerSecond--)/
    (LONG[minFPS]*ProcessorFace.microsecondsPerHundredPulses);

Wait: PROC = INLINE { timeDone: LONG CARDINAL ← ProcessorFace.GetClockPulses[];
  WHILE ProcessorFace.GetClockPulses[] - timeDone < maxPulsesPerRefresh DO ENDLOOP };

InitializeCleanup: PUBLIC PROC = { OPEN DeviceCleanup;
  item: Item;
  reason: Reason;
  state: CSBState;
  DO
    reason ← Await[@item];
    SELECT reason FROM
      turnOff => { state ← csb↑; csb.mcb ← rpNIL; Wait[] };
      turnOn => { csb↑ ← state };
      ENDCASE;
    ENDLOOP;
  };

--  HeadStartChain

Start: PUBLIC PROC = {
  hasColorBoard: BOOLEAN ← (DoradoInputOutput.InputNoPE[360B] # 0);
  IF hasColorBoard THEN {
    device: MACHINE DEPENDENT RECORD[type: [0..17B], junk: [0..7777B]] ←
      DoradoInputOutput.InputNoPE[361B];
    SELECT device.type FROM
      17B => {
        displayType ← Standard525;
        screenwidth ← 640; -- must be a multiple of 32
        screenheight ← 480; -- must be a multiple of 2
        pixelsPerInch ← 64; -- *** approximate ***
        };
      16B => {
        displayType ← Hitachi3619;
        screenwidth ← 1024; -- must be a multiple of 32
        screenheight ← 1024; -- must be a multiple of 2
        pixelsPerInch ← 72; -- *** approximate ***
        };
      ENDCASE => ErrorHalt[]; -- unknown display type
    -- determine whether an old rev DispY board is installed
    oldRev ← (DoradoInputOutput.RWMufMan[[useDMD: FALSE, dMuxAddr: 3107B]].dMuxData # 0);
    };
  csb↑ ← [mcb: rpNIL];
  RemainingHeads.Start[];
  };


}.