UNIT CDAUDIO; { by Eric Miller, Jan. 15, 1996 } { public domain } INTERFACE Uses Objects; CONST cdPlaying = 1; { drive is playing audio } cdDisc = 2; { disc is in drive } cdDoor = 4; { drive door is closed } TYPE TRedBook = RECORD Frame, Second, Minute, Unused: Byte; END; TTrack = RECORD Start, Finish: TRedBook; END; TDeviceHeader = RECORD NextDriver: Pointer; Attribute, StratEntry, IntEntry: Word; Name: array[0..7] of Char; Res: Word; Letter, Units: Byte; END; TDevice = RECORD Subunit: Byte; Header: Pointer; END; TRequestHeader = RECORD Length, Subunit, Command: Byte; Status: Word; Res: array[0..7] of Byte; END; TPlayRequest = RECORD Header: TRequestHeader; Addressing: Byte; Start, Length: LongInt; END; TIOCTLRequest = RECORD Header: TRequestHeader; Media: Byte; Address: Pointer; Length, Start: Word; Res: Pointer; END; TQChannel = RECORD Command, CTRL_ADR, Track, Point: Byte; Min, Sec, Frame, Zero, AMin, ASec, AFrame: Byte; END; TDeviceStatus = RECORD Command: Byte; Status: LongInt; END; TCommand = RECORD Command: Byte; END; TDiskInfo = RECORD Command: Byte; LowTrack, HighTrack: Byte; LeadOut: TRedBook; END; TTrackInfo = RECORD Command, Track: Byte; Start: TRedBook; Control: Byte; END; TYPE TCD = OBJECT Drive, { CD-ROM drive } Letter: Byte; { drive letter } Header: ^TDeviceHeader; Subunit: Byte; Name: String[8]; { name of driver } Handle: Word; LowTrack, HighTrack, CurTrack: Byte; cMin, cSec, cFrame, { track time } tMin, tSec, tFrame, { track length } dMin, dSec, dFrame: Byte; { disc time } Tracks: array[1..64] of TTrack; { track info } Status: Word; { drive status } PROCEDURE Eject; PROCEDURE Retract; FUNCTION State(AState: Word): Boolean; { test Status field } PROCEDURE GetStatus; { set Status field } PROCEDURE Error(Code: Word); PROCEDURE Stop; FUNCTION RedToHSG(Redbook: TRedbook): LongInt; PROCEDURE HSGToRed(HSG: Longint; VAR Redbook: TRedbook); FUNCTION Time(M, S, F: Byte): String; { Redbook > 'mm:ss:ff' } FUNCTION Drives: Byte; { # of CD-ROM drives } FUNCTION Version: Word; { MSCDEX version } FUNCTION MediaChanged: Byte; { disc ready/different/etc } PROCEDURE PlayTrack(Track: Byte); PROCEDURE GetPosition; { get play position } PROCEDURE GetTracks; { get track info } PROCEDURE GetHandle; PROCEDURE GetSubUnit; PROCEDURE GetDriveLetter; PROCEDURE GetDriverName; FUNCTION Init: Boolean; PROCEDURE Done; END; IMPLEMENTATION PROCEDURE TCD.Done; BEGIN Drive := 0; Header := NIL; Name := ''; LowTrack := 0; HighTrack := 0; Status := 0; END; FUNCTION TCD.State(AState: Word): Boolean; BEGIN State := Status AND AState = AState; END; PROCEDURE TCD.Error(Code: Word); BEGIN IF Status AND $8000 > 0 THEN BEGIN Write('ERROR - (', Status AND $F, ') '); CASE (Status AND $F) of 0: Writeln('Write-protect violation'); 1: Writeln('Unknown unit'); 2: Writeln('Drive not ready'); 3: Writeln('Unknown command'); 4: Writeln('CRC error'); 5: Writeln('Bad drive request structure length'); 6: Writeln('Seek error'); 7: Writeln('Unknown media'); 8: Writeln('Sector not found'); 9: Writeln('Printer out of paper'); 10: Writeln('Write fault'); 11: Writeln('Read fault'); 12: Writeln('General failure'); 13..14: Writeln('Reserved error'); 15: Writeln('Invalid disk change'); END; END; END; FUNCTION TCD.Init: Boolean; BEGIN IF Drives > 0 THEN BEGIN Init := True; Drive := 1; GetDriveLetter; GetSubunit; GetDriverName; GetHandle; MediaChanged; GetPosition; GetStatus; END ELSE Init := False; END; PROCEDURE TCD.Eject; VAR E: TCommand; IOCTLO: TIOCTLRequest; Offs, Segm: Word; ALetter: Byte; BEGIN ALetter := Letter; Segm := Seg(IOCTLO); Offs := Ofs(IOCTLO); WITH IOCTLO DO BEGIN Media := 0; Address := @E; Length := SizeOf(E); Start := 0; Res := NIL; WITH Header DO BEGIN Length := 26; Command := 12; END; Header.Subunit := Subunit; END; E.Command := $0; ASM mov ax,Segm mov es,ax mov bx,Offs mov cl,ALetter mov ax,1510h int 2fh END; END; PROCEDURE TCD.Retract; VAR R: TCommand; IOCTLO: TIOCTLRequest; Offs, Segm: Word; ALetter: Byte; BEGIN ALetter := Letter; Segm := Seg(IOCTLO); Offs := Ofs(IOCTLO); WITH IOCTLO DO BEGIN Media := 0; Address := @R; Length := SizeOf(R); Start := 0; Res := NIL; WITH Header DO BEGIN Length := 26; Command := 12; END; Header.Subunit := Subunit; END; R.Command := $5; ASM mov ax,Segm mov es,ax mov bx,Offs mov cl,ALetter mov ax,1510h int 2fh END; END; PROCEDURE TCD.Stop; VAR Request: TRequestHeader; Segm, Offs: Word; ALetter: Byte; BEGIN ALetter := Letter; Segm := Seg(Request); Offs := Ofs(Request); WITH Request DO BEGIN Length := 5; Command := $85; END; Request.Subunit := Subunit; ASM push ds mov ax,Segm mov es,ax mov bx,Offs xor cx,cx mov cl,ALetter mov ax,1510h int 2fh pop ds END END; PROCEDURE TCD.GetStatus; VAR ALetter: Byte; DeviceStatus: TDeviceStatus; IOCTLI: TIOCTLRequest; Segm, Offs: Word; BEGIN ALetter := Letter; Segm := Seg(IOCTLI); Offs := Ofs(IOCTLI); WITH IOCTLI DO BEGIN Media := 0; Address := @DeviceStatus; Length := SizeOf(DeviceStatus); Start := 0; Res := NIL; WITH Header DO BEGIN Length := 26; Command := 3; END; Header.Subunit := Subunit; END; DeviceStatus.Command := $6; ASM mov ax,Segm mov es,ax mov bx,Offs mov cl,ALetter mov ax,1510h int 2fh END; Status := 0; IF IOCTLI.Header.Status AND $200 > 0 THEN Inc(Status, cdPlaying); IF NOT (DeviceStatus.Status AND $800 > 0) THEN Inc(Status, cdDisc); IF DeviceStatus.Status AND 1 = 0 THEN Inc(Status, cdDoor); END; FUNCTION TCD.Time(M, S, F: Byte): String; VAR St: String; T: LongInt; BEGIN T := (longint(M MOD 100) * 10000) + ((S MOD 60) * 100) + (F MOD 75); Str(T:6, St); IF T > 99 THEN BEGIN Insert(':', St, 5); IF T > 9999 THEN Insert(':', St, 3) ELSE St := Concat(' ', St); END ELSE St := Concat(' ', St); Time := St; END; PROCEDURE TCD.GetPosition; VAR Segm, Offs: Word; QChannel: TQChannel; IOCTLI: TIOCTLRequest; T: Longint; S: String; ALetter: Byte; BEGIN ALetter := Letter; Segm := Seg(IOCTLI); Offs := Ofs(IOCTLI); WITH IOCTLI DO BEGIN Media := 0; Address := @QChannel; Length := SizeOf(QChannel); Start := 0; Res := NIL; WITH Header DO BEGIN Length := 26; Command := 3; END; Header.Subunit := Subunit; END; QChannel.Command := 12; ASM mov ax,Segm mov es,ax mov bx,Offs xor cx,cx mov cl,ALetter mov ax,1510h int 2fh END; WITH QChannel DO BEGIN cMin := Min; cSec := Sec; cFrame := Frame; dMin := AMin; dSec := ASec; dFrame := AFrame; END; END; FUNCTION TCD.RedToHSG(Redbook: TRedBook): LongInt; BEGIN WITH Redbook DO RedToHSG := (longint(Minute) * 4500) + (Second * 75) + Frame - 150; END; PROCEDURE TCD.HSGToRed(HSG: Longint; VAR Redbook: TRedbook); BEGIN Inc(HSG, 150); WITH RedBook DO BEGIN Minute := HSG DIV 4500; Second := HSG DIV 75 MOD 60; Frame := HSG MOD 75; END; END; PROCEDURE TCD.PlayTrack(Track: Byte); VAR Segm, Offs: Word; ADrive: Byte; PlayRequest: TPlayRequest; Red_Length: TRedbook; StartHSG, EndHSG, HSG_Length: Longint; BEGIN CurTrack := Track; StartHSG := RedToHSG(Tracks[Track].Start); EndHSG := RedToHSG(Tracks[Track].Finish); HSG_Length := EndHSG - StartHSG; HSGToRed(HSG_Length, Red_Length); WITH Red_Length DO BEGIN tMin := Minute; tSec := Second; tFrame := Frame; END; Segm := Seg(PlayRequest); Offs := Ofs(PlayRequest); ADrive := Letter; WITH PlayRequest DO BEGIN Header.Length := SizeOf(PlayRequest); Header.Subunit := SubUnit; Header.Command := $84; Addressing := 0; Start := RedToHSG(Tracks[Track].Start); Length := RedToHSG(Tracks[Track].Finish) - RedToHSG(Tracks[Track].Start); END; ASM push ds mov ax,Segm mov es,ax mov bx,Offs xor cx,cx mov cl,ADrive mov ax,1510h int 2fh pop ds END; END; PROCEDURE TCD.GetTracks; VAR DiskInfo: TDiskInfo; TrackInfo: TTrackInfo; Segm, Offs: Word; Z: Byte; AHandle: Word; BEGIN AHandle := Handle; Segm := Seg(DiskInfo); Offs := Ofs(DiskInfo); DiskInfo.Command := $A; ASM push ds mov ax,Segm mov ds,ax mov dx,Offs mov bx,AHandle mov cx,7 mov ax,4402h int 21h pop ds END; HighTrack := DiskInfo.HighTrack; LowTrack := DiskInfo.LowTrack; Segm := Seg(TrackInfo); Offs := Ofs(TrackInfo); TrackInfo.Command := $B; FOR Z := LowTrack TO HighTrack DO BEGIN TrackInfo.Track := Z; ASM push ds mov ax,Segm mov ds,ax mov dx,Offs mov bx,AHandle mov cx,7 mov ax,4402h int 21h pop ds END; Tracks[Z].Start := TrackInfo.Start; END; FOR Z := LowTrack + 1 TO HighTrack DO BEGIN TrackInfo.Track := Z; Tracks[Z - 1].Finish := Tracks[Z].Start; END; Tracks[HighTrack].Finish := DiskInfo.LeadOut; END; FUNCTION TCD.MediaChanged: Byte; VAR AHandle, Segm, Offs: Word; Buffer: array[0..127] of Byte; BEGIN Segm := Seg(Buffer); Offs := Ofs(Buffer); AHandle := Handle; ASM push ds mov ax,Segm mov ds,ax mov dx,Offs mov bx,dx mov al,9h mov ds:[bx],al mov bx,AHandle mov cx,2 mov ax,4402h int 21h pop ds END; MediaChanged := Buffer[1]; END; PROCEDURE TCD.GetHandle; VAR Result, Segm, Offs: Word; BEGIN Segm := Seg(Name); Offs := Succ(Ofs(Name)); ASM push ds mov ax,Segm mov ds,ax mov dx,Offs mov ah,3dh mov al,2h int 21h jc @1 mov Result,ax jmp @2 @1: mov Result,0h @2: pop ds END; Handle := Result; END; PROCEDURE TCD.GetDriverName; BEGIN Name := ''; Move(Header^.Name, Name[1], 8); REPEAT Inc(Name[0]); UNTIL (Length(Name) = 8) OR (Name[Length(Name)] = #32); END; PROCEDURE TCD.GetDriveLetter; VAR DriveLetterList: array[1..26] of Byte; Segm, Offs: Word; BEGIN Segm := Seg(DriveLetterList); Offs := Ofs(DriveLetterList); ASM mov ax,Segm mov es,ax mov bx,Offs mov ax,150Dh int 2fh END; Letter := DriveLetterList[Drive]; END; FUNCTION TCD.Version: Word; assembler; ASM mov ax,150ch int 2fh mov ax,bx END; PROCEDURE TCD.GetSubunit; VAR DeviceList: array[1..26] of TDevice; Segm, Offs: Word; BEGIN Segm := Seg(DeviceList); Offs := Ofs(DeviceList); ASM mov ax,Segm mov es,ax mov bx,Offs mov ax,1501h int 2fh END; Subunit := DeviceList[Drive].Subunit; Header := DeviceList[Drive].Header; END; FUNCTION TCD.Drives: Byte; assembler; ASM mov ax,1500h mov bx,0 int 2fh mov al,bl END; END. { ---------------------- DEMO PROGRAM ------------------ } PROGRAM CDPLAY; { Test program for CDAUDIO unit. Plays audio tracks sequentially until keypressed. } Uses CRT, CDAUDIO; VAR CD: TCD; Track: Byte; Ticker: Longint ABSOLUTE $40:$6c; Tick: Longint; BEGIN IF CD.Init THEN WITH CD DO BEGIN GetTracks; Writeln(CD.HighTrack, ' tracks'); Track := LowTrack; WHILE (Track <= HighTrack) AND NOT Keypressed DO BEGIN PlayTrack(Track); Write('TRACK ', Track, ' LENGTH ', Time(tMin, tSec, tFrame), ' TIME '); REPEAT Tick := Ticker; REPEAT UNTIL Ticker <> Tick; GetPosition; Write(Time(cMin, cSec, cFrame)); GetStatus; Write(#8#8#8#8#8#8#8#8); UNTIL Keypressed OR NOT State(cdPlaying); Writeln; Inc(Track); END; IF State(cdPlaying) THEN Stop; Done; END ELSE Writeln('No CD-ROM drive?!'); END.