{---------------------------------------------------------------------------
                   Unit SBVoice (v1.10) for Turbo Pascal 6.0
       For interfacing with the SoundBlaster's digitized voice channel.
           Copyright (c) 1991, Amit K. Mathur, Windsor, Ontario.

                        By: Amit K. Mathur
                            3215 St. Patrick's Drive
                            Windsor, Ontario
                            N9E 3H2 CANADA
                        Ph: (519) 966-6924

 Networks:  RIME(tm) R/O ->WINDSOR, ILink (Shareware), NA-Net (Gaming),
            WWIVNet (#198@5950), or direct on NorthSTAR (519)735-1504.

 These routines are released to the public domain.  However I will gladly
 accept contributions towards further development of this and other products.
 Please send any changes or improvements my way.  And I'm interested in
 other SoundBlaster utilities and programming tools.  Thanks in advance.
 --------------------------------------------------------------------------}

{$O+,F+}
{ Allow this Unit to Be Overlayed (doesn't affect compilation if you decide
  not to overlay it), and force far calls.                                 }

unit SBVoice;

interface

uses MemAlloc;                                    { Memory Allocation Proc }

var
{$IFNDEF NoSBVoiceArray}                          { to use your own        }
     SoundFile: Array[1..25000] of byte;          { whatever size you want }
{$ENDIF}
     sgSBDriver, ofSBDriver: word;                { seg and ofs of Driver  }
     SBDriver: Pointer;                           { pointer to the driver  }
     StatusWord: Word;                            { stores SB status       }
     SBFound: Boolean;                            { whether Init worked    }

procedure loaddriver(fi:string);
{ Loads CT-VOICE.DRV into memory.  'fi' is the path to the driver.         }

procedure closedriver;
{ Clean up routine.  Not really necessary if your program is over.         }

procedure loadvoice(f:string;start,size:word);
{ Load 'f' into memory.  Start is the start of the area within
  'f' to load and size is the amount to laod.  If you set size to 0
  then it will load the entire file.                                      }

function sb_getversion:integer;
{ Get the version number of the CT-VOICE.DRV
  Returns the Version number                                              }

function sb_init:integer;
{ Initialize the SoundBlaster.  Call this right after load driver, unless
  you have to change the BaseIOAddress or Interrupt number and haven't
  changed the CT-VOICE.DRV file itself.
  Returns:  0 - no problem
            1 - sound card failiure
            2 - I/O failiure
            3 - DMA interrupt failiure                                    }

procedure sb_output(sg,os:word);
{ Output the digitized sound.  You must load the sound first!
  sg and os are the segment and offset of either SoundFile or whatever
  array you use to store the sound.  If you use a .VOC file then call
  with 26 added to the offset.                                            }

procedure sb_setstatusword(sg,os:word);
{ Sets the location of the status word.  This is the third thing you should
  do, after loading the driver and initializing it.
  The StatusWord will contain $0FFFF if input/output is in output, and
  0 when it's done.  It will also hold the values of the markers in voice
  files if any are encounterred, allowing you to coordinate output with
  your programs.                                                          }

procedure sb_speaker(mode:word);
{ Set the speaker on/off.  Off is mode 0, and On is anything else.  This
  is the fourth thing you should do in your initialization.               }

procedure sb_uninstall;
{ Uninstall the driver from memory.   Used by CloseDriver.                }

procedure sb_setIOaddress(add:word);
{ Override the IOaddress found inside the CT-VOICE.DRV file.  Add is the
  new IO address.                                                         }

procedure sb_setinterruptnumber(intno:word);
{ Allows you to override the Interrupt number in the driver.  IntNo is your
  new interrupt number (3, 5, 7 or 9).                                    }

procedure sb_stopoutput;
{ Stops the output in progress                                            }

function sb_pauseoutput: integer;
{ Pauses the output in progress.
  Returns:  0 - success
            1 - fail                                                      }

function sb_continueoutput: integer;
{ Continues a paused output.
  Returns:  0 - success
            1 - fail (nothing to continue)                                }

function sb_breakloop(mode:word): integer;
{ Breaks out of the currect output loop.
  Modes:  0 - continue round, stop when done
          1 - stop immediately
  Returns:  0 - success
            1 - not in loop                                               }

procedure sb_input(highlength,lowlength,seginputbuff,ofsinputbuff:word);
{ Input digitized sound.
  HighLength: The high byte of the length of the input buffer.
  LowLength:  The low byte of the length of the input buffer.
  SegInputBuff: The Segment of the start of the input buffer.
  OfsInputBuff: The Offset of the start of the input buffer.              }

procedure sb_setuserfunction(segaddress,ofsaddress:word);
{ Sets up a user function that the SB calls when it encounters a new data
  block.  It must perform a FAR ret, preserve DS,DI,SI and flag register.
  Clear Carry flag if you want the driver to process the block, or set it
  if your routine will.  It must be clear if the block type is 0, that
  is the terminate block.
  SegAddress is the segment of your user function in memory.
  OfsAddress is the ofset of your user function in memory.                }
  
implementation

uses DOS;

procedure Abort(s:string);
begin
  writeln('The Following Error Has Occurred: ',s);
  writeln('Remedy and try again.  We apologize for any inconvenience.');
  halt(1);
end;

procedure loaddriver(fi:string);
var f: file;
    k: integer;
    t: string[8];
begin
    assign(f,fi+'CT-VOICE.DRV');
    {$I-} Reset(f,1); {$I+}
    if IOResult <> 0 then
        Abort('Cannot Open '+fi+'CT-VOICE.DRV');
    blockread(f,Mem[sgSBDriver:ofSBDriver],filesize(f));
    close(f);
    t:='';
    for k:=0 to 7 do
        t:=t+chr(Mem[sgSBDriver:ofSBDriver+k+3]);
    if t<>'CT-VOICE' then
        abort('Invalid CT-VOICE Driver!');
end;

procedure closedriver;
begin
    sb_uninstall;
    if dalloc(sbdriver)=0 then
        abort('Uninstall Error!');
end;

procedure loadvoice(f:string;start,size:word);
var fi: file;
    k: word;
begin
    assign(fi,f);
    {$I-} Reset(fi,1); {$I+}
    if IOResult <> 0 then
       abort('Cannot Open '+f+'!');
    k:=0;
    seek(fi,start);
    if size=0 then size:=filesize(fi);
    blockread(fi,Mem[seg(soundfile):ofs(soundfile)],size);
    close(fi);
end;

function sb_getversion: integer; assembler;
asm
   push  bp
   mov   bx,0
   call  SBDriver
   pop   bp
end;

procedure sb_setIOaddress(add:word); assembler;
asm
   push  bp
   mov   bx,1
   mov   ax,add
   call  SBDriver
   pop   bp
end;

procedure sb_setinterruptnumber(intno:word); assembler;
asm
   push  bp
   mov   bx,2
   mov   ax,intno
   call  SBDriver
   pop   bp
end;

procedure sb_stopoutput; assembler;
asm
   push  bp
   mov   bx,8
   call  SBDriver
   pop   bp
end;

function sb_init: integer; assembler;
asm
   push  bp
   mov   bx, 3
   call  SBDriver
   pop   bp
end;

function sb_pauseoutput: integer; assembler;
asm
   push  bp
   mov   bx,10
   call  SBDriver
   pop   bp
end;

function sb_continueoutput: integer; assembler;
asm
   push  bp
   mov   bx,11
   call  SBDriver
   pop   bp
end;

function sb_breakloop(mode:word): integer; assembler;
asm
   push  bp
   mov   bx,12
   mov   ax,mode
   call  SBDriver
   pop   bp
end;

procedure sb_output(sg,os:word); assembler;
asm
    push bp
    push di
    mov  bx,6
    mov  di,os             { offset of voice  }
    mov  es,sg             { segment of voice }
    call SBDriver
    pop  di
    pop  bp
end;

procedure sb_input(highlength,lowlength,seginputbuff,ofsinputbuff:word); assembler;
asm
    push bp
    push di
    mov  bx,7
    mov  dx,highlength
    mov  cx,lowlength
    mov  es,seginputbuff
    mov  di,ofsinputbuff
    call SBDriver
    pop  di
    pop  bp
end;

procedure sb_setstatusword(sg,os:word); assembler;
asm
    push bp
    push di
    mov  bx,5
    mov  di,os
    mov  es,sg
    call SBDriver
    pop  di
    pop  bp
end;

procedure sb_speaker(mode:word); assembler;
asm
   push  bp
   mov   bx,4
   mov   ax,mode
   call  SBDriver
   pop   bp
end;

procedure sb_uninstall; assembler;
asm
   push  bp
   mov   bx,9
   call  SBDriver
   pop   bp
end;

procedure sb_setuserfunction(segaddress,ofsaddress:word); assembler;
asm
   push  bp
   mov   dx,segaddress
   mov   ax,ofsaddress
   mov   bx,13
   call  SBDriver
   pop   bp
end;


begin {set up SB}

  If DOSMemAvail < 2500 then                           { lower the heap   }
      abort('Not Enough Memory');                      { with $M to fix   }
  StatusWord:=MAlloc(SBDriver,2500);
  if StatusWord<>0 then
      abort('Memory Allocation Error');

  sgSBDriver:=MemW[seg(SBDriver):ofs(SBDriver)+2];
  ofSBDriver:=MemW[seg(SBDriver):ofs(SBDriver)];

  Loaddriver('');                                      { change at will   }
  if sb_init<>0 then                                   { or stick in your }
      SBFound:=FALSE                                   { own program init }
  else
      SBFound:=TRUE;

  if SBFound then begin
      sb_setstatusword(seg(statusword),ofs(statusword));
      sb_speaker(1);                                   { turn SB on       }
  end;
end.
