[Back to SOUND SWAG index] [Back to Main SWAG index] [Original]
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.
[Back to SOUND SWAG index] [Back to Main SWAG index] [Original]