{$X+,O+,F+}
UNIT CDRom1;              { CD Rom Interfacing routines.                 }
                         { Last Updated July 16/94, Greg Estabrooks.    }
INTERFACE
{***********************************************************************}
USES DOS;
CONST
   CDRead  = $4402;
   CDWrite = $4403;

             { Define some CD IOCTL OutPut function codes.              }
   EjectDisk   = 0;
   LockUnlock  = 1;
   ResetCD     = 2;
   AudioCtrl   = 3;
   WriteCtrlStr= 4;
   CloseTray   = 5;

TYPE
CDPosType = RECORD
   CtlAdr : BYTE;  { Control and ADR byte.                 }
   Track  : BYTE;  { Current Track #.                      }
   Indx   : BYTE;  { Point or Index Byte.                  }
   Min    : BYTE;  { Minute. \                             }
   Sec    : BYTE;  { Second.  > Running time within track. }
   Frame  : BYTE;  { Frame.  /                             }
   Zero   : BYTE;  { Should be a 0.                        }
   DMin   : BYTE;  { Minute. \                             }
   DSec   : BYTE;  { Second.  > Running time on disk.      }
   DFrame : BYTE;  { Frame.  /                             }
END;

CDInfoRecord = RECORD
   Status : WORD; { Holds the status of last operation. }
   NumCD  : WORD; { Number of CD Drives available.      }
   DrvChar: CHAR; { First CD drive in CHAR format.      }
   DrvNo  : BYTE; { BYTE value of first drive. 0 = A,ETC}
   DVParam: LONGINT;{ Device parameters.                }
   VolInf : ARRAY[1..8] OF BYTE; { Holds Audio Channel inf.}
   LoTrack: BYTE; { Lowest Audio track #.               }
   HiTrack: BYTE; { Highest Audio Track #.              }
   LdAdr  : LONGINT;{ Address of Lead track in HSG.     }
END;

DriveList = RECORD
   UnitCode : BYTE;
   DOffset,DSegment : WORD;
END;

VAR
   CDDevice :TEXT;              { File Handle for the CD Driver.        }
   CDStatus :WORD;              { Status for last CD operation.         }
   CDControl:ARRAY[0..200] OF BYTE;
   CDHandle :WORD;
   CDInf    :CDInfoRecord;
   PosInf   :CDPosType;

FUNCTION CDGetHandle : WORD;
                         { Routine to get handle for referencing the CD }
                         { Device driver.                               }
PROCEDURE CDCloseHandle;
                         { Routine to close Handle referencing the CD   }
                         { Driver.                                      }
FUNCTION CDIoctl(IntFunc, Len : WORD; VAR CtlBlk) : BOOLEAN;
                         { Routine to call the CD IOCTL.                }
PROCEDURE DriverRequest(Drive : BYTE; VAR CtlBlk);
                         { Routine to make request of MSCDEX.           }
FUNCTION CDEject : BOOLEAN;
                         { Routine to Eject the CD tray.                }
FUNCTION CDCloseTray : BOOLEAN;
                         { Routine to close the CD tray.                }
FUNCTION CDReset : BOOLEAN;
                         { Routine to reset the CD Drive.               }
FUNCTION CDGetVol(VAR InfRec : CDInfoRecord) : BOOLEAN;
                         { Routine to get current audio volume output.  }
FUNCTION CDStop : BOOLEAN;
                         { Routine to stop the playing and Audio CD.    }
PROCEDURE CDInitInfo;
                         { Routine to Intilialize CD Info.              }
FUNCTION CDResumePlay : BOOLEAN;
                         { Routine to Resume playing a previously stopped}
                         { audio track.                                  }
FUNCTION CDGetPos(VAR PosInf : CDPosType ) : BOOLEAN;
                         { Routine to retrieve current position being   }
                         { played.                                      }
FUNCTION Red2HSG(Inf : LONGINT ) : LONGINT;

FUNCTION CDGetTrackStart(Track : BYTE ) : LONGINT;

FUNCTION CDVolSize : LONGINT;
                         { Routine to determine the volume size in      }
                         { sectors.                                     }
FUNCTION CDSectSize : WORD;
                         { Routine to determine the Sector size in      }
                         { bytes.                                       }
FUNCTION CDPlayAudio(Track : BYTE; Len : LONGINT ) : BOOLEAN;

IMPLEMENTATION
{***********************************************************************}
VAR
   OldExit : POINTER;
   CDDL     : DriveList;
   CDDriver : STRING[8];  { CD Driver name.                }

FUNCTION GetDriverName : String;
VAR
   CDNTemp : Array[1..18] OF BYTE;
   Where : POINTER;
   Count : BYTE;
   CDSTemp : STRING[8];
BEGIN
   ASM
      MOV AX, 1501h
      MOV BX, OFFSET CDDL
      MOV DX, SEG CDDL
      MOV ES, DX
      INT $2F
   END;
   Where := Ptr(CDDL.DSegment,CDDL.DOffset);
   Move(Where^,CDNTEMP,18);
   Count := 1;
   REPEAT
      CDStemp[Count] := CHR(CDNTemp[10+Count]);
      INC(Count);
   UNTIL (Count > 8) OR (CDNTemp[10+Count]=32);
   CDSTemp[0] := CHR(Count-1);
   GetDriverName := CDSTemp;
END;


FUNCTION CDGetHandle : WORD;
{ Routine to get handle for referencing the CD }
{ Device driver.                               }
VAR
   Handle : WORD;
BEGIN
   Assign(CDDevice,CDDriver);     { Assign Handle to driver.              }
   {$I-}                          { Turn I/O checking off.                }
   Reset(CDDevice);               { Attempt to open driver.               }
   {$I+}                          { Turn I/O checking on.                 }
   IF (IOResult = 0) THEN
      Handle := TextRec(CDDevice).Handle{ Save DOS Handle.                }
    ELSE
      Handle := 0;
   CDGetHandle := Handle;
END;{CDGetHandle}

PROCEDURE CDCloseHandle;
{ Routine to close Handle referencing the CD   }
{ Driver.                                      }
BEGIN
   {$I-}                          { Turn I/O checking off.                }
   Close(CDDevice);               { Attempt to Close driver.              }
   {$I+}                          { Turn I/O checking on.                 }
   IF (IOResult = 0) THEN           { Dummy IF to clear IOResult.           }
   BEGIN
   END;
END;{CDCloseHandle}

FUNCTION CDIoctl(IntFunc, Len : WORD;VAR CtlBlk ) : BOOLEAN; ASSEMBLER;
{ Routine to call the CD IOCTL.                }
ASM
  PUSH   DS
  MOV    AX,IntFunc                { 4402 = Read, 4403 = Write.            }
  MOV    BX,CDHandle               { Load Handle for Driver into BX.       }
  MOV    CX,Len
  LDS    DX,CtlBlk                 { Point DS:DX to the control block.     }

  INT    $21                       { Call DOS Interrupt.                   }
  MOV    CDStatus,AX               { Save status of function.              }
  JNC    @NoError                  { If there was no error jump to noerror.}
  MOV    AX,0                      { Return FALSE.                         }
@NoError:
  MOV    AX,1                      { Return TRUE.                          }
@Exit:
  POP    DS
END;{CDIoctl}

PROCEDURE DriverRequest(Drive : BYTE;VAR CtlBlk ); ASSEMBLER;
{ Routine to make request of MSCDEX.           }
ASM
   PUSH  ES                       { Save ES.                              }
   MOV   AX,$1510                  { Subfunction to make request of CD driver.}
   XOR   CH,CH                     { Clear High byte of CX.                }
   MOV   CL,Drive                  { Load drive to make request of. 0 = A,etc}
   LES   BX,CtlBlk                 { Point ES:BX to the control block.     }
   INT $2F                       { Call Multiplex interrupt.             }
   POP ES                        { Restore ES.                           }
END;{DriverRequest}

FUNCTION CDEject : BOOLEAN;
{ Routine to Eject the CD tray.                }
BEGIN
   CDControl[0] := EjectDisk;     { Function code to eject CD.            }
   CDEject := CDIoctl(CDWrite,1,CDControl); { Now try function and return }
                                            { result to program/user.     }
END;{CDEject}

FUNCTION CDCloseTray : BOOLEAN;
{ Routine to close the CD tray.                }
BEGIN
   CDControl[0] := CloseTray;     { Function code to close CD.            }
   CDCloseTray := CDIoctl(CDWrite,1,CDControl);{ Now try function and return}
                                          { result to program/user.     }
END;{CDCloseTray}

FUNCTION CDReset : BOOLEAN;
{ Routine to reset the CD Drive.               }
BEGIN
   CDControl[0] := ResetCD;       { Function code to Reset drive.         }
   CDReset := CDIoctl(CDWrite,1,CDControl);{ Now try function and return  }
                                          { result to program/user.     }
END;{CDReset}

FUNCTION CDGetVol(VAR InfRec : CDInfoRecord) : BOOLEAN;
{ Routine to get current audio volume output.  }
VAR
   Temp : BOOLEAN;               { Holds IOCTL Read Result.              }
BEGIN
   CDControl[0] := 4;             { Function to read current volumes.     }
   Temp := CDIoctl(CDRead,8,CDControl);
   IF Temp THEN                    { IF all was fine then save volumes to }
      Move(CDControl[1],InfRec.VolInf,8){ array.                            }
    ELSE
      FillChar(InfRec.VolInf,8,#0);  { Otherwise zero the array.            }
   CDGetVol := Temp;               { Return proper result.                }
END;{CDGetVol}

FUNCTION CDStop : BOOLEAN;
{ Routine to stop the playing and Audio CD.    }
BEGIN
   FillChar(CDControl,Sizeof(CDControl),#0);
   CDControl[0] := 5;             { Byte length of request header.        }
   CDControl[1] := 0;             { Sub unit #.                           }
   CDControl[2] := $85;           { Function to stop CD.                  }
   DriverRequest(CDInf.DrvNo,CDControl);
   CDStatus := CDControl[3] OR CDControl[4] SHL 8;
   CDStop   := (CDStatus AND $8000) = 0;
END;{CDStop}

PROCEDURE CDInitInfo;
{ Routine to Intilialize CD Info.              }
BEGIN
   ASM
     MOV  AX,$1500                  { Function to get installation info.    }
     MOV  BX,0                      { Clear BX.                             }
     INT  $2F                       { Call CD Multiplex.                    }
     MOV  CDInf.NumCD,BX            { Save number of CD drives available.   }
     MOV  CDInf.DrvNo,CL            { Save first drive number.              }
     MOV  CDInf.DrvChar,CL          { Save Drive number and convert it to   }
     ADD  CDInf.DrvChar,'A'         { a CHAR. I.E A,B,C,ETC                 }
   END;

  FillChar(CDControl,SizeOf(CDControl),#0);
  CDControl[0] := $0A;           { Function to get Audio Disk info.      }
  CDIoctl(CDRead,6,CDControl);
  Move(CDControl[1],CDInf.LoTrack,6);
END;{CDInitInfo}

FUNCTION CDResumePlay : BOOLEAN;
{ Routine to Resume playing a previously stopped}
{ audio track.                                  }
BEGIN
   FillChar(CDControl,Sizeof(CDControl),#0);
   CDControl[0] := 5;             { Byte length of request header.        }
   CDControl[1] := 0;             { Sub unit #.                           }
   CDControl[2] := $88;           { Function to Resume play a CD.         }
   DriverRequest(CDInf.DrvNo,CDControl);
   CDStatus := CDControl[3] OR CDControl[4] SHL 8;
   CDResumePlay := (CDStatus AND $8000) = 0;
END;{CDResumePlay}

FUNCTION CDGetPos(VAR PosInf : CDPosType ) : BOOLEAN;
{ Routine to retrieve current position being   }
{ played.                                      }
BEGIN
   CDControl[0] := $0C;           { Function to get Audio Postion info.   }
   CDGetPos := CDIoctl(CDRead,10,CDControl);
   Move(CDControl[1],PosInf,10);
END;{CDGetPos}

FUNCTION Red2HSG(Inf : LONGINT ) : LONGINT;
VAR
   Temp :LONGINT;
BEGIN
   Temp :=        LONGINT(( Inf SHR 16 ) AND $FF )  * 4500;
   Temp := Temp + LONGINT(( Inf SHR  8 ) AND $FF )  * 75;
   Temp := Temp + LONGINT(( Inf ) AND $FF ) ;
   Red2HSG := Temp - 2;
END;{Red2HSG}

FUNCTION CDGetTrackStart(Track : BYTE ) : LONGINT;
VAR
   TrackInf :ARRAY[0..6] OF BYTE;
   Start    :LONGINT;
BEGIN
   TrackInf[0] := $0B;            { Function to get track info.           }
   TrackInf[1] := Track;          { Track to get information of.          }

   CDIoctl(CDRead,6,TrackInf);
   Move(TrackInf[2],Start,4);
   CDGetTrackStart := Red2HSG(Start);
END;{CDGetTrackStart}

FUNCTION CDVolSize : LONGINT;
{ Routine to determine the volume size in      }
{ sectors.                                     }
VAR
   TempLong : LONGINT;           { Holds temporary size info.            }
BEGIN
   CDControl[0] := 8;            { Function code to determine volume size.}
   CDIoctl(CDRead,4,CDControl);  { Now get information.                   }
   Move(CDControl[1],TempLong,4);
   CDVolSize := TempLong;
END;{CDVolSize}

FUNCTION CDSectSize : WORD;
{ Routine to determine the Sector size in      }
{ bytes.                                       }
VAR
   TempWord :WORD;              { Holds temporary size info.            }
BEGIN
   CDControl[0] := 7;            { Function code to determine Sector size.}
   CDIoctl(CDRead,4,CDControl);  { Now get information.                   }
   Move(CDControl[2],TempWord,2);
   CDSectSize := TempWord;
END;{CDSectSize}

FUNCTION CDPlayAudio(Track : BYTE;Len : LONGINT ) : BOOLEAN;
VAR
   TrackStart : LONGINT;
BEGIN
   FillChar(CDControl,SizeOf(CDControl),#0); { Clear Control block.       }
   CDControl[0] := 22;            { Length of request header.             }
   CDControl[1] := 0;             { Zero the sub unit.                    }
   CDControl[2] := $84;           { Function to play audio.               }
   TrackStart := CDGetTrackStart(Track);
   Move(TrackStart,CDControl[14],4);
   Move(Len,CDControl[18],4);     { # of sectors to play.                 }
   DriverRequest(CDInf.DrvNo,CDControl);
   CDStatus := CDControl[3] OR CDControl[4] SHL 8;
   CDPlayAudio := (CDStatus AND $8000) = 0;
END;{CDPlayAudio}

{$F+}
PROCEDURE CDExit;
{ Our Exiting routine to clean up after our    }
{ selves.                                      }
BEGIN
   ExitProc := OldExit;           { Restore original exit procedure.      }
   CDCloseHandle;                 { Close the handle for the CD Driver.   }
END;
{$F-}

BEGIN

   CDDriver := GetDriverName;
   CDHandle := CDGetHandle;
   IF (CDHandle <> 0) THEN
   BEGIN
      CDInitInfo;
      OldExit := ExitProc;
      ExitProc := @CDExit;
   END;
END.{CDRom}
