[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]