[Back to SOUND SWAG index]  [Back to Main SWAG index]  [Original]

{ I noticed that more than a few people wanted to know how to program a
  CD player. I did too, once. So here is the fruit of my labor.
  The programming isn't all that good. I wrote it to be SMALL.

  There are quite a number of unused variables, procedures, etc. Just
  try them!

  I'd like to thank Kristian Leonhard for two of his procedures. Saved a
  big amount of space and bad computing.

  No messages about how I _should_ have coded something. If you can make it
  so much better DO IT.

  I recommend a file called MSCDFUNC.DOC which contains all of the info
  used in this program. EXCEPT for the stuff that Kristian Leonhard wrote.

  Programmed by: Patrick O'Malley, 1995 
}

{$X+}        {I know, bad directive}
Program CDPlayer;
Uses Crt;
Type  Std = record             {read and write}
                 length : byte;
                 subcode : byte;
                 cmdcode : byte;
                 status : word;
                 res : array[1..8] of byte;
                 {---Nothing special between READ and WRITE----}
                 MedDesc : Byte;
                 XFerAddr : Pointer;
                 XFerbytes : Word;
                 SSNum : Word;
                 ReqPtr : Longint;
               end;

      Playreq = record            {play}
                  Length : Byte;
                  SubCode : Byte;        {0?}
                  CmdCode : Byte;        {132}
                  Status : Word;
                  Res : Array[1..8] of byte;
                  AddrMode : Byte;
                  StrtSec : Longint;
                  numSect : Longint;
                end;

      Qinfo = record         {QI}           {uses IOCTLI header}
                CtlCode : Byte; {12}
                CTl_ADR : Byte;
                TNO : Byte;
                POINT : Byte;
                MIN : Byte;
                SEC : Byte;
                Frame : Byte;
                Zero : Byte;
                AMIN : Byte;
                ASEc : Byte;
                AFRAME : Byte;
              end;

      DiskInfo = record          {DI}        {uses IOCTLI header}
                   CtlCode : Byte;  {10}
                   LowTrack : Byte;
                   HiTrack : Byte;
                   StrtSect : Longint;
                 end;

      TrackInfo = record        {TI}         {uses IOCTLI header}
                    CtlCode : Byte;
                    TrackNum : Byte;
                    TStart : Longint;
                    TCI : Byte;
                  end;

      Eject = record           {EJ}          {uses IOCTLO header}
                CtlCode : Byte; {0}
              end;

      RstDrive = record
                   CtlCode : Byte;
                 end;
      MedType = record
                  CtlCode : Byte;
                  MediaByte : Byte;
                end;
      OneTime = record
                  Minute : Byte;
                  Second : Byte;
                  Frame  : Byte;
                end;
      RedBookFormat = Record
                        Frame  : Byte;
                        Second : Byte;
                        Minute : Byte;
                        Unused : Byte;
                      End;
      AudioTrackType = Array[0..1,0..255] Of RedBookFormat;

Var      {I think some of these vars are unused ;) }
    StdHdr : Std;
    Play : Playreq;
    QI : Qinfo;
    DI : DiskInfo;
    TI : TrackInfo;
    EJ : Eject;
    Rst : RstDrive;
    Media : MedType;
    {---Universal variables---}
    TStart : Array[1..64] of OneTime;
    TLength : Array[1..64] of OneTime;
    StdOfs : Word;
    CDNum : Word;
    NumDrives : Word;       {number of CD-ROM devices found}
    TrNum : Byte;
    DrvList : Array[1..26] of byte; {list of CD-ROM devices}
    Tracks : AudioTrackType;

Function LongMul(X,Y : Integer) : Longint; Assembler;
Asm
  mov ax,X
  imul Y
End;

Procedure SendDDR;
{Sends a device driver request. In this case to the CD-ROM driver}
Begin
  asm
    push ds
    mov ax,ds
    mov es,ax
    mov ax,1510h
    mov bx,StdOfs
    mov cx,CDNum
    Int 2fh
    pop ds
  end;
end; {sendDDR}

Function IsInstalled : Boolean;
Begin
  asm
    mov ax,1500h
    int 2fh
    mov numdrives,bx
  end;
  If Numdrives = 0 then IsInstalled := False
    else IsInstalled := True;
End; {IsInstalled func}

Function MediaChanged : Byte;
Begin
  FillChar(StdHdr,SizeOf(StdHdr),0);
  StdHdr.Length := SizeOf(StdHdr);
  StdHdr.CmdCode := 3;
  StdHdr.XFerAddr := Addr(Media);
  StdHdr.XFerBytes := 2;
  Media.CtlCode := 9;
  SendDDR;
  MediaChanged := Media.MediaByte;
end;

Procedure GetDrvLetrs;
{Gets the list of CD-ROM devices in the system. It returns a number for
each drive. For example, if drive 'D' is a CD-ROM and the only one in the
system, DrvList[1] would equal 3. Remember: A=0,B=1,C=2,D=3. DrvList[x]
can be used to play any cd device. DrvList[2] would be the second device,etc.}
Begin
  asm
    mov ax,ds
    mov es,ax
    mov ax,150dh
    mov bx,offset drvlist
    int 2fh
  end;
end; {getdrvletrs proc}

Procedure GetDriverName(CDROMNumber:Byte;Var Name:String);
 { Also from Kristian Leonhard. Interesting procedure. }
 { CDROMNumber = 1 .. 26 }
 { 1 means first cdrom number }
 Var
  Data : Array[0..129] Of Byte;
  Offs : Word;
  Segm : Word;
  DSeg : Word;
  DOfs : Word;
  Z    : Word;
 Begin
  Offs:=Ofs(Data);
  Segm:=Seg(Data);
  Asm
   mov  ax,segm
   mov  es,ax
   mov  bx,offs
   mov  ax,1501h
   int  2fh
  End;
  DSeg:=MemW[segm:(Offs+3)];
  DOfs:=MemW[segm:(Offs+1)]+((CDROMNumber-1)*5);
  Z:=0;
  While (Mem[DSeg:DOfs+Z]<>$20) And (Z<7) Do
  Begin
   Inc(Z);
   Mem[Seg(Name):Ofs(Name)]:=Z;
   Mem[Seg(Name):Ofs(Name)+Z]:=Mem[DSeg:(DOfs+9+Z)];
  End;
  Inc(Z);
  Mem[Seg(Name):Ofs(Name)]:=Z;
  Mem[Seg(Name):Ofs(Name)+Z]:=0;
End; {getdrivername proc}

Procedure GetAudioTrackInfo(Handle:Word;Var AudioTrack:AudioTrackType);
{Thanks to Kristian Leonhard for this procedure. Sure beats my old
 one!                                                             }
Label
 Error,Exit,Error2,Exit2,Error3,Exit3;
Var
 Buffer : Array[0..1024] Of Byte;
 BufSeg : Word;
 BufOfs : Word;
 Err    : Boolean;
 Z      : Byte;
 Max,Min: Byte;

Procedure WriteTimeFromSec(Sec:Word);
Begin
 Writeln((Sec div 60),':',Sec-((Sec div 60)*60));
End;

Begin
 BufSeg:=Seg(Buffer);
 BufOfs:=Ofs(Buffer);
 { Get StartTrack & EndTrack }
 Asm
          push ds
          mov  ax,BufSeg
          mov  ds,ax
          mov  dx,BufOfs
          mov  bx,dx
          mov  al,0ah
          mov  ds:[bx],al
          mov  bx,Handle
          mov  cx,7
          mov  ax,4402h
          int  21h
          jc   Error
          mov  al,0h
          mov  Err,al
          jmp  Exit
 Error  : mov  al,1h
          mov  Err,al
          jmp  Exit
 Exit   : pop  ds
 End;
 If Not Err Then
 Begin
  Max:=Buffer[2];
  Min:=Buffer[1];
  AudioTrack[1][Max].Frame:=Buffer[3];
  AudioTrack[1][Max].Second:=Buffer[4];
  AudioTrack[1][Max].Minute:=Buffer[5];
  { Get Rest of audio info }
  For Z:=Min To Max Do
  Begin
   Asm
          push ds
          mov  ax,BufSeg
          mov  ds,ax
          mov  dx,BufOfs
          mov  bx,dx
          mov  al,0bh
          mov  ds:[bx],al
          mov  al,z
          mov  ds:[bx+1],al
          mov  bx,Handle
          mov  cx,7
          mov  ax,4402h
          int  21h
          jc   Error
          mov  al,0h
          mov  Err,al
          jmp  Exit2
 Error2 : mov  al,1h
          mov  Err,al
          jmp  Exit2
 Exit2  : pop  ds
  End;
  AudioTrack[0][Z].Frame:=Buffer[2];
  AudioTrack[0][Z].Second:=Buffer[3];
  AudioTrack[0][Z].Minute:=Buffer[4];
 End;
For Z:=Min+1 To Max Do
  Begin
   Asm
          push ds
          mov  ax,BufSeg
          mov  ds,ax
          mov  dx,BufOfs
          mov  bx,dx
          mov  al,0bh
          mov  ds:[bx],al
          mov  al,z
          mov  ds:[bx+1],al
          mov  bx,Handle
          mov  cx,7
          mov  ax,4402h
          int  21h
          jc   Error
          mov  al,0h
          mov  Err,al
          jmp  Exit3
 Error3 : mov  al,1h
          mov  Err,al
          jmp  Exit3
 Exit3  : pop  ds
  End;
  AudioTrack[1][Z-1].Frame:=Buffer[2];
  AudioTrack[1][Z-1].Second:=Buffer[3];
  AudioTrack[1][Z-1].Minute:=Buffer[4];
  End;
  Writeln('AudioCD Info : ');
  Writeln('Start       End       Total');
  For Z:=Min To Max Do
  Begin
    Write(AudioTrack[0][Z].Minute,':',AudioTrack[0][Z].Second,'         ',
          AudioTrack[1][Z].Minute,':',AudioTrack[1][Z].Second,'      ');
    WriteTimeFromSec(((AudioTrack[1][Z].Minute-AudioTrack[0][Z].Minute)
                  *60)+(AudioTrack[1][Z].Second-AudioTrack[0][Z].Second));
  End;
 End
 Else
 Begin
  Writeln('Error reading audio info');
 End;
 Writeln('Press a key');
 Readkey;
End; {getaudiotrackinfo proc}

Procedure SubtractTime(Min1,Sec1,F1,Min2,Sec2,F2 : Byte;Var RMin,RSec,RF :
Byte);{Subtracts Min1,Sec1 from Min2,Sec2  (Min2-Min1,etc)}
{Places answers in RMin and RSec}
Var D : Real;
    S1,S2,R : Word;
Begin
  {Check to see if F2 > F1. If not, subtract one from S2, add 75 to F2,
   subtract and place result. then convert rest to packed}
  If F2-F1 < 0 then begin
    S2 := S2 - 1;
    F2 := F2 + 75;
    RF := F2-F1;
  end else RF := F2-F1;      {recent addition -- check}
  {convert to packed}
  S1 := Min1*60+Sec1;
  S2 := Min2*60+Sec2;
  {do subtraction}
  R := S2-S1;
  {Is R > 60, if so then divide r by 60 to find RMin}
  {mult. frac(r) by 60 to fin rsec}
  If R >= 60 then begin
    D := R / 60;
    RMin := Trunc(D);
    D := Frac(D) * 60;
    RSec := Trunc(D);
  end
  else begin
    RMin := 0;
    RSec := R;
  End;
End; {SubtractTime}

Procedure EjectCd;
Begin
  StdHdr.Length := SizeOf(StdHdr);
  StdHdr.Subcode := 0;
  StdHdr.CmdCode := 12;
  StdHdr.MedDesc := 0;
  StdHdr.XFerAddr := ADDR(EJ);
  StdHdr.XFerBytes := 1;
  StdHdr.SSNum := 0;
  StdHdr.ReqPtr := 0;
  EJ.CtlCode := 0;
  SendDDR;
End; {ejectcd proc}

Procedure ResetCD;
Begin
  StdHdr.Length := SizeOf(StdHdr);
  StdHdr.Subcode := 0;
  StdHdr.CmdCode := 12;
  StdHdr.MedDesc := 0;
  StdHdr.XFerAddr := ADDR(RST);
  StdHdr.XFerBytes := 1;
  StdHdr.SSNum := 0;
  StdHdr.ReqPtr := 0;
  Rst.CtlCode := 2;
  SendDDR;
End; {resetcd proc}

Procedure GetDiskInfo;
Begin
  StdHdr.Length := SizeOf(StdHdr);
  StdHdr.SubCode := 0;
  StdHdr.CmdCode := 3;
  StdHdr.MedDesc := 0;
  StdHdr.XFerAddr := Addr(DI);
  StdHdr.XFerBytes := 7;
  StdHdr.SSNum := 0;
  Stdhdr.ReqPtr := 0;
  DI.CtlCode := 10;
  SendDDR;
End; {getdiskinfo proc}

Procedure GetTrackInfo(Track : Byte;Var Start : Longint);
Begin
  StdHdr.Length := SizeOf(StdHdr);
  StdHdr.SubCode := 0;
  StdHdr.CmdCode := 3;
  Stdhdr.MedDesc := 0;
  Stdhdr.XFerAddr := Addr(TI);
  StdHdr.XFerBytes := 7;
  StdHdr.SSNum := 0;
  StdHdr.ReqPtr := 0;
  TI.CtlCode := 11;
  TI.TrackNum := Track;
  SendDDR;
  Start := TI.TStart;
End; {GetTrackInfo Proc}

Procedure GetTrackLengths;
Var Loop : Byte;
Begin
  {place track starts in TStart}
  For Loop := DI.LowTrack to DI.HiTrack do Begin
    TStart[Loop].Minute := Tracks[0,Loop].Minute;
    TStart[Loop].Second := Tracks[0,Loop].Second;
    TStart[Loop].Frame := Tracks[0,Loop].Frame;
  end;
  {find lengths of tracks}
  For Loop := DI.LowTrack to DI.HiTrack do

SubtractTime(Tracks[0,Loop].Minute,Tracks[0,Loop].Second,Tracks[0,Loop].Frame,
Tracks[1,Loop].Minute,Tracks[1,Loop].Second,Tracks[1,Loop].Frame,

TLength[Loop].Minute,TLength[Loop].Second,TLength[Loop].Frame);End;
{gettracklengths proc}
Procedure PlayCD(Start,Length : Longint);
Begin
  Play.Length := 13{SizeOf(Play)};
  Play.SubCode := 0;
  Play.CMdCode := 132;
  Play.AddrMode := 0;
  Play.StrtSec := Start;
  Play.NumSect := Length;
  asm
    push ds
    mov ax,ds
    mov es,ax
    mov ax,1510h
    mov bx,offset play
    mov cx,CDNum
    Int 2fh
    pop ds
  end;
End;   {playcd proc}

Procedure PlayTrack(TrackNum : Byte);
Var LMin,LSec,LF,
    SMin,SSec,SF : Byte;
    S,L : Longint;
Begin
  LMin := TLength[TrackNum].Minute;
  LSec := TLength[TrackNum].Second;
  LF := TLength[TrackNum].Frame;
  SMin := TStart[TrackNum].Minute;
  SSec := TStart[Tracknum].Second;
  SF := TStart[TrackNum].Frame;
  L := LongMul(LMin,75*60)+LongMul(LSec,75)+LF-150;
  S := LongMul(SMin,60*75)+LongMul(SSec,75)+SF-150;
  PlayCD(S,L);
End; {playtrack proc}

Procedure PlayTracks(First,Second : Byte);
{Plays everything from the first to the second tracks. Not used.}
Var Strt : OneTime;
    X : Byte;
    LngthTime : Longint;
    L,S : Longint;
Begin
  LngthTime := 0;
  Strt := TStart[First];
  For X := First to Second do
    LngthTime := LngthTime +
LongMul(TLength[X].Minute,75*60)+LongMul(TLength[X].Second,75)+
TLength[X].Frame-150;  L := LngthTime;
  S := LongMul(Strt.Minute,60*75)+LongMul(Strt.Second,75)+Strt.Frame-150;
  PlayCD(S,L);
End; {playtracks proc}

Procedure GetAudioInfo;
Var CDName : String;
    CDHandle : Word;
    CDFile : File;
Begin
  GetDriverName(1,CDName);
  Assign(CDFile,CDName);
  Reset(CDFile,1);
  CDHandle := MemW[SSeg:Ofs(CDFile)];
  GetAudioTrackInfo(CDHandle,Tracks);
  Close(CDFile);
End; {getaudioinfo}

Begin
  {--Set some universal constants--}
  StdOfs := Ofs(StdHdr);
  If IsInstalled then begin
    GetDrvLetrs;
    CDNum := DrvList[1];
  End
  else Begin
    Writeln('MSCDEX and/or CD-ROM not detected! Aborting');
    Halt(1);
  End;
  {--reset drive then play a track--}
  ResetCD;  {My CD won't play music CDs without a reset first. Driver prob.}
  MediaChanged;  {have to run it once to get a correct answer the next time}
  GetAudioInfo;  {  not that I use it here again ;)}
  GetDiskInfo;
  GetTrackLengths;
  {$I-}
  Repeat
    Writeln('Enter track number to play (',DI.LowTrack,'-',DI.HiTrack,'):');
    Readln(TrNum);
  Until (IOResult = 0) AND (TrNum <= DI.HiTrack) AND (TrNum >= DI.LowTrack);
  {$I+}
  PlayTrack(TrNum);
End.

A word about CDNum: CDNum is the number of the drive where A=0,B=1,C=2,D=3.
On my system the CD is drive D so CDNum would be 3. HOWEVER: In the
GetDriverName procedure, CDROMNumber is NOT the same. It is the number of
the CDROM drive you want to play. That is, if you have more than 1 CDROM.
So if you have 2 CDROM drives, D&E, then CDROMNumber = 1 would activate
drive D and CDROMNumber=2 would activate E. Simple. Keep it on 1 and
things _should_ go OK.

Pat O'Malley AKA Silicon Slim

[Back to SOUND SWAG index]  [Back to Main SWAG index]  [Original]