💾 Archived View for spam.works › mirrors › textfiles › programming › pcgpe10.txt captured on 2023-07-22 at 20:45:20.

View Raw

More Information

⬅️ Previous capture (2023-06-16)

-=-=-=-=-=-=-


                 ?????????????????????????????????????????????
                 ? The PC GAMES PROGRAMMERS ENCYCLOPEDIA 1.0 ?
                 ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Introduction ?
????????????????

Well, here it is! This is the first edition of the PC Games Programmers
Encyclopedia. The PC-GPE as it currently stands is a collection of text
files, each covering a different aspect of programming games for the PC.
Some files were obtained from the net, others were grabbed off Usenet, quite
a few were written for the PC-GPE.

Every effort has been made to contact the original authors of all public
domain articles obtained via ftp. In some cases the original authors were
not able to be contacted. Seeing as these files were already available to the
public the liberty was taken to include them anyway. The files were not
modified in any way. There is a list at the end of this document showing
which files we couldn't contact the authors about. Please note that files
were *not* written exclusively for the PC-GPE unless stated otherwise.

The information in the PC-GPE is provided to you free of charge. The authors
of each article have included their own conditions of use, eg some ask that
you give them credit if you use their source code. As a general rule of
thumb, an e-mail or postcard to an author telling them you found their file
helpful probably wouldn't go astray.....

This first version of the PC-GPE is very hardware oriented. We hope to
include more actual game algorithms in future releases. If you would like to
see a particular topic included in the next PC-GPE release or if you think
you could contribute an article then by all means let us know (btw plugs for
personal projects in articles are accepted). The editor's e-mail address is
at the end of this file.

Some of the text files are pretty long, so the PCGPE uses a protected mode
file viewer (PCGPE.EXE) which may play up when run on 286 machines. If this
happens read the DPMIUSER.DOC file for help on fixing the problem.

?????????????????????????????????????????????????????????????????????????????
? PC-GPE Home Site ?
????????????????????

The Games Programmers Encyclopedia official home site is:

teeri.oulu.fi
/pub/msdos/programming/gpe

There are plans to develop GPE's for the mac and other architectures for
cross-platform game development. The teeri site will also hold PC-GPE
updates/bug fixes/etc.

Many thanks to Jouni Miettunen for all his help and for allowing us to use
teeri as the PC-GPE's home site. He's put a lot of work into it and it's a
great programming resource, particularly for people wanting to develop game
software.


?????????????????????????????????????????????????????????????????????????????
? History ?
???????????

The PC-GPE was conceived, designed and largely built by the same people who
keep the Usenet groups rec.games.programmer and comp.graphics.algorithms
alive. It was noticed that information required for even the most basic game
development was strewn out across the vast wastelands of the Internet and was
time consuming and annoying (if not down-right impossible) to obtain.

Most of us can't afford to go out and buy a book every time we want to look
up a particlar topic, so a bunch of us decided to grab the most commonly
sought-after free info and put it in one place.


?????????????????????????????????????????????????????????????????????????????
? The People Who Did All the Work ?
???????????????????????????????????

First a big thanks goes to everyone who wrote articles or allowed us to use
their existing articles.

Also thanks to the Demo groups Asphixia and VLA (more specifically Lithium
and Denthor) for letting us use the asm and vga trainers they wrote.

A number of people who didn't actually write articles contributed heaps
to the project right from the start with tips/comments/suggestions etc as
well as lots of info on where we could get stuff. Thanks go to Bri, Dizzy,
Claus Anderson, Nathan Clegg, Alex Curylo, Cameron Grant, Chris Matrakidis
and the many others who sent info. If it wasn't for them you probably
wouldn't be reading this now!

And finally thanks to Jouni Miettunen for setting up the PC-GPE directory on
the teeri site, letting us use it as the official home site and supplying
a heap of information.

The editor would also like to thank the scores of other people who e-mailed
him with suggestions, comments, requests etc...and continually hassled him to
hurry up and get the damn thing finished.


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

It's a pity we live in a world where the following kind of crap is
neccessary. Oh well, here goes....

Each article appearing in the PC-GPE is bound by any disclaimer that appears
within it. The editor assumes absolutely no responsibility whatsoever for
any effect that this file viewer or any of the PC-GPE articles have on you,
your sanity, computer, spouse, children, pets or anything else related to
you or your existance. No warranty is provided nor implied with this
information. The accuracy of the information contained is subject to
conjecture. Use all information at your own risk. The file PC-GPE.EXE may
not be distributed without all the original unmodified PC-GPE articles. The
distribution rights of individual articles is at the discretion of the
authors.


?????????????????????????????????????????????????????????????????????????????
? File List ?
?????????????

The following is a list of all the PCGPE 1.0 files:

File               Description
?????????????????????????????????????????????????????????????????????????????

PCGPE    EXE   *   PC-GPE main exe file
DPMIUSER DOC   *   PC-GPE.EXE DPMI info file
RTM      EXE   *   PC-GPE.EXE DPMI support file
RTMRES   EXE   *   PC-GPE.EXE DPMI support file
DPMIINST EXE   *   PC-GPE.EXE DPMI support file
DPMILOAD EXE   *   PC-GPE.EXE DPMI support file
DPMI16BI OVL   *   PC-GPE.EXE DPMI support file

README   TXT   *   PC-GPE main info doc
FTPSITES TXT       List of FTP sites for game development programs/utils

ASMINTRO TXT   *   VLA's assembly tutorial intro file
ASM0     TXT   *   VLA's assembly tutorial
ASM1     TXT   *   VLA's assembly tutorial
ASM2     TXT   *   VLA's assembly tutorial
ASM3     TXT   *   VLA's assembly tutorial
ANSI     TXT   *   VLA's assembly tutorial support file

INTEL    DOC       List of op codes plus timing info up to 486
CPUTYPE  TXT   *   Testing CPU type
TIMER    ASM   *   Testing CPU speed

TUT1     TXT   *   Asphixia's VGA Primer - Mode 13h
TUT2     TXT   *   Asphixia's VGA Primer - Palette/Fading
TUT3     TXT   *   Asphixia's VGA Primer - Lines/Circles
TUT4     TXT   *   Asphixia's VGA Primer - Virtual Screens
TUT5     TXT   *   Asphixia's VGA Primer - Scrolling
TUT6     TXT   *   Asphixia's VGA Primer - Look-up Tables
TUT7     TXT   *   Asphixia's VGA Primer - Animation
TUT8     TXT   *   Asphixia's VGA Primer - 3D/Optimisation
TUT9     TXT   *   Asphixia's VGA Primer - 3D Solids
TUT10    TXT   *   Asphixia's VGA Primer - Chain 4 mode
COPPER   PAS   *   Asphixia's VGA Primer - Copper Effect
WORMIE   PAS   *   Asphixia's VGA Primer - Worm Effect
PALLETTE COL   *   Asphixia's VGA Primer support file
SOFTROCK FNT   *   Asphixia's VGA Primer support file

MODEX    TXT   *   Introduction to mode x
SCROLL   TXT   *   VGA scrolling
VGAREGS  TXT   *   VGA palette and register set
VGABIOS  TXT       VGA BIOS function call list

SVGINTRO TXT   *   SVGA - Intro to programming SVGA cards
VESASP12 TXT       SVGA - The VESA standard
ATI      TXT   *   SVGA - Programming the ATI chip set
CAT      TXT   *   SVGA - Programming the Chips & Technologies chip set
GENOA    TXT   *   SVGA - Programming the Genoa chip set
PARADISE TXT   *   SVGA - Programming the Paradise chip set
TRIDENT  TXT   *   SVGA - Programming the Trident chip set
TSENG    TXT   *   SVGA - Programming the Tseng chip set
VIDEO7   TXT   *   SVGA - Programming the Video7 chip set
XTENDED  TXT   *   SVGA - 640x400x256 with no bank switching

3DROTATE DOC   *   VLA's three dimensional rotations for computer graphics
3DSHADE  DOC   *   VLA's three dimensional shading in computer graphics
PERSPECT TXT   *   Perspective transforms
BRES     TXT   *   Bresenham's line and circle algorithms
CONIC    CC    *   A bresenham-like general conic sections algorithm
BSP      TXT   *   A Simple Explanation of BSP Trees
TEXTURE  TXT   *   Texture mapping
FDTM     TXT   *   Real-time free direction texture mapping

STARS    TXT   *   VLA's programming star fields
FIRE     TXT   *   Programming fire effects

PCX      TXT       PCX graphics file format
BMP      TXT       BMP graphics file format
GIF      TXT       BMP graphics file format
IFF      DOC       IFF/LBM graphics file format
FLI      FOR       FLI/FLC graphics file format

SPEAKER  TXT   *   Programming the PC speaker (inc 8-bit sample playback)
GAMEBLST TXT   *   Programming the GameBlaster sound card
ADLIB    TXT       Programming the Adlib sound card
SBDSP    TXT   *   Programming the SoundBlaster sound card (DSP)
SBPRO    TXT   *   Programming the SoundBlaster Pro sound card
GUSFAQ   TXT   *   The GUS sound card's Frequently Asked Questions
GUS      TXT   *   Programming the GUS sound card

MODFORM  TXT   *   The MOD sound file format
VOC      TXT       The VOC sound file format
WAV      TXT   *   The WAV sound file format
CMF      TXT   *   The CMF sound file format
MIDI     TXT   *   The MID sound file format
UT       TXT       The UltraTracker sound file format

SURROUND TXT       Generating surround sound

MOUSE    TXT   *   Programming the mouse, general info
GMOUSE   DOC       Mouse driver function call list
KEYBOARD TXT   *   Programming for the PC keyboard
JOYSTICK TXT   *   Programming for the PC joystick
GAMEPAD  TXT   *   Programming for the Gravis GamePad and Analog Pro

LIMEMS41 DOC       EMS (Expanded Memory Specification)
XMS30    TXT       XMS (Extended Memory Specification)
DMA_VLA  TXT   *   Intro to DMA

PIT      TXT   *   Programming the Intel 8253 Programmable Interval Timer
DOOM     TXT   *   DOOM techniques


An asterix (*) indicates files which were either written for the PC-GPE or
included with permission from the author.


?????????????????????????????????????????????????????????????????????????????
? Final Words from the Editor ?
???????????????????????????????

Greetz
??????

   Zob: Whaddaya mean you can't come out drinking with us for 6 months? What
        the hell is "glandular fever" anyway?

Wookie: Whaddaya mean I can't play ModemDOOM on a 2400?

  Fink: Live fast, die young, have a good lookin' corpse!

MainFrame, bri_acid, wReam, Nocturnus, MArtist, RetroSpec, Matrix, Syntax,
Andrez, Gideon and the rest of the #coders gang : try and get some sleep
                                                  some time guys!

  Eyre: You/me babe, how 'bout it?

  Aggi: Remember, reality is mass hallucination resulting from alcohol
        deficiency!

Fetish: You know the routine hon, pick a number and join the queue like
        the rest of 'em!


Why all my code is in Pascal
????????????????????????????

Ok, ok, I'm expecting to get lots of crap over this one. To put it simply
Pascal is close to psuedo code and I wanted the routines to be understood
by everyone, Pascal programmers, C programmers and *REAL* (to wit, asm)
programmers alike. Apart from that I'm running a 40Mg doublespaced hard
drive and I have to use the fastest compiler possible. That's a good enough
reason isn't it?....people?.....


Shameless Plug
??????????????

There are two things in life I really can't stand,

1) My ex-girlfriend
2) Being unemployed, which I am now!

So if your company has any openings I'd really like to hear from you,
particularly if you develop game software.

I'm a 3rd year computer engineering student and my specialties lie in
computer graphics and low-level PC hardware programming. I program in C++
(Dos and Windows), Pascal, 80x86 assembly, QBasic (heh heh) and Prolog.


Mark Feldman
Internet: u914097@student.canberra.edu.au
          myndale@cairo.anu.edu.au

??????????????????????????????????????????????????????????????????????
A note from CyberRax (the guy who merged all the textfiles into this BIG doc
and erased the executables): the following files - TIMER.ASM, PALETTE.COL,
SOFTROCK.FNT, COPPER.PAS and WORMIE.PAS - have been compressed with PKZIP,
tansformed into UUE format and included in the very end of this file. To
use them you must extract the UUE file, uuDEcode it, and uncompress with
PKUnZIP or InfoZIP
??????????????????????????????????????????????????????????????????????



                                Resource Guide
                                     for
                            Computer Game Developers
                

This lists various libraries, development systems, and source packages available
on the Internet via anonymous FTP to game development designers and programmers.


Comments: game-dev@netcom.com
Copyright 1993, Tom Czarnik
--------------------------------------------------------------------------------
                                SECTIONS
                                ========
(1) Painting, Sprite Designers
(2) Sound Development
(3) Development Libraries or Systems
(4) Source Code


                        Definitions: archives
                        =====================
        funet
                Finland nic.funet.fi:/pub/msdos/games/programming
        garbo-tp
                Finland garbo.uwasa.fi:/pc/turbopas
                USA     ftp.wustl.edu:/mirrors/garbo.uwasa.fi/pc/turbopas
        netcom
                USA     ftp.netcom.com:/pub/profile/game-dev/tools
                USA     ftp.uwp.edu:/pub/msdos/games/game-dev/tools

                        Definitions: system notations
                        =============================
                MS      MSDOS                   MC Macintosh
                WIN     MS Windows 3.x          UX Unix/X-Windows
                AM      Amiga                   ST Atari ST



------------------------------------------------------------------------------
(1) Painting, Sprite Designers
==============================

  A 256 color VGA paint program.
        Archives:
                netcom, funet
        Distribution:
                256paint.zip


  A sprte designer for ModeX or VGA256
        Archives:
                funet, netcom
        Distribtuion:
                sprdes.zip


  A VGA256 tile and map editor
        Archives:
                netcom
        Distribibution:
                tile11.zip


(2) Sound Development
=====================

  Sound Blaster utility and MOD player. Includes binaries, documentation, and
  objects for Turbo C/C++.
        Archives:
                funet, netcom
        Distribution:
                blast13.lzh


  An Adlib and SoundBlaster FM toolkit with background player. A .rol to
  .scr convertor is included. Borland C 3.0 source available.
        Archives:
                netcom
        Distribution:
                fmplay10.zip


(3) Game Development Libraries or Systems
=========================================       

  A package for making Wolf3D style games.
        Archives:
                funet, netcom
        Distribution:
                ackkit.zip      Main development system
                acksrc.zip      Borland C and Microsoft assembly sources
                pcx2img.zip     PCX to IMG conversions for ACK3D



  Sprite routines, editor, examples, VGA256 binaries with Turbo Pascal
  6.0 source.
        Archives:
                funet
        Distribution:
                anivga11.zip



  Graphics adventure and RPG building system. Includes example games.
        Archives:
                netcom
        Distribution:
                dcg301.zip      Main system
                dcg301sb.zip    SoundBlaster support
                dcg301xb.zip    Voice and music files
                dcg301ut.zip    Utilities
                dcg301up.zip    Upgrade from v3.0 to v3.1 only


  A Turbo/Borland Pascal 7.0 programming library for VGA256, ModeX, VESA
  graphics. Documentation included.
        Archives:
                garbo-tp
        Distribution:
                egof10.zip      Main library
                egof10b.zip     Demo PCX files for egof10.zip
                egof10m.zip     Postscript manual for EGOF v1.0
                egof10p.zip     Protected mode units for EGOF v1.0


  A modeX library for VGA256. No documentation available. Inlcudes Borland
  C and assembly.
        Archives:
                funet, nectom
        Distribution:
                hobbs3.zip      Main system
                hobbspr2.zip    Related examples. C++ source/sprite executable


  A VGA256 game library for Turbo Pascal 6.0 and 7.0 by Scott Ramsay.
  Successor to GameTP libray.
        Archives:
                funet, netcom
        Distribution:
                spx10.zip
                spxdemo1.zip    Demos with binary and source


  A Planar VGA Mode (aka modeX) Enforcer for Borland's IDE by Colin Buckely
        Archives:
                netcom
        Distribution:
                unchain2.zip


  A VGA graphics library by Scott Ramsay. C and assembly source included.
        Archives: 
                netcom
        Distribution:
                vgl20.zip


  A VGA256 sprite library by Barry and Chris Egerter. Shareware
        Archives:
                funet, netcom
        Distribution:
                wgt35.zip       Main system
                wgtdemo1.zip    Demo
                wgtmap15.zip    Map editor v1.5
                wgtspr35.zip    Sprite editor v3.5


  A VGA256 ModeX sprite library by Themie Gouthas. Includes Turbo C/TASM source.
  Free.
        Archives:
                funet, netcom
        Distribution:
                xlib04c.zip     Main system
                xlibtool.zip    Bitmap conversion tools for XLib



  A VGA256 sprite library with higher level routines over xLib, by Victor Putz.
  Borland C++ 3.1 source, with doumentation. Free shareware.
        Archives:
                netcom
        Distribution:
                yak24pat.zip    Patch to yakIcons v2.3
                yakdemo.zip     Demo of yakIcons
                yaktool.zip     Converts PCX to .yak and .ypl 
                yicons24.zip    Main system


(4) Source Code
===============

        Archives:
                ftp.uwp.edu:/pub/msdos/demos/source
        Distribution:
                grphprg.lzh

        ????????????% VLA Presents: Intro to Assembler %????????????
        ?                                                          ?
        ????????????????????????????????????????????????????????????



  ? Dedicated To Those Who Wish To Begin Exploring The Art Of Assembler. ?



???????????????????????????  VLA Members Are  ????????????????????????????

                         (? Draeden - Main Coder ?)
                      (? The Priest - Coder/ Artist ?)
                  (? Lithium -  Coder/Ideas/Ray Tracing ?)
                   (? The Kabal -  Coder/Ideas/Artwork ?)
                      (? Desolation - Artwork/Ideas ?)

?????????????????????????? The Finn - Mods/Sounds ??????????????????????????


   ????????????????????? Contact Us On These Boards ?????????????????????
   ?                                                                    ?
   ?  % Phantasm BBS .................................. (206) 232-5912  ?
   ?  * The Deep ...................................... (305) 888-7724  ?
   ?  * Dark Tanget Systems ........................... (206) 722-7357  ?
   ?  * Metro Holografix .............................. (619) 277-9016  ?
   ?                                                                    ?
   ?        % - World Head Quarters      * - Distribution Site          ?
   ??????????????????????????????????????????????????????????????????????

     Or Via Internet Mail For The Group: tkabal@carson.u.washington.edu

                       Or to reach the other members:

                        - draeden@u.washington.edu -

                        - lithium@u.washington.edu -

                       - desolation@u.washington.edu-


????????????????????????????????????????????????????????????????????????????
    VLA 3/93  Introduction to ASSEMBLER
????????????????????????????????????????????????????????????????????????????

Here's something to help those of you who were having trouble understanding
the instructional programs we released.  Dreaden made these files for the 
Kabal and myself when we were just learning.  These files go over some of 
the basic concepts of assembler.  Bonus of bonuses.  These files also have 
programs imbedded in them.  Most of them have a ton of comments so even
the beginning programmers should be able to figure them out.

If you'd like to learn more, post a message on Phantasm.  We need to know
where you're interests are before we can make more files to bring out the
little programmers that are hiding inside all of us.

    Lithium/VLA

????????????????????????????????????????????????????????????????????????????

    First thing ya need to know is a little jargon so you can talk about 
the basic data structures with your friends and neighbors.  They are (in
order of increasing size) BIT, NIBBLE, BYTE, WORD, DWORD, FWORD, PWORD and
QWORD, PARA, KiloByte, MegaByte.  The ones that you'll need to memorize are
BYTE, WORD, DWORD, KiloByte, and MegaByte.  The others aren't used all that 
much, and you wont need to know them to get started.  Here's a little 
graphical representation of a few of those data structures:

(The zeros in between the || is a graphical representation of the number of
bits in that data structure.)

??????
1 BIT :     |0|

    The simplest piece of data that exists.  Its either a 1 or a zero.
    Put a string of them together and you have a BASE-2 number system.
    Meaning that instead of each 'decimal' place being worth 10, its only 
    worth 2.  For instance: 00000001 = 1; 00000010 = 2; 00000011 = 3, etc..

??????
1 NIBBLE:   |0000|
4 BITs

    The NIBBLE is half a BYTE or four BITS.  Note that it has a maximum value
    of 15 (1111 = 15).  Not by coincidence, HEXADECIMAL, a base 16 number 
    system (computers are based on this number system) also has a maximum 
    value of 15, which is represented by the letter 'F'.  The 'digits' in
    HEXADECIMAL are (in increasing order): 
    
    "0123456789ABCDEF"

    The standard notation for HEXADECIMAL is a zero followed by the number        
    in HEX followed by a lowercase "h"  For instance: "0FFh" = 255 DECIMAL.

??????
1 BYTE      |00000000|
2 NIBBLEs    ?? AL ??
8 BITs

    The BYTE is the standard chunk of information.  If you asked how much 
    memory a machine had, you'd get a response stating the number of BYTEs it
    had. (Usually preceded by a 'Mega' prefix).  The BYTE is 8 BITs or 
    2 NIBBLEs.  A BYTE has a maximum value of 0FFh (= 255 DECIMAL).  Notice
    that because a BYTE is just 2 NIBBLES, the HEXADECIMAL representation is
    simply two HEX digits in a row (ie. 013h, 020h, 0AEh, etc..)

    The BYTE is also that size of the 'BYTE sized' registers - AL, AH, BL, BH,
    CL, CH, DL, DH.

??????
1  WORD      |0000000000000000|
2  BYTEs      ?? AH ???? AL ??     
4  NIBBLEs    ?????? AX ??????
16 BITs

    The WORD is just two BYTEs that are stuck together.  A word has a maximum 
    value of 0FFFFh (= 65,535).  Since a WORD is 4 NIBBLEs, it is represented
    by 4 HEX digits.  This is the size of the 16bit registers on the 80x86
    chips.  The registers are: AX, BX, CX, DX, DI, SI, BP, SP, CS, DS, ES, SS,
    and IP.  Note that you cannot directly change the contents of IP or CS in 
    any way.  They can only be changed by JMP, CALL, or RET.

??????
1  DWORD
2  WORDs     |00000000000000000000000000000000|
4  BYTEs      ?               ?? AH ???? AL ??     
8  NIBBLEs    ?               ?????? AX ??????
32 BITs       ????????????? EAX ??????????????

    A DWORD (or "DOUBLE WORD") is just two WORDs, hence the name DOUBLE-WORD.
    This can have a maximum value of 0FFFFFFFFh (8 NIBBLEs, 8 'F's) which
    equals 4,294,967,295.  Damn large.  This is also the size or the 386's
    32bit registers: EAX, EBX, ECX, EDX, EDI, ESI, EBP, ESP, EIP.  The 'E '
    denotes that they are EXTENDED registers.  The lower 16bits is where the 
    normal 16bit register of the same name is located. (See diagram.)

??????
1    KILOBYTE   |-lots of zeros (8192 of 'em)-|
256  DWORDs
512  WORDs
1024 BYTEs
2048 NIBBLEs
8192 BITs

    We've all heard the term KILOBYTE byte, before, so I'll just point out
    that a KILOBYTE, despite its name, is -NOT- 1000 BYTEs.  It is actually
    1024 bytes.

??????
          1 MEGABYTE   |-even more zeros (8,388,608 of 'em)-|
      1,024 KILOBYTEs
    262,144 DWORDs
    524,288 WORDs
  1,048,576 BYTEs
  2,097,152 NIBBLEs
  8,388,608 BITs

    Just like the KILOBYTE, the MEGABYTE is -NOT- 1 million bytes.  It is
    actually 1024*1024 BYTEs, or 1,048,578 BYTEs

??????????????????????????????

    Now that we know what the different data types are, we will investigate
    an annoying little aspect of the 80x86 processors.  I'm talking about 
    nothing other than SEGMENTS & OFFSETS!


SEGMENTS & OFFSETS:
??????????????????
    Pay close attention, because this topic is (I believe) the single most
    difficult (or annoying, once you understand it) aspect of ASSEMBLER.

An OverView:

    The original designers of the 8088, way back when dinasaurs ruled the 
    planet, decided that no one would ever possibly need more than one MEG
    (short for MEGABYTE :) of memory.  So they built the machine so that it 
    couldn't access above 1 MEG. To access the whole MEG, 20 BITs are needed.
    Problem was that the registers only had 16 bits, and if the used two
    registers, that would be 32 bits, which was way too much (they thought.)  
    So they came up with a rather brilliant (blah) way to do their addressing-
    they would use two registers.  They decided that they would not be 32bits, 
    but the two registers would create 20 bit addressing.  And thus Segments
    and OFfsets were born.  And now the confusing specifics.

??????????????????
    
OFFSET  = SEGMENT*16
SEGMENT = OFFSET /16    ;note that the lower 4 bits are lost

                 
SEGMENT * 16    |0010010000010000----|  range (0 to 65535) * 16
 +                    
OFFSET          |----0100100000100010|  range (0 to 65535)
 =
20 bit address  |00101000100100100010|  range 0 to 1048575 (1 MEG)
                 ?????? DS ??????
                     ?????? SI ??????
                     ?? Overlap??

 This shows how DS:SI is used to construct a 20 bit address.

Segment registers are: CS, DS, ES, SS. On the 386+ there are also FS & GS

Offset registers  are: BX, DI, SI, BP, SP, IP.  In 386+ protected mode, ANY
        general register (not a segment register) can be used as an Offset
        register.  (Except IP, which you can't access.)

    CS:IP => Points to the currently executing code.
    SS:SP => Points to the current stack position.
    
??????????????????

    If you'll notice, the value in the SEGMENT register is multiplied by
    16 (or shifted left 4 bits) and then added to the OFFSET register.
    Together they create a 20 bit address.  Also Note that there are MANY
    combinations of the SEGMENT and OFFSET registers that will produce the 
    same address.  The standard notation for a SEGment/OFFset pair is:

????
SEGMENT:OFFSET or A000:0000 ( which is, of course in HEX )

    Where SEGMENT = 0A000h and OFFSET = 00000h.  (This happens to be the 
    address of the upper left pixel on a 320x200x256 screen.)
????

    You may be wondering what would happen if you were to have a segment
    value of 0FFFFh and an offset value of 0FFFFh.  

    Take notice: 0FFFFh * 16 (or 0FFFF0h ) + 0FFFFh = 1,114,095, which is 
      definately larger than 1 MEG (which is 1,048,576.)

    This means that you can actually access MORE than 1 meg of memory!  
    Well, to actually use that extra bit of memory, you would have to enable
    something called the A20 line, which just enables the 21st bit for
    addressing.  This little extra bit of memory is usually called
    "HIGH MEMORY" and is used when you load something into high memory or
    say DOS = HIGH in your AUTOEXEC.BAT file.  (HIMEM.SYS actually puts it up
    there..)  You don't need to know that last bit, but hey, knowledge is 
    good, right?

??????????????????????
    THE REGISTERS:
??????????????????????

    I've mentioned AX, AL, and AH before, and you're probably wondering what
    exactly they are.  Well, I'm gonna go through one by one and explain
    what each register is and what it's most common uses are.  Here goes:

????
AX (AH/AL): 
    AX is a 16 bit register which, as metioned before, is merely two bytes
    attached together.  Well, for AX, BX, CX, & DX you can independantly 
    access each part of the 16 bit register through the 8bit (or byte sized)
    registers.  For AX, they are AL and AH, which are the Low and High parts
    of AX, respectivly.  It should be noted that any change to AL or AH, 
    will change AX.  Similairly any changes to AX may or may not change AL and
    AH.  For instance:

????????
Let's suppose that AX = 00000h (AH and AL both = 0, too) 

    mov     AX,0
    mov     AL,0
    mov     AH,0

Now we set AL = 0FFh.  
 
    mov     AL,0FFh

:AX => 000FFh  ;I'm just showing ya what's in the registers
:AL =>   0FFh
:AH => 000h

Now we increase AX by one:

    INC     AX

:AX => 00100h (= 256.. 255+1= 256)
:AL =>   000h (Notice that the change to AX changed AL and AH)
:AH => 001h

Now we set AH = 0ABh (=171)

    mov     AH,0ABh

:AX => 0AB00h
:AL =>   000h
:AH => 0ABh

Notice that the first example was just redundant...
We could've set AX = 0 by just doing

    mov     ax,0

:AX => 00000h
:AL =>   000h
:AH => 000h

I think ya got the idea...
????????

    SPECIAL USES OF AX:
        Used as the destination of an IN (in port) 
            ex: IN  AL,DX
                IN  AX,DX

        Source for the output for an OUT           
            ex: OUT DX,AL
                OUT DX,AX

        Destination for LODS (grabs byte/word from [DS:SI] and INCreses SI)
            ex: lodsb   (same as:   mov al,[ds:si] ; inc si )
                lodsw   (same as:   mov ax,[ds:si] ; inc si ; inc si )

        Source for STOS      (puts AX/AL into [ES:DI] and INCreses DI)
            ex: stosb   (same as:   mov [es:di],al ; inc di )
                stosw   (same as:   mov [es:di],ax ; inc di ; inc di )

        Used for MUL, IMUL, DIV, IDIV
????
BX (BH/BL): same as AX (BH/BL)

    SPECIAL USES:
        As mentioned before, BX can be used as an OFFSET register.
            ex: mov ax,[ds:bx]  (grabs the WORD at the address created by
                                    DS and BX)

CX (CH/CL): Same as AX
    
    SPECIAL USES:
        Used in REP prefix to repeat an instruction CX number of times
            ex: mov cx,10
                mov ax,0
                rep stosb ;this would write 10 zeros to [ES:DI] and increase
                          ;DI by 10.
        Used in LOOP
            ex: mov cx,100
            THELABEL:

                ;do something that would print out 'HI'

                loop THELABEL   ;this would print out 'HI' 100 times
                                ;the loop is the same as: dec cx
                                                          jne THELABAL
            
DX (DH/DL): Same as above
    SPECIAL USES:
        USED in word sized MUL, DIV, IMUL, IDIV as DEST for high word
                or remainder

            ex: mov bx,10
                mov ax,5
                mul bx  ;this multiplies BX by AX and puts the result 
                        ;in DX:AX

            ex: (continue from above)
                div bx  ;this divides DX:AX by BX and put the result in AX and
                        ;the remainder (in this case zero) in DX

        Used as address holder for IN's, and OUT's (see ax's examples)
            
INDEX REGISTERS:  

    DI: Used as destination address holder for stos, movs (see ax's examples)
        Also can be used as an OFFSET register

    SI: Used as source address holder for lods, movs (see ax's examples)
        Also can be used as OFFSET register

        Example of MOVS:
            movsb   ;moves whats at [DS:SI] into [ES:DI] and increases
            movsw   ; DI and SI by one for movsb and 2 for movsw

        NOTE: Up to here we have assumed that the DIRECTION flag was cleared.
            If the direction flag was set, the DI & SI would be DECREASED 
            instead of INCREASED.
            ex:     cld     ;clears direction flag
                    std     ;sets direction flag

STACK RELATED INDEX REGISTERS:
    BP: Base Pointer. Can be used to access the stack. Default segment is
        SS.  Can be used to access data in other segments throught the use
        of a SEGMENT OVERRIDE.

        ex: mov al,[ES:BP] ;moves a byte from segment ES, offset BP
            Segment overrides are used to specify WHICH of the 4 (or 6 on the
            386) segment registers to use.

    SP: Stack Pointer. Does just that.  Segment overrides don't work on this 
        guy.  Points to the current position in the stack.  Don't alter unless
        you REALLY know what you are doing.
        
SEGMENT REGISTERS:
    DS: Data segment- all data read are from the segment pointed to be this
        segment register unless a segment overide is used.
        Used as source segment for movs, lods
        This segment also can be thought of as the "Default Segment" because
        if no segment override is present, DS is assumed to be the segmnet
        you want to grab the data from.

    ES: Extra Segment- this segment is used as the destination segment
        for movs, stos
        Can be used as just another segment...  You need to specify [ES:??]
        to use this segment.

    FS: (386+) No particular reason for it's name... I mean, we have CS, DS,
        and ES, why not make the next one FS? :)  Just another segment..
    
    GS: (386+) Same as FS

    
OTHERS THAT YOU SHOULDN'T OR CAN'T CHANGE:
    CS: Segment that points to the next instruction- can't change directly
    IP: Offset pointer to the next instruction- can't even access
        The only was to change CS or IP would be through a JMP, CALL, or RET

    SS: Stack segment- don't mess with it unless you know what you're
        doing.  Changing this will probably crash the computer.  This is the
        segment that the STACK resides in.

????????
Heck, as long as I've mentioned it, lets look at the STACK:

    The STACK is an area of memory that has the properties of a STACK of
    plates- the last one you put on is the first one take off.  The only
    difference is that the stack of plates is on the roof.  (Ok, so that
    can't really happen... unless gravity was shut down...)  Meaning that
    as you put another plate (or piece of data) on the stack, the STACK grows
    DOWNWARD.  Meaning that the stack pointer is DECREASED after each PUSH,
    and INCREASED after each POP.

  _____ Top of the allocated memory in the stack segment (SS)
    ? 
    ?
    ?
    ? ? SP (the stack pointer points to the most recently pushed byte)

    Truthfully, you don't need to know much more than a stack is Last In,
    First Out (LIFO).

  WRONG ex: push    cx  ;this swaps the contents of CX and AX
            push    ax  ;of course, if you wanted to do this quicker, you'd
            ...
            pop     cx  ;just say XCHG cx,ax
            pop     ax  ; but thats not my point.

  RIGHT ex: push    cx  ;this correctly restores AX & CX  
            push    ax
            ...
            pop     ax
            pop     cx

????????????

Now I'll do a quick run through on the assembler instructions that you MUST
know:

????
MOV:

    Examples of different addressing modes: 

        MOV ax,5        ;moves and IMMEDIATE value into ax (think 'AX = 5')
        MOV bx,cx       ;moves a register into another register
        MOV cx,[SI]     ;moves [DS:SI] into cx (the Default Segment is Used)
        MOV [DI+5],ax   ;moves ax into [DS:DI+5]
        MOV [ES:DI+BX+34],al    ;same as above, but has a more complicated
                                ;OFFSET (=DI+BX+34) and a SEGMENT OVERRIDE
        MOV ax,[546]    ;moves whats at [DS:546] into AX
                        
    Note that the last example would be totally different if the brackets 
    were left out.  It would mean that an IMMEDIATE value of 546 is put into
    AX, instead of what's at offset 546 in the Default Segment.
    
ANOTHER STANDARD NOTATION TO KNOW:
    Whenever you see brackets [] around something, it means that it refers to 
    what is AT that offset.  For instance, say you had this situation:

????????????
MyData  dw  55
    ...
    mov ax,MyData
????????????

    What is that supposed to mean?  Is MyData an Immediate Value?  This is
    confusing and for our purposes WRONG.  The 'Correct' way to do this would
    be:

????????????
MyData  dw  55
    ...
    mov ax,[MyData]
????????????

    This is clearly moving what is AT the address of MyData, which would be
    55, and not moving the OFFSET of MyData itself.  But what if you 
    actually wanted the OFFSET?  Well, you must specify directly.

????????????
MyData  dw  55
    ...
    mov ax,OFFSET MyData
????????????

    Similiarly, if you wanted the SEGMENT that MyData was in, you'd do this:

????????????
MyData  dw  55
    ...
    mov ax,SEG MyData
????????????

????????????????????????
INT:
    Examples:
        INT 21h     ;calls DOS standard interrupt # 21h
        INT 10h     ;the Video BIOS interrupt..
        
    INT is used to call a subroutine that performs some function that you'd
    rather not write yourself.  For instance, you would use a DOS interrupt 
    to OPEN a file.  You would similiarly use the Video BIOS interrupt to
    set the screen mode, move the cursor, or to do any other function that 
    would be difficult to program.

    Which subroutine the interrupt preforms is USUALLY specified by AH.
    For instance, if you wanted to print a message to the screen you'd
    use INT 21h, subfunction 9 by doing this:

????????????
    mov ah,9
    int 21h
????????????

    Yes, it's that easy.  Of course, for that function to do anything, you
    need to specify WHAT to print.  That function requires that you have
    DS:DX be a FAR pointer that points to the string to display.  This string
    must terminate with a dollar sign.  Here's an example:

????????????
MyMessage db    "This is a message!$" 
    ...
    mov     dx,OFFSET MyMessage
    mov     ax,SEG MyMessage
    mov     ds,ax
    mov     ah,9
    int     21h
    ...
????????????

    The DB, like the DW (and DD) merely declares the type of a piece of data.

        DB => Declare Byte (I think of it as 'Data Byte')
        DW => Declare Word
        DD => Declare Dword
    
    Also, you may have noticed that I first put the segment value into AX
    and then put it into DS.  I did that because the 80x86 does NOT allow
    you to put an immediate value into a segment register.  You can, however,
    pop stuff into a Segment register or mov an indexed value into the
    segment register.  A few examples:

????????????
  LEGAL:
    mov     ax,SEG MyMessage
    mov     ds,ax

    push    SEG Message
    pop     ds

    mov     ds,[SegOfMyMessage]     
            ;where [SegOfMyMessage] has already been loaded with 
            ; the SEGMENT that MyMessage resides in
  ILLEGAL:
    mov     ds,10
    mov     ds,SEG MyMessage
????????????

Well, that's about it for what you need to know to get started...

????????????????????????????????????????????????????????????????????????
    And now the FRAME for an ASSEMBLER program.
??????????????????????????????????????????????????????????????????????

The Basic Frame for an Assembler program using Turbo Assembler simplified
    directives is:

;===========-

    DOSSEG  ;This arranges the segments in order according DOS standards
            ;CODE, DATA, STACK
    .MODEL SMALL    ;dont worry about this yet
    .STACK  200h    ;tells the compiler to put in a 200h byte stack
    .CODE           ;starts code segment

    ASSUME  CS:@CODE, DS:@CODE 

START:      ;generally a good name to use as an entry point

    mov     ax,4c00h
    int     21h

END START

;===========- By the way, a semicolon means the start of a comment.

    If you were to enter this program and TASM & TLINK it, it would execute
    perfectly.  It will do absolutly nothing, but it will do it well.

    What it does:
        Upon execution, it will jump to START. move 4c00h into AX,
        and call the DOS interrupt, which exits back to DOS.

        Outout seen: NONE
????????????????????????????????????????????????????????????????????????

That's nice, eh?  If you've understood the majority of what was presented 
in this document, you are ready to start programming!

See ASM0.TXT and ASM0.ASM to continue this wonderful assembler stuff...


Written By Draeden/VLA

Text for ASM #0
    
    Hello there, this is Draeden typing this wonderful document.  This is 
an explanation of the basic assembler frame.  This document assumes that you
know what hexdecimal is and somewhat how it works, that you have a copy of  
TASM and TLINK, that you know what AX is, and how it relates to AL and AH, 
and you know the commands: 

    MOV xx,xx 
    JMP xxxx
and INT xx

    I'm also making the rash assumption that you want to learn ASSEMBLER. :)
To assemble ASM0.ASM into an executable do the following:

        TASM ASM0
        TLINK ASM0

    Now you can exececute this wonderful program.  Go ahead.  Try it.  In
case you are having problems figuring out how to execute this, just type:

        ASM0  (followed by the enter key)

    No, you did nothing wrong. This code (ASM0.ASM) does nothing.  All it 
does is return control to DOS.  It is the basic frame for an assembler 
program.  All of the programs that I write use this frame.  If you want to 
know what each part does, read on.  If you already know, just go read 
ASM1.TXT.

    The number followed by the colon means that this is from ASM0.ASM and 
tells which line it is from.
    
1:    DOSSEG        

  DOSSEG Sorts the segment using DOS standard, which is:

     1) 'code' segments (in alphabetical order)
     2) 'data' segments (in alphabetical order)
     3) 'stack' segments (again, in alphabetical order)

  Although it may not seem clear what this does, don't worry about it.  Just
have it as the first line in your assembler programs, until you understand it.

2:    .MODEL SMALL  

MODEL ONLY needs to be used if you use the simplified segments, which I 
strongly recommend.

In a nutshell, .MODEL Selects the MODEL to use.  This is used so that this 
code can be linked with C, PASCAL, ADA, BASIC, other ASSEMBLER program, and 
other languages with ease.  It also tells the compiler how to treat your
code and data segments.

NEAR means that the data/code can be reached using a 16bit pointer (offset)
FAR  means that a SEGMENT:OFFSET pair must be used to access all the data/code

Possible MODELS are:

     TINY: Code and Data must fit in same 64k segment.  
           Both Code and Data are NEAR.

    SMALL: Code & Data have seperate segment, but must be each less than 64k
           Both Code and Data are NEAR.
           For most applications, this will suffice.

   MEDIUM: Code may be larger than 64k, but Data has to be less than 64k
           Code is FAR, Data is NEAR.

  COMPACT: Code is less than 64k, but Data may be greater than 64k
           Code is NEAR, Data is FAR.

    LARGE: Both Code & Data can be greather than 64k.  Both are FAR, but a 
           single array cannot be greater than 64k.  Note that max array size
           means nothing if you are just writing in assembler.  This only
           matters when you link to C or another high level language.

     HUGE: Same as LARGE, but arrays can be greater than 64k.
           What that means is that the array index is a far pointer, instead
           of a NEAR one.
           LARGE and HUGE are identicle to the assembler programmer.

3:    .STACK 200h   

    Tells the compiler to set up a 200h byte stack upon execution of the
program.  NOTE: the size you choose for the stack does not change the size 
of the file on disk.  You can see what I mean by changing the 200h to, say,
400h and then recompiling.  The file sizes are identicle.

    This could be replaced with:

: MyStack SEGMENT PARA PUBLIC STACK 'STACK'
:       db  200h dup (0)
: MyStack ENDS

    BUT, doing it this way makes your executable 512 bytes bigger.  If you
were to double to 400h, the executable would be another 512 bytes bigger.
I think it's pretty obvious why the simplified version is preferred.

4:    .DATA           

    Simplified, unnamed 'data' segment.  This is where those simplified 
segments become very handy.  If you were to write out the segment declaration
the regular way, you'd have to write something like this:

: MyData SEGMENT PARA PUBLIC 'DATA'
:
:  ...                  ;your data goes here...
:
: MyData ENDS

Where 'MyData' is the name of the segment, public means that its, well, 
public, and PARA is the alignment of the start of the segment.  'DATA' 
specifies the type of the segment.  Instead of PARA, WORD or BYTE could 
have been used.  (PARA = segment will start on an adress that is a multiple 
of 16, WORD = even addresses, BYTE = where ever it lands.)

5:    .CODE

    Pretty much the same story as above, but this is for the code segment.
Could be replaced with:

 - IN MASM MODE -
: MyCode SEGMENT PARA PUBLIC 'CODE'
:  ...
: MyCode ENDS

 - IN IDEAL MODE -
: SEGMENT MyCode PARA PUBLIC 'CODE'
:  ...
: ENDS MyCode  ;the 'MyCode' is optional in IDEAL mode
    
6: START:

    This is just a label.  Labels just provide a way of refencing memory 
easily.  Like I could say "JMP START" which would jump to the label START and 
execute the code immediatly after it.  Or I could say MOV AX,[Start], which
would grab the WORD that was immediatly after the label START.

7: mov     ax,4c00h
8: int     21h         

    This bit of code calls DOS function # 4ch, which returns control to DOS
and sends back the error level code that is in AL (which is zero).
Note that for all int 21h DOS functions, AH contains the function number.

THIS MUST BE AT THE END OF THE CODE! If it isn't, the code will continue to
run...  right out of the end of your program and will execute whatever code
is there!  The program will crash with out it!

9: END START

This tells the compiler that we are all done with our program and that it can
stop compiling, now.  And it tells the compiler to put the entry point at
the label START.  This means that DOS is effectivly starting your program by
executing this:

: JMP START

    As you would probably guess, if you just put `END' instead of `END START'
and you compiled and linked the program, when you went to execute the code,
the computer will probably freeze because it does not know where to start
execution.

    Ok, now that you know what the frame is/does, lets actually make the 
program do something.  Lets be wild and crazy, and PRINT A MESSAGE! 


                            CONTINUED IN ASM1.TXT


???????????????????????????????????????????????????????????????????????????
? ASM0.ASM ?
????????????

    DOSSEG
    .MODEL SMALL
    .STACK 200h
    .DATA
    .CODE

START:

;
;   Your code goes here...
;

    mov     ax,4c00h
    int     21h
END START

;   THIS CODE DOES ABSOLUTLY NOTHING EXCEPT RETURN CONTROL TO DOS!


????????????????????????????????????????????????????????????????????????????
    ASM1.ASM - print a string
????????????????????????????????????????????????????????????????????????????

    Well, here's the classic example for the first program in just about
every language.  It prints a message to the screen by using a DOS function.
More specifically, it uses function 9 of interrupt 21h.  Here's the mock
specification for the function:

?-
|IN:     ah = 9      ;ah tells INT 21h which function you want
|        DS:DX = FAR pointer to the string to be printed.
|                    ;the string must terminate with a dollar sign ($)
|
|OUT:    Prints the string to the screen
?-

    Other than that function, there's nothing new that can't easily be
figured out.  The directive SEG, as you might have guessed, returns the 
segment that the specified label is in.  OFFSET returns the offset from 
the begining of the segment to the specified label.  Together, you can
form a FAR pointer to a specified label.

    Another thing you might wonder about is why I put the SEG Message into
AX and THEN Put it in DS.  

    The answer is:  You have to.  An immediate value cannot be put into a 
segment register, but a register or an indexed value can.  For instance:

These are legal:

:   mov     DS,AX
:   mov     DS,[TheSegment]

But these are not:

:   mov     DS,50
:   mov     DS,0a000h
    
    One last piece of info: in the lines:

:Message     db  "This was printed using function 9 " 
:            db  "of the DOS interrupt 21h.$"

    The DB is just a data type.  Its the same as a CHAR in C, which is 1 byte
    in length.

    Other common data types are:

    DW  same as an INT in C - 2 bytes
    DD  same as a double int or long int or a FAR pointer - 4 bytes


    Well, that's pretty much it for this short section...  Try playing around
with the 'print' function... Ya learn best by playing with it.


One last side note:
    I COULD have put the message in the CODE segment instead, by doing this:
    
????????????????????

    DOSSEG
    .MODEL SMALL
    .STACK 200h
    .CODE

Message     db  "Hey look! I'm in the code segment!$" 
            
START:
    mov     ax,cs   ;since CS already points to the same segment as Message,
    mov     ds,ax   ;I don't have to explicitly load the segment that message
                    ;is in..

    mov     dx,offset Message
    mov     ah,9
    int     21h

    mov     ax,4c00h    ;Returns control to DOS
    int     21h         ;MUST be here! Program will crash without it!

END START

????????????????????

    The advantage to having all your data in the CODE segment is that DS and
ES can be pointing anywhere and you can still access your data via a segment
override!  

    Example:
        say I'm in the middle of copying one section of the screen memory to
    another and I need to access the variable "NumLines" I'd do it like this:

????????

    mov ax,[CS:NumLines]    ;this is in IDEAL mode
            ^^^
????????    Code Segment override

    Pretty flexable, eh?


???????????????????????????????????????????????????????????????????????????
? ASM1.ASM ?
????????????

    DOSSEG
    .MODEL SMALL
    .STACK 200h
    .DATA

Message     db  "This was printed using function 9 " 
            db  "of the DOS interrupt 21h.$"
                  
    .CODE
    
START:
    mov     ax,seg Message  ;moves the SEGMENT that `Message' is in into AX
    mov     ds,ax           ;moves ax into ds (ds=ax)
                            ;you cannot do this -> mov ds,seg Message

    mov     dx,offset Message   ;move the OFFSET of `Message' into DX
    mov     ah,9        ;Function 9 of DOS interupt 21h prints a string that
    int     21h         ;terminates with a "$".  It requires a FAR pointer to
                        ;what is to be printed in DS:DX

    mov     ax,4c00h    ;Returns control to DOS
    int     21h         ;MUST be here! Program will crash without it!

END START

?????????????????????????????????????????????????????????????????????????????
    ASM2.TXT - intro to keyboard and flow control
?????????????????????????????????????????????????????????????????????????????
    
    Alright.  This bit of code introduces control flow, keyboard input, and 
    a way to easily print out one character.

????????
    First off, lets examine the easiest one: printing a character.

    It's like this: you put the character to print in DL, put 2 in AH and
    call interrupt 21h.  Damn easy.

????????
    Ok, lets look at the next easiest one: keyboard input.

    There are quite a few functions related to INT 16h (the keyboard
    interrupt.)  They are:

FUNCTION#
---------

    0h  -Gets a key from the keyboard buffer.  If there isn't one, it waits
            until there is.
            Returns the SCAN code in ah, and the ASCII translation in AL

    1h  -Checks to see if a key is ready to grab.  Sets the zero flag if a
            key is ready to grab.  Grab it with Fn# 0
            This also returns the same info about the key as Fn#0, but does
            not remove it from the buffer.

    2h  -Returns the shift flags in al.  They are:
            bit 7 - Insert active
            bit 6 - Caps lock active
            bit 5 - Num Lock active
            bit 4 - Scroll lock active
            bit 3 - Alt pressed
            bit 2 - Ctrl pressed
            bit 1 - Left shift pressed
            bit 0 - right shift pressed

    3h  -You can set the Typematic Rate and delay with this function
            registers must be set as follows
            AL = 5
            BH = Delay value (0-3: 250,500,750,1000 millisec)
            BL = Typematic rate (0-1fh) 1fh = slowest (2 chars per sec)
                0 =fastest (30 chars per second)

    4h  -Key Click control - not important

    5h  -STUFF the keyboard
        input:
            CH = scan code
            CL = ascii code

        output:
            al = 0 no error
            al = 1 keyboard buffer is full

   10h  -Same as #0, but its for the extended keyboard.  Checks all the keys.

   11h  -Same as #1, but for the extended keyboard.

   12h  -Same as #2, but AH contains additional shift flags:
            bit 7 - Sys req pressed
            bit 6 - Caps lock active
            bit 5 - Num Lock active
            bit 4 - Scroll lock active
            bit 3 - Right Alt active
            bit 2 - Right Ctrl active
            bit 1 - Left Alt active
            bit 0 - Right Alt active
        Al is EXACTLY the same as in Fn#2


WHERE AH= the function number when you call INT 16h

????????
    That's neat-o, eh?  Now on to flow controll via CMP and Jcc...

CMP:
???
    CMP is the same as SUB, but it does NOT alter any registers, only the
    flags.  This is used in conjunction with Jcc.

Jcc:
???
    Ok, Jcc is not a real instruction, it means 'jump if conditionis met.'

    I'll break this into 3 sections, comparing signed numbers, comparing 
    unsigned numbers, and misc.

    Note that a number being 'unsigned' or 'signed' only depends on how you
    treat it.  That's why there are different Jcc for each...

    If you treat it as a signed number, the highest bit denotes whether it's
    negative or not.  
    
    Prove to yourself that 0FFFFh is actually -1 by adding 1 to 0FFFFh.  You 
    should get a big zero: 00000h.  (Remember that the number is ONLY 16 bits
    and the carry dissapears..)

UNSIGNED:
????????
    JA  -jumps if the first number was above the second number
    JAE -same as above, but will also jump if they are equal

    JB  -jumps if the first number was below the second
    JBE -duh...

    JNA -jumps if the first number was NOT above... (same as JBE)
    JNAE-jumps if the first number was NOT above or the same as..
            (same as JB)
    JNB -jumps if the first number was NOT below... (same as JAE)
    JNBE-jumps if the first number was NOT below or the same as..
            (same as JA)
    JZ  -jumps if the two numbers were equal (zero flag = 1)
    JE  -same as JZ, just a different name

    JNZ -pretty obvious, I hope...
    JNE -same as above...

SIGNED:
??????
    JG  -jumps if the first number was > the second number
    JGE -same as above, but will also jump if they are equal

    JL  -jumps if the first number was < the second
    JLE -duh...

    JNG -jumps if the first number was NOT >... (same as JLE)
    JNGE-jumps if the first number was NOT >=.. (same as JL)

    JNL -jumps if the first number was NOT <... (same as JGE)
    JNLE-jumps if the first number was NOT <=... (same as JG)

    JZ, JE, JNZ, JNE - Same as for Unsigned

MISC:
????
    JC  -jumps if the carry flag is set
    JNC -Go figgure...

    Here's the rest of them... I've never had to use these, though...

    JO  -jump if overflow flag is set
    JNO -...

    JP  -jump is parity flag is set
    JNP -...
    JPE -jump if parity even (same as JP)
    JPO -jump if parity odd (same as JNP)

    JS  -jumps if sign flag is set
    JNS -...


Here's the flags really quickly:
????????????????????????????????
bit#    8 7 6 5 4 3 2 1 0
        ?????????????????
symbol: O D I T S Z A P C

    O = OverFlow flag
    D = Direction flag  *
    I = Interrupt flag
    T = Trap flag
    S = Sign flag
    Z = Zero flag       *
    A = Auxiliary flag
    C = Carry flag      *

The * denotes the ones that you should know.

?????????????????????????????????????????????????????????????????????????????

That's it for now...  Until next time...

Draeden\VLA


???????????????????????????????????????????????????????????????????????????
? ASM2.ASM ?
????????????

;   ASM2.ASM
; Prints messages get keyboard input and has control flow

    DOSSEG
    .MODEL SMALL
    .STACK 200h
    .DATA

Prompt      db  13,10,"Do you want to be prompted again? (Y/N) $"
NoMessage   db  13,10,"Ok, then I won't prompt you anymore.$"
YesMessage  db  13,10,"Here comes another prompt!$"
UnKnownKey  db  13,10,"Please hit either Y or N.$"

    .CODE

START:
    mov     ax,@DATA    ;moves the segment of data into ax
    mov     ds,ax

MainLoop:
    mov     ah,9
    mov     dx,offset Prompt
    int     21h         ;print a message

    mov     ah,0
    int     16h         ;get a key, returned in AX
                        ;AL is the ASCII part
                        ;AH is the SCAN CODE
    push    ax
    mov     dl,al
    mov     ah,2
    int     21h         ;print character in dl
    pop     ax

    cmp     al,"Y"      ;was the character a 'Y'?
    jne     NotYes      ;nope it was Not Equal

    mov     ah,9
    mov     dx,offset YesMessage
    int     21h
    jmp     MainLoop

NotYes:
    cmp     al,"N"      ;was the character a 'N'
    je      ByeBye      ;Yes, it was Equal

    mov     dx,offset UnknownKey
    mov     ah,9
    int     21h
    jmp     MainLoop

ByeBye:
    mov     dx,offset NoMessage
    mov     ah,9
    int     21h

    mov     ax,4c00h    ;Returns control to DOS
    int     21h         ;MUST be here! Program will crash without it!

END START

                  - ASMVLA01 - File I/O - 04/14/93 -

    Lately we have been quite busy with school, so this second issue is a 
little behind schedule.  But that's life... This little issue will quickly
show off the DOS file functions: read, write, open, close, create & others.  
They are all pretty much the same, so there isn't a whole lot to go over.
But, as a bonus, I'm going to throw in a bit about how to do a subroutine.
Let's do the subroutine stuff first.

`Procedures' as they are called, are declared like this:

????????????????????????????????????????????????????????????????????????????

PROC TheProcedure

    ...             ;do whatever..
    
    ret             ;MUST have a RET statement!
ENDP TheProcedure

????????????????????????????????????????????????????????????????????????????

    In the procedure, you can do basically anything you want, just at the 
end of it, you say ret.  You can also specify how to call the PROC by putting
a NEAR or FAR after the procedure name.  This tells the compiler whether
to change segment AND offset, or just offset when the procedure is called.
Note that if you don't specify, it compiles into whatever the default is for
the current .MODEL (small = near, large = far)

????????????????????????????????????????????????????????????????????????????

PROC TheProc NEAR

    ...

    ret             ;this compiles to `retn' (return near- pops offset off
ENDP TheProc        ; stack only)

    OR

PROC TheProc FAR

    ...

    ret             ;compiles to `retf' pops both offset & segment off stack
ENDP TheProc        ; pops offset first

????????????????????????????????????????????????????????????????????????????

    That's basically all there is to that.  Note that if you REALLY wanted to
be tricky, you could do a far jump by doing this:

????????????????????????????????????????????????????????????????????????????
    push    seg TheProc
    push    offset TheProc
    retf
????????????????????????????????????????????????????????????????????????????

    This would "return" you to the beginning of the procedure "TheProc"...
This code is just to illustrate a point.  If you actually did something like
this and compiled and executed it, it would bomb.  Know why?  What happens 
when it hits the `ret' in the PROC?  Well it pops off the offset and puts 
it in IP and then pops the segment and puts it in CS.  Who knows what was
on the stack... will return to an unknown address and probably crash.  (It
DEFINATELY will not continue executing your code.)

    Of course, the only stack operations are PUSH and POP.  All they do is 
push or pop off the stack a word sized or a Dword sized piece of data.  NEVER
under ANY circumstance try to push a byte sized piece of data!  The results 
are unpredictable.  Well, not really, but just don't do it, ok?

    There are also two commands that'll save you some time and code space:

PUSHA and POPA (push all and Pop all)

    PUSHA pushes the general registers in this order:

AX, CX, DX, BX, SP, BP, SI, DI

    POPA pops the general registers in this order:

DI, SI, BP, (sp), BX, DX, CX, AX

    SP is different because popa does NOT restore the value of SP.  It merely 
pops it off and throws it away.

    For the 386+, pushad and popad push and pop all extended registers in
the same order.  You don't need to memorize the order, because you don't
need to know the order until you go and get tricky. (hint: the location of
AX on the stack is [sp + 14] - useful if you want to change what AX returns,
but you did a pusha cause you wanted to save all the registers (except AX)
Then you'd do a popa, and AX= whatever value you put in there.

    ????

    Alright, now a slightly different topic: memory management

    Ok, this isn't true by-the-book memory management, but you need to know
one thing:  Upon execution of a program, DOS gives it ALL memory up to the
address A000:0000. This happens to be the beginning of the VGA buffer...
Another thing you must know is that, if you used DOSSEG at the top of your
file, the segment is the last piece of your program.  The size of the segment
is derived from the little command `STACK 200h' or whatever the value was
that you put up there.  The 200h is the number of bytes in the stack.  To get
the number of paragraphs, you'd divide by 16.  Here's an example of how I can
get a pointer to the first valid available segment that I can use for data:

????????????????????????????????????????????????????????????????????????????
    mov     ax,ss       ;grab the stack segment
    add     ax,200h/16  ;add the size of the stack 200h/16 = 20h

    ;AX now contains the value of the first available segment the you can
    ; use.
????????????????????????????????????????????????????????????????????????????

    This is very nice, because you can just plop your data right there
and you have a 64k buffer you can use for anything you want.

    Ok, say you want to find out how much memory is available to use.  This
would be done like this:  (no suprises, I hope.)

????????????????????????????????????????????????????????????????????????????
    mov     ax,ss       ;grab the stack segment
    add     ax,200h/16  ;add the size of the stack 200h/16 = 20h
    mov     bx,0A000h   ;upper limit of the free memory
    sub     bx,ax       ;bx= # of paragraphs available
????????????????????????????????????????????????????????????????????????????

    Pretty darn simple.  That's enough of the overhead that you must know
to understand the included ANSI viewer (asm3.asm)  

        Now to the FILE I/O stuff...

    Files can be opened, read from, written to, created, and closed.  To open
a file, all you need to do is give the DOS interrupt a name & path.  All
references to that file are done through what's known as a file handle. A
file handle is simply a 16bit integer that DOS uses to identify the file.
It's used more or less like an index into chart of pointers that point to 
a big structure that holds all the info about the file- like current position
in the file, file type, etc.. all the data needed to maintain a file.
The `FILES= 20' thing in your autoexec simply tells DOS how much memory to
grab for those structures. ( Files=20 grabs enough room for 20 open files. )

    ANYway, here's each of the important function calls and a rundown on what
they do and how to work them.

????????????????????????????????????????????????????????????????????????????
FILE OPEN: Function 3Dh

 IN:
    ah= 3Dh
    al= open mode

        bits 7-3: Stuff that doesn't matter to us
        bits 2-0: Access code
            000 read only access
            001 write only access
            010 read and write access

    DS:DX= pointer to the ASCIIZ filename
        ASCIIZ means that its an ASCII string with a Zero on the end.

 Returns:
        CF=1 error occured
            AX= error code- don't worry about what they are, if the carry
                is set, you didn't open the file.

        CF=0 no error
            AX= File Handle ;you need to keep this- it's your only way to
                            ; reference your file!

  ???? EXAMPLE ????

    [...]   ;header stuff

    .CODE           ;this stuff is used for all the examples

  FileName  db "TextFile.TXT",0
  FileHandle dw 0
  Buffer    db  300 dup (0)
  BytesRead dw  0
  FileSize  dd  0

    [...]   ;more stuff

    mov     ax,3d00h    ; open file for read only
    mov     ax,cs
    mov     ds,ax       ;we use CS, cause it's pointing to the CODE segment
                        ; and our file name is in the code segment
    mov     dx,offset FileName
    int     21h
    jc      FileError_Open

    mov     [FileHandle],ax

    [...]   ;etc...

????????????????????????????????????????????????????????????????????????????
FILE CLOSE: Function 3Eh

  IN:
    AH= 3Eh
    BX= File Handle

  RETURN:
    CF=1 error occured, but who cares?
  
  ???? EXAMPLE ????

    mov     bx,[FileHandle]
    mov     ah,3eh
    int     21h

????????????????????????????????????????????????????????????????????????????
FILE READ: Function 3Fh

  IN:
    AH= 3Fh
    BX= File Handle
    CX= Number of bytes to read
    DS:DX= where to put data that is read from the file (in memory)
    
  RETURN:
    AX= number of bytes actually read- if 0, then you tried to read from
        the end of the file.

  ???? EXAMPLE ????

    mov     bx,[FileHandle]
    mov     ax,cs
    mov     ds,ax
    mov     dx,offset buffer
    mov     ah,3Fh
    mov     cx,300
    int     21h

    mov     [BytesRead],ax

????????????????????????????????????????????????????????????????????????????
FILE WRITE: Function 40h

  IN:
    AH= 40h
    BX= File Handle
    CX= Number of bytes to write
    DS:DX= where to read data from (in memory) to put on disk
    
  RETURN:
    AX= number of bytes actually written- if not equal to the number of bytes
        that you wanted to write, you have an error.
        
  ???? EXAMPLE ????

    mov     bx,[FileHandle]
    mov     ax,cs
    mov     ds,ax
    mov     dx,offset buffer
    mov     ah,40h
    mov     cx,[BytesRead]
    int     21h

    cmp     cx,ax
    jne     FileError_Write

????????????????????????????????????????????????????????????????????????????
FILE CREATE: Function 3Ch

 IN:
    ah= 3Ch
    cl= file attribute

        bit 0: read-only
        bit 1: hidden
        bit 2: system
        bit 3: volume label
        bit 4: sub directory
        bit 5: Archive
        bit 6&7: reserved

    DS:DX= pointer to the ASCIIZ filename
        ASCIIZ means that its an ASCII string with a Zero on the end.

 Returns:
        CF=1 error occured
            AX= error code- don't worry about what they are, if CF
                is set, you didn't create the file.

        CF=0 no error
            AX= File Handle ;you need to keep this- it's your only way to
                            ; reference your file!
  ???? EXAMPLE ????

    mov     ah,3ch
    mov     ax,cs
    mov     ds,ax       ;we use CS, cause it's pointing to the CODE segment
                        ; and our file name is in the code segment
    mov     dx,offset FileName
    mov     cx,0        ;no attributes
    int     21h
    jc      FileError_Create

    mov     [FileHandle],ax

????????????????????????????????????????????????????????????????????????????
FILE DELETE: Function 41h

 IN:
    ah= 41h
    DS:DX= pointer to the ASCIIZ filename
    
 Returns:
        CF=1 error occured
            AX= error code- 2= file not found, 3= path not found
                    5= access denied

        CF=0 no error

  ???? EXAMPLE ????

    mov     ah,41h      ;kill the sucker
    mov     ax,cs
    mov     ds,ax
    mov     dx,offset FileName
    int     21h
    jc      FileError_Delete

????????????????????????????????????????????????????????????????????????????
FILE MOVE POINTER: Function 42h

 IN:
    ah= 42h
    BX= File Handle
    CX:DX= 32 bit pointer to location in file to move to    
    AL= 0  offset from beginning of file
      = 1  offset from curent position
      = 2  offset from the end of the file
      
 Returns:
        CF=1 error occured
            AX= error code- no move occured

        CF=0 no error
            DX:AX 32 bit pointer to indicate current location in file
            
  ???? EXAMPLE ????

    mov     ah,42h      ;find out the size of the file
    mov     bx,[FileHandle]
    xor     cx,cx
    xor     dx,dx
    mov     al,2
    int     21h
    
    mov     [word low FileSize],ax
    mov     [word high FileSize],dx ;load data into filesize

    (or in MASM mode, 

            mov word ptr [FileSize],ax
            mov word ptr [FileSize+2],dx

    need I say why I like Ideal mode? )

????????????????????????????????????????????????????????????????????????????
FILE CHANGE MODE: Function 43h

 IN:
    ah= 43h
    DS:DX= pointer to the ASCIIZ filename
    al= 0
        returns file attributes in CX
    al= 1 
        sets file attributes to what's in CX
    
 Returns:
        CF=1 error occured
            AX= error code- 2= file not found, 3= path not found.
                    5= access denied

        CF=0 no error

  ???? EXAMPLE ???? Lets erase a hidden file in your root directory...

  FileName db   "C:\msdos.sys",0

    [...]

    mov     ah,43h          ;change attribute to that of a normal file
    mov     ax,cs
    mov     ds,ax
    mov     dx,offset FileName
    mov     al,1            ;set to whats in CX
    mov     cx,0            ;attribute = 0
    int     21h

    mov     ah,41h          ;Nuke it with the delete command
    int     21h

????????????????????????????????????????????????????????????????????????????

    Well, that's all for now.  I hope this info is enough for you to do some 
SERIOUS damage... :)  I just don't want to see any 'bombs' running around
erasing the hidden files in the root directory, ok?

    Anyway, go take a look at asm3.asm- it's a SIMPLE ansi/text displayer.
It just opens the file, reads it all into a "buffer" that was "allocated"
immediatly after the stack & reads in the entire file (if it's < 64k) and 
prints out the file character by character via DOS's print char (fn# 2).
Very simple and very slow.  You'd need a better print routine to go faster...
The quickest display programs would decode the ANSI on its own... But that's
kinda a chore...  Oh, well.  Enjoy.

    Draeden/VLA


    Suggested projects:

    1)  Write a program that will try to open a file, but if it does not
        find it, the program creates the file and fills it with a simple
        text message.

    2)  Write a program that will input your keystrokes and write them
        directly to a text file.

    3)  The write & read routines actually can be used for a file or device.
        Try to figure out what the FileHandle for the text screen is by
        writing to the device with various file handles.  This same channel,
        when read from, takes it's data from the keyboard.  Try to read data
        from the keyboard.  Maybe read like 20 characters...  CTRL-Z is the 
        end of file marker.

    4)  Try to use a file as `virtual memory'- open it for read/write access
        and write stuff to it and then read it back again after moving the 
        cursor position.



???????????????????????????????????????????????????????????????????????????
? ASM3.ASM ?
????????????

;   VERY, VERY simple ANSI/text viewer
;
;   Coded by Draeden [VLA]
;

    DOSSEG
    .MODEL SMALL
    .STACK 200h
    .CODE
    Ideal

;===- Data -===

BufferSeg   dw  0

ErrMsgOpen  db  "Error opening `"
FileName    db  "ANSI.TXT",0,8,"'$"     ;8 is a delete character
                                        ;0 is required for filename 
                                        ;(displays a space)
FileLength dw 0

;===- Subroutines -===

PROC DisplayFile NEAR
    push    ds

    mov     ax,cs
    mov     ds,ax
    mov     ax,3d00h    ;open file (ah=3dh)
    mov     dx,offset FileName
    int     21h
    jc      OpenError
    mov     bx,ax       ;move the file handle into bx

    mov     ds,[BufferSeg]
    mov     dx,0            ;load to [BufferSeg]:0000
    mov     ah,3fh
    mov     cx,0FFFFh       ;try to read an entire segments worth
    int     21h

    mov     [cs:FileLength],ax

    mov     ah,3eh
    int     21h             ;close the file

    cld
    mov     si,0
    mov     cx,[cs:FileLength]
PrintLoop:
    mov     ah,2
    lodsb
    mov     dl,al
    int     21h         ;print a character

    dec     cx
    jne     PrintLoop
    
    pop     ds
    ret

OpenError:
    mov     ah,9
    mov     dx,offset ErrMsgOpen
    int     21h

    pop     ds
    ret
ENDP DisplayFile

;===- Main Program -===

START:
    mov     ax,cs
    mov     ds,ax
    mov     bx,ss
    add     bx,200h/10h     ;get past the end of the file
    mov     [BufferSeg],bx  ;store the buffer segment

    call    DisplayFile

    mov     ax,4c00h
    int     21h
END START


Intel 8086 Family Architecture. . . . . . . . . . . . . . . . . . . . .   3

Instruction Clock Cycle Calculation . . . . . . . . . . . . . . . . . .   3

8088/8086  Effective Address (EA) Calculation . . . . . . . . . . . . .   3

Task State Calculation. . . . . . . . . . . . . . . . . . . . . . . . .   4

FLAGS - Intel 8086 Family Flags Register. . . . . . . . . . . . . . . .   4

MSW - Machine Status Word (286+ only) . . . . . . . . . . . . . . . . .   5

8086/80186/80286/80386/80486 Instruction Set. . . . . . . . . . . . . .   6
     AAA - Ascii Adjust for Addition. . . . . . . . . . . . . . . . . .   6
     AAD - Ascii Adjust for Division. . . . . . . . . . . . . . . . . .   6
     AAM - Ascii Adjust for Multiplication. . . . . . . . . . . . . . .   6
     AAS - Ascii Adjust for Subtraction . . . . . . . . . . . . . . . .   6
     ADC - Add With Carry . . . . . . . . . . . . . . . . . . . . . . .   7
     ADD - Arithmetic Addition. . . . . . . . . . . . . . . . . . . . .   7
     AND - Logical And. . . . . . . . . . . . . . . . . . . . . . . . .   7
     ARPL - Adjusted Requested Privilege Level of Selector (286+ PM). .   7
     BOUND - Array Index Bound Check (80188+) . . . . . . . . . . . . .   8
     BSF - Bit Scan Forward (386+). . . . . . . . . . . . . . . . . . .   8
     BSR - Bit Scan Reverse  (386+) . . . . . . . . . . . . . . . . . .   8
     BSWAP - Byte Swap       (486+) . . . . . . . . . . . . . . . . . .   8
     BT - Bit Test           (386+) . . . . . . . . . . . . . . . . . .   9
     BTC - Bit Test with Compliment (386+). . . . . . . . . . . . . . .   9
     BTR - Bit Test with Reset (386+) . . . . . . . . . . . . . . . . .   9
     BTS - Bit Test and Set  (386+) . . . . . . . . . . . . . . . . . .   9
     CALL - Procedure Call. . . . . . . . . . . . . . . . . . . . . . .  10
     CBW - Convert Byte to Word . . . . . . . . . . . . . . . . . . . .  10
     CDQ - Convert Double to Quad (386+). . . . . . . . . . . . . . . .  10
     CLC - Clear Carry. . . . . . . . . . . . . . . . . . . . . . . . .  11
     CLD - Clear Direction Flag . . . . . . . . . . . . . . . . . . . .  11
     CLI - Clear Interrupt Flag (disable) . . . . . . . . . . . . . . .  11
     CLTS - Clear Task Switched Flag (286+ privileged). . . . . . . . .  11
     CMC - Complement Carry Flag. . . . . . . . . . . . . . . . . . . .  11
     CMP - Compare. . . . . . . . . . . . . . . . . . . . . . . . . . .  12
     CMPS - Compare String (Byte, Word or Doubleword) . . . . . . . . .  12
     CMPXCHG - Compare and Exchange . . . . . . . . . . . . . . . . . .  12
     CWD - Convert Word to Doubleword . . . . . . . . . . . . . . . . .  12
     CWDE - Convert Word to Extended Doubleword (386+). . . . . . . . .  13
     DAA - Decimal Adjust for Addition. . . . . . . . . . . . . . . . .  13
     DAS - Decimal Adjust for Subtraction . . . . . . . . . . . . . . .  13
     DEC - Decrement. . . . . . . . . . . . . . . . . . . . . . . . . .  13
     DIV - Divide . . . . . . . . . . . . . . . . . . . . . . . . . . .  13
     ENTER - Make Stack Frame  (80188+) . . . . . . . . . . . . . . . .  14
     ESC - Escape . . . . . . . . . . . . . . . . . . . . . . . . . . .  14
     HLT - Halt CPU . . . . . . . . . . . . . . . . . . . . . . . . . .  14
     IDIV - Signed Integer Division . . . . . . . . . . . . . . . . . .  14
     IMUL - Signed Multiply . . . . . . . . . . . . . . . . . . . . . .  15
     IN - Input Byte or Word From Port. . . . . . . . . . . . . . . . .  15
     INC - Increment. . . . . . . . . . . . . . . . . . . . . . . . . .  16
     INS - Input String from Port  (80188+) . . . . . . . . . . . . . .  16
     INT - Interrupt. . . . . . . . . . . . . . . . . . . . . . . . . .  16
     INTO - Interrupt on Overflow . . . . . . . . . . . . . . . . . . .  17
     INVD - Invalidate Cache  (486+). . . . . . . . . . . . . . . . . .  17
     INVLPG - Invalidate Translation Look-Aside Buffer Entry (486+) . .  17
     IRET/IRETD - Interrupt Return. . . . . . . . . . . . . . . . . . .  17
     Jxx - Jump Instructions Table. . . . . . . . . . . . . . . . . . .  18
     JCXZ/JECXZ - Jump if Register (E)CX is Zero. . . . . . . . . . . .  18
     JMP - Unconditional Jump . . . . . . . . . . . . . . . . . . . . .  19
     LAHF - Load Register AH From Flags . . . . . . . . . . . . . . . .  19
     LAR - Load Access Rights (286+ protected). . . . . . . . . . . . .  19
     LDS - Load Pointer Using DS. . . . . . . . . . . . . . . . . . . .  20
     LEA - Load Effective Address . . . . . . . . . . . . . . . . . . .  20
     LEAVE - Restore Stack for Procedure Exit (80188+). . . . . . . . .  20
     LES - Load Pointer Using ES. . . . . . . . . . . . . . . . . . . .  20
     LFS - Load Pointer Using FS (386+) . . . . . . . . . . . . . . . .  21
     LGDT - Load Global Descriptor Table (286+ privileged). . . . . . .  21
     LIDT - Load Interrupt Descriptor Table (286+ privileged) . . . . .  21
     LGS - Load Pointer Using GS (386+) . . . . . . . . . . . . . . . .  21
     LLDT - Load Local Descriptor Table (286+ privileged) . . . . . . .  22
     LMSW - Load Machine Status Word (286+ privileged). . . . . . . . .  22
     LOCK - Lock Bus. . . . . . . . . . . . . . . . . . . . . . . . . .  22
     LODS - Load String (Byte, Word or Double). . . . . . . . . . . . .  22
     LOOP - Decrement CX and Loop if CX Not Zero. . . . . . . . . . . .  23
     LOOPE/LOOPZ - Loop While Equal / Loop While Zero . . . . . . . . .  23
     LOOPNZ/LOOPNE - Loop While Not Zero / Loop While Not Equal . . . .  23
     LSL - Load Segment Limit (286+ protected). . . . . . . . . . . . .  23
     LSS - Load Pointer Using SS (386+) . . . . . . . . . . . . . . . .  24
     LTR - Load Task Register (286+ privileged) . . . . . . . . . . . .  24
     MOV - Move Byte or Word. . . . . . . . . . . . . . . . . . . . . .  24
     MOVS - Move String (Byte or Word). . . . . . . . . . . . . . . . .  25
     MOVSX - Move with Sign Extend (386+) . . . . . . . . . . . . . . .  25
     MOVZX - Move with Zero Extend (386+) . . . . . . . . . . . . . . .  25
     MUL - Unsigned Multiply. . . . . . . . . . . . . . . . . . . . . .  25
     NEG - Two's Complement Negation. . . . . . . . . . . . . . . . . .  26
     NOP - No Operation (90h) . . . . . . . . . . . . . . . . . . . . .  26
     NOT - One's Compliment Negation (Logical NOT). . . . . . . . . . .  26
     OR - Inclusive Logical OR. . . . . . . . . . . . . . . . . . . . .  26
     OUT - Output Data to Port. . . . . . . . . . . . . . . . . . . . .  27
     OUTS - Output String to Port  (80188+) . . . . . . . . . . . . . .  27
     POP - Pop Word off Stack . . . . . . . . . . . . . . . . . . . . .  27
     POPA/POPAD - Pop All Registers onto Stack  (80188+). . . . . . . .  28
     POPF/POPFD - Pop Flags off Stack . . . . . . . . . . . . . . . . .  28
     PUSH - Push Word onto Stack. . . . . . . . . . . . . . . . . . . .  28
     PUSHA/PUSHAD - Push All Registers onto Stack  (80188+) . . . . . .  28
     PUSHF/PUSHFD - Push Flags onto Stack . . . . . . . . . . . . . . .  29
     RCL - Rotate Through Carry Left. . . . . . . . . . . . . . . . . .  29
     RCR - Rotate Through Carry Right . . . . . . . . . . . . . . . . .  29
     REP - Repeat String Operation. . . . . . . . . . . . . . . . . . .  30
     REPE/REPZ - Repeat Equal / Repeat Zero . . . . . . . . . . . . . .  30
     REPNE/REPNZ - Repeat Not Equal / Repeat Not Zero . . . . . . . . .  30
     RET/RETF - Return From Procedure . . . . . . . . . . . . . . . . .  31
     ROL - Rotate Left. . . . . . . . . . . . . . . . . . . . . . . . .  31
     ROR - Rotate Right . . . . . . . . . . . . . . . . . . . . . . . .  31
     SAHF - Store AH Register into FLAGS. . . . . . . . . . . . . . . .  32
     SAL/SHL - Shift Arithmetic Left / Shift Logical Left . . . . . . .  32
     SAR - Shift Arithmetic Right . . . . . . . . . . . . . . . . . . .  32
     SBB - Subtract with Borrow/Carry . . . . . . . . . . . . . . . . .  33
     SCAS - Scan String  (Byte, Word or Doubleword) . . . . . . . . . .  33
     SETAE/SETNB - Set if Above or Equal / Set if Not Below (386+). . .  33
     SETB/SETNAE - Set if Below / Set if Not Above or Equal (386+). . .  33
     SETBE/SETNA - Set if Below or Equal / Set if Not Above (386+). . .  34
     SETE/SETZ - Set if Equal / Set if Zero (386+). . . . . . . . . . .  34
     SETNE/SETNZ - Set if Not Equal / Set if Not Zero (386+). . . . . .  34
     SETL/SETNGE - Set if Less / Set if Not Greater or Equal (386+) . .  34
     SETGE/SETNL - Set if Greater or Equal / Set if Not Less (386+) . .  35
     SETLE/SETNG - Set if Less or Equal / Set if Not greater or Equal (386+)  35
     SETG/SETNLE - Set if Greater / Set if Not Less or Equal (386+) . .  35
     SETS - Set if Signed (386+). . . . . . . . . . . . . . . . . . . .  35
     SETNS - Set if Not Signed (386+) . . . . . . . . . . . . . . . . .  36
     SETC - Set if Carry (386+) . . . . . . . . . . . . . . . . . . . .  36
     SETNC - Set if Not Carry (386+). . . . . . . . . . . . . . . . . .  36
     SETO - Set if Overflow (386+). . . . . . . . . . . . . . . . . . .  36
     SETNO - Set if Not Overflow (386+) . . . . . . . . . . . . . . . .  36
     SETP/SETPE - Set if Parity / Set if Parity Even  (386+). . . . . .  37
     SETNP/SETPO - Set if No Parity / Set if Parity Odd (386+). . . . .  37
     SGDT - Store Global Descriptor Table (286+ privileged) . . . . . .  37
     SIDT - Store Interrupt Descriptor Table (286+ privileged). . . . .  37
     SHL - Shift Logical Left . . . . . . . . . . . . . . . . . . . . .  37
     SHR - Shift Logical Right. . . . . . . . . . . . . . . . . . . . .  38
     SHLD/SHRD - Double Precision Shift (386+). . . . . . . . . . . . .  38
     SLDT - Store Local Descriptor Table (286+ privileged). . . . . . .  38
     SMSW - Store Machine Status Word (286+ privileged) . . . . . . . .  38
     STC - Set Carry. . . . . . . . . . . . . . . . . . . . . . . . . .  39
     STD - Set Direction Flag . . . . . . . . . . . . . . . . . . . . .  39
     STI - Set Interrupt Flag  (Enable Interrupts). . . . . . . . . . .  39
     STOS - Store String  (Byte, Word or Doubleword). . . . . . . . . .  39
     STR - Store Task Register (286+ privileged). . . . . . . . . . . .  39
     SUB - Subtract . . . . . . . . . . . . . . . . . . . . . . . . . .  40
     TEST - Test For Bit Pattern. . . . . . . . . . . . . . . . . . . .  40
     VERR - Verify Read (286+ protected). . . . . . . . . . . . . . . .  40
     VERW - Verify Write (286+ protected) . . . . . . . . . . . . . . .  40
     WAIT/FWAIT - Event Wait. . . . . . . . . . . . . . . . . . . . . .  41
     WBINVD - Write-Back and Invalidate Cache (486+). . . . . . . . . .  41
     XCHG - Exchange. . . . . . . . . . . . . . . . . . . . . . . . . .  41
     XLAT/XLATB - Translate . . . . . . . . . . . . . . . . . . . . . .  41
     XOR - Exclusive OR . . . . . . . . . . . . . . . . . . . . . . . .  42

Intel 8086 Family Architecture

        General Purpose Registers               Segment Registers

        AH/AL  AX  (EAX)  Accumulator            CS     Code Segment
        BH/BL  BX  (EBX)  Base                   DS     Data Segment
        CH/CL  CX  (ECX)  Counter                SS     Stack Segment
        DH/DL  DX  (EDX)  Data                   ES     Extra Segment
                                                (FS)    386 and newer
        (Exx) indicates 386+ 32 bit register    (GS)    386 and newer


        Pointer Registers                       Stack Registers

        SI (ESI)  Source Index                  SP (ESP)  Stack Pointer
        DI (EDI)  Destination Index             BP (EBP)  Base Pointer
        IP        Instruction Pointer

        Status Registers

        FLAGS Status Flags   (see FLAGS)

        Special Registers (386+ only)

        CR0     Control Register 0        DR0    Debug Register 0
        CR2     Control Register 2        DR1    Debug Register 1
        CR3     Control Register 3        DR2    Debug Register 2
                                          DR3    Debug Register 3
        TR4     Test Register 4           DR6    Debug Register 6
        TR5     Test Register 5           DR7    Debug Register 7
        TR6     Test Register 6
        TR7     Test Register 7
        
        Register          Default Segment    Valid Overrides

        BP                      SS              DS, ES, CS
        SI or DI                DS              ES, SS, CS
        DI strings              ES              None
        SI strings              DS              ES, SS, CS


        - see  CPU   DETECTING  Instruction Timing

Instruction Clock Cycle Calculation


        Some instructions require additional clock cycles due to a "Next
        Instruction Component" identified by a "+m" in the instruction
        clock cycle listings.  This is due to the prefetch queue being
        purge on a control transfers.   Below is the general rule for
        calculating "m":


        88/86 not applicable
        286  "m" is the number of bytes in the next instruction
        386  "m" is the number of components in the next instruction
                (the instruction coding (each byte), plus the data and
                the displacement are all considered components)


8088/8086  Effective Address (EA) Calculation

                   Description                            Clock Cycles

        Displacement                                            6
        Base or Index (BX,BP,SI,DI)                             5
        Displacement+(Base or Index)                            9
        Base+Index (BP+DI,BX+SI)                                7
        Base+Index (BP+SI,BX+DI)                                8
        Base+Index+Displacement (BP+DI,BX+SI)                  11
        Base+Index+Displacement (BP+SI+disp,BX+DI+disp)        12


        - add 4 cycles for word operands at odd addresses
        - add 2 cycles for segment override
        - 80188/80186 timings differ from those of the 8088/8086/80286
Task State Calculation

        "TS" is defined as switching from VM/486 or 80286 TSS to one of
        the following:

                        ?????????????????????????????????????????
                        ?               New Task                ?
                        ?????????????????????????????????????????
        ?????????????????486 TSS?486 TSS?386 TSS?386 TSS?286 TSS?
        ?   Old Task    ? (VM=0)? (VM=1)? (VM=0)? (VM=1)?       ?
        ?????????????????????????????????????????????????????????
        386 TSS (VM=0)  ?       ?       ?  309  ?  226  ?  282  ?
                        ?????????????????????????????????????????
        386 TSS (VM=1)  ?       ?       ?  314  ?  231  ?  287  ?
                        ?????????????????????????????????????????
        386 CPU/286 TSS ?       ?       ?  307  ?  224  ?  280  ?
                        ?????????????????????????????????????????
        486 CPU/286 TSS ?  199  ?  177  ?       ?       ?  180  ?
                        ?????????????????????????????????????????

                             
        Miscellaneous

        - all timings are for best case and do not take into account wait
          states, instruction alignment, the state of the prefetch queue,
          DMA refresh cycles, cache hits/misses or exception processing.
        - to convert clocks to nanoseconds divide one microsecond by the
          processor speed in MegaHertz:
   
          (1000MHz/(n MHz)) = X nanoseconds

        - see   8086 Architecture


FLAGS - Intel 8086 Family Flags Register

      ?11?10?F?E?D?C?B?A?9?8?7?6?5?4?3?2?1?0?
        ?  ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ????  CF Carry Flag
        ?  ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ????  1
        ?  ? ? ? ? ? ? ? ? ? ? ? ? ? ? ????  PF Parity Flag
        ?  ? ? ? ? ? ? ? ? ? ? ? ? ? ????  0
        ?  ? ? ? ? ? ? ? ? ? ? ? ? ????  AF Auxiliary Flag
        ?  ? ? ? ? ? ? ? ? ? ? ? ????  0
        ?  ? ? ? ? ? ? ? ? ? ? ????  ZF Zero Flag
        ?  ? ? ? ? ? ? ? ? ? ????  SF Sign Flag
        ?  ? ? ? ? ? ? ? ? ????  TF Trap Flag  (Single Step)
        ?  ? ? ? ? ? ? ? ????  IF Interrupt Flag
        ?  ? ? ? ? ? ? ????  DF Direction Flag
        ?  ? ? ? ? ? ????  OF Overflow flag
        ?  ? ? ? ??????  IOPL I/O Privilege Level  (286+ only)
        ?  ? ? ??????  NT Nested Task Flag  (286+ only)
        ?  ? ??????  0
        ?  ??????  RF Resume Flag (386+ only)
        ???????  VM  Virtual Mode Flag (386+ only)

        - see   PUSHF  POPF  STI  CLI  STD  CLD
MSW - Machine Status Word (286+ only)


      ?31?30-5?4?3?2?1?0?  Machine Status Word
        ?   ?  ? ? ? ? ????? Protection Enable (PE)
        ?   ?  ? ? ? ?????? Math Present (MP)
        ?   ?  ? ? ??????? Emulation (EM)
        ?   ?  ? ???????? Task Switched (TS)
        ?   ?  ????????? Extension Type (ET)
        ?   ??????????? Reserved
        ?????????????? Paging (PG)


        Bit 0   PE      Protection Enable, switches processor between
                        protected and real mode
        Bit 1   MP      Math Present, controls function of the WAIT
                        instruction
        Bit 2   EM      Emulation, indicates whether coprocessor functions
                        are to be emulated
        Bit 3   TS      Task Switched, set and interrogated by coprocessor
                        on task switches and when interpretting coprocessor
                        instructions
        Bit 4   ET      Extension Type, indicates type of coprocessor in
                        system
        Bits 5-30       Reserved
        bit 31  PG      Paging, indicates whether the processor uses page
                        tables to translate linear addresses to physical
                        addresses

        - see   SMSW  LMSW
8086/80186/80286/80386/80486 Instruction Set

AAA - Ascii Adjust for Addition

        Usage:  AAA
        Modifies flags: AF CF (OF,PF,SF,ZF undefined)

        Changes contents of AL to valid unpacked decimal.  The high order
        nibble is zeroed.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              8     3     4     3             1


AAD - Ascii Adjust for Division

        Usage:  AAD
        Modifies flags: SF ZF PF (AF,CF,OF undefined)

        Used before dividing unpacked decimal numbers.   Multiplies AH by
        10 and the adds result into AL.  Sets AH to zero.  This instruction
        is also known to have an undocumented behavior.

        AL := 10*AH+AL
        AH := 0

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              60    14    19    14            2


AAM - Ascii Adjust for Multiplication


        Usage:  AAM
        Modifies flags: PF SF ZF (AF,CF,OF undefined)

        AH := AL / 10
        AL := AL mod 10

        Used after multiplication of two unpacked decimal numbers, this
        instruction adjusts an unpacked decimal number.  The high order
        nibble of each byte must be zeroed before using this instruction.
        This instruction is also known to have an undocumented behavior.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              83    16    17    15            2


AAS - Ascii Adjust for Subtraction

        Usage:  AAS
        Modifies flags: AF CF (OF,PF,SF,ZF undefined)

        Corrects result of a previous unpacked decimal subtraction in AL.
        High order nibble is zeroed.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              8     3     4     3             1
ADC - Add With Carry

        Usage:  ADC     dest,src
        Modifies flags: AF CF OF SF PF ZF

        Sums two binary operands placing the result in the destination.
        If CF is set, a 1 is added to the destination.

                                Clocks                  Size
        Operands         808x  286   386   486          Bytes

        reg,reg           3     2     2     1             2
        mem,reg         16+EA   7     7     3            2-4  (W88=24+EA)
        reg,mem          9+EA   7     6     2            2-4  (W88=13+EA)
        reg,immed         4     3     2     1            3-4
        mem,immed       17+EA   7     7     3            3-6  (W88=23+EA)
        accum,immed       4     3     2     1            2-3


ADD - Arithmetic Addition

        Usage:  ADD     dest,src
        Modifies flags: AF CF OF PF SF ZF

        Adds "src" to "dest" and replacing the original contents of "dest".
        Both operands are binary.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           3     2     2     1             2
        mem,reg         16+EA   7     7     3            2-4  (W88=24+EA)
        reg,mem          9+EA   7     6     2            2-4  (W88=13+EA)
        reg,immed         4     3     2     1            3-4
        mem,immed       17+EA   7     7     3            3-6  (W88=23+EA)
        accum,immed       4     3     2     1            2-3


AND - Logical And

        Usage:  AND     dest,src
        Modifies flags: CF OF PF SF ZF (AF undefined)

        Performs a logical AND of the two operands replacing the destination
        with the result.

                                Clocks                  Size
        Operands         808x  286   386   486          Bytes

        reg,reg           3     2     2     1             2
        mem,reg         16+EA   7     7     3            2-4  (W88=24+EA)
        reg,mem          9+EA   7     6     1            2-4  (W88=13+EA)
        reg,immed         4     3     2     1            3-4
        mem,immed       17+EA   7     7     3            3-6  (W88=23+EA)
        accum,immed       4     3     2     1            2-3


ARPL - Adjusted Requested Privilege Level of Selector (286+ PM)

        Usage:  ARPL    dest,src
        (286+ protected mode)
        Modifies flags: ZF

        Compares the RPL bits of "dest" against "src".  If the RPL bits
        of "dest" are less than "src", the destination RPL bits are set
        equal to the source RPL bits and the Zero Flag is set.  Otherwise
        the Zero Flag is cleared.

                                Clocks                  Size
        Operands         808x  286   386   486          Bytes

        reg,reg           -     10    20    9             2
        mem,reg           -     11    21    9             4
BOUND - Array Index Bound Check (80188+)

        Usage:  BOUND   src,limit
        Modifies flags: None

        Array index in source register is checked against upper and lower
        bounds in memory source.  The first word located at "limit" is
        the lower boundary and the word at "limit+2" is the upper array bound.
        Interrupt 5 occurs if the source value is less than or higher than
        the source.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16,mem32       -   nj=13 nj=10   7             2
        reg32,mem64       -   nj=13 nj=10   7             2

        - nj = no jump taken


BSF - Bit Scan Forward (386+)

        Usage:  BSF     dest,src
        Modifies flags: ZF

        Scans source operand for first bit set.  Sets ZF if a bit is found
        set and loads the destination with an index to first set bit.  Clears
        ZF is no bits are found set.  BSF scans forward across bit pattern
        (0-n) while BSR scans in reverse (n-0).

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           -     -   10+3n  6-42           3
        reg,mem           -     -   10+3n  7-43          3-7
        reg32,reg32       -     -   10+3n  6-42          3-7
        reg32,mem32       -     -   10+3n  7-43          3-7


BSR - Bit Scan Reverse  (386+)

        Usage:  BSR     dest,src
        Modifies flags: ZF

        Scans source operand for first bit set.  Sets ZF if a bit is found
        set and loads the destination with an index to first set bit.  Clears
        ZF is no bits are found set.  BSF scans forward across bit pattern
        (0-n) while BSR scans in reverse (n-0).

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           -     -   10+3n  6-103          3
        reg,mem           -     -   10+3n  7-104         3-7
        reg32,reg32       -     -   10+3n  6-103         3-7
        reg32,mem32       -     -   10+3n  7-104         3-7


BSWAP - Byte Swap       (486+)

        Usage:  BSWAP   reg32
        Modifies flags: none

        Changes the byte order of a 32 bit register from big endian to
        little endian or vice versa.   Result left in destination register
        is undefined if the operand is a 16 bit register.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg32             -     -     -     1             2
BT - Bit Test           (386+)

        Usage:  BT      dest,src
        Modifies flags: CF

        The destination bit indexed by the source value is copied into the
        Carry Flag.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16,immed8      -     -     3     3            4-8
        mem16,immed8      -     -     6     6            4-8
        reg16,reg16       -     -     3     3            3-7
        mem16,reg16       -     -     12    12           3-7


BTC - Bit Test with Compliment (386+)

        Usage:  BTC     dest,src
        Modifies flags: CF

        The destination bit indexed by the source value is copied into the
        Carry Flag after being complimented (inverted).

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16,immed8      -     -     6     6            4-8
        mem16,immed8      -     -     8     8            4-8
        reg16,reg16       -     -     6     6            3-7
        mem16,reg16       -     -     13    13           3-7


BTR - Bit Test with Reset (386+)

        Usage:  BTR     dest,src
        Modifies flags: CF

        The destination bit indexed by the source value is copied into the
        Carry Flag and then cleared in the destination.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16,immed8      -     -     6     6            4-8
        mem16,immed8      -     -     8     8            4-8
        reg16,reg16       -     -     6     6            3-7
        mem16,reg16       -     -     13    13           3-7


BTS - Bit Test and Set  (386+)

        Usage:  BTS     dest,src
        Modifies flags: CF

        The destination bit indexed by the source value is copied into the
        Carry Flag and then set in the destination.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16,immed8      -     -     6     6            4-8
        mem16,immed8      -     -     8     8            4-8
        reg16,reg16       -     -     6     6            3-7
        mem16,reg16       -     -     13    13           3-7
CALL - Procedure Call

        Usage:  CALL    destination
        Modifies flags: None

        Pushes Instruction Pointer (and Code Segment for far calls) onto
        stack and loads Instruction Pointer with the address of proc-name.
        Code continues with execution at CS:IP.

                                                         Clocks
                   Operands                     808x   286     386     486

        rel16 (near, IP relative)                19     7      7+m      3
        rel32 (near, IP relative)                -      -      7+m      3

        reg16 (near, register indirect)          16     7      7+m      5
        reg32 (near, register indirect)          -      -      7+m      5

        mem16 (near, memory indirect)            -     21+EA    11    10+m      5
        mem32 (near, memory indirect)            -      -     10+m      5

        ptr16:16 (far, full ptr supplied)        28     13    17+m      18
        ptr16:32 (far, full ptr supplied)        -      -     17+m      18
        ptr16:16 (far, ptr supplied, prot. mode) -      26    34+m      20
        ptr16:32 (far, ptr supplied, prot. mode) -      -     34+m      20
        m16:16 (far, indirect)                 37+EA    16    22+m      17
        m16:32 (far, indirect)                   -      -     22+m      17
        m16:16 (far, indirect, prot. mode)       -      29    38+m      20
        m16:32 (far, indirect, prot. mode)       -      -     38+m      20

        ptr16:16 (task, via TSS or task gate)    -     177     TS     37+TS
        m16:16 (task, via TSS or task gate)      -   180/185  5+TS    37+TS
        m16:32 (task)                            -      -      TS     37+TS
        m16:32 (task)                            -      -     5+TS    37+TS

        ptr16:16 (gate, same privilege)          -      41    52+m      35
        ptr16:32 (gate, same privilege)          -      -     52+m      35
        m16:16 (gate, same privilege)            -      44    56+m      35
        m16:32 (gate, same privilege)            -      -     56+m      35

        ptr16:16 (gate, more priv, no parm)      -      82    86+m      69
        ptr16:32 (gate, more priv, no parm)      -      -     86+m      69
        m16:16 (gate, more priv, no parm)        -      83    90+m      69
        m16:32 (gate, more priv, no parm)        -      -     90+m      69

        ptr16:16 (gate, more priv, x parms)      -    86+4x  94+4x+m  77+4x
        ptr16:32 (gate, more priv, x parms)      -      -    94+4x+m  77+4x
        m16:16 (gate, more priv, x parms)        -    90+4x  98+4x+m  77+4x
        m16:32 (gate, more priv, x parms)        -      -    98+4x+m  77+4x


CBW - Convert Byte to Word

        Usage:  CBW
        Modifies flags: None

        Converts byte in AL to word Value in AX by extending sign of AL
        throughout register AH.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              2     2     3     3             1


CDQ - Convert Double to Quad (386+)

        Usage:  CDQ
        Modifies flags: None

        Converts signed DWORD in EAX to a signed quad word in EDX:EAX by
        extending the high order bit of EAX throughout EDX

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              -     -     2     3             1
CLC - Clear Carry

        Usage:  CLC
        Modifies flags: CF

        Clears the Carry Flag.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              2     2     2     2             1


CLD - Clear Direction Flag

        Usage:  CLD
        Modifies flags: DF

        Clears the Direction Flag causing string instructions to increment
        the SI and DI index registers.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              2     2     2     2             1


CLI - Clear Interrupt Flag (disable)

        Usage:  CLI
        Modifies flags: IF

        Disables the maskable hardware interrupts by clearing the Interrupt
        flag.  NMI's and software interrupts are not inhibited.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              2     2     3     5             1


CLTS - Clear Task Switched Flag (286+ privileged)

        Usage:  CLTS
        Modifies flags: None

        Clears the Task Switched Flag in the Machine Status Register.  This
        is a privileged operation and is generally used only by operating
        system code.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              -     2     5     7             2


CMC - Complement Carry Flag

        Usage:  CMC
        Modifies flags: CF

        Toggles (inverts) the Carry Flag

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              2     2     2     2             1
CMP - Compare

        Usage:  CMP     dest,src
        Modifies flags: AF CF OF PF SF ZF

        Subtracts source from destination and updates the flags but does
        not save result.  Flags can subsequently be checked for conditions.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           3     2     2     1             2
        mem,reg          9+EA   7     5     2            2-4  (W88=13+EA)
        reg,mem          9+EA   6     6     2            2-4  (W88=13+EA)
        reg,immed         4     3     2     1            3-4
        mem,immed       10+EA   6     5     2            3-6  (W88=14+EA)
        accum,immed       4     3     2     1            2-3


CMPS - Compare String (Byte, Word or Doubleword)

        Usage:  CMPS    dest,src
                CMPSB
                CMPSW
                CMPSD   (386+)
        Modifies flags: AF CF OF PF SF ZF

        Subtracts destination value from source without saving results.
        Updates flags based on the subtraction and  the index registers
        (E)SI and (E)DI are incremented or decremented depending on the
        state of the Direction Flag.  CMPSB inc/decrements the index
        registers by 1, CMPSW inc/decrements by 2, while CMPSD increments
        or decrements by 4.  The REP prefixes can be used to process
        entire data items.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        dest,src          22    8     10    8             1  (W88=30)


CMPXCHG - Compare and Exchange

        Usage:  CMPXCHG dest,src  (486+)
        Modifies flags: AF CF OF PF SF ZF

        Compares the accumulator (8-32 bits) with "dest".  If equal the
        "dest" is loaded with "src", otherwise the accumulator is loaded
        with "dest".

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           -     -     -     6             2
        mem,reg           -     -     -     7             2

        - add 3 clocks if the "mem,reg" comparison fails


CWD - Convert Word to Doubleword

        Usage:  CWD
        Modifies flags: None

        Extends sign of word in register AX throughout register DX forming
        a doubleword quantity in DX:AX.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              5     2     2     3             1
CWDE - Convert Word to Extended Doubleword (386+)

        Usage:  CWDE
        Modifies flags: None

        Converts a signed word in AX to a signed doubleword in EAX by
        extending the sign bit of AX throughout EAX.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              -     -     3     3             1


DAA - Decimal Adjust for Addition

        Usage:  DAA
        Modifies flags: AF CF PF SF ZF (OF undefined)

        Corrects result (in AL) of a previous BCD addition operation.
        Contents of AL are changed to a pair of packed decimal digits.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              4     3     4     2             1


DAS - Decimal Adjust for Subtraction

        Usage:  DAS
        Modifies flags: AF CF PF SF ZF (OF undefined)

        Corrects result (in AL) of a previous BCD subtraction operation.
        Contents of AL are changed to a pair of packed decimal digits.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              4     3     4     2             1


DEC - Decrement

        Usage:  DEC     dest
        Modifies flags: AF OF PF SF ZF

        Unsigned binary subtraction of one from the destination.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              3     2     2     1             2
        mem             15+EA   7     6     3            2-4
        reg16/32          3     2     2     1             1


DIV - Divide

        Usage:  DIV     src
        Modifies flags: (AF,CF,OF,PF,SF,ZF undefined)

        Unsigned binary division of accumulator by source.  If the source
        divisor is a byte value then AX is divided by "src" and the quotient
        is placed in AL and the remainder in AH.  If source operand is a word
        value, then DX:AX is divided by "src" and the quotient is stored in AX
        and the remainder in DX.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8             80-90  14    14    16            2
        reg16           144-162 22    22    24            2
        reg32              -    -     38    40            2
        mem8        (86-96)+EA  17    17    16           2-4
        mem16     (150-168)+EA  25    25    24           2-4  (W88=158-176+EA)
        mem32              -    -     41    40           2-4
ENTER - Make Stack Frame  (80188+)

        Usage:  ENTER   locals,level
        Modifies flags: None

        Modifies stack for entry to procedure for high level language.
        Operand "locals" specifies the amount of storage to be allocated
        on the stack.   "Level" specifies the nesting level of the routine.
        Paired with the LEAVE instruction, this is an efficient method of
        entry and exit to procedures.

                                     Clocks                     Size
        Operands         808x    286       386       486        Bytes

        immed16,0         -       11       10         14          4
        immed16,1         -       15       12         17          4
        immed16,immed8    -   12+4(n-1) 15+4(n-1)    17+3n        4


ESC - Escape

        Usage:  ESC     immed,src
        Modifies flags: None

        Provides access to the data bus for other resident processors.
        The CPU treats it as a NOP but places memory operand on bus.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        immed,reg         2   9-20    ?                   2
        immed,mem         2   9-20    ?                  2-4


HLT - Halt CPU

        Usage:   HLT
        Modifies flags: None

        Halts CPU until RESET line is activated, NMI or maskable interrupt
        received.  The CPU becomes dormant but retains the current CS:IP
        for later restart.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              2     2     5     4             1


IDIV - Signed Integer Division

        Usage:   IDIV   src
        Modifies flags: (AF,CF,OF,PF,SF,ZF undefined)

        Signed binary division of accumulator by source.  If source is a
        byte value, AX is divided by "src" and the quotient is stored in
        AL and the remainder in AH.  If source is a word value, DX:AX is
        divided by "src", and the quotient is stored in AL and the
        remainder in DX.
                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8          101-112   17    19    19            2
        reg16         165-184   25    27    27            2
        reg32            -      -     43    43            2
        mem8     (107-118)+EA   20    22    20           2-4
        mem16    (171-190)+EA   38    30    28           2-4  (W88=175-194)
        mem32            -      -     46    44           2-4
IMUL - Signed Multiply

        Usage:  IMUL    src
                IMUL    src,immed        (286+)
                IMUL    dest,src,immed8  (286+)
                IMUL    dest,src         (386+)
        Modifies flags: CF OF (AF,PF,SF,ZF undefined)

        Signed multiplication of accumulator by "src" with result placed
        in the accumulator.  If the source operand is a byte value, it
        is multiplied by AL and the result stored in AX.  If the source
        operand is a word value it is multiplied by AX and the result is
        stored in DX:AX.  Other variations of this instruction allow
        specification of source and destination registers as well as a
        third immediate factor.

                                  Clocks                Size
        Operands         808x   286   386   486         Bytes

        reg8             80-98   13   9-14  13-18         2
        reg16           128-154  21   9-22  13-26         2
        reg32              -     -    9-38  12-42         2
        mem8             86-104  16  12-17  13-18        2-4
        mem16           134-160  24  12-25  13-26        2-4
        mem32              -     -   12-41  13-42        2-4
        reg16,reg16        -     -    9-22  13-26        3-5
        reg32,reg32        -     -    9-38  13-42        3-5
        reg16,mem16        -     -   12-25  13-26        3-5
        reg32,mem32        -     -   12-41  13-42        3-5
        reg16,immed        -     21   9-22  13-26         3
        reg32,immed        -     21   9-38  13-42        3-6
        reg16,reg16,immed  -     2    9-22  13-26        3-6
        reg32,reg32,immed  -     21   9-38  13-42        3-6
        reg16,mem16,immed  -     24  12-25  13-26        3-6
        reg32,mem32,immed  -     24  12-41  13-42        3-6


IN - Input Byte or Word From Port

        Usage:  IN      accum,port
        Modifies flags: None

        A byte, word or dword is read from "port" and placed in AL, AX or
        EAX respectively.  If the port number is in the range of 0-255
        it can be specified as an immediate, otherwise the port number
        must be specified in DX.  Valid port ranges on the PC are 0-1024,
        though values through 65535 may be specified and recognized by
        third party vendors and PS/2's.

                                 Clocks                 Size
        Operands         808x  286   386    486         Bytes

        accum,immed8    10/14   5     12     14           2
        accum,immed8 (PM)            6/26  8/28/27        2
        accum,DX         8/12   5     13     14           1
        accum,DX (PM)                7/27  8/28/27        1

        - 386+ protected mode timings depend on privilege levels.

          first number is the timing if:    CPL ? IOPL
          second number is the timing if:   CPL > IOPL or in VM 86 mode (386)
                                            CPL ? IOPL  (486)
          third number is the timing when:    virtual mode on 486 processor
        - 486 virtual mode always requires 27 cycles
INC - Increment

        Usage:  INC     dest
        Modifies flags: AF OF PF SF ZF

        Adds one to destination unsigned binary operand.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              3     2     2     1             2
        reg16             3     2     2     1             1
        reg32             3     2     2     1             1
        mem             15+EA   7     6     3            2-4  (W88=23+EA)


INS - Input String from Port  (80188+)

        Usage:  INS     dest,port
                INSB
                INSW
                INSD    (386+)
        Modifies flags: None

        Loads data from port to the destination ES:(E)DI  (even if a
        destination operand is supplied).  (E)DI is adjusted by the size
        of the operand and increased if the Direction Flag is cleared and
        decreased if the Direction Flag is set.  For INSB, INSW, INSD no
        operands are allowed and the size is determined by the mnemonic.

                                 Clocks                 Size
        Operands         808x  286   386    486         Bytes

        dest,port         -     5     15     17           1
        dest,port (PM)    -     5    9/29 10/32/30        1
        none              -     5     15     17           1
        none (PM)         -     5    9/29 10/32/30        1

        - 386+ protected mode timings depend on privilege levels.

          first number is the timing if:    CPL ? IOPL
          second number is the timing if:   CPL > IOPL
          third number is the timing if:    virtual mode on 486 processor


INT - Interrupt

        Usage:  INT     num
        Modifies flags: TF IF

        Initiates a software interrupt by pushing the flags, clearing the
        Trap and Interrupt Flags, pushing CS followed by IP and loading
        CS:IP with the value found in the interrupt vector table.  Execution
        then begins at the location addressed by the new CS:IP

                                               Clocks           Size
            Operands                    808x  286   386   486   Bytes

 3 (constant)                          52/72  23+m   33    26     2
 3 (prot. mode, same priv.)              -    40+m   59    44     2       
 3 (prot. mode, more priv.)              -    78+m   99    71     2
 3 (from VM86 to PL 0)                   -     -    119    82     2
 3 (prot. mode via task gate)            -   167+m   TS  37+TS    2
 immed8                                51/71  23+m   37    30     1
 immed8 (prot. mode, same priv.)         -    40+m   59    44     1
 immed8 (prot. mode, more priv.)         -    78+m   99    71     1
 immed8 (from VM86 to PL 0)              -     -    119    86     1
 immed8 (prot. mode, via task gate)      -   167+m   TS  37+TS    1
INTO - Interrupt on Overflow

        Usage:  INTO
        Modifies flags: IF TF

        If the Overflow Flag is set this instruction generates an INT 4
        which causes the code addressed by 0000:0010 to be executed.

                                          Clocks           Size
        Operands                808x    286   386   486    Bytes

        none: jump             53/73   24+m    35    28      1
              no jump            4       3     3     3
        (prot. mode, same priv.) -       -     59    46      1
        (prot. mode, more priv.) -       -     99    73      1
        (from VM86 to PL 0)      -       -    119    84      1
        (prot. mode, via task gate)      -     TS  39+TS     1


INVD - Invalidate Cache  (486+)

        Usage:  INVD
        Modifies flags: none

        Flushes CPU internal cache.  Issues special function bus cycle
        which indicates to flush external caches.   Data in write-back
        external caches is lost.
        
                                  Clocks                Size
        Operands         808x   286   386   486         Bytes

        none              -      -     -     4            2


INVLPG - Invalidate Translation Look-Aside Buffer Entry (486+)

        Usage:  INVLPG
        Modifies flags: none

        Invalidates a single page table entry in the Translation
        Look-Aside Buffer.  Intel warns that this instruction may be
        implemented differently on future processors.
        
                                  Clocks                Size
        Operands         808x   286   386   486         Bytes

        none              -      -     -     12           2

        - timing is for TLB entry hit only.


IRET/IRETD - Interrupt Return

        Usage:  IRET
                IRETD  (386+)
        Modifies flags: AF CF DF IF PF SF TF ZF

        Returns control to point of interruption by popping IP, CS
        and then the Flags from the stack and continues execution at
        this location.  CPU exception interrupts will return to the
        instruction that cause the exception because the CS:IP placed
        on the stack during the interrupt is the address of the offending
        instruction.

                                         Clocks                 Size
            Operands            808x   286   386   486          Bytes

    iret                       32/44  17+m    22    15            1
    iret  (prot. mode)           -    31+m    38    15            1
    iret  (to less privilege)    -    55+m    82    36            1
    iret  (different task, NT=1) -   169+m    TS  TS+32           1
    iretd                        -      -   22/38   15            1
    iretd (to less privilege)    -      -     82    36            1
    iretd (to VM86 mode)         -      -     60    15            1
    iretd (different task, NT=1) -      -     TS  TS+32           1

    - 386 timings are listed as real-mode/protected-mode
Jxx - Jump Instructions Table

        Mnemonic              Meaning                    Jump Condition

          JA     Jump if Above                         CF=0 and ZF=0
          JAE    Jump if Above or Equal                CF=0
          JB     Jump if Below                         CF=1
          JBE    Jump if Below or Equal                CF=1 or ZF=1
          JC     Jump if Carry                         CF=1
          JCXZ   Jump if CX Zero                       CX=0
          JE     Jump if Equal                         ZF=1
          JG     Jump if Greater (signed)              ZF=0 and SF=OF
          JGE    Jump if Greater or Equal (signed)     SF=OF
          JL     Jump if Less (signed)                 SF != OF
          JLE    Jump if Less or Equal (signed)        ZF=1 or SF != OF
          JMP    Unconditional Jump                    unconditional
          JNA    Jump if Not Above                     CF=1 or ZF=1
          JNAE   Jump if Not Above or Equal            CF=1
          JNB    Jump if Not Below                     CF=0
          JNBE   Jump if Not Below or Equal            CF=0 and ZF=0
          JNC    Jump if Not Carry                     CF=0
          JNE    Jump if Not Equal                     ZF=0
          JNG    Jump if Not Greater (signed)          ZF=1 or SF != OF
          JNGE   Jump if Not Greater or Equal (signed) SF != OF
          JNL    Jump if Not Less (signed)             SF=OF
          JNLE   Jump if Not Less or Equal (signed)    ZF=0 and SF=OF
          JNO    Jump if Not Overflow (signed)         OF=0
          JNP    Jump if No Parity                     PF=0
          JNS    Jump if Not Signed (signed)           SF=0
          JNZ    Jump if Not Zero                      ZF=0
          JO     Jump if Overflow (signed)             OF=1
          JP     Jump if Parity                        PF=1
          JPE    Jump if Parity Even                   PF=1
          JPO    Jump if Parity Odd                    PF=0
          JS     Jump if Signed (signed)               SF=1
          JZ     Jump if Zero                          ZF=1

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        Jx: jump          16   7+m   7+m    3             2
            no jump        4    3     3     1
        Jx  near-label     -    -    7+m    3             4
            no jump        -    -     3     1

        - It's a good programming practice to organize code so the
          expected case is executed without a jump since the actual
          jump takes longer to execute than falling through the test.
        - see   JCXZ  and  JMP  for their respective timings


JCXZ/JECXZ - Jump if Register (E)CX is Zero

        Usage:  JCXZ    label
                JECXZ   label  (386+)
        Modifies flags: None

        Causes execution to branch to "label" if register CX is zero.  Uses
        unsigned comparision.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        label:  jump      18   8+m   9+m    8             2
                no jump    6    4     5     5
JMP - Unconditional Jump

        Usage:  JMP     target
        Modifies flags: None

        Unconditionally transfers control to "label".  Jumps by default
        are within -32768 to 32767 bytes from the instruction following
        the jump.  NEAR and SHORT jumps cause the IP to be updated while FAR
        jumps cause CS and IP to be updated.

                                                        Clocks
                   Operands                     808x  286    386   486

        rel8  (relative)                        15    7+m    7+m    3
        rel16 (relative)                        15    7+m    7+m    3
        rel32 (relative)                         -     -     7+m    3
        reg16 (near, register indirect)         11    7+m    7+m    5
        reg32 (near, register indirect)          -     -     7+m    5
        mem16 (near, mem indirect)             18+EA  11+m  10+m    5
        mem32 (near, mem indirect)             24+EA  15+m  10+m    5
        ptr16:16 (far, dword immed)              -     -    12+m    17
        ptr16:16 (far, PM dword immed)           -     -    27+m    19
        ptr16:16 (call gate, same priv.)         -    38+m  45+m    32
        ptr16:16 (via TSS)                       -   175+m   TS   42+TS
        ptr16:16 (via task gate)                 -   180+m   TS   43+TS
        mem16:16 (far, indirect)                 -     -    43+m    13
        mem16:16 (far, PM indirect)              -     -    31+m    18
        mem16:16 (call gate, same priv.)         -    41+m  49+m    31
        mem16:16 (via TSS)                       -   178+m  5+TS  41+TS
        mem16:16 (via task gate)                 -   183+m  5+TS  42+TS
        ptr16:32 (far, 6 byte immed)             -     -    12+m    13
        ptr16:32 (far, PM 6 byte immed)          -     -    27+m    18
        ptr16:32 (call gate, same priv.)         -     -    45+m    31
        ptr16:32 (via TSS)                       -     -     TS   42+TS
        ptr16:32 (via task state)                -     -     TS   43+TS
        m16:32 (far, address at dword)           -     -    43+m    13
        m16:32 (far, address at dword)           -     -    31+m    18
        m16:32 (call gate, same priv.)           -     -    49+m    31
        m16:32 (via TSS)                         -     -    5+TS  41+TS
        m16:32 (via task state)                  -     -    5+TS  42+TS


LAHF - Load Register AH From Flags

        Usage:  LAHF
        Modifies flags: None

        Copies bits 0-7 of the flags register into AH.  This includes flags
        AF, CF, PF, SF and ZF other bits are undefined.

        AH := SF ZF xx AF xx PF xx CF

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              4     2     2     3             1


LAR - Load Access Rights (286+ protected)

        Usage:  LAR     dest,src
        Modifies flags: ZF

        The high byte of the of the destination register is overwritten by
        the value of the access rights byte and the low order byte is zeroed
        depending on the selection in the source operand.  The Zero Flag is
        set if the load operation is successful.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16,reg16       -     14    15    11            3
        reg32,reg32       -     -     15    11            3
        reg16,mem16       -     16    16    11           3-7
        reg32,mem32       -     -     16    11           3-7
LDS - Load Pointer Using DS

        Usage:  LDS     dest,src
        Modifies flags: None

        Loads 32-bit pointer from memory source to destination register
        and DS.  The offset is placed in the destination register and the
        segment is placed in DS.  To use this instruction the word at the
        lower memory address must contain the offset and the word at the
        higher address must contain the segment.  This simplifies the loading
        of far pointers from the stack and the interrupt vector table.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16,mem32     16+EA   7     7     6            2-4
        reg,mem (PM)      -     -     22    12           5-7


LEA - Load Effective Address

        Usage:  LEA     dest,src
        Modifies flags: None

        Transfers offset address of "src" to the destination register.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,mem          2+EA   3     2     1            2-4

        - the MOV instruction can often save clock cycles when used in
          place of LEA on 8088 processors


LEAVE - Restore Stack for Procedure Exit (80188+)

        Usage:  LEAVE
        Modifies flags: None

        Releases the local variables created by the previous ENTER
        instruction by restoring SP and BP to their condition before
        the procedure stack frame was initialized.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              -     5     4     5             1


LES - Load Pointer Using ES

        Usage:  LES     dest,src
        Modifies flags: None

        Loads 32-bit pointer from memory source to destination register
        and ES.  The offset is placed in the destination register and the
        segment is placed in ES.  To use this instruction the word at the
        lower memory address must contain the offset and the word at the
        higher address must contain the segment.  This simplifies the loading
        of far pointers from the stack and the interrupt vector table.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,mem         16+EA   7     7     6            2-4  (W88=24+EA)
        reg,mem (PM)      -     -     22    12           5-7
LFS - Load Pointer Using FS (386+)

        Usage:  LFS     dest,src
        Modifies flags: None

        Loads 32-bit pointer from memory source to destination register
        and FS.  The offset is placed in the destination register and the
        segment is placed in FS.  To use this instruction the word at the
        lower memory address must contain the offset and the word at the
        higher address must contain the segment.  This simplifies the loading
        of far pointers from the stack and the interrupt vector table.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,mem           -     -     7     6            5-7
        reg,mem (PM)      -     -     22    12           5-7


LGDT - Load Global Descriptor Table (286+ privileged)

        Usage:  LGDT    src
        Modifies flags: None

        Loads a value from an operand into the Global Descriptor Table
        (GDT) register.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        mem64             -     11    11    11            5


LIDT - Load Interrupt Descriptor Table (286+ privileged)

        Usage:  LIDT    src
        Modifies flags: None

        Loads a value from an operand into the Interrupt Descriptor Table
        (IDT) register.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        mem64             -     12    11    11            5


LGS - Load Pointer Using GS (386+)

        Usage:  LGS     dest,src
        Modifies flags: None

        Loads 32-bit pointer from memory source to destination register
        and GS.  The offset is placed in the destination register and the
        segment is placed in GS.  To use this instruction the word at the
        lower memory address must contain the offset and the word at the
        higher address must contain the segment.  This simplifies the loading
        of far pointers from the stack and the interrupt vector table.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,mem           -     -     7     6            5-7
        reg,mem (PM)      -     -     22    12           5-7
LLDT - Load Local Descriptor Table (286+ privileged)

        Usage:  LLDT    src
        Modifies flags: None

        Loads a value from an operand into the Local Descriptor Table 
        Register (LDTR).

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16             -     17    20    11            3
        mem16             -     19    24    11            5


LMSW - Load Machine Status Word (286+ privileged)

        Usage:  LMSW    src
        Modifies flags: None

        Loads the Machine Status Word (MSW) from data found at "src"

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16             -     3     10    13            3
        mem16             -     6     13    13            5


LOCK - Lock Bus

        Usage:  LOCK
                LOCK: (386+ prefix)
        Modifies flags: None

        This instruction is a prefix that causes the CPU assert bus lock
        signal during the execution of the next instruction.  Used to
        avoid two processors from updating the same data location.  The
        286 always asserts lock during an XCHG with memory operands.  This
        should only be used to lock the bus prior to XCHG, MOV, IN and
        OUT instructions.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              2     0     0     1             1


LODS - Load String (Byte, Word or Double)

        Usage:  LODS    src
                LODSB
                LODSW
                LODSD  (386+)
        Modifies flags: None

        Transfers string element addressed by DS:SI (even if an operand is
        supplied) to the accumulator.   SI is incremented based on the size
        of the operand or based on the instruction used.  If the Direction
        Flag is set SI is decremented, if the Direction Flag is clear SI
        is incremented.  Use with REP prefixes.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        src             12/16   5     5     5             1
LOOP - Decrement CX and Loop if CX Not Zero

        Usage:  LOOP    label
        Modifies flags: None

        Decrements CX by 1 and transfers control to "label" if CX is not
        Zero.  The "label" operand must be within -128 or 127 bytes of the
        instruction following the loop instruction

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        label:  jump      18   8+m  11+m    6             2
                no jump    5    4     ?     2


LOOPE/LOOPZ - Loop While Equal / Loop While Zero

        Usage:  LOOPE   label
                LOOPZ   label
        Modifies flags: None

        Decrements CX by 1 (without modifying the flags) and transfers
        control to "label" if CX != 0 and the Zero Flag is set.  The
        "label" operand must be within -128 or 127 bytes of the instruction
        following the loop instruction.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        label:  jump      18   8+m  11+m    9             2
                no jump    5    4     ?     6


LOOPNZ/LOOPNE - Loop While Not Zero / Loop While Not Equal

        Usage:  LOOPNZ  label
                LOOPNE  label
        Modifies flags: None

        Decrements CX by 1 (without modifying the flags) and transfers
        control to "label" if CX != 0 and the Zero Flag is clear.  The
        "label" operand must be within -128 or 127 bytes of the instruction
        following the loop instruction.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        label:  jump      19   8+m  11+m    9             2
                no jump    5    4     ?     6


LSL - Load Segment Limit (286+ protected)

        Usage:  LSL     dest,src
        Modifies flags: ZF

        Loads the segment limit of a selector into the destination register
        if the selector is valid and visible at the current privilege level.
        If loading is successful the Zero Flag is set, otherwise it is
        cleared.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16,reg16       -     14  20/25   10            3
        reg32,reg32       -     -   20/25   10            3
        reg16,mem16       -     16  21/26   10            5
        reg32,mem32       -     -   21/26   10            5

        - 386 times are listed "byte granular" / "page granular"
LSS - Load Pointer Using SS (386+)

        Usage:  LSS     dest,src
        Modifies flags: None

        Loads 32-bit pointer from memory source to destination register
        and SS.  The offset is placed in the destination register and the
        segment is placed in SS.  To use this instruction the word at the
        lower memory address must contain the offset and the word at the
        higher address must contain the segment.  This simplifies the loading
        of far pointers from the stack and the interrupt vector table.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,mem           -     -     7     6            5-7
        reg,mem (PM)      -     -     22    12           5-7


LTR - Load Task Register (286+ privileged)

        Usage:  LTR     src
        Modifies flags: None

        Loads the current task register with the value specified in "src".

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16             -     17    23    20            3
        mem16             -     19    27    20            5


MOV - Move Byte or Word

        Usage:  MOV     dest,src
        Modifies flags: None

        Copies byte or word from the source operand to the destination
        operand.  If the destination is SS interrupts are disabled except
        on early buggy 808x CPUs.  Some CPUs disable interrupts if the
        destination is any of the segment registers

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           2     2     2     1             2
        mem,reg          9+EA   3     2     1            2-4  (W88=13+EA)
        reg,mem          8+EA   5     4     1            2-4  (W88=12+EA)
        mem,immed       10+EA   3     2     1            3-6  (W88=14+EA)
        reg,immed         4     2     2     1            2-3
        mem,accum         10    3     2     1             3   (W88=14)
        accum,mem         10    5     4     1             3   (W88=14)
        segreg,reg16      2     2     2     3             2
        segreg,mem16     8+EA   5     5     9            2-4  (W88=12+EA)
        reg16,segreg      2     2     2     3             2
        mem16,segreg     9+EA   3     2     3            2-4  (W88=13+EA)
        reg32,CR0/CR2/CR3 -     -     6     4
        CR0,reg32         -     -     10    16
        CR2,reg32         -     -     4     4             3
        CR3,reg32         -     -     5     4             3
        reg32,DR0/DR1/DR2/DR3   -     22   10             3
        reg32,DR6/DR7     -     -     22   10             3
        DR0/DR1/DR2/DR3,reg32   -     22   11             3
        DR6/DR7,reg32     -     -     16   11             3
        reg32,TR6/TR7     -     -     12    4             3
        TR6/TR7,reg32     -     -     12    4             3
        reg32,TR3                           3
        TR3,reg32                           6

        - when the 386 special registers are used all operands are 32 bits
MOVS - Move String (Byte or Word)

        Usage:  MOVS    dest,src
                MOVSB
                MOVSW
                MOVSD  (386+)
        Modifies flags: None

        Copies data from addressed by DS:SI (even if operands are given) to
        the location ES:DI destination and updates SI and DI based on the
        size of the operand or instruction used.  SI and DI are incremented
        when the Direction Flag is cleared and decremented when the Direction
        Flag is Set.  Use with REP prefixes.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        dest,src          18    5     7     7             1   (W88=26)


MOVSX - Move with Sign Extend (386+)

        Usage:  MOVSX   dest,src
        Modifies flags: None

        Copies the value of the source operand to the destination register
        with the sign extended.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           -     -     3     3             3
        reg,mem           -     -     6     3            3-7


MOVZX - Move with Zero Extend (386+)

        Usage:  MOVZX   dest,src
        Modifies flags: None

        Copies the value of the source operand to the destination register
        with the zeroes extended.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           -     -     3     3             3
        reg,mem           -     -     6     3            3-7


MUL - Unsigned Multiply

        Usage:  MUL     src
        Modifies flags: CF OF (AF,PF,SF,ZF undefined)

        Unsigned multiply of the accumulator by the source.  If "src" is
        a byte value, then AL is used as the other multiplicand and the
        result is placed in AX.  If "src" is a word value, then AX is
        multiplied by "src" and DX:AX receives the result.  If "src" is
        a double word value, then EAX is multiplied by "src" and EDX:EAX
        receives the result.  The 386+ uses an early out algorithm which
        makes multiplying any size value in EAX as fast as in the 8 or 16
        bit registers.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8            70-77   13   9-14  13-18          2
        reg16          118-113  21   9-22  13-26          2
        reg32             -     -    9-38  13-42         2-4
        mem8        (76-83)+EA  16  12-17  13-18         2-4
        mem16     (124-139)+EA  24  12-25  13-26         2-4
        mem32             -     -   12-21  13-42         2-4
NEG - Two's Complement Negation

        Usage:  NEG     dest
        Modifies flags: AF CF OF PF SF ZF

        Subtracts the destination from 0 and saves the 2s complement of
        "dest" back into "dest".

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg               3     2     2     1             2
        mem             16+EA   7     6     3            2-4  (W88=24+EA)


NOP - No Operation (90h)

        Usage:  NOP
        Modifies flags: None

        This is a do nothing instruction.  It results in occupation of both
        space and time and is most useful for patching code segments.
        (This is the original XCHG AL,AL instruction)

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              3     3     3     1             1


NOT - One's Compliment Negation (Logical NOT)

        Usage:  NOT     dest
        Modifies flags: None

        Inverts the bits of the "dest" operand forming the 1s complement.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg               3     2     2     1             2
        mem             16+EA   7     6     3            2-4  (W88=24+EA)


OR - Inclusive Logical OR

        Usage:  OR      dest,src
        Modifies flags: CF OF PF SF ZF (AF undefined)

        Logical inclusive OR of the two operands returning the result in
        the destination.  Any bit set in either operand will be set in the
        destination.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           3     2     2     1             2
        mem,reg         16+EA   7     7     3            2-4  (W88=24+EA)
        reg,mem          9+EA   7     6     2            2-4  (W88=13+EA)
        reg,immed         4     3     2     1            3-4
        mem8,immed8     17+EA   7     7     3            3-6
        mem16,immed16   25+EA   7     7     3            3-6
        accum,immed       4     3     2     1            2-3
OUT - Output Data to Port

        Usage:  OUT     port,accum
        Modifies flags: None

        Transfers byte in AL,word in AX or dword in EAX to the specified
        hardware port address.  If the port number is in the range of 0-255
        it can be specified as an immediate.  If greater than 255 then the
        port number must be specified in DX.  Since the PC only decodes 10
        bits of the port address, values over 1023 can only be decoded by
        third party vendor equipment and also map to the port range 0-1023.

                                 Clocks                 Size
        Operands         808x  286   386    486         Bytes

        immed8,accum    10/14   3     10     16           2
        immed8,accum (PM) -     -    4/24 11/31/29        2
        DX,accum         8/12   3     11     16           1
        DX,accum (PM)     -     -    5/25 10/30/29        1

        - 386+ protected mode timings depend on privilege levels.

          first number is the timing when:    CPL ? IOPL
          second number is the timing when:   CPL > IOPL
          third number is the timing when:    virtual mode on 486 processor


OUTS - Output String to Port  (80188+)

        Usage:  OUTS    port,src
                OUTSB
                OUTSW
                OUTSD   (386+)
        Modifies flags: None

        Transfers a byte, word or doubleword from "src" to the hardware
        port specified in DX.  For instructions with no operands the "src"
        is located at DS:SI and SI is incremented or decremented by the
        size of the operand or the size dictated by the instruction format.
        When the Direction Flag is set SI is decremented, when clear, SI is
        incremented.  If the port number is in the range of 0-255 it can
        be specified as an immediate.  If greater than 255 then the port
        number must be specified in DX.  Since the PC only decodes 10 bits
        of the port address, values over 1023 can only be decoded by third
        party vendor equipment and also map to the port range 0-1023.

                                 Clocks                 Size
        Operands         808x  286   386    486         Bytes

        port,src          -     5     14     17           1
        port,src (PM)     -     -    8/28 10/32/30        1

        - 386+ protected mode timings depend on privilege levels.

          first number is the timing when:    CPL ? IOPL
          second number is the timing when:   CPL > IOPL
          third number is the timing when:    virtual mode on 486 processor


POP - Pop Word off Stack

        Usage:  POP     dest
        Modifies flags: None

        Transfers word at the current stack top (SS:SP) to the destination
        then increments SP by two to point to the new stack top.  CS is not
        a valid destination.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16             8     5     4     4             1
        reg32             4     -     -     4             1
        segreg            8     5     7     3             1
        mem16           17+EA   5     5     6            2-4
        mem32             5     -     -     6            2-4
POPA/POPAD - Pop All Registers onto Stack  (80188+)

        Usage:  POPA
                POPAD  (386+)
        Modifies flags: None

        Pops the top 8 words off the stack into the 8 general purpose 16/32
        bit registers.   Registers are popped in the following order: (E)DI,
        (E)SI, (E)BP, (E)SP, (E)DX, (E)CX and (E)AX.  The (E)SP value popped
        from the stack is actually discarded.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              -     19    24    9             1


POPF/POPFD - Pop Flags off Stack

        Usage:  POPF
                POPFD  (386+)
        Modifies flags: all flags

        Pops word/doubleword from stack into the Flags Register and then
        increments SP by 2 (for POPF) or 4 (for POPFD).

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none             8/12   5     5     9             1  (W88=12)
        none  (PM)        -     -     5     6             1


PUSH - Push Word onto Stack

        Usage:  PUSH    src
                PUSH    immed   (80188+ only)
        Modifies flags: None

        Decrements SP by the size of the operand (two or four, byte values
        are sign extended) and transfers one word from source to the stack
        top (SS:SP).

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16           11/15   3     2     1             1
        reg32             -     -     2     1             1
        mem16           16+EA   5     5     4            2-4  (W88=24+EA)
        mem32             -     -     5     4            2-4
        segreg          10/14   3     2     3             1
        immed             -     3     2     1            2-3


PUSHA/PUSHAD - Push All Registers onto Stack  (80188+)

        Usage:  PUSHA
                PUSHAD  (386+)
        Modifies flags: None

        Pushes all general purpose registers onto the stack in the following
        order: (E)AX, (E)CX, (E)DX, (E)BX, (E)SP, (E)BP, (E)SI, (E)DI.  The
        value of SP is the value before the actual push of SP.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              -     19    24    11            1
PUSHF/PUSHFD - Push Flags onto Stack

        Usage:  PUSHF
                PUSHFD  (386+)
        Modifies flags: None

        Transfers the Flags Register onto the stack.  PUSHF saves a 16 bit
        value while PUSHFD saves a 32 bit value.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none            10/14   3     4     4             1
        none  (PM)        -     -     4     3             1


RCL - Rotate Through Carry Left

        Usage:  RCL     dest,count
        Modifies flags: CF OF

           ???     ?????????????????
        ????C?<?????7 <?????????? 0?<??
        ?  ???     ?????????????????  ?
        ???????????????????????????????

        Rotates the bits in the destination to the left "count" times with
        all data pushed out the left side re-entering on the right.  The
        Carry Flag holds the last bit rotated out.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,1             2     2     9     3            2
        mem,1           15+EA   7     10    4           2-4  (W88=23+EA)
        reg,CL           8+4n  5+n    9    8-30          2
        mem,CL        20+EA+4n 8+n    10   9-31         2-4  (W88=28+EA+4n)
        reg,immed8        -    5+n     9   8-30          3
        mem,immed8        -    8+n    10   9-31         3-5


RCR - Rotate Through Carry Right

        Usage:  RCR     dest,count
        Modifies flags: CF OF

           ?????????????????     ???
        ??>?7 ??????????> 0?????>?C????
        ?  ?????????????????     ???  ?
        ???????????????????????????????

        Rotates the bits in the destination to the right "count" times with
        all data pushed out the right side re-entering on the left.  The
        Carry Flag holds the last bit rotated out.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,1             2     2     9     3            2
        mem,1           15+EA   7     10    4           2-4   (W88=23+EA)
        reg,CL           8+4n  5+n    9    8-30          2
        mem,CL        20+EA+4n 8+n    10   9-31         2-4   (W88=28+EA+4n)
        reg,immed8        -    5+n    9    8-30          3
        mem,immed8        -    8+n    10   9-31         3-5
REP - Repeat String Operation

        Usage:  REP
        Modifies flags: None

        Repeats execution of string instructions while CX != 0.  After
        each string operation, CX is decremented and the Zero Flag is
        tested.  The combination of a repeat prefix and a segment override
        on CPU's before the 386 may result in errors if an interrupt occurs
        before CX=0.  The following code shows code that is susceptible to
        this and how to avoid it:

         again:  rep movs  byte ptr ES:[DI],ES:[SI]   ; vulnerable instr.
                     jcxz  next              ; continue if REP successful
                     loop  again             ; interrupt goofed count
         next:

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              2     2     2                   1


REPE/REPZ - Repeat Equal / Repeat Zero

        Usage:  REPE
                REPZ
        Modifies flags: None

        Repeats execution of string instructions while CX != 0 and the Zero
        Flag is set.  CX is decremented and the Zero Flag tested after
        each string operation.   The combination of a repeat prefix and a
        segment override on processors other than the 386 may result in
        errors if an interrupt occurs before CX=0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              2     2     2                   1


REPNE/REPNZ - Repeat Not Equal / Repeat Not Zero

        Usage:  REPNE
                REPNZ
        Modifies flags: None

        Repeats execution of string instructions while CX != 0 and the Zero
        Flag is clear.   CX is decremented and the Zero Flag tested after
        each string operation.   The combination of a repeat prefix and a
        segment override on processors other than the 386 may result in
        errors if an interrupt occurs before CX=0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              2     2     2                   1
RET/RETF - Return From Procedure

        Usage:  RET     nBytes
                RETF    nBytes
                RETN    nBytes
        Modifies flags: None

        Transfers control from a procedure back to the instruction address
        saved on the stack.  "n bytes" is an optional number of bytes to
        release.  Far returns pop the IP followed by the CS, while near
        returns pop only the IP register.

                                 Clocks                 Size
        Operands         808x  286    386   486         Bytes

        retn            16/20  11+m  10+m    5            1
        retn immed      20/24  11+m  10+m    5            3
        retf            26/34  15+m  18+m    13           1
        retf (PM, same priv.)   -    32+m    18           1
        retf (PM, lesser priv.) -      68    33           1
        retf immed      25/33  15+m  18+m    14           3
        retf immed (PM, same priv.)  32+m    17           1
        retf immed (PM, lesser priv.)  68    33           1


ROL - Rotate Left

        Usage:  ROL     dest,count
        Modifies flags: CF OF

        ???     ?????????????????
        ?C?<?????7 <?????????? 0?<??
        ???  ?  ?????????????????  ?
             ???????????????????????

        Rotates the bits in the destination to the left "count" times with
        all data pushed out the left side re-entering on the right.  The
        Carry Flag will contain the value of the last bit rotated out.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,1             2     2     3     3             2
        mem,1           15+EA   7     7     4            2-4  (W88=23+EA)
        reg,CL           8+4n  5+n    3     3             2
        mem,CL        20+EA+4n 8+n    7     4            2-4  (W88=28+EA+4n)
        reg,immed8        -    5+n    3     2             3
        mem,immed8        -    8+n    7     4            3-5


ROR - Rotate Right

        Usage:  ROR     dest,count
        Modifies flags: CF OF

           ?????????????????     ???
        ??>?7 ??????????> 0?????>?C?   
        ?  ?????????????????  ?  ???   
        ???????????????????????        

        Rotates the bits in the destination to the right "count" times with
        all data pushed out the right side re-entering on the left.  The
        Carry Flag will contain the value of the last bit rotated out.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,1             2     2     3     3             2
        mem,1           15+EA   7     7     4            2-4  (W88=23+EA)
        reg,CL           8+4n  5+n    3     3             2
        mem,CL        20+EA+4n 8+n    7     4            2-4  (W88=28+EA+4n)
        reg,immed8        -    5+n    3     2             3
        mem,immed8        -    8+n    7     4            3-5
SAHF - Store AH Register into FLAGS

        Usage:  SAHF
        Modifies flags: AF CF PF SF ZF

        Transfers bits 0-7 of AH into the Flags Register.  This includes
        AF, CF, PF, SF and ZF.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              4     2     3     2             1


SAL/SHL - Shift Arithmetic Left / Shift Logical Left

        Usage:  SAL     dest,count
                SHL     dest,count
        Modifies flags: CF OF PF SF ZF (AF undefined)

        ???     ?????????????????     ???
        ?C?<?????7 <?????????? 0?<?????0?
        ???     ?????????????????     ???

        Shifts the destination left by "count" bits with zeroes shifted
        in on right.  The Carry Flag contains the last bit shifted out.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,1             2     2     3     3            2
        mem,1           15+EA   7     7     4           2-4  (W88=23+EA)
        reg,CL           8+4n  5+n    3     3            2
        mem,CL        20+EA+4n 8+n    7     4           2-4  (W88=28+EA+4n)
        reg,immed8        -    5+n    3     2            3
        mem,immed8        -    8+n    7     4           3-5


SAR - Shift Arithmetic Right

        Usage:  SAR     dest,count
        Modifies flags: CF OF PF SF ZF (AF undefined)

           ?????????????????     ???
        ????7 ??????????> 0?????>?C?
        ?  ?????????????????     ???
        ????^

        Shifts the destination right by "count" bits with the current sign
        bit replicated in the leftmost bit.  The Carry Flag contains the
        last bit shifted out.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,1             2     2     3     3             2
        mem,1           15+EA   7     7     4            2-4  (W88=23+EA)
        reg,CL           8+4n  5+n    3     3             2
        mem,CL        20+EA+4n 8+n    7     4            2-4  (W88=28+EA+4n)
        reg,immed8        -    5+n    3     2             3
        mem,immed8        -    8+n    7     4            3-5
SBB - Subtract with Borrow/Carry

        Usage:  SBB     dest,src
        Modifies flags: AF CF OF PF SF ZF

        Subtracts the source from the destination, and subtracts 1 extra if
        the Carry Flag is set.   Results are returned in "dest".

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           3     2     2     1             2
        mem,reg         16+EA   7     6     3            2-4  (W88=24+EA)
        reg,mem          9+EA   7     7     2            2-4  (W88=13+EA)
        reg,immed         4     3     2     1            3-4
        mem,immed       17+EA   7     7     3            3-6  (W88=25+EA)
        accum,immed       4     3     2     1            2-3


SCAS - Scan String  (Byte, Word or Doubleword)

        Usage:  SCAS    string
                SCASB
                SCASW
                SCASD   (386+)
        Modifies flags: AF CF OF PF SF ZF

        Compares value at ES:DI (even if operand is specified) from the
        accumulator and sets the flags similar to a subtraction.  DI is
        incremented/decremented based on the instruction format (or
        operand size) and the state of the Direction Flag.  Use with REP
        prefixes.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        string            15    7     7     6             1  (W88=19)


SETAE/SETNB - Set if Above or Equal / Set if Not Below (386+)

        Usage:  SETAE   dest
                SETNB   dest
        (unsigned, 386+)
        Modifies flags: none

        Sets the byte in the operand to 1 if the Carry Flag is clear
        otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3


SETB/SETNAE - Set if Below / Set if Not Above or Equal (386+)

        Usage:  SETB    dest
                SETNAE  dest
        (unsigned, 386+)
        Modifies flags: none

        Sets the byte in the operand to 1 if the Carry Flag is set
        otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3
SETBE/SETNA - Set if Below or Equal / Set if Not Above (386+)

        Usage:  SETBE   dest
                SETNA   dest
        (unsigned, 386+)
        Modifies flags: none

        Sets the byte in the operand to 1 if the Carry Flag or the Zero
        Flag is set, otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3


SETE/SETZ - Set if Equal / Set if Zero (386+)

        Usage:  SETE    dest
                SETZ    dest
        Modifies flags: none

        Sets the byte in the operand to 1 if the Zero Flag is set,
        otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3


SETNE/SETNZ - Set if Not Equal / Set if Not Zero (386+)

        Usage:  SETNE   dest
                SETNZ   dest
        Modifies flags: none

        Sets the byte in the operand to 1 if the Zero Flag is clear,
        otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3


SETL/SETNGE - Set if Less / Set if Not Greater or Equal (386+)

        Usage:  SETL    dest
                SETNGE  dest
        (signed, 386+)
        Modifies flags: none

        Sets the byte in the operand to 1 if the Sign Flag is not equal
        to the Overflow Flag, otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3
SETGE/SETNL - Set if Greater or Equal / Set if Not Less (386+)

        Usage:  SETGE   dest
                SETNL   dest
        (signed, 386+)
        Modifies flags: none

        Sets the byte in the operand to 1 if the Sign Flag equals the
        Overflow Flag, otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3


SETLE/SETNG - Set if Less or Equal / Set if Not greater or Equal (386+)

        Usage:  SETLE   dest
                SETNG   dest
        (signed, 386+)
        Modifies flags: none

        Sets the byte in the operand to 1 if the Zero Flag is set or the
        Sign Flag is not equal to the Overflow Flag,  otherwise sets the
        operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3


SETG/SETNLE - Set if Greater / Set if Not Less or Equal (386+)

        Usage:  SETG    dest
                SETNLE  dest
        (signed, 386+)
        Modifies flags: none

        Sets the byte in the operand to 1 if the Zero Flag is clear or the
        Sign Flag equals to the Overflow Flag,  otherwise sets the operand
        to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3


SETS - Set if Signed (386+)

        Usage:  SETS    dest
        Modifies flags: none

        Sets the byte in the operand to 1 if the Sign Flag is set, otherwise
        sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3
SETNS - Set if Not Signed (386+)

        Usage:  SETNS   dest
        Modifies flags: none

        Sets the byte in the operand to 1 if the Sign Flag is clear,
        otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3


SETC - Set if Carry (386+)

        Usage:  SETC    dest
        Modifies flags: none

        Sets the byte in the operand to 1 if the Carry Flag is set,
        otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3


SETNC - Set if Not Carry (386+)

        Usage:  SETNC   dest
        Modifies flags: none

        Sets the byte in the operand to 1 if the Carry Flag is clear,
        otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3


SETO - Set if Overflow (386+)

        Usage:  SETO    dest
        Modifies flags: none

        Sets the byte in the operand to 1 if the Overflow Flag is set,
        otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3


SETNO - Set if Not Overflow (386+)

        Usage:  SETNO   dest
        Modifies flags: none

        Sets the byte in the operand to 1 if the Overflow Flag is clear,
        otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3
SETP/SETPE - Set if Parity / Set if Parity Even  (386+)

        Usage:  SETP    dest
                SETPE   dest
        Modifies flags: none

        Sets the byte in the operand to 1 if the Parity Flag is set,
        otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3


SETNP/SETPO - Set if No Parity / Set if Parity Odd (386+)

        Usage:  SETNP   dest
                SETPO   dest
        Modifies flags: none

        Sets the byte in the operand to 1 if the Parity Flag is clear,
        otherwise sets the operand to 0.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg8              -     -     4     3             3
        mem8              -     -     5     4             3


SGDT - Store Global Descriptor Table (286+ privileged)

        Usage:  SGDT    dest
        Modifies flags: none

        Stores the Global Descriptor Table (GDT) Register into the
        specified operand.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        mem64             -     11    9     10            5


SIDT - Store Interrupt Descriptor Table (286+ privileged)

        Usage:  SIDT    dest
        Modifies flags: none

        Stores the Interrupt Descriptor Table (IDT) Register into the
        specified operand.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        mem64             -     12    9     10            5


SHL - Shift Logical Left

        See: SAL
SHR - Shift Logical Right

        Usage:  SHR     dest,count
        Modifies flags: CF OF PF SF ZF (AF undefined)

        ???     ?????????????????     ???
        ?0?????>?7 ??????????> 0?????>?C?
        ???     ?????????????????     ???

        Shifts the destination right by "count" bits with zeroes shifted
        in on the left.  The Carry Flag contains the last bit shifted out.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,1             2     2     3                   2
        mem,1           15+EA   7     7                  2-4   (W88=23+EA)
        reg,CL           8+4n  5+n    3                   2
        mem,CL        20+EA+4n 8+n    7                  2-4   (W88=28+EA+4n)
        reg,immed8        -    5+n    3                   3
        mem,immed8        -    8+n    7                  3-5


SHLD/SHRD - Double Precision Shift (386+)

        Usage:  SHLD    dest,src,count
                SHRD    dest,src,count
        Modifies flags: CF PF SF ZF (OF,AF undefined)

        SHLD shifts "dest" to the left "count" times and the bit positions
        opened are filled with the most significant bits of "src".  SHRD
        shifts "dest" to the right "count" times and the bit positions
        opened are filled with the least significant bits of the second
        operand.  Only the 5 lower bits of "count" are used.

                                        Clocks                  Size
        Operands                808x  286   386   486           Bytes

        reg16,reg16,immed8       -     -     3     2              4
        reg32,reg32,immed8       -     -     3     2              4
        mem16,reg16,immed8       -     -     7     3              6
        mem32,reg32,immed8       -     -     7     3              6
        reg16,reg16,CL           -     -     3     3              3
        reg32,reg32,CL           -     -     3     3              3
        mem16,reg16,CL           -     -     7     4              5
        mem32,reg32,CL           -     -     7     4              5


SLDT - Store Local Descriptor Table (286+ privileged)

        Usage:  SLDT    dest
        Modifies flags: none

        Stores the Local Descriptor Table (LDT) Register into the
        specified operand.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16             -     2     2     2             3
        mem16             -     2     2     3             5


SMSW - Store Machine Status Word (286+ privileged)

        Usage:  SMSW    dest
        Modifies flags: none

        Store Machine Status Word (MSW) into "dest".

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16             -     2     10    2             3
        mem16             -     3     3     3             5
STC - Set Carry

        Usage:  STC
        Modifies flags: CF

        Sets the Carry Flag to 1.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              2     2     2     2             1


STD - Set Direction Flag

        Usage:  STD
        Modifies flags: DF

        Sets the Direction Flag to 1 causing string instructions to
        auto-decrement SI and DI instead of auto-increment.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              2     2     2     2             1


STI - Set Interrupt Flag  (Enable Interrupts)

        Usage:  STI
        Modifies flags: IF

        Sets the Interrupt Flag to 1, which enables recognition of all
        hardware interrupts.  If an interrupt is generated by a hardware
        device, an End of Interrupt (EOI) must also be issued to enable
        other hardware interrupts of the same or lower priority.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              2     2     2     5             1


STOS - Store String  (Byte, Word or Doubleword)

        Usage:  STOS    dest
                STOSB
                STOSW
                STOSD
        Modifies flags: None

        Stores value in accumulator to location at ES:(E)DI (even if operand
        is given).  (E)DI is incremented/decremented based on the size of
        the operand (or instruction format) and the state of the Direction
        Flag.   Use with REP prefixes.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        dest              11    3     4     5             1  (W88=15)


STR - Store Task Register (286+ privileged)

        Usage:  STR     dest
        Modifies flags: None

        Stores the current Task Register to the specified operand.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16             -     2     2     2             3
        mem16             -     3     2     3             5
SUB - Subtract

        Usage:  SUB     dest,src
        Modifies flags: AF CF OF PF SF ZF

        The source is subtracted from the destination and the result is
        stored in the destination.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           3     2     2     1             2
        mem,reg         16+EA   7     6     3            2-4  (W88=24+EA)
        reg,mem          9+EA   7     7     2            2-4  (W88=13+EA)
        reg,immed         4     3     2     1            3-4
        mem,immed       17+EA   7     7     3            3-6  (W88=25+EA)
        accum,immed       4     3     2     1            2-3


TEST - Test For Bit Pattern

        Usage:  TEST    dest,src
        Modifies flags: CF OF PF SF ZF (AF undefined)

        Performs a logical AND of the two operands updating the flags
        register without saving the result.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           3     2     1     1             2
        reg,mem          9+EA   6     5     1            2-4  (W88=13+EA)
        mem,reg          9+EA   6     5     2            2-4  (W88=13+EA)
        reg,immed         5     3     2     1            3-4
        mem,immed       11+EA   6     5     2            3-6
        accum,immed       4     3     2     1            2-3


VERR - Verify Read (286+ protected)

        Usage:  VERR    src
        Modifies flags: ZF

        Verifies the specified segment selector is valid and is readable
        at the current privilege level.  If the segment is readable,
        the Zero Flag is set, otherwise it is cleared.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16             -     14    10    11            3
        mem16             -     16    11    11            5


VERW - Verify Write (286+ protected)

        Usage:  VERW    src
        Modifies flags: ZF

        Verifies the specified segment selector is valid and is ratable
        at the current privilege level.  If the segment is writable,
        the Zero Flag is set, otherwise it is cleared.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg16             -     14    15    11            3
        mem16             -     16    16    11            5
WAIT/FWAIT - Event Wait

        Usage:  WAIT
                FWAIT
        Modifies flags: None

        CPU enters wait state until the coprocessor signals it has finished
        its operation.  This instruction is used to prevent the CPU from
        accessing memory that may be temporarily in use by the coprocessor.
        WAIT and FWAIT are identical.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              4     3     6+   1-3            1


WBINVD - Write-Back and Invalidate Cache (486+)

        Usage:  WBINVD
        Modifies flags: None

        Flushes internal cache, then signals the external cache to write
        back current data followed by a signal to flush the external cache.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        none              -     -     -     5             2


XCHG - Exchange

        Usage:  XCHG    dest,src
        Modifies flags: None

        Exchanges contents of source and destination.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           4     3     3     3             2
        mem,reg         17+EA   5     5     5            2-4  (W88=25+EA)
        reg,mem         17+EA   5     5     3            2-4  (W88=25+EA)
        accum,reg         3     3     3     3             1
        reg,accum         3     3     3     3             1


XLAT/XLATB - Translate

        Usage:  XLAT    translation-table
                XLATB   (masm 5.x)
        Modifies flags: None

        Replaces the byte in AL with byte from a user table addressed by
        BX.  The original value of AL is the index into the translate table.
        The best way to discripe this is MOV AL,[BX+AL]

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        table offset      11    5     5     4             1
XOR - Exclusive OR

        Usage:  XOR     dest,src
        Modifies flags: CF OF PF SF ZF (AF undefined)

        Performs a bitwise exclusive OR of the operands and returns
        the result in the destination.

                                 Clocks                 Size
        Operands         808x  286   386   486          Bytes

        reg,reg           3     2     2     1             2
        mem,reg         16+EA   7     6     3            2-4  (W88=24+EA)
        reg,mem          9+EA   7     7     2            2-4  (W88=13+EA)
        reg,immed         4     3     2     1            3-4
        mem,immed       17+EA   7     7     3            3-6  (W88=25+EA)
        accum,immed       4     3     2     1            2-3
                   ??????????????????????????????
                   ? Testing the Intel CPU Type ?
                   ??????????????????????????????

            Written for the PC-GPE by Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au

           ?????????????????????????????????????????????
           ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
           ? SEPERATE TO THE ENTIRE PC-GPE COLLECTION. ?
           ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Introduction ?
????????????????

Believe it or not there are people in the world who still own 8088s! Heck I
only just upgraded my 286 to a 486SUX33 a couple of months ago.

As we all know, 286's and below just don't make the cut anymore, and even
386's are becoming a thing of the past. Personally I think a program should
politely tell someone they are a phleb rather than unceremoniously hanging
their machine for them.

This text file will show one mothed of detecting the CPU type. Unfortunately
I don't have a Pentium op code list so it'll only detect up to a 486.

Most of the information in this file came from a well documented assembly
program available on various ftp sites called 80486.asm, written by Robert
Collins. I tried calling Robert for permission to use his original file, but
he appears to have moved house.

?????????????????????????????????????????????????????????????????????????????
? Method ?
??????????

80186 chips and higher generate an interrupt 6 when they come across an
instruction they don't support, this provides us with a real simple method
of determining the cpu type (coupled with the trap interrupt it would
conceivably also allow us to write a Pentium emulator for the 80286, but
that's another story). We simply have to try execting a 486 command, if it
causes an interrupt 6 then we know the machine is a 386 or lower.

The op codes used in the program below all modify the dx register.

     ?????????????????????????????????????????????????????????????????
     ?  Op Code           Machine Language Bytes   Supported by      ?
     ?????????????????????????????????????????????????????????????????
     ?  shl  dx, 5               C1 E2 05           80186 and higher ?
     ?  smsw dx                  0F 01 E2           80286 and higher ?
     ?  mov  edx, cr0            0F 20 C2           80386 and higher ?
     ?  xadd dx, dx              0F C1 D2           80486 and higher ?
     ?????????????????????????????????????????????????????????????????

When an interrupt 6 is generated you have to modify the value of the IP
register which was pushed onto the stack when the interrupt occurred,
otherwise the CPU will go back to it after the interrupt and keep trying
to execute it. Each of the instructions in the table above are 3 bytes long
so our interrupt handler can simply add 3 to the IP value on the stack.

Identifying an 8088 chip is even simpler. If you push the SP register onto
the stack the 8088 increments the SP value before it pushes it, the other
chips all increment it afterwards. So to test for the presence of an 8088
push SP onto the stack and pop it off into another variable, say AX. If AX
and SP are not equal then the chip is an 8088.

Keep in mind that you *MUST* check for the presence of an 8088 before
doing anything else. Attempting to execute an invalid op code on an 8088
will cause it to hang. Each of the functions in the unit below check for
an 8088 first to prevent this happening.

?????????????????????????????????????????????????????????????????????????????
? A Pascal Unit to Test the CPU Type ?
??????????????????????????????????????

The following pascal unit contains some functions your program can use to
make sure it's running on the right kind of machine. If your program will
only work on a 386 and higher (for example) then put this unit first in your
Uses clause and modify the unit's initialization code to terminate the
program if the wrong CPU type is detected. The unit's current initialization
simply test the CPU type and store it in the 'cpu' variable.

{

  CPUTYPE - A Pascal Unit to Test the CPU Type
            By Mark Feldman u914097@student.canberra.edu
                            myndale@cairo.anu.edu.au

            Based on an original assembly program by Robert Collins.


}


Unit CPUTYPE;


Interface

const CPU_8088    =  0;
      CPU_80186   =  1;
      CPU_80286   =  2;
      CPU_80386   =  3;
      CPU_80486   =  4;
      CPU_UNKNOWN = -1;

{ The cpu variable is initialised to the cpu type }
var cpu : integer;

{ Isa8088 returns true only if cpu is an 8088 or 8086 }
function Isa8088 : boolean;

{ Isa80186 returns true if cpu is an 80186 or higher }
function Isa80186 : boolean;

{ Isa80286 returns true if cpu is an 80286 or higher }
function Isa80286 : boolean;

{ Isa80386 returns true if cpu is an 80386 or higher }
function Isa80386 : boolean;

{ Isa80486 returns true if cpu is an 80486 or higher }
function Isa80486 : boolean;




Implementation

Uses Dos;

var OldIntr6Handler : procedure;
      valid_op_code : boolean;

procedure Intr6Handler;
interrupt;
begin
  valid_op_code := false;

  { Stoopid TP7 won't let me modify IP directly }
  asm
    add word ptr ss:[bp + 18], 3
  end;
end;

function Isa8088 : boolean;
var sp1, sp2 : word;
begin
  asm
    mov sp1, sp
    push sp
    pop sp2
  end;
  if sp1 <> sp2 then
    Isa8088 := true
  else
    Isa8088 := false;
end;

function Isa80186 : boolean;
begin
  if Isa8088 then
    Isa80186 := false
  else
    begin
      valid_op_code := true;
      GetIntVec(6, @OldIntr6Handler);
      SetIntVec(6, Addr(Intr6Handler));
      inline($C1/$E2/$05);  { shl dx, 5 }
      SetIntVec(6, @OldIntr6Handler);
      Isa80186 := valid_op_code;
    end;
end;

function Isa80286 : boolean;
begin
  if Isa8088 then
    Isa80286 := false
  else
    begin
      valid_op_code := true;
      GetIntVec(6, @OldIntr6Handler);
      SetIntVec(6, Addr(Intr6Handler));
      inline($0F/$01/$E2);  { smsw dx }
      SetIntVec(6, @OldIntr6Handler);
      Isa80286 := valid_op_code;
    end;
end;

function Isa80386 : boolean;
begin
  if Isa8088 then
    Isa80386 := false
  else
    begin
      valid_op_code := true;
      GetIntVec(6, @OldIntr6Handler);
      SetIntVec(6, Addr(Intr6Handler));
      inline($0F/$20/$C2);  { mov edx, cr0 }
      SetIntVec(6, @OldIntr6Handler);
      Isa80386 := valid_op_code;
    end;
end;

function Isa80486 : boolean;
begin
  if Isa8088 then
    Isa80486 := false
  else
    begin
      valid_op_code := true;
      GetIntVec(6, @OldIntr6Handler);
      SetIntVec(6, Addr(Intr6Handler));
      inline($0F/$C1/$D2);  { xadd dx, dx }
      SetIntVec(6, @OldIntr6Handler);
      Isa80486 := valid_op_code;
    end;
end;


begin
  if Isa8088 then
    cpu := CPU_8088
  else if Isa80486 then
    cpu := CPU_80486
  else if Isa80386 then
    cpu := CPU_80386
  else if Isa80286 then
    cpu := CPU_80286
  else if Isa80186 then
    cpu := CPU_80186
  else
    cpu := CPU_UNKNOWN;
end.
                   ?????????????????????????????????
                   ?         W E L C O M E         ?
                   ?  To the VGA Trainer Program   ? ?
                   ?              By               ? ?
                   ?      DENTHOR of ASPHYXIA      ? ? ?
                   ????????????????????????????????? ? ?
                     ????????????????????????????????? ?
                       ?????????????????????????????????

                           --==[ PART 1 ]==--



? Introduction



Hi there! This is Denthor of ASPHYXIA, AKA Grant Smith. This training
program is aimed at all those budding young demo coders out there. I am
assuming that the reader is fairly young, has a bit of basic Std. 6 math
under his belt, has done a bit of programming before, probably in BASIC,
and wants to learn how to write a demo all of his/her own.

This I what I am going to do. I am going to describe how certain routines
work, and even give you working source code on how you do it. The source
code will assume that you have a VGA card that can handle the
320x200x256 mode. I will also assume that you have Turbo Pascal 6.0 or
above (this is because some of the code will be in Assembly language,
and Turbo Pascal 6.0 makes this incredibly easy to use). By the end of
the first "run" of sections, you will be able to code some cool demo
stuff all by yourself. The info you need, I will provide to you, but it
will be you who decides on the most spectacular way to use it.

Why not download some of our demos and see what I'm trying to head you
towards.

I will be posting one part a week on the Mailbox BBS. I have the first
"run" of sections worked out, but if you want me to also do sections on
other areas of coding, leave a message to Grant Smith in private E-Mail,
or start a conversation here in this conference. I will do a bit of
moderating of a sort, and point out things that have been done wrong.

In this, the first part, I will show you how you are supposed to set up
your Pascal program, how to get into 320x200x256 graphics mode without a
BGI file, and various methods of putpixels and a clearscreen utility.

NOTE : I drop source code all through my explanations. You needn't try
       to grab all of it from all over the place, at the end of each part I
       add a little program that uses all the new routines that we have
       learned. If you do not fully understand a section, leave me
       private mail telling me what you don't understand or asking how I
       got something etc, and I will try to make myself clearer. One
       last thing : When you spot a mistake I have made in one of my
       parts, leave me mail and I will correct it post-haste.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Disclaimer



Hi again, sorry that I have to add this, but here goes. All source code
obtained from this series of instruction programs is used at your own
risk. Denthor and the ASPHYXIA demo team hold no responsibility for any
loss or damage suffered by anyone through the use of this code. Look
guys, the code I'm going to give you has been used by us before in
Demos, Applications etc, and we have never had any compliants of machine
damage, but if something does go wrong with your computer, don't blame
us. Sorry, but that's the way it is.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? The MCGA mode and how you get into it in Pascal without a BGI


Lets face it. BGI's are next to worthless for demo coding. It is
difficult to find something that is slower then the BGI units for doing
graphics. Another thing is, they wern't really meant for 256 color
screens anyhow. You have to obtain a specific external 256VGA BGI to get
into it in Pascal, and it just doesn't make the grade.

So the question remains, how do we get into MCGA 320x200x256 mode in
Pascal without a BGI? The answer is simple : Assembly language.
Obviously assembly language has loads of functions to handle the VGA
card, and this is just one of them. If you look in Norton Gides to
Assembly Language, it says this ...

____________________________________________________________________
INT 10h,  00h (0)        Set Video Mode

    Sets the video mode.

       On entry:      AH         00h
                      AL         Video mode

       Returns:       None

       Registers destroyed:      AX, SP, BP, SI, DI
??????????????????????????????????????????????????????????????????????????

This is all well and good, but what does it mean? It means that if you
plug in the video mode into AL and call interrupt 10h, SHAZAM! you are
in the mode of your choice. Now, the MCGA video mode is mode 13h, and
here is how we do it in Pascal.

Procedure SetMCGA;
BEGIN
  asm
        mov     ax,0013h
        int     10h
  end;
END;

There you have it! One call to that procedure, and BANG you are in
320x200x256 mode. We can't actually do anything in it yet, so to go back
to text mode, you make the video mode equal to 03h, as seen below :

Procedure SetText;
BEGIN
  asm
        mov     ax,0003h
        int     10h
  end;
END;


BANG! We are back in text mode! Now, cry all your enquiring minds, what
use is this? We can get into the mode, but how do we actually SHOW
something on the screen? For that, you must move onto the next section
....

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  Clearing the screen to a specific color

Now that we are in MCGA mode, how do we clear the screen. The answer is
simple : you must just remember that the base adress of the screen is
$a000. From $a000, the next 64000 bytes are what is actually displayed on
the screen (Note : 320 * 200 = 64000). So to clear the screen, you just use
the fillchar command (a basic Pascal command) like so :

      FillChar (Mem [$a000:0],64000,Col);

What the mem command passes the Segment base and the Offset of a part of
memory : in this case the screen base is the Segment, and we are starting
at the top of the screen; Offset 0. The 64000 is the size of the screen
(see above), and Col is a value between 0 and 255, which represents the
color you want to clear the screen to.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  Putting a pixel on the screen (two different methoods)

If you look in Norton Guides about putting a pixel onto the screen, you
will see this  :


??????????????????????????????????????????????????????????????????????????
    Writes a pixel dot of a specified color at a specified screen
    coordinate.

    On entry:      AH         0Ch
                   AL         Pixel color
                   CX         Horizontal position of pixel
                   DX         Vertical position of pixel
                   BH         Display page number (graphics modes with more
                              than 1 page)

    Returns:       None

    Registers destroyed:      AX, SP, BP, SI, DI
??????????????????????????????????????????????????????????????????????????

As seen from our SetMCGA example, you would write this by doing the following:

Procedure INTPutpixel (X,Y : Integer; Col : Byte);
BEGIN
  asm
     mov        ah,0Ch
     mov        al,[col]
     mov        cx,[x]
     mov        dx,[y]
     mov        bx,[1]
     int        10h
  end;
END;

The X would be the X-Coordinate, the Y would be the Y-Coordinate, and the Col
would be the color of the pixel to place. Note that MCGA has 256 colors,
numbered 0 to 255. The startoff pallette is pretty grotty, and I will show
you how to alter it in my next lesson, but for now you will have to hunt for
colors that fit in for what you want to do. Luckily, a byte is 0 to 255, so
that is what we pass to the col variable. Have a look at the following.

    CGA = 4 colours.
    4x4 = 16
    EGA = 16 colors.
    16x16 = 256
    VGA = 256 colors.
    Therefore an EGA is a CGA squared, and a VGA is an EGA squared ;-)

Anyway, back to reality. Even though the abouve procedure is written in
assembly language, it is slooow. Why? I hear your enquiring minds cry. The
reason is simple : It uses interrupts (It calls INT 10h). Interrupts are
sloooow ... which is okay for getting into MCGA mode, but not for trying
to put down a pixel lickety-split. So, why not try the following ...

Procedure MEMPutpixel (X,Y : Integer; Col : Byte);
BEGIN
  Mem [VGA:X+(Y*320)]:=Col;
END;


The Mem command, as we have seen above, allows you to point at a certain
point in memory ... the starting point is $a000, the base of the VGA's
memory, and then we specify how far into this base memory we start.
Think of the monitor this way. It starts in the top left hand corner at
0. As you increase the number, you start to move across the screen to your
right, until you reach 320. At 320, you have gone all the way across the
screen and come back out the left side, one pixel down. This carries on
until you reach 63999, at the bottom right hand side of the screen. This
is how we get the equation X+(Y*320). For every increased Y, we must
increment the number by 320. Once we are at the beginning of the Y line
we want, we add our X by how far out we want to be. This gives us the
exact point in memory that we want to be at, and then we set it equal to
the pixel value we want.

The MEM methood of putpixel is much faster, and it is shown in the sample
program at the end of this lesson. The ASPHYXIA team uses neither putpixel;
we use a DMA-Straight-To-Screen-Kill-Yer-Momma-With-An-Axe type putipixel
which is FAST. We will give it out, but only to those of you who show us
you are serious about coding. If you do do anything, upload it to me,
I will be very interested to see it. Remember : If you do glean anything
from these training sessions, give us a mention in your demos and UPLOAD
YOUR DEMO TO US!

Well, after this is the sample program; have fun with it, UNDERSTAND it,
and next week I will start on fun with the pallette.

See you all later,
    - Denthor


?????????????????????????????????????????????????????????????????????????????
? TUTPROG1.PAS ?
????????????????


{$X+}   (* This is a handy little trick to know. If you put this at the top
           of your program, you do not have to set a variable when calling
           a function, i.e. you may just say 'READKEY' instead of
           'CH:=READKEY'                                                *)

USES Crt;           (* This has a few nice functions in it, such as the
                       READKEY command.                                 *)

CONST VGA = $a000;  (* This sets the constant VGA to the segment of the
                       VGA screen.                                      *)

{??????????????????????????????????????????????????????????????????????????}
Procedure SetMCGA;  { This procedure gets you into 320x200x256 mode. }
BEGIN
  asm
     mov        ax,0013h
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure SetText;  { This procedure returns you to text mode.  }
BEGIN
  asm
     mov        ax,0003h
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure Cls (Col : Byte);
   { This clears the screen to the specified color }
BEGIN
  Fillchar (Mem [$a000:0],64000,col);
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure INTPutpixel (X,Y : Integer; Col : Byte);
   { This puts a pixel on the screen using interrupts. }
BEGIN
  asm
     mov        ah,0Ch
     mov        al,[col]
     mov        cx,[x]
     mov        dx,[y]
     mov        bx,[1]
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure TestINTPutpixel;
   { This tests out the speed of the INTPutpixel procedure. }
VAR loop1,loop2 : Integer;
BEGIN
  For loop1:=0 to 319 do
    For loop2:=0 to 199 do
      INTPutpixel (loop1,loop2,Random (256));
  Readkey;
  Cls (0);
END;



{??????????????????????????????????????????????????????????????????????????}
Procedure MEMPutpixel (X,Y : Integer; Col : Byte);
  { This puts a pixel on the screen by writing directly to memory. }
BEGIN
  Mem [VGA:X+(Y*320)]:=Col;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure TestMEMPutpixel;
   { This tests out the speed of the MEMPutpixel procedure. }
VAR loop1,loop2 : Integer;
BEGIN
  For loop1:=0 to 319 do
    For loop2:=0 to 199 do
      MEMPutpixel (loop1,loop2,Random (256));
  Readkey;
  Cls (0);
END;



{??????????????????????????????????????????????????????????????????????????}
BEGIN    (* Of the main program *)
  ClrScr;               { This clears the text Screen (CRT unit) }
  Writeln ('What will happen is that I will clear the screen twice. After');
  Writeln ('each clear screen you will have to hit a key. I will then fill');
  Writeln ('the screen twice with randomlly colored pixels using two different');
  Writeln ('methoods, after each of which you will have to hit a key. I will');
  Writeln ('then return you to text mode.');
  Writeln; Writeln;
  Write ('Hit any kay to continue ...');
  Readkey;

  SetMCGA;
  CLS (32);
  Readkey;
  CLS (90);
  Readkey;
  TestINTPutpixel;
  TestMEMPutpixel;
  SetText;

  Writeln ('All done. This concludes the first sample program in the ASPHYXIA');
  Writeln ('Training series. You may reach DENTHOR under the name of GRANT');
  Writeln ('SMITH on the MailBox BBS, or leave a message to ASPHYXIA on the');
  Writeln ('ASPHYXIA BBS. Get the numbers from Roblist, or write to :');
  Writeln ('             Grant Smith');
  Writeln ('             P.O. Box 270');
  Writeln ('             Kloof');
  Writeln ('             3640');
  Writeln ('I hope to hear from you soon!');
  Writeln; Writeln;
  Write   ('Hit any key to exit ...');
  Readkey;
END.     (* Of the main program *)                   ?????????????????????????????????
                   ?         W E L C O M E         ?
                   ?  To the VGA Trainer Program   ? ?
                   ?              By               ? ?
                   ?      DENTHOR of ASPHYXIA      ? ? ?
                   ????????????????????????????????? ? ?
                     ????????????????????????????????? ?
                       ?????????????????????????????????

                           --==[ PART 2 ]==--



? Introduction



Hi there again! This is Grant Smith, AKA Denthor of ASPHYXIA. This is the
second part of my Training Program for new programmers. I have only had a
lukewarm response to my first part of the trainer series ... remember, if
I don't hear from you, I will assume that you are all dead and will stop
writing the series ;-). Also, if you do get in contact with me I will give
you some of our fast assembly routines which will speed up your demos no
end. So go on, leave mail to GRANT SMITH in the main section of the
MailBox BBS, start up a discussion or ask a few questions in this Conference,
leave mail to ASPHYXIA on the ASPHYXIA BBS, leave mail to Denthor on
Connectix, or write to Grant Smith,
                       P.O.Box 270
                       Kloof
                       3640
See, there are many ways you can get in contact with me! Use one of them!

In this part, I will put the Pallette through it's paces. What the hell is
a pallette? How do I find out what it is? How do I set it? How do I stop
the "fuzz" that appears on the screen when I change the pallette? How do
I black out the screen using the pallette? How do I fade in a screen?
How do I fade out a screen? Why are telephone calls so expensive?
Most of these quesions will be answered in this, the second part of my
Trainer Series for Pascal.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  What is the Pallette?

A few weeks ago a friend of mine was playing a computer game. In the game
there was a machine with stripes of blue running across it. When the
machine was activated, while half of the the blue stripes stayed the same,
the other half started to change color and glow. He asked me how two stripes
of the same color suddenly become different like that. The answer is simple:
the program was changing the pallette. As you know from Part 1, there are
256 colors in MCGA mode, numbered 0 to 255. What you don't know is that each
if those colors is made up of different intensities of Red, Green and Blue,
the primary colors (you should have learned about the primary colors at
school). These intensities are numbers between 0 and 63. The color of
bright red would for example be obtained by setting red intensity to 63,
green intensity to 0, and blue intensity to 0. This means that two colors
can look exactly the same, eg you can set color 10 to bright red and color
78 to color bright red. If you draw a picture using both of those colors,
no-one will be able to tell the difference between the two.. It is only
when you again change the pallette of either of them will they be able to
tell the difference. Also, by changing the whole pallette, you can obtain
the "Fade in" and "Fade out" effects found in many demos and games.
Pallette manipulation can become quite confusing to some people, because
colors that look the same are in fact totally seperate.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  How do I read in the pallette value of a color?

This is very easy to do. To read in the pallette value, you enter in the
number of the color you want into port $3c7, then read in the values of
red, green and blue respectively from port $3c9. Simple, huh? Here is a
procedure that does it for you :

Procedure GetPal(ColorNo : Byte; Var R,G,B : Byte);
  { This reads the values of the Red, Green and Blue values of a certain
    color and returns them to you. }
Begin
   Port[$3c7] := ColorNo;
   R := Port[$3c9];
   G := Port[$3c9];
   B := Port[$3c9];
End;

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  How do I set the pallette value of a color?

This is also as easy as 3.1415926535897932385. What you do is you enter in
the number of the color you want to change into port $3c8, then enter the
values of red, green and blue respectively into port $3c9. Because you are
all so lazy I have written the procedure for you ;-)


Procedure Pal(ColorNo : Byte; R,G,B : Byte);
  { This sets the Red, Green and Blue values of a certain color }
Begin
   Port[$3c8] := ColorNo;
   Port[$3c9] := R;
   Port[$3c9] := G;
   Port[$3c9] := B;
End;


Asphyxia doesn't use the above pallete procedures, we use assembler versions,
which will be given to PEOPLE WHO RESPOND TO THIS TRAINER SERIES (HINT,
HINT)


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  How do I stop the "fuzz" that appears on my screen when I change the
        pallette?

If you have used the pallette before, you will have noticed that there is
quite a bit of "fuzz" on the screen when you change it. The way we counter
this is as follows : There is an elctron beam on your monitor that is
constantly updating your screen from top to bottom. As it gets to the
bottom of the screen, it takes a while for it to get back up to the top of
the screen to start updating the screen again. The period where it moves
from the bottom to the top is called the Verticle Retrace. During the
verticle retrace you may change the pallette without affecting what is
on the screen. What we do is that we wait until a verticle retrace has
started by calling a certain procedure; this means that everything we do
now will only be shown after the verticle retrace, so we can do all sorts
of strange and unusual things to the screen during this retrace and only
the results will be shown when the retrace is finished. This is way cool,
as it means that when we change the pallette, the fuzz doesn't appear on
the screen, only the result (the changed pallette), is seen after the
retrace! Neat, huh? ;-) I have put the purely assembler WaitRetrace routine
in the sample code that follows this message. Use it wisely, my son.

NOTE : WaitRetrace can be a great help to your coding ... code that fits
       into one retrace will mean that the demo will run at the same
       speed no matter what your computer speed (unless you are doing a lot
       during the WaitRetrace and the computer is slooooow). Note that in
       the following sample program and in our SilkyDemo, the thing will run
       at the same speed whether turbo is on or off.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  How do I black out the screen using the pallette?

This is basic : just set the Red, Green and Blue values of all colors to
zero intensity, like so :

Procedure Blackout;
  { This procedure blackens the screen by setting the pallette values of
    all the colors to zero. }
VAR loop1:integer;
BEGIN
  WaitRetrace;
  For loop1:=0 to 255 do
    Pal (loop1,0,0,0);
END;


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  How do I fade in a screen?

Okay, this can be VERY effective. What you must first do is grab the
pallette into a variable, like so :

   VAR Pall := Array [0.255,1..3] of BYTE;

0 to 255 is for the 256 colors in MCGA mode, 1 to 3 is red, green and blue
intensity values;

Procedure GrabPallette;
VAR loop1:integer;
BEGIN
  For loop1:=0 to 255 do
    Getpal (loop1,pall[loop1,1],pall[loop1,2],pall[loop1,3]);
END;

This loads the entire pallette into variable pall. Then you must blackout
the screen (see above), and draw what you want to screen without the
construction being shown. Then what you do is go throgh the pallette. For
each color, you see if the individual intensities are what they should be.
If not, you increase them by one unit until they are. Beacuse intensites
are in a range from 0 to 63, you only need do this a maximum of 64 times.

Procedure Fadeup;
VAR loop1,loop2:integer;
    Tmp : Array [1..3] of byte;
      { This is temporary storage for the values of a color }
BEGIN
  For loop1:=1 to 64 do BEGIN
      { A color value for Red, green or blue is 0 to 63, so this loop only
        need be executed a maximum of 64 times }
    WaitRetrace;
    For loop2:=0 to 255 do BEGIN
      Getpal (loop2,Tmp[1],Tmp[2],Tmp[3]);
      If Tmp[1]<Pall[loop2,1] then inc (Tmp[1]);
      If Tmp[2]<Pall[loop2,2] then inc (Tmp[2]);
      If Tmp[3]<Pall[loop2,3] then inc (Tmp[3]);
        { If the Red, Green or Blue values of color loop2 are less then they
          should be, increase them by one. }
      Pal (loop2,Tmp[1],Tmp[2],Tmp[3]);
        { Set the new, altered pallette color. }
    END;
  END;
END;

Hey-presto! The screen fades up. You can just add in a delay before the
waitretrace if you feel it is too fast. Cool, no?


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  How do I fade out a screen?

This is just like the fade in of a screen, just in the opposite direction.
What you do is you check each color intensity. If it is not yet zero, you
decrease it by one until it is. BAAASIIIC!

Procedure FadeDown;
VAR loop1,loop2:integer;
    Tmp : Array [1..3] of byte;
      { This is temporary storage for the values of a color }
BEGIN
  For loop1:=1 to 64 do BEGIN
    WaitRetrace;
    For loop2:=0 to 255 do BEGIN
      Getpal (loop2,Tmp[1],Tmp[2],Tmp[3]);
      If Tmp[1]>0 then dec (Tmp[1]);
      If Tmp[2]>0 then dec (Tmp[2]);
      If Tmp[3]>0 then dec (Tmp[3]);
        { If the Red, Green or Blue values of color loop2 are not yet zero,
          then, decrease them by one. }
      Pal (loop2,Tmp[1],Tmp[2],Tmp[3]);
        { Set the new, altered pallette color. }
    END;
  END;
END;

Again, to slow the above down, put in a delay above the WaitRetrace. Fading
out the screen looks SO much more impressive then just clearing the screen;
it can make a world of difference in the impression your demo etc will
leave on the people viewing it. To restore the pallette, just do this :

Procedure RestorePallette;
VAR loop1:integer;
BEGIN
  WaitRetrace;
  For loop1:=0 to 255 do
    pal (loop1,Pall[loop1,1],Pall[loop1,2],Pall[loop1,3]);
END;


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  In closing

Well, there are most of those origional questions answered ;-) The following
sample program is quite big, so it might take you a while to get around it.
Persevere and thou shalt overcome. Pallette manipulation has been a thorn
in many coders sides for quite some time, yet hopefully I have shown you
all how amazingly simple it is once you have grasped the basics.

I need more feedback! In which direction would you like me to head? Is there
any particular section you would like more info on? Also, upload me your
demo's, however trivial they might seem. We really want to get in contact
with/help out new and old coders alike, but you have to leave us that message
telling us about yourself and what you have done or want to do.

IS THERE ANYBODY OUT THERE!?!

P.S. Our new demo should be out soon ... it is going to be GOOOD ... keep
     an eye out for it.

          [ And so she came across him, slumped over his keyboard
            yet again . 'It's three in the morning' she whispered.
            'Let's get you to bed'. He stirred, his face bathed in
            the dull light of his monitor. He mutters something.
            As she leans across him to disconnect the power, she
            asks him; 'Was it worth it?'. His answer surprises her.
            'No.' he says. In his caffiene-enduced haze, he smiles.
            'But it sure is a great way to relax.'                  ]
                                           - Grant Smith
                                              Tue 13 July, 1993
                                               2:23 am.

See you next week!
   - Denthor


?????????????????????????????????????????????????????????????????????????????
? TUTPROG2.PAS ?
????????????????

{$X+}

Uses Crt;

CONST VGA=$a000;

Var Pall,Pall2 : Array[0..255,1..3] of Byte;
     { This declares the PALL variable. 0 to 255 signify the colors of the
       pallette, 1 to 3 signifies the Red, Green and Blue values. I am
       going to use this as a sort of "virtual pallette", and alter it
       as much as I want, then suddenly bang it to screen. Pall2 is used
       to "remember" the origional pallette so that we can restore it at
       the end of the program. }



{??????????????????????????????????????????????????????????????????????????}
Procedure SetMCGA;  { This procedure gets you into 320x200x256 mode. }
BEGIN
  asm
     mov        ax,0013h
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure SetText;  { This procedure returns you to text mode.  }
BEGIN
  asm
     mov        ax,0003h
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
procedure WaitRetrace; assembler;
  { This waits until you are in a Verticle Retrace ... this means that all
    screen manipulation you do only appears on screen in the next verticle
    retrace ... this removes most of the "fuzz" that you see on the screen
    when changing the pallette. It unfortunately slows down your program
    by "synching" your program with your monitor card ... it does mean
    that the program will run at almost the same speed on different
    speeds of computers which have similar monitors. In our SilkyDemo,
    we used a WaitRetrace, and it therefore runs at the same (fairly
    fast) speed when Turbo is on or off. }

label
  l1, l2;
asm
    mov dx,3DAh
l1:
    in al,dx
    and al,08h
    jnz l1
l2:
    in al,dx
    and al,08h
    jz  l2
end;


{??????????????????????????????????????????????????????????????????????????}
Procedure GetPal(ColorNo : Byte; Var R,G,B : Byte);
  { This reads the values of the Red, Green and Blue values of a certain
    color and returns them to you. }
Begin
   Port[$3c7] := ColorNo;
   R := Port[$3c9];
   G := Port[$3c9];
   B := Port[$3c9];
End;


{??????????????????????????????????????????????????????????????????????????}
Procedure Pal(ColorNo : Byte; R,G,B : Byte);
  { This sets the Red, Green and Blue values of a certain color }
Begin
   Port[$3c8] := ColorNo;
   Port[$3c9] := R;
   Port[$3c9] := G;
   Port[$3c9] := B;
End;


{??????????????????????????????????????????????????????????????????????????}
Procedure Putpixel (X,Y : Integer; Col : Byte);
  { This puts a pixel on the screen by writing directly to memory. }
BEGIN
  Mem [VGA:X+(Y*320)]:=Col;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure line(a,b,c,d,col:integer);
  { This draws a line from a,b to c,d of color col. }
   Function sgn(a:real):integer;
   BEGIN
        if a>0 then sgn:=+1;
        if a<0 then sgn:=-1;
        if a=0 then sgn:=0;
   END;
var u,s,v,d1x,d1y,d2x,d2y,m,n:real;
    i:integer;
BEGIN
     u:= c - a;
     v:= d - b;
     d1x:= SGN(u);
     d1y:= SGN(v);
     d2x:= SGN(u);
     d2y:= 0;
     m:= ABS(u);
     n := ABS(v);
     IF NOT (M>N) then
     BEGIN
          d2x := 0 ;
          d2y := SGN(v);
          m := ABS(v);
          n := ABS(u);
     END;
     s := INT(m / 2);
     FOR i := 0 TO round(m) DO
     BEGIN
          putpixel(a,b,col);
          s := s + n;
          IF not (s<m) THEN
          BEGIN
               s := s - m;
               a:= a +round(d1x);
               b := b + round(d1y);
          END
          ELSE
          BEGIN
               a := a + round(d2x);
               b := b + round(d2y);
          END;
     END;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure PalPlay;
  { This procedure mucks about with our "virtual pallette", then shoves it
    to screen. }
Var Tmp : Array[1..3] of Byte;
  { This is used as a "temporary color" in our pallette }
    loop1 : Integer;
BEGIN
   Move(Pall[200],Tmp,3);
     { This copies color 200 from our virtual pallette to the Tmp variable }
   Move(Pall[0],Pall[1],200*3);
     { This moves the entire virtual pallette up one color }
   Move(Tmp,Pall[0],3);
     { This copies the Tmp variable to the bottom of the virtual pallette }
   WaitRetrace;
   For loop1:=1 to 255 do
     pal (loop1,pall[loop1,1],pall[loop1,2],pall[loop1,3]);
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure SetUpScreen;
  { This gets our screen ready but setting up the pallette and drawing
    the lines. }
Var Loop : Integer;
BEGIN
   FillChar(Pall,SizeOf(Pall),0);
       { Clear the entire PALL variable to zero. }
   For Loop := 0 to 200 do BEGIN
      Pall[Loop,1] := Loop mod 64;
   END;
       { This sets colors 0 to 200 in the PALL variable to values between
         0 to 63. the MOD function gives you the remainder of a division,
         ie. 105 mod 10 = 5 }

   For Loop := 1 to 320 do BEGIN
      Line(319,199,320-Loop,0,(Loop Mod 199)+1);
      Line(0,0,Loop,199,(Loop Mod 199)+1);
       { These two lines start drawing lines from the left and the right
         hand sides of the screen, using colors 1 to 199. Look at these
         two lines and understand them. }
      PalPlay;
        { This calls the PalPlay procedure }
   END;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure GrabPallette;
VAR loop1:integer;
BEGIN
  For loop1:=0 to 255 do
    Getpal (loop1,pall2[loop1,1],pall2[loop1,2],pall2[loop1,3]);
END;



{??????????????????????????????????????????????????????????????????????????}
Procedure Blackout;
  { This procedure blackens the screen by setting the pallette values of
    all the colors to zero. }
VAR loop1:integer;
BEGIN
  WaitRetrace;
  For loop1:=0 to 255 do
    Pal (loop1,0,0,0);
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure HiddenScreenSetup;
  { This procedure sets up the screen while it is blacked out, so that the
    user can't see what is happening. }
VAR loop1,loop2:integer;
BEGIN
  For loop1:=0 to 319 do
    For loop2:=0 to 199 do
      PutPixel (loop1,loop2,Random (256));
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure Fadeup;
  { This procedure slowly fades up the new screen }
VAR loop1,loop2:integer;
    Tmp : Array [1..3] of byte;
      { This is temporary storage for the values of a color }
BEGIN
  For loop1:=1 to 64 do BEGIN
      { A color value for Red, green or blue is 0 to 63, so this loop only
        need be executed a maximum of 64 times }
    WaitRetrace;
    For loop2:=0 to 255 do BEGIN
      Getpal (loop2,Tmp[1],Tmp[2],Tmp[3]);
      If Tmp[1]<Pall2[loop2,1] then inc (Tmp[1]);
      If Tmp[2]<Pall2[loop2,2] then inc (Tmp[2]);
      If Tmp[3]<Pall2[loop2,3] then inc (Tmp[3]);
        { If the Red, Green or Blue values of color loop2 are less then they
          should be, increase them by one. }
      Pal (loop2,Tmp[1],Tmp[2],Tmp[3]);
        { Set the new, altered pallette color. }
    END;
  END;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure FadeDown;
  { This procedure fades the screen out to black. }
VAR loop1,loop2:integer;
    Tmp : Array [1..3] of byte;
      { This is temporary storage for the values of a color }
BEGIN
  For loop1:=1 to 64 do BEGIN
    WaitRetrace;
    For loop2:=0 to 255 do BEGIN
      Getpal (loop2,Tmp[1],Tmp[2],Tmp[3]);
      If Tmp[1]>0 then dec (Tmp[1]);
      If Tmp[2]>0 then dec (Tmp[2]);
      If Tmp[3]>0 then dec (Tmp[3]);
        { If the Red, Green or Blue values of color loop2 are not yet zero,
          then, decrease them by one. }
      Pal (loop2,Tmp[1],Tmp[2],Tmp[3]);
        { Set the new, altered pallette color. }
    END;
  END;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure RestorePallette;
  { This procedure restores the origional pallette }
VAR loop1:integer;
BEGIN
  WaitRetrace;
  For loop1:=0 to 255 do
    pal (loop1,Pall2[loop1,1],Pall2[loop1,2],Pall2[loop1,3]);
END;


BEGIN
  ClrScr;
  Writeln ('This program will draw lines of different colors across the');
  Writeln ('screen and change them only by changing their pallette values.');
  Writeln ('The nice thing about using the pallette is that one pallette');
  Writeln ('change changes the same color over the whole screen, without');
  Writeln ('you having to redraw it. Because I am using a WaitRetrace');
  Writeln ('command, turning on and off your turbo during the demonstration');
  Writeln ('should have no effect.');
  Writeln;
  Writeln ('The second part of the demo blacks out the screen using the');
  Writeln ('pallette, fades in the screen, waits for a keypress, then fades');
  Writeln ('it out again. I haven''t put in any delays for the fadein/out,');
  Writeln ('so you will have to put ''em in yourself to get it to the speed you');
  Writeln ('like. Have fun and enjoy! ;-)');
  Writeln; Writeln;
  Writeln ('Hit any key to continue ...');
  Readkey;
  SetMCGA;
  GrabPallette;
  SetUpScreen;
  repeat
     PalPlay;
       { Call the PalPlay procedure repeatedly until a key is pressed. }
  Until Keypressed;
  Readkey;
    { Read in the key pressed otherwise it is left in the keyboard buffer }
  Blackout;
  HiddenScreenSetup;
  FadeUp;
  Readkey;
  FadeDown;
  Readkey;
  RestorePallette;
  SetText;
  Writeln ('All done. This concludes the second sample program in the ASPHYXIA');
  Writeln ('Training series. You may reach DENTHOR under the name of GRANT');
  Writeln ('SMITH on the MailBox BBS, or leave a message to ASPHYXIA on the');
  Writeln ('ASPHYXIA BBS. Get the numbers from Roblist, or write to :');
  Writeln ('             Grant Smith');
  Writeln ('             P.O. Box 270');
  Writeln ('             Kloof');
  Writeln ('             3640');
  Writeln ('I hope to hear from you soon!');
  Writeln; Writeln;
  Write   ('Hit any key to exit ...');
  Readkey;
END.
                   ?????????????????????????????????
                   ?         W E L C O M E         ?
                   ?  To the VGA Trainer Program   ? ?
                   ?              By               ? ?
                   ?      DENTHOR of ASPHYXIA      ? ? ?
                   ????????????????????????????????? ? ?
                     ????????????????????????????????? ?
                       ?????????????????????????????????

                           --==[ PART 3 ]==--



? Introduction



Greetings! This is the third part of the VGA Trainer series! Sorry it 
took so long to get out, but I had a running battle with the traffic
department for three days to get my car registered, and then the MailBox
went down. Ahh, well, life stinks. Anyway, today will do some things
vital to most programs : Lines and circles.

Watch out for next week's part : Virtual screens. The easy way to
eliminate flicker, "doubled sprites", and subjecting the user to watch
you building your screen. Almost every ASPHYXIA demo has used a virtual
screen (with the exception of the SilkyDemo), so this is one to watch out
for. I will also show you how to put all of these loose procedures into
units.

If you would like to contact me, or the team, there are many ways you 
can do it : 1) Write a message to Grant Smith in private mail here on
                  the Mailbox BBS.
            2) Write a message here in the Programming conference here
                  on the Mailbox (Preferred if you have a general
                  programming query or problem others would benefit from)
            3) Write to ASPHYXIA on the ASPHYXIA BBS.
            4) Write to Denthor, Eze or Livewire on Connectix.
            5) Write to :  Grant Smith
                           P.O.Box 270 Kloof
                           3640
            6) Call me (Grant Smith) at 73 2129 (leave a message if you 
                  call during varsity)
                  
NB : If you are a representative of a company or BBS, and want ASPHYXIA 
       to do you a demo, leave mail to me; we can discuss it.
NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling
        quite lonely and want to meet/help out/exchange code with other demo
        groups. What do you have to lose? Leave a message here and we can work
        out how to transfer it. We really want to hear from you!


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  Circle Algorithim

You all know what a circle looks like. But how do you draw one on the
computer?

You probably know circles drawn with the degrees at these points :

                                0
                              ??|??
                             ???|???
                        270 ----+---- 90
                             ???|???
                              ??|??
                               180

Sorry about my ASCI ;-) ... anyway, Pascal doesn't work that way ... it
works with radians instead of degrees. (You can convert radians to degrees,
but I'm not going to go into that now. Note though that in pascal, the
circle goes like this :

                               270
                              ??|??
                             ???|???
                        180 ----+---- 0
                             ???|???
                              ??|??
                                90


Even so, we can still use the famous equations to draw our circle ...
(You derive the following by using the theorem of our good friend
Pythagoras)
                     Sin (deg) = Y/R
                     Cos (deg) = X/R
(This is standard 8(?) maths ... if you haven't reached that level yet,
take this to your dad, or if you get stuck leave me a message and I'll
do a bit of basic Trig with you. I aim to please ;-))

Where Y = your Y-coord
      X = your X-coord
      R = your radius (the size of your circle)
      deg = the degree

To simplify matters, we rewrite the equation to get our X and Y values :

                     Y = R*Sin(deg)
                     X = R*Cos(deg)

This obviousy is perfect for us, because it gives us our X and Y co-ords
to put into our putpixel routine (see Part 1). Because the Sin and Cos
functions return a Real value, we use a round function to transform it
into an Integer.

     Procedure Circle (oX,oY,rad:integer;Col:Byte);
     VAR deg:real;
         X,Y:integer;
     BEGIN
       deg:=0;
       repeat
         X:=round(rad*COS (deg));
         Y:=round(rad*sin (deg));
         putpixel (x+ox,y+oy,Col);
         deg:=deg+0.005;
       until (deg>6.4);
     END;

In the above example, the smaller the amount that deg is increased by,
the closer the pixels in the circle will be, but the slower the procedure.
0.005 seem to be best for the 320x200 screen. NOTE : ASPHYXIA does not use
this particular circle algorithm, ours is in assembly language, but this
one should be fast enough for most. If it isn't, give us the stuff you are
using it for and we'll give you ours.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  Line algorithms

There are many ways to draw a line on the computer. I will describe one
and give you two. (The second one you can figure out for yourselves; it
is based on the first one but is faster)

The first thing you need to do is pass what you want the line to look
like to your line procedure. What I have done is said that x1,y1 is the
first point on the screen, and x2,y2 is the second point. We also pass the
color to the procedure. (Remember the screens top left hand corner is (0,0);
see Part 1)

Ie.            o  (X1,Y1)
                ooooooooo
                         ooooooooo
                                  oooooooo  (X2,Y2)

Again, sorry about my drawings ;-)

To find the length of the line, we say the following :

           XLength = ABS (x1-x2)
           YLength = ABS (y1-y2)

The ABS function means that whatever the result, it will give you an
absolute, or posotive, answer. At this stage I set a variable stating
wheter the difference between the two x's are negative, zero or posotive.
(I do the same for the y's) If the difference is zero, I just use a loop
keeping the two with the zero difference posotive, then exit.

If neither the x's or y's have a zero difference, I calculate the X and Y
slopes, using the following two equations :

           Xslope = Xlength / Ylength
           Yslope = Ylength / Xlength

As you can see, the slopes are real numbers.
NOTE : XSlope = 1 / YSlope

Now, there are two ways of drawing the lines :

           X = XSlope * Y
           Y = YSlope * X

The question is, which one to use? if you use the wrong one, your line
will look like this :

        o
           o
              o

Instead of this :

        ooo
           ooo
              ooo

Well, the solution is as follows :

                           *\``|``/*
                           ***\|/***
                           ----+----
                           ***/|\***
                           */``|``\*

If the slope angle is in the area of the stars (*) then use the first
equation, if it is in the other section (`) then use the second one.
What you do is you calculate the variable on the left hand side by
putting the variable on the right hand side in a loop and solving. Below
is our finished line routine :

Procedure Line (x1,y1,x2,y2:integer;col:byte);
VAR x,y,xlength,ylength,dx,dy:integer;
    xslope,yslope:real;
BEGIN
  xlength:=abs (x1-x2);
  if (x1-x2)<0 then dx:=-1;
  if (x1-x2)=0 then dx:=0;
  if (x1-x2)>0 then dx:=+1;
  ylength:=abs (y1-y2);
  if (y1-y2)<0 then dy:=-1;
  if (y1-y2)=0 then dy:=0;
  if (y1-y2)>0 then dy:=+1;
  if (dy=0) then BEGIN
    if dx<0 then for x:=x1 to x2 do
      putpixel (x,y1,col);
    if dx>0 then for x:=x2 to x1 do
      putpixel (x,y1,col);
    exit;
  END;
  if (dx=0) then BEGIN
    if dy<0 then for y:=y1 to y2 do
      putpixel (x1,y,col);
    if dy>0 then for y:=y2 to y1 do
      putpixel (x1,y,col);
    exit;
  END;
  xslope:=xlength/ylength;
  yslope:=ylength/xlength;
  if (yslope/xslope<1) and (yslope/xslope>-1) then BEGIN
    if dx<0 then for x:=x1 to x2 do BEGIN
                   y:= round (yslope*x);
                   putpixel (x,y,col);
                 END;
    if dx>0 then for x:=x2 to x1 do BEGIN
                   y:= round (yslope*x);
                   putpixel (x,y,col);
                 END;
  END
  ELSE
  BEGIN
    if dy<0 then for y:=y1 to y2 do BEGIN
                   x:= round (xslope*y);
                   putpixel (x,y,col);
                 END;
    if dy>0 then for y:=y2 to y1 do BEGIN
                   x:= round (xslope*y);
                   putpixel (x,y,col);
                 END;
  END;
END;

Quite big, isn't it? Here is a much shorter way of doing much the same
thing :

function sgn(a:real):integer;
begin
     if a>0 then sgn:=+1;
     if a<0 then sgn:=-1;
     if a=0 then sgn:=0;
end;

procedure line(a,b,c,d,col:integer);
var u,s,v,d1x,d1y,d2x,d2y,m,n:real;
    i:integer;
begin
     u:= c - a;
     v:= d - b;
     d1x:= SGN(u);
     d1y:= SGN(v);
     d2x:= SGN(u);
     d2y:= 0;
     m:= ABS(u);
     n := ABS(v);
     IF NOT (M>N) then
     BEGIN
          d2x := 0 ;
          d2y := SGN(v);
          m := ABS(v);
          n := ABS(u);
     END;
     s := INT(m / 2);
     FOR i := 0 TO round(m) DO
     BEGIN
          putpixel(a,b,col);
          s := s + n;
          IF not (s<m) THEN
          BEGIN
               s := s - m;
               a:= a +round(d1x);
               b := b + round(d1y);
          END
          ELSE
          BEGIN
               a := a + round(d2x);
               b := b + round(d2y);
          END;
     end;
END;

This routine is very fast, and should meet almost all of your requirements
(ASPHYXIA used it for quite a while before we made our new one.)
In the end program, both the new line routine and the circle routine are
tested. A few of the procedures of the first parts are also used.

Line and circle routines may seem like fairly trivial things, but they are
a vital component of many programs, and you may like to look up other
methods of drawing them in books in the library (I know that here at the
varsity they have books for doing this kind of stuff all over the place)
A good line routine to look out for is the Bressenhams line routine ...
there is a Bressenhams circle routine too ... I have documentaiton for them
if anybody is interested, they are by far some of the fastest routines
you will use.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  In closing

Varsity has started again, so I am (shock) going to bed before three in
the morning, so my quote this week wasn't written in the same wasted way
my last weeks one was (For last week's one, I had gotten 8 hours sleep in
3 days, and thought up and wrote the quote at 2:23 am before I fell asleep.)

        [  "What does it do?" she asks.
           "It's a computer," he replies.
           "Yes, dear, but what does it do?"
           "It ..er.. computes! It's a computer."
           "What does it compute?"
           "What? Er? Um. Numbers! Yes, numbers!" He smiles
              worriedly.
           "Why?"
           "Why? Well ..um.. why?" He starts to sweat.
           "I mean, is it just something to dust around, or does
              it actually do something useful?"
           "Um...you can call other computers with it!" Hope lights
              up his eyes. "So you can get programs from other computers!"
           "I see. Tell me, what do these programs do?"
           "Do? I don't think I fol..."
           "I see. They compute. Numbers. For no particular reason." He
              withers under her gaze.
           "Yes, but..."
           She smiles, and he trails off, defeated. She takes another look
               at the thing. "Although," she says, with a strange look in
               her eyes. He looks up, an insane look of hope on his
               face. "Does it come in pink?" she asks.
                                                                           ]
                                                     - Grant Smith
                                                        Tue 27 July, 1993
                                                         9:35 pm.

See you next time,
    - Denthor


?????????????????????????????????????????????????????????????????????????????
? TUTPROG3.PAS ?
????????????????

{$X+}
USES crt;

CONST VGA = $a000;

VAR loop1:integer;
    Pall : Array [1..199,1..3] of byte;
      { This is our temporary pallette. We ony use colors 1 to 199, so we
        only have variables for those ones. }

{??????????????????????????????????????????????????????????????????????????}
Procedure SetMCGA;  { This procedure gets you into 320x200x256 mode. }
BEGIN
  asm
     mov        ax,0013h
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure SetText;  { This procedure returns you to text mode.  }
BEGIN
  asm
     mov        ax,0003h
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure Putpixel (X,Y : Integer; Col : Byte);
  { This puts a pixel on the screen by writing directly to memory. }
BEGIN
  Mem [VGA:X+(Y*320)]:=Col;
END;


{??????????????????????????????????????????????????????????????????????????}
procedure WaitRetrace; assembler;
label
  l1, l2;
asm
    mov dx,3DAh
l1:
    in al,dx
    and al,08h
    jnz l1
l2:
    in al,dx
    and al,08h
    jz  l2
end;


{??????????????????????????????????????????????????????????????????????????}
Procedure Pal(ColorNo : Byte; R,G,B : Byte);
  { This sets the Red, Green and Blue values of a certain color }
Begin
   Port[$3c8] := ColorNo;
   Port[$3c9] := R;
   Port[$3c9] := G;
   Port[$3c9] := B;
End;


{??????????????????????????????????????????????????????????????????????????}
Procedure Circle (X,Y,rad:integer;Col:Byte);
  { This draws a circle with centre X,Y, with Rad as it's radius }
VAR deg:real;
BEGIN
  deg:=0;
  repeat
    X:=round(rad*COS (deg));
    Y:=round(rad*sin (deg));
    putpixel (x+160,y+100,col);
    deg:=deg+0.005;
  until (deg>6.4);
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure Line2 (x1,y1,x2,y2:integer;col:byte);
  { This draws a line from x1,y1 to x2,y2 using the first method }
VAR x,y,xlength,ylength,dx,dy:integer;
    xslope,yslope:real;
BEGIN
  xlength:=abs (x1-x2);
  if (x1-x2)<0 then dx:=-1;
  if (x1-x2)=0 then dx:=0;
  if (x1-x2)>0 then dx:=+1;
  ylength:=abs (y1-y2);
  if (y1-y2)<0 then dy:=-1;
  if (y1-y2)=0 then dy:=0;
  if (y1-y2)>0 then dy:=+1;
  if (dy=0) then BEGIN
    if dx<0 then for x:=x1 to x2 do
      putpixel (x,y1,col);
    if dx>0 then for x:=x2 to x1 do
      putpixel (x,y1,col);
    exit;
  END;
  if (dx=0) then BEGIN
    if dy<0 then for y:=y1 to y2 do
      putpixel (x1,y,col);
    if dy>0 then for y:=y2 to y1 do
      putpixel (x1,y,col);
    exit;
  END;
  xslope:=xlength/ylength;
  yslope:=ylength/xlength;
  if (yslope/xslope<1) and (yslope/xslope>-1) then BEGIN
    if dx<0 then for x:=x1 to x2 do BEGIN
                   y:= round (yslope*x);
                   putpixel (x,y,col);
                 END;
    if dx>0 then for x:=x2 to x1 do BEGIN
                   y:= round (yslope*x);
                   putpixel (x,y,col);
                 END;
  END
  ELSE
  BEGIN
    if dy<0 then for y:=y1 to y2 do BEGIN
                   x:= round (xslope*y);
                   putpixel (x,y,col);
                 END;
    if dy>0 then for y:=y2 to y1 do BEGIN
                   x:= round (xslope*y);
                   putpixel (x,y,col);
                 END;
  END;
END;


{??????????????????????????????????????????????????????????????????????????}
procedure line(a,b,c,d,col:integer);
  { This draws a line from x1,y1 to x2,y2 using the first method }

    function sgn(a:real):integer;
    begin
         if a>0 then sgn:=+1;
         if a<0 then sgn:=-1;
         if a=0 then sgn:=0;
    end;

var u,s,v,d1x,d1y,d2x,d2y,m,n:real;
    i:integer;
begin
     u:= c - a;
     v:= d - b;
     d1x:= SGN(u);
     d1y:= SGN(v);
     d2x:= SGN(u);
     d2y:= 0;
     m:= ABS(u);
     n := ABS(v);
     IF NOT (M>N) then
     BEGIN
          d2x := 0 ;
          d2y := SGN(v);
          m := ABS(v);
          n := ABS(u);
     END;
     s := INT(m / 2);
     FOR i := 0 TO round(m) DO
     BEGIN
          putpixel(a,b,col);
          s := s + n;
          IF not (s<m) THEN
          BEGIN
               s := s - m;
               a:= a +round(d1x);
               b := b + round(d1y);
          END
          ELSE
          BEGIN
               a := a + round(d2x);
               b := b + round(d2y);
          END;
     end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure PalPlay;
  { This procedure mucks about with our "virtual pallette", then shoves it
    to screen. }
Var Tmp : Array[1..3] of Byte;
  { This is used as a "temporary color" in our pallette }
    loop1 : Integer;
BEGIN
   Move(Pall[199],Tmp,3);
     { This copies color 199 from our virtual pallette to the Tmp variable }
   Move(Pall[1],Pall[2],198*3);
     { This moves the entire virtual pallette up one color }
   Move(Tmp,Pall[1],3);
     { This copies the Tmp variable to the bottom of the virtual pallette }
   WaitRetrace;
   For loop1:=1 to 199 do
     pal (loop1,pall[loop1,1],pall[loop1,2],pall[loop1,3]);
END;


BEGIN
  ClrScr;
  Writeln ('This sample program will test out our line and circle algorithms.');
  Writeln ('In the first part, many circles will be draw creating (hopefully)');
  Writeln ('a "tunnel" effect. I will the rotate the pallete to make it look');
  Writeln ('nice. I will then draw some lines and rotate the pallette on them');
  Writeln ('too. Note : I am using the slower (first) line algorithm (in');
  Writeln ('procedure line2). Change it to Procedure Line and it will be using');
  Writeln ('the second line routine. NB : For descriptions on how pallette works');
  Writeln ('have a look at part two of this series; I won''t re-explain it here.');
  Writeln;
  Writeln ('Remember to send me any work you have done, I am most eager to help.');
  Writeln; Writeln;
  Writeln ('Hit any key to continue ...');
  Readkey;
  setmcga;

  For Loop1 := 1 to 199 do BEGIN
    Pall[Loop1,1] := Loop1 mod 30+33;
    Pall[Loop1,2] := 0;
    Pall[Loop1,3] := 0;
  END;
       { This sets colors 1 to 199 to values between 33 to 63. The MOD
         function gives you the remainder of a division, ie. 105 mod 10 = 5 }

   WaitRetrace;
   For loop1:=1 to 199 do
     pal (loop1,pall[loop1,1],pall[loop1,2],pall[loop1,3]);
        { This sets the true pallette to variable Pall }

  for loop1:=1 to 90 do
    circle (160,100,loop1,loop1);
       { This draws 90 circles all with centres at 160,100; with increasing
         radii and colors. }

  Repeat
    PalPlay;
  Until keypressed;
  Readkey;

  for loop1:=1 to 199 do
    line2 (0,1,319,loop1,loop1);   { *** Replace Line2 with Line to use the
                                         second line algorithm *** }
       { This draws 199 lines, all starting at 0,1 }

  Repeat
    PalPlay;
  Until keypressed;

  readkey;
  SetText;
  Writeln ('All done. Okay, so maybe it wasn''t a tunnel effect, but you get the');
  Writeln ('general idea ;-) This concludes the third sample program in the ASPHYXIA');
  Writeln ('Training series. You may reach DENTHOR under the name of GRANT SMITH');
  Writeln ('on the MailBox BBS, or leave a message to ASPHYXIA on the ASPHYXIA BBS.');
  Writeln ('Get the numbers from Roblist, or write to :');
  Writeln ('             Grant Smith');
  Writeln ('             P.O. Box 270');
  Writeln ('             Kloof');
  Writeln ('             3640');
  Writeln ('I hope to hear from you soon!');
  Writeln; Writeln;
  Write   ('Hit any key to exit ...');
  Readkey;
END.

                   ?????????????????????????????????
                   ?         W E L C O M E         ?
                   ?  To the VGA Trainer Program   ? ?
                   ?              By               ? ?
                   ?      DENTHOR of ASPHYXIA      ? ? ?
                   ????????????????????????????????? ? ?
                     ????????????????????????????????? ?
                       ?????????????????????????????????

                           --==[ PART 4 ]==--



? Introduction


Howdy all! Welcome to the fourth part of this trainer series! It's a
little late, but I am sure you will find that the wait was worth it,
becase today I am going to show you how to use a very powerful tool :
Virtual Screens.

If you would like to contact me, or the team, there are many ways you
can do it : 1) Write a message to Grant Smith in private mail here on
                  the Mailbox BBS.
            2) Write a message here in the Programming conference here
                  on the Mailbox (Preferred if you have a general
                  programming query or problem others would benefit from)
            3) Write to ASPHYXIA on the ASPHYXIA BBS.
            4) Write to Denthor, Eze or Livewire on Connectix.
            5) Write to :  Grant Smith
                           P.O.Box 270 Kloof
                           3640
            6) Call me (Grant Smith) at 73 2129 (leave a message if you
                  call during varsity)

NB : If you are a representative of a company or BBS, and want ASPHYXIA
       to do you a demo, leave mail to me; we can discuss it.
NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling
        quite lonely and want to meet/help out/exchange code with other demo
        groups. What do you have to lose? Leave a message here and we can work
        out how to transfer it. We really want to hear from you!


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  What is a Virtual Screen and why do we need it?

Let us say you are generating a complex screen numerous times on the fly
(for example scrolling up the screen then redrawing all the sprites for
each frame of a game you are writing.) Do you have any idea how awful it
would look if the user could actually see you erasing and redrawing each
sprite for each frame? Can you visualise the flicker effect this would
give off? Do you realise that there would be a "sprite doubling" effect
(where you see two copies of the same sprite next to each other)? In the
sample program I have included a part where I do not use virtual screens
to demonstrate these problems. Virtual screens are not the only way to
solve these problems, but they are definately the easiest to code in.

A virtual screen is this : a section of memory set aside that is exactly
like the VGA screen on which you do all your working, then "flip" it
on to your true screen. In EGA 640x350x16 you automatically have a
virtual page, and it is possible to have up to four on the MCGA using a
particular tweaked mode, but for our puposes we will set one up using base
memory.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  Setting up a virtual screen

As you will have seen in the first part of this trainer series, the MCGA
screen is 64000 bytes big (320x200=64000). You may also have noticed that
in TP 6.0 you arn't allowed too much space for normal variables. For
example, saying :

VAR Virtual : Array [1..64000] of byte;

would be a no-no, as you wouldn't have any space for your other variables.
What is the solution? I hear you enquiring minds cry. The answer : pointers!
Pointers to not use up the base 64k allocated to you by TP 6.0, it gets
space from somewhere else in the base 640k memory of your computer. Here is
how you set them up :

Type Virtual = Array [1..64000] of byte;  { The size of our Virtual Screen }
     VirtPtr = ^Virtual;                  { Pointer to the virtual screen }

VAR Virscr : VirtPtr;                      { Our first Virtual screen }
    Vaddr  : word;                        { The segment of our virtual screen}

If you put this in a program as it stands, and try to acess VirScr, your
machine will probably crash. Why? Because you have to get the memory for
your pointers before you can acess them! You do that as follows :

Procedure SetUpVirtual;
BEGIN
  GetMem (VirScr,64000);
  vaddr := seg (virscr^);
END;

This procedure has got the memory for the screen, then set vaddr to the
screens segment. DON'T EVER LEAVE THIS PROCEDURE OUT OF YOUR PROGRAM!
If you leave it out, when you write to your virtual screen you will probably
be writing over DOS or some such thing. Not a good plan ;-).

When you have finished your program, you will want to free the memory
taken up by the virtual screen by doing the following :

Procedure ShutDown;
BEGIN
  FreeMem (VirScr,64000);
END;

If you don't do this your other programs will have less memory to use for
themselves.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  Putting a pixel to your virtual screen

This is very similar to putting a pixel to your normal MCGA screen, as
discussed in part one... here is our origonal putpixel :

Procedure PutPixel (X,Y : Integer; Col : Byte);
BEGIN
  Mem [VGA:X+(Y*320)]:=col;
END;

For our virtual screen, we do the following :

Procedure VirtPutPixel (X,Y : Integer; Col : Byte);
BEGIN
  Mem [Vaddr:X+(Y*320)]:=col;
END;

It seems quite wasteful to have two procedures doing exactly the same thing,
just to different screens, doesn't it? So why don't we combine the two like
this :

Procedure PutPixel (X,Y : Integer; Col : Byte; Where : Word);
BEGIN
  Mem [Where:X+(Y*320)]:=col;
END;

To use this, you will say something like :

Putpixel (20,20,32,VGA);
PutPixel (30,30,64,Vaddr);

These two statements draw two pixels ... one to the VGA screen and one to
the virtual screen! Doesn't that make you jump  with joy! ;-) You will
have noticed that we still can't actually SEE the virtual screen, so on to
the next part ...

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  How to "Flip" your virtual screen on to the true screen

You in fact already have to tools to do this yourselves from information
in the previous parts of this trainer series. We will of course use the
Move command, like so :

Move (Virscr^,mem [VGA:0],64000);

simple, eh? Yuo may want to wait for a verticle retrace (Part 2) before you
do that, as it may make the flip much smoother (and, alas, slower).

Note that most of our other procedures may be altered to support the
virtual screen, such as Cls etc. (see Part 1 of this series), using the
methoods described above (I have altered the CLS procedure in the sample
program given at the end of this Part.)

We of ASPHYXIA have used virtual screens in almost all of our demos.
Can you imagine how awful the SoftelDemo would have looked if you had to
watch us redrawing the moving background, text and vectorballs for EACH
FRAME? The flicker, doubling effects etc would have made it awful! So
we used a virtual screen, and are very pleased with the result.
Note, though, that to get the speed we needed to get the demo fast enough,
we wrote our sprites routines, flip routines, pallette routines etc. all
in assembly. The move command is very fast, but not as fast as ASM ;-)

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  In closing

I am writing this on the varsity computers in between lectures. I prefer
writing & coding between 6pm and 4am, but it isn't a good plan when
varsity is on ;-), so this is the first part of the trainer series ever
written before 9pm.

I have been asked to do a part on scrolling the screen, so that is
probably what I will do for next week. Also, ASPHYXIA will soon be putting
up a small demo with source on the local boards. It will use routines
that we have discussed in this series, and demonstrate how powerful these
routines can be if used in the correct manner.

Some projects for you to do :
  1) Rewrite the flip statement so that you can say :
        flip (Vaddr,VGA);
        flip (VGA,Vaddr);
      ( This is how ASPHYXIAS one works )

  2) Put most of the routines (putpixel, cls, pal etc.) into a unit,
     so that you do not need to duplicate the procedures in each program
     you write. If you need help, leave me mail.


See you next week
   - Denthor


?????????????????????????????????????????????????????????????????????????????
? TUTPROG4.PAS ?
????????????????

{$X+}   (* This is a handy little trick to know. If you put this at the top
           of your program, you do not have to set a variable when calling
           a function, i.e. you may just say 'READKEY' instead of
           'CH:=READKEY'                                                *)

USES Crt;           (* This has a few nice functions in it, such as the
                       READKEY command.                                 *)

CONST VGA = $a000;  (* This sets the constant VGA to the segment of the
                       VGA screen.                                      *)

Type Virtual = Array [1..64000] of byte;  { The size of our Virtual Screen }
     VirtPtr = ^Virtual;                  { Pointer to the virtual screen }

VAR Virscr : VirtPtr;                      { Our first Virtual screen }
    Vaddr  : word;                        { The segment of our virtual screen}


{??????????????????????????????????????????????????????????????????????????}
Procedure SetMCGA;  { This procedure gets you into 320x200x256 mode. }
BEGIN
  asm
     mov        ax,0013h
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure SetText;  { This procedure returns you to text mode.  }
BEGIN
  asm
     mov        ax,0003h
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure Cls (Col : Byte; Where:Word);
   { This clears the screen to the specified color, on the VGA or on the
        virtual screen }
BEGIN
  Fillchar (Mem [where:0],64000,col);
END;

{??????????????????????????????????????????????????????????????????????????}
procedure WaitRetrace; assembler;
  { This waits until you are in a Verticle Retrace ... this means that all
    screen manipulation you do only appears on screen in the next verticle
    retrace ... this removes most of the "fuzz" that you see on the screen
    when changing the pallette. It unfortunately slows down your program
    by "synching" your program with your monitor card ... it does mean
    that the program will run at almost the same speed on different
    speeds of computers which have similar monitors. In our SilkyDemo,
    we used a WaitRetrace, and it therefore runs at the same (fairly
    fast) speed when Turbo is on or off. }

label
  l1, l2;
asm
    mov dx,3DAh
l1:
    in al,dx
    and al,08h
    jnz l1
l2:
    in al,dx
    and al,08h
    jz  l2
end;




{??????????????????????????????????????????????????????????????????????????}
Procedure SetUpVirtual;
   { This sets up the memory needed for the virtual screen }
BEGIN
  GetMem (VirScr,64000);
  vaddr := seg (virscr^);
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure ShutDown;
   { This frees the memory used by the virtual screen }
BEGIN
  FreeMem (VirScr,64000);
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure PutPixel (X,Y : Integer; Col : Byte; Where : Word);
   { This puts a pixel at X,Y using color col, on VGA or the Virtual Screen}
BEGIN
  Mem [Where:X+(Y*320)]:=col;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure Flip;
   { This flips the virtual screen to the VGA screen. }
BEGIN
  Move (Virscr^,mem [VGA:0],64000);
END;

{??????????????????????????????????????????????????????????????????????????}
Procedure BlockMove;
   { This tests various ways of moving a block around the screen }
VAR loop1,loop2,loop3:Integer;
BEGIN
  For loop1:=1 to 50 do BEGIN                     { This draw a block    }
    for loop2:=1 to 50 do                         {  directly to VGA, no }
      for loop3:=1 to 50 do                       {  flipping            }
        putpixel (loop1+loop2,loop3,32,VGA);
    cls (0,VGA);
  END;

  For loop1:=1 to 50 do BEGIN                     { This draws a block     }
    for loop2:=1 to 50 do                         { to the virtual screen, }
      for loop3:=1 to 50 do                       { then flips it to VGA   }
        putpixel (loop1+loop2,loop3,32,Vaddr);
    flip;
    cls (0,Vaddr);
  END;

  For loop1:=1 to 50 do BEGIN                     { This draws a block     }
    for loop2:=1 to 50 do                         { to the virtual screen, }
      for loop3:=1 to 50 do                       { waits for a retrace,   }
        putpixel (loop1+loop2,loop3,32,Vaddr);    { then flips it to VGA   }
    waitretrace;
    flip;
    cls (0,Vaddr);
  END;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure PatternDraw;
   { This test the speed of flipping by drawing two patterns and flipping
     them }
VAR loop1,loop2:integer;
BEGIN
  for loop1:=1 to 100 do                        { This draws pattern one }
    for loop2:=1 to 100 do                      { to the virtual screen  }
      putpixel (loop1,loop2,loop1,Vaddr);       { then flips it to VGA   }
  flip;

  for loop1:=1 to 100 do                        { This draws pattern two }
    for loop2:=1 to 100 do                      { to the virtual screen  }
      putpixel (loop1,loop2,loop2,Vaddr);       { then flips it to VGA   }
  flip;
END;


BEGIN
  ClrScr;
  Writeln ('This program will demonstrate the power of virtual screens.');
  Writeln ('A block will firstly move across the screen, being drawn and');
  Writeln ('erased totally on the VGA. Then the same block will move');
  Writeln ('across, but will be drawn on the virtual screen and flipped');
  Writeln ('to the VGA screen without a retrace (see part 2). The the');
  Writeln ('block will go again, with flipping and a retrace.');
  Writeln;
  Writeln ('I will then draw a pattern, flip it to VGA, draw another');
  Writeln ('pattern, flip it to VGA, and repeat that until a key is pressed.');
  Writeln ('This will demonstrate that even when I put down 10000 pixels,');
  Writeln ('then flip them to the VGA, it is still relatively fast.      ');
  Writeln; Writeln;
  Writeln ('Hit any key to continue ...');
  readkey;
  setmcga;
  setupvirtual;
  cls (0,vaddr);    { After you have got the memory for the virtual screen,
                      it is usually filled with random garbage. It is always
                      wise to clear the virtual screen directly afterwards }
  BlockMove;

  Repeat
    PatternDraw;
  Until keypressed;

  Readkey;
  settext;
  shutdown;
  Writeln ('All done. This concludes the fourth sample program in the ASPHYXIA');
  Writeln ('Training series. You may reach DENTHOR under the name of GRANT');
  Writeln ('SMITH on the MailBox BBS, or leave a message to ASPHYXIA on the');
  Writeln ('ASPHYXIA BBS. Get the numbers from Roblist, or write to :');
  Writeln ('             Grant Smith');
  Writeln ('             P.O. Box 270');
  Writeln ('             Kloof');
  Writeln ('             3640');
  Writeln ('I hope to hear from you soon!');
  Writeln; Writeln;
  Write   ('Hit any key to exit ...');
  Readkey;
END.

                   ?????????????????????????????????
                   ?         W E L C O M E         ?
                   ?  To the VGA Trainer Program   ? ?
                   ?              By               ? ?
                   ?      DENTHOR of ASPHYXIA      ? ? ?
                   ????????????????????????????????? ? ?
                     ????????????????????????????????? ?
                       ?????????????????????????????????

                           --==[ PART 5 ]==--



? Introduction

Hello! This is Denthor here with the 5 part of the ASPHYXIA VGA Trainer
Series : The Scrolling Saga. I have had many requests for information on
scrolling, so I decided to make it this weeks topic. Note that I do make
reference to my recently released program TEXTER5, which should be available
from wherever you get this message. (Note to Sysops : If you put the trainer
series up on your boards, please add WORMIE.ZIP and TEXTER5.ZIP as they
both suppliment this series)

By the way, sorry for the delay in the appearance of this part. Tests,
projects and a few wild days of sin at the Wild Coast all conspired
against the prompt appearance of this part. Also note I need more input as
to what I should do future parts on, so leave me mail.

If you would like to contact me, or the team, there are many ways you
can do it : 1) Write a message to Grant Smith in private mail here on
                  the Mailbox BBS.
            2) Write a message here in the Programming conference here
                  on the Mailbox (Preferred if you have a general
                  programming query or problem others would benefit from)
            3) Write to ASPHYXIA on the ASPHYXIA BBS.
            4) Write to Denthor, Eze or Livewire on Connectix.
            5) Write to :  Grant Smith
                           P.O.Box 270 Kloof
                           3640
                           Natal
            6) Call me (Grant Smith) at 73 2129 (leave a message if you
                  call during varsity)

NB : If you are a representative of a company or BBS, and want ASPHYXIA
       to do you a demo, leave mail to me; we can discuss it.
NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling
        quite lonely and want to meet/help out/exchange code with other demo
        groups. What do you have to lose? Leave a message here and we can work
        out how to transfer it. We really want to hear from you!


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  What is scrolling?

If you have ever seen a demo, you have probably seen some form of scrolling.
Our SILKYDEMO has quite a nice example of scrolling. What it is is a long
row of text moving across your screen, usually from right to left, eg :

                                       H     : Step 1
                                      He     : Step 2
                                     Hel     : Step 3
                                    Hell     : Step 4
                                   Hello     : Step 5
                                  Hello      : Step 6

etc. etc. See the program attatched for an example of scrolling.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  What do we scroll?

Usually, letters. Most groups put greetings and information in their
'scrollies', as they are termed. You can also scroll and entire screen
using the scrolling technique. Scrolling your text is a hell of a lot
less boring then just having it appear on your screen. Unfortunately,
'scrollies' have been used so many times in demos they are wearing a
bit thin, so usually they are accompanied by a cool picture or some nice
routine happening at the same time (In our SILKYDEMO we had a moving
checkerboard and colour bars going at the same time).

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  How do we scroll from side to side?

The theory behind scrolling is quite easy. Let us imagine that we are
scrolling a 16x16 font grabbed by TEXTER (;-)) across the top of the
screen (ie. 320 pixels) As we know, the VGA screen starts at zero at the
top left hand part of the screen, then counts up to the right to 319, then
goes back to the left hand side one pixel down at 320. (See Tut 1) This means
that a 16*320 scroller takes up the space 0 to 5119 on the screen. In ascii
this looks like this :

            (0)   .                                    .  (319)
            (320) .                                    .  (639)
                            "             "           "
           (4800) .                                    .   (5119)

Simple enough. Now what we do is we put down the first Y-line of the first
character onto the very right hand side of the screen , like so :

              For loop1:=1 to 16 do
                Putpixel (319,loop1-1,font['A',1,loop1],vga);

This will draw some stuff on the very right hand side. Your screen should now
look like this :

            (0)   .                                   X.  (319)
            (320) .                                   X.  (639)
                            "             "           "
           (4800) .                                   X.   (5119)

Next, we move each line one to the left, ie :

              For loop1:=0 to 15 do
                Move (mem[VGA:loop1*320+1],mem[VGA:loop1*320],320);

This scrolls the screen from right to left, which is the easiest to read.
To scroll the screen from left to right, swap the +1 onto the other side
of the command. Also, to increase the size of the portion scrolled, increase
the 15 to however many lines from the top you wish to scroll-1.

After this move, your screen will look like this :

            (0)   .                                  X .  (319)
            (320) .                                  X .  (639)
                            "             "           "
           (4800) .                                  X .   (5119)
                                                      ^
                                                Note this space


What you then do is draw in the next line on the right hand side, move it,
draw the next line, move it etc. etc. Tah-Dah! You have a scrolly! Fairly
simple, isn't it?

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  How do we scroll up or down?

To scroll up or down is also fairly simple. This can be used for 'movie
credit' endings (I once wrote a little game with a desert scrolling down
with you being a little robot on the bottom of the screen). The theory is
this : Draw the top line (or bottom line) then move the entire screen :

             Move (mem[vga:0],mem[vga:320],63680);
                       { 64000 - 320 = 63680 }

For scrolling down, or :

             Move (mem[vga:320],mem[vga:0],63680);

For scrolling up. You then draw the next line and repeat.

Because of the simplicity of coding in a scrolly, most demos have one. It
is usually best to have something extra happening on the screen so that
the viewer doesn't get too bored, even, as I say, if it is only a really nice
picture.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  In closing

The University of Natal, Durban, Science Dept., now has 10 new 486's!
This is a great boon, as now I can program nice routines during frees
(even though I am a Commerce Student (Shhhhh) ;-) ). I can now use those
previously wasted hours that I spent socialising and making friends
coding instead ;-)

I suggest you get a copy of TEXTER, for coding demos with fonts, or in fact
almost any graphics application, it is an amazing help, and we have used it
for *ALL* our demos. (P.S. We have written many demos, but many have been
written for companies and have not been released for the general public)
NOTE : For TEXTER's test program TEST.PAS, add {$X+} {$R-} if you have range
checking on (I code with it off.)

            [  "I'm from the Computer Inspection Agency, sir,
                   I'm here to check your computer. Here is
                   my identification."
               "Certainly. Have a look, I'm clean. I don't have
                   any pirated software."
               The C-man pushes past him and sits in front of the
                   computer. He notes the fact that the computer
                   is currently off with a look of disdain. He
                   makes a note on his clipboard. He boots up.
               "What is this?" he asks, pointing at the screen.
               "It's MasterMenu" stutters the man. "I wrote it
                   myself!"
               "Do you know what the penalty is for using junk
                   like this on a private machine?" The C-man smiles.
                   "This is a two-month sentance in itself!"
               "I'm sorry sir! It won't happen again!"
               "I know. I'll make sure of that." He smiles again.
               The C-man runs through the hard drive, checking for
                   illeagal software, bad programs and anti-government
                   propaganda. He notes with satisfaction that he has
                   enough to put this weenie away for ten years, not that
                   it mattered. He usually could just make something up.
               He comes to the last entry on the aphebetised menu tree.
                   His hands jerk away from the keyboard. Then, tentatively,
                   he types in the three letters of doom. He looks at the
                   man, who is backing away with wide eyes and his hands
                   outstretched in front of him, as if to ward off a blow.
               The C-man smiles, his lips a thin, hard line.
               "Windows!"
                                                                     ]
                                                           - Grant Smith
                                                               1:55pm
                                                                 16/9/93

Cheers,
  - Denthor

?????????????????????????????????????????????????????????????????????????????
? TUTPROG5.PAS ?
????????????????

{$X+} {$R-}
Uses Crt;

CONST VGA = $a000;
      XSize = 16;
      YSize = 16;

TYPE
        Letter = Array[1..xsize,1..ysize] of Byte;
        Letters = Array[' '..']'] of Letter;

VAR Font : ^Letters;

{??????????????????????????????????????????????????????????????????????????}
Procedure SetMCGA;  { This procedure gets you into 320x200x256 mode. }
BEGIN
  asm
     mov        ax,0013h
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure SetText;  { This procedure returns you to text mode.  }
BEGIN
  asm
     mov        ax,0003h
     int        10h
  end;
END;

{??????????????????????????????????????????????????????????????????????????}
procedure WaitRetrace; assembler;
  { This waits until you are in a Verticle Retrace }

label
  l1, l2;
asm
    mov dx,3DAh
l1:
    in al,dx
    and al,08h
    jnz l1
l2:
    in al,dx
    and al,08h
    jz  l2
end;

{??????????????????????????????????????????????????????????????????????????}
Procedure Pal(ColorNo : Byte; R,G,B : Byte);
  { This sets the Red, Green and Blue values of a certain color }
Begin
   Port[$3c8] := ColorNo;
   Port[$3c9] := R;
   Port[$3c9] := G;
   Port[$3c9] := B;
End;


{??????????????????????????????????????????????????????????????????????????}
Procedure PutPixel (X,Y : Integer; Col : Byte; Where : Word);
   { This puts a pixel at X,Y using color col, on VGA or the Virtual Screen}
BEGIN
  Mem [Where:X+(Y*320)]:=col;
END;

{??????????????????????????????????????????????????????????????????????????}
procedure LoadPal (FileName : string);
   { This loads the Pallette file and puts it on screen }
type DACType = array [0..255] of record
                                R, G, B : byte;
                              end;
var DAC : DACType;
    Fil : file of DACType;
    I : integer;
BEGIN
  assign (Fil, FileName);
  reset (Fil);
  read (Fil, DAC);
  close (Fil);
  for I := 0 to 255 do Pal(I,Dac[I].R,Dac[I].G,Dac[I].B);
end;

{??????????????????????????????????????????????????????????????????????????}
function Exist(FileName: string): Boolean;
    { Checks to see if filename exits or not }
var f: file;
begin
  {$I-}
  Assign(f, FileName);
  Reset(f);
  Close(f);
  {$I+}
  Exist := (IOResult = 0) and
   (FileName <> '');
end;


{??????????????????????????????????????????????????????????????????????????}
Procedure Setup;
  { This loads the font and the pallette }
VAR f:file;
    loop1:char;
    loop2,loop3:integer;
BEGIN
  getmem (font,sizeof (font^));
  If exist ('softrock.fnt') then BEGIN
    Assign (f,'softrock.fnt');
    reset (f,1);
    blockread (f,font^,sizeof (font^));
    close (f);
    Writeln ('SoftRock.FNT from TEXTER5 found in current directory. Using.');
  END
  ELSE BEGIN
    Writeln ('SoftRock.FNT from TEXTER5 not found in current directory.');
    For loop1:=' ' to ']' do
      For loop2:=1 to 16 do
        for loop3:=1 to 16 do
          font^[loop1,loop2,loop3]:=loop2;
  END;
  If exist ('pallette.col') then
    Writeln ('Pallette.COL from TEXTER5 found in current directory. Using.')
  ELSE
    Writeln ('Pallette.COL from TEXTER5 not found in current directory.');
  Writeln;
  Writeln;
  Write ('Hit any key to continue ...');
  readkey;
  setmcga;
  If exist ('pallette.col') then loadpal ('pallette.col');
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure ScrollMsg (Msg : String);
  { This scrolls the string in MSG across the screen }
Var Loop1,loop2,loop3 : Integer;
Begin
  For loop1:=1 to length (msg) do BEGIN
    For loop2:=1 to xsize do BEGIN

      { This bit scrolls the screen by one then puts in the new row of
        letters }

      waitretrace;
      For Loop3 := 100 to 99+ysize do
        move (mem[vga:1+(loop3*320)],mem[vga:(loop3*320)],319);
      for loop3:=100 to 99+ysize do
        putpixel (319,loop3,font^[msg[loop1],loop2,loop3-99],vga);
           { Change the -99 above to the minimum of loop3-1, which you
             will change in order to move the position of the scrolly }
    END;

    {This next bit scrolls by one pixel after each letter so that there
      are gaps between the letters }

    waitretrace;
    For Loop3 := 100 to 99+ysize do
      move (mem[vga:1+(loop3*320)],mem[vga:(loop3*320)],319);
      for loop3:=100 to 99+ysize do
        putpixel (319,loop3,0,vga);
  END;
End;


BEGIN
  ClrScr;
  Writeln ('This program will give you an example of a scrolly. If the file');
  Writeln ('SOFTROCK.FNT is in the current directory, this program will scroll');
  Writeln ('letters, otherwise it will only scroll bars. It also searches for');
  Writeln ('PALLETTE.COL, which it uses for it''s pallette. Both SOFTROCK.FNT');
  Writeln ('and PALLETTE.COL come with TEXTER5.ZIP, at a BBS near you.');
  Writeln;
  Writeln ('You will note that you can change what the scrolly says merely by');
  Writeln ('changing the string in the program.');
  Writeln;
  Setup;
  repeat
    ScrollMsg ('ASPHYXIA RULZ!!!   ');
  until keypressed;
  Settext;
  freemem (font, sizeof (font^));
  Writeln ('All done. This concludes the fifth sample program in the ASPHYXIA');
  Writeln ('Training series. You may reach DENTHOR under the name of GRANT');
  Writeln ('SMITH on the MailBox BBS, or leave a message to ASPHYXIA on the');
  Writeln ('ASPHYXIA BBS. Get the numbers from Roblist, or write to :');
  Writeln ('             Grant Smith');
  Writeln ('             P.O. Box 270');
  Writeln ('             Kloof');
  Writeln ('             3640');
  Writeln ('I hope to hear from you soon!');
  Writeln; Writeln;
  Write   ('Hit any key to exit ...');
  Readkey;
END.

                   ?????????????????????????????????
                   ?         W E L C O M E         ?
                   ?  To the VGA Trainer Program   ? ?
                   ?              By               ? ?
                   ?      DENTHOR of ASPHYXIA      ? ? ?
                   ????????????????????????????????? ? ?
                     ????????????????????????????????? ?
                       ?????????????????????????????????

                           --==[ PART 6 ]==--



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Introduction

Hi there! I'm back, with the latest part in the series : Pregenerated
arrays. This is a fairly simple concept that can treble the speed of
your code, so have a look.

I still suggest that if you haven't got a copy of TEXTER that you get it.
This is shareware, written by me, that allows you to grab fonts and use
them in your own programs.

I downloaded the Friendly City BBS Demo, an intro for a PE BBS, written
by a new group called DamnRite, with coder Brett Step. The music was
excellent, written by Kon Wilms (If I'm not mistaken, he is an Amiga
weenie ;-)). A very nice first production, and I can't wait to see more
of their work. I will try con a local BBS to allow me to send Brett some
fido-mail.

If you would like to contact me, or the team, there are many ways you
can do it : 1) Write a message to Grant Smith in private mail here on
                  the Mailbox BBS.
            2) Write a message here in the Programming conference here
                  on the Mailbox (Preferred if you have a general
                  programming query or problem others would benefit from)
            3) Write to ASPHYXIA on the ASPHYXIA BBS.
            4) Write to Denthor, Eze or Livewire on Connectix.
            5) Write to :  Grant Smith
                           P.O.Box 270 Kloof
                           3640
                           Natal
            6) Call me (Grant Smith) at (031) 73 2129 (leave a message if you
                  call during varsity)

NB : If you are a representative of a company or BBS, and want ASPHYXIA
       to do you a demo, leave mail to me; we can discuss it.
NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling
        quite lonely and want to meet/help out/exchange code with other demo
        groups. What do you have to lose? Leave a message here and we can work
        out how to transfer it. We really want to hear from you!


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  Why do I need a lookup table? What is it?

A lookup table is an imaginary table in memory where you look up the
answers to certain mathematical equations instead of recalculating them
each time. This may speed things up considerably. Please note that a
lookup table is sometimes referred to as a pregenerated array.

One way of looking at a lookup table is as follows : Let us say that for
some obscure reason you need to calculate a lot of multiplications (eg.
5*5 , 7*4 , 9*2 etc.). Instead of actually doing a slow multiply each
time, you can generate a kind of bonds table, as seen below :


?????????????????????????????????????????????????????????????????????
?????   1   ?  2   ?  3   ?  4   ?  5   ?  6   ?  7   ?  8   ?  9   ?
?????????????????????????????????????????????????????????????????????
? 1 ?   1   ?  2   ?  3   ?  4   ?  5   ?  6   ?  7   ?  8   ?  9   ?
?????????????????????????????????????????????????????????????????????
? 2 ?   2   ?  4   ?  6   ?  8   ?  10  ?  12  ?  14  ?  16  ?  18  ?
?????????????????????????????????????????????????????????????????????
? 3 ?   3   ?  6   ?  9   ?  12  ?  15  ?  18  ?  21  ?  24  ?  27  ?
?????????????????????????????????????????????????????????????????????
? 4 ?   4   ?  8   ?  12  ?  16  ?  20  ?  24  ?  28  ?  32  ?  36  ?
?????????????????????????????????????????????????????????????????????
? 5 ?   5   ?  10  ?  15  ?  20  ?  25  ?  30  ?  35  ?  40  ?  45  ?
?????????????????????????????????????????????????????????????????????
? 6 ?   6   ?  12  ?  18  ?  24  ?  30  ?  36  ?  42  ?  48  ?  54  ?
?????????????????????????????????????????????????????????????????????
? 7 ?   7   ?  14  ?  21  ?  28  ?  35  ?  42  ?  49  ?  56  ?  63  ?
?????????????????????????????????????????????????????????????????????
? 8 ?   8   ?  16  ?  24  ?  32  ?  40  ?  48  ?  56  ?  64  ?  72  ?
?????????????????????????????????????????????????????????????????????
? 9 ?   9   ?  18  ?  27  ?  36  ?  45  ?  54  ?  63  ?  72  ?  81  ?
?????????????????????????????????????????????????????????????????????

This means that instead of calculating 9*4, you just find the 9 on the
top and the 4 on the side, and the resulting number is the answer.  This
type of table is very useful when the equations are very long to do.

The example I am going to use for this part is that of circles. Cast
your minds back to Part 3 on lines and circles. The circle section took
quite a while to finish drawing, mainly because I had to calculate the
SIN and COS for EVERY SINGLE POINT. Calculating SIN and COS is obviously
very slow, and that was reflected in the speed of the section.



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  How do I generate a lookup table?

This is very simple. In my example, I am drawing a circle. A circle has
360 degrees, but for greater accuracy, to draw my circle I will start
with zero and increase my degrees by 0.4. This means that in each circle
there need to be 8000 SINs and COSes (360/0.4=8000). Putting these into
the base 64k that Pascal allocates for normal variables is obviously not
a happening thing, so we define them as pointers in the following
manner: 
        TYPE   table = Array [1..8000] of real;

        VAR    sintbl : ^table;
               costbl : ^table;

Then in the program we get the memory for these two pointers. Asphyxia 
was originally thinking of calling itself Creative Reboot Inc., mainly 
because we always forgot to get the necessary memory for our pointers.
(Though a bit of creative assembly coding also contributed to this. We
wound up rating our reboots on a scale of 1 to 10 ;-)). The next obvious
step is to place our necessary answers into our lookup tables.  This can
take a bit of time, so in a demo, you would do it in the very beginning
(people just think it's slow disk access or something), or after you
have shown a picture (while the viewer is admiring it, you are
calculating pi to its 37th degree in the background ;-)) Another way of
doing it is, after calculating it once, you save it to a file which you
then load into the variable at the beginning of the program. Anyway,
this is how we will calculate the table for our circle :

    Procedure Setup;
    VAR deg:real;
    BEGIN
      deg:=0;
      for loop1:=1 to 8000 do BEGIN
        deg:=deg+0.4;
        costbl^[loop1]:=cos (rad(deg));
        sintbl^[loop1]:=sin (rad(deg));
      END;
    END;

This will calculate the needed 16000 reals and place them into our two
variables. The amount of time this takes is dependant on your computer.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  How do I use a lookup table?

This is very easy. In your program, wherever you put
               cos (rad(deg)),
you just replace it with :
               costbl^[deg]

Easy, no? Note that the new "deg" variable is now an integer, always
between 1 and 8000.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Where else do I use lookup tables?

Lookup tables may be used in many different ways. For example, when
working out 3-dimensional objects, sin and cos are needed often, and are
best put in a lookup table. In a game, you may pregen the course an
enemy may take when attacking. Even saving a picture (for example, a
plasma screen) after generating it, then loading it up later is a form
of pregeneration.

When you feel that your program is going much too slow, your problems
may be totally sorted out by using a table. Or, maybe not. ;-)


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  In closing

As you have seen above, lookup tables aren't all that exciting, but they
are useful and you need to know how to use them. The attached sample
program will demonstrate just how big a difference they can make.

Keep on coding, and if you finish anything, let me know about it! I
never get any mail, so all mail is greatly appreciated ;-)

Sorry, no quote today, it's hot and I'm tired. Maybe next time ;-)

  - Denthor


?????????????????????????????????????????????????????????????????????????????
? TUTPROG6.PAS ?
????????????????

{$X+}
USES crt;

CONST VGA = $a000;

TYPE tbl = Array [1..8000] of real;
             { This will be the shape of the 'table' where we look up
               values, which is faster then calculating them }

VAR loop1:integer;
    Pall : Array [1..20,1..3] of byte;
      { This is our temporary pallette. We ony use colors 1 to 20, so we
        only have variables for those ones. }

{??????????????????????????????????????????????????????????????????????????}
Procedure SetMCGA;  { This procedure gets you into 320x200x256 mode. }
BEGIN
  asm
     mov        ax,0013h
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure SetText;  { This procedure returns you to text mode.  }
BEGIN
  asm
     mov        ax,0003h
     int        10h
  end;
END;

{??????????????????????????????????????????????????????????????????????????}
Procedure Cls (Col : Byte);
   { This clears the screen to the specified color }
BEGIN
  Fillchar (Mem [VGA:0],64000,col);
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure Putpixel (X,Y : Integer; Col : Byte);
  { This puts a pixel on the screen by writing directly to memory. }
BEGIN
  Mem [VGA:X+(Y*320)]:=Col;
END;


{??????????????????????????????????????????????????????????????????????????}
procedure WaitRetrace; assembler;
  {  This waits for a vertical retrace to reduce snow on the screen }
label
  l1, l2;
asm
    mov dx,3DAh
l1:
    in al,dx
    and al,08h
    jnz l1
l2:
    in al,dx
    and al,08h
    jz  l2
end;


{??????????????????????????????????????????????????????????????????????????}
Procedure Pal(ColorNo : Byte; R,G,B : Byte);
  { This sets the Red, Green and Blue values of a certain color }
Begin
   Port[$3c8] := ColorNo;
   Port[$3c9] := R;
   Port[$3c9] := G;
   Port[$3c9] := B;
End;


{??????????????????????????????????????????????????????????????????????????}
Function rad (theta : real) : real;
  {  This calculates the degrees of an angle }
BEGIN
  rad := theta * pi / 180
END;



{??????????????????????????????????????????????????????????????????????????}
Procedure NormCirc;
  { This generates a spireal without using a lookup table }
VAR deg,radius:real;
    x,y:integer;

BEGIN
  gotoxy (1,1);
  Writeln ('Without pregenerated arrays.');
  for loop1:=60 downto 43 do BEGIN
    deg:=0;
    radius:=loop1;
    repeat
      X:=round(radius*COS (rad (deg)));
      Y:=round(radius*sin (rad (deg)));
      putpixel (x+160,y+100,61-loop1);
      deg:=deg+0.4;           { Increase the degree so the circle is round }
      radius:=radius-0.02;    { Decrease the radius for a spiral effect }
    until radius<0; {  Continue till at the centre (the radius is zero) }
  END;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure LookupCirc;
  {  This draws a spiral using a lookup table }
VAR radius:real;
    x,y,pos:integer;
    costbl : ^tbl;
    sintbl : ^tbl;

    Procedure Setupvars;
      {  This is a nested procedure (a procedure in a procedure), and may
         therefore only be used from within the main part of this procedure.
         This section gets the memory for the table, then generates the
         table. }
    VAR deg:real;
    BEGIN
      getmem (costbl,sizeof(costbl^));
      getmem (sintbl,sizeof(sintbl^));
      deg:=0;
      for loop1:=1 to 8000 do BEGIN         { There are 360 degrees in a    }
        deg:=deg+0.4;                       { circle. If you increase the   }
        costbl^[loop1]:=cos (rad(deg));     { degrees by 0.4, the number of }
        sintbl^[loop1]:=sin (rad(deg));     { needed parts of the table is  }
      END;                                  { 360/0.4=8000                  }
    END;
    { NB : For greater accuracy I increase the degrees by 0.4, because if I
           increase them by one, holes are left in the final product as a
           result of the rounding error margin. This means the pregen array
           is bigger, takes up more memory and is slower to calculate, but
           the finished product looks better.}

BEGIN
  cls (0);
  gotoxy (1,1);
  Writeln ('Generating variables....');
  setupvars;
  gotoxy (1,1);
  Writeln ('With pregenerated arrays.');
  for loop1:=60 downto 43 do BEGIN
    pos:=1;
    radius:=loop1;
    repeat
      X:=round (radius*costbl^[pos]);   { Note how I am not recalculating sin}
      Y:=round (radius*sintbl^[pos]);   { and cos for each point.            }
      putpixel (x+160,y+100,61-loop1);
      radius:=radius-0.02;
      inc (pos);
      if pos>8000 then pos:=1;    { I only made a table from 1 to 8000, so it}
                                  { must never exceed that, or the program   }
                                  { will probably crash.                     }
    until radius<0;
  END;
  freemem (costbl,sizeof(costbl^));   { Freeing the memory taken up by the   }
  freemem (sintbl,sizeof(sintbl^));   { tables. This is very important.      }
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure PalPlay;
  { This procedure mucks about with our "virtual pallette", then shoves it
    to screen. }
Var Tmp : Array[1..3] of Byte;
  { This is used as a "temporary color" in our pallette }
    loop1 : Integer;
BEGIN
   Move(Pall[1],Tmp,3);
     { This copies color 1 from our virtual pallette to the Tmp variable }
   Move(Pall[2],Pall[1],18*3);
     { This moves the entire virtual pallette down one color }
   Move(Tmp,Pall[18],3);
     { This copies the Tmp variable to no. 18 of the virtual pallette }
   WaitRetrace;
   For loop1:=1 to 18 do
     pal (loop1,pall[loop1,1],pall[loop1,2],pall[loop1,3]);
END;


BEGIN
  ClrScr;
  writeln ('Hi there! This program will demonstrate the usefullness of ');
  writeln ('pregenerated arrays, also known as lookup tables. The program');
  writeln ('will first draw a spiral without using a lookup table, rotate');
  writeln ('the pallette until a key is pressed, the calculate the lookup');
  writeln ('table, then draw the same spiral using the lookup table.');
  writeln;
  writeln ('This is merely one example for the wide range of uses of a ');
  writeln ('lookup table.');
  writeln;
  writeln;
  Write ('  Hit any key to contine ...');
  Readkey;
  setmcga;
  directvideo:=FALSE;  { This handy trick allows you to use GOTOXY and }
                       { Writeln in GFX mode. Hit CTRL-F1 on it for more }
                       { info/help }
  For Loop1 := 1 to 18 do BEGIN
    Pall[Loop1,1] := (Loop1*3)+9;
    Pall[Loop1,2] := 0;
    Pall[Loop1,3] := 0;
  END;
       { This sets colors 1 to 18 to values between 12 to 63. }

   WaitRetrace;
   For loop1:=1 to 18 do
     pal (loop1,pall[loop1,1],pall[loop1,2],pall[loop1,3]);
        { This sets the true pallette to variable Pall }

  normcirc;         { This draws a spiral without lookups }
  Repeat
    PalPlay;
  Until keypressed;
  readkey;
  lookupcirc;       { This draws a spiral with lookups }
  Repeat
    PalPlay;
  Until keypressed;
  Readkey;

  SetText;
  Writeln ('All done. This concludes the sixth sample program in the ASPHYXIA');
  Writeln ('Training series. You may reach DENTHOR under the name of GRANT');
  Writeln ('SMITH on the MailBox BBS, or leave a message to ASPHYXIA on the');
  Writeln ('ASPHYXIA BBS. I am also an avid Connectix BBS user.');
  Writeln ('Get the numbers from Roblist, or write to :');
  Writeln ('             Grant Smith');
  Writeln ('             P.O. Box 270');
  Writeln ('             Kloof');
  Writeln ('             3640');
  Writeln ('I hope to hear from you soon!');
  Writeln; Writeln;
  Write   ('Hit any key to exit ...');
  Readkey;
END.
                   ?????????????????????????????????
                   ?         W E L C O M E         ?
                   ?  To the VGA Trainer Program   ? ?
                   ?              By               ? ?
                   ?      DENTHOR of ASPHYXIA      ? ? ?
                   ????????????????????????????????? ? ?
                     ????????????????????????????????? ?
                       ?????????????????????????????????

                           --==[ PART 7 ]==--



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Introduction

Hello! By popular request, this part is all about animation. I will be
going over three methods of doing animation on a PC, and will
concerntrate specifically on one, which will be demonstrated in the
attached sample code.

Although not often used in demo coding, animation is usually used in
games coding, which can be almost as rewarding ;-)

In this part I will also be a lot less stingy with assembler code :)
Included will be a fairly fast pure assembler putpixel, an asm screen
flip command, an asm icon placer, an asm partial-flip and one or two
others. I will be explaining how these work in detail, so this may also
be used as a bit of an asm-trainer too.

By the way, I apologise for this part taking so long to be released, but
I only finished my exams a few days ago, and they of course took
preference ;-). I have also noticed that the MailBox BBS is no longer
operational, so the trainer will be uploaded regularly to the BBS lists
shown at the end of this tutorial.

If you would like to contact me, or the team, there are many ways you
can do it : 1) Write a message to Grant Smith/Denthor/Asphyxia in private mail
                  on the ASPHYXIA BBS.
            2) Write a message in the Programming conference on the
                  For Your Eyes Only BBS (of which I am the Moderator )
                  This is preferred if you have a general programming query
                  or problem others would benefit from.
            4) Write to Denthor, Eze or Livewire on Connectix.
            5) Write to :  Grant Smith
                           P.O.Box 270 Kloof
                           3640
                           Natal
            6) Call me (Grant Smith) at (031) 73 2129 (leave a message if you
                  call during varsity)
            7) Write to mcphail@beastie.cs.und.ac.za on InterNet, and
                  mention the word Denthor near the top of the letter.

NB : If you are a representative of a company or BBS, and want ASPHYXIA
       to do you a demo, leave mail to me; we can discuss it.
NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling
        quite lonely and want to meet/help out/exchange code with other demo
        groups. What do you have to lose? Leave a message here and we can work
        out how to transfer it. We really want to hear from you!



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  The Principals of Animation

I am sure all of you have seen a computer game with animation at one or
other time. There are a few things that an animation sequence must do in
order to give an impression of realism. Firstly, it must move,
preferably using different frames to add to the realism (for example,
with a man walking you should have different frames with the arms an
legs in different positions). Secondly, it must not destroy the
background, but restore it after it has passed over it.

This sounds obvious enough, but can be very difficult to code when you
have no idea of how to go about achieving that.

In this trainer I will discuss various methods of meeting these two
objectives.



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  Frames and Object Control

It is quite obvious that for most animation to succeed, you must have
numerous frames of the object in various poses (such as a man with
several frames of him walking). When shown one after the other, these
give the impression of natural movement.

So, how do we store these frames? I hear you cry. Well, the obvious
method is to store them in arrays. After drawing a frame in Autodesk
Animator and saving it as a .CEL, we usually use the following code to
load it in :

TYPE icon = Array [1..50,1..50] of byte;

VAR tree : icon;

Procedure LoadCEL (FileName :  string; ScrPtr : pointer);
var
  Fil : file;
  Buf : array [1..1024] of byte;
  BlocksRead, Count : word;
begin
  assign (Fil, FileName);
  reset (Fil, 1);
  BlockRead (Fil, Buf, 800);    { Read and ignore the 800 byte header }
  Count := 0; BlocksRead := $FFFF;
  while (not eof (Fil)) and (BlocksRead <> 0) do begin
    BlockRead (Fil, mem [seg (ScrPtr^): ofs (ScrPtr^) + Count], 1024, BlocksRead);
    Count := Count + 1024;
  end;
  close (Fil);
end;

BEGIN
  Loadcel ('Tree.CEL',addr (tree));
END.

We now have the 50x50 picture of TREE.CEL in our array tree. We may access
this array in the usual manner (eg. col:=tree [25,30]). If the frame is
large, or if you have many frames, try using pointers (see previous
parts)

Now that we have the picture, how do we control the object? What if we
want multiple trees wandering around doing their own thing? The solution
is to have a record of information for each tree. A typical data
structure may look like the following :

TYPE Treeinfo = Record
                  x,y:word;       { Where the tree is }
                  speed:byte;     { How fast the tree is moving }
                  Direction:byte; { Where the tree is facing }
                  frame:byte      { Which animation frame the tree is
                                    currently involved in }
                  active:boolean; { Is the tree actually supposed to be
                                    shown/used? }
                END;

VAR Forest : Array [1..20] of Treeinfo;

You now have 20 trees, each with their own information, location etc.
These are accessed using the following means :
                  Forest [15].x:=100;
This would set the 15th tree's x coordinate to 100.



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  Restoring the Overwritten Background

I will discuss three methods of doing this. These are NOT NECESSARILY
THE ONLY OR BEST WAYS TO DO THIS! You must experiment and decide which
is the best for your particular type of program.

METHOD 1 :

Step 1 : Create two virtual pages, Vaddr and Vaddr2.
Step 2 : Draw the background to Vaddr2.
Step 3 : Flip Vaddr2 to Vaddr.
Step 4 : Draw all the foreground objects onto Vaddr.
Step 5 : Flip Vaddr to VGA.
Step 6 : Repeat from 3 continuously.

In ascii, it looks like follows ...

    +---------+           +---------+           +---------+
    |         |           |         |           |         |
    |  VGA    | <=======  |  VADDR  |  <======  |  VADDR2 |
    |         |           | (bckgnd)|           | (bckgnd)|
    |         |           |+(icons) |           |         |
    +---------+           +---------+           +---------+

The advantages of this approach is that it is straightforward, continual
reading of the background is not needed, there is no flicker and it is
simple to implement.  The disadvantages are that two 64000 byte virtual
screens are needed, and the procedure is not very fast because of the
slow speed of flipping.


METHOD 2 :

Step 1 : Draw background to VGA.
Step 2 : Grab portion of background that icon will be placed on.
Step 3 : Place icon.
Step 4 : Replace portion of background from Step 2 over icon.
Step 5 : Repeat from step 2 continuously.

In terms of ascii ...

      +---------+
      |      +--|------- + Background restored (3)
      |      * -|------> * Background saved to memory (1)
      |      ^  |
      |      +--|------- # Icon placed (2)
      +---------+

The advantages of this method is that very little extra memory is
needed. The disadvantages are that writing to VGA is slower then writing
to memory, and there may be large amounts of flicker.


METHOD 3 :

Step 1 : Set up one virtual screen, VADDR.
Step 2 : Draw background to VADDR.
Step 3 : Flip VADDR to VGA.
Step 4 : Draw icon to VGA.
Step 5 : Transfer background portion from VADDR to VGA.
Step 6 : Repeat from step 4 continuously.

In ascii ...

     +---------+           +---------+
     |         |           |         |
     |   VGA   |           |  VADDR  |
     |         |           | (bckgnd)|
     | Icon>* <|-----------|--+      |
     +---------+           +---------+

The advantages are that writing from the virtual screen is quicker then
from VGA, and there is less flicker then in Method 2. Disadvantages are
that you are using a 64000 byte virtual screen, and flickering occurs
with large numbers of objects.

In the attached sample program, a mixture of Method 3 and Method 1 is
used. It is faster then Method 1, and has no flicker, unlike Method 3.
What I do is I use VADDR2 for background, but only restore the
background that has been changed to VADDR, before flipping to VGA.

In the sample program, you will see that I restore the entire background
of each of the icons, and then place all the icons. This is because if I
replace the background then place the icon on each object individually,
if two objects are overlapping, one is partially overwritten.

The following sections are explanations of how the various assembler
routines work. This will probably be fairly boring for you if you
already know assembler, but should help beginners and dabblers alike.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  The ASM Putpixel

To begin with, I will explain a few of the ASM variables and functions :

<NOTE THAT THIS IS AN EXTREMELY SIMPLISTIC VIEW OF ASSEMBLY LANGUAGE!
There are numerous books to advance your knowledge, and the Norton
Guides assembler guide  may be invaluable for people beginning to code
in assembler. I haven't given you the pretty pictures you are supposed
to have to help you understand it easier, I have merely laid it out like
a programming language with it's own special procedures. >

There are 4 register variables : AX,BX,CX,DX. These are words (double
bytes) with a range from 0 to 65535. You may access the high and low
bytes of these by replacing the X with a "H" for high or "L" for low.
For example, AL has a range from 0-255.

You also have two pointers : ES:DI and DS:SI. The part on the left is
the segment to which you are pointing (eg $a000), and the right hand
part is the offset, which is how far into the segment you are pointing.
Turbo Pascal places a variable over 16k into the base of a segment, ie.
DI or SI will be zero at the start of the variable.

If you wish to be pointing to pixel number 3000 on the VGA screen (see
previous parts for the layout of the VGA screen), ES would be equal to
$a000 and DI would be equal to 3000.  You can quite as easily make ES or
DS be equal to the offset of a virtual screen.

Here are a few functions that you will need to know :

      mov   destination,source       This moves the value in source to
                                     destination. eg  mov ax,50
      add   destination,source       This adds source to destination,
                                     the result being stored in destination
      mul   source                   This multiplies AX by source. If
                                     source is a byte, the source is
                                     multiplied by AL, the result being
                                     stored in AX. If source is a word,
                                     the source is multiplied by AX, the
                                     result being stored in DX:AX
      movsb                          This moves the byte that DS:SI is
                                     pointing to into ES:DI, and
                                     increments SI and DI.
      movsw                          Same as movsb except it moves a
                                     word instead of a byte.
      stosw                          This moves AX into ES:DI. stosb
                                     moves AL into ES:DI. DI is then
                                     incremented.
      push register                  This saves the value of register by
                                     pushing it onto the stack. The
                                     register may then be altered, but
                                     will be restored to it's original
                                     value when popped.
      pop register                   This restores the value of a pushed
                                     register. NOTE : Pushed values must
                                     be popped in the SAME ORDER but
                                     REVERSED.
      rep  command                   This repeats Command by as many
                                     times as the value in CX


SHL Destination,count      ;
and SHR Destination,count  ;
need a bit more explaining. As you know, computers think in ones and
zeroes. Each number may be represented in this base 2 operation. A byte
consists of 8 ones and zeroes (bits), and have a range from 0 to 255. A 
word consists of 16 ones and zeroes (bits), and has a range from 0 to 
65535. A double word consists of 32 bits.

The number 53 may be represented as follows :  00110101.  Ask someone who
looks clever to explain to you how to convert from binary to decimal and
vice-versa.

What happens if you shift everything to the left? Drop the leftmost
number and add a zero to the right? This is what happens :

                00110101     =  53
                 <-----
                01101010     =  106

As you can see, the value has doubled! In the same way, by shifting one
to the right, you halve the value! This is a VERY quick way of
multiplying or dividing by 2. (note that for dividing by shifting, we
get the trunc of the result ... ie.  15 shr 1 = 7)

In assembler the format is SHL destination,count    This shifts
destination by as many bits in count (1=*2, 2=*4, 3=*8, 4=*16 etc)
Note that a shift takes only 2 clock cycles, while a mul can take up to 133
clock cycles. Quite a difference, no? Only 286es or above may have count
being greater then one.

This is why to do the following to calculate the screen coordinates for
a putpixel is very slow :

           mov    ax,[Y]
           mov    bx,320
           mul    bx
           add    ax,[X]
           mov    di,ax

But alas! I hear you cry. 320 is not a value you may shift by, as you
may only shift by 2,4,8,16,32,64,128,256,512 etc.etc. The solution is
very cunning. Watch.

    mov     bx,[X]
    mov     dx,[Y]
    push    bx
    mov     bx, dx                  {; bx = dx = Y}
    mov     dh, dl                  {; dh = dl = Y}
    xor     dl, dl                  {; These 2 lines equal dx*256 }
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1                   {; bx = bx * 64}
    add     dx, bx                  {; dx = dx + bx (ie y*320)}
    pop     bx                      {; get back our x}
    add     bx, dx                  {; finalise location}
    mov     di, bx

Let us have a look at this a bit closer shall we?
bx=dx=y        dx=dx*256  ;   bx=bx*64     ( Note, 256+64 = 320 )

dx+bx=Correct y value, just add X!

As you can see, in assembler, the shortest code is often not the
fastest.

The complete putpixel procedure is as follows :

Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
  { This puts a pixel on the screen by writing directly to memory. }
BEGIN
  Asm
    push    ds                      {; Make sure these two go out the }
    push    es                      {; same they went in }
    mov     ax,[where]
    mov     es,ax                   {; Point to segment of screen }
    mov     bx,[X]
    mov     dx,[Y]
    push    bx                      {; and this again for later}
    mov     bx, dx                  {; bx = dx}
    mov     dh, dl                  {; dx = dx * 256}
    xor     dl, dl
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1                   {; bx = bx * 64}
    add     dx, bx                  {; dx = dx + bx (ie y*320)}
    pop     bx                      {; get back our x}
    add     bx, dx                  {; finalise location}
    mov     di, bx                  {; di = offset }
    {; es:di = where to go}
    xor     al,al
    mov     ah, [Col]
    mov     es:[di],ah              {; move the value in ah to screen
                                       point es:[di] }
    pop     es
    pop     ds
  End;
END;

Note that with DI and SI, when you use them :
      mov   di,50      Moves di to position 50
      mov   [di],50    Moves 50 into the place di is pointing to


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  The Flip Procedure

This is fairly straightforward. We get ES:DI to point to the start of
the destination screen, and DS:SI to point to the start of the source
screen, then do 32000 movsw (64000 bytes).

procedure flip(source,dest:Word);
  { This copies the entire screen at "source" to destination }
begin
  asm
    push    ds
    mov     ax, [Dest]
    mov     es, ax                  { ES = Segment of source }
    mov     ax, [Source]
    mov     ds, ax                  { DS = Segment of source }
    xor     si, si                  { SI = 0   Faster then mov si,0 }
    xor     di, di                  { DI = 0 }
    mov     cx, 32000
    rep     movsw                   { Repeat movsw 32000 times }
    pop     ds
  end;
end;

The cls procedure works in much the same way, only it moves the color
into AX then uses a rep stosw (see program for details)

The PAL command is almost exactly the same as it's Pascal equivalent
(see previous tutorials). Look in the sample code to see how it uses the
out and in commands.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  In Closing

The assembler procedures presented to you in here are not at their best.
Most of these are procedures ASPHYXIA abandoned for better ones after
months of use. But, as you will soon see, they are all MUCH faster then
the original Pascal equivalents I originally gave you. In future, I
hope to give you more and more assembler procedures for your ever
growing collections. But, as you know, I am not always very prompt with
this series (I don't know if even one has been released within one week
of the previous one), so if you want to get any stuff done, try do it
yourself. What do you have to lose, aside from your temper and a few
rather inventive reboots ;-)

What should I do for the next trainer? A simple 3-d tutorial? You may
not like it, because I would go into minute detail of how it works :)
Leave me suggestions for future trainers by any of the means discussed
at the top of this trainer.

After the customary quote, I will place a listing of the BBSes I
currently know that regularly carry this Trainer Series. If your BBS
receives it regularly, no matter where in the country you are, get a
message to me and I'll add it to the list. Let's make it more convenient
for locals to grab a copy without calling long distance ;-)

            [  There they sit, the preschooler class encircling their
                  mentor, the substitute teacher.
               "Now class, today we will talk about what you want to be
                  when you grow up. Isn't that fun?" The teacher looks
                  around and spots the child, silent, apart from the others
                  and deep in thought. "Jonny, why don't you start?" she
                  encourages him.
               Jonny looks around, confused, his train of thought
                  disrupted. He collects himself, and stares at the teacher
                  with a steady eye. "I want to code demos," he says,
                  his words becoming stronger and more confidant as he
                  speaks. "I want to write something that will change
                  peoples perception of reality. I want them to walk
                  away from the computer dazed, unsure of their footing
                  and eyesight. I want to write something that will
                  reach out of the screen and grab them, making
                  heartbeats and breathing slow to almost a halt. I want
                  to write something that, when it is finished, they
                  are reluctant to leave, knowing that nothing they
                  experience that day will be quite as real, as
                  insightful, as good. I want to write demos."
               Silence. The class and the teacher stare at Jonny, stunned. It
                  is the teachers turn to be confused. Jonny blushes,
                  feeling that something more is required.  "Either that
                  or I want to be a fireman."
                                                                     ]
                                                       - Grant Smith
                                                            14:32
                                                               21/11/93

See you next time,
  - DENTHOR

These fine BBS's carry the ASPHYXIA DEMO TRAINER SERIES : (alphabetical)

?????????????????????????????????????????????????????????????????
?BBS Name                  ?Telephone No.   ?Open ?Msg?File?Past?
?????????????????????????????????????????????????????????????????
?ASPHYXIA BBS #1           ?(031) 765-5312  ?ALL  ? * ? *  ? *  ?
?ASPHYXIA BBS #2           ?(031) 765-6293  ?ALL  ? * ? *  ? *  ?
?Connectix BBS             ?(031) 266-9992  ?ALL  ? * ? *  ? *  ?
?For Your Eyes Only BBS    ?(031) 285-318   ?A/H  ? * ? *  ? *  ?
?????????????????????????????????????????????????????????????????

Open = Open at all times or only A/H
Msg  = Available in message base
File = Available in file base
Past = Previous Parts available


?????????????????????????????????????????????????????????????????????????????
? TUTPROG7.PAS ?
????????????????

{$X+}
USES crt;

CONST VGA = $a000;

Type Toastinfo = Record                 { This is format of of each of our }
                 x,y:integer;              { records for the flying toasters }
                 speed,frame:integer;
                 active:boolean;
               END;

     icon = Array [1..30*48] of byte;  { This is the size of our pictures }

     Virtual = Array [1..64000] of byte;  { The size of our Virtual Screen }
     VirtPtr = ^Virtual;                  { Pointer to the virtual screen }

CONST frame1 : icon = (
0,0,0,0,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,5,
7,7,7,7,0,0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,
5,7,7,7,7,7,7,7,8,8,7,7,7,7,7,7,0,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,
0,0,0,0,0,5,5,7,7,7,7,7,8,8,7,8,8,7,8,7,8,7,7,7,5,8,8,8,8,5,5,5,5,5,5,5,5,5,5,5,
5,0,0,0,0,0,0,0,0,0,0,0,5,7,7,7,7,7,7,8,7,7,7,8,7,7,7,7,7,7,0,0,0,0,0,0,8,5,5,5,
5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,5,7,7,8,8,7,7,8,7,7,8,7,7,7,7,7,0,0,0,0,0,
0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,5,7,8,8,8,7,7,8,7,7,8,7,7,7,
7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,5,7,8,8,8,7,7,
8,8,8,8,8,8,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,
9,5,7,8,8,8,8,8,7,7,8,8,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,9,9,9,9,5,7,7,8,8,8,8,7,7,8,8,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,
1,1,0,0,0,1,1,1,1,1,1,1,9,9,9,5,7,8,8,7,7,8,8,7,8,8,8,7,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,5,7,8,8,7,7,7,7,8,8,7,7,7,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,7,8,8,8,8,8,8,8,7,
7,7,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,7,
7,7,7,7,7,7,7,7,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,
1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,
1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,
1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,
4,6,6,6,6,6,6,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,3,3,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,
9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,
9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
);
      frame2 : icon = (
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,
9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,
1,1,0,0,0,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,5,
5,5,5,5,5,5,5,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,
1,1,1,2,2,2,2,2,5,5,5,5,5,5,5,5,5,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,
1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,5,5,5,5,5,5,5,5,5,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,5,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,5,5,5,5,5,5,5,5,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,5,5,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,5,5,5,5,
5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,
2,2,2,2,2,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,5,1,1,1,1,1,1,0,0,0,1,1,1,
1,1,1,2,2,2,2,2,2,2,2,2,2,2,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,5,1,7,1,4,
4,6,6,6,6,6,6,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,
0,0,0,5,5,1,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,5,5,5,5,5,5,5,5,0,0,
0,0,0,0,0,0,0,0,0,0,0,5,5,1,1,1,1,1,1,1,1,1,3,3,1,1,1,1,9,9,9,9,9,9,9,9,9,9,5,5,
5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,
9,9,9,9,9,9,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,9,9,9,9,9,9,9,9,9,9,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,
1,7,7,1,7,1,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,5,5,1,7,7,7,1,1,5,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,5,5,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,1,1,5,5,5,5,0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,
5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,0,0,0,0,0,0,0,0,0,0,0,
0,0,5,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
);
      frame3 : icon = (
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,
9,9,9,9,9,9,9,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,9,9,9,9,9,9,9,9,9,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,7,1,1,1,1,1,
1,1,0,0,0,1,1,1,1,1,1,1,9,9,9,9,9,9,9,5,5,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,
0,7,1,1,7,7,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,5,5,5,1,7,7,7,7,5,5,5,5,5,5,
5,0,0,0,0,0,0,0,7,1,7,7,7,1,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,5,5,1,1,1,7,7,
1,1,7,5,5,5,5,5,5,5,0,0,0,0,0,0,1,1,7,1,1,7,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,
2,1,7,7,7,1,7,7,7,7,7,5,5,5,5,5,5,5,5,0,0,0,0,0,1,7,7,7,7,1,1,1,1,1,0,0,0,1,1,1,
1,1,1,2,2,2,2,2,2,1,7,7,7,7,7,7,7,1,1,5,5,5,5,5,5,5,5,5,0,0,0,0,7,7,1,7,1,7,1,1,
1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,1,1,1,1,1,1,2,2,5,5,5,5,5,5,5,5,5,5,5,0,0,0,
7,7,7,7,7,1,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,5,5,5,5,5,5,
5,5,5,5,5,0,0,0,7,7,0,0,7,7,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,5,5,0,0,5,5,0,5,5,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,
1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,
4,6,6,6,6,6,6,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,3,3,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,
9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,
9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
);


VAR Virscr : VirtPtr;                      { Our first Virtual screen }
    VirScr2 : VirtPtr;                     { Our second Virtual screen }
    Vaddr  : word;                      { The segment of our virtual screen}
    Vaddr2 : Word;                      { The segment of our 2nd virt. screen}
    ourpal : Array [0..255,1..3] of byte; { A virtual pallette }
    toaster : Array [1..10] of toastinfo; { The toaster info }

{??????????????????????????????????????????????????????????????????????????}
Procedure SetMCGA;  { This procedure gets you into 320x200x256 mode. }
BEGIN
  asm
     mov        ax,0013h
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure SetText;  { This procedure returns you to text mode.  }
BEGIN
  asm
     mov        ax,0003h
     int        10h
  end;
END;

{??????????????????????????????????????????????????????????????????????????}
Procedure Cls (Col : Byte; Where:word);
   { This clears the screen to the specified color }
BEGIN
     asm
        push    es
        mov     cx, 32000;
        mov     es,[where]
        xor     di,di
        mov     al,[col]
        mov     ah,al
        rep     stosw
        pop     es
     End;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
  { This puts a pixel on the screen by writing directly to memory. }
BEGIN
  Asm
    push    ds
    push    es
    mov     ax,[where]
    mov     es,ax
    mov     bx,[X]
    mov     dx,[Y]
    push    bx                      {; and this again for later}
    mov     bx, dx                  {; bx = dx}
    mov     dh, dl                  {; dx = dx * 256}
    xor     dl, dl
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1                   {; bx = bx * 64}
    add     dx, bx                  {; dx = dx + bx (ie y*320)}
    pop     bx                      {; get back our x}
    add     bx, dx                  {; finalise location}
    mov     di, bx
    {; es:di = where to go}
    xor     al,al
    mov     ah, [Col]
    mov     es:[di],ah
    pop     es
    pop     ds
  End;
END;


{??????????????????????????????????????????????????????????????????????????}
procedure WaitRetrace; assembler;
  {  This waits for a vertical retrace to reduce snow on the screen }
label
  l1, l2;
asm
    mov dx,3DAh
l1:
    in al,dx
    and al,08h
    jnz l1
l2:
    in al,dx
    and al,08h
    jz  l2
end;


{??????????????????????????????????????????????????????????????????????????}
Procedure Pal(Col,R,G,B : Byte);
  { This sets the Red, Green and Blue values of a certain color }
Begin
   asm
      mov    dx,3c8h
      mov    al,[col]
      out    dx,al
      inc    dx
      mov    al,[r]
      out    dx,al
      mov    al,[g]
      out    dx,al
      mov    al,[b]
      out    dx,al
   end;
End;

{??????????????????????????????????????????????????????????????????????????}
Procedure GetPal(Col : Byte; Var R,G,B : Byte);
  { This gets the Red, Green and Blue values of a certain color }
Var
   rr,gg,bb : Byte;
Begin
   asm
      mov    dx,3c7h
      mov    al,col
      out    dx,al

      add    dx,2

      in     al,dx
      mov    [rr],al
      in     al,dx
      mov    [gg],al
      in     al,dx
      mov    [bb],al
   end;
   r := rr;
   g := gg;
   b := bb;
end;

{??????????????????????????????????????????????????????????????????????????}
Procedure SetUpVirtual;
   { This sets up the memory needed for the virtual screen }
BEGIN
  GetMem (VirScr,64000);
  vaddr := seg (virscr^);
  GetMem (VirScr2,64000);
  vaddr2 := seg (virscr2^);
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure ShutDown;
   { This frees the memory used by the virtual screen }
BEGIN
  FreeMem (VirScr,64000);
  FreeMem (VirScr2,64000);
END;


{??????????????????????????????????????????????????????????????????????????}
procedure flip(source,dest:Word);
  { This copies the entire screen at "source" to destination }
begin
  asm
    push    ds
    mov     ax, [Dest]
    mov     es, ax
    mov     ax, [Source]
    mov     ds, ax
    xor     si, si
    xor     di, di
    mov     cx, 32000
    rep     movsw
    pop     ds
  end;
end;


{??????????????????????????????????????????????????????????????????????????}
Procedure putico(X,Y:Word;VAR sprt : icon;Where:Word); ASSEMBLER;
  { This puts an icon, EXCEPT it's color 0 (black) pixels, onto the screen
    "where", at position X,Y }
label
  _Redraw, _DrawLoop, _Exit, _LineLoop, _NextLine, _Store, _NoPaint;

asm
    push  ds
    push  es
    lds   si,Sprt
    mov   ax,X     { ax = x }
    mov   bx,Y     { bx = y }
_Redraw:
    push    ax
    mov     ax,[where]
    mov     es,ax

    mov     ax, bx                  {; ax = bx  x = y}
    mov     bh, bl                  {; y = y * 256  bx = bx * 256}
    xor     bl, bl
    shl     ax, 1
    shl     ax, 1
    shl     ax, 1
    shl     ax, 1
    shl     ax, 1
    shl     ax, 1                   {; y = y * 64   ax = ax * 64}
    add     bx, ax                  {; y = (y*256) + (Y*64)  bx = bx + ax (ie y*320)}

    pop     ax                      {; get back our x}


    add     ax, bx                  {; finalise location}
    mov     di, ax

    mov   dl,30    { dl = height of sprite }
    xor   ch,ch
    mov   cl,48     { cx = width of sprite }
    cld
    push  ax
    mov   ax,cx
_DrawLoop:
    push  di            { store y adr. for later }
    mov   cx,ax          { store width }
_LineLoop:
    mov   bl,byte ptr [si]
    or    bl,bl
    jnz   _Store
_NoPaint:
    inc    si
    inc    di
    loop   _LineLoop
    jmp    _NextLine
_Store:
    movsb
    loop  _LineLoop
_NextLine:
    pop   di
    dec   dl
    jz    _Exit
    add   di,320        { di = next line of sprite }
    jmp   _DrawLoop
_Exit:
    pop   ax
    pop   es
    pop   ds
end;





{??????????????????????????????????????????????????????????????????????????}
Procedure Funny_line(a,b,c,d:integer;where:word);
  { This procedure draws a line from a,b to c,d on screen "where". After
    each pixel it plots, it increments a color counter for the next pixel.
    you may easily alter this to be a normal line procedure, and it will
    be quite a bit faster than the origional one I gave you. This is
    because I replaced all the reals with integers. }

  function sgn(a:real):integer;
  begin
       if a>0 then sgn:=+1;
       if a<0 then sgn:=-1;
       if a=0 then sgn:=0;
  end;
var i,s,d1x,d1y,d2x,d2y,u,v,m,n:integer;
    count:integer;
begin
     count:=50;
     u:= c - a;
     v:= d - b;
     d1x:= SGN(u);
     d1y:= SGN(v);
     d2x:= SGN(u);
     d2y:= 0;
     m:= ABS(u);
     n := ABS(v);
     IF NOT (M>N) then
     BEGIN
          d2x := 0 ;
          d2y := SGN(v);
          m := ABS(v);
          n := ABS(u);
     END;
     s := m shr 1;
     FOR i := 0 TO m DO
     BEGIN
          putpixel(a,b,count,where);
          inc (count);
          if count=101 then count:=50;
          s := s + n;
          IF not (s<m) THEN
          BEGIN
               s := s - m;
               a:= a + d1x;
               b := b + d1y;
          END
          ELSE
          BEGIN
               a := a + d2x;
               b := b + d2y;
          END;
     end;
END;



{??????????????????????????????????????????????????????????????????????????}
Procedure SetUpScreen;
  { This procedure sets up the static background to be used in the program }

CONST circ : Array [1..5,1..5] of byte =
        ((0,10,10,10,0),
         (10,13,12,11,10),
         (10,12,12,11,10),
         (10,11,11,11,10),
         (0,10,10,10,0));

VAR x,y:integer;
    loop1,loop2,loop3:integer;

BEGIN
  pal (1,22,22,22);
  pal (2,45,45,45);
  pal (3,59,59,59);
  pal (4,63,63,27);
  pal (5,39,63,3);
  pal (6,51,39,3);
  pal (7,3,27,3);
  pal (8,15,39,15);
  pal (9,35,35,35);
  pal (10, 0, 0,40);
  pal (11,10,10,50);
  pal (12,20,20,60);
  pal (13,30,30,63);

  For loop1:=50 to 100 do
    pal (loop1,0,0,loop1-36);

  For loop1:=0 to 255 do
     getpal (loop1,OurPal[loop1,1],OurPal[loop1,2],OurPal[loop1,3]);

  For loop1:=0 to 319 do
    Funny_line (0,199,loop1,0,vaddr);
  For loop1:=0 to 199 do
    Funny_line (0,199,319,loop1,vaddr);

  For loop1:=1 to 200 do BEGIN
    x:=random (315);
    y:=random (195);
    For loop2:=1 to 5 do
      For loop3:=1 to 5 do
        if circ [loop2,loop3]<>0 then
          putpixel (x+loop2,y+loop3,circ [loop2,loop3],vaddr);
  END;
  flip (vaddr,vga);  { Copy the entire screen at vaddr, our virtual screen }
                     { on which we have done all our graphics, onto the    }
                     { screen you see, VGA }
  flip (vaddr,vaddr2);
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure rotatepal;
  { This procedure rotates the colors between 50 and 100 }
VAR temp : Array [1..3] of byte;
    loop1:integer;
BEGIN
  Move(OurPal[100],Temp,3);
  Move(OurPal[50],OurPal[51],50*3);
  Move(Temp,OurPal[50],3);
  For loop1:=50 to 100 do
    pal (loop1,OurPal[loop1,1],OurPal[loop1,2],OurPal[loop1,3]);
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure ScreenTrans (x,y:word);
  { This is a small procedure to copy a 30x30 pixel block from coordinates
    x,y on the virtual screen to coordinates x,y on the true vga screen }
BEGIN
  asm
    push    ds
    push    es
    mov     ax,vaddr
    mov     es,ax
    mov     ax,vaddr2
    mov     ds,ax
    mov     bx,[X]
    mov     dx,[Y]
    push    bx                      {; and this again for later}
    mov     bx, dx                  {; bx = dx}
    mov     dh, dl                  {; dx = dx * 256}
    xor     dl, dl
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1                   {; bx = bx * 64}
    add     dx, bx                  {; dx = dx + bx (ie y*320)}
    pop     bx                      {; get back our x}
    add     bx, dx                  {; finalise location}
    mov     di, bx                  {; es:di = where to go}
    mov     si, di
    mov     al,60
    mov     bx, 30         { Hight of block to copy }
@@1 :
    mov     cx, 24         { Width of block to copy divided by 2 }
    rep     movsw
    add     di,110h        { 320 - 48 = 272 .. or 110 in hex }
    add     si,110h
    dec     bx
    jnz     @@1

    pop     es
    pop     ds
  end;
  { I wrote this procedure late last night, so it may not be in it's
    most optimised state. Sorry :-)}
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure NewToaster;
  { This adds a new toaster to the screen }
VAR loop1:integer;
BEGIN
  loop1:=0;
  repeat
    inc (loop1);
    if not (toaster[loop1].active) then BEGIN
      toaster[loop1].x:=random (200)+70;
      toaster[loop1].y:=0;
      toaster[loop1].active:=true;
      toaster[loop1].frame:=1;
      toaster[loop1].speed:=Random (3)+1;
      loop1:=10;
    END;
  until loop1=10;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure Fly;
  { This is the procedure where we move and put the toasters }
VAR loop1,loop2:integer;
    ch:char;
BEGIN
  For loop1:=1 to 10 do
    toaster[loop1].active:=FALSE;
  ch:=#0;
  NewToaster;
  Repeat
    if keypressed then BEGIN
      ch:=readkey;
      if ch='+' then NewToaster;      { If '+' is pressed, add a toaster }
      if ch='-' then BEGIN            { if '-' is pressed, remove a toaster }
        loop1:=0;
        repeat
          inc (loop1);
          if toaster[loop1].active then BEGIN
            screentrans (toaster[loop1].x,toaster[loop1].y);
            toaster [loop1].active:=FALSE;
            loop1:=10;
          END;
        until loop1=10;
      END;
    END;
    for loop1:=1 to 10 do
      if toaster[loop1].active then BEGIN
        screentrans (toaster[loop1].x,toaster[loop1].y);
          { Restore the backgrond the toaster was over }
        dec (toaster[loop1].x,toaster[loop1].speed);
        inc (toaster[loop1].y,toaster[loop1].speed);
          { Move the toaster }
        if (toaster[loop1].x<1) or (toaster[loop1].y>170) then BEGIN
          toaster[loop1].active:=FALSE;
          NewToaster;
        END;
          { When toaster reaches the edge of the screen, render it inactive
            and bring a new one into existance. }
      END;
    for loop1:=1 to 10 do
      if toaster[loop1].active then BEGIN
        CASE toaster [loop1].frame of
           1   : putico (toaster[loop1].x,toaster[loop1].y,frame1,vaddr);
           3   : putico (toaster[loop1].x,toaster[loop1].y,frame2,vaddr);
           2,4 : putico (toaster[loop1].x,toaster[loop1].y,frame3,vaddr);
        END;
        toaster[loop1].frame:=toaster[loop1].frame+1;
        if toaster [loop1].frame=5 then toaster[loop1].frame:=1;
          { Draw all the toasters on the VGA screen }
      END;
    waitretrace;
    flip (vaddr,vga);
    rotatepal;
  Until ch=#27;
END;


BEGIN
  Randomize;       { Make sure that the RANDOM funcion really is random }
  SetupVirtual;    { Set up virtual page, VADDR }
  ClrScr;
  writeln ('Hello! This program will demonstrate the principals of animation.');
  writeln ('The program will firstly generate an arb background screen to a');
  writeln ('virtual page, then flip it to the VGA. A toaster will then start');
  writeln ('to move across the screen. Note that the background will be restored');
  writeln ('after the toaster has passed over it. You may add or remove toasters');
  writeln ('by hitting "+" or "-" respectively. Note that the more frames you');
  writeln ('use, usually the better the routine looks. Because of space');
  writeln ('restrictions, we only had room for three frames.');
  writeln;
  writeln ('The toasters were drawn by Fubar (Pieter Buys) in Autodesk Animator.');
  writeln ('I wrote a small little program to convert them into CONSTANTS. See');
  writeln ('the main text to find out how to load up AA CEL files directly.');
  writeln;
  writeln;
  Write ('  Hit any key to contine ...');
  Readkey;
  SetMCGA;
  SetupScreen;     { Draw the background screen to VADDR, then flip it to
                     the VGA screen }
  Fly;             { Make the toasters fly around the screen }
  SetText;
  ShutDown;        { Free the memory taken up by virtual page }
  Writeln ('All done. This concludes the seventh sample program in the ASPHYXIA');
  Writeln ('Training series. You may reach DENTHOR under the names of GRANT');
  Writeln ('SMITH/DENTHOR/ASPHYXIA on the ASPHYXIA BBS. I am also an avid');
  Writeln ('Connectix BBS user, which is unfortunatly offline for the moment.');
  Writeln ('For discussion purposes, I am also the moderator of the Programming');
  Writeln ('newsgroup on the For Your Eyes Only BBS.');
  Writeln ('The numbers are available in the main text. You may also write to me at:');
  Writeln ('             Grant Smith');
  Writeln ('             P.O. Box 270');
  Writeln ('             Kloof');
  Writeln ('             3640');
  Writeln ('I hope to hear from you soon!');
  Writeln; Writeln;
  Write   ('Hit any key to exit ...');
  Readkey;
END.
                   ?????????????????????????????????
                   ?         W E L C O M E         ?
                   ?  To the VGA Trainer Program   ? ?
                   ?              By               ? ?
                   ?      DENTHOR of ASPHYXIA      ? ? ?
                   ????????????????????????????????? ? ?
                     ????????????????????????????????? ?
                       ?????????????????????????????????

                           --==[ PART 8 ]==--



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Introduction

Hello everybody! Christmas is over, the last of the chocolates have been
eaten, so it's time to get on with this, the eighth part of the ASPHYXIA
Demo Trainer Series. This particular part is primarily about 3-D, but
also includes a bit on optimisation.

If you are already a 3-D guru, you may as well skip this text file, have
a quick look at the sample program then go back to sleep, because I am
going to explain in minute detail exactly how the routines work ;)

If you would like to contact me, or the team, there are many ways you
can do it : 1) Write a message to Grant Smith/Denthor/Asphyxia in private mail
                  on the ASPHYXIA BBS.
            2) Write a message in the Programming conference on the
                  For Your Eyes Only BBS (of which I am the Moderator )
                  This is preferred if you have a general programming query
                  or problem others would benefit from.
            4) Write to Denthor, EzE or Goth on Connectix.
            5) Write to :  Grant Smith
                           P.O.Box 270 Kloof
                           3640
                           Natal
            6) Call me (Grant Smith) at (031) 73 2129 (leave a message if you
                  call during varsity)
            7) Write to mcphail@beastie.cs.und.ac.za on InterNet, and
                  mention the word Denthor near the top of the letter.

NB : If you are a representative of a company or BBS, and want ASPHYXIA
       to do you a demo, leave mail to me; we can discuss it.
NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling
        quite lonely and want to meet/help out/exchange code with other demo
        groups. What do you have to lose? Leave a message here and we can work
        out how to transfer it. We really want to hear from you!



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Optimisation

Before I begin with the note on 3-D, I would like to stress that many of
these routines, and probably most of your own, could be sped up quite a
bit with a little optimisation. One must realise, however, that you must
take a look at WHAT to optimise ... converting a routine that is only
called once at startup into a tightly coded assembler routine may show
off your merits as a coder, but does absolutely nothing to speed up your
program. Something that is called often per frame is something that
needs to be as fast as possible. For some, a much used procedure is the
PutPixel procedure. Here is the putpixel procedure I gave you last week:

Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
BEGIN
  Asm
    push    ds                      { 14   clock ticks }
    push    es                      { 14 }
    mov     ax,[where]              { 8  }
    mov     es,ax                   { 2 }
    mov     bx,[X]                  { 8  }
    mov     dx,[Y]                  { 8  }
    push    bx                      { 15 }
    mov     bx, dx                  { 2  }
    mov     dh, dl                  { 2  }
    xor     dl, dl                  { 3  }
    shl     bx, 1                   { 2  }
    shl     bx, 1                   { 2  }
    shl     bx, 1                   { 2  }
    shl     bx, 1                   { 2  }
    shl     bx, 1                   { 2  }
    shl     bx, 1                   { 2  }
    add     dx, bx                  { 3  }
    pop     bx                      { 12 }
    add     bx, dx                  { 3  }
    mov     di, bx                  { 2 }
    xor     al,al                   { 3  }
    mov     ah, [Col]               { 8  }
    mov     es:[di],ah              { 10 }
    pop     es                      { 12 }
    pop     ds                      { 12 }
  End;
END;
                            Total = 153 clock ticks
NOTE : Don't take my clock ticks as gospel, I probably got one or two
       wrong.

Right, now for some optimising. Firstly, if you have 286 instructions
turned on, you may replace the 6 shl,1 with shl,6. Secondly, the Pascal
compiler automatically pushes and pops ES, so those two lines may be
removed. DS:[SI] is not altered in this procedure, so we may remove
those too. Also, instead of moving COL into ah, we move it into AL and
call stosb (es:[di]:=al; inc di). Let's have a look at the routine now :

Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
BEGIN
  Asm
    mov     ax,[where]              { 8  }
    mov     es,ax                   { 2 }
    mov     bx,[X]                  { 8  }
    mov     dx,[Y]                  { 8  }
    push    bx                      { 15 }
    mov     bx, dx                  { 2  }
    mov     dh, dl                  { 2  }
    xor     dl, dl                  { 3  }
    shl     bx, 6                   { 8  }
    add     dx, bx                  { 3  }
    pop     bx                      { 12 }
    add     bx, dx                  { 3  }
    mov     di, bx                  { 2 }
    mov     al, [Col]               { 8  }
    stosb                           { 11 }
  End;
END;
                            Total = 95 clock ticks

Now, let us move the value of BX directly into DI, thereby removing a
costly push and pop. The MOV and the XOR of DX can be replaced by it's
equivalent, SHL DX,8

Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); assembler;
asm
    mov     ax,[where]              { 8  }
    mov     es,ax                   { 2  }
    mov     bx,[X]                  { 8  }
    mov     dx,[Y]                  { 8  }
    mov     di,bx                   { 2  }
    mov     bx, dx                  { 2  }
    shl     dx, 8                   { 8  }
    shl     bx, 6                   { 8  }
    add     dx, bx                  { 3  }
    add     di, dx                  { 3  }
    mov     al, [Col]               { 8  }
    stosb                           { 11 }
end;
                            Total = 71 clock ticks

As you can see, we have brought the clock ticks down from 153 ticks to
71 ticks ... quite an improvement. (The current ASPHYXIA putpixel takes
48 clock ticks) . As you can see, by going through your routines a few
times, you can spot and remove unnecessary instructions, thereby greatly
increasing the speed of your program.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Defining a 3-D object

Drawing an object in 3-D is not that easy. Sitting down and plotting a
list of X,Y and Z points can be a time consuming business. So, let us
first look at the three axes you are drawing them on :

                    Y    Z
                   /|\  /
                    | /
             X<-----|----->
                    |
                   \|/

X is the horisontal axis, from left to right. Y is the vertical axis,
from top to bottom. Z is the depth, going straight into the screen.

In this trainer, we are using lines, so we define 2 X,Y and Z
coordinates, one for each end of the line. A line from far away, in the
upper left of the X and Y axes, to close up in the bottom right of the
X and Y axes, would look like this :

{       x1 y1  z1   x2  y2 z2    }
    ( (-10,10,-10),(10,-10,10) )


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Rotating a point with matrixes

NOTE : I thought that more then one matix are matrisese (sp), but my 
       spellchecker insists it is matrixes, so I let it have it's way 
       ;-)

Having a 3-D object is useless unless you can rotate it some way. For
demonstration purposes, I will begin by working in two dimensions, X and
Y.

Let us say you have a point, A,B, on a graph.
                      Y
                      |  /O1 (Cos (a)*A-Sin (a)*B , Sin (a)*A+Cos (a)*B)
                      |/      (A,B)
               X<-----|------O-->
                      |
                      |

Now, let us say we rotate this point by 45 degrees anti-clockwise. The
new A,B can be easily be calculated using sin and cos, by an adaption of
our circle algorithm, ie.
           A2:=Cos (45)*A - Sin (45)*B
           B2:=Sin (45)*A + Cos (45)*B
I recall that in standard 8 and 9, we went rather heavily into this in
maths. If you have troubles, fine a 8/9/10 maths book and have a look;
it will go through the proofs etc.

Anyway, we have now rotated an object in two dimensions, AROUND THE Z
AXIS. In matrix form, the equation looks like this :

   [  Cos (a)   -Sin (a)      0        0     ]    [  x ]
   [  Sin (a)    Cos (a)      0        0     ]  . [  y ]
   [     0         0          1        0     ]    [  z ]
   [     0         0          0        1     ]    [  1 ]

I will not go to deeply into matrixes math at this stage, as there are
many books on the subject (it is not part of matric maths, however). To
multiply a matrix, to add the products of the row of the left matrix and
the column of the right matrix, and repeat this for all the columns of the
left matrix. I don't explain it as well as my first year maths lecturer,
but have a look at how I derived A2 and B2 above. Here are the other
matrixes :

Matrix for rotation around the Y axis :
   [  Cos (a)      0       -Sin (a)    0     ]    [  x ]
   [     0         1          0        0     ]  . [  y ]
   [  Sin (a)      0        Cos (a)    0     ]    [  z ]
   [     0         0          0        1     ]    [  1 ]

Matrix for rotation around the X axis :
   [     1         0                   0     ]    [  x ]
   [     0       Cos (a)   -Sin (a)    0     ]  . [  y ]
   [     0       Sin (a)    Cos (a)    0     ]    [  z ]
   [     0         0          0        1     ]    [  1 ]

By putting all these matrixes together, we can translate out 3D points
around the origin of 0,0,0. See the sample program for how we put them
together.

In the sample program, we have a constant, never changing base object.
This is rotated into a second variable, which is then drawn. I am sure
many of you can thing of cool ways to change the base object, the
effects of which will appear while the object is rotating. One idea is
to "pulsate" a certain point of the object according to the beat of the
music being played in the background. Be creative. If you feel up to it,
you could make your own version of transformers ;)



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Drawing a 3D point to screen

Having a rotated 3D object is useless unless we can draw it to screen.
But how do we show a 3D point on a 2D screen? The answer needs a bit of
explaining. Examine the following diagram :

              |         ________-------------
          ____|___------      o Object at X,Y,Z     o1 Object at X,Y,Z2
 Eye -> O)____|___
              |   ------________
              |                 -------------- Field of vision
            Screen

Let us pretend that the centre of the screen is the horizon of our
little 3D world. If we draw a three dimensional line from object "o" to
the centre of the eye, and place a pixel on the X and Y coordinates
where it passes through the screen, we will notice that when we do the
same with object o1, the pixel is closer to the horizon, even though
their 3D X and Y coords are identical, but "o1"'s Z is larger then
"o"'s. This means that the further away a point is, the closer to the
horizon it is, or the smaller the object will appear. That sounds
right, doesent it? But, I hear you cry, how do we translate this into a
formula? The answer is quite simple. Divide your X and your Y by your Z.
Think about it. The larger the number you divide by, the closer to zero,
or the horizon, is the result! This means, the bigger the Z, the
further away is the object! Here it is in equation form :

       nx := 256*x div (z-Zoff)+Xoff
       ny := 256*y div (z-Zoff)+Yoff

NOTE : Zoff is how far away the entire object is, Xoff is the objects X
       value, and Yoff is the objects Y value. In the sample program,
       Xoff start off at 160 and Yoff starts off at 100, so that the
       object is in the middle of the screen.

The 256 that you times by is the perspective with which you are viewing.
Changing this value gives you a "fish eye" effect when viewing the
object. Anyway, there you have it! Draw a pixel at nx,ny, and viola! you
are now doing 3D! Easy, wasn't it?


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Possible improvements

This program is not the most optimised routine you will ever encounter
(;-)) ... it uses 12 muls and 2 divs per point. (Asphyxia currently has
9 muls and 2 divs per point) Real math is used for all the calculations
in the sample program, which is slow, so fixed point math should be
implemented (I will cover fixed point math in a future trainer). The
line routine currently being used is very slow. Chain-4 could be used to
cut down on screen flipping times.

Color values per line should be added, base object morphing could be put
in, polygons could be used instead of lines, handling of more then one
object should be implemented, clipping should be added instead of not
drawing something if any part of it is out of bounds.

In other words, you have a lot of work ahead of you ;)


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  In closing

There are a lot of books out there on 3D, and quite a few sample
programs too. Have a look at them, and use the best bits to create your
own, unique 3D engine, with which you can do anything you want. I am
very interested in 3D (though EzE and Goth wrote most of ASPHYXIA'S 3D
routines), and would like to see what you can do with it. Leave me a
message through one of the means described above.

I am delving into the murky world of texture mapping. If anyone out 
there has some routines on the subject and are interested in swapping, 
give me a buzz!

What to do in future trainers? Help me out on this one! Are there any
effects/areas you would like a bit of info on? Leave me a message!

I unfortunately did not get any messages regarding BBS's that carry this
series, so the list that follows is the same one from last time. Give
me your names, sysops!

Aaaaargh!!! Try as I might, I can't think of a new quote. Next time, I
promise! ;-)

Bye for now,
  - Denthor


These fine BBS's carry the ASPHYXIA DEMO TRAINER SERIES : (alphabetical)

?????????????????????????????????????????????????????????????????
?BBS Name                  ?Telephone No.   ?Open ?Msg?File?Past?
?????????????????????????????????????????????????????????????????
?ASPHYXIA BBS #1           ?(031) 765-5312  ?ALL  ? * ? *  ? *  ?
?ASPHYXIA BBS #2           ?(031) 765-6293  ?ALL  ? * ? *  ? *  ?
?Connectix BBS             ?(031) 266-9992  ?ALL  ? * ?    ?    ?
?For Your Eyes Only BBS    ?(031) 285-318   ?A/H  ? * ? *  ? *  ?
?????????????????????????????????????????????????????????????????

Open = Open at all times or only A/H
Msg  = Available in message base
File = Available in file base
Past = Previous Parts available

?????????????????????????????????????????????????????????????????????????????
? TUTPROG8.PAS ?
????????????????


{$X+}
USES Crt;

CONST VGA = $A000;
      MaxLines = 12;
      Obj : Array [1..MaxLines,1..2,1..3] of integer =
        (
        ((-10,-10,-10),(10,-10,-10)),((-10,-10,-10),(-10,10,-10)),
        ((-10,10,-10),(10,10,-10)),((10,-10,-10),(10,10,-10)),
        ((-10,-10,10),(10,-10,10)),((-10,-10,10),(-10,10,10)),
        ((-10,10,10),(10,10,10)),((10,-10,10),(10,10,10)),
        ((-10,-10,10),(-10,-10,-10)),((-10,10,10),(-10,10,-10)),
        ((10,10,10),(10,10,-10)),((10,-10,10),(10,-10,-10))
        );  { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), }
            { (X2,Y2,Z2) ... for the two ends of a line }


Type Point = Record
               x,y,z:real;                { The data on every point we rotate}
             END;
     Virtual = Array [1..64000] of byte;  { The size of our Virtual Screen }
     VirtPtr = ^Virtual;                  { Pointer to the virtual screen }


VAR Lines : Array [1..MaxLines,1..2] of Point;  { The base object rotated }
    Translated : Array [1..MaxLines,1..2] of Point; { The rotated object }
    Xoff,Yoff,Zoff:Integer;               { Used for movement of the object }
    lookup : Array [0..360,1..2] of real; { Our sin and cos lookup table }
    Virscr : VirtPtr;                     { Our first Virtual screen }
    Vaddr  : word;                        { The segment of our virtual screen}


{??????????????????????????????????????????????????????????????????????????}
Procedure SetMCGA;  { This procedure gets you into 320x200x256 mode. }
BEGIN
  asm
     mov        ax,0013h
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure SetText;  { This procedure returns you to text mode.  }
BEGIN
  asm
     mov        ax,0003h
     int        10h
  end;
END;

{??????????????????????????????????????????????????????????????????????????}
Procedure Cls (Where:word;Col : Byte);
   { This clears the screen to the specified color }
BEGIN
     asm
        push    es
        mov     cx, 32000;
        mov     es,[where]
        xor     di,di
        mov     al,[col]
        mov     ah,al
        rep     stosw
        pop     es
     End;
END;

{??????????????????????????????????????????????????????????????????????????}
Procedure SetUpVirtual;
   { This sets up the memory needed for the virtual screen }
BEGIN
  GetMem (VirScr,64000);
  vaddr := seg (virscr^);
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure ShutDown;
   { This frees the memory used by the virtual screen }
BEGIN
  FreeMem (VirScr,64000);
END;


{??????????????????????????????????????????????????????????????????????????}
procedure flip(source,dest:Word);
  { This copies the entire screen at "source" to destination }
begin
  asm
    push    ds
    mov     ax, [Dest]
    mov     es, ax
    mov     ax, [Source]
    mov     ds, ax
    xor     si, si
    xor     di, di
    mov     cx, 32000
    rep     movsw
    pop     ds
  end;
end;


{??????????????????????????????????????????????????????????????????????????}
Procedure Pal(Col,R,G,B : Byte);
  { This sets the Red, Green and Blue values of a certain color }
Begin
   asm
      mov    dx,3c8h
      mov    al,[col]
      out    dx,al
      inc    dx
      mov    al,[r]
      out    dx,al
      mov    al,[g]
      out    dx,al
      mov    al,[b]
      out    dx,al
   end;
End;


{??????????????????????????????????????????????????????????????????????????}
Function rad (theta : real) : real;
  {  This calculates the degrees of an angle }
BEGIN
  rad := theta * pi / 180
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure SetUpPoints;
  { This sets the basic offsets of the object, creates the lookup table and
    moves the object from a constant to a variable }
VAR loop1:integer;
BEGIN
  Xoff:=160;
  Yoff:=100;
  Zoff:=-256;
  For loop1:=0 to 360 do BEGIN
    lookup [loop1,1]:=sin (rad (loop1));
    lookup [loop1,2]:=cos (rad (loop1));
  END;
  For loop1:=1 to MaxLines do BEGIN
    Lines [loop1,1].x:=Obj [loop1,1,1];
    Lines [loop1,1].y:=Obj [loop1,1,2];
    Lines [loop1,1].z:=Obj [loop1,1,3];
    Lines [loop1,2].x:=Obj [loop1,2,1];
    Lines [loop1,2].y:=Obj [loop1,2,2];
    Lines [loop1,2].z:=Obj [loop1,2,3];
  END;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
  { This puts a pixel on the screen by writing directly to memory. }
BEGIN
  Asm
    mov     ax,[where]
    mov     es,ax
    mov     bx,[X]
    mov     dx,[Y]
    mov     di,bx
    mov     bx, dx                  {; bx = dx}
    shl     dx, 8
    shl     bx, 6
    add     dx, bx                  {; dx = dx + bx (ie y*320)}
    add     di, dx                  {; finalise location}
    mov     al, [Col]
    stosb
  End;
END;



{??????????????????????????????????????????????????????????????????????????}
Procedure Line(a,b,c,d:integer;col:byte;where:word);
  { This draws a solid line from a,b to c,d in colour col }
  function sgn(a:real):integer;
  begin
       if a>0 then sgn:=+1;
       if a<0 then sgn:=-1;
       if a=0 then sgn:=0;
  end;
var i,s,d1x,d1y,d2x,d2y,u,v,m,n:integer;
begin
     u:= c - a;
     v:= d - b;
     d1x:= SGN(u);
     d1y:= SGN(v);
     d2x:= SGN(u);
     d2y:= 0;
     m:= ABS(u);
     n := ABS(v);
     IF NOT (M>N) then
     BEGIN
          d2x := 0 ;
          d2y := SGN(v);
          m := ABS(v);
          n := ABS(u);
     END;
     s := m shr 1;
     FOR i := 0 TO m DO
     BEGIN
          putpixel(a,b,col,where);
          s := s + n;
          IF not (s<m) THEN
          BEGIN
               s := s - m;
               a:= a + d1x;
               b := b + d1y;
          END
          ELSE
          BEGIN
               a := a + d2x;
               b := b + d2y;
          END;
     end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure DrawLogo;
  { This draws 'ASPHYXIA' at the top of the screen in little balls }
CONST ball : Array [1..5,1..5] of byte =
         ((0,1,1,1,0),
          (1,4,3,2,1),
          (1,3,3,2,1),
          (1,2,2,2,1),
          (0,1,1,1,0));

VAR Logo : Array [1..5] of String;
    loop1,loop2,loop3,loop4:integer;
BEGIN
  pal (13,0,63,0);
  pal (1,0,0,40);
  pal (2,0,0,45);
  pal (3,0,0,50);
  pal (4,0,0,60);
  Logo[1]:=' O  OOO OOO O O O O O O OOO  O ';
  Logo[2]:='O O O   O O O O O O O O  O  O O';
  Logo[3]:='OOO OOO OOO OOO  O   O   O  OOO';
  Logo[4]:='O O   O O   O O  O  O O  O  O O';
  Logo[5]:='O O OOO O   O O  O  O O OOO O O';
  For loop1:=1 to 5 do
    For loop2:=1 to 31 do
      if logo[loop1][loop2]='O' then
        For loop3:=1 to 5 do
          For loop4:=1 to 5 do
            putpixel (loop2*10+loop3,loop1*4+loop4,ball[loop3,loop4],vaddr);
END;



{??????????????????????????????????????????????????????????????????????????}
Procedure RotatePoints (X,Y,Z:Integer);
  { This rotates object lines by X,Y and Z; then places the result in
    TRANSLATED }
VAR loop1:integer;
    temp:point;
BEGIN
  For loop1:=1 to maxlines do BEGIN
    temp.x:=lines[loop1,1].x;
    temp.y:=lookup[x,2]*lines[loop1,1].y - lookup[x,1]*lines[loop1,1].z;
    temp.z:=lookup[x,1]*lines[loop1,1].y + lookup[x,2]*lines[loop1,1].z;

    translated[loop1,1]:=temp;

    If y>0 then BEGIN
      temp.x:=lookup[y,2]*translated[loop1,1].x - lookup[y,1]*translated[loop1,1].y;
      temp.y:=lookup[y,1]*translated[loop1,1].x + lookup[y,2]*translated[loop1,1].y;
      temp.z:=translated[loop1,1].z;
      translated[loop1,1]:=temp;
    END;

    If z>0 then BEGIN
      temp.x:=lookup[z,2]*translated[loop1,1].x + lookup[z,1]*translated[loop1,1].z;
      temp.y:=translated[loop1,1].y;
      temp.z:=-lookup[z,1]*translated[loop1,1].x + lookup[z,2]*translated[loop1,1].z;
      translated[loop1,1]:=temp;
    END;

    temp.x:=lines[loop1,2].x;
    temp.y:=cos (rad(X))*lines[loop1,2].y - sin (rad(X))*lines[loop1,2].z;
    temp.z:=sin (rad(X))*lines[loop1,2].y + cos (rad(X))*lines[loop1,2].z;

    translated[loop1,2]:=temp;

    If y>0 then BEGIN
      temp.x:=cos (rad(Y))*translated[loop1,2].x - sin (rad(Y))*translated[loop1,2].y;
      temp.y:=sin (rad(Y))*translated[loop1,2].x + cos (rad(Y))*translated[loop1,2].y;
      temp.z:=translated[loop1,2].z;
      translated[loop1,2]:=temp;
    END;

    If z>0 then BEGIN
      temp.x:=cos (rad(Z))*translated[loop1,2].x + sin (rad(Z))*translated[loop1,2].z;
      temp.y:=translated[loop1,2].y;
      temp.z:=-sin (rad(Z))*translated[loop1,2].x + cos (rad(Z))*translated[loop1,2].z;
      translated[loop1,2]:=temp;
    END;
  END;
END;



{??????????????????????????????????????????????????????????????????????????}
Procedure DrawPoints;
  { This draws the translated object to the virtual screen }
VAR loop1:Integer;
    nx,ny,nx2,ny2:integer;
    temp:integer;
BEGIN
  For loop1:=1 to MaxLines do BEGIN
    If (translated[loop1,1].z+zoff<0) and (translated[loop1,2].z+zoff<0) then BEGIN
      temp:=round (translated[loop1,1].z+zoff);
      nx :=round (256*translated[loop1,1].X) div temp+xoff;
      ny :=round (256*translated[loop1,1].Y) div temp+yoff;
      temp:=round (translated[loop1,2].z+zoff);
      nx2:=round (256*translated[loop1,2].X) div temp+xoff;
      ny2:=round (256*translated[loop1,2].Y) div temp+yoff;
      If (NX > 0) and (NX < 320) and (NY > 25) and (NY < 200) and
         (NX2> 0) and (NX2< 320) and (NY2> 25) and (NY2< 200) then
           line (nx,ny,nx2,ny2,13,vaddr);
    END;
  END;
END;

{??????????????????????????????????????????????????????????????????????????}
Procedure ClearPoints;
  { This clears the translated object from the virtual screen ... believe it
    or not, this is faster then a straight "cls (vaddr,0)" }
VAR loop1:Integer;
    nx,ny,nx2,ny2:Integer;
    temp:integer;
BEGIN
  For loop1:=1 to MaxLines do BEGIN
    If (translated[loop1,1].z+zoff<0) and (translated[loop1,2].z+zoff<0) then BEGIN
      temp:=round (translated[loop1,1].z+zoff);
      nx :=round (256*translated[loop1,1].X) div temp+xoff;
      ny :=round (256*translated[loop1,1].Y) div temp+yoff;
      temp:=round (translated[loop1,2].z+zoff);
      nx2:=round (256*translated[loop1,2].X) div temp+xoff;
      ny2:=round (256*translated[loop1,2].Y) div temp+yoff;
      If (NX > 0) and (NX < 320) and (NY > 25) and (NY < 200) and
         (NX2> 0) and (NX2< 320) and (NY2> 25) and (NY2< 200) then
           line (nx,ny,nx2,ny2,0,vaddr);
    END;
  END;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure MoveAround;
  { This is the main display procedure. Firstly it brings the object towards
    the viewer by increasing the Zoff, then passes control to the user }
VAR deg,loop1:integer;
    ch:char;
BEGIN
  deg:=0;
  ch:=#0;
  Cls (vaddr,0);
  DrawLogo;
  For loop1:=-256 to -40 do BEGIN
    zoff:=loop1*2;
    RotatePoints (deg,deg,deg);
    DrawPoints;
    flip (vaddr,vga);
    ClearPoints;
    deg:=(deg+5) mod 360;
  END;

  Repeat
    if keypressed then BEGIN
      ch:=upcase (Readkey);
      Case ch of 'A' : zoff:=zoff+5;
                 'Z' : zoff:=zoff-5;
                 ',' : xoff:=xoff-5;
                 '.' : xoff:=xoff+5;
                 'S' : yoff:=yoff-5;
                 'X' : yoff:=yoff+5;
      END;
    END;
    DrawPoints;
    flip (vaddr,vga);
    ClearPoints;
    RotatePoints (deg,deg,deg);
    deg:=(deg+5) mod 360;
  Until ch=#27;
END;


BEGIN
  SetUpVirtual;
  Writeln ('Greetings and salutations! Hope you had a great Christmas and New');
  Writeln ('year! ;-) ... Anyway, this tutorial is on 3-D, so this is what is');
  Writeln ('going to happen ... a wireframe square will come towards you.');
  Writeln ('When it gets close, you get control. "A" and "Z" control the Z');
  Writeln ('movement, "," and "." control the X movement, and "S" and "X"');
  Writeln ('control the Y movement. I have not included rotation control, but');
  Writeln ('it should be easy enough to put in yourself ... if you have any');
  Writeln ('hassles, leave me mail.');
  Writeln;
  Writeln ('Read the main text file for ideas on improving this code ... and');
  Writeln ('welcome to the world of 3-D!');
  writeln;
  writeln;
  Write ('  Hit any key to contine ...');
  Readkey;
  SetMCGA;
  SetUpPoints;
  MoveAround;
  SetText;
  ShutDown;
  Writeln ('All done. This concludes the eigth sample program in the ASPHYXIA');
  Writeln ('Training series. You may reach DENTHOR under the names of GRANT');
  Writeln ('SMITH/DENTHOR/ASPHYXIA on the ASPHYXIA BBS. I am also an avid');
  Writeln ('Connectix BBS user, and occasionally read RSAProg.');
  Writeln ('For discussion purposes, I am also the moderator of the Programming');
  Writeln ('newsgroup on the For Your Eyes Only BBS.');
  Writeln ('The numbers are available in the main text. You may also write to me at:');
  Writeln ('             Grant Smith');
  Writeln ('             P.O. Box 270');
  Writeln ('             Kloof');
  Writeln ('             3640');
  Writeln ('I hope to hear from you soon!');
  Writeln; Writeln;
  Write   ('Hit any key to exit ...');
  Readkey;
END.
                   ?????????????????????????????????
                   ?         W E L C O M E         ?
                   ?  To the VGA Trainer Program   ? ?
                   ?              By               ? ?
                   ?      DENTHOR of ASPHYXIA      ? ? ?
                   ????????????????????????????????? ? ?
                     ????????????????????????????????? ?
                       ?????????????????????????????????

                           --==[ PART 9 ]==--



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Introduction

Hi there! ASPHYXIA is BACK with our first MegaDemo, Psycho Neurosis! A
paltry 1.3MB download is all it takes to see the group from Durbs first
major production! We are quite proud of it, and think you should see it
;)

Secondly, I released a small little trainer (a trainerette ;-)) on
RsaPROG and Connexctix BBS mail, also on the ASPHYXIA BBS as COPPERS.ZIP
It is a small Pascal program demonstrating how to display copper bars in
text mode. Also includes a check for horizontal retrace (A lot of people
wanted it, that is why I wrote the program) (ASPHYXIA ... first with the
trainer goodies ;-)  aargh, sorry, had to be done ))

Thirdly, sorry about the problems with Tut 8! If you had all the
checking on, the tutorial would probably die on the first points. The
reason is this : in the first loop, we have DrawPoints then
RotatePoints. The variables used in DrawPoints are set in RotatePoints,
so if you put RotatePoints before DrawPoints, the program should work
fine. Alternatively, turn off error checking 8-)

Fourthly, I have had a surprisingly large number of people saying that
"I get this, like, strange '286 instructions not enabled' message!
What's wrong with your code, dude?"  To all of you, get into Pascal, hit
Alt-O (for options), hit enter and a 2 (for Enable 286 instructions). Hard
hey? Doesn't anyone EVER set up their version of Pascal?

Now, on to todays tutorial! 3D solids. That is what the people wanted,
that is what the people get! This tutorial is mainly on how to draw the
polygon on screen. For details on how the 3D stuff works, check out tut
8.



If you would like to contact me, or the team, there are many ways you
can do it : 1) Write a message to Grant Smith/Denthor/Asphyxia in private mail
                  on the ASPHYXIA BBS.
            2) Write to Denthor, EzE or Goth on Connectix.
            3) Write to :  Grant Smith
                           P.O.Box 270 Kloof
                           3640
                           Natal
            4) Call me (Grant Smith) at (031) 73 2129 (leave a message if you
                  call during varsity)
            5) Write to mcphail@beastie.cs.und.ac.za on InterNet, and
                  mention the word Denthor near the top of the letter.

NB : If you are a representative of a company or BBS, and want ASPHYXIA
       to do you a demo, leave mail to me; we can discuss it.
NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling
        quite lonely and want to meet/help out/exchange code with other demo
        groups. What do you have to lose? Leave a message here and we can work
        out how to transfer it. We really want to hear from you!



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? How to draw a polygon

Sounds easy enough, right? WRONG! There are many, many different ways to
go about this, and today I'll only be showing you one. Please don't take
what is written here as anything approaching the best method, it is just
here to get you on your way...

The procedure I will be using here is based on something most of us
learned in standard eight ... I think. I seem to recall doing something
like this in Mrs. Reids maths class all those years ago ;)

Take two points, x1,y1 and x2,y2. Draw them :

                  + (x1,y1)
                   \
                     \  <-- Point a somewhere along the line
                       \
                         + (x2,y2)

Right, so what we have to do is this : if we know the y-coord of a, what
is it's x-coord? To prove the method we will give the points random
values.

                 + (2,10)
                  \
                    \  <-- a.y = 12
                      \
                        +  (15,30)

Right. Simple enough problem. This is how we do it :
   (a.y-y1) = (12 - 10)  {to get a.y as though y1 was zero}
   *(x2-x1) = *(15 - 2)  {the total x-length of the line}
   /(y2-y1) = /(30 - 10) {the total y-length of the line}
        +x1 = +2         { to get the equation back to real coords}

So our equation is :  (a.y-y1)*(x2-x1)/(y2-y1)+x4    or
                      (12-10)*(15-2)/(30-10)+2
      which gives you :
                      2*13/20+2 = 26/20+2
                                = 3.3

That means that along the line with y=12, x is equal to 3.3. Since we
are not concerned with the decimal place, we replace the  /  with a div,
which in Pascal gives us an integer result, and is faster too. All well
and good, I hear you cry, but what does this have to do with life and
how it relates to polygons in general. The answer is simple. For each of
the four sides of the polygon we do the above test for each y line. We
store the smallest and the largest x values into separate variables for
each line, and draw a horizontal line between them. Ta-Dah! We have a
cool polygon!

For example : Two lines going down :
    
                +             +
               / <-x1     x2->|   <--For this y line
             /                |
           +                  +

Find x1 and x2 for that y, then draw a line between them. Repeat for all
y values.

Of course, it's not as simple as that. We have to make sure we only
check those y lines that contain the polygon (a simple min y, max y test
for all the points). We also have to check that the line we are
calculating actually extends as far as where our current y is (check
that the point is between both y's). We have to compare each x to see
weather it is smaller then the minimum x value so far, or bigger then
the maximum (the original x min is set as a high number, and the x max
is set as a small number). We must also check that we only draw to the
place that we can see ( 0-319 on the x ; 0-199 on the y (the size of the
MCGA screen))

To see how this looks in practice, have a look at the sample code
provided. (Mrs. Reid would probably kill me for the above explanation,
so when you learn it in school, split it up into thousands of smaller
equations to get the same answer ;))

Okay, that's it! What's that? How do you draw a vertical line? Thats
simple ...

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Drawing a vertical line

Right, this is a lot easier than drawing a normal line (Tut 5 .. I
think), because you stay on the same y value. So, what you do is you set
ES to the screen you want to write to, and get DI to the start of the
y-line (see earlier trainers for a description of how SEGMENT:OFFSET
works.

IN   : x1 , x2, y, color, where

           asm
             mov    ax,where
             mov    es,ax
             mov    di,y
             mov    ax,y
             shl    di,8   { di:=di*256 }
             shl    ax,6   { ax:=ax*64 }
             add    di,ax  { di := (y*256)+(y*64) := y*320 Faster then a
                             straight multiplication }

Right, now you add the first x value to get your startoff.
             add    di,x1
Move the color to store into ah and al
             mov    al,color
             mov    ah,al       { ah:=al:=color }
then get CX equal to how many pixels across you want to go
             mov    cx,x2
             sub    cx,x1   { cx:=x2-x1 }
Okay, as we all know, moving a word is a lot faster then moving a byte,
so we halve CX
             shr    cx,1    { cx:=cx/2 }
but what happens if CX was an odd number. After a shift, the value of
the last number is placed in the carry flag, so what we do is jump over
a single byte move if the carry flag is zero, or execute it if it is
one.
            jnc     @Start  { If there is no carry, jump to label Start }
            stosb           { ES:[DI]:=al ; increment DI }
        @Start :            { Label Start }
            rep     stosw   { ES:[DI]:=ax ; DI:=DI+2; repeat CX times }

Right, the finished product looks like this :

Procedure Hline (x1,x2,y:word;col:byte;where:word); assembler;
  { This draws a horizontal line from x1 to x2 on line y in color col }
asm
  mov   ax,where
  mov   es,ax
  mov   ax,y
  mov   di,ax
  shl   ax,8
  shl   di,6
  add   di,ax
  add   di,x1

  mov   al,col
  mov   ah,al
  mov   cx,x2
  sub   cx,x1
  shr   cx,1
  jnc   @start
  stosb
@Start :
  rep   stosw
end;

Done!

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
?  In closing

This 3D system is still not perfect. It needs to be faster, and now I
have also dumped the problem of face-sorting on you! Nyahahahaha!

           [ My sister and I were driving along the other day when she
               asked me, what would I like for my computer.
             I thought long and hard about it, and came up with the
               following hypothesis. When a girl gets a Barbie doll, she
               then wants the extra ballgown for the doll, then the
               hairbrush, and the car, and the house, and the friends
               etc.
             When a guy gets a computer, he wants the extra memory, the
               bigger hard drive, the maths co-pro, the better
               motherboard, the latest software, and the bigger monitor
               etc.
             I told my sister all of this, and finished up with : "So as
               you can see, computers are Barbie dolls for MEN!"
             She called me a chauvinist. And hit me. Hard.
                                                                   ]
                                                       - Grant Smith
                                                           19:24
                                                             26/2/94

See you next time!
  - Denthor

These fine BBS's carry the ASPHYXIA DEMO TRAINER SERIES : (alphabetical)

?????????????????????????????????????????????????????????????????
?BBS Name                  ?Telephone No.   ?Open ?Msg?File?Past?
?????????????????????????????????????????????????????????????????
?ASPHYXIA BBS #1           ?(031) 765-5312  ?ALL  ? * ? *  ? *  ?
?ASPHYXIA BBS #2           ?(031) 765-6293  ?ALL  ? * ? *  ? *  ?
?Connectix BBS             ?(031) 266-9992  ?ALL  ?   ? *  ? *  ?
?????????????????????????????????????????????????????????????????

Open = Open at all times or only A/H
Msg  = Available in message base
File = Available in file base
Past = Previous Parts available

Does no other BBS's ANYWHERE carry the trainer? Am I writing this for
three people who get it from one of these BBS's each week? Should I go
on? (Hehehehe ... I was pleased to note that Tut 8 was THE most
downloaded file from ASPHYXIA BBS last month ... )       


?????????????????????????????????????????????????????????????????????????????
? TUTPROG9.PAS ?
????????????????

{$X+}
USES Crt;

CONST VGA = $A000;
      maxpolys = 5;
      A : Array [1..maxpolys,1..4,1..3] of integer =
        (
         ((-10,10,0),(-2,-10,0),(0,-10,0),(-5,10,0)),
         ((10,10,0),(2,-10,0),(0,-10,0),(5,10,0)),
         ((-2,-10,0),(2,-10,0),(2,-5,0),(-2,-5,0)),
         ((-6,0,0),(6,0,0),(7,5,0),(-7,5,0)),
         ((0,0,0),(0,0,0),(0,0,0),(0,0,0))
        );  { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), }
            { (X2,Y2,Z2) ... for the 4 points of a poly }
     S : Array [1..maxpolys,1..4,1..3] of integer =
        (
         ((-10,-10,0),(10,-10,0),(10,-7,0),(-10,-7,0)),
         ((-10,10,0),(10,10,0),(10,7,0),(-10,7,0)),
         ((-10,1,0),(10,1,0),(10,-2,0),(-10,-2,0)),
         ((-10,-8,0),(-7,-8,0),(-7,0,0),(-10,0,0)),
         ((10,8,0),(7,8,0),(7,0,0),(10,0,0))
        );  { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), }
            { (X2,Y2,Z2) ... for the 4 points of a poly }
     P : Array [1..maxpolys,1..4,1..3] of integer =
        (
         ((-10,-10,0),(-7,-10,0),(-7,10,0),(-10,10,0)),
         ((10,-10,0),(7,-10,0),(7,0,0),(10,0,0)),
         ((-9,-10,0),(9,-10,0),(9,-7,0),(-9,-7,0)),
         ((-9,-1,0),(9,-1,0),(9,2,0),(-9,2,0)),
         ((0,0,0),(0,0,0),(0,0,0),(0,0,0))
        );  { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), }
            { (X2,Y2,Z2) ... for the 4 points of a poly }
     H : Array [1..maxpolys,1..4,1..3] of integer =
        (
         ((-10,-10,0),(-7,-10,0),(-7,10,0),(-10,10,0)),
         ((10,-10,0),(7,-10,0),(7,10,0),(10,10,0)),
         ((-9,-1,0),(9,-1,0),(9,2,0),(-9,2,0)),
         ((0,0,0),(0,0,0),(0,0,0),(0,0,0)),
         ((0,0,0),(0,0,0),(0,0,0),(0,0,0))
        );  { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), }
            { (X2,Y2,Z2) ... for the 4 points of a poly }
     Y : Array [1..maxpolys,1..4,1..3] of integer =
        (
         ((-7,-10,0),(0,-3,0),(0,0,0),(-10,-7,0)),
         ((7,-10,0),(0,-3,0),(0,0,0),(10,-7,0)),
         ((-2,-3,0),(2,-3,0),(2,10,0),(-2,10,0)),
         ((0,0,0),(0,0,0),(0,0,0),(0,0,0)),
         ((0,0,0),(0,0,0),(0,0,0),(0,0,0))
        );  { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), }
            { (X2,Y2,Z2) ... for the 4 points of a poly }
     X : Array [1..maxpolys,1..4,1..3] of integer =
        (
         ((-7,-10,0),(10,7,0),(7,10,0),(-10,-7,0)),
         ((7,-10,0),(-10,7,0),(-7,10,0),(10,-7,0)),
         ((0,0,0),(0,0,0),(0,0,0),(0,0,0)),
         ((0,0,0),(0,0,0),(0,0,0),(0,0,0)),
         ((0,0,0),(0,0,0),(0,0,0),(0,0,0))
        );  { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), }
            { (X2,Y2,Z2) ... for the 4 points of a poly }
     I : Array [1..maxpolys,1..4,1..3] of integer =
        (
         ((-10,-10,0),(10,-10,0),(10,-7,0),(-10,-7,0)),
         ((-10,10,0),(10,10,0),(10,7,0),(-10,7,0)),
         ((-2,-9,0),(2,-9,0),(2,9,0),(-2,9,0)),
         ((0,0,0),(0,0,0),(0,0,0),(0,0,0)),
         ((0,0,0),(0,0,0),(0,0,0),(0,0,0))
        );  { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), }
            { (X2,Y2,Z2) ... for the 4 points of a poly }


Type Point = Record
               x,y,z:real;                { The data on every point we rotate}
             END;
     Virtual = Array [1..64000] of byte;  { The size of our Virtual Screen }
     VirtPtr = ^Virtual;                  { Pointer to the virtual screen }


VAR Lines : Array [1..maxpolys,1..4] of Point;  { The base object rotated }
    Translated : Array [1..maxpolys,1..4] of Point; { The rotated object }
    Xoff,Yoff,Zoff:Integer;               { Used for movement of the object }
    lookup : Array [0..360,1..2] of real; { Our sin and cos lookup table }
    Virscr : VirtPtr;                     { Our first Virtual screen }
    Vaddr  : word;                        { The segment of our virtual screen}


{??????????????????????????????????????????????????????????????????????????}
Procedure SetMCGA;  { This procedure gets you into 320x200x256 mode. }
BEGIN
  asm
     mov        ax,0013h
     int        10h
  end;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure SetText;  { This procedure returns you to text mode.  }
BEGIN
  asm
     mov        ax,0003h
     int        10h
  end;
END;

{??????????????????????????????????????????????????????????????????????????}
Procedure Cls (Where:word;Col : Byte);
   { This clears the screen to the specified color }
BEGIN
     asm
        push    es
        mov     cx, 32000;
        mov     es,[where]
        xor     di,di
        mov     al,[col]
        mov     ah,al
        rep     stosw
        pop     es
     End;
END;

{??????????????????????????????????????????????????????????????????????????}
Procedure SetUpVirtual;
   { This sets up the memory needed for the virtual screen }
BEGIN
  GetMem (VirScr,64000);
  vaddr := seg (virscr^);
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure ShutDown;
   { This frees the memory used by the virtual screen }
BEGIN
  FreeMem (VirScr,64000);
END;


{??????????????????????????????????????????????????????????????????????????}
procedure flip(source,dest:Word);
  { This copies the entire screen at "source" to destination }
begin
  asm
    push    ds
    mov     ax, [Dest]
    mov     es, ax
    mov     ax, [Source]
    mov     ds, ax
    xor     si, si
    xor     di, di
    mov     cx, 32000
    rep     movsw
    pop     ds
  end;
end;


{??????????????????????????????????????????????????????????????????????????}
Procedure Pal(Col,R,G,B : Byte);
  { This sets the Red, Green and Blue values of a certain color }
Begin
   asm
      mov    dx,3c8h
      mov    al,[col]
      out    dx,al
      inc    dx
      mov    al,[r]
      out    dx,al
      mov    al,[g]
      out    dx,al
      mov    al,[b]
      out    dx,al
   end;
End;


{??????????????????????????????????????????????????????????????????????????}
Procedure Hline (x1,x2,y:word;col:byte;where:word); assembler;
  { This draws a horizontal line from x1 to x2 on line y in color col }
asm
  mov   ax,where
  mov   es,ax
  mov   ax,y
  mov   di,ax
  shl   ax,8
  shl   di,6
  add   di,ax
  add   di,x1

  mov   al,col
  mov   ah,al
  mov   cx,x2
  sub   cx,x1
  shr   cx,1
  jnc   @start
  stosb
@Start :
  rep   stosw
end;


{??????????????????????????????????????????????????????????????????????????}
Procedure DrawPoly(x1,y1,x2,y2,x3,y3,x4,y4:integer;color:byte;where:word);
  { This draw a polygon with 4 points at x1,y1 , x2,y2 , x3,y3 , x4,y4
    in color col }
var
  x:integer;
  mny,mxy:integer;
  mnx,mxx,yc:integer;
  mul1,div1,
  mul2,div2,
  mul3,div3,
  mul4,div4:integer;

begin
  mny:=y1; mxy:=y1;
  if y2<mny then mny:=y2;
  if y2>mxy then mxy:=y2;
  if y3<mny then mny:=y3;
  if y3>mxy then mxy:=y3;    { Choose the min y mny and max y mxy }
  if y4<mny then mny:=y4;
  if y4>mxy then mxy:=y4;

  if mny<0 then mny:=0;
  if mxy>199 then mxy:=199;
  if mny>199 then exit;
  if mxy<0 then exit;        { Verticle range checking }

  mul1:=x1-x4; div1:=y1-y4;
  mul2:=x2-x1; div2:=y2-y1;
  mul3:=x3-x2; div3:=y3-y2;
  mul4:=x4-x3; div4:=y4-y3;  { Constansts needed for intersection calc }

  for yc:=mny to mxy do
    begin
      mnx:=320;
      mxx:=-1;
      if (y4>=yc) or (y1>=yc) then
        if (y4<=yc) or (y1<=yc) then   { Check that yc is between y1 and y4 }
          if not(y4=y1) then
            begin
              x:=(yc-y4)*mul1 div div1+x4; { Point of intersection on x axis }
              if x<mnx then
                mnx:=x;
              if x>mxx then
                mxx:=x;       { Set point as start or end of horiz line }
            end;
      if (y1>=yc) or (y2>=yc) then
        if (y1<=yc) or (y2<=yc) then   { Check that yc is between y1 and y2 }
          if not(y1=y2) then
            begin
              x:=(yc-y1)*mul2 div div2+x1; { Point of intersection on x axis }
              if x<mnx then
                mnx:=x;
              if x>mxx then
                mxx:=x;       { Set point as start or end of horiz line }
            end;
      if (y2>=yc) or (y3>=yc) then
        if (y2<=yc) or (y3<=yc) then   { Check that yc is between y2 and y3 }
          if not(y2=y3) then
            begin
              x:=(yc-y2)*mul3 div div3+x2; { Point of intersection on x axis }
              if x<mnx then
                mnx:=x;
              if x>mxx then
                mxx:=x;       { Set point as start or end of horiz line }
            end;
      if (y3>=yc) or (y4>=yc) then
        if (y3<=yc) or (y4<=yc) then   { Check that yc is between y3 and y4 }
          if not(y3=y4) then
            begin
              x:=(yc-y3)*mul4 div div4+x3; { Point of intersection on x axis }
              if x<mnx then
                mnx:=x;
              if x>mxx then
                mxx:=x;       { Set point as start or end of horiz line }
            end;
      if mnx<0 then
        mnx:=0;
      if mxx>319 then
        mxx:=319;          { Range checking on horizontal line }
      if mnx<=mxx then
        hline (mnx,mxx,yc,color,where);   { Draw the horizontal line }
    end;
  end;



{??????????????????????????????????????????????????????????????????????????}
Function rad (theta : real) : real;
  {  This calculates the degrees of an angle }
BEGIN
  rad := theta * pi / 180
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure SetUpPoints;
  { This creates the lookup table }
VAR loop1,loop2:integer;
BEGIN
  For loop1:=0 to 360 do BEGIN
    lookup [loop1,1]:=sin (rad (loop1));
    lookup [loop1,2]:=cos (rad (loop1));
  END;
END;


{??????????????????????????????????????????????????????????????????????????}
Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
  { This puts a pixel on the screen by writing directly to memory. }
BEGIN
  Asm
    mov     ax,[where]
    mov     es,ax
    mov     bx,[X]
    mov     dx,[Y]
    mov     di,bx
    mov     bx, dx                  {; bx = dx}
    shl     dx, 8
    shl     bx, 6
    add     dx, bx                  {; dx = dx + bx (ie y*320)}
    add     di, dx                  {; finalise location}
    mov     al, [Col]
    stosb
  End;
END;



{??????????????????????????????????????????????????????????????????????????}
Procedure RotatePoints (X,Y,Z:Integer);
  { This rotates object lines by X,Y and Z; then places the result in
    TRANSLATED }
VAR loop1,loop2:integer;
    temp:point;
BEGIN
  For loop1:=1 to maxpolys do BEGIN
    For loop2:=1 to 4 do BEGIN
      temp.x:=lines[loop1,loop2].x;
      temp.y:=lookup[x,2]*lines[loop1,loop2].y - lookup[x,1]*lines[loop1,loop2].z;
      temp.z:=lookup[x,1]*lines[loop1,loop2].y + lookup[x,2]*lines[loop1,loop2].z;

      translated[loop1,loop2]:=temp;

      If y>0 then BEGIN
        temp.x:=lookup[y,2]*translated[loop1,loop2].x - lookup[y,1]*translated[loop1,loop2].y;
        temp.y:=lookup[y,1]*translated[loop1,loop2].x + lookup[y,2]*translated[loop1,loop2].y;
        temp.z:=translated[loop1,loop2].z;
        translated[loop1,loop2]:=temp;
      END;

      If z>0 then BEGIN
        temp.x:=lookup[z,2]*translated[loop1,loop2].x + lookup[z,1]*translated[loop1,loop2].z;
        temp.y:=translated[loop1,loop2].y;
        temp.z:=-lookup[z,1]*translated[loop1,loop2].x + lookup[z,2]*translated[loop1,loop2].z;
        translated[loop1,loop2]:=temp;
      END;
    END;
  END;
END;



{??????????????????????????????????????????????????????????????????????????}
Procedure DrawPoints;
  { This draws the translated object to the virtual screen }
VAR loop1:Integer;
    nx,ny,nx2,ny2,nx3,ny3,nx4,ny4:integer;
    temp:integer;
BEGIN
  For loop1:=1 to maxpolys do BEGIN
    If (translated[loop1,1].z+zoff<0) and (translated[loop1,2].z+zoff<0) and
       (translated[loop1,3].z+zoff<0) and (translated[loop1,4].z+zoff<0) then BEGIN
      temp:=round (translated[loop1,1].z+zoff);
      nx :=round (256*translated[loop1,1].X) div temp+xoff;
      ny :=round (256*translated[loop1,1].Y) div temp+yoff;
      temp:=round (translated[loop1,2].z+zoff);
      nx2:=round (256*translated[loop1,2].X) div temp+xoff;
      ny2:=round (256*translated[loop1,2].Y) div temp+yoff;
      temp:=round (translated[loop1,3].z+zoff);
      nx3:=round (256*translated[loop1,3].X) div temp+xoff;
      ny3:=round (256*translated[loop1,3].Y) div temp+yoff;
      temp:=round (translated[loop1,4].z+zoff);
      nx4:=round (256*translated[loop1,4].X) div temp+xoff;
      ny4:=round (256*translated[loop1,4].Y) div temp+yoff;
      drawpoly (nx,ny,nx2,ny2,nx3,ny3,nx4,ny4,13,vaddr);
    END;
  END;
END;



{??????????????????????????????????????????????????????????????????????????}
Procedure MoveAround;
  { This is the main display procedure. Firstly it brings the object towards
    the viewer by increasing the Zoff, then passes control to the user }
VAR deg,loop1,loop2:integer;
    ch:char;

  Procedure Whizz (sub:boolean);
  VAR loop1:integer;
  BEGIN
    For loop1:=-64 to -5 do BEGIN
      zoff:=loop1*8;
      if sub then xoff:=xoff-7 else xoff:=xoff+7;
      RotatePoints (deg,deg,deg);
      DrawPoints;
      flip (vaddr,vga);
      Cls (vaddr,0);
      deg:=(deg+5) mod 360;
    END;
  END;

BEGIN
  deg:=0;
  ch:=#0;
  Yoff:=100;
  Xoff:=350;
  Cls (vaddr,0);
  For loop1:=1 to maxpolys do
    For loop2:=1 to 4 do BEGIN
      Lines [loop1,loop2].x:=a [loop1,loop2,1];
      Lines [loop1,loop2].y:=a [loop1,loop2,2];
      Lines [loop1,loop2].z:=a [loop1,loop2,3];
    END;
  Whizz (TRUE);

  For loop1:=1 to maxpolys do
    For loop2:=1 to 4 do BEGIN
      Lines [loop1,loop2].x:=s [loop1,loop2,1];
      Lines [loop1,loop2].y:=s [loop1,loop2,2];
      Lines [loop1,loop2].z:=s [loop1,loop2,3];
    END;
  Whizz (FALSE);

  For loop1:=1 to maxpolys do
    For loop2:=1 to 4 do BEGIN
      Lines [loop1,loop2].x:=p [loop1,loop2,1];
      Lines [loop1,loop2].y:=p [loop1,loop2,2];
      Lines [loop1,loop2].z:=p [loop1,loop2,3];
    END;
  Whizz (TRUE);

  For loop1:=1 to maxpolys do
    For loop2:=1 to 4 do BEGIN
      Lines [loop1,loop2].x:=h [loop1,loop2,1];
      Lines [loop1,loop2].y:=h [loop1,loop2,2];
      Lines [loop1,loop2].z:=h [loop1,loop2,3];
    END;
  Whizz (FALSE);

  For loop1:=1 to maxpolys do
    For loop2:=1 to 4 do BEGIN
      Lines [loop1,loop2].x:=y [loop1,loop2,1];
      Lines [loop1,loop2].y:=y [loop1,loop2,2];
      Lines [loop1,loop2].z:=y [loop1,loop2,3];
    END;
  Whizz (TRUE);

  For loop1:=1 to maxpolys do
    For loop2:=1 to 4 do BEGIN
      Lines [loop1,loop2].x:=x [loop1,loop2,1];
      Lines [loop1,loop2].y:=x [loop1,loop2,2];
      Lines [loop1,loop2].z:=x [loop1,loop2,3];
    END;
  Whizz (FALSE);

  For loop1:=1 to maxpolys do
    For loop2:=1 to 4 do BEGIN
      Lines [loop1,loop2].x:=i [loop1,loop2,1];
      Lines [loop1,loop2].y:=i [loop1,loop2,2];
      Lines [loop1,loop2].z:=i [loop1,loop2,3];
    END;
  Whizz (TRUE);

  For loop1:=1 to maxpolys do
    For loop2:=1 to 4 do BEGIN
      Lines [loop1,loop2].x:=a [loop1,loop2,1];
      Lines [loop1,loop2].y:=a [loop1,loop2,2];
      Lines [loop1,loop2].z:=a [loop1,loop2,3];
    END;
  Whizz (FALSE);

  cls (vaddr,0);
  cls (vga,0);
  Xoff := 160;

  Repeat
    if keypressed then BEGIN
      ch:=upcase (Readkey);
      Case ch of 'A' : zoff:=zoff+5;
                 'Z' : zoff:=zoff-5;
                 ',' : xoff:=xoff-5;
                 '.' : xoff:=xoff+5;
                 'S' : yoff:=yoff-5;
                 'X' : yoff:=yoff+5;
      END;
    END;
    DrawPoints;
    flip (vaddr,vga);
    cls (vaddr,0);
    RotatePoints (deg,deg,deg);
    deg:=(deg+5) mod 360;
  Until ch=#27;
END;


BEGIN
  SetUpVirtual;
  clrscr;
  Writeln ('Hello there! Varsity has begun once again, so it is once again');
  Writeln ('back to the grindstone ;-) ... anyway, this tutorial is, by');
  Writeln ('popular demand, on poly-filling, in relation to 3-D solids.');
  Writeln;
  Writeln ('In this program, the letters of ASPHYXIA will fly past you. As you');
  Writeln ('will see, they are solid, not wireframe. After the last letter has');
  Writeln ('flown by, a large A will be left in the middle of the screen.');
  Writeln;
  Writeln ('You will be able to move it around the screen, and you will notice');
  Writeln ('that it may have bits only half on the screen, i.e. clipping is');
  Writeln ('perfomed. To control it use the following : "A" and "Z" control the Z');
  Writeln ('movement, "," and "." control the X movement, and "S" and "X"');
  Writeln ('control the Y movement. I have not included rotation control, but');
  Writeln ('it should be easy enough to put in yourself ... if you have any');
  Writeln ('hassles, leave me mail.');
  Writeln;
  Writeln ('I hope this is what you wanted...leave me mail for new ideas.');
  writeln;
  writeln;
  Write ('  Hit any key to contine ...');
  Readkey;
  SetMCGA;
  SetUpPoints;
  MoveAround;
  SetText;
  ShutDown;
  Writeln ('All done. This concludes the ninth sample program in the ASPHYXIA');
  Writeln ('Training series. You may reach DENTHOR under the names of GRANT');
  Writeln ('SMITH/DENTHOR/ASPHYXIA on the ASPHYXIA BBS. I am also an avid');
  Writeln ('Connectix BBS user, and occasionally read RSAProg.');
  Writeln ('The numbers are available in the main text. You may also write to me at:');
  Writeln ('             Grant Smith');
  Writeln ('             P.O. Box 270');
  Writeln ('             Kloof');
  Writeln ('             3640');
  Writeln ('I hope to hear from you soon!');
  Writeln; Writeln;
  Write   ('Hit any key to exit ...');
  Readkey;
END.

                   ?????????????????????????????????
                   ?         W E L C O M E         ?
                   ?  To the VGA Trainer Program   ? ?
                   ?              By               ? ?
                   ?      DENTHOR of ASPHYXIA      ? ? ?
                   ????????????????????????????????? ? ?
                     ????????????????????????????????? ?
                       ?????????????????????????????????

                           --==[ PART 10 ]==--



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Introduction

Wow! The trainer has finally reached part 10! This will also be the
first part introduced simultaneously to local BBS's and the INTERNET at
the same time! Yes folks, I put up a copy of previous tutorials onto
various ftp sites, and awaited the flames saying that the net.gurus
already knew this stuff, and why was I wasting disk space! The flames
did not appear (well, except for one), and I got some messages saying
keep it up, so from now on I will upload all future trainers to ftp
sites too (wasp.eng.ufl.edu , cs.uwp.edu etc.). I will also leave a
notice in the USENET groups comp.lang.pascal and comp.sys.ibm.pc.demos
when a new part is finished (Until enough people say stop ;-))

I can also be reached at my new E-Mail address,
                 smith9@batis.bis.und.ac.za

Well, this tutorial is on Chain-4. When asked to do a trainer on
Chain-4, I felt that I would be walking on much travelled ground (I have
seen numerous trainers on the subject), but the people who asked me said
that they hadn't seen any, so could I do one anyway? Who am I to say no?

The sample program attached isn't that great, but I am sure that all you
people out there can immediately see the potential that Chain-4 holds.


If you would like to contact me, or the team, there are many ways you
can do it : 1) Write a message to Grant Smith/Denthor/Asphyxia in private mail
                  on the ASPHYXIA BBS.
            2) Write to Denthor, EzE or Goth on Connectix.
            3) Write to :  Grant Smith
                           P.O.Box 270 Kloof
                           3640
                           Natal
                           South Africa
            4) Call me (Grant Smith) at (031) 73 2129 (leave a message if you
                  call during varsity). Call +27-31-73-2129 if you call
                  from outside South Africa. (It's YOUR phone bill ;-))
            5) Write to smith9@batis.bis.und.ac.za in E-Mail.

NB : If you are a representative of a company or BBS, and want ASPHYXIA
       to do you a demo, leave mail to me; we can discuss it.
NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling
        quite lonely and want to meet/help out/exchange code with other demo
        groups. What do you have to lose? Leave a message here and we can work
        out how to transfer it. We really want to hear from you!



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? What is Chain-4?

You people out there all have at least 256k vga cards. Most of you have
512k vga cards, and some have 1MB vga cards. But what you see on your
screen, as discussed in previous trainers, is 64k of data! What happened
to the other 192k??? Chain-4 is a method of using all 256k at one time.

The way this is done is simple. 1 screen = 64k. 64k * 4 = 256k.
Therefore, chain-4 allows you to write to four screens, while displaying
one of them. You can then move around these four screens to see the data
on them. Think of the Chain-4 screen as a big canvas. The viewport,
the bit you see out of, is a smaller rectangle which can be anywhere
over the bigger canvas.

     +----------------------------+ Chain-4 screen
     |          +--+              |
     |          |  | <- Viewport  |
     |          +--+              |
     |                            |
     +----------------------------+


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? The size of the chain-4 screen

The Chain-4 screen, can be any size that adds up to 4 screens.

For example, it can be 4 screens across and one screen down, or one
screen across and 4 screens down, or two screens across and two screens
down, and any size in between.

In the sample program, the size is a constant. The size * 8 is how many
pixels across there are on the chain-4 screen, ie
   Size = 40   = 320 pixels across = 1 screen across, 4 screens down
   Size = 80   = 640 pixels across = 2 screens across, 2 screens down
etc.

We need to know the size of the screen for almost all dealings with the
Chain-4 screen, for obvious reasons.


=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Layout of the chain-4 screen, and accessing it

If you will remember all the way back to Part 1 of this series, I
explained that the memory layout of the MCGA screen is linear. Ie, the
top left hand pixel was pixel zero, the one to the right of it was
number one, the next one was number two etc. With Chain-4, things are
very different.

Chain-4 gets the 4 screens and chains them together (hence the name :)).
Each screen has a different plane value, and must be accessed
differently. The reason for this is that a segment of memory is only 64k
big, so that we could not fit the entire Chain-4 screen into one
segment.

All Chain-4 screens are accessed from $a000, just like in MCGA mode.
What we do is, before we write to the screen, find out what plane we are
writing to, set that plane, then plot the pixel. Here is how we find out
how far in to plot the pixel and what plane it is on :

 Instead of the linear model of MCGA mode, ie :
        ?????????????????????????????????????
        ?00?01?02?03?04?05?06?07?08?09?10?11? ...

 Each plane of the Chain-4 screen accesses the memory in this way :

       Plane 0 :
        ?????????????????????????????????????
        ?00?  ?  ?  ?01?  ?  ?  ?02?  ?  ?  ? ...

       Plane 1 :
        ?????????????????????????????????????
        ?  ?00?  ?  ?  ?01?  ?  ?  ?02?  ?  ? ...

       Plane 2 :
        ?????????????????????????????????????
        ?  ?  ?00?  ?  ?  ?01?  ?  ?  ?02?  ? ...

       Plane 3 :
        ?????????????????????????????????????
        ?  ?  ?  ?00?  ?  ?  ?01?  ?  ?  ?02? ...

In this way, by choosing the right plane to write to, we can access all
of the 256k of memory available to us. The plane that we write to can
easily be found by the simple calculation of  x mod 4, and the x
coordinate is also found by  x div 4. We work out our y by multiplying
it by the size of our chain-4 screen.

NOTE : It is possible to write to all four planes at once by setting the
       correct port values.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? Uses of Chain-4

The uses of Chain-4 are many. One could write data to one screen, then
flip to it (the move_to command is almost instantaneous). This means
that 64k of memory does not need to be set aside for a virtual screen,
you are using the vga cards memory instead!

Scrolling is much easier to code for in Chain-4 mode.

It is possible to "tweak" the mode into other resolutions. In our demo,
our vectors were in 320x240 mode, and our dot vectors were in 320x400
mode.

The main disadvantage of chain-4 as I see it is the plane swapping,
which can be slow. With a bit of clever coding however, these can be
kept down to a minimum.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? The sample programs

The first sample program is GFX.PAS. This is a until in which I have
placed most of our routines from previous tuts. All the procedures and
variables you can see under the INTERFACE section can be used in any
program with GFX in the USES clause. In other words, I could do this :

USES GFX,crt;

BEGIN
  Setupvirtual;
  cls (vaddr,0);
  Shutdown;
END.

This program would compile perfectly. What I suggest you do is this :
Rename the file to a name that suites you (eg your group name), change
the first line of the unit to that name, then add all useful procedures
etc. to the unit. Make it grow :-).

The second file is the sample program (note the USES GFX,crt; up near
the top!). The program is easy to understand and is documented. The bit
that I want to draw your attention to is the constant, BIT. Because I
am distributing this file to many places in text form, not binary form,
I could not just add a .CEL file with the program. So what I did was
write some text in one color then saved it as a .CEL . I then wrote a
ten line program that did the following : Moving from left to right, it
counted how many pixels were of color zero, then saved the byte value to
an array. When it came across color one, is counted for how long that
went on then saved the byte value and saved it to an array and so on.
When it was finished, I converted the array into a text file in the
CONST format. Not too cunning, but I thought I had better explain it ;-)

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
? In closing

There are other documents and sample programs available on Chain-4 and
it's like : Try XLIB for one...

Finally! Some BBS's have joined my BBS list! (Okay, only two new ones,
but it's a start ;-)) All you international BBS's! If you will regularly
download the tuts from an FTP site, give me your names!

I own a car. The car's name is Bob. A few days ago, Bob was in an
accident, and now has major damage to his front. Knowing insurance, I
probably won't get much, probably nothing (the other guy wasn't insured,
and I am only 18 :( ). I will probably have to find work in order to pay
for my repairs. The point to this meandering is this : I am upset, so if
you think you are getting a quote, you can just forget it.

Oh, well. Life goes on!

See you next time,
  - Denthor

These fine BBS's carry the ASPHYXIA DEMO TRAINER SERIES : (alphabetical)

?????????????????????????????????????????????????????????????????
?BBS Name                  ?Telephone No.   ?Open ?Msg?File?Past?
?????????????????????????????????????????????????????????????????
?ASPHYXIA BBS #1           ?(031) 765-5312  ?ALL  ? * ? *  ? *  ?
?ASPHYXIA BBS #2           ?(031) 765-6293  ?ALL  ? * ? *  ? *  ?
?Connectix BBS             ?(031) 266-9992  ?ALL  ?   ? *  ? *  ?
?POP!                      ?(012) 661-1257  ?ALL  ?   ? *  ? *  ?
?Pure Surf BBS             ?(031) 561-5943  ?A/H  ?   ? *  ? *  ?
?????????????????????????????????????????????????????????????????

For international users : If you live outside the Republic of South
Africa, do the following : Dial +27, dont dial the first 0, but dial
the rest of the number. Eg, for the ASPHYXIA BBS : +27-31-765-5312

Open = Open at all times or only A/H
Msg  = Available in message base
File = Available in file base
Past = Previous Parts available


?????????????????????????????????????????????????????????????????????????????
? C4TUT.PAS ?
?????????????

(*
Well folks, here it is - the long awaited for Chain-4 trainer. The
routines are commented so I'm not going to say too much more here,
except a few things.

1: If ya don't understand this (not suprising its bloody cryptic!)
   then if ur serious go out and buy - Programming the EGA & VGA Cards
   I don't know who the book is by, so don't ask. Perhaps you know Greg?

2: The code is unoptimised. I wrote it specifically for this conf. and
   I'm buggered if I'm gonna give out my wholely (sp? ahh stuff it :-))
   optimised code. If you want it faster, OPTIMISE IT!!
   HINT: Its faster to load ax, with a low byte/high byte combination
         and out a word instead of a byte at a time. If u don't know
         what I'm talking about, too bad :-)

3: If you use/like/whatever this code, please give Asphyxia a mention.
   It wos bloody hard work figuring out how all this cr*p works, we
   couldn't have done it with out a little guidence (thanx Gregie Poo).

4: LiveWire got interested in the whole tut/trainer idea and MAY be
   putting together a doc on how the whole thing works, including
   Pel-Panning which I haven't included here.


5: Good luck with the code, and if you write anything with it, I'd
   appreciate having a look at it :-). Feel free to direct any comments
   about the code to me in this conf. Or at one of the contact addresses
   given in the code.


l8rs
EzE / Asphyxia



--------------------------------=[ Cut Here ]=-------------------------

{$X+,G+}
Program Chain4_Tut;
Uses
   Crt;


Const
   Size : Byte = 80;


Var Loop : Integer;



Procedure Init_C4; Assembler;
Asm
   mov   ax, 0013h
   int   10h               { set up bios initially for 13h            }

   mov   dx, 03c4h         { Sequencer Address Register               }
   mov   al, 4             { Index 4 - Memory mode                    }
   out   dx, al            { select it.                               }
   inc   dx                { 03c5h - here we set the mem mode.        }
   in    al, dx            { get whats already inside the reg         }
   and   al, 11110111b     { un-set 4th bit - chain4                  }
   out   dx, al

   mov   dx, 3d4h
   mov   al, 13h           { Offset Register - allocates amt. mem for }
   out   dx, al            { 1 displayable line as - length div 8, so }
   inc   dx                { we use 80 (80*8) = 640 = 2 pages across  }
   mov   al, [Size]        { and cause of chain-4 i.e. 256k display   }
   out   dx, al            { mem, 2 pages down for four pages         }

                           { NOTE: setting AL above to 40 selects 1   }
                           { page across and four down (nice for      }
                           { 1942 type scrolling games) and setting   }
                           { AL to 160 selects 4 pages across and 1   }
                           { down, nice for horizontal scrolling      }

End;



Procedure Cls_C4; Assembler;
Asm
   mov   dx, 03c4h         { 03c4h                                    }
   mov   al, 2             { Map Mask Register                        }
   out   dx, al
   inc   dx
   mov   al, 00001111b     { Select all planes to write to            }
   out   dx, al            { Doing this to clear all planes at once   }

   mov   ax, 0a000h
   mov   es, ax
   xor   di, di            { set es:di = Screen Mem                   }
   mov   ax, 0000h         { colour to put = black                    }
   mov   cx, 32768         { 32768 (words) *2 = 65536 bytes - vga mem }
   cld
   rep   stosw             { clear it                                 }
End;



Procedure PutPixel_C4(X, Y : Integer; Col : Byte); Assembler;
Asm
   mov   ax, [Y]           { Y val multiplied by...                   }
   xor   bx, bx
   mov   bl, [Size]        { Size....                                 }
   shl   bx, 1             { *2 - just 'cause! (I can't remember why!)}
   mul   bx
   mov   bx, ax

   mov   ax, [X]
   mov   cx, ax
   shr   ax, 2
   add   bx, ax            { add X val div 4 (four planes)            }

   and   cx, 00000011b     { clever way of finding x mod 4, i.e.      }
   mov   dx, 03c4h         { which plane we're in.                    }
   mov   al, 2             { then use 03c4h index 2 - write plane sel.}
   out   dx, al            { to set plane to write to.                }
   mov   al, 1             { plane to write to = 1 shl (X mod 4)      }
   shl   al, cl
   inc   dx
   out   dx, al

   mov   ax, 0a000h
   mov   es, ax
   mov   al, [Col]
   mov   es: [bx], al      { then write pixel.                        }
End;


Function GetPixel_C4(X, Y : Integer): Byte; Assembler;
Asm
   mov   ax, [Y]           { Y val multiplied by...                   }
   xor   bx, bx
   mov   bl, [Size]        { Size....                                 }
   shl   bx, 1             { *2 - just 'cause! (I can't remember why!)}
   mul   bx
   mov   bx, ax

   mov   ax, [X]
   mov   cx, ax
   shr   ax, 2
   add   bx, ax            { add X val div 4 (four planes)            }

   and   cx, 00000011b     { clever way of finding x mod 4, i.e.      }
   mov   dx, 03c4h         { which plane we're in.                    }
   mov   al, 4h            { then use 03c4h index 4 - read plane sel. }
   out   dx, al            { to set plane to read from.               }
   mov   al, cl            { Plane to read from = X mod 4             }
   inc   dx
   out   dx, al

   mov   ax, 0a000h
   mov   es, ax
   mov   al, es: [bx]      { then return pixel read                   }
End;



Procedure MoveScr_C4(X,Y : Integer); Assembler;
Asm
   mov   ax, [Y]           { Y val multiplied by...                   }
   xor   bx, bx
   mov   bl, [Size]        { Size....                                 }
   shl   bx, 1             { *2 - just 'cause! (I can't remember why!)}
   mul   bx
   mov   bx, ax

   add   bx, [X]           { Add X val                                }

   mov   dx, 03d4h
   mov   al, 0ch           { CRTC address reg.                        }
   out   dx, al            { Start Address High Reg.                  }
   inc   dx
   mov   al, bh            { send high byte of start address.         }
   out   dx, al

   dec   dx
   mov   al, 0dh           { Start Address Low Reg.                   }
   out   dx, al
   inc   dx
   mov   al, bl            { send low byte of start address.          }
   out   dx, al

End;


Procedure SetText; Assembler;
Asm
   mov   ax, 0003h
   int   10h
End;

Procedure Creds;
Begin
   SetText;
   While KeyPressed do ReadKey;

   Asm
      mov   ah, 1
      mov   ch, 1
      mov   cl, 0
      int   10h
   End;

   WriteLn('Chain-4 Trainer...');
   WriteLn('By EzE of Asphyxia.');
   WriteLn;
   WriteLn('Contact Us on ...');
   WriteLn;
   WriteLn;
   WriteLn('the Asphyxia BBS (031) - 7655312');
   WriteLn;
   WriteLn('Email :       eze@');
   WriteLn('         asphyxia@');
   WriteLn('          edwards@');
   WriteLn('           bailey@');
   WriteLn('          mcphail@');
   WriteLn('                  beastie.cs.und.ac.za');
   WriteLn;
   WriteLn('or  peter.edwards@datavert.co.za');
   WriteLn;
   WriteLn('Write me snail-mail at...');
   WriteLn('P.O. Box 2313');
   WriteLn('Hillcrest');
   WriteLn('Natal');
   WriteLn('3650');
   Asm
      mov   ah, 1
      mov   ch, 1
      mov   cl, 0
      int   10h
   End;

End;




Begin
   Init_C4;
   Cls_C4;
   Repeat
      Putpixel_C4(Random(320),Random(200),Random(256)+1);
   Until KeyPressed;
   For Loop := 0 to 80 do
   begin
      MoveScr_C4(0,Loop);
      Delay(10);
   End;
   ReadKey;
   Loop := GetPixel_C4(100,100);
   Creds;
   WriteLn('Colour at location X:100, Y:100 was: ',Loop);
End.

--------------------------------=[ Cut Here ]=-------------------------

                      ??????????????????????????
                      ? INTRODUCTION TO MODE X ?
                      ??????????????????????????

                 By Robert Schmidt <robert@stud.unit.no>



?????????????????????????????????????????????????????????????????????????????
? XINTRO18.TXT ?
????????????????

Title:          INTRODUCTION TO MODE X

Version:        1.8

Author:         Robert Schmidt <robert@stud.unit.no>

Copyright:      (C) 1993 of Ztiff Zox Softwear - refer to Status below.

Last revision:  25-Nov-93 (Modified for the PCGPE 17-Apr-94)

Figures:        1. M13ORG - memory organization in mode 13h
                2. MXORG - memory organization in unchained modes
                (Both files are appended to the end of this document)

                The figures are available as 7-bit ASCII text (ASC) files.

Status:         This article, its associated figures and source listings
                named above, are all donated to the public domain.
                Do with it whatever you like, but give credit where
                credit is due.

                The standard disclaimer applies.

Index:          0. ABSTRACT
                1. INTRODUCTION TO THE VGA AND ITS 256-COLOR MODE
                2. GETTING MORE PAGES AND PUTTING YOUR FIRST PIXEL
                3. THE ROAD FROM HERE
                4. BOOKS ON THE SUBJECT
                5. BYE - FOR NOW


0. ABSTRACT

This text gives a fairly basic, yet technical, explanation to what, why
and how Mode X is.  It first tries to explain the layout of the VGA
memory and the shortcomings of the standard 320x200 256-color mode,
then gives instructions on how one can progress from mode 13h to a
multipage, planar 320x200 256-color mode, and from there to the
quasi-standard 320x240 mode, known as Mode X.

A little experience in programming the standard VGA mode 13h
(320x200 in 256 colors) is assumed.  Likewise a good understanding of
hexadecimal notation and the concepts of segments and I/O ports is
assumed.  Keep a VGA reference handy, which at least should have
definitions of the VGA registers at bit level.

Throughout the article, a simple graphics library for unchained (planar)
256-color modes is developed.  The library supports the 320x200 and
320x240 modes, active and visible pages, and writing and reading
individual pixels.


1. INTRODUCTION TO THE VGA AND ITS 256-COLOR MODE

Since its first appearance on the motherboards of the IBM PS/2 50, 60
and 80 models in 1987, the Video Graphics Array has been the de facto
standard piece of graphics hardware for IBM and compatible personal
computers.  The abbreviation, VGA, was to most people synonymous with
acceptable resolution (640x480 pixels), and a stunning rainbow of colors
(256 from a palette of 262,144), at least compared to the rather gory
CGA and EGA cards.

Sadly, to use 256 colors, the VGA BIOS limited the users to 320x200
pixels, i.e. the well-known mode 13h.  This mode has one good and one
bad asset.  The good one is that each one of the 64,000 pixels is easily
addressable in the 64 Kb video memory segment at 0A000h.  Simply calculate
the offset using this formula:

offset = (y * 320) + x;

Set the byte at this address (0A000h:offset) to the color you want, and
the pixel is there.  Reading a pixel is just as simple: just read the
corresponding byte.  This was heaven, compared to the havoc of planes and
masking registers needed in 16-color modes.  Suddenly, the distance from a
graphics algorithm on paper to an implemented graphics routine in assembly
was cut down to a fraction.  The results were impressively fast, too!

The bad asset is that mode 13h is also limited to only one page, i.e.
the VGA can hold only one screenful at any one time (plus 1536 pixels, or
about four lines).  Most 16-color modes let the VGA hold more than one page,
and this enables you to show one of the pages to the user, while drawing on
another page in the meantime.  Page flipping is an important concept in making
flicker free animations.  Nice looking and smooth scrolling is also almost
impossible in mode 13h using plain VGA hardware.

Now, the alert reader might say: "Hold on a minute!  If mode 13h enables
only one page, this means that there is memory for only one page.  But I
know for a fact that all VGAs have at least 256 Kb RAM, and one 320x200
256-color page should consume only 320*200=64000 bytes, which is less
than 64 Kb.  A standard VGA should room a little more than four 320x200
pages!"  Quite correct, and to see how the BIOS puts this limitation on
mode 13h, I'll elaborate a little on the memory organization of the VGA.

The memory is separated into four bit planes.  The reason for this stems
from the EGA, where graphics modes were 16-color.  Using bit planes, the
designers chose to let each pixel on screen be addressable by a single
bit in a single byte in the video segment.  Assuming the palette has
not been modified from the default, each plane represent one of the EGA
primary colors: red, green, blue and intensity.  When modifying the bit
representing a pixel, the Write Plane Enable register is set to the
wanted color.  Reading is more complex and slower, since you can
only read from a single plane at a time, by setting the Read Plane
Select register.  Now, since each address in the video segment can
access 8 pixels, and there are 64 Kb addresses, 8 * 65,536 = 524,288
16-color pixels can be accessed.  In a 320x200 16-color mode, this makes
for about 8 (524,288/(320*200)) pages, in 640x480 you get nearly 2
(524,288/(640*480)) pages.

In a 256-color mode, the picture changes subtly.  The designers decided
to fix the number of bit planes to 4, so extending the logic above to 8
planes and 256 colors does not work.  Instead, one of their goals was to
make the 256-color mode as easily accessible as possible.  Comparing the
8 pixels/address in 16-color modes to the 1-to-1 correspondence of
pixels and addresses of mode 13h, one can say that they have
succeeded, but at a certain cost.  For reasons I am not aware of, the
designers came up with the following effective, but memory-wasting
scheme:

The address space of mode 13h is divided evenly across the four bit
planes.  When an 8-bit color value is written to a 16-bit address in the
VGA segment, a bit plane is automatically selected by the 2 least
significant bits of the address.  Then all 8 bits of the data is written
to the byte at the 16-bit address in the selected bitplane (have a look at
figure 1).  Reading works exactly the same way.  Since the bit planes are so
closely tied to the address, only every fourth byte in the video memory is
accessible, and 192 Kb of a 256 Kb VGA go to waste.  Eliminating the
need to bother about planes sure is convenient and beneficial, but to
most people the loss of 3/4 of the total VGA memory sounds just hilarious.

To accomodate this new method of accessing video memory, the VGA
designers introduced a new configuration bit called Chain-4, which
resides as bit number 3 in index 4 of the Sequencer.  In 16-color modes,
the default state for this bit is off (zero), and the VGA operates as
described earlier.  In the VGA's standard 256-color mode, mode 13h, this
bit is turned on (set to one), and this turns the tieing of bit
planes and memory address on.

In this state, the bit planes are said to be chained together, thus mode
13h is often called a _chained mode_.

Note that Chain-4 in itself is not enough to set a 256-color mode -
there are other registers which deals with the other subtle changes in
nature from 16 to 256 colors.  But, as we now will base our work with
mode X on mode 13h, which already is 256-color, we won't bother about
these for now.



2. GETTING MORE PAGES AND PUTTING YOUR FIRST PIXEL

The observant reader might at this time suggest that clearing the
Chain-4 bit after setting mode 13h will give us access to all 256 Kb of
video memory, as the two least significant bits of the byte address
won't be `wasted' on selecting a bit plane.  This is correct.  You might
also start feeling a little uneasy, because something tells you that
you'll instantly loose the simple addressing scheme of mode 13h.  Sadly,
that is also correct.

At the moment Chain-4 is cleared, each byte offset addresses *four*
sequential pixels, corresponding to the four planes addressed in 16-color
modes.  Every fourth pixel belong in the same plane.  Before writing to a byte
offset in the video segment, you should make sure that the 4-bit mask in the
Write Plane Enable register is set correctly, according to which of the four
addressable pixels you want to modify.  In essence, it works like a 16-color
mode with a twist.  See figure 2.

So, is this mode X?  Not quite.  We need to elaborate to the VGA how to
fetch data for refreshing the monitor image.  Explaining the logic
behind this is beyond the scope of this getting-you-started text, and it
wouldn't be very interesting anyway.  Also, mode 13h has only 200 lines,
while I promised 240 lines.  I'll fix that later below.  Here is the minimum
snippet of code to initiate the 4 page variant of mode 13h (320x200), written
in plain C, using some DOS specific features (see header for a note about the
sources included):

----8<-------cut begin------

/* width and height should specify the mode dimensions.  widthBytes
   specify the width of a line in addressable bytes. */

int width, height, widthBytes;

/* actStart specifies the start of the page being accessed by
   drawing operations.  visStart specifies the contents of the Screen
   Start register, i.e. the start of the visible page */

unsigned actStart, visStart;

/*
 * set320x200x256_X()
 *      sets mode 13h, then turns it into an unchained (planar), 4-page
 *      320x200x256 mode.
 */

set320x200x256_X()
        {

        union REGS r;

        /* Set VGA BIOS mode 13h: */

        r.x.ax = 0x0013;
        int86(0x10, &r, &r);

        /* Turn off the Chain-4 bit (bit 3 at index 4, port 0x3c4): */

        outport(SEQU_ADDR, 0x0604);

        /* Turn off word mode, by setting the Mode Control register
           of the CRT Controller (index 0x17, port 0x3d4): */

        outport(CRTC_ADDR, 0xE317);

        /* Turn off doubleword mode, by setting the Underline Location
           register (index 0x14, port 0x3d4): */

        outport(CRTC_ADDR, 0x0014);

        /* Clear entire video memory, by selecting all four planes, then
           writing 0 to the entire segment. */

        outport(SEQU_ADDR, 0x0F02);
        memset(vga+1, 0, 0xffff); /* stupid size_t exactly 1 too small */
        vga[0] = 0;

        /* Update the global variables to reflect the dimensions of this
           mode.  This is needed by most future drawing operations. */

        width   = 320;
        height  = 200;

        /* Each byte addresses four pixels, so the width of a scan line
           in *bytes* is one fourth of the number of pixels on a line. */

        widthBytes = width / 4;

        /* By default we want screen refreshing and drawing operations
           to be based at offset 0 in the video segment. */

        actStart = visStart = 0;

        }

----8<-------cut end------

As you can see, I've already provided some of the mechanics needed to
support multiple pages, by providing the actStart and visStart variables.
Selecting pages can be done in one of two contexts:

        1) selecting the visible page, i.e. which page is visible on
           screen, and

        2) selecting the active page, i.e. which page is accessed by
           drawing operations

Selecting the active page is just a matter of offsetting our graphics
operations by the address of the start of the page, as demonstrated in
the put pixel routine below.  Selecting the visual page must be passed
in to the VGA, by setting the Screen Start register.  Sadly enough, the
resolution of this register is limited to one addressable byte, which
means four pixels in unchained 256-color modes.  Some further trickery is 
needed for 1-pixel smooth, horizontal scrolling, but I'll make that a subject
for later.  The setXXXStart() functions provided here accept byte
offsets as parameters, so they'll work in any mode.  If widthBytes and
height are set correctly, so will the setXXXPage() functions.

----8<-------cut begin------

/*
 * setActiveStart() tells our graphics operations which address in video
 * memory should be considered the top left corner.
 */

setActiveStart(unsigned offset)
        {
        actStart = offset;
        }

/*
 * setVisibleStart() tells the VGA from which byte to fetch the first
 * pixel when starting refresh at the top of the screen.  This version
 * won't look very well in time critical situations (games for
 * instance) as the register outputs are not synchronized with the
 * screen refresh.  This refresh might start when the high byte is
 * set, but before the low byte is set, which produces a bad flicker.
 * I won't bother with this now.
 */

setVisibleStart(unsigned offset)
        {
        visStart = offset;
        outport(CRTC_ADDR, 0x0C);               /* set high byte */
        outport(CRTC_ADDR+1, visStart >> 8);
        outport(CRTC_ADDR, 0x0D);               /* set low byte */
        outport(CRTC_ADDR+1, visStart & 0xff);
        }

/*
 * setXXXPage() sets the specified page by multiplying the page number
 * with the size of one page at the current resolution, then handing the
 * resulting offset value over to the corresponding setXXXStart()
 * function.  The first page number is 0.
 */

setActivePage(int page)
        {
        setActiveStart(page * widthBytes * height);
        }

setVisiblePage(int page)
        {
        setVisibleStart(page * widthBytes * height);
        }

----8<-------cut end------

Due to the use of bit planes, the graphics routines tend to get more
complex than in mode 13h, and your first versions will generally tend to
be a little slower than mode 13h algorithms.  Here's a put pixel routine
for any unchained 256-color mode (it assumes that the 'width' variable
from the above code is set correctly).  Optimizing is left as an exercise
to you, the reader.  This will be the only drawing operation I'll cover
in this article, but all general primitives like lines and circles can be 
based on this routine.  (You'll probably not want to do that though, due
to the inefficiency.)

----8<-------cut begin------

putPixel_X(int x, int y, char color)
        {

        /* Each address accesses four neighboring pixels, so set
           Write Plane Enable according to which pixel we want
           to modify.  The plane is determined by the two least
           significant bits of the x-coordinate: */

        outportb(0x3c4, 0x02);
        outportb(0x3c5, 0x01 << (x & 3));

        /* The offset of the pixel into the video segment is
           offset = (width * y + x) / 4, and write the given
           color to the plane we selected above.  Heed the active
           page start selection. */

        vga[(unsigned)(widthBytes * y) + (x / 4) + actStart] = color;

        }

char getPixel_X(int x, int y)
        {

        /* Select the plane from which we must read the pixel color: */

        outport(GRAC_ADDR, 0x04);
        outport(GRAC_ADDR+1, x & 3);

        return vga[(unsigned)(widthBytes * y) + (x / 4) + actStart];

        }

----8<-------cut end------


However, by now you should be aware of that the Write Plane Enable
register isn't limited to selecting just one bit plane, like the
Read Plane Select register is.  You can enable any combination of all
four to be written.  This ability to access 4 pixels with one
instruction helps quadrupling the speed in certain respects, especially when 
drawing horizontal lines and filling polygons of a constant color.  Also, most 
block algorithms can be optimized in various ways so that they need only
a constant number of OUTs (typically four) to the Write Plane Enable
register.  OUT is a relatively slow instruction.

The gained ability to access the full 256 Kb of memory on a standard
VGA enables you to do paging and all the goodies following from that:
smooth scrolling over large maps, page flipping for flicker free
animation... and I'll leave something for your own imagination.

In short, the stuff gained from unchaining mode 13h more than 
upweighs the additional complexity of using a planar mode.  

Now, the resolution of the mode is of little interest in this
context.  Nearly any 256-color resolution from (about) 80x8 to 400x300
is available for most VGAs.  I'll dwell particularly by 320x240, as this
is the mode that Michael Abrash introduced as 'Mode X' in his DDJ
articles.  It is also the resolution that most people refer to when
using that phrase.

The good thing about the 320x240 mode is that the aspect ratio is
1:1, which means that each pixel is 'perfectly' square, i.e. not
rectangular like in 320x200.  An ellipse drawn with the same number of
pixels along both main axes will look like a perfect circle in 320x240,
but like a subtly tall ellipse in 320x200.

Here's a function which sets the 320x240 mode.  You'll notice that
it depends on the first piece of code above:

----8<-------cut begin------

set320x240x256_X()
        {

        /* Set the unchained version of mode 13h: */

        set320x200x256_X();

        /* Modify the vertical sync polarity bits in the Misc. Output
           Register to achieve square aspect ratio: */

        outportb(0x3C2, 0xE3);

        /* Modify the vertical timing registers to reflect the increased
           vertical resolution, and to center the image as good as
           possible: */

        outport(0x3D4, 0x2C11);         /* turn off write protect */
        outport(0x3D4, 0x0D06);         /* vertical total */
        outport(0x3D4, 0x3E07);         /* overflow register */
        outport(0x3D4, 0xEA10);         /* vertical retrace start */
        outport(0x3D4, 0xAC11);         /* vertical retrace end AND wr.prot */
        outport(0x3D4, 0xDF12);         /* vertical display enable end */
        outport(0x3D4, 0xE715);         /* start vertical blanking */
        outport(0x3D4, 0x0616);         /* end vertical blanking */

        /* Update mode info, so future operations are aware of the
           resolution: */

        height = 240;

        }

----8<-------cut end------


As you've figured out, this mode will be completely compatible with the
utility functions presented earlier, thanks to the global variable
'height'.  Boy, am I foreseeing or what!

Other resolutions are achieved through giving other values to the sync
timing registers of the VGA, but this is quite a large and complex
subject, so I'll postpone this to later, if ever.

Anyway, I hope I've helped getting you started using mode X.  As far as
I know, the two modes I've used above should work on *any* VGA and Super
VGA available, so this is pretty stable stuff.  Let me know of any 
trouble, and -
                        good luck!



3. THE ROAD FROM HERE

I'm providing information on various libraries and archives which relate
to what this article deals with.  If you want me to add anything to this
list (for future articles), let me know, although I can't promise anything.
I am assuming you have ftp access.


wuarchive.wustl.edu:/pub/MSDOS_UPLOADS/programming/xlib06.zip

This is the current de facto C/assembler library for programming
unchained modes (do not confuse with a X Windows library).  All sources
are included, and the library is totally free.  It has functions for
pixels, lines, circles, bezier curves, mouse handling, sprites (bitmaps),
compiled bitmaps, and supports a number of resolutions.  The version number
('06') is current as of November 1993.


graphprg.zip

Michael Abrash' articles in Doctor Dobbs Journal is always mentioned
with awe.  In this 350 Kb archive, most of his interesting stuff has
been gathered.  Read about Mode X development and techniques from month
to month.  Included is also all the individual source code snippets from
each article, and also the full XSHARP library providing linedrawing,
polygons, bitmaps, solid 3D projection and speedy rendering, and even an
implementation of 2D texture mapping (can be used for quasi-3D texture
mapping), plus an article on assembly optimization on the i86 processor
family.  Definitely recommended.


oak.oakland.edu:/pub/msdos/vga/vgadoc2.zip

This is a bare bones VGA register reference.  It also contains register
references for the CGA, EGA and Hercules cards, in addition to dozens of
SuperVGAs.  Check out the BOOKS section for some decent VGA references
though - you don't want to start tweaking without a real one.


wuarchive.wustl.edu:/pub/MSDOS_UPLOADS/programming/tweak15b.zip

TWEAK might be of interest to the more adventurous reader.  TWEAK lets you
play around with the registers of the VGA in an interactive manner.
Various testing screens for viewing your newmade modes are applied at
the press of a key.  Version 1.5 adds a test screen which autodetects your 
graphics mode and displays various information about resolutions etc.
Keep a VGA reference handy.  Don't try it if this is the first time you've 
heard of 'registers' or 'mode X' or 'tweaking'.  I was planning a version
based on the Turbo Vision interface, but time has been short.  Maybe later!




4. BOOKS ON THE SUBJECT

Extremely little has been published in written form about using
'Mode X'-style modes.  Below are some books which cover VGA programming
at varying degrees of technical level, but the only one to mention
unchained modes and Mode X, is Michael Abrash'.  I'd get one of the VGA
references first, though.

  o  George Sutty & Steve Blair : "Advanced Pogrammer's Guide to the
     EGA/VGA" from Brady.  A bit old perhaps, but covers all *standard*
     EGA/VGA registers, and discusses most BIOS functions and other
     operations.  Contains disk with C/Pascal/assembler source code.
     There's a sequel out for SuperVGAs, which I haven't seen.

  o  Michael Abrash : "Power Graphics Programming" from QUE/Programmer's
     Journal.  Collections of (old) articles from Programmer's Journal on
     EGA/VGA, read modes and write modes, animation, tweaking (320x400
     and 360x480).  His newer ravings in DDJ covers fast 256-color
     bitmaps, compiled bitmaps, polygons, 3D graphics, texture mapping
     among other stuff.

  o  Richard F. Ferraro : "Programmer's Guide to the EGA and VGA video
     cards including Super VGA".  I don't have this one, but heard it's
     nice.  Detailed coverage of all EGA/VGA registers.  The Super VGA
     reference makes it attractive.

  o  Richard Wilton : "Programmer's Guide to PC & PS/2 Video Systems"
     Less technical, more application/algorithm oriented.  Nice enough,
     even though it is a bit outdated, in that he discusses CGA and
     Hercules cards just as much as EGA/VGA.




5. BYE - FOR NOW

I am considering writing a text describing in more detail the process of
using TWEAK to achieve the VGA resolution you want or need.  However, I
thought I'd let this document go first, and see if I get any reactions.
If I don't, I'll stop.  Feel free to forward any suggestions,
criticisms, bombs and beers.

I can be reached via:

  o  e-mail: robert@stud.unit.no

  o  land mail:

     Robert Schmidt
     Stud.post 170
     NTH
     N-7034 Trondheim
     NORWAY

Nothing would encourage or please me more than a postcard from where you
live!

?????????????????????????????????????????????????????????????????????????????
? M1ORG.ASC ?
?????????????

Figure 1: Memory organization in mode 13h (ASCII version)
          by Robert Schmidt
          (C) 1993 Ztiff Zox Softwear

a. Imagine that the top of the screen looks like this (pixel values are
   represented by color digits 0-9 for simplicity - actual colors may
   range from 0 to 255) - a screen width of 320 pixels is assumed:

    address:  0         10                310      319
             ----------------------------------------
             |0123456789012345    .....   0123456789|
             |                                      |
             |                                      |
             |

b. In VGA memory, the screen is represented as follows (question marks
   represent unused bytes):

   Plane 0:

    address:  0         10                310      319
             ----------------------------------------
             |0???4???8???2???    .....   ??2???6???|
             |                                      |
             |                                      |

   Plane 1:

    address:  0         10                310      319
             ----------------------------------------
             |?1???5???9???3??    .....   ???3???7??|
             |                                      |
             |                                      |

   Plane 2:

    address:  0         10                310      319
             ----------------------------------------
             |??2???6???0???4?    .....   0???4???8?|
             |                                      |
             |                                      |

   Plane 3:

    address:  0         10                310      319
             ----------------------------------------
             |???3???7???1???5    .....   ?1???5???9|
             |                                      |
             |                                      |

   I.e. a plane is selected automatically by the two least significant
   bits of the address of the byte being read from or written two.
   This renders 3/4 of the video memory unavailable and useless, but
   all visible pixels are easily accessed, as each address in the video
   segment provides access to one and ONLY ONE pixel.

?????????????????????????????????????????????????????????????????????????????
? MXORG.ASC ?
?????????????

Figure 2: Memory organization in unchained 256-color modes (like
          Mode X) (ASCII version)
          by Robert Schmidt
          (C) 1993 Ztiff Zox Softwear


Imagine that the screen looks the same as in figure 1a.  A screen width
of 320 pixels is still assumed.

In VGA memory, the screen will be represented as follows:

   Plane 0:

    address:  0         10                70       79 (NOT 319!)
             ----------------------------------------
             |0482604826048260    .....   0482604826|
             |                                      |
             |                                      |

   Plane 1:

    address:  0         10                70       79
             ----------------------------------------
             |1593715937159371    .....   1593715937|
             |                                      |
             |                                      |

   Plane 2:

    address:  0         10                70       79
             ----------------------------------------
             |2604826048260482    .....   2604826048|
             |                                      |
             |                                      |

   Plane 3:

    address:  0         10                70       79
             ----------------------------------------
             |3715937159371593    .....   3715937159|
             |                                      |
             |                                      |

Note that if pixel i is in plane p, pixel i+1 is in plane (p+1)%4.
When the planes are unchained, we need to set the Write Plane Enable
register to select which planes should receive the data when writing,
or the Read Plane Select register when reading.  As is evident, one 
address in the video segment provides access to no less than FOUR
different pixels.

????????????????????????????????????????????????????????????????????????????
? Zox3D ?
?????????

Available via ftp :
ftp.wustl.edu:/pub/MSDOS_UPLOADS/games/programming/zox3d15.zip
wasp.eng.ufl.edu:/pub/msdos/demos/<somewhere>/zox3d15.zip

zox3d15.zip contains a demo of my 3D graphics engine.
It resembles Wolf3D, but has a number of additional
features:

- texture mapped floor and ceiling (sky, in this demo)
- real, recursive MIRRORS!
- partly TRANSPARENT walls
- input from keyboard, joystick and mouse (at the same
  time, too, if you wish)
- controllable camera height - NOT fixed like Wolf3D
- quick resizable window
- online help and fps rating
- advanced collision detection and handling
- supports a variety of tweaked X modes, from 256x256 to 400x300.

The sky and mirrors have to be seen to be beleived!

Zox3D does NOT implement objects, like the guards in
Wolf3D, but that should be a breeze to add.

The complete sources are available.  Read ZOX3D.DOC in
the demo archive for information.

????????????????????????????????????????????????
Robert Schmidt - robert@stud.unit.no - Buuud@IRC



      SSSSS   CCCCC  RRRRR    OOOOO  LL    LL    IIIIII NN    NN  GGGGG
     SS   SS CC   CC RR  RR  OO   OO LL    LL      II   NNN   NN GG   GG
     SS      CC      RR   RR OO   OO LL    LL      II   NNNN  NN GG
      SSSSS  CC      RR  RR  OO   OO LL    LL      II   NN NN NN GG
          SS CC      RRRRR   OO   OO LL    LL      II   NN  NNNN GG  GGG
     SS   SS CC   CC RR  RR  OO   OO LL    LL      II   NN   NNN GG   GG
      SSSSS   CCCCC  RR   RR  OOOOO  LLLLL LLLLL IIIIII NN    NN  GGGGG

                    by Alec Thomas (Kestrel) of FORGE Software Australia
                                          (c9223826@cs.newcastle.edu.au)


------------
INTRODUCTION
------------
Okay, here it is fans (and air conditioners, open windows...geez I hate that
joke!), how to do scrolling using either X-mode (and associated variants) and
standard mode 13h (not hard but I thought I'd put it in anyway :) as well as
the basics of parallax scrolling...

First things first - X-mode. Throughout this little dissertation, I'm going
to assume that you know the basics of X-mode (or mode-X or mode-Y or
whatever you want to call it) such as how to get into it, how to set the
offset register, etc. and just get on with the scrolling :) I'm not trying
to teach you X-mode, but SCROLLING!!

One further thing. I'm not saying that the methods I'll explain below are
the best method of scrolling, I'm just showing how I got it to work myself
in the hope that someone out there can use it. Anyway, enough of this crap,
on with the STUFF!!!

(just a little note, when I'm talking about rows, they number from 0-199 and
the same with columns (except 0-319), etc. unless otherwise stated)


------------------
VERTICAL SCROLLING
------------------
Ok, this is the easiest form of scrolling using the VGA hardware...fast and
clean. The following example assumes you are using 320x200 X-mode with the
visible page starting at the top of the first page (offset 0).

To scroll what is on the screen up off the top, you simply add 80 (decimal)
to the screen offset register. This causes the screen to jump up by one
row. However, it also causes whatever is off the bottom of the screen
(the next page!) to become visible...not a desireable effect.

Easily fixed however. Draw the image you want to scroll, on the row that
will scroll on. So, when the screen offset is changed to scroll the screen
up, the new data is already there for all to see. Beautiful!!!

----------- Scrolling A (up) --------------
OFFSET = 0
WHILE NOT FINISHED DO
  OFFSET = OFFSET + 80
  DRAW TO ROW 200
  SET VGA OFFSET = OFFSET
END WHILE
-------------------------------------------

Bzzzzz! Wrong! This works fine, until you have scrolled down to the
bottom of page 4. Because you're effectively off the bottom of the VGA
window (starting at segment A000h), you can't write to the rest of the
VGA memory (if there is any - only SVGA's have more than 256K on board
memory) and so, you'll be viewing garbage.

No problem. The way around it is to only use two pages!!! "What?" I hear
you say. In fact, by using only two pages for scrolling, you gain two
major advantages: page flipping (because you're only using two pages for
the actual scrolling, you can use the spare two to perform page flipping)
and infinite scroll regions.

You perform the infinite scrolling in exactly the same way as before, with
two minor additions: after changing the offset register, you copy the row
just scrolled on to the row just scrolled off. Also, after you have scrolled
a full page, you reset the offset to the top of the original page.

----------- Scrolling B (up) --------------
OFFSET = 0
WHILE NOT FINISHED DO
  OFFSET = OFFSET + 80
  IF OFFSET >= (200 * 80) THEN OFFSET = 0
  DRAW TO ROW 200
  SET VGA OFFSET = OFFSET
  DRAW TO ROW -1 (was row 0 before scroll)
END WHILE
-------------------------------------------

Ok, so that's how to do vertical scrolling, now on with horizontal scrolling.



--------------------
HORIZONTAL SCROLLING
--------------------
Horizontal scrolling is essentially the same as vertical scrolling, all
you do is increment or decrement the VGA offset register by 1 instead of
80 as with vertical scrolling.

However, horizontal scrolling is complicated by two things

  1. Incrementing the offset register by one actually scrolls by FOUR
     pixels (and there are FOUR planes on the VGA, what a coincidence)

  2. You can't draw the image off the screen and then scroll it on
     because of the way the VGA wraps to the next row every 80 bytes
     (80 bytes * 4 planes = 320 pixels), if you tried it, you would
     actually be drawing to the other side of the screen (which is
     entirely visible)

I'll solve these problems one at a time.

Firstly, to get the VGA to scroll by only one pixel you use the horizontal
pixel panning (HPP) register. This register resides at

  PORT:     3C0H
  INDEX:    13h

and in real life, you use it like this

----------------- Pixel Panning ---------------
IN PORT 3DAH (this clears an internal
  flip-flop of the VGA)
OUT 13H TO PORT 3C0H
OUT value TO PORT 3C0H (where "value" is the
  number of pixels to offset)
-----------------------------------------------

To implement smooth horizontal scrolling, you would do the following:

-------------- Horizontal Scrolling ------------
FOR X = 0 TO 319 DO
  SET HPP TO ( X MOD 4 )
  SET VGA OFFSET TO ( X/4 )
END FOR
------------------------------------------------

Okay, no problem at all (although I think you might have to fiddle
around with the HPP a bit to get it right...try different values and
see what works :).

So, the next problem is with drawing the images off the screen where
they aren't visible and then scrolling them on!!! As it turns out,
there's yet ANOTHER register to accomplish this. This one's called the
offset register (no, not the one I was talking about before, that one
was actually the "start address" register) and it's at

  PORT:     3D4H/3D5H
  OFFSET:   13H

and here's how to use it

-------------- Offset Register ---------------
OUT 13H TO PORT 3D4H
OUT value TO PORT 3D5H
----------------------------------------------

Now, what my VGA reference says is that this register holds the number
of bytes (not pixels) difference between the start address of each row.
So, in X-mode it normally contains the value 80 (as we remember,
80 bytes * 4 planes = 320 pixels). This register does not affect the
VISIBLE width of the display, only the difference between addresses on
each row.

When we scroll horizontally, we need a little bit of extra working space
so we can draw off the edge of the screen.

Perhaps a little diagram will clarify it. The following picture is of a
standard X-mode addressing scheme with the OFFSET register set to 80.

      ROW    OFFSET
      0         0 ========================
      1        80 [                      ]
      2       160 [                      ]
      ..       .. [       VISIBLE        ]
                  [        SCREEN        ]
                  [                      ]
                  [                      ]
      ..       .. [                      ]
      199   15920 ========================

and the next diagram is of a modified addressing scheme with the OFFSET
register set to 82 (to give us 4 extra pixels on each side of the screen)

ROW    OFFSET
0         0 ------========================------
1        82 |   V [                      ]   V |
2       164 |   I [                      ]   I |
..       .. | N S [      VISIBLE         ] N S |
            | O I [       SCREEN         ] O I |
            | T B [                      ] T B |
            |   L [                      ]   L |
..       .. |   E [                      ]   E |
199   16318 ------========================------

Beautiful!!!

As with vertical scrolling, however, you still have the problem of when
you reach the bottom of page 4...and it's fixed in the same manner.

I haven't actually managed to get infinite horizontal scrolling working,
but the method I have just stated will give you a horizontal scrolling
range of over 200 screens!!!! So if you need more (which is extremely
unlikely), figure it out yourself.


------------------
COMBINED SCROLLING
------------------
To do both horizontal and vertical scrolling, all you have to do is combine
the two methods with a few little extras (it's always the way isn't it).

You have to start off with the original screen on the current page and the
next page as well. When you scroll horizontally, you have to draw the edge
that's coming in to the screen to BOTH pages (that means you'll be drawing
the incoming edge twice, once for each page). You do this so that when you
have scrolled vertically down through a complete page, you can jump back
to the first page and it will (hopefully) have an identical copy, and you
can then continue scrolling again.

I'm sorry about this being so confusing but it's a bit difficult to explain.






Without X-mode, there is no easy way to do scrolling using the VGA hardware.
So basically, you have to resort to redrawing the entire screen for every
frame. Several popular games (Raptor and Mortal Kombat spring to mind)
utilise this method with excellent effect, so it is quite effective.

Basically all you do to implement this is redraw the screen every frame
with a slightly different offset into the "map".

The following bit of pseudo-code will scroll down and to the right
through the map.

------------- Standard Scrolling ---------------
X = 0
Y = 0
WHILE NOT FINISHED DO
  DRAW TO SCREEN( 0, 0 ) FROM MAP( X, Y )
  X = X + 1
  Y = Y + 1
END WHILE
------------------------------------------------






Parallax scrolling is when the "world" appears to have different levels
of perspective. That is, images further away from the viewer move
proportionately slower than images closer to the screen.

To implement parallax scrolling, you need two or more "maps". You start
from the most distant map and end with the closest map. When you scroll,
you offset the map furthest away by the smallest value and the map
closest to you by the largest value.

The following pseudo-code implements a 3 level parallax scrolling world,
scrolling (as above) down to the right.

--------------- Parallax Scrolling ------------------
X = 0
Y = 0
WHILE NOT FINISHED DO
  DRAW TO SCREEN( 0, 0 ) USING MAP_FAR AT ( X/4, Y/4 )
  DRAW TO SCREEN( 0, 0 ) USING MAP_MEDIUM AT ( X/2, Y/2 )
  DRAW TO SCREEN( 0, 0 ) USING MAP_NEAR AT ( X, Y )
  X = X + 4
  Y = Y + 4
END WHILE
-----------------------------------------------------

Obviously, with parallax scrolling, each successive map shouldn't delete
the previous map entirely. So you'll have to draw the maps using some
sort of masking (masking being where you can see through the background
colour to what was there previously).



I'm sorry if any of this is confusing, but hey that's half the fun of it -
figuring out what the hell I'm raving on about :)

So, if you can figure it out, have fun and make games (preferably good ones!)

Later,
      Kestrel => FORGE Software Australia

                         Programming the VGA Registers
                      by Boone (boone@ucsd.edu), March '94

   The IBM PC has long been slammed by owners of other computers which come 
with superior graphics capabilities built right into hardware.  The PC is a 
strange beast to program in general, and when it comes to graphics the 
programmer doesn't get much help from the video hardware.  However, there are 
quite a few neat tricks you can do using the VGA registers, as I'm sure you're 
aware.  The trick is knowing just which registers to use and how to use them to 
achieve the desired results.  In particular, precise timing is necessary to 
avoid screen flicker and/or "snow".  The registers on your video card are 
necessary for just about any communication with the VGA besides basic 
reading/writing of pixels.  Some of the registers are standard, which are the 
ones we will be discussing here.  Most SVGA chipsets have their own special 
functions associated with different registers for things such as bank 
switching, which is part of what makes trying to write SVGA programs so 
difficult.  The registers are also used to set the various attributes of each 
video mode: horizontal and vertical resolution, color depth, refresh rate, 
chain-4 mode, and so on.  Luckily, BIOS handles all this for us and since we 
only need to set the video mode once at program start-up and once at exit, you 
should need to mess with these particular functions too much, unless you are 
using a special mode, such as mode X.  (See the mode X section for more info on 
all this.)  If you want to experiment with the video mode registers, ftp 
yourself a file called TWEAK*.* (my version is TWEAK10.ZIP).  For now we'll 
just assume the video mode has already been set to whatever mode you wish.
   One of the most common techniques used by game programmers is fade in/out.  
A clean fade is simple but very effective.  Suprisingly, even big-budget games 
like Ultima VII often have a lot of screen noise during their fades.  With a 
little effort you can easily write your own noise-free fade routines.  There's 
nothing like giving a professional first impression on your intro screen, since 
the fade-in is likely to be the very first thing they see of your program.
   BIOS is much to slow for this timing-critical opperation, so we'll have to 
get down and dirty with our VGA card.  Fading is a fairly simple process.  As 
you should know, the VGA palette consists of 256 colors with 3 attributes for 
each color: red, green and blue.  Every cycle of the fade, we have to go 
through all 768 attributes and if it is larger than 0 subtract one.  We'll use 
regsiters 3C8h and 3C9h for palette opperations.  The operation for sending a 
palette to the card is straight-forward: send a 0 to port 3C8h and then your 
768 byte buffer to port 3C9h.  This is good enough for setting the palette at 
the start of your program, but of course it has to go in a loop for the fade, 
since you'll have to do this 256 times, subtracting one from each non-zero 
member of the buffer.  The pseudo-code looks something like this:

   constant PALSIZE = 256*3;
   unsigned character buffer[PALSIZE];
   boolean done;
   counter i,j;

      for j = 255 to 0
       {
         for i = 0 to PALSIZE-1
            if buffer[i] > 0
               buffer[i] = buffer[i] - 1;

         output 0 to port 3C8h;
         for i = 0 to PALSIZE-1
            output buffer[i] to port 3C9h;
       }

   Easy enough, right?  If you convert this to the language of your choice it 
should run fine.  (Make sure you have the buffer pre-loaded with the correct 
palette, however, or you will get very strange results...)  But you'll notice 
the "snow" mentioned earlier.  Depending on your video card, this could mean 
that you see no noise at all to fuzz covering your entire screen.  Even if it 
look fine on your system, however, we want to make sure it will be smooth on 

ask the video card when it's safe to send the palette buffer to the card, and 
for that we'll need the retrace register.
   Putting aside palette concerns for a moment, I'll briefly cover the retrace 
on your video card.  (See the next section of this article for a more in-depth 
discussion of this.)  Bascially the vertical retrace is a short time in which 
the screen is not being updated (from video memory to your monitor) and you can 
safely do writes to your video memory or palette without worrying about getting 
snow, flicker, tearing, or other unwanted side-effects.  This is a pretty quick 
period (retrace occurs 60 to 70 times a second) so you can't do too much at 
once.
   Returning to our fade: we want to update the palette during the vertical 
retrace.  The value we want is bit 3 of register 3DAh.  While that bit is zero 
we're safe to write.  The best practice in this case is to wait for the bit to 
change to one (screen is being traced) and then the instant it changes to 0, 
blast all our new video info to the card.  It won't be necessary in this case 
since all we are doing is fading the palette and then waiting for the next 
retrace, but if you're doing animation or playing music at the same time 
you'll want to include this extra bit of code as a safety net.  Otherwise you 
might detect the 0 in the refresh bit at the very last instant of the retrace 
and end up writing while the screen is being traced.  The pseudo-code now goes 
like this:

      for j = 255 to 0
       {
         for i = 0 to PALSIZE-1
            if buffer[i] > 0
               buffer[i] = buffer[i] - 1;

         while bit 3 of port 3DAh is 0
            no opperation;
         while bit 3 of port 3DAh is 1
            no opperation;

         output 0 to port 3C8h;
         for i = 0 to PALSIZE-1
            output buffer[i] to port 3C9h;
       }

   That's it!  All that's left is for you to implement it in your favorite 
language.  However, I can hear the cries right now: "Code!  Give us some real 
assembly code we can use!"  I'm reluctant to provided it as this is the exact 
sort of thing that is easy to cut and paste into your program without knowing 
how it works.  However, I'll give you the unoptimized main loop in 80x86 
assembly as this may be clearer to you that my explanation or pseudo-code.  Two 
things to remember about this code: it is optimized enough to be smooth on any 
video card (or any that I've seen, anyway) assuming that the fade is the _only_ 
thing going on.  There's some other things you may want to change if you plan 
to say, play music during this process.  Secondly, you'll need to have the 
current palette loaded into the buffer beforehand.  You could read it from the 
VGA card using either registers or BIOS, but this is both slow and (in my 
oppinion) sloppy coding.  You should *never* ask the video card about anything 
(excluding retrace) that you could keep track of yourself.  In the case of the 
palette, you probably already loaded it from disk anyway, or if you are using 
the default palette <cough, gag, choke> just read the values once and store 
them in your executable or in a resource file.

     palbuf   DB                  768 DUP (?)
     fadecnt  DW                  040h

; At this point, you should:
;  1) have the video mode set
;  2) have palbuf loaded with the current palette
;  3) have something on the screen to fade!

fadeloop:

     xor      al,al               ; used for comparisons and port 3D8h
     mov      cx,768              ; loop counter
     mov      si,offset palbuf    ; save palette buffer in si

decloop:
     mov      dl,[si]              ; put next pal reg in dx
     cmp      al,dl                ; is it 0?
     je       next                 ; nope...
     dec      dl                   ; yes, so subtract one
     mov      [si],dl              ; put it back into palette buffer

next:
     dec      cx                   ; decrement counter
     inc      si                   ; increment our buffer
     cmp      cx,0
     jne      decloop              ; not done yet, so loop around

     mov      cx,768              ; reset for palette output
     sub      si,768              ; reset palbuf pointer
     mov      dx,03c8h
     out      dx,al               ; inform VGA of palette change
     inc      dx                  ; DX = 3C8h + 1 = 3C9h

     mov      ch,02h              ; do outter loop 2 times
     mov      dx,03dah            ; prepare refresh register
     mov      bx,03c9h            ; prepare palette reg (for quick loading)

     cli                          ; disable interrupts!

outloop:
     mov      cl,80h              ; do inner loop 128 times

     in       al,dx               ; wait for current retrace to end
     test     al,08h
     jnz      $-5

     in       al,dx               ; wait for current screen trace to end
     test     al,08h
     jz       $-5

     mov      dx,bx               ; load up the palette change register

innerloop:
     mov      al,[si]             ; load next byte of palbuf
     out      dx,al               ; send it to the VGA card
     dec      cl                  ; decrement counter
     inc      si                  ; increment palbuf pointer
     cmp      cl,0
     jne      innerloop           ; loop while not done

     dec      ch                  ; decrement outer loop counter
     cmp      ch,0
     jne      outloop             ; loop while not done

     sti                          ; restore interrupts

     mov      ax,fadecnt          ; entire palette has been sent
     dec      ax                  ; so check fade loop 
     mov      fadecnt,ax
     cmp      ax,0                ; ready to quit?
     jne      fadeloop            ; nope, keep fading!


   I should add a few comments about this code segment.  First of all, it 
assumes you want to fade every color all the way down.  You may only want to 
fade certain sections of the palette (if your screen was only using a certain 
number of colors) or maybe your palette is low-intensity so you don't need to 
go the full 256 loops to get every color down to 0.  It also goes by ones, so 
if you want a faster fade you can have it subtract two from each attribute.  
If you want to fade to a certain color other than black (for instance, fade to 
red such as the "getting hit" effect in Doom), you'll need to check if each 
attribute is above or below your target color and increment or decrement 
accordingly.  Also, you may have noticed something in the code absent from the 
pseudo-code: it only sends 128 colors to the card each retrace!  This is 
because if you use all 256 the next retrace may start before you get all colors 
sent to the video card, thanks to the unoptimized code.  Some recommend as 
little as 64 colors per retrace, however I've found 128 to be okay and 
certainly much faster.  The above code works for any VGA-equiped machine, 
regardless of processor, but you'll probably want to compress all the IN and 
OUT loops into REP INSB/OUTSB, REP INSW/OUTSW, or REP INSD/OUTSD instructions 
depending upon the minimum processor requirement for your game/demo.
   I won't describe fading in since it's the same sort of thing, and I'm sure 
you can figure it out once you know how to use the registers themselves.  It's 
a little more complicated since you need a second buffer of target values for 
your attributes, but otherwise quite similar.

   Next up is vertical retrace.  This is simply one of many read registers on 
your VGA, but it happens to be one of the most useful for animation and palette 
fades (as shown above).  Here's a quick rundown of what exactly the vertical 
retrace is, and why it's useful.
   There's an electron gun in the back of your monitor that keeps the pixels 
"refreshed" with their correct values every 1/60th of a second or so.  It fires 
electrons at each pixel, row by row.  The horizontal retrace is the time it 
takes it to return from the right side of the screen after it has traced a row.  
This is a very short time and I wouldn't worry about that too much right now, 
as it is only useful for very specialized (and quite tricky) hardware effects.
More useful, however, is the vertical retrace which occurs when the electron 
gun reaches the bottom of the screen (one entire screen traced) and it returns 
diagonally to the upper-right hand corner of the screen.  During this time you 
are free to update anything you like having to do with video with no noise or 
interference (since nothing on the screen is being updated).  This is a fairly 
short amount of time, though, so whatever you want to do you better do it 
_quickly_.  For animation, you'll usually want to keep a second buffer in main 
memory (remember that video RAM is quite slow compared to main RAM) which you 
can use to write your animations to.  When the vertical retrace occurs, you'll 
want to blast the entire thing to the VGA as quickly as possible, using a 
memory copy instruction.  You can find more on this in articles which cover 
animation.

   Lastly I'll briefly describe the VGA mode-set registers.  There are quite a 
number of them and for the most part they're pretty boring.  By sending 
different values to these registers you can achieve the various video modes 
that your card is capable of.  These registers set values such as horizontal 
and vertical resolution, retrace timing, addressing modes, color depth, timing, 
and other fun stuff.  The truth is that it's easier and just as effective to
let the BIOS (gasp!) handle setting the screen mode for you, particularly since 
most games use standard modes such as 320x200 anyway.  At the very least you 
can let BIOS set the mode to begin with and then just modify the registers to 
"tweak" the mode the way you want it.  Any of these non-BIOS modes are 
generally refered to as mode X.  I don't want to go deep into detail on the 
setting and usage of mode X because there is already so much info availible on 
the topic.  Check out the Mode X Faq (regularly posted in comp.sys.ibm.pc.demos 
and rec.games.programmer), Micheal Abrash's collumn in Dr. Dobb's and his 
X-sharp library, or the section on mode X in the PC-GPE.
   One mode register I'll cover quickly is the chain-4 enable/disable.  A lot 
of programmers seem to have trouble visualizing what this thing does exactly. 
Bit 3 of port 3C4h (index 4) controls chain-4 mode.  Normally it is on.  This 
allows fast linear addressing of the bytes in video memory, which is the way 
you are probably used to addressing them.  For example, to change the second 
pixel on the screen to a certain color, you simply write the value to address 
A000:0001.  With chain-4 disabled (the main feature of mode X besides better 
resolution) A000:0000 refers to the first pixel in the upper-left corner of 
your screen, A000:0001 refers to the fourth pixel, A000:0002 to the eight pixel 
and so on.  The odd pixels are accessed by changing the write plane.  Since 
there are four planes, you effectively get an extra two bits of addressing 
space, boosting the total bit width for your pixel addressing from 16 to 18.  
Standard chain-4 four only allows access to 64K of memory (2^16) while 
disabling this feature gives you the full 256K (2^18) of memory to work with.  
The disadvantage, of course, is that pixel writes are slower due to the port 
writes required to access odd pixels.  How can this be an advantage?  For one 
thing, you can write four pixels at a time as long as they are all the same 
color - handy for single-color polygons, as in flight simulators.  Secondly, 
you get four times as much memory.  This allows you to have higher resolutions 
without bank switching, or scroll the screen using hardware scrolling, or do 
page flipping for smooth animation.  And since you can change the resolution, 
you can give yourself a sqaure aspect ration (320x240) which is better for 
bitmap rotations and the like.  But remember that it can be slower for 
bitmapped graphics because you have to do at least four writes to the card (to 
change planes) in order to copy bitmaps from main RAM to video memory.  Don't 
use mode X just because you think it's "cool"; make sure you have a good reason 
for wanting to use it in your program, or otherwise you're wasting a lot of 
effort for no reason.

   Now, I'm sure you want me to continue until I divulge all the secrets of the 
VGA register to you - but, I only have some much time and space.  Besides, I 
still haven't uncovered all of their mysteries and capabilities myself.  
However, below is a list of the registers which you may want to play with for 
various effects.  The following list was posted on rec.games.programmer by 
Andrew Bromage (bromage@mundil.cs.mu.OZ.AU), so thanks to him for posting in to 
begin with.
   That's it for this article and I hope it helped you understand your VGA card 
a little better.  If not, re-read it, and try writing your own programs which 
use the registers.  The only way to really understand it (as with most things) 
is to get some hands-on experience.
   If you've got any questions, comments, flames, or corrections related to 
this document or game programming/design in general, feel free to post an 
article in rec.games.programmer (in case you haven't noticed by now, I hang out 
there regularly) or send mail to boone@ucsd.edu.

Here's the list.  Have fun...

          Documentation Over the I/O Registers for Standard VGA Cards

                    Documentated by Shaggy of The Yellow One
                           Email: D91-SJD@TEKN.HJ.SE

Feel free to spread this to whoever wants it.....
------------------------------------------------------------
Port-Index:  -               Port: Write/03c2h Read/03cch
usage:       d7   Vertical sync polarity
             d6   Horizontal sunc polarity
             d5   Odd /even page
             d4   Disable video
             d3   Clock select 1
             d2   Clock select 0
             d1   Enable/Disable display RAM
             d0   I/O address select
Description: Sync polarity: Bits are set as below for VGA displays
             that use sync polarity to determine screen resolution.
             Many newer multiple frequency displays are insensitive
             to sync polarity

             d7 d6      Resolution
             0  0       Invalid
             0  1       400 lines
             1  0       350 lines
             1  1       480 lines

             I/O address select: When set to zero, selects the
             monochrome I/O address space (3bx). When set to one,
             it selects the color I/O address space (3dx)

------------------------------------------------------------
Port-Index: -                Port: 03c2h ; read only
usage:      d7    Vertical Retrace Interrupt pendling
            d6    Feature connector bit 1
            d5    Feature connector bit 0
            d4    Switch sense
            d0-d3 Unused

Description: d7 uses IRQ2

------------------------------------------------------------
Port-Index: -                Port: 03bah,03dah ; read only
usage:      d3  Vertical retrace
            d0  Horizontal retrace

------------------------------------------------------------
Port-Index: -                Port: 03c3h,46e8h
usage:      d7-d1  Reserved
            d0     VGA enable/disable (03c3h only)

Description: Disables access to display memmory and the other
             VGA's ports

------------------------------------------------------------
Port-Index: 00h              Port: 03d4h, 03b4h
usage:      Horizontal total
Description: Total number of characters in horizontal scan minus
             five ( including blanked and border characters)

------------------------------------------------------------
Port-Index: 01h              Port: 03d4h, 03b4h
usage:      Horizontal display enable
Description: Total number of characters displayed in horizontal
             scan minus one.
------------------------------------------------------------
Port-Index: 02h              Port: 03d4h, 03b4h
usage:      Start horizontal blanking
Description: Character at which blanking starts

------------------------------------------------------------
Port-Index: 03h              Port: 03d4h, 03b4h
usage:      End horizontal blanking
            d7    Test
            d6    Skew control
            d5    Skew control
            d0-d4 End blanking
Description: End blanking: is five LSB bits of six-bit value,
             which define the character at which blanking stops.
             The MSB bit of this value is in register index 5.

------------------------------------------------------------
Port-Index: 04h              Port: 03d4h, 03b4h
usage:      Start horizontal retrace
Description: Character at which horizontal retrace starts

------------------------------------------------------------
Port-Index: 05h              Port: 03d4h, 03b4h
usage:      End horizontal retrace
            d7    End horizontal blanking bit 5
            d6    Horizontal retrace delay
            d5    Horizontal retrace delay
            d0-d4 End horizontal retrace
Description: End horizontal retrace: defines the character at
             which horizontal retrace ends

------------------------------------------------------------
Port-Index: 06h              Port: 03d4h, 03b4h
usage:      Vertical total
Description: Total number of horizontal scan lines minus two
             (including blanked and border characters). MSB bits
             of this value are in register index 7

------------------------------------------------------------
Port-Index: 07h              Port: 03d4h, 03b4h
usage:      Overflow register
            d7  Vertical retrace start (bit 9)
            d6  Vertical display enable end (bit 9)
            d5  Vertical total (bit 9)
            d4  Line compare (bit 8)
            d3  Start vertical blank (bit 8)
            d2  Vertical retrace start (bit 8)
            d1  Vertical display enable end (bit 8)
            d0  Vertical total (bit 8)
------------------------------------------------------------
Port-Index: 08h              Port: 03d4h, 03b4h
usage:      Preset row scan
            d7    Unused
            d6    Byte panning control
            d5    Byte panning control
            d0-d4 Preset row scan
Description: Byte panning control: is used to control byte
             panning. This register together with attribute
             controller register 13h allows for up to 31 pixels of
             panning in double word modes
             Preset row scan: Which character scan line is the
             first to be displayed
------------------------------------------------------------
Port-Index: 09h              Port: 03d4h, 03b4h
usage:      Maximum scan line/Character height
            d7    double scan
            d6    bit d9 of line compare register
            d5    bit d9 of start vertical blank register
            d0-d4 Maximum scan line
Description: d0-d5=Character height-1,  only in textmodes
------------------------------------------------------------
Port-Index: 0ah              Port: 03d4h, 03b4h
usage:      Cursor start
            d7,d6 Reserved (0)
            d5    Cursor off
            d4-d0 Cursor start
Description:
------------------------------------------------------------
Port-Index: 0bh              Port: 03d4h, 03b4h
usage:      Cursor end
            d7    reserved
            d6,d5 Cursor skew
            d4-d0 Cursor end
Description:
------------------------------------------------------------
Port-Index: 0ch              Port: 03d4h, 03b4h
usage:      Start address high
------------------------------------------------------------
Port-Index: 0dh              Port: 03d4h, 03b4h
usage:      Start address low
Description: Determine the offset in display memory to be
             displayed on the upper-left corner on the screen
------------------------------------------------------------
Port-Index: 0eh              Port: 03d4h, 03b4h
usage:      Cursor location (high byte)
------------------------------------------------------------
Port-Index: 0fh              Port: 03d4h, 03b4h
usage:      Cursor location (low byte)
Description: Where the cursor is displayed on screen
------------------------------------------------------------
Port-Index: 10h              Port: 03d4h, 03b4h
usage:      Vertical retrace start
Description: 8 bits out of 10
------------------------------------------------------------
Port-Index: 11h              Port: 03d4h, 03b4h
usage:      Vertical retrace end
            d7    Write protect CRTC register 0 to 7
            d6    refresh cycle select
            d5    enable vertical interrupt (when 0)
            d4    Clear vertical interrupt (when 0)
            d0-d3 Vertical retrace end
------------------------------------------------------------
Port-Index: 12h              Port: 03d4h, 03b4h
usage:      Vertical display enable end
Description: eight LSB bits out of ten-bit value which define
             scan line minus one at which the display ends.
             The other two are in CRTC register index 7
------------------------------------------------------------
Port-Index: 13h              Port: 03d4h, 03b4h
usage:      Offset / Logical screen width
Description: Logical screen width between successive scan lines
------------------------------------------------------------
Port-Index: 14h              Port: 03d4h, 03b4h
usage:      Underline location register
            d7    Reserved
            d6    Double word mode
            d5    count by 4
            d0-d4 Underline location
Description: Underline location: Monochrome textmode only
------------------------------------------------------------
Port-Index: 15h              Port: 03d4h, 03b4h
usage:      Start vertical blanking
Description: eight LSB bits of ten-bit value minus one which
             define at which scan line the vertical blanking
             starts. The other two bits are in CRTC registers
             index 7 and 9
------------------------------------------------------------
Port-Index: 16h              Port: 03d4h, 03b4h
usage:      End vertical blanking
Description: eight LSB bits of a value which determine the scan
             line after which vertical blanking ends.
------------------------------------------------------------
Port-Index: 17h              Port: 03d4h, 03b4h
usage:      Mode control register
            d7  Enable vertical and hoizontal retrace
            d6  Byte mode (1), word mode (0)
            d5  Address wrap
            d4  Reserved
            d3  count by 2
            d2  multiple vertical by 2 (use half in
                CRTC (8,10,12,14,18)
            d1  Select row scan counter (not used)
            d0  compatibilty mode support (enable interleave)
------------------------------------------------------------
Port-Index: 18h              Port: 03d4h, 03b4h
usage:      Line compare register
Description: Split screen,  8 bit value out of a ten-bit value
------------------------------------------------------------
Port-Index: 00h              Port: 03c4h
usage:      Reset register
            d7-d2 Reserved
            d1    Synchronous reset
            d0    Asynchronous reset
Description: Synchr. when set to zero, will halt and reset
             the sequencer at the end of its current cycle
             Asyncht. when set to zero, will immediatly halt
             and reset the sequencer. Data can be loss.
------------------------------------------------------------
Port-Index: 01h              Port: 03c4h
usage:      Clock mode register
            d7,d6 Reserved
            d5    display off
            d4    Allow 32-bit Fetch (not used in standard modes)
            d3    Divide dot clock by 2 (used in some 320*200 modes)
            d2    Allow 16-bit fetch (used in mon graphics modes)
            d1    Reserved
            d0    Enable (0) 9 dot characters (mono text and 400-line)
Description: Display off: Will blank screen and give the cpu
             uninterrupted access the display memory.
------------------------------------------------------------
Port-Index: 02h              Port: 03c4h
usage:      Color plane write enable register
            d7,d6 Reserved
            d3    Plane 3 Write enable
            d2    Plane 2 Write enable
            d1    Plane 1 Write enable
            d0    Plane 0 Write enable
Description:
------------------------------------------------------------
Port-Index: 03h              Port: 03c4h
usage:      Character generator select register
            d7,d6 Reserved
            d5    Character generator table select A (MSB)
            d4    Character generator table select B (MSB)
            d3,d2 Character generator table select A
            d1,d0 Character generator table select B
Description: This register is only of interest if your software
             will be using multiple character sets. Either one
             or two character sets can be active. Table A selects
             the charcater with attribute d3 set to zero and
             Table B is the one with d3 set to one.
------------------------------------------------------------
Port-Index: 04h              Port: 03c4h
usage:      Memory mode register
            d4-d7 Reserved
            d3    Chain 4 (address bits 0&1 to select plan, mode 13h)
            d2    Odd/even (address bit 0 to select plane 0&2 or   
                  1&3 text modes)
            d1    Extended memory (disable 64k modes)
            d0    Reserved
Description:
------------------------------------------------------------
Port-Index: 00h              Port: 03ceh
usage:      Set / Reset register
            d7-d4 Reserved (0)
            d3    Fill data for plane 3
            d2    Fill data for plane 2
            d1    Fill data for plane 1
            d0    Fill data for plane 0
------------------------------------------------------------
Port-Index: 01h              Port: 03ceh
usage:      Set / Reset enable register
            d7-d4 Reserved (0)
            d3    enable set/reset for plane 3 (1 = enable)
            d2    enable set/reset for plane 2 (1 = enable)
            d1    enable set/reset for plane 1 (1 = enable)
            d0    enable set/reset for plane 0 (1 = enable)
Description: Set/Reset enable defines which memory planes will
             receive fill data from set/reset register. Any plane
             that is disable for set/reset will be written with
             normal processor output data
------------------------------------------------------------
Port-Index: 02h              Port: 03ceh
usage:      Color compare register
            d7-d4 Reserved
            d3    Color compare value for plane 3
            d2    Color compare value for plane 2
            d1    Color compare value for plane 1
            d0    Color compare value for plane 0
Description: one indicate that color is the same
------------------------------------------------------------
Port-Index: 03h              Port: 03ceh
usage:      Data rotate / Function select register
            d7-d5 Resrved (0)
            d4,d3 Function select
            d2-d0 Rotate count

            d4 d3  Function
            0  0   Write data unmodified
            0  1   Write data ANDed with processor latches
            1  0   Write data ORed with processor latches
            1  1   Write data XORed with processor latches
Description: Rotation is made before writing data
------------------------------------------------------------
Port-Index: 04h              Port: 03ceh
usage:      Read plane select register
            d7-d2 Reserved (0)
            d1,d0 Defines color plane for reading (0-3)
Description: Doesnt matter in color compare mode
------------------------------------------------------------
Port-Index: 05h              Port: 03ceh
usage:      Mode register
            d7    Reserved (0)
            d6    256-colour mode
            d5    Shift register mode
            d4    Odd / Even mode
            d3    Color compare mode enable (1 = enable)
            d2    Reserved (0)
            d1,d0 Write mode

            d1 d0 Write mode
            0  0  Direct write (data rotate, set/reset may apply)
            0  1  Use processor latches as write data
            1  0  Color plane n (0-3) is filled with the value of
                  bit n in the write data
            1  1  Use (rotated) write data ANDed with Bit mask as
                  bit mask. Use set/reset as if set/reset was
                  enable for all planes
Description:
------------------------------------------------------------
Port-Index: 06h              Port: 03ceh
usage:      Miscellaneous register
            d7-d4 Reserved
            d3-d2 Memory map
                  00 = A000h for 128k
                  01 = A000h for 64k
                  10 = B000h for 32k
                  11 = B800h for 32k
            d1    Odd/even enable (used in text modes)
            d0    Graphics mode enable
Description: Memory map defines the location and size of the
             host window
------------------------------------------------------------
Port-Index: 07h              Port: 03ceh
usage:      Color don't care register
            d7-d4 Reserved (0)
            d3    Plane 3 don't care
            d2    Plane 2 don't care
            d1    Plane 1 don't care
            d0    Plane 0 don't care
Description: Color don't care is used in conjunction with color
             compare mode. This register masks particular planes
             from being tested during color compare cycles.
------------------------------------------------------------
Port-Index: 08h              Port: 03ceh
usage:      Bitmask register
Description: The bitmask register is used to mask certain bit
             positons from being modified.
------------------------------------------------------------
Port-Index: -                 Port: 03c0h both index and data
usage:      d7,d6 Reserved
            d5    Palette address source
                  0 = palette can be modified, screen is blanked
                  1 = screen is enable, palette cannot be modified
            d4-d0 Palette register address
Description: Palette register address selects which register of
             the attributes controller will be addres,sed by the
             next I/O write cycle
------------------------------------------------------------
Port-Index: 00h-0fh          Port: 03c0h
usage:      Color palette register
            d6,d7 Reserved
            d5-d0 Color value
Description: not used in 256 color modes
------------------------------------------------------------
Port-Index: 10h              Port: 03c0h
usage:      Mode control register
            d7  p4,p5 source select
            d6  pixel width
            d5  Horizontal panning compatibility
            d4  Reserved
            d3  Background intensify / enable blinking
            d2  Line graphics enable (text modes only)
            d1  display type
            d0  graphics / text mode
Description: p4,p5 source select: selects the source for video
              outputs p4 and p5 to the DACs. If set to zero, p4
              and p5 are driven from the palette registers (normal
              operation). If set to one, p4 and p5 video outputs
              come from bits 0 and 1 of the color select register.
             pixel width: is set to one in mode 13h (256-color mode)
             horizontal panning compatibility: enhances the
              operation of the line compare register of the CRT
              controller, which allows one section of the screen
              to be scrolled while another section remains stationary.
              When this bit is set to one, the stationary
              section of the screen will also be immune to horizontal
              panning.
------------------------------------------------------------
Port-Index: 11h              Port: 03c0h
usage:      Screen border color
Description: In text modes, the screen border color register
             selects the color of the border that sorrounds the
             text display area on the screen. This is also referred
             to by IBM as overscan. Unfortunately, this feature
             does not work properly on EGA displays in 350-line
             modes.
------------------------------------------------------------
Port-Index: 12h              Port: 03c0h
usage:      Color plane enable register
            d7,d6 Reserved
            d5,d4 Video status mux
            d3    Enable color plane 3
            d2    Enable color plane 2
            d1    Enable color plane 1
            d0    Enable color plane 0
Description:  The video status mux bits can be used in conjunction
             with the diagnostic bits of input status register 1
             to read palette registers. For the EGA, this is the
             only means available for reading the palette registers.
              Enable color planes can be used to enable or disable
             color planes at the input to the color lockup table.
             A zero in any of these bit positions will mask the
             data from that color plane. The effect on the display
             will be the same as if that color plane were cleared
             to all zeros.
------------------------------------------------------------
Port-Index: 13h              Port: 03c0h
usage:      Horizontal panning register
            d7-d4 reserved
            d3-d0 Horizontal pan
Description: Horizontal pan allows the display to be shifted
             horizontally one pixel at a time.

             d3-d0      Number of pixels shifted to the left
                        0+,1+,2+     13h     Other modes
                        3+,7,7+
             0          1            0       0
             1          2            1       -
             2          3            2       1
             3          4            3       -
             4          5            4       2
             5          6            5       -
             6          7            6       3
             7          8            7       -
             8          9            -       -
------------------------------------------------------------
Port-Index: 14h              Port: 03c0h
usage:      Color select register
            d7-d4 Reserved
            d3    color 7
            d2    color 6
            d1    color 5
            d0    color 4
Description:  Color 7 and color 6: are normally used as the high
             order bits of the eight-bit video color data from the
             attribute controller to the DACs. The only exceptions
             are 256-color modes
              Color 5 and color 4: can be used in place of the p5
             and p6 outputs from the palette registers (see mode
             control register - index 10h). In 16-color modes, the
             color select register can be used to rapidly cycle
             between sets of colors in the video DAC.
------------------------------------------------------------
Port-Index: -                Port: 03c6h
usage:      Pixel mask register
Description: ???
------------------------------------------------------------
Port-Index: -                Port: 03c7h
usage:      DAC state register (read-only)
Description: if d0 and d1 is set to zero it indicates that
             the lookup table is in a write mode
------------------------------------------------------------
Port-Index: -                Port: 03c7h
usage:      Lookup table read index register (Write only)
Description: Used when you want to read the palette (set color
             number)
------------------------------------------------------------
Port-Index: -                Port: 03c8h
usage:      Lookup table write index register
Description: Used when you want to change palette (set color
             number)
------------------------------------------------------------
Port-Index: -                Port: 03c9h
usage:      Lookup table data register
Description: Read color value (Red-Green-Blue) or write same data.
------------------------------------------------------------
----------1000-------------------------------
INT 10 - VIDEO - SET VIDEO MODE
        AH = 00h
        AL = mode (see below)
Return: AL = video mode flag (Phoenix BIOS)
             20h mode > 7
             30h modes <= 7 except mode 6
             3Fh mode 6
        AL = CRT controller mode byte (Phoenix 386 BIOS v1.10)
Notes:       IBM standard modes do not clear the screen if the high bit of AL is set
      (EGA or higher only)
SeeAlso: AX=0070h,AX=007Eh,AX=10F0h,AX=6F05h,AH=FFh"GO32",INT 5F/AH=00h

Values for video mode:
     text/ text   pixel pixel    colors  display  scrn system
     grph  resol  box   resoltn          pages   addr
 00h = T   40x25  8x14           16gray     8    B800 EGA
     = T   40x25  8x16             16       8    B800 MCGA
     = T   40x25  9x16             16       8    B800 VGA
 01h = T   40x25  8x14             16       8    B800 EGA
     = T   40x25  8x16             16       8    B800 MCGA
     = T   40x25  9x16             16       8    B800 VGA
 02h = T   80x25  8x14           16gray     4    B800 EGA
     = T   80x25  8x16             16       4    B800 MCGA
     = T   80x25  9x16             16       4    B800 VGA
 03h = T   80x25  8x14             16       4    B800 EGA
     = T   80x25  8x16             16       4    B800 MCGA
     = T   80x25  9x16             16       4    B800 VGA
 04h = G   40x25  8x8     320x200           4    B800 CGA,PCjr,EGA,MCGA,VGA
 05h = G   40x25  8x8     320x200 4gray          B800 CGA,PCjr,EGA
     = G   40x25  8x8     320x200   4            B800 MCGA,VGA
 06h = G   80x25  8x8     640x200   2            B800 CGA,PCjr,EGA,MCGA,VGA
 07h = T   80x25  9x14            mono     var   B000 MDA,Hercules,EGA
     = T   80x25  9x16            mono           B000 VGA
 0Bh =                 reserved (used internally by EGA BIOS)
 0Ch =                 reserved (used internally by EGA BIOS)
 0Dh = G   40x25  8x8     320x200  16       8    A000 EGA,VGA
 0Eh = G   80x25  8x8     640x200  16       4    A000 EGA,VGA
 0Fh = G   80x25  8x14    640x350 mono      2    A000 EGA,VGA
 10h = G   80x25  8x14    640x350   4       2    A000 64k EGA
     = G                  640x350  16            A000 256k EGA,VGA
 11h = G   80x30  8x16    640x480 mono           A000 VGA,MCGA,ATI EGA,ATI VIP
 12h = G   80x30  8x16    640x480 16/256k        A000 VGA,ATI VIP
     = G   80x30  8x16    640x480 16/64          A000 ATI EGA Wonder
 13h = G   40x25  8x8     320x200 256/256k       A000 VGA,MCGA,ATI VIP
----------1001-------------------------------
INT 10 - VIDEO - SET TEXT-MODE CURSOR SHAPE
        AH = 01h
        CH = bit 7    should be zero
             bits 6,5 cursor blink
                (00=normal, 01=invisible, 10=erratic, 11=slow)
                (00=normal, other=invisible on EGA/VGA)
             bits 4-0 top scan line containing cursor
        CL = bottom scan line containing cursor (bits 0-4)
Notes: buggy on EGA systems--BIOS remaps cursor shape in 43 line modes, but
       returns unmapped cursor shape
       applications which wish to change the cursor by programming the
       hardware directly on EGA or above should call INT 10/AX=1130h or
       read 0040h:0085h first to determine the current font height
BUG:   AMI 386 BIOS and AST Premier 386 BIOS will lock up the system if AL
       is not equal to the current video mode
SeeAlso: AH=03h,AX=CD05h
----------1002-------------------------------
INT 10 - VIDEO - SET CURSOR POSITION
        AH = 02h
        BH = page number
              0-3 in modes 2&3
              0-7 in modes 0&1
                0 in graphics modes
        DH = row (00h is top)
        DL = column (00h is left)
SeeAlso: AH=03h,AH=05h
----------1003-------------------------------
INT 10 - VIDEO - GET CURSOR POSITION AND SIZE
        AH = 03h
        BH = page number
              0-3 in modes 2&3
              0-7 in modes 0&1
                0 in graphics modes
Return: AX = 0000h (Phoenix BIOS)
        CH = start scan line
        CL = end scan line
        DH = row (00h is top)
        DL = column (00h is left)
Notes: a separate cursor is maintained for each of up to 8 display pages
       many ROM BIOSes incorrectly return the default size for a color display
       (start 06h, end 07h) when a monochrome display is attached
SeeAlso: AH=01h,AH=02h
----------1004-------------------------------
INT 10 - VIDEO - READ LIGHT PEN POSITION (EGA Only)
        AH = 04h
Return: AH = light pen trigger flag
             00h not down/triggered
             01h down/triggered 
        DH,DL = row,column of character light pen is on 
        CH = pixel row (graphics modes 04h-06h)
        CX = pixel row (graphics modes with >200 rows)
        BX = pixel column
Notes: on a CGA, returned column numbers are always multiples of 2 (320-
       column modes) or 4 (640-column modes)
       returned row numbers are only accurate to two lines
----------1005-------------------------------
INT 10 - VIDEO -  SELECT ACTIVE DISPLAY PAGE
        AH = 05h
        AL = new page number (00h to number of pages - 1) (see AH=00h)
SeeAlso: AH=0Fh
----------1006-------------------------------
INT 10 - VIDEO - SCROLL UP WINDOW
        AH = 06h
        AL = number of lines by which to scroll up (00h = clear entire window)
        BH = attribute used to write blank lines at bottom of window
     CH,CL = row,column of window's upper left corner
     DH,DL = row,column of window's lower right corner
Note:  affects only the currently active page (see AH=05h)
Warning: some implementations have a bug which destroys BP
SeeAlso: AH=07h,AH=72h,AH=73h
----------1007-------------------------------
INT 10 - VIDEO - SCROLL DOWN WINDOW
        AH = 07h
        AL = number of lines by which to scroll down (00h=clear entire window)
        BH = attribute used to write blank lines at top of window
     CH,CL = row,column of window's upper left corner
     DH,DL = row,column of window's lower right corner
Note:  affects only the currently active page (see AH=05h)
Warning: some implementations have a bug which destroys BP
SeeAlso: AH=06h,AH=72h,AH=73h
----------1008-------------------------------
INT 10 - VIDEO - READ CHARACTER AND ATTRIBUTE AT CURSOR POSITION
        AH = 08h
        BH = page number (00h to number of pages - 1) (see AH=00h)
Return: AH = attribute
             bit    7: blink
             bits 6-4: background color
                       000 black
                       001 blue
                       010 green
                       011 cyan
                       100 red
                       101 magenta
                       110 brown
                       111 white
             bits 3-0: foreground color
                       0000 black       1000 dark gray
                       0001 blue        1001 light blue
                       0010 green       1010 light green
                       0011 cyan        1011 light cyan
                       0100 red         1100 light red
                       0101 magenta     1101 light magenta
                       0110 brown       1110 yellow
                       0111 light gray  1111 white
        AL = character
Notes: for monochrome displays, a foreground of 1 with background 0 is underlined
       the blink bit may be reprogrammed to enable intense background colors
       using AX=1003h or by programming the CRT controller
SeeAlso: AH=09h,AX=1003h
----------1009-------------------------------
INT 10 - VIDEO - WRITE CHARACTER AND ATTRIBUTE AT CURSOR POSITION
        AH = 09h
        AL = character to display
        BH = page number (00h to number of pages - 1) (see AH=00h)
        BL = attribute (text mode) or color (graphics mode)
             if bit 7 set in graphics mode, character is xor'ed onto screen
        CX = number of times to write character
Notes: all characters are displayed, including CR, LF, and BS
       replication count in CX may produce an unpredictable result in graphics
       modes if it is greater than the number of positions remaining in the
       current row
SeeAlso: AH=08h,AH=0Ah,AH=4Bh,INT 17/AH=60h,INT 1F,INT 43,INT 44
----------100A-------------------------------
INT 10 - VIDEO - WRITE CHARACTER ONLY AT CURSOR POSITION
        AH = 0Ah
        AL = character to display
        BH = page number (00h to number of pages - 1) (see AH=00h)
        BL = attribute (PCjr only) or color (graphics mode)
             if bit 7 set in graphics mode, character is xor'ed onto screen
        CX = number of times to write character
Notes: all characters are displayed, including CR, LF, and BS
       replication count in CX may produce an unpredictable result in graphics
       modes if it is greater than the number of positions remaining in the
       current row
SeeAlso: AH=08h,AH=09h,AH=4Bh,INT 17/AH=60h,INT 1F,INT 43,INT 44
----------100B--BH00-------------------------
INT 10 - VIDEO - SET BACKGROUND/BORDER COLOR
        AH = 0Bh
        BH = 00h
        BL = background/border color (border only in text modes)
SeeAlso: AH=0Bh/BH=01h
----------100B--BH01-------------------------
INT 10 - VIDEO - SET PALETTE
        AH = 0BH
        BH = 01h
        BL = palette ID
             00h background, green, red, and brown/yellow
             01h background, cyan, magenta, and white
SeeAlso: AH=0Bh/BH=00h
----------100C-------------------------------
INT 10 - VIDEO - WRITE GRAPHICS PIXEL
        AH = 0Ch
        BH = page number
        AL = pixel color (if bit 7 set, value is xor'ed onto screen)
        CX = column
        DX = row
Notes: valid only in graphics modes
       BH is ignored if the current video mode supports only one page
SeeAlso: AH=0Dh,AH=46h
----------100D-------------------------------
INT 10 - VIDEO - READ GRAPHICS PIXEL
        AH = 0Dh
        BH = page number
        CX = column
        DX = row
Return: AL = pixel color
Notes: valid only in graphics modes
       BH is ignored if the current video mode supports only one page
SeeAlso: AH=0Ch,AH=47h
----------100E-------------------------------
INT 10 - VIDEO - TELETYPE OUTPUT
        AH = 0Eh
        AL = character to write
        BH = page number
        BL = foreground color (graphics modes only)
Notes: characters 07h (BEL), 08h (BS), 0Ah (LF), and 0Dh (CR) are interpreted
       and do the expected things
       IBM PC ROMs dated 4/24/81 and 10/19/81 require that BH be the same as
       the current active page
SeeAlso: AH=02h,AH=0Ah
----------100F-------------------------------
INT 10 - VIDEO - GET CURRENT VIDEO MODE
        AH = 0Fh
Return: AH = number of character columns
        AL = display mode (see AH=00h)
        BH = active page (see AH=05h)
Notes: if mode was set with bit 7 set ("no blanking"), the returned mode will
       also have bit 7 set
       EGA, VGA, and UltraVision return either AL=03h (color) or AL=07h
       (monochrome) in all extended-row text modes
SeeAlso: AH=00h,AH=05h,AX=1130h,AX=CD04h
----------101000----------------------------
INT 10 - VIDEO - SET SINGLE PALETTE REGISTER (PCjr,EGA,MCGA,VGA)
        AX = 1000h
        BL = palette register number (00h-0Fh)
           = attribute register number (undocumented)
             10h attribute mode control register (should let BIOS control this)
             11h overscan color register (see also AX=1001h)
             12h color plane enable register (bits 3-0 enable corresponding
                 text attribute bit)
             13h horizontal PEL panning register
             14h color select register
        BH = color or attribute register value
Notes: on MCGA, only BX = 0712h is supported
       under UltraVision, the palette locking status (see AX=CD01h)
       determines the outcome
SeeAlso: AX=1002h,AX=1007h,AX=CD01h
----------101001-----------------------------
INT 10 - VIDEO - SET BORDER (OVERSCAN) COLOR (PCjr,EGA,VGA)
        AX = 1001h
        BH = border color (00h-3Fh)
BUG: the original IBM VGA BIOS incorrectly updates the parameter save area
     and places the border color at offset 11h of the palette table
     rather than offset 10h
Note: under UltraVision, the palette locking status (see AX=CD01h)
      determines the outcome
SeeAlso: AX=1002h,AX=1008h,AX=CD01h
----------101002-----------------------------
INT 10 - VIDEO - SET ALL PALETTE REGISTERS (PCjr,EGA,VGA)
        AX = 1002h
        ES:DX -> palette register list
Note: under UltraVision, the palette locking status (see AX=CD01h)
      determines the outcome
SeeAlso: AX=1000h,AX=1001h,AX=1009h,AX=CD01h

Format of palette register list:
Offset  Size     Description
 00h   16 BYTEs  colors for palette registers 00h through 0Fh
 10h      BYTE   border color
----------101003-----------------------------
INT 10 - VIDEO - TOGGLE INTENSITY/BLINKING BIT (Jr, PS, TANDY 1000, EGA, VGA)
        AX = 1003h
        BL = new state
             00h background intensity enabled
             01h blink enabled
Note: although there is no function to get the current status, bit 5 of
      0040h:0065h indicates the state
SeeAlso: AH=08h
----------101007-----------------------------
INT 10 - VIDEO - GET INDIVIDUAL PALETTE REGISTER (VGA,UltraVision v2+)
        AX = 1007h
        BL = palette or attribute (undoc) register number (see AX=1000h)
Return: BH = palette or attribute register value
Notes: UltraVision v2+ supports this function even on color EGA systems in
       video modes 00h-03h, 10h, and 12h; direct programming of the palette
       registers will cause incorrect results because the EGA registers are
       write-only.  To guard against older versions or unsupported video
       modes, programs which expect to use this function on EGA systems
       should set BH to FFh on entry.
SeeAlso: AX=1000h,AX=1009h
----------101008-----------------------------
INT 10 - VIDEO - READ OVERSCAN (BORDER COLOR) REGISTER (VGA,UltraVision v2+)
        AX = 1008h
Return: BH = border color (00h-3Fh)
Notes: UltraVision v2+ supports this function even on color EGA systems in
       video modes 00h-03h, 10h, and 12h; direct programming of the palette
       registers will cause incorrect results because the EGA registers are
       write-only.  To guard against older versions or unsupported video
       modes, programs which expect to use this function on EGA systems
       should set BH to FFh on entry.
SeeAlso: AX=1001h
----------101009-----------------------------
INT 10 - VIDEO - READ ALL PALETTE REGISTERS AND OVERSCAN REGISTER (VGA)
        AX = 1009h
        ES:DX -> 17-byte buffer (see AX=1002h)
Notes: UltraVision v2+ supports this function even on color EGA systems in
       video modes 00h-03h, 10h, and 12h; direct programming of the palette
       registers will cause incorrect results because the EGA registers are
       write-only.  To guard against older versions or unsupported video
       modes, programs which expect to use this function on EGA systems
       should set the ES:DX buffer to FFh before calling.
SeeAlso: AX=1002h,AX=1007h,AX=CD02h
----------101010-----------------------------
INT 10 - VIDEO - SET INDIVIDUAL DAC REGISTER (VGA/MCGA)
        AX = 1010h
        BX = register number
        CH = new value for green (0-63)
        CL = new value for blue (0-63)
        DH = new value for red (0-63)
SeeAlso: AX=1012h,AX=1015h
----------101012-----------------------------
INT 10 - VIDEO - SET BLOCK OF DAC REGISTERS (VGA/MCGA)
        AX = 1012h
        BX = starting color register
        CX = number of registers to set
        ES:DX -> table of 3*CX bytes where each 3 byte group represents one
                 byte each of red, green and blue (0-63)
SeeAlso: AX=1010h,AX=1017h
----------101013-----------------------------
INT 10 - VIDEO - SELECT VIDEO DAC COLOR PAGE (VGA)
        AX = 1013h
        BL = subfunction
             00h select paging mode
                 BH = 00h select 4 blocks of 64
                 BH = 01h select 16 blocks of 16
             01h select page
                 BH = page number (00h to 03h) or (00h to 0Fh)
Note: not valid in mode 13h
SeeAlso: AX=101Ah
----------101015-----------------------------
INT 10 - VIDEO - READ INDIVIDUAL DAC REGISTER (VGA/MCGA)
        AX = 1015h
        BL = palette register number
Return: DH = red value
        CH = green value
        CL = blue value
SeeAlso: AX=1010h,AX=1017h
----------101017-----------------------------
INT 10 - VIDEO - READ BLOCK OF DAC REGISTERS (VGA/MCGA)
        AX = 1017h
        BX = starting palette register
        CX = number of palette registers to read
        ES:DX -> buffer (3 * CX bytes in size) (see also AX=1012h)
Return: buffer filled with CX red, green and blue triples
SeeAlso: AX=1012h,AX=1015h
----------101018-----------------------------
INT 10 - VIDEO - undocumented - SET PEL MASK (VGA/MCGA)
        AX = 1018h
        BL = new PEL value
SeeAlso: AX=1019h
----------101019-----------------------------
INT 10 - VIDEO - undocumented - READ PEL MASK (VGA/MCGA)
        AX = 1019h
Return: BL = value read
SeeAlso: AX=1018h
----------10101A-----------------------------
INT 10 - VIDEO - GET VIDEO DAC COLOR-PAGE STATE (VGA)
        AX = 101Ah
Return: BL = paging mode
             00h four pages of 64
             01h sixteen pages of 16
        BH = current page
SeeAlso: AX=1013h
----------10101B-----------------------------
INT 10 - VIDEO - PERFORM GRAY-SCALE SUMMING (VGA/MCGA)
        AX = 101Bh
        BX = starting palette register
        CX = number of registers to convert
SeeAlso: AH=12h/BL=33h
----------1011-------------------------------
INT 10 - VIDEO - TEXT-MODE CHARACTER GENERATOR FUNCTIONS (PS, EGA, VGA)
        AH = 11h
The following functions will cause a mode set, completely resetting
the video environment, but without clearing the video buffer
        AL = 00h, 10h: load user-specified patterns
             ES:BP -> user table
             CX  = count of patterns to store
             DX  = character offset into map 2 block
             BL  = block to load in map 2
             BH  = number of bytes per character pattern
        AL = 01h, 11h: load ROM monochrome patterns (8 by 14)
             BL  = block to load
        AL = 02h, 12h: load ROM 8 by 8 double-dot patterns
             BL  = block to load
        AL = 03h: set block specifier
             BL  = block specifier
               (EGA/MCGA) bits 0,1 = block selected by chars with attribute bit 3=0
                          bits 2,3 = block selected by chars with attribute bit 3=1
                    (VGA) bits 0,1,4 = block selected by attribute bit 3 = 0
                          bits 2,3,5 = block selected by attribute bit 3 = 1
        AL = 04h, 14h: load ROM 8x16 character set (VGA)
             BL  = block to load
The routines called with AL=1xh are designed to be called only
immediately after a mode set and are similar to the routines called
with AL=0xh, except that:
      Page 0 must be active.
      Bytes/character is recalculated.
      Max character rows is recalculated.
      CRT buffer length is recalculated.
      CRTC registers are reprogrammed as follows:
           R09 = bytes/char-1 ; max scan line (mode 7 only)
           R0A = bytes/char-2 ; cursor start
           R0B = 0            ; cursor end
           R12 = ((rows+1)*(bytes/char))-1 ; vertical display end
           R14 = bytes/char     ; underline loc
                  (*** BUG: should be 1 less ***)
SeeAlso: AX=CD10h
----------1011-------------------------------
INT 10 - VIDEO - GRAPHICS-MODE CHARACTER GENERATOR FUNCTIONS (PS, EGA, VGA)
        AH = 11h
        AL = 20h: set user 8 by 8 graphics characters (INT 1F)
                  ES:BP -> user table
        AL = 21h: set user graphics characters
                  ES:BP -> user table
                  CX      = bytes per character
                  BL      = row specifier
                        00h user set
                            DL = number of rows
                        01h 14 rows
                        02h 25 rows
                        03h 43 rows
        AL = 22h: ROM 8 by 14 set
                  BL = row specifier (see above)
        AL = 23h: ROM 8 by 8 double dot
                  BL = row specifier (see above)
        AL = 24h: load 8x16 graphics characters (VGA/MCGA)
                  BL = row specifier (see above)
        AL = 29h: load 8x16 graphics characters (Compaq Systempro)
                  BL = row specifier (see above)
Notes: these functions are meant to be called only after a mode set
       UltraVision v2+ sets INT 43 to the appropriate font for AL=22h,23h,24h,
       and 29h
SeeAlso: INT 1F, INT 43
----------101130-----------------------------
INT 10 - VIDEO - GET FONT INFORMATION (EGA, MCGA, VGA)
        AX = 1130h
        BH = pointer specifier
             00h INT 1Fh pointer
             01h INT 43h pointer
             02h ROM 8x14 character font pointer
             03h ROM 8x8 double dot font pointer
             04h ROM 8x8 double dot font (high 128 characters)
             05h ROM alpha alternate (9 by 14) pointer (EGA,VGA)
             06h ROM 8x16 font (MCGA, VGA)
             07h ROM alternate 9x16 font (VGA only)
             11h (UltraVision v2+) 8x20 font (VGA) or 8x19 font (autosync EGA)
             12h (UltraVision v2+) 8x10 font (VGA) or 8x11 font (autosync EGA)
Return: ES:BP = specified pointer
        CX    = bytes/character
        DL    = character rows on screen - 1
Note: for UltraVision v2+, the 9xN alternate fonts follow the corresponding
      8xN font at ES:BP+256N
SeeAlso: AX=1100h,AX=1120h,INT 1F,INT 43
----------1012--BL10-------------------------
INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (PS, EGA, VGA, MCGA) - GET EGA INFO
        AH = 12h
        BL = 10h
Return: BH = 00h color mode in effect (I/O port 3Dxh)
             01h mono mode in effect (I/O port 3Bxh)
        BL = 00h  64k bytes memory installed
             01h 128k bytes memory installed
             02h 192k bytes memory installed
             03h 256k bytes memory installed
        CH = feature bits
        CL = switch settings
----------1012--BL20-------------------------
INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (PS,EGA,VGA,MCGA) - ALTERNATE PRTSC
        AH = 12h
        BL = 20h  select alternate print screen routine
Notes: installs a PrtSc routine from the video card's BIOS to replace the
       default PrtSc handler from the ROM BIOS, which usually does not
       understand screen heights other than 25 lines
       some adapters disable print-screen instead of enhancing it
SeeAlso: INT 05
----------1012--BL30-------------------------
INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (VGA) - SELECT VERTICAL RESOLUTION
        AH = 12h
        BL = 30h
        AL = vertical resolution
             00h 200 scan lines
             01h 350 scan lines
             02h 400 scan lines
Return: AL = 12h if function supported
----------1012--BL31-------------------------
INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (VGA, MCGA) - PALETTE LOADING
        AH = 12h
        BL = 31h
        AL = 00h enable default palette loading
             01h disable default palette loading
Return: AL = 12h if function supported
----------1012--BL32-------------------------
INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (VGA, MCGA) - VIDEO ADDRESSING
        AH = 12h
        BL = 32h
        AL = 00h enable video addressing
             01h disable video addressing
Return: AL = 12h if function supported
----------1012--BL33-------------------------
INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (VGA, MCGA) - GRAY-SCALE SUMMING
        AH = 12h
        BL = 33h
        AL = 00h enable gray scale summing
             01h disable gray scale summing
Return: AL = 12h if function supported
SeeAlso: AX=101Bh,AX=BF06h
----------1012--BL34-------------------------
INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (VGA) - CURSOR EMULATION
        AH = 12h
        BL = 34h
        AL = 00h enable alphanumeric cursor emulation
             01h disable alphanumeric cursor emulation
Return: AL = 12h if function supported
----------1012--BL35-------------------------
INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (PS) - DISPLAY-SWITCH INTERFACE
        AH = 12h
        BL = 35h
        AL = 00h initial adapter video off
             01h initial planar video on
             02h switch active video off
             03h switch inactive video on
             80h *UNDOCUMENTED* set system board video active flag
        ES:DX -> buffer (128 byte save area if AL = 0, 2 or 3)
Return: AL = 12h if function supported
----------1012--BL36-------------------------
INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (PS, VGA) - VIDEO REFRESH CONTROL
        AH = 12h
        BL = 36h
        AL = 00h enable refresh
             01h disable refresh
Return: AL = 12h if function supported
----------1013-------------------------------
INT 10 - VIDEO - WRITE STRING (AT and later,EGA)
        AH = 13h
        AL = write mode
             bit 0: update cursor after writing
                 1: string contains alternating characters and attributes
        BH = page number
        BL = attribute if string contains only characters
        CX = number of characters in string
        DH,DL = row,column at which to start writing
        ES:BP -> string to write
Notes: recognizes CR, LF, BS, and bell
       also available PC or XT with EGA or higher
       HP 95LX only supports write mode 00h
BUG: on the IBM VGA Adapter, any scrolling which may occur is performed on
     the active page rather than the requested page
SeeAlso: AH=09h,AH=0Ah
----------101A-------------------------------
INT 10 - VIDEO - DISPLAY COMBINATION (PS,VGA/MCGA)
        AH = 1Ah
        AL = 00h read display combination code
Return:          BL = active display code (see below)
                 BH = alternate display code
             01h set display combination code
                 BL = active display code (see below)
                 BH = alternate display code
Return: AL = 1Ah if function was supported

Values for display combination code:
  00h no display
  01h monochrome adapter w/ monochrome display
  02h CGA w/ color display
  03h reserved
  04h EGA w/ color display
  05h EGA w/ monochrome display
  06h PGA w/ color display
  07h VGA w/ monochrome analog display
  08h VGA w/ color analog display
  09h reserved
  0Ah MCGA w/ digital color display
  0Bh MCGA w/ monochrome analog display
  0Ch MCGA w/ color analog display
  FFh unknown display type
----------101B-------------------------------
INT 10 - VIDEO - FUNCTIONALITY/STATE INFORMATION (PS,VGA/MCGA)
        AH = 1Bh
        BX = implementation type
             0000h return functionality/state information
        ES:DI -> 64 byte buffer for state information (see below)
Return: AL = 1Bh if function supported
        ES:DI buffer filled with state information
SeeAlso: AH=15h

Format of state information:
Offset Size   Description
 00h   DWORD  address of static functionality table (see below)
 04h   BYTE   video mode in effect
 05h   WORD   number of columns
 07h   WORD   length of regen buffer in bytes
 09h   WORD   starting address of regen buffer
 0Bh   WORD   cursor position for page 0
 0Dh   WORD   cursor position for page 1
 0Fh   WORD   cursor position for page 2
 11h   WORD   cursor position for page 3
 13h   WORD   cursor position for page 4
 15h   WORD   cursor position for page 5
 17h   WORD   cursor position for page 6
 19h   WORD   cursor position for page 7
 1Bh   WORD   cursor type
 1Dh   BYTE   active display page
 1Eh   WORD   CRTC port address
 20h   BYTE   current setting of register (3?8)
 21h   BYTE   current setting of register (3?9)
 22h   BYTE   number of rows
 23h   WORD   bytes/character
 25h   BYTE   display combination code of active display
 26h   BYTE   DCC of alternate display
 27h   WORD   number of colors supported in current mode
 29h   BYTE   number of pages supported in current mode
 2Ah   BYTE   number of scan lines active
              (0,1,2,3) = (200,350,400,480)
 2Bh   BYTE   primary character block
 2Ch   BYTE   secondary character block
 2Dh   BYTE   miscellaneous flags
              bit 0 all modes on all displays on
                  1 gray summing on
                  2 monochrome display attached
                  3 default palette loading disabled
                  4 cursor emulation enabled
                  5 0 = intensity; 1 = blinking
                  6 PS/2 P70 plasma display (without 9-dot wide font) active
                  7 reserved
 2Eh  3 BYTEs reserved (00h)
 31h   BYTE   video memory available
              00h = 64K, 01h = 128K, 02h = 192K, 03h = 256K
 32h   BYTE   save pointer state flags
              bit 0 512 character set active
                  1 dynamic save area present
                  2 alpha font override active
                  3 graphics font override active
                  4 palette override active
                  5 DCC override active
                  6 reserved
                  7 reserved
 33h 13 BYTEs reserved (00h)

Format of Static Functionality Table:
Offset Size    Description
 00h   BYTE    modes supported #1
               bit 0 to bit 7 = 1 modes 0,1,2,3,4,5,6 supported
 01h   BYTE    modes supported #2
               bit 0 to bit 7 = 1 modes 8,9,0Ah,0Bh,0Ch,0Dh,0Eh,0Fh supported
 02h   BYTE    modes supported #3
               bit 0 to bit 3 = 1 modes 10h,11h,12h,13h supported
               bit 4 to bit 7 reserved
 03h  4 BYTEs  reserved
 07h   BYTE    scan lines supported
               bit 0 to bit 2 = 1 if scan lines 200,350,400 supported
 08h   BYTE    total number of character blocks available in text modes
 09h   BYTE    maximum number of active character blocks in text modes
 0Ah   BYTE    miscellaneous function flags #1
               bit 0 all modes on all displays function supported
                   1 gray summing function supported
                   2 character font loading function supported
                   3 default palette loading enable/disable supported
                   4 cursor emulation function supported
                   5 EGA palette present
                   6 color palette present
                   7 color paging function supported
 0Bh   BYTE    miscellaneous function flags #2
               bit 0 light pen supported
                   1 save/restore state function 1Ch supported
                   2 intensity blinking function supported
                   3 Display Combination Code supported
                 4-7 reserved
 0Ch   WORD    reserved
 0Eh   BYTE    save pointer function flags
               bit 0 512 character set supported
                   1 dynamic save area supported
                   2 alpha font override supported
                   3 graphics font override supported
                   4 palette override supported
                   5 DCC extension supported
                   6 reserved
                   7 reserved
 0Fh   BYTE    reserved
----------101C-------------------------------
INT 10 - VIDEO - SAVE/RESTORE VIDEO STATE (PS50+,VGA)
        AH = 1Ch
        AL = 00h return state buffer size
Return: BX = number of 64-byte blocks needed
             01h save video state
                 ES:BX -> buffer
             02h restore video state
                 ES:BX -> buffer containing previously saved state
        CX = requested states
             bit 0 video hardware
                 1 BIOS data areas
                 2 color registers and DAC state
              3-15 reserved
Return: AL = 1Ch if function supported
            ???????????????????????????????????????????????
            ? Introduction to Programming the SVGA Cards  ?
            ???????????????????????????????????????????????

            Written for the PC-GPE by Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.


?????????????????????????????????????????????????????????????????????????????
? SVGA Section Overview ?
?????????????????????????

The vast majority of the information presented in the PC-GPE was obtained
from the book "Programmer's Guide to the EGA and VGA Cards - Includes Super
VGAs, Second Edition" by Richard Ferraro, ISBN 0-201-57025-4, published
by Addison-Wesley. This book is by far the most comprehensive VGA/SVGA
reference I have seen to date and is more than worth it's price tag. I
heartily recommend it to anyone wishing to do any serious graphics
programming for the PC.

The PC-GPE SVGA section was originally not going to be included in version 1
due to the fact that I have only been able to verify that the info on the
Paradise SVGA is correct. I will include it however, in the hope that
everyone (and I mean *EVERYONE*) who reads these files and tries out the
routines will e-mail me with the results they get so I can make the
modifications in time for version 2.

I will need to know these things:

1) Your SVGA board name

2) The id and revision number of the chip inside (if possible)

3) What you tried and the results you got. This applies to *all* routines,
   bank switching, chip detection etc....  I need to know everything!

If a routine doesn't work as expected then let me know if it's doing anything
at all. "The routine is stuffed you idiot" won't exactly help me much, but
"I can only read pixels in bank 0 you idiot" just might......

And of course there's always the chance that I've misunderstood my references
so I need to have my mistakes pointed out to me as well. I'm a big boy...I
can take it!

????????????????????????????????????????????????????????????????????????????
? Writing to the VGA Ports ?
????????????????????????????

Many of the PC-GPE SVGA texts have the PortW Pascal command as follows:

PortW[PORTNUM] := VALUE;

This command writes a 16 bit word to the port, the same as the asm op code:

out dx, ax

The effect of this code is the same as the following two Pascal statements:

Port[PORTNUM] := Lo(VALUE);
Port[PORTNUM + 1] := Hi(VALUE);

I'm not sure if this is common to all the PC ports or only works on the
VGA. (Perhaps someone could enlighten me?)

The PortW command is very handy when writing to the SVGA extended registers.
The SVGA register sets are all extensions of the VGA register sets and
use an indexed addressing scheme to cut down on the number of ports they
use. The texts often have register maps which look similar to the following:

          PR0A Address Offset A
          Index : 09h at port 3CEh
          Read/Write at port 3CFh
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
                ?????????????????????????
                           Bank

For this particular map, the register name is PR0A Offset A. To select the
register and get it ready for reading and/or writing you write the value
09h to port 3CEh (the index port). The register can then be read from or
written to port 3CFh (the read/write port).

????????????????????????????????????????????????????????????????????????????
? Bank Switching ?
??????????????????

In real mode, the PC has addresses A000:0000-B000:FFFF allocated for video
memory, although most graphics modes only use the A000 segment.

If you set an SVGA card to 640x480x256 color mode (for example) then there
will be a total of 307200 pixels on the screen. Since each pixel takes up
one byte in 256 color modes around 300K of video memory will be used to
store the screen data. In most cases all this memory is accessed through the
A000 segment. When you initially set the mode, bank 0 on the card will be
active and anything you read to or write from this segment will be in the
first 64K bytes in video memory (i.e. lines 0-101 and the first 256 bytes in
line number 102). If you want to access the next 64K you must switch the
card to bank number 1 so that the A000 segment now maps to the second bank,
and so forth.

The problem here is that each card has a different method of doing the bank
switching. The PC-GPE files contain info on how to do the bank switching for
a number of the most commonly used SVGA cards. The VESA standard helped
inject some sanity into the otherwise chaotic world of SVGA programming by
introducing a "standard" method of bank switching for all cards.

A note should be made here about bank granularity. In the section above I
assumed that bank 0 corresponded to the first 64K, bank 1 to the next etc..
ie each bank has a 64K granularity. This is true for most cards, but some
do have smaller granularities (see the table below). The Paradise for
instance has a 4K granularity. It's very similar in concept to the PC's
segmented memory, segments are 64K long but they have a 16 byte granularity.
The Paradise chip's banks are also 64K long, but they have a 4K granularity.
All the bank switching code given in the PC-GPE SVGA files adjust for this
so that your code can assume the card has a 64K granularity in all cases.


?????????????????????????????????????????????????????????????????????????????
? SVGA Libraries ?
??????????????????

There are a few SVGA libraries available via anonymouse ftp. I haven't had
a chance to use any of them yet, but I've heard some of them are pretty good,
so they might be worth checking out. Here's two C libraries that I know of:

     site: ftp.fasttax.com
directory: /pc/graphic/scitech/beta
 filename: svkt44bl.zip

     site: garbo.uwasa.fi
directory: /pc/programming
 filename: SVGACC20.ZIP


????????????????????????????????????????????????????????????????????????????
? Common SVGA Cards ?
?????????????????????

The PC-GPE files contain information on programming the 7 VGA "standards"
as covered by Ferraro. According to Ferraro the majority of SVGA cards on
the market today conform to one of these standards. The standards are
Ati, Chips and Technologies, Genoa, Paradise, Trident, Tseng and Video7.
I've also included a file on the VESA specifications (VESASP12.TXT). VESA
seems to be the way to go now since public domain drivers are available for
most cards and you only need to write one set of graphics drivers if you use
it. VESA BIOS calls can be slow however, so if your program needs to do LOTS
of bank switching then you may need to work with the cards on a hardware
level.

The following is a list of common SVGA's along with the chip it is based on,
the number of banks the card contains and the modes they support. The GR
field is the bank granularity. This information was obtained by examining
the configuration files in the shareware program VPIC. VPIC is a great
little program which supports numerous graphics file formats as well
as all the cards listed below (and a few more). VPIC can be obtained via
anonymous ftp from oak.oakland.edu, directory /pub/msdos/gif, filename
vpic. I tried to contact the author so I'd feel better about blatently
ripping all the info out of his data files but he doesn't seem to have an
e-mail address. Are you out there Bob Montgomery?

Quite a number of the chip sets in the list are not mentioned in Ferraro. If
anyone has information on programming any of them drop me a line.

Each mode in the table below has a mode number. To set the mode, load the AX
register with this value and do an interrupt 10h. Some modes below have two
numbers. In these cases load AX with the first number and BX with the second
before calling interrupt 10h.

Only 16 and 256 color are presented in the table below. True-color modes
are not included in this version.


Board                   Chip           Banks     Modes    Resolution  Col GR
?????????????????????????????????????????????????????????????????????????????

Acumos                  ACUMOS         8         5Eh      640x400     256 64k
                                                 5Fh      640x480     256 64k
                                                 5Ch      800x256     256 64k
                                                 10h      640x350     16  64k
                                                 12h      640x480     16  64k
                                                 58h      800x600     16  64k
                                                 5dh      1024x768    16  64k

Ahead A Chip            AHEADA         4/8       60h      640x400     256 64k
                                                 61h      640x480     256 64k
                                                 62h      800x600     256 64k
                                                 6Ah      800x600     16  64k
                                                 74h      1024x768    16  64k

Ahead B Chip            AHEADB         8/16      60h      640x400     256 64k
                                                 61h      640x480     256 64k
                                                 62h      800x600     256 64k
                                                 63h      1024x768    256 64k
                                                 6Ah      800x600     16  64k
                                                 74h      1024x768    16  64k

ATI VGA Wonder          ATI OLD        4/8       61h      640x400     256 32k
                                                 62h      640x480     256 32k
                                                 63h      800x600     256 32k
                                                 54h      800x600     16  32k
                                                 65h      1024x768    16  64k

ATI VGA Wonder+         ATI NEW        4/8/16    61h      640x400     256 32k
                                                 62h      640x480     256 32k
                                                 63h      800x600     256 32k
                                                 64h      1024x768    256 32k
                                                 54h      800x600     16  32k
                                                 55h      1024x768    16  32k

ATI Ultra 8514A GA      ATI NEW        4/8/16    61h      640x400     256 32k
                                                 62h      640x480     256 32k
                                                 63h      800x600     256 32k
                                                 54h      800x600     16  32k
                                                 55h      1024x768    16  32k

ATI XL                  ATI NEW        4/8/16    61h      640x400     256 32k
                                                 62h      640x480     256 32k
                                                 63h      800x600     256 32k
                                                 64h      1024x768    256 32k
                                                 54h      800x600     16  32k
                                                 55h      1024x768    16  32k

Chips & Technology      Chips and      4/8       78h      640x400     256 16k
                        Technologies             79h      640x480     256 16k
                                                 7Ah      720x540     256 16k
                                                 7Bh      800x600     256 16k
                                                 70h      800x600     16  16k
                                                 71h      960x720     16  16k
                                                 72h      1024x768    16  16k

Cirrus Logic GD54       VESA           16/64  4F02h,100h  640x400     256  4k
                                              4F02h,101h  640x480     256  4k
                                              4F02h,103h  800x600     256  4k
                                              4F02h,105h  1024x768    256  4k
                                              4F02h,102h  800x600     16   4k
                                              4F02h,104h  1024x768    16   4k

Definicon, 16 Bit       TSENG 4000     8/16      2dh      640x350     256 64k
                                                 2fh      640x400     256 64k
                                                 2eh      640x480     256 64k
                                                 30h      800x600     256 64k
                                                 38h      1024x768    256 64k
                                                 29h      800x600     16  64k
                                                 37h      1024x768    16  64k
                                                 3Dh      1280x1024   16  64k

Diamond 24x             PARADISE       4/16      5eh      640x400     256  4k
                                                 5fh      640x480     256  4k
                                                 5ch      800x600     256  4k
                                                 60h      1024x768    256  4k
                                                 58h      800x600     16   4k
                                                 5Dh      1024x768    16   4k
                                                 6Ch      1280x960    16   4k
                                                 64h      1280x1024   16   4k

Diamond Speedstar 24    TSENG 4000     8/16      2dh      640x350     256 64k
                                                 2fh      640x400     256 64k
                                                 2eh      640x480     256 64k
                                                 30h      800x600     256 64k
                                                 38h      1024x768    256 64k
                                                 29h      800x600     16  64k
                                                 37h      1024x768    16  64k

Everex EV-673           EVEREX         4/8       70h,13h  640x350     256 64k
                                                 70h,14h  640x400     256 64k
                                                 70h,15h  512x480     256 64k
                                                 70h,30h  640x480     256 64k
                                                 70h,31h  800x600     256 64k
                                                 70h,02h  800x600     16  64k
                                                 70h,20h  1024x768    16  64k

Everev 678              TRIDENT        4/8       70h,14h  640x400     256 64k
                        8800                     70h,15h  512x480     256 64k
                                                 70h,30h  640x480     256 64k
                                                 70h,31h  800x600     256 64k
                                                 70h,02h  800x600     16  64k
                                                 70h,20h  1024x768    16  64k

Everex Vision VGA HC    TSENG 4000     8/16      2fh      640x400     256 64k
                                                 2eh      640x480     256 64k

Genoa 5400              TSENG 3000     4/8       2dh      640x350     256 64k
                                                 2eh      640x480     256 64k
                                                 30h      800x600     256 64k
                                                 29h      800x600     16  64k
                                                 37h      1024x768    16  64k

Genoa 6400              GENOA          4/8       2bh      640x350     256 64k
                                                 2eh      640x480     256 64k
                                                 30h      800x600     256 64k
                                                 29h      800x600     16  64k
                                                 37h      1024x768    16  64k

Genoa 7900 24 bit       TSENG 4000     8/16      2dh      640x350     256 64k
                                                 2fh      640x400     256 64k
                                                 2eh      640x480     256 64k
                                                 30h      800x600     256 64k
                                                 38h      1024x768    256 64k
                                                 29h      800x600     16  64k
                                                 37h      1024x768    16  64k

Headland 1024i          HEADLAND       4/8    6F05h,66h   640x400     256 64k
                                              6F05h,67h   640x480     256 64k
                                              6F05h,68h   720x540     256 64k
                                              6F05h,69h   800x600     256 64k
                                              6F05h,61h   720x540     16  64k
                                              6F05h,62h   800x600     16  64k
                                              6F05h,65h   1024x768    16  64k

Hi Res 512              ZYMOS          4/8       5ch      640x400     256 64k
                                                 5dh      640x480     256 64k
                                                 5eh      800x600     256 64k
                                                 6ah      800x600     16  64k
                                                 5fh      1024x768    16  64k

Maxxon                  TRIDENT 8800   4/8       5ch      640x400     256 64k
                                                 5dh      640x480     256 64k
                                                 5eh      800x600     256 64k
                                                 5bh      800x600     16  64k
                                                 5fh      1024x768    16  64k

C&T MK82452             CHIPS AND      8         78h      640x400     256 16k
                        TECHNOLOGIES             79h      640x480     256 16k
                                                 70h      800x600     16  16k
                                                 72h      1024x768    16  16k

NCR 77C22               NCR            8/16      5eh      640x400     256 64k
                                                 5fh      640x480     256 64k
                                                 5ch      800x600     256 64k
                                                 62h      1024x768    256 64k
                                                 58h      800x600     16  64k
                                                 5dh      1024x768    16  64k

OAK                     OAK            8/16      53h      640x480     256 64k
                                                 54h      800x600     256 64k
                                                 59h      1024x768    256 64k
                                                 52h      800x600     16  64k
                                                 56h      1024x768    16  64k

Orchid
Fahrenheight 1280       S3             8/16   4F02h,201h  640x480     256 64k
                                              4F02h,203h  800x600     256 64k
                                              4F02h,205h  1024x768    256 64k
                                              4F02h,202h  800x600     16  64k
                                              4F02h,204h  1024x768    16  64k
                                              4F02h,206h  1280x960    16  64k
                                              4F02h,206h  1280x1024   16  64k

Orchid Pro
Designer II             TSENG 4000     8/16      2dh      640x350     256 64k
                                                 2fh      640x400     256 64k
                                                 2eh      640x480     256 64k
                                                 30h      800x600     256 64k
                                                 38h      1024x768    256 64k
                                                 29h      800x600     16  64k
                                                 37h      1024x768    16  64k

Paradise VGA Pro        PARADISE       4/16      5eh      640x400     256  4k
                                                 5fh      640x480     256  4k
                                                 5ch      800x600     256  4k
                                                 60h      1024x768    256  4k
                                                 58h      800x600     16   4k
                                                 5Dh      1024x768    16   4k

Primus P2000 GA         PRIMUS         8/16      2dh      640x480     256 64k
                                                 2bh      800x600     256 64k
                                                 31h      1024x768    256 64k
                                                 37h      1280x1024   256 64k
                                                 2ah      800x600     16  64k
                                                 30h      1024x768    16  64k
                                                 36h      1280x1024   16  64k

Compaq QVision          QVISION        8/16      32h      640x480     256  4k
                                                 38h      1024x768    256  4k
                                                 10h      640x350     16   4k
                                                 12h      640x480     16   4k
                                                 29h      800x600     16   4k
                                                 37h      1024x768    16   4k

Realtek RTVGA           REALTEK        8/16      25h      640x400     256 64k
                                                 26h      640x480     256 64k
                                                 27h      800x600     256 64k
                                                 28h      1024x768    256 64k
                                                 1Fh      800x600     16  64k
                                                 21h      1024x768    16  64k
                                                 2Ah      1280x1024   16  64k

Realtek RTVGA           REALTEK        8/16      25h      640x400     256 64k
                                                 26h      640x480     256 64k
                                                 27h      800x600     256 64k
                                                 28h      1024x768    256 64k
                                                 1Fh      800x600     16  64k
                                                 21h      1024x768    16  64k
                                                 2Ah      1280x1024   16  64k

S3 Graphics Accelerator S3             8/16   4F02h,201h  640x480     256 64k
                                              4F02h,203h  800x600     256 64k
                                              4F02h,205h  1024x768    256 64k
                                              4F02h,202h  800x600     16  64k
                                              4F02h,204h  1024x768    16  64k
                                              4F02h,206h  1280x960    16  64k

STB EM 16               TSENG 4000     8/16      2dh      640x350     256 64k
                                                 78h      640x400     256 64k
                                                 2eh      640x480     256 64k
                                                 30h      800x600     256 64k
                                                 38h      1024x768    256 64k
                                                 29h      800x600     16  64k
                                                 37h      1024x768    16  64k

Phoebes                 TRIDENT 8800CS 4/8       5ch      640x400     256 64k
                                                 5dh      640x480     256 64k
                                                 5bh      800x600     16  64k
                                                 5fh      1024x768    16  64k

Maxxon                  TRIDENT 8800CS 4/8       5ch      640x400     256 64k
                                                 5dh      640x480     256 64k
                                                 5eh      800x600     256 64k
                                                 5bh      800x600     16  64k
                                                 5fh      1024x768    16  64k

Trident 8900            TRIDENT 8900   8/16      5Ch      640x400     256 64k
                                                 5Dh      640x480     256 64k
                                                 5Eh      800x600     256 64k
                                                 62h      1024x768    256 64k
                                                 5Bh      800x600     16  64k
                                                 5Fh      1024x768    16  64k

Tseng ET-3000           TSENG ET3000   4/8       2dh      640x350     256 64k
                                                 2eh      640x480     256 64k
                                                 30h      800x600     256 64k
                                                 29h      800x600     16  64k
                                                 36h      960x720     16  64k
                                                 37h      1024x768    16  64k

Tseng ET-4000           TSENG ET4000   8/16      2dh      640x350     256 64k
                                                 2fh      640x400     256 64k
                                                 2eh      640x480     256 64k
                                                 30h      800x600     256 64k
                                                 38h      1024x768    256 64k
                                                 29h      800x600     16  64k
                                                 37h      1024x768    16  64k

Video 7 VRAM            VIDEO7         4/8    6f05h,66h   640x400     256 64k
                                              6f05h,67h   640x480     256 64k
                                              6f05h,68h   720x540     256 64k
                                              6f05h,69h   800x600     256 64k
                                              6f05h,61h   720x540     16  64k
                                              6f05h,62h   800x600     16  64k
                                              6f05h,65h   1024x768    16  64k

Western Digital 90C     PARADISE      4/8        5eh      640x400     256  4k
                                                 5fh      640x480     256  4k
                                                 5ch      800x600     256  4k
                                                 58h      800x600     16   4k
                                                 5Dh      1024x768    16   4k
VESA                                                         Super VGA Standard
Video Electronics Standards Association
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2150 North First Street, Suite 360                        Phone: (408) 435-0333
San Jose, CA 95131-2020                                     Fax: (408) 435-8225

                            Super VGA BIOS Extension
                               Standard #VS911022
                                October 22, 1991
                              Document Version 1.0
                                VBE Version 1.2

PURPOSE
~~~~~~~
To standardize a common software interface to Super VGA video adapters in order
to provide simplified software application access to advanced VGA products.

SUMMARY
~~~~~~~
The standard provides a  set of functions which an application program can use
to A) obtain information about the capabilities and characteristics of a
specific Super VGA implementation and B) to control the operation of such
hardware in terms of video mode initialization and video memory access.  The
functions are provided as an extension to the VGA BIOS video services, accessed
through interrupt 10h.

                      VESA Super VGA Standard VS911022-2

Contents
~~~~~~~~
1.      Introduction ................................................. Page 3

2.      Goals and Objectives .........................................      4
        2.1     Video environment information ........................      4
        2.2     Programming support ..................................      4
        2.3     Compatibility ........................................      5
        2.4     Scope of standard ....................................      5

3.      Standard VGA BIOS ............................................      6

4.      Super VGA Mode Numbers .......................................      7

5.      CPU Video Memory Control .....................................      9
        5.1     Hardware design consideration ........................      9
                5.1.1   Limited to 64k/128k of CPU address space .....      9
                5.1.2   Crossing CPU video memory window boundaries ..     10
                5.1.3   Operating on data frolm different areas ......     10
                5.1.4   Combining data from two different windows ....     10
        5.2     Different types of hardware windows ..................     11
                5.2.1   Single window systems ........................     11
                5.2.2   Dual window systems ..........................     11

6.      Extended VGA BIOS ............................................     12
        6.1     Status Information ...................................     12
        6.2     00h - Return Super VGA Information ...................     12
        6.3     01h - Return Super VGA mode information ..............     14
        6.4     02h - Set Super VGA mode .............................     20
        6.5     03h - Return Super VGA mode ..........................     20
        6.6     04h - Save/restore Super VGA video state .............     21
        6.7     05h - Super VGGA video memory window control .........     22
        6.8     06h - Set/Get Logical Scan Line Length ...............     23
        6.9     07h - Set/Get Display Start ..........................     24
        6.10    08h - Set/Get DAC Palette Control ....................     25

7.      Application Example ..........................................     26

                      VESA Super VGA Standard VS911022-3

1.      Introduction
~~~~~~~~~~~~~~~~~~~~
This document contains a specification for a standardized interface to extended
VGA video modes and functions.  The specification consists of mechanisms for
supporting standard extended video modes and functions that have been approved
by the main VESA committee and non-standard video modes that an individual VGA
supplier may choose to add, in a uniform manner that application software can
utilize without having to understand the intricate details of the particular VGA
hardware.

The primary topics of this specification are definitions of extended VGA video
modes and the functions necessary for application software to understand the
characteristics of the video mode and manipulate the extended memory associated
with the video mode.

Readers of this document should already be familiar with programming VGAs at the
hardware level and Intel iAPX real mode assembly language.  Readers who are
unfamiliar with programming the VGA should first read one of the many VGA
programming tutorials before attempting to understand these extensions to the
standard VGA.

                      VESA Super VGA Standard VS911022-4

2.      Goals and Objectives
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The IBM VGA has become a defacto standard in the PC graphics world.  A multitude
of different VGA offerings exist in the marketplace, each one providing BIOS or
register compatibility with the IBM VGA.  More and more of these VGA compatible
products implements various supersets of the VGA standard.  These extensions
range from higher resolutions and more colors to improved performance and even
some graphics processing capabilities.  Intense competition has dramatically
improved the price/performance ratio, to the benefit of the end user.

However, several serious problems face a software developer who intends to take
advantage of these "Super VGA" environments.  Because there is no standard
hardware implementation, the developer is faced with widely disparate Super VGA
hardware architectures.  Lacking a common software interface, designing
applications for these environments is costly and technically difficult.  Except
for applications supported by OEM-specific display drivers, very few software
packages can take advantage of the power and capabilities of Super VGA products.

The purpose of the VESA VGA BIOS Extension is to remedy this situation.  Being a
common software interface to Super VGA graphics products, the primary objective
is to enable application and system software to adapt to and exploit the wide
range of features available in these VGA extensions.

Specifically, the VESA BIOS Extension attempts to address the following issues:
A) Return information about the video environment to the application, and B)
Assist the application in initializing and programming the hardware.

2.1             Video environment information

Today, an application has no standard mechanism to determine what Super VGA
hardware it is running on.  Only by knowing OEM-specific features can an
application determine the presence of a particular video board.  This often
involves reading and testing registers located at I/O addresses unique to each
OEM.  By not knowing what hardware an application is running on, few, if any, of
the extended features of the underlying hardware can be used.

The VESA BIOS Extension provides several functions to return information about
the video environment.  These functions return system level information as well
as video mode specific details.  Function 00h returns general system level
information, including an OEM identification string.  The function also returns
a pointer to the supported video modes.  Function 01h may be used by the
application to obtain information about each supported video mode.  Function 03h
returns the current video mode.

                      VESA Super VGA Standard VS911022-5

2.2             Programming support

Due to the fact that different Super VGA products have different hardware
implementations, application software has great difficulty in adapting to each
environment.  However, since each is based on the VGA hardware architecture,
differences are most common in video mode initialization and memory mapping.
The rest of the architecture is usually kept intact, including I/O mapped
registers, video buffer location in the CPU address space, DAC location and
function, etc.

The VESA BIOS Extension provides several functions to interface to the different
Super VGA hardware implementations.  The most important of these is Function
02h, Set Super VGA video mode.  This function isolates the application from the
tedious and complicated task of setting up a video mode.  Function 05h provides
an interface to the underlying memory mapping hardware.  Function 04h enables an
application to save and restore a Super VGA state without knowing anything of
the specific implementation.

2.3             Compatibility

A primary design objective of the VESA BIOS Extension is to preserve maximum
compatibility to the standard VGA environment.  In no way should the BIOS
extensions compromise compatibility or performance.  Another but related concern
is to minimiza the changes necessary to an existing VGA BIOS.  Ram, as well as
ROM-based implementations of the BIOS extension should be possible.

2.4             Scope of standard

The purpose of the VESA BIOS Extension is to provide support for extended VGA
environments.  Thus, the underlying hardware architecture is assumed to be a
VGA.  Graphics software that drives a Super VGA board will perform its graphics
output in generally the same way it drives a standard VGA, i.e. writing directly
to a VGA style frame buffer, manipulating graphics controller registers,
directly programming the palette, etc.  No significant graphics processing will
be done in hardware.  For this reason, the VESA BIOS Extension does not provide
any graphics output functions, such as BitBlt, line or circle drawing, etc.

An important constraint of the functionalities that can be placed into the VESA
BIOS Extension is that ROM space is severely limited in certain existing BIOS
implementations.

Outside the scope of this VESA BIOS Extension is the handling of different
monitors and monitor timings.  Such items are dealt with in other VESA fora.
The purpose of the VESA BIOS Extension is to provide a standardized software
interface to Super VGA graphics modes, independent of monitor and monitor timing
issues.

                      VESA Super VGA Standard VS911022-6

3.      Standard VGA BIOS
~~~~~~~~~~~~~~~~~~~~~~~~~
A primary design goal with the VESA BIOS Extension is to minimize the effects on
the standard VGA BIOS.  Standard VGA BIOS functions should need to be modified
as little as possible.  This is important since ROM, as well as RAM based
versions of the extensions, may be implemented.

However, two standard VGA BIOS functions are affected by the VESA extension.
These are Function 00h (Set video mode) and Function 0Fh (Read current video
state).  VESA-aware applications will not set the video mode using VGA BIOS
function 00h.  Nor will such applications use VGA BIOS function 0Fh.  VESA BIOS
functions 02h (Set Super VGA mode) and 03h (Get Super VGA mode) will be used
instead.

However, VESA-unaware applications (such as old Pop-Up programs and other TSRs,
or the CLS command of MS-DOS), might use VGA BIOS function 0Fh to get the
present video mode.  Later it may call VGA BIOS function 00h to
restore/reinitialize the old video mode.

To make such applications work, VESA recommends that whatever value returned by
VGA BIOS function 0Fh (it is up to the OEM to define this number) should be used
to reinitialize the video mode through VGA BIOS function 00h.  Thus, the BIOS
should keep track of the last Super VGA mode in effect.

It is recommended, but not mandatory, to support output functions (such as
TTY-output, scroll, set pixel, etc.) in Super VGA modes.  If the BIOS extension
doesn't support such output functions, bit D2 (Output functions supported) of
the ModeAttributes field (returned by VESA BIOS function 01h) should be clear.

                      VESA Super VGA Standard VS911022-7

4.      Super VGA mode numbers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Standard VGA mode numbers are 7 bits wide and presently range from 00h to 13h.
OEMs have defined extended video modes in the range 14h to 7Fh.  Values in the
range 80h to FFh cannot be used, since VGA BIOS function 00h (Set video mode)
interprets bit 7 as a flag to clear/not clear video memory.

Due to the limitations of 7 bit mode numbers, VESA video mode numbers are 15
bits wide.  To initialize a Super VGA mode, its number is passed in the BX
register to VESA BIOS function 02h (Set Super VGA mode).

The format of VESA mode numbers is as follows:

D0-D8  = Mode number
              If D8 == 0, this is not a VESA defined mode
              If D8 == 1, this is a VESA defined mode
D9-D14 = Reserved by VESA for future expansion (= 0)
D15    = Reserved (= 0)

Thus, VESA mode numbers begin at 100h.  This mode numbering scheme implements
standard VGA mode numbers as well as OEM-defined mode numbers as subsets of the
VESA mode number.  That means that regular VGA modes may be initialized through
VESA BIOS function 02h (Set Super VGA mode), simply by placing the mode number
in BL and clearing the upper byte (BH).  OEM-defined modes may be initialized in
the same way.

To date, VESA has defined a 7-bit video mode number, 6Ah, for the 800x600,
16-color, 4-plane graphics mode.  The corresponding 15-bit mode number for this
mode is 102h.

The following VESA mode numbers have been defined:

                GRAPHICS                                TEXT

15-bit   7-bit    Resolution   Colors   15-bit   7-bit    Columns   Rows
mode     mode                           mode     mode
number   number                         number   number
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
100h     -        640x400      256      108h     -        80        60
101h     -        640x480      256
                                        109h     -        132       25
102h     6Ah      800x600      16       10Ah     -        132       43
103h     -        800x600      256      10Bh     -        132       50
                                        10Ch     -        132       60
104h     -        1024x768     16
105h     -        1024x768     256

106h     -        1280x1024    16
107h     -        1280x1024    256

                      VESA Super VGA Standard VS911022-8

10Dh     -        320x200      32K   (1:5:5:5)
10Eh     -        320x200      64K   (5:6:5)
10Fh     -        320x200      16.8M (8:8:8)
110h     -        640x480      32K   (1:5:5:5)
111h     -        640x480      64K   (5:6:5)
112h     -        640x480      16.8M (8:8:8)
113h     -        800x600      32K   (1:5:5:5)
114h     -        800x600      64K   (5:6:5)
115h     -        800x600      16.8M (8:8:8)
116h     -        1024x768     32K   (1:5:5:5)
117h     -        1024x768     64K   (5:6:5)
118h     -        1024x768     16.8M (8:8:8)
119h     -        1280x1024    32K   (1:5:5:5)
11Ah     -        1280x1024    64K   (5:6:5)
11Bh     -        1280x1024    16.8M (8:8:8)

                      VESA Super VGA Standard VS911022-9

5.      CPU Video Memory Windows
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A standard VGA sub-system provides 256k bytes of memory and a corresponding
mechanism to address this memory.  Super VGAs and their modes require more than
the standard 256k bytes of memory but also require that the address space for
this memory be restricted to the standard address space for compatibility
reasons.  CPU video memory windows provide a means of accessing this extended
VGA memory within the standard CPU address space.

This chapter describes how several hardware implementations of CPU video memory
windows operate, their impact on application software design, and relates them
to the software model presented by the VESA VGA BIOS extensions.

The VESA CPU video memory windows functions have been designed to put the
performance insensitive, non-standard hardware functions into the BIOS while
putting the performance sensitive, standard hardware functions into the
application.  This provides portability among VGA systems together with the
performance that comes from accessing the hardware directly.  In particular, the
VESA BIOS is responsible for mapping video memory into the CPU address space
while the application is responsible for performing the actual memory read and
write operations.

This combination software and hardware interface is accomplished by informing
the application of the parameters that control the hardware mechanism of mapping
the video memory into the CPU address space and then letting the application
control the mapping within those parameters.

5.1             Hardware
5.1.1           Limited to 64k/128k of CPU address space

The first consideration in implementing extended video memory is to give access
to the memory to application software.

The standard VGA CPU address space for 16 color graphics modes is typically at
segment A000h for 64k.  This gives access to the 256k bytes of a standard VGA,
i.e. 64k per plane.  Access to the extended video memory is accomplished by
mapping portions of the video memory into the standard VGA CPU address space.

Every Super VGA hardware implementation provides a mechanism for software to
specify the offset from the start of video memory which is to be mapped to the
start of the CPU address space.  Providing both read and write access to the
mapped memory provides a necessary level of hardware support for an application
to manipulate the extended video memory.

                      VESA Super VGA Standard VS911022-10

5.1.2           Crossing CPU video memory window boundaries

The organization of most software algorithms which perform video operations
consists of a pair of nested loops: and outer loop over rows or scan lines and
an inner loop across the row or scan line.  The latter is the proverbial inner
loop, which is the bottle neck to high performance software.

If a target rectangle is large enough, or poorly located, part of the required
memory may be with within the video memory mapped into the CPU address space and
part of it may not be addressable by the CPU without changing the mapping.  It
is desirable that the test for remapping the video memory is located outside of
the inner loop.

This is typically accomplished by selecting the mapping offset of the start of
video memory to the start of the CPU address space so that at least one entire
row or scan line can be processed without changing the video memory mapping.
There are currently no Super VGAs that allow this offset to be specified on a
byte boundary and there is a wide range among Super VGAs in the ability to
position a desired video memory location at the start of the CPU address space.

The number of bytes between the closest two bytes in video memory that can be
placed on any single CPU address is defined as the granularity of the window
function.  Some Super VGA systems allow any 4k video memory boundary to be
mapped to the start of the CPY address space, while other Super VGA systems
allow any 64k video memory boundary to be mapped to the start of the CPU address
space.  These two example systems would have granularities of 4k and 64k,
respectively.  This concept is very similar to the bytes that can be accessed
with a 16 bit pointer in an Intel CPU before a segment register must be changed
(the granularity of the segment register or mapping here is 16 bytes).

Notes
~~~~~
If the granularity is equal to the length of the CPU address space, i.e. the
least significant address bit of the hardware mapping function is more
significant than the most significant bit of the CPU address, then the inner
loop will have to contain the test for crossing the end or beginning of the CPU
address space.  This is because if the length of the CPU address space (which is
the granularity in this case) is not evenly divisible by the length of a scan
line, then the scan line at the end of the CPU address will be in two different
video memory which cannot be mapped into the CPU address space simultaneously.

5.1.3           Operating on data from different areas

It is sometimes required or convenient to move or combine data from two
different areas of video memory.  One example of this is storing menus in the
video memory beyond the displayed memory because there is hardware support in
all VGAs for transferring 32 bits of video data with an 8 bit CPU read and
write.  Two separately mappable CPU video memory windows must be used if the
distance between the source and destination is larger than the size of the CPU
video memory window.

5.1.4           Combining data from two different windows

The above example of moving data from one CPU video memory window to another CPU
video memory only required read access to one window and only required write
access to the other window.  Sometimes it is convenient to have read access to
both windows and write access to one window.  An example of this would be a
raster operation where the resulting destination is the source data logically
combined with the original destination data.

                      VESA Super VGA Standard VS911022-11

5.2             Different types of hardware windows

Different hardware implementations of CPU video memory windows can be supported
by the VESA BIOS extension.  The information necessary for an application to
understand the type of hardware implementation is provided by the BIOS to the
application.  There are three basic types of hardware windowing implementations
and they are described below.

The types of windowing schemes described below do not include differences in
granularity.

Also note that is possible for a VGA to use a CPU address space of 128k starting
at segment A000h.

5.2.1           Single window systems

Some hardware implementations only provide a single window.  This single window
will be readable as well as writeable.  However, this causes a significant
performance degradation when moving data in video memory a distance that is
larger than the CPU address space.

5.2.2           Dual window systems

Many Super VGAs provide two windows to facilitate moving data within video
memory.  There are two separate methods of providing two windows.

5.2.2.1         Overlapping windows

Some hardware implementations distinguish window A and window B by determining
if the CPU is attempting to do a memory read or a memory write operation.  When
the two windows are distinguished by whether the CPU is trying to read or write
they can, and usually do, share the same CPU address space.  However, one window
will be read only and the other will be write only.

5.2.2.2         Non-overlapping windows

Another mechanism used by two window systems to distinguish window A and window
B is by looking at the CPU address within the total VGA CPU address space.  When
the two windows are distinguished by the CPU address within the VGA CPU address
space the windows cannot share the same address space, but they can each be both
read and written.

                      VESA Super VGA Standard VS911022-12

6.      Extended VGA BIOS
~~~~~~~~~~~~~~~~~~~~~~~~~
Several new BIOS calls have been defined to support Super VGA modes.  For
maximum compatibility with the standard VGA BIOS, these calls are grouped under
one function number.  This number is passed in the AH register to the INT 10h
handler.

The designated Super VGA extended function number is 4Fh.  This function number
is presently unused in most, if not all, VGA BIOS implementations.  A standard
VGA BIOS performs no action when function call 4Fh is made.  Super VGA Standard
VS900602 defines subfunctions 00h through 07h.  Subfunction numbers 08h through
0FFh are reserved for future use.

6.1             Status Information

Every function returns status information in the AX register.  The format of the
status word is as follows:

        AL == 4Fh:      Function is supported
        Al != 4Fh:      Function is not supported
        AH == 00h:      Function call successful
        AH == 01h:      Function call failed

Software should treat a non-zero value in the AH register as a general failure
condition.  In later versions of the VESA BIOS Extension new error codes might
be defined.

6.2             Function 00h - Return Super VGA Information

The purpose of this function is to provide information to the calling program
about the general capabilities of the Super VGA environment.  The function fills
an information block structure at the address specified by the caller.  The
information block size is 256 bytes.

        Input:  AH = 4Fh        Super VGA support
                AL = 00h        Return Super VGA information
                ES:DI = Pointer to buffer

        Output: AX = Status
                (All other registers are preserved)

                      VESA Super VGA Standard VS911022-13

The information block has the following structure:

VgaInfoBlock    STRUC
      VESASignature   db   'VESA'      ; 4 signature bytes
      VESAVersion     dw   ?           ; VESA version number
      OEMStringPtr    dd   ?           ; Pointer to OEM string
      Capabilities    db   4 dup(?)    ; capabilities of the video environment
      VideoModePtr    dd   ?           ; pointer to supported Super VGA modes
      TotalMemory     dw   ?           ; Number of 64kb memory blocks on board
      Reserved        db   236 dup(?)  ; Remainder of VgaInfoBlock
VgaInfoBlock    ENDS

The VESASignature field contains the characters 'VESA' if this is a valid block.

The VESAVersion is a binary field which specifies what level of the VESA
standard the Super VGA BIOS conforms to.  The higher byte specifies the major
version number.  The lower byte specifies the minor version number.  The current
VESA version number is 1.2.  Applications written to use the features of a
specific version of the VESA BIOS Extension, are guaranteed to work in later
versions.  The VESA BIOS Extension will be fully upwards compatible.

The OEMStringPtr is a far pointer to a null terminated OEM-defined string.  The
string may used to identify the video chip, video board, memory configuration,
etc. to hardware specific display drivers.  There are no restrictions on the
format of the string.

The Capabilities field describes what general features are supported in the
video environment.  The bits are defined as follows:

        D0      = DAC is switchable
                  0 = DAC is fixed width, with 6-bits per primary color
                  1 = DAC width is switchable
        D1-31   = Reserved

The VideoModePtr points to a list of supported Super VGA (VESA-defined as well
as OEM-specific) mode numbers.  Each mode number occupies one word (16 bits).
The list of mode numbers is terminated by a -1 (0FFFFh).  Please refer to
chapter 2 for a description of VESA mode numbers.  The pointer could point into
either ROM or RAM, depending on the specific implementation.  Either the list
would be a static string stored in ROM, or the list would be generated at
run-time in the information block (see above) in RAM.  It is the application's
responsibility to verify the current availability of any mode returned by this
Function through the Return Super VGA mode information (Function 1) call.  Some
of the returned modes may not be available due to the video board's current
memory and monitor configuration.

The TotalMemory field indicates the amount of memory installed on the VGA
board.  Its value represents the number of 64kb blocks of memory currently
installed.

                      VESA Super VGA Standard VS911022-14

6.3             Function 01h - Return Super VGA mode information

This function returns information about a specific Super VGA video mode that was
returned by Function 0.  The function fills a mode information block structure
at the address specified by the caller.  The mode information block size is
maximum 256 bytes.

Some information provided by this function is implicitly defined by the VESA
mode number.  However, some Super VGA implementations might support other video
modes than those defined by VESA.  To provide access to these modes, this
function also returns various other information about the mode.

        Input:  AH = 4Fh        Super VGA support
                AL = 01h        Return Super VGA mode information
                CX = Super VGA video mode
                     (mode number must be one of those returned by Function 0)
                ES:DI = Pointer to 256 byte buffer

        Output: AX = Status
                (All other registers are preserved)

The mode information block has the following structure:

ModeInfoBlock   STRUC

; mandatory information

        ModeAttributes      dw  ?  ; mode attributes
        WinAAttributes      db  ?  ; window A attributes
        WinBAttributes      db  ?  ; window B attributes
        WinGranularity      dw  ?  ; window granularity
        WinSize             dw  ?  ; window size
        WinASegment         dw  ?  ; window A start segment
        WinBSegment         dw  ?  ; window B start segment
        WinFuncPtr          dd  ?  ; pointer to windor function
        BytesPerScanLine    dw  ?  ; bytes per scan line

; formerly optional information (now mandatory)

        XResolution         dw  ?  ; horizontal resolution
        YResolution         dw  ?  ; vertical resolution
        XCharSize           db  ?  ; character cell width
        YCharSize           db  ?  ; character cell height
        NumberOfPlanes      db  ?  ; number of memory planes
        BitsPerPixel        db  ?  ; bits per pixel
        NumberOfBanks       db  ?  ; number of banks
        MemoryModel         db  ?  ; memory model type
        BankSize            db  ?  ; bank size in kb
        NumberOfImagePages  db  ?  ; number of images
        Reserved            db  1  ; reserved for page function

                      VESA Super VGA Standard VS911022-15

; new Direct Color fields

        RedMaskSize         db  ?  ; size of direct color red mask in bits
        RedFieldPosition    db  ?  ; bit position of LSB of red mask
        GreenMaskSize       db  ?  ; size of direct color green mask in bits
        GreenFieldPosition  db  ?  ; bit position of LSB of green mask
        BlueMaskSize        db  ?  ; size of direct color blue mask in bits
        BlueFieldPosition   db  ?  ; bit position of LSB of blue mask
        RsvdMaskSize        db  ?  ; size of direct color reserved mask in bits
        DirectColorModeInfo db  ?  ; Direct Color mode attributes
        Reserved            db  216 dup(?)      ; remainder of ModeInfoBlock
ModeInfoBlock   ENDS

The ModeAttributes field describes certain important characteristics of the
video mode.  Bit D0 specifies whether this mode can be initialized in the
present video configuration.  This bit can be used to block access to a video
mode if it requires a certain monitor type, and that this monitor is presently
not connected.  Prior to Version 1.2 of the VESA BIOS Extension, it was not
required that the BIOS return valid information for the fields after
BytesPerScanline.  Bit D1 was used to signify if the optional information was
present.  Version 1.2 of the VBE requires that all fields of the ModeInfoBlock
contain valid data, except for the Direct Color fields, which are valid only if
MemoryModel field is set to a 6 (Direct Color) or 7 (YUV).  Bit D1 is now
reserved, and must be set to a 1.  Bit D2 indicates whether the BIOS has support
for output functions like TTY output, scroll, pixel output, etc. in this mode
(it is recommended, but not mandatory, that the BIOS have support for all output
functions).  If bit D2 is 1 then the BIOS must support all of the standard
output functions.

The field is defined as follows:

        D0 = Mode supported in hardware
                0 = Mode not supported in hardware
                1 = Mode supported in hardware
        D1 = 1 (Reserved)
        D2 = Output functions supported by BIOS
                0 = Output functions not supported by BIOS
                1 = Output functions supported by BIOS
        D3 = Monochrome/color mode (see note below)
                0 = Monochrome mode
                1 = Color mode
        D4 = Mode type
                0 = Text mode
                1 = Graphics mode
        D5-D15 = Reserved

                      VESA Super VGA Standard VS911022-16

Note: Monochrome modes have their CRTC address at 3B4h.  Color modes have their
CRTC address at 3D4h.  Monochrome modes have attributes in which only bit 3
(video) and bit 4 (intensity) of the attribute controller output are
significant.  Therefore, monochrome text modes have attributes of off, video,
high intensity, blink, etc.  Monochrome graphics modes are two plane graphics
modes and have attributes of off, video, high intensity, and blink.  Extended
two color modes that have their CRTC address at 3D4h are color modes with one
bit per pixel and one plane.  The standard VGA modes 06h and 11h would be
classified as color modes, while the standard VGA modes 07h and 0Fh would be
classified as monochrome modes.

The BytesPerScanline field specifies how many bytes each logical scanline
consists of.  The logical scanline could be equal to or larger then the
displayed scanline.

                      VESA Super VGA Standard VS911022-17

The WinAAttributes and WinBAttributes describe the characteristics of the CPU
windowing scheme such as whether the windows exist and are read/writeable, as
follows:

        D0 = Window supported
                0 = Window is not supported
                1 = Window is supported
        D1 = Window readable
                0 = Window is not readable
                1 = Window is readable
        D2 = Window writeable
                0 = Window is not writeable
                1 = Window is writeable
        D3-D7 = Reserved

If windowing is not supported (bit D0 = 0 for both Window A and Window B), then
an application can assume that the display memory buffer resides at the standard
CPU address appropriate for the MemoryModel of the mode.

WinGranularity specifies the smallest boundary, in KB, on which the window can
be placed in the video memory.  The value of this field is undefined if Bit D0
of the appropriate WinAttributes field is not set.

WinSize specifies the size of the window in KB.

WinASegment and WinBSegment address specify the segment addresses where the
windows are located in CPU address space.

WinFuncAddr specifies the address of the CPU video memory windowing function.
The windowing function can be invoked either through VESA BIOS function 05h, or
by calling the function directly.  A direct call will provide faster access to
the hardware paging registers than using Int 10h, and is intended to be used by
high performance applications.  If this field is Null, then Function 05h must be
used to set the memory window, if paging is supported.

The XResolution and YResolution specify the width and height of the video mode.
In graphics modes, this resolution is in units of pixels.  In text modes, this
resolution is in units of characters.  Note that text mode resolutions, in units
of pixels, can be obtained by multiplying XResolution and YResolution by the
cell width and height, if the extended information is present.

The XCharCellSize and YCharSellSize specify the size of the character cell in
pixels.

The NumberOfPlanes field specifies the number of memory planes available to
software in that mode.  For standard 16-color VGA graphics, this would be set to
4.  For standard packed pixel modes, the field would be set to 1.

The BitsPerPixel field specifies the total number of bits that define the color
of one pixel.  For example, a standard VGA 4 Plane 16-color graphics mode would
have a 4 in this field and a packed pixel 256-color graphics mode would specify
8 in this field.  The number of bits per pixel per plane can normally be derived
by dividing the BitsPerPixel field by the NumberOfPlanes field.

                      VESA Super VGA Standard VS911022-18

The MemoryModel field specifies the general type of memory organization used in
this mode.  The following models have been defined:

        00h =           Text mode
        01h =           CGA graphics
        02h =           Hercules graphics
        03h =           4-plane planar
        04h =           Packed pixel
        05h =           Non-chain 4, 256 color
        06h =           Direct Color
        07h =           YUV
        08h-0Fh =       Reserved, to be defined by VESA
        10h-FFh =       To be defined by OEM

In Version 1.1 and earlier of the VESA Super VGA BIOS Extension, OEM defined
Direct Color video modes with pixel formats 1:5:5:5, 8:8:8, and 8:8:8:8 were
described as a Packed Pixel model with 16, 24, and 32 bits per pixel,
respectively.  In Version 1.2 and later of the VESA Super VGA BIOS Extension, it
is recommended that Direct Color modes use the Direct Color MemoryModel and use
the MaskSize and FieldPosition fields of the ModeInfoBlock to describe the pixel
format.  BitsPerPixel is always defined to be the total memory size of the
pixel, in bits.

The NumberOfBanks field specifies the number of banks in which the scan lines
are grouped.  The remainder from dividing the scan line number by the number of
banks is the bank that contains the scan line and the quotient is the scan line
number within the bank.  For example, CGA graphics modes have two banks and
Hercules graphics mode has four banks.  For modes that don't have scanline banks
(such as VGA modes 0Dh-13h), this field should be set to 1.

The BankSize field specifies the size of a bank (group of scan lines) in units
of 1KB.  For CGA and Hercules graphics modes this is 8, as each bank is 8192
bytes in length.  For modes that don't have scanline banks (such as VGA modes
0Dh-13h), this field should be set to 0.

The NumberOfImagePages field specifies the number of additional complete display
images that will fit into the VGA's memory, at one time, in this mode.  The
application may load more than one image into the VGA's memory if this field is
non-zero, and flip the display between them.

The Reserved field has been defined to support a future VESA BIOS extension
feature and will always be set to one in this version.

The RedMaskSize, GreenMaskSize, BlueMaskSize, and RsvdMaskSize fields define the
size, in bits, of the red, green, and blue components of a direct color pixel.
A bit mask can be constructed from the MaskSize fields using simple shift
arithmetic.  For example, the MaskSize values for a Direct Color 5:6:5 mode
would be 5, 6, 5, and 0, for the red, green, blue, and reserved fields,
respectively.  Note that in the YUV MemoryModel, the red field is used for V,
the green field is used for Y, and the blue field is used for U.  The MaskSize
fields should be set to 0 in modes using a MemoryModel that does not have pixels
with component fields.

                      VESA Super VGA Standard VS911022-19

The RedFieldPosition, GreenFieldPosition, BlueFieldPosition, and
RsvdFieldPosition fields define the bit position within the direct color pixel
or YUV pixel of the least significant bit of the respective color component.  A
color value can be aligned with its pixel field by shifting the value left by
the FieldPosition.  For example, the FieldPosition values for a Direct Color
5:6:5 mode would be 11, 5, 0, and 0, for the red, green, blue, and reserved
fields, respectively.  Note that in the YUV MemoryModel, the red field is used
for V, the green field is used for Y, and the blue field is used for U.  The
FieldPosition fields should be set to 0 in modes using a MemoryModel that does
not have pixels with component fields.

The DirectColorModeInfo field describes important characteristics of direct
color modes.  Bit D0 specifies whether the color ramp of the DAC is fixed or
programmable.  If the color ramp is fixed, then it can not be changed.  If the
color ramp is programmable, it is assumed that the red, green, and blue lookup
tables can be loaded using a standard VGA DAC color registers BIOS call
(AX=1012h).  Bit D1 specifies whether the bits in the Rsvd field of the direct
color pixel can be used by the application or are reserved, and thus unusable.

        D0 = Color ramp is fixed/programmable
                0 = Color ramp is fixed
                1 = Color ramp is programmable
        D1 = Bits in Rsvd field are usable/reserved
                0 = Bits in Rsvd field are reserved
                1 = Bits in Rsvd field are usable by the application

Notes
~~~~~
Version 1.1 and later VESA BIOS extensions will zero out all unused fields in
the Mode Information Block, always returning exactly 256 bytes.  This
facilitates upward compatibility with future versions of the standard, as any
newly added fields will be designed such that values of zero will indicate
nominal defaults or non-implementation of optional features (for example, a
field containing a bit-mask of extended capabilities would reflect the absence
of all such capabilities).  Applications that wish to be backwards compatible to
Version 1.0 VESA BIOS extensions should pre-initialize the 256 byte buffer
before calling Return Super VGA mode information.

                      VESA Super VGA Standard VS911022-20

6.4             Function 02h - Set Super VGA video mode

This function initializes a video mode.  The BX register contains the mode to
set.  The format of VESA mode numbers is described in chapter 2.  If the mode
cannot be set, the BIOS should leave the video environment unchanged and return
a failure error code.

        Input:  AH = 4Fh        Super VGA support
                AL = 02h        Set Super VGA video mode
                BX = Video mode
                     D0-D14 = Video mode
                     D15 = Clear memory flag
                           0 = Clear video memory
                           1 = Don't clear video memory

        Output: AX = Status
                (All other registers are preserved)

6.5             Function 03h - Return current video mode

This function returns the current video mode in BX.  The format of VESA video
mode numbers is described in chapter 2 of this document.

        Input:  AH = 4Fh        Super VGA support
                AL = 03h        Return current video mode

        Output: AX = Status
                BX = Current video mode
                (All other registers are preserved)

Notes
~~~~~
In a standard VGA BIOS, function 0Fh (Read current video state) returns the
current video mode in the AL register.  In D7 of AL, it also returns the status
of the memory clear bit (D7 of 40:87).  This bit is set if the mode was set
without clearing memory.  In this Super VGA function, the memory clear bit will
not be returned in BX since the purpose of the function is to return the video
mode only.  If an application wants to obtain the memory clear bit, it should
call VGA BIOS function 0Fh.

                      VESA Super VGA Standard VS911022-21

6.6             Function 04h - Save/Restore Super VGA video state

These functions provide a mechanism to save and restore the Super VGA video
state.  The functions are a superset of the three subfunctions under standard
VGA BIOS function 1Ch (Save/restore video state).  The complete Super VGA video
state (except video memory) should be saveable/restoreable by setting the
requested states mask (in the CX register) to 000Fh.

        Input:  AH = 4Fh        Super VGA support
                AL = 04h        Save/restore Super VGA video state
                DL = 00h        Return save/restore state buffer size
                CX = Requested states
                        D0 = Save/restore video hardware state
                        D1 = Save/restore video BIOS data state
                        D2 = Save/restore video DAC state
                        D3 = Save/restore Super VGA state

        Output: AX = Status
                BX = Number of 64-byte blocks to hold the state buffer
                (All other registers are preserved)


        Input:  AH = 4Fh        Super VGA support
                AL = 04h        Save/restore Super VGA video state
                DL = 01h        Save Super VGA video state
                CX = Requested states (see above)
                ES:BX = Pointer to buffer

        Output: AX = Status
                (All other registers are preserved)


        Input:  AH = 4Fh        Super VGA support
                AL = 04h        Save/restore Super VGA video state
                DL = 02h        Restore Super VGA video state
                CX = Requested states (see above)
                ES:BX = Pointer to buffer

        Output: AX = Status
                (All other registers are preserved)

Notes
~~~~~
Due to the goal of complete compatibility with the VGA environment, the standard
VGA BIOS function 1Ch (Save/restore VGA state) has not been extended to save the
Super VGA video state.  VGA BIOS compatibility requires that function 1Ch
returns a specific buffer size with specific contents, in which there is no room
for the Super VGA state.

                      VESA Super VGA Standard VS911022-22

6.7             Function 05h - CPU Video Memory Window Control

This function sets or gets the position of the specified window in the video
memory.  The function allows direct access to the hardware paging registers.  To
use this function properly, the software should use VESA BIOS Function 01h
(Return Super VGA mode information) to determine the size, location, and
granularity of the windows.

        Input:  AH = 4Fh        Super VGA support
                AL = 05h        Super VGA video memory window control
                BH = 00h        Select Super VGA video memory window
                BL = Window number
                        0 = Window A
                        1 = Window B
                DX = Window position in video memory
                     (in window granularity units)

        Output: AX = Status
                (See notes below)


        Input:  AH = 4Fh        Super VGA support
                AL = 05h        Super VGA video memory window control
                BH = 01h        Return Super VGA video memory window
                BL = Window number
                        0 = Window A
                        1 = Window B

        Output: AX = Status
                DX = Window position in video memory
                     (in window granularity units)
                (See notes below)

Notes
~~~~~
This function is also directly accessible through a far call from the
application.  The address of the BIOS function may be obtained by using VESA
BIOS Function 01h, Return Super VGA mode information.  A field in the
ModeInfoBlock contains the address of this function.  Note that this function
may be different among video modes in a particular BIOS implementation, so the
function pointer should be obtained after each set mode.

In the far call version, no status information is returned to the application.
Also, the AX and DX registers will be destroyed.  Therefore, if AX and/or DX
must be preserved, the application must do so priot to making the far call.

The application must load the input arguments in BH, BL, and DX (for set window)
but does not need to load either AH or AL in order to use the far call version
of this function.

                      VESA Super VGA Standard VS911022-23

6.8             Function 06h - Set/Get Logical Scan Line Length

This function sets or gets the length of a logical scan line.  This function
allows an application to set up a logical video memory buffer that is wider than
the displayed area.  Function 07h then allows the application to set the
starting position that is to be displayed.

        Input:  AH = 4Fh        Super VGA support
                AL = 06h        Logical Scan Line Length
                BL = 00h        Select Scan Line Length
                CX = Desired width in pixels

        Output: AX = Status
                BX = Bytes Per Scan Line
                CX = Actual Pixels Per Scan Line
                DX = Maximum Number of Scan Lines


        Input:  AH = 4Fh        Super VGA support
                AL = 06h        Logical Scan Line Length
                BL = 01h        Return Scan Line Length

        Output: AX = Status
                BX = Bytes Per Scan Line
                CX = Actual Pixels Per Scan Line
                DX = Maximum Number of Scan Lines

Notes
~~~~~
The desired width in pixels may not be achieveable because of VGA hardware
considerations.  The next larger value will be selected thta will accommodate
the desired number of pixels, and the actual number of pixels will be returned
in CX.  BX returns a value that, when added to a pointer into video memory, will
point to the next scan line.  For example, in a mode 13h this would be 320, but
in mode 12h this would be 80.  DX returns the number of logical scan lines based
upon the new scan line length and the total memory installed and useable in this
display mode.  This function is also valid in text modes.  In text modes, the
application should find out the current character cell width through normal BIOS
functions, multiply that times the desired number of characters per line, and
pass the value in the CX register.

                      VESA Super VGA Standard VS911022-24

6.9             Function 07h - Set/Get Display Start

This function selects the pixel to be displayed in the upper left corner of the
display from the logical page.  This function can be used to pan and scroll
around logical screens that are larger than the displayed screen.  This function
can also be used to rapidly switch between two different displayed screens for
double buffered animation effects.

        Input:  AH = 4Fh        Super VGA support
                AL = 07h        Display Start Control
                BH = 00h        Reserved and must be 0
                BL = 00h        Select Display Start
                CX = First Displayed Pixel in Scan Line
                DX = First Displayed Scan Line

        Output: AX = Status


        Input:  AH = 4Fh        Super VGA support
                AL = 07h        Display Start Control
                BL = 01h        Return Display Start

        Output: AX = Status
                BH = 00h Reserved and will be 0
                CX = First Displayed Pixel in Scan Line
                DX = First Displayed Scan Line

Notes
~~~~~
This function is also valid in text modes.  In text modes, the application
should find out the current character cell width through normal BIOS functions,
multiply that times the desired starting character column, and pass that value
in the CX register.  It should also multiply the current character cell height
times the desired starting character row, and pass that value in the DX
register.

                      VESA Super VGA Standard VS911022-25

6.10            Function 08h - Set/Get DAC Palette Control

This function queries and selects the operating mode of the DAC palette.  Some
DACs are configurable to provide 6-bits, 8-bits, or more of color definition per
red, green, and blue primary color.  The DAC palette width is assumed to be
reset to standard VGA 6-bits per primary during a standard or VESA Set Super VGA
Mode (AX = 4F02h) call.

        Input:  AH = 4Fh        Super VGA support
                AL = 08h        Set/Get DAC Palette Control
                BL = 00h        Set DAC palette width
                BH = Desired number of bits of color per primary
                     (Standard VGA = 6)

        Output: AX = Status
                BH = Current number of bits of color per primary
                (Standard VGA = 6)


        Input:  AH = 4Fh        Super VGA support
                AL = 08h        Set/Get DAC Palette Control
                BL = 01h        Get DAC palette width

        Output: AX = Status
                BH = Current number of bits of color per primary
                (Standard VGA = 6)

Notes
~~~~~
An application can find out if DAC switching is available by querying Bit D0 of
the Capabilities field of the VgaInfoBlock structure returned by VESA Return
Super VGA Information (AX = 4F00h).  The application can then attempt to set the
DAC palette width to the desired value.  If the Super VGA is not capable of
selecting the requested palette width, then the next lower value that the Super
VGA is capable of will be selected.  The resulting palette width is returned.

                      VESA Super VGA Standard VS911022-26

7.      Application Example
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following sequence illustrates how an application interface to the VESA BIOS
Extension.  The hypothetical application is VESA-aware and calls the VESA BIOS
functions.  However, the application is not limited to supporting just
VESA-defined video modes.  This it will inquire what video modes are available
before setting up the video mode.

1)      The application would first allocate a 256 byte buffer.  This buffer
        will be used by the VESA BIOS to return information about the video
        environment.  Some applications will statically allocate this buffer,
        others will use system calls to temporarily obtain buffer space.

2)      The application would then call VESA BIOS function 00h (Return Super VGA
        information).  If the AX register does not contain 004Fh on return from
        the function call, the application can determine that the VESA BIOS
        Extension is not present and handle such situation.

        If no error code is passed in AX, the function call was successful.  The
        buffer has been filled by the VESA BIOS Extension with various
        information.  The application can verify that indeed this is a valid
        VESA block by identifying the characters 'VESA' in the beginning of the
        block.  The application can inspect the VESAVersion field to determine
        whether the VESA BIOS Extension ha sufficient functionality.  The
        application may use the OEMStringPtr to locate OEM-specific information.

        Finally, the application can obtain a list of the supported Super VGA
        modes by using the VideoModePtr.  This field points to a list of the
        video modes supported by the video environment.

3)      The application would then create a new buffer and call the VESA BIOS
        function 01h (Return Super VGA mode information) to obtain information
        about the supported video modes.  Using the VideoModePtr obtained in
        step 2) above, the application would call this function with a new mode
        number until a suitable video mode is found.  If no appropriate video
        mode is found, it is up to the application to handle this situation.

        The Return Super VGA mode information function fills a buffer specified
        by the application with information describing the features of the video
        mode.  The data block contains all the information an application needs
        to take advantage of the video mode.

        The application would examine the ModeAttributes field.  To verify that
        the mode indeed is supported, the application would inspect bit D0.  If
        D0 is clear, then the mode is not supported by the hardware.  This might
        happen is a specific mode requires a certain type of monitor but that
        monitor is not present.

4)      After the application has selected a video mode, the next step is to
        initialize the mode.  However, the application might first want to save
        the present video mode.  When the application exits, this mode would be
        restored.  To obtain the present video mode, the VESA BIOS function 03h
        (Get Super VGA mode) would be used.  If a non-VESA (standard VGA or
        OEM-specific) mode is in effect, only the lower byte in the mode number
        is filled.  The upper byte is cleared.

5)      To initialize the video mode, the application would use VESA BIOS
        function 02h (Set Super VGA mode).  The application has from this point
        on full access to the VGA hardware and video memory.

                      VESA Super VGA Standard VS911022-27

6)      When the application is about to terminate, it would restore the prior
        video mode.  The prior video mode, obtained in step 4) above could be
        either a standard VGA mode, OEM-specific mode, or VESA-supported mode.
        It would reinitialize the video mode by calling VESA BIOS function 02h
        (Set Super VGA mode).  The application would then exit.

             ??????????????????????????????????????????????
             ? Programming the ATI Technologies SVGA Chip ?
             ??????????????????????????????????????????????

                 Written for the PC-GPE by Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au

                  Please read the file SVGINTRO.TXT
              (Graphics/SVGA/Intro PC-GPE menu option)

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Locating the Extended Register Set ?
??????????????????????????????????????

The ATI extended register set is based on the vga's index register scheme,
ie you write the value of the register you want to modify to Index Register
Port and write the actual data to the Data Port (the Data Port is one port
number higher than the Index Register Port). The value of the Index Register
for the ATI extended register set is stored in a word in BIOS ROM at
C000:0010. Apparently ATI want to change the value of this register in
future so they recommend you obtain it by reading the value at this memory
address.

?????????????????????????????????????????????????????????????????????????????
? Identifying the ATI Chip ?
????????????????????????????

The ATI chip can be identified by checking the string in memory locations
C000:0031-003A for the following characters : 761295520

?????????????????????????????????????????????????????????????????????????????
? Identifying which ATI Chip ?
??????????????????????????????

The first version of the ATI chip is the 18800. The second version is the
28800, which from a programming perspective is identical to the 18800-2.
The 18800 can be identified by it's lack of support for display mode
55h.

?????????????????????????????????????????????????????????????????????????????
? Determining the ATI Chip Revision Number ?
????????????????????????????????????????????

The ATI chip revision number is stored at BIOS location C000:0043.

?????????????????????????????????????????????????????????????????????????????
? ATI Graphics Display Modes ?
??????????????????????????????

             ?????????????????????????????????????????????????
             ? Mode    Resolution   Colors          Chip     ?
             ?????????????????????????????????????????????????
             ?  53h     800x600      16              18800   ?
             ?  54h     800x600      16              18800   ?
             ?  55h     1024x768     16 (planar)     18800-1 ?
             ?  61h     640x400      256             18800   ?
             ?  62h     640x480      256             18800   ?
             ?  63h     800x600      256             18800   ?
             ?  65h     1024x768     256 (packed)    18800   ?
             ?  67h     1024x768     4               ?       ?
             ?????????????????????????????????????????????????

?????????????????????????????????????????????????????????????????????????????
? ATI Display Memory ?
??????????????????????

In the following examples the EXT variable is the extended register index
value obtained from reading the word at C000:0010.

The ATI supports both single and duel bank memory mapping. It supports 64K
byte pages, each of these can be mapped into the host address space.



Single or duel bank mode is selected by the E2B bit in register BE

          Index : BEh at port EXT
          Read/Write at port EXT + 1
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
                            ?
                            ??????????????? E2B 0 = Single Bank Mode
                                                1 = Duel Bank Mode

Selecting a bank to write to in single bank mode is done by writing the bank
number to the Bank Select Register :

          Index : B2h
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
                        ?????????????
                         Bank number

The following procedure will select a bank in single bank mode :

Port[EXT] := $B2;
Port[EXT + 1] := (Port[EXT + 1] And $E1) Or (bank_number shl 1);

where bank_number = 0 - 15. Each bank is 64K long and has a 64K
granularity.

Duel Bank Mode is only supported on the 18800-1 and 28800 chips. You can
map one bank to A000:0000-FFFF for read operations and another to the
same address space for write operations.

          Index : B2h
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
            ?????????       ?????????
              Read            Write
              Bank            Bank
              Number          Number

The following code will set the write bank number:

Port[EXT] := $B2;
Port[EXT + 1] := (Port[EXT + 1] And $F0) Or (write_bank_number shl 1);

The following code will set the read bank number:

Port[EXT] := $B2;
Port[EXT + 1] := (Port[EXT + 1] And $0F) Or (read_bank_number shl 5);

?????????????????????????????????????????????????????????????????????????????
? ATI IsModeAvailable BIOS Call ?
?????????????????????????????????

Int 10h
Inputs :
    AH = 12h            Extended VGA Control
    BX = 5506h          Get Mode Information
    BP = FFFF           Set up for Return Argument
    AL = Mode Number    Mode number you want to test

Returns:
BP = FFFFh Mode not supported
     Anything else : mode is supported, BP = offset into CRTC table for mode


          ????????????????????????????????????????????????????
          ? Programming the Chips And Technologies SVGA Chip ?
          ????????????????????????????????????????????????????

                 Written for the PC-GPE by Mark Feldman
              e-mail address : u914097@student.canberra.edu.au
                               myndale@cairo.anu.edu.au

                  Please read the file SVGINTRO.TXT
              (Graphics/SVGA/Intro PC-GPE menu option)

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Setup mode ?
??????????????

To modify some of the CAT's internal SVGA registers the card must be placed
into setup mode. This is done by writing the value 1Eh to port 46E8h. To
exit setup mode write the value 0Eh to port 46E8h.

?????????????????????????????????????????????????????????????????????????????
? Enabling Extensions ?
???????????????????????

The CAT's extended registers are normally locked and must be enabled before
you attempt to modify them. To enable them, you must enter setup mode,
write the value 80h to port 103h and exit setup mode. To disable them
you must enter setup mode, write the value 00h to port 103h and exit
setup mode.

?????????????????????????????????????????????????????????????????????????????
? Identifying the CAT Chip ?
????????????????????????????

Detecting the presence of a CAT chip can be done by entering setup mode,
checking that the value returned from reading port 104h is A5h and then
exiting setup mode.

?????????????????????????????????????????????????????????????????????????????
? Identifying which CAT Chip and Revision Number ?
??????????????????????????????????????????????????

The CAT chip type and revision number can be determined by enabling
extensions, reading the value of register 0 and disabling extensions.
The top 4 bits (4-7) are the chip id and the lower 4 are the version
number.

                     ?????????????????????????????????
                     ? Chip ID      Chip             ?
                     ?????????????????????????????????
                     ?    1         82c451 or 82c452 ?
                     ?    2         82c455           ?
                     ?    3         82c453           ?
                     ?    5         82c456           ?
                     ?????????????????????????????????


The 82c451 and 82c452 can be distinguished by attempting to modify
register 3Ah (Graphics Cursor Color 1, make sure you set it back to what it
was). If the register exists the chip is an 82c452.

Alternatively the chip ID can be determined using the Get Controller
Information BIOS call (see below).

?????????????????????????????????????????????????????????????????????????????
? CAT Graphics Display Modes ?
??????????????????????????????

        ?????????????????????????????????????????????????????
        ? Mode    Resolution   Colors          Chip         ?
        ?????????????????????????????????????????????????????
        ? 25h     640x480      16              451/452/453  ?
        ? 6Ah     800x600      16              451/452/453  ?
        ? 70h     800x600      16              451/452/453  ?
        ? 71h     960x720      16              452          ?
        ? 72h     1024x768     16              452/453      ?
        ? 78h     640x400      256             451/452/453  ?
        ? 79h     640x480      256             452/453      ?
        ? 7Ah     768x576      256             452          ?
        ? 7Ch     800x600      256             453          ?
        ? 7Eh     1024x768     256             453          ?
        ?????????????????????????????????????????????????????

?????????????????????????????????????????????????????????????????????????????
? The CAT Display Memory ?
??????????????????????????

The following registers can only be modified while the extended registers
are enabled (See Enabling Extensions above).

The 451, 455 and 456 are always in single-paging mode and have 4 64K
banks. To switch to a bank you must first enable access to extended memory
with the following procedure:

Port[$3D6] := $0B;
Port[$3D6] := Port[$3D6] and $FD;

Selecting a bank can be done with the following procedure:

Port[$3D6] := $0B;
Port[$3D7] := bank_number;

where bank_number = 0 - 3. Each bank is 64K long and has a 86K granularity.

The 452 and 453 banks have a 16K granularity, so if you want 64K granularity
you must multiply the bank number by 4 before writing it to the registers :

Port[$3D6] := $10;
Port[$3D7] := bank_number Shl 2;  { = bank_numer * 4 }

The 452 and 453 allow duel paging. The 64K host address space is split in
two, one low area A000:0000-7FFFh and a high area A000:8000-FFFFh. This
mode can be enabled with the following procedure:

Port[$3D6] := $10;
Port[$3D6] := Port[$3D6] or 2;

In this mode each bank also has a granularity of 16K. The lower bank is
selected with the same procedure for setting the bank in single-paging
mode. The upper bank is selected with the following call:

Port[$3D6] := $11;
Port[$3D7] := bank_number Shl 2;  { = bank_numer * 4 }

None of the CAT chips allow you to select one bank for reading and
another for writing.

?????????????????????????????????????????????????????????????????????????????
? CAT Get Controller Information BIOS Call ?
????????????????????????????????????????????

Int 10h
Inputs :
    AH = 5Fh            Extended VGA Control
    AL = 00h            Get Controller Information

Returns:
AL = 5Fh                Extended VGA control function supported
BL = Chip type          bits 7-4 contain the chip type number
                            0 = 82c451
                            1 = 82c452
                            2 = 82c455
                            ? = 82c453
                        bits 3-0 contain the revision number
BH = Memory Size        Video memory size
                          0 = 256k
                          1 = 512k
                          2 = 1M

                  ???????????????????????????????????
                  ? Programming the Genoa SVGA Chip ?
                  ???????????????????????????????????

                 Written for the PC-GPE by Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au

                  Please read the file SVGINTRO.TXT
              (Graphics/SVGA/Intro PC-GPE menu option)

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Introduction ?
????????????????

Genoa has produced 2 SVGA cards. The earlier Genoa cards were based on the
Tseng ET3000 chip, the more recents cards are based on the Genoa chip. This
file will deal only with the cards based on the Genoa chip (the GVGA).

?????????????????????????????????????????????????????????????????????????????
? The Extended Register Set ?
?????????????????????????????

The Genoa uses the same ports as the VGA sequencer register set to access
most of it's extended registers, ie the Index Register port for the Genoa
is 3C4h and Data port is 3C5h.

?????????????????????????????????????????????????????????????????????????????
? Identifying the Genoa SVGA Card ?
???????????????????????????????????

To identify if a Genoa SVGA is present read the byte at address C000:0000.
Let's call this byte SIG_OFFSET. Next read the four bytes at
C000:SIG_OFFSET. These four bytes should have the following values :

                ???????????????????????????????????????
                ?  Memory Address              Value  ?
                ???????????????????????????????????????
                ?  C000:SIG_OFFSET              77h   ?
                ?  C000:SIG_OFFSET + 1          xx    ?
                ?  C000:SIG_OFFSET + 2          66h   ?
                ?  C000:SIG_OFFSET + 3          99h   ?
                ???????????????????????????????????????

?????????????????????????????????????????????????????????????????????????????
? Identifying which Genoa Card is Present ?
???????????????????????????????????????????

The value of the byte at C000:SIG_OFFSET + 1 is the chip identify code. The
values for each of the Genoa cards is as follows

              ??????????????????????????????????????????
              ? xx       Card             Chip         ?
              ??????????????????????????????????????????
              ? 33h      5100/5200        Tseng ET3000 ?
              ? 55h      5300/5400        Tseng ET3000 ?
              ? 22h      6100             Genoa GVGA   ?
              ? 00h      6200/6300        Genoa GVGA   ?
              ? 11h      6400/6600        Genoa GVGA   ?
              ??????????????????????????????????????????

There is no method for determining the card revision number.

?????????????????????????????????????????????????????????????????????????????
? Genoa Graphics Display Modes ?
????????????????????????????????

All Genoa cards support the following graphics modes :

                  ?????????????????????????????????????
                  ? Mode     Resolution       Colors  ?
                  ?????????????????????????????????????
                  ? 59h      720x512          16      ?
                  ? 5Bh      640x350          256     ?
                  ? 5Ch      640x480          256     ?
                  ? 5Dh      720x512          256     ?
                  ? 5Eh      800x600          256     ?
                  ? 5Fh      1024x768         16      ?
                  ? 6Ah      800x600          16      ?
                  ? 6Ch      800x600          256     ?
                  ? 73h      640x480          16      ?
                  ? 79h      800x600          16      ?
                  ? 7Ch      512x512          16      ?
                  ? 7Dh      512x512          256     ?
                  ? 7Eh      640x400          256     ?
                  ? 7Fh      1024x768         4       ?
                  ?????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Genoa Display Memory ?
????????????????????????

Two banks can be mapped to the segment A000:0000-FFFFh, one for
reading and one for writing. The banks can be selected by writing to
the Memory Segment Register :

          Index : 06h at port 3C4h
          Read/Write at port 3C5h
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
          MEM ???   ?????????   ?????????
                      Write        Read
                      Bank         Bank

The following code can be used to set the write bank:

Port[$3C4] := $06;
Port[$3C5] := (Port[$3C5] and $C7) or (write_bank_number shl 3);

The following code can be used to set the read bank:

Port[$3C4] := $06;
Port[$3C5] := (Port[$3C5] and $F8) or read_bank_number;

There are 8 banks (numbered 0 -7). Each bank is 64K long, has a 64K
granularity and is mapped to host memory A000:0000-FFFFh.

                ??????????????????????????????????????
                ? Programming the Paradise SVGA Chip ?
                ??????????????????????????????????????

                 Written for the PC-GPE by Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au

                  Please read the file SVGINTRO.TXT
              (Graphics/SVGA/Intro PC-GPE menu option)

            ?????????????????????????????????????????????
            ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
            ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
            ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.


?????????????????????????????????????????????????????????????????????????????
? Introduction ?
????????????????

Western Digital have made a series of Paradise chips, the PVGA1A, WD90C00
and WD90C11. Each chip is fully compatible with it's predecessors. There
is also a WD90C10 which is a stripped down version of the WD90C00 used for
motherboard VGA implementations and does not support 256 color modes higher
that 320x200; this chip will not be discussed here.

?????????????????????????????????????????????????????????????????????????????
? Paradise Extensions ?
???????????????????????

To modify any of the Paradise extended registers you must enable the
extensions. Disable them once you are done.

To enable extensions:

PortW[$3CE] := $050F; { Extensions on             }
PortW[$3D4] := $8529; { Unlock PR10-PR17          }
PortW[$3C4] := $4806; { Unlock extended sequencer }

To disable extensions :

PortW[$3CE] := $000F; { Extensions off          }
PortW[$3D4] := $0029; { Lock PR10-PR17          }
PortW[$3C4] := $0006; { Lock extended sequencer }

???????????????????????????????????????????????????????????????????????????????
? Identifying the Paradise SVGA Chip ?
??????????????????????????????????????

To identify if a Paradise SVGA chip is present read the 4 bytes at memory
address C000:007D. These bytes should be the string "VGA=".

                ????????????????????????????????
                ?  Memory Address   Value      ?
                ????????????????????????????????
                ?  C000:007Dh        86d ('V') ?
                ?  C000:007Eh        71d ('G') ?
                ?  C000:007Fh        65d ('A') ?
                ?  C000:0080h        61d ('=') ?
                ????????????????????????????????

?????????????????????????????????????????????????????????????????????????????
? Identifying which Paradise Chip is Present ?
??????????????????????????????????????????????

The Paradise chip present can be determined by trying to access selected
registers. The following pseudo-code will determine the chip id:

var old_value : byte;

Enable Extensions

{ Test for a PVGA1A }
Port[$3D4] := $2B
old_value := Port[$3D5]
Port[$3D5] := $AA
if Port[$3D5] <> $AA then
  begin
    chip is a PVGA1A
    Port[$3D5] := old_value
    return
  end
Port[$3D5] := old_value

{ Distinguish between WD90C00 and WD90C10 }
Port[$3C4] := $12
old_value := Port[$3C5]
Port[$3C5] := old_value and $BF
if (Port[$3C5] and $40) <> 0 then
  begin
    chip is a WD90C00
    return
  end
Port[$3C5] := old_value or $40
if (Port[$3C5] and $40) = 0 then
  begin
    chip is a WD90C00
    Port[$3C5] := old_value
    return
  end
Port[$3C5] := old_value

{ Distinguish between WD90C10 and WD90C11 }
Port[$3C4] := $10
old_value := Port[$3C5]
Port[$3C5] := old_value and $FB
if (Port[$3C5] and $04) <> 0 then
  begin
    chip is a WD90C10
    Port[$3C5] := old_value
    return
  end
Port[$3C5] := old_value or $04
if (Port[$3C5] and $04) = 0 then
  begin
    chip is a WD90C10
    Port[$3C5] := old_value
    return
  end

{ We made it this far so it's a WD90C11 }
chip is a WD90C11
Port[$3C5] := old_value

?????????????????????????????????????????????????????????????????????????????
? Paradise Graphics Display Modes ?
???????????????????????????????????

        ???????????????????????????????????????????????????????????
        ? Mode     Resolution       Colors    Chips               ?
        ???????????????????????????????????????????????????????????
        ? 58h      800x600          16        pVGA1, WDC90cxx     ?
        ? 59h      800x600          2         pVGA1, WDC90cxx     ?
        ? 5Eh      640x400          256       pVGA1, WDC90cxx     ?
        ? 5Fh      640x480          256       pVGA1, WD90cxx      ?
        ? 5Ah      1024x768         2         WD90cxx             ?
        ? 5Bh      1024x768         4         WD90cxx             ?
        ? 5Dh      1024x768         16        WD90cxx, c11 (512K) ?
        ? 5Ch      800x600          256       WD90c11 (512K)      ?
        ???????????????????????????????????????????????????????????

?????????????????????????????????????????????????????????????????????????????
? Paradise Display Memory ?
???????????????????????????

Remember, extensions must be enabled before any of the following procedures
are called.

The Paradise can work in either single-paging mode, duel-paging mode or
read/write mode. There are two registers used to select banks in each of
the Paradise bank selection modes:

          PR0A Address Offset A
          Index : 09h at port 3CEh
          Read/Write at port 3CFh
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
                ?????????????????????????
                           Bank

          PR0B Address Offset A
          Index : 0Ah at port 3CEh
          Read/Write at port 3CFh
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
                ?????????????????????????
                           Bank

There are 128 banks and the bank granularity is 4k, so if you want a bank
granularity of 64k you must multiply the bank number by 16.


Single Paging Mode
??????????????????

In single paging mode PR0A is set to map a bank to host memory at
A000:0000-FFFFh. The bank is used for both reading and writing operations.
To set up for single paging mode use the following procedure:

Port[$3C4] := $11;                 { Disable read/write mode }
Port[$3C5] := Port[$3C5] and $7F;
Port[$3CE] := $0B;                 { Disable PR0B            }
Port[$3CF] := Port[$3CF] and $F7;

To set a 64k bank number in single paging mode use the following procedure:

PortW[$3CE] := bank_number Shl 12 + $09;


Duel Paging Mode
????????????????

In duel paging mode PR0A is set to map a bank to host memory at
A000:0000-7FFFh and PR0B is set to map a bank to host memory at
A000:8000-FFFFh. Each bank is used for both reading and writing operations.

To set up for duel paging mode use the following procedure:

Port[$3C4] := $11;                 { Disable read/write mode }
Port[$3C5] := Port[$3C5] and $7F;
Port[$3CE] := $0B;                 { Enable PR0B             }
Port[$3CF] := Port[$3CF] or $80;

To set the lower bank use the same procedure as given for single-paging
mode. The upper bank can be set with the following procedure:

PortW[$3CE] := bank_number Shl 12 + $0A;


Read/Write Paging Mode
??????????????????????

In read/write paging mode PR0A is used to map a bank at A000:0000-FFFFh for
read operations and PR0B is used to map a bank at A000:0000-FFFFh for write
operations. To set up for read/write paging mode use the following procedure:

Port[$3C4] := $11;                 { Enable read/write mode }
Port[$3C5] := Port[$3C5] or $80;
Port[$3CE] := $0B;                 { Enable PR0B             }
Port[$3CF] := Port[$3CF] or $80;

Setting PR0A and PR0B is the same as for duel paging mode.


                 ?????????????????????????????????????
                 ? Programming the Trident SVGA Chip ?
                 ?????????????????????????????????????

                 Written for the PC-GPE by Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au

                  Please read the file SVGINTRO.TXT
              (Graphics/SVGA/Intro PC-GPE menu option)

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.


?????????????????????????????????????????????????????????????????????????????
? Identifying the Trident SVGA Card ?
?????????????????????????????????????

There are two Trident SVGA chips, the TVGA 8800 and 8900.

The Trident SVGA chips can be identified by attempting to change the
Mode Control #1 register as follows:

          Index : 0Eh at port 3C4h
          Read/write data from port 3C5h
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
                                    ?
                                   PAGE

First write the value 0Eh to port 3C4h. Then read the value in from port
3C5h and save it. for rest Next write the value 00h to port 3C5h and
read the value back in from the port. If bit 1 in the value read is set
(ie = 1) then a trident chip is present. Finally write the original value
back to port 3C5h to leave the SVGA adapter in it's original state.

?????????????????????????????????????????????????????????????????????????????
? Identifying which Trident Chip is Present ?
?????????????????????????????????????????????

The Trident chip can be identified with the following psuedo code :

Port[$3C4] := $0B
Port[$3C5] := $00
hardware_version_number := Port[$3C5]
if hardware_version_number >= 3 then
  chip is an 8900
else
  chip is an 8800

This procedure leaves the chip in "New Mode". New Mode and Old mode are
discussed below.

?????????????????????????????????????????????????????????????????????????????
? Trident Graphics Display Modes ?
??????????????????????????????????

           ?????????????????????????????????????????????????
           ? Mode     Resolution       Colors    Chip      ?
           ?????????????????????????????????????????????????
           ? 5Bh      800x600          16        8800/8900 ?
           ? 5Ch      640x400          256       8800/8900 ?
           ? 5Dh      640x480          256       8800/8900 ?
           ? 5Eh      800x600          256       8900      ?
           ? 5Fh      1024x768         16        8800/8900 ?
           ? 61h      768x1024         16        8800/8900 ?
           ? 62h      1024x768         256       8900      ?
           ?????????????????????????????????????????????????

?????????????????????????????????????????????????????????????????????????????
? Trident Display Memory ?
??????????????????????????

Both Trident chips can map video memory in either 64K or 128K paging
schemes. The 8800 defaults to the 128K paging scheme at power up. This
scheme is known as the "Old Mode". The 8900 defaults to the 64K paging
scheme, the "New Mode". This file will concentrate solely on the 64K new
mode operation.

The new mode can be set with the following procedure:

Port[$3C4] := $0B               { Set the old mode 128K scheme }
Port[$3C5] := $00
dummy_variable := Port[$3C5]    { Toggle over to the new mode }

Trident bank switching is weird, REALLY weird! In new mode, the New Mode
Control Register # 1 is used to select the active bank:

          Index : 0Eh at port 3C4h
          Read/write data from port 3C5h
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
                            ?????   ?   ?
                            Bank  Page Seg

Bits 3-0 can be considered as a single 4 bit bank number. However, when
you write to video memory the Trident inverts the Page bit to determine
which bank should actually be written to. So if you set these bits to the
value 0 (0000) then bank 0 will be used for all read operations and bank 2
(0010) will be used for all write operations.

The following code will set the bank number for all read operations:

PortW[$3C4] := bank_number shl 8 + $0E;

The following code will set the bank number for all write operations:

PortW[$3C4] := (bank_number xor 2) shl 8 + $0E;

It is important to realise that setting the write bank number changes the
read bank number, and visa-versa. How you are supposed to rapidly transfer
blocks of data around on the Trident screen is beyond me.


                  ???????????????????????????????????
                  ? Programming the Tseng SVGA Chip ?
                  ???????????????????????????????????

                 Written for the PC-GPE by Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au

                  Please read the file SVGINTRO.TXT
              (Graphics/SVGA/Intro PC-GPE menu option)

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Identifying the Tseng SVGA Card ?
???????????????????????????????????

Tseng Labs have produced two SVGA Chips, the ET3000 and the ET4000.

The Tseng SVGA chips can be identified by attempting to change the
Miscellaneous register as follows:

          Index : 06h at port 3C0h
          Read/write data from port 3C1h
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
                    ?????
                     High

Output the value 6 to port 3C0h and read a byte from port 3C1h. Modify
the high field in this byte (eg new byte = byte XOR 30h) and write this
new byte to port 3C1h. Read the byte from port 3C1h and see if the byte
was successfully modified, if it was then a Tseng chip is present. Having
done this, write the original byte back to port 3C1h to leave the graphics
adapter in it's original state.

?????????????????????????????????????????????????????????????????????????????
? Identifying which Tseng Card is Present ?
???????????????????????????????????????????

The ET4000 can be distinguished from the ET3000 by attempting to change the
ET4000 Extended Start Address register as follows:

          Index : 33h at port 3D4h
          Read/write data from port 3D5h
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
                            ?????   ?????
                             CAD     DAD

The same technique is used as was used to identify the presence of a
Tseng chip, both fields should be modified, written, tested for a successful
write and then restored to their original values. If the change was
successful an ET4000 chip is present, otherwise an ET3000 chip is.

?????????????????????????????????????????????????????????????????????????????
? Tseng Graphics Display Modes ?
????????????????????????????????

            ???????????????????????????????????????
            ? Mode     Resolution       Colors    ?
            ???????????????????????????????????????
            ? 25h      640x480          16        ?
            ? 29h      800x600          16        ?
            ? 2Dh      640x350          256       ?
            ? 2Eh      640x480          256       ?
            ? 2Fh      640x400          256       ?
            ? 30h      800x600          256       ?
            ? 37h      1024x768         16        ?
            ? 38h      1024x768         256       ?
            ???????????????????????????????????????

All graphics modes in the above table are supported by the ET4000. I am not
sure which modes are supported by the ET3000.

?????????????????????????????????????????????????????????????????????????????
? Tseng Display Memory ?
????????????????????????

In my opinion the Tseng memory mapping was designed to prevent graphics
programmers from suffering nervous breakdowns!

Two banks can be mapped to the segment A000:0000-FFFFh, one for
reading and one for writing. The banks can be selected by writing to
the Segment Select Registers at port 3Cdh:

ET3000 Segment Select Register:
          Port 3CDh
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
                    ?????????   ?????????
                      Read         Write
                      Bank         Bank

ET4000 Segment Select Register:
          Port 3CDh
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
            ?????????????   ?????????????
                 Read           Write
                 Bank           Bank

Both of these registers can be read from as well as written to.

Each bank is 64K long, has a 64K granularity and is mapped to host
memory A000:0000-FFFFh.

?????????????????????????????????????????????????????????????????????????????
? DPMI and the ET4000 ?
???????????????????????

Apparently the ET4000 chip is capable of linear addressing in dos protect-
mode programs. To enable this feature write the value 36h to port 3D4h,
read the value from port 3D5h, set the lower nibble (bits 0 -> 3) to the
value 1 and rewrite the value to port 3D5h. Resetting these bits to the
value 0 puts the chip back in regular segmented addressing mode.

I have no information where or how the entire ET4000 memory would then be
mapped to linear memory. If anyone has more information on this or has a
Tseng card they are willing to try it on let me know.


                 ????????????????????????????????????
                 ? Programming the Video7 SVGA Chip ?
                 ????????????????????????????????????

                 Written for the PC-GPE by Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au

                  Please read the file SVGINTRO.TXT
              (Graphics/SVGA/Intro PC-GPE menu option)

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Video7 Extensions ?
?????????????????????

To modify any of the Video7 extended registers you must enable the
extensions. Disable them once you are done.

To enable extensions:

PortW[$3C4] := $EA06;

To disable extensions:

PortW[$3C4] := $AE06;



?????????????????????????????????????????????????????????????????????????????
? Identifying the Video7 SVGA Chip ?
????????????????????????????????????

The presence of a Video7 chip can be detected with the following procedure:

var old_value, new_value, id : byte;

EnableVideo7Extensions;
Port[$3D4] := $0C;
old_value := Port[$3D5];
Port[$3D5] := $55;
new_value := Port[$3D5];
Port[$3D4] := $1F;
id := Port[$3D5];
Port[$3D4] := $0C;
Port[$3D5] := old_value;
DisableVideo7Extentions;

{ Check that register value is $55 Xor $EA }
if id = $BF then
  card is a video7
else
  card isn't a video7


?????????????????????????????????????????????????????????????????????????????
? Identifying which Video7 Chip is Present ?
????????????????????????????????????????????

Once you know that the video card has a video7 in it you can read the Chip
Revision register to find out which chip it is:

Port[$3C4] := $8E;
chip := Port[$3C5];

              ??????????????????????????????????????????
              ? Value in                               ?
              ? chip variable     Video7 Chip          ?
              ??????????????????????????????????????????
              ? 40h-49h           1024i                ?
              ? 50h-59h           V7VGA Version 5      ?
              ? 70h-7Eh           V7VGA FASTWRITE/VRAM ?
              ? 80h-FFh           VEGA VGA             ?
              ??????????????????????????????????????????

?????????????????????????????????????????????????????????????????????????????
? Video7 Graphics Display Modes ?
?????????????????????????????????

                  ????????????????????????????????????
                  ? Mode     Resolution      Colors  ?
                  ????????????????????????????????????
                  ? 60h      752x410          16     ?
                  ? 61h      720x540          16     ?
                  ? 62h      800x600          16     ?
                  ? 63h      1024x768         2      ?
                  ? 64h      1024x768         4      ?
                  ? 65h      1024x768         16     ?
                  ? 66h      640x400          256    ?
                  ? 67h      640x480          256    ?
                  ? 68h      720x540          256    ?
                  ? 69h      800x600          256    ?
                  ????????????????????????????????????

?????????????????????????????????????????????????????????????????????????????
? Video7 Display Memory ?
?????????????????????????

Remeber, the extensions must be enabled before calling any of the following
procedures.

The Video7 version 1-3 chips use a ridiculously complex method to switch
banks (in my opinion at least), so for these chips I'll only include the code
to bank switch and leave the technical info on what it does and why it does
it till a future PC-GPE version (if ever).

The version 1-3 chips map two banks to host memory A000:0000-FFFFh. One bank
is used for read operations, the other is used for write operations. For 256
color modes there are 16 64k banks (numbered 0 - 15 for the following
procedures). One really "stuffed in the head" thing about these chips (from
a programmers point of view anyway) is that both bank registers use a common
SVGA register to store their 2 low order bits, so if you set the Read Bank
number, the Write Bank number's 2 low order bits will be set the same as the
Read Bank number's 2 low order bits.

The Write Bank number for 256 color modes can be set with the following
procedure:

Port[$3C4] := $F9;
Port[$3C5] := bank_number and 1;
Port[$3C2] := (Port[$3CC] and $DF) or ((bank_number and 2) shl 4);
Port[$3C4] := $F6;
Port[$3C5] := (Port[$3C5] and $FC) or (bank_number shr 2);

The Read Bank number for 256 color modes can be set with the following
procedure:

Port[$3C4] := $F9;
Port[$3C5] := bank_number and 1;
Port[$3C2] := (Port[$3CC] and $DF) or ((bank_number and 2) shl 4);
Port[$3C4] := $F6;
Port[$3C5] := (Port[$3C5] and $F3) or (bank_number and $0C);

By version 4 Headlands Technologies had gotten their act together and
adopted a more "sane" bank switching scheme. Version 4 supports both single
and duel paging schemes. There are 16 64k long banks, and a 64k granularity
with the techniques used here.

The single paging scheme maps a bank to host memory A000:0000-FFFFh for
both read and write operations. The single paging scheme is the
default for version 4, but can also be set with the following procedure:

Port[$3C4] := $E0;
Port[$3C5] := Port[$3C5] and $7F

The Single/Write Bank Register is used to select which bank to map to
host memory:

          Index : E8h at port 3C4h
          Read/Write at port 3C5h
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
            ?????????????
                 Bank

A bank can be selected with the following procedure:

PortW[$3C4] := (bank_number shl 12) + $E8;

In duel paging mode one bank is mapped to A000:0000-FFFF for write operations
and another for read operations. Duel paging mode can be selected with the
following procedure:

Port[$3C4] := $E0;
Port[$3C5] := Port[$3C5] or $80;

The Single/Write Bank Register (see above) is used to select which bank to
map to host memory for writing operations. The Read Bank Register selects
which bank to use for read operations:

          Index : E9h at port 3C4h
          Read/Write at port 3C5h
          ?????????????????????????????????
          ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
          ?????????????????????????????????
            ?????????????
                 Bank

A read bank can be selected with the following procedure:

PortW[$3C4] := (bank_number shl 12) + $E9;

                 ????????????????????????????????????????
                 ? Xtended Mode - Unchained 640x400x256 ?
                 ????????????????????????????????????????

                 Written for the PC-GPE by Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au

                    Please read the file SVGINTRO.TXT
                (Graphics/SVGA/Intro PC-GPE menu option)

               ?????????????????????????????????????????????
               ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
               ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
               ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.


?????????????????????????????????????????????????????????????????????????????
? Introduction ?
????????????????

I am calling this mode Xtended mode simply because I don't know if it 
already has a name. It is a variation of mode x and it has worked on every 
SVGA I have tried it on. It seems very very unlikely that I was the first 
person to try this, so if this mode has already been documented elsewhere I 
would very much like to hear about it.

Xtended mode is 640x400x256 and will only work on SVGA's supporting the
"regular" 640x400x256 mode. It's advantage is that it requires no
bank switching to access the entire display memory and, like mode x, polygon
fill graphics can be up to 4 times faster.

?????????????????????????????????????????????????????????????????????????????
? Setting Xtended Mode ?
????????????????????????

Xtended mode is set similar to the way unchained mode 13h is set, the only
difference is that you you call BIOS to set the 640x400x256 graphics mode
instead of mode 13h. The 640x400x256 mode number varies from card to card.
The following table lists the mode number for each of the 7 "standard"
SVGAs:

           640x400x256 mode numbers for various SVGA cards
               ???????????????????????????????????????
               ?      SVGA Chip          Mode Number ?
               ???????????????????????????????????????
               ? ATI                        61h      ?
               ? Chips & Technologies       78h      ?
               ? Genoa                      7Eh      ?
               ? Paradise                   5Eh      ?
               ? Trident                    5Ch      ?
               ? Tseng                      2Fh      ?
               ? Video 7                    66h      ?
               ???????????????????????????????????????

Alternatively the mode can be set with the VESA Set Super VGA Mode BIOS
call, the VESA SVGA mode number is 100h. Refer to the file "VESASP12.TXT"
for more information on VESA BIOS calls.

The following Pascal procedure will set Xtended mode for a card with a VESA
driver:

const VIDEO     = $10;  { Video interrupt number                    }
      CRTC_ADDR	= $3d4; { Base port of the CRT Controller (color)   }
      SEQU_ADDR	= $3c4; { Base port of the Sequencer                }

procedure InitXtended;
begin

  { Set VESA 640x400x256 mode }
  asm
    mov ax, $4F02
    mov bx, $100
    int VIDEO
  end;

  { Turn the VGA screen off }
  Port[SEQU_ADDR] := 1;
  Port[SEQU_ADDR + 1] := Port[SEQU_ADDR + 1] or $20;

  { Turn off the Chain-4 bit (bit 3 at index 4, port 0x3c4) }
  PortW[SEQU_ADDR] := $0604;

  { Turn off word mode, by setting the Mode Control register
    of the CRT Controller (index 0x17, port 0x3d4) }
  PortW[CRTC_ADDR] := $E317;

  { Turn off doubleword mode, by setting the Underline Location
    register (index 0x14, port 0x3d4) }
  PortW[CRTC_ADDR] := $0014;

  { Clear entire video memory, by selecting all four planes, then writing
  color 0 to the entire segment. Stoopid FillChar fills 1 byte too short! }
  PortW[SEQU_ADDR] := $0F02;
  FillChar(Mem[$A000 : 0], $8000, 0);
  FillChar(Mem[$A000 : $8000], $8000, 0);

  { Give a small delay to let the screen sort itself out }
  Delay(100);

  { Turn the screen back on }
  Port[SEQU_ADDR] := 1;
  Port[SEQU_ADDR + 1] := Port[SEQU_ADDR + 1] and $DF;
end;

????????????????????????????????????????????????????????????????????????????
? Drawing a Pixel ?
???????????????????

Drawing a pixel in Xtended mode is similar to drawing one in unchained mode
13h or mode x, we just have to keep in mind that the display is now twice
as wide. Also keep in mind that 640x400 has 4 times as many pixels as
320x200, so there is only one page in Xtended mode.

This example Pascal routine will draw a pixel at any screen position. I'll
let you do the job of converting it to assembly:

procedure XtendedPutPixel(x, y : word; color : byte);
begin
  { Set map mask to select proper plane }
  PortW[SEQU_ADDR] := $100 shl (x and 3) + 2;

  { Calculate address (y * 160 + x div 4) and write pixel }
  Mem[$A000 : y shl 7 + y shl 5 + x shr 2] := color;
end;

        ??????????????????% VLA Proudly Presents %??????????????????
        ?                                                          ?
        ????????????????????????????????????????????????????????????

??????????????????????????????????????????????????????????????????????????????
     ????????????????????????????????????????????????????????????????????
          ??????????????????????????????????????????????????????????
              Three Dimensional Rotations For Computer Graphics
          ??????????????????????????????????????????????????????????
     ????????????????????????????????????????????????????????????????????
??????????????????????????????????????????????????????????????????????????????
                              By Lithium /VLA



    One of the most difficult programming difficulties you will face is that
of representing a 3D world on that 2D screen in front of you.  It requires
some linear alegbra that may be difficult for some, so I will spend some
bytes explaining the mathmatic computations of using matricies in addition
to the equations to get you going.  This document is intended to be an aid
to anyone programming in any language, as a result it will use mathmatic
notation.  If you are worthy of using these routines, you ought to be able
to get them into your favorite language.  All I ask is that you pay a little
tribute to your programming buddies in VLA.


    If you aren't a math person, skip to the end and get the final equations.  
Just be forewarned, implimenting these equations into a coherient 3D world 
is hard enough when you undersand the mathmatics behind them...

                
                   REAL PROGRAMMERS AREN'T AFRAID OF MATH


3D Coordinates
??????????????

    Just so we all understand each other, 3D is defined in of course three
directions, we'll call them (X,Y,Z).  X will be the horizontal plane of your
screen, Z will stretch vertically, and Y will extend out of and into your
screen.  Got it?  Hope so, becuase it gets a bit tricky now.  The next 
system is called Sphereical Coordinates it is defined by angles and distance 
in (?,?,p) These Greek letters are Theta (?), Phi (?), and Roe (p)

        Z                                  Z
        |                                  |         ? - Angle in the XY 
        |                                  |\            plane
        |                                  |\\
        |                                  | \\      ? - Angle from the Z
        |______ X                          |?_|\___X     axis
       /                                  / \ v \
      /                                  / ? \   o   p - Distance to point
     /                                  /\    \  |       from the origin
    /                                  /  -->  \ |       (0,0,0)
   Y                                  Y         \|


    To relate the two systems you can use these equations.

    X = p(sin?)(cos?)                 ? = arctan (X/Y)
    Y = p(sin?)(sin?)                 ? = arccos (Z/p)
    Z = p(cos?)                       p = ?(X^2 + Y^2 + Z^2)

    If these don't seem right, do a couple of example problems for yourself,
it should make since after a bit of trig.


Matrix Notation
???????????????

    Lets say I can define Xt and Yt with the equations:

Xt = aX + bY        Where a,b,c,d are coeffiencets
Yt = cX + dY

    The matrix notation for this system of equations would be:
               ?   ?
(Xt,Yt) = (X,Y)?a c?
               ?   ?        And we solve for this with these steps
               ?b d?
               ?   ?
           
           ?   ? 
 Xt = (X,Y)?a .?  = aX + bY
           ?   ?            We move across the coordinates left to right
           ?b .?            and multiply them by the coeffients in the
           ?   ?            matrix, top to bottom
           ?   ? 
 Yt = (X,Y)?. c?  = cX + dY
           ?   ?            For Y, the second number, we use the second
           ?. d?            column of the matrix
           ?   ?            

 We can also multiply matricies in this fashion    
                        ?   ?           ?   ?
 T = T1*T2  Where  T1 = ?a c? and  T2 = ?e g?
                        ?   ?           ?   ?
                        ?b d?           ?f h?
                        ?   ?           ?   ?

?   ??   ?   ?                     ?
?a c??e g?   ?(ae + cf)   (ag + ch)?    rows ->   columns |
?   ??   ? = ?                     ?                      v
?b d??f h?   ?(be + df)   (bg + dh)?
?   ??   ?   ?                     ?

    This product is dependent on position, so that means that 
    T1*T2 *DOES NOT* equal T2*T1

    In English, the process above went like this, we move left to right in 
the first matrix, T1, and top to bottom in the second, T2.  AE + CF is our 
first position.  

    The numbers in the first row are multiplied by the numbers in the
first column.  1st * 1st + 2nd * 2nd is our first value for the new matrix.
Then you repeat the process for the next column of the second matrix.  

    After that, you move down to the next row of the first matrix, and 
multiply it by the 1st column of the second matrix.  You then do the same 
for the next column of the second matrix.  This process is repeated until 
you've done all of the rows and columns.  

If this is your introduction to matricies, don't feel bad if you're a bit 
confused.  They are a different mode of thinking about equations.  The 
operations above give the same results as if you were to do the long hand 
algebra to solve them.  It may seem a bit more difficult for these examples, 
but when you get to systems of equations with many variables, this way is 
MUCH faster to compute.  Trust me, especially when you make your program do 
it.


So, now you have the basic math....


    One important point for these matricies below.  I will use a homogeneous
coordinate system, (X/r, Y/r, Z/r, r) Now I'll use r=1, so nothing will
really be different in my calculations, but you need to understand the 
purpose.  

    This form is very convienent for the translations and rotation
equations we will need to do because it allows for scaling of our points with
respect to a center point.  
    
    Consider a point (2,2,2) in an object centered at (1,1,1).  If we were 
to scale the X direction by 3,(the X length to the center is 3 times what it 
was) the point we want would be (4,2,2).  Our new X = 3*(OldX-CenterX).  
Without the added factor of the homogeneous system, calculations assume all 
objects are centered at the origin, so our point would have turned out to be 
(6,2,2), NOT the one we wanted.  So that's why we are going to do it that way.



                        ROTATIONS AND TRANSFORMATIONS
?????????????????????????????????????????????????????????????????????????????

Translation
???????????
    We will start with translation from the origin.  Most objects are not
at (0,0,0,1), so we'll call their center (Tx,Ty,Tz,1).  

?             ?
? 1   0   0  0? = T1
?             ?
? 0   1   0  0?     This physically moves the object, so it is centered
?             ?     at the origin for our calcuations, eliminating the
? 0   0   1  0?     need for a -Tx for each X, the matrix will factor it
?             ?     in when we multiply it by the others
?-Tx -Ty -Tz 1?
?             ?     But, we need sphereical coordinates...

?                                            ?
?       1             0            0      0  ?
?                                            ?  =  T1  
?       0             1            0      0  ?
?                                            ?
?       0             0            1      0  ?
?                                            ?
?-p(cos?)(sin?) -p(sin?)(sin?) -p(cos?)   1  ?
?                                            ?


XY Clockwise Rotation
?????????????????????
    This will be our first rotation, about the Z-Axis

?                   ?
? sin?  cos?  0   0 ?
?                   ?  =  T2
?-cos?  sin?  0   0 ?
?                   ?
?  0     0    1   0 ?
?                   ?
?  0     0    0   1 ?
?                   ?


YZ Counter-Clockwise Rotation
?????????????????????????????
    Now we rotate about the X axis
?                   ?
?  1    0    0    0 ?
?                   ?  =  T3
?  0 -cos? -sin?  0 ?
?                   ?
?  0  sin? -cos?  0 ?
?                   ?
?  0    0    0    1 ?
?                   ?

    Notice that with two rotations that we can get any position in 3D space.

Left Hand Correction 
????????????????????
    This will flip the X coordinates.  Think about when you
look into the mirror, your left hand looks like your right.
These rotations do the same thing, so by flipping the X, it
will make your X move right when you increase it's value.

?             ?
? -1  0  0  0 ?
?             ?  =  T4
?  0  1  0  0 ?
?             ?
?  0  0  1  0 ?
?             ?
?  0  0  0  1 ?
?             ?


The Final Viewing Matrix
????????????????????????
    This is the net transformation matrix for our viewing perspective

The math for this one is really messy, and I would need to go over even
more matrix stuff to get it reduced, so I will ask you to trust my 
calculations

V = T1*T2*T3*T4

?                                        ?
? -sin?  -(cos?)(cos?)  -(cos?)(sin?)  0 ?
?                                        ?  =  V
?  cos?  -(sin?)(cos?)  -(sin?)(sin?)  0 ?
?                                        ?
?   0         sin?          -cos?      0 ?
?                                        ?
?   0          0              p        1 ?
?                                        ?


Lets say our original (X,Y,Z,1) were just that, and the point after the 
rotation is (Xv,Yv,Zv,1)

(Xv,Yv,Zv,1) = (X,Y,Z,1) * V


Xv = -Xsin? + Ycos?

Yv = -X(cos?)(cos?) - Y(sin?)(cos?) + Zsin?

Zv = -X(cos?)(sin?) - Y(sin?)(sin?) - Zcos? + p


????????????????????????
????????????????????????


    Some people have had trouble concepts of this implimentation, so I have
another way of setting up the equations.  This works off of the straight
X,Y, and Z coordinates too, but uses another angle.


We will define the following variables

Xan = Rotation about the X-Axis  
Yan = Rotation about the Y-Axis  
Zan = Rotation about the Z-Axis


Rotation about the Y Axis 
????????????????????????

?                                   ?
?  cos(Yan)     0       sin(Yan)    ?
?                                   ?  
?   0           1           0       ?
?                                   ?
? -sin(Yan)     0       cos(Yan)    ?
?                                   ?


Rotation about the Z Axis 
????????????????????????

?                                   ?
?   1           0           0       ?
?                                   ?  
?   0        cos(Zan)   -sin(Zan)   ?
?                                   ?
?   0        sin(Zan)    cos(Zan)   ?
?                                   ?


Rotation about the X Axis 
????????????????????????

?                                   ?
?  cos(Xan)  -sin(Xan)      0       ?
?                                   ?
?  sin(Xan)   cos(Xan)      0       ?
?                                   ?
?   0           0           1       ?
?                                   ?


For simplification, lets call sin(Yan) = s1, cos(Xan) = c3, 
 sin(Zan) = s2, etc

Final Rotation Matrix
????????????????????????

?                                                       ?
?  c1c3 + s1s2s3        -c1s3 + c3s1s2          c2s1    ?
?                                                       ?
?      c2s3                  c2c3               -s2     ?
?                                                       ?
? -c3s1 + c1s2s3         s1s3 + c1c3s2          c1c2    ?
?                                                       ?


????????????????????????

Xv = x(s1s2s3 + c1c3) + y(c2s3) + z(c1s2s3 - c3s1)

Yv = x(c3s1s2 - c1s3) + y(c2c3) + z(c1c3s2 + s1s3)

Zv = x(c1s2s3 - c3s1) + y(-s2)  + z(c1c2)

????????????????????????


Where Xv,Yv, and Zv are the final rotated points and the little x,y,z are
the original points.





        Normal Vectors - The Secret To Shading and Plane Elimination
?????????????????????????????????????????????????????????????????????????????


    So, now you have the rotation equations...  But, how do we make it fast?
Well, one of the best optimizations you can impliment is plane elimination.
It boils down to not displaying the planes that won't be seen.  With that
said, here comes more math....

                        BE A MAN, KNOW YOUR NORMALS

    A 'normal' vector is perpendicular to a plane.  Imagine the face of a
clock as a plane.  Take your right hand and point your thumb toward yourself
and the other end toward the clock.  Now curl your fingers in the 
counter-clockwise direction.  Your thumb is pointing in the direction of the
normal vector.  This is called 'The Right Hand Rule' and it is the basis for
figuring the facing of planes.

    A plane can be determined with three points, try it.  That's the minimum
you need, so that's what we will base our process on.  Now if we have a line
segment, we could move it to the origin, maintaining it's direction and 
lenght by subtracting the (X,Y,Z) of one of the points from both ends.  This
is our definition of a vector.  A line segment, starting at the origin and
extending in the direction (X,Y,Z).  

    Here will be our plane, built from the three points below.

(X1,Y1,Z1)      V = (X1-X2, Y1-Y2, Z1-Z2)
(X2,Y2,Z2)      W = (X1-X3, Y1-Y3, Z1-Z3)
(X3,Y3,Z3)

    So, we have our three points that define a plane.  From these points we
create two vectors V and W.  Now if you where to use the right hand rule with
these vectors, pointing your fingers in the direction of V and curling them
toward the direction of W, you would have the direction of the Normal vector.
This vector is perpendicular to both vectors, and since we have defined the
plane by these vectors, the normal is perpendicular to the plane as well.

The process of finding the normal vector is called the 'Cross Product' and
it is of this form:

     ?                     ?
V*W =?   i      k      j   ? 
     ?                     ?
     ? X1-X2  Y1-Y2  Z1-Z2 ?
     ?                     ?
     ? X1-X3  Y1-Y3  Z1-Z3 ?
     ?                     ?

 i = (Y1-Y2)(Z1-Z3) - (Z1-Z2)(Y1-Y3)

-k = (Z1-Z2)(X1-X3) - (X1-X2)(Z1-Z3) 

 j = (X1-X2)(Y1-Y3) - (Y1-Y2)(X1-X3)

The Normal to the plane is (i,-k,j)


NOTE: V*W *DOESN'T* equal W*V, it will be pointing in the negative direction
        To prove that to yourself, lets go back to how I explained it before
        We pointed in the direction of V and curled our fingers toward W, the
        normal vector in the direction of your thumb.  Try it in the 
        direction of W, toward V.  It should be in the opposite direction.
        Your normal, still perpendicular to both vectors, but it is negative.
        If you use in your program, you will have the planes appearing when
        they shouldn't and dissapearing when they are coming into view.

    So, now that we have a way to determin the direction of the plane,
how do we hide the plane?  If the angle between the view point and the
normal is greater than 90 degrees, don't show it.  One quick way that I
always use is to place the view point on an axis.  I tipically set the 
Z axis to come out of the screen, Y up and X across.  Set the view point
to be at a positive point on the Z and then, if that normal vector has Z
greater than zero, I display it, otherwise I skip to the next one.

    This also has an application in shading.  If you define a light scource,
just like the view point, you find the angle the normal and the light form.
Since you don't usually just want two colors, our 90 degree trick won't work
for you, but by finding this angle, and dividing all of the possible angles
by the number of colors you will allow in the shading, that color can be
assigned to the plane and, presto-chango, it looks like you know what your 
doing...

    As you do your rotations, just rotate the coordinates of the normal and
that will keep everything updated.


Tips To Speed-Up Your Routines
??????????????????????????????

Pre-Calculate as many values as possible
    The main limitation you will have is the speed of your math, using
    precalculated values like Normals, Sin/Cos charts, and distance from the
    origin are all good candidates.

If you can get away with using a math-coprocessor, well...
    This will greatly increase the speed of your routine.  Unfortunately,
    not everyone has one.

Only figure values once
    If you multiply (Sin?)(Cos?) and will use that same value later, by all 
    means, keep it and use it then instead of doing the multiplication again.

Another thing to keep in mind
    The order of rotations *DOES* make a difference.  Try it out and you'll
    understand.  Also, when you start to use these routines, you'll find
    yourself making arrays of points and plane structures.  


Counter-Clockwise points
    Be sure to list your points for the planes in counter-clockwise order.  
    If you don't, not all of your planes will display correctly when you 
    start hiding planes.

And as always, be clever
    Just watch out, because when you have clever ideas you can lose a foot.
    My brother once had a clever idea to cut his toe nails with an axe and
    he lost his foot.

?????????????????????????????????????????????????????????????????????????????

Books to look for...
????????????????????
    Any math book, the topics I covered will be found in:

    Normal Vectors      - Analytic Geometry
    Matrix Operations   - Linear Algebra
    Sines and Cosines   - Trigonometry

    The Art of Graphics, by Jim McGregor and Alan Watt
        1986 Addison-Wesley Publishers



Read the VLA.NFO file to find out how to contact us.

        ??????????????????% VLA Proudly Presents %??????????????????
        ?                                                          ?
        ????????????????????????????????????????????????????????????

??????????????????????????????????????????????????????????????????????????????
     ????????????????????????????????????????????????????????????????????
          ??????????????????????????????????????????????????????????
                Three Dimensional Shading In Computer Graphics
          ??????????????????????????????????????????????????????????
     ????????????????????????????????????????????????????????????????????
??????????????????????????????????????????????????????????????????????????????
                              By Lithium /VLA



    Hopefully you have read the companion document 3DROTATE.DOC, as this one
will build apon the concepts presented in my attempt to teach some of the
math need to make 3D graphics a reality.  This file will cover such important
topics as the Dot Product and how routines are best constructed for real-time 
3D rotations and planar shading.




                        Our Friend, The Dot Product

    The Dot Product is a neat relation that will allow you to quickly find
the angle between any two vectors.  It's easiest to explain graphicly, so
I will exercise my extended-ASCII keys.


Two Vectors A & B

A (Xa, Ya, Za)     ?A? = ?( (Xa)? + (Ya)? + (Za)? )

B (Xb, Yb, Zb)     ?B? = ?( (Xb)? + (Yb)? + (Zb)? )


Where Xa, and the others coorispond to some value on their respective Axis's


       ?A
      /
     /
    /
   /  
   \ ?   <-- Angle Theta between vector A and B
    \
     \
      \
       ?B


 Cos(?) =  Xa * Xb + Ya * Yb + Za * Zb
          ????????????????????????????
                  ?A?*?B?



Example:

A (1,2,3)         ?A? = ?( 1? + 2? + 3?) = ?(14) = 3.7417

B (4,5,6)         ?b? = ?( 4? + 5? + 6?) = ?(77) = 8.7750


 Cos(?) =  1 * 4 + 2 * 5 + 3 * 6   =  4 + 10 + 18    =    32    =  0.9746
           ?????????????????????     ???????????????    ?????
             (3.7417)*(8.7750)           32.8334       32.8334
 

 ArcCos (.9746) = 12.9? 


    So, your wondering how this revolutionizes you code, huh?  Well, remember
our other friend, the Normal vector?  You use Normal vectors that define
the directions of everything in our 3D world.  Let's say that vector A was
the Normal vector from my plane, and B is a vector that shows the direction
that the light in my scene is pointing.  If I do the Dot Product of them,
you will get the angle between them, if that angle is >= 90? and <= 270?
then no light falls on the visible surface and it doesn't need to be 
displayed.


Also notice, the way the values of the Cosine orient themselves

            

           90?                  Cos 000? =  1
                                Cos 090? =  0
            ?                   Cos 180? = -1 
  Negative  ?  Positive         Cos 270? =  0
            ?
            ?
180? ???????????????  0?        An angle between a light and a plane that
            ?                   is less than 90? or greater than 270? will
            ?                   be visible, so you can check if the Cos(?)
  Negative  ?  Positive         is greater than 0 to see if it is visible.
            ?
            ?

           270?

                
                How Do You Implement The Code?  Easy As ?.

Examples in ASM structures

We will define our points like this

    STRUC XYZs
        Xpos    dd  ?
        Ypos    dd  ?
        Zpos    dd  ?
        Dist    dd  ?       
    ENDS  XYZs              ;size is 16 bytes


The X,Y,Zpos define a point in 3D space, Dist is the distance from the origin

Dist = ?( X? + Y? + Z? )

Precalculate these values and have them handy in your data area


Our planes should look something like this

    STRUC PlaneSt
        NumPts      db      ?               ;3 or 4
        NormIndex   dw      ?
        PtsIndex    dw      ?
                    dw      ?
                    dw      ?
                    dw      ?
    ENDS  PlaneSt

The number of points that in the plane depends on the number your fill
routines can handle you must have at least 3 and more than 6 is not suggested


Then we set up our data like this

MaxPoints   =           100
MaxPlanes   =           100

PointList   XYZs        MaxPoints DUP()
PlaneList   PlaneSt     MaxPlanes DUP()
NormalList  XYZs        <0,0,0, 10000h> , MaxPlanes DUP()

    Non-ASM User Note:   
            
            I set up points in a structure that had an X,Y,Z and Distance
        value.  I set up a plane structure that had the number of points
        the index number of the normal vector for that plane and the index
        numbers for the points in the plane.

            The next lines set up arrays of these points in PointList, and
        the number of points was defined as MaxPoints.  An array of planes
        was created as PlaneList with MaxPlanes as the total number of 
        plane structures in the array.  NormalList is an array of the vectors
        that are normal to the planes, one is set up initally (I'll explain 
        that next) and then one for each possible plane is allocated.


You'll notice that I defined the first Normal and then created space for 
the rest of the possible normals.  I'll call this first normal, the 
Zero Normal.  It will have special properties for planes that don't shade 
and are never hidden.



    Well, before I start telling all the tricks to the writting code, let me
make sure a couple of points are clear.

-       In the 3DROTATE.DOC I said that you could set your view point on the 
    Z-Axis and then figure out if planes were visible by the post-rotation
    Normal vectors, if their Z was > 0 then display, if not, don't
        That is an easy way to set up the data, and I didn't feel like going
    into the Dot Product at the time, so I generalized.  So, what if you
    don't view your plane from the Z-Axis, the answer is you use the...
    
    Dot Product!  
    
    that's right.  The angle will be used now to figure wheither or not to
    display the plane.

-       I have been mentioning lights and view points as vectors that I can
    use with the Normal vector from my plane.  To work correctly, these 
    vectors for the lights and view should point in the direction that you
    are looking or the direction that the light is pointing, *NOT* a vector 
    drawn from the origin to the viewer position or light position.

-       True Normal vectors only state a direction, and should therefore have
    a unit distance of 1.  This will have the advantage of simplifying the
    math involved to figure you values.  Also, for God's sake, pre-compute
    your normal, don't do this everytime.  Just rotate them when you do your
    points and that will update their direction.

        If the Normal's have a length of 1 then ?A?*?B? = 1 * 1 = 1

    So:
        Cos(?) = Xa * Xb + Ya * Yb + Za * Zb
                 ????????????????????????????
                          ?A?*?B?

    Is Reduced To:    
        
        Cos(?) = Xa * Xb + Ya * Yb + Za * Zb
     

    We eliminated a multiply and a divide!  Pat yourself on the back.

-       You ASM users might be wondering why I defined my Zero Normal as:
    <0,0,0,10000h>  How does 10000h = a length of 1 ?

    Well, this is a trick you can do in ASM, instead of using floating point
    values that will be slow on computers without math co-processors, we can
    use a double word to hold our value.  The high word holds the integer
    value, and the low word is our decimal.  You do all of your computations
    with the whole register, but only pull the high word when you go to 
    display the point.  So, with that under consideration, 10000h = 1.00000
    Not bad for integers.


-       How does the Zero Normal work?  Since the X,Y,and Z are all 0, the
    Cos(?) = 0, so if you always display when Cos(?) = 0, then that plane
    will always be seen.


            So, Beyond The Babble...  How To Set Up Your Code    


Define Data Points, Normals, and Planes
    Pre-Calculate as many values as possible

 Rotate Points and Normals

 Determin Visible Planes With Dot Product
    (Save this value if you want to shade)

    Sort Visible Planes Back to Front

         (Determin Shade From Dot Product)

         Clip Plane to fit scene

         Draw to the screen

 Change Angles

 Goto Rotation



        A quick way to figure out which color to shade your plane if you are
    using the double word values like I described before is to take the
    Dot Product result, it will lie between 10000h - 0h if you would like
    say 16 shades over the angles, then take that value and shr ,12 that will
    give you a value from 0h - 10h (0-16, or 17 colors)  if you make 10h into
    0fh, add that offset to a gradient in your palette, then you will have
    the color to fill your polygon with.

        Note also that the Cosine function is weighted toward the extremes.
    If you want a smooth palette change as the angles change, your palette
    should weight the gradient accordingly.


        A useful little relation for depth sorting is to be able to find the
    center of a triangle.

        E         The center C = (D + E + F)/3
        ^
       / \        Divide each cooridinate by (Xd + Xe + Xf)/3 = Xc
      / C \         and do the same for the Y's and Z's if you 
     /     \        choose to sort with this method.  Then rotate
   D?????????F      that point and use it to depth sort the planes


Phong and Goraud Shading

    Recently, someone asked me about the practiblity of real-time phong and
goraud shading.  The technique is common to ray-tracers and requires a great
deal of calculation when working with individual rays cast from each pixel,
but when only using this for each plane, it is possible.  This type of shading
involves taking into account the reduced luminousity of light as distance
increases.  For each light, you define a falloff value.  This value should be
the distance a which the light will be at full intensity.  Then at 2*FallOff 
you will have 1/2 intensity, 3*FallOff will yeild 1/3 and so on.  To implement
this type of shading, you will need to determin the distance from the light
to the center of the plane.  If distance < FallOff, then use the normal
intensity.  If it is greater, divide the FallOff value by the distance.  This
will give you a scalar value that you can multiple by the shading color that
the plane should have.  Use that offset and it will be darker since it is
further away from the light source.
    However, to determin the distance form the light to each plane, you must
use a Square Root function, these are inherently slow unless you don't care
about accuracy.  Also, it would be difficult to notice the use of this 
technique unless you have a relatively small FallOff value and your objects
move about in the low intesity boundries.




Well, that's all that I feel like doing tonight, and besides, Star Trek is on!
So, see VLA.NFO for information about contacting myself or any of the other 
members of VLA.

                            Happy Coding!



                   ??????????????????????????
                   ? Perspective Transforms ?
                   ??????????????????????????

        By Andre Yew (andrey@gluttony.ugcs.caltech.edu)



    This is how I learned perspective transforms --- it was
intuitive and understandable to me, so perhaps it'll be to
others as well.  It does require knowledge of matrix math
and homogeneous coordinates.  IMO, if you want to write a
serious renderer, you need to know both.

   First, let's look at what we're trying to do:
               S (screen)
               |    * P (y, z)
               |   /|
               |  / |
               | /  |
               |/   |
               * R  |
             / |    |
            /  |    |
           /   |    |
   E (eye)/    |    | W
---------*-----|----*-------------
         <- d -><-z->

   E is the eye, P is the point we're trying to project, and
R is its projected position on the screen S (this is the point
you want to draw on your monitor).  Z goes into the monitor (left-
handed coordinates), with X and Y being the width and height of the
screen.  So let's find where R is:

    R = (xs, ys)

    Using similar triangles (ERS and EPW)

    xs/d = x/(z + d)
    ys/d = y/(z + d)
    (Use similar triangles to determine this)

    So,

    xs = x*d/(z + d)
    ys = y*d/(z + d)

    Express this homogeneously:

    R = (xs, ys, zs, ws).

    Make xs = x*d
         ys = y*d
         zs = 0 (the screen is a flat plane)
         ws = z + d

    and express this as a vector transformed by a matrix:

    [x y z 1][ d 0 0 0 ]
             [ 0 d 0 0 ]    =  R
             [ 0 0 0 1 ]
             [ 0 0 0 d ]

    The matrix on the right side can be called a perspective transform.
But we aren't done yet.  See the zero in the 3rd column, 3rd row of
the matrix?  Make it a 1 so we retain the z value (perhaps for some
kind of Z-buffer).  Also, this isn't exactly what we want since we'd
also like to have the eye at the origin and we'd like to specify some
kind of field-of-view.  So, let's translate the matrix (we'll call
it M) by -d to move the eye to the origin:

    [ 1 0 0  0 ][ d 0 0 0 ]
    [ 0 1 0  0 ][ 0 d 0 0 ]
    [ 0 0 1  0 ][ 0 0 1 1 ]  <--- Remember, we put a 1 in (3,3) to
    [ 0 0 -d 1 ][ 0 0 0 d ]       retain the z part of the vector.

    And we get:

    [ d 0 0  0 ]
    [ 0 d 0  0 ]
    [ 0 0 1  1 ]
    [ 0 0 -d 0 ]

    Now parametrize d by the angle PEW, which is half the field-of-view
(FOV/2).  So we now want to pick a d such that ys = 1 always and we get
a nice relationship:

    d = cot( FOV/2 )

    Or, to put it another way, using this formula, ys = 1 always.

    Replace all the d's in the last perspective matrix and multiply
through by sin's:

    [ cos 0   0    0   ]
    [ 0   cos 0    0   ]
    [ 0   0   sin  sin ]
    [ 0   0   -cos 0   ]

    With all the trig functions taking FOV/2 as their arguments.
Let's refine this a little further and add near and far Z-clipping
planes.  Look at the lower right 2x2 matrix:

   [ sin sin ]
   [-cos 0   ]

   and replace the first column by a and b:

   [ a sin ]
   [ b 0   ]
   [ b 0   ]

   Transform out near and far boundaries represented homogeneously
as (zn, 1), (zf, 1), respectively and we get:

   (zn*a + b, zn*sin) and (zf*a + b, zf*sin).

   We want the transformed boundaries to map to 0 and 1, respectively,
so divide out the homogeneous parts to get normal coordinates and equate:

    (zn*a + b)/(zn*sin) = 0 (near plane)
    (zf*a + b)/(zf*sin) = 1 (far plane)

   Now solve for a and b and we get:

   a = (zf*sin)/(zf - zn)
     = sin/(1 - zn/zf)
   b = -a*zn
   b = -a*zn

   At last we have the familiar looking perspective transform matrix:

   [ cos( FOV/2 ) 0                        0            0 ]
   [ 0            cos( FOV/2 )             0            0 ]
   [ 0            0 sin( FOV/2 )/(1 - zn/zf) sin( FOV/2 ) ]
   [ 0            0                    -a*zn            0 ]

   There are some pretty neat properties of the matrix.  Perhaps
the most interesting is how it transforms objects that go through
the camera plane, and how coupled with a clipper set up the right
way, it does everything correctly.  What's interesting about this
is how it warps space into something called Moebius space, which
is kind of like a fortune-cookie except the folds pass through
each other to connect the lower folds --- you really have to see
it to understand it.  Try feeding it some vectors that go off to
infinity in various directions (ws = 0) and see where they come
out.

               ??????????????????????????????????????????
               ? Bresenham's Line and Circle Algorithms ?
               ??????????????????????????????????????????

                 Written for the PC-GPE by Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

???????????????????????????????????????????????????????????????????????????
? Introduction ?
????????????????

Bresenham is a pretty smart cookie (note the use of the word "is", last I
heard he was still working for IBM). This file contains the algorithms he
developped for drawing lines and circles on a pixelated display system
such as the VGA.

???????????????????????????????????????????????????????????????????????????
? Line Algorithm ?
??????????????????

The basic algorithm works for lines which look like this:


      o-------                         ?
     p1       --------                 ? deltay
                      -------      p2  ?
                             -------o  ?

      ??????????????????????????????
                  deltax

where p1 = (x1,y1),
      p2 = (x2, y2),
      x and y are both increasing from p1 to p2,
      deltax = x2 - x1,
      deltay = y2 - y1 and
      deltax >= deltay.

All other types of lines can be derived from this type. I'll get to this
bit later.

First you need to perform the following intialisation:

x = x1
y = y1
d = (2 * deltay) - deltax

x is the current x location, you will add 1 to this variable after every
pixel you draw until all pixels have been drawn.
y is the current y location. The decision variable is used to determine
when to add 1 to this value. d is the decision variable which will be used
to keep a track of what to do.

Now you loop across the screen from x1 to x2 and for each loop perform the
following operations for each pixel :

PutPixel(x, y);  { Draw a pixel at the current point }
if d < 0 then
    d := d + (2 * deltay)
else
  begin
    d := d + 2 * (deltay - deltax);
    y := y + 1;
  end;
x := x + 1;

It's that simple!

???????????????????????????????????????????????????????????????????????????
? Speeding Up The Line Algorithm ?
??????????????????????????????????

There are several useful techniques for speeding up Bresenhams line
algorithm.

For starters, notice that all multiplications are by 2. This can be
performed with a simple shift left instruction (Shl in Pascal, << in C).

Next notice that the values you add to the decision variable do not change
throughout the loop, so they can be precalculated beforehand.

One property of lines is that they are symetrical about their mid-points,
and we can use this property to speed up the algorithm. Store two x and y
values, (xa, ya) and (xb, yb). Have each pair start on either end of the
line. For each pass through the loop you draw the pixel at both points, add
1 to xa and subtract one from xb. When d >= 0 add 1 to ya and subtract one
from yb. You then only need to loop until xa = xb.

It's also obvious that if the decision variable becomes the same value
it was when it was initialised, then the rest of the line is just
copies of the line you have already drawn up to that point. You might be
able to speed the algorithm up by keeping an array of how y has been
modified and then use this array if the line starts repeating itself. If
you are using the Intel registers to store all values then you probably
wouldn't get much of a speed increase (in fact it could slow it down), but
it would probably be useful for thing like linear texture mapping (discussed
below). I've never actually tried implementing this technique, and I would
like to hear the results if anyone does.

Above all remember that these optimisations will only significantly speed
up the line drawing algorithm if the whole thing is done in assembly. A
profile of the example program at the end of this file showed that 40% of
CPU time was spent in the slow PutPixel routine I was using, the loop
mechanics and testing the sign of the decision variable.

???????????????????????????????????????????????????????????????????????????
? Other Uses for the Line Algorithm ?
?????????????????????????????????????

A line can be represented by the equation y = mx + c, where
m = deltay / deltax. Note that this is a version of the standard linear
equation ax + bx + c = 0. There are many algorithms which use this equation.

One good use for the bresenham line algorithm is for quickly drawing filled
concave polygons (eg triangles). You can set up an array of minimum and
maximum x values for every horizontal line on the screen. You then use
bresenham's algorithm to loop along each of the polygon's sides, find where
it's x value is on every line and adjust the min and max values accordingly.
When you've done it for every line you simply loop down the screen drawing
horizontal lines between the min and max values for each line.

Another area is in linear texture mapping (see the PC-GPE article on texture
mapping). This method involves taking a string of bitmap pixels and
stretching them out (or squashing them in) to a line of pixels on the screen.
Typically you would draw a vertical line down the screen and use Bresenhams
to calculate which bitmap pixel should be drawn at each screen pixel.

???????????????????????????????????????????????????????????????????????????
? Circle Algorithm ?
????????????????????

Circles have the property of being highly symetrical, which is handy
when it comes to drawing them on a display screen.

      |y          (This diagram is supposed to be a circle, try viewing
      |           it in 50 line mode).
  \ ..... /
   .  |  .        We know that there are 360 degrees in a circle. First we
  . \ | / .       see that a circle is symetrical about the x axis, so
  .  \|/  .       only the first 180 degrees need to be calculated. Next
--.---+---.--     we see that it's also symetrical about the y axis, so now
  .  /|\  . x     we only need to calculate the first 90 degrees. Finally
  . / | \ .       we see that the circle is also symetrical about the 45
   .  |  .        degree diagonal axis, so we only need to calculate the
  / ..... \       first 45 degrees.
      |
      |

Bresenhams circle algorithm calculates the locations of the pixels in the
first 45 degrees. It assumes that the circle is centered on the origin. So
for every pixel (x,y) it calculates we draw a pixel in each of the 8 octants
of the circle :

PutPixel(CenterX + X, Center Y + Y)
PutPixel(CenterX + X, Center Y - Y)
PutPixel(CenterX - X, Center Y + Y)
PutPixel(CenterX - X, Center Y - Y)
PutPixel(CenterX + Y, Center Y + X)
PutPixel(CenterX + Y, Center Y - X)
PutPixel(CenterX - Y, Center Y + X)
PutPixel(CenterX - Y, Center Y - X)

So let's get into the actual algorithm. Given a radius for the circle
we perform this initialisation:

d := 3 - (2 * RADIUS)
x := 0
y := RADIUS

Now for each pixel we do the following operations:

Draw the 8 circle pixels
if d < 0 then
    d := d + (4 * x) + 6
else
  begin
    d := d + 4 * (x - y) + 10
    y := y - 1;
  end;

And we keep doing this until x = y. Note that the values added to the
decision variable in this algorithm (x and y) are constantly changing, so
we cannot precalculate them. The muliplications however are by 4, and we
can accomplish this by shifting left twice.


????????????????????????????????????????????????????????????????????????????
? A Pascal General Line Procedure ?
???????????????????????????????????

The basic bresenham line algorithm can be modified to handle all types of
lines. In this section assume that deltax = abs(x2 - x1) and
deltay = abs(y2 - y1).

First let's take lines where deltax >= deltay. Now if x1 > x2 then you will
need to subtract 1 from x for every pass through the loop. Similarly if y1 >
y2 then you will be also need to subtract 1 from y for every pass through the
loop where d < 0.

Lines where deltax < deltay can be handled the same way, you just swap all
the deltax's and deltay's around.

The fastest method of handling all cases is to write a custom routine for
each of the 8 line types:

1) x1 <= x2, y1 <= y2, deltax >= deltay
2) x1 <= x2, y1 <= y2, deltax <  deltay
3) x1 <= x2, y1 >  y2, deltax >= deltay
4) x1 <= x2, y1 >  y2, deltax <  deltay
5) x1 >  x2, y1 <= y2, deltax >= deltay
6) x1 >  x2, y1 <= y2, deltax <  deltay
7) x1 >  x2, y1 >  y2, deltax >= deltay
8) x1 >  x2, y1 >  y2, deltax <  deltay

This will give you the fastest results, but will also make your code 8
times larger! Alternatively you can declare a few extra variables and
use a common inner loop for all lines:

numpixels = number of pixels to draw
          = deltax if deltax >= deltay or
          = deltay if deltax < deltay
dinc1 = the amount to add to d when d < 0
dinc2 = the amount to add to d when d >= 0
xinc1 = the amount to add to x when d < 0
xinc2 = the amount to add to x when d >= 0
yinc1 = the amount to add to y when d < 0
yinc2 = the amount to add to y when d >= 0

The following is a simple example program which uses this technique:


????????????????????????????????????????????????????????????????????????

{

BRESLINE.PAS - A general line drawing procedure.
               By Mark Feldman

This is a very simple implementation of bresenhams' line algorithm with
no optimisations. It can draw about 6000 random lines a second in mode 13h
on my 486SX33 with sloooooow Paradise Extended VGA.

}

procedure Line(x1, y1, x2, y2 : integer; color : byte);
var i, deltax, deltay, numpixels,
    d, dinc1, dinc2,
    x, xinc1, xinc2,
    y, yinc1, yinc2 : integer;
begin

  { Calculate deltax and deltay for initialisation }
  deltax := abs(x2 - x1);
  deltay := abs(y2 - y1);

  { Initialize all vars based on which is the independent variable }
  if deltax >= deltay then
    begin

      { x is independent variable }
      numpixels := deltax + 1;
      d := (2 * deltay) - deltax;
      dinc1 := deltay Shl 1;
      dinc2 := (deltay - deltax) shl 1;
      xinc1 := 1;
      xinc2 := 1;
      yinc1 := 0;
      yinc2 := 1;
    end
  else
    begin

      { y is independent variable }
      numpixels := deltay + 1;
      d := (2 * deltax) - deltay;
      dinc1 := deltax Shl 1;
      dinc2 := (deltax - deltay) shl 1;
      xinc1 := 0;
      xinc2 := 1;
      yinc1 := 1;
      yinc2 := 1;
    end;

  { Make sure x and y move in the right directions }
  if x1 > x2 then
    begin
      xinc1 := - xinc1;
      xinc2 := - xinc2;
    end;
  if y1 > y2 then
    begin
      yinc1 := - yinc1;
      yinc2 := - yinc2;
    end;

  { Start drawing at <x1, y1> }
  x := x1;
  y := y1;

  { Draw the pixels }
  for i := 1 to numpixels do
    begin
      PutPixel(x, y, color);
      if d < 0 then
        begin
          d := d + dinc1;
          x := x + xinc1;
          y := y + yinc1;
        end
      else
        begin
          d := d + dinc2;
          x := x + xinc2;
          y := y + yinc2;
        end;
    end;
end;

????????????????????????????????????????????????????????????????????????






Note that if you are writing a line routine for mode 13h (for example) you
can speed it up by converting the inner loop to assembly and including
mode 13h specific code. This portion of the above routine works the same but
the <x, y> values are stored in a single variable (screen) which holds the
memory address of the current pixel, screeninc1 and screeninc2 are the
update values for screen.

????????????????????????????????????????????????????????????????????????

var screen : word;
    screeninc1, screeninc2 : integer;
     .
     .
     .
  { Start drawing at <x1, y1> }
  screen := word(y1) * 320 + x1;
  screeninc1 := yinc1 * 320 + xinc1;
  screeninc2 := yinc2 * 320 + xinc2;

  { Draw the pixels }
  asm

    { Use as many registers as are available }
    push $A000
    pop es
    mov di, screen
    mov dx, d
    mov al, color
    mov cx, numpixels
    mov bx, dinc1

    @bres1:

    { Draw the current pixel and compare the decision variable to 0 }
    mov es:[di], al
    cmp dx, 0
    jnl @bres2

    { D < 0 }
    add dx, bx { bx = dinc1 }
    add di, screeninc1
    jmp @bres3

    @bres2:

    { D >= 0 }
    add dx, dinc2
    add di, screeninc2

    @bres3:

    loop @bres1
  end;

????????????????????????????????????????????????????????????????????????



           ?????????????????????????????????????????????????
           ? A General Conics Sections Scan Line Algorithm ?
           ?????????????????????????????????????????????????


The following code is the complete algorithm for the general conic
drawer as mentioned in Foley/VanDam. It is included here with the
permission of Andrew W. Fitzgibbon, who derived the remaining code
sections not included in the book.


//
// CONIC  2D Bresenham-like conic drawer.
//       CONIC(Sx,Sy, Ex,Ey, A,B,C,D,E,F) draws the conic specified
//       by A x^2 + B x y + C y^2 + D x + E y + F = 0, between the
//       start point (Sx, Sy) and endpoint (Ex,Ey).

// Author: Andrew W. Fitzgibbon (andrewfg@ed.ac.uk),
//         Machine Vision Unit,
//         Dept. of Artificial Intelligence,
//         Edinburgh University,
//         
// Date: 31-Mar-94

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

static int DIAGx[] = {999, 1,  1, -1, -1, -1, -1,  1,  1};
static int DIAGy[] = {999, 1,  1,  1,  1, -1, -1, -1, -1};
static int SIDEx[] = {999, 1,  0,  0, -1, -1,  0,  0,  1};
static int SIDEy[] = {999, 0,  1,  1,  0,  0, -1, -1,  0};
static int BSIGNS[] = {99, 1,  1, -1, -1,  1,  1, -1, -1};

int   debugging = 1;

struct ConicPlotter {
  virtual void plot(int x, int y);
};

struct DebugPlotter : public ConicPlotter {
  int xs;
  int ys;
  int xe;
  int ye;
  int A;
  int B;
  int C;
  int D;
  int E;
  int F;      

  int octant;
  int d;

  void plot(int x, int y);
};

void DebugPlotter::plot(int x, int y)
{
  printf("%3d %3d\n",x,y);

  if (debugging) {
    // Translate start point to origin...
    float tF = A*xs*xs + B*xs*ys + C*ys*ys + D*xs + E*ys + F;
    float tD = D + 2 * A * xs + B * ys;
    float tE = E + B * xs + 2 * C * ys;
  
    float tx = x - xs + ((float)DIAGx[octant] + SIDEx[octant])/2;
    float ty = y - ys + ((float)DIAGy[octant] + SIDEy[octant])/2;
    // Calculate F
    
    float td = 4*(A*tx*tx + B*tx*ty + C*ty*ty + tD*tx + tE*ty + tF);
    
    fprintf(stderr,"O%d ", octant);
    if (d<0)
      fprintf(stderr," Inside "); 
    else 
      fprintf(stderr,"Outside "); 
    float err = td - d;
    fprintf(stderr,"Real(%5.1f,%5.1f) = %8.2f Recurred = %8.2f err = %g\n", 
            tx, ty, td/4, d/4.0f, err);
    if (fabs(err) > 1e-14)
      abort();
  }
  
}

inline int odd(int n)
{
  return n&1;
}

inline int abs(int a)
{
  if (a > 0)
    return a;
  else
    return -a;
}
    
int getoctant(int gx, int gy)
{
  // Use gradient to identify octant.
  int upper = abs(gx)>abs(gy);
  if (gx>=0)                            // Right-pointing
    if (gy>=0)                          //    Up
      return 4 - upper;
    else                                //    Down
      return 1 + upper;
  else                                  // Left
    if (gy>0)                           //    Up
      return 5 + upper;
    else                                //    Down
      return 8 - upper;
}

int conic(int xs, int ys, int xe, int ye,
          int A, int B, int C, int D, int E, int F,
          ConicPlotter * plotterdata)
{
  A *= 4;
  B *= 4;
  C *= 4;
  D *= 4;
  E *= 4;
  F *= 4;
  
  // Translate start point to origin...
  F = A*xs*xs + B*xs*ys + C*ys*ys + D*xs + E*ys + F;
  D = D + 2 * A * xs + B * ys;
  E = E + B * xs + 2 * C * ys;
  
  // Work out starting octant
  int octant = getoctant(D,E);
  
  int dxS = SIDEx[octant]; 
  int dyS = SIDEy[octant]; 
  int dxD = DIAGx[octant];
  int dyD = DIAGy[octant];

  int bsign = BSIGNS[octant];
  int d,u,v;
  switch (octant) {
  case 1:
    d = A + B/2 + C/4 + D + E/2 + F;
    u = A + B/2 + D;
    v = u + E;
    break;
  case 2:
    d = A/4 + B/2 + C + D/2 + E + F;
    u = B/2 + C + E;
    v = u + D;
    break;
  case 3:
    d = A/4 - B/2 + C - D/2 + E + F;
    u = -B/2 + C + E;
    v = u - D;
    break;
  case 4:
    d = A - B/2 + C/4 - D + E/2 + F;
    u = A - B/2 - D;
    v = u + E;
    break;
  case 5:
    d = A + B/2 + C/4 - D - E/2 + F;
    u = A + B/2 - D;
    v = u - E;
    break;
  case 6:
    d = A/4 + B/2 + C - D/2 - E + F;
    u = B/2 + C - E;
    v = u - D;
    break;
  case 7:
    d = A/4 - B/2 + C + D/2 - E + F;
    u =  -B/2 + C - E;
    v = u + D;
    break;
  case 8:
    d = A - B/2 + C/4 + D - E/2 + F;
    u = A - B/2 + D;
    v = u - E;
    break;
  default:
    fprintf(stderr,"FUNNY OCTANT\n");
    abort();
  }
  
  int k1sign = dyS*dyD;
  int k1 = 2 * (A + k1sign * (C - A));
  int Bsign = dxD*dyD;
  int k2 = k1 + Bsign * B;
  int k3 = 2 * (A + C + Bsign * B);

  // Work out gradient at endpoint
  int gxe = xe - xs;
  int gye = ye - ys;
  int gx = 2*A*gxe +   B*gye + D;
  int gy =   B*gxe + 2*C*gye + E;
  
  int octantcount = getoctant(gx,gy) - octant;
  if (octantcount <= 0)
    octantcount = octantcount + 8;
  fprintf(stderr,"octantcount = %d\n", octantcount);
  
  int x = xs;
  int y = ys;
  
  while (octantcount > 0) {
    if (debugging)
      fprintf(stderr,"-- %d -------------------------\n", octant); 
    
    if (odd(octant)) {
      while (2*v <= k2) {
        // Plot this point
        ((DebugPlotter*)plotterdata)->octant = octant;
        ((DebugPlotter*)plotterdata)->d = d;
        plotterdata->plot(x,y);
        
        // Are we inside or outside?
        if (d < 0) {                    // Inside
          x = x + dxS;
          y = y + dyS;
          u = u + k1;
          v = v + k2;
          d = d + u;
        }
        else {                          // outside
          x = x + dxD;
          y = y + dyD;
          u = u + k2;
          v = v + k3;
          d = d + v;
        }
      }
    
      d = d - u + v/2 - k2/2 + 3*k3/8; 
      // error (^) in Foley and van Dam p 959, "2nd ed, revised 5th printing"
      u = -u + v - k2/2 + k3/2;
      v = v - k2 + k3/2;
      k1 = k1 - 2*k2 + k3;
      k2 = k3 - k2;
      int tmp = dxS; dxS = -dyS; dyS = tmp;
    }
    else {                              // Octant is even
      while (2*u < k2) {
        // Plot this point
        ((DebugPlotter*)plotterdata)->octant = octant;
        ((DebugPlotter*)plotterdata)->d = d;
        plotterdata->plot(x,y);
        
        // Are we inside or outside?
        if (d > 0) {                    // Outside
          x = x + dxS;
          y = y + dyS;
          u = u + k1;
          v = v + k2;
          d = d + u;
        }
        else {                          // Inside
          x = x + dxD;
          y = y + dyD;
          u = u + k2;
          v = v + k3;
          d = d + v;
        }
      }
      int tmpdk = k1 - k2;
      d = d + u - v + tmpdk;
      v = 2*u - v + tmpdk;
      u = u + tmpdk;
      k3 = k3 + 4*tmpdk;
      k2 = k1 + tmpdk;
      
      int tmp = dxD; dxD = -dyD; dyD = tmp;
    }
    
    octant = (octant&7)+1;
    octantcount--;
  }

  // Draw final octant until we reach the endpoint
  if (debugging)
    fprintf(stderr,"-- %d (final) -----------------\n", octant); 
    
  if (odd(octant)) {
    while (2*v <= k2 && x != xe && y != ye) {
      // Plot this point
      ((DebugPlotter*)plotterdata)->octant = octant;
      ((DebugPlotter*)plotterdata)->d = d;
      plotterdata->plot(x,y);
      
      // Are we inside or outside?
      if (d < 0) {                      // Inside
        x = x + dxS;
        y = y + dyS;
        u = u + k1;
        v = v + k2;
        d = d + u;
      }
      else {                            // outside
        x = x + dxD;
        y = y + dyD;
        u = u + k2;
        v = v + k3;
        d = d + v;
      }
    }
  }
  else {                        // Octant is even
    while ((2*u < k2) && (x != xe) && (y != ye)) {
      // Plot this point
      ((DebugPlotter*)plotterdata)->octant = octant;
      ((DebugPlotter*)plotterdata)->d = d;
      plotterdata->plot(x,y);
      
      // Are we inside or outside?
      if (d > 0) {                      // Outside
        x = x + dxS;
        y = y + dyS;
        u = u + k1;
        v = v + k2;
        d = d + u;
      }
      else {                            // Inside
        x = x + dxD;
        y = y + dyD;
        u = u + k2;
        v = v + k3;
        d = d + v;
      }
    }
  }



  return 1;
}

main(int argc, char ** argv)
{
  DebugPlotter db;
  db.xs = -7;
  db.ys = -19;
  db.xe = -8;
  db.ye = -8;
  db.A = 1424;
  db.B = -964;
  db.C = 276;
  db.D = 0;
  db.E = 0;
  db.F = -40000;
  conic(db.xs,db.ys,db.xe,db.ye,db.A,db.B,db.C,db.D,db.E,db.F, &db);
}
                 ?????????????????????????????????????
                 ? A Simple Explanation of BSP Trees ?
                 ?????????????????????????????????????

            Written for the PC-GPE by Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? BSP Trees ?
?????????????

Binary Space Partition trees are handy for drawing 3D scenes where the
positions of objects are fixed and the user's viewing coordinate changes
(flight simulators being a classic example).

BSP's are an extention of the "Painter's Algorithm". The painter's algorithm
works by drawing all the polygons (or texture maps) in a scene in back-to-
front order, so that polygon's in the background are drawn first, and
polygons in the foreground are drawn over them. The "classic" painter's
algorithm does have a few problems however:
1) polygon's will not be drawn correctly if they pass through any other
polygon
2) it's difficult and computationally expensive calculating the order that
the polygons should be drawn in for each frame
3) the algorithm cannot handle cases of cyclic overlap such as the
following :
                           ___       ___
                          |   |     |   |
                        __|   |_____|___|___
                       |  |   |             |
                       |__|   |_____________|
                          |   |     |   |
                        __|___|_____|   |___
                       |            |   |   |
                       |____________|   |___|
                          |   |     |   |
                          |___|     |___|

In this case it doesn't matter which order you draw the polygon's it still
won't look right!

BSP's help solve all these problems.

Ok, so let's get down to business. BSP's work by building an ordered tree of
all the objects in a scene. Let's imagine we live in a 2D world and we have
a scene like this:

             ??????????????????????????????????????
             ?                                    ?
             ?                                    ?
             ?              ????????????????????  ?
             ?                    line 1          ?
             ?           \                        ?
             ?             \                      ?
             ?               \ line 2             ?
             ?                 \                  ?
             ?                   \                ?
             ?   ????????          \              ?
             ?   line 3              \            ?
             ?                                    ?
             ??????????????????????????????????????

                              ^
                              viewpoint (assume the viewpoint is the
                                         origin for this example)


Now if we were to draw this scene using the painters algorithm we would
draw line 1 first, then line 2, finally line 3. Using BSP's we can figure
out the order beforehand and create a tree. First we note that any
arbitrary point <x,y> can be on either of it's 2 sides or on the line (which
can be regarded as being on either of the sides). When we build our tree, we
take a line and put all the lines on one side of it to the left and all the
nodes on the other side on the right. So for the above example could wind up
with the following tree:

          1
         /
        2
       /
      3

Alternatively, we could also wind up with this tree:

        2
       / \
      3   1

Notice that line 2 is the head node, line 3 is on the same side of line 2
as the origin is and line 1 is on the opposite side.

Now, I hear you say "but hang on a tic, what if line 3 is the head node? What
side of it is line 2 on?". Yes boys and girls, there had to be a catch
somewhere and this is it. What you have to do here is split line 2 into

line 3, so you get a tree like this:

       3
      / \
    2a   2b
          \
           1

The lines 2a and 2b are portions of the original line 2. If you draw *BOTH*
of them on the screen it will look as though you've drawn the entire original
line.

You don't have to worry about balancing a BSP tree, since you have to
traverse every node in it every time you draw the scene anyway. The trick
is to figure out how to organise the tree so that you get the *least* number
of polygon splits. I tackled this by looking at each polygon yet to be
inserted into the tree, calculating how many splits it will cause if it
is inserted next and selecting the one which will cause the fewest. This
is a very slow way of going about things, O(N^2) I think, but for most games
you only need to sort the tree once when you are developping the game and not
during the game itself.

Extending these concepts to 3D is pretty straight-forward. Let's say that
polygon 1 is at the top of the BSP tree and we want to insert polygon 2. If
all the points in polygon 2 fall on one side or the other of polygon 1 then
you insert it into polygon 2's left or right node. If some points fall on
one side and the rest fall on the other, then you have to figure out the line
of intersection formed by the planes that each polygon lies in and split
polygon 2 along this line. Each of these 2 new polygons will then fall on
either side of polygon 1.


To draw the objects in a BSP tree you start at the top node and figure out
which side of the object your view coordinate is on. You then traverse the
node for the *other* side, draw the current object, then traverse the node
for the side the view coordinate is on.


                            ???????????????????
                            ? Texture Mapping ?
                            ???????????????????

                 Written for the PC-GPE by Sean Barrett.

               ?????????????????????????????????????????????
               ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
               ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
               ?????????????????????????????????????????????


-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=-
TEXTURE
TEXUE  almost everything you need to know to texture map with the PC
TXR
X
-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=-

Copyright 1994 Sean Barrett.
Not for reproduction (electronic
or hardcopy) except for personal use.

Contents:

  0. warnings
  1. terminology and equations
  2. the basics
      Perfect texture mapping
      DOOM essentials
      Wraparound textures
      Non-rectangular polygons
  3. the hazards
      going off the texture map
      divide by 0
      it'll never be fast enough
  4. the complexities
      handling arbitrarily-angled polygons
      lighting
      slow 16-bit VGA cards
      mipmapping

 -=-=-=-=-=-
| Note:  I am not providing any references as I simply
|        derived the math myself and worked out the various
|        techniques for myself (the 32-bit ADC trick was
|        pointed out to me in another context by TJC,
|        author of the Mars demo) over the last two years
|        (since Wolfenstein 3D and Underworld came out).
 -=-=-=-=-=-

TEXTURE
TEXUE
TXR   0. Warnings
X

  I assume a RIGHT-handed 3D coordinate system, with X positive
to the right, Y positive disappearing into the screen/distance,
and Z positive up.  To adjust this to the typical left-handed
3D space, simply swap all the 3D Ys & Zs.

  I assume the screen space is positive-X to the right,
positive-Y goes down.  Adjust the signs as appropriate for
your system.

  I will present code and pseudo-code in C.  I also include
some relatively tight inner loops written in assembly, but I'm
omitting the details of the loop setup.  The inner loops, while
usually from real, working code, should generally be taken
as examples showing how fast it ought to be possible to run
a given task, not as necessarily perfect examples.  I often
use 32-bit instructions (sorry 286 programmers) because they
can double the performance.  However, I write in real mode,
because 16-bit addressing is often convenient for texture maps,
and it's straightforward to use segment registers as pointers
to texture maps.  The translation to protected mode should not
prove problematic, but again, these should more be taken as
examples rather than simply being used directly.  I optimize
for the 486, but I skip some obvious optimizations.  For example,
I write "loop", because it's simpler and more clear.
Production code for the 486 should explicitly decrement and
then branch.  Similarly, I write "stosb", etc. etc.


TEXTURE
TEXUE
TXR   1. Terminology and Equations
X


  You really probably don't want to read this section first,
but rather refer back to it whenever you feel the need.  So
skip up to section 2 and refer back as appropriate.  I could've
made this an appendix, but it seems too important to put last.

TEX
TE   Terms
T
    texture:  A texture is a pixelmap of colors which is mapped
              onto a polygon using "texture mapping".  The size
              of the polygon has nothing to do with the size (the
              number of pixels) in the texture map.

    run:  A run is a row or column of pixels.  Normal texture
          mapping routines process one run in an "inner loop".

    arbitrarily-angled polygon:  a polygon which isn't a floor,
          wall, or ceiling; technically, a polygon which isn't
          parallel to the X or Z axes (or X or Y axes in a
          Z-is-depth coordinate system).

    texture space:
    polygon space:
    polygon coordinate space:
          Since a texture is flat, or two-dimensional, the relation
          of the texture to the 3D world can be described with a
          special coordinate space known by one of these names.
          Because it is only 2D, the space can be characterized with
          the location of the texture space in 2D, and two 3D vectors
          which represent the axes of the coordinate space.  Sometimes
          called "uv" space, because the name of the coordinates are
          usually u & v.

TEX
TE   Notation
T

    Vectors appear in all caps.
    Components of vectors are P = < Px, Py, Pz >.

    Certain variables have consistent usage:

      x,y,z are coordinates in three-space
      i,j are screen coordinates
      u,v are coordinates in texture space
      a,b,c are "magic coordinates" such that
         u = a/c, v = b/c

TEX
TE   Equations
T

    Don't let this scare you off!  Go read section 2, and
    come back to this when you're ready.

    Let P,M, and N be vectors defining the texture space: P is the
    origin, and M and N are the vectors for the u&v axes.

    Assume these vectors are in _view space_, where view space is
    defined as being the space in which the transformation from
    3D to 2D is:

      (x,y,z) -> (i,j)
        i = x / y
        j = z / y

    In other words, you have to adjust P, M, and N to be relative
    to the view, and if you have multiplications in your perspective
    computation, you have to multiply the appropriate components of
    P, M, and N to compute them.  Note that since typically in 3D
    up is positive, and in 2D down is positive, there may be a
    multiplication by -1 that needs to go into Py, My, Ny.  Note that
    this also assumes that (0,0) in screen space is at the center
    of the screen.  Since it's generally not, simply translate
    your screen coordinates (i,j) as if they were before applying
    the texture mapping math (or if you're funky you can modify
    your viewspace to pre-skew them).

      For example, if your transforms are:
         i = Hscale * x / y + Hcenter
         j = -Vscale * z / y + Vcenter

      Then you should simply multiply Px, Mx, and Nx by Hscale,
      and multiply Py, My, and Mz by -Vscale.  Then just remember
      to subtract Hcenter and Vcenter from the i,j values right
      before plugging them into the texture mapping equations.

    We begin by computing 9 numbers which are constant
    for texture mapping the entire polygon (O stands for
    Origin, H for Horizontal, and V for vertical; why I use
    these names should become clear eventually):

         Oa = Nx*Pz - Nz*Px
         Ha = Nz*Py - Ny*Pz
         Va = Ny*Px - Nx*Py

         Ob = Mx*Pz - Mz*Px
         Hb = Mz*Py - My*Pz
         Vb = My*Px - Mx*Py

         Oc = Mz*Nx - Mx*Nz
         Hc = My*Nz - Mz*Ny
         Vc = Mx*Ny - My*Nx

    Ok.  Then, for a given screen location (i,j), the formula
    for the texture space (u,v) coordinates corresponding to
    it is:

         a = Oa + i*Ha + j*Va
         b = Ob + i*Hb + j*Vb
         c = Oc + i*Hc + j*Hc

         u = a/c
         v = b/c


TEXTURE
TEXUE
TXR   2.  The Basics
X

    So you've got your polygon 3D engine running, and
you'd like to start adding a bit of texture to your
flat- or Gouraud-shaded polygons.  Well, it will make
it look a lot cooler.  But let's point out the
disadvantages of texture mapping right away:

      Slower
      Sometimes hard to see polygon edges

    Each of these has certain ramifications on the
overall approach you want to take with your code,
which we'll come back to later, in sections 3 and 4.

    Practical advice: Don't try to get your riproaringly
fast texture mapper running first.  Get a very simple,
slow, "perfect" texture mapper working first, as described
in the first subsection below.  This will allow you to make
sure you've gotten the equations right.  Realize that I
can't present the equations appropriate to every scenario,
since there are simply too many spaces people can work
in.  I've chosen to present the math from an extremely
simple coordinate space which keeps the texture mapping
relatively simple.  You'll have to work out the correct
transformations to make it work right, and a slow but
correct texture mapping routine may help you tweak the
code as necessary to achieve this.  Use very simple
polygons to start your testing; centered and facing the
viewer should be your very first one (if done correctly,
this will simply scale the texture).

TEX
TE   Perfect Texture Mapping
T

    To start with, we'll do slow but exact "perfect" texture
mapping of a square tile with a simple texture map mapped onto
it.  The polygon is defined in three-space using four points,
and the texture map is 256x256 pixels.  Note that this is all
talking about using floating point, so those of you working in
C or Pascal are fine.  Those in assembly should realize that
you have to do a bit of extra work to use fixed point, or you
can beat out the floating point by hand if you want.

    First we have to "map" the texture onto the polygon.
We have to define how the square texture map corresponds
to the square polygon.  This is relatively simple.  Let
one corner of the polygon be the origin (location <0,0>)
of the texture map.  Let each of the other corners
correspond to corners just off the edge of the texture
map (locations <256, 0>, <256, 256>, and <0, 256>).

    We'd like to use the equations in section 1, which
require vectors P, M, and N, where P is the origin,
and M & N are the axes for u&v (which are _roughly_
the coordinates in the texture map, but see below).
In other words, P, M and N tells us where the texture
lies relative to the polygon.  P is the coordinate in
three-space where the origin of the texture is.  M
tells us which way the "horizontal" dimension of the
texture lies in three-space, and N the "vertical".

    Suppose the polygon has four vertices V[0], V[1],
V[2], and V[3] (all four of these are vectors).  Then,
P is simply V[0].  M is a vector going from the origin
to the corner <256, 0>, so M is a vector from V[0] to
V[1], so M = V[1] - V[0].  N is a vector from the origin
to the corner <0, 256>, so N is V[3] - v[0].

    P = V[0]
    M = V[1] - V[0]    { note these are vector subtractions }
    N = V[3] - V[0]

    Again, remember that we need P, M, and N in the viewspace
discussed with the equation, so make sure you've transformed
the Vs appropriately, or you can compute P, M, and N in world
or object space and transform them into viewspace.

    Compute the 9 magic numbers (vectors O, H, and V) as described
in section 1.  Now, take your 3D polygon and process it as normal.
Scan convert it so that you have a collection of rows of pixels
to process.

    Now, iterate across each row.  For each pixel in the polygon
whose screen coordinates are <i,j>, apply the rest of the math
described in section 1; that is, compute a, b, and c, and from
them compute <u,v>.

    I said before that <u,v> are basically the texture map
coordinates.  What they are in truth are the coordinates in texture
map space.  Because of the way we defined texture map space,
we'll actually find that u and v both run from 0..1, not 0..256.
This is an advantage for descriptive purposes because u and v
are always 0 to 1, regardless of the size of the texture map.

    So, to convert u&v to pixelmap coordinates, multiply them
both by 256.  Now, use them as indices into the texture map,
output the value found there, and voila, you've texture mapped!

The loop should look something like this:

[ loop #1 ]
    for every j which is a row in the polygon
      screen = 0xA0000000 + 320*j
      for i = start_x to end_x for this row
        a = Oa + (Ha * i) + (Va * j)
        b = Ob + (Hb * i) + (Vb * j)
        c = Oc + (Hc * i) + (Vc * j)
        u = 256 * a / c
        v = 256 * b / c
        screen[i] = texture_map[v][u]
      endfor
    endfor

    Once you've got that working, congratulations!  You're
done dealing with the annoying messy part, which is getting
those 9 magic numbers computed right.  The rest of this is
just hard grunt work and trickery trying to make the code
faster.

    From here on in, I'm only going to look at the inner
loop, that is, a single run (row or column), and let the
rest of the runs be understood.

TEX
TE   Prepare to meet thy DOOM
T

    This subsection is concerned with vastly speeding
up texture mapping by restricting the mapper to walls,
floors and ceilings, or what is commonly called
DOOM-style texture mapping, although it of course predates
DOOM (e.g. Ultima Underworld [*], Legends of Valour).

[*  Yes, Underworld allowed you to tilt the view,
but it distorted badly.  Underworld essentially used
DOOM-style tmapping, and tried to just use that on
arbitrarily-angled polygons.  I can't even begin to
guess what Underworld II was doing for the same thing.]

    To begin with, let's take loop #1 and get as much
stuff out of the inner loop as we can, so we can see
what's going on.  Note that I'm not going to do low-level
optimizations, just mathematical optimizations; I assume
you understand that array walks can be turned into
pointer walks, etc.

[ loop #2 ]
      a = Oa + Va*j
      b = Ob + Vb*j
      c = Oc + Vc*j

      a += start_x * Ha
      b += start_x * Hb
      c += start_x * Hc

      for i = start_x to end_x
        u = 256 * a / c
        v = 256 * b / c
        screen[i] = texture_map[v][u]
        a += Ha
        b += Hb
        c += Hc
      endfor

    With fixed point math, the multiplies by 256
are really just shifts.  Furthermore, they can be
"premultiplied" into a and b (and Ha and Hb) outside
the loop.

    Ok, so what do we have left?  Three integer adds,
a texture map lookup, and two extremely expensive fixed-point
divides.  How can we get rid of the divides?

    This is the big question in texture mapping, and most
answers to it are _approximate_.  They give results that
are not quite the same as the above loop, but are difficult
for the eye to tell the difference.

    However, before we delve into these, there's a very
special case in which we can get rid of the divides.

    We can move the divide by c out of the loop without
changing the results IFF c is constant for the duration
of the loop.  This is true if Hc is 0.  It turns out that
Hc is 0 if all the points on the run of pixels are the same
depth from the viewer, that is, they lie on a line of
so-called "constant Z" (I would call it "constant Y" in
my coordinate system).

    The requirement that a horizontal line of pixels be the
same depth turns out to be met by ceilings and floors.
For ceilings and floors, Hc is 0, and so the loop can be
adjusted to:

[ loop #3 ]
      __setup from loop #2__
      u = 256 * a / c
      v = 256 * b / c
      du = 256 * Ha / c
      dv = 256 * Hb / c
      for i = start_x to end_x
        screen[i] = texture_map[v][u]
        u += du
        v += dv
     endfor

    Now _that_ can be a very fast loop, although adjusting
the u&v values so they can be used as indices has been
glossed over.  I'll give some sample assembly in the next
section and make it all explicit.

    First, though, let's look at walls.  Walls are almost
identical to floors and ceilings.  However, with walls,
Vc is 0, instead of Hc.  This means that to write a loop
in which c is constant, we have to walk down columns instead
of across rows.  This affects scan-conversion, of course.

    The other thing about walls is that with floors, since
you can rotate about the vertical axis (Z axis for me, Y axis
for most of you), the horizontal runs on the floors cut
across the texture at arbitrary angles.  Since you're
bound to not tilt your head up or down, and since the
polygons themselves aren't tilted, you generally find
that for walls, Va is 0 as well.  In other words, as you
walk down a column of a wall texture, both a & c are constant,
so u is constant; you generally only change one coordinate,
v, in the texture map.  This means the inner loop only needs
to update one variable, and can be made to run _very_ fast.

    The only thing missing from this discussion for creating
a DOOM clone is how to do transparent walls, how to do
lighting things, and how to make it fast enough.  These will
be discussed in section 4, although the some of the speed
issue is addressed by the inner loops in the next subsection,
and the rest of the speed issue is discussed in general terms
in section 3.

TEX
TE   ...wrapped around your finger...
T

    So far, we've only looked at texture mapping a single
polygon.  Of course, it's obvious how to texture map
a lot of polygons--just lather, rinse, repeat.  But it
may seem sort of wasteful to go through all the 3D math
and all over and over again if we just want to have one
long wall with the same texture repeating over and over
again--like linoleum tiles or wallpaper.

    Well, we don't have to.  Let's think about this
idea of a "texture map space" some more.  We defined
it as being a coordinate system "superimposed" on
the polygon that told us where the texture goes.
However, when we implemented it, we simply used the
polygon itself (in essence) as the coordinate space.

    To see this, make a polygon which is a rectangle,
perhaps four times as long as it is tall.  When it
is drawn, you will see the texture is distorted,
stretched out to four times its length in one dimension.
Suppose we wanted it to repeat four times instead?

    The first step is to look at what the definition
of the texture map space means.  The texture map space
shows how the physical pixelmap itself goes onto the
polygon.  To get a repeating texture map, our first
step is to just get one of the copies right.  If
we set up our P,M, & N so that the M only goes one
quarter of the way along the long edge of the
rectangle, we'll map the texture onto just that
quarter of the rectangle.

     Here's a picture to explain it:

              Polygon A-B-C-D                    Texture map

   A          E                                  u=0     u=1
    o--------o-___________          B        v=0  11112222
    |111112222            ---------o              11112222
    |111112222                     |              33334444
    |111112222                     |              33334444
    |333334444                     |         v=1
    |333334444                     |
    |333334444 ___________---------o
    o--------o-                     C
   D          F

    So, we used to map (u,v)=(0,0) to A, (u,v)=(1,0) to B, and
(u,v) = (0,1) to D.  This stretched the texture map out to
fill the entire polygon map.

    Now, instead, we map (u,v)=(1,0) to E.  In other words,
let P = A, M = E-A, and N = D-A.  In this new coordinate
space, we will map the texture onto the first quarter of
the polygon.

    What about the rest of the polygon?  Well, it simply
turns out that for the first quarter 0 <= u <= 1.  For
the rest, 1 <= u <= 4.

    To make the texture wrap around, all we have to do is
ignore the integer part of u, and look at the fractional part.
Thus, as u goes from 1 to 2, we lookup in the texture map using
the fractional part of u, or (u-1).

    This is all very simple, and the upshot is that, once
you define P, M, and N correctly, you simply have to mask
your fixed-point u&v values; this is why we generally use
texture maps whose sides are powers of two, so that we can
mask to stay within the texture map.  (Also because they
fit conveniently into segments this way, and also so that
the multiply to convert u&v values from 0..1 to indices
is just a shift.)

    I'm assuming that's a sufficient explanation of the
idea for you to get it all setup.  So here's the assembly
inner loops I promised.  I'm not going to bother giving
the ultra-fast vertical wall-drawing case, just the
horizontal floor/ceiling-drawing case.

    Note that a mask of 255 (i.e. for a 256x256 texture)
can be gotten for free; however, no program that I'm
aware of uses texture maps that large, since they
simply require too much storage, and they can cache very
poorly in the internal cache.

    First, here's your basic floor/ceiling texture mapper,
in C, with wraparound, and explicitly using fixed point
math--but no setup.

[ loop #4 ]
    mask = 127, 63, 31, 15, whatever.
    for (i=0; i < len; ++i) {
      temp = table[(v >> 16) & mask][(u >> 16) & mask];
      u += du;
      v += dv;
    }

    Now, here's an assembly one.  This one avoids the
shifts and does both masks at the same time, and uses
16 bits of "fractional" precision and however many bits
are needed for the coordinates.  Note that I assume
that the texture, even if it is 64x64, still has each
row starting 256 bytes apart.  This just requires some
creative storage approaches, and is crucial for a fast
inner loop, since no shifting&masking is required to
assemble the index.

       mov  al,mask
       mov  ah,mask
       mov  mask2,ax      ; setup mask to do both at the same time

loop5  and  bx,mask2      ; mask both coordinates
       mov  al,[bx]       ; fetch from the texture map
       stosb
       add  dx,ha_low     ; update fraction part of u
       adc  bl,ha_hi      ; update index part of u
       add  si,hb_low     ;   these are constant for the loop
       adc  bh,hb_hi      ;   they should be on the stack
       loop loop5         ;   so that ds is free for the texture map

    This code is decent, but nowhere near as fast as it can be.
The main trick to improving performance is to use 32 bit adds
instead of two adds.  The problem with this is that extra operations
are required to setup the indexing into the texture map.  Through
the use of the ADC trick, these can be minimized.  In the following
code, bl and bh are unchanged.  However, the top half of EBX now
contains what used to be in si, and the other values have been
moved into registers.  ESI contains hb_low in the top half, and
ha_hi in the low 8 bits.  This means that ADC EBX,ESI achieves
the result of two of the additions above.  Also, we start using
BP, so we move our variables into the data segment and the texture
map into FS.

loop6  and  bx,mask2
       mov  al,fs:[bx]
       stosb
       add  dx,bp          ; update fractional part of u
       adc  ebx,esi        ; update u (BL) and frac. part of v (EBX)
       adc  bh,ch          ; update index part of v
       dec  cl
       jnz  loop6

    This is a bit faster, although it has one bug.  It's
possible for the addition into BL to overflow into BH.  It might
not seem to be, since BL is masked every iteration back down to
stay in 0..127, 0..63, or whatever.  However, if the step is
negative, then BL will be decremented each iteration, and may
"underflow" and subtract one from BH.  To handle this, you need
a seperate version of the loop for those cases.

    If you're not doing wraparound textures, you can speed the
loop up a bit more by removing the and.  You can run entirely
from registers except for the texture map lookup.  Additionally,
unrolling the loop once cuts down on loop overhead, and is crucial
if you're writing straight to the VGA, since it doubles your
throughput to a 16-bit VGA card.

    Here's a very fast no-wraparound texture mapper.  It uses
the ADC trick twice.  Note that the carry flag is maintained
around the loop every iteration; unfortunately the 'and' required
for wraparound textures clears the carry flag (uselessly).  EBX
and EDX contain u & v in their bottom 8 bits, and contain the
fractional parts of v & u in their top bits (note they keep the
_other_ coordinate's fractional parts).  You have to have prepped
the carry flag first; if you can't figure this technique out, don't
sweat it, or look to see if someone else has a more clear discussion
of how to do fast fixed-point walks using 32-bit registers.

    This loop is longer because it does two pixels at a time.

loop7   mov  al,[bx]       ; get first sample
        adc  edx,esi       ; update v-high and u-low
        adc  ebx,ebp       ; update u-high and v-low
        mov  bh,dl         ; move v-high into tmap lookup register
        mov  ah,[bx]       ; get second sample
        adc  edx,esi
        adc  ebx,ebp
        mov  bh,dl
        mov  es:[di],ax    ; output both pixels
        inc  di            ; add 2 to di without disturbing carry
        inc  di
        dec  cx
        jnz  loop7

    I went ahead and 486-optimized the stosw/loop at the end to make
cycle-counting easier.  All of these instructions are single cycle
instructions, except the branch, and the segment-override.  So you're
looking at roughly 15 cycles for every two pixels.  Your caching
behavior on the reads and writes will determine the actual speed.
It can be unrolled another time to further reduce the loop overhead;
the core operations are 9 instructions (10 cycles) for every two
pixels.  Note the "inc di/inc di", which protects the carry flag.
If you unroll it again, four "inc di"s will be required.  Unroll it
another time, and you're better off saving the carry flag, adding,
and restoring, for example "adc ax,ax/add di,8/shr ax,1", rather
than 8 "inc di"s.

TEX
TE   Lost My Shape (trying to act casual)
T

    Non-rectangular polygons are trivial under this system.
Some approaches require you to specify the (u,v) coordinates
for each of the vertices of the polygon.  With this technique,
you instead specify the 3D coordinates for three of the
"vertices" of the texture map.  So the easiest way of handling
a texture of a complex polygon is simply to use a square
texture which is larger than the polygon.  For example:

   P1                       P2
     x    B  _______  C    x
           /         \
         /             \
     A /                 \
       \                 / D
         \             /
           \ _______ /
     x    F           E
   P3

    Now, we simply define the texture map such that P is P1,
M is P2-P1, and N is P3-P1.  Then, if our texture looks like
this:

     u=0        u=1
      ------------
 v=0 |..XXoooooo..
     |.XXXXoooooo.
     |XXXXXXoooooo
     |.XXXXXXoooo.
 v=1 |..XXXXXXoo..

    Then the regions marked by '.' in the texture map will
simply never be displayed anywhere on the polygon.

    Wraparound textures can still be used as per normal,
and concave polygons require no special handling either.

    Also, you can get special effects by having M and N not
be perpendicular to each other.

TEXTURE
TEXUE
TXR   3.  The Hazards
X

    This sections discusses some of the pitfalls and
things-aren't-quite-as-simple-as-they-sounded issues
that come up while texture mapping.  All of the
information is, to some extent, important, whether
you've encountered this problem or not.

TEX
TE   Cl-cl-cl-close to the Edge
T

    At some time when you're texture mapping, you'll
discover (perhaps from the screen, perhaps from a
debugger) that your U & V values aren't within the
0..1 range; they'll be just outside it.

    This is one of these "argh" problems.  It is
possible through very very careful definition of
scan-conversion operations to avoid it, but you're
likely to encounter it.

    If you use wraparound textures, you may not ever
notice it, however, since when it happens, the
texture will simply wraparound and display an
appropriate pixel.

    If not, you may get a black pixel, or just
garbage.  It'll only happen at the edges of your
polygon.

    The reason this happens is because your scan-conversion
algorithm may generate pixels "in the polygon" whose
pixel-centers (or corners, depending on how you've
defined it) are just outside the texture--that is,
they're outside the polygon itself.

    The right solution to this is to fix your scan-converter.
If your texture mapper computes u&v coordinates based on
the top-left corner of the pixel (as the one I've defined
so far has), make sure your scan-converter only generates
pixels whose top-left corner is really within the polygon.
If you do this, you may need to make a minor change to my
definition of M & N, but I'm not going to discuss this
further, since you probably won't do this.

    A second option is to define P, M, and N such that the
texture map space is slightly bigger than the polygon; that
is, so that if you go just off the edge of the polygon,
you'll still be within the texture map.

    This is a pain since you end up having to transform extra
3D points to do it.

    The third, and probably most common solution, is to always
use wraparound textures, which hide the problem, but prevent
you from using textures that have one edge that highly contrasts
with another.

    The fourth, and probably second most common solution,
and the one that turns out to be a real pain, is to "clamp"
the u&v values to be within the texture all the time.

    Naively, you just put this in your inner loop:

      if (u < 0) u = 0; else if (u > 1) u = 1;
      if (v < 0) v = 0; else if (v > 1) v = 1;

    Of course, you don't really do this, since it'd slow
you down far too much.  You can do this outside the loop,
clamping your starting location for each run.  However,
you can't, under this system, clamp the ending value
easily.

    Remember that in the loop we update u and v with
(essentially) Ha/c and Hb/c.  These are constant
across the entire run, but not constant across the
entire polygon, because c has different values for
different runs.

    We can compute du and dv in a different way to
allow for clamping.  What we do is we explicitly compute
(a,b,c) at (start_x, j) as we did before, but we also
compute (a,b,c) at (end_x, j).  From these we compute
(u,v) at start_x & at end_x.  Next we clamp both sets
of u & v.  Then we compute du and dv with

    du = (u2 - u1) / (end_x - start_x - 1)
    dv = (v2 - v1) / (end_x - start_x - 1)

    This is slightly more expensive than the old way,
because we have to compute u2 and v2, which requires
extra divides.  However, for methods that explicitly
calculate u&v sets and then compute deltas (and we'll
see some in section 4), this is the way to go.

    One final thing you can do is interpolate the (a,b,c)
triple from the vertices as you scan convert.  This will
guarantee that all (a,b,c) triples computed will lie be
within the polygon, and no clamping will be necessary
(but deltas must still be computed as above).  However,
you have to make sure the (a,b,c) values you compute at
the vertices are clamped themselves, which is not too hard
by a bit more complicated than clamping (u,v) values.

TEX
TE   Out of This Domain -- Zero's Paradox
T

    Divides by zero are ugly.  We programmers don't
like them.  If this were an ideal world (a quick
nod to mathematicians and some physicists), the
texture mapping equations would be divide-by-zero-free.

    Unfortunately, it's a repercussion of the exact
same problem as above that you can bump into them.

    Remember above, I noted that it's possible to
get (u,v) pairs with a value just outside of the
0..1 range, because a pixel we're texture mapping
isn't even in the polygon?

    Well, even worse, it's possible for this pixel,
which isn't in the polygon, to be along the horizon
line (vanishing point) for the polygon.  If this
happens, your Y value (sorry, Z for most of you)
would be infinite if you tried to compute the 3D
coordinates from the screen coordinates; and in
the (u,v) computation, you end up with a 0 value
for c.  Since u = a/c, blammo, divide by 0.

    Well, the solution is simple.  Test if c is 0,
and if it is, don't divide.  But what _should_ you do?

    Well, let's look at an "even worse" case.  Suppose
the pixel is so far off the polygon it's across the
horizon line.  In this case, we'll end up with c having
the "wrong" sign, and while our divide won't fault on
us, our u&v values will be bogus.

    What do we do then?

    We can't clamp our a&b&c values very easily.
Fortunately, it turns out we don't have to.  If
this happens, it means the edge of the polygon
must be very close to the horizon, or the viewer
must be very, very flat to the polygon (if you know
what I mean).  If so, the viewer can't really tell
what should be "right" for the polygon, so if we
screw up the u&v values, it really doesn't matter.

    So the answer is, don't worry if c gets the
wrong sign, and if c comes out to be 0, use any
value for u&v that you like--(0,0) makes an obvious
choice.

    I've never had a serious problem with this, but
it is possible that this could actually give you some
pretty ugly results, if, say, two corners of a polygon
both "blew up", and you treated them both as being
(0,0).  It can also cause problems with wraparound
polygons not repeating the right amount.

TEX
TE   Do the Dog
T

    Most polygon 3D graphics engines probably use
the painter's algorithm for hidden surface removal.
You somehow figure out what order to paint the polygons
in (depth sort, BSP trees, whatever), and then paint
them back-to-front.  The nearer polygons obscure the
farther ones, and voila!, you're done.

    This works great, especially in a space combat
simulator, where it's rare that you paint lots of pixels.

    You can texture map this way, too.  For example,
Wing Commander II doesn't texture map, but it does
real time rotation, which involves essentially the
same inner loop.  Wing Commander II is fast--until
a lot of ships are on the screen close to you, at which
point it bogs down a bit.

    If you care about not slowing down too much in the above
case, or you want to do an "indoor" renderer with lots of
hidden surfaces, you'll find that with texture mapping,
you can ill-afford to use the painter's algorithm.

    You pay a noticeable cost for every pixel you texture
map.  If you end up hiding 80% of your surfaces (i.e. there
are five "layers" everywhere on the screen), you end up
"wasting" 80% of the time you spend on texture mapping.

    To prevent this, you have to use more complex methods
of hidden surface removal.  These will probably slow you down
somewhat, but you should make up for it with the gain in texture
mapping.

    The essential idea is to only texture map each screen pixel once.
To do this, you do some sort of "front-to-back" painting, where
you draw the nearest surface first.  Any pixel touched by this
surface should never be considered for drawing again.

    There are many ways to do this.  You can process a single
scanline or column at a time and use ray-casting or just
"scanline processing", then resolve the overlap between the
runs with whatever method is appropriate.  You can stay
polygonal and maintain "2D clipping" information (a data
structure which tracks which pixels have been drawn so far).

    Beyond getting a fast inner loop for texture mapping,
getting a fast hidden-surface-removal technique (and a fast
depth-sorting technique if appropriate) is probably the
next most crucial thing for your frame rate.

    But the details are beyond the scope of this article.

    Note that if you attempt to use a Z-buffer, you will
still end up paying all of the costs of texture mapping for
every forward-facing polygon (or at least 50% of them if
you get really tricky; if you get really, really tricky,
the sky's the limit.)  I strongly doubt that any PC game
now out, or that will come out in the next year, will
render full-screen texture mapping through a Z-buffer.
(Superimposing a rendered image on a Z-buffered background
is a different issue and is no doubt done all the time.)


TEXTURE
TEXUE
TXR   4.  The Complexities
X

    In this section we will discuss lots of miscellaneous
topics.  We'll look at some more optimizations, such as
considerations for dealing with slow VGA cards, and how to
texture map arbitrarily-angled polygons without doing two
divides per pixel.  We'll talk about a technique that lets
you use textures with high-frequency components, and one way
to integrate lighting into texture-mapping.

TEX
TE   Arbitrarily-Angled Polygons
T

    First suggestion: Don't.  Set up your world to
have all (or mostly) walls and floors.  Supporting
arbitrarily-angled polygons is going to slow you
down, no matter what.

    The original texture mapping loop, which supported
arbitrarily-angled polygons, required two divides per
pixel.  We don't have to go that slow, but we'll never
go as fast as DOOM-style rendering can go.  (However,
as you start to use more sophisticated lighting algorithms
in your inner loop, the cost of handling arbitrarily-
angled polygons may start to become less important.)

    There is one way to texture map such polygons
"perfectly" without two divides per pixel, and a
host of ways to do it "imperfectly".  I'll discuss
several of these ways in varying amounts of detail.
Your best bet is to implement them all and see
which ones you can get to run the fastest but still
look good.  You might find that one is faster for
some cases but not for others.  You could actually
have an engine which uses all the methods, depending
on the polygon it's considering and perhaps a "detail"
setting which controls how accurate the approximations
are.

    The "perfect" texture mapping algorithm is
described in another article, "Free-direction texture
mapping".  I'll summarize the basic idea and the
main flaw.  The basic idea is this.  For ceilings
and walls, we were able to walk along a line on
the screen for which the step in the "c" parameter
was 0; this was a line of "constant Z" on the
polygon.

    It turns out that every polygon has lines of
"constant Z"--however, they can be at any angle,
not necessarily vertical or horizontal.

    What this means, though, is that if you walk
along those lines instead of walking along a horizontal
or vertical, you do not need a divide to compute your
texture map coordinates, just deltas.

    The details can be found in the other article.
The slope of the line to walk on the screen is something
like Hc/Vc.

    Note, however, that the "DOOM" approach was _just_
an optimization for a special case.  The wall & ceiling
renderers produce exactly the same results as a
perfect texture mapper, for the polygons that they
handle (ignoring rounding errors and fixed-point precision
effects).  This is not true for the "free-direction"
texture mapper.  While there is a line across the
screen for which the polygon has constant Z,
you cannot walk exactly along that line, since you
must step by pixels.  The end result is that while
in the texture map space, you move by even steps,
in the screen space, you move with ragged jumps.
With perfect texture mapping, you always sample
from the texture map from the position corresponding
to the top-left/center of each pixel.  With the
free-direction mapper, you sample from a "random"
location within the pixel, depending on how you're
stepping across the screen.  This "random" displacement
is extremely systematic, and leads to a systematic
distortion of the texture.  I find it visually
unacceptable with high-contrast textures, compared to
perfect texture mapping, but you should try it and decide
for yourself.  The technically inclined should note that
this is simply the normal "floor" renderer with an extra
2D skew, and that while 2D skews are trivial, they are
non-exact and suffer from the flaw described above.

    The only other alternative for arbitrarily-angled
polygons is to use some kind of approximation.  We
can characterize u and v as functions of i (the horizontal
screen position; or use 'j' if you wish to draw columns);
for instance, u = a / c, where a = q + i*Ha, c = p + i*Hc.
So we can say  u(i) = (q + i*Ha) / (r + i*Hc).

    Now, instead of computing u(i) exactly for each i,
as we've done until now, we can instead compute some
function u'(i) which is approximately equal to u(i) and
which can be computed much faster.

    There are two straightforward functions which we
can compute very fast.  One is the simple linear
function we used for DOOM-style mapping, u'(x) = r + x*s.
Since the function we're approximating is curved (a
hyperbola), a curved function is another possibility,
such as u'(x) = r + x*s + x^2*t.  (SGI's Reality Engine
apparently uses a cubic polynomial.)

    If you try both of these approximations on a very
large polygon at a sharp angle, you will find that
they're not very good, and still cause visible
curvature.  They are, of course, only approximations.
The approximations can be improved with a simple
speed/quality trade-off through subdivision.  The
idea of subdivision is that the approximation is
always of high quality for a small enough region,
so you can simply subdivide each region until the
subregions are small enough to have the desired
quality.

    There are two ways to subdivide.  One simple way
is to subdivide the entire polygon into smaller
polygons.  This should be done on the fly, not
ahead of time, because only polygons that are at
"bad" angles need a lot of subdivision.  After
dividing a polygon into multiple smaller ones,
render each one seperately.  Use the original
P, M, and N values for all of the new polygons
to make the texture remain where it should be
after subdivision.

    The (probably) better way to subdivide is to
subdivide runs instead of polygons, and so I'll
discuss this in more detail.  The essential thing
is that to do an approximation, you evaluate the
original function at two or more locations and
then fit your approximate function to the computed
values.  One advantage of run subdivision is that
you can share points that you evaluated for one
subrun with those of the next.

    First lets turn back to the two approximations
under consideration.  The first is what is called
"bilinear texture mapping", because the function
is linear and we're tracking two ("bi") values.
To use this method, we compute the function at
both endpoints: u1 = u(start_x), u2 = u(end_x).
Then we compute our start and step values.  To
keep things simple, I'm going to assume the approximation
function u'(x) is defined from 0..end_x-start_x, not
from start_x..end_x.

    So, the linear function u'(x) = r + s*x, where
u'(0) = u1 and u'(end_x - start_x) = u2 is met by
letting r = u1, s = (u2 - u1) / (end_x - startx).

    Now, suppose our run goes from x = 10 to x = 70.
If we evaluate u(10), u(20), u(30), u(40),... u(70),
then we can have six seperate sections of bilinear
texture mapping.

    For a quadratic, there are several ways to compute
it.  One way is to compute an additional sample in the
middle; u3 = u((start_x + end_x)/2).  Then we can
fit u1,u2, and u3 to u'(x) = r + s*x + t*x^2 with:

   len = (end_x - start_x)
   k   = u1 + u2 - u3*2
   r   = u1
   s   = (u2 - u1 - 2*k)/len
   t   = 2*k / len^2

    Note that to use this in code, you cannot simply
use a loop like this:

   r += s;
   s += t;

    because the r,s, and t values aren't correct
for discrete advancement.  To make them correct,
do this during the setup code:

    R = r
    S = s + t
    T = 2*t

    Then the loop of (...use R..., R += S, S += T) will work correctly.

    The biquadratic loop will be slower than the linear
loop, but will look better with fewer subdivisions.  You
can share one of the endpoints from one biquadratic
section to the next.  Note, though, that you require twice
as many calculations of u&v values for the same number of
subdivisions with a biquadratic vs. a bilinear.

    Another thing to do is to choose how to subdivide the run
more carefully.  If you simply divide it in half or into quarters,
you'll discover that some of the subruns come out looking better
than others.  So there are some things you can do to improve the
subdivision system.  Another thing you can do is to try to make
most of your subruns have lengths which are powers of two.  This
will let you use shifts instead of divides when computing r,s, and
t, which cuts down on your overhead, which lets you use more
subdivisions and get the same speed.

    Note something very important.  Subdivision increases the
overhead per run; biquadratic and other things increase the
cost of the inner loop.  Before you go crazy trying to optimize
your arbitrarily-angled polygon renderer, make sure you're
rendering some "typical" scenes.  The "right" answer is going
to depend on whether you have lots of very shorts runs or
fewer, longer runs.  If you optimize based on a simple test
case, you may end up suboptimal on the final code.

    You probably still want to have both a column-based and a
row-based renderer, and use whichever one the polygon is
"closer to" (e.g. if Hc is closer to 0 than Vc, use the row-based).
Note that the free-direction renderer looks its worst (to me)
for very small rotations, i.e. when Hc or Vc are very close to 0.
Since in these cases not much subdivision is needed, even if you
choose to use a free-direction mapper as your primary renderer,
you might still want to have "almost wall" and "almost floor"
renderers as well.

    Finally, there is one more approximation method you can use,
which is faster than any of the ones discussed so far, but is
simply totally and utterly wrong.  This is the approach used
by Michael Abrash in his graphics column in Dr. Dobbs.  While
it's quite wrong, it works on polygons which are entirely
constant Y (sorry, Z), and can be a noticeable speedup.

    What you do is 2D (instead of 3D) interpolation.  You mark
each vertex with its coordinates in the texture map.  Then when
you scan convert, you interpolate these values between vertices
on the edges of your runs.  Thus, scan conversion will generate
runs with (u,v) values for the left and right end.  Now simply
compute (du,dv) by subtracting and dividing by the length (no
clamping will be necessary), and use your fast bilinear inner
loop.  When combined with 3D polygon subdivision, this approach
can actually be useful.

  A cheat:

    When the player is moving, set your internal quality
settings a little lower.  When the player stops, switch
back to the normal quality; if the player pauses the game,
render one frame in normal quality.

    If done right, you can get a small boost to your fps
without anyone being able to tell that you did it.  You
may have to use normal quality if the player is only
moving very slowly, as well.

    Note that while this may sound like an utterly cheap
trick just to improve the on-paper fps number, it's actually
quite related to the "progressive refinement" approach used
by some real VR systems (which, when the viewer isn't moving,
reuse information from the previous frame to allow them to
draw successive frames with more detail).

    There are a number of ways of improving this cheat
intelligently.  If the player is moving parallel to a polygon,
that polygon will tend to be "stably" texture mapped (similar
mapping from frame to frame).  If there is any distortion from
your approximation, this will be visible to the player.  So
this means a rule of thumb is to only cheat (draw with
above-average distortion) on polygons that are not facing
parallel to the direction of motion of the player.

TEX
TE   Light My Fire
T

    If you're texture mapping, it's generally a good idea
to light your polygons.  If you don't light them, then it
may be difficult to see the edge between two walls which
have the same texture (for instance, check out the "warehouse"
section of registered DOOM, which is sometimes confusing
when a near crate looks the same color as a far crate).

    Lighting is actually pretty straightforward, although
you take a speed hit in your inner loop.  I'm not going
to worry about actual lighting models and such; see other
articles for discussion on how to do light-sourced polygons.

    Instead I'm going to assume you've computed the lighting
already.  We'll start with "flat-run" shading, wherein an
entire run has the same intensity of light falling on it.

    DOOM uses flat-run shading.  A given polygon has a certain
amount of light hitting it, which is the same for the entire
polygon.  In addition, each run of the polygon is sort-of
lit by the player.  Since runs are always at a constant
depth, you can use constant lighting across the run and
still change the brightness with distance from the player
(DOOM uses something that resembles black fog, technically).

    So the only real issue is _how_ you actually get the
lighting to affect the texture.  Several approaches are
possible, but the only one that I think anyone actually uses
is with a lighting table.

    The lighting table is a 2D array.  You use the light
intensity as one index, and the pixelmap color as the
other index.  You lookup in the table, and this gives
you your final output color to display.  (With two
tables, you can do simple dithering.)  So the only thing
you have to do is precompute this table.

    Basically, your inner loop would look something like this:

  ...compute light...
  for (i=start_x; i <= end_x; ++i) {
    color = texture[v >> 16][u >> 16];
    output = light_table[light][color];
    screen[i] = output;
    u += du;
    v += dv;
  }

    The next thing to consider is to Gouraud shade your
texture map.  To do this, you need to compute the light
intensity at the left and right edge of the run; look
elsewhere for more details on Gouraud shading.

    Once you've got that, you just do something like this:

  z = light1 << 16;
  dz = ((light2 - light1) << 16) / (end_x - start_x);
  for (i=start_x; i <= end_x; ++i) {
    color = texture[v >> 16][u >> 16];
    output = light_table[z >> 16][color];
    screen[i] = color;
    u += du;
    v += dv;
    z += dz;
  }

    Note that you shouldn't really do this as I've written the
code.  light1 and light2 should be calculated with 16 bits of extra
precision in the first place, rather than having to be shifted
left when computing z.  I just did it that way so the code
would be self-contained.

    I'm going to attempt to give a reasonably fast assembly
version of this.  However, there's a big problem with doing
it fast.  The 80x86 only has one register that you can
address the individual bytes in, and also use for indexing--BX.
This means that it's a real pain to make our inner loop alternate
texture map lookup and lighting fetch--whereas it's almost
trivial on a 680x0.  I avoid this somewhat by processing two
pixels at a time; first doing two texture map lookups, then
doing two lighting lookups.

     Here's a flat-shading inner loop.  I'm doing this code off the
top of my head, so it may have bugs, but it's trying to show
at least one way you might try to do this.  Since I use BP,
I put variables in the FS segment, which means DS points
to the texture, GS to the lighting table.

        mov  ch,fs:light
        adc  ax,ax
loop8   shr  ax,1          ; restore carry
        mov  cl,[bx]       ; get first sample, setting up cx for color lookup
        adc  edx,esi       ; update v-high and u-low
        adc  ebx,ebp       ; update u-high and v-low
        mov  bh,dl         ; move v-high into tmap lookup register
        mov  ah,[bx]       ; get second sample, save it in ah
        adc  edx,esi
        adc  ebx,ebp
        mov  dh,bl         ; save value of bl
        mov  bx,cx         ; use bx to address color map
        mov  al,gs:[bx]    ; lookup color for pixel 1
        mov  bl,ah         ; switch to pixel 2
        mov  ah,gs:[bx]    ; lookup color for pixel 2
        mov  es:[di],ax    ; output both pixels
        mov  bl,dh         ; restore bl from dh
        mov  bh,dl
        adc  ax,ax         ; save carry so we can do CMP
        add  di,2
        cmp  di,fs:last_di ; rather than having to decrement cx
        jne  loop8

    For a Gouraud shading inner loop, we can now have three
different numbers u, v, and z, which we're all adding at every
step.  To do this, we use THREE adc, and we have to shuffle
around which high-bits correspond to which low-bits in a
complex way.  I'll leave you to figure this out for yourself,
but here's an attempt at the inner loop.

loop9   shr  ax,1          ; restore carry
        mov  al,fs:[bx]    ; get first sample
        mov  ah,cl         ; save away current z-high into AH
                           ; this makes AX a value we want to lookup
        adc  edx,esi       ; update v-high and u-low
        adc  ebx,ebp       ; update u-high and z-low
        adc  ecx,z_v_inc   ; update z-high and v-low
        mov  bh,dl         ; move v-high into tmap lookup register
        mov  ch,fs:[bx]    ; get second sample, save it in ch
        mov  dh,bl         ; save value of bl
        mov  bx,ax
        mov  al,gs:[bx]    ; lookup first color value
        mov  bl,ch
        mov  bh,cl
        mov  ah,gs:[bx]    ; lookup second color value
        mov  es:[di],ax    ; output both pixels
        mov  bl,dh         ; restore bl from dh
        adc  edx,esi
        adc  ebx,ebp
        adc  ecx,z_v_inc
        mov  bh,dl
        adc  ax,ax         ; save carry
        add  di,2
        cmp  di,last_di    ; rather than having to decrement cx
        jne  loop9

    Notice that both of these loops are significantly slower
than the original loop.  I'm not personally aware of any
generally faster way to do this sort of thing (but the code
can be tweaked to be faster).  The one exception is
that for flat-run shading, you could precompute the entire
texture with the right lighting.  This would require a lot
of storage, of course, but if you view it as a cache, it
would let you get some reuse of information from frame to
frame, since polygons tend to be lit the same from frame to
frame.

    Finally, here's a brief discussion of transparency.
There are two ways to get transparency effects.  The first
one is slower, but more flexible.  You use _another_ lookup
table.  You have to paint the texture that is transparent
after you've drawn things behind it.  Then, in the inner
loop, you fetch the texture value (and light it) to draw.
Then you fetch the pixel that's currently in that location.
Lookup in a "transparency" table with those two values as
indices, and write out the result.  The idea is that you
do this: table[new][old].  If new is a normal, opaque,
color, then table[new][old] == new, for every value of old.
If new is a special "color" which is supposed to be transparent,
then table[new][old] == old, for every value of old.  This causes
old to show through.  In addition, you can have translucency
effects, where table[new][old] gives a mixture of the colors
of old and new.  This will let you do effects like the
translucent ghosts in the Ultima Underworlds.

    However, the above approach is extremely slow, since you
have to load the value from the pixel map and do the extra
table lookup.  But it works for arbitrary polygons.   DOOM
only allows transparency on walls, not on ceilings and floors.
Remember we noticed that the special thing about walls is
that u is constant as you draw a column from a wall; you
are walking down a column in the texture map at the same
time you are drawing a column on screen.  What this means
is that you can use a data structure which encodes where
the transparency in each column of the texture map is, and
use that _outside_ the inner loop to handle transparency.
For example, your data structure tells you that you have
a run of 8 opaque pixels, then 3 transparent pixels, then
5 more opaque ones.  You scale 8, 3, and 5 by the rate at
which you're walking over the textures, and simply treat
this as two seperate opaque runs.

    The details of this method depend on exactly how you're
doing your hidden surface removal, and since it doesn't
generalize to floors&ceilings, much less to arbitrarily
angled polygons, I don't think going into further detail
will be very useful (I've never bothered writing such a
thing, but I'm pretty sure that's all there is to it).


TEX
TE   The Postman Always Rings Twice
T

    If you're going to write to a slow 16-bit VGA card, you
should try your darndest to always write 2 pixels at a time.

    For texture mapping, your best bet is to build your screen
in a buffer in RAM, and then copy it to the VGA all at once.
You can do this in Mode 13h or in Mode X or Y, as your heart
desires.  You should definitely do this if you're painting
pixels more than once while drawing.

    If, however, you wish to get a speedup by not paying
for the extra copy, you might like to write directly to the
VGA card from your inner loop.

    You might not think this is very interesting.  If the
write to the screen buffer in regular RAM is fast, how much
can you gain by doing both steps at once, instead of splitting
them in two?

    The reason it is interesting is because the VGA, while
slow to accept multiple writes, will let you continue doing
processing after a single write.  What this means is that
if you overlap your texture mapping computation with your
write to the VGA, you can as much as double your speed on
a slow VGA card.  For example, the fastest I can blast my
slow VGA card is 45 fps.  I can texture map floor-style directly
to it at 30 fps.  If I texture map to a memory buffer,
this is still somewhat slow, more than just the difference
between the 30 and 45 fps figures.  Thus, my total rate if
I write to an offscreen buffer drops as low as 20 fps, depending
on exactly what I do in the texture map inner loop.

    Ok, so, now suppose you've decided it might be a speedup
to write directly to the VGA.  There are two problems.  First
of all, if you're in mode X or Y, it's very difficult to
write two bytes at a time, which is necessary for this
approach to be a win.  Second of all, even in mode 13h, it's
difficult to write two bytes at a time when you're drawing
a column of pixels.

    I have no answer here.  I expect people to stick to
offscreen buffers, or to simply process columns at a time
and write (at excruciatingly slow rates on some cards) to
the VGA only one byte at a time.

    One option is to set up a page flipping mode 13h (which
is possible on some VGA cards), and to paint two independent
but adjacent columns at the same time, so that you can write
a word at a time.  I have a very simple demo that does the
latter, but it's not for the faint of heart, and I don't
think it's a win once you have a lot of small polygons.

    Another answer is to have a DOOM-style "low-detail"
mode which computes one pixel, duplicates it, and always
writes both pixels at the same time.

    A final answer is just to ignore the market of people
with slow VGA cards.  I wouldn't be surprised if this
approach was commonplace in a year or two.  But if you do
so with commercial software, please put a notice of this
requirement on the box.

TEX
TE   Mipmapping (or is it Mip-Mapping?)
T

    Mipmapping is a very straightforward technique that
can be used to significantly improve the quality of your
textures, so much so that textures that you could not
otherwise use because they look ugly become usable.

    The problem that mipmapping addresses is as follows.
When a texture is far in the distance, such that its
on-screen size in pixels is significantly smaller than
its actual size as a texture, only a small number of
pixels will actually be visible.  If the texture contains
areas with lots of rapidly varying high contrast data,
the texture may look ugly, and, most importantly,
moire artifacts will occur.  (To see this in DOOM, try
shrinking the screen to the smallest setting and going
outside in shareware DOOM.  Many of the buildings will
show moire patterns.  In registered DOOM, there is
a black-and-blue ceiling pattern which has very bad
artifacts if it is brightly lit.  Go to the mission
with the gigantic round acid pool near the beginning.
Cheat to get light amplification goggles (or maybe
invulnerability), and you'll see it.)

    Mipmapping reduces these artifacts by precomputing
some "anti-aliased" textures and using them when the
textures are in the distance.

    The basic idea is to substitute a texture map half as
big when the polygon is so small that only every other
pixel is being drawn anyway.  This texture map contains
one pixel for every 2x2 square in the original, and is
the color average of those pixels.

    For a 64x64 texture map, you'd have the original
map, a 32x32 map, a 16x16 map, an 8x8 map, etc.

    The mipmaps will smear out colors and lose details.
You can best test them by forcing them to be displayed
while they're still close to you; once they appear to
be working, set them up as described above.

    Mipmapping causes a somewhat ugly effect when you
see the textures switch from one mipmap to the next.
However, especially for some textures, it is far less
ugly than the effect you would get without them.

    For example, a fine white-and-black checkerboard
pattern (perhaps with some overlaid text) would look
very ugly without mipmapping, as you would see random
collections of white and black pixels (which isn't too
bad), and you would see curving moire patterns (which
is).  With mipmapping, at a certain distance the whole
polygon would turn grey.

    I do not believe any existing games for the PC
use mipmapping.  However, examining the data file
for the Amiga demo version of Legends of Valour showed
smaller copies of textures, which made it look like
mipmapping was being used.

    Mipmapping requires 33% extra storage for the
extra texture maps (25% for the first, 25% of 25%
for the second, etc.).

    This may also be a good idea for 2D bitmaps which
are scaled (e.g. critters in Underworld & DOOM, or
ships in Wing Commander II--although none of those
appeared to use it.)

    SGI's Reality Engine does mipmapping.  Actually,
it does a texturemap lookup on two of the mipmaps,
the "closer" one and the "farther" one, and uses
a weighted average between them depending on which
size is closer to correct.  (The RE also does
anti-aliasing, which helps even more.)


TEXTURE
TEXUE
TXR    Where Do We Go From Here?
X

    The above discussion mostly covers what is basically
the state of the art of texture mapping on the PC.  Hopefully
in the future every game will be at least as fast as the
inner loops in this article allow.

    As long as people want full-screen images, it'll
be a while before we have enough computational power
to do more than that.  But if we did have more power,
what else could we do with it?

    o  Better lighting
      o  Colored lighting (requires complex lookup tables)
      o  Phong shading (interpolation of normals--one sqrt() per pixel!)
    o  Higher resolution (640x400, or 640x400 and anti-alias to 320x200)
    o  A lot more polygons
    o  Bump mapping (can be done today with huge amounts of precomputation)
    o  Curved surfaces

                   ??????????????????????????????????
                   ? Free Direction Texture Mapping ?
                   ??????????????????????????????????

The following article was posted by Hannu Helminen (dm@stekt.oulu.fi) to
comp.graphics.algorithms (article 4061). It has been included in the PC-GPE
with his permission.

?????????????????????????????????????????????????????????????????????????????

From X Sat Apr  2 10:24:14 EST 1994
Article: 4061 of comp.graphics.algorithms
Newsgroups: comp.graphics.algorithms
Path: csc.canberra.edu.au!newshost.anu.edu.au!harbinger.cc.monash.edu.au!msuinfo!agate!howland.reston.ans.net!EU.net!news.funet.fi!ousrvr.oulu.fi!news.oulu.fi!dm
From: dm@stekt13.oulu.fi (Hannu Helminen)
Subject: Re: extended DOOM: free-direction texture mapping
In-Reply-To: dm@stekt13.oulu.fi's message of Fri, 25 Mar 1994 10:37:02 GMT
Message-ID: <DM.94Mar28152625@stekt13.oulu.fi>
Lines: 160
Sender: news@ousrvr.oulu.fi
Organization: University of Oulu, Department of Electrical Engineering, Finland
References: <DM.94Mar25123702@stekt13.oulu.fi>
Date: Mon, 28 Mar 1994 12:26:24 GMT

The idea of free-direction texture-mapping seems to be new to many
few people, so I decided to post this short introduction.

Warning: The level of this discussion is quite introductory, if you know
(or guess) what I'm going to talk about, you probably know as much as I
do.


First look at the principles. In Doom (and in Wolfenstain) the method
used to draw the walls is quite simple. You divide the wall into
vertical lines. Then you calculate where the wall should start and where
to end on the screen (A and B in my nice ascii-picture), and where in
the texture space the corresponding line should start and end.

Wall:      Texture:
 \            Y
  \B       \/\^\/\/\
  ^\       /\/./\/\/
  . \      \/\.\/\/\
  . /      /\/./\/\/
  ./          X
  /A
 /      

Then you simply do a highly optimized loop in which you do all the
pixels in the vertical line, pick a color from the texture, put it onto
the screen, and move to next position in the texture.


The floor is a bit more complicated. (I understand that Wolfenstain had
no floor texturing, am I correct?) This time, the floor segment is
mapped to a horizontal line, which is simple enough. However, in texture
space that same line may be in any direction, so you'll have a 2D line
in the texture, like this:

Floor:     Texture:
    /\                Y
 A/...>\B       \/\/\.\/\
/        \      /\/\.\/\/
                \/./\/\/\
                /./\/\/\/
                X
 

This is old and dull. Now for the new and exciting part: suppose we wish
to draw a polygon in 3-space that has free orientation. A bit of thought
and a simple extension of the above ideas tell us that we should use a
free-direction line in the display coordinates as well.

When we map a plane with free orientation to the screen, there is
always one direction on the screen, in which the z-coordinate
(distance) stays the same. In doom's walls it is vertical, in doom's
floors it is horisontal.  But there is one such direction for every
plane.

Why is constant z-coordinate important? These lines have the special
property that constant movement along them corresponds to constant
movement in texture space.

Read the above two paragraphs again until you have understood them,
since they are the key thing. The rest is only implementation,
following is a short explanation on how I did it.

For each polygon you are about to draw on the screen, do the following.
Find the plane equation. From that, derive the "constant-z" direction.
(Come on, take a piece of paper and a pen, it is quite easy.)

It helps to make the distinction between two cases here.  Either the
"constant-z" direction is more horisontal, or it is more vertical.
Suppose that it is more horisontal. The constant-z line equation is now
something like y = p*x, where -1 <= p <= 1.

 ----
     ---   Example of a constant-z line
        ----
            ----

Now, a change in the coordinate system is in order. x is the same x as
before, but y is "slanted" by the factor of p. This means that the
x-axis will be "slanted" but y-axis will be the same as before.

The next thing is to convert the polygon to this coordinate system.
Scan convert it line by line, but along these "slanted" (constant-z)
lines.

Suppose that we are about to draw a triangle shown below, and the
slanted line is the one shown above. So the path to follow on the
is as follows (ascii art is back again). The path in the texture is
also determined.
        
On the screen:     In texture (eg.):

 \-------            Y
 A...    -----/      /./\/\/\/
   \ ...     /       \/./\/\/\
    \   ..../        /\/./\/\/
     \     /B        \/\/./\/\
      \   /               X
       \ /
        X


So when you render the triangle, the result would be like this. The
numbers are lines of constant Z-value.

 22221110   
  3332221111000
   44333222211
    544433332  
     5554444   
      66555    
       766     
        7      

Note: you should stack the constant-z lines just as shown in the picture.

Implementation notes: this will be a bit slower than DOOM floors, since
the algorithm is a bit more complicated. Another thing is that it will
not be quite as cache-coherent.

If you are rendering big polygons (and have a large cache), it helps
to precalculate the pixels lying on the line, so you need not worry about
your Bresenham having to choose right pixels. All you need to do is offset
the line to right memory offset.

The inner loop of this machine could look something like this:

zbufpointer = zbufbase + offset;
pixelpointer = pixelbase + offset;

while (--count >= 0) {
  off = *precalculatedline++;
  if (z > zbufpoiner[off]) {
    zbufpointer[off] = z;
    pixelpointer[off] = texture(x,y);
  }
  x += dx;
  y += dy;
}

There is an error of about 0.5 pixel-lengths, since the pixels lying on
the constant-z lines are rounded to nearest pixels.

Another error can also be seen in the above picture, the line marked
with 0's has a small "gap" in it, what should we do with it?

Happy programming!

--dm
--

  Hannu    dm@stekt.oulu.fi  || You have been hacking too long when you
 Helminen dm@phoenix.oulu.fi || talk of people as users (or end-users)
----------------------------- VLA.NFO -----------------------------------
        ??????????? (% VLA Presents Intro To Starfields %) ???????????
        ?                                                            ?
        ??????????????????? Written ?y : Draeden ?????????????????????

???????????????????????????  VLA Members Are  ????????????????????????????????


                        (? Draeden - Main Coder ?)
                  (? Lithium - Coder/Ideas/Ray Tracing ?)
                   (? The Kabal - Coder/Ideas/Artwork ?)
                      (? Desolation - Artwork/Ideas ?)

?????????????????????????? The Finn - Mods/Sounds ??????????????????????????????


   ???????????????????? Contact Us On These Boards: ???????????????????????
   ?                                                                      ?
   ?   % Phantasm BBS .................................. (206) 232-5912   ?
   ?   * The Deep ...................................... (305) 888-7724   ?
   ?   * Dark Tanget Systems ........................... (206) 722-7357   ?
   ?   * Metro Holografix .............................. (619) 277-9016   ?
   ?                                                                      ?
   ?       % - World Head Quarters      * - Distribution Site             ?
   ????????????????????????????????????????????????????????????????????????

     Or Via Internet Mail For The Group : tkabal@carson.u.washington.edu

                      Or to reach the other members :

                       - draeden@u.washington.edu -

                       - lithium@u.washington.edu -

                     - desolation@u.washington.edu -



?????????????????????????????????????????????????????????????????????????????
? STARS.TXT ?
?????????????


????????????????????????????????????????????????????????????????????????????
;
;     TITLE: Star field
;WRITTEN BY: DRAEDEN
;      DATE: 03/15/93
;
;     NOTES:
;
;ASSOCIATED FILES:
;
;       STARGEN.BAS =>  Basic program that generates a set of 'randomized'
;                       numbers.  Creates STARRND.DW
;
;       STARS.ASM   =>  The asm file.
;
;       STARRND.DW  =>  File that contains a set of shuffled numbers order.
;                       Used to create 'random' star field.
;
????????????????????????????????????????????????????????????????????????????

    A star field is just a series of 3d point plotted onto a 2d plane (your
screen).  The movement effect is achieved by simply decreasing the Z
cordinate and redisplaying the results.  The formula for the 3d to 2d
conversion is:

????????????????
    ScreenX = ScreenDist * Xpos / Zpos
    ScreenY = ScreenDist * Ypos / Zpos
???????????????? 

    This should make perfect sense.  As the object gets futher away, (X,Y)
cordinates converge to (0,0).  The screen dist is how far away the 'eye' is
from the screen, or, as I like to think of it, the window.  Naturally, as you
get closer to the window, your field of view is greatly enhanced (you can see
more).  But, because we can't make the monitor bigger, we have to shrink the
data that is being displayed.  And when we have a large screen distance, we
should see less of the virtual world, and the objects should appear bigger.
When this formula is translated into assembler, you would immediatly decide
that 256 is the best screen distance.  Why?  Multiplying by 256 on the 386 is
as simple as this:

???????????????? 
;we want to multiply ax by 256 and put it into dx:ax to set up for division

    movsx   dx,ah   ;3 cycles
    shl     ax,8    ;3 cycles -- total 6

;or we could do it the 'normal way'...

    mov     dx,256  ;2 cycles, but we can have any screen distance
    imul    dx      ;9-22 cycles on a 386, 13-26 on a 486
                    ;a total of 11-28 cycles!
???????????????? 

    If you'll take note, the 6 cycle trick is AT LEAST 5 cycles faster than
the imul.  Anyway... I bet you really don't care about a few cycles at this
point, so I won't spend much more time on it... 
    So, as you can see, the math part of it is easy..  the hard part is the
what's left.  You need a routine that creates a star, presumably random, and
another routine that displays all the stars and advances them.  Well, that's
how I broke it into subroutines...

    For the routine that creates the star you need it to:

    1)  See if we already have enough stars going (is NUMSTARS > MAXSTARS ?)
    2)  If there's room, scan for the first open slot... 
    3)  Now that we've found where to put it, create a star by getting a set
        of random numbers for the (X,Y) and setting the Z to the maximum.
        Also select a color for the star.

    The display routine would need to:

    1)  Erase the old star.
    2)  Calculate the screen X & Y positions for the new position.  Are they 
        inside the screen boundries?  If not, 'kill' the star, otherwise 
        display it.  The shade of the color to use must be calculated by 
        using the Z cordinate. Color = BaseColor + Zpos / 256
    3)  Decrease the Zpos.

    And the main routine would:

    1)  Call MakeStars
    2)  Wait for verticle retrace
    3)  Call DisplayStars
    4)  Check for keypress, if there is one, handle it, if its not one we're
        looking for then exit program.
    5)  Loop to step 1

    To impliment this, we need to create an array of records which has enough
room for MAXSTARS.  The record would contain the (X,Y,Z) cordinates, the
OldDi and the base color for the star.  To create a star, it first checks to
see if there is room.  If there is, then we scan through the array
looking%wor an open slot.  If we don't find an empty space, then we don't
create a star.  We create the star by grabbing a pair of (X,Y) cordinates
from the list of 'random' numbers and set the Z to MAXZPOS.  Then, increase
NUMSTARS and return.

    In displaying the star, we would like to only have to calculate DI once.
So we save off a copy of DI in an array after we calculate it for the drawing
so that erasing the dot is really quick.  Next we calculate the new DI for
the dot.  This is done by using the formula mentioned above and this one: 

???????????????? 

    DI = ScreenY * ScreenWidth + ScreenX

???????????????? 

    When doing the math, care must be taken to make sure that:

        a) the Zpos is not zero and X*256/ZPOS is not greater than 32767.
            will cause a DIVIDE BY ZERO or a DIVIDE OVERFLOW

        b) SY and SX do not go outside the border of the screen.

    If either of these conditions are broken, the star must be terminated and
calculations for that star must be aborted.  Actually, Zpos = 0 is used to
signify a nonactive star.  To terminate the star, you'd simply change its
zpos to 0 and decrease NUMSTARS.

    To create the different shades, I used:

????????????????

  Color = BaseColor + Zpos/256

????????????????

    I used 256 as the number to divide by because that enables me to do no
dividing at all- I just use AH, because AH = AX / 256 (AH is the upper 8 bits
of AX). This relation suggests that the MAXZPOS shoul be 16*256 for 16
shades.  So, the MAXZPOS = 4096.  The palette will have to be set up so that
the shades go from light to black (lower # is lighter).  Simple enough. (I
hope.)

???????????????????????????????? 
        RANDOM NUMBERS
???????????????????????????????? 

    Well, not truly random numbers, but random enough for a starfield.

    The problem:
        There is no way on a PC to create truly random numbers with 
        great speed.

    Solution:
        Don't use truly random numbers.  Use a chart of non-repeating,
        shuffled numbers that fall within your desired range.  That way
        the stars will be evenly spread out and the creation of a new star
        is incredably fast. ( A few MOV instructions) All you have to is grab
        the number and increase the NEXTRANDOM pointer.  I chose to fill in
        the array half with positive numbers, half with negative with a
        minimum distance of 10 from 0.  I did this so that no stars will
        'hit' the screen and just vanish.  That doesn't look too good.

    Here's the BASIC file that made my numbers for me...

???????????????? 


    NumStars = 400
    dim     RndArray(NumStars)
    randomize (timer)

    'fill the array with numbers from -Numstars/2 to -10
    'and from 10 to Numstars/2

    i=10
    for r = 0 to NumStars/2
        RndArray(r)=i
        i=i+1
    next

    i=-10
    for r = NumStars/2 to NumStars
        RndArray(r)=i
        i=i-1
    next

    'randomly shuffle them..

    print "Total numbers: ";NumStars
    print "Shuffling - Please wait... "

    for q = 1 to numstars/5
        for r = 0 to NumStars
            swnum1 = int(rnd*NumStars+.5)
            swap RndArray(swnum1),RndArray(r)
        next
    next

    'write the numbers neatly to a file

    open "starrnd.dw" for output as 1
    cc= 0          ' CC is my "Column Control"
    print#1, "StarRnd dw ";:print#1, using"####";RndArray(0)
    for r = 1 to NumStars

        IF cc=0 THEN   ' is this the first one on the line?
            print#1, "dw ";:print#1, using"####" ;RndArray(r);
        ELSE 
            print#1, ",";:print#1, using"####"; RndArray(r);
        END IF

        cc=cc+1:if cc= 10 then cc=0:print#1," "   'goto the next line
    next
    close #1

???????????????? 

    This brings up another point.  Whenever you can write a program in a
higher level language to create data for you, do it.  It sure beats typing
then in by hand.  For instance, the palette was made using the REPT macro,
the actual data is created by the compiler at compile time.  Doing it that 
way happens to be a whole lot easier than typing in every byte.

    Last minute note: I rigged the plus and minus keys up so that they
control the 'Warpspeed' can be from 0 - MaxWarp, which I set to 90 or 
something like that.

?????????????????????????????????????????????????????????????????????????????

   Well, that's it for now.  See INFO.VLA for information on contacting us.

   I would like some suggestions on what to write code for.  What would you
   like to see done?  What code would you like to get your hands on?

   Send question, comments, suggestions to draeden@u.washington.edu or post
    on Phantasm BBS.


?????????????????????????????????????????????????????????????????????????????
? STARS.ASM ?
?????????????


?????????????????????????????????????????????????????????????????????????????
;
;     TITLE: Star field
;WRITTEN BY: DRAEDEN
;      DATE: 03/15/93
;
;     NOTES: Need 386 to execute.
;
;ASSOCIATED FILES:
;
;       STARGEN.BAS =>  Basic program that generates a set of 'randomized'
;                       numbers.  Creates STARRND.DW
;
;       STARS.TXT   =>  The text file that explains starfields...
;
;       STARRND.DW  =>  File that contains a set of shuffled numbers.
;                       Used to create 'random' star field.
;
????????????????????????????????????????????????????????????????????????????

    DOSSEG
    .MODEL SMALL
    .STACK 200h
    .CODE
    .386
    ASSUME CS:@CODE, DS:@CODE
    LOCALS

;=== GLOBALS
;=== Data Includes

INCLUDE starrnd.dw      ;file that has label StarRnd numbers

;=== DATA Structures

    Star_Struc      STRUC
        X       dw  0
        Y       dw  0
        Z       dw  0
        OldDi   dw  0       ;where to erase last dot
        Color   db  0       ;BASE color. a number 0-16 is added to it
    Star_Struc      ENDS

    StarStrucSize = 9       ;number of bytes per entry

;=== DATA

ScreenWidth EQU 320
ScreenHeight EQU 200

NumRnds     EQU 400     ;number of random numbers defined

MaxZpos     EQU 4096
MinZpos     EQU 2
MaxStars    EQU 190
NumColors   EQU 5       ;number of Base colors in the Color Chart

WarpSpeed   dw  15      ;how quickly the stars move toward ya
MaxWarp     EQU 90

Xindex      dw  30      ;index into the StarRnd chart for X & Y
Yindex      dw  230     ; -note they must be different; set em the same to
                        ;see why
Cindex      dw  0       ;index into ColorChart

ColorChart  db  0,16,32,48,64,80    ;a list of base colors (-1)

Stars       Star_Struc MaxStars DUP (<>) ;where all the data is held
NumActive   dw  0       ;number of stars active

Palette     db  3 dup (0)   ;the palette.. first entrie is BG color (black)
    i = 15
    REPT    16
            db  2*i,3*i,4*i
        i=i-1
    ENDM
    i = 15
    REPT    16
            db  2*i,2*i,4*i
        i=i-1
    ENDM
    i = 15
    REPT    16
            db  3*i,3*i,4*i
        i=i-1
    ENDM
    i = 15
    REPT    16
            db  3*i,2*i,4*i
        i=i-1
    ENDM
    i = 15
    REPT    16
            db  3*i,3*i,3*i
        i=i-1
    ENDM
    i = 15
    REPT    16
            db  2*i,4*i,3*i
        i=i-1
    ENDM

;=== Code Includes
;=== SUBROUTINES

    ;finds 1st available slot for a star and puts it there
MakeStar PROC NEAR
    pusha
    mov     ax,cs
    mov     es,ax
    mov     ds,ax

    cmp     [NumActive],MaxStars    ;is there room for another star?
    jae     NoEmptySpace            

    ;search for 1st available slot

    mov     si,0
TryAgain:
    cmp     word ptr [Stars.Z+si],0     ;is this slot empty?
    je      GotOne                      ;yes, go fill it

    add     si,StarStrucSize
    cmp     si,MaxStars*StarStrucSize
    jb      TryAgain
    jmp     NoEmptySpace

GotOne:         ;si points to the record for the star to fill
    mov     di,[Yindex]         ;grab index for Ypos
    add     di,di               ;multiply by 2 to make it a WORD index
    mov     ax,[StarRnd+di]     ;get the number
    shl     ax,3                ;multiply by 8- could been done in BAS file
    mov     [Stars.Y+si],ax     ;and save off the number
    
    mov     di,[Xindex]         ;grab index for Xpos
    add     di,di               ;... same as above, but for Xpos
    mov     ax,[StarRnd+di]
    shl     ax,3
    mov     [Stars.X+si],ax

    mov     [Stars.Z+si],MaxZpos    ;reset Zpos to the max
    inc     [NumActive]             ;we added a star so increase the counter

    mov     di,[Cindex]             ;grab the color index
    mov     al,[ColorChart+di]      ;grab the BaseColor for the star
    mov     [Stars.Color+si],al     ;save it in the record

    ;increase all the index pointers

    inc     [Cindex]                ;increases the color counter
    cmp     [Cindex],NumColors
    jb      OkColor
    mov     [Cindex],0
OkColor:
    inc     [Yindex]                ;increases Yindex
    cmp     [Yindex],NumRnds        ;note that for this one we
    jb      YindNotZero             ; subtract NumRnds from Yindex if we
    sub     [Yindex],NumRnds        ; go off the end of the chart
YindNotZero:
    inc     [Xindex]                ;increase Xindex
    cmp     [Xindex],NumRnds        ;have we gone through the entire chart?
    jb      XindNotZero             ;nope...

;This clever bit of code makes more use out of the chart by increasing Yindex
; one additional unit each time Xindex goes through the entire chart... the
; result is nearly NumRND^2 random non-repeating points
        
    inc     [Yindex]                ;yes, so change Yindex so that we get a
    mov     ax,[Yindex]             ;new set of random (x,y)
    cmp     ax,[Xindex]             ;does Xindex = Yindex?
    jne     NotTheSame              ;if the index were the same, you'd see 
                                    ;a graph of the line Y = X, not good...
    inc     [Yindex]                ;if they are the same, inc Yindex again
NotTheSame:
    mov     [Xindex],0              ;reset Xindex to 0
XindNotZero:                        ;all done making the star...

NoEmptySpace:
    popa
    ret
MakeStar ENDP

DisplayStars PROC NEAR
    pusha
    mov     ax,cs
    mov     ds,ax
    mov     ax,0a000h
    mov     es,ax

    mov     si,0
DispLoop:
    mov     cx,[Stars.Z+si]
    or      cx,cx                   ;if Zpos = 0 then this star is dead...
    je      Cont                    ;continue to the next one- skip this one

    mov     di,[Stars.OldDi+si]     ;grab old Di
    mov     byte ptr es:[di],0      ;erase the star
    
    cmp     cx,MinZpos
    jl      TermStar                ;if Zpos < MinZpos then kill the star

    mov     ax,[Stars.Y+si]
    movsx   dx,ah                   ;'multiply' Ypos by 256
    shl     ax,8
    
    idiv    cx                      ;and divide by Zpos
    add     ax,ScreenHeight/2       ;center it on the screen
    mov     di,ax
    cmp     di,ScreenHeight         ;see if the star is in range. 
    jae     PreTermStar             ; If not, kill it
    imul    di,ScreenWidth          ; DI = Y*ScreenWidth

    mov     ax,[Stars.X+si]
    movsx   dx,ah                   ;multiply Xpos by 256
    shl     ax,8

    idiv    cx                      ;and divide by Zpos
    add     ax,ScreenWidth/2        ;center it on the screen
    cmp     ax,ScreenWidth          ;are we inside the screen boundries?
    jae     PreTermStar
    add     di,ax                   ; DI = Y * ScreenWidth + X

    mov     [Stars.OldDi+si],di     ;save old di

    ;calculate the color below

    add     ch,cs:[Stars.Color+si]  ;i'm dividing cx (the zpos) by 256 and
                                    ; putting the result in ch and adding
                                    ; the base color to it in one instruction
    mov     es:[di],ch              ;put the dot on the screen

    mov     ax,cs:[WarpSpeed]
    sub     cs:[Stars.Z+si],ax      ;move the stars inward at WarpSpeed

Cont:
    add     si,StarStrucSize        ;point to next record
    cmp     si,MaxStars*StarStrucSize   ;are we done yet?
    jb      DispLoop
    popa
    ret

PreTermStar:
    mov     [Stars.Z+si],1  ;this is here so that the star will get erased
    jmp     short Cont      ;next time through if I just went off and killed
                            ;the star, it would leave a dot on the screen
TermStar:
    mov     [Stars.Z+si],0  ;this actually kills the star, after it has
    dec     [NumActive]     ;been erased
    jmp     short Cont

DisplayStars ENDP

;=== CODE

START:
    mov     ax,cs
    mov     ds,ax
    mov     es,ax

    mov     ax,0013h                ;set vid mode 320x200x256 graph
    int     10h
    
    mov     dx,offset Palette
    mov     ax,1012h                ; WRITE palette 
    mov     bx,0                    
    mov     cx,256                  ;write entire palette
    int     10h                     ;doesn't matter if we didnt define it all

StarLoop:
    call    MakeStar        ;make stars 2x as thick
    call    MakeStar

    mov     dx,3dah
VRT:
    in      al,dx
    test    al,8
    jnz     VRT             ;wait until Verticle Retrace starts

NoVRT:
    in      al,dx
    test    al,8
    jz      NoVRT           ;wait until Verticle Retrace Ends

    call    DisplayStars

    mov     ah,1            ;check to see if a char is ready
    int     16h
    jz      StarLoop        ;nope, continue
    
    mov     ah,0
    int     16h             ;get the character & put in AX

    cmp     al,"+"          ;compare ASCII part (al) to see what was pressed
    jne     NotPlus

    inc     [WarpSpeed]
    cmp     [WarpSpeed],MaxWarp
    jbe     StarLoop

    mov     [WarpSpeed],MaxWarp
    jmp     StarLoop

NotPlus:
    cmp     al,"-"
    jne     NotMinus

    dec     [WarpSpeed]
    cmp     [WarpSpeed],0
    jge     StarLoop

    mov     [WarpSpeed],0
    Jmp     StarLoop

NotMinus:

    mov     ax,0003h        ;set 80x25x16 char mode
    int     10h
    mov     ax,4c00h        ;return control to DOS
    int     21h
END START



?????????????????????????????????????????????????????????????????????????????
? STARRND.DW ?
??????????????


StarRnd dw  166
dw   67, 102,  46,-173,-154,-210,-192, 173,-196, -81 
dw  -50,  36,  50,-200, -95, 209, -16,-179, -30,  18 
dw  174, 197, 127,  71,  29,-121,-160,-176,  19, -52 
dw -185,  89, 172,  74,-156, 157,-125, 144, -34,  69 
dw   17, -40,  64, -98,-153, 125, 160, 140,-204, 141 
dw  137,-165, -14, 154,-146, 119, 123, 165,-130, 168 
dw -180, 143,  52, 107,-107,-102,  57,  27, 117,  37 
dw  126,  15, -89, 184, 116, 183, -99,-139, 150, 188 
dw   38,  90,  93,-194, 207,-187,  62,  59, 196,  12 
dw -174,  54, 146,-137, 198, 162, 155,-163, -77,-144 
dw  191,-132, -43, 151,-103,  20, -46,  13,-140,  31 
dw  130,-169,-188, 109, -33,-150,-170,  68, -75,-201 
dw -100,-171, -19, -61,-206, 149,  99, -76,-186, -44 
dw -178,  34,  61,  28, 114, 199, 201, -83, -27,  63 
dw  -38, 204, 208,-112,-208, 122, -90,  23,-122, 161 
dw   35,-168, 170,-164,-151,  75, -60,-109,  85, 193 
dw   45,-175,-134, 205, -21,  49, 133, -85, -47, -37 
dw  -29, -96, -66,  73,-118, 147, -53, 120, 153,-155 
dw  -11,  11,  95, -26, 134,-145, -49, -74,  42,-124
dw  189, -42,  92,-167,  88,-126,-129,-108,-193, 195 
dw  190,-106,-117, 203,  84, 139,-123, -94, -88,-158 
dw  181, -97, -20,  82, -57, 112, -35,  14, -56, -58 
dw  200,  80,-183, 106,  87,  30,  51, -28,  98, -12 
dw -191,-128, -13,-184, 136,  43,-166, -62, -73,-116 
dw  -31,-135,-101,  25,  41, -82, 110,  10, -45, -41 
dw   97, 175, 138, 171,  72,-133,-157,  58,-104, 187 
dw  192, -68, -87, 169,-110,  91, 129, 104, -70,-114 
dw -138,-115,-141, -67,-195, -79, -69,  40,-147, -80 
dw -119, 128, 152,-209,  83,  53, 159,  66,-190,  81 
dw  -92, -10,-181, 135,  60,  33, -25,  70,  22, -72 
dw  103, -23, 131,  79, -64,  55, -86, -32,-182,-136 
dw   26, -54,-172,-148, 148, -65,-152,-207, -39, -71 
dw   65, 179,-177,  24, 118, -59, -63,  44, 105, 206 
dw  178, -84,-202, 132, 186, -17,  76, 176, -22, 177 
dw -198,-159,-162,  78,  77, -55,-120,-203,-113, 156 
dw -189,-197, 124, 121,-142, -15,-205,  56, 158, -18 
dw  -93,-161,  39,  48, 101, -91, 182,-127, 108, 111 
dw  -36,-143,  21,-149, -78, -48, 164, 202, 185, 180 
dw  -51,-199, 100, 194,  32, -24, 142,  86,-111,  47
dw  115,-105,  16, 167,  94, 163,  96, 113,-131, 145


?????????????????????????????????????????????????????????????????????????????
? STARGEN.BAS ?
???????????????


'
'Written by: Draeden /VLA
'      Date: 03/15/93
'
'     Notes: Used for generating 'random' data for Stars.asm
'


        NumStars = 400
        dim     RndArray(NumStars)
        randomize (timer)

        'fill the array with numbers from -Numstars/2 to -10
        'and from 10 to Numstars/2

        i=10
        for r = 0 to NumStars/2
                RndArray(r)=i
                i=i+1
        next
        i=-10
        for r = NumStars/2 to NumStars
                RndArray(r)=i
                i=i-1
        next

        'randomly shuffle them..

        print "Total numbers: ";NumStars
        print "Shuffling - Please wait... "

        for q = 1 to numstars/5
                for r = 0 to NumStars
                        swnum1 = int(rnd*NumStars+.5)
                        swap RndArray(swnum1),RndArray(r)
                next
        next

        'write the numbers neatly to a file

        open "starrnd.dw" for output as 1
        cc= 0
        print#1, "StarRnd dw ";:print#1, using"####";RndArray(0)
        for r = 1 to NumStars

                IF cc=0 THEN
                        print#1, "dw ";:print#1, using"####" ;RndArray(r);
                ELSE 
                        print#1, ",";:print#1, using"####"; RndArray(r);
                END IF

                cc=cc+1:if cc= 10 then cc=0:print#1," "
        next
        close #1


         ???????????????????????????????????????????????????????
         ?        How to code youre own "Fire" Routines        ?
         ???????????????????????????????????????????????????????

    Hiya Puppies!

    Hopefully this information file will give you all the information you
    need to code youre own fire routines, seen in many demo's and also to
    actually take it all further and develop youre own effects..

    Ok, so lets get on....

    Setting up


    first thing we need to do is set up two arrays, the size of the arrays
    depends on the many things, screen mode, speed of computer etc, its not
    really important, just that they should both be the same size... I'll
    use 320x200 (64000 byte) arrays for this text, because that happens to
    be the size needed for using a whole screen in vga mode 13h.

    The next thing we need to do is set a gradient palette, this can be
    smoothly gradiated through ANY colours, but for the purpose of this
    text lets assume the maximum value is a white/yellow and the bottom
    value is black, and it grades through red in the middle.

    Ok, we have two arrays, lets call them startbuffer and screenbuffer, so
    we know whats going on. Firstly, we need to setup an initial value for
    the start buffer...  so what we need is a random function, that returns
    a value between 0 and 199 (because our screen is 200 bytes wide) this
    will give us the initial values for our random "hotspots" so we do this
    as many times as we think is needed, and set all our bottom line values
    of the start buffer to the maximum colour value. (so we have the last
    300 bytes of the start buffer set randomly with our maximum colour,
    usually if we use a full palette this would be 255 but it can be
    anything that is within our palette range.)

    Ok, thats set the bottom line up.. so now we need to add the effect,
    for this we need to copy the start buffer, modify it and save it to the
    screenbuffer, we do this by averaging the pixels (this is in effect
    what each byte in our array represents) surrounding our target....

    It helps to think of these operations in X,Y co-ordinates....

    Lets try a little diagram for a single pixel.....

    This is the startbuffer             This is our screenbuffer

    ?????????????????????               ?????????????????????
    ?0,0?0,1?0,2?0,3?0,4? etc...        ?   ?   ?   ?   ?   ?
    ?????????????????????               ?????????????????????
    ?1,0?1,1?1,2?1,3?1,4? etc..         ?   ?X,Y?   ?   ?   ?
    ?????????????????????               ?????????????????????
    ?2,0?2,1?2,2?2,3?2,4? etc..         ?   ?   ?   ?   ?   ?
    ?????????????????????               ?????????????????????

    Here we're going to calulate the value for X,Y (notice I didnt start at
    0,0 for calculating our new pixel values?? thats because we need to
    average the 8 surrounding pixels to get out new value.. and the pixels
    around the edges wouldn't have 8 pixels surrounding them), so what we
    need to do to get the value for X,Y is to average the values for all
    the surrounding pixels... that means adding 0,0 0,1 0,2 + 1,0 1,2 + 2,0
    2,1 2,2 and then dividing the total by 8 (the number of pixels we've
    takes our averages from), but there's two problems still facing us..

    1) The fire stays on the bottom line....
    2) Its slow....
    3) The fire colours dont fade...

    Ok, so first thing, we need to get the fire moving! :) this is really
    VERY easy. All we need to do is to take our average values from the
    pixel value BELOW the pixel we are calculating for, this in effect,
    moves the lines of the new array up one pixel... so for example our old
    X,Y value we were calculating for was 1,1 so now we just calculate for
    2,1 and put the calculated value in the pixel at 1,1 instead.. easy..

    The second problem can be approached in a few ways.. first and easiest
    is to actually calculate less pixels in our averaging.. so instead of
    the 8 surrounding pixels we calculate for example, 2 pixels, the one
    above and the one below our target pixel (and divide by 2 instead of 8)
    this saves a lot of time, another approach is to use a screen mode,
    where you can set 4 pixels at a time, or set up the screen so that you
    can use smaller arrays (jare's original used something like 80X50 mode)
    which in effect reduces to 1/4 the number of pixels needed to be
    calculated.

    The third problem is just a matter of decrementing the calculated value
    that we get after averaging by 1 (or whatever) and storing that value.

    Last but not least, we need to think about what else can be done...
    well, you can try setting a different palette, you can also try setting
    the pixel value we calculated from to another place, so say, instead of
    calculating from one pixel below our target pixel, you use one pixel
    below and 3 to the right of our target... FUN! :))

    Well, I hope I didnt confuse you all too much, if you need anything
    clearing up about this, then email me at pc@espr.demon.co.uk ok?

    Written by  Phil Carlisle (aka Zoombapup // CodeX) 1994.

ZSoft PCX File Format Technical Reference Manual        
        
        
        
Introduction                            2 
Image  File (.PCX) Format               3 
ZSoft .PCX FILE HEADER FORMAT           4 
Decoding .PCX Files                     6 
Palette Information Description         7 
EGA/VGA 16 Color Palette Information    7 
VGA 256 Color Palette Information       7 
24-Bit .PCX Files                       8 
CGA Color Palette Information           8 
CGA Color Map                           8 
PC Paintbrush Bitmap Character Format   9 
Sample "C" Routines                    10 
FRIEZE Technical Information           14 
General FRIEZE Information             14 
7.00 and Later FRIEZE                  14 
FRIEZE Function Calls                  15 
FRIEZE Error Codes                     18 





Introduction

This booklet was designed to aid developers and users in understanding
the technical aspects of the .PCX file format and the use of FRIEZE.
Any comments, questions or suggestions about this booklet should be
sent to:

        ZSoft Corporation
        Technical Services
        ATTN: Code Librarian
        450 Franklin Rd. Suite 100
        Marietta, GA  30067



Technical Reference Manual information compiled by:
Dean Ansley


Revision 5

To down load additional information and the source for a complete
Turbo Pascal program to show .PCX files on a CGA/EGA/VGA graphics
display, call our BBS at (404)427-1045.  You may use a 9600 baud 
modem or a 2400 baud standard modem.  Your modem should be set for
8 data bits, 1 stop bit, and NO parity.

Image  File (.PCX) Format

If you have technical questions on the format, please do not call
technical support.  ZSoft provides this document as a courtesy to
its users and developers.  It is not the function of Technical Support
to provide programming assistance.  If something is not clear, leave a
message on our BBS, Compuserve, or write us a letter at the above address.

The information in this section will be useful if you want to write a
program to read or write PCX files (images).  If you want to write a
special case program for one particular image format you should be able
to produce something that runs twice as fast as "Load from..." in
PC Paintbrush.  

Image files used by PC Paintbrush product family and FRIEZE (those with a
.PCX extension) begin with a 128 byte header.  Usually you can ignore this
header, since your images will probably all have the same resolution.  If
you want to process different resolutions or colors, you will need to
interpret the header correctly.  The remainder of the image file consists
of encoded graphic data.  The encoding method is a simple byte oriented
run-length technique.  We reserve the right to change this method to
improve space efficiency.  When more than one color plane is stored in
the file, each line of the image is stored by color plane (generally ordered
red, green, blue, intensity), As shown below.

Scan line 0:         RRR...        (Plane 0)
                     GGG...        (Plane 1)
                     BBB...        (Plane 2)
                     III...        (Plane 3)
Scan line 1:         RRR...
                     GGG...
                     BBB...
                     III...        (etc.)

The encoding method is:
    FOR  each  byte,  X,  read from the file
        IF the top two bits of X are  1's then
            count = 6 lowest bits of X
            data = next byte following X
        ELSE
            count = 1
            data = X

Since the overhead this technique requires is, on average,  25% of
the non-repeating data and is at least offset whenever bytes are repeated,
the file storage savings are usually considerable.

ZSoft .PCX FILE HEADER FORMAT

Byte      Item          Size   Description/Comments 
 0         Manufacturer 1      Constant Flag, 10 = ZSoft .pcx 
 1         Version      1      Version information 
                               0 = Version 2.5 of PC Paintbrush 
                               2 = Version 2.8 w/palette information 
                               3 = Version 2.8 w/o palette information 
                               4 = PC Paintbrush for Windows(Plus for
                                  Windows uses Ver 5) 
                               5 = Version 3.0 and > of PC Paintbrush
                                  and PC Paintbrush +, includes
                                  Publisher's Paintbrush . Includes
                                  24-bit .PCX files 
 2         Encoding      1     1 = .PCX run length encoding 
 3         BitsPerPixel  1     Number of bits to represent a pixel
                                  (per Plane) - 1, 2, 4, or 8 
 4         Window        8     Image Dimensions: Xmin,Ymin,Xmax,Ymax 
12         HDpi          2     Horizontal Resolution of image in DPI* 
14         VDpi          2     Vertical Resolution of image in DPI* 
16         Colormap     48     Color palette setting, see text 
64         Reserved      1     Should be set to 0. 
65         NPlanes       1     Number of color planes 
66         BytesPerLine  2     Number of bytes to allocate for a scanline
                                  plane.  MUST be an EVEN number.  Do NOT
                                  calculate from Xmax-Xmin. 
68         PaletteInfo   2     How to interpret palette- 1 = Color/BW,
                                  2 = Grayscale (ignored in PB IV/ IV +) 
70         HscreenSize   2     Horizontal screen size in pixels. New field
                                  found only in PB IV/IV Plus 
72         VscreenSize   2     Vertical screen size in pixels. New field
                                  found only in PB IV/IV Plus 
74         Filler       54     Blank to fill out 128 byte header.  Set all
                                  bytes to 0 

NOTES:

All sizes are measured in BYTES. 

All variables of SIZE 2 are integers.  


image was created (either printer or scanner); i.e. an image which was
scanned might have 300 and 300 in each of these fields.

Decoding .PCX Files

First, find the pixel dimensions of the image by calculating
[XSIZE = Xmax - Xmin + 1] and [YSIZE = Ymax - Ymin + 1].  Then calculate
how many bytes are required to hold one complete uncompressed scan line:

TotalBytes = NPlanes * BytesPerLine

Note that since there are always an even number of bytes per scan line,
there will probably be unused data at the end of each scan line.  TotalBytes
shows how much storage must be available to decode each scan line, including
any blank area on the right side of the image.  You can now begin decoding
the first scan line - read the first byte of data from the file.  If the
top two bits are set, the remaining six bits in the byte show how many times
to duplicate the next byte in the file.  If the top two bits are not set,
the first byte is the data itself, with a count of one.

Continue decoding the rest of the line.  Keep a running subtotal of how
many bytes are moved and duplicated into the output buffer.  When the
subtotal equals TotalBytes, the scan line is complete.  There should always
be a decoding break at the end of each scan line.  But there will not be a
decoding break at the end of each plane within each scan line.  When the
scan line is completed, there may be extra blank data at the end of each
plane within the scan line.  Use the XSIZE and YSIZE values to find where
the valid image data is.  If the data is multi-plane, BytesPerLine shows
where each plane ends within the scan line.

Continue decoding the remainder of the scan lines (do not just read to
end-of-file).  There may be additional data after the end of the image
(palette, etc.)

Palette Information Description

EGA/VGA 16 Color Palette Information

In standard RGB format (IBM EGA, IBM VGA) the data is stored as 16 triples.
Each triple is a 3 byte quantity of Red, Green, Blue values.  The values can
range from 0-255, so some interpretation may be necessary.  On an IBM EGA,
for example, there are 4 possible levels of RGB for each color.  Since
256/4 = 64, the following is a list of the settings and levels:

Setting                Level
   0-63                0
 64-127                1
128-192                2
193-254                3

VGA 256 Color Palette Information

ZSoft has recently added the capability to store palettes containing more
than 16 colors in the .PCX image file.  The 256 color palette is formatted
and treated the same as the 16 color palette, except that it is substantially
longer.  The palette (number of colors x 3 bytes in length) is appended to
the end of the .PCX file, and is preceded by a 12 decimal.  Since the VGA
device expects a palette value to be 0-63 instead of 0-255, you need to
divide the values read in the palette by 4.

To access a 256 color palette:

First, check the version number in the header; if it contains a 5 there is
a palette.

Second, read to the end of the file and count back 769 bytes.  The value
you find should be a 12 decimal, showing the presence of a 256 color palette.

24-Bit .PCX Files

24 bit images are stored as version 5 or above as 8 bit, 3 plane images.

24 bit images do not contain a palette.

Bit planes are ordered as lines of red, green, blue in that order.

CGA Color Palette Information

NOTE: This is no longer supported for PC Paintbrush IV/IV Plus.

For a standard IBM CGA board, the palette settings are a bit more complex.
Only the first byte of the triple is used.  The first triple has a valid
first byte which represents the background color.  To find the background,
take the (unsigned) byte value and divide by 16.  This will give a result
between 0-15, hence the background color.  The second triple has a valid
first byte, which represents the foreground palette.  PC Paintbrush supports
8 possible CGA palettes, so when the foreground setting is encoded between
0 and 255, there are 8 ranges of numbers and the divisor is 32.

CGA Color Map

Header Byte #16 

Background color is determined in the upper four bits.

Header Byte #19

Only upper 3 bits are used, lower 5 bits are ignored.  The first three bits
that are used are ordered C, P, I.  These bits are interpreted as follows:

c: color burst enable - 0 = color; 1 = monochrome

p: palette - 0 = yellow; 1 = white

i: intensity - 0 = dim; 1 = bright

PC Paintbrush Bitmap Character Format

NOTE: This format is for PC Paintbrush (up to Vers 3.7) and PC Paintbrush
Plus (up to Vers 1.65)

The bitmap character fonts are stored in a particularly simple format.  The
format of these characters is as follows:


Header

font width         byte                0xA0 + character width  (in pixels)
font height        byte                character height  (in pixels)

Character Width Table

char widths        (256 bytes)         each char's width + 1 pixel of kerning

Character Images

(remainder of the file)                starts at char 0  (Null)

The characters are stored in ASCII order and as many as 256 may be provided.
Each character is left justified in the character block, all characters take
up the same number of bytes.

Bytes are organized as N strings, where each string is one scan line of the
character.

For example, each character in a 5x7 font requires 7 bytes.  A 9x14 font
uses 28 bytes per character (stored two bytes per scan line in 14 sets of
2 byte packets).  Custom fonts may be any size up to the current maximum of
10K bytes allowed for a font file.  There is a maximum of 4 bytes per scan
line.

Sample "C" Routines

The following is a simple set of C subroutines to read data from a .PCX file.

/* This procedure reads one encoded block from the image file and stores a
count and data byte.

Return result:  0 = valid data stored, EOF = out of data in file */

encget(pbyt, pcnt, fid)
int *pbyt;        /* where to place data */
int *pcnt;        /* where to place count */
FILE *fid;        /* image file handle */
{
int i;
        *pcnt = 1;        /* assume a "run" length of one */
        if (EOF == (i = getc(fid)))
                return (EOF);
        if (0xC0 == (0xC0 & i))
                {
                *pcnt = 0x3F & i;
                if (EOF == (i = getc(fid)))
                        return (EOF);
                }
        *pbyt = i;
        return (0);
}
/* Here's a program fragment using encget.  This reads an entire file and
stores it in a (large) buffer, pointed to by the variable "bufr". "fp" is
the file pointer for the image */

int i;
long l, lsize;
     lsize = (long )hdr.BytesPerLine * hdr.Nplanes * (1 + hdr.Ymax - hdr.Ymin);
     for (l = 0; l < lsize; )             /* increment by cnt below */
                {
                if (EOF == encget(&chr, &cnt, fp))
                        break;
                for (i = 0; i < cnt; i++)
                        *bufr++ = chr;
                l += cnt;
                }

The following is a set of C subroutines to write data to a .PCX file.

/* Subroutine for writing an encoded byte pair (or single byte if it
doesn't encode) to a file. It returns the count of bytes written, 0 if error */

encput(byt, cnt, fid)
unsigned char byt, cnt;
FILE *fid;
{
  if (cnt) {
        if ((cnt == 1) && (0xC0 != (0xC0 & byt)))
                {
                if (EOF == putc((int )byt, fid))
                        return(0);     /* disk write error (probably full) */
                return(1);
                }
        else
                {
                if (EOF == putc((int )0xC0 | cnt, fid))
                        return (0);      /* disk write error */
                if (EOF == putc((int )byt, fid))
                        return (0);      /* disk write error */
                return (2);
                }
        }
   return (0);
}

/* This subroutine encodes one scanline and writes it to a file.
It returns number of bytes written into outBuff, 0 if failed. */

encLine(inBuff, inLen, fp)
unsigned char *inBuff;    /* pointer to scanline data */
int inLen;                        /* length of raw scanline in bytes */
FILE *fp;                        /* file to be written to */
{
unsigned char this, last;
int srcIndex, i;
register int total;
register unsigned char runCount;     /* max single runlength is 63 */
  total = 0;
  runCount = 1;
  last = *(inBuff);

/* Find the pixel dimensions of the image by calculating 
[XSIZE = Xmax - Xmin + 1] and [YSIZE = Ymax - Ymin + 1].  
Then calculate how many bytes are in a "run" */

  for (srcIndex = 1; srcIndex < inLen; srcIndex++)
        {
        this = *(++inBuff);
        if (this == last)     /* There is a "run" in the data, encode it */
                {
                runCount++;
                if (runCount == 63)
                        {
                        if (! (i = encput(last, runCount, fp)))
                                return (0);
                        total += i;
                        runCount = 0;
                        }
                }
        else                /* No "run"  -  this != last */
                {
                if (runCount)
                        {
                        if (! (i = encput(last, runCount, fp)))
                                return(0);
                        total += i;
                        }
                last = this;
                runCount = 1;
                }
        }        /* endloop */
  if (runCount)        /* finish up */
        {
        if (! (i = encput(last, runCount, fp)))
                return (0);
        return (total + i);
        }
  return (total);
}

FRIEZE Technical Information

General FRIEZE Information

FRIEZE is a memory-resident utility that allows you to capture and save
graphic images from other programs.  You can then bring these images into
PC Paintbrush for editing and enhancement.

FRIEZE 7.10 and later can be removed from memory (this can return you up
to 90K of DOS RAM, depending on your configuration). To remove FRIEZE from
memory, change directories to your paintbrush directory and type the word
"FRIEZE".

7.00 and Later FRIEZE

The FRIEZE command line format is:

FRIEZE {PD} {Xn[aarr]} {flags} {video} {hres} {vres} {vnum}
Where:
{PD}        Printer driver filename (without the .PDV extension)
{Xn[aarr]}
                X=S for Serial Printer, P for Parallel Printer, D for disk file.
                        (file is always named FRIEZE.PRN)
                n = port number
                aa = Two digit hex code for which return bits cause
                         an abort (optional)
                rr = Two digit hex code for which return bits cause
                        a retry (optional)
                NOTE:  These codes represent return values from serial or
                       parallel port  BIOS calls.  For values see and IBM
                       BIOS reference (such as Ray Duncan's Advanced MS-DOS
                       Programming).
{flags}Four digit hex code
        First Digit controls Length Flag
        Second Digit controls Width Flag
                Third Digit controls Mode Flag
                Fourth Digit controls BIOS Flag
                        0 - None
                        1 - Dual Monitor Present
                        2 - Use internal (true) B/W palette for dithering
                                2 color images
                        4 - Capture palette along with screen IN VGA ONLY
                                Frieze 8.08 & up ONLY)

NOTE: The length, width and mode flags are printer driver specific.
See PRINTERS.DAT on disk 1 (or Setup Disk) for correct use.  In general
width flag of 1 means wide carriage, and 0 means standard width.  Length
flag of 0 and mode flag of 0 means use default printer driver settings.

If you need to use more than one BIOS flag option, add the needed flag values
and use the sum as the flag value.

{video}       Video driver combination, where the leading digit signifies the
                high level video driver and the rest signifies the low
                level video driver
                Example = 1EGA - uses DRIVE1 and EGA.DEV
{hres}        Horizontal resolution of the desired graphics mode
{vres}        Vertical resolution of the desired graphics mode
{vnum}        Hardware specific parameter (usually number of color planes)

Note: The last four parameters can be obtained from the CARDS.DAT file,
in your PC Paintbrush product directory.


FRIEZE Function Calls

FRIEZE is operated using software interrupt number 10h (the video interrupt
call).

To make a FRIEZE function call, load 75 (decimal) into the  AH register and
the function number into the CL register, then either load AL with the
function argument or load ES and BX with a segment and offset which point
to the function argument. Do an int 10h. FRIEZE will return a result code
number in AX.  All other registers are preserved.  In general, a result
code of 0 means success and other values indicate errors.  However, function
20 (get Frieze Version) behaves differently; see below.
No.      Definition         Arguments
0        Reserved
1        Load Window
                            ES:BX - string  (filename to read from)
2        Save Window
                            ES:BX - string  (filename to write to)
3        Reserved
4        Reserved        
6        Reserved        
7        Set Window Size
                            ES:BX - 4 element word vector of window settings:
                            Xmin, Ymin, Xmax, Ymax
8        Reserved
9        Set Patterns        
                            ES:BX - 16 element vector of byte values
                            containing the screen-to-printer color                                          correspondence
10        Get Patterns
                            ES:BX - room for 16 bytes as above
11        Set Mode
12,13,14  Reserved
15        Get Window
                            ES:BX - room for 4 words of the current window
                            settings
16         Set Print Options
                            ES:BX - character string of printer options.
                            Same format as for the FRIEZE command.
17, 18, 19        Reserved
20        Get FRIEZE Version.
                            AH gets the whole number portion and AL gets the
                            decimal portion of the version number.  (eg. for
                            Freize vesion 7.41, AH will contain 7 and AL will
                            contain 41.  If AH =0, you are calling a pre-7.0
                            version of FRIEZE).
21        Set Parameters
                            ES:BX points to an 8 word table  (16 bytes) of
                            parameter settings: TopMargin, LeftMargin,
                            HSize,VSize, Quality/Draft Mode, PrintHres,
                            PrintVres, Reserved.
                            Margins and sizes are specified in hundredths
                            of inches.
                                    Q/D mode parameter values:
                                    0 - draft print mode
                                    1 - quality print mode
                                    Print resolutions are specified in DPI.
                            Any parameter which should be left unchanged may
                            be filled with a (-1) (0FFFF hex).  The reserved
                            settings should be filled with a (-1).
22        Get Parameters
                            ES:BX points to an 8 word table (16 bytes) where
                            parameter settings are held.
23        Get Printer Res
                            ES:BX points to a 12 word table (24 bytes) that
                            holds six printer resolution pairs.
24        Reserved (versions 8.00 & up)

FRIEZE Error Codes

When FRIEZE is called using interrupt 10 hex, it will return an error code
in the AX register.  A value of zero shows that there was no error.  A
nonzero result means there was an error.  These error codes are explained
below.

 0        No Error
 1        Printout was stopped by user with the ESC key
 2        Reserved
 3        File read error
 4        File write error
 5        File not found
 6        Invalid Header - not an image, wrong screen mode
 7        File close error
 8        Disk error - usually drive door open
 9        Printer error - printer is off or out of paper
10        Invalid command - CL was set to call a nonexistent  FRIEZE function
11        Can't create file - write protect tab or disk is full
12        Wrong video mode - FRIEZE cannot capture text screens.

Technical Reference Manual

Including information for:
Publisher's Paintbrushr
PC Paintbrush IVTM
PC Paintbrush IV PlusTM
PC Paintbrush PlusTM
PC Paintbrushr
FRIEZETM Graphics
PaintbrushTM
Revision 5

ZSoft Corporation
450 Franklin Rd. Suite 100
Marietta, GA  30067
(404) 428-0008
(404) 427-1150 Fax
(404) 427-1045 BBS

Copyright c 1985, 1987, 1988, 1990, 1991, ZSoft Corporation 
All Rights Reserved



Graphics File Formats

This topic describes the graphics-file formats used by the Microsoft Windows
operating system. Graphics files include bitmap files, icon-resource files,
and cursor-resource files.

Bitmap-File Formats

Windows bitmap files are stored in a device-independent bitmap (DIB) format
that allows Windows to display the bitmap on any type of display device. The
term "device independent" means that the bitmap specifies pixel color in a
form independent of the method used by a display to represent color. The
default filename extension of a Windows DIB file is .BMP.

Bitmap-File Structures

Each bitmap file contains a bitmap-file header, a bitmap-information header,
a color table, and an array of bytes that defines the bitmap bits. The file
has the following form:

BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER bmih;
RGBQUAD          aColors[];
BYTE             aBitmapBits[];

The bitmap-file header contains information about the type, size, and layout
of a device-independent bitmap file. The header is defined as a
BITMAPFILEHEADER structure.

The bitmap-information header, defined as a BITMAPINFOHEADER structure,
specifies the dimensions, compression type, and color format for the bitmap.

The color table, defined as an array of RGBQUAD structures, contains as many
elements as there are colors in the bitmap. The color table is not present
for bitmaps with 24 color bits because each pixel is represented by 24-bit
red-green-blue (RGB) values in the actual bitmap data area. The colors in the
table should appear in order of importance. This helps a display driver
render a bitmap on a device that cannot display as many colors as there are
in the bitmap. If the DIB is in Windows version 3.0 or later format, the
driver can use the biClrImportant member of the BITMAPINFOHEADER structure to
determine which colors are important.

The BITMAPINFO structure can be used to represent a combined
bitmap-information header and color table.  The bitmap bits, immediately
following the color table, consist of an array of BYTE values representing
consecutive rows, or "scan lines," of the bitmap. Each scan line consists of
consecutive bytes representing the pixels in the scan line, in left-to-right
order. The number of bytes representing a scan line depends on the color
format and the width, in pixels, of the bitmap. If necessary, a scan line
must be zero-padded to end on a 32-bit boundary. However, segment boundaries
can appear anywhere in the bitmap. The scan lines in the bitmap are stored
from bottom up. This means that the first byte in the array represents the
pixels in the lower-left corner of the bitmap and the last byte represents
the pixels in the upper-right corner.

The biBitCount member of the BITMAPINFOHEADER structure determines the number
of bits that define each pixel and the maximum number of colors in the
bitmap. These members can have any of the following values:

Value   Meaning

1       Bitmap is monochrome and the color table contains two entries. Each
bit in the bitmap array represents a pixel. If the bit is clear, the pixel is
displayed with the color of the first entry in the color table. If the bit is
set, the pixel has the color of the second entry in the table.

4       Bitmap has a maximum of 16 colors. Each pixel in the bitmap is
represented by a 4-bit index into the color table. For example, if the first
byte in the bitmap is 0x1F, the byte represents two pixels. The first pixel
contains the color in the second table entry, and the second pixel contains
the color in the sixteenth table entry.

8       Bitmap has a maximum of 256 colors. Each pixel in the bitmap is
represented by a 1-byte index into the color table. For example, if the first
byte in the bitmap is 0x1F, the first pixel has the color of the
thirty-second table entry.

24      Bitmap has a maximum of 2^24 colors. The bmiColors (or bmciColors)
member is NULL, and each 3-byte sequence in the bitmap array represents the
relative intensities of red, green, and blue, respectively, for a pixel.

The biClrUsed member of the BITMAPINFOHEADER structure specifies the number
of color indexes in the color table actually used by the bitmap. If the
biClrUsed member is set to zero, the bitmap uses the maximum number of colors
corresponding to the value of the biBitCount member.  An alternative form of
bitmap file uses the BITMAPCOREINFO, BITMAPCOREHEADER, and RGBTRIPLE
structures.

Bitmap Compression

Windows versions 3.0 and later support run-length encoded (RLE) formats for
compressing bitmaps that use 4 bits per pixel and 8 bits per pixel.
Compression reduces the disk and memory storage required for a bitmap.

Compression of 8-Bits-per-Pixel Bitmaps

When the biCompression member of the BITMAPINFOHEADER structure is set to
BI_RLE8, the DIB is compressed using a run-length encoded format for a
256-color bitmap. This format uses two modes: encoded mode and absolute mode.
Both modes can occur anywhere throughout a single bitmap.

Encoded Mode

A unit of information in encoded mode consists of two bytes. The first byte
specifies the number of consecutive pixels to be drawn using the color index
contained in the second byte.  The first byte of the pair can be set to zero
to indicate an escape that denotes the end of a line, the end of the bitmap,
or a delta. The interpretation of the escape depends on the value of the
second byte of the pair, which must be in the range 0x00 through 0x02.
Following are the meanings of the escape values that can be used in the
second byte:

Second byte     Meaning

0       End of line. 
1       End of bitmap. 
2       Delta. The two bytes following the escape contain unsigned values
indicating the horizontal and vertical offsets of the next pixel from the
current position.

Absolute Mode

Absolute mode is signaled by the first byte in the pair being set to zero and
the second byte to a value between 0x03 and 0xFF. The second byte represents
the number of bytes that follow, each of which contains the color index of a
single pixel. Each run must be aligned on a word boundary.  Following is an
example of an 8-bit RLE bitmap (the two-digit hexadecimal values in the
second column represent a color index for a single pixel):

Compressed data         Expanded data

03 04                   04 04 04 
05 06                   06 06 06 06 06 
00 03 45 56 67 00       45 56 67 
02 78                   78 78 
00 02 05 01             Move 5 right and 1 down 
02 78                   78 78 
00 00                   End of line 
09 1E                   1E 1E 1E 1E 1E 1E 1E 1E 1E 
00 01                   End of RLE bitmap 

Compression of 4-Bits-per-Pixel Bitmaps

When the biCompression member of the BITMAPINFOHEADER structure is set to
BI_RLE4, the DIB is compressed using a run-length encoded format for a
16-color bitmap. This format uses two modes: encoded mode and absolute mode.

Encoded Mode

A unit of information in encoded mode consists of two bytes. The first byte
of the pair contains the number of pixels to be drawn using the color indexes
in the second byte.

The second byte contains two color indexes, one in its high-order nibble
(that is, its low-order 4 bits) and one in its low-order nibble.

The first pixel is drawn using the color specified by the high-order nibble,
the second is drawn using the color in the low-order nibble, the third is
drawn with the color in the high-order nibble, and so on, until all the
pixels specified by the first byte have been drawn.

The first byte of the pair can be set to zero to indicate an escape that
denotes the end of a line, the end of the bitmap, or a delta. The
interpretation of the escape depends on the value of the second byte of the
pair. In encoded mode, the second byte has a value in the range 0x00 through
0x02. The meaning of these values is the same as for a DIB with 8 bits per
pixel.

Absolute Mode

In absolute mode, the first byte contains zero, the second byte contains the
number of color indexes that follow, and subsequent bytes contain color
indexes in their high- and low-order nibbles, one color index for each pixel.
Each run must be aligned on a word boundary.

Following is an example of a 4-bit RLE bitmap (the one-digit hexadecimal
values in the second column represent a color index for a single pixel):

Compressed data         Expanded data

03 04                   0 4 0
05 06                   0 6 0 6 0 
00 06 45 56 67 00       4 5 5 6 6 7 
04 78                   7 8 7 8 
00 02 05 01             Move 5 right and 1 down 
04 78                   7 8 7 8 
00 00                   End of line 
09 1E                   1 E 1 E 1 E 1 E 1 
00 01                   End of RLE bitmap 

Bitmap Example

The following example is a text dump of a 16-color bitmap (4 bits per pixel):

Win3DIBFile
              BitmapFileHeader
                  Type       19778
                  Size       3118
                  Reserved1  0
                  Reserved2  0
                  OffsetBits 118
              BitmapInfoHeader
                  Size            40
                  Width           80
                  Height          75
                  Planes          1
                  BitCount        4
                  Compression     0
                  SizeImage       3000

                  XPelsPerMeter   0
                  YPelsPerMeter   0
                  ColorsUsed      16
                  ColorsImportant 16
              Win3ColorTable
                  Blue  Green  Red  Unused
[00000000]        84    252    84   0
[00000001]        252   252    84   0
[00000002]        84    84     252  0
[00000003]        252   84     252  0
[00000004]        84    252    252  0
[00000005]        252   252    252  0
[00000006]        0     0      0    0
[00000007]        168   0      0    0
[00000008]        0     168    0    0
[00000009]        168   168    0    0
[0000000A]        0     0      168  0
[0000000B]        168   0      168  0
[0000000C]        0     168    168  0
[0000000D]        168   168    168  0
[0000000E]        84    84     84   0
[0000000F]        252   84     84   0
              Image
    .
    .                                           Bitmap data
    .

Icon-Resource File Format

An icon-resource file contains image data for icons used by Windows
applications. The file consists of an icon directory identifying the number
and types of icon images in the file, plus one or more icon images. The
default filename extension for an icon-resource file is .ICO.

Icon Directory

Each icon-resource file starts with an icon directory. The icon directory,
defined as an ICONDIR structure, specifies the number of icons in the
resource and the dimensions and color format of each icon image. The ICONDIR
structure has the following form:



typedef struct ICONDIR {
    WORD          idReserved;
    WORD          idType;
    WORD          idCount;
    ICONDIRENTRY  idEntries[1];
} ICONHEADER;

Following are the members in the ICONDIR structure:

idReserved      Reserved; must be zero. 
idType          Specifies the resource type. This member is set to 1. 
idCount         Specifies the number of entries in the directory. 
idEntries       Specifies an array of ICONDIRENTRY structures containing
information about individual icons. The idCount member specifies the number
of structures in the array.

The ICONDIRENTRY structure specifies the dimensions and color format for an
icon. The structure has the following form:



struct IconDirectoryEntry {
    BYTE  bWidth;
    BYTE  bHeight;
    BYTE  bColorCount;
    BYTE  bReserved;
    WORD  wPlanes;
    WORD  wBitCount;
    DWORD dwBytesInRes;
    DWORD dwImageOffset;
};

Following are the members in the ICONDIRENTRY structure: 

bWidth          Specifies the width of the icon, in pixels. Acceptable values
are 16, 32, and 64.

bHeight         Specifies the height of the icon, in pixels. Acceptable
values are 16, 32, and 64.

bColorCount     Specifies the number of colors in the icon. Acceptable values
are 2, 8, and 16.

bReserved       Reserved; must be zero. 
wPlanes         Specifies the number of color planes in the icon bitmap. 
wBitCount       Specifies the number of bits in the icon bitmap. 
dwBytesInRes    Specifies the size of the resource, in bytes. 
dwImageOffset   Specifies the offset, in bytes, from the beginning of the
file to the icon image.

Icon Image

Each icon-resource file contains one icon image for each image identified in
the icon directory. An icon image consists of an icon-image header, a color
table, an XOR mask, and an AND mask. The icon image has the following form:



BITMAPINFOHEADER    icHeader;
RGBQUAD             icColors[];
BYTE                icXOR[];
BYTE                icAND[];

The icon-image header, defined as a BITMAPINFOHEADER structure, specifies the
dimensions and color format of the icon bitmap. Only the biSize through
biBitCount members and the biSizeImage member are used. All other members
(such as biCompression and biClrImportant) must be set to zero.

The color table, defined as an array of RGBQUAD structures, specifies the
colors used in the XOR mask. As with the color table in a bitmap file, the
biBitCount member in the icon-image header determines the number of elements
in the array. For more information about the color table, see Section 1.1,
"Bitmap-File Formats."

The XOR mask, immediately following the color table, is an array of BYTE
values representing consecutive rows of a bitmap. The bitmap defines the
basic shape and color of the icon image. As with the bitmap bits in a bitmap
file, the bitmap data in an icon-resource file is organized in scan lines,
with each byte representing one or more pixels, as defined by the color
format. For more information about these bitmap bits, see Section 1.1,
"Bitmap-File Formats."

The AND mask, immediately following the XOR mask, is an array of BYTE values,
representing a monochrome bitmap with the same width and height as the XOR
mask. The array is organized in scan lines, with each byte representing 8
pixels.

When Windows draws an icon, it uses the AND and XOR masks to combine the icon
image with the pixels already on the display surface. Windows first applies
the AND mask by using a bitwise AND operation; this preserves or removes
existing pixel color.  Windows then applies the XOR mask by using a bitwise
XOR operation. This sets the final color for each pixel.

The following illustration shows the XOR and AND masks that create a
monochrome icon (measuring 8 pixels by 8 pixels) in the form of an uppercase
K:

Windows Icon Selection

Windows detects the resolution of the current display and matches it against
the width and height specified for each version of the icon image. If Windows
determines that there is an exact match between an icon image and the current
device, it uses the matching image. Otherwise, it selects the closest match
and stretches the image to the proper size.

If an icon-resource file contains more than one image for a particular
resolution, Windows uses the icon image that most closely matches the color
capabilities of the current display. If no image matches the device
capabilities exactly, Windows selects the image that has the greatest number
of colors without exceeding the number of display colors. If all images
exceed the color capabilities of the current display, Windows uses the icon
image with the least number of colors.



Cursor-Resource File Format

A cursor-resource file contains image data for cursors used by Windows
applications. The file consists of a cursor directory identifying the number
and types of cursor images in the file, plus one or more cursor images. The
default filename extension for a cursor-resource file is .CUR.

Cursor Directory

Each cursor-resource file starts with a cursor directory. The cursor
directory, defined as a CURSORDIR structure, specifies the number of cursors
in the file and the dimensions and color format of each cursor image. The
CURSORDIR structure has the following form:


typedef struct _CURSORDIR {
    WORD           cdReserved;
    WORD           cdType;
    WORD           cdCount;
    CURSORDIRENTRY cdEntries[];
} CURSORDIR;

Following are the members in the CURSORDIR structure: 

cdReserved      Reserved; must be zero. 
cdType          Specifies the resource type. This member must be set to 2. 
cdCount         Specifies the number of cursors in the file. 
cdEntries       Specifies an array of CURSORDIRENTRY structures containing
information about individual cursors. The cdCount member specifies the number
of structures in the array.

A CURSORDIRENTRY structure specifies the dimensions and color format of a
cursor image. The structure has the following form:



typedef struct _CURSORDIRENTRY {
    BYTE  bWidth;
    BYTE  bHeight;
    BYTE  bColorCount;
    BYTE  bReserved;
    WORD  wXHotspot;
    WORD  wYHotspot;
    DWORD lBytesInRes;
    DWORD dwImageOffset;
} CURSORDIRENTRY;

Following are the members in the CURSORDIRENTRY structure: 

bWidth          Specifies the width of the cursor, in pixels. 
bHeight         Specifies the height of the cursor, in pixels. 
bColorCount     Reserved; must be zero. 
bReserved       Reserved; must be zero.
wXHotspot       Specifies the x-coordinate, in pixels, of the hot spot. 
wYHotspot       Specifies the y-coordinate, in pixels, of the hot spot. 
lBytesInRes     Specifies the size of the resource, in bytes. 
dwImageOffset   Specifies the offset, in bytes, from the start of the file to
the cursor image.

Cursor Image

Each cursor-resource file contains one cursor image for each image identified
in the cursor directory. A cursor image consists of a cursor-image header, a
color table, an XOR mask, and an AND mask. The cursor image has the following
form:



BITMAPINFOHEADER    crHeader;
RGBQUAD             crColors[];
BYTE                crXOR[];
BYTE                crAND[];

The cursor hot spot is a single pixel in the cursor bitmap that Windows uses
to track the cursor. The crXHotspot and crYHotspot members specify the x- and
y-coordinates of the cursor hot spot. These coordinates are 16-bit integers.

The cursor-image header, defined as a BITMAPINFOHEADER structure, specifies
the dimensions and color format of the cursor bitmap. Only the biSize through
biBitCount members and the biSizeImage member are used. The biHeight member
specifies the combined height of the XOR and AND masks for the cursor. This
value is twice the height of the XOR mask. The biPlanes and biBitCount
members must be 1. All other members (such as biCompression and
biClrImportant) must be set to zero.

The color table, defined as an array of RGBQUAD structures, specifies the
colors used in the XOR mask. For a cursor image, the table contains exactly
two structures, since the biBitCount member in the cursor-image header is
always 1.

The XOR mask, immediately following the color table, is an array of BYTE
values representing consecutive rows of a bitmap. The bitmap defines the
basic shape and color of the cursor image. As with the bitmap bits in a
bitmap file, the bitmap data in a cursor-resource file is organized in scan
lines, with each byte representing one or more pixels, as defined by the
color format. For more information about these bitmap bits, see Section 1.1,
"Bitmap-File Formats."

The AND mask, immediately following the XOR mask, is an array of BYTE values
representing a monochrome bitmap with the same width and height as the XOR
mask. The array is organized in scan lines, with each byte representing 8
pixels.

When Windows draws a cursor, it uses the AND and XOR masks to combine the
cursor image with the pixels already on the display surface. Windows first
applies the AND mask by using a bitwise AND operation; this preserves or
removes existing pixel color.  Window then applies the XOR mask by using a
bitwise XOR operation. This sets the final color for each pixel.

The following illustration shows the XOR and the AND masks that create a
cursor (measuring 8 pixels by 8 pixels) in the form of an arrow:

Following are the bit-mask values necessary to produce black, white,
inverted, and transparent results:

Pixel result    AND maskXOR mask

Black           0               0 
White           0               1 
Transparent     1               0 
Inverted1               1 

Windows Cursor Selection

If a cursor-resource file contains more than one cursor image, Windows
determines the best match for a particular display by examining the width and
height of the cursor images.


==============================================================================


BITMAPFILEHEADER (3.0)



typedef struct tagBITMAPFILEHEADER {    /* bmfh */
    UINT    bfType;
    DWORD   bfSize;
    UINT    bfReserved1;
    UINT    bfReserved2;
    DWORD   bfOffBits;
} BITMAPFILEHEADER;

The BITMAPFILEHEADER structure contains information about the type, size, and
layout of a device-independent bitmap (DIB) file.

Member          Description

bfType          Specifies the type of file. This member must be BM. 
bfSize          Specifies the size of the file, in bytes. 
bfReserved1     Reserved; must be set to zero. 
bfReserved2     Reserved; must be set to zero.
bfOffBits       Specifies the byte offset from the BITMAPFILEHEADER structure
to the actual bitmap data in the file.

Comments

A BITMAPINFO or BITMAPCOREINFO structure immediately follows the
BITMAPFILEHEADER structure in the DIB file.

See Also

BITMAPCOREINFO, BITMAPINFO 


==============================================================================
BITMAPINFO (3.0)



typedef struct tagBITMAPINFO {  /* bmi */
    BITMAPINFOHEADER    bmiHeader;
    RGBQUAD             bmiColors[1];
} BITMAPINFO;

The BITMAPINFO structure fully defines the dimensions and color information
for a Windows 3.0 or later device-independent bitmap (DIB).

Member          Description

bmiHeader       Specifies a BITMAPINFOHEADER structure that contains
information about the dimensions and color format of a DIB.

bmiColors       Specifies an array of RGBQUAD structures that define the
colors in the bitmap.

Comments

A Windows 3.0 or later DIB consists of two distinct parts: a BITMAPINFO
structure, which describes the dimensions and colors of the bitmap, and an
array of bytes defining the pixels of the bitmap. The bits in the array are
packed together, but each scan line must be zero-padded to end on a LONG
boundary. Segment boundaries, however, can appear anywhere in the bitmap. The
origin of the bitmap is the lower-left corner.

The biBitCount member of the BITMAPINFOHEADER structure determines the number
of bits which define each pixel and the maximum number of colors in the
bitmap. This member may be set to any of the following values:

Value   Meaning

1       The bitmap is monochrome, and the bmciColors member must contain two
entries. Each bit in the bitmap array represents a pixel. If the bit is
clear, the pixel is displayed with the color of the first entry in the
bmciColors table. If the bit is set, the pixel has the color of the second
entry in the table.

4       The bitmap has a maximum of 16 colors, and the bmciColors member
contains 16 entries. Each pixel in the bitmap is represented by a four-bit
index into the color table.

For example, if the first byte in the bitmap is 0x1F, the byte represents two
pixels. The first pixel contains the color in the second table entry, and the
second pixel contains the color in the sixteenth table entry.

8       The bitmap has a maximum of 256 colors, and the bmciColors member
contains 256 entries. In this case, each byte in the array represents a
single pixel.

24      The bitmap has a maximum of 2^24 colors. The bmciColors member is
NULL, and each 3-byte sequence in the bitmap array represents the relative
intensities of red, green, and blue, respectively, of a pixel.

The biClrUsed member of the BITMAPINFOHEADER structure specifies the number
of color indexes in the color table actually used by the bitmap. If the
biClrUsed member is set to zero, the bitmap uses the maximum number of colors
corresponding to the value of the biBitCount member.

The colors in the bmiColors table should appear in order of importance.
Alternatively, for functions that use DIBs, the bmiColors member can be an
array of 16-bit unsigned integers that specify an index into the currently
realized logical palette instead of explicit RGB values. In this case, an
application using the bitmap must call DIB functions with the wUsage
parameter set to DIB_PAL_COLORS.

Note:   The bmiColors member should not contain palette indexes if the bitmap
is to be stored in a file or transferred to another application. Unless the
application uses the bitmap exclusively and under its complete control, the
bitmap color table should contain explicit RGB values.

See Also

BITMAPINFOHEADER, RGBQUAD 

==============================================================================
BITMAPINFOHEADER (3.0)



typedef struct tagBITMAPINFOHEADER {    /* bmih */
    DWORD   biSize;
    LONG    biWidth;
    LONG    biHeight;
    WORD    biPlanes;
    WORD    biBitCount;
    DWORD   biCompression;
    DWORD   biSizeImage;
    LONG    biXPelsPerMeter;
    LONG    biYPelsPerMeter;
    DWORD   biClrUsed;
    DWORD   biClrImportant;
} BITMAPINFOHEADER;

The BITMAPINFOHEADER structure contains information about the dimensions and
color format of a Windows 3.0 or later device-independent bitmap (DIB).

Member          Description

biSize          Specifies the number of bytes required by the
BITMAPINFOHEADER structure.

biWidth         Specifies the width of the bitmap, in pixels. 
biHeightSpecifies the height of the bitmap, in pixels. 

biPlanesSpecifies the number of planes for the target device. This
member must be set to 1.

biBitCount      Specifies the number of bits per pixel. This value must be 1,
4, 8, or 24.

biCompression   Specifies the type of compression for a compressed bitmap. It
can be one of the following values:

Value           Meaning

BI_RGB          Specifies that the bitmap is not compressed. 

BI_RLE8         Specifies a run-length encoded format for bitmaps with 8 bits
per pixel. The compression format is a 2-byte format consisting of a count
byte followed by a byte containing a color index.  For more information, see
the following Comments section.

BI_RLE4         Specifies a run-length encoded format for bitmaps with 4 bits
per pixel. The compression format is a 2-byte format consisting of a count
byte followed by two word-length color indexes.  For more information, see
the following Comments section.

biSizeImage     Specifies the size, in bytes, of the image. It is valid to
set this member to zero if the bitmap is in the BI_RGB format.

biXPelsPerMeter Specifies the horizontal resolution, in pixels per meter, of
the target device for the bitmap. An application can use this value to select
a bitmap from a resource group that best matches the characteristics of the
current device.

biYPelsPerMeter Specifies the vertical resolution, in pixels per meter, of
the target device for the bitmap.

biClrUsed       Specifies the number of color indexes in the color table
actually used by the bitmap. If this value is zero, the bitmap uses the
maximum number of colors corresponding to the value of the biBitCount member.
For more information on the maximum sizes of the color table, see the
description of the BITMAPINFO structure earlier in this topic.

If the biClrUsed member is nonzero, it specifies the actual number of colors
that the graphics engine or device driver will access if the biBitCount
member is less than 24. If biBitCount is set to 24, biClrUsed specifies the
size of the reference color table used to optimize performance of Windows
color palettes.  If the bitmap is a packed bitmap (that is, a bitmap in which
the bitmap array immediately follows the BITMAPINFO header and which is
referenced by a single pointer), the biClrUsed member must be set to zero or
to the actual size of the color table.

biClrImportant  Specifies the number of color indexes that are considered
important for displaying the bitmap. If this value is zero, all colors are
important.

Comments

The BITMAPINFO structure combines the BITMAPINFOHEADER structure and a color
table to provide a complete definition of the dimensions and colors of a
Windows 3.0 or later DIB. For more information about specifying a Windows 3.0
DIB, see the description of the BITMAPINFO structure.

An application should use the information stored in the biSize member to
locate the color table in a BITMAPINFO structure as follows:

pColor = ((LPSTR) pBitmapInfo + (WORD) (pBitmapInfo->bmiHeader.biSize))

Windows supports formats for compressing bitmaps that define their colors
with 8 bits per pixel and with 4 bits per pixel. Compression reduces the disk
and memory storage required for the bitmap. The following paragraphs describe
these formats.

BI_RLE8

When the biCompression member is set to BI_RLE8, the bitmap is compressed
using a run-length encoding format for an 8-bit bitmap. This format may be
compressed in either of two modes: encoded and absolute. Both modes can occur
anywhere throughout a single bitmap.

Encoded mode consists of two bytes: the first byte specifies the number of
consecutive pixels to be drawn using the color index contained in the second
byte. In addition, the first byte of the pair can be set to zero to indicate
an escape that denotes an end of line, end of bitmap, or a delta. The
interpretation of the escape depends on the value of the second byte of the
pair. The following list shows the meaning of the second byte:

Value   Meaning

0       End of line. 
1       End of bitmap. 
2       Delta. The two bytes following the escape contain unsigned values
indicating the horizontal and vertical offset of the next pixel from the
current position.

Absolute mode is signaled by the first byte set to zero and the second byte
set to a value between 0x03 and 0xFF. In absolute mode, the second byte
represents the number of bytes that follow, each of which contains the color
index of a single pixel. When the second byte is set to 2 or less, the escape
has the same meaning as in encoded mode. In absolute mode, each run must be
aligned on a word boundary.  The following example shows the hexadecimal
values of an 8-bit compressed bitmap:



03 04 05 06 00 03 45 56 67 00 02 78 00 02 05 01
02 78 00 00 09 1E 00 01

This bitmap would expand as follows (two-digit values represent a color index
for a single pixel):



04 04 04
06 06 06 06 06
45 56 67
78 78
move current position 5 right and 1 down
78 78
end of line
1E 1E 1E 1E 1E 1E 1E 1E 1E
end of RLE bitmap

BI_RLE4

When the biCompression member is set to BI_RLE4, the bitmap is compressed
using a run-length encoding (RLE) format for a 4-bit bitmap, which also uses
encoded and absolute modes. In encoded mode, the first byte of the pair
contains the number of pixels to be drawn using the color indexes in the
second byte. The second byte contains two color indexes, one in its
high-order nibble (that is, its low-order four bits) and one in its low-order
nibble. The first of the pixels is drawn using the color specified by the
high-order nibble, the second is drawn using the color in the low-order
nibble, the third is drawn with the color in the high-order nibble, and so
on, until all the pixels specified by the first byte have been drawn.  In
absolute mode, the first byte contains zero, the second byte contains the
number of color indexes that follow, and subsequent bytes contain color
indexes in their high- and low-order nibbles, one color index for each pixel.
In absolute mode, each run must be aligned on a word boundary. The
end-of-line, end-of-bitmap, and delta escapes also apply to BI_RLE4.

The following example shows the hexadecimal values of a 4-bit compressed
bitmap:



03 04 05 06 00 06 45 56 67 00 04 78 00 02 05 01
04 78 00 00 09 1E 00 01

This bitmap would expand as follows (single-digit values represent a color
index for a single pixel):



0 4 0
0 6 0 6 0
4 5 5 6 6 7
7 8 7 8
move current position 5 right and 1 down
7 8 7 8
end of line
1 E 1 E 1 E 1 E 1
end of RLE bitmap

See Also

BITMAPINFO 

==============================================================================
RGBQUAD (3.0)



typedef struct tagRGBQUAD {     /* rgbq */
    BYTE    rgbBlue;
    BYTE    rgbGreen;
    BYTE    rgbRed;
    BYTE    rgbReserved;
} RGBQUAD;

The RGBQUAD structure describes a color consisting of relative intensities of
red, green, and blue. The bmiColors member of the BITMAPINFO structure
consists of an array of RGBQUAD structures.

Member          Description

rgbBlue         Specifies the intensity of blue in the color. 
rgbGreenSpecifies the intensity of green in the color. 
rgbRed          Specifies the intensity of red in the color. 
rgbReserved     Not used; must be set to zero. 

See Also

BITMAPINFO 

==============================================================================
RGB (2.x)

COLORREF RGB(cRed, cGreen, cBlue)

BYTE cRed;      /* red component of color       */
BYTE cGreen;    /* green component of color     */
BYTE cBlue;     /* blue component of color      */


The RGB macro selects an RGB color based on the parameters supplied and the
color capabilities of the output device.

Parameter       Description

cRed    Specifies the intensity of the red color field. 
cGreen  Specifies the intensity of the green color field. 
cBlue   Specifies the intensity of the blue color field. 

Returns

The return value specifies the resultant RGB color. 

Comments

The intensity for each argument can range from 0 through 255. If all three
intensities are specified as zero, the result is black. If all three
intensities are specified as 255, the result is white.

Comments

The RGB macro is defined in WINDOWS.H as follows: 



#define RGB(r,g,b)   ((COLORREF)(((BYTE)(r)|((WORD)(g)<<8))| \
    (((DWORD)(BYTE)(b))<<16)))

See Also

GetBValue, GetGValue, GetRValue, PALETTEINDEX, PALETTERGB

==============================================================================
BITMAPCOREINFO (3.0)



typedef struct tagBITMAPCOREINFO {  /* bmci */
    BITMAPCOREHEADER bmciHeader;
    RGBTRIPLE        bmciColors[1];
} BITMAPCOREINFO;

The BITMAPCOREINFO structure fully defines the dimensions and color
information for a device-independent bitmap (DIB).  Windows applications
should use the BITMAPINFO structure instead of BITMAPCOREINFO whenever
possible.

Member          Description

bmciHeader      Specifies a BITMAPCOREHEADER structure that contains
information about the dimensions and color format of a DIB.

bmciColors      Specifies an array of RGBTRIPLE structures that define the
colors in the bitmap.

Comments

The BITMAPCOREINFO structure describes the dimensions and colors of a bitmap.
It is followed immediately in memory by an array of bytes which define the
pixels of the bitmap. The bits in the array are packed together, but each
scan line must be zero-padded to end on a LONG boundary. Segment boundaries,
however, can appear anywhere in the bitmap. The origin of the bitmap is the
lower-left corner.

The bcBitCount member of the BITMAPCOREHEADER structure determines the number
of bits that define each pixel and the maximum number of colors in the
bitmap. This member may be set to any of the following values:

Value   Meaning

1       The bitmap is monochrome, and the bmciColors member must contain two
entries. Each bit in the bitmap array represents a pixel. If the bit is
clear, the pixel is displayed with the color of the first entry in the
bmciColors table. If the bit is set, the pixel has the color of the second
entry in the table.

4       The bitmap has a maximum of 16 colors, and the bmciColors member
contains 16 entries. Each pixel in the bitmap is represented by a four-bit
index into the color table.

For example, if the first byte in the bitmap is 0x1F, the byte represents two
pixels. The first pixel contains the color in the second table entry, and the
second pixel contains the color in the sixteenth table entry.

8       The bitmap has a maximum of 256 colors, and the bmciColors member
contains 256 entries. In this case, each byte in the array represents a
single pixel.

24      The bitmap has a maximum of 2^24 colors. The bmciColors member is
NULL, and each 3-byte sequence in the bitmap array represents the relative
intensities of red, green, and blue, respectively, of a pixel.

The colors in the bmciColors table should appear in order of importance.
Alternatively, for functions that use DIBs, the bmciColors member can be an
array of 16-bit unsigned integers that specify an index into the currently
realized logical palette instead of explicit RGB values. In this case, an
application using the bitmap must call DIB functions with the wUsage
parameter set to DIB_PAL_COLORS.

Note:   The bmciColors member should not contain palette indexes if the
bitmap is to be stored in a file or transferred to another application.
Unless the application uses the bitmap exclusively and under its complete
control, the bitmap color table should contain explicit RGB values.

See Also

BITMAPINFO, BITMAPCOREHEADER, RGBTRIPLE 


==============================================================================
BITMAPCOREHEADER (3.0)



typedef struct tagBITMAPCOREHEADER {    /* bmch */
    DWORD   bcSize;
    short   bcWidth;
    short   bcHeight;
    WORD    bcPlanes;
    WORD    bcBitCount;
} BITMAPCOREHEADER;

The BITMAPCOREHEADER structure contains information about the dimensions and
color format of a device-independent bitmap (DIB). Windows applications
should use the BITMAPINFOHEADER structure instead of BITMAPCOREHEADER
whenever possible.

Member          Description

bcSize          Specifies the number of bytes required by the
BITMAPCOREHEADER structure.

bcWidth         Specifies the width of the bitmap, in pixels. 
bcHeightSpecifies the height of the bitmap, in pixels. 

bcPlanesSpecifies the number of planes for the target device. This
member must be set to 1.

bcBitCount      Specifies the number of bits per pixel. This value must be 1,
4, 8, or 24.

Comments

The BITMAPCOREINFO structure combines the BITMAPCOREHEADER structure and a
color table to provide a complete definition of the dimensions and colors of
a DIB. See the description of the BITMAPCOREINFO structure for more
information about specifying a DIB.

An application should use the information stored in the bcSize member to
locate the color table in a BITMAPCOREINFO structure with a method such as
the following:



lpColor = ((LPSTR) pBitmapCoreInfo + (UINT) (pBitmapCoreInfo->bcSize))

See Also

BITMAPCOREINFO, BITMAPINFOHEADER, BITMAPINFOHEADER 

=============================================================================
RGBTRIPLE (3.0)



typedef struct tagRGBTRIPLE {   /* rgbt */
    BYTE    rgbtBlue;
    BYTE    rgbtGreen;
    BYTE    rgbtRed;
} RGBTRIPLE;

The RGBTRIPLE structure describes a color consisting of relative intensities
of red, green, and blue. The bmciColors member of the BITMAPCOREINFO
structure consists of an array of RGBTRIPLE structures.  Windows applications
should use the BITMAPINFO structure instead of BITMAPCOREINFO whenever
possible. The BITMAPINFO structure uses an RGBQUAD structure instead of the
RGBTRIPLE structure.

Member  Description

rgbtBlueSpecifies the intensity of blue in the color. 
rgbtGreen       Specifies the intensity of green in the color. 
rgbtRed         Specifies the intensity of red in the color.

See Also

BITMAPCOREINFO, BITMAPINFO, RGBQUAD

==============================================================================


                   LZW and GIF explained----Steve Blackstock


      I hope this little document will help enlighten those of you out there
who want to know more about the Lempel-Ziv Welch compression algorithm, and,
specifically, the implementation that GIF uses.
     Before we start, here's a little terminology, for the purposes of this
document:

      "character": a fundamental data element. In normal text files, this is
just a single byte. In raster images, which is what we're interested in, it's
an index that specifies the color of a given pixel. I'll refer to an arbitray
character as "K".
      "charstream": a stream of characters, as in a data file.
      "string": a number of continuous characters, anywhere from one to very
many characters in length. I can specify an arbitrary string as "[...]K".
      "prefix": almost the same as a string, but with the implication that a
prefix immediately precedes a character, and a prefix can have a length of
zero. So, a prefix and a character make up a string. I will refer to an
arbitrary prefix as "[...]".
      "root": a single-character string. For most purposes, this is a
character, but we may occasionally make a distinction. It is [...]K, where
[...] is empty.
      "code": a number, specified by a known number of bits, which maps to a
string.
      "codestream": the output stream of codes, as in the "raster data"
      "entry": a code and its string.
      "string table": a list of entries; usually, but not necessarily, unique.
      That should be enough of that.

     LZW is a way of compressing data that takes advantage of repetition of
strings in the data. Since raster data usually contains a lot of this
repetition, LZW is a good way of compressing and decompressing it.
     For the moment, lets consider normal LZW encoding and decoding. GIF's
variation on the concept is just an extension from there.
     LZW manipulates three objects in both compression and decompression: the
charstream, the codestream, and the string table. In compression, the
charstream is the input and the codestream is the output. In decompression,
the codestream is the input and the charstream is the output. The string table
is a product of both compression and decompression, but is never passed from
one to the other.
     The first thing we do in LZW compression is initialize our string table.
To do this, we need to choose a code size (how many bits) and know how many
values our characters can possibly take. Let's say our code size is 12 bits,
meaning we can store 0->FFF, or 4096 entries in our string table. Lets also
say that we have 32 possible different characters. (This corresponds to, say,
a picture in which there are 32 different colors possible for each pixel.) To
initialize the table, we set code#0 to character#0, code #1 to character#1,
and so on, until code#31 to character#31. Actually, we are specifying that
each code from 0 to 31 maps to a root. There will be no more entries in the
table that have this property.
     Now we start compressing data. Let's first define something called the
"current prefix". It's just a prefix that we'll store things in and compare
things to now and then. I will refer to it as "[.c.]". Initially, the current
prefix has nothing in it. Let's also define a "current string", which will be
the current prefix plus the next character in the charstream. I will refer to
the current string as "[.c.]K", where K is some character. OK, look at the
first character in the charstream. Call it P. Make [.c.]P the current string.
(At this point, of course, it's just the root P.) Now search through the
string table to see if [.c.]P appears in it. Of course, it does now, because
our string table is initialized to have all roots. So we don't do anything.
Now make [.c.]P the current prefix. Look at the next character in the
charstream. Call it Q. Add it to the current prefix to form [.c.]Q, the
current string. Now search through the string table to see if [.c.]Q appears
in it. In this case, of course, it doesn't. Aha! Now we get to do something.
Add [.c.]Q (which is PQ in this case) to the string table for code#32, and
output the code for [.c.] to the codestream. Now start over again with the
current prefix being just the root P. Keep adding characters to [.c.] to form
[.c.]K, until you can't find [.c.]K in the string table. Then output the code
for [.c.] and add [.c.]K to the string table. In pseudo-code, the algorithm
goes something like this:

     [1] Initialize string table;
     [2] [.c.] <- empty;
     [3] K <- next character in charstream;
     [4] Is [.c.]K in string table?
      (yes: [.c.] <- [.c.]K;
            go to [3];
      )
      (no: add [.c.]K to the string table;
           output the code for [.c.] to the codestream;
           [.c.] <- K;
           go to [3];
      )

       It's as simple as that! Of course, when you get to step [3] and there
aren't any more characters left, you just output the code for [.c.] and throw
the table away. You're done.
      Wanna do an example? Let's pretend we have a four-character alphabet:
A,B,C,D. The charstream looks like ABACABA. Let's compress it. First, we
initialize our string table to: #0=A, #1=B, #2=C, #3=D. The first character is
A, which is in the string table, so [.c.] becomes A. Next we get AB, which is
not in the table, so we output code #0 (for [.c.]),
     and add AB to the string table as code #4. [.c.] becomes B. Next we get
[.c.]A = BA, which is not in the string table, so output code #1, and add BA
to the string table as code #5. [.c.] becomes A. Next we get AC, which is not
in the string table. Output code #0, and add AC to the string table as code
#6. Now [.c.] becomes C. Next we get [.c.]A = CA, which is not in the table.
Output #2 for C, and add CA to table as code#7. Now [.c.] becomes A. Next we
get AB, which IS in the string table, so [.c.] gets AB, and we look at ABA,
which is not in the string table, so output the code for AB, which is #4, and
add ABA to the string table as code #8. [.c.] becomes A. We can't get any more
characters, so we just output #0 for the code for A, and we're done. So, the
codestream is #0#1#0#2#4#0.
      A few words (four) should be said here about efficiency: use a hashing
strategy. The search through the string table can be computationally
intensive, and some hashing is well worth the effort. Also, note that
"straight LZW" compression runs the risk of overflowing the string table -
getting to a code which can't be represented in the number of bits you've set
aside for codes. There are several ways of dealing with this problem, and GIF
implements a very clever one, but we'll get to that.
      An important thing to notice is that, at any point during the
compression, if [...]K is in the string table, [...] is there also. This fact
suggests an efficient method for storing strings in the table. Rather than
store the entire string of K's in the table, realize that any string can be
expressed as a prefix plus a character: [...]K. If we're about to store [...]K
in the table, we know that [...] is already there, so we can just store the
code for [...] plus the final character K.
      Ok, that takes care of compression. Decompression is perhaps more
difficult conceptually, but it is really easier to program.
      Here's how it goes: We again have to start with an initialized string
table. This table comes from what knowledge we have about the charstream that
we will eventually get, like what possible values the characters can take. In
GIF files, this information is in the header as the number of possible pixel
values. The beauty of LZW, though, is that this is all we need to know. We
will build the rest of the string table as we decompress the codestream. The
compression is done in such a way that we will never encounter a code in the
codestream that we can't translate into a string.
      We need to define something called a "current code", which I will refer
to as "<code>", and an "old-code", which I will refer to as "<old>". To start
things off, look at the first code. This is now <code>. This code will be in
the intialized string table as the code for a root. Output the root to the
charstream. Make this code the old-code <old>. *Now look at the next code, and
make it <code>. It is possible that this code will not be in the string table,
but let's assume for now that it is. Output the string corresponding to <code>
to the codestream. Now find the first character in the string you just
translated. Call this K. Add this to the prefix [...] generated by <old> to
form a new string [...]K. Add this string [...]K to the string table, and set
the old-code <old> to the current code <code>. Repeat from where I typed the
asterisk, and you're all set. Read this paragraph again if you just skimmed
it!!!  Now let's consider the possibility that <code> is not in the string
table. Think back to compression, and try to understand what happens when you
have a string like P[...]P[...]PQ appear in the charstream. Suppose P[...] is
already in the string table, but P[...]P is not. The compressor will parse out
P[...], and find that P[...]P is not in the string table. It will output the
code for P[...], and add P[...]P to the string table. Then it will get up to
P[...]P for the next string, and find that P[...]P is in the table, as
     the code just added. So it will output the code for P[...]P if it finds
that P[...]PQ is not in the table. The decompressor is always "one step
behind" the compressor. When the decompressor sees the code for P[...]P, it
will not have added that code to it's string table yet because it needed the
beginning character of P[...]P to add to the string for the last code, P[...],
to form the code for P[...]P. However, when a decompressor finds a code that
it doesn't know yet, it will always be the very next one to be added to the
string table. So it can guess at what the string for the code should be, and,
in fact, it will always be correct. If I am a decompressor, and I see
code#124, and yet my string table has entries only up to code#123, I can
figure out what code#124 must be, add it to my string table, and output the
string. If code#123 generated the string, which I will refer to here as a
prefix, [...], then code#124, in this special case, will be [...] plus the
first character of [...]. So just add the first character of [...] to the end
of itself. Not too bad.  As an example (and a very common one) of this special
case, let's assume we have a raster image in which the first three pixels have
the same color value. That is, my charstream looks like: QQQ.... For the sake
of argument, let's say we have 32 colors, and Q is the color#12. The
compressor will generate the code sequence 12,32,.... (if you don't know why,
take a minute to understand it.) Remember that #32 is not in the initial
table, which goes from #0 to #31. The decompressor will see #12 and translate
it just fine as color Q. Then it will see #32 and not yet know what that
means. But if it thinks about it long enough, it can figure out that QQ should
be entry#32 in the table and QQ should be the next string output.  So the
decompression pseudo-code goes something like:

      [1] Initialize string table;
     [2] get first code: <code>;
     [3] output the string for <code> to the charstream;
     [4] <old> = <code>;
     [5] <code> <- next code in codestream;
     [6] does <code> exist in the string table?
      (yes: output the string for <code> to the charstream;
            [...] <- translation for <old>;
            K <- first character of translation for <code>;
            add [...]K to the string table;        <old> <- <code>;  )
      (no: [...] <- translation for <old>;
           K <- first character of [...];
           output [...]K to charstream and add it to string table;
           <old> <- <code>
      )
     [7] go to [5];

      Again, when you get to step [5] and there are no more codes, you're
finished.  Outputting of strings, and finding of initial characters in strings
are efficiency problems all to themselves, but I'm not going to suggest ways
to do them here. Half the fun of programming is figuring these things out!
      ---
      Now for the GIF variations on the theme. In part of the header of a GIF
file, there is a field, in the Raster Data stream, called "code size". This is
a very misleading name for the field, but we have to live with it. What it is
really is the "root size". The actual size, in bits, of the compression codes
actually changes during compression/decompression, and I will refer to that
size here as the "compression size". The initial table is just the codes for
all the roots, as usual, but two special codes are added on top of those.
Suppose you have a "code size", which is usually the number of bits per pixel
in the image, of N. If the number of bits/pixel is one, then N must be 2: the
roots take up slots #0 and #1 in the initial table, and the two special codes
will take up slots #4 and #5. In any other case, N is the number of bits per
pixel, and the roots take up slots #0 through #(2**N-1), and the special codes
are (2**N) and (2**N + 1). The initial compression size will be N+1 bits per
code. If you're encoding, you output the codes (N+1) bits at a time to start
with, and if you're decoding, you grab (N+1) bits from the codestream at a
time.  As for the special codes: <CC> or the clear code, is (2**N), and <EOI>,
or end-of-information, is (2**N + 1). <CC> tells the compressor to re-
initialize the string table, and to reset the compression size to (N+1). <EOI>
means there's no more in the codestream.  If you're encoding or decoding, you
should start adding things to the string table at <CC> + 2. If you're
encoding, you should output <CC> as the very first code, and then whenever
after that you reach code #4095 (hex FFF), because GIF does not allow
compression sizes to be greater than 12 bits. If you're decoding, you should
reinitialize your string table when you observe <CC>.  The variable
compression sizes are really no big deal. If you're encoding, you start with a
compression size of (N+1) bits, and, whenever you output the code
(2**(compression size)-1), you bump the compression size up one bit. So the
next code you output will be one bit longer. Remember that the largest
compression size is 12 bits, corresponding to a code of 4095. If you get that
far, you must output <CC> as the next code, and start over.  If you're
decoding, you must increase your compression size AS SOON AS YOU write entry
#(2**(compression size) - 1) to the string table. The next code you READ will
be one bit longer. Don't make the mistake of waiting until you need to add the
code (2**compression size) to the table. You'll have already missed a bit from
the last code.  The packaging of codes into a bitsream for the raster data is
also a potential stumbling block for the novice encoder or decoder. The lowest
order bit in the code should coincide with the lowest available bit in the
first available byte in the codestream. For example, if you're starting with
5-bit compression codes, and your first three codes are, say, <abcde>,
<fghij>, <klmno>, where e, j, and o are bit#0, then your codestream will start
off like:

       byte#0: hijabcde
       byte#1: .klmnofg

      So the differences between straight LZW and GIF LZW are: two additional
special codes and variable compression sizes. If you understand LZW, and you
understand those variations, you understand it all!
      Just as sort of a P.S., you may have noticed that a compressor has a
little bit of flexibility at compression time. I specified a "greedy" approach
to the compression, grabbing as many characters as possible before outputting
codes. This is, in fact, the standard LZW way of doing things, and it will
yield the best compression ratio. But there's no rule saying you can't stop
anywhere along the line and just output the code for the current prefix,
whether it's already in the table or not, and add that string plus the next
character to the string table. There are various reasons for wanting to do
this, especially if the strings get extremely long and make hashing difficult.
If you need to, do it.
      Hope this helps out.----steve blackstock

---------------------------------------------------------------------------
Article 5729 of comp.graphics:
Path: polya!shelby!labrea!agate!ucbvax!tut.cis.ohio-state.edu!rutgers!cmcl2!phri!cooper!john
>From: john@cooper.cooper.EDU (John Barkaus)
Newsgroups: comp.graphics
Subject: GIF file format responses 4/5
Keywords: GIF LZW
Message-ID: <1489@cooper.cooper.EDU>
Date: 21 Apr 89 20:56:35 GMT
Organization: The Cooper Union (NY, NY)
Lines: 1050


>From: cmcl2!neuron1.Jpl.Nasa.Gov!harry (Harry Langenbacher)

                                G I F (tm)

                     Graphics Interchange Format (tm)

                      A standard defining a mechanism

                     for the storage and transmission

                   of raster-based graphics information

                               June 15, 1987

                     (c) CompuServe Incorporated, 1987

                            All rights reserved

            While this document is copyrighted, the information

          contained within is made available for use in computer

          software without royalties, or licensing restrictions.

          GIF and 'Graphics Interchange Format' are trademarks of

                         CompuServe, Incorporated.

                           an H&R Block Company

                        5000 Arlington Centre Blvd.

                           Columbus, Ohio 43220

                              (614) 457-8600

                                                                     Page 2

              Graphics Interchange Format (GIF) Specification

                             Table of Contents

        INTRODUCTION . . . . . . . . . . . . . . . . . page 3

        GENERAL FILE FORMAT  . . . . . . . . . . . . . page 3

        GIF SIGNATURE  . . . . . . . . . . . . . . . . page 4

        SCREEN DESCRIPTOR  . . . . . . . . . . . . . . page 4

        GLOBAL COLOR MAP . . . . . . . . . . . . . . . page 5

        IMAGE DESCRIPTOR . . . . . . . . . . . . . . . page 6

        LOCAL COLOR MAP  . . . . . . . . . . . . . . . page 7

        RASTER DATA  . . . . . . . . . . . . . . . . . page 7

        GIF TERMINATOR . . . . . . . . . . . . . . . . page 8

        GIF EXTENSION BLOCKS . . . . . . . . . . . . . page 8

        APPENDIX A - GLOSSARY  . . . . . . . . . . . . page 9

        APPENDIX B - INTERACTIVE SEQUENCES . . . . . . page 10

        APPENDIX C - IMAGE PACKAGING & COMPRESSION . . page 12

        APPENDIX D - MULTIPLE IMAGE PROCESSING . . . . page 15

Graphics Interchange Format (GIF)                                    Page 3

Specification

INTRODUCTION

        'GIF' (tm) is CompuServe's standard for defining generalized  color

   raster   images.    This   'Graphics  Interchange  Format'  (tm)  allows

   high-quality, high-resolution graphics to be displayed on a  variety  of

   graphics  hardware  and is intended as an exchange and display mechanism

   for graphics images.  The image format described  in  this  document  is

   designed  to  support  current  and  future image technology and will in

   addition serve as a basis for future CompuServe graphics products.

        The main focus  of  this  document  is  to  provide  the  technical

   information  necessary  for  a  programmer to implement GIF encoders and

   decoders.  As such, some assumptions are made as to terminology relavent

   to graphics and programming in general.

        The first section of this document describes the  GIF  data  format

   and its components and applies to all GIF decoders, either as standalone

   programs or as part of  a  communications  package.   Appendix  B  is  a

   section  relavent to decoders that are part of a communications software

   package and describes the protocol requirements for entering and exiting

   GIF mode, and responding to host interrogations.  A glossary in Appendix

   A defines some of the terminology used in  this  document.   Appendix  C

   gives  a  detailed  explanation  of  how  the  graphics  image itself is

   packaged as a series of data bytes.

                Graphics Interchange Format Data Definition

 GENERAL FILE FORMAT

        +-----------------------+

        | +-------------------+ |

        | |   GIF Signature   | |

        | +-------------------+ |

        | +-------------------+ |

        | | Screen Descriptor | |

        | +-------------------+ |

        | +-------------------+ |

        | | Global Color Map  | |

        | +-------------------+ |

        . . .               . . .

        | +-------------------+ |    ---+

        | |  Image Descriptor | |       |

        | +-------------------+ |       |

        | +-------------------+ |       |

        | |  Local Color Map  | |       |-   Repeated 1 to n times

        | +-------------------+ |       |

        | +-------------------+ |       |

        | |    Raster Data    | |       |

        | +-------------------+ |    ---+

        . . .               . . .

        |-    GIF Terminator   -|

        +-----------------------+

Graphics Interchange Format (GIF)                                    Page 4

Specification

 GIF SIGNATURE

        The following GIF Signature identifies  the  data  following  as  a

   valid GIF image stream.  It consists of the following six characters:

             G I F 8 7 a

        The last three characters '87a' may be viewed as a  version  number

   for  this  particular  GIF  definition  and will be used in general as a

   reference  in  documents  regarding  GIF  that   address   any   version

   dependencies.

 SCREEN DESCRIPTOR

        The Screen Descriptor describes the overall parameters for all  GIF

   images  following.  It defines the overall dimensions of the image space

   or logical screen required, the existance of color mapping  information,

   background  screen color, and color depth information.  This information

   is stored in a series of 8-bit bytes as described below.

              bits

         7 6 5 4 3 2 1 0  Byte #

        +---------------+

        |               |  1

        +-Screen Width -+      Raster width in pixels (LSB first)

        |               |  2

        +---------------+

        |               |  3

        +-Screen Height-+      Raster height in pixels (LSB first)

        |               |  4

        +-+-----+-+-----+      M = 1, Global color map follows Descriptor

        |M|  cr |0|pixel|  5   cr+1 = # bits of color resolution

        +-+-----+-+-----+      pixel+1 = # bits/pixel in image

        |   background  |  6   background=Color index of screen background

        +---------------+          (color is defined from the Global color

        |0 0 0 0 0 0 0 0|  7        map or default map if none specified)

        +---------------+

        The logical screen width and height can both  be  larger  than  the

   physical  display.   How  images  larger  than  the physical display are

   handled is implementation dependent and can take advantage  of  hardware

   characteristics  (e.g.   Macintosh scrolling windows).  Otherwise images

   can be clipped to the edges of the display.

        The value of 'pixel' also defines  the  maximum  number  of  colors

   within  an  image.   The  range  of  values  for 'pixel' is 0 to 7 which

   represents 1 to 8 bits.  This translates to a range of 2 (B & W) to  256

   colors.   Bit  3 of word 5 is reserved for future definition and must be

   zero.

Graphics Interchange Format (GIF)                                    Page 5

Specification

 GLOBAL COLOR MAP

        The Global Color Map is optional but recommended for  images  where

   accurate color rendition is desired.  The existence of this color map is

   indicated in the 'M' field of byte 5 of the Screen Descriptor.  A  color

   map  can  also  be associated with each image in a GIF file as described

   later.  However this  global  map  will  normally  be  used  because  of

   hardware  restrictions  in equipment available today.  In the individual

   Image Descriptors the 'M' flag will normally be  zero.   If  the  Global

   Color  Map  is  present,  it's definition immediately follows the Screen

   Descriptor.   The  number  of  color  map  entries  following  a  Screen

   Descriptor  is equal to 2**(# bits per pixel), where each entry consists

   of three byte values representing the relative intensities of red, green

   and blue respectively.  The structure of the Color Map block is:

              bits

         7 6 5 4 3 2 1 0  Byte #

        +---------------+

        | red intensity |  1    Red value for color index 0

        +---------------+

        |green intensity|  2    Green value for color index 0

        +---------------+

        | blue intensity|  3    Blue value for color index 0

        +---------------+

        | red intensity |  4    Red value for color index 1

        +---------------+

        |green intensity|  5    Green value for color index 1

        +---------------+

        | blue intensity|  6    Blue value for color index 1

        +---------------+

        :               :       (Continues for remaining colors)

        Each image pixel value received will be displayed according to  its

   closest match with an available color of the display based on this color

   map.  The color components represent a fractional intensity  value  from

   none  (0)  to  full (255).  White would be represented as (255,255,255),

   black as (0,0,0) and medium yellow as (180,180,0).  For display, if  the

   device  supports fewer than 8 bits per color component, the higher order

   bits of each component are used.  In the creation of  a  GIF  color  map

   entry  with  hardware  supporting  fewer  than 8 bits per component, the

   component values for the hardware  should  be  converted  to  the  8-bit

   format with the following calculation:

        <map_value> = <component_value>*255/(2**<nbits> -1)

        This assures accurate translation of colors for all  displays.   In

   the  cases  of  creating  GIF images from hardware without color palette

   capability, a fixed palette should be created  based  on  the  available

   display  colors for that hardware.  If no Global Color Map is indicated,

   a default color map is generated internally  which  maps  each  possible

   incoming  color  index to the same hardware color index modulo <n> where

   <n> is the number of available hardware colors.

Graphics Interchange Format (GIF)                                    Page 6

Specification

 IMAGE DESCRIPTOR

        The Image Descriptor defines the actual placement  and  extents  of

   the  following  image within the space defined in the Screen Descriptor.

   Also defined are flags to indicate the presence of a local color  lookup

   map, and to define the pixel display sequence.  Each Image Descriptor is

   introduced by an image separator  character.   The  role  of  the  Image

   Separator  is simply to provide a synchronization character to introduce

   an Image Descriptor.  This is desirable if a GIF file happens to contain

   more  than  one  image.   This  character  is defined as 0x2C hex or ','

   (comma).  When this character is encountered between images,  the  Image

   Descriptor will follow immediately.

        Any characters encountered between the end of a previous image  and

   the image separator character are to be ignored.  This allows future GIF

   enhancements to be present in newer image formats and yet ignored safely

   by older software decoders.

              bits

         7 6 5 4 3 2 1 0  Byte #

        +---------------+

        |0 0 1 0 1 1 0 0|  1    ',' - Image separator character

        +---------------+

        |               |  2    Start of image in pixels from the

        +-  Image Left -+       left side of the screen (LSB first)

        |               |  3

        +---------------+

        |               |  4

        +-  Image Top  -+       Start of image in pixels from the

        |               |  5    top of the screen (LSB first)

        +---------------+

        |               |  6

        +- Image Width -+       Width of the image in pixels (LSB first)

        |               |  7

        +---------------+

        |               |  8

        +- Image Height-+       Height of the image in pixels (LSB first)

        |               |  9

        +-+-+-+-+-+-----+       M=0 - Use global color map, ignore 'pixel'

        |M|I|0|0|0|pixel| 10    M=1 - Local color map follows, use 'pixel'

        +-+-+-+-+-+-----+       I=0 - Image formatted in Sequential order

                                I=1 - Image formatted in Interlaced order

                                pixel+1 - # bits per pixel for this image

        The specifications for the image position and size must be confined

   to  the  dimensions defined by the Screen Descriptor.  On the other hand

   it is not necessary that the image fill the entire screen defined.

 LOCAL COLOR MAP

Graphics Interchange Format (GIF)                                    Page 7

Specification

        A Local Color Map is optional and defined here for future use.   If

   the  'M' bit of byte 10 of the Image Descriptor is set, then a color map

   follows the Image Descriptor that applies only to the  following  image.

   At the end of the image, the color map will revert to that defined after

   the Screen Descriptor.  Note that the 'pixel' field of byte  10  of  the

   Image  Descriptor  is used only if a Local Color Map is indicated.  This

   defines the parameters not only for the image pixel size, but determines

   the  number  of color map entries that follow.  The bits per pixel value

   will also revert to the value specified in the  Screen  Descriptor  when

   processing of the image is complete.

 RASTER DATA

        The format of the actual image is defined as the  series  of  pixel

   color  index  values that make up the image.  The pixels are stored left

   to right sequentially for an image row.  By default each  image  row  is

   written  sequentially, top to bottom.  In the case that the Interlace or

   'I' bit is set in byte 10 of the Image Descriptor then the row order  of

   the  image  display  follows  a  four-pass process in which the image is

   filled in by widely spaced rows.  The first pass writes every  8th  row,

   starting  with  the top row of the image window.  The second pass writes

   every 8th row starting at the fifth row from the top.   The  third  pass

   writes every 4th row starting at the third row from the top.  The fourth

   pass completes the image, writing  every  other  row,  starting  at  the

   second row from the top.  A graphic description of this process follows:

   Image

   Row  Pass 1  Pass 2  Pass 3  Pass 4          Result

   ---------------------------------------------------

     0  **1a**                                  **1a**

     1                          **4a**          **4a**

     2                  **3a**                  **3a**

     3                          **4b**          **4b**

     4          **2a**                          **2a**

     5                          **4c**          **4c**

     6                  **3b**                  **3b**

     7                          **4d**          **4d**

     8  **1b**                                  **1b**

     9                          **4e**          **4e**

    10                  **3c**                  **3c**

    11                          **4f**          **4f**

    12          **2b**                          **2b**

   . . .

        The image pixel values are processed as a series of  color  indices

   which  map  into the existing color map.  The resulting color value from

   the map is what is actually displayed.  This series  of  pixel  indices,

   the  number  of  which  is equal to image-width*image-height pixels, are

   passed to the GIF image data stream one value per pixel, compressed  and

   packaged  according  to  a  version  of the LZW compression algorithm as

   defined in Appendix C.

Graphics Interchange Format (GIF)                                    Page 8

Specification

 GIF TERMINATOR

        In order to provide a synchronization for the termination of a  GIF

   image  file,  a  GIF  decoder  will process the end of GIF mode when the

   character 0x3B hex or ';' is found after an image  has  been  processed.

   By  convention  the  decoding software will pause and wait for an action

   indicating that the user is ready to continue.  This may be  a  carriage

   return  entered  at  the  keyboard  or  a  mouse click.  For interactive

   applications this user action must  be  passed  on  to  the  host  as  a

   carriage  return  character  so  that the host application can continue.

   The decoding software will then typically leave graphics mode and resume

   any previous process.

 GIF EXTENSION BLOCKS

        To provide for orderly extension of the GIF definition, a mechanism

   for  defining  the  packaging  of extensions within a GIF data stream is

   necessary.  Specific GIF extensions are to be defined and documented  by

   CompuServe in order to provide a controlled enhancement path.

        GIF Extension Blocks are packaged in a manner similar to that  used

   by the raster data though not compressed.  The basic structure is:

         7 6 5 4 3 2 1 0  Byte #

        +---------------+

        |0 0 1 0 0 0 0 1|  1       '!' - GIF Extension Block Introducer

        +---------------+

        | function code |  2       Extension function code (0 to 255)

        +---------------+    ---+

        |  byte count   |       |

        +---------------+       |

        :               :       +-- Repeated as many times as necessary

        |func data bytes|       |

        :               :       |

        +---------------+    ---+

        . . .       . . .

        +---------------+

        |0 0 0 0 0 0 0 0|       zero byte count (terminates block)

        +---------------+

        A GIF Extension Block may immediately preceed any Image  Descriptor

   or occur before the GIF Terminator.

        All GIF decoders must be able to recognize  the  existence  of  GIF

   Extension  Blocks  and  read past them if unable to process the function

   code.  This ensures that older decoders will be able to process extended

   GIF   image   files   in  the  future,  though  without  the  additional

   functionality.

Graphics Interchange Format (GIF)                                    Page 9

Appendix A - Glossary

                                 GLOSSARY

Pixel - The smallest picture element of a  graphics  image.   This  usually

   corresponds  to  a single dot on a graphics screen.  Image resolution is

   typically given in units of  pixels.   For  example  a  fairly  standard

   graphics  screen  format  is  one 320 pixels across and 200 pixels high.

   Each pixel can  appear  as  one  of  several  colors  depending  on  the

   capabilities of the graphics hardware.

Raster - A horizontal row of pixels representing one line of an  image.   A

   typical method of working with images since most hardware is oriented to

   work most efficiently in this manner.

LSB - Least Significant Byte.  Refers to a convention for two byte  numeric

   values in which the less significant byte of the value preceeds the more

   significant byte.  This convention is typical on many microcomputers.

Color Map - The list of definitions of each color  used  in  a  GIF  image.

   These  desired  colors are converted to available colors through a table

   which is derived by assigning an incoming color index (from  the  image)

   to  an  output  color  index  (of  the  hardware).   While the color map

   definitons are specified in a GIF image, the output  pixel  colors  will

   vary  based  on  the  hardware used and its ability to match the defined

   color.

Interlace - The method of displaying a GIF image in which  multiple  passes

   are  made,  outputting  raster  lines  spaced  apart to provide a way of

   visualizing the general content of an entire image  before  all  of  the

   data has been processed.

B Protocol - A CompuServe-developed error-correcting file transfer protocol

   available  in  the  public  domain  and implemented in CompuServe VIDTEX

   products.  This error checking mechanism will be used  in  transfers  of

   GIF images for interactive applications.

LZW - A sophisticated data compression algorithm  based  on  work  done  by

   Lempel-Ziv  &  Welch  which  has  the feature of very efficient one-pass

   encoding and decoding.  This allows the image  to  be  decompressed  and

   displayed  at  the  same  time.   The  original  article from which this

   technique was adapted is:

          Terry  A.   Welch,  "A  Technique  for  High   Performance   Data

          Compression", IEEE Computer, vol 17 no 6 (June 1984)

        This basic algorithm is also used in the  public  domain  ARC  file

   compression  utilities.   The  CompuServe  adaptation  of LZW for GIF is

   described in Appendix C.

Graphics Interchange Format (GIF)                                   Page 10

Appendix B - Interactive Sequences

           GIF Sequence Exchanges for an Interactive Environment

        The following sequences are defined for use  in  mediating  control

   between a GIF sender and GIF receiver over an interactive communications

   line.  These  sequences  do  not  apply  to  applications  that  involve

   downloading  of  static  GIF  files and are not considered part of a GIF

   file.

 GIF CAPABILITIES ENQUIRY

        The GCE sequence is issued from a host and requests an  interactive

   GIF  decoder  to  return  a  response  message that defines the graphics

   parameters for the decoder.  This involves returning  information  about

   available screen sizes, number of bits/color supported and the amount of

   color detail supported.  The escape sequence for the GCE is defined as:

        ESC [ > 0 g     (g is lower case, spaces inserted for clarity)

                         (0x1B 0x5B 0x3E 0x30 0x67)

 GIF CAPABILITIES RESPONSE

        The GIF Capabilities Response message is returned by an interactive

   GIF  decoder  and  defines  the  decoder's  display capabilities for all

   graphics modes that are supported by the software.  Note that  this  can

   also include graphics printers as well as a monitor screen.  The general

   format of this message is:

     #version;protocol{;dev, width, height, color-bits, color-res}... <CR>

   '#'          - GCR identifier character (Number Sign)

   version      - GIF format version number;  initially '87a'

   protocol='0' - No end-to-end protocol supported by decoder

                  Transfer as direct 8-bit data stream.

   protocol='1' - Can use an error correction protocol to transfer GIF data

               interactively from the host directly to the display.

   dev = '0'    - Screen parameter set follows

   dev = '1'    - Printer parameter set follows

   width- Maximum supported display width in pixels

   height       - Maximum supported display height in pixels

   color-bits   - Number of  bits  per  pixel  supported.   The  number  of

               supported colors is therefore 2**color-bits.

   color-res    - Number of bits  per  color  component  supported  in  the

               hardware  color  palette.   If  color-res  is  '0'  then  no

               hardware palette table is available.

        Note that all values in the  GCR  are  returned  as  ASCII  decimal

   numbers and the message is terminated by a Carriage Return character.

Graphics Interchange Format (GIF)                                   Page 11

Appendix B - Interactive Sequences

        The  following   GCR   message   describes   three   standard   EGA

   configurations  with  no  printer;  the GIF data stream can be processed

   within an error correcting protocol:

        #87a;1 ;0,320,200,4,0 ;0,640,200,2,2 ;0,640,350,4,2<CR>

 ENTER GIF GRAPHICS MODE

        Two sequences are currently defined to invoke  an  interactive  GIF

   decoder into action.  The only difference between them is that different

   output media are selected.  These sequences are:

     ESC [ > 1 g   Display GIF image on screen

                   (0x1B 0x5B 0x3E 0x31 0x67)

     ESC [ > 2 g   Display image directly to an attached graphics  printer.

                   The  image  may optionally be displayed on the screen as

                   well.

                   (0x1B 0x5B 0x3E 0x32 0x67)

        Note that the 'g' character terminating each sequence is  in  lower

   case.

 INTERACTIVE ENVIRONMENT

        The assumed environment for the transmission of GIF image data from

   an  interactive  application  is  a  full 8-bit data stream from host to

   micro.  All 256 character codes must be transferrable.  The establishing

   of  an 8-bit data path for communications will normally be taken care of

   by the host application programs.  It is however  up  to  the  receiving

   communications programs supporting GIF to be able to receive and pass on

   all 256 8-bit codes to the GIF decoder software.

Graphics Interchange Format (GIF)                                   Page 12

Appendix C - Image Packaging & Compression

        The Raster Data stream that represents the actual output image  can

   be represented as:

         7 6 5 4 3 2 1 0

        +---------------+

        |   code size   |

        +---------------+     ---+

        |blok byte count|        |

        +---------------+        |

        :               :        +-- Repeated as many times as necessary

        |  data bytes   |        |

        :               :        |

        +---------------+     ---+

        . . .       . . .

        +---------------+

        |0 0 0 0 0 0 0 0|       zero byte count (terminates data stream)

        +---------------+

        The conversion of the image from a series  of  pixel  values  to  a

   transmitted or stored character stream involves several steps.  In brief

   these steps are:

   1.  Establish the Code Size -  Define  the  number  of  bits  needed  to

       represent the actual data.

   2.  Compress the Data - Compress the series of image pixels to a  series

       of compression codes.

   3.  Build a Series of Bytes - Take the  set  of  compression  codes  and

       convert to a string of 8-bit bytes.

   4.  Package the Bytes - Package sets of bytes into blocks  preceeded  by

       character counts and output.

ESTABLISH CODE SIZE

        The first byte of the GIF Raster Data stream is a value  indicating

   the minimum number of bits required to represent the set of actual pixel

   values.  Normally this will be the same as the  number  of  color  bits.

   Because  of  some  algorithmic constraints however, black & white images

   which have one color bit must be indicated as having a code size  of  2.

   This  code size value also implies that the compression codes must start

   out one bit longer.

COMPRESSION

        The LZW algorithm converts a series of data values into a series of

   codes  which may be raw values or a code designating a series of values.

   Using text characters as an analogy,  the  output  code  consists  of  a

   character or a code representing a string of characters.

Graphics Interchange Format (GIF)                                   Page 13

Appendix C - Image Packaging & Compression

        The LZW algorithm used in  GIF  matches  algorithmically  with  the

   standard LZW algorithm with the following differences:

   1.  A   special   Clear   code   is    defined    which    resets    all

       compression/decompression parameters and tables to a start-up state.

       The value of this code is 2**<code size>.  For example if  the  code

       size  indicated  was 4 (image was 4 bits/pixel) the Clear code value

       would be 16 (10000 binary).  The Clear code can appear at any  point

       in the image data stream and therefore requires the LZW algorithm to

       process succeeding codes as if  a  new  data  stream  was  starting.

       Encoders  should output a Clear code as the first code of each image

       data stream.

   2.  An End of Information code is defined that explicitly indicates  the

       end  of  the image data stream.  LZW processing terminates when this

       code is encountered.  It must be the last code output by the encoder

       for an image.  The value of this code is <Clear code>+1.

   3.  The first available compression code value is <Clear code>+2.

   4.  The output codes are of variable length, starting  at  <code size>+1

       bits  per code, up to 12 bits per code.  This defines a maximum code

       value of 4095 (hex FFF).  Whenever the LZW code value  would  exceed

       the  current  code length, the code length is increased by one.  The

       packing/unpacking of these codes must then be altered to reflect the

       new code length.

BUILD 8-BIT BYTES

        Because the LZW compression  used  for  GIF  creates  a  series  of

   variable  length  codes, of between 3 and 12 bits each, these codes must

   be reformed into a series of 8-bit bytes that  will  be  the  characters

   actually stored or transmitted.  This provides additional compression of

   the image.  The codes are formed into a stream of bits as if  they  were

   packed  right to left and then picked off 8 bits at a time to be output.

   Assuming a character array of 8 bits per character and using 5 bit codes

   to be packed, an example layout would be similar to:

         byte n       byte 5   byte 4   byte 3   byte 2   byte 1

        +-.....-----+--------+--------+--------+--------+--------+

        | and so on |hhhhhggg|ggfffffe|eeeedddd|dcccccbb|bbbaaaaa|

        +-.....-----+--------+--------+--------+--------+--------+

        Note that the physical  packing  arrangement  will  change  as  the

   number  of  bits per compression code change but the concept remains the

   same.

PACKAGE THE BYTES

        Once the bytes have been created, they are grouped into blocks  for

   output by preceeding each block of 0 to 255 bytes with a character count

   byte.  A block with a zero byte count terminates the Raster Data  stream

   for  a  given  image.  These blocks are what are actually output for the

Graphics Interchange Format (GIF)                                   Page 14

Appendix C - Image Packaging & Compression

   GIF image.  This block format has the side effect of allowing a decoding

   program  the  ability to read past the actual image data if necessary by

   reading block counts and then skipping over the data.

Graphics Interchange Format (GIF)                                   Page 15

Appendix D - Multiple Image Processing

        Since a  GIF  data  stream  can  contain  multiple  images,  it  is

   necessary  to  describe  processing and display of such a file.  Because

   the image descriptor allows  for  placement  of  the  image  within  the

   logical  screen,  it is possible to define a sequence of images that may

   each be a partial screen, but in total  fill  the  entire  screen.   The

   guidelines for handling the multiple image situation are:

   1.  There is no pause between images.  Each is processed immediately  as

       seen by the decoder.

   2.  Each image explicitly overwrites any image  already  on  the  screen

       inside  of  its window.  The only screen clears are at the beginning

       and end of the  GIF  image  process.   See  discussion  on  the  GIF

       terminator.




"EA IFF 85" Standard for Interchange Format Files

Document Date:          January 14, 1985
From:                   Jerry Morrison, Electronic Arts
Status of Standard:     Released and in use

1. Introduction

Standards are Good for Software Developers

As home computer hardware evolves to better and better media machines, 
the demand increases for higher quality, more detailed data. Data 
development gets more expensive, requires more expertise and better 
tools, and has to be shared across projects. Think about several ports 
of a product on one CD-ROM with 500M Bytes of common data!

Development tools need standard interchange file formats. Imagine 
scanning in images of "player" shapes, moving them to a paint program 
for editing, then incorporating them into a game. Or writing a theme 
song with a Macintosh score editor and incorporating it into an Amiga 
game. The data must at times be transformed, clipped, filled out, 
and moved across machine kinds. Media projects will depend on data 
transfer from graphic, music, sound effect, animation, and script 
tools.

Standards are Good for Software Users

Customers should be able to move their own data between independently 
developed software products. And they should be able to buy data libraries 
usable across many such products. The types of data objects to exchange 
are open-ended and include plain and formatted text, raster and structured 
graphics, fonts, music, sound effects, musical instrument descriptions, 
and animation.

The problem with expedient file formats typically memory dumps is 
that they're too provincial. By designing data for one particular 
use (e.g. a screen snapshot), they preclude future expansion (would 
you like a full page picture? a multi-page document?). In neglecting 
the possibility that other programs might read their data, they fail 
to save contextual information (how many bit planes? what resolution?). 
Ignoring that other programs might create such files, they're intolerant 
of extra data (texture palette for a picture editor), missing data 
(no color map), or minor variations (smaller image). In practice, 
a filed representation should rarely mirror an in-memory representation. 
The former should be designed for longevity; the latter to optimize 
the manipulations of a particular program. The same filed data will 
be read into different memory formats by different programs.

The IFF philosophy: "A little behind-the-scenes conversion when programs 
read and write files is far better than NxM explicit conversion utilities 
for highly specialized formats."

So we need some standardization for data interchange among development 
tools and products. The more developers that adopt a standard, the 
better for all of us and our customers.

Here is "EA IFF 1985"

Here is our offering: Electronic Arts' IFF standard for Interchange 
File Format. The full name is "EA IFF 1985". Alternatives and justifications 
are included for certain choices. Public domain subroutine packages 
and utility programs are available to make it easy to write and use 
IFF-compatible programs.

Part 1 introduces the standard. Part 2 presents its requirements and 
background. Parts 3, 4, and 5 define the primitive data types, FORMs, 
and LISTs, respectively, and how to define new high level types. Part 
6 specifies the top level file structure. Appendix A is included for 
quick reference and Appendix B names the committee responsible for 
this standard.

References

American National Standard Additional Control Codes for Use with ASCII, 
ANSI standard 3.64-1979 for an 8-bit character set. See also ISO standard 
2022 and ISO/DIS standard 6429.2.

Amiga[tm] is a trademark of Commodore-Amiga, Inc.

C, A Reference Manual, Samuel P. Harbison and Guy L. Steele Jr., Tartan 
Laboratories. Prentice-Hall, Englewood Cliffs, NJ, 1984.

Compiler Construction, An Advanced Course, edited by F. L. Bauer and 
J. Eickel (Springer-Verlag, 1976). This book is one of many sources 
for information on recursive descent parsing.

DIF Technical Specification (c)1981 by Software Arts, Inc. DIF[tm] is 
the format for spreadsheet data interchange developed by Software 
Arts, Inc.
DIF[tm] is a trademark of Software Arts, Inc.

Electronic Arts[tm] is a trademark of Electronic Arts.

"FTXT" IFF Formatted Text, from Electronic Arts. IFF supplement document 
for a text format.

Inside Macintosh (c) 1982, 1983, 1984, 1985 Apple Computer, Inc., a 
programmer's reference manual.
Apple(R) is a trademark of Apple Computer, Inc.
Macintosh[tm] is a trademark licensed to Apple Computer, Inc.

"ILBM" IFF Interleaved Bitmap, from Electronic Arts. IFF supplement 
document for a raster image format.

M68000 16/32-Bit Microprocessor Programmer's Reference Manual(c) 1984, 
1982, 1980, 1979 by Motorola, Inc.

PostScript Language Manual (c) 1984 Adobe Systems Incorporated.
PostScript[tm] is a trademark of Adobe Systems, Inc.
Times and Helvetica(R) are trademarks of Allied Corporation.

InterScript: A Proposal for a Standard for the Interchange of Editable 
Documents (c)1984 Xerox Corporation.
Introduction to InterScript (c) 1985 Xerox Corporation.



2. Background for Designers

Part 2 is about the background, requirements, and goals for the standard. 
It's geared for people who want to design new types of IFF objects. 
People just interested in using the standard may wish to skip this 
part.

What Do We Need?

A standard should be long on prescription and short on overhead. It 
should give lots of rules for designing programs and data files for 
synergy. But neither the programs nor the files should cost too much 
more than the expedient variety. While we're looking to a future with 
CD-ROMs and perpendicular recording, the standard must work well on 
floppy disks.

For program portability, simplicity, and efficiency, formats should 
be designed with more than one implementation style in mind. (In practice, 
pure stream I/O is adequate although random access makes it easier 
to write files.) It ought to be possible to read one of many objects 
in a file without scanning all the preceding data. Some programs need 
to read and play out their data in real time, so we need good compromises 
between generality and efficiency.

As much as we need standards, they can't hold up product schedules. 
So we also need a kind of decentralized extensibility where any software 
developer can define and refine new object types without some "standards 
authority" in the loop. Developers must be able to extend existing 
formats in a forward- and backward-compatible way. A central repository 
for design information and example programs can help us take full 
advantage of the standard.

For convenience, data formats should heed the restrictions of various 
processors and environments. E.g. word-alignment greatly helps 68000 
access at insignificant cost to 8088 programs.

Other goals include the ability to share common elements over a list 
of objects and the ability to construct composite objects containing 
other data objects with structural information like directories.

And finally, "Simple things should be simple and complex things should 
be possible."   Alan Kay.

Think Ahead

Let's think ahead and build programs that read and write files for 
each other and for programs yet to be designed. Build data formats 
to last for future computers so long as the overhead is acceptable. 
This extends the usefulness and life of today's programs and data.

To maximize interconnectivity, the standard file structure and the 
specific object formats must all be general and extensible. Think 
ahead when designing an object. It should serve many purposes and 
allow many programs to store and read back all the information they 
need; even squeeze in custom data. Then a programmer can store the 
available data and is encouraged to include fixed contextual details. 
Recipient programs can read the needed parts, skip unrecognized stuff, 
default missing data, and use the stored context to help transform 
the data as needed.

Scope

IFF addresses these needs by defining a standard file structure, some 
initial data object types, ways to define new types, and rules for 
accessing these files. We can accomplish a great deal by writing programs 
according to this standard, but don't expect direct compatibility 
with existing software. We'll need conversion programs to bridge the 
gap from the old world.

IFF is geared for computers that readily process information in 8-bit 
bytes. It assumes a "physical layer" of data storage and transmission 
that reliably maintains "files" as strings of 8-bit bytes. The standard 
treats a "file" as a container of data bytes and is independent of 
how to find a file and whether it has a byte count.

This standard does not by itself implement a clipboard for cutting 
and pasting data between programs. A clipboard needs software to mediate 
access, to maintain a "contents version number" so programs can detect 
updates, and to manage the data in "virtual memory".

Data Abstraction

The basic problem is how to represent information  in a way that's 
program-independent, compiler- independent, machine-independent, and 
device-independent.

The computer science approach is "data abstraction", also known as 
"objects", "actors", and "abstract data types". A data abstraction 
has a "concrete representation" (its storage format), an "abstract 
representation" (its capabilities and uses), and access procedures 
that isolate all the calling software from the concrete representation. 
Only the access procedures touch the data storage. Hiding mutable 
details behind an interface is called "information hiding". What data 
abstraction does is abstract from details of implementing the object, 
namely the selected storage representation and algorithms for manipulating 
it.

The power of this approach is modularity. By adjusting the access 
procedures we can extend and restructure the data without impacting 
the interface or its callers. Conversely, we can extend and restructure 
the interface and callers without making existing data obsolete. It's 
great for interchange!

But we seem to need the opposite: fixed file formats for all programs 
to access. Actually, we could file data abstractions ("filed objects") 
by storing the data and access procedures together. We'd have to encode 
the access procedures in a standard machine-independent programming 
language   la PostScript. Even still, the interface can't evolve freely 
since we can't update all copies of the access procedures. So we'll 
have to design our abstract representations for limited evolution 
and occasional revolution (conversion).

In any case, today's microcomputers can't practically store data abstractions. 
They can do the next best thing: store arbitrary types of data in 
"data chunks", each with a type identifier and a length count. The 
type identifier is a reference by name to the access procedures (any 
local implementation). The length count enables storage-level object 
operations like "copy" and "skip to next" independent of object type.

Chunk writing is straightforward. Chunk reading requires a trivial 
parser to scan each chunk and dispatch to the proper access/conversion 
procedure. Reading chunks nested inside other chunks requires recursion, 
but no lookahead or backup.

That's the main idea of IFF. There are, of course, a few other detailsI

Previous Work

Where our needs are similar, we borrow from existing standards.

Our basic need to move data between independently developed programs 
is similar to that addressed by the Apple Macintosh desk scrap or 
"clipboard" [Inside Macintosh chapter "Scrap Manager"]. The Scrap 
Manager works closely with the Resource Manager, a handy filer and 
swapper for data objects (text strings, dialog window templates, pictures, 
fontsI) including types yet to be designed [Inside Macintosh chapter 
"Resource Manager"]. The Resource Manager is a kin to Smalltalk's 
object swapper.

We will probably write a Macintosh desk accessory that converts IFF 
files to and from the Macintosh clipboard for quick and easy interchange 
with programs like MacPaint and Resource Mover.

Macintosh uses a simple and elegant scheme of 4-character "identifiers" 
to identify resource types, clipboard format types, file types, and 
file creator programs. Alternatives are unique ID numbers assigned 
by a central authority or by hierarchical authorities, unique ID numbers 
generated by algorithm, other fixed length character strings, and 
variable length strings. Character string identifiers double as readable 
signposts in data files and programs. The choice of 4 characters is 
a good tradeoff between storage space, fetch/compare/store time, and 
name space size. We'll honor Apple's designers by adopting this scheme.

"PICT" is a good example of a standard structured graphics format 
(including raster images) and its many uses [Inside Macintosh chapter 
"QuickDraw"]. Macintosh provides QuickDraw routines in ROM to create, 
manipulate, and display PICTs. Any application can create a PICT by 
simply asking QuickDraw to record a sequence of drawing commands. 
Since it's just as easy to ask QuickDraw to render a PICT to a screen 
or a printer, it's very effective to pass them between programs, say 
from an illustrator to a word processor. An important feature is the 
ability to store "comments" in a PICT which QuickDraw will ignore. 
Actually, it passes them to your optional custom "comment handler".

PostScript, Adobe's print file standard, is a more general way to 
represent any print image (which is a specification for putting marks 
on paper) [PostScript Language Manual]. In fact, PostScript is a full-fledged 
programming language. To interpret a PostScript program is to render 
a document on a raster output device. The language is defined in layers: 
a lexical layer of identifiers, constants, and operators; a layer 
of reverse polish semantics including scope rules and a way to define 
new subroutines; and a printing-specific layer of built-in identifiers 
and operators for rendering graphic images. It is clearly a powerful 
(Turing equivalent) image definition language. PICT and a subset of 
PostScript are candidates for structured graphics standards.

A PostScript document can be printed on any raster output device (including 
a display) but cannot generally be edited. That's because the original 
flexibility and constraints have been discarded. Besides, a PostScript 
program may use arbitrary computation to supply parameters like placement 
and size to each operator. A QuickDraw PICT, in comparison, is a more 
restricted format of graphic primitives parameterized by constants. 
So a PICT can be edited at the level of the primitives, e.g. move 
or thicken a line. It cannot be edited at the higher level of, say, 
the bar chart data which generated the picture.

PostScript has another limitation: Not all kinds of data amount to 
marks on paper. A musical instrument description is one example. PostScript 
is just not geared for such uses.

"DIF" is another example of data being stored in a general format 
usable by future programs [DIF Technical Specification]. DIF is a 
format for spreadsheet data interchange. DIF and PostScript are both 
expressed in plain ASCII text files. This is very handy for printing, 
debugging, experimenting, and transmitting across modems. It can have 
substantial cost in compaction and read/write work, depending on use. 
We won't store IFF files this way but we could define an ASCII alternate 
representation with a converter program.

InterScript is Xerox' standard for interchange of editable documents 
[Introduction to InterScript]. It approaches a harder problem: How 
to represent editable word processor documents that may contain formatted 
text, pictures, cross-references like figure numbers, and even highly 
specialized objects like mathematical equations? InterScript aims 
to define one standard representation for each kind of information. 
Each InterScript-compatible editor is supposed to preserve the objects 
it doesn't understand and even maintain nested cross-references. So 
a simple word processor would let you edit the text of a fancy document 
without discarding the equations or disrupting the equation numbers.

Our task is similarly to store high level information and preserve 
as much content as practical while moving it between programs. But 
we need to span a larger universe of data types and cannot expect 
to centrally define them all. Fortunately, we don't need to make programs 
preserve information that they don't understand. And for better or 
worse, we don't have to tackle general-purpose cross-references yet.



3. Primitive Data Types

Atomic components such as integers and characters that are interpretable 
directly by the CPU are specified in one format for all processors. 
We chose a format that's most convenient for the Motorola MC68000 
processor [M68000 16/32-Bit Microprocessor Programmer's Reference 
Manual].

N.B.: Part 3 dictates the format for "primitive" data types where and 
only where used in the overall file structure and standard kinds of 
chunks (Cf. Chunks). The number of such occurrences will be small 
enough that the costs of conversion, storage, and management of processor-
specific files would far exceed the costs of conversion during I/O by "foreign" 
programs. A particular data chunk may be specified with a different 
format for its internal primitive types or with processor- or environment-
speci fic variants if necessary to optimize local usage. Since that hurts 
data interchange, it's not recommended. (Cf. Designing New Data Sections, 
in Part 4.)

Alignment

All data objects larger than a byte are aligned on even byte addresses 
relative to the start of the file. This may require padding. Pad bytes 
are to be written as zeros, but don't count on that when reading.

This means that every odd-length "chunk" (see below) must be padded 
so that the next one will fall on an even boundary. Also, designers 
of structures to be stored in chunks should include pad fields where 
needed to align every field larger than a byte. Zeros should be stored 
in all the pad bytes.

Justification: Even-alignment causes a little extra work for files 
that are used only on certain processors but allows 68000 programs 
to construct and scan the data in memory and do block I/O. You just 
add an occasional pad field to data structures that you're going to 
block read/write or else stream read/write an extra byte. And the 
same source code works on all processors. Unspecified alignment, on 
the other hand, would force 68000 programs to (dis)assemble word and 
long-word data one byte at a time. Pretty cumbersome in a high level 
language. And if you don't conditionally compile that out for other 
processors, you won't gain anything.

Numbers

Numeric types supported are two's complement binary integers in the 
format used by the MC68000 processor high byte first, high word first the 
reverse of 8088 and 6502 format. They could potentially include signed 
and unsigned 8, 16, and 32 bit integers but the standard only uses 
the following:

UBYTE    8 bits unsigned
WORD    16 bits signed
UWORD   16 bits unsigned
LONG    32 bits signed

The actual type definitions depend on the CPU and the compiler. In 
this document, we'll express data type definitions in the C programming 
language. [See C, A Reference Manual.] In 68000 Lattice C:

typedef unsigned char   UBYTE;  /*  8 bits unsigned     */
typedef short   WORD;   /* 16 bits signed       */
typedef unsigned short  UWORD;  /* 16 bits unsigned     */
typedef long    LONG;   /* 32 bits signed       */

Characters

The following character set is assumed wherever characters are used, 
e.g. in text strings, IDs, and TEXT chunks (see below).

Characters are encoded in 8-bit ASCII. Characters in the range NUL 
(hex 0) through DEL (hex 7F) are well defined by the 7-bit ASCII standard. 
IFF uses the graphic group RJS (SP, hex 20) through R~S (hex 7E).

Most of the control character group hex 01 through hex 1F have no 
standard meaning in IFF. The control character LF (hex 0A) is defined 
as a "newline" character. It denotes an intentional line break, that 
is, a paragraph or line terminator. (There is no way to store an automatic 
line break. That is strictly a function of the margins in the environment 
the text is placed.) The control character ESC (hex 1B) is a reserved 
escape character under the rules of ANSI standard 3.64-1979 American 
National Standard Additional Control Codes for Use with ASCII, ISO 
standard 2022, and ISO/DIS standard 6429.2.

Characters in the range hex 7F through hex FF are not globally defined 
in IFF. They are best left reserved for future standardization. But 
note that the FORM type FTXT (formatted text) defines the meaning 
of these characters within FTXT forms. In particular, character values 
hex 7F through hex 9F are control codes while characters hex A0 through 
hex FF are extended graphic characters like  , as per the ISO and 
ANSI standards cited above. [See the supplementary document "FTXT" 
IFF Formatted Text.]

Dates

A "creation date" is defined as the date and time a stream of data 
bytes was created. (Some systems call this a "last modified date".) 
Editing some data changes its creation date. Moving the data between 
volumes or machines does not.

The IFF standard date format will be one of those used in MS-DOS, 
Macintosh, or Amiga DOS (probably a 32-bit unsigned number of seconds 
since a reference point). Issue: Investigate these three.

Type IDs

A "type ID", "property name", "FORM type", or any other IFF identifier 
is a 32-bit value: the concatenation of four ASCII characters in the 
range R S (SP, hex 20) through R~S (hex 7E). Spaces (hex 20) should 
not precede printing characters; trailing spaces are ok. Control characters 
are forbidden.

typedef CHAR ID[4];

IDs are compared using a simple 32-bit case-dependent equality test.

Data section type IDs (aka FORM types) are restriced IDs. (Cf. Data 
Sections.) Since they may be stored in filename extensions (Cf. Single 
Purpose Files) lower case letters and punctuation marks are forbidden. 
Trailing spaces are ok.

Carefully choose those four characters when you pick a new ID. Make 
them mnemonic so programmers can look at an interchange format file 
and figure out what kind of data it contains. The name space makes 
it possible for developers scattered around the globe to generate 
ID values with minimal collisions so long as they choose specific 
names like "MUS4" instead of general ones like "TYPE" and "FILE". 
EA will "register" new FORM type IDs and format descriptions as they're 
devised, but collisions will be improbable so there will be no pressure 
on this "clearinghouse" process. Appendix A has a list of currently 
defined IDs.

Sometimes it's necessary to make data format changes that aren't backward 
compatible. Since IDs are used to denote data formats in IFF, new 
IDs are chosen to denote revised formats. Since programs won't read 
chunks whose IDs they don't recognize (see Chunks, below), the new 
IDs keep old programs from stumbling over new data. The conventional 
way to chose a "revision" ID is to increment the last character if 
it's a digit or else change the last character to a digit. E.g. first 
and second revisions of the ID "XY" would be "XY1" and "XY2". Revisions 
of "CMAP" would be "CMA1" and "CMA2".

Chunks

Chunks are the building blocks in the IFF structure. The form expressed 
as a C typedef is:

typedef struct {
        ID      ckID;
        LONG    ckSize; /* sizeof(ckData) */
        UBYTE   ckData[/* ckSize */];
        } Chunk;

We can diagram an example chunk a "CMAP" chunk containing 12 data 
bytes like this:
                        ----------------
                ckID:   |    'CMAP'    |
                ckSize: |      12      |
                ckData: | 0, 0, 0, 32  |   -------- 
                        | 0, 0, 64, 0  |    12 bytes
                        | 0, 0, 64, 0  |   ---------
                        ----------------

The fixed header part means "Here's a type ckID chunk with ckSize 
bytes of data."

The ckID identifies the format and purpose of the chunk. As a rule, 
a program must recognize ckID to interpret ckData. It should skip 
over all unrecognized chunks. The ckID also serves as a format version 
number as long as we pick new IDs to identify new formats of ckData 
(see above).

The following ckIDs are universally reserved to identify chunks with 
particular IFF meanings: "LIST", "FORM", "PROP", "CAT ", and "    
". The special ID "    " (4 spaces) is a ckID for "filler" chunks, 
that is, chunks that fill space but have no meaningful contents. The 
IDs "LIS1" through "LIS9", "FOR1" through "FOR9", and "CAT1" through 
"CAT9" are reserved for future "version number" variations. All IFF-compatible 
software must account for these 23 chunk IDs. Appendix A has a list 
of predefined IDs.

The ckSize is a logical block size how many data bytes are in ckData. 
If ckData is an odd number of bytes long, a 0 pad byte follows which 
is not included in ckSize. (Cf. Alignment.) A chunk's total physical 
size is ckSize rounded up to an even number plus the size of the header. 
So the smallest chunk is 8 bytes long with ckSize = 0. For the sake 
of following chunks, programs must respect every chunk's ckSize as 
a virtual end-of-file for reading its ckData even if that data is 
malformed, e.g. if nested contents are truncated.

We can describe the syntax of a chunk as a regular expression with 
"#" representing the ckSize, i.e. the length of the following {braced} 
bytes. The "[0]" represents a sometimes needed pad byte. (The regular 
expressions in this document are collected in Appendix A along with 
an explanation of notation.)

Chunk   ::= ID #{ UBYTE* } [0]

One chunk output technique is to stream write a chunk header, stream 
write the chunk contents, then random access back to the header to 
fill in the size. Another technique is to make a preliminary pass 
over the data to compute the size, then write it out all at once.

Strings, String Chunks, and String Properties

In a string of ASCII text, LF denotes a forced line break (paragraph 
or line terminator). Other control characters are not used. (Cf. Characters.)

The ckID for a chunk that contains a string of plain, unformatted 
text is "TEXT". As a practical matter, a text string should probably 
not be longer than 32767 bytes. The standard allows up to 231 - 1 
bytes.

When used as a data property (see below), a text string chunk may 
be 0 to 255 characters long. Such a string is readily converted to 
a C string or a Pascal STRING[255]. The ckID of a property must be 
the property name, not "TEXT".

When used as a part of a chunk or data property, restricted C string 
format is normally used. That means 0 to 255 characters followed by 
a NUL byte (ASCII value 0).

Data Properties

Data properties specify attributes for following (non-property) chunks. 
A data property essentially says "identifier = value", for example 
"XY = (10, 200)", telling something about following chunks. Properties 
may only appear inside data sections ("FORM" chunks, cf. Data Sections) 
and property sections ("PROP" chunks, cf. Group PROP).

The form of a data property is a special case of Chunk. The ckID is 
a property name as well as a property type. The ckSize should be small 
since data properties are intended to be accumulated in RAM when reading 
a file. (256 bytes is a reasonable upper bound.) Syntactically:

Property::= Chunk

When designing a data object, use properties to describe context information 
like the size of an image, even if they don't vary in your program. 
Other programs will need this information.

Think of property settings as assignments to variables in a programming 
language. Multiple assignments are redundant and local assignments 
temporarily override global assignments. The order of assignments 
doesn't matter as long as they precede the affected chunks. (Cf. LISTs, 
CATs, and Shared Properties.)

Each object type (FORM type) is a local name space for property IDs. 
Think of a "CMAP" property in a "FORM ILBM" as the qualified ID "ILBM.CMAP". 
Property IDs specified when an object type is designed (and therefore 
known to all clients) are called "standard" while specialized ones 
added later are "nonstandard".

Links

Issue: A standard mechanism for "links" or "cross references" is very 
desirable for things like combining images and sounds into animations. 
Perhaps we'll define "link" chunks within FORMs that refer to other 
FORMs or to specific chunks within the same and other FORMs. This 
needs further work. EA IFF 1985 has no standard link mechanism.

For now, it may suffice to read a list of, say, musical instruments, 
and then just refer to them within a musical score by index number.

File References

Issue: We may need a standard form for references to other files. 
A "file ref" could name a directory and a file in the same type of 
operating system as the ref's originator. Following the reference 
would expect the file to be on some mounted volume. In a network environment, 
a file ref could name a server, too.

Issue: How can we express operating-system independent file refs?

Issue: What about a means to reference a portion of another file? 
Would this be a "file ref" plus a reference to a "link" within the 
target file?



4. Data Sections

The first thing we need of a file is to check: Does it contain IFF 
data and, if so, does it contain the kind of data we're looking for? 
So we come to the notion of a "data section".

A "data section" or IFF "FORM" is one self-contained "data object" 
that might be stored in a file by itself. It is one high level data 
object such as a picture or a sound effect. The IFF structure "FORM" 
makes it self- identifying. It could be a composite object like a 
musical score with nested musical instrument descriptions.

Group FORM

A data section is a chunk with ckID "FORM" and this arrangement:

FORM    ::= "FORM" #{ FormType (LocalChunk | FORM | LIST | CAT)* 
}
FormType::= ID
LocalChunk      ::= Property | Chunk

The ID "FORM" is a syntactic keyword like "struct" in C. Think of 
a "struct ILBM" containing a field "CMAP". If you see "FORM" you'll 
know to expect a FORM type ID (the structure name, "ILBM" in this 
example) and a particular contents arrangement or "syntax" (local 
chunks, FORMs, LISTs, and CATs). (LISTs and CATs are discussed in 
part 5, below.) A "FORM ILBM", in particular, might contain a local 
chunk "CMAP", an "ILBM.CMAP" (to use a qualified name).

So the chunk ID "FORM" indicates a data section. It implies that the 
chunk contains an ID and some number of nested chunks. In reading 
a FORM, like any other chunk, programs must respect its ckSize as 
a virtual end-of-file for reading its contents, even if they're truncated.

The FormType (or FORM type) is a restricted ID that may not contain 
lower case letters or punctuation characters. (Cf. Type IDs. Cf. Single 
Purpose Files.)

The type-specific information in a FORM is composed of its "local 
chunks": data properties and other chunks. Each FORM type is a local 
name space for local chunk IDs. So "CMAP" local chunks in other FORM 
types may be unrelated to "ILBM.CMAP". More than that, each FORM type 
defines semantic scope. If you know what a FORM ILBM is, you'll know 
what an ILBM.CMAP is.

Local chunks defined when the FORM type is designed (and therefore 
known to all clients of this type) are called "standard" while specialized 
ones added later are "nonstandard".

Among the local chunks, property chunks give settings for various 
details like text font while the other chunks supply the essential 
information. This distinction is not clear cut. A property setting 
cancelled by a later setting of the same property has effect only 
on data chunks in between. E.g. in the sequence:

prop1 = x  (propN = value)*  prop1 = y

where the propNs are not prop1, the setting prop1 = x has no effect.

The following universal chunk IDs are reserved inside any FORM: "LIST", 
"FORM", "PROP", "CAT ", "JJJJ", "LIS1" through "LIS9", "FOR1" through 
"FOR9", and "CAT1" through "CAT9". (Cf. Chunks. Cf. Group LIST. Cf. 
Group PROP.) For clarity, these universal chunk names may not be FORM 
type IDs, either.

Part 5, below, talks about grouping FORMs into LISTs and CATs. They 
let you group a bunch of FORMs but don't impose any particular meaning 
or constraints on the grouping. Read on.

Composite FORMs

A FORM chunk inside a FORM is a full-fledged data section. This means 
you can build a composite object like a multi-frame animation sequence 
from available picture FORMs and sound effect FORMs. You can insert 
additional chunks with information like frame rate and frame count.

Using composite FORMs, you leverage on existing programs that create 
and edit the component FORMs. Those editors may even look into your 
composite object to copy out its type of component, although it'll 
be the rare program that's fancy enough to do that. Such editors are 
not allowed to replace their component objects within your composite 
object. That's because the IFF standard lets you specify consistency 
requirements for the composite FORM such as maintaining a count or 
a directory of the components. Only programs that are written to uphold 
the rules of your FORM type should create or modify such FORMs.

Therefore, in designing a program that creates composite objects, 
you are strongly requested to provide a facility for your users to 
import and export the nested FORMs. Import and export could move the 
data through a clipboard or a file.

Here are several existing FORM types and rules for defining new ones.

FTXT

An FTXT data section contains text with character formatting information 
like fonts and faces. It has no paragraph or document formatting information 
like margins and page headers. FORM FTXT is well matched to the text 
representation in Amiga's Intuition environment. See the supplemental 
document "FTXT" IFF Formatted Text.

ILBM

"ILBM" is an InterLeaved BitMap image with color map; a machine-independent 
format for raster images. FORM ILBM is the standard image file format 
for the Commodore-Amiga computer and is useful in other environments, 
too. See the supplemental document "ILBM" IFF Interleaved Bitmap.

PICS

The data chunk inside a "PICS" data section has ID "PICT" and holds 
a QuickDraw picture. Issue: Allow more than one PICT in a PICS? See 
Inside Macintosh chapter "QuickDraw" for details on PICTs and how 
to create and display them on the Macintosh computer.

The only standard property for PICS is "XY", an optional property 
that indicates the position of the PICT relative to "the big picture". 
The contents of an XY is a QuickDraw Point.

Note: PICT may be limited to Macintosh use, in which case there'll 
be another format for structured graphics in other environments.

Other Macintosh Resource Types

Some other Macintosh resource types could be adopted for use within 
IFF files; perhaps MWRT, ICN, ICN#, and STR#.

Issue: Consider the candidates and reserve some more IDs.

Designing New Data Sections

Supplemental documents will define additional object types. A supplement 
needs to specify the object's purpose, its FORM type ID, the IDs and 
formats of standard local chunks, and rules for generating and interpreting 
the data. It's a good idea to supply typedefs and an example source 
program that accesses the new object. See "ILBM" IFF Interleaved Bitmap 
for a good example.

Anyone can pick a new FORM type ID but should reserve it with Electronic 
Arts at their earliest convenience. [Issue: EA contact person? Hand 
this off to another organization?] While decentralized format definitions 
and extensions are possible in IFF, our preference is to get design 
consensus by committee, implement a program to read and write it, 
perhaps tune the format, and then publish the format with example 
code. Some organization should remain in charge of answering questions 
and coordinating extensions to the format.

If it becomes necessary to revise the design of some data section, 
its FORM type ID will serve as a version number (Cf. Type IDs). E.g. 
a revised "VDEO" data section could be called "VDE1". But try to get 
by with compatible revisions within the existing FORM type.

In a new FORM type, the rules for primitive data types and word-alignment 
(Cf. Primitive Data Types) may be overriden for the contents of its 
local chunks but not for the chunk structure itself if your documentation 
spells out the deviations. If machine-specific type variants are needed, 
e.g. to store vast numbers of integers in reverse bit order, then 
outline the conversion algorithm and indicate the variant inside each 
file, perhaps via different FORM types. Needless to say, variations 
should be minimized.

In designing a FORM type, encapsulate all the data that other programs 
will need to interpret your files. E.g. a raster graphics image should 
specify the image size even if your program always uses 320 x 200 
pixels x 3 bitplanes. Receiving programs are then empowered to append 
or clip the image rectangle, to add or drop bitplanes, etc. This enables 
a lot more compatibility.

Separate the central data (like musical notes) from more specialized 
information (like note beams) so simpler programs can extract the 
central parts during read-in. Leave room for expansion so other programs 
can squeeze in new kinds of information (like lyrics). And remember 
to keep the property chunks manageably short let's say 2 256 bytes.

When designing a data object, try to strike a good tradeoff between 
a super-general format and a highly-specialized one. Fit the details 
to at least one particular need, for example a raster image might 
as well store pixels in the current machine's scan order. But add 
the kind of generality that makes it usable with foreseeable hardware 
and software. E.g. use a whole byte for each red, green, and blue 
color value even if this year's computer has only 4-bit video DACs. 
Think ahead and help other programs so long as the overhead is acceptable. 
E.g. run compress a raster by scan line rather than as a unit so future 
programs can swap images by scan line to and from secondary storage.

Try to design a general purpose "least common multiple" format that 
encompasses the needs of many programs without getting too complicated. 
Let's coalesce our uses around a few such formats widely separated 
in the vast design space. Two factors make this flexibility and simplicity 
practical. First, file storage space is getting very plentiful, so 
compaction is not a priority. Second, nearly any locally-performed 
data conversion work during file reading and writing will be cheap 
compared to the I/O time.

It must be ok to copy a LIST or FORM or CAT intact, e.g. to incorporate 
it into a composite FORM. So any kind of internal references within 
a FORM must be relative references. They could be relative to the 
start of the containing FORM, relative from the referencing chunk, 
or a sequence number into a collection.

With composite FORMs, you leverage on existing programs that create 
and edit the components. If you write a program that creates composite 
objects, please provide a facility for your users to import and export 
the nested FORMs. The import and export functions may move data through 
a separate file or a clipboard.

Finally, don't forget to specify all implied rules in detail.



5. LISTs, CATs, and Shared Properties

Data often needs to be grouped together like a list of icons. Sometimes 
a trick like arranging little images into a big raster works, but 
generally they'll need to be structured as a first class group. The 
objects "LIST" and "CAT" are IFF-universal mechanisms for this purpose.

Property settings sometimes need to be shared over a list of similar 
objects. E.g. a list of icons may share one color map. LIST provides 
a means called "PROP" to do this. One purpose of a LIST is to define 
the scope of a PROP. A "CAT", on the other hand, is simply a concatenation 
of objects.

Simpler programs may skip LISTs and PROPs altogether and just handle 
FORMs and CATs. All "fully-conforming" IFF programs also know about 
"CAT ", "LIST", and "PROP". Any program that reads a FORM inside a 
LIST must process shared PROPs to correctly interpret that FORM.

Group CAT

A CAT is just an untyped group of data objects.

Structurally, a CAT is a chunk with chunk ID "CAT " containing a "contents 
type" ID followed by the nested objects. The ckSize of each contained 
chunk is essentially a relative pointer to the next one.

CAT     ::= "CAT " #{ ContentsType (FORM | LIST | CAT)* }
ContentsType    ::= ID  -- a hint or an "abstract data type" ID

In reading a CAT, like any other chunk, programs must respect it's 
ckSize as a virtual end-of-file for reading the nested objects even 
if they're malformed or truncated.

The "contents type" following the CAT's ckSize indicates what kind 
of FORMs are inside. So a CAT of ILBMs would store "ILBM" there. It's 
just a hint. It may be used to store an "abstract data type". A CAT 
could just have blank contents ID ("JJJJ") if it contains more than 
one kind of FORM.

CAT defines only the format of the group. The group's meaning is open 
to interpretation. This is like a list in LISP: the structure of cells 
is predefined but the meaning of the contents as, say, an association 
list depends on use. If you need a group with an enforced meaning 
(an "abstract data type" or Smalltalk "subclass"), some consistency 
constraints, or additional data chunks, use a composite FORM instead 
(Cf. Composite FORMs).

Since a CAT just means a concatenation of objects, CATs are rarely 
nested. Programs should really merge CATs rather than nest them.

Group LIST

A LIST defines a group very much like CAT but it also gives a scope 
for PROPs (see below). And unlike CATs, LISTs should not be merged 
without understanding their contents.

Structurally, a LIST is a chunk with ckID "LIST" containing a "contents 
type" ID, optional shared properties, and the nested contents (FORMs, 
LISTs, and CATs), in that order. The ckSize of each contained chunk 
is a relative pointer to the next one. A LIST is not an arbitrary 
linked list the cells are simply concatenated.

LIST    ::= "LIST" #{ ContentsType PROP* (FORM | LIST | CAT)* }
ContentsType    ::= ID

Group PROP

PROP chunks may appear in LISTs (not in FORMs or CATs). They supply 
shared properties for the FORMs in that LIST. This ability to elevate 
some property settings to shared status for a list of forms is useful 
for both indirection and compaction. E.g. a list of images with the 
same size and colors can share one "size" property and one "color 
map" property. Individual FORMs can override the shared settings.

The contents of a PROP is like a FORM with no data chunks:

PROP    ::= "PROP" #{ FormType Property* }

It means, "Here are the shared properties for FORM type <<FormType>."

A LIST may have at most one PROP of a FORM type, and all the PROPs 
must appear before any of the FORMs or nested LISTs and CATs. You 
can have subsequences of FORMs sharing properties by making each subsequence 
a LIST.

Scoping: Think of property settings as variable bindings in nested 
blocks of a programming language. Where in C you could write:

TEXT_FONT text_font = Courier;  /* program's global default     */

File(); {
        TEXT_FONT text_font = TimesRoman;       /* shared setting       */

                {
                TEXT_FONT text_font = Helvetica;  /* local setting      */
                Print("Hello ");/* uses font Helvetica  */
                }

                {
                Print("there.");/* uses font TimesRoman */
                }
        }

An IFF file could contain:

LIST {
        PROP TEXT {
                FONT {TimesRoman}       /* shared setting       */
                }

        FORM TEXT {
                FONT {Helvetica}/* local setting*/
                CHRS {Hello }           /* uses font Helvetica  */
                }

        FORM TEXT {
                CHRS {there.}   /* uses font TimesRoman */
                }
        }

The shared property assignments selectively override the reader's 
global defaults, but only for FORMs within the group. A FORM's own 
property assignments selectively override the global and group-supplied 
values. So when reading an IFF file, keep property settings on a stack. 
They're designed to be small enough to hold in main memory.

Shared properties are semantically equivalent to copying those properties 
into each of the nested FORMs right after their FORM type IDs.

Properties for LIST

Optional "properties for LIST" store the origin of the list's contents 
in a PROP chunk for the fake FORM type "LIST". They are the properties 
originating program "OPGM", processor family "OCPU", computer type 
"OCMP", computer serial number or network address "OSN ", and user 
name "UNAM". In our imperfect world, these could be called upon to 
distinguish between unintended variations of a data format or to work 
around bugs in particular originating/receiving program pairs. Issue: 
Specify the format of these properties.

A creation date could also be stored in a property but let's ask that 
file creating, editing, and transporting programs maintain the correct 
date in the local file system. Programs that move files between machine 
types are expected to copy across the creation dates.



6. Standard File Structure

File Structure Overview

An IFF file is just a single chunk of type FORM, LIST, or CAT. Therefore 
an IFF file can be recognized by its first 4 bytes: "FORM", "LIST", 
or "CAT ". Any file contents after the chunk's end are to be ignored.

Since an IFF file can be a group of objects, programs that read/write 
single objects can communicate to an extent with programs that read/write 
groups. You're encouraged to write programs that handle all the objects 
in a LIST or CAT. A graphics editor, for example, could process a 
list of pictures as a multiple page document, one page at a time.

Programs should enforce IFF's syntactic rules when reading and writing 
files. This ensures robust data transfer. The public domain IFF reader/writer 
subroutine package does this for you. A utility program "IFFCheck" 
is available that scans an IFF file and checks it for conformance 
to IFF's syntactic rules. IFFCheck also prints an outline of the chunks 
in the file, showing the ckID and ckSize of each. This is quite handy 
when building IFF programs. Example programs are also available to 
show details of reading and writing IFF files.

A merge program "IFFJoin" will be available that logically appends 
IFF files into a single CAT group. It "unwraps" each input file that 
is a CAT so that the combined file isn't nested CATs.

If we need to revise the IFF standard, the three anchoring IDs will 
be used as "version numbers". That's why IDs "FOR1" through "FOR9", 
"LIS1" through "LIS9", and "CAT1" through "CAT9" are reserved.

IFF formats are designed for reasonable performance with floppy disks. 
We achieve considerable simplicity in the formats and programs by 
relying on the host file system rather than defining universal grouping 
structures like directories for LIST contents. On huge storage systems, 
IFF files could be leaf nodes in a file structure like a B-tree. Let's 
hope the host file system implements that for us!

Thre are two kinds of IFF files: single purpose files and scrap files. 
They differ in the interpretation of multiple data objects and in 
the file's external type.

Single Purpose Files

A single purpose IFF file is for normal "document" and "archive" storage. 
This is in contrast with "scrap files" (see below) and temporary backing 
storage (non-interchange files).

The external file type (or filename extension, depending on the host 
file system) indicates the file's contents. It's generally the FORM 
type of the data contained, hence the restrictions on FORM type IDs.

Programmers and users may pick an "intended use" type as the filename 
extension to make it easy to filter for the relevant files in a filename 
requestor. This is actually a "subclass" or "subtype" that conveniently 
separates files of the same FORM type that have different uses. Programs 
cannot demand conformity to its expected subtypes without overly restricting 
data interchange since they cannot know about the subtypes to be used 
by future programs that users will want to exchange data with.

Issue: How to generate 3-letter MS-DOS extensions from 4-letter FORM 
type IDs?

Most single purpose files will be a single FORM (perhaps a composite 
FORM like a musical score containing nested FORMs like musical instrument 
descriptions). If it's a LIST or a CAT, programs should skip over 
unrecognized objects to read the recognized ones or the first recognized 
one. Then a program that can read a single purpose file can read something 
out of a "scrap file", too.

Scrap Files

A "scrap file" is for maximum interconnectivity in getting data between 
programs; the core of a clipboard function. Scrap files may have type 
"IFF " or filename extension ".IFF".

A scrap file is typically a CAT containing alternate representations 
of the same basic information. Include as many alternatives as you 
can readily generate. This redundancy improves interconnectivity in 
situations where we can't make all programs read and write super-general 
formats. [Inside Macintosh chapter "Scrap Manager".] E.g. a graphically-
annotated musical score might be supplemented by a stripped down 4-voice 
melody and by a text (the lyrics).

The originating program should write the alternate representations 
in order of "preference": most preferred (most comprehensive) type 
to least preferred (least comprehensive) type. A receiving program 
should either use the first appearing type that it understands or 
search for its own "preferred" type.

A scrap file should have at most one alternative of any type. (A LIST 
of same type objects is ok as one of the alternatives.) But don't 
count on this when reading; ignore extra sections of a type. Then 
a program that reads scrap files can read something out of single 
purpose files.

Rules for Reader Programs

Here are some notes on building programs that read IFF files. If you 
use the standard IFF reader module "IFFR.C", many of these rules and 
details will be automatically handled. (See "Support Software" in 
Appendix A.) We recommend that you start from the example program 
"ShowILBM.C". You should also read up on recursive descent parsers. 
[See, for example, Compiler Construction, An Advanced Course.]

%       The standard is very flexible so many programs can exchange 
data. This implies a program has to scan the file and react to what's 
actually there in whatever order it appears. An IFF reader program 
is a parser.

%       For interchange to really work, programs must be willing to 
do some conversion during read-in. If the data isn't exactly what 
you expect, say, the raster is smaller than those created by your 
program, then adjust it. Similarly, your program could crop a large 
picture, add or drop bitplanes, and create/discard a mask plane. The 
program should give up gracefully on data that it can't convert.

%       If it doesn't start with "FORM", "LIST", or "CAT ", it's not 
an IFF-85 file.

%       For any chunk you encounter, you must recognize its type ID 
to understand its contents.

%       For any FORM chunk you encounter, you must recognize its FORM 
type ID to understand the contained "local chunks". Even if you don't 
recognize the FORM type, you can still scan it for nested FORMs, LISTs, 
and CATs of interest.

%       Don't forget to skip the pad byte after every odd-length chunk.

%       Chunk types LIST, FORM, PROP, and CAT are generic groups. They 
always contain a subtype ID followed by chunks.

%       Readers ought to handle a CAT of FORMs in a file. You may treat 
the FORMs like document pages to sequence through or just use the 
first FORM.

%       Simpler IFF readers completely skip LISTs. "Fully IFF-conforming" 
readers are those that handle LISTs, even if just to read the first 
FORM from a file. If you do look into a LIST, you must process shared 
properties (in PROP chunks) properly. The idea is to get the correct 
data or none at all.

%       The nicest readers are willing to look into unrecognized FORMs 
for nested FORM types that they do recognize. For example, a musical 
score may contain nested instrument descriptions and an animation 
file may contain still pictures.

Note to programmers: Processing PROP chunks is not simple! You'll 
need some background in interpreters with stack frames. If this is 
foreign to you, build programs that read/write only one FORM per file. 
For the more intrepid programmers, the next paragraph summarizes how 
to process LISTs and PROPs. See the general IFF reader module "IFFR.C" 
and the example program "ShowILBM.C" for details.

Allocate a stack frame for every LIST and FORM you encounter and initialize 
it by copying the stack frame of the parent LIST or FORM. At the top 
level, you'll need a stack frame initialized to your program's global 
defaults. While reading each LIST or FORM, store all encountered properties 
into the current stack frame. In the example ShowILBM, each stack 
frame has a place for a bitmap header property ILBM.BMHD and a color 
map property ILBM.CMAP. When you finally get to the ILBM's BODY chunk, 
use the property settings accumulated in the current stack frame.

An alternate implementation would just remember PROPs encountered, 
forgetting each on reaching the end of its scope (the end of the containing 
LIST). When a FORM XXXX is encountered, scan the chunks in all remembered 
PROPs XXXX, in order, as if they appeared before the chunks actually 
in the FORM XXXX. This gets trickier if you read FORMs inside of FORMs.

Rules for Writer Programs

Here are some notes on building programs that write IFF files, which 
is much easier than reading them. If you use the standard IFF writer 
module "IFFW.C" (see "Support Software" in Appendix A), many of these 
rules and details will automatically be enforced. See the example 
program "Raw2ILBM.C".

%       An IFF file is a single FORM, LIST, or CAT chunk.

%       Any IFF-85 file must start with the 4 characters "FORM", "LIST", 
or "CAT ", followed by a LONG ckSize. There should be no data after 
the chunk end.

%       Chunk types LIST, FORM, PROP, and CAT are generic. They always 
contain a subtype ID followed by chunks. These three IDs are universally 
reserved, as are "LIS1" through "LIS9", "FOR1" through "FOR9", "CAT1" 
through "CAT9", and "    ".

%       Don't forget to write a 0 pad byte after each odd-length chunk.

%       Four techniques for writing an IFF group: (1) build the data 
in a file mapped into virtual memory, (2) build the data in memory 
blocks and use block I/O, (3) stream write the data piecemeal and 
(don't forget!) random access back to set the group length count, 
and (4) make a preliminary pass to compute the length count then stream 
write the data.

%       Do not try to edit a file that you don't know how to create. 
Programs may look into a file and copy out nested FORMs of types that 
they recognize, but don't edit and replace the nested FORMs and don't 
add or remove them. That could make the containing structure inconsistent. 
You may write a new file containing items you copied (or copied and 
modified) from another IFF file, but don't copy structural parts you 
don't understand.

%       You must adhere to the syntax descriptions in Appendex A. E.g. 
PROPs may only appear inside LISTs.




Appendix A. Reference

Type Definitions

The following C typedefs describe standard IFF structures. Declarations 
to use in practice will vary with the CPU and compiler. For example, 
68000 Lattice C produces efficient comparison code if we define ID 
as a "LONG". A macro "MakeID" builds these IDs at compile time.

/* Standard IFF types, expressed in 68000 Lattice C.    */

typedef unsigned char UBYTE;    /*  8 bits unsigned     */
typedef short WORD;     /* 16 bits signed       */
typedef unsigned short UWORD;   /* 16 bits unsigned     */
typedef long LONG;      /* 32 bits signed       */

typedef char ID[4];     /* 4 chars in ' ' through '~'   */

typedef struct {
        ID      ckID;
        LONG    ckSize; /* sizeof(ckData)       */
        UBYTE   ckData[/* ckSize */];
        } Chunk;

/* ID typedef and builder for 68000 Lattice C. */
typedef LONG ID;        /* 4 chars in ' ' through '~'   */
#define MakeID(a,b,c,d) ( (a)<<<<24 | (b)<<<<16 | (c)<<<<8 | (d) )

/* Globally reserved IDs. */
#define ID_FORM   MakeID('F','O','R','M')
#define ID_LIST   MakeID('L','I','S','T')
#define ID_PROP   MakeID('P','R','O','P')
#define ID_CAT    MakeID('C','A','T',' ')
#define ID_FILLER MakeID(' ',' ',' ',' ')

Syntax Definitions

Here's a collection of the syntax definitions in this document.

Chunk   ::= ID #{ UBYTE* } [0]

Property::= Chunk

FORM    ::= "FORM" #{ FormType (LocalChunk | FORM | LIST | CAT)* 
}
FormType::= ID
LocalChunk      ::= Property | Chunk

CAT     ::= "CAT " #{ ContentsType (FORM | LIST | CAT)* }
ContentsType    ::= ID  -- a hint or an "abstract data type" ID

LIST    ::= "LIST" #{ ContentsType PROP* (FORM | LIST | CAT)* }
PROP    ::= "PROP" #{ FormType Property* }

In this extended regular expression notation, the token "#" represents 
a ckSize LONG count of the following {braced} data bytes. Literal 
items are shown in "quotes", [square bracketed items] are optional, 
and "*" means 0 or more instances. A sometimes-needed pad byte is 
shown as "[0]".

Defined Chunk IDs

This is a table of currently defined chunk IDs. We may also borrow 
some Macintosh IDs and data formats.

Group chunk IDs
        FORM, LIST, PROP, CAT.
Future revision group chunk IDs
        FOR1 I FOR9, LIS1 I LIS9, CAT1 I CAT9.
FORM type IDs
        (The above group chunk IDs may not be used for FORM type IDs.)
        (Lower case letters and punctuation marks are forbidden in FORM 
type IDs.)
        8SVX 8-bit sampled sound voice, ANBM animated bitmap, FNTR raster 
font, FNTV vector font, FTXT formatted text, GSCR general-use musical 
score, ILBM interleaved raster bitmap image, PDEF Deluxe Print page 
definition, PICS Macintosh picture, PLBM (obsolete), USCR Uhuru Sound 
Software musical score, UVOX Uhuru Sound Software Macintosh voice, 
SMUS simple musical score, VDEO Deluxe Video Construction Set video.
Data chunk IDs
        "JJJJ", TEXT, PICT.
PROP LIST property IDs
        OPGM, OCPU, OCMP, OSN, UNAM.



Support Software

These public domain C source programs are available for use in building 
IFF-compatible programs:

IFF.H, IFFR.C, IFFW.C   

                IFF reader and writer package. 
                These modules handle many of the details of reliably 
                reading and writing IFF files.

IFFCheck.C      This handy utility program scans an IFF file, checks 
                that the contents are well formed, and prints an outline 
                of the chunks.

PACKER.H, Packer.C, UnPacker.C  

                Run encoder and decoder used for ILBM files.

ILBM.H, ILBMR.C, ILBMW.C

                Reader and writer support routines for raster image 
                FORM ILBM. ILBMR calls IFFR and UnPacker. ILBMW calls 
                IFFW and Packer.

ShowILBM.C      
                Example caller of IFFR and ILBMR modules. This 
                Commodore-Amiga program reads and displays a FORM ILBM.
Raw2ILBM.C      
                Example ILBM writer program. As a demonstration, it 
                reads a raw raster image file and writes the image 
                as a FORM ILBM file.
ILBM2Raw.C      
                Example ILBM reader program.  Reads a FORM ILBM file
                and writes it into a raw raster image.

REMALLOC.H, Remalloc.c

                Memory allocation routines used in these examples.

INTUALL.H       generic "include almost everything" include-file
                with the sequence of includes correctly specified.

READPICT.H, ReadPict.c  

                given an ILBM file, read it into a bitmap and 
                a color map

PUTPICT.H, PutPict.c    

                given a bitmap and a color map, save it as
                an ILBM file.

GIO.H, Gio.c    generic I/O speedup package.  Attempts to speed
                disk I/O by buffering writes and reads.

giocall.c       sample call to gio.

ilbmdump.c      reads in ILBM file, prints out ascii representation
                for including in C files.

bmprintc.c      prints out a C-language representation of data for
                a bitmap.



Example Diagrams

Here's a box diagram for an example IFF file, a raster image FORM 
ILBM. This FORM contains a bitmap header property chunk BMHD, a color 
map property chunk CMAP, and a raster data chunk BODY. This particular 
raster is 320 x 200 pixels x 3 bit planes uncompressed. The "0" after 
the CMAP chunk represents a zero pad byte; included since the CMAP 
chunk has an odd length. The text to the right of the diagram shows 
the outline that would be printed by the IFFCheck utility program 
for this particular file.

        +-----------------------------------+
        |'FORM'         24070               |   FORM 24070 IBLM
        +-----------------------------------+
        |'ILBM'                             |
        +-----------------------------------+
        | +-------------------------------+ |
        | | 'BMHD'      20                | |   .BMHD  20
        | | 320, 200, 0, 0, 3, 0, 0, ...  | |
        | + ------------------------------+ |
        | | 'CMAP'      21                | |   .CMAP  21
        | | 0, 0, 0; 32, 0, 0; 64,0,0; .. | |
        | +-------------------------------+ |
        | 0                                 |
        +-----------------------------------+
        |'BODY'         24000               |   .BODY 24000
        |0, 0, 0, ...                       |
        +-----------------------------------+

This second diagram shows a LIST of two FORMs ILBM sharing a common 
BMHD property and a common CMAP property. Again, the text on the right 
is an outline  a la IFFCheck.


     +-----------------------------------------+
     |'LIST'            48114                  |  LIST  48114  AAAA
     +-----------------------------------------+
     |'AAAA'                                   |  .PROP  62  ILBM
     |  +-----------------------------------+  |
     |  |'PROP'         62                  |  |  
     |  +-----------------------------------+  |
     |  |'ILBM'                             |  |
     |  +-----------------------------------+  |
     |  | +-------------------------------+ |  |
     |  | | 'BMHD'      20                | |  |  ..BMHD  20
     |  | | 320, 200, 0, 0, 3, 0, 0, ...  | |  |
     |  | | ------------------------------+ |  |
     |  | | 'CMAP'      21                | |  |  ..CMAP  21
     |  | | 0, 0, 0; 32, 0, 0; 64,0,0; .. | |  |
     |  | +-------------------------------+ |  |
     |  | 0                                 |  |
     |  +-----------------------------------+  |
     |  +-----------------------------------+  |
     |  |'FORM'         24012               |  |  .FORM  24012  ILBM
     |  +-----------------------------------+  |
     |  |'ILBM'                             |  |  
     |  +-----------------------------------+  |
     |  |  +-----------------------------+  |  |
     |  |  |'BODY'              24000    |  |  |  ..BODY  24000
     |  |  |0, 0, 0, ...         |  |  |
     |  |  +-----------------------------+  |  |
     |  +-----------------------------------+  |
     |  +-----------------------------------+  |
     |  |'FORM'         24012               |  |  .FORM  24012  ILBM
     |  +-----------------------------------+  |
     |  |'ILBM'                             |  |
     |  +-----------------------------------+  |
     |  |  +-----------------------------+  |  |
     |  |  |'BODY'              24000    |  |  |  ..BODY  24000
     |  |  |0, 0, 0, ...         |  |  |
     |  |  +-----------------------------+  |  |
     |  +-----------------------------------+  |
     +-----------------------------------------+



Appendix B. Standards Committee

The following people contributed to the design of this IFF standard:

Bob "Kodiak" Burns, Commodore-Amiga
R. J. Mical, Commodore-Amiga
Jerry Morrison, Electronic Arts
Greg Riker, Electronic Arts
Steve Shaw, Electronic Arts
Barry Walsh, Commodore-Amiga

 
Flic Files (.FLI) Format description:
 
     The details of a FLI file are moderately complex, but the
idea behind it is simple: don't bother storing the parts of a
frame that are the same as the last frame.  Not only does this
save space, but it's very quick.  It's faster to leave a pixel
alone than to set it.
 
     A FLI file has a 128-byte header followed by a sequence of
frames. The first frame is compressed using a bytewise run-length
compression scheme.  Subsequent frames are stored as the
difference from the previous frame.  (Occasionally the first
frame and/or subsequent frames are uncompressed.)  There is one
extra frame at the end of a FLI which contains the difference
between the last frame and the first frame.
 
     The FLI header:
 
     byte size name      meaning
     offset
 
     0    4    size      Length of file, for programs that want
                         to read the FLI all at once if possible.
     4    2    magic     Set to hex AF11.  Please use another
                         value here if you change format (even to
                         a different resolution) so Autodesk
                         Animator won't crash trying to read it.
     6    2    frames    Number of frames in FLI.  FLI files have
                         a maxium length of 4000 frames.
     8    2    width     Screen width (320).
     10   2    height    Screen height (200).
     12   2    depth     Depth of a pixel (8).
     14   2    flags     Must be 0.
     16   2    speed     Number of video ticks between frames.
     18   4    next      Set to 0.
     22   4    frit      Set to 0.
     26   102  expand    All zeroes -- for future enhancement.
 
     Next are the frames, each of which has a header:
 
     byte size name      meaning
     offset
     0    4    size      Bytes in this frame.  Autodesk Animator
                         demands that this be less than 64K.
     4    2    magic     Always hexadecimal F1FA
     6    2    chunks    Number of 'chunks' in frame.
     8    8    expand    Space for future enhancements.  All
                         zeros.
 
     After the frame header come the chunks that make up the
frame.  First comes a color chunk if the color map has changed
from the last frame.  Then comes a pixel chunk if the pixels have
changed.  If the frame is absolutely identical to the last frame
there will be no chunks at all.
 
     A chunk itself has a header, followed by the data.  The
chunk header is:
 
     byte size name meaning
     offset
     0    4    size Bytes in this chunk.
     4    2    type Type of chunk (see below).
 
     There are currently five types of chunks you'll see in a FLI
file.
 
     number    name      meaning
     11        FLI_COLOR Compressed color map
     12        FLI_LC    Line compressed -- the most common type
                         of compression for any but the first
                         frame.  Describes the pixel difference
                         from the previous frame.
     13        FLI_BLACK Set whole screen to color 0 (only occurs
                         on the first frame).
     15        FLI_BRUN  Bytewise run-length compression -- first
                         frame only
     16        FLI_COPY  Indicates uncompressed 64000 bytes soon
                         to follow.  For those times when
                         compression just doesn't work!
 
     The compression schemes are all byte-oriented.  If the
compressed data ends up being an odd length a single pad byte is
inserted so that the FLI_COPY's always start at an even address
for faster DMA.
 
FLI_COLOR Chunks
     The first word is the number of packets in this chunk. This
is followed directly by the packets.  The first byte of a packet
says how many colors to skip.  The next byte says how many colors
to change.  If this byte is zero it is interpreted to mean 256. 
Next follows 3 bytes for each color to change (one each for red,
green and blue).
 
FLI_LC Chunks
     This is the most common, and alas, most complex chunk.   The
first word (16 bits) is the number of lines starting from the top
of the screen that are the same as the previous frame. (For
example, if there is motion only on the bottom line of screen
you'd have a 199 here.)  The next word is the number of lines
that do change.  Next there is the data for the changing lines
themselves.  Each line is compressed individually; among other
things this makes it much easier to play back the FLI at a
reduced size.
 
     The first byte of a compressed line is the number of packets
in this line.  If the line is unchanged from the last frame this 
is zero.  The format of an individual packet is:
 
skip_count
size_count
data
 
     The skip count is a single byte.  If more than 255 pixels
are to be skipped it must be broken into 2 packets. The size
count is also a byte.  If it is positive, that many bytes of data
follow and are to be copied to the screen.  If it's negative a
single byte follows, and is repeated -skip_count times.
 
     In the worst case a FLI_LC frame can be about 70K.  If it
comes out to be 60000 bytes or more Autodesk Animator decides
compression isn't worthwhile and saves the frame as FLI_COPY.
 
FLI_BLACK Chunks
     These are very simple.  There is no data associated with
them at all. In fact they are only generated for the first frame
in Autodesk Animator after the user selects NEW under the FLIC
menu.
 
FLI_BRUN Chunks
     These are much like FLI_LC chunks without the skips.  They
start immediately with the data for the first line, and go line-
by-line from there.  The first byte contains the number of
packets in that line.  The format for a packet is:

size_count
data
 
     If size_count is positive the data consists of a single byte
which is repeated size_count times. If size_count is negative
there are -size_count bytes of data which are copied to the
screen. In Autodesk Animator if the "compressed" data shows signs
of exceeding 60000 bytes the frame is stored as FLI_COPY instead.
 
FLI_COPY Chunks
     These are 64000 bytes of data for direct reading onto the
screen.
 
-----------------------------------------------------------------------
And here's the PRO extensions:
-----------------------------------------------------------------------
 
This is supplemental info on the AutoDesk Animator FLI and FLC formats.
 
The following is an attempt at describing the newer chunks and frames
that are not described in the Turbo C FLI library documentation.
 
  Chunk type       Chunk ID 
  ----------       -----------
  FLI_DELTA        7 (decimal)
  
  First WORD (16 bits) is the number of compressed lines to follow.  Next
  is the data for the changing lines themselves, always starting with the
  first line.   Each line is compressed individually.
 
  The first WORD (16 bits) of a compressed line is the number of packets in
  the line.  If the number of packets is a negative skip -packets lines.
  If the number of packets is positive, decode the packets.  The format of
  an individual packet is:
 
  skip_count
  size_count
  data
 
  The skip count is a single byte.  If more than 255 pixels are to be
  skipped, it must be broken into 2 packets.  The size_count is also a byte.
  If it is positive, that many WORDS of data follow and are to be copied to
  the screen.  If it is negative, a single WORDS value follows, and is to be
  repeated -size_count times.

  Chunk type       Chunk ID 
  ----------       -----------
  FLI_256_COLOR    4 (decimal)
 
  The first WORD is the number of packets in this chunk.  This is followed
  directly by the packets.  The first byte of a packet is how many colors
  to skip.  The next byte is how many colors to change.  If this number is
  0, (zero), it means 256.  Next follow 3 bytes for each color to change.
  (One each for red, green and blue).
 
  The only difference between a FLI_256_COLOR chunk (type 4 decimal) and a
  FLI_COLOR chunk (type 11 decimal) is that the values in the type 4 chunk
  range from 0 to 255, and the values in a type 11 chunk range from 0 to 63.
 
  NOTE:  WORD  refer to a 16 bit int in INTEL (Little Endian) format.
         WORDS refer to two-bytes (16 bits) of consecutive data. (Big Endian)
 
  .FLC special frames and chunks
 
  FLC's may contain all the above chunks plus one other:
 
  Chunk type       Chunk ID 
  ----------       -----------
  FLI_MINI         18 (decimal) 12 (Hex)
 
  From what I understand,  this is a miniture 64 x 32 version of the first
  frame in FLI_BRUN format, used as an button for selecting flc's from
  within Animator Pro.  Simply do nothing with this chunk.
 
  FLC New Frame
 
  FLC's also contains a frame with the magic bytes set to hex 00A1.  This
  is the first frame in the .flc file.  Actually it isn't a frame at all
  but to have several chunks within it that specify file location info
  specific to Animator Pro.  IE:  filepath, font to use, and .COL file info.
  This FRAME may be skipped while loading.  That's right!  Ignore it!  The
  frame header is the same length as all other frames.  So you may read the
  frame header, then skip past the rest of the frame.
 
 
  NOTE:  When reading the FLI header on the newer FLI and FLC files, the
  FLI signature bytes are AF12 instead of AF11 used in the older FLI files.
  Also, you cannot ignore the screen width and height they may not be
  320 x 200.

  Allowable screen sizes include:
 
  320 x 200, 640 x 480, 800 x 600, 1280 x 1024
 
 
  NOTE:  the delay value between frames appears to be in 1000th's of a
  second instead of 70th's.
 
If you have any questions or more info on the FLI or FLC formats,
please let me know.
 
Mike Haaland
 
CompuServe : 72300,1433
Delphi     : MikeHaaland
Internet   : mike@htsmm1.las-vegas.nv.us
Usenet     : ...!htsmm1.las-vegas.nv.us!mike
 
                     ??????????????????????????????
                     ? Programming the PC Speaker ?
                     ??????????????????????????????

            Written for the PC-GPE by Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au

              ?????????????????????????????????????????????
              ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
              ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
              ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Basic Programming Info ?
??????????????????????????

The PC speaker has two states, in and out (0 and 1, on and off, Adam and
Eve etc). You can directly set the state of the PC speaker or you can hook
the speaker up to the output of PIT timer 2 to get various effects.

Port 61h controls how the speaker will operate as follows:

Bit 0    Effect
?????????????????????????????????????????????????????????????????
  0      The state of the speaker will follow bit 1 of port 61h
  1      The speaker will be connected to PIT channel 2, bit 1 is
         used as switch ie 0 = not connected, 1 = connected.

Playing around with the bits in port 61h can prevent the Borland BC++ and
Pascal sound() procedures from working properly. When you are done using the
speaker make sure you set bit's 0 and 1 of port 61h to 0.

?????????????????????????????????????????????????????????????????????????????
? Your First Tone ?
???????????????????

Ok, so lets generate a simple tone. We'll send a string of 0's and 1's to the
PC speaker to generate a square wave. Here's the Pascal routine:


Uses Crt;

const SPEAKER_PORT = $61;

var portval : byte;

begin

  portval := Port[SPEAKER_PORT] and $FC;

  while not KeyPressed do
    begin
      Port[SPEAKER_PORT] := portval or 2;
      Delay(5);
      Port[SPEAKER_PORT] := portval;
      Delay(5);
    end;
  ReadKey;
end.

On my 486SX33 this generates a tone of around about 100Hz.

First this routine grabs the value from the speaker port, sets the lower two
bits to 0 and stores it. The loop first sets the speaker to "on", waits a
short while, sets it to "off" and waits another short while. I write the loop
to do it in this order so that when a key is pressed and the program exits
the loop the lower two bits in the speaker port will both be 0 so it won't
prevent other programs which then use the speaker from working properly.

This is a really bad way of generating a tone. While the program is running
interrupts are continually occurring in the PC and this prevents the timing
from being accurate. Try running the program and moving the mouse around.
You can get a nicer tone by disabling interrupts first, but this would
prevent the KeyPressed function from working. In any case we want to
generate a nice tone of a given frequency, and using the Delay procedure
doesn't really allow us to do this. To top it all off, this procedure uses
all of the CPU's time so we can't do anything in the background while the
tone is playing.


?????????????????????????????????????????????????????????????????????????????
? Using PIT Channel 2 ?
???????????????????????

Connecting the PC speaker to PIT channel 2 is simply a matter of programming
the channel to generate a square wave of a given frequency and then setting
the lower two bits in the speaker port to a 1. Detailed information on
programming the PIT chip can be found in the file PIT.TXT, but here is
the pascal source you'll need to do the job:

const SPEAKER_PORT = $61;
       PIT_CONTROL = $43;
     PIT_CHANNEL_2 = $42;
          PIT_FREQ = $1234DD;

procedure Sound(frequency : word);
var counter : word;
begin

  { Program the PIT chip }
  counter := PIT_FREQ div frequency;
  Port[PIT_CONTROL] := $B6;
  Port[PIT_CHANNEL_2] := Lo(counter);
  Port[PIT_CHANNEL_2] := Hi(counter);

  { Connect the speaker to the PIT }
  Port[SPEAKER_PORT] := Port[SPEAKER_PORT] or 3;
end;

procedure NoSound;
begin
  Port[SPEAKER_PORT] := Port[SPEAKER_PORT] and $FC;
end;


?????????????????????????????????????????????????????????????????????????????
? Playing 8-bit Sound Through the PC Speaker ?
??????????????????????????????????????????????

Terminolgy
??????????

To clear up any confusion, here's my own definition of some words I'll be
using in this section:

sample : A single value in the range 0-255 representing the input level of
         the microphone at any given moment.

volume : A sort of generic version of sample, not limited to the 0-255 range.

  song : A bunch of samples in a row representing a continuous sound.

string : A bunch of binary values (0-1) in a row.



Programs like the legendary "Magic Mushroom" demo do a handly little trick
to play 8-bit sound from the PC speaker by sending binary strings to the
PC speaker for every sample they play. If the bits are all 0's, then the
speaker will be "off". If they are all 1's the speaker will be "on". If they
alternate 0's and 1's then the speaker will behave as if it's "half" on, and
so forth.

                ?????????????????????????????????????
                ? Bit string     Time speaker is on ?
                ?????????????????????????????????????
                ? 11111111              100%        ?
                ? 11101110               75%        ?
                ? 10101010               50%        ?
                ? 10001000               25%        ?
                ? 00000000                0%        ?
                ?????????????????????????????????????

Note that in this table I've used strings which are 8 bits long meaning that
there can only be 9 discrete volume levels (since anywhere from 0 to 8 of
them can be set to 1). In reality the strings would be longer.

The problem with using bit strings such as this is getting accurate timing
between each bit you send. One way around this is to put all the 1's at the
front of the string and all the 0's at the end, like so:

                ?????????????????????????????????????
                ? Bit string     Time speaker is on ?
                ?????????????????????????????????????
                ? 11111111              100%        ?
                ? 11111100               75%        ?
                ? 11110000               50%        ?
                ? 11000000               25%        ?
                ? 00000000                0%        ?
                ?????????????????????????????????????

This way you can send all the 1's as a single pulse and your timing doesn't
have to be quite as accurate. The sound isn't quite as good, but I've found
it to be pretty reasonable. A real advantage in using this method is that
you can program the PIT chip for "interrupt on terminal count" mode, this
mode is similar to the one-shot mode, but counting starts as soon as you
load the PIT counter. So if you are playing an 11kHz song you simply load the
PIT counter 11000 times a second with a value that's proportional to the
sample value and trigger it. The speaker output will go low for the set time
and then remain high until the next time you trigger it (in practise it
doesn't matter whether the string of 1's make the speaker go "low" or
"high", just so long as it's consistent). I've managed to get good results
using PIT channel 2 to handle the one-shot for each sample and PIT channel 0
to handle when to trigger channel 2 (ie 11000 times a second). *PLUS* I was
able to have a program drawing stuff on the screen while all this was going
on in the background!

Incidently I should mention here that the "interrupt on terminal count" mode
does not generate an actual interrupt on the Intel CPU. The mode was given
this name since the PIT can can be hooked up to a CPU to generate an
interrupt. As far as I can tell IBM didn't do it like this.

This technique does have one nasty side-effect though. If you are playing an
11kHz tone for example then the PC speaker will be being turned on and off
exactly 11000 times a second, in other words you'll hear a nice 11kHz sine
wave superimposed over the song (do any of you math weirdo's want to do
a FFT to prove this for me?). A way around this is to play the song back
at 22kHz and play each sample twice. This will result in a 22kHz sine wave
which will pretty much be filtered out by the tiny PC speaker and the simple
low-pass filter circuit that it's usually connected to on the motherboard.

The PIT chip runs at a frequency of 1193181 Hz (1234DDh). If you are playing
an 11kHz song at 22kHz then 1193181 / 22000 = 54 clocks per second, so
you'll have to program the PIT to count a maximum of 54 clocks for each
sample. What I'm getting at is that you'll only be able to play 54 discreet
sample levels using this method, so you'll have to scale the 256 different
levels in an 8-bit song to fit into this range which will also result in
futher loss of sound quality. I sped things up considerably by pre-
calculating a lookup table like so:

var count_values : array[0..255] of byte;

for level := 0 to 255 do
  count_values[level] := level * 54 div 255;

Then for each sample I just look up what it's counter value is and send
that to the PIT chip. Since each value is of byte size you can program the
PIT chip to accept the LSB only (see PIT.TXT for more info). The following
pascal code will set the PIT chip up for "interrupt on terminal count" mode
where only the LSB of the count needs to be loaded:

Port[PIT_CONTROL] := $90;
Port[SPEAKER_PORT] := Port[SPEAKER_PORT] or 3;

And the following line will trigger the one-shot for a given sample value
from 0-255:

Port[PIT_CHANNEL_2] := count_values[sample_value];

Do that 22000 times a second and whaddaya know, you'll hear "8-bit" sound
from your PC speaker! Here's a bit of code which works ok on my machine:


????????????????????????????????????????????????????????????????????????????

const SPEAKER_PORT = $61;
       PIT_CONTROL = $43;
     PIT_CHANNEL_2 = $42;
          PIT_FREQ = $1234DD;

     DELAY_LENGTH = 100;

procedure PlaySound(sound : PChar; length : word);
var count_values : array[0..255] of byte;
    i, loop : word;
begin

  { Set up the count table }
  for i := 0 to 255 do
    count_values[i] := i * 54 div 255;

  { Set up the PIT and connect the speaker to it }
  Port[PIT_CONTROL] := $90;
  Port[SPEAKER_PORT] := Port[SPEAKER_PORT] or 3;

  { Play the sound }

  asm cli end;
  for i := 0 to length - 1 do
    begin
      Port[PIT_CHANNEL_2] := count_values[byte(sound^)];
      for loop := 0 to DELAY_LENGTH do;
      Port[PIT_CHANNEL_2] := count_values[byte(sound^)];
      for loop := 0 to DELAY_LENGTH do;
      sound := sound + 1;
    end;
  asm sti end;

  { Reprogram the speaker for normal operation }
  Port[SPEAKER_PORT] := Port[SPEAKER_PORT] and $FC;
  Port[PIT_CONTROL] := $B6;
end;

????????????????????????????????????????????????????????????????????????????


Note that in this simple example I used a loop of DELAY_LENGTH to get the
timing between samples. I had to fiddle around to get the right value for my
machine and it varies from machine to machine. I also disable interrupts
while the inner loop is playing, otherwise you hear the 18.2Hz timer tick
while the sound was playing.


Both of these techniques suffer from two drawbacks. The first is that
samples played from the speaker do not sound very loud. You can make them
louder by making the song you are playing louder, but this eventually means
the sample values will start falling outside the 0-255 range and you'll have
to clip them which starts distorting the sound. The second problem is that
this technique doesn't work on the psezio-electric "speakers" inside lap-top
computers.


               ??????????????????????????????????????????
               ? Programming the GameBlaster Sound Card ?
               ??????????????????????????????????????????

                  Written for the PC-GPE by Mark Feldman
              e-mail address : u914097@student.canberra.edu.au
                               myndale@cairo.anu.edu.au

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Introduction ?
????????????????

The GameBlaster sound card (also known as the CMS) is now pretty much
obsolete, well I haven't seen any apart from the one I own. The card was
developped by Creative Labs, the same people who designed the SoundBlaster.
You can still buy the CMS chips to place inside your SoundBlaster 1.0 and
2.0 cards to make them compatible, but believe me, it isn't worth it. The
only reason I'm writing this file is because the GameBlaster chips are really
easy to program (due to the fact that it can't do anything) so this should be
of use to those of you who already own one.

I have no idea how to detect the presence of the CMS chips in a SoundBlaster,
trying to write to it's registers while the chips were not installed made
my machine hang.

?????????????????????????????????????????????????????????????????????????????
? General Programming Info ?
????????????????????????????

The GameBlaster has 12 channels. Each channel can produce either a single
sine wave of a given frequency and magnitude (in stereo), or noise.

The GameBlaster is programmed through 4 ports as follows:

    For voices 1 to 6
        Write 2x1 with register address
        Write 2x0 with data to register

    For voices 7 to 12
        Write 2x3 with register address
        Write 2x2 with data to register

The x value depends on the jumper setting on your card, x = 1 for 210h, x =
2 for 220h etc...

?????????????????????????????????????????????????????????????????????????????
? Reseting the GameBlaster ?
????????????????????????????

First you have to reset the GameBlaster and enable all voices. This is done
by setting register 1Ch, which is laid out as follows:

                      ?????????????????????????????????
                      ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
                      ?????????????????????????????????
                                                ?   ?
Sound Enable for all channels ???????????????????   ?
  0 all channels disabled                           ?
  1 all channels enabled                            ?
                                                    ?
Reset signal to all generators ??????????????????????
  0 all generators enabled
  1 all generators reset and synchronized

The following Pascal code will reset and enable the card (assuming jumper
setting is for 220h) :

  Port[$221] := $1C;
  Port[$220] := $02; { Reset voices }
  Port[$221] := $15;
  Port[$220] := $00; { Disable noise * }
  Port[$221] := $1C;
  Port[$220] := $01; { Enable voices }




?????????????????????????????????????????????????????????????????????????????
? Enabling Voice Frequency ?
????????????????????????????

To hear a frequency from a voice you must set the appropriate bit in
register 14h:

       ?????????????????????????????????
       ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
       ?????????????????????????????????
                 ?   ?   ?   ?   ?   ?
                 ?   ?   ?   ?   ?   ??? Frequency enable 1/7
                 ?   ?   ?   ?   ??????? Frequency enable 2/8
                 ?   ?   ?   ??????????? Frequency enable 3/9
                 ?   ?   ??????????????? Frequency enable 4/10
                 ?   ??????????????????? Frequency enable 5/11
                 ??????????????????????? Frequency enable 6/12

Note that for each bit in the above table two voice numbers are given. If
ports 2x1 and 2x0 are used then the first voice is modified. If ports 2x3
and 2x2 are used then the second voice is modified.

Setting a bit to 1 enables the voice. Clearing it to 0 disables the voice.

?????????????????????????????????????????????????????????????????????????????
? Setting a Voice Volume ?
??????????????????????????

Each voice can have a volume between 0 and 15 on both left and right
channels. The amplitude registers are laid out as follows:

                      ?????????????????????????????????
                      ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
                      ?????????????????????????????????
                        ?????????????   ?????????????
                              ?               ?
 Right channel volume ?????????               ????????? Left channel volume


To set a channel volume you write the register address to the appropriate
Register Address port and write the volume byte to the Data To Register
port. The register address map for amplitudes is as follows:

                   ??????????????????????????????
                   ? Register Address     Voice ?
                   ??????????????????????????????
                   ?       00h             1/7  ?
                   ?       01h             2/8  ?
                   ?       02h             3/9  ?
                   ?       03h             4/10 ?
                   ?       04h             5/11 ?
                   ?       05h             6/12 ?
                   ??????????????????????????????

?????????????????????????????????????????????????????????????????????????????
? Setting a Voice Frequency ?
?????????????????????????????

Setting a voice frequency is similar to setting the volume, one byte is
written to the appropriate register. The frequency register map is as
follows:

                   ??????????????????????????????
                   ? Register Address     Voice ?
                   ??????????????????????????????
                   ?       08h             1/7  ?
                   ?       09h             2/8  ?
                   ?       0Ah             3/9  ?
                   ?       0Bh             4/10 ?
                   ?       0Ch             5/11 ?
                   ?       0Dh             6/12 ?
                   ??????????????????????????????

The following table is a list of the bytes you write for each note:


                         ????????????????
                         ? Note   Value ?
                         ????????????????
                         ?  A        3  ?
                         ?  A#      31  ?
                         ?  B       58  ?
                         ?  C       83  ?
                         ?  C#     107  ?
                         ?  D      130  ?
                         ?  D#     151  ?
                         ?  E      172  ?
                         ?  F      191  ?
                         ?  F#     209  ?
                         ?  G      226  ?
                         ?  G#     242  ?
                         ????????????????

This is the first octave available. The C frequency in this octave is
55 Hz.

To get tones in higher octaves you need to set the octave register for a 
voice. Each octave register stores the octave number for two voices. The
octave registers are laid out as follows:

                      ?????????????????????????????????
                      ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
                      ?????????????????????????????????
                            ?????????       ?????????
                                ?               ?
        Octave Number 2 ?????????               ??????? Octave number 1

The octave register address map is as follows:

                    ????????????????????????????????????
                    ? Register Address     Voices      ?
                    ????????????????????????????????????
                    ?     10h              2;1 / 8;7   ?
                    ?     11h              4;3 / 10;9  ?
                    ?     12h              6;5 / 12;11 ?
                    ????????????????????????????????????

?????????????????????????????????????????????????????????????????????????????
? Noise ?
?????????

There are 4 noise generators, each noise genrator can be connected up to
any of three voices:

                          ????????????????????????
                          ?   Noise              ?
                          ?  Generator   Voices  ?
                          ????????????????????????
                          ?   1           1,2,3  ?
                          ?   2           4,5,6  ?
                          ?   3           7,8,9  ?
                          ?   4         10,11,12 ?
                          ????????????????????????


The noise generators are controlled via two registers:

Register 16h at ports 2x1 and 2x0 :

                   ?????????????????????????????????
                   ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
                   ?????????????????????????????????
                             ?????           ?????
                               ?               ?
           Noise Gen 2 ?????????               ??????? Noise Gen 1

Register 16h at ports 2x3 and 2x2 :

                   ?????????????????????????????????
                   ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
                   ?????????????????????????????????
                             ?????           ?????
                               ?               ?
           Noise Gen 4 ?????????               ??????? Noise Gen 3

Each generator has two bits associated with it which control the noise
generator rate:

                       ????????????????????????
                       ? Nn1 Nn0    Frequency ?
                       ????????????????????????
                       ? 0   0     28.0 kHz   ?
                       ? 0   1     14.0 kHz   ?
                       ? 1   0      6.8 kHz   ?
                       ????????????????????????

A voice can be connected to it's noise generator by setting the appropriate
bit in register 15h:

                ?????????????????????????????????
                ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
                ?????????????????????????????????
                          ?   ?   ?   ?   ?   ?
                          ?   ?   ?   ?   ?   ??? Noice enable 1/7
                          ?   ?   ?   ?   ??????? Noice enable 2/8
                          ?   ?   ?   ??????????? Noice enable 3/9
                          ?   ?   ??????????????? Noice enable 4/10
                          ?   ??????????????????? Noice enable 5/11
                          ??????????????????????? Noice enable 6/12

Setting a bit to 1 enables the voice. Clearing it to 0 disables the voice.


                      Programming the AdLib/Sound Blaster
                                FM Music Chips
                           Version 2.0 (24 Feb 1992)

                  Copyright (c) 1991, 1992 by Jeffrey S. Lee

                               jlee@smylex.uucp


 
                        Warranty and Copyright Policy

     This document is provided on an "as-is" basis, and its author makes
     no warranty or representation, express or implied, with respect to
     its quality performance or fitness for a particular purpose.  In no
     event will the author of this document be liable for direct, indirect,
     special, incidental, or consequential damages arising out of the use
     or inability to use the information contained within.  Use of this
     document is at your own risk.

     This file may be used and copied freely so long as the applicable
     copyright notices are retained, and no modifications are made to the
     text of the document.  No money shall be charged for its distribution
     beyond reasonable shipping, handling and duplication costs, nor shall
     proprietary changes be made to this document so that it cannot be
     distributed freely.  This document may not be included in published
     material or commercial packages without the written consent of its
     author.



                                   Overview

     Two of the most popular sound cards for the IBM-PC, the AdLib and the
     Sound Blaster, suffer from a real dearth of clear documentation for 
     programmers.  AdLib Inc. and Creative Labs, Inc. both sell developers'
     kits for their sound cards, but these are expensive, and (in the case
     of the Sound Blaster developers' kit) can be extremely cryptic.

     This document is intended to provide programmers with a FREE source
     of information about the programming of these sound cards.

     The information contained in this document is a combination of
     information found in the Sound Blaster Software Developer's Kit, and
     that learned by painful experience.  Some of the information may not
     be valid for AdLib cards; if this is so, I apologize in advance.

     Please note that numbers will be given in hexadecimal, unless otherwise
     indicated.  If a number is written out longhand (sixteen instead of 16)
     it is in decimal.

 |   Changes from Version 1 of the file will be indicated by the use of change
 |   bars in the left-hand margin.



                         Chapter One - Sound Card I/O

     The sound card is programmed by sending data to its internal registers
     via its two I/O ports:

             0388 (hex) - Address/Status port  (R/W)
             0389 (hex) - Data port            (W/O)

 |   The Sound Blaster Pro is capable of stereo FM music, which is accessed
 |   in exactly the same manner.  Ports 0220 and 0221 (hex) are the address/
 |   data ports for the left speaker, and ports 0222 and 0223 (hex) are the
 |   ports for the right speaker.  Ports 0388 and 0389 (hex) will cause both
 |   speakers to output sound.

     The sound card possesses an array of two hundred forty-four registers;
     to write to a particular register, send the register number (01-F5) to
     the address port, and the desired value to the data port.

     After writing to the register port, you must wait twelve cycles before 
     sending the data; after writing the data, eighty-four cycles must elapse
     before any other sound card operation may be performed.

 |   The AdLib manual gives the wait times in microseconds: three point three
 |   (3.3) microseconds for the address, and twenty-three (23) microseconds
 |   for the data.
 |
 |   The most accurate method of producing the delay is to read the register
 |   port six times after writing to the register port, and read the register
 |   port thirty-five times after writing to the data port.

     The sound card registers are write-only.

     The address port also functions as a sound card status byte.  To 
     retrieve the sound card's status, simply read port 388.  The status 
     byte has the following structure:

              7      6      5      4      3      2      1      0
          +------+------+------+------+------+------+------+------+
          | both | tmr  | tmr  |              unused              |
          | tmrs |  1   |  2   |                                  |
          +------+------+------+------+------+------+------+------+

          Bit 7 - set if either timer has expired.
              6 - set if timer 1 has expired.
              5 - set if timer 2 has expired.



                       Chapter Two - The Registers

The following table shows the function of each register in the sound 
card.  Registers will be explained in detail after the table.  Registers
not listed are unused.

   Address      Function
   -------      ----------------------------------------------------
     01         Test LSI / Enable waveform control
     02         Timer 1 data
     03         Timer 2 data
     04         Timer control flags
     08         Speech synthesis mode / Keyboard split note select
   20..35       Amp Mod / Vibrato / EG type / Key Scaling / Multiple
   40..55       Key scaling level / Operator output level
   60..75       Attack Rate / Decay Rate
   80..95       Sustain Level / Release Rate
   A0..A8       Frequency (low 8 bits)
   B0..B8       Key On / Octave / Frequency (high 2 bits)
     BD         AM depth / Vibrato depth / Rhythm control
   C0..C8       Feedback strength / Connection type
   E0..F5       Wave Select

The groupings of twenty-two registers (20-35, 40-55, etc.) have an odd
order due to the use of two operators for each FM voice.  The following
table shows the offsets within each group of registers for each operator.


   Channel        1   2   3   4   5   6   7   8   9
   Operator 1    00  01  02  08  09  0A  10  11  12
   Operator 2    03  04  05  0B  0C  0D  13  14  15

Thus, the addresses of the attack/decay bytes for channel 3 are 62 for
the first operator, and 65 for the second.  (The address of the second
operator is always the address of the first operator plus three).

To further illustrate the relationship, the addresses needed to control
channel 5 are:

    29 - Operator 1  AM/VIB/EG/KSR/Multiplier
    2C - Operator 2  AM/VIB/EG/KSR/Multiplier
    49 - Operator 1  KSL/Output Level
    4C - Operator 2  KSL/Output Level
    69 - Operator 1  Attack/Decay
    6C - Operator 2  Attack/Decay
    89 - Operator 1  Sustain/Release
    8C - Operator 2  Sustain/Release
    A4 -             Frequency (low 8 bits)
    B4 -             Key On/Octave/Frequency (high 2 bits)
    C4 -             Feedback/Connection Type
    E9 - Operator 1  Waveform
    EC - Operator 2  Waveform



                       Explanations of Registers

Byte 01 - This byte is normally used to test the LSI device.  All bits
          should normally be zero.  Bit 5, if enabled, allows the FM 
          chips to control the waveform of each operator.

             7     6     5     4     3     2     1     0
          +-----+-----+-----+-----+-----+-----+-----+-----+
          |   unused  | WS  |            unused           |
          +-----+-----+-----+-----+-----+-----+-----+-----+


Byte 02 - Timer 1 Data.  If Timer 1 is enabled, the value in this 
          register will be incremented until it overflows.  Upon
          overflow, the sound card will signal a TIMER interrupt
          (INT 08) and set bits 7 and 6 in its status byte.  The
          value for this timer is incremented every eighty (80)
          microseconds.


Byte 03 - Timer 2 Data.  If Timer 2 is enabled, the value in this 
          register will be incremented until it overflows.  Upon
          overflow, the sound card will signal a TIMER interrupt
          (INT 08) and set bits 7 and 5 in its status byte.  The
          value for this timer is incremented every three hundred
          twenty (320) microseconds.


Byte 04 - Timer Control Byte

        7     6     5     4     3     2     1     0
     +-----+-----+-----+-----+-----+-----+-----+-----+
     | IRQ | T1  | T2  |     unused      | T2  | T1  |
     | RST | MSK | MSK |                 | CTL | CTL |
     +-----+-----+-----+-----+-----+-----+-----+-----+

          bit 7 - Resets the flags for timers 1 & 2.  If set,
                  all other bits are ignored.
          bit 6 - Masks Timer 1.  If set, bit 0 is ignored.
          bit 5 - Masks Timer 2.  If set, bit 1 is ignored.
          bit 1 - When clear, Timer 2 does not operate.
                  When set, the value from byte 03 is loaded into
                  Timer 2, and incrementation begins.
          bit 0 - When clear, Timer 1 does not operate.
                  When set, the value from byte 02 is loaded into
                  Timer 1, and incrementation begins.


Byte 08 - CSM Mode / Keyboard Split.

        7     6     5     4     3     2     1     0
     +-----+-----+-----+-----+-----+-----+-----+-----+
     | CSM | Key |              unused               |
     | sel | Spl |                                   |
     +-----+-----+-----+-----+-----+-----+-----+-----+
     
          bit 7 - When set, selects composite sine-wave speech synthesis
                  mode (all KEY-ON bits must be clear).  When clear,
                  selects FM music mode.

          bit 6 - Selects the keyboard split point (in conjunction with
                  the F-Number data).  The documentation in the Sound 
                  Blaster manual is utterly incomprehensible on this;
                  I can't reproduce it without violating their copyright.


Bytes 20-35 - Amplitude Modulation / Vibrato / Envelope Generator Type /
              Keyboard Scaling Rate / Modulator Frequency Multiple

        7     6     5     4     3     2     1     0
     +-----+-----+-----+-----+-----+-----+-----+-----+
     | Amp | Vib | EG  | KSR |  Modulator Frequency  |
     | Mod |     | Typ |     |       Multiple        |
     +-----+-----+-----+-----+-----+-----+-----+-----+

          bit 7 - Apply amplitude modulation when set; AM depth is
                  controlled by the AM-Depth flag in address BD.
          bit 6 - Apply vibrato when set;  vibrato depth is controlled
                  by the Vib-Depth flag in address BD.
          bit 5 - When set, the sustain level of the voice is maintained
                  until released; when clear, the sound begins to decay
                  immediately after hitting the SUSTAIN phase.
          bit 4 - Keyboard scaling rate.  This is another incomprehensible
                  bit in the Sound Blaster manual.  From experience, if 
                  this bit is set, the sound's envelope is foreshortened as
                  it rises in pitch.
          bits 3-0 - These bits indicate which harmonic the operator will 
                  produce sound (or modulation) in relation to the voice's 
                  specified frequency:

                      0 - one octave below
                      1 - at the voice's specified frequency
                      2 - one octave above
                      3 - an octave and a fifth above
                      4 - two octaves above
                      5 - two octaves and a major third above
                      6 - two octaves and a fifth above
                      7 - two octaves and a minor seventh above
                      8 - three octaves above
                      9 - three octaves and a major second above
                      A - three octaves and a major third above
                      B -  "       "     "  "   "     "     "
                      C - three octaves and a fifth above
                      D -   "      "     "  "   "     "
                      E - three octaves and a major seventh above
                      F -   "      "     "  "   "      "      "
                  

Bytes 40-55 - Level Key Scaling / Total Level

        7     6     5     4     3     2     1     0
     +-----+-----+-----+-----+-----+-----+-----+-----+
     |  Scaling  |             Total Level           |
     |   Level   | 24    12     6     3    1.5   .75 | <-- dB
     +-----+-----+-----+-----+-----+-----+-----+-----+

          bits 7-6 - causes output levels to decrease as the frequency
                     rises:

                          00   -  no change
                          10   -  1.5 dB/8ve
                          01   -  3 dB/8ve
                          11   -  6 dB/8ve

          bits 5-0 - controls the total output level of the operator.
                     all bits CLEAR is loudest; all bits SET is the
                     softest.  Don't ask me why.


Bytes 60-75 - Attack Rate / Decay Rate

        7     6     5     4     3     2     1     0
     +-----+-----+-----+-----+-----+-----+-----+-----+
     |         Attack        |          Decay        |
     |          Rate         |          Rate         |
     +-----+-----+-----+-----+-----+-----+-----+-----+

          bits 7-4 - Attack rate.  0 is the slowest, F is the fastest.
          bits 3-0 - Decay rate.  0 is the slowest, F is the fastest.


Bytes 80-95 - Sustain Level / Release Rate

        7     6     5     4     3     2     1     0
     +-----+-----+-----+-----+-----+-----+-----+-----+
     |     Sustain Level     |         Release       |
     | 24    12     6     3  |          Rate         |
     +-----+-----+-----+-----+-----+-----+-----+-----+

          bits 7-4 - Sustain Level.  0 is the loudest, F is the softest.
          bits 3-0 - Release Rate.  0 is the slowest, F is the fastest.


Bytes A0-B8 - Octave / F-Number / Key-On

        7     6     5     4     3     2     1     0
     +-----+-----+-----+-----+-----+-----+-----+-----+
     |        F-Number (least significant byte)      |  (A0-A8)
     |                                               |
     +-----+-----+-----+-----+-----+-----+-----+-----+

        7     6     5     4     3     2     1     0
     +-----+-----+-----+-----+-----+-----+-----+-----+
     |  Unused   | Key |    Octave       | F-Number  |  (B0-B8)
     |           | On  |                 | most sig. |
     +-----+-----+-----+-----+-----+-----+-----+-----+

          bit   5  - Channel is voiced when set, silent when clear.
          bits 4-2 - Octave (0-7).  0 is lowest, 7 is highest.
          bits 1-0 - Most significant bits of F-number.

     In octave 4, the F-number values for the chromatic scale and their 
     corresponding frequencies would be:

        F Number     Frequency     Note
           16B          277.2       C#
           181          293.7       D
           198          311.1       D#
           1B0          329.6       E
           1CA          349.2       F
           1E5          370.0       F#
           202          392.0       G
           220          415.3       G#
           241          440.0       A
           263          466.2       A#
           287          493.9       B
           2AE          523.3       C


Bytes C0-C8 - Feedback / Algorithm

        7     6     5     4     3     2     1     0
     +-----+-----+-----+-----+-----+-----+-----+-----+
     |         unused        |    Feedback     | Alg |
     |                       |                 |     |
     +-----+-----+-----+-----+-----+-----+-----+-----+

          bits 3-1 - Feedback strength.  If all three bits are set to
                     zero, no feedback is present.  With values 1-7,
                     operator 1 will send a portion of its output back
                     into itself.  1 is the least amount of feedback,
                     7 is the most.
          bit 0    - If set to 0, operator 1 modulates operator 2.  In this
                     case, operator 2 is the only one producing sound.
                     If set to 1, both operators produce sound directly.
                     Complex sounds are more easily created if the algorithm
                     is set to 0.


Byte BD - Amplitude Modulation Depth / Vibrato Depth / Rhythm

        7     6     5     4     3     2     1     0
     +-----+-----+-----+-----+-----+-----+-----+-----+
     | AM  | Vib | Rhy | BD  | SD  | TOM | Top | HH  |
     | Dep | Dep | Ena |     |     |     | Cym |     |
     +-----+-----+-----+-----+-----+-----+-----+-----+

          bit 7 - Set:    AM depth is 4.8dB
                  Clear:  AM depth is 1 dB
          bit 6 - Set:    Vibrato depth is 14 cent
                  Clear:  Vibrato depth is 7 cent
          bit 5 - Set:    Rhythm enabled  (6 melodic voices) 
                  Clear:  Rhythm disabled (9 melodic voices)
          bit 4 - Bass drum on/off
          bit 3 - Snare drum on/off
          bit 2 - Tom tom on/off
          bit 1 - Cymbal on/off
          bit 0 - Hi Hat on/off

          Note:  KEY-ON registers for channels 06, 07, and 08 must be OFF
                 in order to use the rhythm section.  Other parameters
                 such as attack/decay/sustain/release must also be set
                 appropriately.


Bytes E0-F5 - Waveform Select

        7     6     5     4     3     2     1     0
     +-----+-----+-----+-----+-----+-----+-----+-----+
     |               unused              |  Waveform |
     |                                   |  Select   |
     +-----+-----+-----+-----+-----+-----+-----+-----+

          bits 1-0 - When bit 5 of address 01 is set, the output waveform
                     will be distorted according to the waveform indicated
                     by these two bits.  I'll try to diagram them here,
                     but this medium is fairly restrictive.

         ___              ___            ___    ___       _      _
        /   \            /   \          /   \  /   \     / |    / |
       /_____\_______   /_____\_____   /_____\/_____\   /__|___/__|___
              \     /
               \___/

            00              01               10               11



 |                          Detecting a Sound Card
 |
 |   According to the AdLib manual, the 'official' method of checking for a 
 |   sound card is as follows:
 |
 |      1)  Reset both timers by writing 60h to register 4.
 |      2)  Enable the interrupts by writing 80h to register 4.  NOTE: this
 |          must be a separate step from number 1.
 |      3)  Read the status register (port 388h).  Store the result.
 |      4)  Write FFh to register 2 (Timer 1).
 |      5)  Start timer 1 by writing 21h to register 4.
 |      6)  Delay for at least 80 microseconds.
 |      7)  Read the status register (port 388h).  Store the result.
 |      8)  Reset both timers and interrupts (see steps 1 and 2).
 |      9)  Test the stored results of steps 3 and 7 by ANDing them
 |          with E0h.  The result of step 3 should be 00h, and the 
 |          result of step 7 should be C0h.  If both are correct, an
 |          AdLib-compatible board is installed in the computer.
 |
 |
 |                              Making a Sound
 | 
 |   Many people have asked me, upon reading this document, what the proper
 |   register values should be to make a simple sound.  Well, here they are.
 | 
 |   First, clear out all of the registers by setting all of them to zero.
 |   This is the quick-and-dirty method of resetting the sound card, but it
 |   works.  Note that if you wish to use different waveforms, you must then
 |   turn on bit 5 of register 1.  (This reset need be done only once, at the
 |   start of the program, and optionally when the program exits, just to 
 |   make sure that your program doesn't leave any notes on when it exits.)
 |
 |   Now, set the following registers to the indicated value:
 |
 |     REGISTER     VALUE     DESCRIPTION
 |        20          01      Set the modulator's multiple to 1
 |        40          10      Set the modulator's level to about 40 dB
 |        60          F0      Modulator attack:  quick;   decay:   long
 |        80          77      Modulator sustain: medium;  release: medium
 |        A0          98      Set voice frequency's LSB (it'll be a D#)
 |        23          01      Set the carrier's multiple to 1
 |        43          00      Set the carrier to maximum volume (about 47 dB)
 |        63          F0      Carrier attack:  quick;   decay:   long
 |        83          77      Carrier sustain: medium;  release: medium
 |        B0          31      Turn the voice on; set the octave and freq MSB
 |
 |   To turn the voice off, set register B0h to 11h (or, in fact, any value 
 |   which leaves bit 5 clear).  It's generally preferable, of course, to
 |   induce a delay before doing so.
 |
 |
 |                             Acknowledgements
 |
 |   Thanks are due to the following people:
 |
 |   Ezra M. Dreisbach (ed10+@andrew.cmu.edu), for providing the information
 |     about the recommended port write delay from the AdLib manual, and the
 |     'official' method of detecting an AdLib-compatible sound card.
 |
 |   Nathan Isaac Laredo (gt7080a@prism.gatech.edu), for providing the
 |     port numbers for stereo sound on the Sound Blaster Pro.

                  ????????????????????????????????????
                  ? Programming the SoundBlaster DSP ?
                  ????????????????????????????????????

                  Written for the PC-GPE by Mark Feldman
              e-mail address : u914097@student.canberra.edu.au
                               myndale@cairo.anu.edu.au

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Introduction ?
????????????????

The SoundBlaster is capable of both FM and digitised sounds. The FM wave
is fully Adlib compatible, so check the ADLIB.TXT file for info
on how to program it. This file will concentrate on recording and playback
of digital samples through the SoundBlaster CT-DSP 1321 chip.

?????????????????????????????????????????????????????????????????????????????
? The SoundBlaster DSP I/O Ports ?
??????????????????????????????????

The DSP (Digital Sound Processor) chip is programmed through 4 ports which
are determined by the SoundBlaster base address jumper setting:

                    RESET    2x6h

                READ DATA    2xAh

WRITE COMMAND/DATA output
WRITE BUFFER STATUS input    2xCh


           DATA AVAILABLE    2xEh

where x = 1 for base address jumper setting 210h
      x = 2 for base address jumper setting 220h
      .
      .
      x = 6 for base address jumper setting 260h


?????????????????????????????????????????????????????????????????????????????
? Resetting the DSP ?
?????????????????????

You have to reset the DSP before you program it. This is done with the
following procedure :

1) Write a 1 to the SoundBlaster RESET port (2x6h)
2) Wait for 3 micro-seconds
3) Write a 0 to the SoundBlaster RESET port (2x6h)
4) Read the byte from the DATA AVAILABLE (2xEh) port until bit 7 = 1
5) Poll for a ready byte (AAh) from the READ DATA port (2xAh). Before
   reading the READ DATA port it is avdvisable.

The DSP usually takes somewhere around 100 micro-seconds to reset itself.
If it fails to do within a reasonable time (say 200 micro-seconds) then
an error has occurred, possibly an incorrect I/O address is being used.

?????????????????????????????????????????????????????????????????????????????
? Writing to the DSP ?
??????????????????????

A value can be written to the DSP with the following procedure :

1) Read the DSP's WRITE BUFFER STATUS port (2xCh) until bit 7 = 0
2) Write the value to the WRITE COMMAND/DATA port (2xCh)

?????????????????????????????????????????????????????????????????????????????
? Reading the DSP ?
???????????????????

A value can be read from the DSP with the following procedure :

1) Read the DSP's DATA AVAILABLE port (2xEh) until bit 7 = 1
2) Read the data from the READ DATA port (2xAh)

?????????????????????????????????????????????????????????????????????????????
? Turning the speaker on and controlling DMA ?
??????????????????????????????????????????????

Speaker and DMA control are handled by writing one of the following bytes
to the DSP:

                     ???????????????????????????
                     ? Value   Description     ?
                     ???????????????????????????
                     ? D0h    DMA Stop         ?
                     ? D1h    Turn speaker on  ?
                     ? D3h    Turn speaker off ?
                     ? D4h    DMA Continue     ?
                     ???????????????????????????

DMA is discussed below. The DMA commands shown here can be used to pause
the sample during DMA playback playback.

?????????????????????????????????????????????????????????????????????????????
? Writing to the DAC ?
??????????????????????

The DAC (Digital to Analog Converter) is the part of the card which converts
a sample number (ie 0 -> 255) to a sound level. To generate a square sound
wave at maximum volume (for example) you could alternate writing 0's and
255's to the DAC.

Programming the DAC in direct mode involves the main program setting the
DAC to a desired value. Only 8 bit DAC is available in direct mode. To set
the DAC level you write the value 10h to the DSP followed by the sample
number (0 -> 255). Note that no sound will be heard unless the speaker has
been turned on. In direct mode the main program is responsible for the
timing between samples, the DAC can output sound samples as fast as the
calling program can change it. Typically the timer interrupt is reprogrammed
and used to generate the timing required for a sample playback. Info on
programming the PIT chip can be found in the PIT.TXT file.

The DAC can also be programmed to accept values sent to it via the DMA
chip. Draeden has written an excellent article on programming the DMA chip
(see DMA_VLA.TXT) so only a brief example of it's use will be given here.
The important thing to remember is that the DMA chip cannot transfer data
which crosses between page breaks. If the data does cross page breaks then
it will have to be split up into several transfers, with one page per
transfer.

Setting the playback frequency for the DMA transfer is done by writing
the value 40h to the DSP followed by TIME_CONSTANT, where
TIME_CONSTANT = 256 - 1000000 / frequency

There are several types of DMA transfers available. The following table
lists them:

      ??????????????????????????????????????????????????????????????
      ?DMA_TYPE_VALUE   Description             Frequency Range    ?
      ??????????????????????????????????????????????????????????????
      ?    14h          8 bit                   4KHz -> 23 KHz     ?
      ?    74h          4 bit ADPCM             4KHz -> 12 KHz     ?
      ?    75h          4 bit ADPCM with        4KHz -> 12 KHz     ?
      ?                 reference byte                             ?
      ?    76h          2.6 bit ADPCM           4KHz -> 13 KHz     ?
      ?    77h          2.6 bit ADPCM with      4KHz -> 13 KHz     ?
      ?                 reference byte                             ?
      ?    16h          2 bit ADPCM             4KHz -> 11 KHz     ?
      ?    17h          2 bit ADPCM with        4KHz -> 11 KHz     ?
      ?                 reference byte                             ?
      ??????????????????????????????????????????????????????????????

ADPCM stands for Adaptive Pulse Code Modulation, a sound compression
technique where the difference between successive samples is stored rather
than their actual values. In the modes with reference bytes, the first
byte is the actual starting value. Having modes with and without reference
bytes means you can output successive blocks without the need for a
reference byte at the start of each one.

The procedure for doing a DMA transfer is as follows:

1) Load the sound data into memory
2) Set up the DMA chip for the tranfer
3) Set the DSP TIME_CONSTANT to the sampling rate
4) Write DMA_TYPE_VALUE value to the DSP
5) Write DATA_LENGTH to the DSP (2 bytes, LSB first) where
   DATA_LENGTH = number of bytes to send - 1

Note that the DMA chip must be programmed before the BSP.

?????????????????????????????????????????????????????????????????????????????
? Reading from the ADC ?
????????????????????????

Reading samples from the ADC (Analog to Digital Converter) can also be
done in either direct or DMA mode.

To read a sample in direct mode write the value 20h to the DSP and then
read the value from the DSP. Simple as that!

To set up the DSP for a DMA transfer, follow this procedure :

1) Get a memory buffer ready to hold the sample
2) Set up the DMA chip for the transfer
3) Set the DSP TIME_CONSTANT to the sampling rate
4) Write the value 24h to the DSP
5) Write DATA_LENGTH to the DSP (2 bytes, LSB first) where
   DATA_LENGTH = number of bytes to read - 1

Note that the DMA chip must be programmed before the BSP.

DMA reads only support 8 bit mode, compressed modes are done by software and
stored in the voc file. I haven't tried to figure out how the compression is
done. If someone does figure it out I'd like to know about it!

?????????????????????????????????????????????????????????????????????????????
? Programming the DMA Chip ?
????????????????????????????

As mentioned before, Draeden has written a very good article on the dma
chip, but here is a brief run down on what you would need to do to program
the DMA channel 1 for the DSP in real mode:

1) Calculate the 20 bit address of the memory buffer you are using
   where Base Address = Segment * 16 + Offset
   eg 1234h:5678h = 179B8h
2) Send the value 05h to port 0Ah (mask off channel 1)
3) Send the value 00h to port 0Ch (clear the internal DMA flip/flop)
4) Send the value 49h to port 0Bh (for playback) or
                  45h to port 0Bh (for recording)
5) Write the LSB (bits 0 -> 7) of the 20 bit memory address to port 02h
6) Write the MSB (bits 8 -> 15) of the 20 bit memory address to ort 02h
7) Write the Page (bits 16 -> 19) of the 20 bit memory address to port 83h
8) Send the LSB of DATA_LENGTH to port 03h
9) Send the MSB of DATA_LENGTH to port 03h
10) Send the value 01h to port 0Ah (enable channel 1)

?????????????????????????????????????????????????????????????????????????????
? End of DMA Interrupt ?
????????????????????????

When a DMA transfer is complete an interrupt is generated. The actual
interrupt number depends on the SoundBlaster card's IRQ jumper setting:

                         ??????????????????????????
                         ? IRQ Jumper             ?
                         ?  Setting     Interrupt ?
                         ??????????????????????????
                         ?    2            0Ah    ?
                         ?    3            0Bh    ?
                         ?    5            0Dh    ?
                         ?    7            0Fh    ?
                         ??????????????????????????

To service one of these interrupts you must perform these 3 tasks:

1) Acknowledge the DSP interrupt by reading the DATA AVAILABLE port (2xEh)
   once.
2) If there are more blocks to transfer then set them up
3) Output value 20h (EOI) to the interrupt controller port 20h

Of course, as with any hardware interrupt you must also leave the
state of the system (registers etc..) the way it was when the interrupt
was called.

????????????????????????????????????????????????????????????????????????????
? A Simple DSP Pascal Unit ?
????????????????????????????

{

  DSP.PAS - A demo SoundBlaster DSP unit for real mode

  By Mark Feldman
}

Unit DSP;

Interface

{ ResetDSP returns true if reset was successful
  base should be 1 for base address 210h, 2 for 220h etc... }
function ResetDSP(base : word) : boolean;

{ Write DAC sets the speaker output level }
procedure WriteDAC(level : byte);

{ ReadDAC reads the microphone input level }
function ReadDAC : byte;

{ SpeakerOn connects the DAC to the speaker }
function SpeakerOn: byte;

{ SpeakerOff disconnects the DAC from the speaker,
  but does not affect the DAC operation }
function SpeakerOff: byte;

{ Functions to pause DMA playback }
procedure DMAStop;
procedure DMAContinue;

{ Playback plays a sample of a given size back at a given frequency using
  DMA channel 1. The sample must not cross a page boundry }
procedure Playback(sound : Pointer; size : word; frequency : word);

Implementation

Uses Crt;

var      DSP_RESET : word;
     DSP_READ_DATA : word;
    DSP_WRITE_DATA : word;
  DSP_WRITE_STATUS : word;
    DSP_DATA_AVAIL : word;

function ResetDSP(base : word) : boolean;
begin

  base := base * $10;

  { Calculate the port addresses }
  DSP_RESET := base + $206;
  DSP_READ_DATA := base + $20A;
  DSP_WRITE_DATA := base + $20C;
  DSP_WRITE_STATUS := base + $20C;
  DSP_DATA_AVAIL := base + $20E;

  { Reset the DSP, and give some nice long delays just to be safe }
  Port[DSP_RESET] := 1;
  Delay(10);
  Port[DSP_RESET] := 0;
  Delay(10);
  if (Port[DSP_DATA_AVAIL] And $80 = $80) And
     (Port[DSP_READ_DATA] = $AA) then
    ResetDSP := true
  else
    ResetDSP := false;
end;

procedure WriteDSP(value : byte);
begin
  while Port[DSP_WRITE_STATUS] And $80 <> 0 do;
  Port[DSP_WRITE_DATA] := value;
end;

function ReadDSP : byte;
begin
  while Port[DSP_DATA_AVAIL] and $80 = 0 do;
  ReadDSP := Port[DSP_READ_DATA];
end;

procedure WriteDAC(level : byte);
begin
  WriteDSP($10);
  WriteDSP(level);
end;

function ReadDAC : byte;
begin
  WriteDSP($20);
  ReadDAC := ReadDSP;
end;

function SpeakerOn: byte;
begin
  WriteDSP($D1);
end;

function SpeakerOff: byte;
begin
  WriteDSP($D3);
end;

procedure DMAContinue;
begin
  WriteDSP($D4);
end;

procedure DMAStop;
begin
  WriteDSP($D0);
end;

procedure Playback(sound : Pointer; size : word; frequency : word);
var time_constant : word;
     page, offset : word;
begin

  SpeakerOn;

  size := size - 1;

  { Set up the DMA chip }
  offset := Seg(sound^) Shl 4 + Ofs(sound^);
  page := (Seg(sound^) + Ofs(sound^) shr 4) shr 12;
  Port[$0A] := 5;
  Port[$0C] := 0;
  Port[$0B] := $49;
  Port[$02] := Lo(offset);
  Port[$02] := Hi(offset);
  Port[$83] := page;
  Port[$03] := Lo(size);
  Port[$03] := Hi(size);
  Port[$0A] := 1;

  { Set the playback frequency }
  time_constant := 256 - 1000000 div frequency;
  WriteDSP($40);
  WriteDSP(time_constant);

  { Set the playback type (8-bit) }
  WriteDSP($14);
  WriteDSP(Lo(size));
  WriteDSP(Hi(size));
end;

end.


?????????????????????????????????????????????????????????????????????????????
? References ?
??????????????

Title : The SoundBlaster Developpers Kit
Publishers : Creative Labs Inc
             Creative Technology PTE LTD

Title : Sound Blaster - The Official Book
Authors : Richard Heimlich, David M. Golden, Ivan Luk, Peter M. Ridge
Publishers : Osborne/McGraw Hill
ISBN : 0-07-881907-5

Some of the information in this file was either obtained from or verified
by the source code in a public domain library called SOUNDX by Peter
Sprenger. I haven't tried using his library yet (I don't have a C compiler
at the moment) but it looks very well done and contains numerous sound card
detection routines. Says Peter : "It would be nice, that when you make
something commercial with my routines, that you send me a copy of your
project or send me some bucks, just enough for pizza and coke to support my
night programming sessions. If you send me nothing, ok. But USE the stuff,
if you can need it!". Heh...a REAL programmer!

ftpsite: ftp.uwp.edu
directory: /pub/msdos/demos/programming/game-dev/source
filename: soundx.zip

????????????????????????????????????????????????????????????????????????????
? Sound Familiar? ?
???????????????????

What the...why is there a faint glimmer of sunlight outside? HOLY $#!^!! It's
5:30am! I'm goin' to bed!


                  ????????????????????????????????????
                  ? Programming the SoundBlaster Pro ?
                  ????????????????????????????????????

                  Written for the PC-GPE by Mark Feldman
              e-mail address : u914097@student.canberra.edu.au
                               myndale@cairo.anu.edu.au

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Introduction ?
????????????????

I still own a SoundBlaster 1.0 (don't laugh) so I haven't been able to
test any of the information in this file, ie don't take any of this as
fact.

?????????????????????????????????????????????????????????????????????????????
? Stereo Sound ?
????????????????

Generating stereo FM sound on the SB Pro is similar to the way it's done
on the SB 1.x, you just use different ports for the left and right channels.
The file ADLIB.TXT has more information on this.

Generating stereo sounds with the DSP is similar to the mono method, but you
send *two* bytes for every sample. The first one goes to the left channel
and the second one goes to the right. You also need to reset the mixer chip
and tell the soundblaster you want to play a stereo sound (see below). This
has the advantage in that you can store the info for both channels in a
single data block and transfer it by still using only one DMA channel. The
WAV file format actually stores it's audio waveform data like this (see the
PC-GPE file WAV.TXT).


  Left channel bytes

  0       1       2       3       4       5       6
????????????????????????????????????????????????????????????
?   ?   ?   ?   ?   ?   ?   ?   ?   ?   ?   ?   ?   ?   ?   ........
????????????????????????????????????????????????????????????
      0       1       2       3       4       5       6

  Right channel bytes

To play the sound the SoundBlaster Pro is set for stereo output and the DMA
chip is programmed to send this chunk as is.


?????????????????????????????????????????????????????????????????????????????
? The CT 1345 Mixer Chip ?
??????????????????????????


You access the mixer registers the same way you access the regular SB
registers, but Port 2x4h is the index port and 2x5h is the data read/write
port, where x = 2 for base address jumper setting 220h
            x = 3 for base address jumper setting 230h
            x = 4 for base address jumper setting 240h

So setting a mixer register to a given value can be accomplished with the
following procedure:

{ base = 220h, 230 or 240h }
procedure SetMixerReg(base : word; index, value : byte);
begin
  Port[base + 4] := index;
  Port[base + 5] := value;
end;


You can also read a register's current value:

function GetMixerReg(base : word; index : byte) : byte;
begin
  Port[base + 4] := index;
  GetMixerReg := Port[base + 5];
end;





The Data Reset register is used to reset the mixer chip. Set this register to
0 before changing any of the other mixer registers.

              Index = 00h
              ?????????????????????????????????
              ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
              ?????????????????????????????????
                ?????????????????????????????
                              ?
                          Data Reset




The Input register selects the SB Pro sound input source and filter type.

              Index = 0Ch
              ?????????????????????????????????
              ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
              ?????????????????????????????????
                        ?????????   ?????
                            ?         ?
                ??????????????????? ?????????????????????
                ? In Filter       ? ? ADC Source        ?
                ? 000 - Low       ? ? 00 - Microphone 1 ?
                ? 001 - High      ? ? 01 - CD           ?
                ? 010 - No Filter ? ? 10 - Microphone 2 ?
                ??????????????????? ? 11 - Line In      ?
                                    ?????????????????????



The Output register determines whether to output sound in stereo or mono, in
stereo two bytes must be sent for each sample, the first one goes to the left
channel and the next one goes to the right. This register allows you to
bypass the output filter.

              Index = 0Eh
              ?????????????????????????????????
              ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
              ?????????????????????????????????
                        ?               ?
           ????????????????????????? ??????????????
           ? DNFI                  ? ? VSTC       ?
           ? 0 - Use O/P Filter    ? ? 0 - Mono   ?
           ? 1 - Bypass O/P Filter ? ? 1 - Stereo ?
           ????????????????????????? ??????????????


The Master Volume register allows you to set the master volume of each
channel:

              Index = 22h
              ?????????????????????????????????
              ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
              ?????????????????????????????????
                ?????????????   ?????????????
                      ?               ?
             ??????????????????????????????????
             ? Master Volume ?? Master Volume ?
             ?     Left      ??     Right     ?
             ??????????????????????????????????


The Voice Volume register allows you to set the volume of each channel for
DSP output:

              Index = 04h
              ?????????????????????????????????
              ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
              ?????????????????????????????????
                ?????????????   ?????????????
                      ?               ?
              ????????????????????????????????
              ? Voice Volume ?? Voice Volume ?
              ?     Left     ??     Right    ?
              ????????????????????????????????

                 Voice Volume    Voice Volume
                    Left            Right


The FM Volume register allows you to set the volume of each channel for
FM wave synthesis:



              Index = 26h
              ?????????????????????????????????
              ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
              ?????????????????????????????????
                ?????????????   ?????????????
                      ?               ?
                ?????????????   ?????????????
                ? FM Volume ?   ? FM Volume ?
                ?   Left    ?   ?   Right   ?
                ?????????????   ?????????????


The CD Volume register allows you to set the volume of each channel for
CD output:

              Index = 28h
              ?????????????????????????????????
              ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
              ?????????????????????????????????
                ?????????????   ?????????????
                      ?               ?
                ?????????????   ?????????????
                ? CD Volume ?   ? CD Volume ?
                ?   Left    ?   ?   Right   ?
                ?????????????   ?????????????


The Line Volume register allows you to set the volume of each channel for
line in channel:


              Index = 2Eh
              ?????????????????????????????????
              ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
              ?????????????????????????????????
                ?????????????   ?????????????
                      ?               ?
               ??????????????? ???????????????
               ? Line Volume ? ? Line Volume ?
               ?    Left     ? ?    Right    ?
               ??????????????? ???????????????

The Mic Mixing register allows you to set the input volume for the
microphone:

              Index = 0Ah
              ?????????????????????????????????
              ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
              ?????????????????????????????????
                                    ?????????
                                        ?
                                 ??????????????
                                 ? Mic Mixing ?
                                 ??????????????

?????????????????????????????????????????????????????????????????????????????
? References ?
??????????????

Title : The SoundBlaster Developpers Kit
Publishers : Creative Labs Inc
             Creative Technology PTE LTD

Title : Sound Blaster - The Official Book
Authors : Richard Heimlich, David M. Golden, Ivan Luk, Peter M. Ridge
Publishers : Osborne/McGraw Hill
ISBN : 0-07-881907-5

Some of the information in this file was either obtained from or verified
by the source code in a public domain library called SOUNDX by Peter
Sprenger. I haven't tried using his library yet (I don't have a C compiler
at the moment) but it looks very well done and contains numerous sound card
detection routines. Says Peter : "It would be nice, that when you make
something commercial with my routines, that you send me a copy of your
project or send me some bucks, just enough for pizza and coke to support my
night programming sessions. If you send me nothing, ok. But USE the stuff,
if you can need it!". Heh...a REAL programmer!

ftp site: ftp.uwp.edu
directory: /pub/msdos/demos/programming/game-dev/source
filename: soundx.zip


        GRAVIS ULTRASOUND ("GUS") FAQ VERSION 1.50 [94/01/12]

----------------------------------------------------------------------

        Certain questions concerning the Gravis UltraSound ("GUS")
sound card are asked over and over on the UltraSound Daily Digest (a
mailing list for GUS users) and on comp.sys.ibm.pc.soundcard.  In an
attempt to alleviate some redundancy from the lives of USENET/Internet
folk, this FAQ (Frequently Asked Questions, with answers) list has
been created.  It is maintained by Matthew Bernold (MEB117@PSUVM.PSU.EDU)
If you have any questions, comments, complaints, or extra cash, (especially
the cash) please feel free to send them to him.  Please do not send your
question more than once, as Matthew does have other things to do aside from
answering FAQ mail.  If you do not get an answer after a month or so, then
there may be a mail problem.  :-)

        If you would like to join the mailing list and be privy to the
latest and greatest information, banter, and poor spelling concerning
the GUS, mail to <ultrasound-request@dsd.es.com>.  The automated
server will tell you how to sign up for the mailing list, tell you
where the FTP sites associated with the Digest are (they recieve
software updates directly from Gravis often), and other such
information that will eventually lead you down the trail to Nirvana,
Valhalla, Heaven, or whatever Land O' Happiness your religion wants to
get to.

        BTW: All FAQs, including this one, are available on the
archive site rtfm.mit.edu in the directory pub/usenet/news.answers.
The name under which a FAQ is archived appears in the "Archive-Name:"
line at the top of the article.  This FAQ is archived as
PCsoundcards/gravis-ultrasound/faq.

        Special thanks are due to many people who helped (and are
helping) with this FAQ.  I won't try to name off people; I'll probably
forget half of you, and you all know who you are, anyway.

----------------------------------------------------------------------

        BIG IMPORTANT NOTE: Neither this FAQ, the mailing lists or
digests, nor the FTP sites are owned or operated by Gravis.  Gravis
employees *read* the digest and mailing lists and they upload things
to the FTP sites, but that's it.

        SO: Please don't email me about problems with your card, if
the latest release of software hasn't arrived on disks in the mail
yet, lack of documentation, etc., etc.  I'm doing this on my own time,
and I have no desire to receive hate mail intended for Gravis.

----------------------------------------------------------------------

Index of Questions
------------------
         1] What is the GUS?
         2] How does the GUS emulate other soundcards?
         3] Where can I get a GUS, and how much will it cost?
         4] What version of the GUS hardware is the latest?
         5] What GUS software is available?  What version is it?
         6] Where can I get the latest GUS software?
            (AKA: Where is the GUS FTP site and/or Gravis BBS?)
                6a] What if I don't have FTP access?
         7] What machines will the GUS work with?
                7a] I've heard about problems with the OPTi chipset...
         8] Why should I upgrade the memory onboard my GUS?
         9] Where can I get memory for the GUS, and how much will it cost?
        10] I'm having trouble getting the GUS to work with Windows...
        11] What new hardware is coming out for the GUS?
        12] How do I build the MIDI interface for the GUS?
        13] What exactly is GUS 3D?
        14] What are *.PAT *.VOC *.WAV *.SND *.MOD *.669, and *.MID
                files, and how do I use them?
        15] What exactly is Wavetable Synthesis?
        16] Is there a GUS device driver for Linux/BSD386/*IX?
        17] How do I get the GUS to work with OS/2?
        18] How do I go about programming the GUS?
        19] What are the pinouts for the CD Audio IN on the GUS?
        20] I'm having trouble with... GENERAL TROUBLESHOOTING TIPS
        21] I can't seem to fit the new disks onto a floppy.
        22] Why shouldn't I use the comp.sys.ibm.pc.soundcard.GUS
                newgroup?
        23] What are "Miles Drivers", and how do I use them?

----------------------------------------------------------------------

 1] What is the GUS?

        The Gravis UltraSound (generall referred to as the "GUS") is a
sound card built by Advanced Gravis Technologies (GRVSF on the Nasdaq
exchange).  It is a stereo card that can play 32 synthesized voices
and 32 sampled voices simultaneously.  It is also MIDI compatible.

        The synthesizer on the GUS is based on a technology called
Wavetable Synthesis (WS) instead of FM synthesis (like the Adlib and
Soundblaster series).  WS is flexible enough to emulate FM synthesis,
and so an emulator has been created so SoundBlaster and Adlib programs
can use the GUS (see question #2).

        The GUS, in its basic state, can sample 8 bit stereo at 44kHz.
It can playback 16 bit stereo samples at 44kHz.  There is a
daughterboard that you can buy (to be released) and plug on to the GUS
that makes it possible to sample at 16 bit stereo 44kHz.

        Each voice can play independantly, but as the maximum number
of voices goes up, the sample playback rate drops.  With 14 active
voices, the GUS can playback at 44100Hz.  At 28 active voices, the
playback rate drops to 22050Hz.  With the maximum 32 voices, the GUS
can playback at a rate of 19293Hz.  Following is a chart taken from the
GUS SDK v2.01 listing the number of active voices and the playback rate.

  Active   Playback    Active   Playback    Active   Playback
  voices     rate      voices     rate      voices     rate
    14      44100        21      29400        27      22866
    15      41160        22      28063        28      22050
    16      38587        23      26843        29      21289
    17      36317        24      25725        30      20580
    18      34300        25      24696        31      19916
    19      32494        26      23746        32      19293
    20      30870

        If you tell the GUS to play at a different rate than listed
above, the GF1 processor automatically interpolates the sample, and
simulates playback at the desired rate.

        Each voice also has 15 panning positions, and 4096 settings of
volume.  The GUS has automated volume-ramping that can be used as
one-shot or oscillating volume modulators.  Thus, amplitude envelopes
use very little CPU horsepower.  For more technical information, read
the GUS SDK (see question #24).

        The GUS has the following "external" ports:
        o Stereo line in
        o Stereo line out
        o Stereo amplified out
        o Stereo microphone in
        o Game port / MIDI port

        The GUS has several "internal" ports, including:
        o CD Audio IN
        o Expansion ports for daughtercards (see question #15).
        o Other as of yet unexplained pins/ports.

        The game port can be changed to MIDI in/out/through ports by
means of an adapter available from Gravis.  Alternatively (and for a
LOT less money) you can build your own (see question #17).

----------------------------------------------------------------------

 2] How does the GUS emulate other soundcards?

        Right now, there are several ways the GUS may emulate other
soundcards/soundcard combinations.  Following is a list of combinations
that the GUS may emulate, and the program to be used for this emulation:

        Sound Blaster/Adlib        SBOS
        Roland/SB Digital          MegaEm
        General MIDI/SB Digital    MegaEm

 (* Insert info about lists here *)


----------------------------------------------------------------------

 3] Where can I get a GUS, and how much will it cost?

        The "suggested retail" for the card is $200 (U.S. dollars),
but if you pay that much, you haven't done your homework.  However,
homework on this card isn't easy because Gravis still hasn't actually
advertised (they have a weird policy concerning advertising).

Here are some mail order places that supposedly carry the GUS.  Since
prices tend to change faster than FAQs, I am not posting prices.  For
our non-american users, there are some FAX or non-800 numbers as well.

Vendor                 800 Number          FAX             Voice
Zeroes & Ones        1-800-788-2193   1-702-897-1571
Disk-Count Software  1-800-448-6658   1-908-396-8881   1-908-396-8880
Mission Control      1-800-999-7995   1-201-677-9484   1-201-677-1400
Bit Wit Software     1-800-259-2453   1-214-306-9603   1-214-539-5473
Viking Software      1-800-852-6187   1-404-840-7925
Chips & Bits         1-800-753-4263   1-802-767-3382   1-802-767-3033
Computer Express     1-800-228-7449   1-508-443-5645

        If you call around, you should have no trouble getting the GUS
for less than $150.  Suggested places are Babbages, Bizmart, OfficeMax,
and Disk-Count software.

----------------------------------------------------------------------

 4] What version of the GUS hardware is the latest?

        This is a question that is actually pretty irrelevant.  Yes,
there have been different "releases" of the GUS card (the number is
etched into the board), but there really aren't any differences.
Evidently, some of the newer cards have been redesigned to require
less hardware (and less cost to Gravis), but no functionality changes
have been made.

        Also, the newest versions of the GUS (v3.4+) have volume
control on some of the inputs, and adds an on/off and volume control
on the CD input.  The new windows mixer takes advantage of this.
If you have an older GUS, the mixer just grays out the volume sliders.

----------------------------------------------------------------------

 5] What version of the GUS software is the latest?


    Title       Ver    Filename   Where?
    ---------  -----   ---------  -------
    Install    2.06L              GUS FTP
               2.06               Mailed
    SBOS       3.4                GUS FTP
    MegaEm     2.02               GUS FTP

----------------------------------------------------------------------

 6] Where can I get the latest GUS software?
    (AKA: Where is the GUS FTP site and/or Gravis BBS?)

GUS FTP sites:
        archive.epas.utoronto.ca              /pub/pc/ultrasound
        wuarchive.wustl.edu            /systems/ibmpc/ultrasound
        archive.orst.edu                    /pub/packages/gravis
        theoris.rz.uni-konstanz.de                /pub/sound/gus
        nctuccca.edu.tw                           /PC/ultrasound

GUS Mailserver:
        mail-server@nike.rz.uni-konstanz.de

        BTW: You can get a LOT more than just GUS software releases
from Gravis on the FTP sites.  There's lots of PD software written
specifically for the GUS, music (midi music, midi patches, mods, 669
music, samples, etc., etc), tech info on the card, back issues of the
UltraSound Daily Digest, etc., etc... check it out!

Gravis BBS:
        (604) 431-5927

6a] What if I don't have FTP access?

        Use the GUS Mailserver!

        Send mail to mail-server@nike.rz.uni-konstanz.de with the body
of the message as follows:

        begin
        send help
        end

        Alternatively, you can call the Gravis BBS.  There are several
major disadvantages with this, though:

        1] Long distance to Canada (no offense to you Canadians :).
        2] 2400 baud.
        3] The BBS doesn't have all the public domain stuff that
                the FTP sites do.
        4] It's almost *always* busy.

        Please *DO NOT* ask people to post binaries to
comp.sys.ibm.pc.soundcard.  It's not a binary newsgroup, and that's a
lot of wasted bandwidth to people who don't want the programs.  Use
email.  It saves bandwidth, fights cavities, and builds character.

----------------------------------------------------------------------

 7] What machines will the GUS work with?

        You need an IBM compatible computer with at least a 286
processor.  It needs to be at least a 386 if you want to use the GUS
with Windows.

7a] I've heard about problems with the OPTi chipset...

        There have been troubles with the GUS if your computer's
chipset is made by OPTI.  Not all OPTI chipsets are bad, but some of
them have a faulty DMA controller.  We're still trying to pin down
which chipsets are flawed; when we have a better idea of exactly which
ones are bad they'll be added here.  Until then, be careful if your
computer has an OPTI set, and try reading the UltraSound Daily Digest,
or comp.sys.ibm.pc.soundcard on USENET.

Written by: dantonio@magick.tay2.dec.com
        Actually, it's not just OPTi chipsets, UMC has been implicated
as well (Gravis first noticed the problem with UMC chipsets) and
according to Digital Audio Labs (who told Gravis what was going on),
the bad datacode is 9149 and the bad chip is the 82C206.  This is all
explained in the docs for GUS0013.ZIP (I think), the OPTi fix posted
to the GUS FTP sites.

----------------------------------------------------------------------

 8] Why should I upgrade the memory onboard my GUS?

        For starters, the announcement has already come out of Gravis
that the standard GUS will come with 512k instead of 256k.  This means
that software companies will write their programs to use *at least*
512k onboard the GUS.  And with all the users going to 1meg, chances
are that things will be written for that limit.

        It's a cheap upgrade.  If your board came with 256k, it will
only cost you about $30 to go up to 1meg (see question #10).

        There's already a lot of MIDI files out there that require the
full 1meg to play them, because they use lots of different instrument
patches.

        If you plan on doing any sampling, you'll need the space.  You
can do direct-to-disk sampling, but it can cause "skips" to go into
the sample each time the sample goes down the bus to the drive.  In a
worst case situation, you could be sampling 16 bits in stereo at
44kHz.  So, you're doing 88000 samples (stereo, remember) of 16 bits
each every second.  That's 171k (176000 bytes) every second, which
means the full 1meg memory will fill up in 5 seconds at that rate.
With only 256k, you can get about 1.5 seconds.

        Of course, only people doing very serious stuff with the card
need to sample at that high of a rate in 16 bits.  MOD files generally
do 16kHz 8 bit mono samples.  But upgrading the card is still pretty
important in that case... do the math, and you'll see.

----------------------------------------------------------------------

 9] Where can I get memory for the GUS, and how much will it cost?

        You need six 256x4 DRAM chips, with speeds of 80ns or better
(in other words, 80ns OR LESS).  They tend to run about $4 a piece, so
the total price will be $24 + shipping.  Make sure you ask for
"page mode" ram, or they will not work correctly with your GUS.

        To find  a  place with them, just  look through  the  Computer
Shopper magazine.  Check the index for  'memory' and call a few places
for prices.  (After a little calling, I found a place selling them for
$3.45 apiece.)

        To ensure compatibility, look for the number "44256" in the
chip number.  If you do not see this number, you probably do not have
the right chip.

        NEW NOTE: Gravis is now offering to sell the chips directly to
you for a much lower cost (they can buy in bulk).  Give them a call
for latest chip prices.

----------------------------------------------------------------------

10] I'm having trouble getting the GUS to work with Windows...

        There in one possibility that accounts for about 50% of the
problems people have with the GUS and Windows: you can't have SBOS
loaded before going to Windows.  (You don't need it... if the Windows
program was written right, Windows will handle the link to the card;
the program shouldn't care.)  Try running ULTRINIT (it clears the GUS'
program memory), or rebooting.

        Other problems:

(a) No sound at all in Windows...

Written by: dantonio@magick.tay2.dec.com
        People often put ultrasnd.ini into \windows\system which they
shouldn't.  They SHOULD put \ultrasnd\windows\midimap.cfg into
\windows\system to get the MIDI stuff setup correctly.

(b) I'm not getting any sound when I play MIDI files under Windows.
        The Patch Manager shows empty boxes.

Written by: bs@mda.ca (Bruce Sharpe)
        You need a file  called ULTRASND.INI.  You  can find this file
in any one of the following places:

        1. The v2.06 distribution disk set.
        2. One of the GUS FTP sites.
        3. The Gravis BBS.
        4. CompuServe: GO PCVENB, Library 14, name is ULTSND.INI
                (rename it to ULTRASND.INI after downloading).

        ULTRASND.INI must be placed in the directory pointed to by the
environment variable ULTRADIR (usually C:\ULTRASND).  It does *not* go
into the WINDOWS or WINDOWS\SYSTEM directory.

        Even if you have an ULTRASND.INI in your ULTRADIR directory,
look at it.  It should have many lines in it saying things like
"0=acpiano".  If it is only a few lines long, get another copy and put
it into the ULTRADIR directory.  Reboot Windows and you will soon be
hearing beautiful music!

        (The purpose of the ULTRASND.INI file is to let the Windows
driver know what patch file goes with what patch number.  If the
driver doesn't find the .INI file in the ULTRADIR directory it creates
a truncated version with no patch names in it.)

(c) All the list boxes are blacked out in the UltraSound Patch
        Manager.

Written by: bs@mda.ca (Bruce Sharpe)
        This was a problem that was fixed in v1.02.  It only occurs in
certain Windows color schemes (e.g., Ocean).  If you can't get your
hands on a more recent PatchManager, then change your color scheme.

(d) Other general Windows/GUS problems.

Written by: john.smith@gravis.com (John Smith)
        At least one major problem people have been having with the
new release has been solved.  Many thanks to Fransisco Perez. He
noticed that he had a grvsultr.386 file in his \windows directory and
it was NOT the new one.  Apparently, windows looks in the path and
uses the first one that it finds. It should have gotten the one in the
windows\system directory. Using the old one with the new patches etc.
causes SERIOUS problems. The old install software required the user to
copy some things manually and some people put the files in the windows
directory instead of the windows\system directory.  The new install
will install windows automatically and puts the files in the
windows\system directory.  To correct the problem, make sure the
following files are in your windows\system and ultrasnd\windows
directory ONLY!!!  If you find them anywhere else, you should remove
them....


...\windows\system\
    grvsultr.386                     <
    midimap.cfg                      < These files are also located
    ultmport.drv                     < in the UltraSnd\Windows
    ultrasnd.drv                     <

...\ultrasnd\
    ultrasnd.ini

...\ultrasnd\windows\
    ultrasnd.ini
    oemsetup.inf
    mixer.exe
    patchmgr.exe
    patchmgr.hlp
    ultrahlp.hlp

        Some of you have been trying to re-run the automatic Windows
install simply by running WINGUS from your UltraSound\Windows
directory.  The problem with this is WINGUS is looking for an install
script file that has an extension of .INF.  The first file it
encounters is OEMSETUP.INF, which it trys to execute but because this
is NOT a script file you will get MANY error messages.  Try renaming
OEMSETUP.INF to OEM.TMP then run WINGUS.  WINGUS will then see WIN.INF
and load that instead.

----------------------------------------------------------------------

11] What new hardware is coming out for the GUS?

Ed. Note: I know this list is out of date, but I don't have anything
          better/more up to date, so I'm leaving it.  If you have
          some more recent info, let me know, and I'll put it in here.

Written by: Bruce Sharpe (bs@mda.ca)

   -------------------------------------------------------------------
   | Advanced Gravis Product Support BBS      Pricing & Availability |
   -------------------------------------------------------------------
   |                Item                           When?    SRP($US) |
   -------------------------------------------------------------------
   | MIDI Connector Box                        |  Apr '93  |  $49.95 |
   | 16-bit Stereo Recording Interface Card    |  Apr '93  | $149.95 |
   | LMSI CD-ROM Daughter Card for CM205 and   | Qtr 1 '93 |  $59.95 |
   |  and CM215 (Phillips, Magnavox, LMSI)     |           |         |
   | Sony CD-ROM Daughter Card for Sony 31A    | Qtr 1 '93 |  $49.95 |
   | Mitsumi CD-ROM Daughter Card              | Qtr 1 '93 |  $49.95 |
   | SCSI CD-ROM Daughter Card                 | Qtr 1 '93 |   TBA   |
   -------------------------------------------------------------------

Details?  Good question.

----------------------------------------------------------------------

12] How do I build the MIDI interface for the GUS?

Written by: pcunnell@micrognosis.co.uk (Paul Cunnell)

> Has anyone made the midi interface for the GUS that is in the FAQ?
> If so, were did you find the part# 6N138?  I cant seem to locate
> this anywhere.  Also, (excuse my ingorance i'm not an EE) but
> what exactly is that part and its purpose? Thanks...

        The 6N138 is a high sensitivity opto-isolator, manufactured by
Hewlett Packard (and I believe, a company called Quality Technology)
The main point in using this part as opposed to other more common
opto-isolators is the low LED ON current spec. (1.6mA)

        A midi out circuit is basically a LED in series with 600 ohms,
and a 5V supply. Taking into account the 1.7V forward drop across the
LED, you get about 5mA in the on state.  Other optos generally need
more current to turn them on (say 15-60mA, but this varies a lot). A
'high speed' 6N137 opto will also work, I believe, but that would be a
bit marginal on the input current (spec. is min 5mA).

        Since a number of people have been asking, I'll add below the
midi circuit that I'm using, plus a bit of general explanation I've
culled from other peoples' postings on the subject.

Generic Midi Out/In/Through Circuit
===================================

The following shows a typical OUT, cable, and IN circuit

      MIDI OUT port ---->|<- cable ->|<---- MIDI IN port              +5V
                                                                 270   |
            +5V        DIN           DIN                     +--\/\/\/-+
             |  220    +-+ +-------+ +-+   220    +--------+ |
        |\   +-\/\/\/--|4|-|-------|-|4|--\/\/\/--|  OPTO  |-+-+- UART RXD
 UART   | \            | | |       | | |          |ISOLATOR|   |
 TXD ---|  \---\/\/\/--|5|-|-------|-|5|----------|        |-+ |
        |  /    220    | | +-------+ | |          +--------+ | |
        | /         +--|2|-+       +-|2|            6N138   GND|
        |/ 7407     |  +-+           +-+                       |
                   GND                                         |
                                                               |
                   +-------------------------------------------+
                   |
                   |      +5V        DIN
                   |       |  220    +-+
                   |  |\   +-\/\/\/--|4|
                   |  | \            | |
                   +--|  \---\/\/\/--|5|   MIDI THRU
                      |  /    220    | |
                      | /         +--|2|
                      |/ 7407     |  +-+
                                 GND

        Note that when the UART TXD is high, no current flows through
the resistors and optoisolator's LED, causing the optoisolator's
phototransistor to remain off, allowing the UART RXD to be pulled high
by the 270 ohm resistor.  When the UART TXD is low, current flows
through the resistors and optoisolator's LED, turning on
optoisolator's phototransistor, grounding the UART RXD.  The voltage
drop across the optoisolator's LED is typically 1.5 volts, leaving 3.5
volts to be dropped across (3 times 220) 660 ohms, which allows about
5 ma to flow.

        The reason a current loop is used is that it allows an ground
isolated interconnection.  Note that the ground from the MIDI OUT
port's device is not connected to the ground of the MIDI IN port's
device.  This prevents ground loops in systems where appropriate
attention has not been paid to grounding issues, such as the case of
typical musicians in a typical club!

Gravis Ultrasound Circuit
=========================

15 pin D connector
                                          220R
pin-1 +5v ----+--------------------------/\/\/\---------------\
              |                                                \ 4
              |                                          Gnd--2   MIDI OUT
              |      |\      |\            220R                / 5
pin-12 tr >---|------| o-----| o----------/\/\/\--------------/
              |    13|/ 12 11|/ 10
              |                            220R
              +---------------------------/\/\/\-------------\
              |                                               \ 4
pin-15 rx <---|--------------------+                    Gnd--2    MIDI THRU
              |      |\      |\    |        220R              / 5
              |   +--| o-----| o---+-------/\/\/\------------/
              |   | 1|/ 2   3|/ 4
              |   |
              |   +------+
              |   270R   |                       220R
              +--/\/\/\--+    +------+----------/\/\/\--------\
              |B         |C   |A     |                         \  4
            +-|----------|----|-+    |                             MIDI IN
            |  8        6     2 |  -----                       /  5
            |                   |   / \ IN914 or IN4148     +-/
            |      6N138        |   ---                     |
            |                   |    |                      |
            |           5     3 |    |                      |
            +------------|----|-+    |                      |
                         |    |K     |                      |
pin-5 Gnd  --------------+    +------+----------------------+

        Inverters are 74LS04. (This is a 14-pin IC containing 6
inverters.  Connect pin 14 to +5V, pin 7 to GND)

        Leave pin 2 of the MIDI IN unconnected (Don't connect to ground).

Some hints for testing your circuit
===================================

        1] Check *all* connections (use a continuity tester, and tick
them off on a printout of the circuit).

        2] Check them again ;-)

        3] Make sure you have the latest (GUS0012.zip) windows midi
driver, and make sure it is installed properly.

        4] Make sure your midi sequencer package is set up to use the
Ultrasound Midi In/Out ports. (As opposed to the Ultrasound Synth)

        5] If you still have no joy,

                a] Just connect the +5V and GND to your midi circuit,
        (leave the d-connector pins 12 and 15 unconnected), and then
        connect pin 13 of the 7404 to +5V check you have (about) +5V
        appearing on pin 10.  This checks midi out.

                b] Connect pin 4 of the midi-in DIN socket via 2 extra
        220R resistors to +5V.  Check pin 4 of the 7404.  It should be
        low (about 0V).  Then connect pin 4 of the midi-in DIN socket
        to 0V.  Pin 4 of the 7404 should go high.  This checks midi in.

                c] Reconnect the d-type pins 12 and 15, and connect a
        midi cable between midi-out on the circuit and and midi-in on
        your synth.  Set up your sequencer to use the Ultrasound MIDI
        port as an output, and ensure that one of the tracks is set to
        use this port.  Check your synth is expecting MIDI data on the
        same channel as sequencer is transmitting.  Start sequencer
        playing.  Check that midi data is being transmitted at pin 12
        of the d-type (look at it with an oscilloscope, if possible).

Note
====

        Standard disclaimers apply - use this information at your own
risk, and if your fry your card/PC/synth/toaster, then you have my
sympathy, but not much else ;-)

        If you're not happy about messing with circuits and soldering
irons and wires and stuff, then you may wish to wait for the midi
connector box from Gravis to become available.

        I notice that in the older FAQs, there is a description (from
Dustin Caldwell <DUSTIN@gse.utah.edu>) of the solder side pinout for a
15-pin D-type connector. This looks wrong to me. I have a 15-pin male
d-type in from of me, and it looks like this from the solder side
(i.e. the side you attach the wires to, rather than the side with the
pins that plugs into the card):

                    Gnd             +5V
         8   7   6   5   4   3   2   1
 +-----/-------------------------------\-----+
 |     \ o   o   o   o   o   o   o   o /     |
 | ( )  \                             / ( )  |
 |       \ o   o   o   o   o   o   o /       |
 +--------\-------------------------/--------+
           15  14  13  12  11  10  9
           Rx          Tx

        It is easy to get the pins confused on these connectors - the
female version seen from the solder side of course has everything the
other way around (pin 1 is on the left hand side).

        Hope this helps (or at least doesn't add to the confusion :-).
All reasonable quality D-type connectors have pin numbers marked
against the pins anyway.

----------------------------------------------------------------------

13] What exactly is GUS 3D?

        First and foremost: YES, this is SOFTWARE.  You will NOT need
to upgrade your GUS to be able to do the GUS-3D stuff.

Written By: dionf@ERE.UMontreal.CA (Francois Dion)

        There are several systems that are in use to get 3D sounds on
recordings and some have been around since the 50s. Now i wont go into
the "how it works" of the more recent ones, but i think this will
clear up some confusion.  The first part is a "hands-on" experiment,
the second is informations, including the address and phone of the
owner of the technology that is used with the Gravis Ultrasound.

        Let's get back to the early days of stereo. One record company
(i cant seem to remember) was pushing it, while another (again, blank.
anyone?)  competed.  Interestingly enough, technological development
was put on stereo, and not on the first 3D system which was called
"binaural recording" and it simply consisted of two microphones placed
like the ears. You can try it this way:

        Go to a hat store and buy an extruded foam mannequin head.
You'll then need two microphones. Condenser will do, but you will need
to power them if you want to use them with the GUS, since it take a
dynamic microphone because it does not supply phantom power like some
mixer with XLR plugs. I will post a circuit later for Radio-shack
condenser mike unit (a small element that cost about 2$) if there is
some interest. If you dont want to mess with that, go with a cardiod
dynamic element.  Note that sensitive enough cardiod will cost you a
lot, so think about that.  You cut holes in the ears of the head, to
insert the microphone units (dont forget to make the wires of the
elements go inside the head and out the rear (or wherever). Use glue
to fill the crack around the mic.  Also, the more the ears look like
real ears, the better it will work. If you trim the foam, dont forget
to use an hairdryer to soften it (it will be more uniform). That's it.
Try recording sounds, and you'll be surprised.  I was! I did the
experiment with a polystyrene head on which i incrusted two PZM
microphones.

        Now that you understand how 3D recording is nothing like
stereo recording, we'll see what is accesible presently.

        First, the gadget we just built in the previous section exist
commercially, and is called "Mikey" and is made by Spherical Sound.
It's the only system commercialised where the microphones are placed
in a head.

        Another system is made by Virtual Audio and claims to enhance
stereo depth, but is not labeled 3D audio. I dont have much more info
on it, but from the description it looks like the same thing as the
"mikey".

        Two other systems use less restraining microphones situation
and can also be used on any signal because a DSP simulate a 3D signal
from parameters entered on the machine.  QSound (no hyphen) was
developped in Quebec, and the inventor sold the concept to another
company (Archer it seems). It is not that good even with electrostatic
headphones, and is pretty bad if you are listening to it thru speakers
and you are not in the sweet spot. And for trivia: Madonna, Sting,
Wilson Phillips and Paula Abdul to name a few have used the QSound on
their latest recordings. Another trivia: The Q logo is very very close
to Hydro-Quebec logo... QSound cost around 18K$ and is not midi
controllable.

        The other variant with a DSP is Roland RSS (Roland Sound
Space). It is a bit better (depending on how it is used) than QSound
with headphones, but suffers the same faith as QSound when you are
listening with speakers. Just move a bit from the sweet spot, and
suddenly what was in front left is now back left. RSS was used on
Suzanne Cianni _Hotel Luna_ album.  RSS cost around 40K$ and is midi
controllable.

        Another system on which i have zero information is called
Audio Cybernetics.

        The last technology is called Focal Point 3D Audio. It was
developped by Bo Gehring and first used on the Macintosh computers
with a modified Audiomedia (Digidesign). It cost around 1400$ in this
configuration. But, Gravis saw that (Focal Point is from Seattle) and
it is the system that we will be getting. At a much better price. The
system produce the sounds with these parameters: direction, elevation
and distance.  I am pretty sure that Gravis will have to develop a
SYSEX command set. We already need it badly, but with 3D, i will shoot
myself if i cant control it thru sysex.

        By the way, here's how to get in touch with Focal Point 3D
Audio, if you're interested.

        Focal Point(tm) 3D audio
        1402 Pine av., #127
        Niagara Falls, NY 14301
        Voice/fax: 1-416-963-9188

        Ok, you have read the 3D thing, and you cant wait. You want
big sound. The only possibility for now is surround. Now surround cost
a lot of money, and it will not be useable anymore once you get the 3D
driver. Wrong.

        Now, i hope you have an amplifier, cause if you dont, you
can't use this little hack to get surrounding sound. WARNING: i am not
responsible for any damage resulting from the use or misuse or
anything else related to this circuit.  Check that your - posts are
connected to ground and not the +. If it's the case reverse the
connections to the amplifier.

        It works surprisingly well considering the cost. Have fun!

  | Amplifier |
  | + -   - + |   You connect the front speakers as usual (dont mixup
   /| |   | |\    the polarities!)          _
  | |_|   |_| |   FLS: Front left speaker (/_\)
  | /_\   /_\ |   FRS: Front right speaker
  | FLS   FRS |   R: variable pot 50 ohm. 10 watts or more (depends on
  |_         _|      the amplifier)
  > |       | <
  ><'R     R'><    RLS: Rear left speaker (use a much smaller speaker
  >           <         for rear than front. 8 ohm also.)
  | RLS   RRS |    RRS: Rear right speaker (")
  |  _     _  |
  | \_/   \_/ |    the 2 - on front speakers are connected to the
  |_/ \_._/ \_|    ground of the amplifier internally, so you dont
   +   -|-   +     have to connect them.
        |_
        > |
        ><'R
        >          Here, you do need to connect the 2 - thru R to the
       _|_         amplifier ground.
        -  AMP GND

        Put the 3 potentiometer in a box so that you have the control
in one place, and use enough wire so you can move with it. You'll have
to experiment so that the R going to ground is a little higher than
the other 2 and once that adjusment made, the other two must be
adjusted so that the rear speakers are just adding a touch of depth
(if you turn them off, you notice that the surround is gone). Also, if
you have A-B speaker selection, plug the rear speakers on the + of B
instead of A, you will then be able to switch them off easily.  Of
course, when you will use the 3D audio, it will affect the signal, so
it's better to unplug the rear section.  But for your video, tape, CD
and regular GUS, you will still find it cool.

----------------------------------------------------------------------

14] What are *.PAT *.VOC *.WAV *.SND *.MOD *.669, and *.MID files, and
        how do I use them?

Written by: Matthew E. Bernold <MEB117@PSUVM.PSU.EDU>

        These are all different types of sound files.

        *.PAT files are GUS instrument files, or PATCH files.  These
files are what your GUS uses to recreate the various instruments it is
capable of playing.  Your .PAT files should be in your /ULTRASND/MIDI
and /ULTRASND/SBOS directories.

        *.VOC and *.WAV files are basic digital sound files with
headers.  The *.VOC files are used on the soundblaster, and the *.WAV
files are used by Microsoft Windows.  Players capable of using these
formats can read information on sampling rate, 8 or 16 bit, and
mono/stereo from the header of these files.  *.WAV files can be played
in MS Windows by many programs.  *.VOC files can be converted to *.WAV
by many different programs, including SOX which is available via FTP.
The latest version (7.0) has been ported to PC clones and can be found
on the GUS FTP sites.

        *.SND files are raw sound files with no header information.
This is the format currently used by the GUS.  This means that you
have to tell the player program about the sample, because the
information on how to play it is NOT in the file, like with the *.VOC
or *.WAV files.  You can play these files using PLAYFILE which came
with the GUS.

        *.MOD files are 4-voice 15 or 31 instrument music files which
originated on the Amiga.  They use 8-bit, 16kHz samples to produce the
instruments, and note information to play the songs.  *.MOD files are
similar to MIDI files, but they are a bit more flexible because you
can use any sample as an instrument (including voices and sound
effects) instead of relying on the MIDI synth's own built in
instruments.  You can play these files using GUSMOD which can be found
on epas.

        *.669 files are 8-voice music files.  I don't know much about
them, so maybe Tran (author of the GUS 669 player) can fill in this
area.  You can play these files using P669GU0 which can be found on
epas.

        *.MID files are MIDI files.  You can play these files with
PLAYMIDI that came with the Ultrasound package, or with MediaPlayer in
MS Windows.  You might have to create a *.cfg file for the MIDI file
if it was originally created for a synth that does not conform to the
GM Midi standard.

----------------------------------------------------------------------

15] What exactly is Wavetable Synthesis?

Written By: dionf@ERE.UMontreal.CA (Francois Dion)

        It is easier to find the Holy Grail than to find a text
describing precisely what synthesis method the GUS uses, so it's time
i take a shot at it. For this text i have searched thru ftp archives
troughout the world, have asked info from Ensoniq, Roland,
TurtleBeach, Advanced Gravis, Forte Creative Labs and i also took into
account the numerous comments, praise and flames i received to model
the text. Since this text is a result of a collective internet and
industry wisdom, flames will go the way of /dev/nul.  And please, read
the text carefully, because i have received some comments from people
who were thinking i wrote something when in fact i wrote the opposite
(particularly from non anglophones).

        You probably have heard about the GUS beeing a wavetable
soundcard.  I have received some comments that the GUS is not such a
thing, but since the industry uses this term (i.e. CL waveblaster,
GUS, TB multisound etc...), i am not in a position to create confusion
by renaming the technology.  Wavetable explains perfectly what it is.
A table containing a waveform.

        The GUS uses the third generation of wavetable synthesis, so
before i start explaining it, i'll talk about the first two
generations first.

        The first generation of wavetable synthesis was actually a
_digitally_ controlled _analog_ oscillator(s) where parameters
controlling the waveform were kept in memory. The curtis based synths
and some others are directly derived from this concept.

        The second generation of wavetable synthesis uses a digital
oscillator, with the waveform held in memory in it's basic form (one
period usually).  Parameters to alter the oscillator behaviour are
also in memory.  I use the general term "memory" instead of RAM,
because in some case it's actually ROM, FlashROM, PROM, EPROM,
switches, buffers etc... The Ensoniq chip found in the Macintosh Plus
is an example (8 bit, 4 oscillators, 4096 byte wavetable).

        The third generation of wavetable synthesis which can be found
in two flavors (RAM or ROM) is based on the second generation, but
uses bigger wavetables to hold the waveform (either in single period
or multi period format) including this time the attack and release. In
this section, i will focus only on the GUS implementation, which
basically encompass all other implementations.  Basically, what you
have are 32 oscillators which can do the exact same thing, and be
programmed separately and/or simultaneously. What the hardware can do
without the operating system is not too important here since we are
looking at what the GUS _can_presently_do_ (with modifications to the
OS, the GUS could do pretty much any synthesis method one can dream
up), not what it would have done if the OS wasn't available.  Of
course, more processing done in hardware means more CPU cycles left
for other things.

        So in the GUS, you have some RAM (up to 1Mb) that holds 1, 2,
3, etc, wavetables which consist of a sampled (or soft-synthesised)
waveform, some parameters and optionally a sampled attack and release.
The GF1 chip (an asic based on the Ensoniq DOC-II chip) will then
playback a waveform when triggered based on some parameters it is
given, and on others it will fetch from the wavetable. I dont know if
all parameters can be fetched from RAM by the GF1, nor if the GF1 can
fetch some instructions from RAM, but by using the current OS built in
the windows drivers or in the DOS library, this is what the GUS
_can_presently_use_ to synthesise music:

        - sampled or envelopped attack in 8/16 bit, signed/unsigned
                format *
        - sampled waveform (anything! a period, or a several seconds
                sample) *
        - sampled or envelopped release *

with:
        - velocity (volume) *
        - panning (balance) *
        - precise frequency playback rates (with frequency based
                antialiasing and oversampling) *
        - mixing of all the channels *

Up to here, it's sample playback. But there is more:

        - full vibrato (FM, depth, rate, sweep)
        - full tremolo (AM, depth, rate, sweep)
        - LFO (Low Frequency Oscillator) *
        - forward, reverse, dual direction looping or no looping *
        - the loop points can be anywhere (for sampled attack and release) *
                |-------|-------------|--------------|
                Start   Start loop    End loop     End
        - 6 point envelope
        - tuning *
        - fractional endpoint *
        - combination of oscillators (up to 4 if the GF1
                implementation is the same as Ensoniq) *
        - previous waveform usage *

And more recently:
        - 3D (focal point 3D positioning)

        ( "*" indicates that the operation is done in hardware. Some
others may be done in hardware but i have not done any tests or found
any technical information to confirm it. I also base 1 item on the DOC
II capability, which should be implemented in the GF1.)

        Also, reverb, flanger, phasing etc...  could be easily
implemente within the drivers. Presently it can be done with a little
work on the patches and/or midi timestamp (i have succesfully made
flanger and phasing).  Another thing that could be implemented is
dynamic patch loading since the card supports it (i have done it). You
can even get a distorted sound (ideal for guitars, vox, analog synths)
by simply changing the 2's complement flag (work best with
soft-synthesised patches).

        Last, it is far better to have a RAM wavetable synth than a
ROM one, since you can upload your samples.  Even sound canvas owners
(and other synths too) complain that their ROM based GS synth lacks
interesting drum and bass sounds, cannot play sound effects, and is
not usable for dance and techno.  Also you can have more space for
each samples, because you always have only the samples you need in
memory, so you can have better sampling rates and better waveforms.

----------------------------------------------------------------------

16] Is there a GUS device driver for Linux/BSD386/*IX?

        There is a group of people working on device drivers and C
libraries for Linux, BSD386, 386bsd, Minix, SysVR3/386, and whatever
other PC/UNIX flavors there are out there.

        I know there is at least a beta driver out for Linux.  If
anyone out there has more information on this, please mail it to
me.  I had some information from Hannu Solvanen (Forgive my spelling)
but I lost it.  If you're reading this, Hannu, please mail me that
info on your driver again.

----------------------------------------------------------------------

17] How do I get the GUS to work with OS/2?

        As of now, there is no OS/2 specific device driver for the
GUS.  According to Gravis, they are working directly with IBM to get
OS/2 drivers for the GUS written.  A specific release date has not
been announced.

        There are a few simple tricks to get the GUS to work with OS/2
to a small degree right now:

Written by: Thomas Wong <twong@civil.ubc.ca>

        As it is right now, what you'll have to do is use a 8 bit DMA
channel in your setup of the GUS to make it work under a DOS window
under OS/2.  If you have already installed/setup your GUS card, just
go into the c:\autoexec.bat file under OS/2 and manually change the
number in the environment variable.  So, for example, use DMA channel
#1.  By doing this, you can now use playmidi, 669 player, gusmod... a
number of GUS programs. But you still can't run playfile or SBOS (it
may crash).  In other words, you can use a play a list of midi, 669,
mod...etc files in a DOS window, but can't play games.  Gravis did say
they will come out with an OS/2 driver but no date is set.

----------------------------------------------------------------------

18] How do I go about programming the GUS?

        Gravis and Forte have released a very detailed SDK for the
GUS.  It includes source code, libraries, documentation, etc., etc,
and it's available on the FTP sites (see question #6).

        Also, there are two UltraDox files written by Phat Tran up for
FTP as well.  Read them carefully, learn to love them.

        (If you want to use the GUS with another OS besides MSDOS,
read questions #21 and #23.)

----------------------------------------------------------------------

19] What are the pinouts for the CD Audio IN on the GUS?

Written by: <grtorlba@seattleu.edu>

        About two days ago I posted requested some info on the 4-pin
CD audio pin on the GUS.  I never got a reply but I got the info by
downloading volume 1 of the digest.

        The pin info was:

        left ground ground right

        I've tried this pin assignment and it seems to work.  The
articles in the digest pointed out that they weren't certain of the
left-right assignment but the two pins in the middle are definitely
the grounds.

----------------------------------------------------------------------

20] I'm having trouble with... GENERAL TROUBLESHOOTING TIPS

Written by: john.smith@gravis.com (John Smith)

        It looks like a lot of the problems are incorrect
installations.

        Make sure that you put ALL the correct files in the
/ultrasnd/sbos directory and remove any old ones. Sbosdrv.exe,
Loadsbos.exe and Sboslib.sbs MUST all be from the same release
revision. They are NOT mixable. A lot of the problems you are seeing
could happen if the wrong driver is used with the new loader and patch
library.  To make sure you are using the correct files, delete ALL
files from /ultrasnd/sbos. Then unzip the new release into the sbos
directory. Then COPY sbosdrv.exe up to the /ultrasnd directory.  Then
COPY loadsbos.exe up to the /ultrasnd directory also.  Now pick either
sboslo.bat or sboshi.bat up to /ultrasnd/sbos.bat.  These two batch
files assume you are using emm386. If you are using another memory
manager (like qemm, 386max etc), use the appropriate command to load
it into high memory.  (NOTE: If you installed your software in some
other directory, substitute it in place of /ultrasnd).  ] Not all of
the tips below apply to all programs. This is just a brief summary of
some of the things we had to do to get some games running properly.

        1) Make sure the BLASTER environment string tracks our
ULTRASND string. Many games look at BLASTER to set up their stuff.
SBOS needs ULTRASND. If they are not the same, the game will be
looking one place and SBOS will using another. This is another reason
NOT to have an SB and GUS in the same system. Presumably, the SB would
want BLASTER set up for it and any game looking at it would not work
with SBOS. BLASTER is set up like this:

    BLASTER=A220 I5 D1 T1
              |    |  |  |
              |    |  |  -  Type of SB (1 = regular SB)
              |    |  ----- DMA channel (MUST be 1)
              |    -------- IRQ used.     (same as GUS midi irq)
              ------------- I/O base address

        This variable is set up by the GUS setup program.  It should
never need to be modified unless you modify ULTRASND by hand.

        For example, wolf3d looks at BLASTER to get its parameters.
Sound will NOT function if the IRQs are different, but it will detect
an Adlib.

        2) Make sure that SBOS is up and running BEFORE you install
your game. Some games configure themselves during their installation
procedure. If SBOS is not running, it will assume there is no sound
board present.

        3) Some games have a separate setup/configuration section.
Make sure you run this after you install the game OR change the
ULTRASND variable. They are usually called setup, install or config.
Look around for it. Some games also save the last configuration to use
the next time the game is run. This means that if it didn't detect the
card (because SBOS wasn't loaded), it will save that info and will
start up the NEXT time with sound disabled. You will have to manually
turn sound back on somehow.  See your games manual. For example,
Wolf-3d will do this.

        4) Some games need all available RAM to run.  Since SBOS
currently takes approximately 19K, it may not have enough to run. Some
games will shut off some of the sounds if RAM is short.  Check your
manual. It may also be necessary to load SBOS high to reclaim some of
the RAM.

        5) If you have poor performance with SBOS loaded, see if you
have an expanded memory manager running.  (qemm, 386max, emm386 etc)
There is a SEVERE performance penalty to be paid if you run with
these. Its a byproduct of your machine running in protected mode.
Usually, only games that use direct I/O (mod players for example) are
seriously effected by this. If you must have SBOS loaded high, then
you will have to live with this. It is possible to disable the virtual
DMA if you are using qemm. (NOVDS) Doing so should speed things up a
bit.

Comments on above paragraph by mike@batpad.org (Mike Batchelor)
]
]               This paragraph contains some errors, from where I sit.
]       You may disagree, but I offer my perspective anyway:
]
]               1.  Virtual 8086 mode entails no more than a 5%
]       performance penalty over real mode.  It does not matter which
]       memory manager you use, the degradation is dependent on the
]       CPU and the motherboard.  In any case, the penalty is hardly
]       what you might call SEVERE.
]
]               2.  QEMM's NOVDS parameter has NOTHING to do with
]       virtualization of the standard DMA channels.  There is no
]       switch to disable this feature of QEMM, DMA would not fuction
]       in V86 mode if the memory manager does not virtualize it.
]       They all do this, they all MUST do this.  NOVDS tells QEMM not
]       to support the Virtual DMA Specification, which has to do with
]       virtualizing non-standard DMA used by bus-mastering adapters
]       (usually SCSI host adapters, but can be network cards, etc.).
]       The VDS spec is a means by which these non-standard DMA
]       operations may be virtualized in V86 mode.  QEMM normally
]       virtualizes the DMA channels handled by the motherboard's DMA
]       controller.  So-called bus-mastering disk controllers do DMA
]       on their own, without help from the DMA controller, so the
]       normal way of virtualizing DMA will not work.  VDS is the
]       solution for this.  Adding NOVDS to the QEMM line will disable
]       support for ASPI4DOS.SYS, USPI24.SYS and other VDS-supporting
]       SCSI host adapter drivers.  This will prevent the user from
]       loading anything into mapped memory in the first megabyte
]       (High RAM) from the SCSI hard disk.
]
]               The usual way to improve DMA performance is to
]       increase QEMM's DMA buffer.  The default on ISA systems is
]       12K, and 64K on MCA systems.  It can be increased to 128K max.
]       DMA=nnn specifies how large the length of a single DMA
]       transfer can be, in nnn Kb.  QEMM should prompt you to
]       increase the DMA buffer if a program attempts to exceed the
]       capacity of the current buffer.  I have found that 64K is
]       plenty for all programs I have used with the GUS.

        6) It is possible for an application to detect the Adlib side
of the GUS without SBOS being loaded. It depends on the method it uses
to detect it. Obviously if that happens, the application will think it
has an Adlib, but nothing is going to work.

        7) Many games need to detect (and use) extended/expanded RAM
before some sounds will be activated (usually digitized stuff) Refer
to your manual for these kind of problems.  An SB will not operate
properly under these conditions either.  For example, Falcon III will
not play digitized sounds until EMS is set up properly.  SBOS has
nothing to do with this problem.

        8) Some games hard code their I/O address and/or irq
selections. Refer to your manual.  You will have to make the GUS'
selections match these. I believe some Sierra games do this.  Wing
Commander requires a base port of address of 220 for digital speech to
work.

        9) Unless you are POSITIVE that a particular game needs an
option, (-o1 -o2 etc) DON'T specify one, 99% of the games do NOT need
one. You may screw up the driver by specifying one that you don't
need.  You should unload and reload the driver before specifying an
option. Since it is possible to use more than one option, you may be
telling it conflicting things if you don't unload it.

        10) There are several new features in SBOS that you should be
aware of:

                a) SBOS reloads its patches before an application
        runs.  This should eliminate having to reload it between
        running windows or a native GUS application (GUSMOD Star Con
        II, playmidi etc) and a game that uses SBOS.

                b) You can change the vector that it uses for
        communicating between sbosdrv.exe and loadsbos.exe.  The
        option is -Cxx, where xx is the new software vector to use.
        This is specified to sbosdrv.  Currently, only 1 application
        is known to need this.  Netroom uses the default vector (7E)
        so sbosdrv thinks it is already loaded.  If you are using
        netroom, you MUST change the vector #.  Netroom is the only
        application that we know of that has this problem.  There may
        be others.  We don't know of ANY games that do.

                c) You can tell SBOS to leave line-in enabled by
        specifying a -L when SBOS is loaded. This can be useful if you
        want to monitor some other audio output source thru the GUS.

        11) The volume up and down keys (defaults are [ and ]) do not
work in all games. Any game that takes over the keyboard vectors will
disable this feature. You must use the -V option when loading sbos to
alter the volume for these games.  This option works like this: -vxx
where xx ranges from 0 to 31 (31 being max volume) Note: in SOME
versions prior to 1.4B2, hitting the volume keys would hang your
system. This has been fixed.

        12) Some games grab all possible SB irqs (2,5 and 7) when they
initialize to find what IRQ the SB is on.  If they do this with SBOS
and SBOS happens to have the UltraSound IRQ on one of the SB irqs, it
will not let SBOS get its irq. Make sure that you set the UltraSound
irq to one of the upper ones (11,12 or 15). Jill of the Jungle is an
example of a game that exhibits this problem.

        13) Now for some simple things to look for.

                a) Is board seated properly?
                b) Is DRAM in sockets correctly (bent pins etc)?
                c) Are stereo/speakers hooked up properly?
                d) Are you connected to the right outputs on GUS?
                   (Some Ultrasound boxes are labeled wrong ...)

                        TOP OF ULTRASOUND
                        =================

                        Amplified Out
                        Line Out

                        Joystick/Midi 15 pin connector

                        Microphone In
                        Line In

                        BOTTOM OF ULTRASOUND
                        ====================

                e) Do you have enough environment space for ULTRASND
                   and BLASTER variables?
                f) Did you set the volume too low?
                g) Is \ultrasnd in your path?
                h) Could you have gotten a bad download of new SBOS?

        14) Several people have complained about sbos loading VERY
slowly.  Is your joystick or MIDI plugged in?  Try unplugging it.  As
of now, we haven't been able to reproduce this problem.  It may be
related to installing the software incorrectly or a DMA conflict.

        15) If your joystick doesn't operate properly in a game, look
for these things.

                a) Has it been calibrated (see manual)
                b) Do you have 2 games ports in your system? (GUS and
                   another game port). If so, one MUST be disabled.
                c) DO you have a line like the following in your autoexec

                   joycomp 20

                   where 20 is the compensation factor determined thru
                   the calibration utility, ultrajoy.

        16) There are several things people have noticed that seem to
effect SBOS that need to be investigated. None of these have been
verified, but you should be aware of them and you might try
eliminating them as possible sources of your problem.

                a) Loading SBOS hi can cause some FM stuff to sound
                   'weird'.
                b) Using 'Stealth' mode on QEMM seems to have a
                   detrimental effect.
                c) Change sbos.bat file to use loadhi instead of lh if
                   using QEMM.
                d) Stacker seems to cause some people problems.  It
                   works OK for others.
                e) Order that TSR's are loaded may have an effect. Try
                   loading SBOS first, last etc.
                f) When using XWing make sure that you have at least
                   896K of EMS (not XMS) and 563K of conventional.  If
                   you are having problems with slowdowns try turning
                   off the music.

        17) The only other thing we can think of is a hardware problem
on your card. The diagnostics in the new setup program should be able
to isolate it.

        Granted, we are a bit biased, but we believe that you should
get SUPERB sound out of your GUS.  If you are getting less than
satisfactory results, there can only be a few explanations.

                a) in windows, make sure its in 'high fidelity' mode.
                b) Incorrect software installation.
                c) Incorrect hardware installation (IRQ,DMA etc)
                   (probably)
                4) Bad hardware.(PC or GUS)

----------------------------------------------------------------------

21] I can't seem to fit the new disks onto a floppy.

        First of all, the files need to go on to a HD 3.5" disk.

        Next, some of the disks were zip'ed a second time to include a
small README file (in other words, the .zip file you downloaded
contains two files: a README file, and another .zip file).  This would
have been a good idea, except the .zip file got bigger; too big for a
HD 3.5" disk.  So, you'll need to unzip the file, read the README, and
copy the new .zip file to a floppy.

----------------------------------------------------------------------

22] Why shouldn't I use the comp.sys.ibm.pc.soundcard.GUS newgroup?

        c.s.i.p.s.GUS wasn't created legally; ie: there was no formal
call for discussion, voting, etc., etc.  As such, many sites refuse to
carry the group.  Posts there get to few readers.

        If anyone wants to take the time and energy to go through the
steps needed to get a new group created the correct way, I'm sure all
the GUSers would be more than happy to move there.

        (USENET tip for newbies: Don't create a new group for every
new topic that comes along.  Find the group that your topic fits best
in, and use that.  If you don't like all the other posts in the group,
learn the magic incantations that go along with killfiles in your
newsreader.)

----------------------------------------------------------------------

23] What are "Miles Drivers", and how do I use them?

Written by: Matthew E. Bernold <MEB117@PSUVM.PSU.EDU>

        Miles drivers (also known as MIDPAK/DIGIPAK) are a set of
drivers that software companies to easily support many soundcards.
The game is programmed to use these drivers, and then any soundcard
with an appropriate driver will automatically be supported.  The Miles
drivers for the GUS can be found on the Epas archive site.  The
current version of these drivers is v.97beta (filename GUSAIL97.ZIP)

        There are three driver files and one TSR in the GUS Miles
Drivers.  The drivers are GF1MIDI.ADV, GF1DIGI.ADV, GF166.COM and the
TSR is ULTRAMID.EXE In order to use these drivers, you need to copy
them over existing sound drivers for another card.  These drivers
should have easily recognizable names like:

        (List taken from Monopoly Deluxe)

        SBDIG.ADV               Sound Blaster Digital
        SBFM.ADV                Sound Blaster FM Music
        SBPDIG.ADV              Sound Blaster Pro Digital
        SBP1FM.ADV              Sound Blaster Pro v1 Music
        SBP2FM.ADV              Sound Blaster Pro v2 Music (OPL3)
        MT32MPU.ADV             Roland MT32 Music
        PCSPKR.ADV              PC Speaker driver

        The above names are typical, but they may change.

        To get the game to work, you should do the following (This
example assumes that your Ultrasound directory is c:\ultrasnd and that
your miles drivers are in c:\ultrasnd\miles and your game is in the
directory c:\game):

        1) Change into your Game's directory

C:\>CD GAME

        NOTE: Any of the below steps MAY not be necessary, depending
on what your application uses.  If the app uses only Digital sound,
and no MIDI music, for example, you will not have to do step 3.

        2) Copy GF1DIGI.ADV over a Digital driver.  I would suggest
choosing the one that is most functional.  Choose the SBPro driver
over the SB one and you MIGHT get stereo (depending on what the game
does) and choose the PAS-16 driver (if one is present) and you MIGHT
get 16-bit sound if the game uses it.  We'll choose the SBPro driver.

C:\GAME>COPY C:\ULTRASND\MILES\GF1DIGI.ADV SBPDIG.ADV

        3) Copy GF1MIDI.ADV over a Music driver.  Here, I would
suggest that you try different ones and see which sounds best.
Sometimes the program plays a different version of the music depending
on your card.  For Terminator 2029, I found that the MT32 setting
sounds better, but the SCC-1 setting sounds more like the movie music,
even though it isn't as clear and nice sounding.  For this example,
we'll try the MT32 driver.

C:\GAME>COPY C:\ULTRASND\MILES\GF1MIDI.ADV MT32MPU.ADV

        4) Copy GF166.COM over the .COM file for the card you selected
above.  This should be fairly simple.  If you chose 2 different cards
as we did in this example, then copy the GF166.COM over the .COM file
for BOTH cards (just to be safe)

C:\GAME>COPY C:\ULTRASND\MILES\GF166.COM SBLASTER.COM

        (For this game [Monopoly Deluxe] there doesn't seem to be a
.COM file for the Roland MT32, so I didn't copy over it here)

        5) This step is MANDITORY.  Run the game's SETUP utility and
choose the cards you chose above.  In this example, we chose SBPro for
Digital, and MT32 for Music.  If the SETUP utility does NOT allow you
to choose two different cards, you must redo steps 2-4 patching only
ONE card's drivers.  Most programs now allow you to choose 2 cards,
however.

        6) Run ULTRAMID.EXE.  This needs to be done before you run any
games that use the Miles Drivers.  There should be instructions on
different command line options for ULTRAMID in the readme file that
comes with the archive.  Realize that ULTRAMID takes around 50k right
now, so you may have to load it high to get enough conventional memory
to run your game.

        That's it!  Your game SHOULD now have full GUS support.  If it
doesn't, here are a few hints on how to possibly fix things:

        1) Try copying the GUS's *.ADV drivers over ALL the *.ADV
drivers in the game's directory.  According to the README file, a good
indication of what a driver is is that if the driver is <10k then it
is a Digital driver, and should be replaced with GUSDIGI.ADV, if
larger, then it is a MIDI driver, and should be replaced with
GUSMIDI.ADV.  The name should also give you a clue as to what to
replace it with.

                a) MIDI drivers: MT32, SCC1, ADLIB (Usually), Anything
        with 'FM' like SBFM or SBP2FM

                b) Digital drivers: SBDIG, SBPDIG, PASDIG, PCSPKR.
        Usually these drivers will have 'DIG' in them, but not
        necessarily.

        2) Try copying the GF166.COM file over ALL the .COM files in
the directory.  BE CAREFUL WHEN YOU DO THIS!  Some games have .COM
files other than the music drivers that should NOT be copied over.
Most of the time, the .COM files you are looking for will be small,
and will usually have a recognizable name, although this is not always
the case.

        3) Some games on the list in the readme file from the archive
may use the Miles drivers, but NOT have *.ADV files anywhere.  From
what I understand, the Miles drivers will have the word "Miles"
embedded in them somewhere near the beginning.  Look through some of
the smaller files in the directory with an editor and see if you can
find the word "Miles" somewhere.  Some games rename the Miles drivers
to *.DRV.

        Good luck, and happy GUSing.

----------------------------------------------------------------------


?????????????????????????????????????????????????????????????????????????????
? GUSDOC ?
??????????





                               THE OFFICAL



                GRAVIS ULTRASOUND PROGRAMMERS ENCYCLOPEDIA

                               ( G.U.P.E )



                                 v 0.1


                           Written by Mark Dixon.






  -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

 INTRODUCTION
 ~~~~~~~~~~~~
    The Gravis Ultrasound is by far the best & easiest sound card to
  program. Why? Because the card does all the hard stuff for you, leaving
  you and the CPU to do other things! This reference will document some
  (but not all) of the Gravis Ultrasound's hardware functions, allowing
  you to play music & sound effects on your GUS.

    We will not be going into great detail as to the theory behind
  everything - if you want to get technical information then read the
  GUS SDK. We will be merely providing you with the routines necessary
  to play samples on the GUS, and a basic explanation of how they work.
  
    This document will NOT go into DMA transfer or MIDI specifications.
  If someone knows something about them, and would like to write some
  info on them, we would appreciate it very much.

    All source code is in Pascal (tested under Turbo Pascal v7.0, but
  should work with TP 6.0 and possibly older versions). This document
  will assume reasonable knowledge of programming, and some knowledge of
  soundcards & music.


 INITIALISATION & AUTODETECTION
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   Since we are not using DMA, we only need to find the GUS's I/O port,
 which can be done from the DOS environment space, or preferably from a
 routine that will scan all possible I/O ports until it finds a GUS.

   The theory behind the detection routine is to store some values into
 GUS memory, and then read them back. If we have the I/O port correct,
 we will read back exactly what we wrote. So first, we need a routine
 that will write data to the memory of the GUS :


  Function  GUSPeek(Loc : Longint) : Byte;

  { Read a value from GUS memory }

  Var
    B : Byte;
    AddLo : Word;
    AddHi : Byte;
  Begin
    AddLo := Loc AND $FFFF;
    AddHi := LongInt(Loc AND $FF0000) SHR 16;

    Port [Base+$103] := $43;
    Portw[Base+$104] := AddLo;
    Port [Base+$103] := $44;
    Port [Base+$105] := AddHi;

    B := Port[Base+$107];
    GUSPeek := B;
  End;


  Procedure GUSPoke(Loc : Longint; B : Byte);

  { Write a value into GUS memory }

  Var
    AddLo : Word;
    AddHi : Byte;
  Begin
    AddLo := Loc AND $FFFF;
    AddHi := LongInt(Loc AND $FF0000) SHR 16;
    Port [Base+$103] := $43;
    Portw[Base+$104] := AddLo;
    Port [Base+$103] := $44;
    Port [Base+$105] := AddHi;
    Port [Base+$107] := B;
  End;


   Since the GUS can have up to 1meg of memory, we need to use a 32bit
 word to address all possible memory locations. However, the hardware of
 the GUS will only accept a 24bit word, which means we have to change
 the 32bit address into a 24bit address. The first two lines of each
 procedure does exactly that.

   The rest of the procedures simply send commands and data out through
 the GUS I/O port defined by the variable BASE (A word). So to test for
 the presence of the GUS, we simply write a routine to read/write memory
 for all possible values of BASE :


  Function GUSProbe : Boolean;

  { Returns TRUE if there is a GUS at I/O address BASE }

  Var
    B : Byte;
  Begin
    Port [Base+$103] := $4C;
    Port [Base+$105] := 0;
    GUSDelay;
    GUSDelay;
    Port [Base+$103] := $4C;
    Port [Base+$105] := 1;
    GUSPoke(0, $AA);
    GUSPoke($100, $55);
    B := GUSPeek(0);
    If B = $AA then GUSProbe := True else GUSProbe := False;
  End;


  Procedure GUSFind;

  { Search all possible I/O addresses for the GUS }

  Var
    I : Word;
  Begin
    for I := 1 to 8 do
    Begin
      Base := $200 + I*$10;
      If GUSProbe then I := 8;
    End;
    If Base < $280 then
      Write('Found your GUS at ', Base, ' ');
  End;


   The above routines will obviously need to be customised for your own
 use - for example, setting a boolean flag to TRUE if you find a GUS,
 rather than just displaying a message.

   It is also a good idea to find out exactly how much RAM is on the
 GUS, and this can be done in a similar process to the above routine.
 Since the memory can either be 256k, 512k, 768k or 1024k, all we have
 to do is to read/write values on the boundaries of these memory
 addresses. If we read the same value as we wrote, then we know exactly
 how much memory is available.


  Function  GUSFindMem : Longint;

  { Returns how much RAM is available on the GUS }

  Var
    I : Longint;
    B : Byte;
  Begin
    GUSPoke($40000, $AA);
    If GUSPeek($40000) <> $AA then I := $3FFFF
      else
    Begin
      GUSPoke($80000, $AA);
      If GUSPeek($80000) <> $AA then I := $8FFFF
        else
      Begin
        GUSPoke($C0000, $AA);
        If GUSPeek($C0000) <> $AA then I := $CFFFF
          else I := $FFFFF;
      End;
    End;
    GUSFindMem := I;
  End;


   Now that we know where the GUS is, and how much memory it has, we
 need to initialise it for output. Unfortunately, the below routine is
 slightly buggy. If you run certain programs (I discovered this after
 running Second Reality demo) that use the GUS, and then your program
 using this init routine, it will not initialise the GUS correctly.

   It appears that I am not doing everything that is necessary to
 initialise the GUS. However, I managed to correct the problem by
 either re-booting (not a brilliant solution) or running Dual Module
 Player, which seems to initialise it properly. If someone knows where
 i'm going wrong, please say so!

   Anyway, the following routine should be called after you have found
 the GUS, and before you start doing anything else with the GUS.



  Procedure GUSDelay; Assembler;

  { Pause for approx. 7 cycles. }

  ASM
    mov   dx, 0300h
    in    al, dx
    in    al, dx
    in    al, dx
    in    al, dx
    in    al, dx
    in    al, dx
    in    al, dx
  End;

 
  Procedure GUSReset;

  { An incomplete routine to initialise the GUS for output. }

  Begin
    port [Base+$103]   := $4C;
    port [Base+$105] := 1;
    GUSDelay;
    port [Base+$103]   := $4C;
    port [Base+$105] := 7;
    port [Base+$103]   := $0E;
    port [Base+$105] := (14 OR $0C0);
  End;


   Now you have all the routine necessary to find and initialise the
 GUS, let's see just what we can get the GUS to do!


 MAKING SOUNDS
 ~~~~~~~~~~~~~
   The GUS is unique in that it allows you to store the data to be
 played in it's onboard DRAM. To play the sample, you then tell it what
 frequency to play it at, what volume and pan position, and which sample
 to play. The GUS will then do everything in the background, it will
 interpolate the data to give an effective 44khz (or less, depending on
 how many active voices) sample. This means that an 8khz sample will
 sound better on the GUS than most other cards, since the GUS will play
 it at 44khz!

   The GUS also has 32 seperate digital channels (that are mixed by a
 processor on the GUS) which all have their own individual samples,
 frequencies, volumes and panning positions. For some reason, however,
 the GUS can only maintain 44khz output with 16 channels - the more
 channels, the lower the playback rate (which basically means, lower
 quality). If you are using all 32 channels (unlikely), then playback is
 reduced to 22khz.

   Since you allready know how to store samples in the GUS dram (simply
 use the GUSPoke routine to store the bytes) we will now look at various
 routines to change the way the gus plays a sample. The first routine we
 will look at will set the volume of an individual channel :

  Procedure GUSSetVolume( Voi : Byte; Vol : Word);

  { Set the volume of channel VOI to Vol, a 16bit logarithmic scale
    volume value -  0 is off, $ffff is full volume, $e0000 is half
    volume, etc }

  Begin
    Port [Base+$102] := Voi;
    Port [Base+$102] := Voi;
    Port [Base+$102] := Voi;
    Port [Base+$103] := 9;
    Portw[Base+$104] := Vol;  { 0-0ffffh, log scale not linear }
  End;

   The volume (and pan position & frequency) can be changed at ANY time
 regardless of weather the GUS is allready playing the sample or not.
 This means that to fade out a sample, you simply make several calls to
 the GUSSetVolume routine with exponentially (to account for the
 logarithmic scale) decreasing values.

   The next two routines will set the pan position (from 0 to 15, 0
   being left, 15 right and 7 middle) and the frequency respectively :

  Procedure GUSSetBalance( V, B : Byte);
  Begin
    Port [Base+$102] := V;
    Port [Base+$102] := V;
    Port [Base+$102] := V;
    Port [Base+$103] := $C;
    Port [Base+$105] := B;
  End;

  Procedure GUSSetFreq( V : Byte; F : Word);
  Begin
    Port [Base+$102] := V;
    Port [Base+$102] := V;
    Port [Base+$102] := V;
    Port [Base+$103] := 1;
    Portw[Base+$104] := F;
  End;

   I'm not sure the the value F in the set frequency procedure. The GUS
 SDK claims that it is the exact frequency at which the sample should be
 played.

   When playing a sample, it is necessary to set the volume, position
 and frequency BEFORE playing the sample. In order to start playing a
 sample, you need to tell the GUS where abouts in memory the sample is
 stored, and how big the sample is  :

 
  Procedure GUSPlayVoice( V, Mode : Byte;VBegin, VStart, VEnd : Longint);

  { This routine tells the GUS to play a sample commencing at VBegin,
    starting at location VStart, and stopping at VEnd }

  Var
    GUS_Register : Word;
  Begin
    Port [Base+$102] := V;
    Port [Base+$102] := V;
    Port [Base+$103] := $0A;
    Portw[Base+$104] := (VBegin SHR 7) AND 8191;
    Port [Base+$103] := $0B;
    Portw[Base+$104] := (VBegin AND $127) SHL 8;
    Port [Base+$103] := $02;
    Portw[Base+$104] := (VStart SHR 7) AND 8191;
    Port [Base+$103] := $03;
    Portw[Base+$104] := (VStart AND $127) SHL 8;
    Port [Base+$103] := $04;
    Portw[Base+$104] := ((VEnd)   SHR 7) AND 8191;
    Port [Base+$103] := $05;
    Portw[Base+$104] := ((VEnd)   AND $127) SHL 8;
    Port [Base+$103] := $0;
    Port [Base+$105] := Mode;

    { The below part isn't mentioned as necessary, but the card won't
      play anything without it! }

    Port[Base] := 1;
    Port[Base+$103] := $4C;
    Port[Base+$105] := 3;
  end;

   There are a few important things to note about this routine. Firstly,
 the value VEnd refers to the location in memory, not the length of the
 sample. So if the sample commenced at location 1000, and was 5000 bytes
 long, the VEnd would be 6000 if you wanted the sample to play to the
 end. VBegin and VStart are two weird values, one of them defines the
 start of the sample, and the other defines where abouts to actually
 start playing. I'm not sure why both are needed, since I have allways
 set them to the same value.

   Now that the gus is buisy playing a sample, the CPU is totally free
 to be doing other things. We might, for example, want to spy on the gus
 and see where it is currently up to in playing the sample :

  Function VoicePos( V : Byte) : Longint;
  Var
    P : Longint;
    Temp0, Temp1 : Word;
  Begin
    Port [Base+$102] := V;
    Port [Base+$102] := V;
    Port [Base+$102] := V;
    Port [Base+$103] := $8A;
    Temp0 := Portw[Base+$104];
    Port [Base+$103] := $8B;
    Temp1 := Portw[Base+$104];
    VoicePos := (Temp0 SHL 7)+ (Temp1 SHR 8);
  End;

   This routine will return the memory location that the channel V is
 currently playing. If the GUS has reached the end of the sample, then
 the returned value will be VEnd. If you want to see what BYTE value is
 currently being played (for visual output of the sample's waveform),
 then you simply PEEK the location pointed to by this routine.

   Finally, we might want to stop playing the sample before it has
 reached it's end - the following routine will halt the playback on
 channel V.


  Procedure GUSStopVoice( V : Byte);
  Var
    Temp : Byte;
  Begin
    Port [Base+$102] := V;
    Port [Base+$102] := V;
    Port [Base+$102] := V;
    Port [Base+$103] := $80;
    Temp := Port[Base+$105];
    Port [Base+$103] := 0;
    Port [Base+$105] := (Temp AND $df) OR 3;
    GUSDelay;
    Port [Base+$103] := 0;
    Port [Base+$105] := (Temp AND $df) OR 3;
  End;


 SPECIAL EFFECTS
 ~~~~~~~~~~~~~~~
   There are a few extra features of the GUS that are worthy of mention,
 the main one being hardware controlled sample looping. The GUS has a
 control byte for each of the 32 channels. This control byte consists of
 8 flags that effect the way the sample is played, as follows :
  ( The table is taken directly from the GUS Software Developers Kit )

           =================================
           | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
           =================================
             |   |   |   |   |   |   |   |
             |   |   |   |   |   |   |   +---- Voice Stopped
             |   |   |   |   |   |   +-------- Stop Voice
             |   |   |   |   |   +------------ 16 bit data
             |   |   |   |   +---------------- Loop enable
             |   |   |   +-------------------- Bi-directional loop enable
             |   |   +------------------------ Wave table IRQ
             |   +---------------------------- Direction of movement
             +-------------------------------- IRQ pending
        (*)Bit 0 = 1 : Voice is stopped. This gets set by hitting the end
                   address (not looping) or by setting bit 1 in this reg.
           Bit 1 = 1 : Stop Voice. Manually force voice to stop.
           Bit 2 = 1 : 16 bit wave data, 0 = 8 bit data
           Bit 3 = 1 : Loop to begin address when it hits the end address.
           Bit 4 = 1 : Bi-directional looping enabled
           Bit 5 = 1 : Enable wavetable IRQ. Generate an irq when the voice
                       hits the end address. Will generate irq even if looping
                       is enabled.
        (*)Bit 6 = 1 - Decreasing addresses, 0 = increasing addresses. It is
                       self-modifying because it might shift directions when
                       it hits one of the loop boundaries and looping is enabled.
        (*)Bit 7 = 1 - Wavetable IRQ pending. If IRQ's are enabled and
                       looping is NOT enabled, an IRQ will be constantly
                       generated until voice is stopped. This means that
                       you may get more than 1 IRQ if it isn't handled
                       properly.


  Procedure GUSVoiceControl( V, B : Byte);
  Begin
    Port [Base+$102] := V;
    Port [Base+$102] := V;
    Port [Base+$103] := $0;
    Port [Base+$105] := B;
  End;


   The above routine will set the Voice Control byte for the channel
 defined in V. For example, if you want channel 1 to play the sample in
 a continuous loop, you would use the procedure like this :

    GUSVoiceControl( 1, $F );  { Bit 3 ON = $F }


 CONCLUSION
 ~~~~~~~~~~

   The above routines are all that is necessary to get the GUS to start
 playing music. To prove this, I have included my 669 player & source
 code in the package as a practical example. The GUSUnit contains all
 the routines discussed above. I won't go into the theory of the 669
 player, but it is a good starting point if you want to learn about
 modplayers. The player is contained within the archive 669UNIT.ARJ



?????????????????????????????????????????????????????????????????????????????
? README ?
??????????


  GUS669 Unit  v0.2b
  Copyright 1994 Mark Dixon.
  (aka C.D. of Silicon Logic)


  LEGAL STUFF
  ~~~~~~~~~~~
  I'd like to avoid this, but it has to be done. Basically, if anything
  in this archive causes any kind of damage, I cannot be held
  responsable - USE AT YOUR OWN RISK.

  In adition, since I spent long hours working on this project, and
  attempting to decode the GUS SDK, I would appreciate it if people
  didn't rip off my work. Give me credit for what I have done, and if
  your planning to use my routines for commercial purposes, talk to me
  first, or you might find yourself on the wrong side of a legal battle.
  (Hey, let's sound tough while i'm at it, I have lawyer's in the
  family, so it's not gonna cost me much to sue someone. And don't
  criticise my spelling! :)



  BORING STUFF
  ~~~~~~~~~~~~
  Well, if your the sort of person who likes to ignore all the rubishy
  bits that go into a README text file, then you'd better stop now and
  go and try out the source code!

  Basically, this readme isn't going to say much more than what the
  source code is, and then go dribling on for five pages about
  absolutely nothing.


  SOURCE CODE! DID SOMEONE SAY - SOURCE CODE!! - ????
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Yes, that's right, free with every download of this wonderful archive
  comes the complete Pascal source code to a 669 module player for the
  GUS. I'd have included my MOD player, but I haven't been able to get
  all the MOD commands working, so you'll just have to make do with a
  669 player :)

  Feel free to make use of this source code for any non-commercial
  purposes you might be able to think of - and mention my name while
  your at it! Since the source code is here, people are bound to modify
  it for their personal uses. If you do this, I would very much like to
  see your modifications - so that I can include them in the next
  release of the player.


  Well, I don't want to bore you anymore, and it's getting late (not!)
  so i'd better let you go and play around with the source code :)


  SILICON LOGIC
  ~~~~~~~~~~~~~
  What ever happened to Silicon Logic? Well, after being killed off over
  in Perth, a major revival is underway here in Canberra, with a more
  commercial view - more on that later.

  For those of you who have never heard of Silicon Logic, then you're
  either not Australian, or not into the ausie demo scene. But then,
  that covers about 99.999999999999% of the world population :)


  GREETINGS
  ~~~~~~~~~
  I've allways wanted to dribble some thanks, so here goes.

   Thanks go to...

    Darren Lyon    - Who got me into this programming lark in the first
                     place. Finally wrote myself a mod player :)
    Tran           - Your source code really helped!
    Kitsune        - Love those mods, keep up the good work!

    ... and Advanced Gravis, for making the best sound card ever.

   Greetings to...

    FiRE members   - I'll probably never join you guys, but good luck
                     anyway!
    UNiQUE         - How's the board going?
    CRaSH          - Still ripping other peoples source code?
    Old SL members - Thanks for the support, good luck with your new
                     group!
    Oliver White   - G'day... just thought i'd say hi, since you so
                     kindly beta tested the player for me.
    Murray Head    - Rick Price sux! :-) SoundBlaster sux too! :-)
    Perth people   - I'm coming back... someday!


    THE PICK / MINNOW   -  Hey, give me a call sometime, long time no
                           talk...



  INTERESTED IN A DEMO GROUP IN CANBERRA?
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  If there is anyone interested in joining a demo / coding group in
  Canberra (ACT), then drop me a line.



?????????????????????????????????????????????????????????????????????????????
? GUSUNIT.PAS?
??????????????

Unit  GUSUnit;

{
  GUS DigiUnit  v1.0
  Copyright 1994 Mark Dixon.

  This product is "Learnware".

  All contents of this archive, including source and executables, are the
  intellectual property of the author, Mark Dixon. Use of this product for
  commercial programs, or commercial gain in ANY way, is illegal. Private
  use, or non-commercial use (such as demos, PD games, etc) is allowed,
  provided you give credit to the author for these routines.

  Feel free to make any modifications to these routines, but I would
  appreciate it if you sent me these modifications, so that I can include
  them in the next version of the Gus669 Unit.

  If you wish to use these routines for commercial purposes, then you will
  need a special agreement. Please contact me, Mark Dixon, and we can work
  something out.

  What's "Learnware"? Well, I think I just made it up actually. What i'm
  getting at is that the source code is provided for LEARNING purposes only.
  I'd get really angry if someone ripped off my work and tried to make out
  that they wrote a mod player.

  As of this release (Gus699 Unit), the Gus DigiUnit has moved to version
  1.0, and left the beta stage. I feel these routines are fairly sound,
  and I haven't made any changes to them in weeks.


  Notice the complete absence of comments here? Well, that's partially
  the fault of Gravis and their SDK, since it was so hard to follow, I
  was more worried about getting it working than commenting it. No offense
  to Gravis though, since they created this wonderful card! :-) It helps
  a lot if you have the SDK as a reference when you read this code,
  otherwise you might as well not bother reading it.

}



INTERFACE

Procedure GUSPoke(Loc : Longint; B : Byte);
Function  GUSPeek(Loc : Longint) : Byte;
Procedure GUSSetFreq( V : Byte; F : Word);
Procedure GUSSetBalance( V, B : Byte);
Procedure GUSSetVolume( Voi : Byte; Vol : Word);
Procedure GUSPlayVoice( V, Mode : Byte;VBegin, VStart, VEnd : Longint);
Procedure GUSVoiceControl( V, B : Byte);
Procedure GUSReset;
Function VoicePos( V : Byte) : Longint;

Const
  Base : Word = $200;
  Mode : Byte = 0;

IMPLEMENTATION


Uses Crt;

Function Hex( W : Word) : String;
Var
  I, J : Word;
  S : String;
  C : Char;
Const
  H : Array[0..15] of Char = ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
Begin
  S := '';
  S := S + H[(W DIV $1000) MOD 16];
  S := S + H[(W DIV $100 ) MOD 16];
  S := S + H[(W DIV $10  ) MOD 16];
  S := S + H[(W DIV $1   ) MOD 16];
  Hex := S+'h';
End;


Procedure GUSDelay; Assembler;
ASM
  mov   dx, 0300h
  in    al, dx
  in    al, dx
  in    al, dx
  in    al, dx
  in    al, dx
  in    al, dx
  in    al, dx
End;



Function VoicePos( V : Byte) : Longint;
Var
  P : Longint;
  I, Temp0, Temp1 : Word;
Begin
  Port [Base+$102] := V;
  Port [Base+$103] := $8A;
  Temp0 := Portw[Base+$104];
  Port [Base+$103] := $8B;
  Temp1 := Portw[Base+$104];
  VoicePos := (Temp0 SHL 7)+ (Temp1 SHR 8);
  For I := 1 to 10 do GusDelay;
End;


Function  GUSPeek(Loc : Longint) : Byte;
Var
  B : Byte;
  AddLo : Word;
  AddHi : Byte;
Begin
  AddLo := Loc AND $FFFF;
  AddHi := LongInt(Loc AND $FF0000) SHR 16;

  Port [Base+$103] := $43;
  Portw[Base+$104] := AddLo;
  Port [Base+$103] := $44;
  Port [Base+$105] := AddHi;

  B := Port[Base+$107];
  GUSPeek := B;
End;


Procedure GUSPoke(Loc : Longint; B : Byte);
Var
  AddLo : Word;
  AddHi : Byte;
Begin
  AddLo := Loc AND $FFFF;
  AddHi := LongInt(Loc AND $FF0000) SHR 16;
{  Write('POKE  HI :', AddHi:5, '  LO : ', AddLo:5, '    ');}
  Port [Base+$103] := $43;
  Portw[Base+$104] := AddLo;
  Port [Base+$103] := $44;
  Port [Base+$105] := AddHi;
  Port [Base+$107] := B;
{  Writeln(B:3);}
End;


Function GUSProbe : Boolean;
Var
  B : Byte;
Begin
  Port [Base+$103] := $4C;
  Port [Base+$105] := 0;
  GUSDelay;
  GUSDelay;
  Port [Base+$103] := $4C;
  Port [Base+$105] := 1;
  GUSPoke(0, $AA);
  GUSPoke($100, $55);
  B := GUSPeek(0);
{  Port [Base+$103] := $4C;
  Port [Base+$105] := 0;}
  { Above bit disabled since it appears to prevent the GUS from accessing
    it's memory correctly.. in some bizare way.... }

  If B = $AA then GUSProbe := True else GUSProbe := False;
End;


Procedure GUSFind;
Var
  I : Word;
Begin
  for I := 1 to 8 do
  Begin
    Base := $200 + I*$10;
    If GUSProbe then I := 8;
  End;
  If Base < $280 then
    Write('Found your GUS at ', Hex(Base), ' ');
End;


Function  GUSFindMem : Longint;
{ Returns how much RAM is available on the GUS }
Var
  I : Longint;
  B : Byte;
Begin
  GUSPoke($40000, $AA);
  If GUSPeek($40000) <> $AA then I := $3FFFF
    else
  Begin
    GUSPoke($80000, $AA);
    If GUSPeek($80000) <> $AA then I := $8FFFF
      else
    Begin
      GUSPoke($C0000, $AA);
      If GUSPeek($C0000) <> $AA then I := $CFFFF
        else I := $FFFFF;
    End;
  End;
  GUSFindMem := I;
End;


Procedure GUSSetFreq( V : Byte; F : Word);
Begin
  Port [Base+$102] := V;
  Port [Base+$102] := V;
  Port [Base+$102] := V;
  Port [Base+$103] := 1;
  Portw[Base+$104] := (F { DIV 19}); { actual frequency / 19.0579083837 }
End;

Procedure GUSVoiceControl( V, B : Byte);
Begin
  Port [Base+$102] := V;
  Port [Base+$102] := V;
  Port [Base+$103] := $0;
  Port [Base+$105] := B;
End;



Procedure GUSSetBalance( V, B : Byte);
Begin
  Port [Base+$102] := V;
  Port [Base+$102] := V;
  Port [Base+$102] := V;
  Port [Base+$103] := $C;
  Port [Base+$105] := B;
End;


Procedure GUSSetVolume( Voi : Byte; Vol : Word);
Begin
  Port [Base+$102] := Voi;
  Port [Base+$102] := Voi;
  Port [Base+$102] := Voi;
  Port [Base+$103] := 9;
  Portw[Base+$104] := Vol;  { 0-0ffffh, log ... not linear }
End;


Procedure GUSSetLoopMode( V : Byte);
Var
  Temp : Byte;
Begin
  Port [Base+$102] := V;
  Port [Base+$102] := V;
  Port [Base+$102] := V;
  Port [Base+$103] := $80;
  Temp := Port[Base+$105];
  Port [Base+$103] := 0;
  Port [Base+$105] := (Temp AND $E7) OR Mode;
End;


Procedure GUSStopVoice( V : Byte);
Var
  Temp : Byte;
Begin
  Port [Base+$102] := V;
  Port [Base+$102] := V;
  Port [Base+$102] := V;
  Port [Base+$103] := $80;
  Temp := Port[Base+$105];
  Port [Base+$103] := 0;
  Port [Base+$105] := (Temp AND $df) OR 3;
  GUSDelay;
  Port [Base+$103] := 0;
  Port [Base+$105] := (Temp AND $df) OR 3;
End;


Procedure GUSPlayVoice( V, Mode : Byte;VBegin, VStart, VEnd : Longint);
Var
  GUS_Register : Word;
Begin
  Port [Base+$102] := V;
  Port [Base+$102] := V;
  Port [Base+$103] := $0A;
  Portw[Base+$104] := (VBegin SHR 7) AND 8191;
  Port [Base+$103] := $0B;
  Portw[Base+$104] := (VBegin AND $127) SHL 8;
  Port [Base+$103] := $02;
  Portw[Base+$104] := (VStart SHR 7) AND 8191;
  Port [Base+$103] := $03;
  Portw[Base+$104] := (VStart AND $127) SHL 8;
  Port [Base+$103] := $04;
  Portw[Base+$104] := ((VEnd)   SHR 7) AND 8191;
  Port [Base+$103] := $05;
  Portw[Base+$104] := ((VEnd)   AND $127) SHL 8;
  Port [Base+$103] := $0;
  Port [Base+$105] := Mode;

  { The below part isn't mentioned as necessary, but the card won't
    play anything without it! }

  Port[Base] := 1;
  Port[Base+$103] := $4C;
  Port[Base+$105] := 3;

end;


Procedure GUSReset;
Begin
  port [Base+$103]   := $4C;
  port [Base+$105] := 1;
  GUSDelay;
  port [Base+$103]   := $4C;
  port [Base+$105] := 7;
  port [Base+$103]   := $0E;
  port [Base+$105] := (14 OR $0C0);
End;



Var
  I : Longint;
  F : File;
  Buf : Array[1..20000] of Byte;
  S : Word;


Begin
  Clrscr;
  Writeln('GUS DigiUnit V1.0');
  Writeln('Copyright 1994 Mark Dixon.');
  Writeln;
  GUSFind;
  Writeln('with ', GUSFindMem, ' bytes onboard.');
  Writeln;
  GUSReset;
End.


?????????????????????????????????????????????????????????????????????????????
? GUS669.PAS ?
??????????????

UNIT Gus669;

{
  GUS669 Unit  v0.2b
  Copyright 1994 Mark Dixon.

  This product is "Learnware".

  All contents of this archive, including source and executables, are the
  intellectual property of the author, Mark Dixon. Use of this product for
  commercial programs, or commercial gain in ANY way, is illegal. Private
  use, or non-commercial use (such as demos, PD games, etc) is allowed,
  provided you give credit to the author for these routines.

  Feel free to make any modifications to these routines, but I would
  appreciate it if you sent me these modifications, so that I can include
  them in the next version of the Gus669 Unit.

  If you wish to use these routines for commercial purposes, then you will
  need a special agreement. Please contact me, Mark Dixon, and we can work
  something out.

  What's "Learnware"? Well, I think I just made it up actually. What i'm
  getting at is that the source code is provided for LEARNING purposes only.
  I'd get really angry if someone ripped off my work and tried to make out
  that they wrote a mod player.

  Beta version? Yes, since the product is still slightly unstable, I feel
  it is right to keep it under beta status until I find and fix a few
  bugs.

  FEATURES
    - Only works with the GUS!
    - 8 channel, 669 music format.
    - That's about it really.
    - Oh, 100% Pascal high level source code = NO ASSEMBLER!
      (So if you want to learn about how to write your own MOD player, this
       should make it easier for you)
    - Tested & compiled with Turbo Pascal v7.0

  BUGS
    - Not yet, give me a chance!
      (If you find any, I would very much appreciate it if you could take
       the time to notify me)
    - Doesn't sound right with some modules, advice anyone??
    - Could do with some better I/O handling routines when loading the
      669 to give better feedback to the user about what went wrong
      if the module didn't load.


 You can contact me at any of the following :

 FidoNet  : Mark Dixon  3:620/243
 ItnerNet : markd@cairo.anu.edu.au         ( prefered )
            d9404616@karajan.anu.edu.au    ( might not work for mail :) )
            sdixonmj@cc.curtin.edu.au      ( Don't use this one often )
            sdixonmj01@cc.curtin.edu.au    ( Might not exist any more,
                                             that's how often it's used! )
            I collect internet accounts.... :)

 If you happen to live in the Australian Capital Territory, you can
 call me on  231-2000, but at respectable hours please.


 "Want more comments? Write em!"
 Sorry, I just had to quote that. I'm not in the mood for writing lots
 of comments just yet. The main reason for writing it in Pascal is so
 that it would be easy to understand. Comments may (or may not) come later
 on.

 Okay, enough of me dribbling, here's the source your after!

}




Interface

Procedure Load669(N : String);
Procedure PlayMusic;
Procedure StopMusic;

Type
  { This is so that we can keep a record of what each channel is
    currently doing, so that we can inc/dec the Frequency or volume,
    or pan left/right, etc }
  Channel_Type    = Record
                      Vol : Word;
                      Freq : Word;
                      Pan : Byte;
                    End;

Var
  Channels : Array[1..8] of Channel_Type;
  Flags : Array[0..15] of Byte;
  { Programmer flags. This will be explained when it is fully implemented. }

Const
  Loaded : Boolean = False;    { Is a module loaded? }
  Playing : Boolean = False;   { Is a module playing? }
  WaitState : Boolean = False; { Set to TRUE whenever a new note is played }
                               { Helpful for timing in with the player }


Const
  NumChannels = 8;

  { Thanks to Tran for releasing the Hell demo source code, from which
    I managed to find these very helpfull volume and frequency value
    tables, without which this player would not have worked! }

  voltbl : Array[0..15] of Byte =
                     (  $004,$0a0,$0b0,$0c0,$0c8,$0d0,$0d8,$0e0,
                        $0e4,$0e8,$0ec,$0f1,$0f4,$0f6,$0fa,$0ff);
  freqtbl : Array[1..60] of Word = (
                        56,59,62,66,70,74,79,83,88,94,99,105,
                        112,118,125,133,141,149,158,167,177,188,199,211,
                        224,237,251,266,282,299,317,335,355,377,399,423,
                        448,475,503,532,564,598,634,671,711,754,798,846,
                        896,950,1006,1065,1129,1197,1268,1343,1423,1508,1597,1692 );



Type
  Header_669_Type = Record
                      Marker      : Word;
                      Title       : Array[1..108] of Char;
                      NOS,                     { No of Samples  0 - 64 }
                      NOP         : Byte;      { No of Patterns 0 - 128 }
                      LoopOrder   : Byte;
                      Order       : Array[0..127] of Byte;
                      Tempo       : Array[0..127] of Byte;
                      Break       : Array[0..127] of Byte;
                    End;
  Sample_Type     = Record
                      FileName  : Array[1..13] of Char;
                      Length    : Longint;
                      LoopStart : Longint;
                      LoopLen   : Longint;
                    End;
  Sample_Pointer  = ^Sample_Type;
  Note_Type       = Record
                      Info,  { <- Don't worry about this little bit here }
                      Note,
                      Sample,
                      Volume,
                      Command,
                      Data    : Byte;
                    End;
  Event_Type      = Array[1..8] of Note_Type;
  Pattern_Type    = Array[0..63] of Event_Type;
  Pattern_Pointer = ^Pattern_Type;



Var
  Header : Header_669_Type;
  Samples : Array[0..64] of Sample_Pointer;
  Patterns : Array[0..128] of Pattern_Pointer;
  GusTable : Array[0..64] of Longint;
  GusPos : Longint;
  Speed : Byte;
  Count : Word;
  OldTimer : Procedure;
  CurrentPat, CurrentEvent : Byte;


Implementation

Uses Dos, Crt, GUSUnit;


Procedure Load669(N : String);
Var
  F : File;
  I, J, K : Byte;
  T : Array[1..8,1..3] of Byte;

  Procedure LoadSample(No, Size : Longint);
  Var
    Buf : Array[1..1024] of Byte;
    I : Longint;
    J, K : Integer;
  Begin
    GusTable[No] := GusPos;

    I := Size;
    While I > 1024 do
    Begin
      BlockRead(F, Buf, SizeOf(Buf), J);
      For K := 1 to J do GusPoke(GusPos+K-1, Buf[K] XOR 127);
      Dec(I, J);
      Inc(GusPos, J);
    End;
    BlockRead(F, Buf, I, J);
    For K := 1 to J do GusPoke(GusPos+K-1, Buf[K] XOR 127);
    Inc(GusPos, J);
  End;

Begin
  {$I-}
  Assign(F, N);
  Reset(F, 1);
  BlockRead(F, Header, SizeOf(Header));
  If Header.Marker = $6669 then
  Begin
    For I := 1 to Header.NOS do
    Begin
      New(Samples[I-1]);
      BlockRead(F, Samples[I-1]^, SizeOf(Samples[I-1]^));
    End;

    For I := 0 to Header.NOP-1 do
    Begin
      New(Patterns[I]);
      For J := 0 to 63 do
      Begin
        BlockRead(F, T, SizeOf(T));
        For K := 1 to 8 do
        Begin
          Patterns[I]^[J,K].Info    := t[K,1];
          Patterns[I]^[J,K].Note    := ( t[K,1] shr 2);
          Patterns[I]^[J,K].Sample  := ((t[K,1] AND 3) SHL 4) +  (t[K,2] SHR 4);
          Patterns[I]^[J,K].Volume  := ( t[K,2] AND 15);
          Patterns[I]^[J,K].Command := ( t[K,3] shr 4);
          Patterns[I]^[J,K].Data    := ( t[K,3] AND 15);
        End;
      End;
    End;

    For I := 1 to Header.NOS do
      LoadSample(I-1, Samples[I-1]^.Length);
  End;

  Close(F);
  {$I+}
  If (IOResult <> 0) OR (Header.Marker <> $6669) then
    Loaded := False else Loaded := True;

End;




Procedure UpDateNotes;
Var
  I : Word;
  Inst : Byte;
  Note : Word;
Begin
  WaitState := True;
  For I := 1 to NumChannels do
  With Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I] do

  For I := 1 to NumChannels do
  If (Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I].Info < $FE) then
  Begin
    Inst := Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I].Sample;
    Note := Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I].Note;
    Channels[I].Freq := FreqTbl[Note];
{    Channels[I].Pan  := (1-(I AND 1)) * 15;}
    Channels[I].Vol  := $100*VolTbl[Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I].Volume];
{    Write(Note:3,Inst:3,' -');}

    GUSSetVolume    (I, 0);
    GUSVoiceControl (I, 1);
    GUSSetBalance   (I, Channels[I].Pan);
    GusSetFreq      ( I, Channels[I].Freq);
{    GUSPlayVoice    ( I, 0, GusTable[Inst],
                            GusTable[Inst],
                            GusTable[Inst]+Samples[Inst]^.Length  );}

{    Write(Samples[Inst]^.LoopLen:5);}
    If Samples[Inst]^.LoopLen < 1048575 then
    Begin
    GUSPlayVoice    ( I, 8, GusTable[Inst],
                            GusTable[Inst]+Samples[Inst]^.LoopStart,
                            GusTable[Inst]+Samples[Inst]^.LoopLen  );
    End
      Else
    Begin
    GUSPlayVoice    ( I, 0, GusTable[Inst],
                            GusTable[Inst],
                            GusTable[Inst]+Samples[Inst]^.Length  );
    End;


  End;

{  Writeln;}

  For I := 1 to NumChannels do
    If (Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I].Info < $FF) then
      GUSSetVolume (I, $100*VolTbl[Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I].Volume]);

  For I := 1 to NumChannels do
  With Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I] do
  Case Command of
    5 : Speed := Data;
    3 : Begin
          Channels[I].Freq := Channels[I].Freq + 10;
          GUSSetFreq(I, Channels[I].Freq);
        End;
    8 : Inc(Flags[Data]);
    6 : Case Data of
          0 : If Channels[I].Pan > 0 then
              Begin
                Dec(Channels[I].Pan);
                GusSetBalance(I, Channels[I].Pan);
              End;
          1 : If Channels[I].Pan < 15 then
              Begin
                Inc(Channels[I].Pan);
                GusSetBalance(I, Channels[I].Pan);
              End;
        End;
  End;





  Inc(CurrentEvent);
  If CurrentEvent > Header.Break[CurrentPat] then Begin CurrentEvent := 0; Inc(CurrentPat) End;
  If Header.Order[CurrentPat] > (Header.NOP) then Begin CurrentEvent := 0; CurrentPat := 0; End;

End;


Procedure UpDateEffects;
Var
  I : Word;
Begin
  For I := 1 to 4 do
  With Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I] do
  Begin
    Case Command of
      0 : Begin
            Inc(Channels[I].Freq, Data);
            GusSetFreq(I, Channels[I].Freq);
          End;
      1 : Begin
            Dec(Channels[I].Freq, Data);
            GusSetFreq(I, Channels[I].Freq);
          End;
    End;
  End;
End;




{ $ F+,S-,W-}
Procedure ModInterrupt; Interrupt;
Begin
  Inc(Count);
  If Count = Speed then
  Begin
    UpDateNotes;
    Count := 0;
  End;
  UpDateEffects;
  If (Count MOD 27) = 1 then
  Begin
    inline ($9C);
    OldTimer;
  End;
  Port[$20] := $20;
End;
{ $ F-,S+}

Procedure TimerSpeedup(Speed : Word);
Begin
  Port[$43] := $36;
  Port[$40] := Lo(Speed);
  Port[$40] := Hi(Speed);
end;

Procedure PlayMusic;
Begin
  If Loaded then
  Begin
    TimerSpeedUp( (1192755 DIV 32));
    GetIntVec($8, Addr(OldTimer));
    SetIntVec($8, Addr(ModInterrupt));
    Speed := Header.Tempo[0];
    Playing := True;
  End
  { If the module is not loaded, then the Playing flag will not be set,
    so your program should check the playing flag just after calling
    PlayMusic to see if everything was okay. }
End;


Procedure StopMusic;
Var
  I : Byte;
Begin
  If Playing then
  Begin
    SetIntVec($8, Addr(OldTimer));
    For I := 1 to NumChannels do GusSetVolume(I, 0);
  End;
  TimerSpeedUp($FFFF);
End;


Procedure Init;
Var
  I : Byte;
Begin
  GusPos := 1;
  Count := 0;
  Speed := 6;
  CurrentPat := 0;
  CurrentEvent := 0;
  For I := 1 to NumChannels do Channels[I].Pan  := (1-(I AND 1)) * 15;
  For I := 1 to NumChannels do GUSVoiceControl(I, 1);
  For I := 0 to 15 do Flags[I] := 0;
End;


Var
  I, J : Byte;


Begin
  Init;
  Writeln('GUS669 Unit V0.2b');
  Writeln('Copyright 1994 Mark Dixon.');
  Writeln;
End.


?????????????????????????????????????????????????????????????????????????????
? PLAY669.PAS ?
???????????????

Program Testout_Gus669_Unit;

Uses Crt, GUS669;

Begin

  If ParamCount > 0 then Load669(Paramstr(1))
    else
  Begin
    Writeln;
    Writeln('Please specify the name of the 669 module you wish to play');
    Writeln('from the command line.');
    Writeln;
    Writeln('eg :    Play669  Hardwired.669 ');
    Writeln;
    Halt(1);
  End;
  PlayMusic;
  If Playing then
  Begin
    Writeln('Playing ', ParamStr(1) );
    Writeln('Press any key to stop and return to DOS.');
    Repeat
    Until Keypressed
  End
    else
  Begin
    Writeln;
    Writeln('Couldn''t load or play the module for some reason!');
    Writeln;
    Writeln('Please check your GUS is working correctly, and that you have');
    Writeln('correctly specified the 669 filename.');
    Writeln;
  End;
  StopMusic;
End.

Protracker 1.1B Song/Module Format:
 
Offset  Bytes  Description
   0     20    Songname. Remember to put trailing null bytes at the end...
 
Information for sample 1-31:
 
Offset  Bytes  Description
  20     22    Samplename for sample 1. Pad with null bytes.
  42      2    Samplelength for sample 1. Stored as number of words.
               Multiply by two to get real sample length in bytes.
  44      1    Lower four bits are the finetune value, stored as a signed
               four bit number. The upper four bits are not used, and
               should be set to zero.
               Value:  Finetune:
                 0        0
                 1       +1
                 2       +2
                 3       +3
                 4       +4
                 5       +5
                 6       +6
                 7       +7
                 8       -8
                 9       -7
                 A       -6
                 B       -5
                 C       -4
                 D       -3
                 E       -2
                 F       -1
 
  45      1    Volume for sample 1. Range is $00-$40, or 0-64 decimal.
  46      2    Repeat point for sample 1. Stored as number of words offset
               from start of sample. Multiply by two to get offset in bytes.
  48      2    Repeat Length for sample 1. Stored as number of words in
               loop. Multiply by two to get replen in bytes.
 
Information for the next 30 samples starts here. It's just like the info for
sample 1.
 
Offset  Bytes  Description
  50     30    Sample 2...
  80     30    Sample 3...
   .
   .
   .
 890     30    Sample 30...
 920     30    Sample 31...

Offset  Bytes  Description
 950      1    Songlength. Range is 1-128.
 951      1    Well... this little byte here is set to 127, so that old
               trackers will search through all patterns when loading.
               Noisetracker uses this byte for restart, but we don't.
 952    128    Song positions 0-127. Each hold a number from 0-63 that
               tells the tracker what pattern to play at that position.
1080      4    The four letters "M.K." - This is something Mahoney & Kaktus
               inserted when they increased the number of samples from
               15 to 31. If it's not there, the module/song uses 15 samples
               or the text has been removed to make the module harder to
               rip. Startrekker puts "FLT4" or "FLT8" there instead.

Offset  Bytes  Description
1084    1024   Data for pattern 00.
   .
   .
   .
xxxx  Number of patterns stored is equal to the highest patternnumber
      in the song position table (at offset 952-1079).

Each note is stored as 4 bytes, and all four notes at each position in
the pattern are stored after each other.

00 -  chan1  chan2  chan3  chan4
01 -  chan1  chan2  chan3  chan4
02 -  chan1  chan2  chan3  chan4
etc.

Info for each note:

 _____byte 1_____   byte2_    _____byte 3_____   byte4_
/                 /        /                 /
0000          0000-00000000  0000          0000-00000000

Upper four    12 bits for    Lower four    Effect command.
bits of sam-  note period.   bits of sam-
ple number.                  ple number.

Periodtable for Tuning 0, Normal
  C-1 to B-1 : 856,808,762,720,678,640,604,570,538,508,480,453
  C-2 to B-2 : 428,404,381,360,339,320,302,285,269,254,240,226
  C-3 to B-3 : 214,202,190,180,170,160,151,143,135,127,120,113

To determine what note to show, scan through the table until you find
the same period as the one stored in byte 1-2. Use the index to look
up in a notenames table.

This is the data stored in a normal song. A packed song starts with the
four letters "PACK", but i don't know how the song is packed: You can
get the source code for the cruncher/decruncher from us if you need it,
but I don't understand it; I've just ripped it from another tracker...
 
In a module, all the samples are stored right after the patterndata.
To determine where a sample starts and stops, you use the sampleinfo
structures in the beginning of the file (from offset 20). Take a look
at the mt_init routine in the playroutine, and you'll see just how it
is done.
 
Lars "ZAP" Hamre/Amiga Freelancers
 
--------------------------
 
Found that document...
 
Mark J Cox ------------------------------------------- m.j.h.cox@bradford.ac.uk
University of Bradford ---------------------------- bc732@cleveland.freenet.edu
Mark
 
             EFFECT COMMANDS
             ---------------
 Effect commands on protracker should
 be compatible with all other trackers.
 0 - None/Arpeggio     8 - * NOT USED *
 1 - Portamento Up     9 - SampleOffset
 2 - Portamento Down   A - VolumeSlide
 3 - TonePortamento    B - PositionJump
 4 - Vibrato           C - Set Volume
 5 - ToneP + VolSlide  D - PatternBreak
 6 - Vibra + VolSlide  E - Misc. Cmds
 7 - Tremolo           F - Set Speed
 
 
              E - COMMANDS
              ------------
 The E command has been altered to
 contain more commands than one.
 E0- Filter On/Off     E8- * NOT USED *
 E1- Fineslide Up      E9- Retrig Note
 E2- Fineslide Down    EA- FineVol Up
 E3- Glissando Control EB- FineVol Down
 E4- Vibrato Control   EC- NoteCut
 E5- Set Finetune      ED- NoteDelay
 E6- Patternloop       EE- PatternDelay
 E7- Tremolo Control   EF- Invert Loop
 
 
  Cmd 0. Arpeggio [Range:$0-$F/$0-$F]
  -----------------------------------
 Usage: $0 + 1st halfnote add
           + 2nd halfnote add
 Arpeggio is used to simulate chords.
 This is done by rapidly changing the
 pitch between 3(or 2) different notes.
 It sounds very noisy and grainy on
 most samples, but ok on monotone ones.
 Example: C-300047  C-major chord:
          (C+E+G  or C+4+7 halfnotes)
          C-300037  C-minor chord:
          (C+D#+G or C+3+7 halfnotes)
 
 
  Cmd 1. Portamento up [Speed:$00-$FF]
  ------------------------------------
 Usage: $1 + portamento speed
 Portamento up will simply slide the
 sample pitch up. You can NOT slide
 higher than B-3! (Period 113)
 Example: C-300103  1 is the command,
            3 is the portamentospeed.
 NOTE: The portamento will be called as
 many times as the speed of the song.
 This means that you'll sometimes have
 trouble sliding accuratly. If you
 change the speed without changing the
 sliderates, it will sound bad...
 
 
  Cmd 2. Portamento down [Speed:$00-FF]
  -------------------------------------
 Usage: $2 + portamento speed
 Just like command 1, except that this
 one slides the pitch down instead.
 (Adds to the period).
 You can NOT slide lower than C-1!
 (Period 856)
 Example: C-300203  2 is the command,
            3 is the portamentospeed.
 
 
 Cmd 3. Tone-portamento [Speed:$00-$FF]
 --------------------------------------
 Usage: Dest-note + $3 + slidespeed
 This command will automatically slide
 from the old note to the new.
 You don't have to worry about which
 direction to slide, you need only set
 the slide speed. To keep on sliding,
 just select the command $3 + 00.
 Example: A-200000  First play a note.
          C-300305  C-3 is the note to
               slide to, 3 the command,
               and 5 the speed.
 
 
 Cmd 4. Vibrato [Rate:$0-$F,Dpth:$0-$F]
 --------------------------------------
 Usage: $4 + vibratorate + vibratodepth
 Example: C-300481  4 is the command,
       8 is the speed of the vibrato,
   and 1 is the depth of the vibrato.
 To keep on vibrating, just select
 the command $4 + 00. To change the
 vibrato, you can alter the rate,
 depth or both. Use command E4- to
 change the vibrato-waveform.
 
 
 Cmd 5. ToneP + Volsl [Spd:$0-$F/$0-$F]
 --------------------------------------
 Usage: $5 + upspeed + downspeed
 This command will continue the current
 toneportamento and slide the volume
 at the same time. Stolen from NT2.0.
 Example: C-300503  3 is the speed to
                turn the volume down.
          C-300540  4 is the speed to
                         slide it up.
 
 
 Cmd 6. Vibra + Volsl [Spd:$0-$F/$0-$F]
 --------------------------------------
 Usage: $6 + upspeed + downspeed
 This command will continue the current
 vibrato and slide the volume at the
 same time. Stolen from NT2.0.
 Example: C-300605  5 is the speed to
                turn the volume down.
          C-300640  4 is the speed to
                         slide it up.
 
 
 Cmd 7. Tremolo [Rate:$0-$F,Dpth:$0-$F]
 --------------------------------------
 Usage: $7 + tremolorate + tremolodepth
 Tremolo vibrates the volume.
 Example: C-300794  7 is the command,
       9 is the speed of the tremolo,
   and 4 is the depth of the tremolo.
 To keep on tremoling, just select
 the command $7 + 00. To change the
 tremolo, you can alter the rate,
 depth or both. Use command E7- to
 change the tremolo-waveform.
 
 
 Cmd 9. Set SampleOffset [Offs:$00-$FF]
 --------------------------------------
 Usage: $9 + Sampleoffset
 This command will play from a chosen
 position in the sample, and not from
 the beginning. The two numbers equal
 the two first numbers in the length
 of the sample. Handy for speech-
 samples.
 Example: C-300923  Play sample from
                    offset $2300.
 
 
 Cmd A. Volumeslide [Speed:$0-$F/$0-$F]
 --------------------------------------
 Usage: $A + upspeed + downspeed
 Example: C-300A05  5 is the speed to
                turn the volume down.
          C-300A40  4 is the speed to
                         slide it up.
 NOTE: The slide will be called as
 many times as the speed of the song.
 The slower the song, the more the
 volume will be changed on each note.
 
 
   Cmd B. Position-jump [Pos:$00-$7F]
   ----------------------------------
 Usage: $B + position to continue at
 Example: C-300B01  B is the command,
                 1 is the position to
                 restart the song at.
 This command will also perform a
 pattern-break (see 2 pages below).
 You can use this command instead of
 restart as on noisetracker, but you 
 must enter the position in hex!
 
 
   Cmd C. Set volume [Volume:$00-$40]
   ----------------------------------
 Usage: $C + new volume
 Well, this old familiar command will
 set the current volume to your own
 selected. The highest volume is $40.
 All volumes are represented in hex.
 (Programmers do it in hex, you know!)
 Example: C-300C10  C is the command,
       10 is the volume (16 decimal).
 
 
      Cmd D. Pattern-break
      [Pattern-pos:00-63, decimal]
      ----------------------------
 Usage: $D + pattern-position
 This command just jumps to the next
 song-position, and continues play
 from the patternposition you specify.
 Example: C-300D00  Jump to the next
     song-position and continue play
            from patternposition 00.
      Or: C-300D32  Jump to the next
     song-position and continue play
    from patternposition 32 instead.
 
 
    Cmd E0. Set filter [Range:$0-$1]
    --------------------------------
 Usage: $E0 + filter-status
 This command jerks around with the
 sound-filter on some A500 + A2000.
 All other Amiga-users should keep out
 of playing around with it.
 Example: C-300E01  disconnects filter
                 (turns power LED off)
          C-300E00  connects filter
                  (turns power LED on)
 
 
   Cmd E1. Fineslide up [Range:$0-$F]
   ----------------------------------
 Usage: $E1 + value
 This command works just like the
 normal portamento up, except that
 it only slides up once. It does not
 continue sliding during the length of
 the note.
 Example: C-300E11  Slide up 1 at the
               beginning of the note.
 (Great for creating chorus effects)
 
 
  Cmd E2. Fineslide down [Range:$0-$F]
  ------------------------------------
 Usage: $E2 + value
 This command works just like the
 normal portamento down, except that
 it only slides down once. It does not
 continue sliding during the length of
 the note.
 Example: C-300E26  Slide up 6 at the
               beginning of the note.
 
 
  Cmd E3. Glissando Ctrl [Range:$0-$1]
  ------------------------------------
 Usage: $E3 + Glissando-Status
 Glissando must be used with the tone-
 portamento command. When glissando is
 activated, toneportamento will slide
 a halfnote at a time, instead of a
 straight slide.
 Example: C-300E31  Turn Glissando on.
          C-300E30  Turn Glissando off.
 
 
      Cmd E4. Set vibrato waveform
      [Range:$0-$3]
      ----------------------------
 Usage: $E4 + vibrato-waveform
 Example: C-300E40  Set sine(default)
               E44  Don't retrig WF
          C-300E41  Set Ramp Down
               E45  Don't retrig WF
          C-300E42  Set Squarewave
               E46  Don't retrig WF
          C-300E43  Set Random
               E47  Don't retrig WF
 
 
   Cmd E5. Set finetune [Range:$0-$F]
   ----------------------------------
 Usage: $E5 + finetune-value
 Example: C-300E51  Set finetune to 1.
 Use these tables to figure out the
 finetune-value.
 Finetune: +7 +6 +5 +4 +3 +2 +1  0
    Value:  7  6  5  4  3  2  1  0
 Finetune: -1 -2 -3 -4 -5 -6 -7 -8
    Value:  F  E  D  C  B  A  9  8
 
 
   Cmd E6. PatternLoop [Loops:$0-$F]
   ----------------------------------
 Usage: $E6 + number of loops
 This command will loop a part of a
 pattern.
 Example: C-300E60  Set loopstart.
          C-300E63  Jump to loop 3
           times before playing on.
 
 
      Cmd E7. Set tremolo waveform
      [Range:$0-$3]
      ----------------------------
 Usage: $E7 + tremolo-waveform
 Example: C-300E70  Set sine(default)
               E74  Don't retrig WF
          C-300E71  Set Ramp Down
               E75  Don't retrig WF
          C-300E72  Set Squarewave
               E76  Don't retrig WF
          C-300E73  Set Random
               E77  Don't retrig WF
 
 
    Cmd E9. Retrig note [Value:$0-$F]
    ---------------------------------
 Usage: $E9 + Tick to Retrig note at.
 This command will retrig the same note
 before playing the next. Where to
 retrig depends on the speed of the
 song. If you retrig with 1 in speed 6
 that note will be trigged 6 times in
 one note slot. Retrig on hi-hats!
 Example: C-300F06  Set speed to 6.
          C-300E93  Retrig at tick 3
                    out of 6.
 
 
   Cmd EA. FineVolsl up [Range:$0-$F]
   ----------------------------------
 Usage: $EA + value
 This command works just like the
 normal volumeslide up, except that
 it only slides up once. It does not
 continue sliding during the length of
 the note.
 Example: C-300EA3  Slide volume up 1
        at the beginning of the note.
 
 
  Cmd EB. FineVolsl down [Range:$0-$F]
  ------------------------------------
 Usage: $EB + value
 This command works just like the
 normal volumeslide down, except that
 it only slides down once. It does not
 continue sliding during the length of
 the note.
 Example: C-300EB6  Slide volume down
       6 at the beginning of the note.
 
 
     Cmd EC. Cut note [Value:$0-$F]
     ------------------------------
 Usage: $EC + Tick to Cut note at.
 This command will cut the note
 at the selected tick, creating
 extremely short notes.
 Example: C-300F06  Set speed to 6.
          C-300EC3  Cut at tick 3 out
                    of 6.
 Note that the note is not really cut,
 the volume is just turned down.
 
 
     Cmd ED. NoteDelay [Value:$0-$F]
     -------------------------------
 Usage: $ED + ticks to delay note.
 This command will delay the note
 to the selected tick.
 Example: C-300F06  Set speed to 6.
          C-300ED3  Play note at tick
                    3 out of 6.
 
 
   Cmd EE. PatternDelay [Notes:$0-$F]
   ----------------------------------
 Usage: $EE + notes to delay pattern.
 This command will delay the pattern
 the selected numbers of notes.
 Example: C-300EE8  Delay pattern 8
           notes before playing on.
 All other effects are still active
 when the pattern is being delayed.
 
 
    Cmd EF. Invert Loop [Speed:$0-$F]
    ---------------------------------
 Usage: $EF + Invertspeed
 This command will need a short loop
 ($10,20,40,80 etc. bytes) to work.
 It will invert the loop byte by byte.
 Sounds better than funkrepeat...
 Example: C-300EF8 Set invspeed to 8.
 To turn off the inverting, set 
 invspeed to 0, or press ctrl + Z.
 
 
    Cmd F. Set speed [Speed:$00-$FF]
    --------------------------------
 Usage: $F + speed
 This command will set the speed of the
 song.  


?????????????????????????????????????????????????????????????????????????????
? Annotation by Mark Feldman (u914097@student.canberra.edu.au ?
???????????????????????????????????????????????????????????????

The 6 and 8 channel mod files differ from the normal mods in two ways:

1) The signature string "M.K." at offset 1080 is either "6CHN" or "8CHN".
2) The pattern data table starting at offset 1084 stores 6 or 8 notes for
   each pattern position position.

Creative Voice (VOC) file format
--------------------------------

~From: galt@dsd.es.com

(byte numbers are hex!)

    HEADER (bytes 00-19)
    Series of DATA BLOCKS (bytes 1A+) [Must end w/ Terminator Block]

- ---------------------------------------------------------------

HEADER:
=======
     byte #     Description
     ------     ------------------------------------------
     00-12      "Creative Voice File"
     13         1A (eof to abort printing of file)
     14-15      Offset of first datablock in .voc file (std 1A 00
                in Intel Notation)
     16-17      Version number (minor,major) (VOC-HDR puts 0A 01)
     18-19      2's Comp of Ver. # + 1234h (VOC-HDR puts 29 11)

- ---------------------------------------------------------------

DATA BLOCK:
===========

   Data Block:  TYPE(1-byte), SIZE(3-bytes), INFO(0+ bytes)
   NOTE: Terminator Block is an exception -- it has only the TYPE byte.

      TYPE   Description     Size (3-byte int)   Info
      ----   -----------     -----------------   -----------------------
      00     Terminator      (NONE)              (NONE)
      01     Sound data      2+length of data    *
      02     Sound continue  length of data      Voice Data
      03     Silence         3                   **
      04     Marker          2                   Marker# (2 bytes)
      05     ASCII           length of string    null terminated string
      06     Repeat          2                   Count# (2 bytes)
      07     End repeat      0                   (NONE)
      08     Extended        4                   ***

      *Sound Info Format:       **Silence Info Format:
       ---------------------      ----------------------------
       00   Sample Rate           00-01  Length of silence - 1
       01   Compression Type      02     Sample Rate
       02+  Voice Data

    ***Extended Info Format:
       ---------------------
       00-01  Time Constant: Mono: 65536 - (256000000/sample_rate)
                             Stereo: 65536 - (25600000/(2*sample_rate))
       02     Pack
       03     Mode: 0 = mono
                    1 = stereo


  Marker#           -- Driver keeps the most recent marker in a status byte
  Count#            -- Number of repetitions + 1
                         Count# may be 1 to FFFE for 0 - FFFD repetitions
                         or FFFF for endless repetitions
  Sample Rate       -- SR byte = 256-(1000000/sample_rate)
  Length of silence -- in units of sampling cycle
  Compression Type  -- of voice data
                         8-bits    = 0
                         4-bits    = 1
                         2.6-bits  = 2
                         2-bits    = 3
                         Multi DAC = 3+(# of channels) [interesting--
                                       this isn't in the developer's manual]

          ??????????????????????????????????????????????????
          ? The Microsoft Multimedia WAV Sound File Format ?
          ??????????????????????????????????????????????????

                 Written for the PC-GPE by Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? The RIFF File Format ?
????????????????????????

WAV files use the RIFF file structure. The RIFF format was designed
for multi-media purposes. A RIFF files consists of a number of "chunks":

  ??????????????????????????????????????????????????????????????????????????
  ? Byte              Length                                               ?
  ? Offset   Name   (in bytes)    Description                              ?
  ??????????????????????????????????????????????????????????????????????????
  ? 00h      rID        4h        Contains the characters "RIFF"           ?
  ? 04h      rLen       4h        The length of the data in the next chunk ?
  ? 08h      rData     rLen       The data chunk                           ?
  ??????????????????????????????????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? The WAVE Form Definition ?
????????????????????????????

The rData chunk in a WAV file is split up into several further chunks:

 ???????????????????????????????????????????????????????????????????????????
 ? rData                                                                   ?
 ? Byte              Length                                                ?
 ? Offset   Name   (in bytes)    Description                               ?
 ???????????????????????????????????????????????????????????????????????????
 ? 00h      wID        4h        Contains the characters "WAVE"            ?
 ? 04h      Format    14h        Contains data which specifies the format  ?
 ?          Chunk                  of the Data in the Data Chunk           ?
 ? 18h      WAVE Data  ?         Contains the WAV audio data               ?
 ?          Chunk                                                          ?
 ???????????????????????????????????????????????????????????????????????????



?????????????????????????????????????????????????????????????????????????????
? The Format Chunk ?
????????????????????

The Format Chunk is split up into these fields:

???????????????????????????????????????????????????????????????????????????
? Format                                                                  ?
? Chunk                  Length                                           ?
? Offset  Name         (in bytes)   Description                           ?
???????????????????????????????????????????????????????????????????????????
? 00h     fId               4       Contains the characters "fmt"         ?
? 04h     fLen              4       Length of data in the format chunk    ?
? 08h     wFormatTag        2       *                                     ?
? 0Ah     nChannels         2       Number of channels, 1=mono, 2=stereo  ?
? 0Ch     nSamplesPerSec    2       Playback frequency                    ?
? 0Eh     nAvgBytesPerSec   2       **                                    ?
? 10h     nBlockAlign       2       ***                                   ?
? 12h     FormatSpecific    2       Format specific data area             ?
???????????????????????????????????????????????????????????????????????????


  (or in plain english, regular 8 bit sampled uncompressed sound)


   transferred at = nChannels * nSamplesPerSec * (nBitsPerSample / 8)


    needs to process a multiplt of nBlockAlign at a time.
    nBlockAlign = nChannels * (nBitsPerSample / 8)


?????????????????????????????????????????????????????????????????????????????
? The Data Chunk ?
??????????????????

The Data Chunk is split up into these fields:

???????????????????????????????????????????????????????????????????????????
?  Data                                                                   ?
? Chunk                  Length                                           ?
? Offset  Name         (in bytes)   Description                           ?
???????????????????????????????????????????????????????????????????????????
? 00h     dId              4        Contains the characters "data"        ?
? 02h     dLen             4        Length of data in the dData field     ?
? 00h     dData            dLen     The actual waveform data              ?
???????????????????????????????????????????????????????????????????????????

In mono 8-bit files each byte represents one sample. In stereo 8-bit files
two bytes are stored for each sample, the first byte is the left channel
value, the next is the right channel value.

               ????????????????????????????????????????????
               ? Creative Labs File Formats (SBI/CMF/IBK) ?
               ????????????????????????????????????????????

                 Written for the PC-GPE by Mark Feldman
              e-mail address : u914097@student.canberra.edu.au
                               myndale@cairo.anu.edu.au

              ?????????????????????????????????????????????
              ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
              ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
              ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Sound Blaster Instrument File Format (SBI) ?
??????????????????????????????????????????????

The SBI format contains the register values for the FM chip to synthesize
an instrument.

??????????????????????????????????????????????????????????????????????????
? Offset    Description                                                  ?
??????????????????????????????????????????????????????????????????????????
? 00h-03h   Contains id characters "SBI" followed by byte 1Ah            ?
? 04h-23h   Instrument name, NULL terminated string                      ?
?   24h     Modulator Sound Characteristic (Mult, KSR, EG, VIB, AM)      ?
?   25h     Carrier Sound Characteristic                                 ?
?   26h     Modulator Scaling/Output Level                               ?
?   27h     Carrier Scaling/Output Level                                 ?
?   28h     Modulator Attack/Delay                                       ?
?   29h     Carrier Attack/Delay                                         ?
?   2Ah     Modulator Sustain/Release                                    ?
?   2Bh     Carrier Sustain/Release                                      ?
?   2Ch     Modulator Wave Seelct                                        ?
?   2Dh     Carrier Wave Select                                          ?
?   2Eh     Feedback/Connection                                          ?
? 2Fh-33h   Reserved                                                     ?
??????????????????????????????????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Creative Music File Format (CMF) ?
????????????????????????????????????

The CMF file format consists of 3 blocks: the header block, the instrument
block and the music block.


The CMF Header Block
????????????????????

??????????????????????????????????????????????????????????????????????????
? Offset    Description                                                  ?
??????????????????????????????????????????????????????????????????????????
? 00h-03h   Contains id characters "CTMF"                                ?
? 04h-05h   CMF Format Version MSB = major version, lsb = minor version  ?
? 06h-07h   File offset of the instrument block                          ?
? 08h-09h   File offset of the music block                               ?
? 0Ah-0Bh   Clock ticks per quarter note (one beat) default = 120        ?
? 0Ch-0Dh   Clock ticks per second                                       ?
? 0Eh-0Fh   File offset of the music title (0 = none)                    ?
? 10h-11h   File offset of the composer name (0 = none)                  ?
? 12h-13h   File offset of the remarks (0 = none)                        ?
? 14h-23h   Channel-In-Use Table                                         ?
? 24h-25h   Number of instruments used                                   ?
? 26h-27h   Basic Tempo                                                  ?
? 28h-?     Title, composer and remarks stored here                      ?
??????????????????????????????????????????????????????????????????????????


The CMF Instrument Block
????????????????????????

The instrument block contains one 16 byte data structure for each instrument
in the piece. Each record is of the same format as bytes 24h-33h in the
SBI file format.


The CMF Music Block
???????????????????

The music block adheres to the standard MIDI file format, and can have from
1 to 16 instruments. The PC-GPE file MIDI.TXT contains more information
on this file format.

The music block consists of an alternating seqence of time and MIDI event
records:

?????????????????????????????????????????????????????
?dTime?MIDI Event?dTime?MIDI Event?dTime?MIDI Event? ........
?????????????????????????????????????????????????????

dTime (delta Time) is the amount of time before the following MIDI event.
MIDI Event is any MIDI channel message (see MIDI.TXT).


The CMF file format defines the following MIDI Control Change events:

??????????????????????????????????????????????????????????????????????????
? Control                                                                ?
? Number        Control Data                                             ?
??????????????????????????????????????????????????????????????????????????
?  66h          1-127, used as markers in the music                      ?
?  67h          0 - melody mode, 1 = rhythm mode                         ?
?  68h          0-127, changes the pitch of all following notes upward   ?
?               by the given number of 1/128 semitones                   ?
?  69h          0-127, changes the pitch of all following notes downward ?
?               by the given number of 1/128 semitones                   ?
??????????????????????????????????????????????????????????????????????????

In rhythm mode, the last five channels are allocated for the percussion
instruments:

                   ?????????????????????????????
                   ? Channel   Instrument      ?
                   ?????????????????????????????
                   ?  12h      Bass Drum       ?
                   ?  13h      Snare Drum      ?
                   ?  14h      Tom-Tom         ?
                   ?  15h      Top Cymbal      ?
                   ?  16h      High-hat Cymbal ?
                   ?????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Sound Blaster Instrument Bank File Format (IBK) ?
???????????????????????????????????????????????????

A bank file is a group of up to 128 instruments.

??????????????????????????????????????????????????????????????????????????
? Offset    Description                                                  ?
??????????????????????????????????????????????????????????????????????????
? 00h-03h   Contains id characters "IBK" followed by byte 1Ah            ?
? 04h-803h  Parameters for 128 instruments, 16 bytes for each instrument ?
?           in the same format as bytes 24h-33h in the SBI format        ?
? 804h-C83h Instrument names for 128 instruments, 9 bytes for each       ?
?           instrument, each name must be null terminated                ?
??????????????????????????????????????????????????????????????????????????

?????????????????????????????????????????????????????????????????????????????
? References ?
??????????????

Title : Sound Blaster - The Official Book
Authors : Richard Heimlich, David M. Golden, Ivan Luk, Peter M. Ridge
Publishers : Osborne/McGraw Hill
ISBN : 0-07-881907-5

                           Standard MIDI File Format
                                Dustin Caldwell

      The standard MIDI file format is a very strange beast. When viewed as a
whole, it can be quite overwhelming. Of course, no matter how you look at it,
describing a piece of music in enough detail to be able to reproduce it
accurately is no small task. So, while complicated, the structure of the midi
file format is fairly intuitive when understood. 
      I must insert a disclaimer here that I am by no means an expert with
midi nor midi files. I recently obtained a Gravis UltraSound board for my PC,
and upon hearing a few midi files (.MID) thought, "Gee, I'd like to be able to
make my own .MID files." Well, many aggravating hours later, I discovered that
this was no trivial task. But, I couldn't let a stupid file format stop me.
(besides, I once told my wife that computers aren't really that hard to use,
and I'd hate to be a hypocrite) So if any errors are found in this
information, please let me know and I will fix it. Also, this document's scope
does not extend to EVERY type of midi command and EVERY possible file
configuration. It is a basic guide that should enable the reader (with a
moderate investment in time) to generate a quality midi file.

1. Overview

      A midi (.MID) file contains basically 2 things, Header chunks and Track
chunks. Section 2 explains the header chunks, and Section 3 explains the track
chunks. A midi file contains ONE header chunk describing the file format,
etc., and any number of track chunks. A track may be thought of in the same
way as a track on a multi-track tape deck. You may assign one track to each
voice, each staff, each instrument or whatever you want. 

2. Header Chunk

      The header chunk appears at the beginning of the file, and describes the
file in three ways. The header chunk always looks like:

4D 54 68 64 00 00 00 06 ff ff nn nn dd dd

The ascii equivalent of the first 4 bytes is MThd. After MThd comes the 4-byte
size of the header. This will always be 00 00 00 06, because the actual header
information will always be 6 bytes. 

ff ff is the file format. There are 3 formats:

0 - single-track 
1 - multiple tracks, synchronous
2 - multiple tracks, asynchronous

Single track is fairly self-explanatory - one track only. Synchronous multiple
tracks means that the tracks will all be vertically synchronous, or in other
words, they all start at the same time, and so can represent different parts
in one song. Asynchronous multiple tracks do not necessarily start at the same
time, and can be completely asynchronous. 

nn nn is the number of tracks in the midi file.

dd dd is the number of delta-time ticks per quarter note. (More about this
later)


3. Track Chunks

The remainder of the file after the header chunk consists of track chunks.
Each track has one header and may contain as many midi commands as you like.
The header for a track is very similar to the one for the file:

4D 54 72 6B xx xx xx xx

As with the header, the first 4 bytes has an ascii equivalent. This one is
MTrk. The 4 bytes after MTrk give the length of the track (not including the
track header) in bytes. 
      Following the header are midi events. These events are identical to the
actual data sent and received by MIDI ports on a synth with one addition. A
midi event is preceded by a delta-time. A delta time is the number of ticks
after which the midi event is to be executed. The number of ticks per quarter
note was defined previously in the file header chunk. This delta-time is a
variable-length encoded value. This format, while confusing, allows large
numbers to use as many bytes as they need, without requiring small numbers to
waste bytes by filling with zeros. The number is converted into 7-bit bytes,
and the most-significant bit of each byte is 1 except for the last byte of the
number, which has a msb of 0. This allows the number to be read one byte at a
time, and when you see a msb of 0, you know that it was the last (least
significant) byte of the number. According to the MIDI spec, the entire delta-
time should be at most 4 bytes long. 
      Following the delta-time is a midi event. Each midi event (except a
running midi event) has a command byte which will always have a msb of 1 (the
value will be >= 128). A list of most of these commands is in appendix A. Each
command has different parameters and lengths, but the data that follows the
command will have a msb of 0 (less than 128). The exception to this is a meta-
event, which may contain data with a msb of 1. However, meta-events require a
length parameter which alleviates confusion. 
      One subtlety which can cause confusion is running mode. This is where
the actual midi command is omitted, and the last midi command issued is
assumed. This means that the midi event will consist of a delta-time and the
parameters that would go to the command if it were included. 

4. Conclusion

      If this explanation has only served to confuse the issue more, the
appendices contain examples which may help clarify the issue. Also, 2
utilities and a graphic file should have been included with this document: 

DEC.EXE - This utility converts a binary file (like .MID) to a tab-delimited
text file containing the decimal equivalents of each byte.

REC.EXE - This utility converts a tab-delimited text file of decimal values
into a binary file in which each byte corresponds to one of the decimal
values.

MIDINOTE.PS - This is the postscript form of a page showing note numbers with
a keyboard and with the standard grand staff.
                                                                         Appendix A

1. MIDI Event Commands

Each command byte has 2 parts. The left nybble (4 bits) contains the actual
command, and the right nybble contains the midi channel number on which the
command will be executed. There are 16 midi channels, and 8 midi commands (the
command nybble must have a msb of 1).
In the following table, x indicates the midi channel number. Note that all
data bytes will be <128 (msb set to 0).

Hex      Binary       Data          Description
8x       1000xxxx     nn vv         Note off (key is released)
                                    nn=note number
                                    vv=velocity

9x       1001xxxx     nn vv         Note on (key is pressed)
                                    nn=note number
                                    vv=velocity

Ax       1010xxxx     nn vv         Key after-touch
                                    nn=note number
                                    vv=velocity

Bx       1011xxxx     cc vv         Control Change
                                    cc=controller number
                                    vv=new value

Cx       1100xxxx     pp            Program (patch) change
                                    pp=new program number

Dx       1101xxxx     cc            Channel after-touch
                                    cc=channel number

Ex       1110xxxx     bb tt         Pitch wheel change (2000H is normal or no
                                    change)
                                    bb=bottom (least sig) 7 bits of value
                                    tt=top (most sig) 7 bits of value
The following table lists meta-events which have no midi channel number. They
are of the format:

FF xx nn dd

All meta-events start with FF followed by the command (xx), the length, or
number of bytes that will contain data (nn), and the actual data (dd).

Hex      Binary       Data          Description
00       00000000     nn ssss       Sets the track's sequence number.
                                    nn=02 (length of 2-byte sequence number)
                                    ssss=sequence number

01       00000001     nn tt ..      Text event- any text you want.
                                    nn=length in bytes of text
                                    tt=text characters

02       00000010     nn tt ..      Same as text event, but used for
                                    copyright info.
                                    nn tt=same as text event

03       00000011     nn tt ..      Sequence or Track name
                                    nn tt=same as text event

04       00000100     nn tt ..      Track instrument name
                                    nn tt=same as text event

05       00000101     nn tt ..      Lyric
                                    nn tt=same as text event

06       00000110     nn tt ..      Marker
                                    nn tt=same as text event

07       00000111     nn tt ..      Cue point
                                    nn tt=same as text event

2F       00101111     00            This event must come at the end of each
                                    track

51       01010001     03 tttttt     Set tempo
                                    tttttt=microseconds/quarter note

58       01011000     04 nn dd ccbb Time Signature
                                    nn=numerator of time sig.
                                    dd=denominator of time sig. 2=quarter
                                    3=eighth, etc.
                                    cc=number of ticks in metronome click
                                    bb=number of 32nd notes to the quarter
                                    note

59       01011001     02 sf mi      Key signature
                                    sf=sharps/flats (-7=7 flats, 0=key of C,
                                    7=7 sharps)
                                    mi=major/minor (0=major, 1=minor)

7F       01111111     xx dd ..      Sequencer specific information
                                    xx=number of bytes to be sent
                                    dd=data
The following table lists system messages which control the entire system.
These have no midi channel number. (these will generally only apply to
controlling a midi keyboard, etc.)

Hex      Binary       Data          Description
F8       11111000                   Timing clock used when synchronization is
                                    required.

FA       11111010                   Start current sequence

FB       11111011                   Continue a stopped sequence where left
                                    off

FC       11111100                   Stop a sequence


The following table lists the numbers corresponding to notes for use in note 
on and note off commands.


Octave||                     Note Numbers
   #  ||
      || C   | C#  | D   | D#  | E   | F   | F#  | G   | G#  | A   | A#  | B
-----------------------------------------------------------------------------
   0  ||   0 |   1 |   2 |   3 |   4 |   5 |   6 |   7 |   8 |   9 |  10 | 11
   1  ||  12 |  13 |  14 |  15 |  16 |  17 |  18 |  19 |  20 |  21 |  22 | 23
   2  ||  24 |  25 |  26 |  27 |  28 |  29 |  30 |  31 |  32 |  33 |  34 | 35
   3  ||  36 |  37 |  38 |  39 |  40 |  41 |  42 |  43 |  44 |  45 |  46 | 47
   4  ||  48 |  49 |  50 |  51 |  52 |  53 |  54 |  55 |  56 |  57 |  58 | 59
   5  ||  60 |  61 |  62 |  63 |  64 |  65 |  66 |  67 |  68 |  69 |  70 | 71
   6  ||  72 |  73 |  74 |  75 |  76 |  77 |  78 |  79 |  80 |  81 |  82 | 83
   7  ||  84 |  85 |  86 |  87 |  88 |  89 |  90 |  91 |  92 |  93 |  94 | 95
   8  ||  96 |  97 |  98 |  99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107
   9  || 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119
  10  || 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |


                                BIBLIOGRAPHY

  "MIDI Systems and Control" Francis Rumsey  1990 Focal Press

  "MIDI and Sound Book for the Atari ST" Bernd Enders and Wolfgang Klemme
          1989 M&T Publishing, Inc.

  MIDI file specs and general MIDI specs were also obtained by sending e-mail
         to LISTSERV@AUVM.AMERICAN.EDU with the phrase GET MIDISPEC PACKAGE
         in the message.


------------------------------- DEC.CPP ------------------------------------

/*  file  dec.cpp

by  Dustin Caldwell    (dustin@gse.utah.edu)




#include <dos.h>
#include <stdio.h>
#include <stdlib.h>

void helpdoc();

main()
{
        FILE *fp;

        unsigned char ch, c;

        if((fp=fopen(_argv[1], "rb"))==NULL)            /* open file to read */
        {
                printf("cannot open file %s\n",_argv[1]);
                helpdoc();
                exit(-1);
        }

        c=0;
        ch=fgetc(fp);

        while(!feof(fp))                        /* loop for whole file */
        {
                printf("%u\t", ch);             /* print every byte's decimal equiv. */
                c++;
                if(c>8)                                 /* print 8 numbers to a line */
                {
                        c=0;
                        printf("\n");
                }

                ch=fgetc(fp);
        }

        fclose(fp);                     /* close up */
}

void helpdoc()                  /* print help message */
{
        printf("\n   Binary File Decoder\n\n");

        printf("\n Syntax:  dec binary_file_name\n\n");

        printf("by Dustin Caldwell  (dustin@gse.utah.edu)\n\n");
        printf("This is a filter program that reads a binary file\n");
        printf("and prints the decimal equivalent of each byte\n");
        printf("tab-separated. This is mostly useful when piped \n");
        printf("into another file to be edited manually.  eg:\n\n");
        printf("c:\>dec sonata3.mid > son3.txt\n\n");
        printf("This will create a file called son3.txt which can\n");
        printf("be edited with any ascii editor. \n\n");
        printf("(rec.exe may also be useful, as it reencodes the \n");
        printf("ascii text file).\n\n");
        printf("Have Fun!!\n");
}

---------------------------- REC.CPP ----------------------------------

/*  File  rec.cpp
        by Dustin Caldwell   (dustin@gse.utah.edu)


#include <dos.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>

void helpdoc();

main()
{
        FILE *rfp, *wfp;

        unsigned char ch, c;
        char s[20];

        if((rfp=fopen(_argv[1], "r"))==NULL)                    /* open the read file */
        {
                printf("cannot open file %s \n",_argv[1]);
                helpdoc();
                exit(-1);
        }

        if((wfp=fopen(_argv[2], "wb"))==NULL)                   /* open the write file */
        {
                printf("cannot open file %s \n",_argv[1]);
                helpdoc();
                exit(-1);
        }

        c=0;

        ch=fgetc(rfp);

        while(!feof(rfp))                       /* loop for whole file */
        {

                if(isalnum(ch))                 /* only 'see' valid ascii chars */
                {
                        c=0;
                        while(isdigit(ch))      /* only use decimal digits (0-9) */
                        {
                                s[c]=ch;        /* build a string containing the number */
                                c++;
                                ch=fgetc(rfp);
                        }
                        s[c]=NULL;                      /* must have NULL terminator */

                        fputc(atoi(s), wfp);/* write the binary equivalent to file */

                }

                ch=fgetc(rfp);                  /* loop until next number starts */


        }

        fclose(rfp);                    /* close up */
        fclose(wfp);
}


void helpdoc()          /* print help message */
{
        printf("\n   Text File Encoder\n\n");

        printf("\n Syntax:  rec text_file_name binary_file_name\n\n");

        printf("by Dustin Caldwell  (dustin@gse.utah.edu)\n\n");
        printf("This is a program that reads an ascii tab-\n");
        printf("delimited file and builds a binary file where\n");
        printf("each byte of the binary file is one of the decimal\n");
        printf("digits in the text file.\n");
        printf(" eg:\n\n");
        printf("c:\>rec son3.txt son3.mid\n\n");
        printf("(This will create a file called son3.mid which is\n");
        printf("a valid binary file)\n\n");
        printf("(dec.exe may also be useful, as it decodes binary files)\n\n");
        printf("Have Fun!!\n");
}

----------------------------- MIDIFILE.PS ---------------------------------
%-12345X@PJL ENTER LANGUAGE = POSTSCRIPT
%!PS-Adobe
/wpdict 120 dict def
wpdict  begin
/bdef   {bind def} bind def

/bflg   false def
/Bfont  0 def
/bon    false def

/psz    0 def
/_S     /show load def
/_t     {0 rmoveto} bdef

/_pixelsnap
        {transform .25 sub round .25 add
         exch .25 sub round .25 add exch itransform
        } bdef
/_pixeldsnap
        { dtransform round exch round exch idtransform } bdef

/_lt    {_pixelsnap lineto} bdef
/_rlt   {_pixeldsnap rlineto} bdef
/_mt    {_pixelsnap moveto} bdef
/_rmt   {_pixeldsnap rmoveto} bdef

/bshow  {gsave psz 30 div 0 _rmt dup show grestore show} bdef

/DUx    0 def
/DUy    0 def
/hscl   0 def

/M      {_mt
         2 mul -2 2
         { -2 roll 0 _rmt _S } for
        } bdef

/makeoutl
        { dup /OutlineFlag known not
          { dup dup length 2 add dict begin
            {1 index /FID ne { def }{ pop pop } ifelse } forall
            /UniqueID known {/UniqueID UniqueID 10000 add def} if
            /PaintType PaintType 0 eq { 2 }{ PaintType } ifelse def
            /StrokeWidth 15 def
            /OutlineFlag true def
            /OutlineFont currentdict end definefont
          } if
        } bdef

/nbuff  50 string def
/orntsv 0 def
/plen   0 def
/pwid   0 def
/picstr 1 string def

/WPencoding StandardEncoding 256 array copy def 0
 [ 127/Aacute/Acircumflex/Adieresis/Agrave/Aring/Atilde/Ccedilla
  /Delta/Eacute/Ecircumflex/Edieresis/Egrave/Eth/Gamma/Iacute
  /Icircumflex/Idieresis/Igrave/Lambda/Ntilde/Oacute
  /Ocircumflex/Odieresis/Ograve/Omega/Otilde/Phi/Pi/Psi
  /Scaron/Sigma/TeXtext32/Theta/Thorn
  209/Uacute/Ucircumflex/Udieresis/Ugrave/Upsilon/Xi/Yacute
  /Ydieresis/Zcaron/aacute/acircumflex/adieresis/agrave
  /aring/atilde/brokenbar
  228/ccedilla/copyright/degree/divide
  236/dotlessj/eacute/ecircumflex/edieresis/egrave
  242/eth/ff/ffi
  246/ffl/iacute
  252/icircumflex/idieresis/igrave/logicalnot
  1/minus/mu/multiply/ntilde/oacute/ocircumflex/odieresis
  /ograve/onehalf/onequarter/onesuperior/otilde/plusminus
  /registered/scaron/thorn/threequarters/threesuperior
  /trademark/twosuperior/uacute/ucircumflex/udieresis
  /ugrave/yacute/ydieresis/zcaron
]
{ dup type /nametype eq
  { WPencoding 2 index 2 index put pop 1 add }
  { exch pop } ifelse
} forall pop

/reencode
{ dup FontDirectory exch known
   { findfont }
   {  dup nbuff cvs dup length 1 sub get 82 eq
   {dup nbuff cvs dup length 1 sub 0 exch getinterval
   findfont begin
   currentdict dup length dict begin
{ 1 index /FID ne {def} {pop pop} ifelse } forall
/FontName exch def

/Encoding WPencoding def
currentdict dup end end
/FontName get exch definefont
     }
     { findfont } ifelse
  } ifelse
} bdef

/WPDLencoding StandardEncoding 256 array copy def 0
[ 127     /SA420000/SD630000/SF010000/SF020000/SF030000
/SF040000/SF050000/SF060000/SF070000/SF080000/SF090000
/SF100000/SF110000/SF140000/SF150000/SF160000/SF190000
/SF200000/SF210000/SF220000/SF230000/SF240000/SF250000/SF260000
/SF270000/SF280000/SF360000/SF370000/SF380000/SF390000/SF400000
/SF410000/SF420000/SF430000
209 /SF440000/SF450000/SF460000/SF470000/SF480000
/SF490000/SF500000/SF510000/SF520000/SF530000/SF540000
/SF570000/SF580000/SF590000/SF600000/SF610000
228 /SM570001/SM590000/SM600000/SM630000
236 /SM680000/SM690000/SM700000/SM750000/SM750002
242 /SM770000/SM790000/SP320000
246 /SS000000/SS010000
252 /SS260000/SS270000/SV040000/apostrophereverse
1/arrowboth/arrowdown/arrowleft/arrowright/arrowup/club
/deutschmark/diamond/diamondopen/exclamdbl/female
/fiveeighths/franc/heart/male/musicalnote/musicalnotedbl
/napostrophe/nsuperior/oneeighths/seveneighths/spade
/threeeights/underscoredbl/SM760000
]
{ dup type /nametype eq
  { WPDLencoding 2 index 2 index put pop 1 add }
  { exch pop } ifelse
} forall pop

/reencodeL
    { dup FontDirectory exch known
      { findfont }
      {  dup nbuff cvs dup length 1 sub get 76 eq
         {    dup nbuff cvs dup length 1 sub 0 exch getinterval
         findfont begin
         currentdict dup length dict begin
         { 1 index /FID ne {def} {pop pop} ifelse } forall
         /FontName exch def
         /Encoding WPDLencoding def
         currentdict dup end end
         /FontName get exch definefont
         }
         { findfont } ifelse
      } ifelse
    } bdef

/ron    false def
/sflg   false def
/slan   0 def
/sp     32 def

/sshow
        { save exch
           gsave
            psz 20 div dup neg _rmt dup show
           grestore
           dup
           save exch
            Bfont setfont
            1 setgray show
           restore
           currentfont makeoutl setfont show
           currentpoint 3 -1 roll
          restore _mt
        } bdef

/Sx     0 def
/Sy     0 def
/Ux     0 def
/Uy     0 def
/W      /widthshow load def

/_B     {/bflg true def
         sflg not {/_S /bshow load def /bon true def} if
        } bdef
/_b     {/bflg false def
         bon {/_S /show load def /bon false def} if
        } bdef
/_bd    {save} bdef
/_bp    {save 2 setmiterlimit .06 .06 scale 0 0 _mt} bdef
/_ccprocs
        {/proc2 exch cvlit def
         /proc1 exch cvlit def
         /newproc proc1 length proc2 length add
         array def
         newproc 0 proc1 putinterval
         newproc proc1 length proc2 putinterval
         newproc cvx
        } def
/_clr   {3 {255 div 3 1 roll} repeat
         ron {6 3 roll pop pop pop} {setrgbcolor} ifelse
        } bdef
/_cp    /closepath load def
/_cw    {stroke initclip _mt 0 2 index
         _rlt 0 _rlt 0 exch neg
         _rlt clip newpath
        } bdef
/_d     /setdash load def
/_DU    {currentpoint /DUy exch def /DUx exch def} bdef
/_du    {gsave
          save
          8 setlinewidth
      currentpoint -30 add _mt
      DUx DUy -30 add _lt stroke
          restore
          8 setlinewidth
          currentpoint -50 add _mt
          DUx DUy -50 add _lt stroke
         grestore
        } bdef
/_ed    {restore} bdef
/_ep    {restore showpage 0 0 _mt} bdef
/_f     /eofill load def
/_ff    { exch reencode exch
          3 div dup /psz exch def
          scalefont dup /Bfont exch def setfont
        } bdef
/_ffs   { /slan exch 10 div def /hscl exch 1000 div def
          /psz exch 3 div def
          [ psz hscl mul 0 slan dup sin exch cos div psz mul psz 0 0 ]
          exch reencode exch makefont dup /Bfont exch def setfont
        } bdef
/_g     /setgray load def
/_gs    {neg 100 add 100 div setgray} bdef
/_i     {gsave
          dup /picstr exch 7 add 8 idiv string def
          3 1 roll translate dup 1 scale
          dup 1 1 [5 -1 roll 0 0 1 0 0]
          {currentfile picstr readhexstring pop} image
         grestore
        } bdef
/_is    {save 4 1 roll
          dup /picstr exch 7 add 8 idiv string def
          3 1 roll translate dup 1 scale
          dup 1 1 [5 -1 roll 0 0 1 0 0]
          {currentfile picstr readhexstring pop} image
         restore
        } bdef
/_ie    {1 eq { {1 exch sub} currenttransfer _ccprocs settransfer} if
         /_isx exch def /_isy exch def
         _isx mul exch _isy mul translate
         add 2 div /_txc exch def
         add 2 div /_tyc exch def
         _txc _isx mul _tyc _isy mul translate
         360 exch sub rotate
         1 eq { _isx neg _isy scale }
         { _isx _isy scale }
         ifelse _txc neg _tyc neg translate
        } bdef
/_irms  {save
          12 1 roll
          1 eq {{1 exch sub} currenttransfer _ccprocs settransfer} if
          /picstr exch string def translate
          2 index 6 index sub 2 div 2 index 6 index sub 2 div neg
          translate
          5 index 5 index 2 div neg exch 2 div exch
          2 copy neg exch neg exch 5 2 roll translate
          360 exch sub rotate
          3 index 3 index 7 index div exch 8 index div exch scale
          translate pop pop 2 index 2 index scale
          3 index 0 eq
          { [ 3 index 0 0 5 index neg 0 0 ] }
          { 3 index 1 eq
      { [ 3 index 0 0 5 index 0 7 index ] }
      { 3 index 128 eq
          { [ 3 index neg 0 0 5 index neg 7 index 0 ] }
          { [ 3 index neg 0 0 5 index 7 index 7 index ] } ifelse
            } ifelse
          } ifelse
          {currentfile picstr readhexstring pop} image
          pop
         restore
        } bdef

/_l     {_lt} bdef
/_lr    {_rlt} bdef
/_m     {_mt} bdef
/_O     {currentfont makeoutl setfont} bdef
/_o     {Bfont setfont} bdef
/_ornt  {/pwid exch def /plen exch def
         orntsv 1 eq {0 pwid translate -90 rotate} if
         orntsv 2 eq {pwid plen translate 180 rotate} if
         orntsv 3 eq {plen 0 translate 90 rotate} if
         dup 1 eq {pwid 0 translate 90 rotate} if
         dup 2 eq {pwid plen translate 180 rotate} if
         dup 3 eq {0 plen translate -90 rotate} if
         /orntsv exch def
        } bdef
/_lod1  {currentpoint orntsv plen pwid 6 -1 roll restore save} bdef
/_lod2  {_bp 7 2 roll _ornt _mt} bdef
/_unlod {currentpoint orntsv plen pwid 7 -2 roll restore restore
         _bp 6 1 roll _ornt _mt
        } bdef
/_p     {2 copy _mt 1 0 _rlt _mt} bdef
/_pl    {{_lt} repeat} bdef
/_R      { /ron true def /_S /_rshow load def /_t /_red load def} bdef
/_rshow  { save exch
           currentpoint
           /RSy exch def /RSx exch def
           ron {
                 sflg
                 {      currentpoint
                        /Ry exch def /Rx exch def
                        dup stringwidth pop Rx Ry psz 4 div add _mt
                        Rx psz 15 add setlinewidth .95 setgray 0 setlinecap
                        add Ry psz 4 div add _lt stroke Rx Ry _mt 0 0 0 setrgbcolor
                        dup show Rx Ry _mt
                        sshow
                 }
                 { _redshow
                 }ifelse
           }
           {     sflg {sshow} if
           }ifelse
           currentpoint 3 -1 roll
           restore _mt
         } bdef
/_red   { gsave dup
         currentpoint /Ry exch def /Rx exch def
         Rx Ry psz 4 div add _mt
         Rx psz 15 add setlinewidth .95 setgray 0 setlinecap
         add Ry psz 4 div add _lt stroke
         Rx Ry _mt
         grestore
         0 rmoveto
    }bdef
/_redshow {currentpoint
         /Ry exch def /Rx exch def
         dup stringwidth pop Rx Ry psz 4 div add _mt
         Rx psz 15 add setlinewidth .95 setgray 0 setlinecap
         add Ry psz 4 div add _lt stroke Rx Ry _mt 0 0 0 setrgbcolor
         show currentpoint _mt
    }bdef
/_rmxy  {_rmt} bdef
/_s     /stroke load def
/_SH    bon {/bon false def} if
        {/sflg true def /_S /_rshow load def
        } bdef
/_sh    { ron   {/sflg false def bflg {_B} if}
                {/_S /show load def /sflg false def bflg {_B} if}ifelse
        }bdef
/_sp    { gsave stroke grestore } bdef
/_ST    {currentpoint /Sy exch def /Sx exch def} bdef
/_st    {gsave
          currentpoint pop
          Sx dup Sy _mt sub
          (\320) stringwidth pop div
          dup floor cvi dup
      dup 0 gt {{(\320) show} repeat}{pop} ifelse sub
          dup 0 gt {1 scale (\320) show}{pop} ifelse
         grestore
        } bdef
/_U     {currentpoint /Uy exch def /Ux exch def} bdef
/_u     {gsave
          currentpoint
          -30 add _mt
          Ux Uy -30 add _lt
          12 setlinewidth
          stroke
         grestore
        } bdef
/_w     /setlinewidth load def
end
/#copies 1 def /wpdict2 100 dict def
wpdict begin wpdict2 begin

_bd
/_rhs{readhexstring}bdef/_tr{translate}bdef
/_ix{index}bdef/_mx{matrix}bdef
/ife{ifelse}bdef/_x{exch}bdef
/_is{save 4 1 roll
dup/picstr _x 7 add 8 idiv string def
3 1 roll _tr dup 1 scale
dup 1 1[5 -1 roll 0 0 1 0 0]
{currentfile picstr _rhs pop}image restore}bdef
/_epsi{1 eq{{1 _x sub}currenttransfer _ccprocs settransfer}if
/yp _x def/xp _x def/dhgt _x def/dwid _x def
4 copy sub/swid _x def
sub/shgt _x def
add 2 div/icx _x def add 2 div/icy _x def
xp dwid 2 div add icx sub yp dhgt 2 div sub
icy sub _tr icx icy _tr
360 _x sub rotate
dwid swid div/xsc _x def _x
dhgt shgt div/ysc _x def _x
dup 1 eq{xsc neg/xsc _x def pop}
{dup 2 eq{ysc neg /ysc _x def pop}
{3 eq{ysc neg/ysc _x def xsc neg/xsc _x def}
{}ife}ife}ife
xsc ysc scale
100 div _x 100 div _x scale
icx neg icy neg _tr}bdef
/_c{3{255 div 3 1 roll}repeat setrgbcolor}bdef
/eq3{3 copy 2 _ix eq{eq{true}{false}ife}{pop
pop false}ife}bdef
/g{255 div setgray}bdef
/_clr{ron{6 3 roll pop pop pop}{eq3{pop
pop g}{_c}ife}ife}bdef
/_r{/ron false def eq3{1 sub neg g pop
pop}{setrgbcolor}ife}bdef
/_ircms{save 15 1 roll
1 eq{{1 exch sub}currenttransfer _ccprocs settransfer}if
/pstr _x string def _tr
/Cli _x def/USy _x def/USx _x def/Rot _x def/HTd _x def
/WDd _x def/Bdep _x def/HTs _x def/WDs _x def/MIR _x def
USx 100 div USy 100 div scale
WDd WDs sub 2 div HTd HTs sub 2 div neg _tr
WDs HTs 2 div neg _x 2 div _x _tr
Rot 360 _x sub rotate WDd HTd HTs div _x WDs div _x scale
WDs 2 div neg HTs 2 div _tr
WDs HTs scale WDs HTs Bdep MIR 0
eq{[WDs 0 0 HTs neg 0 0]}{MIR 1 eq{[WDs 0 0 HTs 0 HTs]}
{MIR 128 eq{[WDs neg 0 0 HTs neg WDs 0]}
{[WDs neg 0 0 HTs WDs HTs]}ife}ife}ife
{currentfile pstr _rhs pop}Cli
0 eq{image}{false 3 colorimage}ife
restore}bdef
/_bp{save 2 setlinecap 2 setmiterlimit
.06 .06 scale 0 0 moveto}bdef
/tctm _mx def/trot _mx def/tscale _mx def/rmtx _mx def
/fr{72 0 rmtx defaultmatrix dtransform
/yres _x def/xres _x def
xres dup mul yres dup mul add sqrt}bdef
/sus{/spotf _x def/sang _x def/csz _x def
/m tctm currentmatrix def/rm sang trot rotate def
/sm csz dup tscale scale def
sm rm m m concatmatrix m concatmatrix pop
1 0 m dtransform /y1 _x def/x1 _x def
/veclength x1 dup mul y1 dup mul add sqrt def
/frcy fr veclength div def /nsang y1 x1 atan def
frcy nsang/spotf load setscreen}bdef
/bitis{/ybit _x def /xbit _x def
/bval bstring ybit bwidth mul xbit 8 idiv add get def
/mask 1 7 xbit 8 mod sub bitshift def
bval mask and 0 ne}bdef
/bps{/y _x def /x _x def
/xndx x 1 add 2 div bpside mul cvi def
/yndx y 1 add 2 div bpside mul cvi def
xndx yndx bitis
{/onb onb 1 add def 1}{/ofb ofb 1 add def 0}ife}bdef
/stpatt{/csz _x def /angle _x def /bwidth _x def
/bpside _x def /bstring _x def
/onb 0 def /ofb 0 def
csz angle /bps load
sus{}settransfer
ofb ofb onb add div _g}bdef
/_fp{8 1 0 cpi stpatt}bdef
/_pf{gsave eofill grestore}bdef
/_np{newpath}bdef/_lc{setlinecap}bdef
/_sr{/cpi _x def}bdef
/nbuff 50 string def
letter _bp 0 13200 10200 _ornt /NewCenturySchlbk-RomanR 500 _ff
0 13200 10200 _ornt 
/_r      { sflg {/_t {0 rmoveto}bdef /ron false def}
         { /_S /show load def /_t {0 rmoveto}bdef /ron false def}ifelse
     }bdef
8907 11870 _m
(1)_S 3462 11439 _m
/NewCenturySchlbk-RomanR 750 _ff
(Standard)_S 83 _t
(MIDI)_S 83 _t
(File)_S 83 _t
(Format)_S /NewCenturySchlbk-RomanR 500 _ff
4469 11220 _m
(Dustin)_S 56 _t
(Caldwell)_S 1800 10820 _m
(The)_S 56 _t
(standard)_S 56 _t
(MIDI)_S 56 _t
(file)_S 56 _t
(format)_S 56 _t
(is)_S 56 _t
(a)_S 56 _t
(very)_S 56 _t
(strange)_S 56 _t
(beast.)_S 56 _t
(When)_S 56 _t
(viewed)_S 56 _t
(as)_S 56 _t
(a)_S 56 _t
(whole,)_S 56 _t
(it)_S 56 _t
(can)_S 56 _t
(be)_S 1200 10620 _m
(quite)_S 56 _t
(overwhelming.)_S 56 _t
(Of)_S 56 _t
(course,)_S 56 _t
(no)_S 56 _t
(matter)_S 56 _t
(how)_S 56 _t
(you)_S 56 _t
(look)_S 56 _t
(at)_S 56 _t
(it,)_S 56 _t
(describing)_S 56 _t
(a)_S 56 _t
(piece)_S 56 _t
(of)_S 56 _t
(music)_S 56 _t
(in)_S 56 _t
(enough)_S 1200 10420 _m
(detail)_S 56 _t
(to)_S 56 _t
(be)_S 56 _t
(able)_S 56 _t
(to)_S 56 _t
(reproduce)_S 56 _t
(it)_S 56 _t
(accurately)_S 56 _t
(is)_S 56 _t
(no)_S 56 _t
(small)_S 56 _t
(task.)_S 56 _t
(So,)_S 56 _t
(while)_S 56 _t
(complicated,)_S 56 _t
(the)_S 56 _t
(structure)_S 56 _t
(of)_S 1200 10220 _m
(the)_S 56 _t
(midi)_S 56 _t
(file)_S 56 _t
(format)_S 56 _t
(is)_S 56 _t
(fairly)_S 56 _t
(intuitive)_S 56 _t
(when)_S 56 _t
(understood.)_S 56 _t
1800 10020 _m
(I)_S 56 _t
(must)_S 56 _t
(insert)_S 56 _t
(a)_S 56 _t
(disclaimer)_S 56 _t
(here)_S 56 _t
(that)_S 56 _t
(I)_S 56 _t
(am)_S 56 _t
(by)_S 56 _t
(no)_S 56 _t
(means)_S 56 _t
(an)_S 56 _t
(expert)_S 56 _t
(with)_S 56 _t
(midi)_S 56 _t
(nor)_S 56 _t
(midi)_S 56 _t
(files.)_S 56 _t
(I)_S 1200 9820 _m
(recently)_S 56 _t
(obtained)_S 56 _t
(a)_S 56 _t
(Gravis)_S 56 _t
(UltraSound)_S 56 _t
(board)_S 56 _t
(for)_S 56 _t
(my)_S 56 _t
(PC,)_S 56 _t
(and)_S 56 _t
(upon)_S 56 _t
(hearing)_S 56 _t
(a)_S 56 _t
(few)_S 56 _t
(midi)_S 56 _t
(files)_S 56 _t
(\(.MID\))_S 1200 9620 _m
(thought,)_S 56 _t
("Gee,)_S 56 _t
(I'd)_S 56 _t
(like)_S 56 _t
(to)_S 56 _t
(be)_S 56 _t
(able)_S 56 _t
(to)_S 56 _t
(make)_S 56 _t
(my)_S 56 _t
(own)_S 56 _t
(.MID)_S 56 _t
(files.")_S 56 _t
(Well,)_S 56 _t
(many)_S 56 _t
(aggravating)_S 56 _t
(hours)_S 56 _t
(later,)_S 1200 9420 _m
(I)_S 56 _t
(discovered)_S 56 _t
(that)_S 56 _t
(this)_S 56 _t
(was)_S 56 _t
(no)_S 56 _t
(trivial)_S 56 _t
(task.)_S 56 _t
(But,)_S 56 _t
(I)_S 56 _t
(couldn't)_S 56 _t
(let)_S 56 _t
(a)_S 56 _t
(stupid)_S 56 _t
(file)_S 56 _t
(format)_S 56 _t
(stop)_S 56 _t
(me.)_S 56 _t
(\(besides,)_S 56 _t
(I)_S 1200 9220 _m
(once)_S 56 _t
(told)_S 56 _t
(my)_S 56 _t
(wife)_S 56 _t
(that)_S 56 _t
(computers)_S 56 _t
(aren't)_S 56 _t
(really)_S 56 _t
(that)_S 56 _t
(hard)_S 56 _t
(to)_S 56 _t
(use,)_S 56 _t
(and)_S 56 _t
(I'd)_S 56 _t
(hate)_S 56 _t
(to)_S 56 _t
(be)_S 56 _t
(a)_S 56 _t
(hypocrite\))_S 56 _t
(So)_S 1200 9020 _m
(if)_S 56 _t
(any)_S 56 _t
(errors)_S 56 _t
(are)_S 56 _t
(found)_S 56 _t
(in)_S 56 _t
(this)_S 56 _t
(information,)_S 56 _t
(please)_S 56 _t
(let)_S 56 _t
(me)_S 56 _t
(know)_S 56 _t
(and)_S 56 _t
(I)_S 56 _t
(will)_S 56 _t
(fix)_S 56 _t
(it.)_S 56 _t
(Also,)_S 56 _t
(this)_S 1200 8820 _m
(document's)_S 56 _t
(scope)_S 56 _t
(does)_S 56 _t
(not)_S 56 _t
(extend)_S 56 _t
(to)_S 56 _t
(EVERY)_S 56 _t
(type)_S 56 _t
(of)_S 56 _t
(midi)_S 56 _t
(command)_S 56 _t
(and)_S 56 _t
(EVERY)_S 56 _t
(possible)_S 56 _t
(file)_S 1200 8620 _m
(configuration.)_S 56 _t
(It)_S 56 _t
(is)_S 56 _t
(a)_S 56 _t
(basic)_S 56 _t
(guide)_S 56 _t
(that)_S 56 _t
(should)_S 56 _t
(enable)_S 56 _t
(the)_S 56 _t
(reader)_S 56 _t
(\(with)_S 56 _t
(a)_S 56 _t
(moderate)_S 56 _t
(investment)_S 56 _t
(in)_S 1200 8420 _m
(time\))_S 56 _t
(to)_S 56 _t
(generate)_S 56 _t
(a)_S 56 _t
(quality)_S 56 _t
(midi)_S 56 _t
(file.)_S 1200 8020 _m
(1.)_S 56 _t
(Overview)_S 1800 7620 _m
(A)_S 56 _t
(midi)_S 56 _t
(\(.MID\))_S 56 _t
(file)_S 56 _t
(contains)_S 56 _t
(basically)_S 56 _t
(2)_S 56 _t
(things,)_S 56 _t
(Header)_S 56 _t
(chunks)_S 56 _t
(and)_S 56 _t
(Track)_S 56 _t
(chunks.)_S 56 _t
(Section)_S 56 _t
(2)_S 1200 7420 _m
(explains)_S 56 _t
(the)_S 56 _t
(header)_S 56 _t
(chunks,)_S 56 _t
(and)_S 56 _t
(Section)_S 56 _t
(3)_S 56 _t
(explains)_S 56 _t
(the)_S 56 _t
(track)_S 56 _t
(chunks.)_S 56 _t
(A)_S 56 _t
(midi)_S 56 _t
(file)_S 56 _t
(contains)_S 56 _t
(ONE)_S 1200 7220 _m
(header)_S 56 _t
(chunk)_S 56 _t
(describing)_S 56 _t
(the)_S 56 _t
(file)_S 56 _t
(format,)_S 56 _t
(etc.,)_S 56 _t
(and)_S 56 _t
(any)_S 56 _t
(number)_S 56 _t
(of)_S 56 _t
(track)_S 56 _t
(chunks.)_S 56 _t
(A)_S 56 _t
(track)_S 56 _t
(may)_S 56 _t
(be)_S 1200 7020 _m
(thought)_S 56 _t
(of)_S 56 _t
(in)_S 56 _t
(the)_S 56 _t
(same)_S 56 _t
(way)_S 56 _t
(as)_S 56 _t
(a)_S 56 _t
(track)_S 56 _t
(on)_S 56 _t
(a)_S 56 _t
(multi-track)_S 56 _t
(tape)_S 56 _t
(deck.)_S 56 _t
(You)_S 56 _t
(may)_S 56 _t
(assign)_S 56 _t
(one)_S 56 _t
(track)_S 56 _t
(to)_S 1200 6820 _m
(each)_S 56 _t
(voice,)_S 56 _t
(each)_S 56 _t
(staff,)_S 56 _t
(each)_S 56 _t
(instrument)_S 56 _t
(or)_S 56 _t
(whatever)_S 56 _t
(you)_S 56 _t
(want.)_S 56 _t
1200 6420 _m
(2.)_S 56 _t
(Header)_S 56 _t
(Chunk)_S 1800 6020 _m
(The)_S 56 _t
(header)_S 56 _t
(chunk)_S 56 _t
(appears)_S 56 _t
(at)_S 56 _t
(the)_S 56 _t
(beginning)_S 56 _t
(of)_S 56 _t
(the)_S 56 _t
(file,)_S 56 _t
(and)_S 56 _t
(describes)_S 56 _t
(the)_S 56 _t
(file)_S 56 _t
(in)_S 56 _t
(three)_S 56 _t
(ways.)_S 1200 5820 _m
(The)_S 56 _t
(header)_S 56 _t
(chunk)_S 56 _t
(always)_S 56 _t
(looks)_S 56 _t
(like:)_S 1200 5420 _m
(4D)_S 56 _t
(54)_S 56 _t
(68)_S 56 _t
(64)_S 56 _t
(00)_S 56 _t
(00)_S 56 _t
(00)_S 56 _t
(06)_S 56 _t
(ff)_S 56 _t
(ff)_S 56 _t
(nn)_S 56 _t
(nn)_S 56 _t
(dd)_S 56 _t
(dd)_S 1200 5020 _m
(The)_S 56 _t
(ascii)_S 56 _t
(equivalent)_S 56 _t
(of)_S 56 _t
(the)_S 56 _t
(first)_S 56 _t
(4)_S 56 _t
(bytes)_S 56 _t
(is)_S 56 _t
(MThd.)_S 56 _t
(After)_S 56 _t
(MThd)_S 56 _t
(comes)_S 56 _t
(the)_S 56 _t
(4-byte)_S 56 _t
(size)_S 56 _t
(of)_S 56 _t
(the)_S 56 _t
(header.)_S 1200 4820 _m
(This)_S 56 _t
(will)_S 56 _t
(always)_S 56 _t
(be)_S 56 _t
(00)_S 56 _t
(00)_S 56 _t
(00)_S 56 _t
(06,)_S 56 _t
(because)_S 56 _t
(the)_S 56 _t
(actual)_S 56 _t
(header)_S 56 _t
(information)_S 56 _t
(will)_S 56 _t
(always)_S 56 _t
(be)_S 56 _t
(6)_S 56 _t
(bytes.)_S 56 _t
1200 4420 _m
(ff)_S 56 _t
(ff)_S 56 _t
(is)_S 56 _t
(the)_S 56 _t
(file)_S 56 _t
(format.)_S 56 _t
(There)_S 56 _t
(are)_S 56 _t
(3)_S 56 _t
(formats:)_S 1200 4020 _m
(0)_S 56 _t
(-)_S 56 _t
(single-track)_S 56 _t
1200 3820 _m
(1)_S 56 _t
(-)_S 56 _t
(multiple)_S 56 _t
(tracks,)_S 56 _t
(synchronous)_S 1200 3620 _m
(2)_S 56 _t
(-)_S 56 _t
(multiple)_S 56 _t
(tracks,)_S 56 _t
(asynchronous)_S 1200 3220 _m
(Single)_S 56 _t
(track)_S 56 _t
(is)_S 56 _t
(fairly)_S 56 _t
(self-explanatory)_S 56 _t
(-)_S 56 _t
(one)_S 56 _t
(track)_S 56 _t
(only.)_S 56 _t
(Synchronous)_S 56 _t
(multiple)_S 56 _t
(tracks)_S 56 _t
(means)_S 56 _t
(that)_S 56 _t
(the)_S 1200 3020 _m
(tracks)_S 56 _t
(will)_S 56 _t
(all)_S 56 _t
(be)_S 56 _t
(vertically)_S 56 _t
(synchronous,)_S 56 _t
(or)_S 56 _t
(in)_S 56 _t
(other)_S 56 _t
(words,)_S 56 _t
(they)_S 56 _t
(all)_S 56 _t
(start)_S 56 _t
(at)_S 56 _t
(the)_S 56 _t
(same)_S 56 _t
(time,)_S 56 _t
(and)_S 56 _t
(so)_S 1200 2820 _m
(can)_S 56 _t
(represent)_S 56 _t
(different)_S 56 _t
(parts)_S 56 _t
(in)_S 56 _t
(one)_S 56 _t
(song.)_S 56 _t
(Asynchronous)_S 56 _t
(multiple)_S 56 _t
(tracks)_S 56 _t
(do)_S 56 _t
(not)_S 56 _t
(necessarily)_S 56 _t
(start)_S 56 _t
(at)_S 1200 2620 _m
(the)_S 56 _t
(same)_S 56 _t
(time,)_S 56 _t
(and)_S 56 _t
(can)_S 56 _t
(be)_S 56 _t
(completely)_S 56 _t
(asynchronous.)_S 56 _t
1200 2220 _m
(nn)_S 56 _t
(nn)_S 56 _t
(is)_S 56 _t
(the)_S 56 _t
(number)_S 56 _t
(of)_S 56 _t
(tracks)_S 56 _t
(in)_S 56 _t
(the)_S 56 _t
(midi)_S 56 _t
(file.)_S 1200 1820 _m
(dd)_S 56 _t
(dd)_S 56 _t
(is)_S 56 _t
(the)_S 56 _t
(number)_S 56 _t
(of)_S 56 _t
(delta-time)_S 56 _t
(ticks)_S 56 _t
(per)_S 56 _t
(quarter)_S 56 _t
(note.)_S 56 _t
(\(More)_S 56 _t
(about)_S 56 _t
(this)_S 56 _t
(later\))_S _ep
_bp /NewCenturySchlbk-RomanR 500 _ff
0 13200 10200 _ornt 
/_r      { sflg {/_t {0 rmoveto}bdef /ron false def}
         { /_S /show load def /_t {0 rmoveto}bdef /ron false def}ifelse
     }bdef
8907 11870 _m
(2)_S 1200 11503 _m
(3.)_S 56 _t
(Track)_S 56 _t
(Chunks)_S 1200 11103 _m
(The)_S 56 _t
(remainder)_S 56 _t
(of)_S 56 _t
(the)_S 56 _t
(file)_S 56 _t
(after)_S 56 _t
(the)_S 56 _t
(header)_S 56 _t
(chunk)_S 56 _t
(consists)_S 56 _t
(of)_S 56 _t
(track)_S 56 _t
(chunks.)_S 56 _t
(Each)_S 56 _t
(track)_S 56 _t
(has)_S 56 _t
(one)_S 1200 10903 _m
(header)_S 56 _t
(and)_S 56 _t
(may)_S 56 _t
(contain)_S 56 _t
(as)_S 56 _t
(many)_S 56 _t
(midi)_S 56 _t
(commands)_S 56 _t
(as)_S 56 _t
(you)_S 56 _t
(like.)_S 56 _t
(The)_S 56 _t
(header)_S 56 _t
(for)_S 56 _t
(a)_S 56 _t
(track)_S 56 _t
(is)_S 56 _t
(very)_S 1200 10703 _m
(similar)_S 56 _t
(to)_S 56 _t
(the)_S 56 _t
(one)_S 56 _t
(for)_S 56 _t
(the)_S 56 _t
(file:)_S 1200 10303 _m
(4D)_S 56 _t
(54)_S 56 _t
(72)_S 56 _t
(6B)_S 56 _t
(xx)_S 56 _t
(xx)_S 56 _t
(xx)_S 56 _t
(xx)_S 1200 9903 _m
(As)_S 56 _t
(with)_S 56 _t
(the)_S 56 _t
(header,)_S 56 _t
(the)_S 56 _t
(first)_S 56 _t
(4)_S 56 _t
(bytes)_S 56 _t
(has)_S 56 _t
(an)_S 56 _t
(ascii)_S 56 _t
(equivalent.)_S 56 _t
(This)_S 56 _t
(one)_S 56 _t
(is)_S 56 _t
(MTrk.)_S 56 _t
(The)_S 56 _t
(4)_S 56 _t
(bytes)_S 56 _t
(after)_S 1200 9703 _m
(MTrk)_S 56 _t
(give)_S 56 _t
(the)_S 56 _t
(length)_S 56 _t
(of)_S 56 _t
(the)_S 56 _t
(track)_S 56 _t
(\(not)_S 56 _t
(including)_S 56 _t
(the)_S 56 _t
(track)_S 56 _t
(header\))_S 56 _t
(in)_S 56 _t
(bytes.)_S 56 _t
1800 9503 _m
(Following)_S 56 _t
(the)_S 56 _t
(header)_S 56 _t
(are)_S 56 _t
(midi)_S 56 _t
(events.)_S 56 _t
(These)_S 56 _t
(events)_S 56 _t
(are)_S 56 _t
(identical)_S 56 _t
(to)_S 56 _t
(the)_S 56 _t
(actual)_S 56 _t
(data)_S 56 _t
(sent)_S 1200 9303 _m
(and)_S 56 _t
(received)_S 56 _t
(by)_S 56 _t
(MIDI)_S 56 _t
(ports)_S 56 _t
(on)_S 56 _t
(a)_S 56 _t
(synth)_S 56 _t
(with)_S 56 _t
(one)_S 56 _t
(addition.)_S 56 _t
(A)_S 56 _t
(midi)_S 56 _t
(event)_S 56 _t
(is)_S 56 _t
(preceded)_S 56 _t
(by)_S 56 _t
(a)_S 56 _t
(delta-time.)_S 1200 9103 _m
(A)_S 56 _t
(delta)_S 56 _t
(time)_S 56 _t
(is)_S 56 _t
(the)_S 56 _t
(number)_S 56 _t
(of)_S 56 _t
(ticks)_S 56 _t
(after)_S 56 _t
(which)_S 56 _t
(the)_S 56 _t
(midi)_S 56 _t
(event)_S 56 _t
(is)_S 56 _t
(to)_S 56 _t
(be)_S 56 _t
(executed.)_S 56 _t
(The)_S 56 _t
(number)_S 56 _t
(of)_S 1200 8903 _m
(ticks)_S 56 _t
(per)_S 56 _t
(quarter)_S 56 _t
(note)_S 56 _t
(was)_S 56 _t
(defined)_S 56 _t
(previously)_S 56 _t
(in)_S 56 _t
(the)_S 56 _t
(file)_S 56 _t
(header)_S 56 _t
(chunk.)_S 56 _t
(This)_S 56 _t
(delta-time)_S 56 _t
(is)_S 56 _t
(a)_S 1200 8703 _m
(variable-length)_S 56 _t
(encoded)_S 56 _t
(value.)_S 56 _t
(This)_S 56 _t
(format,)_S 56 _t
(while)_S 56 _t
(confusing,)_S 56 _t
(allows)_S 56 _t
(large)_S 56 _t
(numbers)_S 56 _t
(to)_S 56 _t
(use)_S 56 _t
(as)_S 56 _t
(many)_S 1200 8503 _m
(bytes)_S 56 _t
(as)_S 56 _t
(they)_S 56 _t
(need,)_S 56 _t
(without)_S 56 _t
(requiring)_S 56 _t
(small)_S 56 _t
(numbers)_S 56 _t
(to)_S 56 _t
(waste)_S 56 _t
(bytes)_S 56 _t
(by)_S 56 _t
(filling)_S 56 _t
(with)_S 56 _t
(zeros.)_S 56 _t
(The)_S 1200 8303 _m
(number)_S 56 _t
(is)_S 56 _t
(converted)_S 56 _t
(into)_S 56 _t
(7-bit)_S 56 _t
(bytes,)_S 56 _t
(and)_S 56 _t
(the)_S 56 _t
(most-significant)_S 56 _t
(bit)_S 56 _t
(of)_S 56 _t
(each)_S 56 _t
(byte)_S 56 _t
(is)_S 56 _t
(1)_S 56 _t
(except)_S 56 _t
(for)_S 56 _t
(the)_S 1200 8103 _m
(last)_S 56 _t
(byte)_S 56 _t
(of)_S 56 _t
(the)_S 56 _t
(number,)_S 56 _t
(which)_S 56 _t
(has)_S 56 _t
(a)_S 56 _t
(msb)_S 56 _t
(of)_S 56 _t
(0.)_S 56 _t
(This)_S 56 _t
(allows)_S 56 _t
(the)_S 56 _t
(number)_S 56 _t
(to)_S 56 _t
(be)_S 56 _t
(read)_S 56 _t
(one)_S 56 _t
(byte)_S 56 _t
(at)_S 56 _t
(a)_S 1200 7903 _m
(time,)_S 56 _t
(and)_S 56 _t
(when)_S 56 _t
(you)_S 56 _t
(see)_S 56 _t
(a)_S 56 _t
(msb)_S 56 _t
(of)_S 56 _t
(0,)_S 56 _t
(you)_S 56 _t
(know)_S 56 _t
(that)_S 56 _t
(it)_S 56 _t
(was)_S 56 _t
(the)_S 56 _t
(last)_S 56 _t
(\(least)_S 56 _t
(significant\))_S 56 _t
(byte)_S 56 _t
(of)_S 56 _t
(the)_S 1200 7703 _m
(number.)_S 56 _t
(According)_S 56 _t
(to)_S 56 _t
(the)_S 56 _t
(MIDI)_S 56 _t
(spec,)_S 56 _t
(the)_S 56 _t
(entire)_S 56 _t
(delta-time)_S 56 _t
(should)_S 56 _t
(be)_S 56 _t
(at)_S 56 _t
(most)_S 56 _t
(4)_S 56 _t
(bytes)_S 56 _t
(long.)_S 56 _t
1800 7503 _m
(Following)_S 56 _t
(the)_S 56 _t
(delta-time)_S 56 _t
(is)_S 56 _t
(a)_S 56 _t
(midi)_S 56 _t
(event.)_S 56 _t
(Each)_S 56 _t
(midi)_S 56 _t
(event)_S 56 _t
(\(except)_S 56 _t
(a)_S 56 _t
(running)_S 56 _t
(midi)_S 56 _t
(event\))_S 1200 7303 _m
(has)_S 56 _t
(a)_S 56 _t
(command)_S 56 _t
(byte)_S 56 _t
(which)_S 56 _t
(will)_S 56 _t
(always)_S 56 _t
(have)_S 56 _t
(a)_S 56 _t
(msb)_S 56 _t
(of)_S 56 _t
(1)_S 56 _t
(\(the)_S 56 _t
(value)_S 56 _t
(will)_S 56 _t
(be)_S 56 _t
(>=)_S 56 _t
(128\).)_S 56 _t
(A)_S 56 _t
(list)_S 56 _t
(of)_S 56 _t
(most)_S 56 _t
(of)_S 1200 7103 _m
(these)_S 56 _t
(commands)_S 56 _t
(is)_S 56 _t
(in)_S 56 _t
(appendix)_S 56 _t
(A.)_S 56 _t
(Each)_S 56 _t
(command)_S 56 _t
(has)_S 56 _t
(different)_S 56 _t
(parameters)_S 56 _t
(and)_S 56 _t
(lengths,)_S 56 _t
(but)_S 56 _t
(the)_S 1200 6903 _m
(data)_S 56 _t
(that)_S 56 _t
(follows)_S 56 _t
(the)_S 56 _t
(command)_S 56 _t
(will)_S 56 _t
(have)_S 56 _t
(a)_S 56 _t
(msb)_S 56 _t
(of)_S 56 _t
(0)_S 56 _t
(\(less)_S 56 _t
(than)_S 56 _t
(128\).)_S 56 _t
(The)_S 56 _t
(exception)_S 56 _t
(to)_S 56 _t
(this)_S 56 _t
(is)_S 56 _t
(a)_S 1200 6703 _m
(meta-event,)_S 56 _t
(which)_S 56 _t
(may)_S 56 _t
(contain)_S 56 _t
(data)_S 56 _t
(with)_S 56 _t
(a)_S 56 _t
(msb)_S 56 _t
(of)_S 56 _t
(1.)_S 56 _t
(However,)_S 56 _t
(meta-events)_S 56 _t
(require)_S 56 _t
(a)_S 56 _t
(length)_S 1200 6503 _m
(parameter)_S 56 _t
(which)_S 56 _t
(alleviates)_S 56 _t
(confusion.)_S 56 _t
1800 6303 _m
(One)_S 56 _t
(subtlety)_S 56 _t
(which)_S 56 _t
(can)_S 56 _t
(cause)_S 56 _t
(confusion)_S 56 _t
(is)_S 56 _t
(running)_S 56 _t
(mode.)_S 56 _t
(This)_S 56 _t
(is)_S 56 _t
(where)_S 56 _t
(the)_S 56 _t
(actual)_S 56 _t
(midi)_S 1200 6103 _m
(command)_S 56 _t
(is)_S 56 _t
(omitted,)_S 56 _t
(and)_S 56 _t
(the)_S 56 _t
(last)_S 56 _t
(midi)_S 56 _t
(command)_S 56 _t
(issued)_S 56 _t
(is)_S 56 _t
(assumed.)_S 56 _t
(This)_S 56 _t
(means)_S 56 _t
(that)_S 56 _t
(the)_S 56 _t
(midi)_S 1200 5903 _m
(event)_S 56 _t
(will)_S 56 _t
(consist)_S 56 _t
(of)_S 56 _t
(a)_S 56 _t
(delta-time)_S 56 _t
(and)_S 56 _t
(the)_S 56 _t
(parameters)_S 56 _t
(that)_S 56 _t
(would)_S 56 _t
(go)_S 56 _t
(to)_S 56 _t
(the)_S 56 _t
(command)_S 56 _t
(if)_S 56 _t
(it)_S 56 _t
(were)_S 1200 5703 _m
(included.)_S 56 _t
1200 5303 _m
(4.)_S 56 _t
(Conclusion)_S 1800 4903 _m
(If)_S 56 _t
(this)_S 56 _t
(explanation)_S 56 _t
(has)_S 56 _t
(only)_S 56 _t
(served)_S 56 _t
(to)_S 56 _t
(confuse)_S 56 _t
(the)_S 56 _t
(issue)_S 56 _t
(more,)_S 56 _t
(the)_S 56 _t
(appendices)_S 56 _t
(contain)_S 1200 4703 _m
(examples)_S 56 _t
(which)_S 56 _t
(may)_S 56 _t
(help)_S 56 _t
(clarify)_S 56 _t
(the)_S 56 _t
(issue.)_S 56 _t
(Also,)_S 56 _t
(2)_S 56 _t
(utilities)_S 56 _t
(and)_S 56 _t
(a)_S 56 _t
(graphic)_S 56 _t
(file)_S 56 _t
(should)_S 56 _t
(have)_S 56 _t
(been)_S 1200 4503 _m
(included)_S 56 _t
(with)_S 56 _t
(this)_S 56 _t
(document:)_S 56 _t
1200 4103 _m
(DEC.EXE)_S 56 _t
(-)_S 56 _t
(This)_S 56 _t
(utility)_S 56 _t
(converts)_S 56 _t
(a)_S 56 _t
(binary)_S 56 _t
(file)_S 56 _t
(\(like)_S 56 _t
(.MID\))_S 56 _t
(to)_S 56 _t
(a)_S 56 _t
(tab-delimited)_S 56 _t
(text)_S 56 _t
(file)_S 56 _t
(containing)_S 56 _t
(the)_S 1200 3903 _m
(decimal)_S 56 _t
(equivalents)_S 56 _t
(of)_S 56 _t
(each)_S 56 _t
(byte.)_S 1200 3503 _m
(REC.EXE)_S 56 _t
(-)_S 56 _t
(This)_S 56 _t
(utility)_S 56 _t
(converts)_S 56 _t
(a)_S 56 _t
(tab-delimited)_S 56 _t
(text)_S 56 _t
(file)_S 56 _t
(of)_S 56 _t
(decimal)_S 56 _t
(values)_S 56 _t
(into)_S 56 _t
(a)_S 56 _t
(binary)_S 56 _t
(file)_S 56 _t
(in)_S 1200 3303 _m
(which)_S 56 _t
(each)_S 56 _t
(byte)_S 56 _t
(corresponds)_S 56 _t
(to)_S 56 _t
(one)_S 56 _t
(of)_S 56 _t
(the)_S 56 _t
(decimal)_S 56 _t
(values.)_S 1200 2903 _m
(MIDINOTE.PS)_S 56 _t
(-)_S 56 _t
(This)_S 56 _t
(is)_S 56 _t
(the)_S 56 _t
(postscript)_S 56 _t
(form)_S 56 _t
(of)_S 56 _t
(a)_S 56 _t
(page)_S 56 _t
(showing)_S 56 _t
(note)_S 56 _t
(numbers)_S 56 _t
(with)_S 56 _t
(a)_S 56 _t
(keyboard)_S 56 _t
(and)_S 1200 2703 _m
(with)_S 56 _t
(the)_S 56 _t
(standard)_S 56 _t
(grand)_S 56 _t
(staff.)_S _ep
_bp /NewCenturySchlbk-RomanR 500 _ff
0 13200 10200 _ornt 
/_r      { sflg {/_t {0 rmoveto}bdef /ron false def}
         { /_S /show load def /_t {0 rmoveto}bdef /ron false def}ifelse
     }bdef
8907 11870 _m
(3)_S 4645 11503 _m
(Appendix)_S 56 _t
(A)_S 1200 11103 _m
(1.)_S 56 _t
(MIDI)_S 56 _t
(Event)_S 56 _t
(Commands)_S 1200 10703 _m
(Each)_S 56 _t
(command)_S 56 _t
(byte)_S 56 _t
(has)_S 56 _t
(2)_S 56 _t
(parts.)_S 56 _t
(The)_S 56 _t
(left)_S 56 _t
(nybble)_S 56 _t
(\(4)_S 56 _t
(bits\))_S 56 _t
(contains)_S 56 _t
(the)_S 56 _t
(actual)_S 56 _t
(command,)_S 56 _t
(and)_S 56 _t
(the)_S 1200 10503 _m
(right)_S 56 _t
(nybble)_S 56 _t
(contains)_S 56 _t
(the)_S 56 _t
(midi)_S 56 _t
(channel)_S 56 _t
(number)_S 56 _t
(on)_S 56 _t
(which)_S 56 _t
(the)_S 56 _t
(command)_S 56 _t
(will)_S 56 _t
(be)_S 56 _t
(executed.)_S 56 _t
(There)_S 56 _t
(are)_S 1200 10303 _m
(16)_S 56 _t
(midi)_S 56 _t
(channels,)_S 56 _t
(and)_S 56 _t
(8)_S 56 _t
(midi)_S 56 _t
(commands)_S 56 _t
(\(the)_S 56 _t
(command)_S 56 _t
(nybble)_S 56 _t
(must)_S 56 _t
(have)_S 56 _t
(a)_S 56 _t
(msb)_S 56 _t
(of)_S 56 _t
(1\).)_S 1200 10103 _m
(In)_S 56 _t
(the)_S 56 _t
(following)_S 56 _t
(table,)_S 56 _t
(x)_S 56 _t
(indicates)_S 56 _t
(the)_S 56 _t
(midi)_S 56 _t
(channel)_S 56 _t
(number.)_S 56 _t
(Note)_S 56 _t
(that)_S 56 _t
(all)_S 56 _t
(data)_S 56 _t
(bytes)_S 56 _t
(will)_S 56 _t
(be)_S 56 _t
(<128)_S 1200 9903 _m
(\(msb)_S 56 _t
(set)_S 56 _t
(to)_S 56 _t
(0\).)_S 1200 9503 _m
_U (Hex)_S 2109 9503 _m
(Binary)_S 3422 9503 _m
(Data)_S 4836 9503 _m
(Description)_S _u 1200 9303 _m
(8x)_S 2109 9303 _m
(1000xxxx)_S 3422 9303 _m
(nn)_S 56 _t
(vv)_S 4836 9303 _m
(Note)_S 56 _t
(off)_S 56 _t
(\(key)_S 56 _t
(is)_S 56 _t
(released\))_S 4836 9103 _m
(nn=note)_S 56 _t
(number)_S 4836 8903 _m
(vv=velocity)_S 1200 8503 _m
(9x)_S 2109 8503 _m
(1001xxxx)_S 3422 8503 _m
(nn)_S 56 _t
(vv)_S 4836 8503 _m
(Note)_S 56 _t
(on)_S 56 _t
(\(key)_S 56 _t
(is)_S 56 _t
(pressed\))_S 4836 8303 _m
(nn=note)_S 56 _t
(number)_S 4836 8103 _m
(vv=velocity)_S 1200 7703 _m
(Ax)_S 2109 7703 _m
(1010xxxx)_S 3422 7703 _m
(nn)_S 56 _t
(vv)_S 4836 7703 _m
(Key)_S 56 _t
(after-touch)_S 4836 7503 _m
(nn=note)_S 56 _t
(number)_S 4836 7303 _m
(vv=velocity)_S 1200 6903 _m
(Bx)_S 2109 6903 _m
(1011xxxx)_S 3422 6903 _m
(cc)_S 56 _t
(vv)_S 4836 6903 _m
(Control)_S 56 _t
(Change)_S 4836 6703 _m
(cc=controller)_S 56 _t
(number)_S 4836 6503 _m
(vv=new)_S 56 _t
(value)_S 1200 6103 _m
(Cx)_S 2109 6103 _m
(1100xxxx)_S 3422 6103 _m
(pp)_S 4836 6103 _m
(Program)_S 56 _t
(\(patch\))_S 56 _t
(change)_S 4836 5903 _m
(pp=new)_S 56 _t
(program)_S 56 _t
(number)_S 1200 5503 _m
(Dx)_S 2109 5503 _m
(1101xxxx)_S 3422 5503 _m
(cc)_S 4836 5503 _m
(Channel)_S 56 _t
(after-touch)_S 4836 5303 _m
(cc=channel)_S 56 _t
(number)_S 1200 4903 _m
(Ex)_S 2109 4903 _m
(1110xxxx)_S 3422 4903 _m
(bb)_S 56 _t
(tt)_S 4836 4903 _m
(Pitch)_S 56 _t
(wheel)_S 56 _t
(change)_S 56 _t
(\(2000H)_S 56 _t
(is)_S 56 _t
(normal)_S 56 _t
(or)_S 56 _t
(no)_S 56 _t
(change\))_S 4836 4703 _m
(bb=bottom)_S 56 _t
(\(least)_S 56 _t
(sig\))_S 56 _t
(7)_S 56 _t
(bits)_S 56 _t
(of)_S 56 _t
(value)_S 4836 4503 _m
(tt=top)_S 56 _t
(\(most)_S 56 _t
(sig\))_S 56 _t
(7)_S 56 _t
(bits)_S 56 _t
(of)_S 56 _t
(value)_S _ep
_bp /NewCenturySchlbk-RomanR 500 _ff
0 13200 10200 _ornt 
/_r      { sflg {/_t {0 rmoveto}bdef /ron false def}
         { /_S /show load def /_t {0 rmoveto}bdef /ron false def}ifelse
     }bdef
8907 11870 _m
(4)_S 1200 11503 _m
(The)_S 56 _t
(following)_S 56 _t
(table)_S 56 _t
(lists)_S 56 _t
(meta-events)_S 56 _t
(which)_S 56 _t
(have)_S 56 _t
(no)_S 56 _t
(midi)_S 56 _t
(channel)_S 56 _t
(number.)_S 56 _t
(They)_S 56 _t
(are)_S 56 _t
(of)_S 56 _t
(the)_S 56 _t
(format:)_S 1200 11103 _m
(FF)_S 56 _t
(xx)_S 56 _t
(nn)_S 56 _t
(dd)_S 1200 10703 _m
(All)_S 56 _t
(meta-events)_S 56 _t
(start)_S 56 _t
(with)_S 56 _t
(FF)_S 56 _t
(followed)_S 56 _t
(by)_S 56 _t
(the)_S 56 _t
(command)_S 56 _t
(\(xx\),)_S 56 _t
(the)_S 56 _t
(length,)_S 56 _t
(or)_S 56 _t
(number)_S 56 _t
(of)_S 56 _t
(bytes)_S 56 _t
(that)_S 1200 10503 _m
(will)_S 56 _t
(contain)_S 56 _t
(data)_S 56 _t
(\(nn\),)_S 56 _t
(and)_S 56 _t
(the)_S 56 _t
(actual)_S 56 _t
(data)_S 56 _t
(\(dd\).)_S 1200 10103 _m
_U (Hex)_S 2109 10103 _m
(Binary)_S 3422 10103 _m
(Data)_S 4836 10103 _m
(Description)_S _u 1200 9903 _m
(00)_S 2109 9903 _m
(00000000)_S 3422 9903 _m
(nn)_S 56 _t
(ssss)_S 4836 9903 _m
(Sets)_S 56 _t
(the)_S 56 _t
(track's)_S 56 _t
(sequence)_S 56 _t
(number.)_S 4836 9703 _m
(nn=02)_S 56 _t
(\(length)_S 56 _t
(of)_S 56 _t
(2-byte)_S 56 _t
(sequence)_S 56 _t
(number\))_S 4836 9503 _m
(ssss=sequence)_S 56 _t
(number)_S 1200 9103 _m
(01)_S 2109 9103 _m
(00000001)_S 3422 9103 _m
(nn)_S 56 _t
(tt)_S 56 _t
(..)_S 4836 9103 _m
(Text)_S 56 _t
(event-)_S 56 _t
(any)_S 56 _t
(text)_S 56 _t
(you)_S 56 _t
(want.)_S 4836 8903 _m
(nn=length)_S 56 _t
(in)_S 56 _t
(bytes)_S 56 _t
(of)_S 56 _t
(text)_S 4836 8703 _m
(tt=text)_S 56 _t
(characters)_S 1200 8303 _m
(02)_S 2109 8303 _m
(00000010)_S 3422 8303 _m
(nn)_S 56 _t
(tt)_S 56 _t
(..)_S 4836 8303 _m
(Same)_S 56 _t
(as)_S 56 _t
(text)_S 56 _t
(event,)_S 56 _t
(but)_S 56 _t
(used)_S 56 _t
(for)_S 56 _t
(copyright)_S 56 _t
(info.)_S 4836 8103 _m
(nn)_S 56 _t
(tt=same)_S 56 _t
(as)_S 56 _t
(text)_S 56 _t
(event)_S 1200 7703 _m
(03)_S 2109 7703 _m
(00000011)_S 3422 7703 _m
(nn)_S 56 _t
(tt)_S 56 _t
(..)_S 4836 7703 _m
(Sequence)_S 56 _t
(or)_S 56 _t
(Track)_S 56 _t
(name)_S 4836 7503 _m
(nn)_S 56 _t
(tt=same)_S 56 _t
(as)_S 56 _t
(text)_S 56 _t
(event)_S 1200 7103 _m
(04)_S 2109 7103 _m
(00000100)_S 3422 7103 _m
(nn)_S 56 _t
(tt)_S 56 _t
(..)_S 4836 7103 _m
(Track)_S 56 _t
(instrument)_S 56 _t
(name)_S 4836 6903 _m
(nn)_S 56 _t
(tt=same)_S 56 _t
(as)_S 56 _t
(text)_S 56 _t
(event)_S 1200 6503 _m
(05)_S 2109 6503 _m
(00000101)_S 3422 6503 _m
(nn)_S 56 _t
(tt)_S 56 _t
(..)_S 4836 6503 _m
(Lyric)_S 4836 6303 _m
(nn)_S 56 _t
(tt=same)_S 56 _t
(as)_S 56 _t
(text)_S 56 _t
(event)_S 1200 5903 _m
(06)_S 2109 5903 _m
(00000110)_S 3422 5903 _m
(nn)_S 56 _t
(tt)_S 56 _t
(..)_S 4836 5903 _m
(Marker)_S 4836 5703 _m
(nn)_S 56 _t
(tt=same)_S 56 _t
(as)_S 56 _t
(text)_S 56 _t
(event)_S 1200 5303 _m
(07)_S 2109 5303 _m
(00000111)_S 3422 5303 _m
(nn)_S 56 _t
(tt)_S 56 _t
(..)_S 4836 5303 _m
(Cue)_S 56 _t
(point)_S 4836 5103 _m
(nn)_S 56 _t
(tt=same)_S 56 _t
(as)_S 56 _t
(text)_S 56 _t
(event)_S 1200 4703 _m
(2F)_S 56 _t
2109 4703 _m
(00101111)_S 3422 4703 _m
(00)_S 4836 4703 _m
(This)_S 56 _t
(event)_S 56 _t
(must)_S 56 _t
(come)_S 56 _t
(at)_S 56 _t
(the)_S 56 _t
(end)_S 56 _t
(of)_S 56 _t
(each)_S 56 _t
(track)_S 1200 4303 _m
(51)_S 2109 4303 _m
(01010001)_S 3422 4303 _m
(03)_S 56 _t
(tttttt)_S 4836 4303 _m
(Set)_S 56 _t
(tempo)_S 4836 4103 _m
(tttttt=microseconds/quarter)_S 56 _t
(note)_S 1200 3703 _m
(58)_S 2109 3703 _m
(01011000)_S 3422 3703 _m
(04)_S 56 _t
(nn)_S 56 _t
(dd)_S 56 _t
(cc)_S 56 _t
(bb)_S 4836 3703 _m
(Time)_S 56 _t
(Signature)_S 4836 3503 _m
(nn=numerator)_S 56 _t
(of)_S 56 _t
(time)_S 56 _t
(sig.)_S 4836 3303 _m
(dd=denominator)_S 56 _t
(of)_S 56 _t
(time)_S 56 _t
(sig.)_S 56 _t
(2=quarter)_S 56 _t
(3=eighth,)_S 56 _t
(etc.)_S 4836 3103 _m
(cc=number)_S 56 _t
(of)_S 56 _t
(ticks)_S 56 _t
(in)_S 56 _t
(metronome)_S 56 _t
(click)_S 4836 2903 _m
(bb=number)_S 56 _t
(of)_S 56 _t
(32nd)_S 56 _t
(notes)_S 56 _t
(to)_S 56 _t
(the)_S 56 _t
(quarter)_S 56 _t
(note)_S 1200 2503 _m
(59)_S 2109 2503 _m
(01011001)_S 3422 2503 _m
(02)_S 56 _t
(sf)_S 56 _t
(mi)_S 4836 2503 _m
(Key)_S 56 _t
(signature)_S 4836 2303 _m
(sf=sharps/flats)_S 56 _t
(\(-7=7)_S 56 _t
(flats,)_S 56 _t
(0=key)_S 56 _t
(of)_S 56 _t
(C,)_S 56 _t
(7=7)_S 56 _t
(sharps\))_S 4836 2103 _m
(mi=major/minor)_S 56 _t
(\(0=major,)_S 56 _t
(1=minor\))_S 1200 1703 _m
(7F)_S 2109 1703 _m
(01111111)_S 3422 1703 _m
(xx)_S 56 _t
(dd)_S 56 _t
(..)_S 4836 1703 _m
(Sequencer)_S 56 _t
(specific)_S 56 _t
(information)_S 4836 1503 _m
(xx=number)_S 56 _t
(of)_S 56 _t
(bytes)_S 56 _t
(to)_S 56 _t
(be)_S 56 _t
(sent)_S 4836 1303 _m
(dd=data)_S _ep
_bp /NewCenturySchlbk-RomanR 500 _ff
0 13200 10200 _ornt 
/_r      { sflg {/_t {0 rmoveto}bdef /ron false def}
         { /_S /show load def /_t {0 rmoveto}bdef /ron false def}ifelse
     }bdef
8907 11870 _m
(5)_S 1200 11303 _m
(The)_S 56 _t
(following)_S 56 _t
(table)_S 56 _t
(lists)_S 56 _t
(system)_S 56 _t
(messages)_S 56 _t
(which)_S 56 _t
(control)_S 56 _t
(the)_S 56 _t
(entire)_S 56 _t
(system.)_S 56 _t
(These)_S 56 _t
(have)_S 56 _t
(no)_S 56 _t
(midi)_S 1200 11103 _m
(channel)_S 56 _t
(number.)_S 56 _t
(\(these)_S 56 _t
(will)_S 56 _t
(generally)_S 56 _t
(only)_S 56 _t
(apply)_S 56 _t
(to)_S 56 _t
(controlling)_S 56 _t
(a)_S 56 _t
(midi)_S 56 _t
(keyboard,)_S 56 _t
(etc.\))_S 1200 10703 _m
_U (Hex)_S 2109 10703 _m
(Binary)_S 3422 10703 _m
(Data)_S 4836 10703 _m
(Description)_S _u 1200 10503 _m
(F8)_S 2109 10503 _m
(11111000)_S 4836 10503 _m
(Timing)_S 56 _t
(clock)_S 56 _t
(used)_S 56 _t
(when)_S 56 _t
(synchronization)_S 56 _t
(is)_S 56 _t
(required.)_S 1200 10103 _m
(FA)_S 2109 10103 _m
(11111010)_S 4836 10103 _m
(Start)_S 56 _t
(current)_S 56 _t
(sequence)_S 1200 9703 _m
(FB)_S 2109 9703 _m
(11111011)_S 4836 9703 _m
(Continue)_S 56 _t
(a)_S 56 _t
(stopped)_S 56 _t
(sequence)_S 56 _t
(where)_S 56 _t
(left)_S 56 _t
(off)_S 1200 9303 _m
(FC)_S 2109 9303 _m
(11111100)_S 4836 9303 _m
(Stop)_S 56 _t
(a)_S 56 _t
(sequence)_S 1200 8703 _m
(The)_S 56 _t
(following)_S 56 _t
(table)_S 56 _t
(lists)_S 56 _t
(the)_S 56 _t
(numbers)_S 56 _t
(corresponding)_S 56 _t
(to)_S 56 _t
(notes)_S 56 _t
(for)_S 56 _t
(use)_S 56 _t
(in)_S 56 _t
(note)_S 56 _t
1200 8503 _m
(on)_S 56 _t
(and)_S 56 _t
(note)_S 56 _t
(off)_S 56 _t
(commands.)_S /CourierR 500 _ff
1200 7952 _m
(Octave||)_S 2100 _t
(Note)_S 100 _t
(Numbers)_S 1200 7785 _m
100 _t
100 _t
100 _t
(#)_S 200 _t
(||)_S 1200 7618 _m
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
(||)_S 100 _t
(C)_S 300 _t
(|)_S 100 _t
(C#)_S 200 _t
(|)_S 100 _t
(D)_S 300 _t
(|)_S 100 _t
(D#)_S 200 _t
(|)_S 100 _t
(E)_S 300 _t
(|)_S 100 _t
(F)_S 300 _t
(|)_S 100 _t
(F#)_S 200 _t
(|)_S 100 _t
(G)_S 300 _t
(|)_S 100 _t
(G#)_S 200 _t
(|)_S 100 _t
(A)_S 300 _t
(|)_S 100 _t
(A#)_S 200 _t
(|)_S 100 _t
(B)_S 1200 7451 _m
(-----------------------------------------------------------------------------)_S 1200 7284 _m
100 _t
100 _t
100 _t
(0)_S 200 _t
(||)_S 300 _t
(0)_S 100 _t
(|)_S 300 _t
(1)_S 100 _t
(|)_S 300 _t
(2)_S 100 _t
(|)_S 300 _t
(3)_S 100 _t
(|)_S 300 _t
(4)_S 100 _t
(|)_S 300 _t
(5)_S 100 _t
(|)_S 300 _t
(6)_S 100 _t
(|)_S 300 _t
(7)_S 100 _t
(|)_S 300 _t
(8)_S 100 _t
(|)_S 300 _t
(9)_S 100 _t
(|)_S 200 _t
(10)_S 100 _t
(|)_S 100 _t
(11)_S 1200 7117 _m
100 _t
100 _t
100 _t
(1)_S 200 _t
(||)_S 200 _t
(12)_S 100 _t
(|)_S 200 _t
(13)_S 100 _t
(|)_S 200 _t
(14)_S 100 _t
(|)_S 200 _t
(15)_S 100 _t
(|)_S 200 _t
(16)_S 100 _t
(|)_S 200 _t
(17)_S 100 _t
(|)_S 200 _t
(18)_S 100 _t
(|)_S 200 _t
(19)_S 100 _t
(|)_S 200 _t
(20)_S 100 _t
(|)_S 200 _t
(21)_S 100 _t
(|)_S 200 _t
(22)_S 100 _t
(|)_S 100 _t
(23)_S 1200 6950 _m
100 _t
100 _t
100 _t
(2)_S 200 _t
(||)_S 200 _t
(24)_S 100 _t
(|)_S 200 _t
(25)_S 100 _t
(|)_S 200 _t
(26)_S 100 _t
(|)_S 200 _t
(27)_S 100 _t
(|)_S 200 _t
(28)_S 100 _t
(|)_S 200 _t
(29)_S 100 _t
(|)_S 200 _t
(30)_S 100 _t
(|)_S 200 _t
(31)_S 100 _t
(|)_S 200 _t
(32)_S 100 _t
(|)_S 200 _t
(33)_S 100 _t
(|)_S 200 _t
(34)_S 100 _t
(|)_S 100 _t
(35)_S 1200 6783 _m
100 _t
100 _t
100 _t
(3)_S 200 _t
(||)_S 200 _t
(36)_S 100 _t
(|)_S 200 _t
(37)_S 100 _t
(|)_S 200 _t
(38)_S 100 _t
(|)_S 200 _t
(39)_S 100 _t
(|)_S 200 _t
(40)_S 100 _t
(|)_S 200 _t
(41)_S 100 _t
(|)_S 200 _t
(42)_S 100 _t
(|)_S 200 _t
(43)_S 100 _t
(|)_S 200 _t
(44)_S 100 _t
(|)_S 200 _t
(45)_S 100 _t
(|)_S 200 _t
(46)_S 100 _t
(|)_S 100 _t
(47)_S 1200 6616 _m
100 _t
100 _t
100 _t
(4)_S 200 _t
(||)_S 200 _t
(48)_S 100 _t
(|)_S 200 _t
(49)_S 100 _t
(|)_S 200 _t
(50)_S 100 _t
(|)_S 200 _t
(51)_S 100 _t
(|)_S 200 _t
(52)_S 100 _t
(|)_S 200 _t
(53)_S 100 _t
(|)_S 200 _t
(54)_S 100 _t
(|)_S 200 _t
(55)_S 100 _t
(|)_S 200 _t
(56)_S 100 _t
(|)_S 200 _t
(57)_S 100 _t
(|)_S 200 _t
(58)_S 100 _t
(|)_S 100 _t
(59)_S 1200 6449 _m
100 _t
100 _t
100 _t
(5)_S 200 _t
(||)_S 200 _t
(60)_S 100 _t
(|)_S 200 _t
(61)_S 100 _t
(|)_S 200 _t
(62)_S 100 _t
(|)_S 200 _t
(63)_S 100 _t
(|)_S 200 _t
(64)_S 100 _t
(|)_S 200 _t
(65)_S 100 _t
(|)_S 200 _t
(66)_S 100 _t
(|)_S 200 _t
(67)_S 100 _t
(|)_S 200 _t
(68)_S 100 _t
(|)_S 200 _t
(69)_S 100 _t
(|)_S 200 _t
(70)_S 100 _t
(|)_S 100 _t
(71)_S 1200 6282 _m
100 _t
100 _t
100 _t
(6)_S 200 _t
(||)_S 200 _t
(72)_S 100 _t
(|)_S 200 _t
(73)_S 100 _t
(|)_S 200 _t
(74)_S 100 _t
(|)_S 200 _t
(75)_S 100 _t
(|)_S 200 _t
(76)_S 100 _t
(|)_S 200 _t
(77)_S 100 _t
(|)_S 200 _t
(78)_S 100 _t
(|)_S 200 _t
(79)_S 100 _t
(|)_S 200 _t
(80)_S 100 _t
(|)_S 200 _t
(81)_S 100 _t
(|)_S 200 _t
(82)_S 100 _t
(|)_S 100 _t
(83)_S 1200 6115 _m
100 _t
100 _t
100 _t
(7)_S 200 _t
(||)_S 200 _t
(84)_S 100 _t
(|)_S 200 _t
(85)_S 100 _t
(|)_S 200 _t
(86)_S 100 _t
(|)_S 200 _t
(87)_S 100 _t
(|)_S 200 _t
(88)_S 100 _t
(|)_S 200 _t
(89)_S 100 _t
(|)_S 200 _t
(90)_S 100 _t
(|)_S 200 _t
(91)_S 100 _t
(|)_S 200 _t
(92)_S 100 _t
(|)_S 200 _t
(93)_S 100 _t
(|)_S 200 _t
(94)_S 100 _t
(|)_S 100 _t
(95)_S 1200 5948 _m
100 _t
100 _t
100 _t
(8)_S 200 _t
(||)_S 200 _t
(96)_S 100 _t
(|)_S 200 _t
(97)_S 100 _t
(|)_S 200 _t
(98)_S 100 _t
(|)_S 200 _t
(99)_S 100 _t
(|)_S 100 _t
(100)_S 100 _t
(|)_S 100 _t
(101)_S 100 _t
(|)_S 100 _t
(102)_S 100 _t
(|)_S 100 _t
(103)_S 100 _t
(|)_S 100 _t
(104)_S 100 _t
(|)_S 100 _t
(105)_S 100 _t
(|)_S 100 _t
(106)_S 100 _t
(|)_S 100 _t
(107)_S 1200 5781 _m
100 _t
100 _t
100 _t
(9)_S 200 _t
(||)_S 100 _t
(108)_S 100 _t
(|)_S 100 _t
(109)_S 100 _t
(|)_S 100 _t
(110)_S 100 _t
(|)_S 100 _t
(111)_S 100 _t
(|)_S 100 _t
(112)_S 100 _t
(|)_S 100 _t
(113)_S 100 _t
(|)_S 100 _t
(114)_S 100 _t
(|)_S 100 _t
(115)_S 100 _t
(|)_S 100 _t
(116)_S 100 _t
(|)_S 100 _t
(117)_S 100 _t
(|)_S 100 _t
(118)_S 100 _t
(|)_S 100 _t
(119)_S 1200 5614 _m
100 _t
100 _t
(10)_S 200 _t
(||)_S 100 _t
(120)_S 100 _t
(|)_S 100 _t
(121)_S 100 _t
(|)_S 100 _t
(122)_S 100 _t
(|)_S 100 _t
(123)_S 100 _t
(|)_S 100 _t
(124)_S 100 _t
(|)_S 100 _t
(125)_S 100 _t
(|)_S 100 _t
(126)_S 100 _t
(|)_S 100 _t
(127)_S 100 _t
(|)_S 1200 5113 _m
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
100 _t
(BIBLIOGRAPHY)_S 1200 4779 _m
100 _t
100 _t
("MIDI)_S 100 _t
(Systems)_S 100 _t
(and)_S 100 _t
(Control")_S 100 _t
(Francis)_S 100 _t
(Rumsey)_S 200 _t
(1990)_S 100 _t
(Focal)_S 100 _t
(Press)_S 1200 4445 _m
100 _t
100 _t
("MIDI)_S 100 _t
(and)_S 100 _t
(Sound)_S 100 _t
(Book)_S 100 _t
(for)_S 100 _t
(the)_S 100 _t
(Atari)_S 100 _t
(ST")_S 100 _t
(Bernd)_S 100 _t
(Enders)_S 100 _t
(and)_S 100 _t
(Wolfgang)_S 100 _t
(Klemme)_S 2109 4278 _m
100 _t
(1989)_S 100 _t
(M&T)_S 100 _t
(Publishing,)_S 100 _t
(Inc.)_S 1200 3944 _m
100 _t
100 _t
(MIDI)_S 100 _t
(file)_S 100 _t
(specs)_S 100 _t
(and)_S 100 _t
(general)_S 100 _t
(MIDI)_S 100 _t
(specs)_S 100 _t
(were)_S 100 _t
(also)_S 100 _t
(obtained)_S 100 _t
(by)_S 100 _t
(sending)_S 100 _t
(e-mail)_S 1200 3777 _m
(to)_S 2109 3777 _m
100 _t
(LISTSERV@AUVM.AMERICAN.EDU)_S 100 _t
(with)_S 100 _t
(the)_S 100 _t
(phrase)_S 100 _t
(GET)_S 100 _t
(MIDISPEC)_S 100 _t
(PACKAGE)_S 100 _t
(in)_S 1200 3610 _m
(the)_S 100 _t
(message.)_S 1200 3276 _m
100 _t
100 _t
_ep
_ed end end
%-12345X

---------------------------- MIDINOTE.PS ----------------------------------

%!PS-Adobe-3.0 EPSF-2.0
%%Creator: Windows PSCRIPT
%%Title: KEYS.CDR from CorelDRAW!
%%BoundingBox: 19 17 594 776
%%DocumentNeededResources: (atend)
%%DocumentSuppliedResources: (atend)
%%Pages: 0
%%BeginResource: procset Win35Dict 3 1
/Win35Dict 60 dict def Win35Dict begin/bd{bind def}bind def/in{72
mul}bd/ed{exch def}bd/ld{load def}bd/tr/translate ld/gs/gsave ld/gr
/grestore ld/fPP false def/SS{fPP{/SV save def}{gs}ifelse}bd/RS{fPP{SV
restore}{gr}ifelse}bd/EJ{gsave showpage grestore}bd/#C{userdict begin
/#copies ed end}bd/FEbuf 2 string def/FEglyph(G  )def/FE{1 exch{dup
16 FEbuf cvrs FEglyph exch 1 exch putinterval 1 index exch FEglyph
cvn put}for}bd/SM{/iRes ed/cyP ed/cxPg ed/cyM ed/cxM ed 0 ne{0 cyP
72 mul 100 div tr -90 rotate}if pop}bd/CB{moveto/dy ed/dx ed dx 0 rlineto
0 dy rlineto dx neg 0 rlineto closepath clip newpath}bd end
%%EndResource
/SVDoc save def
%%EndProlog
%%BeginSetup
Win35Dict begin
%%EndSetup
SS
0 0 26 22 799 1100 300 SM
2397 3162 0 0 CB
%%BeginSetup
/AutoFlatness false def
% Options: Emulsion Up
% Options: Print Positive Output
/SepsColor false def
/ATraps false def
%%EndSetup
%%BeginProlog
%%BeginResource: procset wCorel4Dict
%Copyright (c)1992, 1993 Corel Corporation.  All rights reserved. v4.00.00
/wCorel4Dict 300 dict def wCorel4Dict begin
/bd{bind def}bind def/ld{load def}bd/xd{exch def}bd
/_ null def/rp{{pop}repeat}bd/@cp/closepath ld
/@gs/gsave ld/@gr/grestore ld/@np/newpath ld
/Tl/translate ld/$sv 0 def/@sv{/$sv save def}bd
/@rs{$sv restore}bd/spg/showpage ld/showpage{}bd
currentscreen/@dsp xd/$dsp/@dsp def/$dsa xd
/$dsf xd/$sdf false def/$SDF false def/$Scra 0 def
/SetScr/setscreen ld/setscreen{3 rp}bd/@ss{2 index 0 eq{$dsf 3 1 roll
4 -1 roll pop}if exch $Scra add exch load SetScr}bd
/$c 0 def/$m 0 def/$y 0 def/$k 0 def/$t 1 def
/$n _ def/$o 0 def/$fil 0 def/$C 0 def/$M 0 def
/$Y 0 def/$K 0 def/$T 1 def/$N _ def/$O 0 def
/$PF false def/s1c 0 def/s1m 0 def/s1y 0 def
/s1k 0 def/s1t 0 def/s1n _ def/$bkg false def
/SK 0 def/SM 0 def/SY 0 def/SC 0 def/SepMode 0 def
/CurrentInkName (Composite) def/$ink -1 def
/$op false def matrix currentmatrix/$ctm xd
/$ptm matrix def/$ttm matrix def/$stm matrix def
/$fst 128 def/$pad 0 def/$rox 0 def/$roy 0 def
/CorelDrawReencodeVect [ 16#0/grave 16#5/breve 16#6/dotaccent 16#8/ring 16#A/hungarumlaut 16#B/ogonek 16#C/caron 16#D/dotlessi
16#82/quotesinglbase/florin/quotedblbase/ellipsis/dagger/daggerdbl
16#88/circumflex/perthousand/Scaron/guilsinglleft/OE
16#91/quoteleft/quoteright/quotedblleft/quotedblright/bullet/endash/emdash
16#98/tilde/trademark/scaron/guilsinglright/oe
16#9F/Ydieresis 16#A1/exclamdown/cent/sterling/currency/yen/brokenbar/section
16#a8/dieresis/copyright/ordfeminine/guillemotleft/logicalnot/minus/registered/macron
16#b0/degree/plusminus/twosuperior/threesuperior/acute/mu/paragraph/periodcentered
16#b8/cedilla/onesuperior/ordmasculine/guillemotright/onequarter/onehalf/threequarters/questiondown
16#c0/Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla
16#c8/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex/Idieresis
16#d0/Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis/multiply
16#d8/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn/germandbls
16#e0/agrave/aacute/acircumflex/atilde/adieresis/aring/ae/ccedilla
16#e8/egrave/eacute/ecircumflex/edieresis/igrave/iacute/icircumflex/idieresis
16#f0/eth/ntilde/ograve/oacute/ocircumflex/otilde/odieresis/divide
16#f8/oslash/ugrave/uacute/ucircumflex/udieresis/yacute/thorn/ydieresis
] def AutoFlatness{/@ifl{dup currentflat exch sub 10 gt{
([Error: PathTooComplex; OffendingCommand: AnyPaintingOperator]\n)
print flush newpath exit}{currentflat 2 add setflat}ifelse}bd
/@fill/fill ld/fill{currentflat{{@fill}stopped{@ifl}{exit}ifelse
}bind loop setflat}bd/@eofill/eofill ld/eofill{currentflat{
{@eofill}stopped{@ifl}{exit}ifelse}bind loop
setflat}bd/@clip/clip ld/clip{currentflat{{@clip}stopped{@ifl}{exit}
ifelse}bind loop setflat}bd/@eoclip/eoclip ld
/eoclip{currentflat{{@eoclip}stopped{@ifl}{exit}ifelse}bind loop
setflat}bd/@stroke/stroke ld/stroke{currentflat{{@stroke}stopped{@ifl}
{exit}ifelse}bind loop setflat}bd}if/d/setdash ld
/j/setlinejoin ld/J/setlinecap ld/M/setmiterlimit ld
/w/setlinewidth ld/O{/$o xd}bd/R{/$O xd}bd
/W/eoclip ld/c/curveto ld/C/c ld/l/lineto ld
/L/l ld/rl/rlineto ld/m/moveto ld/n/newpath ld
/N/newpath ld/P{11 rp}bd/u{}bd/U{}bd/A{pop}bd
/q/@gs ld/Q/@gr ld/`{}bd/~{}bd/@{}bd/&{}bd
/@j{@sv @np}bd/@J{@rs}bd/g{1 exch sub/$k xd
/$c 0 def/$m 0 def/$y 0 def/$t 1 def/$n _ def/$fil 0 def}bd
/G{1 sub neg/$K xd _ 1 0 0 0/$C xd/$M xd/$Y xd/$T xd
/$N xd}bd/k{1 index type/stringtype eq{/$t xd
/$n xd}{/$t 0 def/$n _ def}ifelse/$k xd/$y xd
/$m xd/$c xd/$fil 0 def}bd/K{1 index type
/stringtype eq{/$T xd/$N xd}{/$T 0 def/$N _ def}ifelse
/$K xd/$Y xd/$M xd/$C xd}bd/sf{1 index type
/stringtype eq{/s1t xd/s1n xd}{/s1t 0 def
/s1n _ def}ifelse/s1k xd/s1y xd/s1m xd/s1c xd}bd
/i{dup 0 ne{setflat}{pop}ifelse}bd/v{4 -2 roll
2 copy 6 -2 roll c}bd/V/v ld/y{2 copy c}bd
/Y/y ld/@w{matrix rotate/$ptm xd matrix scale
$ptm dup concatmatrix/$ptm xd 1 eq{$ptm exch dup concatmatrix
/$ptm xd}if 1 w}bd/@g{1 eq dup/$sdf xd{/$scp xd
/$sca xd/$scf xd}if}bd/@G{1 eq dup/$SDF xd{/$SCP xd
/$SCA xd/$SCF xd}if}bd/@D{2 index 0 eq{$dsf 3 1 roll
4 -1 roll pop}if 3 copy exch $Scra add exch load
SetScr/$dsp xd/$dsa xd/$dsf xd}bd/$ngx{$SDF{$SCF
SepMode 0 eq{$SCA}{$dsa}ifelse $SCP @ss}if}bd
/p{/$pm xd 7{pop}repeat/$pyf xd/$pxf xd/$pn xd
/$fil 1 def}bd/@MN{2 copy le{pop}{exch pop}ifelse}bd
/@MX{2 copy ge{pop}{exch pop}ifelse}bd/InRange{3 -1 roll
@MN @MX}bd/wDstChck{2 1 roll dup 3 -1 roll
eq{1 add}if}bd/@dot{dup mul exch dup mul add
1 exch sub}bd/@lin{exch pop abs 1 exch sub}bd
/SetRgb/setrgbcolor ld/SetHsb/sethsbcolor ld
/GetRgb/currentrgbcolor ld/GetHsb/currenthsbcolor ld
/SetGry/setgray ld/GetGry/currentgray ld/cmyk2rgb{3{dup 5 -1 roll
add 1 exch sub dup 0 lt{pop 0}if exch}repeat
pop}bd/rgb2cmyk{3{1 exch sub 3 1 roll}repeat
3 copy @MN @MN 3{dup 5 -1 roll sub neg exch}repeat}bd
/rgb2hsb{SetRgb GetHsb}bd/hsb2rgb{3 -1 roll
dup floor sub 3 1 roll SetHsb GetRgb}bd/rgb2g{2 index .299 mul
2 index .587 mul add 1 index .114 mul add 4 1 roll
3 rp}bd/WaldoColor where{pop}{/setcmykcolor where{pop
/SetCmyk/setcmykcolor ld}{/SetCmyk{cmyk2rgb
SetRgb}bd}ifelse/currentcmykcolor where{pop
/GetCmyk/currentcmykcolor ld}{/GetCmyk{GetRgb
rgb2cmyk}bd}ifelse/setoverprint where{pop}{/setoverprint{/$op xd}bd
}ifelse/currentoverprint where{pop}{/currentoverprint{$op}bd}ifelse
/colorimage where{pop/ColorImage/colorimage ld}{/ColorImage{
/ncolors exch def pop/dataaq exch def{dataaq
ncolors dup 3 eq{/$dat exch def 0 1 $dat length
3 div 1 sub{dup 3 mul $dat 1 index get 255 div
$dat 2 index 1 add get 255 div $dat 3 index 2 add get
255 div rgb2g 255 mul cvi exch pop $dat 3 1 roll put}for
$dat 0 $dat length 3 idiv getinterval pop}{4 eq{/$dat exch def
0 1 $dat length 4 div 1 sub{dup 4 mul $dat 1 index get
255 div $dat 2 index 1 add get 255 div $dat 3 index 2 add get
255 div $dat 4 index 3 add get 255 div cmyk2rgb rgb2g 255 mul
cvi exch pop $dat 3 1 roll put}for $dat 0 $dat length
ncolors idiv getinterval}if}ifelse}image}bd}ifelse
/@tc{5 -1 roll dup 1 ge{pop}{4{dup 6 -1 roll
mul exch}repeat pop}ifelse}bd/@scc{1 eq setoverprint
dup _ eq{pop SepMode 0 eq{SetCmyk 0}{0 4 $ink sub index
exch pop 5 1 roll 4 rp SepsColor true eq{$ink 3 gt{1 sub neg dup SetGry
exch}{dup 0 0 0 4 $ink roll SetCmyk}ifelse}{1 sub neg dup SetGry}ifelse
}ifelse exch pop}{SepMode 0 eq{pop @tc SetCmyk 0}{CurrentInkName eq{
4 index}{0}ifelse 6 1 roll 5 rp 1 sub neg dup SetGry}ifelse}ifelse
SepMode 0 eq{pop true}{1 eq currentoverprint and not}ifelse}bd
/setcmykcolor{1 5 1 roll _ currentoverprint @scc
pop}bd/currentcmykcolor{0 0 0 0}bd/setrgbcolor{rgb2cmyk
setcmykcolor}bd/currentrgbcolor{currentcmykcolor
cmyk2rgb}bd/sethsbcolor{hsb2rgb setrgbcolor}bd
/currenthsbcolor{currentrgbcolor rgb2hsb}bd
/setgray{dup dup setrgbcolor}bd/currentgray{currentrgbcolor
rgb2g}bd}ifelse/WaldoColor true def/@sft{$tllx $pxf add dup $tllx gt
{$pwid sub}if/$tx xd $tury $pyf sub dup $tury lt{$phei add}if
/$ty xd}bd/@stb{pathbbox/$ury xd/$urx xd/$lly xd/$llx xd}bd
/@ep{{cvx exec}forall}bd/@tp{@sv/$in true def
2 copy dup $lly le{/$in false def}if $phei sub $ury ge{/$in false def}if
dup $urx ge{/$in false def}if $pwid add $llx le{/$in false def}if
$in{@np 2 copy m $pwid 0 rl 0 $phei neg rl $pwid neg 0 rl
0 $phei rl clip @np $pn cvlit load aload pop
7 -1 roll 5 index sub 7 -1 roll 3 index sub Tl
matrix currentmatrix/$ctm xd @ep 4 rp}{2 rp}ifelse
@rs}bd/@th{@sft 0 1 $tly 1 sub{dup $psx mul $tx add{dup $llx gt
{$pwid sub}{exit}ifelse}loop exch $phei mul
$ty exch sub 0 1 $tlx 1 sub{$pwid mul 3 copy
3 -1 roll add exch @tp pop}for 2 rp}for}bd/@tv{@sft
0 1 $tlx 1 sub{dup $pwid mul $tx add exch $psy mul $ty exch sub{
dup $ury lt{$phei add}{exit}ifelse}loop 0 1 $tly 1 sub{$phei mul
3 copy sub @tp pop}for 2 rp}for}bd/@pf{@gs $ctm setmatrix
$pm concat @stb eoclip Bburx Bbury $pm itransform
/$tury xd/$turx xd Bbllx Bblly $pm itransform
/$tlly xd/$tllx xd/$wid $turx $tllx sub def
/$hei $tury $tlly sub def @gs $vectpat{1 0 0 0 0 _ $o @scc{eofill}if}{
$t $c $m $y $k $n $o @scc{SepMode 0 eq $pfrg or{$tllx $tlly Tl
$wid $hei scale <00> 8 1 false [ 8 0 0 1 0 0 ]{}imagemask}{
/$bkg true def}ifelse}if}ifelse @gr $wid 0 gt $hei 0 gt and{
$pn cvlit load aload pop/$pd xd 3 -1 roll sub/$phei xd
exch sub/$pwid xd $wid $pwid div ceiling 1 add/$tlx xd
$hei $phei div ceiling 1 add/$tly xd $psx 0 eq{@tv}{@th}ifelse}if
@gr @np/$bkg false def}bd/@dlt{$fse $fss sub/nff xd
$frb dup 1 eq exch 2 eq or{$frt dup $frc $frm $fry $frk
@tc 4 copy cmyk2rgb rgb2hsb 3 copy/myb xd/mys xd
/myh xd $tot $toc $tom $toy $tok @tc cmyk2rgb
rgb2hsb 3 1 roll 4 1 roll 5 1 roll sub neg nff div/kdb xd
sub neg nff div/kds xd sub neg dup 0 eq{pop
$frb 2 eq{.99}{-.99}ifelse}if dup $frb 2 eq
exch 0 lt and{1 add}if dup $frb 1 eq exch 0 gt and{1 sub}if
nff div/kdh xd}{$frt dup $frc $frm $fry $frk
@tc 5 copy $tot dup $toc $tom $toy $tok @tc 5 1 roll
6 1 roll 7 1 roll 8 1 roll 9 1 roll sub neg nff div/$dk xd
sub neg nff div/$dy xd sub neg nff div/$dm xd
sub neg nff div/$dc xd sub neg nff div/$dt xd}ifelse}bd
/ffcol{5 copy $fsit 0 eq{setcmykcolor pop}{SepMode 0 ne{
4 index 1 sub neg SetGry 5 rp}{setcmykcolor pop}ifelse}ifelse}bd
/@ftl{1 index 4 index sub dup $pad mul dup/$pdw xd
2 mul sub $fst div/$wid xd 2 index sub/$hei xd
pop Tl @dlt $fss 0 eq{ffcol 0 0 m 0 $hei l $pdw $hei l
$pdw 0 l @cp fill $pdw 0 Tl}if $fss $wid mul 0 Tl
nff{ffcol 0 0 m 0 $hei l $wid $hei l $wid 0 l
@cp fill $wid 0 Tl $frb dup 1 eq exch 2 eq or{4 rp
myh mys myb kdb add 3 1 roll kds add 3 1 roll
kdh add 3 1 roll 3 copy/myb xd/mys xd/myh xd
hsb2rgb rgb2cmyk}{$dk add 5 1 roll $dy add 5 1 roll
$dm add 5 1 roll $dc add 5 1 roll $dt add 5 1 roll}ifelse}repeat
5 rp $tot dup $toc $tom $toy $tok @tc ffcol 0 0 m
0 $hei l $pdw $hei l $pdw 0 l @cp fill 5 rp}bd
/@ftr{1 index 4 index sub dup $rox mul/$row xd
2 div 1 index 4 index sub dup $roy mul/$roh xd
2 div 2 copy dup mul exch dup mul add sqrt $row dup mul
$roh dup mul add sqrt add dup/$hei xd $fst div/$wid xd
4 index add $roh add exch 5 index add $row add
exch Tl 4 rp @dlt $fss 0 eq{ffcol fill 1.0 $pad 2 mul sub
dup scale}if $hei $fss $wid mul sub/$hei xd
nff{ffcol $wid 0 m 0 0 $hei 0 360 arc fill/$hei $hei $wid sub def
$frb dup 1 eq exch 2 eq or{4 rp myh mys myb
kdb add 3 1 roll kds add 3 1 roll kdh add 3 1 roll
3 copy/myb xd/mys xd/myh xd hsb2rgb rgb2cmyk}{$dk add 5 1 roll
$dy add 5 1 roll $dm add 5 1 roll $dc add 5 1 roll
$dt add 5 1 roll}ifelse}repeat 5 rp}bd/@ftc{1 index 4 index sub
dup $rox mul/$row xd 2 div 1 index 4 index sub
dup $roy mul/$roh xd 2 div 2 copy dup mul exch dup mul add sqrt
$row dup mul $roh dup mul add sqrt add dup/$hei xd
$fst div/$wid xd 4 index add $roh add exch 5 index add $row add
exch Tl 4 rp @dlt $fss 0 eq{ffcol fill}{n}ifelse
/$dang 180 $fst 1 sub div def/$sang $dang -2 div 180 add def
/$eang $dang 2 div 180 add def/$sang $sang $dang $fss mul add def
/$eang $eang $dang $fss mul add def/$sang $eang $dang sub def
nff{ffcol $wid 0 m 0 0 $hei $sang $fan add $eang $fan add arc fill
$wid 0 m 0 0 $hei $eang neg $fan add $sang neg $fan add arc fill
/$sang $eang def/$eang $eang $dang add def
$frb dup 1 eq exch 2 eq or{4 rp myh mys myb
kdb add 3 1 roll kds add 3 1 roll kdh add 3 1 roll
3 copy/myb xd/mys xd/myh xd hsb2rgb rgb2cmyk}{$dk add 5 1 roll
$dy add 5 1 roll $dm add 5 1 roll $dc add 5 1 roll
$dt add 5 1 roll}ifelse}repeat 5 rp}bd/@ff{/$fss 0 def
1 1 $fsc 1 sub{dup 1 sub $fsit 0 eq{$fsa exch 5 mul
5 getinterval aload 2 rp/$frk xd/$fry xd/$frm xd/$frc xd
/$frn _ def/$frt 1 def $fsa exch 5 mul 5 getinterval aload pop
$fss add/$fse xd/$tok xd/$toy xd/$tom xd/$toc xd
/$ton _ def/$tot 1 def}{$fsa exch 7 mul 7 getinterval aload 2 rp
/$frt xd/$frn xd/$frk xd/$fry xd/$frm xd/$frc xd
$fsa exch 7 mul 7 getinterval aload pop $fss add/$fse xd
/$tot xd/$ton xd/$tok xd/$toy xd/$tom xd/$toc xd}ifelse
$fsit 0 eq SepMode 0 eq or dup not CurrentInkName $frn eq
and or{@sv eoclip currentflat dup 5 mul setflat
Bbllx Bblly Bburx Bbury $fty 2 eq{@ftc}{$fty 1 eq{1 index 3 index m
2 copy l 3 index 1 index l 3 index 3 index l
@cp @ftr}{1 index 3 index m 2 copy l 3 index 1 index l
3 index 3 index l @cp 4 rp $fan rotate pathbbox
@ftl}ifelse}ifelse setflat @rs/$fss $fse def}if}for
@np}bd/@Pf{@sv SepMode 0 eq $ink 3 eq or{0 J 0 j [] 0 d
$t $c $m $y $k $n $o @scc pop $ctm setmatrix
72 1000 div dup matrix scale dup concat dup Bburx exch Bbury exch
itransform ceiling cvi/Bbury xd ceiling cvi/Bburx xd
Bbllx exch Bblly exch itransform floor cvi/Bblly xd
floor cvi/Bbllx xd $Prm aload pop $Psn load exec}{1 SetGry eofill}ifelse
@rs @np}bd/F{matrix currentmatrix $sdf{$scf $sca $scp @ss}if
$fil 1 eq{@pf}{$fil 2 eq{@ff}{$fil 3 eq{@Pf}{$t $c $m $y $k $n $o
@scc{eofill}{@np}ifelse}ifelse}ifelse}ifelse
$sdf{$dsf $dsa $dsp @ss}if setmatrix}bd/f{@cp F}bd
/S{matrix currentmatrix $ctm setmatrix $SDF{$SCF $SCA $SCP @ss}if
$T $C $M $Y $K $N $O @scc{matrix currentmatrix
$ptm concat stroke setmatrix}{@np}ifelse $SDF{$dsf $dsa $dsp @ss}if
setmatrix}bd/s{@cp S}bd/B{@gs F @gr S}bd/b{@cp B}bd
/E{5 array astore exch cvlit exch def}bd/@cc{
currentfile $dat readhexstring pop}bd/@sm{/$ctm $ctm currentmatrix def
}bd/@E{/Bbury xd/Bburx xd/Bblly xd/Bbllx xd}bd
/@c{@cp}bd/@p{/$fil 1 def 1 eq dup/$vectpat xd{/$pfrg true def}{@gs
$t $c $m $y $k $n $o @scc/$pfrg xd @gr}ifelse
/$pm xd/$psy xd/$psx xd/$pyf xd/$pxf xd/$pn xd}bd
/@P{/$fil 3 def/$Psn xd array astore/$Prm xd}bd
/@k{/$fil 2 def/$roy xd/$rox xd/$pad xd/$fty xd/$fan xd
$fty 1 eq{/$fan 0 def}if/$frb xd/$fst xd/$fsc xd
/$fsa xd/$fsit 0 def}bd/@x{/$fil 2 def/$roy xd/$rox xd/$pad xd
/$fty xd/$fan xd $fty 1 eq{/$fan 0 def}if/$frb xd
/$fst xd/$fsc xd/$fsa xd/$fsit 1 def}bd/@ii{concat
3 index 3 index m 3 index 1 index l 2 copy l
1 index 3 index l 3 index 3 index l clip 4 rp}bd
/tcc{@cc}def/@i{@sm @gs @ii 6 index 1 ne{/$frg true def
2 rp}{1 eq{s1t s1c s1m s1y s1k s1n $o @scc
/$frg xd}{/$frg false def}ifelse 1 eq{@gs $ctm setmatrix
F @gr}if}ifelse @np/$ury xd/$urx xd/$lly xd/$llx xd
/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul 8 div ceiling cvi string def
$bkg $frg or{$SDF{$SCF $SCA $SCP @ss}if $llx $lly Tl
$urx $llx sub $ury $lly sub scale $bkg{$t $c $m $y $k $n $o @scc pop}if
$wid $hei abs $bts 1 eq{$bkg}{$bts}ifelse [ $wid 0 0
$hei neg 0 $hei 0 gt{$hei}{0}ifelse]/tcc load
$bts 1 eq{imagemask}{image}ifelse $SDF{$dsf $dsa $dsp @ss}if}{
$hei abs{tcc pop}repeat}ifelse @gr $ctm setmatrix}bind def
/@M{@sv}bd/@N{/@cc{}def 1 eq{12 -1 roll neg 12 1 roll
@I}{13 -1 roll neg 13 1 roll @i}ifelse @rs}bd
/@I{@sm @gs @ii @np/$ury xd/$urx xd/$lly xd/$llx xd
/$ncl xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul $ncl mul 8 div ceiling cvi string def
$ngx $llx $lly Tl $urx $llx sub $ury $lly sub scale
$wid $hei abs $bts [ $wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse]
/@cc load false $ncl ColorImage $SDF{$dsf $dsa $dsp @ss}if
@gr $ctm setmatrix}bd/z{exch findfont exch scalefont setfont}bd
/ZB{9 dict dup begin 4 1 roll/FontType 3 def
/FontMatrix xd/FontBBox xd/Encoding 256 array def
0 1 255{Encoding exch/.notdef put}for/CharStrings 256 dict def
CharStrings/.notdef{}put/Metrics 256 dict def
Metrics/.notdef 3 -1 roll put/BuildChar{exch
dup/$char exch/Encoding get 3 index get def
dup/Metrics get $char get aload pop setcachedevice
begin Encoding exch get CharStrings exch get
end exec}def end definefont pop}bd/ZBAddChar{findfont begin
dup 4 1 roll dup 6 1 roll Encoding 3 1 roll put
CharStrings 3 1 roll put Metrics 3 1 roll put
end}bd/Z{findfont dup maxlength 2 add dict exch
dup{1 index/FID ne{3 index 3 1 roll put}{2 rp}ifelse}forall
pop dup dup/Encoding get 256 array copy dup/$fe xd
/Encoding exch put dup/Fontname 3 index put
3 -1 roll dup length 0 ne{0 exch{dup type 0 type eq{exch pop}{
$fe exch 2 index exch put 1 add}ifelse}forall
pop}if dup 256 dict dup/$met xd/Metrics exch put
dup/FontMatrix get 0 get 1000 mul 1 exch div
3 index length 256 eq{0 1 255{dup $fe exch get
dup/.notdef eq{2 rp}{5 index 3 -1 roll get
2 index mul $met 3 1 roll put}ifelse}for}if
pop definefont pop pop}bd/@ftx{{currentpoint 3 -1 roll
(0) dup 3 -1 roll 0 exch put dup @gs true charpath
$ctm setmatrix @@txt @gr @np stringwidth pop 3 -1 roll add exch moveto
}forall}bd/@ft{matrix currentmatrix exch $sdf{$scf $sca $scp @ss}if
$fil 1 eq{/@@txt/@pf ld @ftx}{$fil 2 eq{/@@txt/@ff ld @ftx}{$fil 3 eq
{/@@txt/@Pf ld @ftx}{$t $c $m $y $k $n $o @scc{show}{pop}ifelse}ifelse
}ifelse}ifelse $sdf{$dsf $dsa $dsp @ss}if setmatrix}bd
/@st{matrix currentmatrix exch $SDF{$SCF $SCA $SCP @ss}if
$T $C $M $Y $K $N $O @scc{{currentpoint 3 -1 roll
(0) dup 3 -1 roll 0 exch put dup @gs true charpath
$ctm setmatrix $ptm concat stroke @gr @np stringwidth pop 3 -1 roll add exch moveto
}forall}{pop}ifelse $SDF{$dsf $dsa $dsp @ss}if
setmatrix}bd/@te{@ft}bd/@tr{@st}bd/@ta{dup
@gs @ft @gr @st}bd/@t@a{dup @gs @st @gr @ft}bd
/@tm{@sm concat}bd/e{/t{@te}def}bd/r{/t{@tr}def}bd
/o{/t{pop}def}bd/a{/t{@ta}def}bd/@a{/t{@t@a}def}bd
/t{@te}def/T{@np $ctm setmatrix/$ttm matrix def}bd
/ddt{t}def/@t{/$stm $stm currentmatrix def
3 1 roll moveto $ttm concat ddt $stm setmatrix}bd
/@n{/$ttm exch matrix rotate def}bd/@s{}bd
/@l{}bd/@B{@gs S @gr F}bd/@b{@cp @B}bd/@sep{
CurrentInkName (Composite) eq{/$ink -1 def}{CurrentInkName (Cyan) eq
{/$ink 0 def}{CurrentInkName (Magenta) eq{/$ink 1 def}{
CurrentInkName (Yellow) eq{/$ink 2 def}{CurrentInkName (Black) eq
{/$ink 3 def}{/$ink 4 def}ifelse}ifelse}ifelse}ifelse}ifelse}bd
/@whi{@gs -72000 dup moveto -72000 72000 lineto
72000 dup lineto 72000 -72000 lineto closepath 1 SetGry fill
@gr}bd/@neg{ [{1 exch sub}/exec cvx currenttransfer/exec cvx] cvx settransfer
@whi}bd/currentscale{1 0 dtransform matrix defaultmatrix idtransform
dup mul exch dup mul add sqrt 0 1 dtransform
matrix defaultmatrix idtransform dup mul exch dup mul add sqrt}bd
/@unscale{currentscale 1 exch div exch 1 exch div exch scale}bd
/@square{dup 0 rlineto dup 0 exch rlineto neg 0 rlineto
closepath}bd/corelsym{gsave newpath Tl -90 rotate
7{45 rotate -.75 2 moveto 1.5 @square fill}repeat
grestore}bd/@reg{gsave newpath Tl -6 -6 moveto 12 @square
gsave 1 GetGry sub SetGry fill grestore 4{90 rotate
0 4 m 0 4 rl}repeat stroke 0 0 corelsym grestore}bd
/$corelmeter [1 .95 .75 .50 .25 .05 0] def
/@colormeter{@gs newpath 0 SetGry 0.3 setlinewidth
/Courier findfont 5 scalefont setfont/y exch def
/x exch def 0 1 6{x 20 sub y m 20 @square @gs $corelmeter exch get dup SetGry fill @gr
stroke x 2 add y 8 add moveto 100 mul 100 exch sub cvi 20 string cvs show
/y y 20 add def}for @gr}bd/@crop{gsave .3 setlinewidth
0 SetGry Tl rotate 0 0 m 0 -24 rl -4 -24 m 8 @square
-4 -20 m 8 0 rl stroke grestore}bd/@colorbox{gsave
newpath Tl 100 exch sub 100 div SetGry -8 -8 moveto 16 @square fill
0 SetGry 10 -2 moveto show grestore}bd/deflevel 0 def
/@sax{/deflevel deflevel 1 add def}bd/@eax{
/deflevel deflevel dup 0 gt{1 sub}if def deflevel 0 gt{/eax load}{eax}
ifelse}bd/eax{{exec}forall}bd/@rax{deflevel 0 eq{@rs @sv}if}bd
/@daq{dup type/arraytype eq{{}forall}if}bd
/@BMP{/@cc xd 12 index 1 eq{12 -1 roll pop
@i}{7 -2 roll 2 rp @I}ifelse}bd end
%%EndResource

%%EndProlog
/#copies 1 def
wCorel4Dict begin
0.00 0.00 Tl
1.0000 1.0000 scale
%%BeginSetup
11.4737 setmiterlimit
0 45 /@dot @D
1.00 setflat
/$fst 128 def
%%EndSetup
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 278 
278 355 556 556 889 667 191 333 333 389 584 278 333 278 278 556 
556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 
667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 
778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 333 
556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 
556 333 500 278 556 500 722 500 500 500 334 260 334 584 750 750 
750 222 556 333 1000 556 556 333 1000 667 333 1000 750 750 750 750 
222 222 333 333 350 556 1000 333 1000 500 333 944 750 750 667 278 
333 556 556 556 556 260 556 333 737 370 556 584 333 737 552 400 
549 333 333 333 576 537 278 333 333 365 556 834 834 834 611 667 
667 667 667 667 667 1000 722 667 667 667 667 278 278 278 278 722 
722 778 778 778 778 778 584 778 722 722 722 722 667 667 611 556 
556 556 556 556 556 889 500 556 556 556 556 278 278 278 278 556 
556 556 556 556 556 556 549 611 556 556 556 556 500 556 500 ]
CorelDrawReencodeVect /_R1-Helvetica /Helvetica Z

%StartPage
@sv
/$ctm matrix currentmatrix def
@sv
%StartColorLayer (COMPOSITE)
%StartTile
/$ctm matrix currentmatrix def
@sv @sv
@rs 0 0 Tl 1.000000 1.000000 scale 
0.000000 0.000000 Tl /$ctm matrix currentmatrix def @sv
@rax %%Note: Object
132.34 700.99 187.20 709.20 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 700.99 m
132.34 709.20 L
187.20 709.20 L
187.20 700.99 L
132.34 700.99 L
@c
S

@rax %%Note: Object
132.41 270.43 168.05 275.90 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 270.43 m
132.41 275.90 L
168.05 275.90 L
168.05 270.43 L
132.41 270.43 L
@c
B

@rax %%Note: Object
132.41 311.54 168.05 317.02 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 311.54 m
132.41 317.02 L
168.05 317.02 L
168.05 311.54 L
132.41 311.54 L
@c
B

@rax %%Note: Object
132.41 303.34 168.05 308.81 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 303.34 m
132.41 308.81 L
168.05 308.81 L
168.05 303.34 L
132.41 303.34 L
@c
B

@rax %%Note: Object
132.41 295.06 168.05 300.60 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 295.06 m
132.41 300.60 L
168.05 300.60 L
168.05 295.06 L
132.41 295.06 L
@c
B

@rax %%Note: Object
132.41 264.89 187.27 273.17 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 264.89 m
132.41 273.17 L
187.27 273.17 L
187.27 264.89 L
132.41 264.89 L
@c
S

@rax %%Note: Object
132.41 314.28 187.27 322.49 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 314.28 m
132.41 322.49 L
187.27 322.49 L
187.27 314.28 L
132.41 314.28 L
@c
S

@rax %%Note: Object
132.41 306.07 187.27 314.28 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 306.07 m
132.41 314.28 L
187.27 314.28 L
187.27 306.07 L
132.41 306.07 L
@c
S

@rax %%Note: Object
132.41 297.86 187.27 306.07 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 297.86 m
132.41 306.07 L
187.27 306.07 L
187.27 297.86 L
132.41 297.86 L
@c
S

@rax %%Note: Object
132.41 273.17 187.27 281.38 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 273.17 m
132.41 281.38 L
187.27 281.38 L
187.27 273.17 L
132.41 273.17 L
@c
S

@rax %%Note: Object
132.41 289.58 187.27 297.86 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 289.58 m
132.41 297.86 L
187.27 297.86 L
187.27 289.58 L
132.41 289.58 L
@c
S

@rax %%Note: Object
132.41 281.38 187.27 289.58 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 281.38 m
132.41 289.58 L
187.27 289.58 L
187.27 281.38 L
132.41 281.38 L
@c
S

@rax %%Note: Object
132.41 278.64 168.05 284.11 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 278.64 m
132.41 284.11 L
168.05 284.11 L
168.05 278.64 L
132.41 278.64 L
@c
B

@rax %%Note: Object
132.41 97.70 168.05 103.18 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 97.70 m
132.41 103.18 L
168.05 103.18 L
168.05 97.70 L
132.41 97.70 L
@c
B

@rax %%Note: Object
132.41 138.82 168.05 144.29 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 138.82 m
132.41 144.29 L
168.05 144.29 L
168.05 138.82 L
132.41 138.82 L
@c
B

@rax %%Note: Object
132.41 130.61 168.05 136.08 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 130.61 m
132.41 136.08 L
168.05 136.08 L
168.05 130.61 L
132.41 130.61 L
@c
B

@rax %%Note: Object
132.41 122.33 168.05 127.87 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 122.33 m
132.41 127.87 L
168.05 127.87 L
168.05 122.33 L
132.41 122.33 L
@c
B

@rax %%Note: Object
132.41 92.16 187.27 100.44 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 92.16 m
132.41 100.44 L
187.27 100.44 L
187.27 92.16 L
132.41 92.16 L
@c
S

@rax %%Note: Object
132.41 141.55 187.27 149.76 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 141.55 m
132.41 149.76 L
187.27 149.76 L
187.27 141.55 L
132.41 141.55 L
@c
S

@rax %%Note: Object
132.41 133.34 187.27 141.55 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 133.34 m
132.41 141.55 L
187.27 141.55 L
187.27 133.34 L
132.41 133.34 L
@c
S

@rax %%Note: Object
132.41 125.14 187.27 133.34 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 125.14 m
132.41 133.34 L
187.27 133.34 L
187.27 125.14 L
132.41 125.14 L
@c
S

@rax %%Note: Object
132.41 100.44 187.27 108.65 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 100.44 m
132.41 108.65 L
187.27 108.65 L
187.27 100.44 L
132.41 100.44 L
@c
S

@rax %%Note: Object
132.41 116.86 187.27 125.14 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 116.86 m
132.41 125.14 L
187.27 125.14 L
187.27 116.86 L
132.41 116.86 L
@c
S

@rax %%Note: Object
132.41 108.65 187.27 116.86 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 108.65 m
132.41 116.86 L
187.27 116.86 L
187.27 108.65 L
132.41 108.65 L
@c
S

@rax %%Note: Object
132.41 105.91 168.05 111.38 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 105.91 m
132.41 111.38 L
168.05 111.38 L
168.05 105.91 L
132.41 105.91 L
@c
B

@rax %%Note: Object
132.41 155.23 168.05 160.70 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 155.23 m
132.41 160.70 L
168.05 160.70 L
168.05 155.23 L
132.41 155.23 L
@c
B

@rax %%Note: Object
132.41 196.34 168.05 201.82 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 196.34 m
132.41 201.82 L
168.05 201.82 L
168.05 196.34 L
132.41 196.34 L
@c
B

@rax %%Note: Object
132.41 188.14 168.05 193.61 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 188.14 m
132.41 193.61 L
168.05 193.61 L
168.05 188.14 L
132.41 188.14 L
@c
B

@rax %%Note: Object
132.41 179.86 168.05 185.40 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 179.86 m
132.41 185.40 L
168.05 185.40 L
168.05 179.86 L
132.41 179.86 L
@c
B

@rax %%Note: Object
132.41 149.69 187.27 157.97 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 149.69 m
132.41 157.97 L
187.27 157.97 L
187.27 149.69 L
132.41 149.69 L
@c
S

@rax %%Note: Object
132.41 199.08 187.27 207.29 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 199.08 m
132.41 207.29 L
187.27 207.29 L
187.27 199.08 L
132.41 199.08 L
@c
S

@rax %%Note: Object
132.41 190.87 187.27 199.08 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 190.87 m
132.41 199.08 L
187.27 199.08 L
187.27 190.87 L
132.41 190.87 L
@c
S

@rax %%Note: Object
132.41 182.66 187.27 190.87 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 182.66 m
132.41 190.87 L
187.27 190.87 L
187.27 182.66 L
132.41 182.66 L
@c
S

@rax %%Note: Object
132.41 157.97 187.27 166.18 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 157.97 m
132.41 166.18 L
187.27 166.18 L
187.27 157.97 L
132.41 157.97 L
@c
S

@rax %%Note: Object
132.41 174.38 187.27 182.66 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 174.38 m
132.41 182.66 L
187.27 182.66 L
187.27 174.38 L
132.41 174.38 L
@c
S

@rax %%Note: Object
132.41 166.18 187.27 174.38 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 166.18 m
132.41 174.38 L
187.27 174.38 L
187.27 166.18 L
132.41 166.18 L
@c
S

@rax %%Note: Object
132.41 163.44 168.05 168.91 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 163.44 m
132.41 168.91 L
168.05 168.91 L
168.05 163.44 L
132.41 163.44 L
@c
B

@rax %%Note: Object
132.34 673.56 167.98 679.03 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 673.56 m
132.34 679.03 L
167.98 679.03 L
167.98 673.56 L
132.34 673.56 L
@c
B

@rax %%Note: Object
132.34 698.26 167.98 703.73 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 698.26 m
132.34 703.73 L
167.98 703.73 L
167.98 698.26 L
132.34 698.26 L
@c
B

@rax %%Note: Object
132.34 668.09 187.20 676.30 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 668.09 m
132.34 676.30 L
187.20 676.30 L
187.20 668.09 L
132.34 668.09 L
@c
S

@rax %%Note: Object
132.34 676.30 187.20 684.50 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 676.30 m
132.34 684.50 L
187.20 684.50 L
187.20 676.30 L
132.34 676.30 L
@c
S

@rax %%Note: Object
132.34 692.78 187.20 700.99 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 692.78 m
132.34 700.99 L
187.20 700.99 L
187.20 692.78 L
132.34 692.78 L
@c
S

@rax %%Note: Object
132.34 684.58 187.20 692.78 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 684.58 m
132.34 692.78 L
187.20 692.78 L
187.20 684.58 L
132.34 684.58 L
@c
S

@rax %%Note: Object
132.34 681.77 167.98 687.24 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 681.77 m
132.34 687.24 L
167.98 687.24 L
167.98 681.77 L
132.34 681.77 L
@c
B

@rax %%Note: Object
132.41 500.90 168.05 506.38 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 500.90 m
132.41 506.38 L
168.05 506.38 L
168.05 500.90 L
132.41 500.90 L
@c
B

@rax %%Note: Object
132.41 542.02 168.05 547.49 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 542.02 m
132.41 547.49 L
168.05 547.49 L
168.05 542.02 L
132.41 542.02 L
@c
B

@rax %%Note: Object
132.41 533.81 168.05 539.28 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 533.81 m
132.41 539.28 L
168.05 539.28 L
168.05 533.81 L
132.41 533.81 L
@c
B

@rax %%Note: Object
132.41 525.53 168.05 531.07 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 525.53 m
132.41 531.07 L
168.05 531.07 L
168.05 525.53 L
132.41 525.53 L
@c
B

@rax %%Note: Object
132.41 495.36 187.27 503.64 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 495.36 m
132.41 503.64 L
187.27 503.64 L
187.27 495.36 L
132.41 495.36 L
@c
S

@rax %%Note: Object
132.41 544.75 187.27 552.96 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 544.75 m
132.41 552.96 L
187.27 552.96 L
187.27 544.75 L
132.41 544.75 L
@c
S

@rax %%Note: Object
132.41 536.54 187.27 544.75 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 536.54 m
132.41 544.75 L
187.27 544.75 L
187.27 536.54 L
132.41 536.54 L
@c
S

@rax %%Note: Object
132.41 528.34 187.27 536.54 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 528.34 m
132.41 536.54 L
187.27 536.54 L
187.27 528.34 L
132.41 528.34 L
@c
S

@rax %%Note: Object
132.41 503.64 187.27 511.85 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 503.64 m
132.41 511.85 L
187.27 511.85 L
187.27 503.64 L
132.41 503.64 L
@c
S

@rax %%Note: Object
132.41 520.06 187.27 528.34 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 520.06 m
132.41 528.34 L
187.27 528.34 L
187.27 520.06 L
132.41 520.06 L
@c
S

@rax %%Note: Object
132.41 511.85 187.27 520.06 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 511.85 m
132.41 520.06 L
187.27 520.06 L
187.27 511.85 L
132.41 511.85 L
@c
S

@rax %%Note: Object
132.41 509.11 168.05 514.58 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 509.11 m
132.41 514.58 L
168.05 514.58 L
168.05 509.11 L
132.41 509.11 L
@c
B

@rax %%Note: Object
132.41 558.50 168.05 563.98 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 558.50 m
132.41 563.98 L
168.05 563.98 L
168.05 558.50 L
132.41 558.50 L
@c
B

@rax %%Note: Object
132.41 599.62 168.05 605.09 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 599.62 m
132.41 605.09 L
168.05 605.09 L
168.05 599.62 L
132.41 599.62 L
@c
B

@rax %%Note: Object
132.41 591.41 168.05 596.88 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 591.41 m
132.41 596.88 L
168.05 596.88 L
168.05 591.41 L
132.41 591.41 L
@c
B

@rax %%Note: Object
132.41 583.13 168.05 588.67 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 583.13 m
132.41 588.67 L
168.05 588.67 L
168.05 583.13 L
132.41 583.13 L
@c
B

@rax %%Note: Object
132.41 552.96 187.27 561.24 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 552.96 m
132.41 561.24 L
187.27 561.24 L
187.27 552.96 L
132.41 552.96 L
@c
S

@rax %%Note: Object
132.41 602.35 187.27 610.56 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 602.35 m
132.41 610.56 L
187.27 610.56 L
187.27 602.35 L
132.41 602.35 L
@c
S

@rax %%Note: Object
132.41 594.14 187.27 602.35 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 594.14 m
132.41 602.35 L
187.27 602.35 L
187.27 594.14 L
132.41 594.14 L
@c
S

@rax %%Note: Object
132.41 585.94 187.27 594.14 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 585.94 m
132.41 594.14 L
187.27 594.14 L
187.27 585.94 L
132.41 585.94 L
@c
S

@rax %%Note: Object
132.41 561.24 187.27 569.45 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 561.24 m
132.41 569.45 L
187.27 569.45 L
187.27 561.24 L
132.41 561.24 L
@c
S

@rax %%Note: Object
132.41 577.66 187.27 585.94 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 577.66 m
132.41 585.94 L
187.27 585.94 L
187.27 577.66 L
132.41 577.66 L
@c
S

@rax %%Note: Object
132.41 569.45 187.27 577.66 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 569.45 m
132.41 577.66 L
187.27 577.66 L
187.27 569.45 L
132.41 569.45 L
@c
S

@rax %%Note: Object
132.41 566.71 168.05 572.18 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 566.71 m
132.41 572.18 L
168.05 572.18 L
168.05 566.71 L
132.41 566.71 L
@c
B

@rax %%Note: Object
132.41 616.03 168.05 621.50 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 616.03 m
132.41 621.50 L
168.05 621.50 L
168.05 616.03 L
132.41 616.03 L
@c
B

@rax %%Note: Object
132.41 657.14 168.05 662.62 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 657.14 m
132.41 662.62 L
168.05 662.62 L
168.05 657.14 L
132.41 657.14 L
@c
B

@rax %%Note: Object
132.41 648.94 168.05 654.41 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 648.94 m
132.41 654.41 L
168.05 654.41 L
168.05 648.94 L
132.41 648.94 L
@c
B

@rax %%Note: Object
132.41 640.66 168.05 646.20 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 640.66 m
132.41 646.20 L
168.05 646.20 L
168.05 640.66 L
132.41 640.66 L
@c
B

@rax %%Note: Object
132.41 610.49 187.27 618.77 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 610.49 m
132.41 618.77 L
187.27 618.77 L
187.27 610.49 L
132.41 610.49 L
@c
S

@rax %%Note: Object
132.41 659.88 187.27 668.09 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 659.88 m
132.41 668.09 L
187.27 668.09 L
187.27 659.88 L
132.41 659.88 L
@c
S

@rax %%Note: Object
132.41 651.67 187.27 659.88 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 651.67 m
132.41 659.88 L
187.27 659.88 L
187.27 651.67 L
132.41 651.67 L
@c
S

@rax %%Note: Object
132.41 643.46 187.27 651.67 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 643.46 m
132.41 651.67 L
187.27 651.67 L
187.27 643.46 L
132.41 643.46 L
@c
S

@rax %%Note: Object
132.41 618.77 187.27 626.98 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 618.77 m
132.41 626.98 L
187.27 626.98 L
187.27 618.77 L
132.41 618.77 L
@c
S

@rax %%Note: Object
132.41 635.18 187.27 643.46 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 635.18 m
132.41 643.46 L
187.27 643.46 L
187.27 635.18 L
132.41 635.18 L
@c
S

@rax %%Note: Object
132.41 626.98 187.27 635.18 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 626.98 m
132.41 635.18 L
187.27 635.18 L
187.27 626.98 L
132.41 626.98 L
@c
S

@rax %%Note: Object
132.41 624.24 168.05 629.71 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 624.24 m
132.41 629.71 L
168.05 629.71 L
168.05 624.24 L
132.41 624.24 L
@c
B

@rax %%Note: Object
132.41 385.63 168.05 391.10 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 385.63 m
132.41 391.10 L
168.05 391.10 L
168.05 385.63 L
132.41 385.63 L
@c
B

@rax %%Note: Object
132.41 426.74 168.05 432.22 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 426.74 m
132.41 432.22 L
168.05 432.22 L
168.05 426.74 L
132.41 426.74 L
@c
B

@rax %%Note: Object
132.41 418.54 168.05 424.01 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 418.54 m
132.41 424.01 L
168.05 424.01 L
168.05 418.54 L
132.41 418.54 L
@c
B

@rax %%Note: Object
132.41 410.26 168.05 415.80 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 410.26 m
132.41 415.80 L
168.05 415.80 L
168.05 410.26 L
132.41 410.26 L
@c
B

@rax %%Note: Object
132.41 380.09 187.27 388.37 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 380.09 m
132.41 388.37 L
187.27 388.37 L
187.27 380.09 L
132.41 380.09 L
@c
S

@rax %%Note: Object
132.41 429.48 187.27 437.69 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 429.48 m
132.41 437.69 L
187.27 437.69 L
187.27 429.48 L
132.41 429.48 L
@c
S

@rax %%Note: Object
132.41 421.27 187.27 429.48 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 421.27 m
132.41 429.48 L
187.27 429.48 L
187.27 421.27 L
132.41 421.27 L
@c
S

@rax %%Note: Object
132.41 413.06 187.27 421.27 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 413.06 m
132.41 421.27 L
187.27 421.27 L
187.27 413.06 L
132.41 413.06 L
@c
S

@rax %%Note: Object
132.41 388.37 187.27 396.58 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 388.37 m
132.41 396.58 L
187.27 396.58 L
187.27 388.37 L
132.41 388.37 L
@c
S

@rax %%Note: Object
132.41 404.78 187.27 413.06 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 404.78 m
132.41 413.06 L
187.27 413.06 L
187.27 404.78 L
132.41 404.78 L
@c
S

@rax %%Note: Object
132.41 396.58 187.27 404.78 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 396.58 m
132.41 404.78 L
187.27 404.78 L
187.27 396.58 L
132.41 396.58 L
@c
S

@rax %%Note: Object
132.41 393.84 168.05 399.31 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 393.84 m
132.41 399.31 L
168.05 399.31 L
168.05 393.84 L
132.41 393.84 L
@c
B

@rax %%Note: Object
132.41 212.90 168.05 218.38 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 212.90 m
132.41 218.38 L
168.05 218.38 L
168.05 212.90 L
132.41 212.90 L
@c
B

@rax %%Note: Object
132.41 254.02 168.05 259.49 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 254.02 m
132.41 259.49 L
168.05 259.49 L
168.05 254.02 L
132.41 254.02 L
@c
B

@rax %%Note: Object
132.41 245.81 168.05 251.28 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 245.81 m
132.41 251.28 L
168.05 251.28 L
168.05 245.81 L
132.41 245.81 L
@c
B

@rax %%Note: Object
132.41 237.53 168.05 243.07 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 237.53 m
132.41 243.07 L
168.05 243.07 L
168.05 237.53 L
132.41 237.53 L
@c
B

@rax %%Note: Object
132.41 207.36 187.27 215.64 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 207.36 m
132.41 215.64 L
187.27 215.64 L
187.27 207.36 L
132.41 207.36 L
@c
S

@rax %%Note: Object
132.41 256.75 187.27 264.96 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 256.75 m
132.41 264.96 L
187.27 264.96 L
187.27 256.75 L
132.41 256.75 L
@c
S

@rax %%Note: Object
132.41 248.54 187.27 256.75 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 248.54 m
132.41 256.75 L
187.27 256.75 L
187.27 248.54 L
132.41 248.54 L
@c
S

@rax %%Note: Object
132.41 240.34 187.27 248.54 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 240.34 m
132.41 248.54 L
187.27 248.54 L
187.27 240.34 L
132.41 240.34 L
@c
S

@rax %%Note: Object
132.41 215.64 187.27 223.85 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 215.64 m
132.41 223.85 L
187.27 223.85 L
187.27 215.64 L
132.41 215.64 L
@c
S

@rax %%Note: Object
132.41 232.06 187.27 240.34 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 232.06 m
132.41 240.34 L
187.27 240.34 L
187.27 232.06 L
132.41 232.06 L
@c
S

@rax %%Note: Object
132.41 223.85 187.27 232.06 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 223.85 m
132.41 232.06 L
187.27 232.06 L
187.27 223.85 L
132.41 223.85 L
@c
S

@rax %%Note: Object
132.41 221.11 168.05 226.58 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 221.11 m
132.41 226.58 L
168.05 226.58 L
168.05 221.11 L
132.41 221.11 L
@c
B

@rax %%Note: Object
132.41 328.03 168.05 333.50 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 328.03 m
132.41 333.50 L
168.05 333.50 L
168.05 328.03 L
132.41 328.03 L
@c
B

@rax %%Note: Object
132.41 369.14 168.05 374.62 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 369.14 m
132.41 374.62 L
168.05 374.62 L
168.05 369.14 L
132.41 369.14 L
@c
B

@rax %%Note: Object
132.41 360.94 168.05 366.41 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 360.94 m
132.41 366.41 L
168.05 366.41 L
168.05 360.94 L
132.41 360.94 L
@c
B

@rax %%Note: Object
132.41 352.66 168.05 358.20 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 352.66 m
132.41 358.20 L
168.05 358.20 L
168.05 352.66 L
132.41 352.66 L
@c
B

@rax %%Note: Object
132.41 322.49 187.27 330.77 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 322.49 m
132.41 330.77 L
187.27 330.77 L
187.27 322.49 L
132.41 322.49 L
@c
S

@rax %%Note: Object
132.41 371.88 187.27 380.09 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 371.88 m
132.41 380.09 L
187.27 380.09 L
187.27 371.88 L
132.41 371.88 L
@c
S

@rax %%Note: Object
132.41 363.67 187.27 371.88 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 363.67 m
132.41 371.88 L
187.27 371.88 L
187.27 363.67 L
132.41 363.67 L
@c
S

@rax %%Note: Object
132.41 355.46 187.27 363.67 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 355.46 m
132.41 363.67 L
187.27 363.67 L
187.27 355.46 L
132.41 355.46 L
@c
S

@rax %%Note: Object
132.41 330.77 187.27 338.98 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 330.77 m
132.41 338.98 L
187.27 338.98 L
187.27 330.77 L
132.41 330.77 L
@c
S

@rax %%Note: Object
132.41 347.18 187.27 355.46 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 347.18 m
132.41 355.46 L
187.27 355.46 L
187.27 347.18 L
132.41 347.18 L
@c
S

@rax %%Note: Object
132.41 338.98 187.27 347.18 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 338.98 m
132.41 347.18 L
187.27 347.18 L
187.27 338.98 L
132.41 338.98 L
@c
S

@rax %%Note: Object
132.41 336.24 168.05 341.71 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.41 336.24 m
132.41 341.71 L
168.05 341.71 L
168.05 336.24 L
132.41 336.24 L
@c
B

@rax %%Note: Object
132.34 443.23 167.98 448.70 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 443.23 m
132.34 448.70 L
167.98 448.70 L
167.98 443.23 L
132.34 443.23 L
@c
B

@rax %%Note: Object
132.34 484.34 167.98 489.82 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 484.34 m
132.34 489.82 L
167.98 489.82 L
167.98 484.34 L
132.34 484.34 L
@c
B

@rax %%Note: Object
132.34 476.14 167.98 481.61 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 476.14 m
132.34 481.61 L
167.98 481.61 L
167.98 476.14 L
132.34 476.14 L
@c
B

@rax %%Note: Object
132.34 467.86 167.98 473.40 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 467.86 m
132.34 473.40 L
167.98 473.40 L
167.98 467.86 L
132.34 467.86 L
@c
B

@rax %%Note: Object
132.34 437.69 187.20 445.97 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 437.69 m
132.34 445.97 L
187.20 445.97 L
187.20 437.69 L
132.34 437.69 L
@c
S

@rax %%Note: Object
132.34 487.08 187.20 495.29 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 487.08 m
132.34 495.29 L
187.20 495.29 L
187.20 487.08 L
132.34 487.08 L
@c
S

@rax %%Note: Object
132.34 478.87 187.20 487.08 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 478.87 m
132.34 487.08 L
187.20 487.08 L
187.20 478.87 L
132.34 478.87 L
@c
S

@rax %%Note: Object
132.34 470.66 187.20 478.87 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 470.66 m
132.34 478.87 L
187.20 478.87 L
187.20 470.66 L
132.34 470.66 L
@c
S

@rax %%Note: Object
132.34 445.97 187.20 454.18 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 445.97 m
132.34 454.18 L
187.20 454.18 L
187.20 445.97 L
132.34 445.97 L
@c
S

@rax %%Note: Object
132.34 462.38 187.20 470.66 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 462.38 m
132.34 470.66 L
187.20 470.66 L
187.20 462.38 L
132.34 462.38 L
@c
S

@rax %%Note: Object
132.34 454.18 187.20 462.38 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 454.18 m
132.34 462.38 L
187.20 462.38 L
187.20 454.18 L
132.34 454.18 L
@c
S

@rax %%Note: Object
132.34 451.44 167.98 456.91 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
132.34 451.44 m
132.34 456.91 L
167.98 456.91 L
167.98 451.44 L
132.34 451.44 L
@c
B

@rax 174.17 95.54 177.19 99.94 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 95.61600] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (0) @t
T
@rax 174.17 153.22 180.50 157.54 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 153.21599] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (12) @t
T
@rax 174.17 210.82 180.50 215.14 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 210.81599] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (24) @t
T
@rax 174.17 268.34 180.50 272.74 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 268.41599] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (36) @t
T
@rax 174.17 325.94 180.50 330.34 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 326.01599] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (48) @t
T
@rax 174.17 383.54 180.50 387.94 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 383.61600] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (60) @t
T
@rax 174.17 441.22 180.50 445.54 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 441.21597] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (72) @t
T
@rax 174.17 498.74 180.50 503.14 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 498.81598] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (84) @t
T
@rax 174.17 556.34 180.50 560.74 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 556.41595] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (96) @t
T
@rax 174.17 613.94 183.82 618.34 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 614.01599] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (108) @t
T
@rax 174.17 671.54 183.82 675.94 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 671.61597] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (120) @t
T
@rax 174.17 695.52 183.82 699.91 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 695.59198] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (125) @t
T
@rax 174.17 637.92 183.38 642.31 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 637.99194] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (113) @t
T
@rax 174.17 580.32 182.95 584.71 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 580.39197] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (101) @t
T
@rax 174.17 522.72 180.50 527.11 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 522.79199] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (89) @t
T
@rax 174.17 465.19 180.50 469.44 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 465.19199] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (77) @t
T
@rax 174.17 407.52 180.50 411.91 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 407.59198] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (65) @t
T
@rax 174.17 349.92 180.50 354.31 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 349.99197] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (53) @t
T
@rax 174.17 292.39 179.64 296.71 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 292.39200] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (41) @t
T
@rax 174.17 234.72 180.50 239.11 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 234.79199] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (29) @t
T
@rax 174.17 177.19 180.50 181.51 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 177.19199] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (17) @t
T
@rax 174.17 119.52 177.19 123.84 @E
[0.07199 0.00000 0.00000 0.07199 174.16800 119.59200] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 83.00 z
0 0 (5) @t
T
@rax 194.40 745.06 392.04 755.21 @E
[0.07199 0.00000 0.00000 0.07199 194.39999 745.19995] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 194.00 z
0 0 (MIDI File Format Note Numbers) @t
T
@rax %%Note: Object
288.00 691.27 301.03 705.67 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
295.78 691.27 m
291.67 691.27 288.29 696.38 288.14 700.56 C
288.00 704.30 289.66 705.67 293.40 705.67 C
297.43 705.60 301.03 700.34 301.03 696.31 C
301.03 692.78 299.74 691.27 295.78 691.27 C
@c
293.33 696.10 m
294.34 694.58 296.57 692.64 298.01 692.71 C
300.82 693.00 297.36 698.69 296.35 699.98 c
294.55 702.36 292.54 704.30 290.95 704.09 C
288.14 703.37 292.32 697.46 293.33 696.10 c
@c
B

@rax %%Note: Object
302.40 655.27 315.43 669.67 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
310.18 655.27 m
306.07 655.27 302.69 660.38 302.54 664.56 C
302.40 668.30 304.06 669.67 307.80 669.67 C
311.83 669.60 315.43 664.34 315.43 660.31 C
315.43 656.78 314.14 655.27 310.18 655.27 C
@c
307.73 660.10 m
308.74 658.58 310.97 656.64 312.41 656.71 C
315.22 657.00 311.76 662.69 310.75 663.98 c
308.95 666.36 306.94 668.30 305.35 668.09 C
302.54 667.37 306.72 661.46 307.73 660.10 c
@c
B

@rax %%Note: Object
318.24 619.27 331.27 633.67 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
326.02 619.27 m
321.91 619.27 318.53 624.38 318.38 628.56 C
318.24 632.30 319.90 633.67 323.64 633.67 C
327.67 633.60 331.27 628.34 331.27 624.31 C
331.27 620.78 329.98 619.27 326.02 619.27 C
@c
323.57 624.10 m
324.58 622.58 326.81 620.64 328.25 620.71 C
331.06 621.00 327.60 626.69 326.59 627.98 c
324.79 630.36 322.78 632.30 321.19 632.09 C
318.38 631.37 322.56 625.46 323.57 624.10 c
@c
B

@rax %%Note: Object
331.20 590.47 344.23 604.87 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
338.98 590.47 m
334.87 590.47 331.49 595.58 331.34 599.76 C
331.20 603.50 332.86 604.87 336.60 604.87 C
340.63 604.80 344.23 599.54 344.23 595.51 C
344.23 591.98 342.94 590.47 338.98 590.47 C
@c
336.53 595.30 m
337.54 593.78 339.77 591.84 341.21 591.91 C
344.02 592.20 340.56 597.89 339.55 599.18 c
337.75 601.56 335.74 603.50 334.15 603.29 C
331.34 602.57 335.52 596.66 336.53 595.30 c
@c
B

@rax %%Note: Object
347.04 554.47 360.07 568.87 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
354.82 554.47 m
350.71 554.47 347.33 559.58 347.18 563.76 C
347.04 567.50 348.70 568.87 352.44 568.87 C
356.47 568.80 360.07 563.54 360.07 559.51 C
360.07 555.98 358.78 554.47 354.82 554.47 C
@c
352.37 559.30 m
353.38 557.78 355.61 555.84 357.05 555.91 C
359.86 556.20 356.40 561.89 355.39 563.18 c
353.59 565.56 351.58 567.50 349.99 567.29 C
347.18 566.57 351.36 560.66 352.37 559.30 c
@c
B

@rax %%Note: Object
361.44 518.47 374.47 532.87 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
369.22 518.47 m
365.11 518.47 361.73 523.58 361.58 527.76 C
361.44 531.50 363.10 532.87 366.84 532.87 C
370.87 532.80 374.47 527.54 374.47 523.51 C
374.47 519.98 373.18 518.47 369.22 518.47 C
@c
366.77 523.30 m
367.78 521.78 370.01 519.84 371.45 519.91 C
374.26 520.20 370.80 525.89 369.79 527.18 c
367.99 529.56 365.98 531.50 364.39 531.29 C
361.58 530.57 365.76 524.66 366.77 523.30 c
@c
B

@rax %%Note: Object
375.84 482.47 388.87 496.87 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
383.62 482.47 m
379.51 482.47 376.13 487.58 375.98 491.76 C
375.84 495.50 377.50 496.87 381.24 496.87 C
385.27 496.80 388.87 491.54 388.87 487.51 C
388.87 483.98 387.58 482.47 383.62 482.47 C
@c
381.17 487.30 m
382.18 485.78 384.41 483.84 385.85 483.91 C
388.66 484.20 385.20 489.89 384.19 491.18 c
382.39 493.56 380.38 495.50 378.79 495.29 C
375.98 494.57 380.16 488.66 381.17 487.30 c
@c
B

@rax %%Note: Object
388.80 446.47 401.83 460.87 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
396.58 446.47 m
392.47 446.47 389.09 451.58 388.94 455.76 C
388.80 459.50 390.46 460.87 394.20 460.87 C
398.23 460.80 401.83 455.54 401.83 451.51 C
401.83 447.98 400.54 446.47 396.58 446.47 C
@c
394.13 451.30 m
395.14 449.78 397.37 447.84 398.81 447.91 C
401.62 448.20 398.16 453.89 397.15 455.18 c
395.35 457.56 393.34 459.50 391.75 459.29 C
388.94 458.57 393.12 452.66 394.13 451.30 c
@c
B

@rax %%Note: Object
419.04 410.47 432.07 424.87 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
426.82 410.47 m
422.71 410.47 419.33 415.58 419.18 419.76 C
419.04 423.50 420.70 424.87 424.44 424.87 C
428.47 424.80 432.07 419.54 432.07 415.51 C
432.07 411.98 430.78 410.47 426.82 410.47 C
@c
424.37 415.30 m
425.38 413.78 427.61 411.84 429.05 411.91 C
431.86 412.20 428.40 417.89 427.39 419.18 c
425.59 421.56 423.58 423.50 421.99 423.29 C
419.18 422.57 423.36 416.66 424.37 415.30 c
@c
B

@rax %%Note: Object
433.44 374.47 446.47 388.87 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
441.22 374.47 m
437.11 374.47 433.73 379.58 433.58 383.76 C
433.44 387.50 435.10 388.87 438.84 388.87 C
442.87 388.80 446.47 383.54 446.47 379.51 C
446.47 375.98 445.18 374.47 441.22 374.47 C
@c
438.77 379.30 m
439.78 377.78 442.01 375.84 443.45 375.91 C
446.26 376.20 442.80 381.89 441.79 383.18 c
439.99 385.56 437.98 387.50 436.39 387.29 C
433.58 386.57 437.76 380.66 438.77 379.30 c
@c
B

@rax %%Note: Object
447.84 338.47 460.87 352.87 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
455.62 338.47 m
451.51 338.47 448.13 343.58 447.98 347.76 C
447.84 351.50 449.50 352.87 453.24 352.87 C
457.27 352.80 460.87 347.54 460.87 343.51 C
460.87 339.98 459.58 338.47 455.62 338.47 C
@c
453.17 343.30 m
454.18 341.78 456.41 339.84 457.85 339.91 C
460.66 340.20 457.20 345.89 456.19 347.18 c
454.39 349.56 452.38 351.50 450.79 351.29 C
447.98 350.57 452.16 344.66 453.17 343.30 c
@c
B

@rax %%Note: Object
462.24 302.47 475.27 316.87 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
470.02 302.47 m
465.91 302.47 462.53 307.58 462.38 311.76 C
462.24 315.50 463.90 316.87 467.64 316.87 C
471.67 316.80 475.27 311.54 475.27 307.51 C
475.27 303.98 473.98 302.47 470.02 302.47 C
@c
467.57 307.30 m
468.58 305.78 470.81 303.84 472.25 303.91 C
475.06 304.20 471.60 309.89 470.59 311.18 c
468.79 313.56 466.78 315.50 465.19 315.29 C
462.38 314.57 466.56 308.66 467.57 307.30 c
@c
B

@rax %%Note: Object
476.64 266.47 489.67 280.87 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
484.42 266.47 m
480.31 266.47 476.93 271.58 476.78 275.76 C
476.64 279.50 478.30 280.87 482.04 280.87 C
486.07 280.80 489.67 275.54 489.67 271.51 C
489.67 267.98 488.38 266.47 484.42 266.47 C
@c
481.97 271.30 m
482.98 269.78 485.21 267.84 486.65 267.91 C
489.46 268.20 486.00 273.89 484.99 275.18 c
483.19 277.56 481.18 279.50 479.59 279.29 C
476.78 278.57 480.96 272.66 481.97 271.30 c
@c
B

@rax %%Note: Object
489.60 230.47 502.63 244.87 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
497.38 230.47 m
493.27 230.47 489.89 235.58 489.74 239.76 C
489.60 243.50 491.26 244.87 495.00 244.87 C
499.03 244.80 502.63 239.54 502.63 235.51 C
502.63 231.98 501.34 230.47 497.38 230.47 C
@c
494.93 235.30 m
495.94 233.78 498.17 231.84 499.61 231.91 C
502.42 232.20 498.96 237.89 497.95 239.18 c
496.15 241.56 494.14 243.50 492.55 243.29 C
489.74 242.57 493.92 236.66 494.93 235.30 c
@c
B

@rax %%Note: Object
512.64 151.20 525.67 165.60 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
520.42 151.20 m
516.31 151.20 512.93 156.31 512.78 160.49 C
512.64 164.23 514.30 165.60 518.04 165.60 C
522.07 165.53 525.67 160.27 525.67 156.24 C
525.67 152.71 524.38 151.20 520.42 151.20 C
@c
517.97 156.02 m
518.98 154.51 521.21 152.57 522.65 152.64 C
525.46 152.93 522.00 158.62 520.99 159.91 c
519.19 162.29 517.18 164.23 515.59 164.02 C
512.78 163.30 516.96 157.39 517.97 156.02 c
@c
B

@rax %%Note: Object
330.91 72.00 331.20 720.00 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
331.06 72.00 m
331.06 720.00 L
S

@rax %%Note: Object
345.31 72.00 345.60 720.00 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
345.46 72.00 m
345.46 720.00 L
S

@rax %%Note: Object
359.71 72.00 360.00 720.00 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
359.86 720.00 m
359.86 72.00 L
S

@rax %%Note: Object
374.11 72.00 374.40 720.00 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
374.26 72.00 m
374.26 720.00 L
S

@rax %%Note: Object
388.51 72.00 388.80 720.00 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
388.66 720.00 m
388.66 72.00 L
S

@rax %%Note: Object
431.71 72.00 432.00 720.00 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
431.86 72.00 m
431.86 720.00 L
S

@rax %%Note: Object
446.11 72.00 446.40 720.00 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
446.26 720.00 m
446.26 72.00 L
S

@rax %%Note: Object
460.51 72.00 460.80 720.00 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
460.66 72.00 m
460.66 720.00 L
S

@rax %%Note: Object
474.91 72.00 475.20 720.00 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
475.06 720.00 m
475.06 72.00 L
S

@rax %%Note: Object
489.31 72.00 489.60 720.00 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
489.46 72.00 m
489.46 720.00 L
S

@rax %%Note: Object
504.00 194.47 517.03 208.87 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
511.78 194.47 m
507.67 194.47 504.29 199.58 504.14 203.76 C
504.00 207.50 505.66 208.87 509.40 208.87 C
513.43 208.80 517.03 203.54 517.03 199.51 C
517.03 195.98 515.74 194.47 511.78 194.47 C
@c
509.33 199.30 m
510.34 197.78 512.57 195.84 514.01 195.91 C
516.82 196.20 513.36 201.89 512.35 203.18 c
510.55 205.56 508.54 207.50 506.95 207.29 C
504.14 206.57 508.32 200.66 509.33 199.30 c
@c
B

@rax %%Note: Object
518.26 144.00 518.54 172.80 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
518.40 144.00 m
518.40 172.80 L
S

@rax %%Note: Object
503.86 144.00 504.14 172.80 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
504.00 144.00 m
504.00 172.80 L
S

@rax %%Note: Object
302.26 684.00 302.54 712.80 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
302.40 684.00 m
302.40 712.80 L
S

@rax %%Note: Object
316.66 648.00 316.94 676.80 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
316.80 648.00 m
316.80 676.80 L
S

@rax %%Note: Object
316.66 684.00 316.94 712.80 @E
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
316.80 684.00 m
316.80 712.80 L
S

@rax %%Note: Object
331.20 71.93 395.06 101.38 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
366.12 87.55 m
366.62 85.82 368.57 83.88 370.80 83.88 c
371.95 83.88 374.11 84.53 374.98 86.54 c
375.26 87.34 376.13 87.05 375.19 85.39 c
373.54 82.01 369.22 80.42 365.26 83.09 c
364.10 83.66 362.66 85.32 362.09 86.76 C
356.62 85.68 L
358.06 84.02 359.42 82.58 361.51 80.86 c
365.47 77.47 371.66 75.89 375.98 80.14 c
378.86 83.09 379.08 87.12 378.22 90.00 C
366.12 87.55 L
@c
365.98 88.70 m
377.86 91.08 L
376.78 93.82 374.11 94.90 371.59 94.68 c
368.42 94.32 365.98 92.09 365.98 88.70 C
@c
349.92 85.54 m
348.55 87.77 342.36 91.80 338.69 90.94 c
336.46 90.65 336.46 88.20 338.69 86.83 c
341.57 85.10 346.54 84.10 349.92 85.54 C
@c
350.86 84.53 m
342.58 82.22 334.80 83.88 332.28 87.19 c
331.20 88.63 331.34 88.92 332.42 89.78 c
339.34 94.68 349.99 92.95 355.68 86.69 C
361.80 87.91 L
360.07 97.34 375.19 101.38 378.94 91.37 C
384.48 92.23 389.66 94.97 392.83 90.65 c
395.06 87.55 394.42 83.52 392.40 81.43 c
391.03 79.99 388.51 79.13 386.06 80.86 c
382.75 83.45 385.49 87.70 389.16 86.98 c
391.25 86.54 391.75 84.53 391.61 83.45 C
394.42 85.39 392.62 90.43 389.81 91.30 c
386.64 92.45 383.26 91.01 379.44 90.14 C
381.89 79.92 370.73 71.93 361.08 76.32 c
357.55 77.98 354.02 81.29 350.86 84.53 C
@c
B

@rax %%Note: Object
439.13 72.00 482.40 100.80 @E
 0 O 0 @g
1.00 1.00 1.00 0.21 k
0 J 0 j [] 0 d 0 R 0 @G
1.00 1.00 1.00 0.21 K
0 0.22 0.22 0.00 @w
481.25 72.00 m
478.51 75.24 476.28 77.98 473.62 80.42 c
471.02 82.58 468.29 84.53 465.12 85.82 c
461.81 87.34 457.13 88.70 452.81 88.78 c
449.64 88.78 446.98 88.49 444.96 87.41 c
443.09 86.33 441.58 84.67 441.58 82.44 c
441.58 79.99 442.66 76.75 446.33 75.60 c
447.62 75.24 448.13 76.10 448.13 76.61 c
448.27 77.04 447.70 77.83 447.70 78.70 c
447.70 79.70 448.27 80.57 448.92 81.29 c
449.71 81.86 450.94 82.30 451.94 82.30 c
454.54 82.30 456.77 80.42 456.77 77.98 c
456.77 76.61 456.19 75.53 455.26 74.66 c
454.32 73.73 452.74 73.01 450.79 73.01 c
447.84 73.01 444.74 74.45 442.87 76.39 c
441.86 77.40 441.29 78.70 440.71 79.85 c
439.13 84.24 440.06 87.84 443.38 90.86 c
445.61 93.02 448.78 94.54 453.46 94.54 c
457.92 94.54 463.32 92.81 468.50 89.21 c
470.74 87.62 472.82 85.46 474.84 82.87 c
477.50 79.78 479.88 76.10 482.40 72.14 C
481.25 72.00 L
@c
446.90 96.26 m
446.04 96.26 445.25 96.48 444.74 96.98 c
444.31 97.34 444.02 97.85 444.02 98.57 c
444.02 99.14 444.31 99.72 444.89 100.01 c
445.25 100.44 446.04 100.80 446.98 100.80 c
447.91 100.80 448.56 100.44 449.14 99.94 c
449.64 99.65 449.86 99.07 449.86 98.42 c
449.86 97.92 449.50 97.34 449.14 96.98 c
448.56 96.55 447.84 96.26 446.90 96.26 c
@c
458.57 96.26 m
457.56 96.26 456.98 96.55 456.41 96.98 c
455.90 97.34 455.69 97.85 455.69 98.57 c
455.69 99.14 455.98 99.79 456.55 100.15 c
457.06 100.44 457.70 100.80 458.57 100.80 c
459.50 100.80 460.37 100.44 460.80 99.94 c
461.30 99.65 461.52 99.07 461.52 98.57 c
461.52 97.92 461.30 97.34 460.80 96.98 c
460.22 96.55 459.50 96.26 458.57 96.26 c
@c
B

@rax 513.36 129.60 522.07 142.42 @E
[0.00001 0.07199 -0.07199 0.00001 522.00000 129.59999] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (36) @t
T
@rax 506.16 180.00 514.87 192.82 @E
[0.00001 0.07199 -0.07199 0.00001 514.79999 179.99998] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (38) @t
T
@rax 491.76 216.00 500.40 227.09 @E
[0.00001 0.07199 -0.07199 0.00001 500.39996 215.99998] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (41) @t
T
@rax 477.43 252.00 486.07 264.82 @E
[0.00001 0.07199 -0.07199 0.00001 485.99997 251.99998] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (45) @t
T
@rax 462.96 288.00 471.67 300.82 @E
[0.00001 0.07199 -0.07199 0.00001 471.59998 288.00000] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (48) @t
T
@rax 448.56 324.00 457.27 336.74 @E
[0.00001 0.07199 -0.07199 0.00001 457.19998 324.00000] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (52) @t
T
@rax 434.23 360.00 442.87 372.82 @E
[0.00001 0.07199 -0.07199 0.00001 442.79999 359.99997] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (55) @t
T
@rax 419.76 396.00 428.47 408.82 @E
[0.00001 0.07199 -0.07199 0.00001 428.39999 395.99997] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (59) @t
T
@rax 390.96 432.00 399.67 444.74 @E
[0.00001 0.07199 -0.07199 0.00001 399.59998 431.99997] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (62) @t
T
@rax 376.56 468.00 385.27 480.82 @E
[0.00001 0.07199 -0.07199 0.00001 385.19998 467.99997] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (65) @t
T
@rax 362.16 504.00 370.87 516.82 @E
[0.00001 0.07199 -0.07199 0.00001 370.79999 503.99997] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (69) @t
T
@rax 347.76 540.00 356.40 552.74 @E
[0.00001 0.07199 -0.07199 0.00001 356.39999 540.00000] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (72) @t
T
@rax 333.36 576.00 342.07 588.82 @E
[0.00001 0.07199 -0.07199 0.00001 342.00000 576.00000] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (76) @t
T
@rax 318.96 604.80 327.67 617.62 @E
[0.00001 0.07199 -0.07199 0.00001 327.59998 604.79999] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (79) @t
T
@rax 304.56 640.80 313.27 653.62 @E
[0.00001 0.07199 -0.07199 0.00001 313.19998 640.79999] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (83) @t
T
@rax 290.16 676.80 298.87 689.62 @E
[0.00001 0.07199 -0.07199 0.00001 298.79999 676.79999] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 167.00 z
0 0 (86) @t
T
@rax 187.20 381.53 226.15 388.87 @E
[0.07199 0.00000 0.00000 0.07199 187.20000 381.59998] @tm
 0 O 0 @g
1.00 1.00 1.00 0.21 k
e
/_R1-Helvetica 139.00 z
0 0 (Middle C) @t
T
@rs @rs
/$ctm matrix currentmatrix def
%EndTile
%EndColorLayer
@rs
@rs
%EndPage
%%Trailer
end
%%DocumentProcessColors: Cyan Magenta Yellow Black 
%%DocumentFonts: Helvetica 
%%DocumentSuppliedResources: procset wCorel4Dict
EJ RS
%%PageTrailer
2397 3162 0 0 CB
%%Trailer
SVDoc restore
end
%%DocumentSuppliedResources: procset Win35Dict 3 1

%%DocumentNeededResources: 
%%EOF


UltraTracker 1.5
----------------
<From the IBM "ultra150.zip" archive by Marc Schallehn, text file by
Muchael J. Sutter and update comments added by Marc Schallehn. -JH>

               Mysterious's ULTRA TRACKER File Format
                  by FreeJack of The Elven Nation
   (some additional infos on the new format (V1.4/5) by MAS -> * marked)

I've done my best to document the file format of Ultra Tracker (UT).
If you find any errors please contact me.
The file format has stayed consistent through the first four public releases.
At the time of this writting, Ultra Tracker is up to version 1.3
(* With version V1.4/5 there are some changes done in the format. *)

Thanks go to :
SoJa of YLYSY for help translating stuff.

Marc Andr? Schallehn
Thanks for putting out this GREAT program.
Also thanks for the info on 16bit samples.

With all this crap out of the way lets get to the format.


Sample Structure :
______________________________________________________________________________
Samplename : 32 bytes (Sample name)
DosName    : 12 bytes (when you load a sample into UT,
                      it records the file name here)
LoopStart  : dbl word (loop start point)
LoopEnd    : dbl word (loop end point)
SizeStart  : dbl word (see below)
SizeEnd    : dbl word (see below)
volume     : byte (UT uses a logarithmic volume setting, ranging from 0-255)
             (* V1.4: uses linear Volume ranging from 0-255 *)
Bidi Loop  : byte (see below)
FineTune   : word (Fine tune setting, uses full word value)
______________________________________________________________________________

8 Bit Samples  :

SizeStart  :
The SizeStart is the starting offset of the sample. 
This seems to tell UT how to load the sample into the Gus's onboard memory. 
All the files I have worked with start with a value of 32 for the first sample, 
and the previous SizeEnd value for all sample after that. (See Example below)
If the previous sample was 16bit, then SizeStart = (Last SizeEnd * 2)
SizeEnd : 
Like the SizeStart, SizeEnd seems to tell UT where to load the sample into the 
Gus's onboard memory. SizeEnd equal SizeStart + the length of the sample.

Example :
If a UT file had 3 samples, 1st 12000 bytes, 2nd 5600  bytes, 3rd 8000 byte. 
The SizeStart and SizeEnd would look like this:

Sample        SizeStart         SizeEnd
1st            32                12032
2nd            12032             17632
3rd            17632             25632


Samples may NOT cross 256k boundaries. If a sample is too large to fit into the
remaining space, its Sizestart will equal the start of the next 256k boundary.
UT does keep track of the free space at the top of the 256k boundaries, and
will load a sample in there if it will fit.
Example : EndSize = 252144
If the next sample was 12000 bytes, its SizeStart would be 262144, not 252144.
Note that this leaves 10000 bytes unused. If any of the following sample could
fit between 252144 and 262144, its Sizestart would be 252144.
Say that 2 samples after the 12000 byte sample we had a sample that was only
5000 bytes long. Its SizeStart would be 252144 and its SizeEnd would be 257144.
This also applies to 16 Bit Samples.

16 Bit Samples :
16 bit samples are handled a little different then 8 bit samples.
The SizeStart variable is calculated by dividing offset (last SizeEnd)
by 2. The SizeEnd variable equals SizeStart + (SampleLength / 2).
If the first sample is 16bit, then SizeStart = 16.
Example :
          sample1 = 8bit, 1000 bytes
          sample2 = 16bit, 5000 bytes

          sample1 SizeStart = 32
                  SizeEnd   = 1032 (32 + 1000)

          sample2 SizeStart = 516 (offset (1032) / 2)
                  SizeEnd   = 3016 (516 + (5000/2))


If a 16bit sample is loaded into banks 2,3, or 4
the SizeStart variable will be
(offset / 2) + 262144 (bank 2)
(offset / 2) + 524288 (bank 3)
(offset / 2) + 786432 (bank 4)
The SizeEnd variable will be
SizeStart + (SampleLength / 2) + 262144 (bank 2)
SizeStart + (SampleLength / 2) + 524288 (bank 3)
SizeStart + (SampleLength / 2) + 786432 (bank 4)

BiDi Loop : (Bidirectional Loop)
UT takes advantage of the Gus's ability to loop a sample in several different
ways. By setting the Bidi Loop, the sample can be played forward or backwards,
looped or not looped. The Bidi variable also tracks the sample
resolution (8 or 16 bit).

The following table shows the possible values of the Bidi Loop.
Bidi = 0  : No looping, forward playback,  8bit sample
Bidi = 4  : No Looping, forward playback, 16bit sample
Bidi = 8  : Loop Sample, forward playback, 8bit sample
Bidi = 12 : Loop Sample, forward playback, 16bit sample
Bidi = 24 : Loop Sample, reverse playback 8bit sample
Bidi = 28 : Loop Sample, reverse playback, 16bit sample
______________________________________________________________________________
Event Structure:
______________________________________________________________________________
Note                : byte (See note table below)
SampleNumber        : byte (Sample Number)
Effect1             : nib (Effect1)
Effect2             : nib (Effect2)
EffectVar           : word (Effect variables)

The High order byte of EffectVar is the Effect variable for Effect1.
The Low order byte of EffectVar is the Effect variable for Effect2.

UT uses a form of compression on repetitive events. Say we read in the first
byte, if it = $FC then this signifies a repeat block. The next byte is the
repeat count. followed by the event structure to repeat.
If the first byte read does NOT = $FC then this is the note of the event.
So repeat blocks will be 7 bytes long : RepFlag      : byte ($FC)
                                        RepCount     : byte
                                        note         : byte
                                        samplenumber : byte
                                        effect1      : nib
                                        effect2      : nib
                                        effectVar    : word

Repeat blocks do NOT bridge patterns. 
______________________________________________________________________________
Note Table:
______________________________________________________________________________
note value of 0 = pause
C-0 to B-0    1 to 12
C-1 to B-1    13 to 24
C-2 to B-2    26 to 36
C-3 to B-3    39 to 48
C-4 to B-4    52 to 60
______________________________________________________________________________
Offset     Bytes            Type                   Description
______________________________________________________________________________
0             15           byte           ID block : should contain
                                                     'MAS_UTrack_V001'
                                          
                                          (* V1.4: 'MAS_UTrack_V002')

                                          (* V1.5: 'MAS_UTrack_V003')

15            32           AsciiZ         Song Title
47            1            reserved       This byte is reserved and
                                          always contain 0;

                                          (* V1.4: jump-value: reserved * 32; 
                                           space between is used for song
                                           text;
                                           [reserved * 32] = RES ! )

48+RES        1            byte           Number of Samples (NOS)
49+RES        NOS * 64     SampleStruct   Sample Struct (see Sample Structure)

Patt_Seq = 48 + (NOS * 64) + RES
                       
Patt_Seq          256        byte            Pattern Sequence
Patt_Seq+256      1          byte            Number Of Channels (NOC) Base 0
Patt_Seq+257      1          byte            Number Of patterns (NOP) Base 0

                                             (* V1.5: PAN-Position Table
                                              Length: NOC * 1byte
                                              [0 left] - [0F right] )

NOC+Patt_Seq+258      varies     EventStruct     Pattern Data (See Event
Structure)

______________________________________________________________________________
The remainder of the file is the raw sample data. (signed)
______________________________________________________________________________

That should about cover it. If you have any questions, feel free to e-mail
me at freejack@shell.portal.com

I can also be contacted on The UltraSound Connection   (813) 787-8644 
The UltraSound Connection is a BBS dedicated to the Gravis Ultrasound Card.

Also I'm the author of Ripper and Gvoc. If anyone has any questions or
problems, please contact me.

Midi And 4 Channel Surround Sound
---------------------------------

I got some email today asking how to do Dolby surround midi files. I tought
it could be of interest to other people. Note: Only a ram wavetable midi
device can reproduce the surround channel, with this method, but the center
channel can be done on any midi playback device.

If you want something that comes out from the center channel, just put the
pan on the midi channel at 64. Coming from left? just put the panning to
the left (under 64). Coming from right? just put the panning to the right
(over 64).

For elements that have to be present on both sides i'd recommend dupli
cating the track and giving two opposite panning. This gives much better
results than panning to center and shouldn't come out the front channel.
Also, if you sampled something in stereo, then you can use the left signal
to create a patch for the left track and a separate patch for the right
track. This works very well.

Finally, for the surround channel, it's the same as stereo snce you use
two tracks. One for left, one for right. On the left you use the regular
instrument, and on the right, you use the *inverted* signal (a flip around
the X axis of the signal, as done by the invert function of most wave
editors). When i was asked the question, i hadn't tried this, but i just
did and it works. I use my own circuit to decode the surround channel (a
very simple design, found on archive.epas.utoronto.ca under the filename
surround.txt) and have not tried it on a real dolby surround or pro logic
unit, but it should work equally well.

Of course by using two voices per sounds, you use the GUS voices much more
rapidly, but the sound is much better, even when it's the same signal on
both channels because of the slight phasing between the left and right
channels. Plus, you get much more balance control.

So when you want something on the left, center or right channel, just use
mono panning. For the surround channel, on top of that, just add two voices
panned left and right, one playing the inverse of the other. Note, in mono,
this will disapear totally...

This should be very simple to understand by looking at this diagram:

P=panning, V=volume
                             CENTRE CHANNEL
                             P=64, V=presence
                              V        /|\
                              variation |
                                       \|/
 LEFT CHANNEL                                                   RIGHT CHANNEL
P=1, V=presence               P variation <-->                P=127, V=presence

                              Vleft=Vright /|\
                              variation     |
                                           \|/

                            P=1,normal patch;P=127,inverted patch,
                            Vleft=Vright=presence
                            SURROUND CHANNEL (either 1 or two speakers,
                                             this channel is mono)

For stereo instead of panned mono:
P=1, left signal patch;P=127, right signal patch, Vleft=left presence,
Vright=right presence. So for <--> variation, you use the two volumes instead
of P.

Hope this helps.

Ciao,
--
Francois Dion
    '  _   _   _
 CISM (_) (_)  _) FM       Montreal , Canada       Email: CISM@ERE.UMontreal.CA
      (_)  / . _)             10000 Watts          Telephone no: (514) 343-7511
_______________________________________________________________________________
Audio-C-DJ-Fractals-Future-Label-Multimedia-Music-Radio-Rave-Video-VR-Volvo-...
                    ???????????????????????????????????
                    ? Programming the Microsoft Mouse ?
                    ???????????????????????????????????

                 Written for the PC-GPE by Mark Feldman
              e-mail address : u914097@student.canberra.edu.au
                               myndale@cairo.anu.edu.au

              ?????????????????????????????????????????????
              ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
              ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
              ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Introduction ?
????????????????

A complete list of mouse function calls can be found in the file GMOUSE.TXT,
the file contains calls for both Microsoft (2 button) and Genius (3 button)
modes.

Calling these functions from within a Pascal program is a fairly simple
matter. This procedure would get the mouse position and button states:

const MOUSEINTR = $33;

procedure GetMousePos(var x, y : word; var button1, button2 : boolean);
var regs : registers;
begin
  regs.ax := 3;
  Intr(MOUSEINTR, regs);
  x := regs.cx;
  y := regs.dx;
  button1 := (regs.bx and 1) <> 0;
  button2 := (regs.bx and 2) <> 0;
end;


The mouse position is returned in variables x and y, the button states are
returned in variable button1 and button2 (true = button is pressed).


?????????????????????????????????????????????????????????????????????????????
? Writing Custom Handlers ?
???????????????????????????

Most mouse drivers do not support SVGA modes, so you must write custom
handlers if you want mouse support for these modes.

Rather than writing an entire mouse driver, you can write a simple handler
routine to take care of the graphics and tell the mouse driver to call it
whenever the mouse does anything. This function is descibed in the GMOUSE.DOC
file, but this demo Pascal program shows the general idea. It sets mode 13h,
resets the mouse and waits for a key to be pressed. Whenever you do anything
to the mouse (moving it or pressing a button) the handler will get called
and it will draw a pixel on the screen. The color of the pixel depends on
which buttons are being pressed.

Uses Crt, Dos;

{$F+}
{ called with bl = buttons, cx = x * 2, dx = y }
procedure Handler; far; assembler;
asm

  { This mouse "handler" just draws a pixel at the current mouse pos }
  pusha
  mov ax, $A000
  mov es, ax
  shr cx, 1
  xchg dh, dl
  mov di, dx
  shr dx, 2
  add di, dx
  add di, cx
  mov al, bl
  inc al
  stosb
  popa
end;
{$F-}

begin
  asm

    { Set graphics mode 13h }
    mov ax, $13
    int $10

    { Initialize mouse driver }
    xor ax, ax
    int $33

    { Install custom handler }
    mov ax, SEG Handler
    mov es, ax
    mov dx, OFS Handler
    mov ax, 12
    mov cx, $1F
    int $33

    { Wait for a key press }
    xor ah, ah
    int $16

    { Back to text mode }
    mov ax, 3
    int $10
  end;
end.




Alternatively you may wish to write your own interrupt handler to process
mouse events as they happen. When a mouse event occurs, 3 interrupts are
generated and the bytes are availble via the COM port.

                  ????????????????????????????
                  ?        Interrupt    Port ?
                  ????????????????????????????
                  ? COM1      $0C       $3F8 ?
                  ? COM2      $0B       $3F8 ?
                  ????????????????????????????

The three bytes sent are formatted as follows:


               1st byte        2nd byte         3rd byte
          ???????????????????????????????????????????????????
          ?-?1?????X?X?Y?Y??-?0?X?X?X?X?X?X??-?0?Y?Y?Y?Y?Y?Y?
          ???????????????????????????????????????????????????
               ? ? ??? ???      ???????????      ???????????
               ? ?  ?   ?            ?                ?
               ? ?  ?   ???????????????????????       ?
               ? ?  ??????????       ?        ?       ?
               ? ?          ??? ???????????  ??? ???????????
               ? ?         ??????????????????????????????????
               ? ?         ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ?
 Left Button ??? ?         ??????????????????????????????????
Right Button ?????            X increment      Y increment


The X and Y increment values are in 2's compliment signed char format. (BTW
thanks go to Adam Seychell for posting this info to comp.os.msdos.programmer).


A simple Borland Pascal 7.0 mouse handler follows. First we declare a few
things we'll need:


??????????????????????????????????????????????????????????????????????
Uses Crt, Dos;

{$F+}

const COM1INTR = $0C;
      COM1PORT = $3F8;

var bytenum : word;
    combytes : array[0..2] of byte;
    x, y : longint;
    button1, button2 : boolean;
    MouseHandler : procedure;
??????????????????????????????????????????????????????????????????????

The bytenum variable is used to keep track of which byte is expected next
(ie 0, 1 or 2). The combytes variable is simply an array to keep track of
bytes received so far. The mouse position will be stored in the x and y
varaibles (note that this example will not perfrom any range checking).
Button1 and button2 will be used to store the states of each of the buttons.
MouseHandler will be used to store the normal mouse driver event handler.
We'll need it to reset everything once we are finished.

Here's the actual handler:

??????????????????????????????????????????????????????????????????????
procedure MyMouseHandler; Interrupt;
var dx, dy : integer;
var inbyte : byte;
begin

  { Get the port byte }
  inbyte := Port[COM1PORT];

  { Make sure we are properly "synched" }
  if (inbyte and 64) = 64 then bytenum := 0;

  { Store the byte and adjust bytenum }
  combytes[bytenum] := inbyte;
  inc(bytenum);

  { Have we received all 3 bytes? }
  if bytenum = 3 then
    begin
      { Yes, so process them }
      dx := (combytes[0] and 3) shl 6 + combytes[1];
      dy := (combytes[0] and 12) shl 4 + combytes[2];
      if dx >= 128 then dx := dx - 256;
      if dy >= 128 then dy := dy - 256;
      x := x + dx;
      y := y + dy;
      button1 := (combytes[0] And 32) <> 0;
      button2 := (combytes[0] And 16) <> 0;

      { And start on first byte again }
      bytenum := 0;
    end;

  { Acknowledge the interrupt }
  Port[$20] := $20;
end;
??????????????????????????????????????????????????????????????????????

Once again pretty simple stuff. We just read the byte from the com1 port and
figure out if it's time to do anything yet. If bit 6 is set to 1 then we
know that it's meant to be the first byte of the 3, so we reset our
bytenum variable to zero (in a perfect world bytes would always come in 3's
and we would never need to check, but it never hurts to be careful).

When 3 bytes have been received we simple decode them according to the
diagram above and update the appropriate variables accordingly.

The 'Port[$20] := $20;' command just lets the interrupt controller know we
have processed the interrupt so it can send us the next one when it wants to.

Note that the above "handler" does nothing more than keep track of the
current mouse position and button states. If we were writing a proper mouse
driver for an SVGA game we would also have to write custom cursor routines.
I'll leave that bit to you!

To actually install our mouse driver we'll have to set up all the variables,
save the address of the current mouse handler and install our own. We'll
also need call the existing mouse driver to set up the COM1 port to make
sure it sends us the mouse bytes as it receives them. We could do this
ourselves, but why make life harder than it already is?

??????????????????????????????????????????????????????????????????????
procedure InitMyDriver;
begin

  { Initialize the normal mouse handler }
  asm
    mov ax, 0
    int $33
  end;

  { Initialize some of the variables we'll be using }
  bytenum := 0;
  x := 0;
  y := 0;
  button1 := false;
  button2 := false;

  { Save the current mouse handler and set up our own }
  GetIntVec(COM1INTR, @MouseHandler);
  SetIntVec(COM1INTR, Addr(MyMouseHandler));
end;
??????????????????????????????????????????????????????????????????????


And finally when our program is finished it'll need to clean up after
itself and return control back to the normal mouse driver:

??????????????????????????????????????????????????????????????????????
procedure CleanUpMyDriver;
begin
  SetIntVec(COM1INTR, @MouseHandler);
end;
??????????????????????????????????????????????????????????????????????


This little bit of source will test the above code. It does nothing more
than repeatedly write the mouse position and button states to the screen
until a keyboard key is pressed:

??????????????????????????????????????????????????????????????????????
begin
  ClrScr;
  InitMyDriver;
  while not keypressed do
    WriteLn(x : 5, y : 5, button1 : 7, button2 : 7);
  CleanUpMyDriver;
end.
??????????????????????????????????????????????????????????????????????
             PROGRAMMER'S  REFERENCE  FOR  GENIUS  MOUSE  DRIVER



The Genius Mouse Driver enables you to use mouse hardware to move an on-screen
cursor and control its movement through a software program.  Various functions
allow you to determine cursor placement, cursor shape, and button status.

In order for you to interface your Genius Mouse with an application program, the
following information on the Genius Driver has been provided.



GMOUSE Driver supports a hardware text cursor,  a software  text   cursor, and
a graphics cursor. A hardware text cursor is a blinking cursor which moves from
one character to another on-screen. This blinking cursor may take the form of a
block or underscore. A software text cursor makes use of display attributes to
change the visual appearance of a character on-screen.  Movement is from
character to character. A graphics cursor is a shape that moves over on-screen
images.

You can choose any of these three cursors to use on-screen, however, only one
cursor can be displayed at a given time.  Also, within your application program,
you can switch back and forth between cursors.


Display the Graphics Cursor

The cursor appears on-screen or disappears from the screen through the calling
program.  This cursor consists of a block of pixels.  As this block moves
on-screen and affects the pixels beneath it, the cursor shape and background are
created. This interaction is defined by two 16-by-16 bit arrays;
one is the screen mask and the other is the cursor mask. The screen mask
determines what part of the cursor pixel is to be the shape, and what part  is
is to be the background. The cursor mask determines which pixels contribute to
the color of the cursor. Whenever changes are made to the screen which lie
directly  beneath the cursor, the cursor should be concealed so that old values
are not restored to the screen.

Please note that with a high resolution mode, you have a 16-by-16 pixel block;
with a medium resolution (four color) mode, you have a 8-by-16 pixel block; with
a medium resolution (sixteen color) mode, you have a 4-by-16 pixel block.

Refer to function 9.

To create the cursor, the software uses data from the computer's screen memory
which defines the color of each pixel on-screen.  Operations are performed that
affect individual screen bits.  Software ANDs the screen mask defining the
pixels under the cursor and XORs the cursor mask with the result of the AND
operation.

Note the results when:






                                 page 1

Screen Mask Bit is      Cursor Mask Bit is      Resulting Screen Bit is
------------------      ------------------      -----------------------
        0                        0                         0
        0                        1                         1
        1                        0                     unchanged
        1                        1                      inverted

With each mouse function, a reference to the graphics cursor location is in
reference to a point on-screen directly beneath the cursor.  This point that the
mouse software uses to determine the cursor coordinates is known as the cursor's
hot spot.
Generally, the upper_left hand corner of the cursor block is designated as the
coordinates for the cursor default value. ((0,0) are the upper_left hand corner
coordinates.)

Software Text Cursor

You can use this text cursor when your computer is in one of the text modes.  By
changing the character attributes beneath the cursor, the appearance of the
character is influenced on-screen.  This effect on the text cursor can be
defined by two 16-bit mask values. These bits can be described as follows:
bit 15 sets the blinking (1) or non-blinking (0) character ; bit 12 - 14 set the
background (1); bits 8 - 10 set the foreground color; and bits 0 - 7 set the
character code. These values in the screen mask and the cursor mask
determine the character's new attributes when the cursor is covering the
character.  The screen mask decides which of the character's attributes are
maintained. The cursor mask decides in what manner the attributes are altered
to produce the cursor.

In creating this cursor, the software works from data which defines each
character on the screen.  The software first ANDs the screen mask and the screen
data bit for the character beneath the cursor.  Next, the software XORs the
cursor mask and the result of the AND operation.

When a function refers to the text cursor location, it gives the coordinates of
the character beneath the cursor.

Refer  to function 10.

Hardware Text Cursor

This cursor is also available when the computer is in one of the text modes.
This cursor is the one seen on-screen when the computer is powered on.  It
consists of 8 pixels wide and 8 to 14 pixels tall.  Software allows you to use
this cursor for your needs. Scan lines determine a cursor's appearance
on-screen. A scan line consists of a horizontal set of pixels.
If a line is on, there will be flashing on the screen. If a line is off, there
is no effect. Scan lines are numbered from 0 to 7, or 0 to 11 depending on the
type of display used. 0 indicates the top scan line.

Refer to function 10.







                                 page 2

Mouse functions can give the status of the mouse buttons and the number of times
a certain button has been pressed and released.  The button status is given as
an integer. If a bit is set to 1 the button is down; if a bit is set to 0, the
button is up.
      Bit 0 - Left Button Status
      Bit 1 - Right Button Status
      Bit 2 - Middle Button Status
Each time a mouse button is pressed, a counter records the number of presses and
releases.  The software sets the counter to zero once it has been read or after
a reset.



The motion of the mouse can be expressed in a unit of distance (mouse motion)
and is approximately 1/200 of an inch.

With mouse movement, mouse software determines a horizontal and vertical mouse
motion count.  This count is used by the software to move a cursor a certain
number of pixels on-screen.  Software defines mouse motion sensitivity (the
number of mouse motions needed to move the cursor 8 pixels on-screen) and this
sensitivity determines the rate at which the cursor moves on-screen.

Refer to function 15.



Mouse software supports an internal flag.  This flag determines when the cursor
should appear on-screen.  If the flag equals 0, the cursor appears on-screen; if
the flag is any other number, the cursor disappears from the screen.

You can call functions 1 and 2 a number of times, however, if you call function
2, you must call function 1 later.  This is necessary to restore the flag's
previous value.

Refer to functions 1 and 2.



To make mouse function calls:

Load the appropriate registers (AX, BX, CX, DX) with the parameter values.
These correspond to G1%, G2%, G3%, and G4% as shown in the BASIC example to
follow.  Then execute software interrupt 51 (33H).  The values given by the
mouse functions will be installed in the registers.

Example:

   ; * set cursor to location (150,100)
   Mov AX,4    ;(function call 4)
   Mov CX,150  ;(set horizontal to 150)
   Mov DX,100  ;(set vertical to 100)
   Int 51(33H) ;(interrupt to mouse)

It is important to note that before using INT 33H, one should verify the
presence of the mouse driver.  Executing an INT 33H will cause uncertain results
if the mouse driver is not loaded.  Assume a mouse driver is present when INT
33H vector is non-zero and the vector does not point to an IRET instruction.

                                 page 3

Note:  When making a mouse call in Assembly Language, expect somewhat of a
different value for the fourth parameter (when compared with calls using a BASIC
program) involving functions 9, 12, and 16.



To make  mouse function calls:

  Set a pair of integer variables in your program for the offset and the segment
  of the mouse driver entry point.

  In order to obtain the offset and segment values, the following statements
  must be inserted into your program before any calls to mouse functions:

10 DEF SEG = 0
15 ' GET GMOUSE ENTRY POINT
20 GMSEG   = PEEK( 51*4 + 2 ) + 256 * PEEK( 51*4 + 3 )   ' GET SEGMENT ENTRY
30 GMOUSE  = 2 + PEEK( 51*4 ) + 256 * PEEK( 51*4 + 1 )   ' GET OFFSET  ENTRY
40 DEF SEG = GMSEG           ' SET SEGMENT REGISTER AS THE SEGMENT OF GMOUSE

To enter the mouse driver, use the CALL statement:

  CALL GMOUSE (G1%, G2%, G3%, G4%)

GMOUSE contains the entry offset of the mouse driver.  G1%, G2%, G3%, and G4%
are the integer variables given in the call.  These four must be specified in
the CALL statement even if a value is not assigned.  When a value is assigned,
it must be an integer, that is, a whole number.

Example:

50  ' Find the Activated Mode of Genius Mouse
60  G1% = 0 : G2% = 0
70  CALL GMOUSE ( G1%, G2%, G3%, G4% )
80  IF G2% AND 2 THEN PRINT "Genius Mouse ( 2_Button Mode ) Enable"
90  IF G2% AND 3 THEN PRINT "Genius Mouse ( 3_Button Mode ) Enable"
100 IF NOT G1%   THEN PRINT "Can't Find Genius Mouse"



These functions listed apply to the Genius Mouse.  Further descriptions of each
mouse function will be given in the following pages.

Functions                                         Function Number
-----------------------------------------------------------------
Reset Genius Mouse Driver                                 0
Enable Cursor Display                                     1
Disable Cursor Display                                    2
Read Cursor Location & Button State of Genius Mouse       3
Set Cursor Location of Genius Mouse                       4
Read Button Press State of Genius Mouse                   5
Read Button Release State of Genius Mouse                 6
Define Horizontal (X) Range of Cursor Location            7
Define Vertical (Y) Range of Cursor Location              8
Define Graphics Mode Cursor Style                         9
Define Text Mode Cursor Style                            10
Read Genius Mouse Motion Number                          11

                                 page 4

Define Event Handler Entry Location                      12
Enable Light Pen Emulation Function                      13
Disable Light Pen Emulation Function                     14
Define Sensitivity (Mouse Motion/Pixel) of Genius Mouse  15
Disable Cursor Display in Special Range                  16
Define Double-Speed Threshold                            19
Swap Event Handler Entry Location                        20
Get Mouse Driver State Storage Size                      21
Save Mouse Driver state                                  22
Restore Mouse Driver state                               23
Set CRT Page Number                                      29
Read CRT Page Number                                     30

EGA functions are described in Section *** 7.



You'll notice that with the following mouse function descriptions, the
parameters needed to make the calls and the expected outcome (return) for each
is indicated. Also,  any special conditions regarding any of the mouse functions
have been included.  Further, an example of a program has been provided in order
for you to understand how to make the call.

The input and return values are presented for 8086 registers and for BASIC in
the following pages.

It is important to note that each mouse function call needs four parameters.
The Genius Mouse software does not verify any input values, and therefore, if
any incorrect values are given, uncertain results will occur.

Function 0: Reset Genius Mouse Driver

Function 0 gives the current status of the mouse hardware plus the current
status of the mouse software.  The calling program is able to determine the
presence of a mouse driver and/or a serial port.

This function resets the mouse driver to the following default status as
indicated:

Variable                                Value
------------------------------------------------------------------------------
internal cursor flag                    -1 (cursor concealed)
graphics  cursor shape                  horizontal oval
text cursor                             reverse video
user-defined call mask                  all zeroes
light pen emulation mode                enabled
vertical mouse motion/pixel ratio       16 to 8
horizontal mouse motion/pixel ratio     8 to 8
vertical min/max cursor coordinates     0/current display mode y values minus 1
horizontal min/max cursor coordinates   0/current display mode x values minus 1

8086 Register
Input:  AX = 0
Return: AX = mouse state (-1: installed, 0: not installed)
        BX = number of buttons (2 button mode, 3 button mode)



                                 page 5

BASIC
Input:  G1% = 0
Return: G1% = mouse state (-1: installed, 0: not installed)
        G2% = number of buttons (2 button mode, 3 button mode)

Example:  Used initially to determine if the GMOUSE driver is present and to
          reset GMOUSE.

50  ' Find the Actived Mode of Genius Mouse
60  G1% = 0 : G2% = 0
70  CALL GMOUSE ( G1%, G2%, G3%, G4% )
80  IF G2% AND 2 THEN PRINT "Genius Mouse ( 2_Button Mode ) Enable"
90  IF G2% AND 3 THEN PRINT "Genius Mouse ( 3_Button Mode ) Enable"
100 IF NOT G1%   THEN PRINT "Can't Find Genius Mouse"

Function 1: Enable Cursor Display

Function 1 increments the internal cursor flag counter.  If the counter is zero,
the cursor is enabled and appears on-screen.

The default value is -1 which indicates a concealed cursor.  Function 1 must be
called to display the cursor.  In case the internal cursor flag is already zero,
a call to this function produces no effect.

8086 Register
Input:  AX = 1
Return: none

BASIC
Input:  G1% = 1
Return: none

Example:

110  ' Enable Genius Mouse's Cursor
120  G1% = 1
130 CALL GMOUSE ( G1%, G2%, G3%, G4% )

Function 2: Disable Cursor Display

Function 2 disables the cursor by removing it from the screen and decrementing
the internal cursor flag.  Even though the cursor cannot be seen, it still
tracks any motion made with the mouse.

You should use this function before changing any portion of the screen
containing the cursor.  You will avoid the problem of the cursor affecting
screen data.

Keep in mind that whenever your program calls function 2, it must later call
function 1 to return the internal cursor flag to its default value.  In
addition, if your program changes the screen mode, function 2 is called
automatically. Therefore, the cursor's movement is enabled the next time it is
displayed.

Call function 2 at the end of a program in order to conceal the cursor.  This
ensures that nothing remains on-screen.


                                 page 6

8086 Register
Input:  AX = 2
Return: none

BASIC
Input:  G1% = 2
Return: none

Example:

110  ' Disable Genius Mouse's Cursor
120  G1% = 2
130 CALL GMOUSE ( G1%, G2%, G3%, G4% )

Function 3: Read Cursor Location & Button State of Genius Mouse

Function 3 gives the status of mouse buttons, plus cursor location.

Button status consists of a single integer value:
  Bit 0 = left button (2 button mode, 3 button mode)
  Bit 1 = right button (2 button mode, 3 button mode)
  Bit 2 = middle button (3 button mode)

The bit is 1 when the button is pressed.  The bit is 0 when the button is
released.

8086 Register
Input:  AX = 3
Return: BX = button status
        CX = horizontal cursor coordinate
        DX = vertical cursor coordinate

BASIC
Input:  G1% = 3
Return: G2% = button status
        G3% = horizontal cursor coordinate
        G4% = vertical cursor coordinate

Example:

110  ' Read Genius Mouse Location & Button State
120  G1% = 3
130 CALL GMOUSE ( G1%, G2%, G3%, G4% )
140 PRINT "Genius Mouse Location : X_Coord=" G3% " Y_Coord=" G4%
150 IF G2% AND 1 THEN PRINT "Left Button"
160 IF G2% AND 2 THEN PRINT "Right Button"
170 IF G2% AND 4 THEN PRINT "Middle Button"
180 PRINT "Pressed"

Function 4: Set Cursor Location of Genius Mouse

Function 4 sets the current cursor location.  Values must be within the
coordinate ranges for the virtual screen and, if necessary, are rounded to the
nearest values allowed for the current screen mode.




                                 page 7

  Screen     Display        Virtual         Cell        Bits/Pixel
   Mode      Adapter       Screen (XxY)     Size      Graphics Mode
---------  ------------  ---------------  --------   ----------------
    0      C, E, 3270     640 x 200        16 x 8          -
    1      C, E, 3270     640 x 200        16 x 8          -
    2      C, E, 3270     640 x 200         8 x 8          -
    3      C, E, 3270     640 x 200         8 x 8          -
    4      C, E, 3270     640 x 200         2 x 1          2
    5      C, E, 3270     640 x 200         2 x 1          2
    6      C, E, 3270     640 x 200         1 x 1          1
    7      M, E, 3270     640 x 200         8 x 8          -
    D      E              640 x 200        16 x 8          2
    E      E              640 x 200         1 x 1          1
    F      E              640 x 350         1 x 1          1
    10     E              640 x 350         1 x 1          1
    30     3270           720 x 350         1 x 1          1
           H              720 x 348         1 x 1          1

Display Adapter:
  M = IBM Monochrome Display/Printer Adapter
  C = IBM Color/Graphics Adapter
  E = IBM Enhanced Graphics Adapter
3270 = IBM All Points Addressable Graphics Adapter (3270 PC)
  H = Hercules Monochrome Graphics Card

8086 Register
Input:  AX = 4
        CX = new horizontal cursor coordinate
        DX = new vertical cursor coordinate
Return: none

BASIC
Input:  G1% = 4
        G3% = new horizontal cursor coordinate
        G4% = new vertical cursor coordinate
Return: none

Example:

110  ' Set Cursor Location at the Upper_Left Corner of Screen
120 G1% = 4
130 G3% = 0 : G4% = 0
140 CALL GMOUSE ( G1%, G2%, G3%, G4% )

Function 5: Read Button Press State of Genius Mouse

Function 5 provides status on the specified button, gives the number of button
presses since the last call, and produces the location of the cursor at last
button press.

Button status consists of a single integer value.  Again, as in function 3:
             Bit 0 = left button (2 button mode, 3 button mode)
             Bit 1 = right button (2 button mode, 3 button mode)
             Bit 2 = middle button (3 button mode)

The bit is 1 when the button is pressed.  The bit is 0 when the button is
released.

                                 page 8

The number of button presses will always fall in the range of 0 to 32767.  There
is no indicator for overflow.  Following this function call, the count is reset
to zero.

8086 Register
Input:  AX = 5
        BX = button status (left = 0, right = 1, middle = 2)
Return: AX = button status
        BX = number of button presses
        CX = horizontal cursor coordinate at last press
        DX = vertical cursor coordinate at last press

BASIC
Input:  G1% = 5
        G2% = button status (left = 0, right = 1, middle = 2)
Return: G1% = button status
        G2% = number of button presses
        G3% = horizontal cursor coordinate at last press
        G4% = vertical cursor coordinate at last press

Example:

110 ' Read the Left Button Press State of Genius Mouse
120 G1% = 5 : G2% = 2
130 CALL GMOUSE ( G1%, G2%, G3%, G4% )
140 IF G1% AND 2 THEN PRINT "The Middle Button Pressed at X_loc=" G3%

Function 6: Read Button Release State of Genius Mouse

Function 6 provides status on the specified button, gives the number of button
releases since the last call, and provides the location of the cursor at the
last button release.

Button status consists of a single integer value.  Again, as in function 3:
             Bit 0 = left button (2 button mode, 3 button mode)
             Bit 1 = right button (2 button mode, 3 button mode)
             Bit 2 = middle button (3 button mode)

The bit is 1 when the button is pressed.  The bit is 0 when the button is
released.

The number of button releases will always fall in the range of 0 to 32767.
There is no indicator for overflow.  Following this function call, the count is
reset to zero.

8086 Register
Input:  AX = 6
        BX = button status (left = 0, right = 1, middle = 2)
Return: AX = button status
        BX = number of button releases
        CX = horizontal cursor coordinate at last release
        DX = vertical cursor coordinate at last release

BASIC
Input:  G1% = 6
        G2% = button status (left = 0, right = 1, middle = 2)


                                 page 9

Return: G1% = button status
        G2% = number of button releases
        G3% = horizontal cursor coordinate at last release
        G4% = vertical cursor coordinate at last release

Example:

110 ' Read the Left Button Release State of Genius Mouse
120 G1% = 6 : G2% = 2
130 CALL GMOUSE ( G1%, G2%, G3%, G4% )
140 IF NOT G1% OR &HFFFB THEN PRINT "The Middle Button Released at X_loc=" G3%

Function 7: Define Horizontal (X) Range of Cursor Location

Function 7 defines the horizontal range of the cursor on-screen.  As a result,
cursor movement is limited to this specified area.  If a cursor happens to be
outside of this area when a call is made, the cursor is moved to just inside the
area.

8086 Register
Input:  AX = 7
        CX = minimum horizontal cursor coordinate
        DX = maximum horizontal cursor coordinate
Return: none

BASIC
Input:  G1% = 7
        G3% = minimum horizontal cursor coordinate
        G4% = maximum horizontal cursor coordinate
Return: none

Example:

110 ' Enable Cursor in Horizontal Range between 100 to 200
120 G1% = 7
130 G2% = 100 : G3% = 200
140 CALL GMOUSE ( G1%, G2%, G3%, G4% )

Function 8: Define Vertical (Y) Range of Cursor Location

Function 8 defines the vertical range of the cursor on-screen.  As a result,
cursor movement is limited to this specified area.  If a cursor happens to be
outside of this area when a call is made, the cursor is moved to just inside the
area.

8086 Register
Input:  AX = 8
        CX = minimum vertical cursor coordinate
        DX = maximum vertical cursor coordinate
Return: none

BASIC
Input:  G1% = 8
        G3% = minimum vertical cursor coordinate
        G4% = maximum vertical cursor coordinate
Return: none


                                 page 10

Example:

110 ' Enable Cursor in Vertical Range between 100 to 200
120 G1% = 8
130 G2% = 100 : G3% = 200
140 CALL GMOUSE ( G1%, G2%, G3%, G4% )

Function 9: Define Graphics Mode Cursor Style

Function 9 defines the style of the cursor in terms of color, shape, and center
for the graphics.  As mentioned before, this cursor is a 16-by-16 pixel block
and is defined by two 16-bit arrays (the screen mask bit and the cursor mask
bit). Cursor coordinates for the hot spot must be in the range of -16 to +16.
8086 Register
Input:  AX = 9
        BX = horizontal cursor hot spot
        CX = vertical cursor hot spot
        DX = pointer to screen and cursor mask
Return: none

BASIC
Input:  G1% = 9
        G2% = horizontal cursor hot spot
        G3% = vertical cursor hot spot
        G4% = pointer to screen and cursor mask
Return: none

Example:

10  ' Define the screen mask
20  '
30    cursor (0,0)  = &HFFFF       '1111111111111111
40    cursor (1,0)  = &HFFFF       '1111111111111111
50    cursor (2,0)  = &HFFFF       '1111111111111111
60    cursor (3,0)  = &HFFFF       '1111111111111111
70    cursor (4,0)  = &HFFFF       '1111111111111111
80    cursor (5,0)  = &HF00F       '1111000000001111
90    cursor (6,0)  = &H0000       '0000000000000000
100   cursor (7,0)  = &H0000       '0000000000000000
110   cursor (8,0)  = &H0000       '0000000000000000
120   cursor (9,0)  = &H0000       '0000000000000000
130   cursor (10,0) = &HF00F       '1111000000001111
140   cursor (11,0) = &HFFFF       '1111111111111111
150   cursor (12,0) = &HFFFF       '1111111111111111
160   cursor (13,0) = &HFFFF       '1111111111111111
170   cursor (14,0) = &HFFFF       '1111111111111111
180   cursor (15,0) = &HFFFF       '1111111111111111
190 '
200 ' Define the cursor mask
210 '








                                 page 11

220   cursor (0,1)  = &H0000       '0000000000000000
230   cursor (1,1)  = &H0000       '0000000000000000
240   cursor (2,1)  = &H0000       '0000000000000000
250   cursor (3,1)  = &H0000       '0000000000000000
260   cursor (4,1)  = &H0000       '0000000000000000
270   cursor (5,1)  = &H0000       '0000000000000000
280   cursor (6,1)  = &H07E0       '0000011111100000
290   cursor (7,1)  = &H7FFE       '0111111111111110
300   cursor (8,1)  = &H7FFE       '0111111111111110
310   cursor (9,1)  = &H07E0       '0000011111100000
320   cursor (10,1) = &H0000       '0000000000000000
330   cursor (11,1) = &H0000       '0000000000000000
340   cursor (12,1) = &H0000       '0000000000000000
350   cursor (13,1) = &H0000       '0000000000000000
360   cursor (14,1) = &H0000       '0000000000000000
370   cursor (15,1) = &H0000       '0000000000000000
380 '
390 ' Set the cursor style and hot spot number of Genius Mouse
400 '
410 '
420   G1% = 9
430   G2% = 6 ' horizontal hot spot
440   G3% = 5 ' vertical hot spot
450   CALL GMOUSE ( G1%, G2%, G3%, cursor (0,0))

Function 10: Define Text Mode Cursor Style

Function 10 chooses the hardware or the software text cursor.

For example, if BX (G2%) is 1, the hardware cursor is selected and the hardware
is set up with the first and last scan lines which define the cursor.
(Values for CX (G3%) and DX (G4%) range from 0 to 7 for the color display and 0
to 11 for the monochrome display.)

If BX (G2%) is 0, the software cursor is selected; and CX (G3%) and DX (G4%)
must specify the screen and cursor masks.  (These masks give the attributes and
character code of the cursor, and their values are dependent on the type of
display in use.)

8086 Register
Input:  AX = 10
        BX = select cursor (0: software text, 1: hardware text)
        CX = screen mask value/scan line start
        DX = cursor mask value/scan line stop
Return: none

BASIC
Input:  G1% = 10
        G2% = select cursor (0: software text, 1: hardware text)
        G3% = screen mask value/scan line start
        G4% = cursor mask value/scan line stop
Return: none

Example:




                                 page 12

110 ' Enable an Inverting Cursor
120 G1% = 10
130 G2% = 0
140 G3% = &HFFFF  :  G4% = &H7700
150 CALL GMOUSE ( G1%, G2%, G3%, G4% )

Function 11: Read Genius Mouse Motion Number

Function 11 gives the mouse motion number since the last call.  A positive
horizontal number indicates rightward movement (negative shows leftward
movement).  A positive vertical number indicates downward movement (negative
shows upward movement).
The number is always in the range of -32768 to 32767.  Overflow is disregarded.
Once the call is completed, the number is set to 0.

8086 Registers
Input:  AX = 11
Return: CX = horizontal number
        DX = vertical number

BASIC
Input:  G1% = 11
Return: G3% = horizontal number
        G4% = vertical number

Example:

110 ' Read Genius Mouse Motion Number
120 G1% = 11
130 CALL GMOUSE ( G1%, G2%, G3%, G4% )
140 IF G3% > 0 THEN PRINT "Genius Mouse is Moving to Right"
150 IF G4% > 0 THEN PRINT "Genius Mouse is Moving Down"

Function 12: Define Event Handler Entry Location

Function 12 defines the address entry location of an event handler routine which
is called when a certain event (defined by the call mask) occurs.  The program
is temporarily interrupted by the mouse driver. At the end of the event handler
routine  the program continues at the point it was interrupted.

The call mask is a single integer value defining the conditions which will cause
an interrupt.

A specific condition corresponds to a bit in the call mask:

Mask Bit                Condition
--------------------------------------------------
   0                    cursor location changed
   1                    left button pressed
   2                    left button released
   3                    right button pressed
   4                    right button released
   5                    middle button pressed
   6                    middle button released
   7 - 15               not used



                                 page 13

In order to call the event handler routine, set the mask bit to 1 and put the
mask in at CX (G3%).  To disable, set the mask bit to 0 and put the mask in at
CX (G3%).  Always be sure to set the call mask to 0 before the program finishes.
(Leave the system in the same state upon exit as if was upon entrance.)

8086 Register
Input:  AX = 12
        CX = call mask
        ES:DX = pointer to event handler routine
Return: none

BASIC
Input:  G1% = 12
        G3% = call mask
        G4% = pointer to event handler routine
Return: none

Example:


110 ' Active BUTTDOWN Event Handler Routine, When One or More Buttons Pressed
120 G1% = 12
130 G3% = &H002A  :  G4% = BUTTDOWN%
140 CALL GMOUSE ( G1%, G2%, G3%, G4% )

Function 13: Enable Light Pen Emulation Function

Function 13 permits the mouse to act like a light pen.  When in this mode, calls
to the pen function will give the cursor coordinates at the last pen down
location.

Note that the status of "pen down" and "pen off-screen" is controlled by the
mouse buttons:  all buttons up, pen off-screen; one button pressed, pen down.

Light pen emulation is ON after each call to function 0 (Reset Mouse Driver).

8086 Register
Input:  AX = 13
Return: none

BASIC
Input:  G1% = 13
Return: none

Example:

110 ' Enable Light Pen Emulation Function
120 G1% = 13
130 CALL GMOUSE ( G1%, G2%, G3%, G4% )

Function 14: Disable Light Pen Emulation Function

Function 14 turns off the light pen emulation mode.  When disabled, any call to
the pen function will give information only about a real light pen.

8086 Register
Input:  AX = 14

                                 page 14

Return: none

BASIC
Input:  G1% = 14
Return: none

Example:

110 ' Disable Light Pen Emulation Function
120 G1% = 14
130 CALL GMOUSE ( G1%, G2%, G3%, G4% )

Function 15: Define Sensitivity (Mouse Motion/Pixel) of Genius Mouse

Function 15 defines mouse sensitivity as determined by the mouse motion/pixel
ratio.  This is a way of setting the amount of cursor motion wanted for mouse
movement.  These ratios specify mouse motion per 8 pixels.  These values must
be in the range of 1 to 32767.  With a larger ratio, the cursor movement is
shortened for each mouse movement.

Default values:   horizontal ratio - 8  mouse motions to 8 pixels
                  vertical ratio   - 16 mouse motions to 8 pixels

Note: 1 mouse motion = 1/200 of an inch increment

8086 Register
Input:  AX = 15
        CX = horizontal mouse motion counts to pixel ratio
        DX = vertical mouse motion counts to pixel ratio
Return: none

BASIC
Input:  G1% = 15
        G3% = horizontal mouse motion counts to pixel ratio
        G4% = vertical mouse motion counts to pixel ratio
Return: none

Example:

110 ' Define Horizontal Sensitivity as 8
120 ' Define Vertical Sensitivity as 16
130 G1% = 15
140 G3% =  8
150 G4% = 16
160 CALL GMOUSE ( G1%, G2%, G3%, G4% )

Function 16: Disable Cursor Display in Special Range

Function 16 sets up a special range on-screen.  If the cursor moves to this area
or is in this area, it will be disabled.  After a call is made to this function,
it is necessary to call function 1 to enable the cursor again.

Define the special range with screen location values using four components:





                                 page 15

Components          Values
--------------------------------------------------------
    1               Left horizontal screen location
    2               Upper vertical screen location
    3               Right horizontal screen location
    4               Lower vertical screen location

8086 Register
Input:  AX = 16
        ES:DX = pointer to special range
Return: none

BASIC
Input:  G1% = 16
        G4% = pointer to special range
Return: none

Example:

110 ' Disable Cursor Display in (0,0) to (100,100) Range
120 G1% = 16
130 RANGE%(1) = 0   : RANGE%(2) = 0
140 RANGE%(3) = 100 : RANGE%(4) = 100
150 CALL GMOUSE ( G1%, G2%, G3%, RANGE%(0) )
 .
 .
 .

500 ' Enable Cursor Display Again
510 G1% = 1
520 CALL GMOUSE ( G1%, G2%, G3%, G4% )

Function 19: Define Double-Speed Threshold

Function 19 defines the threshold value (mouse motion per second) for doubling
the cursor's motion.  Should the mouse move faster than the DX (G4%) value, the
cursor motion doubles.  The default value is 64 mouse motions per second.

If you should want to disable double-speed, just set the threshold to 32767
(7FFFH) mouse motions/second.

8086 Register
Input:  AX = 19
        DX = threshold speed in mouse motions/second
Return: none

BASIC
Input:  G1% = 19
        G4% = threshold speed in mouse motions/second
Return: none

Example:

110 ' Define Double-Speed Threshold as 20 Mouse Motions/Second




                                 page 16

120 G1% = 19
130 G4% = 20
140 CALL GMOUSE ( G1%, G2%, G3%, G4% )
.
.
.

500 ' Disable Double-Speed Threshold Function
510 G1% = 19
520 G4% = 256 'MAX. VALUE
530 CALL GMOUSE ( G1%, G2%, G3%, G4% )

Function 20: Swap Event Handler Entry Location

Function 20 sets new values for the call mask and event handler routine
address  for mouse  hardware interrupts and return the values that were
previously specified.

For detail information to reference Function 12 description.

8086 Register
Input:  AX = 20
        CX = new call mask
        ES:DX = new pointer to event handler routine
Return: CX = old call mask
        ES:DX = old pointer to event handler routine

BASIC
Input:  G1% = 20
        G3% = call mask
        G4% = pointer to event handler routine
Return: G3% = old call mask
        G4% = old pointer to event handler routine

Example:

100 ' Swap Event Handler Entry Location
110 ' Active BUTTDOWN Event Handler Routine, When One or More Buttons Pressed
120 G1% = 20
130 G3% = &H002A  :  G4% = BUTTDOWN%
140 CALL MOUSE ( G1%, G2%, G3%, G4% )

Function 21: Get Mouse Driver State Storage Size

Function 21 returns the size of the buffer required to store the current state
of the  mouse  driver.  It is used with  functions 22  and 23 when you want to
temporarily  interrupt a program  that is using the mouse  and execute another
that also uses the mouse.

8086 Register
Input:  AX = 21

Return: BX = buffer size required for mouse driver state

BASIC
Input:  G1% = 21


                                 page 17

Return: G2% = buffer size required for mouse driver state

Example:

110 ' Get Mouse Driver State Storage Size
120 G1% = 21
130 CALL MOUSE ( G1%, G2%, G3%, G4% )
140 STATESIZE% = G2%

Function 22: Save Mouse Driver state

Function 22 saves the current mouse driver state in a buffer allocated by your
program.   It is used with  functions 21  and 23  when you want to temporarily
interrupt a program  that is using the mouse  and execute another program that
also uses the mouse.

Before your program  calls function 22, the program should call function 21 to
determine  the buffer size required  for saving the  mouse driver state,  then
allocate the appropriate amount of memory.

8086 Register
Input:  AX = 22
        ES:DX = pointer to the buffer
Return: None

BASIC
Input:  G1% = 22
        G4% = pointer to the buffer
Return: None

Example:

110 ' Save The Mouse Driver State
120 G1% = 22
130 G4% =  BUFPTR
140 ' Assume BUFPTR contains the address of the buffer
150 CALL MOUSE ( G1%, G2%, G3%, G4% )

Function 23: Restore Mouse Driver State

Function 23 restores the last  mouse driver state saved by function 22.  It is
used with functions 21 and 22 when you want to temporarily interrupt a program
that is  using the mouse  and execute another program that also uses the mouse.
To restore the mouse driver state saved by function 22, call function 23 at the
end of the interrupt program.

8086 Register
Input:  AX = 23
        ES:DX = pointer to the buffer
Return: None

BASIC
Input:  G1% = 23
        G4% = pointer to the buffer
Return: None

Example:

                                 page 18

110 ' Restore The Mouse Driver State
120 G1% = 23
130 G4% =  BUFPTR
140 ' Assume BUFPTR contains the address of the buffer
150 CALL MOUSE ( G1%, G2%, G3%, G4% )

Function 29: Set CRT Page Number

Function 29 specifies the CRT page on which the mouse cursor will be displayed.

For information on the number of CRT pages available in each display mode your
adapter supports, see the documentation that came with the graphics adapter.

8086 Register
Input:  AX = 29
        BX = CRT page for mouse cursor display
Return: none

BASIC
Input:  G1% = 29
        G2% = CRT page for mouse cursor display
Return: none

Example:

110 ; Set CRT page 2 for display mouse cursor
120 G1% = 29
130 G2% = 2
140 CALL MOUSE ( G1%, G2%, G3%, G4% )
 .
 .
 .

500 ; Enable Cursor Display Again
510 G1% = 1
520 CALL MOUSE ( G1%, G2%, G3%, G4% )

Function 30: Read CRT Page Number

Function 30 returns the number of the CRT page on which the mouse cursor is
displayed.

8086 Register
Input:  AX = 30

Return: BX = CRT page number of current cursor display

BASIC
Input:  G1% = 30

Return: G2% = CRT page number of current cursor display

Example:

110 ; Read CRT page number
120 G1% = 30
130 CALL MOUSE ( G1%, G2%, G3%, G4% )

                                 page 19



Within the Genius Mouse driver, you'll find nine EGA functions.  These functions
permit your program to write to and read from write-only registers.

The cursor in use is defined as a monochrome cursor with one bit per pixel.  The
bit masks are determined by function 9 and apply to all active planes.

In order to make an EGA function call from an Assembly-Language program, first
load the AX, BX, CX, DX, and ES registers with the values indicated for the
parameters.  Note that five values must be given for a high level language
program.  Next, execute software interrupt 16 (10h). The values that are
returned are intalled in the registers by EGA functions.

Upon start with DOS, PC BIOS will verify if the EGA BIOS exists.  When this is
verified, the PC will execute the EGA BIOS, booting up the program to write the
INT 10h entry vector to the address of the INT 42h vector.  Now, EGA BIOS
address will be written to INT 10h. Following this, you are able to call EGA
BIOS (by using INT 10h) and PC video BIOS (by using INT 42h).

There are twenty functions in EGA BIOS.  (PC BIOS has only 16.) The EGA BIOS
routines only intercept the BIOS ROM video routines (INT 10h, AH = 13h or less).

The following indicates nine EGA functions and the corresponding function
number:

Function                                             Number (HEX)
-----------------------------------------------------------------
Retrieve Single Data                                       F0
Save Single Data                                           F1
Retrieve Registers on a Specified Port                     F2
Save Registers on a Specified Port                         F3
Retrieve Several Registers Data                            F4
Save Several Registers Data                                F5
Reset All Registers as Initial Values                      F6
Set Initial Values                                         F7
Get Version Number of Genius Mouse Driver                  FA

In the above functions, the EGA I/O port number and address are as follows:

Port No.  Register Name   No. of Registers  Index No.  Address Select Register
------------------------------------------------------------------------------
 00H      CRT Controller         25           0 - 24            3x4H
 08H      Sequencer               5           0 - 4             3C4H
 10H      Graphics Controller     9           0 - 8             3CEH
 18H      Attribute Controlle    20           0 - 19            3C0H
          Singular Registers
 20H      Miscellaneous Output    1           ignored           3C2H
 28H      Feature Control         1           ignored           3xAH
 30H      Graphics 1 Position     1           ignored           3CCH
 38H      Graphics 2 Position     1           ignored           3CAH

  Note: x = B or D depending on the base I/O address;
        determined by Miscellaneous Output Register bit 1.




                                 page 20

Function F0: Retrieve Single Data

This function retrieves data from a single register.

Input:  AH = F0H
        BX = Index number
        DX = Port number
Return: BL = Retrieved data from EGA register


Example:

FUN_F0     EQU     0f0H         ; Function F0
;
GR_CONTR   EQU     010H         ; Graphics Controller
MODE_REG   EQU     005H         ; Mode Regisiter
;
GR1_PORT   EQU     030H         ; Graphics 1 Position Register
GR2_PORT   EQU     038H         ; Graphics 2 Position Register
;
VIDEO      EQU     010H         ; BIOS ROM Video Routine Entry

           ; Retrieve the Mode Register in Graphics Controller
MODE_REG   DB      00
           ;
           MOV     DX, GR_CONTR
           MOV     BX, MODE_REG
           MOV     AH, FUN_F0
           INT     VIDEO
           MOV     MODE_REG, BL


           ; Retrieve Graphics 1 Position Data
GR1_POS    DB      00
           ;
           MOV     DX, GR1_POS
           MOV     AH, FUN_F0
           INT     VIDEO
           MOV     GR1_POS, NL


Function F1: Save Single Data

This function saves data to an EGA register.  Upon finishing a call to this
function, the BH and DX values are altered.

Input:  AH = F1H
        BL = Index number (Non-single register only)
           = Data (Single register only)
        BH = Data (Non-single register only)
           = Disregard (Single register only)
        DX = Port number
Return: None


Example:


                                 page 21

FUN_F1     EQU     0f1H         ; Function F1
;
SEQUENCE   EQU     008H         ; Sequencer
MASK_REG   EQU     002H         ; Map Mask Register
;
FEAT_PORT  EQU     028H         ; Feature Control Register
;
VIDEO      EQU     010H         ; BIOS ROM Video Routine Entry

           ; Save Map Mask Register of Sequencer
MAP_MASK   EQU     03H
           ;
           MOV     DX, SEQUENCE
           MOV     BL, MASK_REG
           MOV     BH, MAP_MASK
           MOV     AH, FUN_F1
           INT     VIDEO
           MOV     MAP_MASK, BL


           ; Save Feature Control Register
FEATURE    DB      02H
           ;
           MOV     DX, FEAT_PORT
           MOV     BL, FEATURE
           MOV     AH, FUN_F1
           INT     VIDEO
           MOV     FEATURE, BL

Function F2: Retrieve Registers on a Specified Port

This function retrieves data from registers on a specifi? port.  Upon finishing
a call to this function, the CX value is altered.

Input:  AH = F3H
        CH = Starting index number
        CL = Number of registers
        DX = Port number
        ES:BX = Destination of returned data
Return: Returned data to destination address

Example:

FUN_F2     EQU     0f2H         ; Function F2
;
GR_CONTR   EQU     010H         ; Graphics Controller
;
VIDEO      EQU     010H         ; BIOS ROM Video Routine Entry


           ; Retrieve Four Registers Data from Graphics Controller
GRAPH_POOL DB      04   DUP (0)
           ;
           MOV     DX, DS
           MOV     ES, DX
           ;


                                 page 22

           MOV     DX, GR_CONTR
           MOV     BX, OFFSET GRAPH_POOL
           MOV     CX, 04H
           MOV     AH, FUN_F2
           INT     VIDEO

Function F3: Save Registers on a Specified Port

This function saves data from registers on a specifi? port.  Upon finishing a
call to this function, the BX, CX, and DX values are altered.

Input:  AH = F3H
        CH = Starting index number
        CL = Number of register
        DX = Port number
        ES:BX = Address source of incoming data
Return: None

Example:

FUN_F3     EQU     0f3H         ; Function F3
;
ATTR_CONTR EQU     018H         ; Attribute Controller
;
VIDEO      EQU     010H         ; BIOS ROM Video Routine Entry


           ; Save Four Registers Data into Attribute Controller
PALET_DATA DB      1, 2, 4, 3
           ;
           MOV     DX, DS
           MOV     ES, DX
           ;
           MOV     DX, ATTR_CONTR
           MOV     BX, OFFSET PALET_DATA
           MOV     CX, 08
           MOV     AH, FUN_F3
           INT     VIDEO

Function F4: Retrieve Several Registers Data At The Same Time

This function retrieves data from several registers at the same time.  Upon
finishing a call to this function, the CX value is altered.

Input:  AH = F4H
        CX = Number of registers (more than 1)
        ES:BX = Address of register packet (each consists of 4 bytes;
                port  address,  byte 1-2;  index number,  byte 3;
                returned data, byte 4)
Return: Returned data is saved into byte 4

Example:

FUN_F4     EQU     0f4H         ; Function F4
;
VIDEO      EQU     010H         ; BIOS ROM Video Routine Entry


                                 page 23

            ; Retrieve Follow  Registers Data
TABLE       DW     030H         ; Graphics 1 Position Register
            DB      00          ; Single Register
            DB      00          ; Retrieved Data
            ;
            DW     010H         ; Graphics Controller
            DB      05          ; Mode Register
            DB      00          ; Retrieved Data
            ;
            ;
            MOV    DX, DS
            MOV    ES, DX
            ;
            MOV    BX, OFFSET TABLE
            MOV    CX, 02
            MOV    AH, FUN_F4
            INT    VIDEO


Function F5: Save Several Registers Data At The Same Time

This function saves data from several registers at the same time.  Upon
finishing a call to this function, the CX value is altered.

Input:  AH = F5H
        CX = Number of registers (more than 1)
        ES:BX = Address of register packet (each consists of 4 bytes;
                port  number, byte 1-2;  index number,  byte 3;
                output data, byte 4)
Return: None

Example:

FUN_F5     EQU     0f5H         ; Function F5
;
VIDEO      EQU     010H         ; BIOS ROM Video Routine Entry

            ; Save Follow Registers Data
TABLE       DW      20H         ; Miscellaneous
            DB      00          ; Single Register
            DB      01          ; Data
            ;
            DW      18H         ; Attribute Controller
            DB      12H         ; Color Plane Enable
            DB      07H         ; Data
            ;
            ;
            MOV    DX, DS
            MOV    ES, DX
            ;
            MOV    BX, OFFSET TABLE
            MOV    CX, 02
            MOV    AH, FUN_F5
            INT    VIDEO




                                 page 24

Function F6: Reset All Registers as Initial Values

This function resets all values to default values for the specific registers.
Function 7 sets the default values.

Input:  AH = F6H
Return: None

Example:

FUN_F6     EQU     0f6H         ; Function F6h
;
VIDEO      EQU     010H         ; BIOS ROM Video Routine Entry

           MOV     AH, FUN_F6
           INT     VIDEO

Function F7: Set Initial Values

This function sets the initial default values.  Upon finishing a call to this
function, the BX and DX values are altered.

Input:  AH = F7H
        DX = Port number
        ES:BX = Table of output data
Return: None

Example:

FUN_F7     EQU     0f7H         ; Function F7
;
ATTR_CONTR EQU     018H         ; Attribute Controller
;
VIDEO      EQU     010H         ; BIOS ROM Video Routine Entry

           ; Setting Initial Values for the Attribute Controller
ATTR_DATA  DB      1,  2,  4,  3,  5,  6,  0,  7
           DB      0,  0,  0,  0,  0,  0,  0,  0
           DB      0,  0, 0fh, 0
           ;
           MOV     DX, DS
           MOV     ES, DX
           ;
           MOV     DX, ATTR_CONTR
           MOV     BX, OFFSET ATTR_DATA
           MOV     AH, FUN_F7
           INT     VIDEO

Function FA: Get Version Number of Genius Mouse Driver

This function will give the Genius Mouse driver version number.

Input:  AH = FAH
        BX = 00H
Return: ES:BX = Pointer to Genius Mouse driver version number.



                                 page 25


                     ????????????????????????????
                     ? Programming the Keyboard ?
                     ????????????????????????????

                 Written for the PC-GPE by Mark Feldman
              e-mail address : u914097@student.canberra.edu.au
                               myndale@cairo.anu.edu.au

             ?????????????????????????????????????????????
             ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
             ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
             ?????????????????????????????????????????????
 

?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Overview ?
????????????

The operation of the keyboard is really quite simple. Every time a key
is pressed or released an interrupt 9 is generated, and reading the value
from port 60h tells you what happened.

?????????????????????????????????????????????????????????????????????????????
? Decoding the Keyboard Byte ?
??????????????????????????????

So let's say you've installed an interrupt handler to handle all keyboard
events and when an interrupt is generated your handler reads the byte from
port 60h. What now?

Well..each key on the keyboard has an associated scan code which is contained
in the lower 7 bits of the byte. The most significant bit (ie bit 7) tells
you what was actually done, 0 = key was just pressed, 1 = key was just
released. If someone had just pressed the ESC key for instance, the port will
show a value of 1 (1 is the ESC key's scan code). If they hold their finger
on the button the keyboard will keep generating interrupt 9's and each
time the port will still show a value of 1. When the person releases the key
a final interrupt will be generated and the port will return 129 (1 + 128,
since the high bit will be set indicating the person has released the key).

Well...it's almost this simple. Some keys are "extended" keys. When an
extended key is pressed an interrupt is generated and the keyboard port
will return a value of 224 (E0h). This means that an extended key was pressed
and it's *extended* scan code will be available during the *next* interrupt.
Note that the left control key has a scan code of 29, while the *right*
control key has an *extended* scan code of 29. The same applies to the alt
keys and the arrow keys (keypad arrows vs the other ones).

It would be nice if all keys were created equal and we could just throw away
the 224 extended bytes and handle all the other bytes normally. Unfortunately
there are two buttons which on my machine at least (and others I have tested)
do some really weird stuff:

PrtScn
??????
Pressing this button will send *2* extended characters to the handler, 42
and 55, so the actual byte sequence will be 224, 42, 224, 55. (Also note
that the left shift key has a regular scan code of 42, so there goes our
idea of just throwing 224's away). Only the extended 55's are sent during
auto-repeat. When the key is released, the two are sent again with the high
bits set (224, 170, 224, and 183). If any of the shift or control keys are
being held down when the PrtScn button is pressed then only the (224, 55) is
sent when the key is pressed and only the (224, 183) is sent when it's
released. If the alt key is being held down (System Request) then the key
behaves like an ordinary key with scan code 84. The practical upshot of all
this is that the handlers you write to handle normal keys and extended keys
will work fine with all the different PrtScn combinations (although a program
would have to check normal key 84 *AND* extended key 55 in order to determine
if the key is currently being pressed).

Pause/Break
???????????
Welcome to hell. If you press this key while either of the the control keys
are being held down, it will behave like extended key 70, at all other times
it will send the following bytes: (225, 29, 69, 225, 157, 197). Holding the
key down does not result in autorepeat. Taking your finger off the key does
not send any extra bytes, they appear to be sent after the "key down" bytes
when you first press the key. Notice that 225 isn't 224, so our normal
extended character handler will not take care of this. My personal theory is
that while a scan code of 224 (E0h) means there is 1 more character
following, a scan code of 225 (E1h) means there are *2* more following. I've
seen a number of keyboard handler libraries and they all seem to overlook
this key. So why not be the first kid on your block to have a keyboard
handler which properly supports the Pause/Break key? CHECK IT OUT!!

?????????????????????????????????????????????????????????????????????????????
? Writing a Handler ?
?????????????????????

Writing a keyboard handler is fairly straightforward. This section will
show how to do it in Pascal (you C and asm programmers would probably already
know this stuff anyway).

First we'll declare a few things we'll need:

const KEYBOARDINTR = 9;
      KEYBOARDPORT = $60;

var BIOSKeyboardHandler : procedure;
    CallBIOSHandler : boolean;

The CallBIOSHandler variable will be initialised by the calling program. If
we also want the BIOS handler to process all keystrokes then this variable
must be set to true.

Next we need to store the value of the current handler and set up own our
own one. We'll use a procedure called KeyboardHandler to handle the actual
interrupt.

CallBIOSHandler := false; { ...or set it to true if you want. }
GetIntVec(KEYBOARDINTR, @BIOSKeyboardHandler);
SetIntVec(KEYBOARDINTR, Addr(KeyboardHandler));

Ok, so everything is now set up and our handler will now be able to process
all keyboard events. The actual interrupt handler could look like this:

{$F+}
procedure KeyboardHandler(Flags, CS, IP, AX, BX, CX, DX,
                          SI, DI, DS, ES, BP: Word);
interrupt;
var key : byte;
begin

  key := Port[KEYBOARDPORT];

  { PROCESS THE KEYSTROKE HERE }

  if CallBIOSHandler then

  { Call the BIOS keyboard handler if the calling program wants us to }
    begin
      asm pushf end;
      BIOSKeyboardHandler;
    end

  { Otherwise just acknowledge the interrupt }
  else Port[$20] := $20;
end;
{$F-}


When the program is finished we can set the old keyboard handler again:

SetIntVec(KEYBOARDINTR, @BIOSKeyboardHandler);

?????????????????????????????????????????????????????????????????????????????
? A Word of Warning ?
?????????????????????

When I was writing a simple handler to test the info in this file I did
something REALLY stoopid which I would like to share with the world. I
thought that my program was stuffing the keyboard up because when I exited
the program my editor (Borland Pascal 7.0) would act as though the control
button was being held down (I'm sure some of you have already started
laughing by now). I had to press it after each time I ran the program
just to sort it out. After spending a few hours looking all over the place
for info on what could possibly be wrong I realised what I was doing. I was
pressing CTRL-F9 to compile the program which would also immediately make it
run and I was releasing the control key when my program was running, ie the
regular BIOS handler was not getting the control key's "key up" command and
still thought it was being held down when my program returned control to
it. Moron.....

?????????????????????????????????????????????????????????????????????????????
? Scan Codes ?
??????????????

The following is a list of all the regular key scan codes in numerical
order:

Scan                                   Scan
Code Key                               Code Key
??????????????????????????             ??????????????????????????
 1   ESC                               44   Z
 2   1                                 45   X
 3   2                                 46   C
 4   3                                 47   V
 5   4                                 48   B
 6   5                                 49   N
 7   6                                 50   M
 8   7                                 51   , <
 9   8                                 52   . >
10   9                                 53   / ?
11   0                                 54   RIGHT SHIFT
12   - _                               55   *            (KEYPAD)
13   = +                               56   LEFT ALT
14   BACKSPACE                         57   SPACEBAR
15   TAB                               58   CAPSLOCK
16   Q                                 59   F1
17   W                                 60   F2
18   E                                 61   F3
19   R                                 62   F4
20   T                                 63   F5
21   Y                                 64   F6
22   U                                 65   F7
23   I                                 66   F8
24   O                                 67   F9
25   P                                 68   F10
26   [ {                               69   NUMLOCK      (KEYPAD)
27   ] }                               70   SCROLL LOCK
28   ENTER (RETURN)                    71   7 HOME       (KEYPAD)
29   LEFT CONTROL                      72   8 UP         (KEYPAD)
30   A                                 73   9 PGUP       (KEYPAD)
31   S                                 74   -            (KEYPAD)
32   D                                 75   4 LEFT       (KEYPAD)
33   F                                 76   5            (KEYPAD)
34   G                                 77   6 RIGHT      (KEYPAD)
35   H                                 78   +            (KEYPAD)
36   J                                 79   1 END        (KEYPAD)
37   K                                 80   2 DOWN       (KEYPAD)
38   L                                 81   3 PGDN       (KEYPAD)
39   ; :                               82   0 INSERT     (KEYPAD)
40   ' "                               83   . DEL        (KEYPAD)
41   ` ~                               87   F11
42   LEFT SHIFT                        88   F12


The following is a list of all the extended key scan codes in numerical
order:

Scan                                   Scan
Code Key                               Code Key
???????????????????????????????        ???????????????????????????????
28   ENTER        (KEYPAD)              75   LEFT         (NOT KEYPAD)
29   RIGHT CONTROL                      77   RIGHT        (NOT KEYPAD)
42   PRINT SCREEN (SEE TEXT)            79   END          (NOT KEYPAD)
53   /            (KEYPAD)              80   DOWN         (NOT KEYPAD)
55   PRINT SCREEN (SEE TEXT)            81   PAGE DOWN    (NOT KEYPAD)
56   RIGHT ALT                          82   INSERT       (NOT KEYPAD)
71   HOME         (NOT KEYPAD)          83   DELETE       (NOT KEYPAD)
72   UP           (NOT KEYPAD)         111   MACRO
73   PAGE UP      (NOT KEYPAD)

                    ????????????????????????????????
                    ? Programming the PC Joystick  ?
                    ????????????????????????????????

                       Written for the PC-GPE by
                     Steve McGowan and Mark Feldman


???????????????????????????????????????????????????????????????????????????
? Programming Info ?
????????????????????

All joystick programming is done via port 201h.

                      ?????????????????????????????????
                      ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
                      ?????????????????????????????????
                        ?   ?   ?   ?   ?   ?   ?   ?
Joystick B, Button 2 ????   ?   ?   ?   ?   ?   ?   ???? Joystick A, X Axis
Joystick B, Button 1 ????????   ?   ?   ?   ?   ???????? Joystick A, Y Axis
Joystick A, Button 2 ????????????   ?   ?   ???????????? Joystick B, X Axis
Joystick A, Button 1 ????????????????   ???????????????? Joystick B, Y Axis

Reading the status of the joystick buttons is fairly simple. Just read the
byte from the joystick port and check the status of the appropriate bit. A
clear bit (0) means the button is pressed, a set bit (1) means it is not
pressed. Note that the button's are not hardware debounced. Each time a
button is pressed it's bit may "bounce" between 0 and 1 a couple of times.

Reading the position of the stick positions is a bit more complicated. You
must first write a dummy byte (any value will do) to the joystick port. This
will set each axis bit to 1. You must then time how long the bit takes to
drop back to 0, this time is roughly proportional to the position of
the joystick axis (see Steve McGowan's discussion below).

AT computers also have a BIOS call which supports the joystick. I have come
across numerous machines which apparently did not support this call. My own
machine supports reading the joystick buttons apparently can't read the
stick position values, so I do not advise using this call for any serious
games. In any case here is info on the call:

Joystick Support BIOS Call

Int 15h

To call:
  AH = 84h
  DX = 00h Read switch settings
       01h Read joystick position

Returns:
    PC, PCjr : Carry flag set, AH = 80h
       PC XT : Carry flag set, AH = 86h
  All others : DX = 00h on calling
                 AL = Switch settings (bits 4 - 7)
                 Carry flag set on error
               DX = 01h on calling
                 AX = A(X) value
                 BX = A(Y) value
                 CX = B(X) value
                 DX = B(Y) value

?????????????????????????????????????????????????????????????????????????????
? Hardware Pinout ?
???????????????????

The joystick connects to a 15 pin female plug :

                     __________________________
                     \ 8  7  6  5  4  3  2  1 /
                      \ 9  10 11 12 13 14 15 /
                       ----------------------

                  ?????????????????????????????????
                  ? Pin #  Joystick               ?
                  ?????????????????????????????????
                  ?  1     +5v                    ?
                  ?  2     Joystick A, Button 1   ?
                  ?  3     Joystick A, X Axis     ?
                  ?  4     Gnd                    ?
                  ?  5     Gnd                    ?
                  ?  6     Joystick A, Y Axis     ?
                  ?  7     Joystick A, Button 2   ?
                  ?  8     +5v                    ?
                  ?  9     +5v                    ?
                  ?  10    Joystick B, Button 1   ?
                  ?  11    Joystick B, X Axis     ?
                  ?  12    Gnd                    ?
                  ?  13    Joystick B, Y Axis     ?
                  ?  14    Joystick B, Button 2   ?
                  ?  15    +5v                    ?
                  ?????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Misc notes on Joystick handling by Steve McGowan ?
????????????????????????????????????????????????????

With a polling loop on a 486-66 I got x/y values between 8 and 980. When
I centered the stick the value was usually a value around 330.

NOTE: a Gravis Game Pad it only put out 3 values, 8(min), 330(center),
and 980(max). Every joystick I have tried has been non-linear.

The "speed compensation" that some games require is due to the fact that
the game designer did not anticipate the range of values that could
come back on faster machines. On a 486-25 you may see max values of 360,
I saw 980, on a Pentium the max value could be well over 2000. If you
had used a unsigned byte value you probably would have been in good
shape on an AT, or 386 but you would be in big trouble with faster machines.

Because the joystick logic returns a non linear value, if you base your
scaling only on the 4 corners then the center will be off (biased towards
a corner). If you just use the center value and a single scaling factor
(i.e. of the center is at 330 then full throw should be at 660), then the
stick will saturate (660) half way to the full throw position (980).
That is why most joystick setup programs make the distinction between
hitting the 4 corners and centering the stick.

Joystick position vs. loop count

     x,y--------------------
     8,8|      330,8       | 980,8
        |                  |
        |                  |    delta 330
        |                  |
   8,330|      330,330     | 980,330 (y centered)
        |                  |
        |                  |    delta 650
        |                  |
   8,980|      330,980     | 980,980
        --------------------
            (x centered)

For the best effect you basically need 2 scale factors, depending on whether
you are above or below the center value. I think the curve is actually an
exponential (charging capacitor) but a straight line approximation should
do fine.

The 10% dead zone in the center is a good idea. The centering mechanism of
joysticks vary in repeatablity, they don't always come back to the same place.
I have a cheap one that (1 time in 8) does not return to the X center if I
just let it snap to center. It hangs on the high side.

I would recommend disabling interrupts while polling. An interrupt
in the middle of your polling loop will really throw off the results. And
any DMA that takes place will also give you bad values.

Joysticks are noisy, so holding the stick in a fixed position will return
values that vary +-5% easily. I added a smoothing function to my joystick
code where I throw away single values that are not continuous. It helped
a lot with the noise and the DMA.

I use protected mode and the interrupt disable() call doesn't actually work
because it only disables interrupts for the process not the processor.
The smoothing trick can help here too.

If I turn on my machine and start the polling loop immediately, it will
put out a centered value of 330,330 but after warming up for 10 minutes
the value changes to 285,285. This variance also needs to be absorbed in
your center dead zone. If after warming up the 'center' value is outside your
dead zone then the cursor will drift (to the left and/or up). Make
sure your game has a "center joystick" command to get around joystick
interfaces with lousy temperature compensation.

You must wait for all of the axis bits to settle before initiating
another read, otherwise strange results may come out. So, instead of
reading X, then Y, in two separate loops (which take twice as much time)
Read both X and Y simultaneously, polling until both bits settle. This
can be extended for two joysticks, assuming that they are both attached.
The respective X/Y bits never come true if there is no joystick attached.


?????????????????????????????????????????????????????????????????????????????
? A Simple Demo Joystick Unit ?
???????????????????????????????

{
  JOY.PAS - By Mark Feldman
            e-mail address : u914097@student.canberra.edu.au
                             myndale@cairo.anu.edu.au


  A simple Pascal Joystick Unit.
}


unit Joy;

Interface

{ Define constants for use as JoystickButton and JoystickPosition parameters }
const JoystickAButton1 = $10;
      JoystickAButton2 = $20;
      JoystickBButton1 = $40;
      JoystickBButton2 = $80;
      JoystickAAxisX   = $01;
      JoystickAAxisY   = $02;
      JoystickBAxisX   = $04;
      JoystickBAxisY   = $08;

function JoystickButton(buttonnum : byte) : boolean;
function JoystickPosition(axisnum : byte) : word;

Implementation

const JOYSTICKPORT = $201;

{ Button returns true is button is pressed }
function JoystickButton(buttonnum : byte) : boolean;
begin
  JoystickButton := (Port[JOYSTICKPORT] and buttonnum) = 0;
end;

{ Returns position value of joystick. The value returned is highly
  dependent on machine speed. Changing the setting of the computer's
  Turbo speed button will affect the value returned.
  Returns $FFFF if the joystick is not connected
}
function JoystickPosition(axisnum : byte) : word;
var count : word;
begin
  asm
    mov word ptr count, 0
    cli          { Disable interrupts so they don't interfere with timing }
    mov dx, JOYSTICKPORT   { Write dummy byte to joystick port }
    out dx, al
    @joystickloop:
    inc count              { Add one to count }
    cmp count, $FFFF       { Check for time out }
    je @done
    in al, dx              { Get joystick port value }
    and al, axisnum        { Test the appropriate bit }
    jne @joystickloop
    @done:
    sti                    { Enable interrupts again }
  end;
  JoystickPosition := count;
end;

end.


?????????????????????????????????????????????????????????????????????????????
? References  ?
???????????????

Title : Flights of Fantasy
Author : Christopher Lampton
Publishers : The Waite Group
ISBN : 1-878739-18-2

Title : DOS and BIOS Functions Quick Reference
Publishers : Que Corporation
ISBN : 0-88022-426-6
             ?????????????????????????????????????????????????
             ? Programming the Gravis GamePad and Analog Pro ?
             ?????????????????????????????????????????????????

                 Written for the PC-GPE by Mark Feldman
              e-mail address : u914097@student.canberra.edu.au
                               myndale@cairo.anu.edu.au

               ?????????????????????????????????????????????
               ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
               ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
               ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Programming the Gravis GamePad ?
??????????????????????????????????

The Gravis GamePad plugs into the standard joystick connector. It is
a pad with a 9 direction controller (including center) and four buttons.
Two of the buttons can be selected as "autofire" by a switch on the
GamePad.

                       _____     ||
                      |     \____||________
                      |  __    GRAVIS      \
                      | /  \  GamePad   B  |
                      | \__/          A   C|
                      |______________   D  |
                                     \_____|

The chief difference between the GamePad and a regular joystick is that
the GamePad uses fixed resistances of about 0?, 50k? and 100k?. The
resistances for each controller position are as follows:

        ????????????????????????????????????
        ? x = 0?   ?  x = 50?  ?  x = 100? ?
        ? y = 0?   ?  y = 0?   ?  y = 0?   ?
        ????????????????????????????????????
        ? x = 0?   ?  x = 50?  ?  x = 100? ?
        ? y = 50k? ?  y = 50?  ?  y = 50?  ?
        ????????????????????????????????????
        ? x = 0?   ?  x = 50?  ?  x = 100? ?
        ? y = 100? ?  y = 100? ?  y = 100? ?
        ????????????????????????????????????

The x axis is read via the regular Joystick A X Axis, the y axis is read
via the Joystick A Y Axis.

The GamePad buttons are accessed the same way that the normal joystick
buttons are accessed:

??????????????????????????????????????????????????????????
? GamePad Button A               =  Joystick A, Button 1 ?
? GamePad Button B               =  Joystick A, Button 2 ?
? GamePad Button C (autofire A)  =  Joystick B, Button 2 ?
? GamePad Button D (autofire B)  =  Joystick B, Button 1 ?
??????????????????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Programming the Gravis Analog Pro ?
?????????????????????????????????????

The Analog Pro is very similar to a regular joystick, except it has 5
buttons and a dial on the joystick, originally intended for use as a
throttle for flight simulators.



                      -- B & A
                     ___
                C -> \ |
                      ||
                 D - || - E
                   _?_||_?_
                   |______|

                   Left Side



The Analog Pro joystick position is read the same as the regular Joystick A.
The throttle value is read the same as for the regular Joystick B X Axis
value.

           ???????????????????????????????????????????????
           ? Analog Pro Button A =  Joystick A, Button 1 ?
           ? Analog Pro Button B =  Joystick A, Button 2 ?
           ? Analog Pro Button C =  Joystick B, Button 1 ?
           ? Analog Pro Button D =  Joystick B, Button 2 ?
           ???????????????????????????????????????????????

The SDK is a bit vague as to how button E is read. It mentions that the
buttons D and E can be set as any of the joystick buttons, but I believe
this is done via switches on the joystick itself (not sure, I don't actually
own one).

?????????????????????????????????????????????????????????????????????????????
? References ?
??????????????

All the information in this file was obtained from the PC GamePad and Analog
Pro SDK V1.1 which can be obtained via anonymous ftp:

site: wasp.eng.ufl.edu
directory: /pub/msdos/demos/programming/source
filename: joysdk11.lzh


          The associated file (LIMEMS41.DOC) is a complete transcription of
          the Lotus/Intel/Microsoft (LIM) Expanded Memory Specification
          (EMS) Version 4.0, updated October 1987.  It can be printed by
          "COPY LIMEMS41.DOC PRN:"

          I created this transcription because of the difficulty I origin-
          ally had finding a copy of the document, because of the number of
          people who have subsequently expressed an interest in having
          access to a machine-readable copy of the specification, and,
          finally, because of the annoying number of typographical errors
          contained in the original and updated documents.

          This transcription is not an exact letter-for-letter duplicate of
          the original document.  Some minor changes were necessitated by
          the simple fact that the document's proportionally-spaced, multi-
          fonted typography and line drawings did not lend themselves to
          the generic fixed-spacing, single-fonted, non-graphical ASCII
          transcription I wanted to produce for general dissemination.

          Other minor changes were made to correct obvious typographical
          and grammatical errors, or to simply improve the visual aes-
          thetics of the presented material.

          In one area, however, I simply trashed their original material
          and substituted my own.  This area is the Index.  The original
          document contains an Index that is little more than a reformatt-
          ing of the Table of Contents.  As anyone who has ever indexed a
          large document knows, it is very difficult to produce an Index
          that is both complete AND easy to use.  I didn't have time to
          produce one that was both, so I aimed for the former.  In fact,
          the Index I have provided is more of an alphabetical listing of
          key words and phrases and the pages where they are referenced,
          than it is a more typical Index with its multi-level headings and
          subheadings.

          You should be able obtain a printed, 3-hole-punched, 5.5 x 8.5"
          copy of the original (and uncorrected) document directly from
          Intel by calling their "Information Department" at 1-800-538-3373
          and asking for a copy of the "LIM EMS 4.0 Developer's Kit."  It
          is available free of charge and mine arrived in about two weeks. 
          (European availability, however, is reported to be from poor to
          non-existent.)

          It is my intent to provide this transcription as a public
          service.  I am, therefore, releasing it into the public domain. 
          The original document has also been released into the public
          domain by Lotus, Intel, and Microsoft, though it remains their
          copyrighted property (I'm not quite sure how they manage to do
          that).

          I have tried as best I can to provide an accurate and corrected
          transcription of the original document.  It is inevitable,
          however, that some typographical errors have slipped through in
          spite of my hours of bleary-eyed proof reading.  For these errors
          I apologize and plead simple human frailty.

               THIS TRANSCRIPTION IS PROVIDED WITHOUT ANY GUARANTEES
               OR WARRANTIES OF ANY KIND, AND I ASSUME ABSOLUTELY NO
               LIABILITY FOR ITS ACCURACY, CONTENT, OR SUBSEQUENT USE.

          Dick Flanagan, W6OLD, Ben Lomond, California        November 1987











                           LOTUS(R)/INTEL(R)/MICROSOFT(R)

                          EXPANDED MEMORY SPECIFICATION [1]












                                     Version 4.0
                                     300275-005
                                    October, 1987












          Copyright (C) 1987

          Lotus Development Corporation
          55 Cambridge Parkway
          Cambridge, MA  02142

          Intel Corporation
          5200 NE Elam Young Parkway
          Hillsboro, OR  97124

          Microsoft Corporation
          16011 NE 35th Way
          Box 97017
          Redmond, WA  98073


               [1] Transcribed into machine-readable form by Dick Flanagan,
          Ben Lomond, California.  This transcription is released into the
          public domain without warranty or assumption of liability.





               This specification was jointly developed by Lotus Develop-
               ment Corporation, Intel Corporation, and Microsoft Corpora-
               tion.  Although it has been released into the public domain
               and is not confidential or proprietary, the specification is
               still the copyright and property of Lotus Development
               Corporation, Intel Corporation, and Microsoft Corporation.


          DISCLAIMER OF WARRANTY

               LOTUS DEVELOPMENT CORPORATION, INTEL CORPORATION, AND MICRO-
               SOFT CORPORATION EXCLUDE ANY AND ALL IMPLIED WARRANTIES,
               INCLUDING WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
               PARTICULAR PURPOSE.  NEITHER LOTUS NOR INTEL NOR MICROSOFT
               MAKE ANY WARRANTY OF REPRESENTATION, EITHER EXPRESS OR
               IMPLIED, WITH RESPECT TO THIS SPECIFICATION, ITS QUALITY,
               PERFORMANCE, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
               PURPOSE.  NEITHER LOTUS NOR INTEL NOR MICROSOFT SHALL HAVE
               ANY LIABILITY FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
               DAMAGES ARISING OUT OF OR RESULTING FROM THE USE OR MODIF-
               ICATION OF THIS SPECIFICATION.



          This specification uses the following trademarks:

          Intel is a trademark of Intel Corporation
          Lotus is a trademark of Lotus Development Corporation
          Microsoft is a trademark of Microsoft Corporation
























                                                                         ii





          CONTENTS



          Chapter 1
          INTRODUCTION
            What is Expanded Memory? . . . . . . . . . . . . . . . . .    1
            How Expanded Memory Works  . . . . . . . . . . . . . . . .    1

          Chapter 2
          WRITING PROGRAMS THAT USE EXPANDED MEMORY
            What Every Program Must Do . . . . . . . . . . . . . . . .    4
            Advanced Programming . . . . . . . . . . . . . . . . . . .    5
              Saving The State of Mapping Hardware . . . . . . . . . .    6
              Retrieving Handle and Page Counts  . . . . . . . . . . .    6
              Mapping and Unmapping Multiple Pages . . . . . . . . . .    6
              Reallocating Pages . . . . . . . . . . . . . . . . . . .    6
              Using Handles and Assigning Names to Handles . . . . . .    6
              Using Handle Attributes  . . . . . . . . . . . . . . . .    7
              Altering Page Maps and Jumping/Calling . . . . . . . . .    7
              Moving or Exchanging Memory Regions  . . . . . . . . . .    7
              Getting the Amount of Mappable Memory  . . . . . . . . .    8
              Operating System Functions . . . . . . . . . . . . . . .    8
            Programming Guidelines . . . . . . . . . . . . . . . . . .   12
            Examples . . . . . . . . . . . . . . . . . . . . . . . . .   14
              Example 1  . . . . . . . . . . . . . . . . . . . . . . .   14
              Example 2  . . . . . . . . . . . . . . . . . . . . . . .   19
              Example 3  . . . . . . . . . . . . . . . . . . . . . . .   30
              Example 4  . . . . . . . . . . . . . . . . . . . . . . .   32

          Chapter 3
          EMM FUNCTIONS
            Function 1. Get Status . . . . . . . . . . . . . . . . . .   37
            Function 2. Get Page Frame Address . . . . . . . . . . . .   38
            Function 3. Get Unallocated Page Count . . . . . . . . . .   40
            Function 4. Allocate Pages . . . . . . . . . . . . . . . .   42
            Function 5. Map/Unmap Handle Pages . . . . . . . . . . . .   46
            Function 6. Deallocate Pages . . . . . . . . . . . . . . .   49
            Function 7. Get Version  . . . . . . . . . . . . . . . . .   51
            Function 8. Save Page Map  . . . . . . . . . . . . . . . .   53
            Function 9. Restore Page Map . . . . . . . . . . . . . . .   55
            Function 10. Reserved  . . . . . . . . . . . . . . . . . .   57
            Function 11. Reserved  . . . . . . . . . . . . . . . . . .   58
            Function 12. Get Handle Count  . . . . . . . . . . . . . .   59
            Function 13. Get Handle Pages  . . . . . . . . . . . . . .   61
            Function 14. Get All Handle Pages  . . . . . . . . . . . .   63
            Function 15. Get/Set Page Map  . . . . . . . . . . . . . .   65
              Get Page Map subfunction . . . . . . . . . . . . . . . .   65
              Set Page Map subfunction . . . . . . . . . . . . . . . .   67
              Get & Set Page Map subfunction . . . . . . . . . . . . .   69
              Get Size of Page Map Save Array subfunction  . . . . . .   71


                                                                        iii





            Function 16. Get/Set Partial Page Map  . . . . . . . . . .   73
              Get Partial Page Map subfunction . . . . . . . . . . . .   73
              Set Partial Page Map subfunction . . . . . . . . . . . .   76
              Get Size of Partial Page Map Save Array subfunction  . .   78
            Function 17. Map/Unmap Multiple Handle Pages . . . . . . .   80
              Mapping Multiple Pages . . . . . . . . . . . . . . . . .   80
              Unmapping Multiple Pages . . . . . . . . . . . . . . . .   80
              Mapping and Unmapping Multiple Pages Simultaneously  . .   80
              Alternate Mapping and Unmapping Methods  . . . . . . . .   81
              Logical Page/Physical Page Method  . .OF THE EXPANDED MEMORY MANAGER
            Which method should your program use?  . . . . . . . . . .  199
            The "open handle" technique  . . . . . . . . . . . . . . .  199
            The "get interrupt vector" technique . . . . . . . . . . .  204

          Appendix C
          EXPANDED MEMORY MANAGER IMPLEMENTATION GUIDELINES
            The amount of expanded memory supported  . . . . . . . . .  206
            The number of handles supported  . . . . . . . . . . . . .  206
            Handle Numbering . . . . . . . . . . . . . . . . . . . . .  206
            New handle type:  Handles versus Raw Handles . . . . . . .  206
            The system Raw Handle (Raw Handle = 0000h) . . . . . . . .  207
            Terminate and Stay Resident (TSR) Program Cooperation  . .  208
            Accelerator Cards  . . . . . . . . . . . . . . . . . . . .  208

          Appendix D
          OPERATING SYSTEM/ENVIRONMENT USE OF FUNCTION 28
            Examples . . . . . . . . . . . . . . . . . . . . . . . . .  209
              Example 1  . . . . . . . . . . . . . . . . . . . . . . .  209
              Example 2  . . . . . . . . . . . . . . . . . . . . . . .  210
              Example 3  . . . . . . . . . . . . . . . . . . . . . . .  211

          GLOSSARY

          INDEX















                                                                          v





          Chapter 1
          INTRODUCTION


               Because even the maximum amount (640K bytes) of conventional
               memory isn't always enough for large application programs,
               Lotus Development Corporation, Intel Corporation, and Micro-
               soft Corporation created the Lotus/Intel/Microsoft (LIM)
               Expanded Memory Specification.

               The LIM Expanded Memory Specification defines the software
               interface between the Expanded Memory Manager (EMM) -- a
               device driver that controls and manages expanded memory --
               and application programs that use expanded memory.


          What is Expanded Memory?

               Expanded memory is memory beyond DOS's 640K-byte limit.  The
               LIM specification supports up to 32M bytes of expanded
               memory.  Because the 8086, 8088, and 80286 (in real mode)
               microprocessors can physically address only 1M bytes of
               memory, they access expanded memory through a window in
               their physical address range.  The next section explains how
               this is done.


          How Expanded Memory Works

               Expanded memory is divided into segments called logical
               pages.  These pages are typically 16K bytes of memory.  Your
               computer accesses logical pages through a physical block of
               memory called a page frame.  The page frame contains
               multiple physical pages, pages that the microprocessor can
               address directly.  Physical pages are also typically 16K
               bytes of memory.

               This page frame serves as a window into expanded memory. 
               Just as your computer screen is a window into a large
               spreadsheet, so the page frame is a window into expanded
               memory.

               A logical page of expanded memory can be mapped into (made
               to appear in) any one of the physical pages in the page
               frame.  Thus, a read or write to the physical page actually
               becomes a read or write to the associated logical page.  One
               logical page can be mapped into the page frame for each
               physical page.

               Figure 1-1 shows the relationship among the page frame,
               physical pages, and logical pages.


          Introduction                                                    1





                                                       32M +--------------+
                                                          /|              |
                                                           |              |
                                                     /     |              |
                                                           |              |
                                                /          |              |
                                                           |              |
                                           /               |              |
                                                           |   Expanded   |
                                      /                    |    Memory    |
          1024K +--------------+                           |              |
                | / / / / / /  | /                         |              |
           960K +--------------+                           |              |
                |  Page Frame  |                           |              |
                |              |                           |              |
                | 12 16K-Byte  |                           |              |
                |   Physical   |                           |              |
                |    Pages     |                           |              |
           768K +--------------+                           | Divided into |
                | / / / / / /  | \                         |   logical    |
           640K +--------------+                           |    pages     |
                |              |   \                       |              |
                |              |                           |              |
                |              |     \                     |              |
                |              |                           |              |
                | 24 16K-Byte  |       \                   |              |
                |   Physical   |                           |              |
                |    Pages*    |         \                 |              |
                |              |                           |              |
                |              |           \               |              |
                |              |                           |              |
                |              |             \             |              |
           256K +--------------+                           |              |
                |              |               \           |              |
                | / / / / / /  |                           |              |
                |              |                 \         |              |
                | / / / / / /  |                           |              |
                |              |                   \       |              |
                | / / / / / /  |                           |              |
                |              |                     \     |              |
              0 +--------------+                           |              |
                                                       \   |              |
                                                           |              |
          *Intended for operating                        \ |              |
           system/environment use only                   0 +--------------+



          Figure 1-1.  Expanded Memory




          Introduction                                                    2





               The page frame is located above 640K bytes.  Normally, only
               video adapters, network cards, and similar devices exist
               between 640K and 1024K.

               This specification also defines methods for operating
               systems and environments to access expanded memory through
               physical pages below 640K bytes.  These methods are intended
               for operating system/environment developers only.













































          Introduction                                                    3





          Chapter 2
          WRITING PROGRAMS THAT USE EXPANDED MEMORY


               This chapter describes what every program must do to use
               expanded memory and describes more advanced techniques of
               using expanded memory.

               This chapter also lists programming guidelines you should
               follow when writing programs that use expanded memory and
               provides the listings of some example programs.


          What Every Program Must Do

               This section describes the steps every program must take to
               use expanded memory.

               In order to use expanded memory, applications must perform
               these steps in the following order:

               1.  Determine if EMM is installed.

               2.  Determine if enough expanded memory pages exist for your
                   application.  (Function 3)

               3.  Allocate expanded memory pages.  (Function 4, 18, or 27)

               4.  Get the page frame base address.  (Function 2)

               5.  Map in expanded memory pages.  (Function 5 or 17)

               6.  Read/write/execute data in expanded memory, just as if
                   it were conventional memory.

               7.  Return expanded memory pages to expand memory pool
                   before exiting.  (Function 6 or 18)

               Table 2-1 overviews the functions while Chapter 3 describes
               each of these functions in detail.  Example programs at the
               end of this chapter illustrate using expanded memory.












          Writing Programs That Use Expanded Memory                       4





          Table 2-1.  The Basic Functions
          ----------------------------------------------------------------

          Function                        Description

          ----------------------------------------------------------------

             1         The Get Status Function returns a status code
                       indicating whether the memory manager hardware is
                       working correctly.

             2         The Get Page Frame Address function returns the
                       address where the 64K-byte page frame is located.

             3         The Get Unallocated Page Count function returns the
                       number of unallocated pages (pages available to your
                       program) and the total number of pages in expanded
                       memory.

             4         The Allocate Pages function allocates the number of
                       pages requested and assigns a unique EMM handle to
                       these pages.

             5         The Map/Unmap Handle Page function maps a logical
                       page to a specific physical page anywhere in the
                       mappable regions of system memory.

             6         The Deallocate Pages deallocates the logical pages
                       currently allocated to an EMM handle.

             7         The Get Version function returns the version number
                       of the memory manager software.

          ----------------------------------------------------------------



          Advanced Programming

               In addition to the basic functions, the Lotus/Intel/Micro-
               soft Expanded Memory Specification provides several advanced
               functions which enhance the capabilities of software that
               uses expanded memory.

               The following sections describe the advanced programming
               capabilities and list the advanced EMM functions.


          Note............................................................
               Before using the advanced functions, programs should first
               call Function 7 (Get Version) to determine whether the
               installed version of EMM supports these functions.

          Writing Programs That Use Expanded Memory                       5





          Saving The State of Mapping Hardware

               Some software (such as interrupt service routines, device
               drivers, and resident software) must save the current state
               of the mapping hardware, switch mapping contexts, manipulate
               sections of expanded memory, and restore the original
               context of the memory mapping hardware.  Use Functions 8 and
               9 or 15 and 16 to save the state of the hardware.


          Retrieving Handle and Page Counts

               Some utility programs need to keep track of how expanded
               memory is being used; use Functions 12 through 14 to do
               this.


          Mapping and Unmapping Multiple Pages

               Mapping multiple pages reduces the overhead an application
               must perform during mapping.  Function 17 lets a program map
               (or unmap) multiple pages at one time.

               In addition, you can map pages using segment addresses
               instead of physical pages.  For example, if the page frame
               base address is set to D000, you can map to either physical
               page 0 or segment D000.  Function 25 (Get Mappable Physical
               Address Array) returns a cross reference between all
               expanded memory physical pages and their corresponding
               segment values.


          Reallocating Pages

               Reallocating pages (Function 18) lets applications dynami-
               cally allocate expanded memory pages without acquiring
               another handle or obtain a handle without allocating pages. 
               Reallocating pages is an efficient means for applications to
               obtain and release expanded memory pages.


          Using Handles and Assigning Names to Handles

               This specification lets you associate a name with a handle,
               so a family of applications can share information in
               expanded memory.  For example, a software package consisting
               of a word processor, spreadsheet, and print spooler can
               share the same data among the different applications.  The
               print spooler could use a handle name to reference data that
               either the spreadsheet or word processor put in expanded
               memory and could check for data in a particular handle
               name's expanded memory pages.

          Writing Programs That Use Expanded Memory                       6





               Use Function 20 (Set Handle Name subfunction) to assign a
               handle name to an EMM handle or Function 21 (Search for
               Named Handle subfunction) to obtain the EMM handle as-
               sociated with the handle name.  In addition, you can use
               Function 14 (Get Handle Pages) to determine the number of
               expanded memory pages allocated to an EMM handle.


          Using Handle Attributes

               In addition to naming a handle, you can use Function 19 to
               associate an attribute (volatile or non-volatile) with a
               handle name.  A non-volatile attribute enables expanded
               memory pages to preserve their data through a warmboot. 
               With a volatile attribute, the data is not preserved.  The
               default attribute for handles is volatile.

               Because using this function depends on the capabilities of
               the expanded memory hardware installed in the system, you
               should use the Get Attribute Capability subfunction before
               attempting to assign an attribute to a handle's pages.


          Altering Page Maps and Jumping/Calling

               You can use Functions 22 (Alter Page Map & Jump) and 23
               (Alter Page Map & Call) to map a new set of values into the
               map registers and transfer program control to a specified
               address within expanded memory.  These functions can be used
               to load and execute code in expanded memory.  An application
               using this feature can significantly reduce the amount of
               conventional memory it requires.  Programs can load needed
               modules into expanded memory at run time and use Functions
               22 and 23 to transfer control to these modules.

               Using expanded memory to store code can improve program
               execution in many ways.  For example, sometimes programs
               need to be divided into small overlays because of conven-
               tional memory size limitations.  Overlays targeted for
               expanded memory can be very large because LIM EMS 4.0
               supports up to 32M bytes of expanded memory.  This method of
               loading overlays improves overall system performance by
               conserving conventional memory and eliminating conventional
               memory allocation errors.


          Moving or Exchanging Memory Regions

               Using Function 24 (Move/Exchange Memory Region), you can
               easily move and exchange data between conventional and
               expanded memory.  Function 24 can manipulate up to one
               megabyte of data with one function call.  Although applica-

          Writing Programs That Use Expanded Memory                       7





               tions can perform this operation without this function,
               having the expanded memory manager do it reduces the amount
               of overhead for the application.

               In addition, this function checks for overlapping regions
               and performs all the necessary mapping, preserving the
               mapping context from before the exchange/move call.


          Getting the Amount of Mappable Memory

               Function 25 enables applications to determine the total
               amount of mappable memory the hardware/system currently
               supports.  Not all expanded memory boards supply the same
               number of physical pages (map registers).

               The Get Mappable Physical Address Array Entries subfunction
               returns the total number of physical pages the expanded
               memory hardware/system is capable of supporting.  The Get
               Mappable Physical Array subfunction returns a cross refer-
               ence between physical page numbers and the actual segment
               address for each of the physical pages.


          Operating System Functions

               In addition to the functions for application programs, this
               specification defines functions for operating systems/en-
               vironments.  These functions can be disabled at any time by
               the operating system/environment, so programs should not
               depend on their presence.  Applications that avoid this
               warning and use these functions run a great risk of being
               incompatible with other programs, including the operating
               system.



















          Writing Programs That Use Expanded Memory                       8





          Table 2-2.  The Advanced Functions
          ----------------------------------------------------------------

          Function                        Description

          ----------------------------------------------------------------

             8         The Save Page Map saves the contents of the page
                       mapping registers from all expanded memory boards in
                       an internal save area.

             9         The Restore Page Map function restores (from an
                       internal save area) the page mapping register
                       contents on the expanded memory boards for a
                       particular EMM handle.

             10        Reserved.

             11        Reserved.

             12        The Get Handle Count function returns the number of
                       open EMM handles in the system.

             13        The Get Handle Pages function returns the number of
                       pages allocated to a specific EMM handle.

             14        The Get All Handle Pages function returns an array
                       of the active EMM handles and the number of pages
                       allocated to each one.

             15        The Get/Set Page Map subfunction saves or restores
                       the mapping context for all mappable memory regions
                       (conventional and expanded) in a destination array
                       which the application supplies.

             16        The Get/Set Partial Page Map subfunction provides a
                       mechanism for saving a partial mapping context for
                       specific mappable memory regions in a system.

             17        The Map/Unmap Multiple Handle Pages function can, in
                       a single invocation, map (or unmap) logical pages
                       into as many physical pages as the system supports.

             18        The Reallocate Pages function can increase or
                       decrease the amount of expanded memory allocated to
                       a handle.

             19        The Get/Set Handle Attribute function allows an
                       application program to determine and set the
                       attribute associated with a handle.



          Writing Programs That Use Expanded Memory                       9





          Table 2-2.  The Advanced Functions (continued)
          ----------------------------------------------------------------

          Function                        Description

          ----------------------------------------------------------------

             20        The Get/Set Handle Name function gets the eight
                       character name currently assigned to a handle and
                       can assign an eight character name to a handle.

             21        The Get Handle Directory function returns informa-
                       tion about active handles and the names assigned to
                       each.

             22        The Alter Page Map & Jump function alters the memory
                       mapping context and transfers control to the
                       specified address.

             23        The Alter Page Map & Call function alters the speci-
                       fied mapping context and transfers control to the
                       specified address.  A return can then restore the
                       context and return control to the caller.

             24        The Move/Exchange Memory Region function copies or
                       exchanges a region of memory from conventional to
                       conventional memory, conventional to expanded
                       memory, expanded to conventional memory, or expanded
                       to expanded memory.

             25        The Get Mappable Physical Address Array function
                       returns an array containing the segment address and
                       physical page number for each mappable physical page
                       in a system.

             26        The Get Expanded Memory Hardware Information
                       function returns an array containing the hardware
                       capabilities of the expanded memory system.

             27        The Allocate Standard/Raw Pages function allocates
                       the number of standard or non-standard size pages
                       that the operating system requests and assigns a
                       unique EMM handle to these pages.

             28        The Alternate Map Register Set function enables an
                       application to simulate alternate sets of hardware
                       mapping registers.

             29        The Prepare Expanded Memory Hardware for Warm Boot
                       function prepares the expanded memory hardware for
                       an "impending" warm boot.


          Writing Programs That Use Expanded Memory                      10





          Table 2-2.  The Advanced Functions (continued)
          ----------------------------------------------------------------

          Function                        Description

          ----------------------------------------------------------------

             30        The Enable/Disable OS/E function enables operating
                       systems developers to enable and disable functions
                       designed for operating system use.

          ----------------------------------------------------------------









































          Writing Programs That Use Expanded Memory                      11





          Programming Guidelines

               The following section contains guidelines for programmers
               writing applications that use EMM.

               o   Do not put a program's stack in expanded memory.

               o   Do not replace interrupt 67h.  This is the interrupt
                   vector the EMM uses.  Replacing interrupt 67h could
                   result in disabling the Expanded Memory Manager.

               o   Do not map into conventional memory address space your
                   application doesn't own.  Applications that use the EMM
                   to swap into conventional memory space, must first
                   allocate this space from the operating system.  If the
                   operating system is not aware that a region of memory it
                   manages is in use, it will think it is available.  This
                   could have disastrous results.  EMM should not be used
                   to "allocate" conventional memory.  DOS is the proper
                   manager of conventional memory space.  EMM should only
                   be used to swap data in conventional memory space
                   previously allocated from DOS.

               o   Applications that plan on using data aliasing in
                   expanded memory must check for the presence of expanded
                   memory hardware.  Data aliasing occurs when mapping one
                   logical page into two or more mappable segments.  This
                   makes one 16K-byte expanded memory page appear to be in
                   more than one 16K-byte memory address space.  Data
                   aliasing is legal and sometimes useful for applications.

                   Software-only expanded memory emulators cannot perform
                   data aliasing.  A simple way to distinguish software
                   emulators from actual expanded memory hardware is to
                   attempt data aliasing and check the results.  For
                   example, map one logical page into four physical pages. 
                   Write to physical page 0.  Read physical pages 1-3 to
                   see if the data is there as well.  If the data appears
                   in all four physical pages, then expanded memory
                   hardware is installed in the system, and data aliasing
                   is supported.

               o   Applications should always return expanded memory pages
                   to the expanded memory manager upon termination.  These
                   pages will be made available for other applications.  If
                   unneeded pages are not returned to the expanded memory
                   manager, the system could "run out" of expanded memory
                   pages or expanded memory handles.

               o   Terminate and stay resident programs (TSR's) should
                   ALWAYS save the state of the map registers before
                   changing them.  Since TSR's may interrupt other programs

          Writing Programs That Use Expanded Memory                      12





                   which may be using expanded memory, they must not change
                   the state of the page mapping registers without first
                   saving them.  Before exiting, TSR's must restore the
                   state of the map registers.

                   The following sections describe the three ways to save
                   and restore the state of the map registers.

                   1.  Save Page Map and Restore Page Map (Functions 8 and
                       9).  This is the simplest of the three methods.  The
                       EMM saves the map register contents in its own data
                       structures -- the application does not need to
                       provide extra storage locations for the mapping
                       context.  The last mapping context to be saved,
                       under a particular handle, will be restored when a
                       call to Restore Page Map is issued with the same
                       handle.  This method is limited to one mapping
                       context for each handle and saves the context for
                       only LIM standard 64K-byte page frames.

                   2.  Get/Set Page Map (Function 15).  This method
                       requires the application to allocate space for the
                       storage array.  The EMM saves the mapping context in
                       an array whose address is passed to the EMM.  When
                       restoring the mapping context with this method, an
                       application passes the address of an array which
                       contains a previously stored mapping context.

                       This method is preferable if an application needs to
                       do more than one save before a restore.  It provides
                       a mechanism for switching between more than one
                       mapping context.

                   3.  Get/Set Partial Page Map (Function 16).  This method
                       provides a way for saving a partial mapping context. 
                       It should be used when the application does not need
                       to save the context of all mappable memory.  This
                       function also requires that the storage array be
                       part of the application's data.

               o   All functions using pointers to data structures must
                   have those data structures in memory which will not be
                   mapped out.  Functions 22 and 23 (Alter Map & Call and
                   Alter Map & Jump) are the only exceptions.









          Writing Programs That Use Expanded Memory                      13





          Examples

               This section lists four example programs that demonstrate
               the use of expanded memory.


          Example 1

               This program was written using the Microsoft C compiler
               Version 3.0.  EMM function calls are made with the int86
               function found in the dos.h library.  To create an ex-
               ecutable program use the following compile command line:

                         msc /Gs /Oat /Ml program,,program;

          #include <dos.h>
          #include <stdio.h>

          #define EMM_INT                 0x67  /* EMM interrupt number */
          #define GET_PAGE_FRAME          0x41  /* EMM get page frame */
                                                /* function number */
          #define GET_UNALLOC_PAGE_COUNT  0x42  /* EMM get unallocated */
                                                /* page count */
                                                /* function number */
          #define ALLOCATE_PAGES          0x43  /* EMM allocate pages */
                                                /* function number */
          #define MAP_PAGES               0x44  /* EMM map pages */
                                                /* function number */
          #define DEALLOCATE_PAGES        0x45  /* EMM deallocate pages */
                                                /* function number */
          #define DEVICE_NAME_LENGTH      8     /* length of a device */
                                                /* name string */
          #define TRUE                    1
          #define FALSE                   0

          union REGS input_regs, output_regs;
          struct SREGS segment_regs;
          int pf_addr;

          /*------------------------------------------------------------*/
          /* Routine to convert a segment:offset pair to a far ptr.     */
          /*------------------------------------------------------------*/
          char *build_ptr (segment, offset)

              unsigned int segment;
              unsigned int offset;
          {
              char *ptr;

              ptr = (char *)(((unsigned long)segment << 16) + offset);
              return (ptr);
          }

          Writing Programs That Use Expanded Memory                      14





          /*------------------------------------------------------------*/
          /* Function which determines whether EMM device driver        */
          /* is installed.                                              */
          /*------------------------------------------------------------*/
          char emm_installed()

          {
              char *EMM_device_name = "EMMXXXX0";
              char *int_67_device_name_ptr;

              /*--------------------------------------------------------*/
              /* AH = DOS get interrupt vector function.                */
              /*--------------------------------------------------------*/
              input_regs.h.ah = 0x35;

              /*--------------------------------------------------------*/
              /* AL = EMM interrupt vector number.                      */
              /*--------------------------------------------------------*/
              input_regs.h.al = EMM_INT;
              intdosx (&input_regs, &output_regs, &segment_regs);

              /*--------------------------------------------------------*/
              /* Upon return ES:0Ah points to location where            */
              /* device name should be.                                 */
              /*--------------------------------------------------------*/
              int_67_device_name_ptr = build_ptr (segment_regs.es, 0x0A);

              /*--------------------------------------------------------*/
              /* Compare memory with EMM device name.                   */
              /*--------------------------------------------------------*/
              if (memcmp (EMM_device_name, int_67_device_name_ptr,
                                             DEVICE_NAME_LENGTH) == 0)
                  return (TRUE);
              else
                  return (FALSE);
          }

          /*------------------------------------------------------------*/
          /* Function which determines if there are enough unallocated  */
          /* expanded memory pages for the application.                 */
          /*------------------------------------------------------------*/
          char enough_unallocated_pages (pages_needed)

              int pages_needed;
          {
              input_regs.h.ah = GET_UNALLOCATED_PAGE_COUNT;
              int86 (EMM_INT, &input_regs, &output_regs);
              if (output_regs.h.ah != 0 || pages_needed > output_regs.x.bx)
                  return (FALSE);
              else
                  return (TRUE);
          }

          Writing Programs That Use Expanded Memory                      15





          /*------------------------------------------------------------*/
          /* Function which allocates expanded memory pages and passes  */
          /* back to the main EMM handle.                               */
          /*------------------------------------------------------------*/
          char allocate_expanded_memory_pages (pages_needed,emm_handle_ptr)

              int pages_needed;
              unsigned int *emm_handle_ptr;
          {
              input_regs.h.ah = ALLOCATE_PAGES;
              input_regs.x.bx = pages_needed;
              int86 (EMM_INT, &input_regs, &output_regs);
              if (output_regs.h.ah == 0) {
                  *emm_handle_ptr = output_regs.x.dx;
                  return (TRUE);
              }
              else
                  return (FALSE);
          }

          /*------------------------------------------------------------*/
          /* Routine to map a logical page to a physical page.          */
          /*------------------------------------------------------------*/
          char map_expanded_memory_pages (emm_handle, physical_page,       
                                                             logical_page)
              unsigned int emm_handle;
              int physical_page;
              int logical_page;
          {
              input_regs.h.ah = MAP_PAGES;
              input_regs.h.al = physical_page;
              input_regs.x.bx = logical_page;
              input_regs.x.dx = emm_handle;
              int86 (EMM_INT, &input_regs, &output_regs);
              if (output_regs.h.ah == 0)
                  return (TRUE);
              else
                  return (FALSE);
          }














          Writing Programs That Use Expanded Memory                      16





          /*------------------------------------------------------------*/
          /* Routine which gets the page frame base address from EMM.   */
          /*------------------------------------------------------------*/
          char get_page_frame_address (pf_ptr)

              char **pf_ptr;
          {
              input_regs.h.ah = GET_PAGE_FRAME;
              int86 (EMM_INT, &input_regs, &output_regs);
              if (output_regs.h.ah != 0)       /* check EMM status */
                  return (FALSE);
              else
                 *pf_ptr = build_ptr (output_regs.x.bx, 0);
              return (TRUE);
          }

          /*------------------------------------------------------------*/
          /* Routine to release all expanded memory pages allocated     */
          /* by an EMM handle.                                          */
          /*------------------------------------------------------------*/

          char deallocate_expanded_memory_pages (emm_handle)

              unsigned int emm_handle;
          {
              input_regs.h.ah = DEALLOCATE_PAGES;
              input_regs.x.dx = emm_handle;
              int86 (EMM_INT, &input_regs, &output_regs);
              if (output_regs.h.ah == 0)
                  return (TRUE);
              else
                  return (FALSE);
          }

          main()

          {
              unsigned int emm_handle;
              char *pf_addr;
              int pages_needed;
              int physical_page;
              int logical_page;
              int index;

              /*--------------------------------------------------------*/
              /* Determine if EMM is installed.                         */
              /*--------------------------------------------------------*/
              if (!emm_installed())
                  exit(1);




          Writing Programs That Use Expanded Memory                      17





              /*--------------------------------------------------------*/
              /* Determine if enough expanded memory pages exist for    */
              /* application.                                           */
              /*--------------------------------------------------------*/
              pages_needed = 1;
              if (!enough_unallocated_pages (pages_needed))
                  exit(1);

              /*--------------------------------------------------------*/
              /* Allocate expanded memory pages.                        */
              /*--------------------------------------------------------*/
              if (!allocate_expanded_memory_pages (pages_needed,
                                                             &emm_handle))
                  exit(1);

              /*--------------------------------------------------------*/
              /* Map in the required pages.                             */
              /*--------------------------------------------------------*/
              physical_page = 0;
              logical_page = 0;
              if (!map_expanded_memory_pages (emm_handle, physical_page,
                                                            logical_page))
                  exit(1);

              /*--------------------------------------------------------*/
              /* Get expanded memory page frame address.                */
              /*--------------------------------------------------------*/
              if (!get_page_frame_address (&pf_addr))
                  exit(1);

              /*--------------------------------------------------------*/
              /* Write to expanded memory.                              */
              /*--------------------------------------------------------*/
              for (index = 0; index < 0x3fff; index++)
                  pf_addr[index] = index;

              /*--------------------------------------------------------*/
              /* Return expanded memory pages before exiting.           */
              /*--------------------------------------------------------*/
              if (!deallocate_expanded_memory_pages (emm_handle))
                  exit(1);
          }











          Writing Programs That Use Expanded Memory                      18





          Example 2

          This program shows you how to use the basic functions of the LIM
          Expanded Memory Specification with Turbo Pascal.  The program
          does the following:

               1.  Makes sure the LIM Expanded Memory Manager (EMM) has
                   been installed.

               2.  Displays the version number of the EMM.

               3.  Determines if there are enough pages of memory for the
                   program.  It then displays the total number of EMM pages
                   present in the system and the number available for use.

               4.  Requests the desired number of pages from the EMM.

               5.  Maps a logical page into one of the physical pages.

               6.  Displays the base address of our EMM memory page frame. 
                   Performs a simple read/write test on the EMM memory.

               7.  Returns the EMM memory given to us back to the EMM.

               8.  Exits.

          All the calls are structured to return the result or error code
          of the Expanded Memory function performed as an integer.  If the
          error code is not zero, an error has occurred, a simple error
          procedure is called, and the program terminates.

          Type
            ST3  = string[3];
            ST80 = string[80];
            ST5  = string[5];

            Registers = record
              case integer of
                1: (AX,BX,CX,DX,BP,SI,DI,DS,ES,FLAGS: Integer);
                2: (AL,AH,BL,BH,CL,CH,DL,DH         : Byte);
              end;

          Const
            EMM_INT                    = $67;
            DOS_Int                    = $21;
            GET_PAGE_FRAME             = $41;
            GET_UNALLOCATED_PAGE_COUNT = $42;
            ALLOCATE_PAGES             = $43;
            MAP_PAGES                  = $44;
            DEALLOCATE_PAGES           = $45;
            GET_VERSION                = $46;
            STATUS_OK                  = 0;

          Writing Programs That Use Expanded Memory                      19





            {------------------------------------------------------------}
            { Assume the application needs one EMM page.                 }
            {------------------------------------------------------------}
            APPLICATION_PAGE_COUNT = 1;

          Var
            Regs: Registers;

            Emm_handle,
            Page_Frame_Base_Address,
            Pages_Needed,
            Physical_Page,
            Logical_Page,
            Offset,
            Error_Code,
            Pages_EMM_Available,
            Total_EMM_Pages,
            Available_EMM_Pages: Integer;

            Version_Number,
            Pages_Number_String: ST3;

            Verify: Boolean;

            {------------------------------------------------------------}
            { The function Hex_String converts an integer into a four    }
            { character hexadecimal number (string) with leading zeros.  }
            {------------------------------------------------------------}
            Function Hex_String (Number: Integer): ST5;
              Function Hex_Char (Number: Integer): Char;
                Begin
                  If Number < 10 then
                    Hex_Char := Char (Number + 48)
                  else
                    Hex_Char := Char (Number + 55);
                end; { Function Hex_char }

            Var
              S: ST5;

            Begin
              S := '';
              S := Hex_Char ((Number shr 1) div 2048);
              Number := (((Number shr 1) mod 2048) shl 1) + (Number and 1);
              S := S + Hex_Char (Number div 256);
              Number := Number mod 256;
              S := S + Hex_Char (Number div 16);
              Number := Number mod 16;
              S := S + Hex_Char (Number);
              Hex_String := S + 'h';
            end; { Function Hex_String }


          Writing Programs That Use Expanded Memory                      20





            {------------------------------------------------------------}
            { The function Emm_Installed checks to see if the            }
            { EMM is loaded in memory.  It does this by looking          }
            { for the string 'EMMXXXX0', which should be located         }
            { at 10 bytes from the beginning of the code segment the     }
            { EMM interrupt, 67h, points to.                             }
            {------------------------------------------------------------}
            Function Emm_Installed: Boolean;
              Var
                Emm_Device_Name   : string[8];
                Int_67_Device_Name: string[8];
                Position          : integer;
                Regs              : registers;

              Begin
                Int_67_Device_Name := '';
                Emm_Device_Name    := 'EMMXXXX0';
                with Regs do
                  Begin
                    {----------------------------------------------------}
                    { Get the code segment interrupt 67h points to       }
                    { the EMM interrupt by using DOS function 35h.       }
                    { (get interrupt vector)                             }
                    {----------------------------------------------------}
                    AH := $35;
                    AL := EMM_INT;
                    Intr (DOS_Int, Regs);
                    {----------------------------------------------------}
                    { The ES pseudo-register contains the segment        }
                    { address pointed to by interrupt 67h.  Create an    }
                    { eight character string from the eight successive   }
                    { bytes at address ES:$000A (10 bytes from ES)       }
                    {----------------------------------------------------}
                    For Position := 0 to 7 do
                      Int_67_Device_Name :=
                        Int_67_Device_Name + Chr (mem[ES:Position + $0A]);
                    Emm_Installed := True;
                    {----------------------------------------------------}
                    { If the string is the EMM manager signature,        }
                    { 'EMMXXXX0', then EMM is installed and ready for    }
                    { use.  If not, then EMM is not present.             }
                    {----------------------------------------------------}
                    If Int_67_Device_Name <> Emm_Device_Name
                      then Emm_Installed := False;
                  end; { with Regs do }
              end; { Function Emm_Installed }







          Writing Programs That Use Expanded Memory                      21





            {------------------------------------------------------------}
            { This function returns the total number of EMM pages        }
            { present in the system, and the number of EMM pages that    }
            { are available.                                             }
            {------------------------------------------------------------}
            Function EMM_Pages_Available
              (Var Total_EMM_Pages, Pages_Available: Integer): Integer;
              Var
                Regs: Registers;

              Begin
                with Regs do
                  Begin
                    {----------------------------------------------------}
                    { Get the number of currently unallocated pages and  }
                    { the total number of pages in the system from EMM.  }
                    { Load pseudo-registers prior to invoking EMM.       }
                    {    AH = get unallocated page count function        }
                    {----------------------------------------------------}
                    AH := GET_UNALLOCATED_PAGE_COUNT;
                    Intr (EMM_INT, Regs);
                    {----------------------------------------------------}
                    { Unload the pseudo-registers after invoking EMM.    }
                    {    BX = currently unallocated pages                }
                    {    DX = total pages in the system                  }
                    {    AH = status                                     }
                    {----------------------------------------------------}
                    Pages_Available := BX;
                    Total_EMM_Pages := DX;
                    EMM_Pages_Available := AH;
                  end;
              end; { Function EMM_Pages_Available }


            {------------------------------------------------------------}
            { This function requests the specified number of pages       }
            { from the EMM.                                              }
            {------------------------------------------------------------}
            Function Allocate_Expanded_Memory_Pages
              (Pages_Needed: Integer; Var Handle: Integer): Integer;
              Var
                Regs: Registers;











          Writing Programs That Use Expanded Memory                      22





              Begin
                with Regs do
                  Begin
                    {----------------------------------------------------}
                    { Allocate the specified number of pages from EMM.   }
                    { Load pseudo-registers prior to invoking EMM.       }
                    {    AH = allocate pages function.                   }
                    {    BX = number of pages to allocate.               }
                    {----------------------------------------------------}
                    AH := ALLOCATE_PAGES;
                    BX := Pages_Needed;
                    Intr (EMM_INT, Regs);
                    {----------------------------------------------------}
                    { Unload the pseudo-registers after invoking EMM.    }
                    {    DX = EMM handle                                 }
                    {    AH = status                                     }
                    {----------------------------------------------------}
                    Handle := DX;
                    Allocate_Expanded_Memory_Pages := AH;
                  end;
              end; { Function Allocate_Expanded_Memory_Pages }


            {------------------------------------------------------------}
            { This function maps a logical page allocated by the         }
            { Allocate_Expanded_Memory_Pages function into one of the    }
            { four physical pages.                                       }
            {------------------------------------------------------------}
            Function Map_Expanded_Memory_Pages
              (Handle, Logical_Page, Physical_Page: Integer): Integer;
              Var
                Regs: Registers;

              Begin
                with Regs do
                  Begin
                    {----------------------------------------------------}
                    { Map a logical page at a physical page.             }
                    { Load pseudo-registers prior to invoking EMM.       }
                    {    AH = map page function                          }
                    {    DX = handle                                     }
                    {    BX = logical page number                        }
                    {    AL = physical page number                       }
                    {----------------------------------------------------}
                    AH := MAP_PAGES;
                    DX := Handle;
                    BX := Logical_Page;
                    AL := Physical_Page;
                    Intr (EMM_INT, Regs);




          Writing Programs That Use Expanded Memory                      23





                    {----------------------------------------------------}
                    { Unload the pseudo-registers after invoking EMM.    }
                    {    AH = status                                     }
                    {----------------------------------------------------}
                    Map_Expanded_Memory_Pages := AH;
                  end; { with Regs do }
              end; { Function Map_Expanded_Memory_Pages }


            {------------------------------------------------------------}
            { This function gets the physical address of the EMM page    }
            { frame we are using.  The address returned is the segment   }
            { of the page frame.                                         }
            {------------------------------------------------------------}
            Function Get_Page_Frame_Base_Address
              (Var Page_Frame_Address: Integer): Integer;
              Var
                Regs: Registers;

              Begin
                with Regs do
                  Begin
                    {----------------------------------------------------}
                    { Get the page frame segment address from EMM.       }
                    { Load pseudo-registers prior to invoking EMM.       }
                    {    AH = get page frame segment function            }
                    {----------------------------------------------------}
                    AH := GET_PAGE_FRAME;
                    Intr (EMM_INT, Regs);
                    {----------------------------------------------------}
                    { Unload the pseudo-registers after invoking EMM.    }
                    {    BX = page frame segment address                 }
                    {    AH = status                                     }
                    {----------------------------------------------------}
                    Page_Frame_Address := BX;
                    Get_Page_Frame_Base_Address := AH;
                  end; { with Regs do }
              end; { Function Get_Page_Frame_Base_Address }


            {------------------------------------------------------------}
            { This function releases the EMM memory pages allocated to   }
            { us, back to the EMM memory pool.                           }
            {------------------------------------------------------------}
            Function Deallocate_Expanded_Memory_Pages
              (Handle: Integer): Integer;
              Var
                Regs: Registers;





          Writing Programs That Use Expanded Memory                      24





              Begin
                with Regs do
                  Begin
                    {----------------------------------------------------}
                    { Deallocate the pages allocated to an EMM handle.   }
                    { Load pseudo-registers prior to invoking EMM.       }
                    {    AH = deallocate pages function                  }
                    {    DX = EMM handle                                 }
                    {----------------------------------------------------}
                    AH := DEALLOCATE_PAGES;
                    DX := Handle;
                    Intr (EMM_INT, Regs);
                    {----------------------------------------------------}
                    { Unload the pseudo-registers after invoking EMM.    }
                    {    AH = status                                     }
                    {----------------------------------------------------}
                    Deallocate_Expanded_Memory_Pages := AH;
                  end; { with Regs do }
              end; { Function Deallocate_Expanded_Memory_Pages }


            {------------------------------------------------------------}
            { This function returns the version number of the EMM as     }
            { a three-character string.                                  }
            {------------------------------------------------------------}
            Function Get_Version_Number (Var Version_String: ST3): Integer;
              Var
                Regs: Registers;
                Integer_Part, Fractional_Part: Char;

              Begin
                with Regs do
                  Begin
                    {----------------------------------------------------}
                    { Get the version of EMM.                            }
                    { Load pseudo-registers prior to invoking EMM.       }
                    {    AH = get EMM version function                   }
                    {----------------------------------------------------}
                    AH := GET_VERSION;
                    Intr (EMM_INT, Regs);













          Writing Programs That Use Expanded Memory                      25





                    {----------------------------------------------------}
                    { If the version number returned was OK, then        }
                    { convert it to a three-character string.            }
                    {----------------------------------------------------}
                    If AH=STATUS_OK then
                      Begin
                        {------------------------------------------------}
                        { The upper four bits of AH are the integer      }
                        { portion of the version number, the lower four  }
                        { bits are the fractional portion.  Convert the  }
                        { integer value to ASCII by adding 48.           }
                        {------------------------------------------------}
                        Integer_Part    := Char (AL shr 4  + 48);
                        Fractional_Part := Char (AL and $F + 48);
                        Version_String  := Integer_Part + '.' +
                                                          Fractional_Part;
                      end; { If AH=STATUS_OK }
                    {----------------------------------------------------}
                    { Unload the pseudo-registers after invoking EMM.    }
                    {    AH = status                                     }
                    {----------------------------------------------------}
                    Get_Version_Number := AH;
                  end; { with Regs do }
              end; { Function Get_Version_Number }


            {------------------------------------------------------------}
            { This procedure prints an error message passed by the       }
            { caller, prints the error code passed by the caller in hex, }
            { and then terminates the program with an error level of 1.  }
            {------------------------------------------------------------}
            Procedure Error (Error_Message: ST80; Error_Number: Integer);
              Begin
                Writeln (Error_Message);
                Writeln ('  Error_Number = ', Hex_String (Error_Number));
                Writeln ('EMM test program aborting.');
                Halt (1);
              end; { Procedure Error }


          {--------------------------------------------------------------}
          { This program is an example of the basic EMM functions that   }
          { you need in order to use EMM memory with Turbo Pascal.       }
          {--------------------------------------------------------------}
          Begin
            ClrScr;
            Window (5,2,77,22);






          Writing Programs That Use Expanded Memory                      26





            {------------------------------------------------------------}
            { Determine if the Expanded Memory Manager is installed.  If }
            { not, then terminate 'main' with an ErrorLevel code of 1.   }
            {------------------------------------------------------------}
            If not (Emm_Installed) then
              Begin
                Writeln ('The LIM EMM is not installed.');
                Halt (1);
              end
            else
              Begin
                { Get the version number and display it }
                Error_Code := Get_Version_Number (Version_Number);
                If Error_Code <> STATUS_OK then
                  Error ('Error getting EMM version number.', Error_Code)
                else
                  Writeln ('LIM Expanded Memory Manager, version ',
                           Version_Number, ' is ready for use.');
              end;
            Writeln;

            {------------------------------------------------------------}
            { Determine if there are enough expanded memory pages for    }
            { this application.                                          }
            {------------------------------------------------------------}
            Pages_Needed := APPLICATION_PAGE_COUNT;
            Error_Code   := EMM_Pages_Available (Total_EMM_Pages,
                                                 Available_EMM_Pages);
            If Error_Code <> STATUS_OK then
              Error ('Error determining number of EMM pages available.',
                     Error_Code);
            Writeln ('There are a total of ', Total_EMM_Pages,
                     ' expanded memory pages present in this system.');
            Writeln ('  ', Available_EMM_Pages,
                     ' of those pages are available for use.');
            Writeln;

            {------------------------------------------------------------}
            { If there is an insufficient number of pages for the        }
            { application, then report the error and terminate the EMM   }
            { example program.                                           }
            {------------------------------------------------------------}
            If Pages_Needed > Available_EMM_Pages then
              Begin
                Str (Pages_Needed, Pages_Number_String);
                Error ('We need ' + Pages_Number_String +
                       ' EMM pages.  There are not that many available.',
                       Error_Code);
              end; { Pages_Needed > Available_EMM_Pages }




          Writing Programs That Use Expanded Memory                      27





            {------------------------------------------------------------}
            { Allocate expanded memory pages for our use.                }
            {------------------------------------------------------------}
            Error_Code :=
              Allocate_Expanded_Memory_Pages (Pages_Needed, Emm_Handle);
            Str (Pages_Needed, Pages_Number_String);
            If Error_Code <> STATUS_OK then
              Error ('EMM test program failed trying to allocate '
                     + Pages_Number_String
                     + ' pages for usage.', Error_Code);
            Writeln (APPLICATION_PAGE_COUNT,
                     ' EMM page(s) allocated for the EMM test program.');
            Writeln;

            {------------------------------------------------------------}
            { Map in the required logical pages to the physical pages    }
            { given to us, in this case just one page.                   }
            {------------------------------------------------------------}
            Logical_Page  := 0;
            Physical_Page := 0;
            Error_Code := Map_Expanded_Memory_Pages (Emm_Handle,
                                                     Logical_Page,
                                                     Physical_Page);
            If Error_Code <> STATUS_OK then
              Error ('EMM test program failed trying to map '
                     + 'logical pages into physical pages.',
                     Error_Code);

            Writeln ('Logical Page ',
                     Logical_Page,
                     ' successfully mapped into Physical Page ',
                     Physical_Page);
            Writeln;

            {------------------------------------------------------------}
            { Get the expanded memory page frame address.                }
            {------------------------------------------------------------}
            Error_Code := Get_Page_Frame_Base_Address
                            (Page_Frame_Base_Address);
            If Error_Code <> STATUS_OK then
              Error ('EMM test program unable to get the base Page'
                     + ' Frame Address.',
                     Error_Code);
            Writeln ('The base address of the EMM page frame is = '
                     + Hex_String (Page_Frame_Base_Address));
            Writeln;







          Writing Programs That Use Expanded Memory                      28





            {------------------------------------------------------------}
            { Write a test pattern to expanded memory.                   }
            {------------------------------------------------------------}
            For Offset := 0 to 16382 do
              Begin
                Mem[Page_Frame_Base_Address:Offset] := Offset mod 256;
              end;

            {------------------------------------------------------------}
            { Make sure that what is in EMM memory is what was just      }
            { written.                                                   }
            {------------------------------------------------------------}
            Writeln ('Testing EMM memory.');

            Offset := 1;
            Verify := True;
            while (Offset <= 16382) and (Verify = True) do
              Begin
                If Mem[Page_Frame_Base_Address:Offset] <> Offset mod 256
                  then Verify := False;
                Offset := Succ (Offset);
              end; { while (Offset <= 16382) and (Verify = True) }

            {------------------------------------------------------------}
            { If what is read does not match what was written,           }
            { an error occurred.                                         }
            {------------------------------------------------------------}
            If not Verify then
              Error ('What was written to EMM memory was not found during'
                     + ' memory verification test.',
                     0);
            Writeln ('EMM memory test successful.');
            Writeln;

            {------------------------------------------------------------}
            { Return the expanded memory pages given to us back to the   }
            { EMM memory pool before terminating our test program.       }
            {------------------------------------------------------------}
            Error_Code := Deallocate_Expanded_Memory_Pages (Emm_Handle);
            If Error_Code <> STATUS_OK then
              Error ('EMM test program was unable to deallocate '
                     + 'the EMM pages in use.',
                     Error_Code);
            Writeln (APPLICATION_PAGE_COUNT,
                     ' pages(s) deallocated.');
            Writeln;
            Writeln ('EMM test program completed.');

          end.




          Writing Programs That Use Expanded Memory                      29





          Example 3

               This program is written in Microsoft's macro assembler.


          CODE SEGMENT
               ASSUME CS:CODE, DS:CODE

          MOV   AX,CS
          MOV   DX,AX
               .
               .
               .
          check_emm_installed:

          MOV   AH,35h                  ; AH = DOS get interrupt vector
                                        ; function
          MOV   AL,67h                  ; AL = EMM interrupt vector number
          INT   21h
          MOV   DI,0Ah                  ; ES:DI points to where device     
                                        ; name should be
          LEA   SI,EMM_device_name      ; DS:SI points to ASCII string     
                                        ; containing EMM device name

          MOV   CX,device_name_length   ; set up loop counter for string op
          CLD                           ; set up direction flag for forward
          REPE  CMPSB                   ; Compare the strings
          JNE   exit                    ; IF strings not equal THEN exit
                                        ; ELSE
          check_enough_unallocated_pages:

          MOV   AH,41h                  ;    AH = EMM get unallocated page
                                        ;    count function code
          INT   67h
          OR    AH,AH                   ; Check EMM status
          JNZ   emm_error_handler       ; IF error THEN goto error handler
                                        ; ELSE
          allocate_expanded_memory_pages:

          MOV   AH,43h                  ;    AH = EMM allocate pages
                                        ;    function code
          MOV   BX,2                    ;    BX = number of pages needed
          INT   67h
          OR    AH,AH                   ; Check EMM status
          JNZ   emm_error_handler       ; IF error THEN goto error handler
                                        ; ELSE
          MOV   emm_handle,DX           ;    save EMM handle

          map_expanded_memory_pages:

          MOV   AH,44h                  ; AH = EMM map pages function
          MOV   DX,emm_handle           ; DX = application's handle

          Writing Programs That Use Expanded Memory                      30





          map_0_to_0:

          MOV   BX,0                    ; BX = logical page 0
          MOV   AL,0                    ; AL = physical page 0
          INT   67h
          OR    AH,AH                   ; Check EMM status
          JNZ   emm_error_handler       ; If error THEN goto error handler
                                        ; ELSE
          get_page_frame_address:

          MOV   AH,41h                  ; AH = EMM get page frame base
                                        ; address function
          INT   67h
          OR    AH,AH                   ; Check EMM status
          JNZ   emm_error_handler       ; IF error THEN goto error handler
          MOV   pf_addr,BX              ; ELSE save pf_addr

          write_to_expanded_memory:     ; Write zeros to memory mapped at
                                        ; physical page 0
          MOV   AX,pf_addr
          MOV   ES,AX                   ; ES points to physical page 0
          MOV   DI,0                    ; DI indexes into physical page 0
          MOV   AL,0                    ; Initialize AL for string STOSB
          MOV   CX,4000h                ; Initialize loop counter to length 
                                        ; of expanded memory page size
          CLD                           ; set up direction flag for forward
          REP   STOSB

          deallocate_pages:

          MOV   AH,45h                  ; AH = EMM deallocate pages        
                                        ; function
          MOV   DX,emm_handle
          INT   67h                     ; return handle's pages to EMM
          OR    AH,AH                   ; Check EMM status
          JNZ   emm_error_handler       ; IF error THEN goto error handler

          exit:

          MOV   AH,4Ch                  ; AH = DOS exit function
          INT   21h                     ; return to DOS


          EMM_device_name DB 'EMMXXXX0' ; ASCII EMM device name string

          device_name_length EQU 8

          CODE ENDS
               END




          Writing Programs That Use Expanded Memory                      31





          Example 4

               This program is an example of how to exchange a 256K-byte
               block of data from conventional memory to expanded memory.


          CODE SEGMENT
               ASSUME CS:CODE, DS:CODE
               .
               .
               .
          xchg_packet_set_up:

          ;DS:SI = xchg_packet

          MOV   AX,SEG xchg_packet
          MOV   DS,AX
          MOV   SI,OFFSET xchg_packet

          ;Moving 256K of data from conventional memory to expanded memory

          MOV   WORD PTR [SI].region_length[0],0
          MOV   WORD PTR [SI].region_length[2],4
          MOV   [SI].src_mem_type,0
          MOV   [SI].dest_mem_type,1

          ;starting at segment: 4000h, offset: 0

          MOV   [SI].src_init_seg_page,4000h
          MOV   [SI].src_init_offset,0

          ;Move data into expanded memory logical page 0, offset 0.

          MOV   [SI].dest_init_seg_page,0
          MOV   [SI].dest_init_offset,0

          ;Initialize for future compatibility

          MOV   [SI].src_handle,0

          ;Need handle for expanded memory destination.

          MOV   DX,emm_handle
          MOV   [SI].dest_handle,DX

          ;AX = EMM Exchange Memory function

          MOV   AX,5701h
          INT   67h
          OR    AH,AH
          JNZ   emm_error_handler


          Writing Programs That Use Expanded Memory                      32





          xchg_struct                    STRUC
             region_length               DD ?
             src_mem_type                DB ?
             src_handle                  DW ?
             src_init_offset             DW ?
             src_init_seg_page           DW ?
             dest_mem_type               DB ?
             dest_handle                 DW ?
             dest_init_offset            DW ?
             dest_init_seg_page          DW ?
          xchg_struct                    ENDS

          xchg_packet                    xchg_struct

          CODE  ENDS
                END





































          Writing Programs That Use Expanded Memory                      33





          Chapter 3
          EMM FUNCTIONS


               This chapter provides you with a standardized set of
               expanded memory functions.  Because they are standardized,
               you avoid potential compatibility problems with other
               expanded memory programs that also adhere to the memory
               manager specification.  Programs that deal directly with the
               hardware or that don't adhere to this specification will be
               incompatible.

               Table 3-1 presents a sequential list of the EMM functions. 
               The remainder of this chapter provides detailed descriptions
               of each function.


          Table 3-1.  List of EMM Functions
          ----------------------------------------------------------------

          Number             Function Name                Hex Value   Page

          ----------------------------------------------------------------

             1    Get Status                                40h         37

             2    Get Page Frame Address                    41h         38

             3    Get Unallocated Page Count                42h         40

             4    Allocate Pages                            43h         42

             5    Map/Unmap Handle Page                     44h         46

             6    Deallocate Pages                          45h         49

             7    Get Version                               46h         51

             8    Save Page Map                             47h         53

             9    Restore Page Map                          48h         55

            10    Reserved                                  49h         57

            11    Reserved                                  4Ah         58

            12    Get Handle Count                          4Bh         59

            13    Get Handle Pages                          4Ch         61

            14    Get All Handle Pages                      4Dh         63


          EMM Functions                                                  34





          Table 3-1.  List of EMM Functions (continued)
          ----------------------------------------------------------------

          Number             Function Name                Hex Value   Page

          ----------------------------------------------------------------

            15    Get Page Map                              4E00h       65
                  Set Page Map                              4E01h       67
                  Get & Set Page Map                        4E02h       69
                  Get Size of Page Map Save Array           4E03h       71

            16    Get Partial Page Map                      4F00h       73
                  Set Partial Page Map                      4F01h       76
                  Get Size of Partial Page Map
                     Save Array                             4F02h       78

            17    Map/Unmap Multiple Handle Pages
                  (Physical page number mode)               5000h       82
                  Map/Unmap Multiple Handle Pages
                  (Segment address mode)                    5001h       85

            18    Reallocate Pages                          51h         88

            19    Get Handle Attribute                      5200h       92
                  Set Handle Attribute                      5201h       94
                  Get Handle Attribute Capability           5202h       96

            20    Get Handle Name                           5300h       98
                  Set Handle Name                           5301h      100

            21    Get Handle Directory                      5400h      102
                  Search for Named Handle                   5401h      105
                  Get Total Handles                         5402h      107

            22    Alter Page Map & Jump
                  (Physical page number mode)               5500h      109
                  Alter Page Map & Jump
                  (Segment address mode)                    5501h      109

            23    Alter Page Map & Call
                  (Physical page number mode)               5600h      113
                  Alter Page Map & Call
                  (Segment address mode)                    5601h      113
                  Get Page Map Stack Space Size             5602h      118

            24    Move Memory Region                        5700h      120
                  Exchange Memory Region                    5701h      126

            25    Get Mappable Physical Address Array       5800h      132
                  Get Mappable Physical Address Array
                     Entries                                5801h      136

          EMM Functions                                                  35





          Table 3-1.  List of EMM Functions (continued)
          ----------------------------------------------------------------

          Number             Function Name                Hex Value   Page

          ----------------------------------------------------------------

            26    Get Hardware Configuration Array          5900h      138
                  Get Unallocated Raw Page Count            5901h      142

            27    Allocate Standard Pages                   5A00h      144
                  Allocate Raw Pages                        5A01h      147

            28    Get Alternate Map Register Set            5B00h      153
                  Set Alternate Map Register Set            5B01h      157
                  Get Alternate Map Save Array Size         5B02h      161
                  Allocate Alternate Map Register Set       5B03h      163
                  Deallocate Alternate Map Register Set     5B04h      166
                  Allocate DMA Register Set                 5B05h      168
                  Enable DMA on Alternate Map
                     Register Set                           5B06h      170
                  Disable DMA on Alternate Map
                     Register Set                           5B07h      173
                  Deallocate DMA Register Set               5B08h      175

            29    Prepare Expanded Memory Hardware
                     for Warmboot                           5Ch        177

            30    Enable OS/E Function Set                  5D00h      179
                  Disable OS/E Function Set                 5D01h      182
                  Return OS/E Access Key                    5D02h      185

          ----------------------------------------------------------------




















          EMM Functions                                                  36





          Function 1. Get Status



          PURPOSE

               The Get Status function returns a status code indicating
               whether the memory manager is present and the hardware is
               working correctly.


          CALLING PARAMETERS

               AH = 40h
                   Contains the Get Status Function.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager is present in the system, and the hardware
                   is working correctly.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.


          EXAMPLE

          MOV   AH,40h                   ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error






          EMM Functions                                                  37





          Function 2. Get Page Frame Address



          PURPOSE

               The Get Page Frame Address function returns the segment
               address where the page frame is located.


          CALLING PARAMETERS

               AH = 41h
                   Contains the Get Page Frame Address function.


          RESULTS

               These results are valid only if the status returned is zero.

               BX = page frame segment address
                   Contains the segment address of the page frame.


          REGISTERS MODIFIED

               AX, BX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has returned the page frame address in the
                   BX register.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.







          EMM Functions                                                  38





          Function 2. Get Page Frame Address



          EXAMPLE

          page_frame_segment             DW ?

          MOV   AH,41h                   ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   page_frame_segment,BX    ; save page frame address








































          EMM Functions                                                  39





          Function 3. Get Unallocated Page Count



          PURPOSE

               The Get Unallocated Page Count function returns the number
               of unallocated pages and the total number of expanded memory
               pages.


          CALLING PARAMETERS

               AH = 42h
                   Contains the Get Unallocated Page Count function.


          RESULTS

               These results are valid only if the status returned is zero.

               BX = unallocated pages
                   The number of expanded memory pages that are currently
                   available for use (unallocated).

               DX = total pages
                   The total number of expanded memory pages.


          REGISTERS MODIFIED

               AX, BX, DX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has returned the number of unallocated pages
                   and the number of total pages in expanded memory.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.


          EMM Functions                                                  40





          Function 3. Get Unallocated Page Count



          EXAMPLE

          un_alloc_pages                 DW ?
          total_pages                    DW ?

          MOV   AH,42h                   ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   un_alloc_pages,BX        ; save unallocated page count
          MOV   total_pages,DX           ; save total page count






































          EMM Functions                                                  41





          Function 4. Allocate Pages



               The Allocate Pages function allocates the number of pages
               requested and assigns a unique EMM handle to these pages. 
               The EMM handle owns these pages until the application
               deallocates them.

               Handles which are assigned using this function will have
               16K-byte pages, the size of a standard expanded memory page. 
               If the expanded memory board hardware isn't able to supply
               16K-byte pages, it will emulate them by combining multiple
               non-standard size pages to form a single 16K-byte page.  All
               application programs and functions that use the handles this
               function returns will deal with 16K-byte pages.

               The numeric value of the handles the EMM returns are in the
               range of 1 to 254 decimal (0001h to 00FEh).  The OS handle
               (handle value 0) is never returned by the Allocate Pages
               function.  Also, the uppermost byte of the handle will be
               zero and cannot be used by the application.  A memory
               manager should be able to supply up to 255 handles, includ-
               ing the OS handle.  An application can use Function 21 to
               find out how many handles an EMM supports.

               Allocating zero pages to a handle is not valid.  If an
               application needs to allocate 0 pages to a handle it should
               use Function 27 (Allocate Standard Pages subfunction)
               provided for this purpose.

          Note............................................................
               This note affects expanded memory manager implementors and
               operating system developers only.  Applications should not
               use the following characteristics of the memory manager.  An
               application violating this rule will be incompatible with
               future versions of Microsoft's operating systems and
               environments.

               To be compatible with this specification, an expanded memory
               manager will provide a special handle which is available to
               the operating system only.  This handle will have a value of
               0000h and will have a set of pages allocated to it when the
               expanded memory manager driver installs.  The pages that the
               memory manager will automatically allocate to handle 0000h
               are those that backfill conventional memory.  Typically,
               this backfill occurs between addresses 40000h (256K) and
               9FFFFh (640K).  However, the range can extend below and
               above this limit if the hardware and memory manager have the
               capability.



          EMM Functions                                                  42





          Function 4. Allocate Pages



               An operating system won't have to invoke Function 4 to
               obtain this handle because it can assume the handle already
               exists and is available for use immediately after the
               expanded memory device driver installs.  When an operating
               system wants to use this handle, it uses the special handle
               value of 0000h.  The operating system will be able to invoke
               any EMM function using this special handle value.  To
               allocate pages to this handle, the operating system need
               only invoke Function 18 (Reallocate Pages).

               There are two special cases for this handle:

               1.  Function 4 (Allocate Pages).  This function must never
                   return zero as a handle value.  Applications must always
                   invoke Function 4 to allocate pages and obtain a handle
                   which identifies the pages which belong to it.  Since
                   Function 4 never returns a handle value of zero, an
                   application will never gain access to this special
                   handle.

               2.  Function 6 (Deallocate Pages).  If the operating system
                   uses it to deallocate the pages which are allocated to
                   this special handle, the pages the handle owns will be
                   returned to the manager for use.  But the handle will
                   not be available for reassignment.  The manager should
                   treat a deallocate pages function request for this
                   handle the same as a reallocate pages function request,
                   where the number of pages to reallocate to this handle
                   is zero.


          CALLING PARAMETERS

               AH = 43h
                   Contains the Allocate Pages function.

               BX = num_of_pages_to_alloc
                   Contains the number of pages you want your program to
                   allocate.










          EMM Functions                                                  43





          Function 4. Allocate Pages



          RESULTS

               These results are valid only if the status returned is zero.

               DX = handle
                   Contains a unique EMM handle.  Your program must use
                   this EMM handle (as a parameter) in any function that
                   requires it.  You can use up to 255 handles.  The
                   uppermost byte of the handle will be zero and cannot be
                   used by the application.


          REGISTERS MODIFIED

               AX, DX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has allocated the requested pages to the
                   assigned EMM handle.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 85h   RECOVERABLE.
                   All EMM handles are being used.

               AH = 87h   RECOVERABLE.
                   There aren't enough expanded memory pages present in the
                   system to satisfy your program's request.

               AH = 88h   RECOVERABLE.
                   There aren't enough unallocated pages to satisfy your
                   program's request.

               AH = 89h   RECOVERABLE.
                   Your program attempted to allocate zero pages.

          EMM Functions                                                  44





          Function 4. Allocate Pages



          EXAMPLE

          num_of_pages_to_alloc          DW ?
          emm_handle                     DW ?

          MOV   BX,num_of_pages_to_alloc ; load number of pages
          MOV   AH,43h                   ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   emm_handle,DX            ; save EMM handle






































          EMM Functions                                                  45





          Function 5. Map/Unmap Handle Pages



          PURPOSE

               The Map/Unmap Handle Page function maps a logical page at a
               specific physical page anywhere in the mappable regions of
               system memory.  The lowest valued physical page numbers are
               associated with regions of memory outside the conventional
               memory range.  Use Function 25 (Get Mappable Physical
               Address Array) to determine which physical pages within a
               system are mappable and determine the segment addresses
               which correspond to a specific physical page number. 
               Function 25 provides a cross reference between physical page
               numbers and segment addresses.

               This function can also unmap physical pages, making them
               inaccessible for reading or writing.  You unmap a physical
               page by setting its associated logical page to FFFFh.

               You might unmap an entire set of mapped pages, for example,
               before loading and executing a program.  Doing so ensures
               the loaded program, if it accesses expanded memory, won't
               access the pages your program has mapped.  However, you must
               save the mapped context before you unmap the physical pages. 
               This enables you to restore it later so you can access the
               memory you mapped there.  To save the mapping context, use
               Function 8, 15, or 16.  To restore the mapping context, use
               Function 9, 15, or 16.

               The handle determines what type of pages are being mapped. 
               Logical pages allocated by Function 4 and Function 27
               (Allocate Standard Pages subfunction) are referred to as
               pages and are 16K bytes long.  Logical pages allocated by
               Function 27 (Allocate Raw Pages subfunction) are referred to
               as raw pages and might not be the same size as logical
               pages.


          CALLING PARAMETERS

               AH = 44h
                   Contains the Map Handle Page function.

               AL = physical_page_number
                   Contains the number of the physical page into which the
                   logical page number is to be mapped.  Physical pages are
                   numbered zero-relative.




          EMM Functions                                                  46





          Function 5. Map/Unmap Handle Pages



               BX = logical_page_number
                   Contains the number of the logical page to be mapped at
                   the physical page within the page frame.  Logical pages
                   are numbered zero-relative.  The logical page must be in
                   the range zero through (number of pages allocated to the
                   EMM handle - 1).  However, if BX contains logical page
                   number FFFFh, the physical page specified in AL will be
                   unmapped (be made inaccessible for reading or writing).

               DX = emm_handle
                   Contains the EMM handle your program received from
                   Function 4 (Allocate Pages).


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has mapped the page.  The page is ready to
                   be accessed.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The memory manager couldn't find the EMM handle your
                   program specified.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager isn't
                   defined.

               AH = 8Ah   RECOVERABLE.
                   The logical page is out of the range of logical pages
                   which are allocated to the EMM handle.  This status is
                   also returned if a program attempts to map a logical
                   page when no logical pages are allocated to the handle.



          EMM Functions                                                  47





          Function 5. Map/Unmap Handle Pages



               AH = 8Bh   RECOVERABLE.
                   The physical page number is out of the range of allow-
                   able physical pages.  The program can recover by
                   attempting to map into memory at a physical page which
                   is within the range of allowable physical pages.


          EXAMPLE

          emm_handle                     DW ?
          logical_page_number            DW ?
          physical_page_number           DB ?

          MOV   DX,emm_handle            ; load EMM handle
          MOV   BX,logical_page_number   ; load logical page number
          MOV   AL,physical_page_number  ; load physical page number
          MOV   AH,44h                   ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error





























          EMM Functions                                                  48





          Function 6. Deallocate Pages



          PURPOSE

               Deallocate Pages deallocates the logical pages currently
               allocated to an EMM handle.  Only after the application
               deallocates these pages can other applications use them. 
               When a handle is deallocated, its name is set to all ASCII
               nulls (binary zeros).

          Note............................................................
               A program must perform this function before it exits to DOS. 
               If it doesn't, no other programs can use these pages or the
               EMM handle.  This means that a program using expanded memory
               should trap critical errors and control-break if there is a
               chance that the program will have allocated pages when
               either of these events occur.


          CALLING PARAMETERS

               AH = 45h
                   Contains the Deallocate Pages function.

               DX = handle
                   Contains the EMM handle returned by Function 4 (Allocate
                   Pages).


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has deallocated the pages previously allo-
                   cated to the EMM handle.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The manager couldn't find the specified EMM handle.

          EMM Functions                                                  49





          Function 6. Deallocate Pages



               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 86h   RECOVERABLE.
                   The memory manager detected a save or restore page
                   mapping context error (Function 8 or 9).  There is a
                   page mapping register state in the save area for the
                   specified EMM handle.  Save Page Map (Function 8) placed
                   it there and a subsequent Restore Page Map (Function 9)
                   has not removed it.

                   If you have saved the mapping context, you must restore
                   it before you deallocate the EMM handle's pages.


          EXAMPLE

          emm_handle                     DW ?

          MOV   DX,emm_handle            ; load EMM handle
          MOV   AH,45h                   ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
























          EMM Functions                                                  50





          Function 7. Get Version



          PURPOSE

               The Get Version function returns the version number of the
               memory manager software.


          CALLING PARAMETERS

               AH = 46h
                   Contains the Get Version function.


          RESULTS

               These results are valid only if the status returned is zero.

               AL = version number
                   Contains the memory manager's version number in binary
                   coded decimal (BCD) format.  The upper four bits contain
                   the integer digit of the version number.  The lower four
                   bits contain the fractional digit of version number. 
                   For example, version 4.0 is represented like this:

                                      0100 0000
                                        /   \
                                       4  .  0

                   When checking for a version number, an application
                   should check for a version number or greater.  Vendors
                   may use the fractional digit to indicate enhancements or
                   corrections to their memory managers.  Therefore, to
                   allow for future versions of memory managers, an
                   application shouldn't depend on an exact version number.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager is present in the system and the hardware is
                   working correctly.




          EMM Functions                                                  51





          Function 7. Get Version



               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.


          EXAMPLE

          emm_version                    DB ?

          MOV   AH,46h                   ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   emm_version,AL           ; save version number



























          EMM Functions                                                  52





          Function 8. Save Page Map



          PURPOSE

               Save Page Map saves the contents of the page mapping
               registers on all expanded memory boards in an internal save
               area.  The function is typically used to save the memory
               mapping context of the EMM handle that was active when a
               software or hardware interrupt occurred.  (See Function 9,
               Restore Page Map, for the restore operation.)

               If you're writing a resident program, an interrupt service
               routine, or a device driver that uses expanded memory, you
               must save the state of the mapping hardware.  You must save
               this state because application software using expanded
               memory may be running when your program is invoked by a
               hardware interrupt, a software interrupt, or DOS.

               The Save Page Map function requires the EMM handle that was
               assigned to your resident program, interrupt service
               routine, or device driver at the time it was initialized. 
               This is not the EMM handle that the application software was
               using when your software interrupted it.

               The Save Page Map function saves the state of the map
               registers for only the 64K-byte page frame defined in
               versions 3.x of this specification.  Since all applications
               written to LIM versions 3.x require saving the map register
               state of only this 64K-byte page frame, saving the entire
               mapping state for a large number of mappable pages would be
               inefficient use of memory.  Applications that use a mappable
               memory region outside the LIM 3.x page frame should use
               Function 15 or 16 to save and restore the state of the map
               registers.


          CALLING PARAMETERS

               AH = 47h
                   Contains the Save Page Map function.

               DX = handle
                   Contains the EMM handle assigned to the interrupt
                   service routine that's servicing the software or
                   hardware interrupt.  The interrupt service routine needs
                   to save the state of the page mapping hardware before
                   mapping any pages.




          EMM Functions                                                  53





          Function 8. Save Page Map



          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has saved the state of the page mapping
                   hardware.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The memory manager couldn't find the EMM handle your
                   program specified.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Ch   NON-RECOVERABLE.
                   There is no room in the save area to store the state of
                   the page mapping registers.  The state of the map
                   registers has not been saved.

               AH = 8Dh   CONDITIONALLY-RECOVERABLE.
                   The save area already contains the page mapping register
                   state for the EMM handle your program specified.


          EXAMPLE

          emm_handle                     DW ?

          MOV   DX,emm_handle            ; load EMM handle
          MOV   AH,47h                   ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error



          EMM Functions                                                  54





          Function 9. Restore Page Map



          PURPOSE

               The Restore Page Map function restores the page mapping
               register contents on the expanded memory boards for a
               particular EMM handle.  This function lets your program
               restore the contents of the mapping registers its EMM handle
               saved.  (See Function 8, Save Page Map for the save opera-
               tion.)

               If you're writing a resident program, an interrupt service
               routine, or a device driver that uses expanded memory, you
               must restore the mapping hardware to the state it was in
               before your program took over.  You must save this state
               because application software using expanded memory might
               have been running when your program was invoked.

               The Restore Page Map function requires the EMM handle that
               was assigned to your resident program, interrupt service
               routine, or device driver at the time it was initialized. 
               This is not the EMM handle that the application software was
               using when your software interrupted it.

               The Restore Page Map function restores the state of the map
               registers for only the 64K-byte page frame defined in
               versions 3.x of this specification.  Since all applications
               written to LIM versions 3.x require restoring the map
               register state of only this 64K-byte page frame, restoring
               the entire mapping state for a large number of mappable
               pages would be inefficient use of memory.  Applications that
               use a mappable memory region outside the LIM 3.x page frame
               should use Function 15 or 16 to save and restore the state
               of the map registers.


          CALLING PARAMETERS

               AH = 48h
                   Contains the Restore Page Map function.

               DX = emm_handle
                   Contains the EMM handle assigned to the interrupt
                   service routine that's servicing the software or
                   hardware interrupt.  The interrupt service routine needs
                   to restore the state of the page mapping hardware.





          EMM Functions                                                  55





          Function 9. Restore Page Map



          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has restored the state of the page mapping
                   registers.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The memory manager couldn't find the EMM handle your
                   program specified.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Eh   CONDITIONALLY-RECOVERABLE.
                   There is no page mapping register state in the save area
                   for the specified EMM handle.  Your program didn't save
                   the contents of the page mapping hardware, so Restore
                   Page can't restore it.


          EXAMPLE

          emm_handle                     DW ?

          MOV   DX,emm_handle            ; load EMM handle
          MOV   AH,48h                   ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error






          EMM Functions                                                  56





          Function 10. Reserved



               In earlier versions of the Lotus/Intel/Microsoft Expanded
               Memory Specification, Function 10 returned the page mapping
               register I/O array.  This function is now reserved and new
               programs should not use it.

               Existing programs that use this function may still work
               correctly if the hardware is capable of supporting them. 
               However, programs that use Functions 16 through 30 in
               Version 4.0 of this specification must not use Functions 10
               and 11.  These functions won't work correctly if your
               program attempts to mix the use of the new functions
               (Functions 16 through 30) and Functions 10 and 11.  Func-
               tions 10 and 11 are specific to the hardware on Intel
               expanded memory boards and will not work correctly on all
               vendors' expanded memory boards.


































          EMM Functions                                                  57





          Function 11. Reserved



               In earlier versions of the Lotus/Intel/Microsoft Expanded
               Memory Specification, Function 11 returned a page transla-
               tion array.  This function is now reserved and new programs
               should not use it.

               Existing programs that use this function may still work
               correctly if the hardware is capable of supporting them. 
               However, programs that use Functions 16 through 30 in
               Version 4.0 of this specification must not use Functions 10
               and 11.  These functions won't work correctly if your
               program attempts to mix the use of the new functions
               (Functions 16 through 30) and Functions 10 and 11.  Func-
               tions 10 and 11 are specific to the hardware on Intel
               expanded memory boards and will not work correctly on all
               vendors' expanded memory boards.


































          EMM Functions                                                  58





          Function 12. Get Handle Count



          PURPOSE

               The Get Handle Count function returns the number of open EMM
               handles (including the operating system handle 0) in the
               system.


          CALLING PARAMETERS

               AH = 4Bh
                   Contains the Get Handle Count function.


          RESULTS

               These results are valid only if the status returned is zero.

               BX = total_open_emm_handles
                   Contains the number of open EMM handles [including the
                   operating system handle (0)].  This number will not
                   exceed 255.


          REGISTERS MODIFIED

               AX, BX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has returned the number of active EMM
                   handles.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.




          EMM Functions                                                  59





          Function 12. Get Handle Count



          EXAMPLE

          total_open_emm_handles              DW ?

          MOV   AH,4Bh                        ; load function code
          INT   67h                           ; call the memory manger
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler on   
                                              ; error
          MOV   total_open_emm_handles,BX     ; save total active handle   
                                              ; count






































          EMM Functions                                                  60





          Function 13. Get Handle Pages



          PURPOSE

               The Get Handle Pages function returns the number of pages
               allocated to a specific EMM handle.


          CALLING PARAMETERS

               AH = 4Ch
                   Contains the Get Handle Pages function.

               DX = emm_handle
                   Contains the EMM handle.


          RESULTS

               These results are valid only if the status returned is zero.

               BX = num_pages_alloc_to_emm_handle
                   Contains the number of logical pages allocated to the
                   specified EMM handle.  This number never exceeds 2048
                   because the memory manager allows a maximum of 2048
                   pages (32M bytes) of expanded memory.


          REGISTERS MODIFIED

               AX, BX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has returned the number of pages allocated
                   to the EMM handle.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The memory manager couldn't find the EMM handle your
                   program specified.

          EMM Functions                                                  61





          Function 13. Get Handle Pages



               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.


          EXAMPLE

          emm_handle                          DW ?
          pages_alloc_to_handle               DW ?

          MOV   DX,emm_handle                 ; load EMM handle
          MOV   AH,4Ch                        ; load function code
          INT   67h                           ; call the memory manager
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler on   
                                              ; error
          MOV   pages_alloc_to_handle,BX      ; save number of pages       
                                              ; allocated to specified     
                                              ; handle






























          EMM Functions                                                  62





          Function 14. Get All Handle Pages



          PURPOSE

               The Get All Handle Pages function returns an array of the
               open EMM handles and the number of pages allocated to each
               one.


          CALLING PARAMETERS

               AH = 4Dh
                   Contains the Get All Handle Pages function.

                   handle_page_struct              STRUC
                       emm_handle                  DW ?
                       pages_alloc_to_handle       DW ?
                   handle_page_struct              ENDS

               ES:DI = pointer to handle_page
                   Contains a pointer to an array of structures where a
                   copy of all open EMM handles and the number of pages
                   allocated to each will be stored.  Each structure has
                   these two members:

                   .emm_handle
                       The first member is a word which contains the value
                       of the open EMM handle.  The values of the handles
                       this function returns will be in the range of 0 to
                       255 decimal (0000h to 00FFh).  The uppermost byte of
                       the handle is always zero.

                   .pages_alloc_to_handle
                       The second member is a word which contains the
                       number of pages allocated to the open EMM handle.


          RESULTS

               These results are valid only if the status returned is zero.

               BX = total_open_emm_handles
                   Contains the number of open EMM handles (including the
                   operating system handle [0]).  The number cannot be zero
                   because the operating system handle is always active and
                   cannot be deallocated.  This number will not exceed 255.





          EMM Functions                                                  63





          Function 14. Get All Handle Pages



          REGISTERS MODIFIED

               AX, BX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has returned the array.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.


          EXAMPLE

          handle_page                    handle_page_struct 255 DUP (?)
          total_open_handles             DW ?

          MOV   AX,SEG handle_page
          MOV   ES,AX
          LEA   DI,handle_page           ; ES:DI points to handle_page
          MOV   AH,4Dh                   ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check the EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   total_open_handles,BX    ; save total open handle count













          EMM Functions                                                  64





          Function 15. Get/Set Page Map
          Get Page Map subfunction



          PURPOSE

               The Get Page Map subfunction saves the mapping context for
               all mappable memory regions (conventional and expanded) by
               copying the contents of the mapping registers from each
               expanded memory board to a destination array.  The applica-
               tion must pass a pointer to the destination array.  This
               subfunction doesn't require an EMM handle.

               Use this function instead of Functions 8 and 9 if you need
               to save or restore the mapping context but don't want (or
               have) to use a handle.


          CALLING PARAMETERS

               AX = 4E00h
                   Contains the Get Page Map subfunction.

               ES:DI = dest_page_map
                   Contains a pointer to the destination array address in
                   segment:offset format.  Use the Get Size of Page Map
                   Save Array subfunction to determine the size of the
                   desired array.


          RESULTS

               These results are valid only if the status returned is zero.

               dest_page_map
                   The array contains the state of all the mapping regis-
                   ters on all boards in the system.  It also contains any
                   additional information necessary to restore the boards
                   to their original state when the program invokes a Set
                   subfunction.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has returned the array.

          EMM Functions                                                  65





          Function 15. Get/Set Page Map
          Get Page Map subfunction



               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.


          EXAMPLE

          dest_page_map                  DB ? DUP (?)

          MOV   AX,SEG dest_page_map
          MOV   ES,AX
          LEA   DI,dest_page_map         ; ES:DI points to dest_page_map
          MOV   AX,4E00h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error





















          EMM Functions                                                  66





          Function 15. Get/Set Page Map
          Set Page Map subfunction



          PURPOSE

               The Set Page Map subfunction restores the mapping context
               for all mappable memory regions (conventional and expanded)
               by copying the contents of a source array into the mapping
               registers on each expanded memory board in the system.  The
               application must pass a pointer to the source array.  This
               subfunction doesn't require an EMM handle.

               Use this function instead of Functions 8 and 9 if you need
               to save or restore the mapping context but don't want (or
               have) to use a handle.


          CALLING PARAMETERS

               AX = 4E01h
                   Contains the Set Page Map subfunction.

               DS:SI = source_page_map
                   Contains a pointer to the source array address in
                   segment:offset format.  The application must point to an
                   array which contains the mapping register state.  Use
                   the Get Size of Page Map Save Array subfunction to
                   determine the size of the desired array.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has passed the array.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.




          EMM Functions                                                  67





          Function 15. Get/Set Page Map
          Set Page Map subfunction



               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = A3h   NON-RECOVERABLE.
                   The contents of the source array have been corrupted, or
                   the pointer passed to the subfunction is invalid.


          EXAMPLE

          source_page_map                DB ? DUP (?)

          MOV   AX,SEG source_page_map
          MOV   DS,AX
          LEA   SI,source_page_map       ; DS:SI points to source_page_map
          MOV   AX,4E01h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error

























          EMM Functions                                                  68





          Function 15. Get/Set Page Map
          Get & Set Page Map subfunction



          PURPOSE

               The Get & Set Page Map subfunction simultaneously saves a
               current mapping context and restores a previous mapping
               context for all mappable memory regions (both conventional
               and expanded).  It first copies the contents of the mapping
               registers from each expanded memory board in the system into
               a destination array.  (The application must pass a pointer
               to the destination array.)  Then, the subfunction copies the
               contents of a source array into the mapping registers on
               each of the expanded memory boards.  (The application must
               pass a pointer to the source array.)

               Use this function instead of Functions 8 and 9 if you need
               to save or restore the mapping context but don't want (or
               have) to use a handle.


          CALLING PARAMETERS

               AX = 4E02h
                   Contains the Get & Set Page Map subfunction.

               ES:DI = dest_page_map
                   Contains a pointer to the destination array address in
                   segment:offset format.  The current contents of the map
                   registers will be saved in this array.

               DS:SI = source_page_map
                   Contains a pointer to the source array address in
                   segment:offset format.  The contents of this array will
                   be copied into the map registers.  The application must
                   point to an array which contains the mapping register
                   state.  This address is required only for the Set or Get
                   and Set subfunctions.


          RESULTS

               These results are valid only if the status returned is zero.

               dest_page_map
                   The array contains the mapping state.  It also contains
                   any additional information necessary to restore the
                   original state when the program invokes a Set subfunc-
                   tion.


          EMM Functions                                                  69





          Function 15. Get/Set Page Map
          Get & Set Page Map subfunction



          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has returned and passed both arrays.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = A3h   NON-RECOVERABLE.
                   The contents of the source array have been corrupted, or
                   the pointer passed to the subfunction is invalid.


          EXAMPLE

          dest_page_map                  DB ? DUP (?)

          source_page_map                DB ? DUP (?)

          MOV   AX,SEG dest_page_map
          MOV   ES,AX
          MOV   AX,SEG source_page_map
          MOV   DS,AX
          LEA   DI,dest_page_map         ; ES:DI points to dest_page_map
          LEA   SI,source_page_map       ; DS:SI points to source_page_map
          MOV   AX,4E02h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error


          EMM Functions                                                  70





          Function 15. Get/Set Page Map
          Get Size of Page Map Save Array subfunction



          PURPOSE

               The Get Size of Page Map Save Array subfunction returns the
               storage requirements for the array passed by the other three
               subfunctions.  This subfunction doesn't require an EMM
               handle.


          CALLING PARAMETERS

               AX = 4E03h
                   Contains the Get Size of Page Map Save Array subfunc-
                   tion.  The size of this array depends on how the
                   expanded memory system is configured and how the
                   expanded memory manager is implemented.  Therefore, the
                   size must be determined after the memory manager is
                   loaded.


          RESULTS

               These results are valid only if the status returned is zero.

               AL = size_of_array
                   Contains the number of bytes that will be transferred to
                   the memory area an application supplies whenever a
                   program requests the Get, Set, or Get and Set subfunc-
                   tions.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has returned the array size.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

          EMM Functions                                                  71





          Function 15. Get/Set Page Map
          Get Size of Page Map Save Array subfunction



               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.


          EXAMPLE

          size_of_array                  DB ?

          MOV   AX,4E03h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   size_of_array,AL         ; save array size































          EMM Functions                                                  72





          Function 16. Get/Set Partial Page Map
          Get Partial Page Map subfunction



          PURPOSE

               The Get Partial Page Map subfunction saves a partial mapping
               context for specific mappable memory regions in a system. 
               Because this function saves only a subset of the entire
               mapping context, it uses much less memory for the save area
               and may be potentially faster than Function 15.  The
               subfunction does this by copying the contents of selected
               mapping registers from each expanded memory board to a
               destination array.

               The application must pass a pair of pointers.  The first
               points to a structure which specifies which mappable
               segments to save; the second points to the destination
               array.

               Use this function instead of Functions 8 and 9 if you need
               to save or restore the mapping context but don't want (or
               have) to use a handle.


          CALLING PARAMETERS

               AX = 4F00h
                   Contains the Get Partial Page Map subfunction.

                   partial_page_map_struct         STRUC
                       mappable_segment_count      DW ?
                       mappable_segment            DW (?) DUP (?)
                   partial_page_map_struct         ENDS

               DS:SI = partial_page_map
                   Contains a pointer to a structure which specifies only
                   those mappable memory regions which are to have their
                   mapping context saved.  The structure members are
                   described below.

                   .mappable_segment_count
                       The first member is a word which specifies the
                       number of members in the word array which immediate-
                       ly follows it.  This number should not exceed the
                       number of mappable segments in the system.






          EMM Functions                                                  73





          Function 16. Get/Set Partial Page Map
          Get Partial Page Map subfunction



                   .mappable_segment
                       The second member is a word array which contains the
                       segment addresses of the mappable memory regions
                       whose mapping contexts are to be saved.  The segment
                       address must be a mappable segment.  Use Function 25
                       to determine which segments are mappable.

               ES:DI = dest_array
                   Contains a pointer to the destination array address in
                   segment:offset format.  To determine the size of the
                   required array, see the Get Size of Partial Page Map
                   Save Array subfunction.


          RESULTS

               These results are valid only if the status returned is zero.

               dest_array
                   The array contains the partial mapping context and any
                   additional information necessary to restore this context
                   to its original state when the program invokes a Set
                   subfunction.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has saved the partial map context.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.


          EMM Functions                                                  74





          Function 16. Get/Set Partial Page Map
          Get Partial Page Map subfunction



               AH = 8Bh   NON-RECOVERABLE.
                   One of the specified segments is not a mappable segment.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = A3h   NON-RECOVERABLE.
                   The contents of the partial page map structure have been
                   corrupted, the pointer passed to the subfunction is
                   invalid, or the mappable_segment_count exceeds the
                   number of mappable segments in the system.


          EXAMPLE

          partial_page_map               partial_page_map_struct <>

          dest_array                     DB ? DUP (?)

          MOV   AX,SEG partial_page_map
          MOV   DS,AX
          LEA   SI,partial_page_map      ; DS:SI points to partial_page_map
          MOV   AX,SEG dest_array
          MOV   ES,AX
          LEA   DI,dest_array            ; ES:DI points to dest_array
          MOV   AX,4F00h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error



















          EMM Functions                                                  75





          Function 16. Get/Set Partial Page Map
          Set Partial Page Map subfunction



          PURPOSE

               The Set Partial Page Map subfunction provides a mechanism
               for restoring the mapping context for a partial mapping
               context for specific mappable memory regions in a system. 
               Because this function restores only a subset of the entire
               mapping context and not the entire systems mapping context,
               it uses much less memory for the save area and is potential-
               ly faster than Function 15.  The subfunction does this by
               copying the contents of the source array to selected mapping
               registers on each expanded memory board.  The application
               passes a pointer to the source array.

               Use this function instead of Functions 8 and 9 if you need
               to save or restore the mapping context but don't want (or
               have) to use a handle.


          CALLING PARAMETERS

               AX = 4F01h
                   Contains the Set Partial Page Map subfunction

                           source_array     DB ? DUP (?)

               DS:SI = source_array
                   Contains a pointer to the source array in segment:offset
                   format.  The application must point to an array which
                   contains the partial mapping register state.  To deter-
                   mine the size of the required array, see the Get Size of
                   Partial Page Map Save Array subfunction.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has restored the partial mapping context.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.


          EMM Functions                                                  76





          Function 16. Get/Set Partial Page Map
          Set Partial Page Map subfunction



               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = A3h   NON-RECOVERABLE.
                   The contents of the source array have been corrupted, or
                   the pointer passed to the subfunction is invalid.


          EXAMPLE

          MOV   AX,SEG source_array
          MOV   DS,AX
          LEA   SI,source_array          ; DS:SI points to source_array
          MOV   AX,4F01h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error























          EMM Functions                                                  77





          Function 16. Get/Set Partial Page Map
          Get Size of Partial Page Map Save Array subfunction



          PURPOSE

               The Get Size of Partial Page Map Save Array subfunction
               returns the storage requirements for the array passed by the
               other two subfunctions.  This subfunction doesn't require an
               EMM handle.


          CALLING PARAMETERS

               AX = 4F02h
                   Contains the Get Size of Partial Page Map Save Array
                   subfunction.  The size of this array depends on the
                   expanded memory system configuration and the implementa-
                   tion of the expanded memory manager.  Therefore, it will
                   vary between hardware configurations and implementations
                   and must be determined after a specific memory manager
                   is loaded.

               BX = number of pages in the partial array
                   Contains the number of pages in the partial map to be
                   saved by the Get/Set Partial Page Map subfunctions. 
                   This number should be the same as the mappable_seg-
                   ment_count in the Get Partial Page Map subfunction.


          RESULTS

               These results are valid only if the status returned is zero.

               AL = size_of_partial_save_array
                   Contains the number of bytes that will be transferred to
                   the memory areas supplied by an application whenever a
                   program requests the Get or Set subfunction.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has returned the array size.



          EMM Functions                                                  78





          Function 16. Get/Set Partial Page Map
          Get Size of Partial Page Map Save Array subfunction



               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Bh   NON-RECOVERABLE.
                   The number of pages in the partial array is outside the
                   range of physical pages in the system.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.


          EXAMPLE

          number_of_pages_to_map              DW ?
          size_of_partial_save_array          DB ?

          MOV   BX,number_of_pages_to_map
          MOV   AX,4F02h                      ; load function code
          INT   67h                           ; call the memory manager
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler on   
                                              ; error
          MOV   size_of_partial_save_array,AL ; save array size
















          EMM Functions                                                  79





          Function 17. Map/Unmap Multiple Handle Pages



          PURPOSE

               This function can, in a single invocation, map (or unmap)
               logical pages into as many physical pages as the system
               supports.  Consequently, it has less execution overhead than
               mapping pages one at a time.  For applications which do a
               lot of page mapping, this is the preferred mapping method.


          Mapping Multiple Pages

               The handle passed to this function determines what type of
               logical pages are being mapped.  Logical pages that Function
               4 and Function 27 (Allocate Standard Pages subfunction)
               allocate are referred to as pages and are 16K bytes. 
               Logical pages that Function 27 (Allocate Raw Pages subfunc-
               tion) allocates are referred to as raw pages and might not
               be the same size as the pages Function 4 and Function 27
               (Allocate Standard Pages subfunction) allocate.


          Unmapping Multiple Pages

               This function can make specific physical pages unavailable
               for reading or writing.  A logical page which is unmapped
               from a specific physical page cannot be read or written from
               that physical page.  The logical page which is unavailable
               (unmapped) can be made available again by mapping it, or a
               new logical page, at the physical page that was unmapped. 
               Unmapping a physical page is accomplished by setting the
               logical page it is associated with to FFFFh.

               You might unmap an entire set of mapped pages, for example,
               before loading and executing a program.  This ensures that
               the loaded program won't be able to access the pages your
               program has mapped.  However, you must save the mapping
               context before you unmap the physical pages.  This enables
               you to restore it later so that you may access the memory
               you had mapped there.  You can save the mapping context with
               Functions 8, 15, or 16.  You can restore the mapping context
               with Functions 9, 15, or 16.


          Mapping and Unmapping Multiple Pages Simultaneously

               Both mapping and unmapping pages can be done in the same
               invocation.


          EMM Functions                                                  80





          Function 17. Map/Unmap Multiple Handle Pages



               Mapping or unmapping no pages is not considered an error. 
               If a request to map or unmap zero pages is made, nothing is
               done and no error is returned.


          Alternate Mapping and Unmapping Methods

               You can map or unmap pages using two methods.  Both methods
               produce identical results.

               1.  The first method specifies both a logical page and a
                   physical page at which the logical page is to be mapped. 
                   This method is an extension of Function 5 (Map Handle
                   Page).

               2.  The second method specifies both a logical page and a
                   corresponding segment address at which the logical page
                   is to be mapped.  While this is functionally the same as
                   the first method, it may be easier to use the actual
                   segment address of a physical page than to use a number
                   which only represents its location.  The memory manager
                   verifies whether the specified segment address falls on
                   the boundary of a mappable physical page.  The manager
                   then translates the segment address passed to it into
                   the necessary internal representation to map the pages.
























          EMM Functions                                                  81





          Function 17. Map/Unmap Multiple Handle Pages
          Logical Page/Physical Page Method


          CALLING PARAMETERS

               AX = 5000h
                   Contains the Map/Unmap Multiple Handle Pages subfunction
                   using the logical page/physical page method.

                   log_to_phys_map_struct          STRUC
                       log_page_number             DW ?
                       phys_page_number            DW ?
                   log_to_phys_map_struct          ENDS

               DX = handle
                   Contains the EMM handle.

               CX = log_to_phys_map_len
                   Contains the number of entries in the array.  For
                   example, if the array contained four pages to map or
                   unmap, then CX would contain 4.  The number in CX should
                   not exceed the number of mappable pages in the system.

               DS:SI = pointer to log_to_phys_map array
                   Contains a pointer to an array of structures that
                   contains the information necessary to map the desired
                   pages.  The array is made up of the following two
                   elements:

                   .log_page_number
                       The first member is a word which contains the number
                       of the logical page which is to be mapped.  Logical
                       pages are numbered zero-relative, so the number for
                       a logical page can only range from zero to (maximum
                       number of logical pages allocated to the handle -
                       1).

                       If the logical page number is set to FFFFh, the
                       physical page associated with it is unmapped rather
                       than mapped.  Unmapping a physical page makes it
                       inaccessible for reading or writing.

                   .phys_page_number
                       The second member is a word which contains the
                       number of the physical page at which the logical
                       page is to be mapped.  Physical pages are numbered
                       zero-relative, so the number for a physical page can
                       only range from zero to (maximum number of physical
                       pages supported in the system - 1).



          EMM Functions                                                  82





          Function 17. Map/Unmap Multiple Handle Pages
          Logical Page/Physical Page Method



          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The logical pages have been mapped, or unmapped, at the
                   specified physical pages.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The manager couldn't find the specified EMM handle.  The
                   manager doesn't currently have any information pertain-
                   ing to the specified EMM handle.  The program has
                   probably corrupted its EMM handle.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Ah   RECOVERABLE.
                   One or more of the mapped logical pages is out of the
                   range of logical pages allocated to the EMM handle.  The
                   program can recover by attempting to map a logical page
                   which is within the bounds for the specified EMM handle. 
                   When this error occurs, the only pages mapped were the
                   ones valid up to the point that the error occurred.

               AH = 8Bh   RECOVERABLE.
                   One or more of the physical pages is out of the range of
                   mappable physical pages, or the log_to_phys_map_len
                   exceeds the number of mappable pages in the system.  The
                   program can recover from this condition by attempting to
                   map into memory at the physical page which is in the
                   range of the physical page numbers supported by the
                   system.  When this error occurs, the only pages mapped
                   were the ones valid up to the point that the error
                   occurred.


          EMM Functions                                                  83





          Function 17. Map/Unmap Multiple Handle Pages
          Logical Page/Physical Page Method



               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.


          EXAMPLE

          log_to_phys_map            log_to_phys_map_struct ? DUP (?)

          emm_handle                 DW ?

          MOV   AX,SEG log_to_phys_map
          MOV   DS,AX
          LEA   SI,log_to_phys_map            ; DS:SI points to
                                              ; log_to_phys_map
          MOV   CX,LENGTH log_to_phys_map     ; set length field
          MOV   DX,emm_handle
          MOV   AX,5000h                      ; load function code
          INT   67h                           ; call the memory manager
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler on   
                                              ; error



























          EMM Functions                                                  84





          Function 17. Map/Unmap Multiple Handle Pages
          Logical Page/Segment Address Method


          CALLING PARAMETERS

               AX = 5001h
                   Contains the Map/Unmap Multiple Handle Pages subfunction
                   using the logical page/segment address method.

                   log_to_seg_map_struct           STRUC
                       log_page_number             DW ?
                       mappable_segment_address    DW ?
                   log_to_seg_map_struct           ENDS

               DX = handle
                   Contains the EMM handle.

               CX = log_to_segment_map_len
                   Contains the number of entries in the array.  For
                   example, if the array contained four pages to map or
                   unmap, then CX would contain four.

               DS:SI = pointer to log_to_segment_map array
                   Contains a pointer to an array of structures that
                   contains the information necessary to map the desired
                   pages.  The array is made up of the following elements:

                   .log_page_number
                       The first member is a word which contains the number
                       of the logical pages to be mapped.  Logical pages
                       are numbered zero-relative, so the number for a
                       logical page can range from zero to (maximum number
                       of logical pages allocated to the handle - 1).

                       If the logical page number is set to FFFFh, the
                       physical page associated with it is unmapped rather
                       than mapped.  Unmapping a physical page makes it
                       inaccessible for reading or writing.

                   .mappable_segment_address
                       The second member is a word which contains the
                       segment address at which the logical page is to be
                       mapped.  This segment address must correspond
                       exactly to a mappable segment address.  The mappable
                       segment addresses are available with Function 25
                       (Get Mappable Physical Address Array).


          REGISTERS MODIFIED

               AX

          EMM Functions                                                  85





          Function 17. Map/Unmap Multiple Handle Pages
          Logical Page/Segment Address Method



          STATUS

               AH = 0   SUCCESSFUL.
                   The logical pages have been mapped (or unmapped) at the
                   specified physical pages.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The manager could not find the specified EMM handle. 
                   The manager doesn't currently have any information
                   pertaining to the specified EMM handle.  The program has
                   probably corrupted its EMM handle.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Ah   RECOVERABLE.
                   One or more of the logical pages to be mapped is out of
                   the range of logical pages allocated to the EMM handle. 
                   The program can recover from this condition by mapping a
                   logical page which is within the bounds for the speci-
                   fied EMM handle.  When this error occurs, the only pages
                   mapped or unmapped were the ones valid up to the point
                   that the error occurred.

               AH = 8Bh   RECOVERABLE.
                   One or more of the mappable segment addresses specified
                   is not mappable, the segment address doesn't fall
                   exactly on a mappable address boundary, or the log_to_-
                   segment_map_len exceeds the number of mappable segments
                   in the system.  The program can recover from this
                   condition by mapping into memory on an exact mappable
                   segment address.  When this error occurs, the only pages
                   mapped were the ones valid up to the point that the
                   error occurred.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.



          EMM Functions                                                  86





          Function 17. Map/Unmap Multiple Handle Pages
          Logical Page/Segment Address Method



          EXAMPLE

          log_to_seg_map             log_to_seg_map_struct 4 DUP (?)

          emm_handle                 DW ?

          MOV   AX,SEG log_to_seg_map
          MOV   DS,AX
          LEA   SI,log_to_seg_map             ; DS:SI points to
                                              ; log_to_seg_map
          MOV   CX,LENGTH log_to_seg_map
          MOV   DX,emm_handle
          MOV   AX,5001h                      ; load function code
          INT   67h                           ; call the memory manager
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler on   
                                              ; error































          EMM Functions                                                  87





          Function 18. Reallocate Pages



          PURPOSE

               This function allows an application program to increase or
               decrease (reallocate) the number of logical pages allocated
               to an EMM handle.  There are four reallocation cases of
               interest:

               1.  A reallocation count of zero.  The handle assigned to
                   the application remains assigned and is still available
                   for use by the application.  The memory manager won't
                   reassign the handle to any other application.  However,
                   the handle will have any currently allocated pages
                   returned to the memory manager.  The application must
                   invoke the Deallocate Pages function (Function 6) before
                   returning to DOS, or the handle will remain assigned and
                   no other application will be able to use it.

               2.  A reallocation count equal to the current allocation
                   count.  This is not treated as an error, and a success-
                   ful status is returned.

               3.  A reallocation count greater than the current allocation
                   count.  The memory manager will attempt to add new pages
                   to those pages already allocated to the specified EMM
                   handle.  The number of new pages added is the difference
                   between the reallocation count and the current alloca-
                   tion count.  The sequence of logical pages allocated to
                   the EMM handle remains continuous after this operation. 
                   The newly allocated pages have logical page numbers
                   which begin where the previously allocated pages ended,
                   and continue in ascending sequence.

               4.  A reallocation count less than the current allocation
                   count.  The memory manager will attempt to subtract some
                   of the currently allocated pages and return them to the
                   memory manager.  The number of old pages subtracted is
                   the difference between the current allocation count and
                   the re-allocation count.  The pages are subtracted from
                   the end of the sequence of pages currently allocated to
                   the specified EMM handle.  The sequence of logical pages
                   allocated to the EMM handle remains continuous after
                   this operation.







          EMM Functions                                                  88





          Function 18. Reallocate Pages



          The handle determines what type of logical pages are being
          reallocated.  Logical pages which were originally allocated with
          Function 4 or Function 27 (Allocate Standard Pages subfunction)
          are called pages and are 16K bytes long.  Logical pages which
          were allocated with Function 27 (Allocate Raw Pages subfunction)
          are called raw pages and might not be the same size as pages
          allocated with Function 4.


          CALLING PARAMETERS

               AH = 51h
                   Contains the Reallocate Handle Pages function.

               DX = handle
                   Contains the EMM handle.

               BX = reallocation_count
                   Contains the total number of pages this handle should
                   have allocated to it after this function is invoked.


          RESULTS

               BX = number of pages allocated to handle after reallocation
                   Contains the number of pages now allocated to the EMM
                   handle after the pages have been added or subtracted. 
                   If the status returned is not zero, the value in BX is
                   equal to the number of pages allocated to the handle
                   prior to the invocation of this function.  This informa-
                   tion can be used to verify that the request generated
                   the expected results.


          REGISTERS MODIFIED

               AX, BX


          STATUS

               AH = 0   SUCCESSFUL.
                   The pages specified have been added to or subtracted
                   from the handle specified.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

          EMM Functions                                                  89





          Function 18. Reallocate Pages



               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The manager could not find the specified EMM handle. 
                   The manager doesn't have any information pertaining to
                   the specified EMM handle.  The program may have cor-
                   rupted its EMM handle.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 87h   RECOVERABLE.
                   The number of pages that are available in the system is
                   insufficient for the new allocation request.  The
                   program can recover from this condition by specifying
                   fewer pages be allocated to the EMM handle.

               AH = 88h   RECOVERABLE.
                   The number of unallocated pages is insufficient for the
                   new allocation request.  The program can recover from
                   this condition by either requesting again when addition-
                   al pages are available or specifying fewer pages.


          EXAMPLE

          emm_handle                          DW ?
          realloc_count                       DW ?
          current_alloc_page_count            DW ?

          MOV   DX,emm_handle                 ; specify EMM handle
          MOV   BX,realloc_count              ; specify count
          MOV   AH,51h                        ; load function code
          INT   67h                           ; call the memory manager
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler on   
                                              ; error
          MOV   current_alloc_page_count,BX









          EMM Functions                                                  90





          Function 19. Get/Set Handle Attribute



          Design Considerations

               This function is an option which will probably not be
               available in a typical expanded memory manager, system, or
               memory board.  Most personal computer systems disable memory
               refresh signals for a considerable period during a warm
               boot.  This can corrupt some of the data in memory boards,
               even though there is no problem with the design of the
               memory board, its operation, or the memory chips.  This
               memory refresh deficiency is present in the software design
               of the ROM BIOS in most personal computer systems.

               The majority of memory board designs, chip types, or
               personal computer systems won't be able to support the non-
               volatility feature.  The reason that this ROM BIOS deficien-
               cy is not evident in the conventional or extended memory
               area is that the ROM BIOS always initializes this area
               during a warm boot.  Memory data integrity is not a problem
               with the conventional or extended memory region, because it
               isn't physically possible to have data retained there across
               a warm boot event -- the ROM BIOS sets it to zero.

               Consequently, expanded memory board manufacturers should not
               supply this function unless their board can guarantee the
               integrity of data stored in the board's memory during a warm
               boot.  Generally, this means the memory board has an
               independent memory refresh controller which does not depend
               on the system board's memory refresh.

               If the expanded memory manager, system, or memory board
               cannot support this feature, it should return the not
               supported status described in the function.

















          EMM Functions                                                  91





          Function 19. Get/Set Handle Attribute
          Get Handle Attribute subfunction



          PURPOSE

               This subfunction returns the attribute associated with a
               handle.  The attributes are volatile or non-volatile. 
               Handles with non-volatile attributes enable the memory
               manager to save the contents of a handle's pages between
               warm boots.  However, this function may be disabled with a
               user option or may not be supported by the memory board or
               system hardware.

               If the handle's attribute has been set to non-volatile, the
               handle, its name (if it is assigned one), and the contents
               of the pages allocated to the handle are all maintained
               after a warm boot.


          CALLING PARAMETERS

               AX = 5200h
                   Contains the Get Handle Attribute subfunction.

               DX = handle
                   Contains the EMM handle.


          RESULTS

               These results are valid only if the status returned is zero.

               AL = handle attribute
                   Contains the EMM handle's attribute.  The only at-
                   tributes a handle may have are volatile or non-volatile. 
                   A value of zero indicates the handle is volatile.  A
                   value of one indicates that the handle is non-volatile.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The handle's attribute has been obtained.



          EMM Functions                                                  92





          Function 19. Get/Set Handle Attribute
          Get Handle Attribute subfunction



               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The manager couldn't find the specified EMM handle.  The
                   manager doesn't have any information pertaining to the
                   specified EMM handle.  The program may have corrupted
                   its EMM handle.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = 91h   NON-RECOVERABLE.
                   This feature is not supported.


          EXAMPLE

          emm_handle                     DW ?
          handle_attrib                  DB ?

          MOV   DX,emm_handle            ; specify EMM handle
          MOV   AX,5200h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   handle_attrib,AL         ; save handle attribute













          EMM Functions                                                  93





          Function 19. Get/Set Handle Attribute
          Set Handle Attribute subfunction



          PURPOSE

               This subfunction can be used to modify the attribute which a
               handle has associated with it.  The attributes which a
               handle may have are volatile or non-volatile.  The non-
               volatile attribute enables the EMM to save the contents of a
               handle's pages between warm boots.  However, this function
               may be disabled with a user option or may not be supported
               by the memory board or system hardware.

               If the handle's attribute has been set to non-volatile, the
               handle, its name (if it is assigned one), and the contents
               of the pages allocated to the handle are all maintained
               after a warm boot.


          CALLING PARAMETERS

               AX = 5201h
                   Contains the Set Handle Attribute function.

               DX = handle
                   Contains the EMM handle.

               BL = new handle attribute
                   Contains the handle's new attribute.  A value of zero
                   indicates that the handle should be made volatile.  A
                   value of one indicates that the handle should be made
                   non-volatile.

                   A volatile handle attribute instructs the memory manager
                   to deallocate both the handle and the pages allocated to
                   it after a warm boot.  If all handles have the volatile
                   attribute (the default attribute) at warm boot, the
                   handle directory will be empty and all of expanded
                   memory will be initialized to zero immediately after a
                   warm boot.


          REGISTERS MODIFIED

               AX






          EMM Functions                                                  94





          Function 19. Get/Set Handle Attribute
          Set Handle Attribute subfunction



          STATUS

               AH = 0   SUCCESSFUL.
                   The handle's attribute has been modified.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The manager could not find the specified EMM handle. 
                   The manager doesn't have any information pertaining to
                   the specified EMM handle.  The program may have cor-
                   rupted its EMM handle.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = 90h   NON-RECOVERABLE.
                   The attribute type is undefined.

               AH = 91h   NON-RECOVERABLE.
                   This feature is not supported.


          EXAMPLE

          emm_handle                     DW ?
          new_handle_attrib              DB ?

          MOV   DX,emm_handle            ; specify EMM handle
          MOV   BL,new_handle_attrib     ; specify the set attribute
          MOV   AX,5201h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error





          EMM Functions                                                  95





          Function 19. Get/Set Handle Attribute
          Get Attribute Capability subfunction



          PURPOSE

               This subfunction can be used to determine whether the memory
               manager can support the non-volatile attribute.


          CALLING PARAMETERS

               AX = 5202h
                   Contains the Get Attribute Capability subfunction.


          RESULTS

               These results are valid only if the status returned is zero.

               AL = attribute capability
                   Contains the attribute capability.  A value of zero
                   indicates that the memory manager and hardware supports
                   only volatile handles.  A value of one indicates that
                   the memory manager/hardware supports both volatile and
                   non-volatile handles.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The attribute capability has been returned.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

          EMM Functions                                                  96





          Function 19. Get/Set Handle Attribute
          Get Attribute Capability subfunction



          EXAMPLE

          attrib_capability              DB ?

          MOV   AX,5202h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   attrib_capability,AL     ; save attribute capability







































          EMM Functions                                                  97





          Function 20. Get/Set Handle Name
          Get Handle Name subfunction



          PURPOSE

               This subfunction gets the eight character name currently
               assigned to a handle.  There is no restriction on the
               characters which may be used in the handle name (that is,
               anything from 00h through FFh).

               The handle name is initialized to ASCII nulls (binary zeros)
               three times:  when the memory manager is installed, when a
               handle is allocated, and when a handle is deallocated.  A
               handle with a name which is all ASCII nulls, by definition,
               has no name.  When a handle is assigned a name, at least one
               character in the name must be a non-null character in order
               to distinguish it from a handle without a name.


          CALLING PARAMETERS

               AX = 5300h
                   Contains the Get Handle Name function.

               DX = handle number
                   Contains the EMM handle.

               ES:DI = pointer to handle_name array
                   Contains a pointer to an eight-byte array into which the
                   name currently assigned to the handle will be copied.


          RESULTS

               These results are valid only if the status returned is zero.

               handle_name array
                   Contains the name associated with the specified handle.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The handle name has been returned.


          EMM Functions                                                  98





          Function 20. Get/Set Handle Name
          Get Handle Name subfunction



               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The manager couldn't find the specified EMM handle.  The
                   manager doesn't have any information on the specified
                   EMM handle.  The program may have corrupted its EMM
                   handle.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.


          EXAMPLE

          handle_name                    DB 8 DUP (?)
          emm_handle                     DW ?

          MOV   AX,SEG handle_name
          MOV   ES,AX
          LEA   DI,handle_name           ; ES:DI points to handle_name
          MOV   DX,emm_handle            ; specify EMM handle
          MOV   AX,5300h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error














          EMM Functions                                                  99





          Function 20. Get/Set Handle Name
          Set Handle Name subfunction



          PURPOSE

               This subfunction assigns an eight character name to a
               handle.  There is no restriction on the characters which may
               be used in the handle name.  The full range of values may be
               assigned to each character in a name (that is, 00h through
               FFh).

               At installation, all handles have their name initialized to
               ASCII nulls (binary zeros).  A handle with a name consisting
               of all ASCII nulls has no name.  When a handle is assigned a
               name, at least one character in the name must be a non-null
               character in order to distinguish it from a handle without a
               name.  No two handles may have the same name.

               A handle can be renamed at any time by setting the handle's
               name to a new value.  A handle can have its name removed by
               setting the handle's name to all ASCII nulls.  When a handle
               is deallocated, its name is removed (set to ASCII nulls).


          CALLING PARAMETERS

               AX = 5301h
                   Contains the Set Handle Name function.

               DX = handle number
                   Contains the EMM handle.

               DS:SI = pointer to handle_name
                   Contains a pointer to a byte array which contains the
                   name that is to be assigned to the handle.  The handle
                   name must be padded with nulls if the name is less than
                   eight characters long.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The handle name has been assigned.



          EMM Functions                                                 100





          Function 20. Get/Set Handle Name
          Set Handle Name subfunction



               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The manager couldn't find the specified EMM handle.  The
                   manager doesn't currently have any information pertain-
                   ing to the specified EMM handle.  The program may have
                   corrupted its EMM handle.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = A1h   RECOVERABLE.
                   A handle with this name already exists.  The specified
                   handle was not assigned a name.


          EXAMPLE

          handle_name                    DB 'AARDVARK'
          emm_handle                     DW ?

          MOV   AX,SEG handle_name
          MOV   DS,AX
          LEA   SI,handle_name           ; DS:SI points to handle_name
          MOV   DX,emm_handle            ; specify EMM handle
          MOV   AX,5301h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error










          EMM Functions                                                 101





          Function 21. Get Handle Directory
          Get Handle Directory subfunction



          PURPOSE

               This function returns an array which contains all active
               handles and the names associated with each.  Handles which
               have not been assigned names have a default name of all
               ASCII nulls (binary zeros).  When a handle is first allo-
               cated, or when all the pages belonging to a handle are
               deallocated (that is, an open handle is closed), its default
               name is set to ASCII nulls.  It takes a subsequent assign-
               ment of a name for a handle to have a name after it has been
               opened.  The full range of values may be assigned to each
               character in a name (that is, 00h through FFh).

               The number of bytes required by the array is:

                           10 bytes * total number of handles

               The maximum size of this array is:

                           (10 bytes/entry) * 255 entries = 2550 bytes.


          CALLING PARAMETERS

               AX = 5400h
                   Contains the Get Handle Directory function.

                   handle_dir_struct               STRUC
                       handle_value                DW ?
                       handle_name                 DB 8 DUP (?)
                   handle_dir_struct               ENDS

               ES:DI = pointer to handle_dir
                   Contains a pointer to an area of memory into which the
                   memory manager will copy the handle directory.  The
                   handle directory is an array of structures.  There are
                   as many entries in the array as there are open EMM
                   handles.  The structure consists of the following
                   elements:

                   .handle_value
                       The first member is a word which contains the value
                       of the open EMM handle.





          EMM Functions                                                 102





          Function 21. Get Handle Directory
          Get Handle Directory subfunction



                   .handle_name
                       The second member is an 8 byte array which contains
                       the ASCII name associated with the EMM handle.  If
                       there is no name currently associated with the
                       handle, it has a value of all zeros (ASCII nulls).


          RESULTS

               These results are valid only if the status returned is zero.

               handle_dir
                   Contains the handle values and handle names associated
                   with each handle value.

               AL = number of entries in the handle_dir array
                   Contains the number of entries in the handle directory
                   array.  This is also the same as the number of open
                   handles.  For example, if only one handle is active, AL
                   will contain a one.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The handle directory has been returned.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.



          EMM Functions                                                 103





          Function 21. Get Handle Directory
          Get Handle Directory subfunction



          EXAMPLE

          handle_dir                    handle_dir_struct 255 DUP (?)

          num_entries_in_handle_dir     DB ?

          MOV   AX,SEG handle_dir
          MOV   ES,AX
          LEA   DI,handle_dir                 ; ES:DI points to handle_dir
          MOV   AX,5400h                      ; load function code
          INT   67h                           ; call the memory manager
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler on   
                                              ; error
          MOV   num_entries_in_handle_dir,AL  ; save number of entries

































          EMM Functions                                                 104





          Function 21. Get Handle Directory
          Search For Named Handle subfunction



          PURPOSE

               This subfunction searches the handle name directory for a
               handle with a particular name.  If the named handle is
               found, this subfunction returns the handle number associated
               with the name.  At the time of installation, all handles
               have their names initialized to ASCII nulls.  A handle with
               a name which is all ASCII nulls has, by definition, no name. 
               When a handle is assigned a name, at least one character in
               the name must be a non-null character in order to distin-
               guish it from a handle without a name.


          CALLING PARAMETERS

               AX = 5401h
                   Contains the Search for Named Handle subfunction.

               DS:SI = handle_name
                   Contains a pointer to an 8-byte string that contains the
                   name of the handle being searched for.


          RESULTS

               These results are valid only if the status returned is zero.

               DX = value of named handle
                   The value of the handle which matches the handle name
                   specified.


          REGISTERS MODIFIED

               AX, DX


          STATUS

               AH = 0   SUCCESSFUL.
                   The handle value for the named handle has been found.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.



          EMM Functions                                                 105





          Function 21. Get Handle Directory
          Search For Named Handle subfunction



               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = A0h   NON-RECOVERABLE.
                   No corresponding handle could be found for the handle
                   name specified.

               AH = A1h   NON-RECOVERABLE.
                   A handle found had no name (all ASCII nulls).


          EXAMPLE

          named_handle                   DB 'AARDVARK'
          named_handle_value             DW ?

          MOV   AX,SEG named_handle
          MOV   DS,AX
          LEA   SI,named_handle          ; DS:SI points to named_handle
          MOV   AX,5401h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   named_handle_value,DX    ; save value of named handle

















          EMM Functions                                                 106





          Function 21. Get Handle Directory
          Get Total Handles subfunction



          PURPOSE

               This subfunction returns the total number of handles that
               the memory manager supports, including the operating system
               handle (handle value 0).


          CALLING PARAMETERS

               AX = 5402h
                   Contains the Get Total Handles subfunction.


          RESULTS

               These results are valid only if the status returned is zero.

               BX = total_handles
                   The value returned represents the maximum number of
                   handles which a program may request the memory manager
                   to allocate memory to.  The value returned includes the
                   operating system handle (handle value 0).


          REGISTERS MODIFIED

               AX, BX


          STATUS

               AH = 0   SUCCESSFUL.
                   The total number of handles supported has been returned.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

          EMM Functions                                                 107





          Function 21. Get Handle Directory
          Get Total Handles subfunction



          EXAMPLE

          total_handles                  DW ?

          MOV   AX,5402h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   total_handles,BX         ; save total handle count







































          EMM Functions                                                 108





          Function 22. Alter Page Map & Jump



          PURPOSE

               This function alters the memory mapping context and trans-
               fers control to the specified address.  It is analogous to
               the FAR JUMP in the 8086 family architecture.  The memory
               mapping context which existed before the invocation of this
               function is lost.

               Mapping no pages and jumping is not considered an error.  If
               a request to map zero pages and jump is made, control is
               transferred to the target address, and this function
               performs a far jump.


          CALLING PARAMETERS

               AH = 55h
                   Contains the Alter Page Map & Jump function.

                   log_phys_map_struct             STRUC
                       log_page_number             DW ?
                       phys_page_number_seg        DW ?
                   log_phys_map_struct             ENDS

                   map_and_jump_struct             STRUC
                       target_address              DD ?
                       log_phys_map_len            DB ?
                       log_phys_map_ptr            DD ?
                   map_and_jump_struct             ENDS

               AL = physical page number/segment selector
                   Contains a code which indicates whether the value
                   contained in the

                       .log_phys_map.phys_page_number_seg

                   members are physical page numbers or are the segment
                   address representation of the physical page numbers.  A
                   zero in AL indicates that the values are physical page
                   numbers.  A one in AL indicates that the values in these
                   members are the segment address representations of the
                   physical page numbers.

               DX = handle number
                   Contains the EMM handle.




          EMM Functions                                                 109





          Function 22. Alter Page Map & Jump



               DS:SI = pointer to map_and_jump structure
                   Contains a pointer to a structure that contains the
                   information necessary to map the desired physical pages
                   and jump to the target address.  The structure consists
                   of the following elements:

                   .target_address
                       The first member is a far pointer which contains the
                       target address to which control is to be trans-
                       ferred.  The address is represented in segment:of-
                       fset format.  The offset portion of the address is
                       stored in the low portion of the double word.

                   .log_phys_map_len
                       The second member is a byte which contains the
                       number of entries in the array of structures which
                       immediately follows it.  The array is as long as the
                       application developer needs in order to map the
                       desired logical pages into physical pages.  The
                       number of entries cannot exceed the number of
                       mappable pages in the system.

                   .log_phys_map_ptr
                       The third member is a pointer to an array of struc-
                       tures which contain the logical page numbers and
                       physical pages or segment address at which they are
                       to be mapped.  Each entry in the array of structures
                       contains the following two elements:

                   .log_page_number
                       The first member of this structure is a word which
                       contains the number of the logical page to be
                       mapped.

                   .phys_page_number_seg
                       The second member of this structure is a word which
                       contains either the physical page number or the
                       segment address representation of the physical page
                       number at which the previous logical page number is
                       to be mapped.  The value passed in AL determines the
                       type of representation.


          REGISTERS MODIFIED

               AX



          EMM Functions                                                 110





          Function 22. Alter Page Map & Jump



          Note............................................................
               Values in registers which don't contain required parameters
               maintain the values across the jump.  The values in regis-
               ters (with the exception of AX) and the flag state at the
               beginning of the function are still in the registers and
               flags when the target address is reached.


          STATUS

               AH = 0   SUCCESSFUL.
                   Control has been transferred to the target address.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The manager could not find the specified EMM handle. 
                   The manager does not currently have any information
                   pertaining to the specified EMM handle.  The program may
                   have corrupted its EMM handle.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Ah   RECOVERABLE.
                   One or more of the logical pages to map into a cor-
                   responding physical page is out of the range of logical
                   pages which are allocated to the EMM handle.  The
                   program can recover from this condition by mapping a
                   logical page which is within the bounds for the EMM
                   handle.

               AH = 8Bh   RECOVERABLE.
                   One or more of the physical pages is out of the range of
                   allowable physical pages, or the log_phys_map_len
                   exceeds the number of mappable pages in the system. 
                   Physical page numbers are numbered zero-relative.  The
                   program can recover from this condition by mapping into
                   memory at a physical page which is in the range of
                   supported physical pages.



          EMM Functions                                                 111





          Function 22. Alter Page Map & Jump



               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.


          EXAMPLE

          log_phys_map              log_phys_map_struct (?) DUP (?)

          map_and_jump              map_and_jump_struct (?)

          emm_handle                          DW ?
          phys_page_or_seg_mode               DB ?

          MOV   AX,SEG map_and_jump
          MOV   DS,AX
          LEA   SI,map_and_jump               ; DS:SI points to
                                              ; map_and_jump
          MOV   DX,emm_handle
          MOV   AH,55h                        ; load function code
          MOV   AL,phys_page_or_seg_mode      ; specify physical page
                                              ; or segment mode
          INT   67h                           ; call memory manager
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler on   
                                              ; error
























          EMM Functions                                                 112





          Function 23. Alter Page Map & Call
          Alter Page Map & Call subfunction



          PURPOSE

               This subfunction saves the current memory mapping context,
               alters the specified memory mapping context, and transfers
               control to the specified address.  It is analogous to the
               FAR CALL in the 8086 family architecture.  Just as a return
               from a FAR CALL restores the original value in the code
               segment register, this subfunction restores the state of the
               specified mapping context after the return.

               There is no explicit expanded memory subfunction which
               emulates a return from a FAR CALL.  However, this facility
               is implicitly available through the standard return from a
               FAR CALL.  The following paragraphs describe how this works:

               After this function is invoked, unless an error is detected,
               the memory manager will transfer control to the address
               specified.  If an error occurs, the memory manager returns
               immediately with the error code in the AH register. 
               Otherwise, the memory manager pushes on the stack informa-
               tion which enables it to restore the mapping context after
               the return.

               When the called procedure wants to return to the calling
               procedure, it simply issues a standard FAR RETURN.  The
               memory manager traps this return, restores the specified
               mapping context, and returns to the calling procedure.  The
               memory manager also returns a status from a successful
               return just as it does for all functions.

               Developers using this subfunction must make allowances for
               the additional stack space this subfunction will use.


          CALLING PARAMETERS

               AH = 56h
                   Contains the Alter Page Map & Call function.

                   log_phys_map_struct             STRUC
                       log_page_number             DW ?
                       phys_page_number_seg        DW ?
                   log_phys_map_struct             ENDS





          EMM Functions                                                 113





          Function 23. Alter Page Map & Call
          Alter Page Map & Call subfunction



                   map_and_call_struct             STRUC
                       target_address              DD ?
                       new_page_map_len            DB ?
                       new_page_map_ptr            DD ?
                       old_page_map_len            DB ?
                       old_page_map_ptr            DD ?
                       reserved                    DW 4 DUP (?)
                   map_and_call_struct             ENDS

               AL = physical page number/segment selector
                   Contains a code which indicates whether the value
                   contained in the

                       .new_page_map.phys_page_number_seg
                       .old_page_map.phys_page_number_seg

                   members are physical page numbers or are the segment
                   address representation of the physical page numbers.  A
                   value of zero in AL indicates that the values in these
                   members are physical page numbers.  A value of one in AL
                   indicates that the values in these members are the
                   segment address representations of the physical page
                   numbers.

               DX = handle number
                   Contains the EMM handle.

               DS:SI = pointer to map_and_call structure
                   Contains a pointer to a structure which contains the
                   information necessary to map the desired physical pages
                   and call the target address.  The structure members are
                   described here:

                   .target_address
                       The first member is a far pointer which contains the
                       target address to which control is to be trans-
                       ferred.  The address is represented in segment:of-
                       fset format.  The offset portion of the address is
                       stored in the low portion of the pointer.  The
                       application must supply this value.

                   .new_page_map_len
                       The second member is a byte which contains the
                       number of entries in the new mapping context to
                       which new_page_map_ptr points.  This number cannot
                       exceed the number of mappable pages in the system.


          EMM Functions                                                 114





          Function 23. Alter Page Map & Call
          Alter Page Map & Call subfunction



                   .new_page_map_ptr
                       The third member is a far pointer that points to an
                       array of structures which contains a list of the
                       logical page numbers and the physical page num-
                       bers/segments at which they are to be mapped im-
                       mediately after the call.  The contents of the new
                       array of structures are described at the end of the
                       map_and_call structure.

                   .old_page_map_len
                       The fourth member is a byte which contains the
                       number of entries in the old mapping context to
                       which old_page_map_ptr points.  This number cannot
                       exceed the number of mappable pages in the system.

                   .old_page_map_ptr
                       The fifth member is a far pointer that points to an
                       array of structures which contains a list of the
                       logical page numbers and the physical page num-
                       bers/segments at which they are to be mapped im-
                       mediately after the return.  The contents of the old
                       array of structures are described at the end of the
                       map_and_call structure.

                   .reserved
                       The sixth member is reserved for use by the memory
                       manager.

               Each entry in the old and new array of structures contains
               two elements:

                   .log_page_number
                       The first member of this structure is a word which
                       contains a logical page number which is to be mapped
                       at the succeeding physical page number/segment
                       immediately after the CALL (in the case of the new
                       array of structures) or after the RETURN (in the
                       case of the old array of structures).

                   .phys_page_number_seg
                       The second member of this structure is a word which
                       contains either the physical page number or the
                       segment address representation of the physical page
                       number/segment at which the preceding logical page
                       is to be mapped immediately after the CALL (in the
                       case of the new array of structures) or after the
                       RETURN (in the case of the old array of structures).

          EMM Functions                                                 115





          Function 23. Alter Page Map & Call
          Alter Page Map & Call subfunction



          REGISTERS MODIFIED

               AX

          Note............................................................
               Values in registers which don't contain required parameters
               maintain the values across the call.  The values in regis-
               ters (with the exception of AX) and the flag state at the
               beginning of the function are still in the registers and
               flags when the target address is reached.


          STATUS

               AH = 0   SUCCESSFUL.
                   Control has been transferred to the target address.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The manager couldn't find the specified EMM handle.  The
                   manager doesn't have any information pertaining to the
                   specified EMM handle.  The program may have corrupted
                   its EMM handle.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Ah   RECOVERABLE.
                   One or more of the logical pages to map into a cor-
                   responding physical page is out of the range of logical
                   pages which are allocated to the EMM handle.  The
                   program can recover from this condition by mapping a
                   logical page which is within the bounds for the EMM
                   handle.







          EMM Functions                                                 116





          Function 23. Alter Page Map & Call
          Alter Page Map & Call subfunction



               AH = 8Bh   RECOVERABLE.
                   One or more of the physical pages is out of the range of
                   allowable physical pages, or you've specified more
                   physical pages than exist in the system.  Physical page
                   numbers are numbered zero-relative.  The program can
                   recover from this condition by mapping a physical page
                   which is in the range from zero to three.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.


          EXAMPLE

          new_page_map              log_phys_map_struct (?) DUP (?)

          old_page_map              log_phys_map_struct (?) DUP (?)

          map_and_call              map_and_call_struct (?)

          emm_handle                DW ?
          phys_page_or_seg_mode     DB ?

          MOV   AX,SEG map_and_call
          MOV   DS,AX
          LEA   SI,map_and_call               ; DS:SI points to
                                              ; map_and_call
          MOV   DX,emm_handle                 ; specify EMM handle
          MOV   AH,56h                        ; load function code
          MOV   AL,phys_page_or_seg_mode      ; specify physical page
                                              ; or segment mode
          INT   67h                           ; control is actually
                                              ; transferred to the called
                                              ; procedure at this point
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler on
                                              ; error











          EMM Functions                                                 117





          Function 23. Alter Page Map & Call
          Get Page Map Stack Space Size subfunction



          PURPOSE

               Since the Alter Page Map & Call function pushes additional
               information onto the stack, this subfunction returns the
               number of bytes of stack space the function requires.


          CALLING PARAMETERS

               AX = 5602h
                   Contains the Get Page Map Stack Space Size subfunction.


          RESULTS

               These results are valid only if the status returned is zero.

               BX = stack space required
                   Contains the number of bytes which the Alter Page Map &
                   Call function will require.  In other words, BX contains
                   the number (including the return address) which has to
                   be added to the stack pointer to remove all elements
                   from the stack.


          REGISTERS MODIFIED

               AX, BX


          STATUS

               AH = 0   SUCCESSFUL.
                   The size of the array has been returned.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.



          EMM Functions                                                 118





          Function 23. Alter Page Map & Call
          Get Page Map Stack Space Size subfunction



               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.


          EXAMPLE

          stack_space_reqd               DW ?

          MOV   AX,5602h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   stack_space_reqd,BX      ; save required stack size count



































          EMM Functions                                                 119





          Function 24. Move/Exchange Memory Region
          Move Memory Region subfunction



          PURPOSE

               This subfunction copies a region of memory in the following
               memory source/destination combinations.

               o   conventional memory to conventional memory

               o   conventional memory to expanded memory

               o   expanded memory to conventional memory

               o   expanded memory to expanded memory

               You do not have to save and restore the expanded memory
               mapping context to perform these move operations.  The
               current mapping context is maintained throughout this
               operation.

               The length of the region is limited by the amount of
               expanded memory allocated to the handles specified. 
               However, in most practical applications, the region length
               will be considerably smaller.  A region length of zero is
               not an error, and no move will be performed.

               A region length which exceeds 16K bytes is not an error.  In
               this case the function assumes that a group of logical pages
               is the target for the move.  The logical page specified
               represents the first logical page in which the move will
               take place.  If the region length exceeds 16K bytes, or if
               the region is less than 16K bytes but spans logical pages,
               there must be sufficient logical pages remaining after the
               first logical page for the entire region to fit.

               If your application needs to save a region of conventional
               memory in expanded memory, you can move it without having to
               perform a save or restore of the current mapping context. 
               The memory manager maintains the context.  A move of up to
               1M bytes may be performed, although practical lengths are
               substantially less than this value.

               If the source and destination handles are identical, the
               source and destination regions are tested for overlap before
               the move.  If they overlap, the move direction is chosen so
               that the destination region receives an intact copy of the
               source region.  A status will be returned indicating that
               this overlap has occurred.


          EMM Functions                                                 120





          Function 24. Move/Exchange Memory Region
          Move Memory Region subfunction



          CALLING PARAMETERS

               AX = 5700h
                   Contains the Move Memory Region function.

                   move_source_dest_struct         STRUC
                       region_length               DD ?
                       source_memory_type          DB ?
                       source_handle               DW ?
                       source_initial_offset       DW ?
                       source_initial_seg_page     DW ?
                       dest_memory_type            DB ?
                       dest_handle                 DW ?
                       dest_initial_offset         DW ?
                       dest_initial_seg_page       DW ?
                   move_source_dest_struct         ENDS

               DS:SI = pointer to move_source_dest structure
                   Contains a pointer to a data structure which contains
                   the source and destination information for the move. 
                   The structure members are described here:

                   .region_length
                       The first member is a double word which specifies
                       the length of the memory region (in bytes) to be
                       moved.

                   .source_memory_type
                       The second member is a byte which specifies the type
                       of memory where the source region resides.  A value
                       of zero indicates that the source region resides in
                       conventional memory (excluding the page frame seg-
                       ment).  A value of one indicates that the source
                       region resides in expanded memory.

                   .source_handle
                       If the source region resides in expanded memory, the
                       third member is a word which specifies the handle
                       number associated with the source memory region.  If
                       the source region resides in conventional memory,
                       this variable has no meaning and should be set to
                       zero for future compatibility.

                   .source_initial_offset
                       The fourth member is a word which specifies the
                       offset within the source region from which to begin
                       the move.

          EMM Functions                                                 121





          Function 24. Move/Exchange Memory Region
          Move Memory Region subfunction



                       If the source region resides in expanded memory, the
                       source_initial_offset is relative to the beginning
                       of the 16K logical page.  Because the offset is
                       relative to the beginning of a 16K expanded memory
                       page, it may only take on values between 0000h and
                       3FFFh.

                       If the source region resides in conventional memory,
                       the source_initial_offset is a word which specifies
                       the offset, relative to the beginning of the source
                       segment, from which to begin the move.  Because the
                       offset is relative to the beginning of a 64K-byte
                       conventional memory segment, it may take on values
                       between 0000h and FFFFh.

                   .source_initial_seg_page
                       The fifth member is a word which specifies the
                       initial segment or logical page number within the
                       source region from which to begin the move.

                       If the source region resides in expanded memory, the
                       value specifies the logical page within the source
                       region from which to begin the move.

                       If the source region resides in conventional memory,
                       the source_initial_seg_page specifies the initial
                       segment address within conventional memory from
                       which to begin the move.

                   .dest_memory_type
                       The sixth member is a byte which specifies the type
                       of memory where the destination region resides.  A
                       value of zero indicates conventional memory; a value
                       of one indicates expanded memory.

                   .dest_handle
                       If the destination region resides in expanded
                       memory, the seventh member is a word which specifies
                       the handle number associated with the destination
                       memory region.  If the destination region resides in
                       conventional memory, this variable has no meaning
                       and should be set to zero for future compatibility.

                   .dest_initial_offset
                       The eighth member is a word which specifies the
                       offset within the destination region from which to
                       begin the move.

          EMM Functions                                                 122





          Function 24. Move/Exchange Memory Region
          Move Memory Region subfunction



                       If the destination region resides in expanded
                       memory, the dest_initial_offset is relative to the
                       beginning of the 16K-byte logical page.  Because the
                       offset is relative to the beginning of a 16K-byte
                       expanded memory page, it may only take on values
                       between 0000h and 3FFFh.

                       If the destination region resides in conventional
                       memory, the dest_initial_offset is a word which
                       specifies the offset, relative to the beginning of
                       the destination segment, to begin the move.  Because
                       the offset is relative to the beginning of a 64K
                       conventional memory segment, it may take on values
                       between 0000h and FFFFh.

                   .dest_initial_seg_page
                       The ninth member is a word which specifies the
                       initial segment or logical page number within the
                       destination region from which to begin the move.

                       If the destination region resides in expanded memory
                       then the value specifies the logical page within the
                       destination region from which to begin the move.

                       If the destination region resides in conventional
                       memory, the dest_initial_seg_page specifies the
                       initial segment address within conventional memory
                       from which to begin the move.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The memory regions have been moved.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

          EMM Functions                                                 123





          Function 24. Move/Exchange Memory Region
          Move Memory Region subfunction



               AH = 83h   NON-RECOVERABLE.
                   The manager couldn't find either the source or destina-
                   tion EMM handles.  The memory manager doesn't have any
                   information on the handles specified.  The program may
                   have corrupted its EMM handles.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Ah   NON-RECOVERABLE.
                   One or more of the logical pages is out of the range of
                   logical pages allocated to the source/destination
                   handle.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = 92h   SUCCESSFUL.
                   The source and destination expanded memory regions have
                   the same handle and overlap.  This is valid for a move. 
                   The move has been completed and the destination region
                   has a full copy of the source region.  However, at least
                   a portion of the source region has been overwritten by
                   the move.  Note that the source and destination expanded
                   memory regions with different handles will never physi-
                   cally overlap because the different handles specify
                   totally different regions of expanded memory.

               AH = 93h   CONDITIONALLY-RECOVERABLE.
                   The length of the source or destination expanded memory
                   region specified exceeds the length of the expanded
                   memory region allocated either the source or destination
                   handle.  Insufficient pages are allocated to this handle
                   to move a region of the size specified.  The program can
                   recover from this condition by allocating additional
                   pages to the destination or source handle and attempting
                   to execute the function again.  However, if the applica-
                   tion program allocated as much expanded memory as it
                   thought it needed, this may be a program error and is
                   not recoverable.

               AH = 94h   NON-RECOVERABLE.
                   The conventional memory region and expanded memory
                   region overlap.  This is invalid, the conventional
                   memory region cannot overlap the expanded memory region.



          EMM Functions                                                 124





          Function 24. Move/Exchange Memory Region
          Move Memory Region subfunction



               AH = 95h   NON-RECOVERABLE.
                   The offset within the logical page exceeds the length of
                   the logical page.  The initial source or destination
                   offsets within an expanded memory region must be between
                   0000h and 3FFFh (16383 or (length of a logical page
                   - 1)).

               AH = 96h   NON-RECOVERABLE.
                   Region length exceeds 1M bytes.

               AH = 98h   NON-RECOVERABLE.
                   The memory source and destination types are undefined.

               AH = A2h   NON-RECOVERABLE.
                   An attempt was made to wrap around the 1M-byte address
                   space of conventional memory during the move.  The
                   combination of source/destination starting address and
                   length of the region to be moved exceeds 1M bytes.  No
                   data was moved.


          EXAMPLE

          move_source_dest               move_source_dest_struct (?)

          MOV   AX,SEG move_source_dest
          MOV   DS,AX
          LEA   SI,move_source_dest      ; DS:SI points to move_source_dest
          MOV   AX,5700h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
















          EMM Functions                                                 125





          Function 24. Move/Exchange Memory Region
          Exchange Memory Region subfunction



          PURPOSE

               This subfunction exchanges (using a string move) a region of
               memory in any of the following memory source/destination
               combinations.

               o   conventional memory to conventional memory

               o   conventional memory to expanded memory

               o   expanded memory to conventional memory

               o   expanded memory to expanded memory

               The term expanded memory region refers only to the area of
               memory above 640K bytes (9FFFFh).  If a system provides
               mappable conventional memory, this function treats the
               mappable conventional memory regions as ordinary convention-
               al memory.  The contents of the source region and the
               destination region are exchanged.

               The exchange operation can be performed without having to
               save and restore the expanded memory mapping context.  The
               current mapping context is maintained throughout this
               operation.  The length of the region is limited to the
               amount of expanded memory allocated to the specified EMM
               handles.  A length of zero is not an error; however, no
               exchange will be performed.  A region length which exceeds
               16K bytes is not an error.  In this case the function
               assumes that a group of logical pages is the target for the
               exchange.  The logical page specified represents the first
               logical page in which the exchange will take place.  If the
               region length exceeds 16K bytes, or if the region is less
               than 16K bytes but spans logical pages, there must be
               sufficient logical pages remaining after the first logical
               page for the entire region to fit.

               If your application needs to exchange a region of conven-
               tional memory with expanded memory, you can simply exchange
               it with the region of interest without having to perform a
               save or restore of the current mapping context.  An exchange
               of up to 1M bytes may be performed, although practical
               lengths are obviously below that value.  Checking is done
               before starting the exchange to prevent the possibility of
               overlap during the exchange operation.  Overlapping source
               and destination regions for an exchange are invalid, and the
               exchange will not take place.

          EMM Functions                                                 126





          Function 24. Move/Exchange Memory Region
          Exchange Memory Region subfunction



          CALLING PARAMETERS

               AX = 5701h
                   Contains the Exchange Memory Region function.

                   xchg_source_dest_struct         STRUC
                       region_length               DD ?
                       source_memory_type          DB ?
                       source_handle               DW ?
                       source_initial_offset       DW ?
                       source_initial_seg_page     DW ?
                       dest_memory_type            DB ?
                       dest_handle                 DW ?
                       dest_initial_offset         DW ?
                       dest_initial_seg_page       DW ?
                   xchg_source_dest_struct         ENDS

               DS:SI = pointer to xchg_source_dest structure
                   Contains a pointer to the data structure which contains
                   the source and destination information for the exchange. 
                   The structure members are described here:

                   .region_length
                       The first member is a double word which specifies
                       the length of the memory region to be exchanged.

                   .source_memory_type
                       The second member is a byte which specifies the type
                       of memory where the source region resides.  A value
                       of zero indicates that the source region resides in
                       conventional memory.  A value of one indicates that
                       the source region resides in expanded memory.

                   .source_handle
                       If the source region resides in expanded memory, the
                       third member is a word which specifies the handle
                       number associated with the source memory region.  If
                       the source region resides in conventional memory,
                       this variable has no meaning and should be set to
                       zero for future compatibility.

                   .source_initial_offset
                       The fourth member is a word which specifies the
                       offset within the source region from which to begin
                       the exchange.



          EMM Functions                                                 127





          Function 24. Move/Exchange Memory Region
          Exchange Memory Region subfunction



                       If the source region resides in expanded memory, the
                       source_initial_offset is relative to the beginning
                       of the 16K logical page.  Because the offset is
                       relative to the beginning of a 16K expanded memory
                       page, it may only take on values between 0000h and
                       3FFFh.

                       If the source region resides in conventional memory,
                       the source_initial_offset is a word which specifies
                       the offset, relative to the beginning of the source
                       segment, from which to begin the exchange at. 
                       Because the offset is relative to the beginning of a
                       64K-byte conventional memory segment, it may take on
                       values between 0000h and FFFFh.

                   .source_initial_seg_page
                       The fifth member is a word which specifies the
                       initial segment or logical page number within the
                       source region from which to begin the exchange.

                       If the source region resides in expanded memory then
                       the value specifies the logical page within the
                       source region from which to begin the exchange.

                       If the source region resides in conventional memory,
                       the source_initial_seg_page specifies the initial
                       segment address within conventional memory from
                       which to begin the exchange.

                   .dest_memory_type
                       The sixth member is a byte which specifies the type
                       of memory where the destination region resides.  A
                       value of zero indicates that the destination region
                       resides in conventional memory (excluding the page
                       frame segment).  A value of one indicates that the
                       destination region resides in expanded memory.

                   .dest_handle
                       If the destination region resides in expanded
                       memory, the seventh member is a word which specifies
                       the handle number associated with the destination
                       memory region.  If the destination region resides in
                       conventional memory, this variable has no meaning
                       and should be set to zero for future compatibility.




          EMM Functions                                                 128





          Function 24. Move/Exchange Memory Region
          Exchange Memory Region subfunction



                   .dest_initial_offset
                       The eighth member is a word which specifies the
                       offset within the destination region from which to
                       begin the exchange.

                       If the destination region resides in expanded
                       memory, the dest_initial_offset is relative to the
                       beginning of the 16K-byte logical page.  Because the
                       offset is relative to the beginning of a 16K-byte
                       expanded memory page, it may only take on values
                       between 0000h and 3FFFh.

                       If the destination region resides in conventional
                       memory, the dest_initial_offset is a word which
                       specifies the offset, relative to the beginning of
                       the destination segment, to begin the exchange at. 
                       Because the offset is relative to the beginning of a
                       64K conventional memory segment, it may take on
                       values between 0000h and FFFFh.

                   .dest_initial_seg_page
                       The ninth member is a word which specifies the
                       initial segment or logical page number within the
                       destination region from which to begin the exchange.

                       If the destination region resides in expanded memory
                       then the value specifies the logical page within the
                       destination region from which to begin the exchange.

                       If the destination region resides in conventional
                       memory, the dest_initial_seg_page specifies the
                       initial segment address within conventional memory
                       from which to begin the exchange.



          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The memory regions have been exchanged.



          EMM Functions                                                 129





          Function 24. Move/Exchange Memory Region
          Exchange Memory Region subfunction



               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 83h   NON-RECOVERABLE.
                   The manager could not find either the source or destina-
                   tion EMM handles.  The memory manager does not currently
                   have any information pertaining to the handles speci-
                   fied.  The program may have corrupted its EMM handles.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Ah   NON-RECOVERABLE.
                   One or more of the logical pages is out of the range of
                   logical pages allocated to the source/destination
                   handle.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = 93h   CONDITIONALLY-RECOVERABLE.
                   The length of the source or destination expanded memory
                   region specified, exceeds the length of the expanded
                   memory region allocated to the source or destination
                   specified EMM handle.  There are insufficient pages
                   allocated to this handle to exchange a region of the
                   size specified.  The program can recover from this
                   condition by attempting to allocate additional pages to
                   the destination or source handle and attempting to
                   execute the function again.  However, if the application
                   program was allocated as much expanded memory as it
                   thought it needed, this may be a program error and is
                   therefore not recoverable.

               AH = 94h   NON-RECOVERABLE.
                   The conventional memory region and expanded memory
                   region overlap.  This is invalid, the conventional
                   memory region cannot overlap the expanded memory region.





          EMM Functions                                                 130





          Function 24. Move/Exchange Memory Region
          Exchange Memory Region subfunction



               AH = 95h   NON-RECOVERABLE.
                   The offset within the logical page exceeds the length of
                   the logical page.  The initial source or destination
                   offsets within an expanded memory region must be between
                   0000h and 3FFFh (16383 or (length of a logical page
                   - 1)).

               AH = 96h   NON-RECOVERABLE.
                   Region length exceeds 1M-byte limit.

               AH = 97h   NON-RECOVERABLE.
                   The source and destination expanded memory regions have
                   the same handle and overlap.  This is invalid, the
                   source and destination expanded memory regions cannot
                   have the same handle and overlap when they are being
                   exchanged.  Note that the source and destination
                   expanded memory regions which have different handles
                   will never physically overlap because the different
                   handles specify totally different regions of expanded
                   memory.

               AH = 98h   NON-RECOVERABLE.
                   The memory source and destination types are undefined.

               AH = A2h   NON-RECOVERABLE.
                   An attempt was made to wrap around the 1M-byte address
                   space of conventional memory during the exchange.  The
                   combination of source/destination starting address and
                   length of the region to be exchanged exceeds 1M bytes. 
                   No data was exchanged.


          EXAMPLE

          xchg_source_dest               xchg_source_dest_struct (?)

          MOV   AX,SEG xchg_source_dest
          MOV   DS,AX
          LEA   SI,xchg_source_dest      ; DS:SI points to xchg_source_dest
          MOV   AX,5701h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error





          EMM Functions                                                 131





          Function 25. Get Mappable Physical Address Array
          Get Mappable Physical Address Array subfunction



          PURPOSE

               This subfunction returns an array containing the segment
               address and physical page number for each mappable physical
               page in a system.  The contents of this array provide a
               cross reference between physical page numbers and the actual
               segment addresses for each mappable page in the system.  The
               array is sorted in ascending segment order.  This does not
               mean that the physical page numbers associated with the
               segment addresses are also in ascending order.


          CALLING PARAMETERS

               AX = 5800h
                   Contains the Get Mappable Physical Address Array
                   subfunction

                   mappable_phys_page_struct       STRUC
                       phys_page_segment           DW ?
                       phys_page_number            DW ?
                   mappable_phys_page_struct       ENDS

               ES:DI = mappable_phys_page
                   Contains a pointer to an application-supplied memory
                   area where the memory manager will copy the physical
                   address array.  Each entry in the array is a structure
                   containing two members:

                   .phys_page_segment
                       The first member is a word which contains the
                       segment address of the mappable physical page
                       associated with the physical page number following
                       it.  The array entries are sorted in ascending
                       segment address order.

                   .phys_page_number
                       The second member is a word which contains the
                       physical page number which corresponds to the
                       previous segment address.  The physical page numbers
                       are not necessarily in ascending order.







          EMM Functions                                                 132





          Function 25. Get Mappable Physical Address Array
          Get Mappable Physical Address Array subfunction



                   Example 1

                       An expanded memory board has its page frame starting
                       at address C0000h and has no mappable conventional
                       memory.  For this configuration, physical page 0
                       corresponds to segment address C000h, physical page
                       1 corresponds to segment address C400h, etc.  The
                       array would contain the following data (in this
                       order):

                       C000h, 00h
                       C400h, 01h
                       C800h, 02h
                       CC00h, 03h


                   Example 2

                       An expanded memory board has a large page frame
                       starting at address C0000h and has mappable conven-
                       tional memory from 90000h through 9FFFFh.  For this
                       configuration, physical page 0 corresponds to
                       segment address C000h, physical page 1 corresponds
                       to segment address C400h, etc.  The array would
                       contain the following data in the order specified. 
                       Note that the expanded memory region always has the
                       lowest numerically valued physical page numbers.

                       9000h, 0Ch
                       9400h, 0Dh
                       9800h, 0Eh
                       9C00h, 0Fh
                       C000h, 00h
                       C400h, 01h
                       C800h, 02h
                       CC00h, 03h
                       D000h, 04h
                       D400h, 05h
                       D800h, 06h
                       DC00h, 07h
                       E000h, 08h
                       E400h, 09h
                       E800h, 0Ah
                       EC00h, 0Bh




          EMM Functions                                                 133





          Function 25. Get Mappable Physical Address Array
          Get Mappable Physical Address Array subfunction



          RESULTS

               These results are valid only if the status returned is zero.

               CX = number of entries in the mappable_phys_page
                   Multiply this number by (SIZE mappable_phys_page_struct)
                   to determine the number of bytes the physical page
                   address array requires.


          REGISTERS MODIFIED

               AX, CX


          STATUS

               AH = 0   SUCCESSFUL.
                   The hardware configuration array has been returned.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.















          EMM Functions                                                 134





          Function 25. Get Mappable Physical Address Array
          Get Mappable Physical Address Array subfunction



          EXAMPLE

          mappable_phys_page             mappable_phys_page_struct (?)

          mappable_page_entry_count      DW ?

          MOV   AX,SEG mappable_phys_page
          MOV   ES,AX
          LEA   DI,mappable_phys_page         ; ES:DI points to
                                              ; mappable_phys_page
          MOV   AX,5800h                      ; load function code
          INT   67h                           ; call the memory
                                              ; manager
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler
                                              ; on error
          MOV   mappable_page_entry_count,CX  ; save mappable
                                              ; page entry count






























          EMM Functions                                                 135





          Function 25. Get Mappable Physical Address Array
          Get Mappable Physical Address Array Entries subfunction



          PURPOSE

               This subfunction gets the number of entries which will be
               required for the array the first subfunction returns.


          CALLING PARAMETERS

               AX = 5801h
                   Contains the Get Physical Page Address Array Entries
                   subfunction.  This subfunction returns a word which
                   represents the number of entries in the array returned
                   by the previous subfunction.  This number also repre-
                   sents the number of mappable physical pages in a system.


          RESULTS

               These results are valid only if the status returned is zero.

               CX = number of entries in the mappable_phys_page
                   Multiply this number by (SIZE mappable_phys_page_struct)
                   to determine the number of bytes the physical page
                   address array will require.


          REGISTERS MODIFIED

               AX, CX


          STATUS

               AH = 0   SUCCESSFUL.
                   The number of mappable physical pages has been returned.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.


          EMM Functions                                                 136





          Function 25. Get Mappable Physical Address Array
          Get Mappable Physical Address Array Entries subfunction



               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.


          EXAMPLE

          mappable_page_entry_count           DW ?

          MOV   AX,5801h                      ; load function code
          INT   67h                           ; call memory manager
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler
                                              ; on error
          MOV   mappable_page_entry_count,CX  ; save mappable
                                              ; page entry count

































          EMM Functions                                                 137





          Function 26. Get Expanded Memory Hardware Information
          Get Hardware Configuration Array subfunction



          Note............................................................
               This function is for use by operating systems only.  This
               function can be disabled at any time by the operating
               system.  Refer to Function 30 for a description of how an
               operating system does this.


          PURPOSE

               This subfunction returns an array containing expanded memory
               hardware configuration information for use by an operating
               system/environment.


          CALLING PARAMETERS

               AX = 5900h
                   Contains the Get Hardware Configuration Array subfunc-
                   tion.

                   hardware_info_struct            STRUC
                       raw_page_size               DW ?
                       alternate_register_sets     DW ?
                       context_save_area_size      DW ?
                       DMA_register_sets           DW ?
                       DMA_channel_operation       DW ?
                   hardware_info_struct            ENDS

               ES:DI = hardware_info
                   Contains a pointer to a memory area that the operating
                   system supplies where the memory manager will copy
                   expanded memory hardware information.  The structure
                   contains these five members:

                   .raw_page_size
                       The first member is a word which contains the size
                       of a raw mappable physical page in paragraphs (16
                       bytes).  LIM standard pages are always 16K bytes. 
                       However, other implementations of expanded memory
                       boards do not necessarily comply with this standard
                       and can emulate a 16K-byte page by mapping in
                       multiple smaller pages.  This member specifies the
                       size of a mappable physical page viewed from the
                       hardware implementation level.




          EMM Functions                                                 138





          Function 26. Get Expanded Memory Hardware Information
          Get Hardware Configuration Array subfunction



                   .alternate_register_sets
                       The second member is a word which specifies the
                       number of alternate mapping register sets.  The
                       additional mapping register sets are termed alter-
                       nate mapping register sets in this document.

                       All expanded memory boards have at least one set of
                       hardware registers to perform the logical to
                       physical page mapping.  Some expanded memory boards
                       have more than one set of these mapping registers. 
                       This member specifies how many of these alternate
                       mapping register sets exist (beyond the one set that
                       all expanded memory boards have) on the expanded
                       memory boards in the system.  If an expanded memory
                       card has only one set of mapping registers (that is,
                       no alternate mapping register sets) this member has
                       a value of zero.

                   .context_save_area_size
                       The third member is a word which contains the
                       storage requirements for the array required to save
                       a mapping context.  The value returned in this
                       member is exactly the same as that returned by
                       Function 15 (Get Size of Page Map Save Array
                       subfunction).

                   .DMA_register_sets
                       The fourth member is a word which contains the
                       number of register sets that can be assigned to DMA
                       channels.  These DMA register sets, although similar
                       in use to alternate register sets, are for DMA
                       mapping and not task mapping.

                       If the expanded memory hardware does not support DMA
                       register sets, care must be taken when DMA is taking
                       place.

                       In a multitasking operating system, when one task is
                       waiting for DMA to complete, it is useful to be able
                       to switch to another task.  However, if the DMA is
                       taking place in memory that the second task will
                       need to remap, remapping would be disastrous.






          EMM Functions                                                 139





          Function 26. Get Expanded Memory Hardware Information
          Get Hardware Configuration Array subfunction



                       If the expanded memory hardware can detect when DMA
                       is occurring, the OS/E should allow task switches
                       and remapping during DMA.  If no special support for
                       DMA is available, no remapping should be done when
                       DMA is in progress.

                   .DMA_channel_operation
                       The fifth member is a word which specifies a special
                       case for the DMA register sets.  A value of zero
                       specifies that the DMA register sets behave as
                       described in Function 28.  A value of one specifies
                       that the expanded memory hardware has only one DMA
                       register set.  In addition, if any channel is mapped
                       through this register set, then all channels are
                       mapped through it.  For LIM standard boards, this
                       value is zero.


          RESULTS

               These results are valid only if the status returned is zero.

               hardware_info
                   Contains the expanded memory hardware-specific informa-
                   tion described above.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The hardware configuration array has been returned.

               AH = 80h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the memory
                   manager software.

               AH = 81h   NON-RECOVERABLE.
                   The manager has detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the manager is not defined.

          EMM Functions                                                 140





          Function 26. Get Expanded Memory Hardware Information
          Get Hardware Configuration Array subfunction



               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = A4h   NON-RECOVERABLE.
                   Access to this function has been denied by the operating
                   system.  The function cannot be used at this time.


          EXAMPLE

          hardware_info                  hardware_info_struct (?)

          MOV   AX,SEG hardware_info
          MOV   ES,AX
          LEA   DI,hardware_info         ; ES:DI points to hardware_info
          MOV   AX,5900h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error





























          EMM Functions                                                 141





          Function 26. Get Expanded Memory Hardware Information
          Get Unallocated Raw Page Count subfunction



          PURPOSE

               The Get Unallocated Raw Page Count subfunction returns the
               number of unallocated non-standard length mappable pages as
               well as the total number of non-standard length mappable
               pages in expanded memory to the operating system.

               One variety of expanded memory board has a page size which
               is a sub-multiple of 16K bytes.  An expanded memory page
               which is a sub-multiple of 16K is termed a raw page.  An
               operating system may deal with mappable physical page sizes
               which are sub-multiples of 16K bytes.

               If the expanded memory board supplies pages in exact
               multiples of 16K bytes, the number of pages this function
               returns is identical to the number Function 3 (Get Unallo-
               cated Page Count) returns.  In this case, there is no
               difference between a page and a raw page.


          CALLING PARAMETERS

               AX = 5901h
                   Contains the Get Unallocated Raw Page Count subfunction.


          RESULTS

               These results are valid only if the status returned is zero.

               BX = unallocated raw pages
                   The number of raw pages that are currently available for
                   use.

               DX = total raw pages
                   The total number of raw pages in expanded memory.


          REGISTERS MODIFIED

               AX, BX, DX







          EMM Functions                                                 142





          Function 26. Get Expanded Memory Hardware Information
          Get Unallocated Raw Page Count subfunction



          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has returned the number of unallocated raw
                   pages and the number of total raw pages in expanded
                   memory.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.


          EXAMPLE

          unalloc_raw_pages              DW ?
          total_raw_pages                DW ?

          MOV   AX,5901h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   unalloc_raw_pages,BX     ; save unallocated raw page count
          MOV   total_raw_pages,DX       ; save total raw page count














          EMM Functions                                                 143





          Function 27. Allocate Standard/Raw Pages
          Allocate Standard Pages subfunction



          PURPOSE

               The Allocate Standard Pages subfunction allocates the number
               of standard size (16K bytes) pages that the operating system
               requests and assigns a unique EMM handle to these pages. 
               The EMM handle owns these pages until the operating system
               deallocates them.  This subfunction allows you to allocate
               zero pages to a handle, unlike Function 4 (Allocate Pages).

          Note............................................................
               This note affects expanded memory manager implementors and
               operating system developers only.  Applications should not
               use the following characteristic of the memory manager.  An
               application violating this rule will be incompatible with
               future versions of Microsoft's operating systems and
               environments.

               To be compatible with this specification, an expanded memory
               manager will provide a special handle which is available to
               the operating system only.  This handle will have a value of
               0000h and will have a set of pages allocated to it when the
               expanded memory manager driver installs.  The pages that the
               memory manager will automatically allocate to handle 0000h
               are those that backfill conventional memory.  Typically,
               this backfill occurs between addresses 40000h (256K) and
               9FFFFh (640K).  However, the range can extend below and
               above this limit if the hardware and memory manager have the
               capability.

               An operating system won't have to invoke Function 27 to
               obtain this handle because it can assume the handle already
               exists and is available for use immediately after the
               expanded memory device driver installs.  When an operating
               system wants to use this handle, it uses the special handle
               value of 0000h.  The operating system will be able to invoke
               any EMM function using this special handle value.  To
               allocate pages to this handle, the operating system need
               only invoke Function 18 (Reallocate Pages).

               There are two special cases for this handle:

               1.  Function 27 (Allocate Standard Pages subfunction).  This
                   function must never return zero as a handle value. 
                   Applications must always invoke Function 27 to allocate
                   pages and obtain a handle which identifies the pages
                   which belong to it.  Since Function 27 never returns a


          EMM Functions                                                 144





          Function 27. Allocate Standard/Raw Pages
          Allocate Standard Pages subfunction



                   handle value of zero, an application will never gain
                   access to this special handle.

               2.  Function 6 (Deallocate Pages).  If the operating system
                   uses it to deallocate the pages which are allocated to
                   this handle, the pages the handle owns will be returned
                   to the manager for use.  But the handle will not be
                   available for reassignment.  The manager should treat a
                   deallocate pages function request for this handle the
                   same as a reallocate pages function request, where the
                   number of pages to reallocate to this handle is zero.


          CALLING PARAMETERS

               AX = 5A00h
                   Contains the Allocate Standard Pages subfunction.

               BX = num_of_standard_pages_to_alloc
                   Contains the number of standard pages the operating
                   system wants to allocate.


          RESULTS

               These results are valid only if the status returned is zero.

               DX = handle
                   Contains a unique EMM handle.  The operating system must
                   use this EMM handle as a parameter in any function that
                   requires it.  Up to 255 handles may be obtained.  (Both
                   Function 27 and Function 4 must share the same 255
                   handles.)

                   For all functions using this handle, the length of the
                   physical and logical pages allocated to it are standard
                   length (that is, 16K bytes).


          REGISTERS MODIFIED

               AX, DX






          EMM Functions                                                 145





          Function 27. Allocate Standard/Raw Pages
          Allocate Standard Pages subfunction



          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has allocated the pages to an assigned EMM
                   standard handle.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 85h   RECOVERABLE.
                   All EMM handles are being used.

               AH = 87h   RECOVERABLE.
                   There aren't enough expanded memory pages present in the
                   system to satisfy the operating system's request.

               AH = 88h   RECOVERABLE.
                   There aren't enough unallocated pages to satisfy the
                   operating system's request.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.


          EXAMPLE

          num_of_standard_pages_to_alloc      DW ?
          emm_handle                          DW ?

          MOV   BX,num_of_standard_pages_to_alloc
          MOV   AX,5A00h                      ; load function code
          INT   67h                           ; call the memory manager
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler on
                                              ; error
          MOV   emm_handle,DX                 ; save handle



          EMM Functions                                                 146





          Function 27. Allocate Standard/Raw Pages
          Allocate Raw Pages subfunction



          PURPOSE

               The Allocate Raw Pages function allocates the number of non-
               standard size pages that the operating system requests and
               assigns a unique EMM handle to these pages.  The EMM handle
               owns these pages until the operating system deallocates
               them.  This function allows you to allocate zero pages to a
               handle, unlike Function 4 (Allocate Pages).

               A hardware vendor may design an expanded memory board that
               has a page size which is a sub-multiple of 16K bytes.  A
               physical page which is a sub-multiple of 16K is termed a raw
               page.  The operating system may deal with page sizes which
               are sub-multiples of 16K bytes.  The memory manager must
               treat any function using a handle with raw pages allocated
               to it by Function 27 (Allocate Raw Pages subfunction)
               differently than it does a handle that has normal 16K-byte
               pages allocated to it.

               Handles which are assigned using Function 4 (Allocate Pages)
               or Function 27 (Allocate Standard Pages subfunction) must
               have pages which are 16K bytes -- this is the length of a
               standard expanded memory page.  If the expanded memory board
               hardware is not able to supply 16K-byte pages, the memory
               manager must emulate pages which are 16K bytes combining
               multiple non-standard size pages to form a single 16K-byte
               page.

               Handles which are assigned using Function 27 (Allocate Raw
               Pages subfunction) are called raw handles.  All logical
               pages allocated to a raw handle may have a non-standard
               length (that is, not 16K bytes).  However, once the operat-
               ing system has allocated a number of raw pages to a handle,
               it is the responsibility of the memory manager to recognize
               that raw handle as one that has non-standard size pages
               allocated to it.  The memory manager must identify these
               handles and treat all functions which use handles which have
               non-standard page lengths differently.  The logical page
               length becomes the length of the non-standard size page for
               any raw handle that Function 27 assigns.

          Note............................................................
               This note affects expanded memory manager implementors and
               operating system developers only.  Applications should not
               use the following characteristic of the memory manager.  An
               application violating this rule will be incompatible with


          EMM Functions                                                 147





          Function 27. Allocate Standard/Raw Pages
          Allocate Raw Pages subfunction



               future versions of Microsoft's operating systems and
               environments.

               To be compatible with this specification, an expanded memory
               manager will provide a special handle which is available to
               the operating system only.  This handle will have a value of
               0000h and will have a set of pages allocated to it when the
               expanded memory manager driver installs.  The pages that the
               memory manager will automatically allocate to handle 0000h
               are those that backfill conventional memory.  Typically,
               this backfill occurs between addresses 40000h (256K) and
               9FFFFh (640K).  However, the range can extend below and
               above this limit if the hardware and memory manager have the
               capability.

               An operating system won't have to invoke Function 27 to
               obtain this handle because it can assume the handle already
               exists and is available for use immediately after the
               expanded memory device driver installs.  When an operating
               system wants to use this handle, it uses the special handle
               value of 0000h.  The operating system will be able to invoke
               any EMM function using this special handle value.  To
               allocate pages to this handle, the operating system need
               only invoke Function 18 (Reallocate Pages).

               There are two special cases for this handle:

               1.  Function 27 (Allocate Raw Pages subfunction).  This
                   function must never return zero as a handle value. 
                   Applications must always invoke Function 27 to allocate
                   pages and obtain a handle which identifies the pages
                   which belong to it.  Since Function 27 never returns a
                   handle value of zero, an application will never gain
                   access to this special handle.

               2.  Function 6 (Deallocate Pages).  If the operating system
                   uses it to deallocate the pages which are allocated to
                   this handle, the pages the handle owns will be returned
                   to the manager for use.  But the handle will not be
                   available for reassignment.  The manager should treat a
                   deallocate pages function request for this handle the
                   same as a reallocate pages function request, where the
                   number of pages to reallocate to this handle is zero.





          EMM Functions                                                 148





          Function 27. Allocate Standard/Raw Pages
          Allocate Raw Pages subfunction



          CALLING PARAMETERS

               AX = 5A01h
                   Contains the Allocate Raw Pages subfunction.

               BX = num_of_raw_pages_to_alloc
                   Contains the number of raw pages the operating system
                   wishes to allocate.


          RESULTS

               These results are valid only if the status returned is zero.

               DX = raw handle
                   Contains a unique EMM raw handle.  The operating system
                   must use this EMM raw handle as a parameter in any
                   function that requires it.  Up to 255 handles may be
                   obtained.  (Both Function 4 and Function 27 must share
                   the same 255 handles).

                   For all functions using this raw handle, the length of
                   the physical and logical pages allocated to it may be
                   non-standard (that is, not 16K bytes).


          REGISTERS MODIFIED

               AX, DX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has allocated the raw pages to an assigned
                   EMM raw handle.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.




          EMM Functions                                                 149





          Function 27. Allocate Standard/Raw Pages
          Allocate Raw Pages subfunction



               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 85h   RECOVERABLE.
                   All EMM handles are being used.

               AH = 87h   RECOVERABLE.
                   There aren't enough expanded memory raw pages present in
                   the system to satisfy the operating system's request.

               AH = 88h   RECOVERABLE.
                   There aren't enough unallocated raw pages to satisfy the
                   operating system's request.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.


          EXAMPLE

          num_of_raw_pages_to_alloc           DW ?
          emm_raw_handle                      DW ?

          MOV   BX,num_of_raw_pages_to_alloc
          MOV   AX,5A01h                      ; load function code
          INT   67h                           ; call the memory manager
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler
                                              ; on error
          MOV   emm_raw_handle,DX             ; save raw handle

















          EMM Functions                                                 150





          Function 28. Alternate Map Register Set



          Note............................................................
               This function is for use by operating systems only.  The
               operating system can disable this function at any time. 
               Refer to Function 30 for a description of how an operating
               system can enable or disable this function.


          Design Considerations

               The hardware support for the entire set of subfunctions
               described is generally not present on every expanded memory
               board from every vendor of expanded memory board products. 
               For some of the subfunctions, software emulation is provid-
               ed.  For other subfunctions, a certain protocol in their use
               must be observed.  The subfunctions for which this is most
               crucial are those which address system DMA capabilities.


          System DMA Capabilities & Expanded Memory Support of DMA

               In a multitasking operating system, when one task is waiting
               for DMA to complete, it is useful to be able to switch to
               another task.  This specification describes a capability
               which may be designed into expanded memory boards to provide
               DMA into memory regions which may be mapped out while the
               DMA is occurring.  For expanded memory boards that do not
               provide this, it is crucial to understand that while DMA is
               in progress into a region of mappable memory, the memory
               mapping context cannot be changed.  That is, all DMA action
               must be complete before any remapping of pages can be done.


          Expanded Memory Support of DMA Register Sets

               Expanded memory boards which have DMA register sets could
               support DMA into a region of mappable memory while the
               memory mapping context is being switched.  It is important
               to realize that these DMA register sets are separate from
               the alternate map register sets.  An example of how an OS/E
               might use DMA register sets follows:

                                      Example 1

               1.  Allocate a DMA register set.

               2.  Get current register set.

               3.  Set the DMA register set.

          EMM Functions                                                 151





          Function 28. Alternate Map Register Set



               4.  Map in the memory desired.

               5.  Get the DMA register set.

               6.  Set the original register set.

               7.  Assign the desired DMA channel to the DMA register set.

               The preceding set of calls makes all DMA accesses for the
               desired DMA channel get mapped through the current DMA
               register set regardless of the current register set.  In
               other words, the DMA register set overrides the current
               mapping register set for DMA operations on the DMA channel
               specified.  A DMA channel that is not assigned to a DMA
               register set has all its DMA operations mapped through the
               current mapping register set.

































          EMM Functions                                                 152





          Function 28. Alternate Map Register Set
          Get Alternate Map Register Set subfunction



          Note............................................................
               This function is for use by operating systems only.  The
               operating system can disable this function at any time. 
               Refer to Function 30 for a description of how an operating
               system can enable or disable this function.


          PURPOSE

               The subfunction does one of two things depending on the map
               register set which is active at the time this function is
               invoked:

               1.  If the preceding Set Alternate Map Register Set call was
                   done with the alternate map register set equal to zero
                   (BL = 0), these points apply:

                   a.  The context save area pointer saved within EMM by
                       the Set Alternate Map Register subfunction is
                       returned by this call.  This pointer is always
                       returned for boards which do not supply alternate
                       mapping register sets.

                   b.  If the context save area pointer returned is not
                       equal to zero, this subfunction copies the contents
                       of the mapping registers on each expanded memory
                       board in the system into the save area specified by
                       the pointer.  The format of this save area is the
                       same as that returned by Function 15 (Get Page Map
                       subfunction).  This is intended to simulate getting
                       an alternate map register set.  Note that the memory
                       manager does not allocate the space for the context: 
                       the operating system must do so.

                   c.  If the context save area pointer returned is equal
                       to zero, this subfunction does not copy the contents
                       of the mapping registers on each expanded memory
                       board in the system into the save area specified by
                       the pointer.

                   d.  The context save area pointer must have been
                       initialized by a previous Set Alternate Map Register
                       Set call.  Note that the value of the context save
                       area pointer saved within EMM is zero immediately
                       after installation.



          EMM Functions                                                 153





          Function 28. Alternate Map Register Set
          Get Alternate Map Register Set subfunction



                   e.  The context save area must be initialized by a
                       previous Get Page Map call (Function 15).

               2.  If the preceding Set Alternate Map Register Set call was
                   done with the alternate map register set greater than
                   zero (BL > 0), then the number of the alternate map
                   register set which is in use at the time that this
                   function is invoked is returned.  The context save area
                   pointer is not returned in this case.


          CALLING PARAMETERS

               AX = 5B00h
                   Contains the Get Alternate Map Register Set subfunction.


          RESULTS

               These results are valid only if the status returned is zero.

               If BL <> 0, current active alternate map register set number
                   Contains the alternate map register set which was active
                   at the time that this function was invoked.

               ES:DI   Unaffected.

               If BL = 0
                   Indicates that a pointer to an area which contains the
                   state of all the map registers on all boards in the
                   system, and any additional information necessary to
                   restore the boards to their original state, has been
                   returned.

               ES:DI = pointer to a map register context save area
                   Contains a pointer to an operating system supplied
                   context save area.  The pointer is in standard seg-
                   ment:offset format.  This pointer is always returned if
                   the expanded memory hardware does not supply alternate
                   mapping register sets.








          EMM Functions                                                 154





          Function 28. Alternate Map Register Set
          Get Alternate Map Register Set subfunction



                   The operating system first passes this pointer to the
                   memory manager whenever it invokes a Set Alternate Map
                   Register Set subfunction (the description follows).  If
                   the OS/E invokes this function before invoking a Set
                   Alternate Map Register Set subfunction, this function
                   returns a pointer value of zero.  The OS/E must have
                   allocated the space for the save area.  However, the OS
                   must request that the memory manager initialize the
                   contents of this save area before it contains any useful
                   information.

                   The OS/E must initialize the save area it has allocated
                   by invoking Function 15 (Get Page Map subfunction). 
                   After the OS/E has done this, the save area will contain
                   the state of all the map registers on all boards in the
                   system.  The save area will also contain any additional
                   information necessary to restore the boards to their
                   original state when the operating system invokes a Set
                   Alternate Map Register Set subfunction.


          REGISTERS MODIFIED

               AX, BX, ES:DI


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager got the alternate map register set.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.



          EMM Functions                                                 155





          Function 28. Alternate Map Register Set
          Get Alternate Map Register Set subfunction



               AH = A4h   NON-RECOVERABLE.
                   The operating system denied access to this function. 
                   The function cannot be used at this time.


          EXAMPLE

          alt_map_reg_set                        DB ?
          context_save_area_ptr_seg              DW ?
          context_save_area_ptr_offset           DW ?

          MOV   AX,5B00h                         ; load function code
          INT   67h                              ; call the memory manager
          OR    AH,AH                            ; check EMM status
          JNZ   emm_err_handler                  ; jump to error handler
                                                 ; on error
          MOV   alt_map_reg_set,BL
          TEST  BL,BL
          JNZ   no_ptr_returned

          MOV   context_save_area_ptr_seg,ES     ; save pointer values
          MOV   context_save_area_ptr_offset,DI

          no_ptr_returned:
























          EMM Functions                                                 156





          Function 28. Alternate Map Register Set
          Set Alternate Map Register Set subfunction



          Note............................................................
               This function is for use by operating systems only.  The
               operating system can disable this function at any time. 
               Refer to Function 30 for a description of how an operating
               system can enable or disable this function.


          PURPOSE

               The subfunction does one of two things, depending on the map
               register set specified:

               1.  If the alternate map register set specified is zero, map
                   register set zero is activated.  If the map register
                   context restore area pointer is not equal to zero, the
                   contents of the restore area pointed to by ES:DI are
                   copied into register set zero on each expanded memory
                   board in the system.  If the pointer is equal to zero,
                   the contents are not copied.

                   Regardless of its value, the map register context
                   restore area pointer is saved within the memory manager. 
                   It will be used during the Get Alternate Map Register
                   Set subfunction.

                   The operating system must supply the pointer to the
                   area.  This subfunction is intended to simulate setting
                   an alternate map register set.  Note that the operating
                   system must allocate the space for the context.  The
                   memory manager saves the context save area pointer
                   internally.

               2.  If the alternate map register set specified is not zero,
                   the alternate map register set specified is activated. 
                   The restore area, which the operating system is pointing
                   to, is not used.


          CALLING PARAMETERS

               AX = 5B01h
                   Contains the Set Alternate Map Register Set subfunction.






          EMM Functions                                                 157





          Function 28. Alternate Map Register Set
          Set Alternate Map Register Set subfunction



               BL = new alternate map register set number
                   Contains the number of the alternate map register set
                   which is to be activated.

                   If BL <> 0
                       A pointer to a map register context restore area is
                       not required and the contents of ES:DI is unaffected
                       and ignored.  The alternate map register set
                       specified in BL is activated if the board supports
                       it.

                   If BL = 0
                       A pointer to an area which contains the state of all
                       the map registers on all boards in the system, and
                       any additional information necessary to restore the
                       boards to their original state, has been passed in
                       ES:DI.

               ES:DI = pointer to a map register context restore area
                   Contains a pointer to an OS/E supplied map register
                   context restore area.  The pointer is in standard
                   segment:offset format.  This pointer must always be
                   passed if the expanded memory hardware does not supply
                   alternate mapping register sets.

                   The memory manager must save this pointer whenever the
                   OS/E invokes this function.  The OS/E must have allo-
                   cated the space for the restore area.  Additionally, the
                   contents of this restore area must have been initialized
                   by the memory manager before it will contain any useful
                   information.  The OS/E initializes the restore area it
                   has allocated by invoking Function 15 (Get Page Map
                   subfunction).  After the OS/E has done this, the restore
                   area will contain the state of the map registers on all
                   boards in the system, and any additional information
                   necessary to restore the boards to their original state
                   when the operating system invokes a Set Alternate Map
                   Register Set subfunction.


          REGISTERS MODIFIED

               AX





          EMM Functions                                                 158





          Function 28. Alternate Map Register Set
          Set Alternate Map Register Set subfunction



          STATUS

               AH = 0   SUCCESSFUL.
                   The manager set the alternate map register set.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = 9Ah   NON-RECOVERABLE.
                   Alternate map register sets are supported, but the
                   alternate map register set specified is not supported.

               AH = 9Ch   NON-RECOVERABLE.
                   Alternate map register sets are not supported, and the
                   alternate map register set specified is not zero.

               AH = 9Dh   NON-RECOVERABLE.
                   Alternate map register sets are supported, but the
                   alternate map register set specified is either not
                   defined or not allocated.

               AH = A3h   NON-RECOVERABLE.
                   The contents of the source array have been corrupted, or
                   the pointer passed to the subfunction is invalid.

               AH = A4h   NON-RECOVERABLE.
                   The operating system has denied access to this function. 
                   The function cannot be used at this time.








          EMM Functions                                                 159





          Function 28. Alternate Map Register Set
          Set Alternate Map Register Set subfunction



          EXAMPLE

          alt_map_reg_set                        DB ?
          context_restore_area_ptr_seg           DW ?
          context_restore_area_ptr_offset        DW ?

          MOV   AX,5B01h                         ; load function code
          MOV   BL,alt_map_reg_set
          TEST  BL,BL
          JZ    no_ptr_passed

          MOV   ES,context_restore_area_ptr_seg
          MOV   DI,context_restore_area_ptr_offset

          no_ptr_passed:

          INT   67h                              ; call the memory manger
          OR    AH,AH                            ; check EMM status
          JNZ   emm_err_handler                  ; jump to error handler
                                                 ; on error




























          EMM Functions                                                 160





          Function 28. Alternate Map Register Set
          Get Alternate Map Save Array Size subfunction



          Note............................................................
               This function is for use by operating systems only.  The
               operating system can disable this function at any time. 
               Refer to Function 30 for a description of how an operating
               system can enable or disable this function.


          PURPOSE

               This subfunction returns the storage requirements for the
               map register context save area referenced by the other
               subfunctions.


          CALLING PARAMETERS

               AX = 5B02h
                   Contains the Get Alternate Map Save Array Size subfunc-
                   tion.


          RESULTS

               These results are valid only if the status returned is zero.

               DX = size_of_array
                   Contains the number of bytes that will be transferred to
                   the memory area supplied by an operating system whenever
                   an operating system requests the Get, Set, or Get and
                   Set subfunction.


          REGISTERS MODIFIED

               AX, DX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has returned the array size.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.



          EMM Functions                                                 161





          Function 28. Alternate Map Register Set
          Get Alternate Map Save Array Size subfunction



               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = A4h   NON-RECOVERABLE.
                   The operating system has denied access to this function. 
                   The function cannot be used at this time.


          EXAMPLE

          size_of_array                  DW ?

          MOV   AX,5B02h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   size_of_array,DX         ; save size of array























          EMM Functions                                                 162





          Function 28. Alternate Map Register Set
          Allocate Alternate Map Register Set subfunction



          Note............................................................
               This function is for use by operating systems only.  The
               operating system can disable this function at any time. 
               Refer to Function 30 for a description of how an operating
               system can enable or disable this function.


          PURPOSE

               The Allocate Alternate Map Register Set subfunction gets the
               number of an alternate map register set for an operating
               system if an alternate map register set is currently
               available for use.  If the hardware does not support
               alternate map register sets, an alternate map register set
               number of zero will be returned.

               The alternate map register set allocated may be referred to
               by this number when using the Get or Set Alternate Map
               Register Set subfunctions.  The operating system can use
               these subfunctions to switch map contexts very rapidly on
               expanded memory boards with alternate map register sets.

               This subfunction copies the currently active alternate map
               register set's contents into the newly allocated alternate
               map register set's mapping registers.  This is done so that
               when the OS/E performs a Set Alternate Map Register Set
               subfunction the memory mapped before the allocation of the
               new alternate map will be available for reading and writing. 
               This function does not actually change the alternate map
               register set in use, but in addition to allocating a new
               alternate map register set, it prepares the new alternate
               map register set for a subsequent Set Alternate Map Register
               Set subfunction.


          CALLING PARAMETERS

               AX = 5B03h
                   Contains the Allocate Alternate Map Register Set
                   subfunction.








          EMM Functions                                                 163





          Function 28. Alternate Map Register Set
          Allocate Alternate Map Register Set subfunction



          RESULTS

               These results are valid only if the status returned is zero.

               BL = alternate map register set number
                   Contains the number of an alternate map register set. 
                   If there are no alternate map register sets supported by
                   the hardware, a zero will be returned.  In this case,
                   the Get Alternate Map function (Function 28) should be
                   invoked in order to obtain a pointer to a map register
                   context save area.  The OS/E must supply this area.  The
                   save area is necessary because the hardware doesn't
                   support alternate map register sets.


          REGISTERS MODIFIED

               AX, BX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has returned the alternate map register set
                   number.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = 9Bh   NON-RECOVERABLE.
                   Alternate map register sets are supported.  However, all
                   alternate map register sets are currently allocated.




          EMM Functions                                                 164





          Function 28. Alternate Map Register Set
          Allocate Alternate Map Register Set subfunction



               AH = A4h   NON-RECOVERABLE.
                   The operating system has denied access to this function. 
                   The function cannot be used at this time.


          EXAMPLE

          alt_map_reg_num                DB ?

          MOV   AX,5B03h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   alt_map_reg_num,BL       ; save number of
                                         ; alternate map register set

































          EMM Functions                                                 165





          Function 28. Alternate Map Register Set
          Deallocate Alternate Map Register Set subfunction



          Note............................................................
               This function is for use by operating systems only.  The
               operating system can disable this function at any time. 
               Refer to Function 30 for a description of how an operating
               system can enable or disable this function.


          PURPOSE

               The Deallocate Alternate Map Register Set subfunction
               returns the alternate map register set to the memory manager
               for future use.  The memory manager may reallocate the
               alternate map register set when needed.

               This subfunction also makes the mapping context of the
               alternate map register specified unavailable for reading or
               writing (unmapping).  This protects the pages previously
               mapped in an alternate map register set by making them
               inaccessible.  Note that the current alternate map register
               set cannot be deallocated.  This makes all memory which was
               currently mapped into conventional and expanded memory
               inaccessible.


          CALLING PARAMETERS

               AX = 5B04h
                   Contains the Deallocate Alternate Map Register Set
                   subfunction.

               BL = alternate register set number
                   Contains the number of the alternate map register set to
                   deallocate.  Map register set zero cannot be allocated
                   or deallocated.  However, if alternate map register set
                   zero is specified and this subfunction is invoked, no
                   error will be returned.  The function invocation is
                   ignored in this case.


          REGISTERS MODIFIED

               AX






          EMM Functions                                                 166





          Function 28. Alternate Map Register Set
          Deallocate Alternate Map Register Set subfunction



          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has deallocated the alternate map register
                   set.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = 9Ch   NON-RECOVERABLE.
                   Alternate map register sets are not supported and the
                   alternate map register set specified is not zero.

               AH = 9Dh   NON-RECOVERABLE.
                   Alternate map register sets are supported, but the
                   alternate map register set specified is either not
                   defined or not allocated.

               AH = A4h   NON-RECOVERABLE.
                   The operating system has denied access to this function. 
                   The function cannot be used at this time.


          EXAMPLE

          alternate_map_reg_set               DB ?

          MOV   BL,alternate_map_reg_set      ; specify alternate map
                                              ; register set
          MOV   AX,5B04h                      ; load function code
          INT   67h                           ; call the memory manager
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler
                                              ; on error


          EMM Functions                                                 167





          Function 28. Alternate Map Register Set
          Allocate DMA Register Set subfunction



          Note............................................................
               This function is for use by operating systems only.  The
               operating system can disable this function at any time. 
               Refer to Function 30 for a description of how an operating
               system can enable or disable this function.


          PURPOSE

               The Allocate DMA Register Set subfunction gets the number of
               a DMA register set for an OS/E, if a DMA register set is
               currently available for use.  If the hardware does not
               support DMA register sets, a DMA register set number of zero
               will be returned.

               In a multitasking operating system, when one task is waiting
               for DMA to complete, it is useful to be able to switch to
               another task.  However, if the DMA is being mapped through
               the current register set, the switching cannot occur.  That
               is, all DMA action must be complete before any remapping of
               pages can be done.

               The operating system would initiate a DMA operation on a
               specific DMA channel using a specific alternate map register
               set.  This alternate map register set would not be used
               again, by the operating system or an application, until
               after the DMA operation is complete.  The operating system
               guarantees this by not changing the contents of the alter-
               nate map register set, or allowing an application to change
               the contents of the alternate map register set, for the
               duration of the DMA operation.


          CALLING PARAMETERS

               AX = 5B05h
                   Contains the Allocate DMA Register Set subfunction.


          RESULTS

               These results are valid only if the status returned is zero.

               BL = DMA register set number
                   Contains the number of a DMA register set.  If there are
                   no DMA register sets supported by the hardware, a zero
                   will be returned.

          EMM Functions                                                 168





          Function 28. Alternate Map Register Set
          Allocate DMA Register Set subfunction



          REGISTERS MODIFIED

               AX, BX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has allocated the DMA register set.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = 9Bh   NON-RECOVERABLE.
                   DMA register sets are supported.  However, all DMA
                   register sets are currently allocated.

               AH = A4h   NON-RECOVERABLE.
                   Access to this function has been denied by the operating
                   system.  The function cannot be used at this time.


          EXAMPLE

          DMA_reg_set_number                  DB ?

          MOV   AX,5B05h                      ; load function code
          INT   67h                           ; call memory manager
          OR    AH,AH                         ; check EMM status
          JNZ   emm_err_handler               ; jump to error handler
                                              ; on error
          MOV   DMA_reg_set_number,BL         ; save number of DMA
                                              ; register set



          EMM Functions                                                 169





          Function 28. Alternate Map Register Set
          Enable DMA on Alternate Map Register Set subfunction



          Note............................................................
               This function is for use by operating systems only.  The
               operating system can disable this function at any time. 
               Refer to Function 30 for a description of how an operating
               system can enable or disable this function.


          PURPOSE

               This subfunction allows DMA accesses on a specific DMA
               channel to be associated with a specific alternate map
               register set.  In a multitasking operating system, when a
               task is waiting for the completion of DMA, it is useful to
               be able to switch to another task until the DMA operation
               completes.

               Any DMA on the specified channel will go through the speci-
               fied DMA register set regardless of the current register
               set.  If a DMA channel is not assigned to a DMA register
               set, DMA for that channel will be mapped through the current
               register set.


          CALLING PARAMETERS

               AX = 5B06h
                   Contains the Enable DMA on Alternate Map Register Set
                   subfunction.

               BL = DMA register set number
                   Contains the number of the alternate map register set to
                   be used for DMA operations on the DMA channel specified
                   by DL.  If the alternate map register set specified is
                   zero, no special action will be taken on DMA accesses
                   for the DMA channel specified.

               DL = DMA channel number
                   Contains the DMA channel which is to be associated with
                   the DMA map register set specified in BL.


          REGISTERS MODIFIED

               AX




          EMM Functions                                                 170





          Function 28. Alternate Map Register Set
          Enable DMA on Alternate Map Register Set subfunction



          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has enabled DMA on the DMA register set and
                   the DMA channel specified.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = 9Ah   NON-RECOVERABLE.
                   Alternate DMA register sets are supported, but the
                   alternate DMA register set specified is not supported.

               AH = 9Ch   NON-RECOVERABLE.
                   Alternate DMA register sets are not supported, and the
                   DMA register set specified is not zero.

               AH = 9Dh   NON-RECOVERABLE.
                   DMA register sets are supported, but the DMA register
                   set specified is either not defined or not allocated.

               AH = 9Eh   NON-RECOVERABLE.
                   Dedicated DMA channels are not supported.

               AH = 9Fh   NON-RECOVERABLE.
                   Dedicated DMA channels are supported, but the DMA
                   channel specified is not supported.

               AH = A4h   NON-RECOVERABLE.
                   The operating system has denied access to this function. 
                   The function cannot be used at this time.





          EMM Functions                                                 171





          Function 28. Alternate Map Register Set
          Enable DMA on Alternate Map Register Set subfunction



          EXAMPLE

          alt_map_reg_set                DB ?
          DMA_channel_num                DB ?

          MOV   BL,alt_map_reg_set
          MOV   DL,DMA_channel_num
          MOV   AX,5B06h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error





































          EMM Functions                                                 172





          Function 28. Alternate Map Register Set
          Disable DMA on Alternate Map Register Set subfunction



          Note............................................................
               This function is for use by operating systems only.  The
               operating system can disable this function at any time. 
               Refer to Function 30 for a description of how an operating
               system can enable or disable this function.


          PURPOSE

               This subfunction disables DMA accesses for all DMA channels
               which were associated with a specific alternate map register
               set.


          CALLING PARAMETERS

               AX = 5B07h
                   Contains the Disable DMA on Alternate Map Register Set
                   subfunction.

               BL = alternate register set number
                   Contains the number of the DMA register set for which
                   all operations are to be disabled.  If the alternate map
                   register set specified is zero, no action will be taken.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has disabled DMA operations on the alternate
                   DMA register set.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.




          EMM Functions                                                 173





          Function 28. Alternate Map Register Set
          Disable DMA on Alternate Map Register Set subfunction



               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = 9Ah   NON-RECOVERABLE.
                   Alternate DMA register sets are supported, but the
                   alternate DMA register set specified is not supported.

               AH = 9Ch   NON-RECOVERABLE.
                   Alternate DMA register sets are not supported, and the
                   DMA register set specified is not zero.

               AH = 9Dh   NON-RECOVERABLE.
                   DMA register sets are supported, but the DMA register
                   set specified is either not defined or not allocated.

               AH = 9Eh   NON-RECOVERABLE.
                   Dedicated DMA channels are not supported.

               AH = 9Fh   NON-RECOVERABLE.
                   Dedicated DMA channels are supported, but the DMA
                   channel specified is not supported.

               AH = A4h   NON-RECOVERABLE.
                   The operating system has denied access to this function. 
                   The function cannot be used at this time.


          EXAMPLE

          DMA_reg_set                    DB ?

          MOV   BL,DMA_reg_set
          MOV   AX,5B07h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error








          EMM Functions                                                 174





          Function 28. Alternate Map Register Set
          Deallocate DMA Register Set subfunction



          Note............................................................
               This function is for use by operating systems only.  The
               operating system can disable this function at any time. 
               Refer to Function 30 for a description of how an operating
               system can enable or disable this function.


          PURPOSE

               The Deallocate DMA Register Set subfunction deallocates the
               specified DMA register set.


          CALLING PARAMETERS

               AX = 5B08h
                   Contains the Deallocate DMA Register Set subfunction.

               BL = DMA register set number
                   Contains the number of the DMA register set which should
                   not be used for DMA operations any longer.  The DMA
                   register set would have been previously allocated and
                   enabled for DMA operations on a specific DMA channel. 
                   If the DMA register set specified is zero, no action
                   will be taken.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has deallocated the DMA register set.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.




          EMM Functions                                                 175





          Function 28. Alternate Map Register Set
          Deallocate DMA on Alternate Map Register Set subfunction



               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = 9Ch   NON-RECOVERABLE.
                   DMA register sets are not supported, and the DMA
                   register set specified is not zero.

               AH = 9Dh   NON-RECOVERABLE.
                   DMA register sets are supported, but the DMA register
                   set specified is either not defined or not allocated.

               AH = A4h   NON-RECOVERABLE.
                   The operating system has denied access to this function. 
                   The function cannot be used at this time.


          EXAMPLE

          DMA_reg_set_num                DB ?

          MOV   BL,DMA_reg_set_num
          MOV   AX,5B08h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error



















          EMM Functions                                                 176





          Function 29. Prepare Expanded Memory Hardware For Warm Boot



          PURPOSE

               This function prepares the expanded memory hardware for an
               impending warm boot.  This function assumes that the next
               operation that the operating system performs is a warm boot
               of the system.  In general, this function will effect the
               current mapping context, the alternate register set in use,
               and any other expanded memory hardware dependencies which
               need to be initialized at boot time.  If an application
               decides to map memory below 640K, the application must trap
               all possible conditions leading to a warm boot and invoke
               this function before performing the warm boot itself.


          CALLING PARAMETERS

               AH = 5Ch
                   Contains the Prepare Expanded Memory Hardware for Warm
                   Boot function.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The manager has prepared the expanded memory hardware
                   for a warm boot.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.






          EMM Functions                                                 177





          Function 29. Prepare Expanded Memory Hardware for Warm Boot



          EXAMPLE

          MOV   AH,5Ch                   ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error











































          EMM Functions                                                 178





          Function 30. Enable/Disable OS/E Function Set Functions
          Enable OS/E Function Set subfunction



          Note............................................................
               This function is for use by operating systems only.  The
               operating system can disable this function at any time.


          PURPOSE

               This subfunction provides an OS/E with the ability to enable
               all programs or device drivers to use the OS/E specific
               functions.  The capability is provided only for an OS/E
               which manages regions of mappable conventional memory and
               cannot permit programs to use any of the functions which
               affect mappable conventional memory regions, but must be
               able to use these functions itself.  When an OS/E disables
               these functions and a program attempts to use them, the
               memory manager returns a status to the program indicating
               that the OS/E has denied the program access to the function. 
               In other words, the functions will not work when disabled. 
               However, all programs may use them when enabled.

               The OS/E (Operating System/Environment) functions this
               subfunction enables are:

               Function 26.  Get Expanded Memory Hardware Information.
               Function 28.  Alternate Map Register Sets.
               Function 30.  Enable/Disable Operating System Functions.

               It appears contradictory that the OS/E can re-enable these
               functions when the function which re-enables them is itself
               disabled.  An overview of the process follows.

               The memory manager enables all the OS/E specific functions,
               including this one, when it is loaded.  The OS/E gets
               exclusive access to these functions by invoking either of
               the Enable/Disable OS/E Function Set subfunctions before any
               other software does.

               On the first invocation of either of these subfunctions, the
               memory manager returns an access_key which the OS/E must use
               in all future invocations of either of these subfunctions. 
               The memory manager does not require the access_key on the
               first invocation of the Enable/Disable OS/E Function Set
               subfunctions.





          EMM Functions                                                 179





          Function 30. Enable/Disable OS/E Function Set Functions
          Enable OS/E Function Set subfunction



               On all subsequent invocations, the access_key is required
               for either the Enable or Disable OS/E Function Set subfunc-
               tions.  Since the access_key is returned only on the first
               invocation of the Enable/Disable OS/E Function Set subfunc-
               tions, and presumably the OS/E is the first software to
               invoke this function, only the OS/E obtains a copy of this
               key.  The memory manager must return an access key with a
               random value, a fixed value key defeats the purpose of
               providing this level of security for an OS/E.


          CALLING PARAMETERS

               AX = 5D00h
                   Contains the Enable OS/E Function Set subfunction.

               BX,CX = access_key
                   Required on all function invocations after the first. 
                   The access_key value returned by the first function
                   invocation is required.


          RESULTS

               These results are valid only if the status returned is zero.

               BX,CX = access_key
                   Returned only on the first function invocation, the
                   memory manager returns a random valued key which will be
                   required thereafter for the execution of this function. 
                   On all invocations after the first, this key is not
                   returned.  Neither BX nor CX is affected after the first
                   time this function is invoked.


          REGISTERS MODIFIED

               AX, BX, CX


          STATUS

               AH = 0   SUCCESSFUL.
                   The operating system function set has been enabled.




          EMM Functions                                                 180





          Function 30. Enable/Disable OS/E Function Set Functions
          Enable OS/E Function Set subfunction



               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = A4h   NON-RECOVERABLE.
                   The operating system has denied access to this function. 
                   The function cannot be used at this time.  The value of
                   the key which was passed to this function does not
                   entitle the program to execute this function.


          EXAMPLE

          First invocation

          access_key                     DW 2 DUP (?)

          MOV   AX,5D00h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   access_key[0],BX
          MOV   access_key[2],CX


          All invocations after the first

          access_key                     DW 2 DUP (?)

          MOV   BX,access_key[0]
          MOV   CX,access_key[2]
          MOV   AX,5D00h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error


          EMM Functions                                                 181





          Function 30. Enable/Disable OS/E Function Set Functions
          Disable OS/E Function Set subfunction



          Note............................................................
               This function is for use by operating systems only.  The
               operating system can disable this function at any time.


          PURPOSE

               This subfunction provides an OS/E with the ability to
               disable all programs or device drivers from using the OS/E
               specific functions.  The capability is provided only for an
               OS/E which manages regions of mappable conventional memory
               and cannot permit programs to use any of the functions which
               would affect mappable conventional memory regions.  When an
               OS/E disables these functions and a program attempts to use
               them, the memory manager returns a status to the program
               indicating that the OS/E has denied the program access to
               the function.  In other words, the functions will not work
               when disabled.

               The OS/E (Operating System) functions which are disabled by
               this subfunction are:

               Function 26.  Get Expanded Memory Hardware Information.
               Function 28.  Alternate Map Register Sets.
               Function 30.  Enable/Disable Operating System Functions.


          CALLING PARAMETERS

               AX = 5D01h
                   Contains the Disable OS/E Function Set subfunction.

               BX,CX = access_key
                   Required on all function invocations after the first. 
                   The access_key value returned by the first function
                   invocation is required.












          EMM Functions                                                 182





          Function 30. Enable/Disable OS/E Function Set Functions
          Disable OS/E Function Set subfunction



          RESULTS

               These results are valid only if the status returned is zero.

               BX,CX = access_key
                   Returned only on the first function invocation, the
                   memory manager returns a random valued key which will be
                   required thereafter for the execution of this function. 
                   On all invocations after the first, this key is not
                   returned.  Neither BX nor CX is affected after the first
                   time this function is invoked.


          REGISTERS MODIFIED

               AX, BX, CX


          STATUS

               AH = 0   SUCCESSFUL.
                   The operating system function set has been disabled.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.

               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = A4h   NON-RECOVERABLE.
                   The operating system has denied access to this function. 
                   The function cannot be used at this time.  The value of
                   the key which was passed to this function does not
                   entitle the program to execute this function.





          EMM Functions                                                 183





          Function 30. Enable/Disable OS/E Function Set Functions
          Disable OS/E Function Set subfunction



          EXAMPLE

          First Function invocation

          access_key                     DW 2 DUP (?)

          MOV   AX,5D01h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
          MOV   access_key[0],BX
          MOV   access_key[2],CX


          All invocations after the first

          access_key                     DW 2 DUP (?)

          MOV   BX,access_key[0]
          MOV   CX,access_key[2]
          MOV   AX,5D01h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
























          EMM Functions                                                 184




 
          Function 30. Enable/Disable OS/E Function Set Functions
          Return Access Key subfunction



          Note............................................................
               This function is for use by operating systems only.  The
               operating system can disable this function at any time.


          PURPOSE

               This subfunction provides an OS/E with the ability to return
               the access key to the memory manager.  Returning the access
               key to the memory manager places the memory manager in the
               state it is in at installation time (regarding the use of
               the OS/E function set and the access key).  That is, access
               to the OS/E function set is enabled.  Upon execution of the
               next enable/disable OS/E function set subfunction, the
               access key will once again be returned.


          CALLING PARAMETERS

               AX = 5D02h
                   Contains the Return Access Key subfunction.

               BX,CX = access_key
                   Required on all function invocations.  The access_key
                   value returned by the first function invocation of the
                   enable or disable subfunctions is required.


          REGISTERS MODIFIED

               AX


          STATUS

               AH = 0   SUCCESSFUL.
                   The access key has been returned to the memory manager.

               AH = 80h   NON-RECOVERABLE.
                   The manager detected a malfunction in the memory manager
                   software.

               AH = 81h   NON-RECOVERABLE.
                   The manager detected a malfunction in the expanded
                   memory hardware.



          EMM Functions                                                 185





          Function 30. Enable/Disable OS/E Function Set Functions
          Return Access Key subfunction



               AH = 84h   NON-RECOVERABLE.
                   The function code passed to the memory manager is not
                   defined.

               AH = 8Fh   NON-RECOVERABLE.
                   The subfunction parameter is invalid.

               AH = A4h   NON-RECOVERABLE.
                   The operating system has denied access to this function. 
                   The function cannot be used at this time.  The value of
                   the key which was passed to this function does not
                   entitle the program to execute this function.


          EXAMPLE

          access_key                     DW 2 DUP (?)

          MOV   BX,access_key[0]
          MOV   CX,access_key[2]
          MOV   AX,5D02h                 ; load function code
          INT   67h                      ; call the memory manager
          OR    AH,AH                    ; check EMM status
          JNZ   emm_err_handler          ; jump to error handler on error
























          EMM Functions                                                 186





          Appendix A
          FUNCTION AND STATUS CODE CROSS REFERENCE TABLES



               This appendix contains two cross reference tables:  one
               lists the function codes and the status codes they return;
               the other lists the status codes and the functions that
               return them.


          Table A-1.  Function and Status Code Cross Reference
          ----------------------------------------------------------------

          Function        Status                 Description

          ----------------------------------------------------------------

             40h    00h, 80h, 81h, 84h   Get Memory Manager Status

             41h    00h, 80h, 81h, 84h   Get Page Frame Segment Address

             42h    00h, 80h, 81h, 84h   Get Unallocated Page Count

             43h    00h, 80h, 81h, 84h   Allocate Pages
                    85h, 87h, 88h, 89h

             44h    00h, 80h, 81h, 83h   Map/Unmap Handle Page
                    84h, 8Ah, 8Bh

             45h    00h, 80h, 81h, 83h   Deallocate Pages
                    84h, 86h

             46h    00h, 80h, 81h, 84h   Get EMM Version

             47h    00h, 80h, 81h, 83h   Save Page Map
                    84h, 8Ch, 8Dh

             48h    00h, 80h, 81h, 83h   Restore Page Map
                    84h, 8Eh

             49h                         Reserved

             4Ah                         Reserved

             4Bh    00h, 80h, 81h, 84h   Get EMM Handle Count

             4Ch    00h, 80h, 81h, 83h   Get EMM Handle Pages
                    84h

             4Dh    00h, 80h, 81h, 84h   Get All EMM Handle Pages


          Cross Reference Tables                                        187





          Table A-1.  Function and Status Code Cross Reference (continued)
          ----------------------------------------------------------------

          Function        Status                 Description

          ----------------------------------------------------------------

           4E00h    00h, 80h, 81h, 84h   Get Page Map
                    8Fh

           4E01h    00h, 80h, 81h, 84h   Set Page Map
                    8Fh, A3h

           4E02h    00h, 80h, 81h, 84h   Get & Set Page Map
                    8Fh, A3h

           4E03h    00h, 80h, 81h, 84h   Get Size of Page Map Save Array
                    8Fh

           4F00h    00h, 80h, 81h, 84h   Get Partial Page Map
                    8Bh, 8Fh, A3h

           4F01h    00h, 80h, 81h, 84h   Set Partial Page Map
                    8Fh, A3h

           4F02h    00h, 80h, 81h, 84h   Get Size of Partial Page Map Array
                    8Bh, 8Fh

           5000h    00h, 80h, 81h, 83h   Map/Unmap Multiple Handle Pages
                    84h, 8Ah, 8Bh, 8Fh   (physical page number mode)

           5001h    00h, 80h, 81h, 83h   Map/Unmap Multiple Handle Pages
                    84h, 8Ah, 8Bh, 8Fh   (segment address mode)

             51h    00h, 80h, 81h, 83h   Reallocate Pages
                    84h, 87h, 88h

           5200h    00h, 80h, 81h, 83h   Get Handle Attribute
                    84h, 8Fh, 91h

           5201h    00h, 80h, 81h, 83h   Set Handle Attribute
                    84h, 8Fh, 90h, 91h

           5202h    00h, 80h, 81h, 84h   Get Handle Attribute Capability
                    8Fh

           5300h    00h, 80h, 81h, 83h   Get Handle Name
                    84h, 8Fh

           5301h    00h, 80h, 81h, 83h   Set Handle Name
                    84h, 8Fh, A1h


          Cross Reference Tables                                        188





          Table A-1.  Function and Status Code Cross Reference (continued)
          ----------------------------------------------------------------

          Function        Status                 Description

          ----------------------------------------------------------------

           5400h    00h, 80h, 81h, 84h   Get Handle Directory
                    8Fh

           5401h    00h, 80h, 81h, 84h   Search for Named Handle
                    8Fh, A0h, A1h

           5402h    00h, 80h, 81h, 84h   Get Total Handles
                    8Fh

           5500h    00h, 80h, 81h, 83h   Alter Page Map & Jump (Physical   
                    84h, 8Ah, 8Bh, 8Fh   page mode)

           5501h    00h, 80h, 81h, 83h   Alter Page Map & Jump (Segment    
                    84h, 8Ah, 8Bh, 8Fh   address mode)

           5600h    00h, 80h, 81h, 83h   Alter Page Map & Call (Physical   
                    84h, 8Ah, 8Bh, 8Fh   page mode)

           5601h    00h, 80h, 81h, 83h   Alter Page Map & Call (Segment    
                    84h, 8Ah, 8Bh, 8Fh   address mode)

           5602h    00h, 80h, 81h, 84h   Get Alter Page Map & Call Stack   
                    8Fh                  Space Size

           5700h    00h, 80h, 81h, 83h   Move Memory Region
                    84h, 8Ah, 8Fh, 92h
                    93h, 94h, 95h, 96h
                    98h, A2h

           5701h    00h, 80h, 81h, 83h   Exchange Memory Region
                    84h, 8Ah, 8Fh, 93h
                    94h, 95h, 96h, 97h
                    98h, A2h

           5800h    00h, 80h, 81h, 84h   Get Mappable Physical Address     
                    8Fh                  Array

           5801h    00h, 80h, 81h, 84h   Get Mappable Physical Address     
                    8Fh                  Array Entries

           5900h    00h, 80h, 81h, 84h   Get Expanded Memory Hardware      
                    8Fh, A4h             Information

           5901h    00h, 80h, 81h, 84h   Get Unallocated Raw Page Count
                    8Fh

          Cross Reference Tables                                        189





          Table A-1.  Function and Status Code Cross Reference (continued)
          ----------------------------------------------------------------

          Function        Status                 Description

          ----------------------------------------------------------------

           5A00h    00h, 80h, 81h, 84h   Allocate Standard Pages
                    85h, 87h, 88h, 8Fh

           5A01h    00h, 80h, 81h, 84h   Allocate Raw Pages
                    85h, 87h, 88h, 8Fh

           5B00h    00h, 80h, 81h, 84h   Get Alternate Map Register Set
                    8Fh, A4h

           5B01h    00h, 80h, 81h, 84h   Set Alternate Map Register Set
                    8Fh, 9Ah, 9Ch, 9Dh
                    A3h, A4h

           5B02h    00h, 80h, 81h, 84h   Get Alternate Map Save Array Size
                    8Fh, A4h

           5B03h    00h, 80h, 81h, 84h   Allocate Alternate Map Register   
                    8Fh, 9Bh, A4h        Set

           5B04h    00h, 80h, 81h, 84h   Deallocate Alternate Map Register 
                    8Fh, 9Ch, 9Dh, A4h   Set

           5B05h    00h, 80h, 81h, 84h   Allocate DMA Register Set
                    8Fh, 9Bh, A4h

           5B06h    00h, 80h, 81h, 84h   Enable DMA on Alternate Map       
                    8Fh, 9Ah, 9Ch, 9Dh   Register Set
                    9Eh, 9Fh, A4h

           5B07h    00h, 80h, 81h, 84h   Disable DMA on Alternate Map      
                    8Fh, 9Ah, 9Ch, 9Dh   Register Set
                    9Eh, 9Fh, A4h

           5B08h    00h, 80h, 81h, 84h   Deallocate DMA Register Set
                    8Fh, 9Ch, 9Dh, A4h

             5Ch    00h, 80h, 81h, 84h   Prepare Expanded Memory Hardware
                                         for Warmboot

           5D00h    00h, 80h, 81h, 84h   Enable Operating System Function  
                    8Fh, A4h             Set

           5D01h    00h, 80h, 81h, 84h   Disable Operating System Function 
                    8Fh, A4h             Set


          Cross Reference Tables                                        190





          Table A-1.  Function and Status Code Cross Reference (continued)
          ----------------------------------------------------------------

          Function        Status                 Description

          ----------------------------------------------------------------

           5D02h    00h, 80h, 81h, 84h   Return Operating System Access Key
                    8Fh, A4h
          ----------------------------------------------------------------











































          Cross Reference Tables                                        191





          Table A-2.  Status and Function Code Cross Reference
          ----------------------------------------------------------------

          Status      Function                   Description

          ----------------------------------------------------------------

           00h   All                  The function completed normally.

           80h   All                  The memory manager has detected a
                                      malfunction in the expanded memory
                                      software.  A condition has been
                                      detected which would not have
                                      occurred if the memory manager had
                                      been operating correctly.

           81h   All                  The memory manager has detected a
                                      malfunction in the expanded memory
                                      hardware.  A condition has been
                                      detected which would not occur if the
                                      memory hardware were working correct-
                                      ly.  Diagnostics should be run on the
                                      expanded memory system to determine
                                      the source of the problem.

           82h   None                 This error code is not returned in
                                      version 3.2 of the memory manager or
                                      above.  In earlier versions of the
                                      memory manager this code meant a
                                      "busy" status.  This status indicated
                                      that the memory manager was already
                                      processing an expanded memory request
                                      when the current request was made and
                                      is unable to process another request. 
                                      In versions 3.2 of the memory manager
                                      and above, the memory manager is
                                      never "busy" and can always honor
                                      requests.

           83h   44h, 45h, 47h, 48h   The memory manager can not find the
                 4Ch, 5000h, 5001h    handle specified.  The program has
                 51h, 5200h, 5201h    probably corrupted its specified
                 5300h, 5301h         handle.  The memory manager does not
                 5500h, 5501h         have any information pertaining to
                 5600h, 5601h         the  specified handle.  The program
                 5700h, 5701h         has probably corrupted its handle.

           84h   All                  The function code passed to the
                                      manager is not currently defined. 
                                      Function codes in the range 40h
                                      through 5Dh are currently defined.


          Cross Reference Tables                                        192





          Table A-2.  Status and Function Code Cross Reference (continued)
          ----------------------------------------------------------------

          Status      Function                   Description

          ----------------------------------------------------------------

           85h   43h, 5A00h, 5A01h    No handles are currently available. 
                                      All assignable handles are currently
                                      in use.  The program may re-request
                                      the assignment of a handle in the
                                      hope that another program has
                                      released a handle.  The maximum
                                      number of handles that may be
                                      supported is 255.

           86h   45h                  A mapping context restoration error
                                      has been detected.  This error occurs
                                      when a program attempts to return a
                                      handle and there is still a "mapping
                                      context" on the context stack for the
                                      indicated handle.  A program can
                                      recover from this error by restoring
                                      the mapping context before returning
                                      the handle.

           87h   43h, 51h, 5A00h,     The number of total pages that are
                 5A01h                available in the system is insuffi-
                                      cient to honor the request.  The
                                      program can recover from this
                                      condition by requesting fewer pages.

           88h   43h, 51h, 5A00h,     The number of unallocated pages
                 5A01h                currently available is insufficient
                                      to honor the allocation request.  The
                                      program can recover from this
                                      condition by re-posting the request
                                      or by requesting fewer pages.

           89h   43h                  A Function 4 (Allocate Pages) request
                                      has been made specifying zero pages. 
                                      Zero pages cannot be assigned to a
                                      handle with Function 4 (Allocate
                                      Pages).  If it is necessary to assign
                                      zero pages to a handle, Function 27
                                      (Allocate Standard Pages and Allocate
                                      Raw Pages subfunctions) may be used.






          Cross Reference Tables                                        193





          Table A-2.  Status and Function Code Cross Reference (continued)
          ----------------------------------------------------------------

          Status      Function                   Description

          ----------------------------------------------------------------

           8Ah   44h, 5000h, 5001h    The logical page to map into memory
                 5500h, 5501h         is out of the range of logical pages
                 5600h, 5601h         which are allocated to the handle. 
                 5700h, 5701h         The program can recover from this
                                      condition by attempting to map a
                                      logical page which is within the
                                      bounds for the handle.

           8Bh   44h, 4F00h, 4F02h    One or more of the physical pages is
                 5000h, 5001h         out of the range of allowable
                 5600h, 5601h         physical pages.  Physical page
                 5500h, 5501          numbers are numbered zero-relative. 
                                      The program can recover from this
                                      condition by mapping at a physical
                                      page which is in the range from zero
                                      to three.

           8Ch   47h                  The mapping register context save
                                      area is full.  The program can
                                      recover from this condition by
                                      attempting to save the mapping
                                      registers again.

           8Dh   47h                  The mapping register context stack
                                      already has a context associated with
                                      the handle.  The program has at-
                                      tempted to save the mapping register
                                      context when there was already a
                                      context for the handle on the stack. 
                                      The program can recover from this
                                      condition by not attempting to save
                                      the context again (this assumes the
                                      mapping register context on the stack
                                      for the handle is correct).












          Cross Reference Tables                                        194





          Table A-2.  Status and Function Code Cross Reference (continued)
          ----------------------------------------------------------------

          Status      Function                   Description

          ----------------------------------------------------------------

           8Eh   48h                  The mapping register context stack
                                      does not have a context associated
                                      with the handle.  The program has
                                      attempted to restore the mapping
                                      register context when there was no
                                      context for the handle on the stack. 
                                      The program can recover from this
                                      condition by not attempting to
                                      restore the context again (this
                                      assumes the current mapping register
                                      context is correct).

           8Fh   All functions        The subfunction parameter passed to
                 requiring            the function is not defined.
                 subfunction codes

           90h   5201h                The attribute type is undefined.

           91h   5200h, 5201h         The system configuration does not
                                      support non-volatility.

           92h   5700h                The source and destination expanded
                                      memory regions have the same handle
                                      and overlap.  This is valid for a
                                      move.  The move has been completed
                                      and the destination region has a full
                                      copy of the source region.  However,
                                      at least a portion of the source
                                      region has been overwritten by the
                                      move.  Note that the source and
                                      destination expanded memory regions
                                      with different handles will never
                                      physically overlap because the
                                      different handles specify totally
                                      different regions of expanded memory.











          Cross Reference Tables                                        195





          Table A-2.  Status and Function Code Cross Reference (continued)
          ----------------------------------------------------------------

          Status      Function                   Description

          ----------------------------------------------------------------

           93h   5700h, 5701h         The length of the specified source or
                                      destination expanded memory region
                                      exceeds the length of the expanded
                                      memory region allocated to the
                                      specified source or destination
                                      handle.  There are insufficient pages
                                      allocated to this handle to move/ex-
                                      change a region of the size speci-
                                      fied.  The program can recover from
                                      this condition by attempting to
                                      allocate additional pages to the
                                      destination or source handle or by
                                      reducing the specified length. 
                                      However, if the application program
                                      has allocated as much expanded memory
                                      as it thought it needed, this may be
                                      a program error and is therefore not
                                      recoverable.

           94h   5700h, 5701h         The conventional memory region and
                                      expanded memory region overlap.  This
                                      is invalid, the conventional memory
                                      region cannot overlap the expanded
                                      memory region.

           95h   5700h, 5701h         The offset within the logical page
                                      exceeds the length of the logical
                                      page.  The initial source or destina-
                                      tion offsets within an expanded
                                      memory region must be between 0 and
                                      the (length of a logical page - 1) or
                                      16383 (3FFFh).

           96h   5700h, 5701h         Region length exceeds 1M-byte limit.












          Cross Reference Tables                                        196





          Table A-2.  Status and Function Code Cross Reference (continued)
          ----------------------------------------------------------------

          Status      Function                   Description

          ----------------------------------------------------------------

           97h   5701h                The source and destination expanded
                                      memory regions have the SAME handle
                                      AND overlap.  This is invalid; the
                                      source and destination expanded
                                      memory regions cannot have the same
                                      handle and overlap when they are
                                      being exchanged.  Note that the
                                      source and destination expanded
                                      memory regions with different handles
                                      will never physically overlap because
                                      the different handles specify totally
                                      different regions of expanded memory.

           98h   5700h, 5701h         The memory source and destination
                                      types are undefined/not supported.

           9Ah   5B01h, 5B06h         Alternate map register sets are
                 5B07h                supported, but the alternate map
                                      register set specified is not
                                      supported.

           9Bh   5B03h, 5B05h         Alternate map/DMA register sets are
                                      supported.  However, all alternate
                                      map/DMA register sets are currently
                                      allocated.

           9Ch   5B01h, 5B04h         Alternate map/DMA register sets are
                 5B06h, 5B07h         not supported, and the alternate
                 5B08h                map/DMA register set specified is not
                                      zero.

           9Dh   5B01h, 5B04h         Alternate map/DMA register sets are
                 5B06h, 5B07h         supported, but the alternate map
                 5B08h                register set specified is not
                                      defined, not allocated, or is the
                                      currently allocated map register set.

           9Eh   5B06h, 5B07h         Dedicated DMA channels are not
                                      supported.

           9Fh   5B06h, 5B07h         Dedicated DMA channels are supported. 
                                      But the DMA channel specified is not
                                      supported.



          Cross Reference Tables                                        197





          Table A-2.  Status and Function Code Cross Reference (continued)
          ----------------------------------------------------------------

          Status      Function                   Description

          ----------------------------------------------------------------

           A0h   5401h                No corresponding handle value could
                                      be found for the handle name speci-
                                      fied.

           A1h   5301h, 5401h         A handle with this name already
                                      exists.  The specified handle was not
                                      assigned a name.

           A2h   5700h, 5701h         An attempt was made to "wrap around"
                                      the 1M-byte address space during the
                                      move/exchange.  The source starting
                                      address together with the length of
                                      the region to be moved/exchanged
                                      exceeds 1M bytes.  No data was
                                      moved/exchanged.

           A3h   4E01h, 4E02h         The contents of the data structure
                 4F00h, 4F01h         passed to the function have either
                 5B01h                been corrupted or are meaningless.

           A4h   5900h, 5B00h         The operating system has denied
                 5B01h, 5B02h         access to this function.  The 
                 5B03h, 5B04h         function cannot be used at this time.
                 5B05h, 5B06h
                 5B07h, 5B08h
                 5D00h, 5D01h
                 5D02h

          ----------------------------------------------------------------

















          Cross Reference Tables                                        198





          Appendix B
          TESTING FOR THE PRESENCE OF THE EXPANDED MEMORY MANAGER



               Before an application program can use the Expanded Memory
               Manager, it must determine whether DOS has loaded the
               manager.  This appendix describes two methods your program
               can use to test for the presence of the memory manager and
               how to choose the correct one for your situation.

               The first method uses the DOS "open handle" technique; the
               second method uses the DOS "get interrupt vector" technique.


          Which method should your program use?

               The majority of application programs can use either the
               "open handle" or the "get interrupt vector" method. 
               However, if your program is a device driver or if it
               interrupts DOS during file system operations, you must use
               only the "get interrupt vector" method.

               Device drivers execute from within DOS and can't access the
               DOS file system; programs that interrupt DOS during file
               system operations have a similar restriction.  During their
               interrupt processing procedures, they can't access the DOS
               file system because another program may be using the system. 
               Since the "get interrupt vector" method doesn't require the
               DOS file system, you must use it for these types of pro-
               grams.


          The "open handle" technique

               Most application programs can use the DOS "open handle"
               technique to test for the presence of the memory manager. 
               This section describes how to use the technique and gives an
               example.

          Caution.........................................................
               Don't use this technique if your program is a device driver
               or if it interrupts DOS during file system operations.  Use
               the "get interrupt vector" technique described later in this
               appendix.


          Using the "open handle" technique

               This section describes how to use the DOS "open handle"
               technique to test for the presence of the memory manager. 
               Follow these steps in order:

          Testing For The Presence Of The EMM                           199





               1.  Issue an "open handle" command (DOS function 3Dh) in
                   "read only" access mode (register AL = 0).  This
                   function requires your program to point to an ASCII
                   string which contains the path name of the file or
                   device in which you're interested (register set DS:DX
                   contains the pointer).  In this case the file is
                   actually the name of the memory manager.

                   You should format the ASCII string as follows:

                   ASCII_device_name  DB  'EMMXXXX0', 0

                   The ASCII codes for the capital letters EMMXXXX0 are
                   terminated by a byte containing a value of zero.

               2.  If DOS returns no error status code, skip Steps 3 and 4
                   and go to Step 5.  If DOS returns a "Too many open
                   files" error status code, go to Step 3.  If DOS returns
                   a "File/Path not found" error status code, skip Step 3
                   and go to Step 4.

               3.  If DOS returns a "Too many open files" (not enough
                   handles) status code, your program should invoke the
                   "open file" command before it opens any other files. 
                   This will guarantee that at least one file handle will
                   be available to perform the function without causing
                   this error.

                   After the program performs the "open file" command, it
                   should perform the test described in Step 6 and close
                   the "file handle" (DOS function 3Eh).  Don't keep the
                   manager "open" after this status test is performed since
                   "manager" functions are not available through DOS.  Go
                   to Step 6.

               4.  If DOS returns a "File/Path not found," the memory
                   manager is not installed.  If your application requires
                   the memory manager, the user will have to reboot the
                   system with a disk containing the memory manager and the
                   appropriate CONFIG.SYS file before proceeding.

               5.  If DOS doesn't return an error status code you can
                   assume that either a device with the name EMMXXXX0 is
                   resident in the system, or a file with this name is on
                   disk in the current disk drive.  Go to Step 6.

               6.  Issue an "I/O Control for Devices" command (DOS function
                   44h) with a "get device information" command (register
                   AL = 0).  DOS function 44h determines whether EMMXXXX0
                   is a device or a file.



          Testing For The Presence Of The EMM                           200





                   You must use the file handle (register BX) which you
                   obtained in Step 1 to access the "EMM" device.

                   This function returns the "device information" in a word
                   (register DX).  Go to Step 7.

               7.  If DOS returns any error status code, you should assume
                   that the memory manager device driver is not installed. 
                   If your application requires the memory manager, the
                   user will have to reboot the system with a disk contain-
                   ing the memory manager and the appropriate CONFIG.SYS
                   file before proceeding.

               8.  If DOS didn't return an error status, test the contents
                   of bit 7 (counting from 0) of the "device information"
                   word (register DX) the function returned.  Go to Step 9.

               9.  If bit 7 of the "device information" word contains a
                   zero, then EMMXXXX0 is a file, and the memory manager
                   device driver is not present.  If your application
                   requires the memory manager, the user will have to
                   reboot the system with a disk containing the memory
                   manager and the appropriate CONFIG.SYS file before
                   proceeding.

                   If bit 7 contains a one, then EMMXXXX0 is a device.  Go
                   to Step 10.

               10. Issue an "I/O Control for Devices" command (DOS function
                   44h) with a "get output status" command (register AL =
                   7).

                   You must use the file handle you obtained in Step 1 to
                   access the "EMM" device (register BX).  Go to Step 11.

               11. If the expanded memory device driver is "ready," the
                   memory manager passes a status value of "FFh" in
                   register AL.  The status value is "00h" if the device
                   driver is "not ready."

                   If the memory manager device driver is "not ready" and
                   your application requires its presence, the user will
                   have to reboot the system with a disk containing the
                   memory manager and the appropriate CONFIG.SYS file
                   before proceeding.

                   If the memory manager device driver is "ready," go to
                   Step 12.





          Testing For The Presence Of The EMM                           201





               12. Issue a "Close File Handle" command (DOS function 3Eh)
                   to close the expanded memory device driver.  You must
                   use the file handle you obtained in Step 1 to close the
                   "EMM" device (register BX).

















































          Testing For The Presence Of The EMM                           202





          An example of the "open handle" technique

               The following procedure is an example of the "open handle"
               technique outlined in the previous section.

          ;--------------------------------------------------------------;
          ;    The following procedure tests for the presence of the     ;
          ;    EMM in the system.  It returns the CARRY FLAG SET if      ;
          ;    the EMM is present.  If the EMM is not present, this      ;
          ;    procedure returns the CARRY FLAG CLEAR.                   ;
          ;--------------------------------------------------------------;

          first_test_for_EMM  PROC  NEAR
          PUSH  DS
          PUSH  CS
          POP   DS
          MOV   AX,3D00h                       ; issue "device open" in
          LEA   DX,ASCII_device_name           ; "read only" mode
          INT   21h
          JC    first_test_for_EMM_error_exit  ; test for error
                                               ; during "device open"
          MOV   BX,AX                          ; get the "file
                                               ; handle" returned by DOS
          MOV   AX,4400h                       ; issue "IOCTL
          INT   21h                            ; get device info"
          JC    first_test_for_EMM_error_exit  ; test for error
                                               ; during "get device info"
          TEST  DX,0080h                       ; test to determine
          JZ    first_test_for_EMM_error_exit  ; ASCII_device_name
                                               ; is a device or a file
          MOV   AX,4407h                       ; issue "IOCTL"
          INT   21h
          JC    first_test_for_EMM_error_exit  ; test for error
                                               ; during "IOCTL"
          PUSH  AX                             ; save "IOCTL" status
          MOV   AH,3Eh                         ; issue "close
          INT   21h                            ; file handle"
          POP   AX                             ; restore "IOCTL" status
          CMP   AL,0FFh                        ; test for "device
          JNE   first_test_for_EMM_error_exit  ; ready" status
                                               ; returned by the driver
          first_test_for_EMM_exit:
          POP   DS                             ; EMM is present
          STC                                  ; in the system
          RET

          first_test_for_EMM_error_exit:
          POP   DS                             ; EMM is NOT present
          CLC                                  ; in the system
          RET
          ASCII_device_name   DB  'EMMXXXX0', 0
          first_test_for_EMM  ENDP

          Testing For The Presence Of The EMM                           203





          The "get interrupt vector" technique

               Any type of program can use the DOS "get interrupt vector"
               technique to test for the presence of the memory manager. 
               This section describes how to use the technique and gives an
               example.

          Caution.........................................................
               Be sure to use this technique (and not the "open handle"
               technique) if your program is a device driver or if it
               interrupts DOS during file system operations.


          Using the "get interrupt vector" technique

               This section describes how to use the DOS "get interrupt
               vector" technique to test for the presence of the memory
               manager.  Follow these steps in order:

               1.  Issue a "get vector" command (DOS function 35h) to
                   obtain the contents of interrupt vector array entry
                   number 67h (addresses 0000:019Ch thru 0000:019Fh).

                   The memory manager uses this interrupt vector to perform
                   all manager functions.  The offset portion of this
                   interrupt service routine address is stored in the word
                   located at address 0000:019Ch; the segment portion is
                   stored in the word located at address 0000:019Eh.

               2.  Compare the "device name field" with the contents of the
                   ASCII string which starts at the address specified by
                   the segment portion of the contents of interrupt vector
                   address 67h and a fixed offset of 000Ah.  If DOS loaded
                   the memory manager at boot time this name field will
                   have the name of the device in it.

                   Since the memory manager is implemented as a character
                   device driver, its program origin is 0000h.  Device
                   drivers are required to have a "device header" located
                   at the program origin.  Within the "device header" is an
                   8 byte "device name field."  For a character mode device
                   driver this name field is always located at offset 000Ah
                   within the device header.  The device name field
                   contains the name of the device which DOS uses when it
                   references the device.

                   If the result of the "string compare" in this technique
                   is positive, the memory manager is present.





          Testing For The Presence Of The EMM                           204





          An example of the "get interrupt vector" technique

               The following procedure is an example of the "get interrupt
               vector" technique outlined in the previous section.


          ;--------------------------------------------------------------;
          ;    The following procedure tests for the presence of the     ;
          ;    EMM in the system.  It returns the CARRY FLAG SET if      ;
          ;    the EMM is present.  If the EMM is not present, this      ;
          ;    procedure returns the CARRY FLAG CLEAR.                   ;
          ;--------------------------------------------------------------;


          second_test_for_EMM  PROC  NEAR
          PUSH  DS
          PUSH  CS
          POP   DS
          MOV   AX,3567h                       ; issue "get interrupt
                                               ; vector"
          INT   21h
          MOV   DI,000Ah                       ; use the segment in ES
                                               ; returned by DOS, place
                                               ; the "device name field"
                                               ; OFFSET in DI
          LEA   SI,ASCII_device_name           ; place the OFFSET of the
                                               ; device name string in SI,
                                               ; the SEGMENT is already
                                               ; in DS
          MOV   CX,8                           ; compare the name strings
          CLD
          REPE  CMPSB
          JNE   second_test_for_EMM_error_exit

          second_test_for_EMM_exit:
          POP   DS                             ; EMM is present in
          STC                                  ; the system
          RET

          second_test_for_EMM_error_exit:
          POP   DS                             ; EMM is NOT present
          CLC                                  ; in the system
          RET

          ASCII_device_name   DB  'EMMXXXX0'
          second_test_for_EMM ENDP







          Testing For The Presence Of The EMM                           205





          Appendix C
          EXPANDED MEMORY MANAGER IMPLEMENTATION GUIDELINES



               In addition to the functional specification, the expanded
               memory manager should provide certain resources.  The
               following guidelines are provided so required resources are
               present in expanded memory managers which comply with this
               version of the LIM specification.

               o   The amount of expanded memory supported:
                   Up to a maximum of 32M bytes of expanded memory should
                   be supported.

               o   The number of handles supported:
                   The maximum number of expanded memory handles provided
                   should be 255, the minimum should be 64.

               o   Handle Numbering:
                   Although a handle is a word quantity, there is a maximum
                   of 255 handles, including the operating system handle. 
                   This specification defines the handle word as follows: 
                   the low byte of the word is the actual handle value, the
                   high byte of the handle is set to 00h by the memory
                   manager.  Previous versions of this specification did
                   not specify the value of handles.

               o   New handle type:  Handles versus Raw Handles:
                   The difference between a raw handle and a regular handle
                   is slight.  If you use Function 27 to "Allocate raw
                   pages to a handle," what is returned in DX is termed a
                   raw handle.  The raw handle does not necessarily refer
                   to 16K-byte pages.  Instead it refers to the "raw" page
                   size, which depends on the expanded memory hardware.

                   An application program can use Function 26 to find the
                   raw page size, and by using the raw handle Function 27
                   returns, it can access them with the finer resolution
                   that a particular expanded memory board may allow.

                   On the other hand, applications which use Function 4 to
                   "allocate pages to handle" receive a handle which always
                   refers to 16K-byte pages.  On expanded memory boards
                   with smaller raw pages, the EMM driver will allocate and
                   maintain the number of raw pages it takes to create a
                   single composite 16K-byte page.  The difference between
                   the expanded memory boards' raw page size and the 16K-
                   byte LIM page size is transparent to the application
                   when it is using a handle obtained with Function 4.



          EMM Implementation Guidelines                                 206





                   The memory manager must differentiate between pages
                   allocated to handles and pages allocated to raw handles. 
                   The meaning of a call to the driver changes depending on
                   whether a handle or a raw handle is passed to the memory
                   manager.  If, for example, a handle is passed to
                   Function 18 (Reallocate), the memory manager will
                   increase or decrease the number of 16K-byte pages
                   allocated to the handle.  If Function 18 is passed a raw
                   handle, the memory manager will increase or decrease the
                   number of raw (non-16K-byte) pages allocated to the raw
                   handle.  For LIM standard boards, there is no difference
                   between pages and raw pages.

               o   The system Raw Handle (Raw Handle = 0000h):
                   For expanded memory boards that can remap the memory in
                   the lower 640K-byte address space, managing the pages of
                   memory which are used to fill in the lower 640K can be a
                   problem.  To solve this problem, the memory manager will
                   create a raw handle with a value of 0000h when DOS loads
                   the manager.  This raw handle is called the system
                   handle.

                   At power up, the memory manager will allocate all of the
                   pages that are mapped into the lower 640K bytes to the
                   system handle.  These pages should be mapped in their
                   logical order.  For example, if the system board
                   supplies 256K bytes of RAM, and the 384K bytes above it
                   is mappable, the system handle should have its logical
                   page zero mapped into the first physical page at 256K,
                   its logical page one mapped into the next physical page,
                   and so on.

                   The system handle should deal with raw pages.  To
                   release some of these pages so application programs can
                   use them, an operating system could decrease the number
                   of pages allocated to the system handle with the
                   "Reallocate" function.  Invoking the "Deallocate"
                   function would decrease the system handle to zero size,
                   but it must not deallocate the raw handle itself.  The
                   "Deallocate" function treats the system handle dif-
                   ferently than it treats other raw handles.  If the
                   operating system can ever be "exited" (for example, the
                   way Windows can be exited), it must increase the size of
                   the system handle back to what is needed to fill 640K
                   and map these logical pages back into physical memory
                   before returning to DOS.







          EMM Implementation Guidelines                                 207





                   There are two functional special cases for this handle:

                   -   The first special case deals with Function 4
                       (Allocate Pages).  This function must never return
                       zero as a handle value.  Applications must always
                       invoke Function 4 to allocate pages and obtain a
                       handle which identifies its pages.  Since Function 4
                       will never return a handle value of zero, an
                       application will never gain access to this special
                       handle.

                   -   The second special case deals with Function 6
                       (Deallocate Pages).  If the operating system uses
                       Function 6 to deallocate the pages which are
                       allocated to the system handle, the pages will be
                       returned to the manager for use, but the handle will
                       not be available for reassignment.  The manager
                       should treat a "deallocate pages" function request
                       for this handle the same as a "reallocate pages"
                       function request, where the number of pages to
                       reallocate to this handle is zero.

               o   Terminate and Stay Resident (TSR) Program Cooperation:
                   In order for TSR's to cooperate with each other and with
                   other applications, TSR's must follow this rule:  a
                   program may only remap the DOS partition it lives in. 
                   This rule applies at all times, even when no expanded
                   memory is present.

               o   Accelerator Cards:
                   To support generic accelerator cards, the support of
                   Function 34, as defined by AST, is encouraged.





















          EMM Implementation Guidelines                                 208





          Appendix D
          OPERATING SYSTEM/ENVIRONMENT USE OF FUNCTION 28



               All expanded memory boards have a set of registers that
               "remember" the logical to physical page mappings.  Some
               boards have extra (or alternate) sets of these mapping
               registers.  Because no expanded memory board can supply an
               infinite number of alternate map register sets, this
               specification provides a way to simulate them using Function
               28 (Alternate Map Register Set).


          Examples

               For the examples in this section, assume the hardware
               supports alternate map register sets.  First Windows is
               brought up, then "Reversi" is started.  Then control is
               switched back to the MS-DOS Executive.  For this procedure,
               here are the calls to the expanded memory manager:

                                      Example 1

          Allocate alt reg set           ; Start up the MS-DOS
          (for MS-DOS Executive)         ; Executive

          Set alt reg set
          (for MS-DOS Executive)

          Allocate alt reg set           ; Start up Reversi
          (for Reversi)

          Set alt reg set
          (for Reversi)

          Map pages
          (for Reversi)

          Set alt ret set                ; Switch back to MS-DOS
          (for MS-DOS Executive)         ; Executive












          Operating System Use Of Function 28                           209





               Notice this procedure needed no "get" calls because the
               register set contained all the information needed to save a
               context.  However, using "Get" calls would have no ill
               effects.

                                      Example 2

          Allocate alt reg set           ; Start up MS-DOS
          (for MS-DOS Executive)         ; Executive

          Set alt reg set
          (for MS-DOS Executive)

          Get alt reg set
          (for MS-DOS Executive)

          Allocate alt reg set           ; Start up Reversi
          (for Reversi)

          Set alt reg set
          (for Reversi)

          Map pages
          (for Reversi)

          Get alt reg set
          (for Reversi)

          Set alt reg set                ; Switch back to MS-DOS
          (for MS-DOS Executive)         ; Executive

               The important point to follow is that a Set must always
               precede a Get.  The model of Set then Get is the inverse of
               what interrupt handlers use, which is Get then Set (Get the
               old map context and Set the new one).  Another crucial point
               is that an alternate map register set must have the current
               mapping when allocated; otherwise, the Set will create
               chaos.

               What happens if this is simulated in software?  The same Set
               and Get model applies.  The main difference is where the
               context is saved.











          Operating System Use Of Function 28                           210





               Since the allocate call is dynamic and there is no limit on
               the number of sets allocated, the OS/E must supply the space
               required.  Device drivers cannot allocate space dynamically,
               since the request would fail.  If the Allocate register set
               call returns a status indicating the alternate map register
               sets aren't supported, the OS/E must allocate space for the
               context.  It must also initialize the context using Function
               15.  At that point it can do the Set, passing a pointer to
               the map context space.  On the Get call, the EMM driver is
               to return a pointer to the same context space.

                                      Example 3

          Allocate alt reg set           ; Start up MS-DOS
          (for MS-DOS Executive)         ; Executive

          Get Page Map
          (for MS-DOS Executive)

          Set alt reg set
          (for MS-DOS Executive)

          Allocate alt reg set           ; Start up Reversi
          (for Reversi)

          Set alt reg set
          (for Reversi)

          Map pages
          (for Reversi)

          Get Page Map
          (for Reversi)

          Set alt ret set                ; Switch back to MS-DOS
          (for MS-DOS Executive)         ; Executive

















          Operating System Use Of Function 28                           211





          GLOSSARY



               The following terms are used frequently in this specifica-
               tion:


          Allocate                 To reserve a specified amount of
                                   expanded memory pages.

          Application Program      An application program is the program
                                   you write and your customer uses.  Some
                                   categories of application software are
                                   word processors, database managers,
                                   spreadsheet managers, and project
                                   managers.

          Conventional memory      The memory between 0 and 640K bytes,
                                   address range 00000h thru 9FFFFh.

          Deallocate               To return previously allocated expanded
                                   memory to the memory manager.

          EMM                      See Expanded Memory Manager.

          Expanded memory          Expanded memory is memory outside DOS's
                                   640K-byte limit (usually in the range of
                                   C0000h thru EFFFFh).

          Expanded Memory          A device driver that controls the
            Manager (EMM)          interface between DOS application
                                   programs and expanded memory.

          Extended memory          The 15M-byte address range between
                                   100000h thru FFFFFFh available on an
                                   80286 processor when it is operating in
                                   protected virtual address mode.

          Handle                   A value that the EMM assigns and uses to
                                   identify a block of memory requested by
                                   an application program.  All allocated
                                   logical pages are associated with a
                                   particular handle.

          Logical Page             The EMM allocates expanded memory in
                                   units (typically 16K bytes) called
                                   logical pages.

          Mappable Segment         A 16K-byte region of memory which can
                                   have a logical page mapped at it.


          Glossary                                                      212





          Map Registers            The set of registers containing the
                                   current mapping context of the EMM
                                   hardware.

          Mapping                  The process of making a logical page of
                                   memory appear at a physical page.

          Mapping Context          The contents of the mapping registers at
                                   a specific instant.  This context
                                   represents a map state.

          Page Frame               A collection of 16K-byte contiguous
                                   physical pages from which an application
                                   program accesses expanded memory.

          Page Frame               A page frame base address is the
            Base Address           location (in segment format) of the
                                   first byte of the page frame.

          Physical Page            A physical page is the range of memory
                                   addresses occupied by a single 16K-byte
                                   page.

          Raw Page                 The smallest unit of mappable memory
                                   that an expanded memory board can
                                   supply.

          Resident Application     A resident application program is loaded
            Program                by DOS, executes, and remains resident
                                   in the system after it returns control
                                   to DOS.  This type of program occupies
                                   memory and is usually invoked by the
                                   operating system, an application
                                   program, or the hardware.  Some example
                                   of resident application programs are RAM
                                   disks, print spoolers, and "pop-up"
                                   desktop programs.

          Status code              A code that an EMM function returns
                                   which indicates something about the
                                   result of running the function.  Some
                                   status codes indicate whether the
                                   function worked correctly and others may
                                   tell you something about the expanded
                                   memory hardware or software.








          Glossary                                                      213





          Transient Application    A transient application program is
            Program                loaded by DOS, executes, and doesn't
                                   remain in the system after it returns
                                   control to DOS.  After a transient
                                   application program returns control to
                                   DOS, the memory it used is available for
                                   other programs.

          Unmap                    To make a logical page inaccessible for
                                   reading or writing.











































          Glossary                                                      214





          INDEX



               Allocate Alternate Map Register Set  36, 163
               Allocate DMA Register Set  36, 168, 190
               Allocate Pages  5, 14, 23, 30, 34, 42, 43, 47, 49, 144,
                   147, 148, 193, 206, 208
               Allocate Raw Pages  36, 46, 80, 89, 147-149, 190, 193,
                   206
               Allocate Standard Pages  36, 42, 46, 80, 89, 144, 145,
                   147, 190, 193
               Alter Page Map & Call  7, 10, 35, 113, 118, 189
               Alter Page Map & Jump  7, 10, 35, 109, 189
               Alternate Map  10, 36, 151, 153-155, 157-159, 161, 163,
                   164, 165-168, 170, 173, 175, 179, 182, 190, 197,
                   209, 210, 211
               Alternate Map Register Set  10, 36, 151, 153-155, 157,
                   158, 159, 161, 163-168, 170, 173, 175, 190, 197,
                   209, 210
               Alternate Mapping and Unmapping Methods  81
               Alternate Register  139, 166, 173, 177
               Data Aliasing  12
               Deallocate Alternate Map Register Set  36, 166
               Deallocate DMA Register Set  36, 175, 190
               Deallocate Pages  5, 14, 25, 31, 34, 43, 49, 88, 145,
                   148, 208
               Design Considerations  91, 151
               Device Driver  1, 15, 43, 53, 55, 144, 148, 199, 201,
                   202, 204, 212
               Disable DMA on Alternate Map Register Set  173
               Disable OS/E Function Set  36, 179, 180, 182, 185
               DMA  36, 138-140, 151, 152, 168-176, 190, 197
               DMA Channels  139, 171, 173, 174, 197
               DMA Register  36, 139, 140, 151, 152, 168-171, 173-176,
                   190, 197
               DOS  1, 12, 14, 15, 19, 21, 30, 31, 49, 53, 88, 199-205,
                   207-214
               Enable DMA on Alternate Map Register Set  170
               Enable OS/E Function Set  36, 179, 180
               Enable/Disable OS/E Function Set  179, 180, 182, 185
               Exchange Memory Region  7, 10, 35, 120, 126, 127, 189
               Expanded Memory Support of DMA  151
               Expanded Memory Support of DMA Register Sets  151
               Extended Memory  91
               Function 1  37
               Function 10  57
               Function 11  58
               Function 12  59
               Function 13  61
               Function 14  7, 63


          Index                                                         215





               Function 15  13, 53, 55, 65, 67, 69, 71, 73, 76, 139,
                   153, 154, 155, 158, 211
               Function 16  13, 73, 76, 78
               Function 17  6, 80, 82, 85
               Function 18  6, 43, 88, 144, 148, 207
               Function 19  7, 91, 92, 94, 96
               Function 2  4, 38
               Function 20  7, 98, 100
               Function 21  7, 42, 102, 105, 107
               Function 22  109
               Function 23  113, 118
               Function 24  7, 120, 126
               Function 25  6, 8, 46, 74, 85, 132, 136
               Function 26  138, 142, 179, 182, 206
               Function 27  42, 46, 80, 89, 144, 145, 147-149, 193, 206
               Function 28  140, 151, 153, 157, 161, 163, 164, 166,
                   168, 170, 173, 175, 179, 182, 209
               Function 29  177
               Function 3  4, 40, 142
               Function 30  138, 151, 153, 157, 161, 163, 166, 168,
                   170, 173, 175, 179, 182, 185
               Function 4  4, 42, 43, 46, 47, 49, 80, 89, 144, 145,
                   147, 149, 193, 206, 208
               Function 5  4, 46, 81
               Function 6  4, 43, 49, 88, 145, 148, 208
               Function 7  5, 51
               Function 8  46, 50, 53, 55
               Function 9  46, 50, 53, 55
               Get & Set Page Map  35, 69
               Get All Handle Pages  9, 34, 63
               Get Alternate Map Register Set  36, 153, 154, 157, 190
               Get Alternate Map Save Array Size  36, 161, 190
               Get Attribute Capability  7, 96
               Get Expanded Memory Hardware Information  10, 138, 142,
                   179, 182
               Get Handle Attribute  35, 92
               Get Handle Count  9, 34, 59
               Get Handle Directory  10, 35, 102, 105, 107
               Get Handle Name  35, 98
               Get Handle Pages  7, 9, 34, 61
               Get Hardware Configuration Array  36, 138
               Get Interrupt Vector  15, 21, 30, 199, 204, 205
               Get Mappable Physical Address Array  6, 8, 10, 35, 46,
                   85, 132, 136
               Get Mappable Physical Address Array Entries  8, 136
               Get Page Frame Address  5, 34, 38
               Get Page Map  35, 65, 118, 153-155, 158, 211
               Get Page Map Stack Space Size  35, 118
               Get Partial Page Map  35, 73, 78
               Get Size of Page Map Save Array  35, 65, 67, 71, 139
               Get Size of Partial Page Map Save Array  74, 76, 78
               Get Status  5, 34, 37

          Index                                                         216





               Get Total Handles  35, 107
               Get Unallocated Page Count  5, 22, 34, 40, 142
               Get Unallocated Raw Page Count  36, 142, 189
               Get Version  5, 34, 51
               Get/Set Handle Attribute  9, 91, 92, 94, 96
               Get/Set Handle Name  10, 98, 100
               Get/Set Page Map  9, 13, 65, 67, 69, 71
               Get/Set Partial Page Map  9, 13, 73, 76, 78
               Handle Attribute  9, 35, 91-94, 96, 188
               Handle Name  6, 7, 10, 35, 98, 100, 105, 106, 188, 198
               Intel  i, ii, 1, 5, 57, 58
               Interrupt Vector  12, 15, 21, 30, 199, 204, 205
               LIM  1, 7, 13, 19, 27, 53, 55, 138, 140, 206, 207
               Logical Page  1, 5, 12, 16, 19, 23, 28, 31, 32, 46-48,
                   80-83, 85, 86, 88, 110, 111, 115, 116, 120, 122,
                   123, 125, 126, 128, 129, 131, 147, 194, 196, 207,
                   212-214
               Logical Page/Physical Page Method  82
               Logical Page/Segment Address Method  85
               Lotus  i, ii, 1, 5, 57, 58
               Map Register  10, 13, 36, 53, 55, 151, 153-155, 157-159,
                   161, 163-168, 170, 173, 175, 179, 182, 190, 197,
                   209-211
               Map/Unmap Handle Pages  46
               Map/Unmap Multiple Handle Pages  9, 35, 80, 82, 85
               Mapping and Unmapping Multiple Pages Simultaneously  80
               Mapping Multiple Pages  6, 80
               Microsoft  i, ii, 1, 5, 14, 30, 42, 57, 58, 144, 148
               Move Memory Region  35, 120, 121, 189
               Move/Exchange Memory Region  7, 10, 120, 126
               Open Handle  64, 102, 199, 200, 203, 204
               Operating System  3, 8, 10-12, 42, 43, 59, 63, 107, 138,
                   139, 141, 142, 144-151, 153-159, 161-163, 165-171,
                   173-177, 179-183, 185, 186, 190, 191, 198, 206,
                   207-209, 213
               Page Frame  1-6, 14, 17-19, 24, 28, 31, 34, 38, 39, 47,
                   53, 55, 121, 128, 133, 187, 213
               Page Map  7, 9, 10, 13, 34, 35, 50, 53, 55, 65, 67, 69,
                   71, 73-76, 78, 109, 113, 118, 139, 153-155, 158,
                   187, 188, 189, 211
               Page Mapping Register I/O Array  57
               Page Translation Array  58
               Physical Page  1, 5, 6, 8, 10, 12, 16, 23, 28, 31, 35,
                   46, 47, 48, 80-83, 85, 109-112, 114-117, 132-134,
                   136, 138, 139, 142, 147, 188, 194, 207, 209, 213
               Prepare Expanded Memory Hardware For Warm Boot  10, 177
               Raw Handle  147, 149, 150, 206, 207
               Raw Page  36, 142, 143, 147, 189, 206
               Reallocate Pages  9, 35, 43, 88, 144, 145, 148, 208
               Restore Page Map  9, 13, 34, 50, 53, 55
               Return Access Key  185
               Save Page Map  9, 13, 34, 50, 53, 55

          Index                                                         217





               Search For Named Handle  7, 35, 105
               Set Alternate Map Register Set  36, 153-155, 157, 158,
                   163, 190
               Set Handle Attribute  9, 35, 91, 92, 94, 96
               Set Handle Name  7, 10, 35, 98, 100
               Set Page Map  9, 13, 35, 65, 67, 69, 71, 188
               Set Partial Page Map  9, 13, 35, 73, 76, 78
               Standard Handle  146
               Standard Page  147
               System DMA Capabilities  151
               TSR  12, 13, 208
               Unmapping Multiple Pages  6, 80









































          Index                                                         218

 M?pALLINFO.TXT
Following are BBSes that are members of DV-NET.  DV-Net is an informal
network of BBS's that carry files that would be useful to DESQview users. 
Not all BBSes that carry the DESQview conference are members of DV-Net.

All address are NetMail addresses.

            ----------------------------------------------
                 DVNet DESQview Support File Network
            ----------------------------------------------
         DESQview is a trademark of Quarterdeck Office Systems
       -----------------------------------------------------------
         DVNet is not affiliated with Quarterdeck Office Systems
   ----------------------------------------------------------------

   Name, City and State             NetAddress  Telephone     Maxbaud
   -------------------------------  ---------- ------------  -------
 *65'North, Fairbanks, AK         1:17/38    907-452-1460  9600HSTV32
  Opus 386, Davis, CA             1:203/910  916-753-6321  2400
  Carl's Corner, San Jose, CA     1:10/1     408-248-9704  9600HSTV32
  Carl's Corner, San Jose, CA     1:10/2     408-248-0198  2400
  SeaHunt BBS, Burlingame, CA     1:125/20   415-344-4348  9600HST
  Stingray!, Clovis CA            1:205/12   209-298-9461  9600HST
  SF PCUG BBS, San Francisco CA   1:1/310    415-621-2609  9600HSTV32RE
  Bink of an Aye, Portland, OR    1:105/42   503-297-9043  9600PEPV32MO
  P C Support, Portland, OR       1:105/66   503-297-9078  2400
  Atarian BBS, Portland, OR       1:105/10   503-245-9730  9600HSTV32
  Busker's BoneYard, Portland,OR  1:105/14   503-771-4773  9600PEP
  Busker's Boneyard, Portland,OR  1:105/41   503-775-7926  9600HSTV32
  Pacifier BBS, Vancouver, WA     1:105/103  206-253-9770  9600HSTV32
  Puget Sound Gtwy., Puyallup, WA 1:138/3    206-566-8854  9600HST
  Rampart General,Kansas City,MO  1:280/6    816-761-4039  9600HST
  Oregon Trail XRoads, Casper WY  1:303/5    307-472-3615  9600H96
  Dawg Byte, Nashville, TN        1:116/29   615-385-4268  9600HST
  Dickson County, Dickson, TN     1:116/25   615-446-4475  2400
  Programmers' Attic, Will., MI   1:159/850  517-655-3347  2400
  Dark Side Of the Moon,Savoy IL  1:233/493  217-356-6922  9600HSTV32
  Ecclesia Place, Monroeville, PA 1:129/75   412-373-8612  9600HST
  The Other BBS, Harrisburg PA    1:270/101  717-657-2223  9600HST
  IBM Tech Fido, Pepperell, MA    1:322/1    508-433-8452  9600HSTV32
  Waystar BBS, Marlborough, MA    1:322/14   508-481-7147  9600HST
  Andromeda Galaxy, Troy NY       1:267/167  518-273-8313  9600HST
  Treasure Island, Danbury, CT    1:141/730  203-791-8532, 9600HST
  Addict's Attic,Germantown MD    1:109/423  301-428-8998  9600HST
  Maple Shade Opus,Maple Shade NJ 1:266/12   609-482-8604  9600HSTV32
  Capital City , Burlington NJ   99:9230/1   609-386-1989  9600HSTV32
  Capital City , Burlington NJ    8:950/10   609-386-1989  9600HSTV32
  Southern Cross BBS, Miami FL    1:135/69   305-220-8752  9600HST
  Software Designer, Albany, GA   1:3617/1   912-432-2440  9600HSTV32
  Software Designer, Albany, GA   8:928/1    912-432-2440  9600HSTV32
  Dragon's Lair, Galveston, TX    1:386/451  409-762-2761  9600HST
  Dragon's Lair, Galveston, TX    1:386/1451 409-762-7456  2400MNP
  Conch Opus, Houston, TX         1:106/357  713-667-7213  2400PCP
  Inns of Court, Dallas, TX       1:124/6101 214-458-2620  9600HSTV32
  Dallas Email, Dallas, TX        8:930/101  214-358-1205  9600HSTV32MO
  Spare Parts, Bedford, TX        1:130/38   817-540-3527  9600HST
  QE2, Austin TX                  1:382/58   512-328-1229  2400
  Ned's Opus HST Ottawa,ON Canada 1:163/211  613-523-8965  9600HST
  Ned's Opus, Ottawa ON Canada    1:163/210  613-731-8132  2400
  Imperial Terran, St Cath,ON     1:247/102  416-646-7105  9600HST
  Arcane BBS, Laval PQ Canada     1:167/116  514-687-9586  9600HST
  Zone 2 & Zone 3
  ------------------------------  ---------  ------------- -------
  The HEKOM Board (Netherlands)   2:286/3    31-3483-4072  2400MNP5
  BBS_D.C.V.V., Maaseik (Belgium) 2:295/26   32-11-568620eXtended Memory Specification (XMS), ver 3.0


January 1991


Copyright (c) 1988, Microsoft Corporation, Lotus Development
Corporation, Intel Corporation, and AST Research, Inc.

Microsoft Corporation                                            
Box 97017

One Microsoft Way                                       
Redmond, WA 98073

LOTUS (r)
INTEL (r)
MICROSOFT (r)
AST (r) Research

This specification was jointly developed by Microsoft Corporation,
Lotus Development Corporation, Intel Corporation,and AST Research,
Inc. Although it has been released into the public domain and is not
confidential or proprietary, the specification is still the copyright
and property of Microsoft Corporation, Lotus Development Corporation,
Intel Corporation, and AST Research, Inc.

Disclaimer of Warranty

MICROSOFT CORPORATION, LOTUS DEVELOPMENT CORPORATION, INTEL
CORPORATION, AND AST RESEARCH, INC., EXCLUDE ANY AND ALL IMPLIED
WARRANTIES, INCLUDING WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE. NEITHER MICROSOFT NOR LOTUS NOR INTEL NOR AST
RESEARCH MAKE ANY WARRANTY OF REPRESENTATION, EITHER EXPRESS OR
IMPLIED, WITH RESPECT TO THIS SPECIFICATION, ITS QUALITY,
PERFORMANCE, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
NEITHER MICROSOFT NOR LOTUS NOR INTEL NOR AST RESEARCH SHALL HAVE ANY
LIABILITY FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
OUT OF OR RESULTING FROM THE USE OR MODIFICATION OF THIS
SPECIFICATION.

This specification uses the following trademarks:

Intel is a registered trademark of Intel Corporation, Microsoft is a
registered trademark of Microsoft Corporation, Lotus is a registered
trademark of Lotus Development Corporation, and AST is a registered
trademark of AST Research, Inc.



Extended Memory Specification

    The purpose of this document is to define the Extended Memory Specification (XMS) version 3.00 for MS-DOS.  XMS allows DOS programs to utilize additional memory found in Intel's 80286 and 80386 based machines in a consistent, machine independent manner.  With some restrictions, XMS adds almost 64K to the 640K which DOS programs can access directly.  Depending on available hardware, XMS may provide even more memory to DOS programs.  XMS also provides DOS programs with a standard method of storing data in extended memory.

        To be considered fully XMS 3.0 compliant, all calls except those associated with UMB support must be implemented. UMB functions 10h, 11h and 12h are optional for XMS 3.0 and may return the Function Not Implemented error code, 80h.

DEFINITIONS:
------------

Extended Memory:
Memory in 80286 and 80386 based machines which is located above the 1MB address boundary.

High Memory Area (HMA):
The first 64K of extended memory.  The High Memory Area is unique because code can be executed in it while in real mode.  The HMA officially starts at FFFF:10h and ends at FFFF:FFFFh making it 64K-16 bytes in length.
                    
Upper Memory Blocks (UMBs):
Blocks of memory available on some 80x86 based machines which are located between DOS's 640K limit and the 1MB address boundary.  The number, size, and location of these blocks vary widely depending upon the types of hardware adapter cards installed in the machine.
                    
Extended Memory Blocks (EMBs):
Blocks of extended memory located above the HMA which can only be used for data storage.
                    
A20 Line:
The 21st address line of 80x86 CPUs.  Enabling the A20 line allows access to the HMA.

XMM:
An Extended Memory Manager.  A DOS device driver which implements XMS.  XMMs are machine specific but allow programs to use extended memory in a machine-independent manner.
    
HIMEM.SYS:
The Extended Memory Manager currently being distributed by Microsoft.



Helpful Diagram:

|               |   Top of Memory
|               |
|               |
|       /\      |
|       /||\    |
|       ||      |
|       ||      |
|               |
|               |
|               |
|       Possible Extended Memory Block  |
|               |
|               |
|               |
|       ||      |
|       ||      |
|       \||/    |
|       \/      |
|               |
|               |
|       Other EMBs could exist above 1088K (1MB+64K)    |
|               |
|               |
|               |   1088K
|               |
|               |
|       The High Memory Area    |
|               |
|               |
|               |   1024K or 1MB
|               |
|       /\      |
|       /||\    |
|       ||      |
|       ||      |
|               |
|               |
|       Possible Upper Memory Block     |
|               |
|       ||      |
|       ||      |
|       \||/    |
|       \/      |
|               |
|       Other UMBs could exist between 640K and 1MB     |
|               |
|               |   640K

|               |
|               |
|               |
|       Conventional or DOS Memory      |
|               |
|               |
|               |
|               |
|               |
+               +   0K

DRIVER INSTALLATION:
--------------------

    An XMS driver is installed by including a DEVICE= statement in the
machine's CONFIG.SYS file.  It must be installed prior to any other
devices or TSRs which use it.  An optional parameter after the driver's 
name (suggested name "/HMAMIN=") indicates the minimum amount of space in
the HMA a program can use.  Programs which use less than the minimum will
not be placed in the HMA.  See "Prioritizing HMA Usage" below for more
information.  A second optional parameter (suggested name "/NUMHANDLES=")
allows users to specify the maximum number of extended memory blocks which
may be allocated at any time.

    NOTE: XMS requires DOS 3.00 or above.


THE PROGRAMMING API:
--------------------

    The XMS API Functions are accessed via the XMS driver's Control Function.
The address of the Control Function is determined via INT 2Fh.  First, a
program should determine if an XMS driver is installed.  Next, it should
retrieve the address of the driver's Control Function.  It can then use any
of the available XMS functions.  The functions are divided into several
groups:

        1. Driver Information Functions (0h)
        2. HMA Management Functions (1h-2h)
        3. A20 Management Functions (3h-7h)
        4. Extended Memory Management Functions (8h-Fh)
        5. Upper Memory Management Functions (10h-11h)


DETERMINING IF AN XMS DRIVER IS INSTALLED:
------------------------------------------

    The recommended way of determining if an XMS driver is installed is to
set AH=43h and AL=00h and then execute INT 2Fh.  If an XMS driver is available,
80h will be returned in AL.

    Example:
            ; Is an XMS driver installed?
            mov     ax,4300h
            int     2Fh         
            cmp     al,80h  
            jne     NoXMSDriver
            

CALLING THE API FUNCTIONS:
--------------------------

    Programs can execute INT 2Fh with AH=43h and AL=10h to obtain the address
of the driver's control function.  The address is returned in ES:BX.  This
function is called to access all of the XMS functions.  It should be called
with AH set to the number of the API function requested.  The API function
will put a success code of 0001h or 0000h in AX.  If the function succeeded
(AX=0001h), additional information may be passed back in BX and DX.  If the
function failed (AX=0000h), an error code may be returned in BL.  Valid
error codes have their high bit set.  Developers should keep in mind that
some of the XMS API functions may not be implemented by all drivers and will
return failure in all cases.

    Example:
            ; Get the address of the driver's control function
            mov     ax,4310h
            int     2Fh
            mov     word ptr [XMSControl],bx        ; XMSControl is a DWORD
            mov     word ptr [XMSControl+2],es
            
            ; Get the XMS driver's version number
            mov     ah,00h
            call    [XMSControl]    ; Get XMS Version Number

    NOTE: Programs should make sure that at least 256 bytes of stack space
          is available before calling XMS API functions.


API FUNCTION DESCRIPTIONS:
--------------------------

    The following XMS API functions are available:

       0h)  Get XMS Version Number
       1h)  Request High Memory Area
       2h)  Release High Memory Area
       3h)  Global Enable A20
       4h)  Global Disable A20
       5h)  Local Enable A20
       6h)  Local Disable A20
       7h)  Query A20
       8h)  Query Free Extended Memory
       9h)  Allocate Extended Memory Block
       Ah)  Free Extended Memory Block
       Bh)  Move Extended Memory Block
       Ch)  Lock Extended Memory Block
       Dh)  Unlock Extended Memory Block
       Eh)  Get Handle Information
       Fh)  Reallocate Extended Memory Block
      10h)  Request Upper Memory Block
      11h)  Release Upper Memory Block
      12h) Realloc Upper Memory Block
      88h) Query any Free Extended Memory
      89h) Allocate any Extended Memory Block
      8Eh) Get Extended EMB Handle
      8Fh) Realloc any Extended Memory

Each is described below.


Get XMS Version Number (Function 00h):
--------------------------------------

    ARGS:   AH = 00h
    RETS:   AX = XMS version number
            BX = Driver internal revision number
            DX = 0001h if the HMA exists, 0000h otherwise
    ERRS:   None

    This function returns with AX equal to a 16-bit BCD number representing
the revision of the DOS Extended Memory Specification which the driver
implements (e.g. AX=0235h would mean that the driver implemented XMS version
2.35).  BX is set equal to the driver's internal revision number mainly for
debugging purposes.  DX indicates the existence of the HMA (not its
availability) and is intended mainly for installation programs.
    
    NOTE: This document defines version 3.00 of the specification.


Request High Memory Area (Function 01h):
----------------------------------------

    ARGS:   AH = 01h
            If the caller is a TSR or device driver,
                DX = Space needed in the HMA by the caller in bytes
            If the caller is an application program,
                DX = FFFFh     
    RETS:   AX = 0001h if the HMA is assigned to the caller, 0000h otherwise
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = 90h if the HMA does not exist
            BL = 91h if the HMA is already in use
            BL = 92h if DX is less than the /HMAMIN= parameter

    This function attempts to reserve the 64K-16 byte high memory area for
the caller.  If the HMA is currently unused, the caller's size parameter is
compared to the /HMAMIN= parameter on the driver's command line.  If the
value passed by the caller is greater than or equal to the amount specified
by the driver's parameter, the request succeeds.  This provides the ability
to ensure that programs which use the HMA efficiently have priority over
those which do not.

    NOTE: See the sections "Prioritizing HMA Usage" and "High Memory Area
          Restrictions" below for more information.


Release High Memory Area (Function 02h):
----------------------------------------

    ARGS:   AH = 02h
    RETS:   AX = 0001h if the HMA is successfully released, 0000h otherwise
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = 90h if the HMA does not exist
            BL = 93h if the HMA was not allocated

    This function releases the high memory area and allows other programs to
use it.  Programs which allocate the HMA must release it before exiting.  
When the HMA has been released, any code or data stored in it becomes invalid
and should not be accessed.


Global Enable A20 (Function 03h):
---------------------------------

    ARGS:   AH = 03h
    RETS:   AX = 0001h if the A20 line is enabled, 0000h otherwise
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = 82h if an A20 error occurs

    This function attempts to enable the A20 line.  It should only be used
by programs which have control of the HMA.  The A20 line should be turned
off via Function 04h (Global Disable A20) before a program releases control
of the system.

    NOTE: On many machines, toggling the A20 line is a relatively slow
          operation.


Global Disable A20 (Function 04h):
----------------------------------

    ARGS:   AH = 04h
    RETS:   AX = 0001h if the A20 line is disabled, 0000h otherwise
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = 82h if an A20 error occurs
            BL = 94h if the A20 line is still enabled
    
    This function attempts to disable the A20 line.  It should only be used
by programs which have control of the HMA.  The A20 line should be disabled
before a program releases control of the system.

    NOTE: On many machines, toggling the A20 line is a relatively slow
          operation.


Local Enable A20 (Function 05h):
--------------------------------

    ARGS:   AH = 05h
    RETS:   AX = 0001h if the A20 line is enabled, 0000h otherwise
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = 82h if an A20 error occurs

    This function attempts to enable the A20 line.  It should only be used
by programs which need direct access to extended memory.  Programs which use
this function should call Function 06h (Local Disable A20) before releasing
control of the system.

    NOTE: On many machines, toggling the A20 line is a relatively slow
          operation.


Local Disable A20 (Function 06h):
---------------------------------

    ARGS:   AH = 06h
    RETS:   AX = 0001h if the function succeeds, 0000h otherwise
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = 82h if an A20 error occurs
            BL = 94h if the A20 line is still enabled

    This function cancels a previous call to Function 05h (Local Enable
A20).  It should only be used by programs which need direct access to
extended memory.  Previous calls to Function 05h must be canceled before
releasing control of the system.

    NOTE: On many machines, toggling the A20 line is a relatively slow
          operation.


Query A20 (Function 07h):
-------------------------

    ARGS:   AH = 07h
    RETS:   AX = 0001h if the A20 line is physically enabled, 0000h otherwise
    ERRS:   BL = 00h if the function succeeds
            BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected

    This function checks to see if the A20 line is physically enabled.  It
does this in a hardware independent manner by seeing if "memory wrap" occurs.


Query Free Extended Memory (Function 08h):
------------------------------------------

    ARGS:   AH = 08h
    RETS:   AX = Size of the largest free extended memory block in K-bytes
            DX = Total amount of free extended memory in K-bytes
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = A0h if all extended memory is allocated

    This function returns the size of the largest available extended memory
block in the system.

    NOTE: The 64K HMA is not included in the returned value even if it is
          not in use.


Allocate Extended Memory Block (Function 09h):
----------------------------------------------

    ARGS:   AH = 09h
            DX = Amount of extended memory being requested in K-bytes
    RETS:   AX = 0001h if the block is allocated, 0000h otherwise
            DX = 16-bit handle to the allocated block
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = A0h if all available extended memory is allocated
            BL = A1h if all available extended memory handles are in use
            
    This function attempts to allocate a block of the given size out of the
pool of free extended memory.  If a block is available, it is reserved
for the caller and a 16-bit handle to that block is returned.  The handle
should be used in all subsequent extended memory calls.  If no memory was
allocated, the returned handle is null.

    NOTE: Extended memory handles are scarce resources.  Programs should
          try to allocate as few as possible at any one time.  When all
          of a driver's handles are in use, any free extended memory is
          unavailable.



Free Extended Memory Block (Function 0Ah):
------------------------------------------

    ARGS:   AH = 0Ah
            DX = Handle to the allocated block which should be freed
    RETS:   AX = 0001h if the block is successfully freed, 0000h otherwise
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = A2h if the handle is invalid
            BL = ABh if the handle is locked

    This function frees a block of extended memory which was previously
allocated using Function 09h (Allocate Extended Memory Block).  Programs
which allocate extended memory should free their memory blocks before
exiting.  When an extended memory buffer is freed, its handle and all data
stored in it become invalid and should not be accessed.


Move Extended Memory Block (Function 0Bh):
------------------------------------------

    ARGS:   AH = 0Bh
            DS:SI = Pointer to an Extended Memory Move Structure (see below)
    RETS:   AX = 0001h if the move is successful, 0000h otherwise
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = 82h if an A20 error occurs
            BL = A3h if the SourceHandle is invalid
            BL = A4h if the SourceOffset is invalid
            BL = A5h if the DestHandle is invalid
            BL = A6h if the DestOffset is invalid
            BL = A7h if the Length is invalid
            BL = A8h if the move has an invalid overlap
            BL = A9h if a parity error occurs

    Extended Memory Move Structure Definition:

        ExtMemMoveStruct    struc
            Length              dd  ?   ; 32-bit number of bytes to transfer
            SourceHandle        dw  ?   ; Handle of source block
            SourceOffset        dd  ?   ; 32-bit offset into source 
            DestHandle          dw  ?   ; Handle of destination block
            DestOffset          dd  ?   ; 32-bit offset into destination block
        ExtMemMoveStruct    ends
            
    This function attempts to transfer a block of data from one location to
another.  It is primarily intended for moving blocks of data between
conventional memory and extended memory, however it can be used for moving
blocks within conventional memory and within extended memory.

    NOTE: If SourceHandle is set to 0000h, the SourceOffset is interpreted
          as a standard segment:offset pair which refers to memory that is
          directly accessible by the processor.  The segment:offset pair
          is stored in Intel DWORD notation.  The same is true for DestHandle
          and DestOffset.
          
          SourceHandle and DestHandle do not have to refer to locked memory
          blocks.
          
          Length must be even.  Although not required, WORD-aligned moves
          can be significantly faster on most machines.  DWORD aligned move
          can be even faster on 80386 machines.
          
          If the source and destination blocks overlap, only forward moves
          (i.e. where the source base is less than the destination base) are
          guaranteed to work properly.
          
          Programs should not enable the A20 line before calling this
          function.  The state of the A20 line is preserved.

          This function is guaranteed to provide a reasonable number of
          interrupt windows during long transfers.
          
          
Lock Extended Memory Block (Function 0Ch):
------------------------------------------

    ARGS:   AH = 0Ch
            DX = Extended memory block handle to lock
    RETS:   AX = 0001h if the block is locked, 0000h otherwise
            DX:BX = 32-bit physical address of the locked block
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = A2h if the handle is invalid
            BL = ACh if the block's lock count overflows
            BL = ADh if the lock fails
            
    This function locks an extended memory block and returns its base 
address as a 32-bit physical address.  Locked memory blocks are guaranteed not
to move.  The 32-bit pointer is only valid while the block is locked.
Locked blocks should be unlocked as soon as possible.

    NOTE: A block does not have to be locked before using Function 0Bh (Move
          Extended Memory Block).
          
          "Lock counts" are maintained for EMBs.



Unlock Extended Memory Block (Function 0Dh):
--------------------------------------------

    ARGS:   AH = 0Dh
            DX = Extended memory block handle to unlock
    RETS:   AX = 0001h if the block is unlocked, 0000h otherwise
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = A2h if the handle is invalid
            BL = AAh if the block is not locked
    
    This function unlocks a locked extended memory block.  Any 32-bit
pointers into the block become invalid and should no longer be used.


Get EMB Handle Information (Function 0Eh):
------------------------------------------

    ARGS:   AH = 0Eh
            DX = Extended memory block handle
    RETS:   AX = 0001h if the block's information is found, 0000h otherwise
            BH = The block's lock count
            BL = Number of free EMB handles in the system
            DX = The block's length in K-bytes
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = A2h if the handle is invalid

    This function returns additional information about an extended memory
block to the caller.

    NOTE: To get the block's base address, use Function 0Ch (Lock Extended
          Memory Block).
          
          
Reallocate Extended Memory Block (Function 0Fh):
------------------------------------------------

    ARGS:   AH = 0Fh
            BX = New size for the extended memory block in K-bytes
            DX = Unlocked extended memory block handle to reallocate
    RETS:   AX = 0001h if the block is reallocated, 0000h otherwise
    ERRS:   BL = 80h if the function is not implemented
            BL = 81h if a VDISK device is detected
            BL = A0h if all available extended memory is allocated
            BL = A1h if all available extended memory handles are in use
            BL = A2h if the handle is invalid
            BL = ABh if the block is locked

    This function attempts to reallocate an unlocked extended memory block
so that it becomes the newly specified size.  If the new size is smaller
than the old block's size, all data at the upper end of the old block is
lost.


Request Upper Memory Block (Function 10h):
------------------------------------------

    ARGS:   AH = 10h
            DX = Size of requested memory block in paragraphs
    RETS:   AX = 0001h if the request is granted, 0000h otherwise
            BX = Segment number of the upper memory block
            If the request is granted,
                DX = Actual size of the allocated block in paragraphs
            otherwise,
                DX = Size of the largest available UMB in paragraphs
    ERRS:   BL = 80h if the function is not implemented
            BL = B0h if a smaller UMB is available
            BL = B1h if no UMBs are available

    This function attempts to allocate an upper memory block to the caller.
If the function fails, the size of the largest free UMB is returned in DX.

    NOTE: By definition UMBs are located below the 1MB address boundary.
          The A20 Line does not need to be enabled before accessing an
          allocated UMB.

          UMBs are paragraph aligned.

          To determine the size of the largest available UMB, attempt to
          allocate one with a size of FFFFh.

          UMBs are unaffected by EMS calls.


Release Upper Memory Block (Function 11h):
------------------------------------------

    ARGS:   AH = 11h
            DX = Segment number of the upper memory block
    RETS:   AX = 0001h if the block was released, 0000h otherwise
    ERRS:   BL = 80h if the function is not implemented
            BL = B2h if the UMB segment number is invalid

    This function frees a previously allocated upper memory block.  When an
UMB has been released, any code or data stored in it becomes invalid and
should not be accessed.




Reallocate Upper Memory Block (Function 12h)

        ARGS:
                AH = 12h
                BX = New size for UMB in paragraphs
                DX = Segment number of the UMB to reallocate
        RETS:
                AX = 1 if the block was reallocated, 0 otherwise
        ERRS:
                BL = 80h if the function is not implemented
                BL = B0h if no UMB large enough to satisfy the request is available.
                        In this event, DX is returned with the size of the largest UMB that is                  available.
                BL = B2h if the UMB segment number is invalid

This function attempts to reallocate an Upper Memory Block to a newly specified size.  If the new size is smaller than the old block's size, all data at the upper end of the block is lost.



Super Extended Memory Support

These changes are intended to provide support for extended memory pools up to 4 Gb in size.  The current XMS API, since it uses 16-bit values to specify block sizes in Kb, is limited to 64 Mb maximum block size.  Future machines are expected to support memory above 64 MB.

This support is implemented in the form of extensions to existing functions, rather than entirely new entry points, to allow for more efficient implementations.

Programs should generally use the existing functions, instead of these extended ones, unless they have an explicit need to deal with memory above 64 Mb.  


Query Any Free Extended Memory (Function 88h)

        Entry:
                AH = 88h                        
        Exit:
                EAX = Size of largest free extended memory block in Kb.
                BL = 0 if no error occurs, otherwise it takes an error code.
                ECX = Highest ending address of any memory block.
                EDX = Total amount of free memory in Kb.
        Errors:
                BL = 80h if the function is not implemented.
                BL = 81h if a VDISK device is detected.
                BL = A0h if all extended memory is allocated.

This function uses 32-bit values to return the size of available memory, thus allowing returns up to 4GByte.  Additionally, it returns the highest known physical memory address, that is, the physical address of the last byte of memory.  There may be discontinuities in the memory map below this address.

The memory pool reported on is the same as that reported on by the existing Query Free Extended Memory function.  If the highest memory address is not more than 64 Mb, then these two functions will return the same results.

Because of its reliance on 32-bit registers, this function is only available on 80386 and higher processors.  XMS drivers on 80286 machines should return error code 80h if this function is called.

If error code 81h is returned, the value in ECX will still be valid.  If error code A0h is returned, EAX and EDX will be 0, and ECX will still be valid.


Allocate Any Extended Memory (Function 89h)

        Entry:
                AH = 89h
                EDX = Amount of extended memory requested, in Kb.
        Exit:
                AX = 1 if the block is allocated, 0 if not
                DX = Handle to allocated block.
        Errors:
                BL = 80h if the function is not implemented.
                BL = 81h if a VDISK device is detected.
                BL = A0h if all available extended memory is allocated.
                BL = A1h if all available extended memory handles are in use.

This function is similar to the existing Allocate Extended Memory, except that it uses a 32-bit instead of a 16-bit value to specify the amount of memory requested.  It allocates from the same memory and handle pool as the current function.  Since it requires a 32-bit register, this function can be supported only on 80386 and higher processors, and XMS drivers on 80286 machines should return error code 80h.


Get Extended EMB Handle Information (Function 8Eh)

        Entry:
                AH = 8Eh
                DX = Extended memory block handle.
        Exit:
                AX = 1 if the block's information is found, 0 if not
                BH = Block lock count
                CX = Number of free EMB handles in the system
                EDX = Block's length in Kb.
        Errors:
                BL = 80h if the function is not implemented.
                BL = 81h if a VDISK device is detected.
                BL = A2h if the handle is invalid.

This function is similar to the Get EMB Handle Information function.  Since it uses a 32-bit register to report the block size, it can be used to get information on blocks larger than 64 Mb.  It also uses a 16-bit instead of 8-bit register to report the number of free handles, allowing the handle pool to be extended beyond 256 entries.

Because of its reliance on a 32-bit register, this function is available on 80386 and higher processors.  XMS drivers on 80286 machines should return error code 80h if this function is called.


Reallocate Any Extended Memory (Function 8Fh)

        Entry:
                AH = 8Fh
                EBX = New size for extended memory block, in Kb.
                DX = Unlocked handle for memory block to be resized.
        Exit:
                AX = 1 if the block is reallocated, 0 if not
        Errors:
                BL = 80h if the function is not implemented.
                BL = 81h if a VDISK device is detected.
                BL = A0h if all available extended memory is allocated.
                BL = A1h if all available extended memory handles are in use.
                BL = A2h if the handle is invalid.
                BL = ABh if the block is locked.

This function is similar to the existing Reallocate Extended Memory, except that it uses a 32-bit instead of a 16-bit value to specify the amount of memory requested.  It allocates from the same memory and handle pool as the current function.  Since it requires a 32-bit register, this function can be supported only on 80386 and higher processors, and XMS drivers on 80286 machines should return error code 80h.




PRIORITIZING HMA USAGE:
-----------------------

    For DOS users to receive the maximum benefit from the High Memory Area,
programs which use the HMA must store as much of their resident code in it as
is possible.  It is very important that developers realize that the HMA is
allocated as a single unit. 

    For example, a TSR program which grabs the HMA and puts 10K of code into
it may prevent a later TSR from putting 62K into the HMA.  Obviously, regular
DOS programs would have more memory available to them below the 640K line if
the 62K TSR was moved into the HMA instead of the 10K one.

    The first method for dealing with conflicts such as this is to require 
programs which use the HMA to provide a command line option for disabling
this feature.  It is crucial that TSRs which do not make full use of the HMA
provide such a switch on their own command line (suggested name "/NOHMA").

    The second method for optimizing HMA usage is through the /HMAMIN=
parameter on the XMS device driver line.  The number after the parameter
is defined to be the minimum amount of HMA space (in K-bytes) used by any
driver or TSR.  For example, if "DEVICE=HIMEM.SYS /HMAMIN=48" is in a
user's CONFIG.SYS file, only programs which request at least 48K would be
allowed to allocate the HMA.  This number can be adjusted either by
installation programs or by the user himself.  If this parameter is not
specified, the default value of 0 is used causing the HMA to be allocated
on a first come, first served basis.

    Note that this problem does not impact application programs.  If the HMA
is available when an application program starts, the application is free to
use as much or as little of the HMA as it wants.  For this reason,
applications should pass FFFFh in DX when calling Function 01h.



HIGH MEMORY AREA RESTRICTIONS:
------------------------------

-   Far pointers to data located in the HMA cannot be passed to DOS.  DOS
    normalizes any pointer which is passed into it.  This will cause data
    addresses in the HMA to be invalidated.

-   Disk I/O directly into the HMA (via DOS, INT 13h, or otherwise) is not
    recommended.
       
-   Programs, especially drivers and TSRs, which use the HMA *MUST* use
    as much of it as possible.  If a driver or TSR is unable to use at
    least 90% of the available HMA (typically ~58K), they must provide
    a command line switch for overriding HMA usage.  This will allow
    the user to configure his machine for optimum use of the HMA.
       
-   Device drivers and TSRs cannot leave the A20 line permanently turned
    on.  Several applications rely on 1MB memory wrap and will overwrite the
    HMA if the A20 line is left enabled potentially causing a system crash.
        
-   Interrupt vectors must not point into the HMA.  This is a result of
    the previous restriction.  Note that interrupt vectors can point into
    any allocated upper memory blocks however.

ERROR CODE INDEX:
-----------------

If AX=0000h when a function returns and the high bit of BL is set,

    BL=80h if the function is not implemented
       81h if a VDISK device is detected
       82h if an A20 error occurs
       8Eh if a general driver error occurs
       8Fh if an unrecoverable driver error occurs
       90h if the HMA does not exist
       91h if the HMA is already in use
       92h if DX is less than the /HMAMIN= parameter
       93h if the HMA is not allocated
       94h if the A20 line is still enabled
       A0h if all extended memory is allocated
       A1h if all available extended memory handles are in use
       A2h if the handle is invalid
       A3h if the SourceHandle is invalid
       A4h if the SourceOffset is invalid
       A5h if the DestHandle is invalid
       A6h if the DestOffset is invalid
       A7h if the Length is invalid
       A8h if the move has an invalid overlap
       A9h if a parity error occurs
       AAh if the block is not locked
       ABh if the block is locked
       ACh if the block's lock count overflows
       ADh if the lock fails
       B0h if a smaller UMB is available
       B1h if no UMBs are available
       B2h if the UMB segment number is invalid


IMPLEMENTATION NOTES FOR DOS XMS DRIVERS:
-----------------------------------------

-   A DOS XMS driver's control function must begin with code similar to the
    following:

XMMControl  proc    far

            jmp     short XCControlEntry    ; For "hookability"
            nop                     ; NOTE: The jump must be a short
            nop                     ;  jump to indicate the end of
            nop                     ;  any hook chainThe nop's
                                            ;  allow a far jump to be
                                            ;  patched in.
XCControlEntry:


-   XMS drivers must preserve all registers except those containing
    returned values across any function call.

-   XMS drivers are required to hook INT 15h and watch for calls to
    functions 87h (Block Move) and 88h (Extended Memory Available).  The
    INT 15h Block Move function must be hooked so that the state of the A20
    line is preserved across the call.  The INT 15h Extended Memory
    Available function must be hooked to return 0h to protect the HMA.

-   In order to maintain compatibility with existing device drivers, DOS XMS
    drivers must not hook INT 15h until the first non-Version Number call
    to the control function is made.

-   XMS drivers are required to check for the presence of drivers which
    use the IBM VDISK allocation scheme.  Note that it is not sufficient to
    check for VDISK users at installation time but at the time when the HMA
    is first allocated.  If a VDISK user is detected, the HMA must not be
    allocated.  Microsoft will publish a standard method for detecting
    drivers which use the VDISK allocation scheme.

-   XMS drivers which have a fixed number of extended memory handles (most
    do) should implement a command line parameter for adjusting that number
    (suggested name "/NUMHANDLES=")

-   XMS drivers should make sure that the major DOS version number is
    greater than or equal to 3 before installing themselves.

-   UMBs cannot occupy memory addresses that can be banked by EMS 4.0.
    EMS 4.0 takes precedence over UMBs for physically addressable memory.

-   All driver functions must be re-entrant.  Care should be taken to not
    leave interrupts disabled for long periods of time.

-   Allocation of a zero length extended memory buffer is allowed.  Programs
    which hook XMS drivers may need to reserve a handle for private use via
    this method.  Programs which hook an XMS driver should pass all requests
    for zero length EMBs to the next driver in the chain.

-   Drivers should control the A20 line via an "enable count."  Local En-
    able only enables the A20 line if the count is zero.  It then increments
    the count.  Local Disable only disables A20 if the count is one.  It
    then decrements the count.  Global Enable/Disable keeps a flag which
    indicates the state of A20.  They use Local Enable/Disable to actually
    change the state.

-  Drivers should always check the physical A20 state in the local Enable-Disable calls, to see
    that the physical state matches the internal count.  If the physical state does not match, it should
    be modified so that it matches the internal count.  This avoids problems with applications that 
    modify A20 directly.


IMPLEMENTATION OF CODE FOR HOOKING THE XMS DRIVER:

  In order to support the hooking of the XMS driver by multiple
  pieces of code, the following code sample should be followed.
  Use of other methods for hooking the XMS driver will not work
  in many cases. This method is the official supported one.

  The basic strategy is:

    Find the XMS driver header which has the "near jump" dispatch.

    Patch the near jump to a FAR jump which jumps to my HOOK XMS
        driver header.

  NOTES:

    o This architecture allows the most recent HOOKer to undo his
        XMS driver hook at any time without having to worry about
        damaging a "hook chain".

    o This architecture allows the complete XMS hook chain to be
        enumerated at any time. There are no "hidden hooks".

    o This architecture allows the HOOKer to not have to worry
        about installing an "INT 2F hook" to hook the AH=43h
        INT 2Fs handled by the XMS driver. The base XMS driver
        continues to be the only one installed on INT 2Fh AH=43h.

        This avoids all of the problems of undoing a software
        interrupt hook.

  ;
  ; When I wish to CHAIN to the previous XMS driver, I execute a FAR JMP
  ;     to the address stored in this DWORD.
  ;
  PrevXMSControlAddr    dd      ?

  ;
  ; The next two data items are needed ONLY if I desire to be able to undo
  ;     my XMS hook.
  ; PrevXMSControlJmpVal stores the previos XMS dispatch near jump offset
  ;     value that is used to unhook my XMS hook
  ; PrevXMSControlBase stores the address of the XMS header that I hooked
  ;
  PrevXMSControlBase    dd      ?
  PrevXMSControlJmpVal  db      ?

  ;
  ; This is MY XMS control header.
  ;
  MyXMSControlFunc proc FAR
        jmp     short XMSControlEntry
        nop
        nop
        nop
  XMSControlEntry:

  ......

  Chain:
        jmp     cs:[PrevXMSControlAddr]

  MyXMSControlFunc endp


  .......
  ;
  ; This is the code which installs my hook into the XMS driver.
  ;
    ;
    ; See if there is an XMS driver to hook
    ;
        mov     ax,4300h
        int     2Fh
        cmp     al,80h
        jne     NoXMSDrvrToHookError
    ;
    ; Get the current XMS driver Control address
    ;
        mov     ax,4310h
        int     2Fh
  NextXMSHeader:
        mov     word ptr [PrevXMSControlAddr+2],es
        mov     word ptr [PrevXMSControlBase+2],es
        mov     word ptr [PrevXMSControlBase],bx
        mov     cx,word ptr es:[bx]
        cmp     cl,0EBh                         ; Near JUMP
        je      ComputeNearJmp
        cmp     cl,0EAh                         ; Far JUMP
        jne     XMSDrvrChainMessedUpError
  ComputeFarJmp:
        mov     si,word ptr es:[bx+1]           ; Offset of jump
        mov     es,word ptr es:[bx+1+2]         ; Seg of jump
        mov     bx,si
        jmp     short NextXMSHeader

  ComputeNearJmp:
        cmp     word ptr es:[bx+2],9090h        ; Two NOPs?
        jne     XMSDrvrChainMessedUpError       ; No
        cmp     byte ptr es:[bx+4],90h          ; Total of 3 NOPs?
        jne     XMSDrvrChainMessedUpError       ; No
        mov     di,bx                           ; Save pointer to header
        xor     ax,ax
        mov     al,ch                           ; jmp addr of near jump
        mov     [PrevXMSControlJmpVal],al
        add     ax,2                            ; NEAR JMP is 2 byte instruction
        add     bx,ax                           ; Target of jump
        mov     word ptr [PrevXMSControlAddr],bx
    ;
    ; Now INSTALL my XMS HOOK
    ;
        cli                             ; Disable INTs in case someone calls
                                        ;       XMS at interrupt time
        mov     byte ptr es:[di],0EAh   ; Far Immed. JUMP instruction
        mov     word ptr es:[di+1],offset MyXMSControlFunc
        mov     word ptr es:[di+3],cs
        sti
    .....

    ;
    ; Deinstall my XMS hook. This can be done IF AND ONLY IF my XMS header
    ;   still contains the near jump dispatch
    ;
        cmp     byte ptr [MyXMSControlFunc],0EBh
        jne     CantDeinstallError
        mov     al,0EBh
        mov     ah,[PrevXMSControlJmpVal]
        les     bx,[PrevXMSControlBase]
        cli                             ; Disable INTs in case someone calls
                                        ;       XMS at interrupt time
        mov     word ptr es:[bx],ax
        mov     word ptr es:[bx+2],9090h
        mov     byte ptr es:[bx+4],90h
        sti
    ....

IMPLEMENTATION NOTES FOR HIMEM.SYS:
-----------------------------------

-   HIMEM.SYS currently supports true AT-compatibles, 386 AT machines, IBM
    PS/2s, AT&T 6300 Plus systems and Hewlett Packard Vectras.

-   If HIMEM finds that it cannot properly control the A20 line or if there
    is no extended memory available when HIMEM.SYS is invoked, the driver
    does not install itself.  HIMEM.SYS displays the message "High Memory
    Area Unavailable" when this situation occurs.

-   If HIMEM finds that the A20 line is already enabled when it is invoked,
    it will NOT change the A20 line's state.  The assumption is that whoever
    enabled it knew what they were doing.  HIMEM.SYS displays the message "A20
    Line Permanently Enabled" when this situation occurs.

-   HIMEM.SYS is incompatible with IBM's VDISK.SYS driver and other drivers
    which use the VDISK scheme for allocating extended memory.  However, 
    HIMEM does attempt to detect these drivers and will not allocate the
    HMA if one is found.

-   HIMEM.SYS supports the optional "/HMAMIN=" parameter.  The valid values
    are decimal numbers between 0 and 63.

-   By default, HIMEM.SYS has 32 extended memory handles available for use.
    This number may be adjusted with the "/NUMHANDLES=" parameter.  The
    maximum value for this parameter is 128 and the minimum is 0.  Each
    handle currently requires 6 bytes of resident space.


Copyright (c) 1988, Microsoft Corporation


?????????????????????????????????????????????????????????????????????????????
                       INTRO TO DMA by Draeden of VLA
?????????????????????????????????????????????????????????????????????????????
    

    DMA means Direct Memory Access.  You probably already know where and
why you use it, so I'll skip right down to the dirty stuff.  This all 
should speak for it's self, so... Enjoy.

    Draeden /VLA

?????????????????????????????????????????????????????????????????????????????

    To do a DMA transfer, you need to know a few things:

        1)  Address of the memory to access

        2)  Length of data to read/write

    This can all be put into a structure:
    
STRUC DMAInfo
    Page        db  ?
    Offset      dw  ?
    Length      dw  ?
ENDS

    Page is the highest 4 bits of the absolute 20 bit address of the memory
location.  Note that DMA transfers CANNOT cross 64k page boundries.
    
    The Length is actually LENGTH-1; sending in a 0 will move 1 byte,
sending a 0FFFFh will move 64k.

    ?????????????????????????????????????????????????????????????????????
    ; IN: DX:AX = segment/offset address of memory area
    ;
    ;OUT: DH = Page (0-F)  (DL is destroyed)
    ;     AX = Offset
    ?????????????????????????????????????????????????????????????????????
PROC MakePage
    push    bx

    mov     bl,dh
    shr     bl,4    ;isolate upper 4 bits of segment

    shl     dx,4    ;make segment into ABS address
    add     ax,dx   ;add the offset and put it in AX
    adc     bl,0    ;complete the addition

    mov     dh,bl   ;put the PAGE where it goes

    pop     bx      ; DH:AX is now the PAGE:OFFSET address
    ret
ENDP

?????????????????????????????????????????????????????????????????????????????
    Programming DMA channels 0 thru 3
?????????????????????????????????????????????????????????????????????????????
    There are 3 ports that are DMA channel specific:

        1) The Page register
        2) The DMA count (length) register
        3) The memory address (offset register)

    They are as follows:

DMACH   PAGE    ADDRESS  LENGTH
 
 0       87h       0       1

 1       83h       2       3

 2       81h       4       5

 3       82h       6       7

        
    And now some general registers:

 DMA Mask Register:  0Ah
????????????????????????
        bit 7 - 3 = 0  Reserved

            bit 2 = 0  clear mask
                  = 1  set mask

       bits 1 - 0 = 00 Select channel 0
                  = 01 select channel 1
                  = 10 select channel 2
                  = 11 select channel 3

       USE: You must set the mask of the channel before you
            can reprogram it.

 DMA Mode Register:  0Bh
????????????????????????
        bit 7 - 6 = 00 Demand mode
                  = 01 Signal mode
                  = 10 Block mode
                  = 11 Cascade mode

        bit 5 - 4 = 0  Reserved

        bit 3 - 2 = 00 Verify operation
                  = 01 Write operation
                  = 10 Read operation
                  = 11 Reserved

       bits 1 - 0 = 00 Select channel 0
                  = 01 select channel 1
                  = 10 select channel 2
                  = 11 select channel 3

       USE: Tell the DMAC what to do. Common modes are:

            48h (Read operation, Signal mode)
                Used to read data from host memory and send to whomever
                polls it.

            44h (Write operation, Signal mode)
                Used to write data taken from a device to memory.

DMA clear byte ptr:  0Ch
????????????????????????
       USE: Send a zero to reset the internal ptrs



    WHAT TO DO:
????????????????????????

    1) Set the Mask bit for the channel

        mov     al,4
        add     al,[DMA_Channel]
        out     0ah,al

    2) Clear Byte Ptr

        sub     al,al
        out     0Ch,al

    3) Set the DMA transfer mode

        mov     al,48h                  ;MODE output (read)
        add     al,[DMA_Channel]
        out     0Bh,al

    4) Set the memory ADDRESS and LENGTH

        ;        AX = offset
        ;        CX = Length
        ;[DMA_Base] = port # of memory address
        
        mov     dx,[DMA_Base]
        out     dx,al                   ;send lower byte address
        mov     al,ah
        out     dx,al                   ;send high byte address

        inc     dl                  ;point to Count port
        mov     al,cl
        out     dx,al                   ;send low byte length
        mov     al,ch
        out     dx,al                   ;send high byte length

    5) Set the DMA page

        ; AL = Page

        mov     dx,[Dma_Page]
        out     dx,al                   ; write the Page

    6) Clear DMA mask bit

        mov     al,[byte DMA_Channel]
        out     0Ah,al                  ; port 0Ah, DMA-1 mask reg bit

    7) Program the other device that is going to use the DMA output/input


    ?????????????????????????????????????????????????????????????????????
    ; This routine programs the DMAC for channels 0-3
    ;
    ; IN: [DMA_Channel], [DMAbaseAdd], [DMApageReg] must be setup
    ;       [DAMBaseAdd] =  Memory Address port
    ;
    ;     dh = mode
    ;     ax = address
    ;     cx = length  
    ;     dl = page
    ?????????????????????????????????????????????????????????????????????
PROC Prog_DMA03 NEAR
        push    bx
        mov     bx,ax

        mov     al,4
        add     al,[DMA_Channel]
        out     0Ah,al          ; mask reg bit

        sub     al,al
        out     0Ch,al          ; clr byte ptr

        mov     al,dh
        add     al,[DMA_Channel]
        out     0Bh,al          ; set mode reg

        push    dx
        
        mov     dx,[DMAbaseAdd]
        mov     al,bl
        out     dx,al           ; set base address low
        mov     al,bh
        out     dx,al           ; set base address high

        inc     dx              ;point to length
        mov     al,cl
        out     dx,al           ; set length low
        mov     al,ch
        out     dx,al           ; set length high

        pop     dx

        mov     al,dl
        mov     dx,[DmaPageReg]
        out     dx,al           ; set DMA page reg

        mov     al,[DMA_Channel]
        out     0Ah,al          ; unmask (activate) dma channel
        pop     bx
        ret
ENDP

?????????????????????????????????????????????????????????????????????????????
?????????????????????????????????????????????????????????????????????????????
    Programming DMA channels 4 thru 7
?????????????????????????????????????????????????????????????????????????????
?????????????????????????????????????????????????????????????????????????????

    Again, there are 3 ports that are DMA channel specific:

        1) The Page register
        2) The DMA count (length) register
        3) The memory address (offset register

    They are as follows:

DMACH   PAGE    ADDRESS  LENGTH
 
 4       8Fh      C0h      C2h  

 5       8Bh      C4h      C6h  

 6       89h      C8h      CAh  

 7       8Ah      CCh      CEh 


    And now some general registers:

 DMA Mask Register: 0D4h
????????????????????????
        bit 7 - 3 = 0  Reserved

            bit 2 = 0  clear mask
                  = 1  set mask

       bits 1 - 0 = 00 Select channel 4
                  = 01 select channel 5
                  = 10 select channel 6
                  = 11 select channel 7

       USE: You must set the mask of the channel before you
            can reprogram it.

 DMA Mode Register: 0D6h
????????????????????????
        bit 7 - 6 = 00 Demand mode
                  = 01 Signal mode
                  = 10 Block mode
                  = 11 Cascade mode

        bit 5 - 4 = 0  Reserved

        bit 3 - 2 = 00 Verify operation
                  = 01 Write operation
                  = 10 Read operation
                  = 11 Reserved

       bits 1 - 0 = 00 Select channel 4
                  = 01 select channel 5
                  = 10 select channel 6
                  = 11 select channel 7

       USE: Tell the DMAC what to do. Common modes are:

            48h (Read operation, Signal mode)
                Used to read data from host memory and send to whomever
                polls it.

            44h (Write operation, Signal mode)
                Used to write data taken from a device to memory.

DMA clear byte ptr: 0D8h
????????????????????????
       USE: Send a zero to reset the internal ptrs


    WHAT TO DO: (exactly the same thing, just different io PORTs)
????????????????????????

    1) Set the Mask bit for the channel

        mov     al,[DMA_Channel]    ;because the DMA's are 4-7, bit #3
        out     0D4h,al             ; is already set

    2) Clear Byte Ptr

        sub     al,al
        out     0D8h,al

    3) Set the DMA transfer mode
        
        mov     al,[DMA_Channel]
        sub     al,4
        or      al,48h                  ;MODE output (read)
        out     0D6h,al

    4) Set the memory ADDRESS and LENGTH

        ;        AX = offset
        ;        CX = Length
        ;[DMA_Base] = port # of memory address
        
        mov     dx,[DMA_Base]
        out     dx,al                   ;send lower byte address
        mov     al,ah
        out     dx,al                   ;send high byte address

        add     dl,2                ;point to Count port (seperated by 2)
        mov     al,cl
        out     dx,al                   ;send low byte length
        mov     al,ch
        out     dx,al                   ;send high byte length

    5) Set the DMA page

        ; AL = Page

        mov     dx,[Dma_Page]
        out     dx,al                   ; write the Page

    6) Clear DMA mask bit

        mov     al,[byte DMA_Channel]
        and     al,00000011b
        out     0d4h,al                 ; port 0Ah, DMA-1 mask reg bit

    7) Program the other device that is going to use the DMA output/input


    ?????????????????????????????????????????????????????????????????????
    ; This routine programs the DMAC for channels 4-7
    ;
    ; IN: [DMA_Channel], [DMAbaseAdd], [DMApageReg] must be setup
    ;       [DAMBaseAdd] =  Memory Address port
    ;
    ;     dh = mode
    ;     ax = address
    ;     cx = length  
    ;     dl = page
    ?????????????????????????????????????????????????????????????????????
PROC Prog_DMA47 NEAR
        push    bx
        mov     bx,ax

        mov     al,[DMA_Channel]
        out     0D4h,al         ; mask reg bit

        sub     al,al
        out     0D8h,al         ; clr byte ptr

        mov     al,[DMA_Channel]
        sub     al,4
        add     al,dh
        out     0D6h,al         ; set mode reg

        push    dx

        mov     dx,[DMAbaseAdd]
        mov     al,bl
        out     dx,al           ; set base address low
        mov     al,bh
        out     dx,al           ; set base address high

        add     dl,2            ;point to length
        mov     al,cl
        out     dx,al           ; set length low
        mov     al,ch
        out     dx,al           ; set length high

        pop     dx

        mov     al,dl
        mov     dx,[DmaPageReg]
        out     dx,al           ; set DMA page reg

        mov     al,[DMA_Channel]
        and     al,00000011b
        out     0D4h,al         ; unmask (activate) dma channel
        pop     bx
        ret
ENDP

    ?????????????????????????????????????????????????????????????????????
    ; This routine programs the DMAC for channels 0-7
    ;
    ; IN: [DMA_Channel], [DMAbaseAdd], [DMApageReg] must be setup
    ;       [DAMBaseAdd] =  Memory Address port
    ;
    ;     dh = mode
    ;     ax = address
    ;     cx = length  
    ;     dl = page
    ?????????????????????????????????????????????????????????????????????
PROC Prog_DMA NEAR
        push    bx
        mov     bx,ax

        cmp     [DMA_Channel],4
        jb      @@DoDMA03

        mov     al,[DMA_Channel]
        out     0D4h,al         ; mask reg bit

        sub     al,al
        out     0D8h,al         ; clr byte ptr

        mov     al,[DMA_Channel]
        sub     al,4
        add     al,dh
        out     0D6h,al         ; set mode reg

        push    dx

        mov     dx,[DMAbaseAdd]
        mov     al,bl
        out     dx,al           ; set base address low
        mov     al,bh
        out     dx,al           ; set base address high

        add     dl,2            ;point to length
        mov     al,cl
        out     dx,al           ; set length low
        mov     al,ch
        out     dx,al           ; set length high

        pop     dx

        mov     al,dl
        mov     dx,[DmaPageReg]
        out     dx,al           ; set DMA page reg

        mov     al,[DMA_Channel]
        and     al,00000011b
        out     0D4h,al         ; unmask (activate) dma channel
        pop     bx
        ret

@@DoDMA03:
        mov     al,4
        add     al,[DMA_Channel]
        out     0Ah,al          ; mask reg bit

        sub     al,al
        out     0Ch,al          ; clr byte ptr

        mov     al,dh
        add     al,[DMA_Channel]
        out     0Bh,al          ; set mode reg

        push    dx
        
        mov     dx,[DMAbaseAdd]
        mov     al,bl
        out     dx,al           ; set base address low
        mov     al,bh
        out     dx,al           ; set base address high

        inc     dx              ;point to length
        mov     al,cl
        out     dx,al           ; set length low
        mov     al,ch
        out     dx,al           ; set length high

        pop     dx

        mov     al,dl
        mov     dx,[DmaPageReg]
        out     dx,al           ; set DMA page reg

        mov     al,[DMA_Channel]
        out     0Ah,al          ; unmask (activate) dma channel
        pop     bx
        ret
ENDP
         ??????????????????????????????????????????????????????????
         ? Programming the Intel 8253 Programmable Interval Timer ?
         ??????????????????????????????????????????????????????????

              Written for the PC-GPE by Mark Feldman
              e-mail address : u914097@student.canberra.edu.au
                               myndale@cairo.anu.edu.au

                ?????????????????????????????????????????????
                ?      THIS FILE MAY NOT BE DISTRIBUTED     ?
                ? SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ?
                ?????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? Disclaimer ?
??????????????

I assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

?????????????????????????????????????????????????????????????????????????????
? Introduction ?
????????????????

The PIT chip has 3 channels, each of which are responsible for a different
task on the PC:

Channel 0 is responsible for updating the system clock. It is usually
programmed to generate around 18.2 clock ticks a second. An interrupt 8 is
generated for every clock tick.

Channel 1 controls DMA memory refreshing. DRAM is cheap, but it's memory
cells must be periodically refreshed or they quickly lose their charge. The
PIT chip is responsible for sending signals to the DMA chip to refresh
memory. Most machines are refreshed at a higher rate than neccesary, and
reprogramming channel 1 to refresh memory at a slower rate can sometime speed
up system performance. I got a 2.5 MHz speed-up when I did it to my 286, but
it didn't seem to work on my 486SUX33.

Channel 2 is connected to the speaker. It's normally programmed to generate
a square wave so a continuous tone is heard. Reprogramming it for "Interrupt
on Terminal Count" mode is a nifty trick which can be used to play 8-bit
samples from the PC speaker.

Each channel has a counter which counts down. The PIT input frequency is
1193181 ($1234DD) Hz. Each counter decrements once for every input clock
cycle. "Terminal Count", mentioned several times below, is when the counter
reaches 0.

Loading the counters with 0 has the same effect as loading them with 10000h,
and is the highest count possible (approx 18.2 Hz).

?????????????????????????????????????????????????????????????????????????????
? The PIT Ports ?
?????????????????

The PIT chip is hooked up to the Intel CPU through the following ports:

                ?????????????????????????????????????????
                ? Port   Description                    ?
                ?????????????????????????????????????????
                ? 40h    Channel 0 counter (read/write) ?
                ? 41h    Channel 1 counter (read/write) ?
                ? 42h    Channel 2 counter (read/write) ?
                ? 43h    Control Word (write only)      ?
                ?????????????????????????????????????????

?????????????????????????????????????????????????????????????????????????????
? The Control Word ?
????????????????????

              ?????????????????????????????????
              ? 7 ? 6 ? 5 ? 4 ? 3 ? 2 ? 1 ? 0 ?
              ?????????????????????????????????
                ?????   ?????   ?????????   ??? BCD 0 - Binary 16 bit
                  ?       ?         ?               1 - BCD 4 decades
????????????????????????  ?         ?
? Select Counter       ?  ?         ??????????? Mode Number 0 - 5
? 0 - Select Counter 0 ?  ?
? 1 - Select Counter 1 ?  ?         ??????????????????????????????
? 2 - Select Counter 2 ?  ?         ? Read/Load                  ?
????????????????????????  ?         ? 0 - Counter Latching       ?
                          ??????????? 1 - Read/Load LSB only     ?
                                    ? 2 - Read/Load MSB only     ?
                                    ? 3 - Read/Load LSB then MSB ?
                                    ??????????????????????????????

?????????????????????????????????????????????????????????????????????????????
? The PIT Modes ?
?????????????????

The PIT is capable of operating in 6 different modes:

MODE 0 - Interrupt on Terminal Count
????????????????????????????????????
When this mode is set the output will be low. Loading the count register
with a value will cause the output to remain low and the counter will start
counting down. When the counter reaches 0 the output will go high and remain
high until the counter is reprogrammed. The counter will continue to count
down after terminal count is reached. Writing a value to the count register
during counting will stop the counter, writing a second byte starts the
new count.

MODE 1 - Programmable One-Shot
??????????????????????????????
The output will go low once the counter has been loaded, and will go high
once terminal count has been reached. Once terminal count has been reached
it can be triggered again.

MODE 2 - Rate Generator
???????????????????????
A standard divide-by-N counter. The output will be low for one period of the
input clock then it will remain high for the time in the counter. This cycle
will keep repeating.

MODE 3 - Square Wave Rate Generator
???????????????????????????????????
Similar to mode 2, except the ouput will remain high until one half of the
count has been completed and then low for the other half.

MODE 4 - Software Triggered Strobe
??????????????????????????????????
After the mode is set the output will be high. Once the count is loaded it
will start counting, and will go low once terminal count is reached.

MODE 5 - Hardware Triggered Strobe
??????????????????????????????????
Hardware triggered strobe. Similar to mode 5, but it waits for a hardware
trigger signal before starting to count.

Modes 1 and 5 require the PIT gate pin to go high in order to start
counting. I'm not sure if this has been implemented in the PC.

?????????????????????????????????????????????????????????????????????????????
? Counter Latching ?
????????????????????

Setting the Read/Load field in the Control Word to 0 (Counter Latch) causes
the appropriate channel to go into a sort of "lap" mode, the counter keeps
counting down internally but it appears to have stopped if you read it's
values through the channel's counter port. In this way you get a stable count
value when you read the counter. Once you send a counter latch command you


?????????????????????????????????????????????????????????????????????????????
? Doing Something Useful ?
??????????????????????????

Ok, so let's say we are writing a game and we need to have a certain
routine called 100 times a second and we want to use channel 0 to do all
this timing in the background while the main program is busy doing other
stuff.

The first thing we have to realise is that BIOS usually uses channel 0 to
keep track of the time, so we have 3 options:

1) Have our own routine handle all timer interrupts. This will effectively
   stop the PC clock and the system time will be wrong from that point on.
   The clock will be reset to the proper time the next time the computer
   is turned off and on again, but it's not a nice thing to do to someone
   unless you really have to.

2) Have our routine do whatever it has to do and then call the BIOS handler.
   This would be fine if our program was receiving the usual 18.2 ticks
   a second, but we need 100 a second and calling the BIOS handler for every
   tick will speed up the system time. Same net result as case 1.

3) Have our routine do the interrupt handling and call the BIOS handler only
   when it needs to be updated! BINGO!

The PIT chip runs at a freqency of 1234DDh Hz, and normally the BIOS timer 
interrupt handler is called for every 10000h cycles of this clock. First we 
need to reprogram channel 0 to generate an interrupt 100 times a second, ie 
every 1234DDh / 100 = 11931 cycles. The best thing to do is keep a running 
total of the number of clock ticks which have occurred. For every interrupt 
generated we will add 11931 to this total. When it reaches 10000h our handler 
will know it's time to tell BIOS about it and do so.

So let's get into some good old Pascal code. First we'll define a few
constants and variables our program will need:

???????????????????????????????????????????????????????????????????????
Uses Crt, Dos;

{$F+} { Force far mode, a good idea when mucking around with interrupts }

const TIMERINTR = 8;
       PIT_FREQ = $1234DD;

var BIOSTimerHandler : procedure;
    clock_ticks, counter : longint;
???????????????????????????????????????????????????????????????????????

The clock_ticks variable will keep track of how many cycles the PIT has
had, it'll be intialised to 0. The counter variable will hold the new
channel 0 counter value. We'll also be adding this number to clock_ticks
every time our handler is called.

Next we need to do some initialization:

???????????????????????????????????????????????????????????????????????
procedure SetTimer(TimerHandler : pointer; frequency : word);
begin

  { Do some initialization }
  clock_ticks := 0;
  counter := $1234DD div frequency;

  { Store the current BIOS handler and set up our own }
  GetIntVec(TIMERINTR, @BIOSTimerHandler);
  SetIntVec(TIMERINTR, TimerHandler);

  { Set the PIT channel 0 frequency }
  Port[$43] := $34;
  Port[$40] := counter mod 256;
  Port[$40] := counter div 256;
end;
???????????????????????????????????????????????????????????????????????

Pretty straightforward stuff. We save the address of the BIOS handler,
install our own, set up the variables we'll use and program PIT channel 0
for the divide-by-N mode at the frequency we need.

This next bit is what we need to do once our program is finished. It just
resets everything back to normal.

???????????????????????????????????????????????????????????????????????
procedure CleanUpTimer;
begin
  { Restore the normal clock frequency }
  Port[$43] := $34;
  Port[$40] := 0;
  Port[$40] := 0;

  { Restore the normal ticker handler }
  SetIntVec(TIMERINTR, @BIOSTimerHandler);
end;
???????????????????????????????????????????????????????????????????????


Ok, here's our actual handler. This particular handler just writes an
asterix (*) to the screen. Then it does the checks to see if the BIOS
handler should be called. If so it calls it, if not it acknowledges the
interrupt itself.

???????????????????????????????????????????????????????????????????????

procedure Handler; Interrupt;
begin

  { DO WHATEVER WE WANT TO DO IN HERE }
  Write('*');

  { Adjust the count of clock ticks }
  clock_ticks := clock_ticks + counter;

  { Is it time for the BIOS handler to do it's thang? }
  if clock_ticks >= $10000 then
    begin

      { Yep! So adjust the count and call the BIOS handler }
      clock_ticks := clock_ticks - $10000;

      asm pushf end;
      BIOSTimerHandler;
    end

  { If not then just acknowledge the interrupt }
  else
    Port[$20] := $20;
end;

???????????????????????????????????????????????????????????????????????

And finally our calling program. What follows is just an example program
which sets everything up, waits for us to press a key and then cleans up
after itself.

???????????????????????????????????????????????????????????????????????
begin
  SetTimer(Addr(Handler), 100);
  ReadKey;
  CleanUpTimer;
end.
???????????????????????????????????????????????????????????????????????


?????????????????????????????????????????????????????????????????????????????
? References ?
??????????????

Title : "Peripheral Components"
Publisher : Intel Corporation
ISBN : 1-55512-127-6


By Brian 'Neuromancer' Marshall
(Email: brianm@vissci.demon.co.uk)

        This document is submitted subject to certain conditions:

1. This Document is not in any way related to Id Software, and is 
   not meant to be representive of their techniques : it is based
   upon my own investigations of a realtime 3d engine that produces
   a screen display similar to 'Doom' by Id software.

2. I take no responsibility for any damange to data or computer equipment
   caused by attempts to implement these algorithms.

3. Although I have made every attempt to ensure that this document is error
   free i take no responsability for any errors it may contain.

4. Anyone is free to use this information as they wish, however I would
   appreciate being credited if the information has been useful.

5. I take no responsability for the spelling or grammar.
   (My written english is none too good...so I won't take offence
    at any corrections: I am a programmer not a writer...)

        Right now that that little lot is out of the way I will start this
document proper....

1:  Definition of Terms
======================

        Throughout this document I will be making use of many graphical terms
using my understanding of them as they apply to this algorithm. I will
explain all the terms below. Feel free to skip this part....

Texture:
        A texture for the purpose of this is a square image.

U and V:
        U and V are the equivelants of x and y but are in texture space.
ie They are the the two axies of the two dimensional texture.

Screen:
        For my purposes 'screen' is the window we wish to fill: it doesn't
have to be the whole screen.

Affine Mapping:
        A affine mapping is a texture map where the texture is sampled
in a linear fashion in both U and V.

Biquadratic Mapping:
        A biquadratic mapping is a mapping where the texture is sampled
along a curve in both U and V that approximates the perspective transform.
This gives almost proper forshortening.


Projective Mapping:
        A projective mapping is a mapping where a changing homogenous
coordinated is added to the texture coordinateds to give (U,V,W) and
a division is performed at every pixel. This is the mathematically and
visual correct for of texture mapping for the square to quadrilateral
mappings we are using.
        (As an aside it is possible to do a projective mapping without
the divide (or 3 multiplies) but that is totally unrelated to the matter
in hand...)

Ray Casting:
        Ray Casting in this context is back-firing 'rays' along a two
dinesional map. The rays do however follow heights... more on that later

Sprite:
        A Sprite is a bitmap that is either a monster or an object. To
put it another way it is anything that is not made out of wall or
floor sectins.

Sprite Scaling:
        By this I mean scaling a bitmap in either x or y or both.

Right... Now thats over with onto the foundation:

2:   Two Dimensional Ray Casting Techniques
===========================================

        In order to make this accessible to anyone I will start by
explaining 2d raycasting as used in Wolfenstein 3d style games.

  2.1: Wolfenstien 3D Style Techniques...
  =======================================

          Wolfenstein 3d was a game that rocked the world (well me anyway!).
  It used a technique where you fire a ray accross a 2d grid based map to
  find all its walls and objects. The walls were then drawn vertically
  using sprite scaling techniques to simulate texture mapping.

          The tracing accross the map looked something like this;


        =============================================
        =   =   =   =   =   =  /=   =   =   =   =   =
        =   =   =   =   =   = / =   =   =   =   =   =
        =   =   =   =   =   =/  =   =   =   =   =   =
        ====================/========================
        =   =   =   =   =  /=   =   =   =   =   =   =
        =   =   =   =   = / =   =   =   =   =   =   =
        =   =   =   =   =/  =   =   =   =   =   =   =
        ================/============================
        =   =   =   =  /#   =   =   =   =   =   =   =
        =   =   =   = / #   =   =   =   =   =   =   =
        =   =   =   =/  #   =   =   =   =   =   =   =
        ============/===#########====================
        =   =   =  /=   =   =   #   =   =   =   =   =
        =   =   = / =   =   =   #   =   =   =   =   =
        =   =   =/  =   =   =   #   =   =   =   =   =
        ========/===============#====================
        =   =  /=   =   =   =   #   =   =   =   =   =
        =   = P =   =   =   =   #   =   =   =   =   =
        =   =  \=   =   =   =   #   =   =   =   =   =
        ========\===============#====================
        =   =   =\  =   =   =   #   =   =   =   =   =
        =   =   = \ =   =   =   #   =   =   =   =   =
        =   =   =  \=   =   =   #   =   =   =   =   =
        ============\=======#####====================
        =   =   =   =\  =   #   =   =   =   =   =   =
        =   =   =   = \ =   #   =   =   =   =   =   =
        =   =   =   =  \=   #   =   =   =   =   =   =
        ================\===#========================
        =   =   =   =   =\  #   =   =   =   =   =   =
        =   =   =   =   = \ #   =   =   =   =   =   =
        =   =   =   =   =  \#   =   =   =   =   =   =
        =============================================

        (#'s are walls, = is the grid....)

        This is just a case of firing a ray for each vertical
  line on the screen. This ray is traced accross the map to
  see where it crosses a grid boundry. Where it crosses a
  boundry you cjeck to see if there is a wall there we see how
  far away it it and draw a scaled vertical line from the texture
  on screen. The line we draw is selected from the texture by
  seeing where the line has intersected on the side of the square it
  hit.
        This is repeated with a ray for each vertical line on the
  screen that we wish to display.
        This is a very quick explaination of how it works missing
  out how the sprites are handled. If you want a more detailed 
  explaination then I suggest getting acksrc.zip from
  ftp.funet.fi in /pub/msdos/games/programming

        This is someone's source for a Wolfenstien engine written
  in Borland C and Assembly language on the Pc.
        Its is not the fastest or best but has good documentation
  and solves similiar sprite probelms, distance probelms and has
  some much better explaination of the tracing technique tahn I have
  put here. I recommend to everyone interested taht you get a copy
  and have a thorough play around with it.
  (Even if you don't have a Pc: Everything but the drawing and video
   mode setting is done in 'C' so it should not be too hard to port
   ....)

 
  2.2 Ray Casting in the Doom Environment
  =======================================

        When you look at a screen from Doom you see floors, steps
  walls and lots of other trappings.
        You look out of windows and accross courtyards and you
  say WOW! what a great 3d game!!
        Then you fire your gun a baddie who's in line with you but
  above you and bang! he's a corpse.
        Then you climb up to the level where the corpse is and look
  out the window to where you were and you say Gosh! a 3d game!!

        Hmmm....

        Stop gawping at the graphics for a minute and look at the map
  screen. Nice line vectors. But isn't the map a bit simple???
        Notice how depite colours showing you that there are different
  heights. Then notice that despite the fact that there is NEVER a
  place where you can exist on two different levels. Smelling a little
  2d yet???
        Look where there are bridges (or sort of bridges) : managed to
  see under them yet??

        The whole point to this is that Doom is a 2D games just like
  its ancestor Wolfenstein but it has rather more advanced raycasting
  which does a very nice job of fooling the player into thinking its a
  3d game that shifting loads of polygons and back-culling, depth
  sorting etc... 

        Right the explaination of how you turn a 2d map into the 3d
  doom screen is complex so if you are having difficulty try reading
  it a few times and if all else fails mail me....


  2.3 What is actually done!
  ==========================

        Right to start with the raycasting is started in the same
  way as Wolfenstien. That is find out where the player is in the 2d
  map and get a ray setup for the first vertical line on the screen.

        Now we have an extra stage from the Wolfenstein I described
  whcih involves a data srtucture that we will use later to actually
  draw the screen.

        In this data structure we start the ray off as at the bottom
  of the screen. This is shown in the diagram below;

        =================================
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =*                              =
        =================================


        Where the '=' show the boundry of the screen and '*' is the virtual
  position of the ray.

        Note: the Data structure is really two structures:
        One which is a set of list for each vertical 'scanline' and
        One which is a corresponding list for horizontal scanlines.

        Now we start tracing the ray. We skip accross the 2d map until
  we hit something interesting. By something interesting I mean something
  that is an actual wall or florr section edge.
        Right we have hit the edge of either a floor or wall section.
  We have several things to do know. These are;

        If it was a wall we hit:

  1: Find out how 'high' of screen this section of wall should be
     due to the distance it is accross the 2d map.
  2: Find out at what 'virtual height' it is: This is so that we can see
     where in the vertical scanline in comes for testing where to insert
     it and for clipping it.
  3: Test in our structure to see if you draw it or not.
     (This is done so that you can look through windows : how this works
      will become apparent later.)
  4: If any of the wall segment is visible then we find out where along
     the texture we have hit it and write into the structure the area of
     the screen it takes up as well as the texture, the point where we
     have hit the texture and the size it should be on screen. (This is
     so that we can draw it correctly even if the whole span is not on
     screen.


        If it was a floor section that we hit:

  1: Find out where on the vertical line we are working the floor section
     that the ray has hit is. (We know the height of the the floor in the
     virtual map (2d) and we know the height of the player and the distance
     of the floor square from the player so it is easy).
     As a side effect of this we now know the U,V value where the ray has
     hit the floor square.

  2: Trace Accross the floor square till we hit the far edge of the floor
     square : we then workout where this is on the vertical scanline using
     the same technique as above. We now know the vertical span of the
     floor section, and where on the span it is.

  3: We check to see if the span is visible on the vertical span.
     If it is or part of it is used then we mark that part of the vertical
     scanline as used.
     We also have to make use of the horizontal buffer I mentioned. We
     insert into this in 2 places. The first is the x coordinate of where
     we hit the floor square into the y line where we where on the screen.
     Phew got that bit?? We also insert here the U,V value which we knew 
     from the tracing. (I told you we'd need it later....)                                                                


        As you can see there's a little more to hiting a floor segment than
a wall segment. Also note that a you exit a floor segment you may also hit
a wall segment.

        Tracing the individual ray is continued until we hit a special kind
of wall. This wall is marked as a wall that connects to the ceiling.
This is one place to stop tracing this ray. However we can stop tracing early
if we have found enough to fill the whole vertical scanline then we can stop
whenevr we have done this.

        Next come a trick. I said we were tracing along a 2d map. Well I
lied a bit. There are (In my implementation at least..) TWO 2d maps. One is
basically from the floor along including all the 'floor' walls and everything
up to and including the walls that join onto the ceiling. The other map
is basically the ceiling (with anything coming down from the ceiling on it
if you are doing this: this makes life a little more complex as I'll explain
below..)
        Now when we have traced along the bottom map and hit a wall that 
connects to the ceiling then we go back and trace along the ceiling from
the start to fill in the gaps. There is a problem with this however.
The problem is when you have things like a monolith or something else built
out of walls jutting down from the ceiling. you have to decide whether to
draw it or draw whatever was already in the scanline structure. This means
either storing extra information in the buffer ie z coordinates or tracing
along both the ceiling and floor at the same time.... for most people I would
suggest just not having anything jutting down from the ceiling.
        Also you could trace backwards instead of starting a new ray. This 
would be fasterfor many cases as you wouldn't be tracing through lots
of floor squares that aren't on screen. By tracing backwards you can keep
going up the vertical scanline and you know that you are on the screen. As
soon as something goes off the top of the screen you can handle that and then
stop tracing.

        Phew. has everyone got that???

        Now we just go back and fire rays up the rest of the vertical
scanlines. Easy!!???

        At the end of this lot we have the necessary data in the two buffers
to go back and draw the screen background.
(There is one more thing done while tracing but I'll explain that later...)


        Oh... one other thing... you have may want to change the raycasting
a bit to subdivide the map... it helps with speed.
        And don't forget the added complexity that walls aren't all at
90 degrees to each other...

3: Drawing the walls and Why it works!!
=======================================

        If you are familiar with Wolfenstein then please still read this
as it is esential background to understanding the floor routine.


        As all of you probably know the walls are drawn by scaling the line
of the texture to the correct size for the screen. The information in the
vertical buffer makes this easy. What you probably don't know is why this
creates texture mapping that is good enough to fool us.

        The wall function is a Affine texture mapping. (well almost)
Now affine texture mappings look abysmal unless you do quite a lot of
subdivision (The amount needed varies according to the angle the projected
square is at.). So why does the Doom technique work??

        Well when we traced the rays we found out exactly where along the
side of the square we hit we were in relation to the width of the texture.
This means that the top and bottom pixels of the scaled wall piece are
calculated correctly. This means that we have effecively subdivided the
texture along vertical scanlines and as the effective subdidvisons are
calculated exactly with proper forshortening as a result of the tracing.
So the ray casting has made the texture mapping easy for us.
        (We have enough subdivision by this scanline effect as the wall
only rotates about one axis and we have proper foreshortening.)

        This knowlege helps us understand how to do the floors and why
that works.

        We can now draw all the wall segments by just looking at the buffer
and drawing the parts marked as walls.(Skiping where we put in the bits used
by the floor/ceiling bits: we draw them later.)

4:  Drawing the Floor/Ceiling and why it works!
===============================================

        If you have grasped why the walls work then you have just about
won for the floors.
        We have the information needed to draw the floors from the horizontal
buffer.
        All we have to do is look at the horizontal spans in the buffer
and draw them in all.
        Each of these spans has 2 end coordinates for which we have
exact texture coorinates. This tells us which line across the texture
we have to step along to do an Affine or linear mapping.
        This is shown below;


        =================================
        =                               =
        =                               =
        =                               =
        =                               = U1,V1 (exit)
        =                              **
        =                           *** =
        =                        ***    =
        =                     ***       =
        =                  ***          =
        =               ***             =
        =            ***                =
        =         ***                   =
        =       **                      =
        =     **                        =
        =   **                          =
        = **                            =
  U0,V0 **                              =
(entry) =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =                               =
        =================================

(apologies for the wonky line: it should be straight!!)

        Now...as the end coordinates are correct and the axis along
which forshortening takes place is not involved (this is a fudge)
we can step linearly along this line across the texture to approximate
the mapping. (This is far easier than a proper texture map).
        This is effectivly a wall lying on its side which works as the
texture coordinates at the ends of the span have been calculated correctly.
This is a benefit of the raycasting we used to find everything.
        Easy huh??


5: Sprites
==========

        The Sprites are really quite easy to do. The basic technique is the
same as used in Wolfenstein 3d.
        This is done as follows:

When you enter a 'square' on the floor map you test to see if there are
any sprites in the square. If there are you flag that sprite as visible
and add it to a list of visible sprites.

When you have finished tracing and drawing the walls and floor you
depth sort the sprites and draw them from the back to the front. (painters
algorithm). The only complication in drawing them is that you have to check 
buffer that has the walls in, in order to clip the sprites correctly.

        (If you're interested in Doom you can occasionally see large 
explosions (ie BFG) slip partially behind a wall segment.)

        On possibly faster way of handling the sprites would be to mark
them like wall segments as you find them in the buffer. The only (ONLY!)
complication to this approach is that sprites can have holes in them. By
this I mean things like the gap between an arm and a leg which should be 
the background colour.


6: Lighting and Depth Cueing
============================

        Lighting and Depth Cueing fits nicely in with the way that we have
prepared the screen ready for drawing.
        All we have to do is see how far away we are when we found either
the floor or wall section and set the light level according to the distance.
        The other thing that is applied is a light level. This is taken from
the map at the edges where you have hit something. As the map is 2D it is
easy to manage lighting, flickering etc.
        For things like pools of light on the floor all you have to do
is subdivide that patch of floor so that you can set the bit under the 
skylight to a lighter colour. Its also very easy to frig this for the
lighting goggles.


7: Controlling the Baddies
==========================
        

        This is pretty easy: all you have to think about is moving and
reacting on a 2d map. the only complications are things like the monsters
looking through windows and seeing a player but this all degenerates into
a simple 2d problem. Things like deciding whether the player has been hit or
has he/she hit a monster is just another case of firing a ray. (Or do it
another way...)


8: Where next???
================

        Thats all folks... hopefully a useful and intersting insight into
my Doom engine works.
        As to the question where next... well I already have some enhancements
to my Doom enigine and others are in the works...

Some of what you may eventually see are:

        Proper lighting (I have done this already...its easier than you
                        think)
        Non-Vertical walls (i.e. Aliens style corridors...)
        Orgranic Walls (i.e. Curved like the Aliens nest...)
        Fractal Landscapes (This one is still very much a theory but how
                        about being able to go outside and walk up and down
                        hills etc??)

        If there are bits people are really shaky about I may post a new
version of this... but I cannot get into implimentation issues as all
implementation work is under copyright...

        By the way if anyone out there implements this I'd love to here
how you get on...

        Anyone got any comments or any other interesting algorithms???

Brian 'Neuromancer' Marshall         'When do graphics not look like graphics?
( Email: brianm@vissci.demon.co.uk )  :when we get it RIGHT.'


??????????????????????????????????????????????????????????????????
So, that was it. The promised UUE file follows now
??????????????????????????????????????????????????????????????????
----------------------------- Cut from here ----------------------------------

begin 644 support.zip
M4$L#!!0````(`".`J1R!)IZM204``(T.```)````5$E-15(N05--S591;]LV
M


X/D/]P!08XP1Q;LAW7<1ZV)F[7/JSIVK3K,.R!$L\19XG42,IV^NMW1TFV
MD[C--G3#!`2*CG<?O_ON>/3YR5=[#@_.X25:!.5`@#.531'FQD*!PE56Z1NP
MF`NOE@BN1)1@YI":HJP\VAY<9Q171S'22N4YO+ZZAGFE4Z^,AM*:$FU^"Y66
M2*A5[I47;H'6=4$X\!G2FD/PJJ!U^BP")X?Y$ET/GGG(B8F'&;H_/BA<=>'J
M77\`0DOX66EI5HZH.@=52;O[K,;I$02C[-#C!%-3WEIUDWF(S\Y&G,DU8E'!
M&\R]T:A!+`3\5`F[Z+_%`N4MO+%&.D:#5W.X-1441JKY+?'<`'>A9(8(TH`V
MGE2R*"0H_P1^X0!QVYHH.4:J=`!1I&5#C70HJC3C-^^1JP6A)I5OD=,,TP6%
M"Q9#WU"69LY(!`@B]97(6Z0C9J!TFE>22Q=H4K4*U!X2]"ND))=HO4HI)E<:
MW3$)PUAG<?W]9",=PHJXDTC;5F@+WW%-,Q`\[\A$J0JE<8YE>0-?>5#4QB[
MH$IREW5"DQ4BSV%!Q6-P+\B=_PF`;MIL?_GF/6R?J\K3OK`4>14D'$W&LX_]
MX?#'EY]H-1J,+K/&_.YC?Q`UYN%L%,S#X#UJS:?/9SOF>-R89Q?/@GDP&0/T
MX\8[/HVB8(Z">=1[^I07QE$TSAJJKPW)B4_@`[-SH>!4WCE:F%M3L#940I$8
M2F#`;6HL2+/2O=`>+C-5+AG&5CKHZ$E'CB6W.FC.9X_;VH6N#X>%_((8=5,X
MQ,*!-PPCRA*%A<)0-4)0#V;*+2`5:=8@&`JWCBQZ<W1XT^VA$7K!<%#<PL7%
M.RH:[4D!3(D=-YU5*DSKVIFY7PF+;?,TAV6E7,8X0DK^M@_[AXAL#U!JM!>I
M!U'YS(0S#"\0<Y(1D6':]8)"<(D:5+,-2Z@['E9"^SO[A4UZ`>FR!9?24M(L
MA<5I.$4\`WIE,P.^=]ZB*'JZRG.-OC=7H#2\TL2:/H,^=V;&]X/I8##HQU&T
MZ5;R?Z&DV>\^GDS)=]>?W&<_J)/&_8I4XO-QH7S-A$L`1]\.3R<G@_AD-)P,
M!Z/1<2@DR;9$)L33JGSM!7>6(8Z8CO[71ZS!"=?[3D\<*3E0@$XO`D[E\(*
M"$;NE7@,G?#1.3QHLY2K^DV)9R"K$HZ^.][BH);N\.#P0`HOV.L.;EDEN4I;
M8'8AW`S72Y$3;E+C=J)X,!R=CI].SIY=7,Z>O^@P.D\MM?5Y^]FKC"<91<B6
M;1,1'1ZD#TW)0Y-X:**<U*ZID_6^Z6Q3;%-.C<1'4F:7'2F%<U6!D+HI+W1!
MNBF#=L&Y:2UH$-;ZJIQNHPJSK*/77=X+..3AJN1K><V\VI4T5[N?&YB\"T.>
MK^T"CRI^1L.,(/+6#.=7[Z_#K$F0\Y%\RZFB0*F$QVWXYYYS-0>+-RH,Q7KF
MU3\EXC"8MP!K8S?$1+Z'5Y0]LK!=<GYOSBE)1]M&6<M-U#]RPA3CEK0XA<\_
MYT7E?'U?BL5.ZA+3!OZ^O[%APFN(^_%D&_"[_A3>O.&=@*.H%YV>GAY3@5.C
M91CBMM)_L9Q?J":';!>5AFU0E.WIHGLU>#S@L1)HO*D=[VOTK"Z!-V8!R;H?
MQV?#>!*U`NRM(I&0.QQX0K;VF!XJ<)3L$,GL9GGTL-V2=9?^'NZ2D'N:[Q&:
M,JUGUZ_)^K<]2I`0V=^ES929>O+O\TN_R"__O\J:_!/:_Z&LHN6W?V!'9]'>
M8T-+9CYWZ*&YZW;/7'V"!_'>O!EUE-Y!O1?17DSU1=4Z8:-/<\{body}lt;'OP)4$L#
M!!0````(`#


gemini - kennedy.gemi.dev




J!H)W(XF-@(````#```,````4$%,3$545$4N0T],'9*+VFH`
M%$0'N1-"DB3))90B)=1^_L<Z_><%9LVL;P`0T7Z_'\=Q&(:^[[NNN]_O3=/4
M=5V695$4:9HF21+'<11%81@&0>#[ON=YKNO:MFU9EF$8NJZKJBK+LBB*/,]S
M',<PS%_X.'[[_G.[S74]%<68).\H>@?!X'DOVWZ:9J]I#TGJ>+YEV1:X`B60
M`3$0`C[@`A:@_Z)"O'S<UBA6B`WX*E;2K_R3Z/JE[$O[+ZT_M/R0,'TP31A>
MZ#NT5S0EZ@SG([((QRWV:VQ76!M8J5A*4'@(')ZTOD_69=#/#R6]"_&%"TMF
MF\,]P8ZQBF"%,`,L?>@>-!>JC>;;Y'-V&"-O\,R7J?:*TLI2(XD742@%/E\L
M3@ONR+$1R^Y89LLP'L,X_\<8@`;(@(AYIF&@KJ.FH:JFO*`DH>A`P8Z\#=D.
M&2:I&HD2<0L"0S\-P`2\@`YH@`K(@1-P`';`!O@QS/\`">#G)[VO],SI$5'K
M46/21:(2E.%]0!N@<I$8"!0X`C06?$=TF:9\&(Y]'[:MWS1.55E%H9].<A2)
M0;#P/-:V&<.`JD(4Q_G['#_WU^?2?\[=G-[G^#:'U\FO)[>:K/.H%Z.2C4+Z
M9D]O)GXR48?PAN`"OX)7P$WA'/_\FCLL-W]FE14D`X(&7@8GT/BB5T?]C;J:
M[F=J4KH<J8KH'%"^F5-O/*V'H_N,G<?!:2.[":TZ,,_;9;;1D[5V<)70EK>6
MY!FBHPN6QB\53I4X66"%!?-[_C]02P,$%`````@`Y#HH&\SEC6=("@```#X`
M``P```!33T944D]#2RY&3E3M6HF.W#H.U/?_IB.`'4+0-ZS(XNG)'+LSF;R\
M;2,8A&VW6[R*+$KC>7WJFI.:3$Q$\YHN,]/Y-\]C/,['Y^Z4#\[%XR+>*B\^
MUQKG?UL>/N]8>^_!YR_C!5S>-_ZLPF?I3>&S_/]&OE_'6+?W31[-P%VFV^]_
MQ25&INOF4E_W%3>6?)"N/1^<E<UZDX8L;L;-X[KV\%CT2UD>@WQ_GMO[SH^T
MWUNRVER/KC@_,`5.,$%:J0B_XY7G];R>U_]P_3Q7E1\_?CZZ_.,A'SSLT\?/
M'_PXW_&''H\'R[_QG=<+_!.`F"@ZUW5IA=(J=<#H0L4Z]4GE@\Z*46.<6G6@
MFSB!160N"'B^NQIZS[5OQ637Q\\;]OC+KUZN3_&<53[VJ/A]#-_J^T'N(U_E
M]C[/7_."FXXYM]1'Z2ZDYSCWI9Z>BJLMAW@)P+_%G4_\?UY_\I(F>%!O[&H0
M2F.W2KZ?J%U_2_J?G&OI))U]DV>_+SG:64-/Q\E=]?O[:C+_5?Y_N[%OLOA_
M?.\*;V+_^1MB<G?!N5ME+:$I7\>!I1(*1E,"O=[+;PM+Y+V+\P6\1Y%GL]1Y
MOAGNND'[38^[FE'_9]S7^N_N.)]/U'_C/ZSA=R[1:-K2:.EWSF/Z<ZH_ZK_(
MJK\T#DOI'?27"J7\U_5W_NOZ2P%3/FSZ2U8HE09#R_HGOSM/GX*G2OTS&H1V
M)BRL\M=ET+QZ3>\$56QP;`O]CQF(;'%A#YL'4-@#\P#U@,\#&/:6G,`\0/^W
M]E<`9,4N1ON7D7E<EWG*.K0@'WE@T;)0NZO]GRS?'H;_+4`H]`5+37VE:Z&B
M+VOXB.^7R9<:0/TOOZBRS4_$_1J@AI(**Z1+4)14=9;'*.NG@QUA&$H:J/:)
M#JJ1M+].I_?2;'39XY]C-$.M_UWO]K^B?Y?M>5K9_TU-NEW[/TFGV>(?,R:=
M!T@`:<!#@;,&))/7%(M\5%>,HG+"I)$HWW>C3+S0Y6.-TEH"]S0\7X]$TMPD
M73G<4N(=;LIX1['+>-?[D@(UWJ_9P5A2J$*@KFE\Z`+.07]BG\Q!W_-WJ?H2
M3Y!9%"&KSV33/%8;8W2E:[=Z31'_E[G&XQ_U_XKX1]3,B'_


D7\#_@_XE^#
M>-KW)3[/HL3&<7\@*O8H^:9QD/FX&GH(_M/-+*5@,5P206#^@95E(0:RC!#2
M`#%9X$LIY:"+L&HO+E/S%WBW//YEP>9YBW\U\.)A^I'JK_&O^4,8NVK\;RNK
M*HD1,XX1LZE2QC]W\"ZR%*DRX=4JF1D+2`LS)?Z-U_"/._Z=.Q7_-,8*_LES
M%?_PFL0_EQW_\GZ(]OZL=5A/ZQ>R1TU3R5]3[$+QFN;_"\"M_K\0B2P/R*R:
MM+RIHS76IN.?];^!?S/];^Y+_W/VO^)_8<P3^D[@W<&_^3K^Z;LK_@W`1N)?
M=`&O0!91!CY;YJQ6#[/$G:Q+(+-X4'NLB`=DZHIX,'M$/31[Z&M6VH-A2L5_
M&%\C0)L)K`#%BZR2>N%P>W!X$S<CB">73/Z62_=#LFV09!5GIBRM,D4)@GR<
M]J^94"!'3ZA8XBG<*<(NC[BMU<40&9Y?MX!]7O]4]B]]3>GTE.-P\C\-_1K?
M[\3_00UQ?X#VI/O[M67YTSN!_YKLO,UF^JRF\A]CXZUES]+KOKV-"2"7_5_V
M&FO]&Z/D!/\EU#_?_Y5V\XK)F.+[5>8!P/M5QRJ=VT[F3A-?,M]/[W_Z/`O[
MGR.6@$IJ75+L=Z+1T2>WRMOW0,?F(#^J\98@Q[ME.4>1QK77E^Q_2C:1<72;
M2Z'Z2E["_][_.O\E]+_!?\G[7V6`#%JD+1UD(SUF"^WUT:BKC/9$;8C^UX86
MRV19F_S:ROD'Q@=\ZW\@^_P#3I@^_Y"ZSW7^@2T<PQ+HCW88\Q_5/^BUZ7]9
MX(;^1G>*_LJY>:/_1_PW_L.C\Q\>G?]8_`?_.1\HGZ*8_QC_G1_DO];_1</G
M_#?]7^=?XG^;?['E/X7^F,9Y_PO^#U.IOE3F'ZG_BNTPUS^GNE5._7U=J\G&
M_^CV_5=ET:.^GRPFRX05A'>-ZG^5B_];_"N1K_%/OXQ_-:'S'^-O.>]:`53;
MXQ_=L_,_Y*O.?_2!"3HE0*;QC_[9_1W^M_SW^(_\?V/^%_R_Z*_%N^BO_+_H
M3V/6^`?_+_HO)$CHK_R?4W_P?R[S+X/_G'])_'.;?VWM"HW?^V0K^+]/1>R^
MA],*OHEP6LE'97/5_-_JUTTV_=G#*/7?9?2B^LL,ECZL/U'7G]O\+^8_R7\Q
M]0S^RRG3R"%D\-^46T'F7^Q\?CK_I^,?6?SK!OD*_;GE?ZRGS'_4ZK/IO\O\
M!X#;]==O%OV!?YH&&(<`__3]R"JXS%AEB7_J^&_\N>0_-?PO^EO^A_X2!_IE
MU7]Q"Z7&A]JY.6:^;05&?$E0^0KD;_)=(`=1KW]8'4?]`]YD_7,@<\@>7]Y,
MBOO*O!_U</;]#ZKQC]X^XY^@?\[[D?E?-N__Q#&1VVF-+HN"7/6/^O^J_JOK
MC\AT_77_K^H_?L/U_?&/-T@7KXB#V9;[?]@K\77>OO^#=L+SG^"-V#]+6K@K
M.P%F%!?MW>?DP^K_%^JO^&<YAZ%AONP&Q2]WM6\RW08@[^V*O[=+3K>Q5_0_
MG]"?.,!DOX)W[3RP\_]6<U=;J.SG5OYV5<*IO8]"GM!`S:=/X=]OUY^G[?]P
MV:Q!_J>;+?Z+??#^W"^W^C_T7=%.\*?U3_X[;^??DO^F_J//OY<1NZS_F.J1
M&0N6Q:1&=/YH_ZO/E_[7?1/];\#IQ_K?-\Y_?2'_4_\'_[/QE>(_Y6X262/I
MVR@N^WU=P4BVIO;0@8=MHX3^%AVA/^6LE>TM@0)]/_#%^/NM\X]O^5_'!;_)
M_PK.Q?]JH^I_&Y2D_W/YYO\H`^;_\:?]?]W\KZU`\;_N!!?_V_Y?^)_J_$.?
MW]W_.B6H_F>\ZBW_!]^!PV<@%39]+^4_5DYE^\<6[JCA_,>VZI+_#.]_G/_,
M&_^Y/LY_YHW_C.Q_K9.V_M_./T`/#):\Z)+-`8/_:#?Y,?[7NQ=ZL?5N.T[?
MQO]LO&`R]6([NSYO\[]6_TQ_KW^\ROD'Q/_X>/\'_5LAO$V6;_V..:R2(LE_
MX3_R**6^@?]W_K-Z_5M[U/JG^]^U_C7]T_^F_VCG/\QL37^OYJ$_SDP$6-3#
MSL`_;I'5]`7^%?71_X;^@B2J3^A/F)^Z_M@Y#?W)ZXV[<X3_5W8JW]#_N7/[
MMD%/([J=8!F^_]\Y\K#^Q^=?G^__[HRN'=(\3UVSS"/)XGWT\P[<^1Y^7`.4
M40M@59_/%KZSC.^X/2UIV"8?'.<]O`ML_$;E"NSEW)*?P+HYI&EK!:+3P?;!
ML4<UD-JCG(J!/1;?[-'.ORVK_SZP/$^MY>??;,H-_WO'23@^N<H&X!7[_1!G
M/4XJ)8ES-]#.FE+];A8\[TU2I8KW\%2YN2K^RVL+_G.>?_(FJIQ_82[XCU-`
M9?XU9\=_>HG_N^/_7E3G7WW^.==J\T__29]_KE?GG^-Y/:__X^L_4$L#!!0`
M```(`.FNJAP05F*;QP8``(<;```*````0T]04$52+E!!4\U8[4_C.!/_OA+_
M@S^L1+N$*FD/6-'KHR6\9-&Q4`4X[H3XX$W,-H_2N$K<7>!T__N-WQ([2=-R
MQTG7BA*/Q[\9SUO&_N/];SM_;KV;YO1;CN?HF"X6)!]OO;LM2(&.<P:/\GOS
MO"!;[Q`"EI0_HPD*243S6!"M3^@T:4$+S4>'R']F9%R;.LUB(18>IU@+.\IS
M_'SO#@;#O;T'1!^U'IK3Q_D:M6`!")0X'N"X-9@Z_Y06+?QM"O//[;2-F=*4
MX&S5!OGW5YSKG7J`H#8\UGNR5/@)4*\>]58ES^5RSMD<_G"19*2HC,J_X-B(
MQ,N<<.0>;-8!]X`W2N/W@=$GWY),H!T5<ZWKG'Z'W_C)0:/HX\RF8H"Y![`'
M3:9+IIAQJFE)%@E:R]*P<Z'!&&S*Z*]FE-;6-J\,$A"F;*)-AL`9+>;1+@J9
M$S#'9Y6%UQKNX"T,UQPC!54W[GW('AP3:"5CL"FC7S%*$W)#H,,)6$,.`CX(
MU,#G`Y\9]K9M?H<3%A*6XPBL?5049/XU%?6FM%]IO=')$1COTR?O4$Z4.FH5
M<19+@JO#\__9"_S"$KYN^(IU?!FL&YIJ5TI?RT#AP0'_":O2M-^^B0BG*3+W
M*JB+93&#G15BD,8%_!:)(R$%K27CJJAQQ;@1+E9L2.X(9@_V/PI*3A9R5?%5
MZD#YF.O0MM$S').K)>M=4EE2X/<Z>2$MJ0"?,P==P,QYQL@WL7M9Q#B&5<?,
M+#FC.3KC(>(A1I$4@V):0L+T13FMI5?S%9#\G(M:6-R?/0R.:7I_\3`(T?^0
MBVYF)$,G).K59_OCSM5!Y^I@S6J_<[5?K;8+4B/6OD3?<'=MP>!AU_5&LRI_
M>5AX[JRSX`'V#7EBFV"[FV';^"<Y_B$;B-XE%>\B!Z%KAG,FWCN_B\>68+J@
M=`'D.YK'XWJTR+F)7LRH?MI!2H:.#RLXSI(T/9[AO/>%S._?8]C0(4?Z,!JZ
M#P[\.%JM'4[>E9C*0_4M6@:4^^/EH)8D>G\.[RC4T_4"1RNR1^W9["8X:5C9
M0859%H6--BD,[-;I,TX?00WSS6]849M"JLMUO7I$8M1WW'X-85*FW4GR'0WE
MK%1B@O9'@JJ8U;L@$#7?'3=<UI+CMH\4[[!DUEIT9CP7.%&)9LR(=.-P*N,X
M,*\'@"V%?!#;J*6P!/,V`PLV`AMN!N:W@+4OFM)"+3(280?U1.1Z?5C>4S[;
M$0'7ATD)O.O!HP['<3OZ[=0`O\F7Q*Y2K<[2?MH!RYF5^JT=I_>%=J7L_ELY
M\57`KW%H!_!_V+DJAWO\/_BTC[[0&(W,:FB7?+,FAI1A1CH+8O4*:)Y14,OG
M5E1&>73JC\U#T@W(J1^2;F0#;YWC^*:=\FFXIC!R5-$R.4C7PW]6Q]9W+ESD
M?6L8/*@:W(RP6G""E>IQ61-B="F-8)N47JFC=$30&4Z+NM,Z))3>WEP"CU$[
M5QHB*OXF;-EWU;7IVWRGL(^:C&S52EN=\@QD)U%)5F<%[M]^+6^L/N)VVM/F
M<:HS_"MZB<K95<O(+6#T8!J_!+':'+N9V52#_HK6Z(2>LZ,T-9J<4OR6>4VA
M[S6$#%0@O3=DC$RMSPV.\669*XJRP0-7=6-**$B+]VQ*=HDZ;ZT;&*T%H)I
M;*HR07NE\?Z>!Y7Q0[(@F)5Q9131.E`3IY+*L\4XE)P_HE_(\S0G14%B.SL:
MI<$L<"ZO6?LCHURMJ"9OI*?20!\V:T@VHYEO8OR4&"^[<O8V8TE:UJ?VX\EQ
M3N+"[L+MNQ[E&@^(\.=[+6^IU0%8':Y@<#=+4F(Z(Z;@<AP#15>,YL%KYB#/
M)D4MI/(J8,713-[)R"ITX(1.X.B3IZ2X_*LH=WG"R$76VY:%0-XWWN08O)8/
M!H-MFZVVQG]&IR^G_);SJ%C,GI\2O&;!,<T8CABZ+1#-T!K\VEHV(Z48Y/O7
MJ.>.O#[:10?[>WLC;]@M^72.$]XCR`]Y(9]J_+WMTL-82>E@023^@?.XZ&)!
M7T$D>>YBF4>+&3!UHF@P@@N6D$%4#)99/,#1X`5W;YGF""T(`S]J96/,\'>2
MLT%$UZX6#VA.4)&!AKO"?)@U7=;;G@ZN!LBG3V@X\D:-V<])FD8Y*5ACYA*T
M21O4T?Z>JXFA9Y3DP!SXWHH39ZV0617,O'GLHLDL,:I`58_XB<!#/Z-07O+P
MEB&TIP,^'533@3WM\VF_FB[!RSKV[U6%5?=]T3(OFG<Q1CFLE\C*JF65T4PM
MS?-;NL02HOQAW[F%MC?LR<#V1>VV;L7E3Z4PK^Y'C(D[DO?NP2:%7KWECJ6)
M>7=6=DF\S5#O(Q`V^`M02P,$%`````@`"[\0&]=EF-RA"0``4BP```H```!7
M3U)-244N4$%3[5I_3QL]$OX?B>\PE:I[H6RC9%/ZMN$X*?PHH+=0E*3\$(G
M9W>2[.%=Y[Q>PK9ZO]U]L//8N\DFV=#`VW`ZJ:%-6'O\>.;QS-CC\/UUZ^V?
MZVO?7U]MZ8]S*?J2A=#YYT#PP-M97UM?^]H^;*^O`<"^5*9A_\M9NV-:+OH,
M&G`II`^[\)I5JU4CH'OVN/#N=%]32I;>U"J5=U7'O-^"Z,%>JE"/V,B$Z;51
M=>9_:DO^E(S==!Z#7A:X%/PQZ*<`EX`OAGXJ\!SX(NCG`,^`ET,_%W@*O`SZ
M45V66.J%T$\#?F3$+/33@1>.FH9^'O`"\"+T\X%+P1^#?@IP"?ABZ*<"SX$O
M@GX.\`SX+^A?T+^@?T'_GT&O,*FN="OXJ1O8"K?=!2+/`W[1(\Y*#V8K/4ZN
M]!"\TJ/[2@N.E99)?[6X&R-OFH)S?>VBV3)MEP/!\9SQ8NGI;K^GVK,^+CUW
M;#F;R"M'OUW3V[[@625K.V70'RC'%Z.HL2<T)(NRB;[_YZ>];+WMH9](A#:J
MT_VCY@[`=^@,@AB&XZX^JAA2D4`0*0%UM_K@5O7_[?<0"A\KH''V#H].SDAQ
M%H<9-Z&XSS@"]N!4J[7Z(.O1,'E/K6H:,2*S#\\.5F]D!Q]4F9$252(C:Z>V
M4FFQS+PE[:O^S^R;&'')`M5")9F'.UK5&,,N1ZDGY:R+G#3A-0>XJUO&AI`=
M_H-3/VAJ57FM85N#"!AW_`?[Q"*?'JL?,A/_%7W32%K<74[\FY[875^S-*QR
M@8]0G3.^L2^XD&<"&C;<X()):#E'SE[6LFF"+/,!B<R/00T0[AE/,*8HI:<6
M^@X<2<3(&+2G^PH2##R4B@61-=&C&8U<[D@:(B1/2D5B0P3[F?"YD.KF==W[
M_18:NY#I:L.^12UY_\=;VWA4UK@WUWBX<G;+J%U(:TQ9XPD\9A26,O5AGJF)
MZ=35*FL\*FO<>Q&F/C$?#\0H*A(R"=.>[K7<Q)[A122*7*7+F7=GG.6BV0(N
MQ+#FT+O;""*%?1/)Y&R=<)CO+W`SWEFZXYT%QG/J?PK#H9!,IA`K_=G7TVN:
MI]V=%<C/,]TG_6Q4:.S62+GW[\`7,.Z&J61C6_(A;C;$W=Z>&0,4H4/&8</(
M.=J2F]JM^7#M1_UV<VS$20^LP#^JI'`$/GJP89OFI-QY*7=>JCXO59R1B#N9
M"W]MUHS76KJ,#<#TBD9"08H*OJ$4DV.'?M%4#LTED<5HDT(W!1'9?=,*G2]+
M"*G7UM.0?A&.'&!<H40?-*4<E4*KV!C:[CCYYV3_66&*2-0P>$!MSY5S#0TX
MR1P7[`%G+DL,$Q4#`SM&1,6@T#2-9*""J`]^(-%3/"6G"C$4,IT^=9QJ5F\N
MCIJ-JZV-ZS=UM[IYV]C5,[[(H6(_B2*M)$]/610,$\X4'0!I->RZ99:&XAYC
M2(;&Q/%Z65^`6.AF9A?6^E:V"77I>XQL^;M(7&C&@,Z%%-N,CUB:91(6HLD<
M3!KQ0HXH21'D;'D*+R;<4ZWD1GZ&O7%KU=O<(^N;.Q.O)C=D]PB?6:Q,6DXD
M%%]_EH!IC`GPK>-6/[[),+\;26BA!OLZ-'PL`-.Z.$7`::4RS<XU047%E(!/
M@=3/0Q%G8)2H/AL*=L%FJEI5DUJ(QPWJGLQEGHH6F`9WML'&ZF1[66&<21PR
M6>IH9N.=^)EQ,VWB@!9L*/5C"GW)_``C+1=$"BS(Q!523SE0Y\7>$F>Y\?T
MU:MS>=X00YG^AEON&KOTRQMW9X&$JR6JBSKKD\X\H&?TJ-M-:BE%%D[CCK5\
M6W^*+G/:O#?:?/SKVCRJQ8RJ"YCY:'2IN4]8HT>L7YJBY92KN5:[[1?6[@=:
M;5NM/KR0.RW)U8="KOHY7#U=J]6?G4^B/D:!2&*>4M+?3^2YB$NV4I/?1!RH
M0$13VR61--XEF11)Y!?/%5,51G[UL@L;]M=-JOH-QUM0VYGL*F;B`Z'GO13R
M#F9>=EL)>O;>QAXPZ7['E"WTN07O`'F,TZUOH;Z3CS3:Y@.O,Y%K/;`^'CAI
M?0MN[B$G/8OU#W"WLZ.M44*+?FI^;A_N%*2NM50MES(3%J2FT/X.M2(8R75:
M7V?`)D(&JR#T$GYR(-G(_)G'E&_XNC4N^(*@.RMZO#AJ%CP@W^*NC+=?9^>A
MDW&9-;W974T=%MX5SPK4?<T7=ENVC"HW1LPQ,]Z"$=;4%03IY)&H<SH*DS->
M;5GEB.JMZ_Q7<M$7"<.V8E*U(W87X%"(*8Z#+/BT+(P&J(5'2,<.W1I$?7/X
M^)MYQI`.&*&@


T%.V\WYX+/Q$*MFJ68W,>+#5EXUK)BON3<T\(A,I43.>,6
M!N3Q`SJMT>*<\S52`8<_,!U*C&/,[F>IA/?S`E]KP/P[3`OK4C"2R[:75>Z7
M,E#((]CX[3@@"B6^@C&E#.*0<0X\4(HC2)&H($);%WP.[G$42/QM<Q:(KE0.
M,%(#6S,TV^?'UU<G33U,X@B4Z"/-`WI)J7C@2>0-H"N1W95`*;AG,@Y46H%+
MA)XY-,=L1,L:C_4);)'6>7M,?Z$%/H9B'JJ;PN&#DABB-JNG%0F%5C&&+O/N
M'',-Y*,7:`HI8*C*T[@*6%RB%,3)D`>A-A&R2![;2"%]H.>'CF1:-0EME`$5
MZ$;%>:Q3%O`]\0![>VTP;JMM.4ADET45>/0U`S6+VQE,ELL341S$*KM6&0D9
MIJ#2(=K8@*[0*T"_V'UI7L?)1F5YXD+<Q?#OA"C"7@\]%=QC!6A*3_C:A!+.
M?.$E1!C=8+!L\]-Z>3A4T$6MAT]L4ZD@`%F<`MZC3$540ED\


gemini - kennedy.gemi.dev




GW]2A@76[J
M"*TURE@Q`[*8MR4HHVN/C+;8H7P1>GWFP(@%2MI[)4#E5<P%BV)W&)58FE\,
M]*0(IYUCVB<L$72Y;RV:AZ)K8(T@XEDK!X(B`%-:S+O*#^QJ(]KPF/9(*K`,
MCJ!O4^P7*7I%%/,4C`(U@"2>0UXTQ3%%2I3"'9K[$(()H@2AHE^PC!=/LI5^
MR+_WL0\E.3__SF1&BR;GX)MK+)._1LPD,!8."ZF+`EO9FYQ\66:MU$CGW%R-
M9?P75G!Q:$,ADIUYQ,*\66JLP+5>^I"E-(TW@(/#L\[QEY9=9GN=1O<GH@='
MK>999QZR?7K2.2Z;G2X'M0'WE.A"C&.Z8-6K,C9B42X:"VB0"MV*6BV2L(LR
MMN[<$ET>Q,I,87.D!F[,0TTM\I%DD8)V&*C!#R3/*U\J0&:XOU=_(/H'%Z+W
M`YGZ^W<E,)<(`S&T%Q#(I#7,A*$0T:LE7)Z^O9UU>7S0SY5*9=ZA#R._\E]0
M2P$"&0`4````"``C@*D<@2:>K4D%``"-#@``"0`````````!`"``````````
M5$E-15(N05--4$L!`AD`%`````@`,0"H&@G<CB8V`@````,```P`````````
M```@````<`4``%!!3$Q%5%1%+D-/3%!+`0(9`!0````(`.0Z*!O,Y8UG2`H`
M```^```,````````````(````-`'``!33T944D]#2RY&3E102P$"&0`4````
M"`#IKJH<$%9BF\<&``"'&P``"@`````````!`"````!"$@``0T]04$52+E!!
M4U!+`0(9`!0````(``N_$!O799C<H0D``%(L```*``````````


gemini - kennedy.gemi.dev




(````#$9
B``!73U)-244N4$%34$L%!@`````%``4`&P


gemini - kennedy.gemi.dev




`/HB`````.!T
`
end
-------------------------------- Cut from here -------------------------------