(*
   Unit for using Creative Lab's CT-VOICE.DRV with Turbo Pascal.
   By Markus Saukkonen 1991, for public domain.

   For more detailed information about driver, refer to Sound Blaster's
   User Reference Manual.
*)

unit ct_voice;

interface

const
   SPEAKER_ON=1;
   SPEAKER_OFF=0;
   BREAK_IMMEDIATELY=1; (* breakloop, loop breaks immediately *)
   BREAK_AFTER_LOOP=0; (* current round continues but looping stops *)

procedure getversion(var major, minor: byte);
procedure setioaddress(address: word);
procedure setinterrupt(int: word);
function initializedriver:byte;
procedure speaker(status: byte);
procedure setstatusaddress(address: pointer);
procedure playsample(address: pointer);
procedure inputsample(address: pointer; speed: word; length: longint);
procedure stopvoice;
procedure uninstalldriver;
function pausevoice: byte;
function continuevoice: byte;
function breakloop(when: word): byte;
procedure setuserfunction(address: pointer);

Implementation

var
   f: file;
   ctdriver, pt: pointer;
   ax, bx, cx, dx: word;

procedure kutsu;
begin
   inline(
      $A1/ax/           (*  MOV  AX,[ax]  *)
      $8B/$1E/bx/       (*  MOV  BX,[bx]  *)
      $8B/$0E/cx/       (*  MOV  CX,[cx]  *)
      $8B/$16/dx/       (*  MOV  DX,[dx]  *)
      $C4/$3E/pt/       (*  LES  DI,[pt]  *)
      $FF/$1E/CTDriver/ (*  CALL FAR [ctdriver]  *)
      $A3/ax/           (*  MOV  [ax],AX  *)
      $89/$1E/bx/       (*  MOV  [bx],BX  *)
      $89/$0E/cx/       (*  MOV  [cx],CX  *)
      $89/$16/dx);      (*  MOV  [dx],DX  *)
end;

procedure getversion(var major,minor: byte);
(*
   Gets version of the driver.
*)
begin
  bx:=0;
  kutsu;
  major:=hi(ax);
  minor:=lo(ax);
end;

procedure setioaddress(address: word);
(*
   Sets base I/O address. If this needs to be called (address 220h is
   default), it must be done before calling "initializedriver".
*)
begin
  bx := 1;
  ax := address;
  kutsu;
end;

procedure setinterrupt(int: word);
(*
   Sets interrupt number. If this needs to be called (IRQ 7 is default), it
   must be done before calling "initializedriver".
*)
begin
  bx := 2;
  ax := int;
  kutsu;
end;

function initializedriver: byte;
(*
   Initializes driver. Returns following values:
      0 - successfully initialize
      1 - Voice card fails
      2 - I/O write/read fails
      3 - Interrupt for DMA fails
*)
begin
  bx := 3;
  kutsu;
  initializedriver := ax;
end;

procedure speaker(status: byte);
(*
   Turns speaker either on or off. Use constants SPEAKER_ON and SPEAKER_OFF.
*)
begin
  bx := 4;
  ax := status;
  kutsu;
end;

procedure setstatusaddress(address: pointer);
(*
   Sets location of driver status word. Possible values for status word:
      0h - there is no voice output in process
   FFFFh - voice output in process
*)
begin
  bx := 5;
  pt := address;
  kutsu;
end;

procedure playsample (address: pointer);
(*
   Starts voice output, "address" is a pointer to start of voice data. It must
   be in Creative Voice File format. When loading .VOC files, remember to skip
   first 26 bytes (VOC-file header). Before voice output has ended, another
   voice output/input shouldn't be invoked.
*)
begin
  bx := 6;
  pt := address;
  kutsu;
end;

procedure inputsample (address: pointer; speed: word; length: longint);
(*
   Starts voice input, "address" is a pointer to buffer, "speed" is integer
   value between 4000 and 12000. Stops when buffer is full or "stopvoice" is
   called.
*)
begin
  bx := 7;
  ax := speed;
  dx := seg(pointer(length));
  cx := ofs(pointer(length));
  pt := address;
  kutsu;
end;

procedure stopvoice;
(*
   Stops voice output/input immediately. Status word is set to 0.
*)
begin
  bx := 8;
  kutsu;
end;

procedure uninstalldriver;
(*
   Stops voice output and uninstalls driver.
*)
begin
  bx := 9;
  kutsu;
end;

function pausevoice: byte;
(*
   Pauses voice output. Returns
      0  if successful, or
      1  if voice output was not in process.
*)
begin
  bx := 10;
  kutsu;
  pausevoice := ax;
end;

function continuevoice: byte;
(*
   Continues paused voice output. Returns
      0  if succesful, or
      1  if there was no voice to continue.
*)
begin
  bx := 11;
  kutsu;
  continuevoice := ax;
end;

function breakloop(when:word): byte;
(*
   Breaks current loop in voice. If when=BREAK_IMMEDIATELY, loop stops
   immediately. If when=BREAK_AFTER_LOOP, current loop is continues and
   looping stops after current round. Returns
      0  if successful, or
      1  if there was no voice in loop.
*)
begin
  bx := 12;
  ax := when;
  Kutsu;
  breakloop := ax;
end;

procedure setuserfunction(address: pointer);
(*
   When address to the user function is specified, CT-VOICE driver calls
   the user function on encountering each voice data block.

   The user function must observe the following conditions:

           a.  the funtion must perform a far RET.
           b.  the function must preserve the DS, DI, SI, and the flag
               register except the carry flag.
           c.  the function must clear the carry flag, if current data
               block has to be processed by the driver, otherwise set the
               carry flag.
           d.  function must not set the carry flag, if current data block
               is the terminate block, i.e. block type is 0.

   To disable the call to user function, invoke this function with the
   "address" pointing to 0000:0000.
*)
begin
  bx := 13;
  dx := seg(address^);
  ax := ofs(address^);
  kutsu;
end;

const
   DRIVER_FILENAME:string='CT-VOICE.DRV';

begin
  assign(f,DRIVER_FILENAME);
  (* $I- *) reset (f,1); (* $I+ *)
  if ioresult<>0 then
  begin
     writeln ('Couldn''t load ',DRIVER_FILENAME);
     halt(1);
  end;

  if maxavail<filesize(f)+16 then
  begin
     writeln ('Not enough memory to load ',DRIVER_FILENAME);
     close(F);
     halt(255);
  end;
  getmem(ctdriver, filesize(f)+16);
  ax:=ofs(ctdriver^);
  bx:=seg(ctdriver^);
  ax:=0;  (* offset must be zero *)
  inc(bx);
  ctdriver:=ptr(bx,ax);
  blockread(f,ctdriver^,filesize(f));
  close(f);
  (* driver is now loaded and ready for use *)
end.
