Contributor: CHAMI


{
Hello Gayle Davis...

I would just like to say how pleased i am with the continuing success of
SWAGS..

Below I have included some source of my own. a cd-player, that I believe
is quite nifty, anyways I like it.

I would be very pleased and honored if it was included in SWAGS...

Yours truly,

Lars Koudal


Denmark, 25th of february 1997

A bit of history:
------------------
   I am one of those people who use my cd-rom drive for playing music while I am working,
eventhough at home I have my left shoulder about 5 inches from the volume knop on my
somewhat larger stereo..

   This is a habbit from my good old days where I worked in far less equipped environments.

   Nowadays I have what I need in win95... 

   Back then though, I was never really satisfied with the cd-players out there. So I wrote
my own... I used a lot of routines grabbed from SWAGS.. (Thanks...) I always intended this
to be only for personal use, but as the program grew larger I felt like I perhaps could
earn some bucks selling the damn thing... THAT was in my young and restless days...

   I recently picked up a newer version of swags (haven't done that in more than a year),
and was very thrilled to see how alive this wonderful source of information is..

   Therefore I decided to post the small version of my program... If people want it, I will
ofcourse send the full-scale version as well.. That hasn't been fully written yet, but
the damn thing works...

   This version is called mini... Probaply due to it's small size (compared to the larger one),
but since it is some years ago, I am not quite sure... :-)

   This program uses, as mentioned before, a lot of routines and units from other people. All
grabbed here from swags... Remember! Credit where credit is due!

   When you run this damn thing, it pops up on your dos-screen with a single line...
It shows what song is playing, and how long it has been playing...

  When you press Pgup, it goes one song up. Guess what happens when you press PgDn! :-)

   Press '.' and up comes a list of songs you can pick. Just use up- and down-keys to scroll
and ENTER to make your selection...

The whole thing ends when you press ESC...

   Try to press F1 from inside the player... I had forgotten this little nifty detail until I tried
it out a few minutes ago...

   BTW: If you have a SoundBlaster in your computer, and the routines I use for detecting it
_can_ find it, you can use '+' and '-' to adjust the volume... Pretty nifty...

   I used it a lot from inside Turbo Pascal.. I made it a tool, and just pressed Shift-F5,
and there it was... pretty handy... and a lot faster... Ever used QCD (comes with SndB)??
Goddamn slow!

(If you can't figure out how to make a new tool in TP, don't bother... put the keyboard down!)


Well, so much about the past, a bit about the future..:
--------------------------------------------------------
  As I have written, this code is from my novice years. If you for some reason want to contact me,
don't do so if you just want to complain about the lousy code, the many unused variables and the many
work-arounds I did for making the whole thing work. I provide this code to
help novices people out, as I was helped myself some years ago...

If you DO decide to contact me, you can e-mail me... (Sorry, left FIDO years ago):

lkoudal@usa.net


Have fun!

Yours truly, 


Lars Koudal.



{Installation notes... Cut and paste the files to their original names, and the compile MINI.PAS... THATS IT!
Play around as much as you like...}

{CUT ... Save this as MINI.PAS }
{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}


                                 PROGRAM mini;


{ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß}

{This program is determined that you start it with a color-screen
 If you don't have that, or you do not know how to change it to a monochrome
 screen, don't bother... put the keyboard down...


 Lars Koudal....

 hint: look about 15 lines down, and read the comment...
}



USES
{  effects,
}  CD_Unit,
  CD_Vars,
  DOS,
  CRT,
  TPTimer,
  TCTimer,
  TPBuffer,
  ScanCode;

TYPE
  TotPlayRec   = RECORD
                   Frames,
                   Seconds,
                   Minutes,
                   Nada     : Byte;
                 END;
CONST
  TextVidSeg   : Word = $b800;
  vga_segment  = $0A000;
  fade_Delay   = 20;
  vSeg         : Word = $B800;  {change for mono}



VAR
 CurrIndex  : Word;
 ScreenLoc  : Pointer;
 ScrollSize : Word;

  vol_time     : longint;
  toslet       : boolean;
  sbfound,
  portok       : Boolean;
  ScrBuf,
  Pdwns        : Word;
  origmode     : word;
  lcv          : Integer;
  temp         : Char;
  a1,
  a2,
  a3           : Byte;
  b1,
  b2,
  b3           : Byte;
  crc          : LongInt;
  cdidstr      : String;

  number       : Byte;
  SaveExit     : Pointer;
  TrackInfo    : ARRAY [1..99] OF PAudioTrackInfo;
  I            : Integer;
  CH           : Char;
  SP,
  EP           : LongInt;
  LeadOut,
  StartP,
  TotalPlayTime: LongInt;
  TotPlay      : TotPlayRec;
  place        : LongInt;
  secs,
  pps,
  s            : LongInt;
  Track        : Byte;
  StartTrack,
  EndTrack,
  NumTracks    : Integer;
  Player       : ARRAY [1..100] OF Byte;
  PlayTime     : TotPlayRec;
  result       : Word;
  resultchar   : Char;
  Hi,
  Hi2          : Byte;
  crstyp       : Word;
  arbejder     : Byte;

  lvolume,
  rvolume      : Byte; {Volume-control}


  Scroll_Lock,
  Caps_Lock,
  Num_Lock,
  Ins,
  Alt,
  Ctrl,
  Left_Shift,
  Right_Shift  : Boolean;
  Bios_Keys    : Byte ABSOLUTE $40:$17;

Procedure WaitForRetrace; Assembler;
Asm
  Mov  DX, 3DAh
  @Rep1:
  In   AL, DX
  Test AL, 08h
  JZ   @Rep1
  @Rep2:
  In   AL, DX
  Test AL, 08h
  JNZ  @Rep2
End;

Function LeadingZero (w : Word) : String;
Var
  s : String;
Begin
  Str (w: 0, s);
  If Length (s) = 1 Then
    s := '0' + s;
  LeadingZero := s;
End;



Function ITOS ( nNum: LongInt; nSpaces: Integer ): String;
Var
   s: ^String;
Begin
  Asm
    mov     sp, BP
    push    ss
    push    Word Ptr @RESULT
  End;
  
  If nSpaces > 0 Then
    Str ( nNum: nSpaces, s^ )
  Else
    Str ( nNum: 0, s^ );
End;

Function returnspace (s: String; wantedspace: Byte): String;
Var
i   : Byte;
temp : String;
Begin
  temp := '';
  For i := Length (s) To wantedspace Do
  Begin
    temp := temp + ' ';
  End;
  returnspace := temp;
End;

{home-made-calculations of which track is currently being played}
Procedure calctrack;
Var
  Min, Sec: Byte;
  i: Byte;
  svar: Boolean;
  {**************}
  Procedure addtime (m, s: Byte);
Begin
  Min := Min + m;
  Sec := Sec + s;
  If Sec = 60 Then
  Begin
    Min := Min + 1;
    Sec := 0;
  End;
  If Sec > 60 Then
  Begin
    Min := Min + 1;
    Sec := Sec - 60;
  End;
End;
{**************}
{**************}
Procedure bigger (m1, s1, m2, s2: Byte; svar: Boolean);
{calculates whether m1:s1 is bigger than m2:s2:}
Begin
  If (m1 * 60 + s1) > (m2 * 60 + s2) Then svar := True
  Else svar := False;
End;
{**************}

Begin
  track := 0;
  Min := 0;
  Sec := 0;
  secs := 0;
  place := Head_Location (1);

  For i := starttrack To endtrack Do
  Begin
    If trackinfo [i]^. startpoint < place Then
    Begin
      track := i;
    End;
    If track = 0 Then track := 1;
  End;
End;


Procedure NoTracks;
Begin
  WriteLn;
  WriteLn ('No tracks on disk');
  WriteLn;
  ExitProc := SaveExit;
End;

Procedure Setup;
Begin
  TotalPlayTime := 0;
  LeadOut := AudioDiskInfo. LeadOutTrack;
  
  StartTrack := AudioDiskInfo. LowestTrack;
  EndTrack := AudioDiskInfo. HighestTrack;
  NumTracks := EndTrack - StartTrack + 1;
  
  
  For I := StartTrack To EndTrack Do
  Begin
    Track := I;
    Audio_Track_Info (StartP, Track);
    New (TrackInfo [I] );
    FillChar (TrackInfo [I]^, SizeOf (TrackInfo [I]^), #0);
    TrackInfo [I]^. StartPoint := StartP;
    TrackInfo [I]^. TrackControl := Track;
  End;

  For I := StartTrack To EndTrack - 1 Do
    TrackInfo [I]^. EndPoint := TrackInfo [I + 1]^. StartPoint - 1;

  TrackInfo [EndTrack]^. EndPoint := AudioDiskInfo. LeadOutTrack - 1;

  For I := StartTrack To EndTrack Do
    Move (TrackInfo [I]^. EndPoint, TrackInfo [I]^. Frames, 4);

  TrackInfo [StartTrack]^. PlayMin := TrackInfo [StartTrack]^. Minutes;
  TrackInfo [StartTrack]^. PlaySec := TrackInfo [StartTrack]^. Seconds - 2;

  For I := StartTrack + 1 To EndTrack Do
  Begin
    EP := (TrackInfo [I]^. Minutes * 60) + TrackInfo [I]^. Seconds;
    SP := (TrackInfo [I - 1]^. Minutes * 60) + TrackInfo [I - 1]^. Seconds;
    EP := EP - SP;
    TrackInfo [I]^. PlayMin := EP Div 60;
    TrackInfo [I]^. PlaySec := EP Mod 60;
  End;

  TotalPlayTime := AudioDiskInfo. LeadOutTrack - TrackInfo [StartTrack]^. StartPoint;
  Move (TotalPlayTime, TotPlay, 4);
End;


Function KeyEnh:  Boolean;
Var
  Enh:  Byte Absolute $0040:$0096;
  
Begin
  KeyEnh := False;
  If (Enh And $10) = $10 Then
    KeyEnh := True;
End;

Function InKey (Var SCAN, ASCII:  Byte): Boolean;
Var
  i     :  Integer;
  Shift,
  Ctrl,
  Alt   : Boolean;
  Temp,
  Flag1 : Byte;
  HEXCH,
  HEXRD,
  HEXFL : Byte;
  reg   : Registers;
  
Begin
  If KeyEnh Then
  Begin
    HEXCH := $11;
    HEXRD := $10;
    HEXFL := $12;
  End
  Else
  Begin
    HEXCH := $01;
    HEXRD := $00;
    HEXFL := $02;
  End;
  
  reg. AH := HEXCH;
  Intr ($16, reg);
  i := reg. Flags And fZero;
  
  reg. AH := HEXFL;
  Intr ($16, reg);
  Flag1 := Reg. AL;
  Temp  := Flag1 And $03;
  
  If Temp = 0 Then
    SHIFT := False
  Else
    SHIFT := True;
  
  Temp  := Flag1 And $04;
  If Temp = 0 Then
    CTRL := False
  Else
    CTRL := True;
  
  Temp  := Flag1 And $08;
  If Temp = 0 Then
    ALT  := False
  Else
    ALT  := True;
  
  If i = 0 Then
  Begin
    reg. AH := HEXRD;
    Intr ($16, reg);
    scan  := reg. AH;
    ascii := reg. AL;
    InKey := True;
  End
  Else
    InKey := False;
End;

{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
FUNCTION UpStr (CONST s: String): String; ASSEMBLER;
  ASM
    push DS
    lds  SI, s
    les  DI, @result
    lodsb            { load and store length of string }
    stosb
    XOR  CH, CH
    mov  CL, AL
    jcxz @empty      { FIX for null length string }
    @upperLoop:
    lodsb
    cmp  AL, 'a'
    jb   @cont
    cmp  AL, 'z'
    ja   @cont
    sub  AL, ' '
    @cont:
    stosb
    loop @UpperLoop
    @empty:
    pop  DS
  END;


procedure vretrace; assembler; { vertical retrace }
asm
  mov dx,3dah
 @vert1:
  in al,dx
  test al,8
  jz @vert1
 @vert2:
  in al,dx
  test al,8
  jnz @vert2
end;

Procedure Setupsc(Col, Row, ScrollSize : Word; Var ScreenLoc : Pointer);
Var Seg1, Ofs1 : Word;
Begin
   {I guess we're assuming an 80 column text mode }
   Ofs1 := (Row-1)*160 + ((Col-1)*2);

   If (Mem[$40:$49] = 7) then Seg1 := $B000
     else Seg1 := $B800;

   ScreenLoc := Ptr(Seg1,Ofs1);
End;



{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
FUNCTION Get_svar: Byte ;
VAR
  CH: Char;
  no_svar: Boolean;

BEGIN
  no_svar := TRUE;
  REPEAT
    CH := UpCase (ReadKey);
    IF CH = NullKey THEN
    BEGIN
      CASE Ord (ReadKey) OF
        dnarrow:
                BEGIN
                  get_svar := dnarrow;
                END;
        uparrow:
                BEGIN
                  get_svar := uparrow;
                END;
        lfarrow:
                BEGIN
                  get_svar := lfarrow;
                END;
        rtarrow:
                BEGIN
                  get_Svar := rtarrow;
                END;
      END;
    END
    ELSE
      CASE CH OF
        EnterKey  :
                   BEGIN
                     get_svar := 100;
                   END;

        EscapeKey :
                   BEGIN
                     get_svar := 27;
                   END;
      END;

  UNTIL no_svar <> FALSE;
END;



Procedure Update;Assembler;
ASM
   CLD
   LES  DI, ScreenLoc
   MOV  CX, ScrollSize

   MOV  SI, CurrIndex
   OR   SI, SI
   JZ   @WriteString

   DEC  CX
@ShiftLeft:
   MOV  AL, ES:[DI+2]
   STOSB
   INC  DI
   LOOP @ShiftLeft

   MOV  AL, CS:[SI]
   OR   AL, AL
   JNZ  @NotEndOfStr
   MOV  SI, Offset @Message
   MOV  AL, CS:[SI]
@NotEndOfStr:
   STOSB

   INC  SI
   JMP  @SaveIndex

@WriteString:
   MOV  SI, Offset @Message
@NextChar:
   MOV  AL, CS:[SI]
   OR   AL, AL
   JZ   @WriteString
   STOSB
   INC  DI
   INC  SI
   LOOP @NextChar

@SaveIndex:
   MOV  CurrIndex, SI
   JMP  @Exit

@Message:
   DB '                                                   '
   DB '                                                   '
   DB   '(\/)ini  HELP!                        '
   DB '           Function keys available:'
   DB   '      PgUP : One track up      ...      PgDN : One track down '
   DB  '     ...      "." : Pick a track using arrow keys      ...      '
   DB 'RightArrow : FastForward      ...      LeftArrow : Rewind      ...     '
   DB 'If you have a Sound Blaster you can use the "+" & "-" keys to control '
   DB 'the volume.....       '
   DB   0                              { terminate it with NULL       }
@Exit:
End;

procedure help;
Var Fedup : Boolean;
time:byte;
c:byte;
emptystr:string;
i:integer;
Begin
   fillchar(emptystr,80,' ');
   emptystr[0]:=#80;
   ScrollSize := 80;
   Setupsc(01,wherey,SCrollSize,ScreenLoc);
   CurrIndex := 0;
   time:=0;
   fedup:=false;
   textcolor(lightgray);
   gotoxy(1,wherey);
   write(emptystr);
   while keypressed do readkey;
   Repeat
     waitforretrace;
     Update;
     if keypressed then
     begin
       c:=get_svar;
       if c=uparrow then inc(time);
       if c=dnarrow then dec(time);
       Fedup := (c = 27);
     end;
   Until (Fedup);
End;


{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
FUNCTION IntToStr (I: LongInt): String;
{Converts any integer type to a string}
VAR
  S: String [11];
BEGIN
  Str (I, S);
  IntToStr := S;
END;


{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
PROCEDURE ShowVolume;
var
i:byte;
BEGIN
    gotoxy(33,wherey);
    TEXTCOLOR (DarkGray);
    FOR i := 1 TO 32 DO
    BEGIN
      WRITE ('ž');
    END;

    TEXTCOLOR (Yellow);
    GOTOXY (33, wherey);
    FOR i := 1 TO lvolume DIV 8 DO
    BEGIN
      WRITE ('Ž');
    END;
vol_time:=readtimer;
toslet:=true;
END;

{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
PROCEDURE Cursoff;
{ Turns the cursor off.  Stores its format for later redisplaying}
BEGIN
  ASM
    Mov AH, 03H
    Mov BH, 00H
    Int 10H
    Mov Crstyp, CX
    Mov AH, 01H
    Mov CX, 65535
    Int 10H
  END;
END;


{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
PROCEDURE Curson;
{Turns the cursor back on, using the cursor display previously stored}
BEGIN
  ASM
    Mov AH, 01H
    Mov CX, Crstyp
    Int 10H
  END;
END;



{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
PROCEDURE SetColor (Color, Red, Green, Blue : Byte);
{Sets the RGB-values for a given color}
BEGIN
  port [$3C8] := Color;
  port [$3C9] := Red;
  port [$3C9] := Green;
  port [$3C9] := Blue;
END;


{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
PROCEDURE GetColor (Nr: Byte; VAR R, G, B: Byte);
{Retrieves the RGB-values for a given color}
BEGIN
  Port [$3C7] := Nr;
  R := Port [$3C9];
  G := Port [$3C9];
  B := Port [$3C9];
END;



{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
PROCEDURE wait_4_refresh; ASSEMBLER;
{Waits for the monitors vertical retrace}
LABEL
  wait, retr;
ASM
  mov  DX, 3DAh
  wait:  IN   AL, DX
  Test AL, 08h
  jz   wait
  retr:  IN   AL, DX
  Test AL, 08h
  jnz  retr
END;





{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
FUNCTION ISCOLOR : Boolean;
{Returns FALSE for MONO or TRUE for COLOR}
VAR
  regs  : Registers;
  video_mode : Integer;
  equ_lo : Byte;
BEGIN
  Intr ($11, regs);
  video_mode := regs. AL AND $30;
  video_mode := video_mode SHR 4;
  CASE video_mode OF
    1 : ISCOLOR := FALSE; { Monochrome }
    2 : ISCOLOR := TRUE{ Color }
  END
END;



{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
PROCEDURE SAVESCR ( VAR screen );
{Saves the screen in an array of bytes}
VAR
  vidc : Byte ABSOLUTE $B800: 0000;
  vidm : Byte ABSOLUTE $B000: 0000;
BEGIN
  IF NOT ISCOLOR THEN { if MONO }
    Move (vidm, screen, 6000)
  ELSE { else COLOR }
    Move (vidc, screen, 6000)
END;



{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
PROCEDURE RESTORESCR ( VAR screen );
{Restores the screen previous stored in an array of bytes}
VAR
  vidc : Byte ABSOLUTE $B800: 0000;
  vidm : Byte ABSOLUTE $B000: 0000;
BEGIN
  IF NOT ISCOLOR THEN { if MONO }
    Move (screen, vidm, 6000)
  ELSE { else COLOR }
    Move (screen, vidc, 6000)
END;



{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
PROCEDURE working;
{Displays an 'working'-status on the screen}
VAR
  X, Y: Byte;
  c: Byte;
BEGIN
  IF playing THEN
  BEGIN
    X := WhereX;
    Y := WhereY;
    c := TextAttr;

    TextBackground (Blue);
    TextColor (Black);
    GotoXY (70, 3);
    IF arbejder = 1 THEN Write ('');
    IF arbejder = 2 THEN Write ('');
    IF arbejder = 3 THEN Write ('');
    IF arbejder = 4 THEN Write ('');

    IF arbejder < 4 THEN Inc (arbejder)
    ELSE
      arbejder := 1;
    GotoXY (X, Y);
    TextAttr := c;
  END;
END;


{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
PROCEDURE center (s: String; Y: Byte);
{Centers a given string on a given line on the screen}
BEGIN
  GotoXY (40 - (Length (s) DIV 2), Y);
  Write (s);
END;
{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
Function hex(a : Word; b : Byte) : String;
Const digit : Array[$0..$F] Of Char = '0123456789ABCDEF';
Var i : Byte;
  xstring : String;
Begin
  xstring:='';
  For i:=1 To b Do
  Begin
    Insert(digit[a And $000F], xstring, 1);
    a:=a ShR 4
  End;
  hex:=xstring
End; {hex}

{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
Procedure SoundPort;
Var xbyte1, xbyte2, xbyte3, xbyte4: Byte;
  xword, xword1, xword2, temp, sbport: Word;

Begin
  sbfound:=False;
  xbyte1:=1;
  While (xbyte1 < 7) And (Not sbfound) Do
  Begin
    sbport:=$200 + ($10 * xbyte1);
    xword1:=0;
    portok:=False;
    While (xword1 < $201) And (Not portok) Do
    Begin
      If (Port[sbport + $0C] And $80) = 0 Then
        portok:=True;
      Inc(xword1)
    End;
    If portok Then
    Begin
      xbyte3:=Port[sbport + $0C];
      Port[sbport + $0C]:=$D3;
      For xword2:=1 To $1000 Do {nothing};
      xbyte4:=Port[sbport + 6];
      Port[sbport + 6]:=1;
      xbyte2:=Port[sbport + 6];
      xbyte2:=Port[sbport + 6];
      xbyte2:=Port[sbport + 6];
      xbyte2:=Port[sbport + 6];
      Port[sbport + 6]:=0;
      xbyte2:=0;
      Repeat
        xword1:=0;
        portok:=False;
        While (xword1 < $201) And (Not portok) Do
        Begin
          If (Port[sbport + $0E] And $80) = $80 Then
            portok:=True;
          Inc(xword1)
        End;
        If portok Then
          If Port[sbport + $0A] = $AA Then
            sbfound:=True;
        Inc(xbyte2);
      Until (xbyte2 = $10) Or (portok);
      If Not portok Then
      Begin
        Port[sbport + $0C]:=xbyte3;
        Port[sbport + 6]:=xbyte4;
      End;
    End;
    If sbfound Then
    Begin
    End
    Else
      Inc(xbyte1);
  End;
End;


{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
FUNCTION pickatrack: Byte;
{Displays the user with a list of tracks to pick}
VAR
  Top, Bottom  : Byte;
  change      : Boolean; {scroller vi op/ned?}
  slut        : Boolean;
  i           : Byte;
  c           : Byte;
  curr        : Byte;
  index       : Byte;
  s: String;
  topl: Byte;
BEGIN
  topl := wherey-1; {topline}

  pickatrack := 0;
  s := '                         ';
  change := FALSE;
  curr  := 1;
  slut  := FALSE;
  Top   := 1;
  index := endtrack;


  TextColor (lightgray);
  gotoxy(32,topl+1);
  write('Select                           ');

  gotoxy(42,topl+1);
  write( '   ³      ');

  curr:=track;
  REPEAT
  BEGIN
    TextBackground (Black);
    TextColor (lightgray);
    FOR i := Top TO Bottom DO
    BEGIN
      GotoXY (43, topl + 1);
      Write (' ');
      IF i = track THEN
      BEGIN
        TextColor (lightgray+ Blink);
        Write (leadingzero (i) );
        TextColor (lightgray);
        Write ('³');
        TextColor (lightgray+ Blink);
        Write (leadingzero (trackinfo [i]^. playmin) );
        TextColor (lightgray);
        Write (':');
        TextColor (lightgray+ Blink);
        Write (leadingzero (trackinfo [i]^. playsec) );
      END
      ELSE
      BEGIN
        Write (leadingzero (i) );
        Write ('³');
        Write (leadingzero (trackinfo [i]^. playmin) );
        Write (':');
        Write (leadingzero (trackinfo [i]^. playsec) );
      END;
    END;
    IF curr = track THEN
    BEGIN
      TextColor (lightgray);
      GotoXY (44, topl +  1);
      Write (leadingzero (curr) );
      TextColor (lightgray);
      Write ('³');
      TextColor (lightgray);
      Write (leadingzero (trackinfo [curr]^. playmin) );
      TextColor (lightgray);
      Write (':');
      TextColor (lightgray);
      Write (leadingzero (trackinfo [curr]^. playsec) );
    END
    ELSE
    BEGIN
      TextColor (lightgray);
      GotoXY (44, topl +  1);
      Write (leadingzero (curr) );
      Write ('³');
      Write (leadingzero (trackinfo [curr]^. playmin) );
      Write (':');
      Write (leadingzero (trackinfo [curr]^. playsec) );
    END;
    textbackground(black);
    textcolor(lightgray);
    gotoxy(39,topl+1);

    if curr=1 then write('( )');
    if curr=index then write('( )');
    if ((curr1)) then write('()');

    repeat
      calctrack;
      q_channel_info;
      textcolor(yellow);
      gotoxy(18,wherey);
      write(leadingzero(track));
      gotoxy(21,wherey);
      textcolor(white);
      write(leadingzero(endtrack));
      textcolor(yellow);
      gotoxy(24,wherey);
      write(leadingzero(qchannelinfo.minutes));
      gotoxy(27,wherey);
      write(leadingzero(qchannelinfo.seconds));
    until keypressed;


    c := get_Svar;


    IF (c = uparrow) THEN
    BEGIN
      IF (curr = Top) AND (Top > 1) THEN
      BEGIN
        Dec (Top);
        Dec (curr);
        change := TRUE;
      END;
      IF (curr > Top) THEN Dec (curr);
    END;
    IF (c = dnarrow) THEN
    BEGIN
      IF (curr < index)THEN
      begin
        Inc (curr);
        inc(top);
      end;
    END;
    IF c = 100 THEN
    BEGIN
      pickatrack := curr;
      slut := TRUE;
    END;
    IF c = 27 THEN
    BEGIN
      TextBackground (Black);
      gotoxy(32,topl+1);write('                     ');
      Exit;
    END;
  END; {while}
  UNTIL slut;
  TextBackground (Black);
  gotoxy(32,topl+1);write('                       ');
END;



{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
PROCEDURE colwrite (col, startline: Byte; s: String);
{Writes a given line downwards from the given column and the given startline}
VAR
i, j: Byte;
BEGIN
  j := 1;
  FOR i := startline TO startline+ Length (s) - 1 DO
  BEGIN
    GotoXY (col, i);
    Write (s [j] );
    Inc (j);
  END;
END;




{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
PROCEDURE stuffthebuff;
{Empties the buffer}
VAR
  chartoskip: Char;

BEGIN
  WHILE KeyPressed DO
    chartoskip := ReadKey;
END;


{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
FUNCTION Get_Scan_Code : Word;
VAR
  HTregs : Registers;
BEGIN
  HTregs. AH := $01;
  Intr ($16, HTregs);
  Get_Scan_Code := HTregs. AX;
END;




{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
PROCEDURE fuckthebuff;
{Flushes the keyboard-buffer}
BEGIN
  ASM
    Mov AX, $0C00;
    Int 21h;
  END;

END;


{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
{ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ}
VAR
  currtrack    : Byte;
  ieren        : LongInt;
  slut         : Boolean;
  newtrack     : Byte;
  xkor,
  ykor         : Byte;
  timepassed:longint;
BEGIN
  origmode     := LastMode;
  cursoff;
  soundport;
  port [$224] := 48;
  lvolume := port [$225];
  port [$224] := 49;
  rvolume := port [$225];
  port [$224] := 65;


  xkor         := WhereX;
  ykor         := WhereY;
  TextColor (LightGray);
  TextBackground (Black);

  gotoxy(1,wherey-1);
  textcolor(white);
  write('(\/)ini');
  textcolor(lightgray);
  writeln('  v.1   Checking CD-ROM ... ');

  gotoxy(1,wherey);
  Audio_Disk_Info;
  Setup;

  IF AudioDiskInfo. HighestTrack < 1 THEN
  BEGIN
    delay(200);
    Audio_Disk_Info;
    Setup;
    WriteLn ('Not an audio-CD!');
    curson;
    Exit;
  END;

  gotoxy(1,wherey-1);

  gotoxy(01,wherey);
  write('(\/)ini  v.1     xx/xx xx:XX                                           L/th 1996');
  gotoxy(1,wherey-1);


  gotoxy(18,wherey);
  slut := FALSE;

  textcolor(white);

  audio_status_info;
  q_channel_info;
  audio_status_info;
  audio_disk_info;
  Play_Audio (trackinfo [starttrack]^. startpoint, trackinfo [endtrack]^. endpoint);


  REPEAT
    REPEAT
      if toslet then
      begin
        if elapsedtime(vol_time,readtimer)>700 then
        begin
          gotoxy(33,wherey);
          write('                                ');
          toslet:=false;
        end;
      end;

      fuckthebuff;
      calctrack;
      q_channel_info;
      gotoxy(18,wherey);
      write(leadingzero(track));
      gotoxy(21,wherey);
      textcolor(white);
      write(leadingzero(endtrack));
      textcolor(yellow);
      gotoxy(24,wherey);
      write(leadingzero(qchannelinfo.minutes));
      gotoxy(27,wherey);
      write(leadingzero(qchannelinfo.seconds));
    UNTIL InKey (Hi, Hi2);

    {ESC (EXIT AUDIO)}
    IF ( (Hi = 1) AND (Hi2 = 27) )  THEN slut := TRUE;

    {Page Up (UP ONE TRACK)}
    IF ( (Hi = 73) AND ( (Hi2 = 0) OR (Hi2 = 224) ) AND (playing) AND (track < endtrack) ) THEN
    BEGIN
      pause_audio;
      play_audio (trackinfo [track + 1]^. startpoint, trackinfo [endtrack]^. endpoint);
    END;

    {Page Down (DOWN ONE TRACK)}
    IF ( (Hi = 81) AND ( (Hi2 = 0) OR (Hi2 = 224) ) AND (playing) AND (track > starttrack) ) THEN
    BEGIN
      IF ( (place < (trackinfo [track]^. startpoint + 3000) ) ) THEN
      BEGIN
        pause_audio;
        play_audio (trackinfo [track - 1]^. startpoint, trackinfo [endtrack]^. endpoint);
      END;
      IF ( (place > (trackinfo [track]^. startpoint + 3000) ) ) THEN
      BEGIN
        pause_audio;
        play_audio (trackinfo [track]^. startpoint, trackinfo [endtrack]^. endpoint);
      END;
    END;

    {Right Arrow (SKIP 3-4 SECS)}
    IF ( (Hi = 77) AND ( (Hi2 = 0) OR (Hi2 = 224) ) AND (playing) AND ( (place+ (3000) ) <
       trackinfo [endtrack]^. endpoint) )
    THEN
    BEGIN
      pause_audio;
      play_audio (place+1000, trackinfo [endtrack]^. endpoint);
    END;

    {Left Arrow (SKIP 3-4 SECS)}
    IF ( (Hi = 75) AND ( (Hi2 = 0) OR (Hi2 = 224) ) AND (playing) AND ( (place- (3000 * 4) ) >
       trackinfo [starttrack]^. startpoint) )
    THEN
    BEGIN
      pause_audio;
      play_audio ((place- 1000), trackinfo [endtrack]^. endpoint);
      delay(20);
    END;

    {Middle key (PAUSE/RESUME)}
    IF ( (Hi = 76) AND (Hi2 = 0) ) THEN
    BEGIN
      IF playing THEN
      BEGIN
        io_control (stopplay);
        audio_status_info;
      END
      ELSE
      BEGIN
        resume_play;
      END;
    END;

    IF ( (Hi = 52) AND (Hi2 = 46) ) THEN
    BEGIN
      newtrack := pickatrack;
      IF newtrack > 0 THEN
      BEGIN
        pause_audio;
        play_audio (trackinfo [newtrack]^. startpoint, trackinfo [endtrack]^. endpoint);
      END;
    END;

    {HELP MENU!}
    IF ( (Hi = 59) AND (Hi2 = 0) ) THEN
    BEGIN
      fuckthebuff;
      help;
      gotoxy(01,wherey-1);
      write('(\/)ini  v.1     xx/xx xx:XX                                           L/th 1996');
      gotoxy(1,wherey-1);
    END;



{ VOLUME-CONTROL!}

{'-' (Reduce BOTH volumes)}
    IF ( (Hi = 74) AND (Hi2 = 45) AND NOT ( (left_shift) OR (right_shift) ) ) THEN
    BEGIN
      if sbfound then
      begin
        IF rvolume > 04 THEN
        BEGIN
          DEC (rvolume, 4);
          port [$224] := 49;
          port [$225] := rvolume;
        END;
        IF lvolume > 04 THEN
        BEGIN
          DEC (lvolume, 4);
          port [$224] := 48;
          port [$225] := lvolume;
        END;
        showvolume;
      end;
    END;


{'+' (Increase BOTH volumes)}
    IF ( (Hi = 78) AND (Hi2 = 43) AND NOT ( (left_shift) OR (right_shift) ) ) THEN
    BEGIN
      if sbfound then
      begin
        IF rvolume < 252 THEN
        BEGIN
          INC (rvolume, 4);
          port [$224] := 49;
          port [$225] := rvolume;
        END;
        IF lvolume < 252 THEN
        BEGIN
          INC (lvolume, 4);
          port [$224] := 48;
          port [$225] := lvolume;
        END;
        showvolume;
      end;
    END;

{ VOLUME-CONTROL!}
   stuffthebuff;
  UNTIL slut;
gotoxy(1,wherey);
textcolor(lightgray);
writeln('(\/)ini  v.1     Mini-CD-ROM-player                                    L/th 1996');
curson;
END.


{CUT OFF ...}

{CUT ... Save this as CD_UNIT.PAS}
UNIT CD_Unit;

INTERFACE

USES DOS, CD_Vars;

VAR
  Drive   : Integer;  { Must set drive before all operations }
  SubUnit : Integer;
  
PROCEDURE IO_Control (Command : Byte);
FUNCTION File_Name (VAR Code : Integer) : String;

FUNCTION Read_VTOC (VAR VTOC : VTOCArray;
                   VAR Index : Integer) : Boolean;

PROCEDURE CD_Check (VAR Code : Integer);

PROCEDURE Vol_Desc (VAR Code : Integer;
                   VAR ErrCode : Integer);

PROCEDURE CD_Dev_Req (DevPointer : Pointer);

PROCEDURE Get_Dir_Entry (PathName : String;
                        VAR Format, ErrCode : Integer);

PROCEDURE DeviceStatus;

PROCEDURE Audio_Channel_Info;

PROCEDURE Audio_Disk_Info;

PROCEDURE Audio_Track_Info (VAR StartPoint : LongInt;
                           VAR TrackControl : Byte);

PROCEDURE Audio_Status_Info;

PROCEDURE Q_Channel_Info;

PROCEDURE Lock (LockDrive : Boolean);

PROCEDURE Resetcd;

PROCEDURE Eject;

PROCEDURE CloseTray;

PROCEDURE Resume_Play;

PROCEDURE Pause_Audio;

PROCEDURE Play_Audio (StartSec, EndSec : LongInt);

FUNCTION Sector_Size (ReadMode : Integer) : Word;

FUNCTION Volume_Size : LongInt;

FUNCTION Media_Changed : Boolean;

FUNCTION Head_Location (AddrMode : Byte) : LongInt;

PROCEDURE Read_Drive_Bytes (VAR ReadBytes : DriveByteArray);

PROCEDURE Read_Long (TransAddr : Pointer; StartSec : LongInt);

PROCEDURE SeekSec (StartSec : LongInt);

PROCEDURE DevClose;

PROCEDURE DevOpen;

PROCEDURE OutputFlush;

PROCEDURE InputFlush;

FUNCTION UPC_Code : String;

IMPLEMENTATION

CONST
  CarryFlag  = $0001;

TYPE
  PointerHalf = RECORD
                  LoHalf, HiHalf : Word;
                END;
  
VAR
  Regs       : Registers;
  IOBlock    : IOControl;
  DriveBytes : ARRAY [1..130] OF Byte;
  
PROCEDURE Clear_Regs;
BEGIN
  FillChar (Regs, SizeOf (Regs), #0);
END;

PROCEDURE CD_Intr;
BEGIN
  Regs. AH := $15;
  Intr ($2F, Regs);
END;

PROCEDURE MSCDEX_Ver;
BEGIN
  Clear_Regs;
  Regs. AL := $0C;
  Regs. BX := $0000;
  CD_Intr;
  MSCDEX_Version. Minor := 0;
  IF Regs. BX = 0 THEN
    MSCDEX_Version. Major := 1
  ELSE
  BEGIN
    MSCDEX_Version. Major := Regs. BH;
    MSCDEX_Version. Minor := Regs. BL;
  END;
END;

PROCEDURE Initialize;
BEGIN
  NumberOfCD := 0;
  Clear_Regs;
  Regs. AL := $00;
  Regs. BX := $0000;
  CD_Intr;
  IF Regs. BX <> 0 THEN
  BEGIN
    NumberOfCD := Regs. BX;
    FirstCD := Regs. CX;
    Clear_Regs;
    FillChar (DriverList, SizeOf (DriverList), #0);
    FillChar (UnitList, SizeOf (UnitList), #0);
    Regs. AL := $01;               { Get List of Driver Header Addresses }
    Regs. ES := Seg (DriverList);
    Regs. BX := Ofs (DriverList);
    CD_Intr;
    Clear_Regs;
    Regs. AL := $0D;               { Get List of CD-ROM Units }
    Regs. ES := Seg (UnitList);
    Regs. BX := Ofs (UnitList);
    CD_Intr;
    MSCDEX_Ver;
  END;
END;


FUNCTION File_Name (VAR Code : Integer) : String;
VAR
  FN : String [38];
BEGIN
  Clear_Regs;
  Regs. AL := Code + 1;
  {
  Copyright Filename     =  1
  Abstract Filename      =  2
  Bibliographic Filename =  3
  }
  Regs. CX := Drive;
  Regs. ES := Seg (FN);
  Regs. BX := Ofs (FN);
  CD_Intr;
  Code := Regs. AX;
  IF (Regs. Flags AND CarryFlag) = 0 THEN
    File_Name := FN
  ELSE
    File_Name := '';
END;


FUNCTION Read_VTOC (VAR VTOC : VTOCArray;
                   VAR Index : Integer) : Boolean;
{ On entry -
     Index = Vol Desc Number to read from 0 to ?
  On return
     Case Index of
            1    : Standard Volume Descriptor
            $FF  : Volume Descriptor Terminator
            0    : All others
}
BEGIN
  Clear_Regs;
  Regs. AL := $05;
  Regs. CX := Drive;
  Regs. DX := Index;
  Regs. ES := Seg (VTOC);
  Regs. BX := Ofs (VTOC);
  CD_Intr;
  Index := Regs. AX;
  IF (Regs. Flags AND CarryFlag) = 0 THEN
    Read_VTOC := TRUE
  ELSE
    Read_VTOC := FALSE;
END;

PROCEDURE CD_Check (VAR Code : Integer);
BEGIN
  Clear_Regs;
  Regs. AL := $0B;
  Regs. BX := $0000;
  Regs. CX := Drive;
  CD_Intr;
  IF Regs. BX <> $ADAD THEN
    Code := 2
  ELSE
  BEGIN
    IF Regs. AX <> 0 THEN
      Code := 0
    ELSE
      Code := 1;
  END;
END;


PROCEDURE Vol_Desc (VAR Code : Integer;
                   VAR ErrCode : Integer);

  FUNCTION Get_Vol_Desc : Byte;
    BEGIN
      Clear_Regs;
      Regs. CX := Drive;
      Regs. AL := $0E;
      Regs. BX := $0000;
      CD_Intr;
      Code := Regs. AX;
      IF (Regs. Flags AND CarryFlag) <> 0 THEN
        ErrCode := $FF;
      Get_Vol_Desc := Regs. DH;
    END;

BEGIN
  Clear_Regs;
  ErrCode := 0;
  IF Code <> 0 THEN
  BEGIN
    Regs. DH := Code;
    Regs. DL := 0;
    Regs. BX := $0001;
    Regs. AL := $0E;
    Regs. CX := Drive;
    CD_Intr;
    Code := Regs. AX;
    IF (Regs. Flags AND CarryFlag) <> 0 THEN
      ErrCode := $FF;
  END;
  IF ErrCode = 0 THEN
    Code := Get_Vol_Desc;
END;

PROCEDURE Get_Dir_Entry (PathName : String;
                        VAR Format, ErrCode : Integer);
BEGIN
  FillChar (DirBuf, SizeOf (DirBuf), #0);
  PathName := PathName + #0;
  Clear_Regs;
  Regs. AL := $0F;
  Regs. CL := Drive;
  Regs. CH := 1;
  Regs. ES := Seg (PathName);
  Regs. BX := Ofs (PathName);
  Regs. SI := Seg (DirBuf);
  Regs. DI := Ofs (DirBuf);
  CD_Intr;
  ErrCode := Regs. AX;
  IF (Regs. Flags AND CarryFlag) = 0 THEN
  BEGIN
    Move (DirBuf. NameArray [1], DirBuf. FileName [1], 38);
    DirBuf. FileName [0] := #12; { File names are only 8.3 }
    Format := Regs. AX
  END
  ELSE
    Format := $FF;
END;

PROCEDURE CD_Dev_Req (DevPointer : Pointer);
BEGIN
  Clear_Regs;
  Regs. AL := $10;
  Regs. CX := Drive;
  Regs. ES := PointerHalf (DevPointer).HiHalf;
  Regs. BX := PointerHalf (DevPointer).LoHalf;
  CD_Intr;
END;

PROCEDURE IO_Control (Command : Byte);
BEGIN
  IOBlock. IOReq_Hdr. Len := 26;
  IOBlock. IOReq_Hdr. SubUnit := SubUnit;
  IOBlock. IOReq_Hdr. Status := 0;
  IOBlock. TransAddr := @DriveBytes;
  IOBlock. IOReq_Hdr. Command := Command;
  
  FillChar (IOBlock. IOReq_Hdr. Reserved, 8, #0);
  
  CD_Dev_Req (@IOBlock);
  
  Busy :=   (IOBlock. IOReq_Hdr. Status AND 512) <> 0;
  
  
END;

PROCEDURE Audio_Channel_Info;
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  DriveBytes [1] := 4;
  IOBlock. NumBytes := 9;
  
  IO_Control (IOCtlInput);
  
  Move (DriveBytes, AudioChannel, 9);
END;

PROCEDURE DeviceStatus;
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  
  DriveBytes [1] := 6;
  IOBlock. NumBytes := 5;
  
  IO_Control (IOCtlInput);
  
  DoorOpen     := DriveBytes [2] AND 1 <> 0;
  DoorLocked   := DriveBytes [2] AND 2 <> 0;
  AudioManip   := DriveBytes [3] AND 1 <> 0;
  DiscInDrive  := DriveBytes [3] AND 8 <> 0;
  
END;

PROCEDURE Audio_Disk_Info;
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  
  DriveBytes [1] := 10;
  IOBlock. NumBytes := 7;
  
  IO_Control (IOCtlInput);
  
  Move (DriveBytes [2], AudioDiskInfo, 6);
  
  Playing := Busy;
  
END;

PROCEDURE Audio_Track_Info (VAR StartPoint : LongInt;
                           VAR TrackControl : Byte);
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);

  DriveBytes [1] := 11;
  DriveBytes [2] := TrackControl;   { Track number }
  IOBlock. NumBytes := 7;
  
  IO_Control (IOCtlInput);
  
  Move (DriveBytes [3], StartPoint, 4);
  
  TrackControl := DriveBytes [7];
  
  Playing := Busy;
END;

PROCEDURE Q_Channel_Info;
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  
  DriveBytes [1] := 12;
  IOBlock. NumBytes := 11;
  
  IO_Control (IOCtlInput);
  
  Move (DriveBytes [2], QChannelInfo, 11);
  
END;

PROCEDURE Audio_Status_Info;
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  
  DriveBytes [1] := 15;
  IOBlock. NumBytes := 11;
  
  IO_Control (IOCtlInput);
  
  Paused := (Word (DriveBytes [2] ) AND 1) <> 0;
  
  Move (DriveBytes [4], Last_Start, 4);
  Move (DriveBytes [8], Last_End, 4);
  
  Playing := Busy;
END;

PROCEDURE Eject;
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  
  DriveBytes [1] := 0;
  IOBlock. NumBytes := 1;
  
  IO_Control (IOCtlOutput);
END;

PROCEDURE Resetcd;
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);

  DriveBytes [1] := 2;
  IOBlock. NumBytes := 1;
  
  IO_Control (IOCtlOutput);
  Busy := TRUE;
END;

PROCEDURE Lock (LockDrive : Boolean);
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  
  DriveBytes [1] := 1;
  IF LockDrive THEN
    DriveBytes [2] := 1
  ELSE
    DriveBytes [2] := 0;
  IOBlock. NumBytes := 2;
  
  IO_Control (IOCtlOutput);
END;

PROCEDURE CloseTray;
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  
  DriveBytes [1] := 5;
  IOBlock. NumBytes := 1;
  
  IO_Control (IOCtlOutput);
END;

VAR
  AudioPlay : Audio_Play;
  
FUNCTION Play (StartLoc, NumSec : LongInt) : Boolean;
BEGIN
  FillChar (AudioPlay, SizeOf (AudioPlay), #0);
  AudioPlay. APReq. Command := PlayCD;
  AudioPlay. APReq. Len := 22;
  AudioPlay. APReq. SubUnit := SubUnit;
  AudioPlay. Start := StartLoc;
  AudioPlay. NumSecs := NumSec;
  AudioPlay. AddrMode := 1;
  
  CD_Dev_Req (@AudioPlay);
  Play := ( (AudioPlay. APReq. Status AND 32768) = 0);
  
END;

PROCEDURE Play_Audio (StartSec, EndSec : LongInt);
VAR
  SP,
  EP     : LongInt;
  SArray : ARRAY [1..4] OF Byte;
  EArray : ARRAY [1..4] OF Byte;
BEGIN
  Move (StartSec, SArray [1], 4);
  Move (EndSec, EArray [1], 4);
  SP := SArray [3];           { Must use longint or get negative result }
  SP := (SP * 75 * 60) + (SArray [2] * 75) + SArray [1];
  EP := EArray [3];
  EP := (EP * 75 * 60) + (EArray [2] * 75) + EArray [1];
  EP := EP - SP;
  
  Playing := Play (StartSec, EP);
  Audio_Status_Info;
END;

PROCEDURE Pause_Audio;
BEGIN
  IF Playing THEN
  BEGIN
    FillChar (AudioPlay, SizeOf (AudioPlay), #0);
    AudioPlay. APReq. Command := stopplay; {stopplay}
    AudioPlay. APReq. Len := 13;
    AudioPlay. APReq. SubUnit := SubUnit;
    CD_Dev_Req (@AudioPlay);
  END;
  Audio_Status_Info;
  Playing := FALSE;
END;

PROCEDURE Resume_Play;
BEGIN
  FillChar (AudioPlay, SizeOf (AudioPlay), #0);
  AudioPlay. APReq. Command := ResumePlay;
  AudioPlay. APReq. Len := 13;
  AudioPlay. APReq. SubUnit := SubUnit;
  CD_Dev_Req (@AudioPlay);
  Audio_Status_Info;
END;

FUNCTION Sector_Size (ReadMode : Integer) : Word;
VAR SecSize : Word;
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  
  DriveBytes [1] := 7;
  DriveBytes [2] := ReadMode;
  
  IOBlock. NumBytes := 4;
  
  IO_Control (IOCtlInput);
  
  Move (DriveBytes [3], SecSize, 2);
  Sector_Size := SecSize;
END;

(*Function CD_GetVol:Boolean;
begin
  CtlBlk[0] := 4;                           { die Lautstaerke lesen }
  CD_GetVol := CD_IOCtl(IoCtlRead, 8);
  if ((R.Flags and FCARRY) = 0)
   then Move(CtlBlk[1], CD.VolInfo, 8)
   else FillChar( CD.VolInfo, 8, 0)
end;

Function CD_SetVol:Boolean;
begin
  CtlBlk[0] := 3;                          { die Lautstaerke setzen }
  CD_SetVol := CD_IOCtl( IoCtlWrite, 8);
end;
*)

FUNCTION Volume_Size : LongInt;
VAR VolSize : LongInt;
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  
  DriveBytes [1] := 8;
  
  IOBlock. NumBytes := 5;

  IO_Control (IOCtlInput);
  
  Move (DriveBytes [2], VolSize, 4);
  Volume_Size := VolSize;
END;

FUNCTION Media_Changed : Boolean;
VAR MedChng : Byte;
  
  {  1  :  Media not changed
  0  :  Don't Know
  -1  :  Media changed
  }
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  
  DriveBytes [1] := 9;
  
  IOBlock. NumBytes := 2;
  
  IO_Control (IOCtlInput);
  
  Move (DriveBytes [2], MedChng, 4);
  Inc (MedChng);
  CASE MedChng OF
    2    : Media_Changed := FALSE;
    1, 0  : Media_Changed := TRUE;
  END;
END;

FUNCTION Head_Location (AddrMode : Byte) : LongInt;
VAR
  HeadLoc : LongInt;
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  
  DriveBytes [1] := 1;
  DriveBytes [2] := AddrMode;
  
  IOBlock. NumBytes := 6;
  
  IO_Control (IOCtlInput);
  
  Move (DriveBytes [3], HeadLoc, 4);
  Head_Location := HeadLoc;
END;

PROCEDURE Read_Drive_Bytes (VAR ReadBytes : DriveByteArray);
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  
  DriveBytes [1] := 5;
  
  IOBlock. NumBytes := 130;
  
  IO_Control (IOCtlInput);

  Move (DriveBytes [3], ReadBytes, 128);
END;


FUNCTION UPC_Code : String;
VAR
  I, J, K : Integer;
  TempStr : String;
BEGIN
  FillChar (DriveBytes, SizeOf (DriveBytes), #0);
  TempStr := '';
  DriveBytes [1] := 14;
  
  IOBlock. NumBytes := 11;
  
  IO_Control (IOCtlInput);
  
  IF ( (IOBlock. IOReq_Hdr. Status AND 32768) = 0) THEN;
  FOR I := 3 TO 9 DO
  BEGIN
    J := DriveBytes [I] AND $0F;
    K := DriveBytes [I] AND $F0;
    TempStr := TempStr + Chr (J + 48);
    TempStr := TempStr + Chr (K + 48);
  END;
  IF Length (TempStr) > 13 THEN
    TempStr [0] := Chr (Ord (TempStr [0] ) - 1);
  UPC_Code := TempStr;
END;


PROCEDURE Read_Long (TransAddr : Pointer; StartSec : LongInt);
VAR
  RL : ReadControl;
  {
  ReadControl = Record
  IOReq_Hdr : Req_Hdr;
  AddrMode  : Byte;
  TransAddr : Pointer;
  NumSecs   : Word;
  StartSec  : LongInt;
  ReadMode  : Byte;
  IL_Size,
  IL_Skip   : Byte;
  End;
  }
BEGIN
  FillChar (RL, SizeOf (RL), #0);
  RL. IOReq_Hdr. Len := 27;
  RL. IOReq_Hdr. SubUnit := SubUnit;
  RL. IOReq_Hdr. Command := ReadLong;
  RL. AddrMode := 1;
  RL. TransAddr := TransAddr;
  RL. NumSecs := 1;
  RL. StartSec := StartSec;
  RL. ReadMode := 0;
  CD_Dev_Req (@RL);
END;

PROCEDURE SeekSec (StartSec : LongInt);
VAR
  RL : ReadControl;
  
BEGIN
  FillChar (RL, SizeOf (RL), #0);
  RL. IOReq_Hdr. Len := 24;
  RL. IOReq_Hdr. SubUnit := SubUnit;
  RL. IOReq_Hdr. Command := SeekCmd;
  RL. AddrMode := 1;
  RL. StartSec := StartSec;
  RL. ReadMode := 0;
  CD_Dev_Req (@RL);
END;

PROCEDURE InputFlush;
VAR
  IOReq : Req_Hdr;
BEGIN
  FillChar (IOReq, SizeOf (IOReq), #0);
  WITH IOReq DO
  BEGIN
    Len     := 13;
    SubUnit := SubUnit;
    Command := 7;
    Status  := 0;
  END;
  CD_Dev_Req (@IOReq);
END;

PROCEDURE OutputFlush;
VAR
  IOReq : Req_Hdr;
BEGIN
  FillChar (IOReq, SizeOf (IOReq), #0);
  WITH IOReq DO
  BEGIN
    Len     := 13;
    SubUnit := SubUnit;
    Command := 11;
    Status  := 0;
  END;
  CD_Dev_Req (@IOReq);
END;

PROCEDURE DevOpen;
VAR
  IOReq : Req_Hdr;
BEGIN
  FillChar (IOReq, SizeOf (IOReq), #0);
  WITH IOReq DO
  BEGIN
    Len     := 13;
    SubUnit := SubUnit;
    Command := 13;
    Status  := 0;
  END;
  CD_Dev_Req (@IOReq);
END;

PROCEDURE DevClose;
VAR
  IOReq : Req_Hdr;
BEGIN
  FillChar (IOReq, SizeOf (IOReq), #0);
  WITH IOReq DO
  BEGIN
    Len     := 13;
    SubUnit := SubUnit;
    Command := 14;
    Status  := 0;
  END;
  CD_Dev_Req (@IOReq);
END;

{************************************************************}

BEGIN
  NumberOfCD := 0;
  FirstCD := 0;
  FillChar (MSCDEX_Version, SizeOf (MSCDEX_Version), #0);
  Initialize;
  Drive := FirstCD;
  SubUnit := 0;
END.

{CUT OFF ...}

{CUT ... Save this as CD_VARS.PAS}

UNIT CD_Vars;

INTERFACE

TYPE
  ListBuf    = RECORD
                 UnitCode : Byte;
                 UnitSeg,
                 UnitOfs  : Word;
               END;
  VTOCArray  = ARRAY [1..2048] OF Byte;
  DriveByteArray = ARRAY [1..128] OF Byte;
  
  Req_Hdr    = RECORD
                 Len     : Byte;
                 SubUnit : Byte;
                 Command : Byte;
                 Status  : Word;
                 Reserved: ARRAY [1..8] OF Byte;
               END;
  
CONST
  Init       = 0;
  IoCtlInput = 3;
  InputFlush = 7;
  IOCtlOutput = 12;
  DevOpen    = 13;
  DevClose   = 14;
  ReadLong   = 128;
  ReadLongP  = 130;
  SeekCmd    = 131;
  PlayCD     = 132;
  StopPlay   = 133;
  ResumePlay = 136;
  
TYPE
  
  Audio_Play = RECORD
                 APReq    : Req_Hdr;
                 AddrMode : Byte;
                 Start    : LongInt;
                 NumSecs  : LongInt;
               END;
  
  IOControl = RECORD
                IOReq_Hdr : Req_Hdr;
                MediaDesc : Byte;
                TransAddr : Pointer;
                NumBytes  : Word;
                StartSec  : Word;
                ReqVol    : Pointer;
              END;
  
  ReadControl = RECORD
                  IOReq_Hdr : Req_Hdr;
                  AddrMode  : Byte;
                  TransAddr : Pointer;
                  NumSecs   : Word;
                  StartSec  : LongInt;
                  ReadMode  : Byte;
                  IL_Size,
                  IL_Skip   : Byte;
                END;
  
  AudioDiskInfoRec = RECORD
                       LowestTrack    : Byte;
                       HighestTrack   : Byte;
                       LeadOutTrack   : LongInt;
                       {new!}
                       VolInfo: ARRAY [1..8] OF Byte; { Lautst.-Einstellungen }
                     END;
  
  PAudioTrackInfo   = ^AudioTrackInfoRec;
  AudioTrackInfoRec = RECORD
                        Track           : Integer;
                        StartPoint      : LongInt;
                        EndPoint        : LongInt;
                        Frames,
                        Seconds,
                        Minutes,
                        PlayMin,
                        PlaySec,
                        TrackControl    : Byte;
                      END;
  
  MSCDEX_Ver_Rec = RECORD
                     Major,
                     Minor       : Integer;
                   END;
  
  DirBufRec    = RECORD
                   XAR_Len   : Byte;
                   FileStart : LongInt;
                   BlockSize : Integer;
                   FileLen   : LongInt;
                   DT        : Byte;
                   Flags     : Byte;
                   InterSize : Byte;
                   InterSkip : Byte;
                   VSSN      : Integer;
                   NameLen   : Byte;
                   NameArray : ARRAY [1..38] OF Char;
                   FileVer   : Integer;
                   SysUseLen : Byte;
                   SysUseData: ARRAY [1..220] OF Byte;
                   FileName  : String [38];
                 END;
  
  Q_Channel_Rec = RECORD
                    Control     : Byte;
                    Track       : Byte;
                    Index       : Byte;
                    Minutes     : Byte;
                    Seconds     : Byte;
                    Frame       : Byte;
                    Zero        : Byte;
                    AMinutes    : Byte;
                    ASeconds    : Byte;
                    AFrame      : Byte;
                  END;
  
VAR
  AudioChannel   : ARRAY [1..9] OF Byte;
  DoorOpen,
  DoorLocked,
  AudioManip,
  DiscInDrive    : Boolean;
  AudioDiskInfo  : AudioDiskInfoRec;
  DriverList     : ARRAY [1..26] OF ListBuf;
  NumberOfCD     : Integer;
  FirstCD        : Integer;
  UnitList       : ARRAY [1..26] OF Byte;
  MSCDEX_Version : MSCDEX_Ver_Rec;
  QChannelInfo   : Q_Channel_Rec;
  Busy,
  Playing,
  Paused         : Boolean;
  Last_Start,
  Last_End       : LongInt;
  DirBuf         : DirBufRec;
  
IMPLEMENTATION

BEGIN
  FillChar (DriverList, SizeOf (DriverList), #0);
  FillChar (UnitList, SizeOf (UnitList), #0);
  NumberOfCD  := 0;
  FirstCD  := 0;
  MSCDEX_Version. Major := 0;
  MSCDEX_Version. Minor := 0;
END.

{CUT OFF ...}


{CUT ... Save this as TPTIMER.PAS}

{$S-,R-,I-,V-,B-}

{*********************************************************}
{*                   TPTIMER.PAS 2.00                    *}
{*                by TurboPower Software                 *}
{*********************************************************}

UNIT TpTimer;
  {-Allows events to be timed with 1 microsecond resolution}

INTERFACE

PROCEDURE InitializeTimer;
  {-Reprogram the timer chip to allow 1 microsecond resolution}

PROCEDURE RestoreTimer;
  {-Restore the timer chip to its normal state}

FUNCTION ReadTimer : LongInt;
  {-Read the timer with 1 microsecond resolution}

FUNCTION ElapsedTime (Start, Stop : LongInt) : Real;
  {-Calculate time elapsed (in milliseconds) between Start and Stop}

FUNCTION ElapsedTimeString (Start, Stop : LongInt) : String;
  {-Return time elapsed (in milliseconds) between Start and Stop as a string}

  {==========================================================================}

IMPLEMENTATION

CONST
  TimerResolution = 1193181.667;
VAR
  SaveExitProc : Pointer;
  Delta : LongInt;
  
FUNCTION Cardinal (L : LongInt) : Real;
    {-Return the unsigned equivalent of L as a real}
  BEGIN                      {Cardinal}
    IF L < 0 THEN
      Cardinal := 4294967296.0 + L
    ELSE
      Cardinal := L;
  END;                       {Cardinal}

  FUNCTION ElapsedTime (Start, Stop : LongInt) : Real;
    {-Calculate time elapsed (in milliseconds) between Start and Stop}
  BEGIN                      {ElapsedTime}
    ElapsedTime := 1000.0 * Cardinal (Stop - (Start + Delta) ) / TimerResolution;
  END;                       {ElapsedTime}

  FUNCTION ElapsedTimeString (Start, Stop : LongInt) : String;
    {-Return time elapsed (in milliseconds) between Start and Stop as a string}
  VAR
    R : Real;
    S : String;
  BEGIN                      {ElapsedTimeString}
    R := ElapsedTime (Start, Stop);
    Str (R: 0: 3, S);
    ElapsedTimeString := S;
  END;                       {ElapsedTimeString}

  PROCEDURE InitializeTimer;
    {-Reprogram the timer chip to allow 1 microsecond resolution}
  BEGIN                      {InitializeTimer}
    {select timer mode 2, read/write channel 0}
    Port [$43] := $34;        {00110100b}
    INLINE ($EB / $00);         {jmp short $+2 ;delay}
    Port [$40] := $00;        {LSB = 0}
    INLINE ($EB / $00);         {jmp short $+2 ;delay}
    Port [$40] := $00;        {MSB = 0}
  END;                       {InitializeTimer}

  PROCEDURE RestoreTimer;
    {-Restore the timer chip to its normal state}
  BEGIN                      {RestoreTimer}
    {select timer mode 3, read/write channel 0}
    Port [$43] := $36;        {00110110b}
    INLINE ($EB / $00);         {jmp short $+2 ;delay}
    Port [$40] := $00;        {LSB = 0}
    INLINE ($EB / $00);         {jmp short $+2 ;delay}
    Port [$40] := $00;        {MSB = 0}
  END;                       {RestoreTimer}

  FUNCTION ReadTimer : LongInt;
    {-Read the timer with 1 microsecond resolution}
  BEGIN                      {ReadTimer}
    INLINE (
    $FA /                   {cli             ;Disable interrupts}
    $BA / $20 / $00 /           {mov  dx,$20     ;Address PIC ocw3}
    $B0 / $0A /               {mov  al,$0A     ;Ask to read irr}
    $EE /                   {out  dx,al}
    $B0 / $00 /               {mov  al,$00     ;Latch timer 0}
    $E6 / $43 /               {out  $43,al}
    $EC /                   {in   al,dx      ;Read irr}
    $89 / $C7 /               {mov  di,ax      ;Save it in DI}
    $E4 / $40 /               {in   al,$40     ;Counter --> bx}
    $88 / $C3 /               {mov  bl,al      ;LSB in BL}
    $E4 / $40 /               {in   al,$40}
    $88 / $C7 /               {mov  bh,al      ;MSB in BH}
    $F7 / $D3 /               {not  bx         ;Need ascending counter}
    $E4 / $21 /               {in   al,$21     ;Read PIC imr}
    $89 / $C6 /               {mov  si,ax      ;Save it in SI}
    $B0 / $FF /               {mov  al,$0FF    ;Mask all interrupts}
    $E6 / $21 /               {out  $21,al}
    $B8 / $40 / $00 /           {mov  ax,$40     ;read low word of time}
    $8E / $C0 /               {mov  es,ax      ;from BIOS data area}
    $26 / $8B / $16 / $6C / $00 /   {mov  dx,es:[$6C]}
    $89 / $F0 /               {mov  ax,si      ;Restore imr from SI}
    $E6 / $21 /               {out  $21,al}
    $FB /                   {sti             ;Enable interrupts}
    $89 / $F8 /               {mov  ax,di      ;Retrieve old irr}
    $A8 / $01 /               {test al,$01     ;Counter hit 0?}
    $74 / $07 /               {jz   done       ;Jump if not}
    $81 / $FB / $FF / $00 /       {cmp  bx,$FF     ;Counter > $FF?}
    $77 / $01 /               {ja   done       ;Done if so}
    $42 /                   {inc  dx         ;Else count int req.}
    {done:}
    $89 / $5E / $FC /           {mov [bp-4],bx   ;set function result}
    $89 / $56 / $FE);          {mov [bp-2],dx}
  END;                       {ReadTimer}

  PROCEDURE Calibrate;
    {-Calibrate the timer}
  CONST
    Reps = 1000;
  VAR
    I : Word;
    L1, L2, Diff : LongInt;
  BEGIN                      {Calibrate}
    Delta := MaxInt;
    FOR I := 1 TO Reps DO BEGIN
      L1 := ReadTimer;
      L2 := ReadTimer;
      {use the minimum difference}
      Diff := L2 - L1;
      IF Diff < Delta THEN
        Delta := Diff;
    END;
  END;                       {Calibrate}

  {$F+}
  PROCEDURE OurExitProc;
    {-Restore timer chip to its original state}
  BEGIN                      {OurExitProc}
    ExitProc := SaveExitProc;
    RestoreTimer;
  END;                       {OurExitProc}
  {$F-}

BEGIN
  {set up our exit handler}
  SaveExitProc := ExitProc;
  ExitProc := @OurExitProc;
  
  {reprogram the timer chip}
  InitializeTimer;
  
  {adjust for speed of machine}
  Calibrate;
END.


{CUT OFF...}


{CUT ... Save this as TCTIMER.PAS}

UNIT tctimer;

INTERFACE
USES tptimer;

  VAR
    start : LongInt;
    
  PROCEDURE StartTimer;

PROCEDURE WriteElapsedTime;



IMPLEMENTATION

PROCEDURE StartTimer;
  BEGIN
    start := ReadTimer;
  END;

PROCEDURE  WriteElapsedTime;
  VAR stop : LongInt;
  BEGIN
    stop := ReadTimer;
    WriteLn ('calc = ', (ElapsedTime (start, stop) / 1000): 10: 6, ' sec');
  END;


END.

{CUT OFF...}

{CUT ... Save this as TPBUFFER.PAS}

UNIT TPbuffer;

(* TP-Buffer unit version 1.1 /Update              *)
(* Using the keyboard's buffer in Turbo Pascal     *)
(* This unit is released to the public domain      *)
(* by Lavi Tidhar on 5-10-1992                     *)

(* This unit adds three special functions not      *)
(* incuded in the Turbo Pascal regular package     *)

(* You may alter this source code, move the        *)
(* procedures to your own programs. Please do      *)
(* NOT change these lines of documentation         *)

(* This source might teach you about how to        *)
(* use interrupts in pascal, and the keyboard's    *)
(* buffer. from the other hand, it might not :-)   *)

(* Used: INT 16, functions 0 and 1                 *)
(*       INT 21, function 0Ch                      *)

(* INT 16 - KEYBOARD - READ CHAR FROM BUFFER, WAIT IF EMPTY
           AH = 00h
           Return: AH = scan code
                   AL = character         *)

(* INT 16 - KEYBOARD - CHECK BUFFER, DO NOT CLEAR
           AH = 01h
           Return: ZF = 0 character in buffer
                       AH = scan code
                       AL = character
                       ZF = 1 no character in buffer *)

(* INT 21 - DOS - CLEAR KEYBOARD BUFFER
        AH = 0Ch
        AL must be 1, 6, 7, 8, or 0Ah.
        Notes: Flushes all typeahead input, then executes function specified by AL
        (effectively moving it to AH and repeating the INT 21 call).
        If AL contains a value not in the list above, the keyboard buffer is
        flushed and no other action is taken. *)

(* For more details/help etc, you can contact me on: *)

(* Mail: Lavi Tidhar
         46 Bantam Dr.
         Blairgowrie
         2194
         South Africa
*)

(* Phone:
          International: +27-11-787-8093
          South Africa:  (011)-787-8093
*)

(* Netmail: The Catacomb BBS 5:7101/45 (fidonet)
            The Catacomb BBS 80:80/100 (pipemail)
*)

INTERFACE

USES DOS;

FUNCTION GetScanCode: Byte; (* Get SCAN CODE from buffer, wait if empty *)
FUNCTION GetKey: Char;      (* Get Char from buffer, do NOT wait *)
PROCEDURE FlushKB;

IMPLEMENTATION

FUNCTION GetKey: Char;
 VAR Regs: Registers;
 BEGIN
   Regs. AH := 1;                (* Int 16 function 1 *)
   Intr ($16, Regs);           (* Read a charecter from the keyboard buffer *)
   GetKey := Chr (Regs. AL);     (* do not wait. If no char was found, CHR(0) *)
 END;                        (* (nul) is returned *)

FUNCTION GetScanCode: Byte;   (* Int 16 function 0 *)
 VAR Regs: Registers;         (* The same as CRT's Readkey, but gives you *)
 BEGIN                      (* the scan code. Esp usefull when you want to *)
   Regs. AH := 1;               (* use special keys as the arrows, there will *)
   Intr ($16, Regs);          (* be a conflict when using ReadKey *)
   GetScanCode := Regs. AH;
 END;

PROCEDURE FlushKB;           (* INT 21 function 0C *)
 VAR Regs: Registers;         (* Flushes (erase) the keyboard buffer *)
 BEGIN                      (* ONLY. No other function is executed *)
   Regs. AH := $0C;
   Regs. AL := 2;
   Intr ($21, Regs);
 END;

END.

{CUT OFF...}


{CUT... Save this as SCANCODE.PAS}

UNIT ScanCode;

{ This UNIT is created by Wayne Boyd, aka Vipramukhya Swami, BBS phone
   (604)431-6260, Fidonet node 1:153/763. It's function is to facilitate
   the use of Function keys and Alt keys in a program. It includes F1
   through F10, Shift-F1 through Shift-F10, Ctrl-F1 through Ctrl-F10,
   and Alt-F1 through Alt-F10. It also includes all of the alt keys, all
   of the Ctrl keys and many other keys as well. This UNIT and source code
   are copyrighted material and may not be used for commercial use
   without express written permission from the author. Use at your own
   risk. I take absolutely no responsibility for it, and there are no
   guarantees that it will do anything more than take up space on your
   disk. }


INTERFACE

CONST
  
  F1  = 59;   CtrlF1  =  94;   AltF1  = 104;   Homekey   = 71;
  F2  = 60;   CtrlF2  =  95;   AltF2  = 105;   Endkey    = 79;
  F3  = 61;   CtrlF3  =  96;   AltF3  = 106;   PgUp      = 73;
  F4  = 62;   CtrlF4  =  97;   AltF4  = 107;   PgDn      = 81;
  F5  = 63;   CtrlF5  =  98;   AltF5  = 108;   UpArrow   = 72;
  F6  = 64;   CtrlF6  =  99;   AltF6  = 109;   RtArrow   = 77;
  F7  = 65;   CtrlF7  = 100;   AltF7  = 110;   DnArrow   = 80;
  F8  = 66;   CtrlF8  = 101;   AltF8  = 111;   LfArrow   = 75;
  F9  = 67;   CtrlF9  = 102;   AltF9  = 112;   InsertKey = 82;
  F10 = 68;   CtrlF10 = 103;   AltF10 = 113;   DeleteKey = 83;
  
  AltQ = 16;   AltA = 30;   AltZ = 44;   Alt1 = 120;  ShftF1 = 84;
  AltW = 17;   AltS = 31;   AltX = 45;   Alt2 = 121;  ShftF2 = 85;
  AltE = 18;   AltD = 32;   AltC = 46;   Alt3 = 122;  ShftF3 = 86;
  AltR = 19;   AltF = 33;   AltV = 47;   Alt4 = 123;  ShftF4 = 87;
  AltT = 20;   AltG = 34;   AltB = 48;   Alt5 = 124;  ShftF5 = 88;
  AltY = 21;   AltH = 35;   AltN = 49;   Alt6 = 125;  ShftF6 = 89;
  AltU = 22;   AltJ = 36;   AltM = 50;   Alt7 = 126;  ShftF7 = 90;
  AltI = 23;   AltK = 37;                Alt8 = 127;  ShftF8 = 91;
  AltO = 24;   AltL = 38;                Alt9 = 128;  ShftF9 = 92;
  AltP = 25;   CtrlLf = 115;             Alt0 = 129;  ShftF10 = 93;
  CtrlRt = 116;
  
  CtrlA  = #1;  CtrlK = #11; CtrlU = #21; CtrlB = #2;  CtrlL = #12;
  CtrlV  = #22; CtrlC = #3;  CtrlM = #13; CtrlW = #23; CtrlD = #4;
  CtrlN  = #14; CtrlX = #24; CtrlE = #5;  CtrlO = #15; CtrlY = #25;
  CtrlF  = #6;  CtrlP = #16; CtrlZ = #26; CtrlG = #7;  CtrlQ = #17;
  CtrlS  = #19; CtrlH = #8;  CtrlR = #18; CtrlI = #9;  CtrlJ = #10;
  CtrlT = #20;  BSpace = #8; EscapeKey = #27; EnterKey = #13; NullKey = #0;
  
IMPLEMENTATION

END.

{CUT OFF...}