UNIT VOCTOOL;
{* Unit - uses CT-VOICE.DRV. *}
INTERFACE
TYPE
   VOCFileTyp = File;
CONST
   VOCToolVersion  = 'v1.5';
   VOCBreakEnd     = 0;
   VOCBreakNow     = 1;
VAR
   VOCStatusWord        : WORD;
   VOCErrStat           : WORD;
   VOCFileHeader        : STRING;
   VOCFileHeaderLength  : BYTE;
   VOCPaused            : BOOLEAN;
   VOCDriverInstalled   : BOOLEAN;
   VOCDriverVersion     : WORD;
   VOCPtrToDriver       : Pointer;
   OldExitProc          : Pointer;
PROCEDURE PrintVOCErrMessage;
FUNCTION  VOCGetBuffer(VAR VoiceBuff : Pointer; Voicefile : STRING):BOOLEAN;
FUNCTION  VOCFreeBuffer(VAR VoiceBuff : Pointer):BOOLEAN;
FUNCTION  VOCGetVersion:WORD;
PROCEDURE VOCSetPort(PortNumber : WORD);
PROCEDURE VOCSetIRQ(IRQNumber : WORD);
FUNCTION  VOCInitDriver:BOOLEAN;
PROCEDURE VOCDeInstallDriver;
PROCEDURE VOCSetSpeaker(OnOff:BOOLEAN);
PROCEDURE VOCOutput(BufferAddress : Pointer);
PROCEDURE VOCOutputLoop (BufferAddress : Pointer);
PROCEDURE VOCStop;
PROCEDURE VOCPause;
PROCEDURE VOCContinue;
PROCEDURE VOCBreakLoop(BreakMode : WORD);
IMPLEMENTATION
USES DOS,Crt;
TYPE
   TypeCastType = ARRAY [0..6000] of Char;
VAR
   Regs : Registers;
PROCEDURE PrintVOCErrMessage;
{* INPUT   : None
 * OUTPUT  : None
 * PURPOSE : Displays SB error as text; no change to error status. }
BEGIN
   CASE VOCErrStat OF
      100 : Write(' Driver file CT-VOICE.DRV not found ');
      110 : Write(' No memory available for driver file ');
      120 : Write(' False driver file ');
      200 : Write(' VOC file not found ');
      210 : Write(' No memory available for driver file ');
      220 : Write(' File not in VOC format ');
      300 : Write(' Memory allocation error occurred ');
      400 : Write(' No sound blaster card found ');
      410 : Write(' False port address used ');
      420 : Write(' False interrupt used ');
      500 : Write(' No loop in process ');
      510 : Write(' No sample for output ');
      520 : Write(' No sample available ');
      END;
   END;

FUNCTION Exists (Filename : STRING):BOOLEAN;
{* INPUT   : Filename as string
 * OUTPUT  : TRUE if file is available, FALSE if not
 * PURPOSE : Checks for availability of file then returns Boolean exp. }
VAR
   F : File;
BEGIN
   Assign(F,Filename);
{$I-}
   Reset(F);
   Close(F);
{$I+}
   Exists := (IoResult = 0) AND (Filename <> '');
   END;
PROCEDURE AllocateMem (VAR Pt : Pointer; Size : LongInt);
{* INPUT   : Buffer variable as pointer, buffer size as LongInt
 * OUTPUT  : Pointer to buffer in variable or NIL
 * PURPOSE : Reserves as many bytes as Size allows, then moves pointer in
             the Pt variable. If not enough memory is available, Pt = NIL. }
VAR
   SizeIntern : WORD;
BEGIN
   Inc(Size,15);
   SizeIntern := (Size shr 4);
   Regs.AH := $48;
   Regs.BX := SizeIntern;
   MsDos(Regs);
   IF (Regs.BX <> SizeIntern) THEN Pt := NIL
   ELSE Pt := Ptr(Regs.AX,0);
   END;
FUNCTION  CheckFreeMem (VAR VoiceBuff : Pointer; VoiceSize : LongInt):BOOLEAN;
{* INPUT   : Buffer variable as pointer, size as LongInt
 * OUTPUT  : Pointer to buffer, TRUE/FALSE, after AllocateMem
 * PURPOSE : Checks for sufficient memory to store a VOC file. }
BEGIN
   AllocateMem(VoiceBuff,VoiceSize);
   CheckFreeMem := VoiceBuff <> NIL;
   END;
FUNCTION  VOCGetBuffer (VAR VoiceBuff : Pointer; Voicefile : STRING):BOOLEAN;
{* INPUT   : Buffer variable as pointer, file name as string
 * OUTPUT  : Pointer to buffer with VOC data, TRUE/FALSE
 * PURPOSE : Loads a file into memory and returns TRUE if file loaded
             successfully, and FALSE if not. }
VAR
   SampleSize : LongInt;
   FPresent   : BOOLEAN;
   VFile      : VOCFileTyp;
   Segs       : WORD;
   Read       : WORD;
BEGIN
   FPresent := Exists(VoiceFile);
{ VOC file not found }
   IF Not(FPresent) THEN BEGIN
      VOCGetBuffer := FALSE;
      VOCErrStat   := 200;
      EXIT
      END;
   Assign(VFile,Voicefile);
   Reset(VFile,1);
   SampleSize := Filesize(VFile);
   AllocateMem(VoiceBuff,SampleSize);
{ Insufficient memory for the VOC file }
   IF (VoiceBuff = NIL) THEN BEGIN
      Close(VFile);
      VOCGetBuffer := FALSE;
      VOCErrStat   := 210;
      EXIT;
      END;
   Segs := 0;
   REPEAT
      Blockread(VFile,Ptr(seg(VoiceBuff^)+4096*Segs,Ofs(VoiceBuff^))^,$FFFF,Read
);
      Inc(Segs);
      UNTIL Read = 0;
   Close(VFile);
{ File not in VOC format }
   IF (TypeCastType(VoiceBuff^)[0]<>'C') OR
      (TypeCastType(VoiceBuff^)[1]<>'r') THEN BEGIN
      VOCGetBuffer := FALSE;
      VOCErrStat := 220;
      EXIT;
      END;
{ Load successful }
   VOCGetBuffer := TRUE;
   VOCErrStat   := 0;
{ Read header length from file }
   VOCFileHeaderLength := Ord(TypeCastType(VoiceBuff^)[20]);
   END;
FUNCTION VOCFreeBuffer (VAR VoiceBuff : Pointer):BOOLEAN;
{* INPUT   : Buffer pointer
 * OUTPUT  : None
 * PURPOSE : Frees memory allocated for VOC data. }
BEGIN
   Regs.AH := $49;
   Regs.ES := seg(VoiceBuff^);
   MsDos(Regs);
   VOCFreeBuffer := TRUE;
   IF (Regs.AX = 7) OR (Regs.AX = 9) THEN BEGIN
      VOCFreeBuffer := FALSE;
      VOCErrStat := 300
      END;
   END;
FUNCTION VOCGetVersion:WORD;
{* INPUT   : None
 * OUTPUT  : Driver version number
 * PURPOSE : Returns driver version number. }
VAR
   VDummy : WORD;
BEGIN
   ASM
      MOV       BX,0
      CALL      VOCPtrToDriver
      MOV       VDummy, AX
      END;
   VOCGetVersion := VDummy;
   END;

PROCEDURE VOCSetPort(PortNumber : WORD);
{* INPUT   : Port address number
 * OUTPUT  : None
 * PURPOSE : Specifies port address before initialization. }
BEGIN
   ASM
      MOV    BX,1
      MOV    AX,PortNumber
      CALL   VOCPtrToDriver
      END;
   END;
PROCEDURE VOCSetIRQ(IRQNumber : WORD);
{* INPUT   : Interrupt number
 * OUTPUT  : None
 * PURPOSE : Specifies interrupt number before initialization.}
BEGIN
   ASM
      MOV    BX,2
      MOV    AX,IRQNumber
      CALL   VOCPtrToDriver
      END;
   END;
FUNCTION  VOCInitDriver: BOOLEAN;
{* INPUT   : None
 * OUTPUT  : Error message number, and initialization result
 * PURPOSE : Initializes driver software. }
VAR
   Out, VSeg, VOfs : WORD;
   F   : File;
   Drivername,
   Pdir        : DirStr;
   Pnam        : NameStr;
   Pext        : ExtStr;
BEGIN
{ Search path for CT-VOICE.DRV driver }
   Pdir := ParamStr(0);
   Fsplit(ParamStr(0),Pdir,Pnam,Pext);
   Drivername := Pdir+'CT-VOICE.DRV';
   VOCInitDriver := TRUE;
{ Driver file not found }
   IF Not Exists(Drivername) THEN BEGIN
      VOCInitDriver := FALSE;
      VOCErrStat    := 100;
      EXIT;
      END;
{ Load driver }
   Assign(F,Drivername);
   Reset(F,1);
   AllocateMem(VOCPtrToDriver,Filesize(F));
{ No memory can be allocated for the driver }
   IF VOCPtrToDriver = NIL THEN BEGIN
      VOCInitDriver := FALSE;
      VOCErrStat    := 110;
      EXIT;
      END;
   Blockread(F,VOCPtrToDriver^,Filesize(F));
   Close(F);
{ Driver file doesn't begin with "CT" - false driver }
   IF (TypeCastType(VOCPtrToDriver^)[3]<>'C') OR
      (TypeCastType(VOCPtrToDriver^)[4]<>'T') THEN BEGIN
         VOCInitDriver := FALSE;
         VOCErrStat    := 120;
         EXIT;
         END;
{ Get version number and pass to global variable }
   VOCDriverVersion := VOCGetVersion;
{ Start driver }
   Vseg := Seg(VOCStatusWord);
   VOfs := Ofs(VOCStatusWord);
   ASM
      MOV       BX,3
      CALL      VOCPtrToDriver
      MOV       Out,AX
      MOV       BX,5
      MOV       ES,VSeg
      MOV       DI,VOfs
      CALL      VOCPtrToDriver
      END;
{ No Sound Blaster card found }
   IF Out = 1 THEN BEGIN
      VOCInitDriver := FALSE;
      VOCErrStat    := 400;
      EXIT;
      END;
{ False port address used }
   IF Out = 2 THEN BEGIN
      VOCInitDriver := FALSE;
      VOCErrStat    := 410;
      EXIT;
      END;
{ False interrupt used }
   IF Out = 3 THEN BEGIN
      VOCInitDriver := FALSE;
      VOCErrStat    := 420;
      EXIT;
      END;
   END;
PROCEDURE VOCDeInstallDriver;
{* INPUT   : None
 * OUTPUT  : None
 * PURPOSE : Disables driver and releases memory. }
VAR
   Check : BOOLEAN;
BEGIN
   IF VOCDriverInstalled THEN
   ASM
      MOV       BX,9
      CALL      VOCPtrToDriver
      END;
   Check := VOCFreeBuffer(VOCPtrToDriver);
   END;
PROCEDURE VOCSetSpeaker(OnOff:BOOLEAN);
{* INPUT   : TRUE=Speaker on, FALSE=Speaker off
 * OUTPUT  : None
 * PURPOSE : Sound Blaster output status. }
VAR
   Switch : BYTE;
BEGIN
   Switch := Ord(OnOff) AND $01;
   ASM
      MOV       BX,4
      MOV       AL,Switch
      CALL      VOCPtrToDriver
      END;
   END;
PROCEDURE VOCOutput (BufferAddress : Pointer);
{* INPUT   : Pointer to sample data
 * OUTPUT  : None
 * PURPOSE : Plays sample. }
VAR
   VSeg, VOfs : WORD;
BEGIN
   VOCSetSpeaker(TRUE);
   VSeg := Seg(BufferAddress^);
   VOfs := Ofs(BufferAddress^)+VOCFileHeaderLength;
   ASM
      MOV       BX,6
      MOV       ES,VSeg
      MOV       DI,VOfs
      CALL      VOCPtrToDriver
      END;
   END;
PROCEDURE VOCOutputLoop (BufferAddress : Pointer);
{*    Different from VOCOutput :
 *    Speaker does not switch on with every sample output, so a
 *    crackling noise may occur with some Sound Blaster cards. }
VAR
   VSeg, VOfs : WORD;
BEGIN
   VSeg := Seg(BufferAddress^);
   VOfs := Ofs(BufferAddress^)+VOCFileHeaderLength;
   ASM
      MOV       BX,6
      MOV       ES,VSeg
      MOV       DI,VOfs
      CALL      VOCPtrToDriver
      END;
   END;
PROCEDURE VOCStop;
{* INPUT   : None
 * OUTPUT  : None
 * PURPOSE : Stops a sample. }
BEGIN
   ASM
      MOV       BX,8
      CALL      VOCPtrToDriver
      END;
   END;
PROCEDURE VOCPause;
{* INPUT   : None
 * OUTPUT  : None
 * PURPOSE : Pauses a sample. }
VAR
   Switch : WORD;
BEGIN
   VOCPaused := TRUE;
   ASM
      MOV       BX,10
      CALL      VOCPtrToDriver
      MOV       Switch,AX
      END;
   IF (Switch = 1) THEN BEGIN
      VOCPaused := FALSE;
      VOCErrStat := 510;
      END;
   END;
PROCEDURE VOCContinue;
{* INPUT   : None
 * OUTPUT  : None
 * PURPOSE : Continues a paused sample. }
VAR
   Switch : WORD;
BEGIN
   ASM
      MOV       BX,11
      CALL      VOCPtrToDriver
      MOV       Switch,AX
      END;
   IF (Switch = 1) THEN BEGIN
      VOCPaused := FALSE;
      VOCErrStat := 520;
      END;
   END;
PROCEDURE VOCBreakLoop(BreakMode : WORD);
{* INPUT   : Break mode
 * OUTPUT  : None
 * PURPOSE : Breaks a sample loop. }
BEGIN
   ASM
      MOV       BX,12
      MOV       AX,BreakMode
      CALL      VOCPtrToDriver
      MOV       BreakMode,AX
      END;
   IF (BreakMode = 1) THEN VOCErrStat := 500;
   END;
{$F+}
PROCEDURE VoiceToolsExitProc;
{$F-}
{* INPUT   : None
 * OUTPUT  : None
 * PURPOSE : De-installs voice driver. }
BEGIN
   VOCDeInstallDriver;
   ExitProc := OldExitProc;
   END;
BEGIN
{* The following statements execute automatically, as soon as the
 * unit is linked to a program, and the program starts. }
{ Replaces old ExitProc with new one from Tool unit }
   OldExitProc := ExitProc;
   ExitProc := @VoiceToolsExitProc;
{ Initialize values }
   VOCStatusWord := 0;
   VOCErrStat    := 0;
   VOCPaused     := FALSE;
   VOCFileHeaderLength := $1A;
   VOCFileHeader :=
      'Creative Voice File'+#$1A+#$1A+#$00+#$0A+#$01+#$29+#$11+#$01;
{* After installation, VOCDriverInstalled contains either TRUE or FALSE. }
   VOCDriverInstalled := VOCInitDriver;
   END.


{    -----------------------    DEMO PROGRAM  --------------------------}

PROGRAM VToolTest;
{* VTTEST.PAS - uses VOCTOOL.TPU *}

{$M 16000,0,50000}
USES Crt,Voctool;
VAR
   Sound : Pointer;
   Check : BOOLEAN;
   Ch    : CHAR;
PROCEDURE TextNumError;
{* INPUT   : None; data comes from the VOCErrStat global variable
 * OUTPUT  : None
 * PURPOSE : Displays SB error on the screen as text, including the
             error number. Program then ends at the error level
             corresponding to the error number. }
BEGIN
   Write(' Error #',VOCErrStat:3,' =');
   PrintVOCErrMessage;
   WriteLn;
   HALT(VOCErrStat);
   END;

BEGIN
  ClrScr;

{ Driver not initialized }
  IF Not(VOCDriverInstalled) THEN TextNumError;
{ Loads DEMO.VOC file into memory }
  Check := VOCGetBuffer(Sound,'\SBPRO\MMPLAY\SBP.VOC');
{ VOC file could not be loaded }
  IF Not(Check) THEN TextNumError;
{ Main loop }
  Write('CT-Voice Driver Version : ');
  WriteLn(Hi(VOCDriverVersion),'.',Lo(VOCDriverVersion));
  WriteLn('(S)ingle play or (M)ultiple play?');
  Write('Press a key : '); Ch := ReadKey;WriteLn;WriteLn;
  CASE UpCase(Ch) OF
   'S' : BEGIN
            Write('Press a key to stop the sound...');
            VOCOutput(Sound);
            REPEAT UNTIL KeyPressed OR (VOCStatusWord = 0);
            IF KeyPressed THEN VOCStop;
            END;
   'M' : BEGIN
            Ch := #0;
            Write('Press <ESC> to cancel...');
            REPEAT
               VOCOutputLoop(Sound);
               REPEAT UNTIL KeyPressed OR (VOCStatusWord = 0);
               IF KeyPressed THEN Ch := ReadKey;
               UNTIL Ch = #27;
            VOCStop;
            END;
   END;
{ Free VOC file memory }
  Check := VOCFreeBuffer(Sound);
  IF Not(Check) THEN TextNumError;
  END.
