Contributor: NORBERT IGL              

{
> Are there anybody out there who has some routins to play CD Audio in a CD
> ROM drive. Just the usual commands like play, stop, resume, eject and so
> on. I would appreciate any help!
}

Unit CDROM;

{  Unit talking to a CD-Rom-Drive
   Low-level CD access,
   only the first drive is supported...!
   Copyright 1992  Norbert Igl  }

Interface

Type
   CD_Record = Record
                    Status : Word;    { Status des Drives/letzte Funktion }
                    DrvChar: Char;    { LW-Buchstabe }
                    DrvNo  : Byte;    { als Byte ablegegt (0...) }
                    HSG_RB : Byte;    { Adressierungs-Modus }

                    Sector : LongInt; { Adresse des Lesekopfes }
                    VolInfo: Array[1..8] of Byte; { Lautst.-Einstellungen }
                    DevPar : LongInt; { Device-parameter, BIT-Feld! }
                    RawMode: Boolean; { Raw/Cooked-Mode ? }
                    SecSize: Word;    { Bytes/Sector }
                    VolSize: LongInt; { sek/Volume => Groesse der CD}

                    MedChg : Byte;    { Disk gewechselt? }

                    LoAuTr : Byte;    { kleinste Audio-Track # }
                    HiAuTr : Byte;    { groesste Audio-Track # }
                    endAdr : LongInt; { Adresse der Auslaufrille (8-) }

                    TrkNo  : Byte;    { Track #. Eingabe-Wert ! }
                    TrkAdr : LongInt; { Adresse dieses Tracks }
                    TrkInf : Byte;    { Info dazu: BIT-Feld! }

                    CntAdr : Byte;   { CONTROL und ADR, von LW }
                    CTrk   : Byte;   { track # }
                    Cindx  : Byte;   { point/index }
                    CMin   : Byte;   { minute\  }
                    CSek   : Byte;   { second > Laufzeit im Track }
                    CFrm   : Byte;   { frame /  }
                    Czero  : Byte;   { immer =0 }
                    CAmin  : Byte;   { minute \ }
                    CAsec  : Byte;   { sekunde > Laufzeit auf Disk }
                    CAFrm  : Byte;   { frame  / }

                    Qfrm   : LongInt;{ start-frame address }
                    Qtrfs  : LongInt;{ Bufferaddresse }
                    Qcnt   : LongInt;{ Anzahl der Sectoren }
                      { pro Sector werden 96 Byte nach buffer kopiert }

                    Uctrl  : Byte;  { CONTROL und ADR Byte }
                    Upn    : Array[1..7] of Byte; { EAN-CODE }
                    Uzero  : Byte;  { immer = 0 }
                    Ufrm   : Byte;  { Frame-# }
                  end;
      OneTrack             = Record
                               Title   : String[20];
                               Runmin,
                               RunSec :  Byte;
                               Start  :  LongInt;  { HSG Format ! }
                             end;
      VolumeTableOfContens = Record
                               Diskname: String[20];
                               UAN_Code: String[13];
                               TrackCnt: Byte;
                               Titles  : Array[1..99] of OneTrack;
                             end;
       TrkInfo  = Record
                     Nummer  : Byte;
                     Start   : LongInt;
                     Cntrl2  : Byte;
                  end;
{===== global verfuegbare Variablen =============}

Var    CD           : CD_Record;
       CD_AVAIL     : Boolean;
       VtoC         : VolumeTableOfContens;
       CD_REDPos    : String;
       CD_HSGPos    : String;

{===== allgemeine Funktionen ===================}

Function CD_Reset   : Boolean;
Function CD_HeadAdr : Boolean;
Function CD_Position: Boolean;
Function CD_MediaChanged: Boolean;


{===== Tray/Caddy-Funktionen ===================}

Function CD_Open:  Boolean;
Function CD_Close: Boolean;
Function CD_Eject: Boolean;

{==== Audio-Funktionen =========================}

Function CD_Play(no:Byte; len:Integer):  Boolean;
Function CD_Stop:  Boolean;
Function CD_Resume:Boolean;
Function CD_SetVol:Boolean;
Function CD_GetVol:Boolean;

Procedure CD_Info;
Procedure CD_TrackInfo( Nr:Byte; Var T:TrkInfo );

{==== Umwandlungen =============================}

Function Red2Time( Var Inf:TrkInfo ):Word;

Implementation Uses Dos;
Type   IOCtlBlk = Array[0..200] of Byte;

Const  IOCtlRead  = $4402;
       IOCtlWrite = $4403;
       DevDrvReq  = $1510;
       All:LongInt= $0f00;

Var  R        : Registers;
     H        : Text;
     Handle   : Word;
     Old_Exit : Pointer;
     CtlBlk   : IOCtlBlk;

     Tracks   : Array[1..100] of TrkInfo;

Procedure CD_Exit;               { wird bei Programmende ausgefuehrt }
begin
  if Old_Exit <> NIL
    then ExitProc := Old_Exit;      { Umleitung wieder zuruecknehmen }
{$I-}
  Close(H);
  If IoResult = 0 then;              { 'H' schliessen, falls offen, }
{$I+}                                      { evtl. Fehler verwerfen }
end;


Function CD_Init:  Boolean;    { Initialisierung beim Programmstart }
begin
 FillChar( CD, SizeOf( CD ), 0);
 With R do
 begin
   AX := $1500;
   BX := $0000;
   CX := $0000;
   Intr( $2F, R );
   CD_Init := (BX > 0);                  { Anzahl der CD-Laufwerke }
   If BX > 0
    then begin
      CD.DrvChar                           { CD-Laufwerksbuchstabe }
         := Char( CL + Byte('A') );
      CD.DrvNo := CL;
      If CD_HeadAdr then
        If CD_GetVol then;
    end
    else CD.DrvChar := '?';                      { im Fehlerfall...}
 end
end;

Procedure CD_TrackInfo( Nr:Byte; Var T:TrkInfo );
begin
  T := Tracks[nr]
end;

Function OpenCDHandle:Word;
Const Name : String[8] = 'MSCD001';        { evt. anpassen!!! ? }
begin
  Assign(H, Name);                         { Filehandle holen }
(*$I-*)
  Reset(H);
(*$I+*)
  if IoResult = 0 then
  begin
    Handle := TextRec(H).Handle;                { Filehandle holen }
    Old_Exit := ExitProc;           { Bei ende/Abbruch muss 'H'... }
    ExitProc := @CD_Exit;      { ...automatisch geschlossen werden }
  end
  else Handle := 0;
  OpenCDHandle := Handle;
end;

Procedure CloseCDHandle;
begin
  if TextRec(H).Mode <> FmClosed
     then ExitProc := Old_Exit;     { Umleitung wieder zuruecknehmen }
  Old_Exit := NIL;
{$I-}
  Close(H);
  If IoResult = 0 then;             { 'H' schliessen, falls offen, }
{$I+}                                     { evtl. Fehler verwerfen }
end;


Function Red2HSG( Var Inf:TrkInfo ):LongInt;
Var l: LongInt;
begin
      l :=     LongInt(( Inf.Start shr 16 ) and $FF )  * 4500;
      l := l + LongInt(( Inf.Start shr  8 ) and $FF )  * 75;
      l := l + LongInt(( Inf.Start        ) and $FF ) ;

  Red2HSG := l -2;
end;

Function Red2Time( Var Inf:TrkInfo ):Word;
begin
  Red2Time:= (( Inf.Start shr 24 ) and $FF ) shl 8
           + (( Inf.Start shr 16 ) and $FF )
end;

Function HSG2Red(L:LongInt):LongInt;
begin
end;

Function CD_IOCtl( Func, Len : Word) :  Boolean;
begin
  With R do
  begin
    AX := Func;
    BX := OpenCDHandle;
    CX := 129;
    DS := DSeg;
    ES := DS;
    DX := Ofs(CtlBlk);
    MsDos( R );
    CD.Status := AX;
    CD_IOCtl  := (Flags and FCARRY) = 0;
    CloseCDHandle;
  end
end;


Function CD_Reset: Boolean;
begin
  CtlBlk[0] := 2;   { Reset }
  CD_Reset  := CD_IoCtl( IoCtlWrite, 1)
end;

Function DieTuer( AufZu:Byte ): Boolean;
begin
  CtlBlk[0] := 1;                                      { die Tuer.. }
  CtlBlk[1] := AufZu;                                { ..freigeben }
  DieTuer := CD_IoCTL(IoCtlWrite, 2);
end;

Function CD_Open: Boolean;
Const Auf = 0;
begin
 CD_Open := DieTuer( Auf );
end;

Function CD_Close: Boolean;
Const Zu = 1;
begin
 CD_Close := DieTuer( Zu );
end;


Function CD_Eject: Boolean;
begin
  CtlBlk[0] := 0;                                   { CD auswerfen }
  CD_Eject  := CD_IOCtl(IoCtlWrite, 1);
end;


Function CD_Play(no:Byte; len:Integer):  Boolean;
begin                                               { CD PlayAudio }

  FillChar(CtlBlk, SizeOf(CtlBlk), 0);
  CtlBlk[0] := 22;                             { laenge des req-hdr }
  CtlBlk[1] := 0;                                       { sub-Unit }
  CtlBlk[2] := $84;                                     { Kommando }
  CtlBlk[3] := 0;                                    { Status-WORT }
  CtlBlk[4] := 0;
  CtlBlk[5] := 0;
  CtlBlk[13]:= CD.HSG_RB;                             { HSG-Modus }

  CD.Sector := VtoC.Titles[no].Start;          { ist im HSG-Format }

  Move( CD.Sector, CtlBlk[14], 4 );                 { Start-Sector }
  if len = -1
    then All := $FFFF
    else All := len;
  Move( All      , CtlBlk[18], 4 );               { Anzahl Sectoren}
  Asm
     mov  ax, $1510
     push ds
     pop  es
     xor  cx, cx
     mov  cl, CD.DrvNo
     mov  bx, offset CtlBlk
     Int $2f
  end;

  CD.Status := CtlBlk[3] or CtlBlk[4] shl 8;
  CD_Play   := CD.Status and $8000 = 0;

end;

Function CD_VtoC:Boolean;
Var i: Byte;
    l: LongInt;
begin
  FillChar( Tracks, SizeOf( Tracks ), 0);
  CtlBlk[0] := 10;                               { Read LeadOut-Tr }
  CD_IoCtl( IoCtlRead, 6);
  Move( CtlBlk[1], CD.LoAuTr, 6);
  i := CD.HiAuTr+1;
  Move( CtlBlk[3], Tracks[i], 4);      { die Auslaufrille 8-) }
  Tracks[i].Start := Red2Hsg(Tracks[i]);

  For i := CD.LoAuTr to CD.HiAuTr do
  begin
    FillChar(CtlBlk, SizeOf(CtlBlk), 0);           { RED-Book-Format }
    CtlBlk[0] := 11;                               { Read VtoC-Entry }
    CtlBlk[1] := i;                                       { track-no }
    CD_IoCtl( IoCtlRead, 6);
    Move( CtlBlk[1], Tracks[i], 6);
{   Tracks[i].Start := Red2Hsg(Tracks[i]); }
  end;


  With VtoC do
  begin
    DiskName := '';
    UAN_Code := '';
    TrackCnt := CD.HiAuTr;
    For i := CD.LoAuTr to CD.HiAuTr do
    With Titles[i] do
    begin
      L := LongInt((Tracks[i+1].Start shr 16) and $FF) * 60
        +         (Tracks[i+1].Start shr  8) and $FF
        - ( LongInt((Tracks[i].Start shr 16) and $FF) * 60
                 +  (Tracks[i].Start shr  8) and $FF);
      Title  := '???';
      RunMin := L div 60;
      RunSec := l - RunMin*60;
      Start  := Red2Hsg(Tracks[i]);
    end
  end;



end;

Function CD_Stop:  Boolean;
begin                                               { CD StopAudio }
  FillChar(CtlBlk, SizeOf(CtlBlk), 0);
  CtlBlk[0] := 5;                             { laenge des req-hdr }
  CtlBlk[1] := 0;                                       { sub-Unit }
  CtlBlk[2] := $85;                                     { Kommando }
  CtlBlk[3] := 0;                                    { Status-WORT }
  CtlBlk[4] := 0;
  CtlBlk[5] := 0;
  Asm
     mov  ax, $1510
     push ds
     pop  es
     xor  cx, cx
     mov  cl, CD.DrvNo
     mov  bx, offset CtlBlk
     Int $2f
  end;

  CD.Status := CtlBlk[3] or CtlBlk[4] shl 8;
  CD_Stop   := CD.Status and $8000 = 0;

end;


Function CD_Resume:Boolean;
begin                                                 { ResumeAudio}
  CtlBlk[0] := 3;                              { laenge des req-hdr }
  CtlBlk[1] := 0;                                       { sub-Unit }
  CtlBlk[2] := $88;                                     { Kommando }
  CtlBlk[3] := 0;                                    { Status-WORT }
  CtlBlk[4] := 0;
  Asm
     mov ax, Seg @DATA
     mov es, ax
     mov ax, DevDrvReq
     lea bx, CtlBlk
     Int 2fh
  end;
  CD.Status := CtlBlk[3] or CtlBlk[4] shl 8;
  CD_Resume := CD.Status and $8000 = 0;

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 CD_HeadAdr: Boolean;
Var  L:LongInt;  S:String;
begin
  FillChar(CtlBlk, SizeOf(CtlBlk), 0);
  CtlBlk[0] := 1;
  CtlBlk[1] := 1;                     { die KopfPosition im RED-Format }
  CD_HeadAdr:= CD_IOCtl(IoCtlRead, 128);
  if ((R.Flags and FCARRY) = 0)
    then begin
           Move(CtlBlk[2], L, 4);
           if CtlBlk[1] = 1 then
           begin
             STR( CtlBlk[4]:2, S);  CD_REDPos := S;
             STR( CtlBlk[3]:2, S);  CD_REDPos := CD_REDPos+ ':'+ S;
             CD.Sector := LongInt(CtlBlk[4]) *4500 +
                          LongInt(CtlBlk[3]) *75   +
                          LongInt(CtlBlk[2])
                          - 150;
           end else
           begin
             CD.Sector := L;
             STR(L:0,CD_HSGPos);
           end

         end
    else FillChar( CD.Sector, 4, 0);
end;


Function CD_Position:Boolean;
Var l : LongInt;
begin
  CtlBlk[0] := 12;                                  { Audio-Infos  }
  CD_Position :=CD_IOCtl(IoCtlRead,10);
  Move(CtlBlk[1], CD.CntAdr, 10);
end;


Procedure CD_GetUAN;
begin
  CtlBlk[0] := 14;                                  { EAN-Nummer   }
  If CD_IOCtl(IoCtlRead,10)
    then Move(CtlBlk[1], CD.Uctrl, 10);
end;


Function CD_MediaChanged:Boolean;
begin
  CtlBlk[0] := 9;                                   { Media-Change }
  If CD_IOCtl(IoCtlRead, 1)
    then Move(CtlBlk[1], CD.MedChg, 1 );
  CD_MediaChanged:= CD.MedChg <> 1
end;

Procedure CD_Info;
begin

 { CD_Reset; }

  If CD_HeadAdr then;

  CtlBlk[0] := 6;                               { Device-parameter }
  If CD_IOCtl(IoCtlRead, 4)
    then Move(CtlBlk[1], CD.DevPar, 4 );

  CtlBlk[0] := 7;                                   { Sector-Groesse }
  If CD_IOCtl(IoCtlRead, 3)                              { & Modus }
    then Move(CtlBlk[1], CD.RawMode, 3 );

  CtlBlk[0] := 8;                                   { Volume-Groesse }
  If CD_IOCtl(IoCtlRead, 4)
    then Move(CtlBlk[1], CD.VolSize, 4 );

  CtlBlk[0] := 12;                                  { Audio-Infos  }
  If CD_IOCtl(IoCtlRead,10)
    then Move(CtlBlk[1], CD.CntAdr, 10);

  CtlBlk[0] := 11;                                  { Track-Infos  }
  CtlBlk[1] := CtlBlk[2];                           { aktueller... }
  If CD_IOCtl(IoCtlRead, 6)
    then Move(CtlBlk[1], CD.TrkNo, 6 );

  CD_VtoC;

end;

{========= minimale Initialisierung =============}
begin
  CD_Avail := CD_Init;
  if CD_Avail then CD_INFO
end. Norbert

{
--- part 2, a Test -----
}
Program CDROM_TEST;
Uses Crt, cdrom, SbTest;
Type a5 = Array[0..4] of Byte;
Var i:Byte;
    L : LongInt;
    ch : Char;
    no,
    len : Integer;

begin
  ClrScr;
  WriteLn('CDROM-Unit TestProgram',#10);
  With CD do
  if CD_Avail then
  begin
   WriteLn('þ CD als Laufwerk ',DrvChar,': gefunden!');
   Write  ('þ Aktuelle CD: ');

   Write('(UPN-CODE:');
   For i := 1 to 7 do Write(Char( (Upn[i] shr 4)  or $30),
                            Char((Upn[i] and $f) or $30));
   WriteLn(#8')');
   WriteLn('þ Audio-Tracks : ',loautr,'..',hiautr);
   WriteLn(' Laufzeiten : ');
   For i := CD.LoAuTr to CD.HiAuTr do
    With VtoC.Titles[i] do
      WriteLn(i,Title:10, RunMin:6,':',RunSec);
   no := 1;
   len := -1;

   if CD_Stop then
     if not CD_Play( no ,len)
        then WriteLn('! Fehler-Status: ',STATUS and $F);

   ch := ' ';
   While ch <> #27 do
   begin
   While ch = ' ' do
     With CD do
     begin
       if CD_Position then
         Write('Playing Track ',CTrk,'  :   ',CMin:2,':',CSek:2,'   '#13);
       Delay(1500);
       if KeyPressed
          then ch := ReadKey;
     end;
     Case ch of
       '+' : Inc(no);
       '-' : Dec(no);
     end;
     if ch <> #27 then ch := ' ';
     if no > cd.HiAUTr then Dec(no);
     if no < cd.LoAuTr then Inc(no);
     if CD_Stop
       then CD_Play(no, len);
   end;
   cd_stop;
   clreol;
   WriteLn(' CD stopped...');
  end
  else WriteLn('Leider kein CD-ROM gefunden...');
end.