Unit Audio;
{----------------------------------------------------------------------------}
{  Audio : An implementation of a Soundblaster-pro driver (BP 7.0). The      }
{          Soundblaster-Pro object features record and playback using DMA    }
{          so your CPU can do something else while sound is being played     }
{          or recorded. Both Real and Protected Mode are supported.          }
{          This code has only been tested on a Soundblaster-Pro clone with   }
{          BasePort=$220, Irq=7 and DMAchannel=1.                            }
{          Do what ever you like with this code, but use it at your own      }
{          risk!!!!!                                                         }
{          Comments and/or bug fixes are welcome at the e-mail address below }
{****************************************************************************}
{  Author            : Menno Victor van der star                             }
{  E-mail            : s795238@dutiws.twi.tudelft.nl                         }
{  Developed on      : 08-02-'95                                             }
{  Last update on    : 13-06-'95                                             }
{  Status            : Working, but only tested on a SB-pro clone with       }
{                      Baseport=$220, Irq=7 and DMAChannel=1                 }
{  Future extensions : - Direct input/output filter for playing .WAV/.VOC's  }
{                      - Extensive testing (Feedback appreciated :)          }
{----------------------------------------------------------------------------}
Interface

{$IFDEF DPMI} Uses WinAPI; {$ENDIF}

Const
{-- Constants for soundblaster-pro object --}

  Stereo = True;         { Stereo/Mono constants }
  Mono   = False;

  Master     = 0;        { Volume/Input devices }
  Microphone = 1;
  CDAudio    = 2;
  LineIn     = 3;
  Voice      = 4;
  FM         = 5;

  Left         = 0;      { Indications for left, right or both channels }
  Right        = 1;
  LeftAndRight = 2;

  HighPass = 0;          { Bandpass filter constants }
  LowPass  = 1;

Type
  PSoundBlasterPro = ^SoundBlasterPro;
  SoundBlasterPro = Object

                      Constructor Init (Port, IRQ, DMA : Word);
                      Destructor  Done; Virtual;

                      { The following 4 virtual procedures have to be redefined via inheritance :
                        - OutBuffer : Write 'Size' recorded bytes from 'Buffer'
                        - InBuffer : Read 'Size' bytes to be played back from 'Buffer'
                        - RecordingReady : This procedure is called after the recording is done
                        - PlaybackReady : This procedure is called after the sound has been played }

                      Procedure   OutBuffer (Var Buffer; Size : Word); Virtual;
                      Procedure   InBuffer  (Var Buffer; Size : Word); Virtual;
                      Procedure   RecordingReady; Virtual;
                      Procedure   PlaybackReady; Virtual;

                      Function    Reset : Boolean;
                      Procedure   PlaySample   (SampleRate : Word; Length : LongInt);
                      Procedure   RecordSample (SampleRate : Word; Length : LongInt);

                      Procedure   SetStereoIn;
                      Procedure   SetMonoIn;
                      Function    InputMode : Boolean;

                      Procedure   SetVolume (VolumeType, Channel, Volume : Byte);
                      Function    GetVolume (VolumeType, Channel : Byte) : Byte;
                      Procedure   SetInput (InputDevice, Filter : Byte; FilterOn : Boolean);
                      Procedure   GetInput (Var InputDevice, Filter : Byte; Var FilterOn : Boolean);
                      Procedure   SetOutput (StereoOut, FilterOutput : Boolean);
                      Procedure   GetOutput (Var StereoOut, FilterOutput : Boolean);

                      Procedure   HandleRecordIrq;
                      Procedure   HandlePlaybackIrq;

                    Private

                      DMAChannel, IrqVector, IrqIntVector, SBPort, PICPort,
                      ResetPort, ReadPort, WritePort, PollPort, MixerIndexPort,
                      MixerWritePort : Word;
                      OldIntVector, DMABuffer1, DMABuffer2, SoundBuffer1, SoundBuffer2 : Pointer;
                      IRQStopMask, IRQStartMask, DMAStartMask, DMAStopMask,
                      DMAModeReg : Byte;

                      StereoMode : Boolean;
                      CurrentPlay, CurrentRecord : Pointer;
                      PlayLength, RecordLength : LongInt;
                      RecSampleSize, RecSampleRate, PlaySampleRate, PlaySampleSize : Word;

                      Procedure WriteMixer (Index, Value : Byte);
                      Function  ReadMixer (Index : Byte) : Byte;
                      Procedure WriteDSP (Value : Byte);
                      Function  ReadDSP : Byte;
                      Procedure PlayBuffer (Buffer : Pointer; SampleRate : Word; Size : Word);
                      Procedure RecBuffer  (Buffer : Pointer; SampleRate : Word; Size : Word);
                      Procedure EnableInterrupts;
                      Procedure DisableInterrupts;
                      Procedure DisableIrq;
                      Procedure EnableIrq;

                    End;

Implementation

Uses Crt, Dos;

Const
  Module_ID = 'AUDIO';
  SBPro : PSoundBlasterPro = NIL;         { Pointer to current soundblaster-pro object }

Procedure RecordIRQ; Interrupt;

Var
  Dummy : Byte;

Begin
  Dummy:=Port [$22E];        { Maybe the value $22E should be replaced with the appropriate value }
                             { if a port other than $220 is used (I can't remember (anyone?)) }
  If Assigned (SBPro) then SBPro^.HandleRecordIrq;
  Port [$20]:=$20;
End;

Procedure PlayIRQ; Interrupt;

Var
  Dummy : Byte;

Begin
  Dummy:=Port [$22E];        { Maybe the value $22E should be replaced with the appropriate value }
                             { if a port other than $220 is used (I can't remember (anyone?)) }
  If Assigned (SBPro) then SBPro^.HandlePlaybackIrq;
  Port [$20]:=$20;
End;

Constructor SoundBlasterPro.Init (Port, IRQ, DMA : Word);

Const
  IrqIntNums : Array [0..15] Of Byte = ($08, $09, $0A, $0B, $0C, $0D, $0E, $0F,
                                        $70, $71, $72, $73, $74, $75, $76, $77);
Var
  l : LongInt;

Begin
  If Assigned (SBPro) then Fail;     { Only one instance of the object is allowed at one time }

  DMAChannel:=DMA;
  IrqVector:=IRQ;
  SBPort:=Port;
  If IrqVector<=7 then PICPort:=$21 Else PICPort := $A1;
  IrqIntVector:=IrqIntNums[IrqVector];
  IrqStopMask:=1 SHL (IrqVector mod 8);
  IrqStartMask:=Not IrqStopMask;
  GetIntVec (IRQIntVector, OldIntVector);

  {$IFDEF DPMI}

    { This code looks a bit silly but I haven't had to time to clean it up }

    DMABuffer1:=Pointer (GlobalDosAlloc (32768));
    DMABuffer2:=Pointer (GlobalDosAlloc (32768));
    LongInt (DMABuffer1):=(LongInt (DMABuffer1) MOD 65536) SHL 16;
    LongInt (DMABuffer2):=(LongInt (DMABuffer2) MOD 65536) SHL 16;

    SoundBuffer1:=Pointer (AllocSelector (LongInt (DMABuffer1) SHR 16));
    LongInt (SoundBuffer1):=LongInt (SoundBuffer1) SHL 16;
    SoundBuffer2:=Pointer (AllocSelector (LongInt (DMABuffer2) SHR 16));
    LongInt (SoundBuffer2):=LongInt (SoundBuffer2) SHL 16;

    l:=GetSelectorBase (LongInt (SoundBuffer1) SHR 16);
    If l MOD 65536>49152 then Begin
      While l MOD 65536>0 Do Inc (l);
      SetSelectorBase (LongInt (SoundBuffer1) SHR 16,l);
    End;

    l:=GetSelectorBase (LongInt (SoundBuffer2) SHR 16);
    If l MOD 65536>49152 then Begin
      While l MOD 65536>0 Do Inc (l);
      SetSelectorBase (LongInt (SoundBuffer2) SHR 16,l);
    End;

  {$ELSE}
    DMABuffer1:=NIL;
    DMABuffer2:=NIL;
    GetMem (DMABuffer1,32768);
    GetMem (DMABuffer2,32768);
    If Not Assigned (DMABuffer1) Or Not Assigned (DMABuffer2) then Begin Done; Fail; End;
    l:=Seg (DMABuffer1^); l:=l*16; l:=l+Ofs (DMABuffer1^);
    If l MOD 65536<=49152 then SoundBuffer1:=DMABuffer1 Else SoundBuffer1:=Ptr (((l DIV 65536)+1)*4096,0);
    l:=Seg (DMABuffer2^); l:=l*16; l:=l+Ofs (DMABuffer2^);
    If l MOD 65536<=49152 then SoundBuffer2:=DMABuffer2 Else SoundBuffer2:=Ptr (((l DIV 65536)+1)*4096,0);
  {$ENDIF}

  ResetPort:=SBPort+$6;
  ReadPort:=SBPort+$A;
  WritePort:=SBPort+$C;
  PollPort:=SBPort+$E;
  MixerIndexPort:=SBPort+$4;
  MixerWritePort:=SBPort+$5;

  DMAStartMask:=DMAChannel+$00;
  DMAStopMask:=DMAChannel+$04;
  DMAModeReg:=DMAChannel+$48;

  If Not Reset then Begin Done; Fail; End;
  SetStereoIn;
  SetVolume (Master,LeftAndRight,15);
  SetVolume (Voice,LeftAndRight,15);
  SetVolume (FM,LeftAndRight,15);
  SetVolume (Microphone,Right,7);
  SetVolume (CDAudio,LeftAndRight,15);
  SetVolume (LineIn,LeftAndRight,15);
  SetInput (LineIn,LowPass,True);
  SetOutput (Stereo,True);
  SBPro:=@SELF;

  DisableIrq;
  SetIntVec (IRQIntVector, @PlayIrq);
  EnableIrq;
End;

Destructor SoundBlasterPro.Done;

Begin
  SetIntVec (IRQIntVector,OldIntVector);
  {$IFDEF DPMI}
    GlobalDosFree (LongInt (DMABuffer1) SHR 16);
    GlobalDosFree (LongInt (DMABuffer2) SHR 16);
    FreeSelector (LongInt (SoundBuffer1) SHR 16);
    FreeSelector (LongInt (SoundBuffer2) SHR 16);
  {$ELSE}
    If Assigned (DMABuffer1) then FreeMem (DMABuffer1,32768);
    If Assigned (DMABuffer2) then FreeMem (DMABuffer2,32768);
  {$ENDIF}
  SBPro:=NIL;
End;

Procedure SoundBlasterPro.OutBuffer (Var Buffer; Size : Word);

Begin
  RunError (211);
End;

Procedure SoundBlasterPro.InBuffer  (Var Buffer; Size : Word);

Begin
  RunError (211);
End;

Procedure SoundBlasterPro.RecordingReady;

Begin
  RunError (211);
End;

Procedure SoundBlasterPro.PlaybackReady;

Begin
  RunError (211);
End;

Function SoundBlasterPro.Reset : Boolean;

Var
  i : Byte;

Begin
  Port[ResetPort]:=1;
  Delay (1);
  Port[ResetPort]:=0;
  i:=1;
  While (ReadDSP<>$AA) And (i<100) Do Inc (i);
  Reset:=i<100;
  WriteMixer (0,0);
End;

Procedure SoundBlasterPro.PlaySample (SampleRate : Word; Length : LongInt);

Begin
  PlayLength:=Length;
  If PlayLength > 0 then Begin
    DisableIrq;
    SetIntVec (IRQIntVector, @PlayIrq);
    EnableIrq;
    CurrentPlay:=SoundBuffer1;
    PlaySampleRate:=SampleRate;
    If PlayLength >= 16384 then PlaySampleSize:=16384 Else PlaySampleSize:=PlayLength;
    Dec (PlayLength,PlaySampleSize);
    InBuffer (CurrentPlay^,PlaySampleSize);
    PlayBuffer (CurrentPlay,SampleRate,PlaySampleSize);
    If PlayLength > 0 then Begin
      If PlayLength >= 16384 then PlaySampleSize:=16384 Else PlaySampleSize:=PlayLength;
      Dec (PlayLength,PlaySampleSize);
      InBuffer (SoundBuffer2^,PlaySampleSize);
    End;
  End;
End;

Procedure SoundBlasterPro.RecordSample (SampleRate : Word; Length : LongInt);

Begin
  RecordLength:=Length;
  If RecordLength > 0 then Begin
    DisableIrq;
    SetIntVec (IRQIntVector, @RecordIrq);
    EnableIrq;
    CurrentRecord:=SoundBuffer1;
    RecSampleRate:=SampleRate;
    If RecordLength >= 16384 then RecSampleSize:=16384 Else RecSampleSize:=RecordLength;
    Dec (RecordLength,RecSampleSize);
    RecBuffer (CurrentRecord,RecSampleRate,RecSampleSize);
  End;
End;

Procedure SoundBlasterPro.SetStereoIn;

Begin
  WriteDSP ($A8);
  StereoMode:=Stereo;
End;

Procedure SoundBlasterPro.SetMonoIn;

Begin
  WriteDSP ($A0);
  StereoMode:=Mono;
End;

Function SoundBlasterPro.InputMode : Boolean;

Begin
  InputMode:=StereoMode;
End;

Procedure SoundBlasterPro.WriteMixer (Index, Value : Byte);

Begin
  Port [MixerIndexPort]:=Index;
  Port [MixerWritePort]:=Value;
End;

Function SoundBlasterPro.ReadMixer (Index : Byte) : Byte;

Begin
  Port [MixerIndexPort]:=Index;
  ReadMixer:=Port [MixerWritePort];
End;

Procedure SoundBlasterPro.SetVolume (VolumeType, Channel, Volume : Byte);

Var
  IndexReg : Byte;

Begin
  If VolumeType<>Microphone then Begin
    Case Channel Of
      Left         : Volume:=Volume SHL 4;
      LeftAndRight : Volume:=Volume Or (Volume SHL 4);
    End;
  End;
  Case VolumeType Of
    Master     : IndexReg:=$22;
    Voice      : IndexReg:=$4;
    FM         : IndexReg:=$26;
    Microphone : IndexReg:=$0A;
    CDAudio    : IndexReg:=$28;
    LineIn     : IndexReg:=$2E;
  End;
  WriteMixer (IndexReg,Volume);
End;

Function SoundBlasterPro.GetVolume (VolumeType, Channel : Byte) : Byte;

Var
  IndexReg, Volume : Byte;

Begin
  Case VolumeType Of
    Master     : IndexReg:=$22;
    Voice      : IndexReg:=$4;
    FM         : IndexReg:=$26;
    Microphone : IndexReg:=$0A;
    CDAudio    : IndexReg:=$28;
    LineIn     : IndexReg:=$2E;
  End;
  Volume:=ReadMixer (IndexReg);
  If (VolumeType<>Microphone) And (Channel=Left) then Volume:=Volume SHR 4;
  If VolumeType=Microphone then Volume:=Volume And 7 Else Volume:=Volume And 15;
  GetVolume:=Volume;
End;

Procedure SoundBlasterPro.SetInput (InputDevice, Filter : Byte; FilterOn : Boolean);

Var
  Value : Byte;

Begin
  Case InputDevice Of
    Microphone : Value:=0;
    CDAudio    : Value:=2;
    LineIn     : Value:=6;
  Else
    Exit;
  End;
  If Filter=LowPass then Value:=Value Or 8;
  If Not FilterOn then Value:=Value Or 32;
  WriteMixer ($0C,Value);
End;

Procedure SoundBlasterPro.GetInput (Var InputDevice, Filter : Byte; Var FilterOn : Boolean);

Var
  Value : Byte;

Begin
  Value:=ReadMixer ($0C);
  Case Value And 6 Of
    0 : InputDevice:=Microphone;
    2 : InputDevice:=CDAudio;
    6 : InputDevice:=LineIn;
  End;
  If Value And 8<>0 then Filter:=LowPass Else Filter:=HighPass;
  FilterOn:=(Value And 32)=0
End;

Procedure SoundBlasterPro.SetOutput (StereoOut, FilterOutput : Boolean);

Var
  Value : Byte;

Begin
  If StereoOut then Value:=2 Else Value:=0;
  If Not FilterOutput then Value:=Value Or 32;
  WriteMixer ($0E,Value);
End;

Procedure SoundBlasterPro.GetOutput (Var StereoOut, FilterOutput : Boolean);

Var
  Value : Byte;

Begin
  Value:=ReadMixer ($0E);
  StereoOut:=(Value And 2)<>0;
  FilterOutput:=(Value And 32)=0;
End;

Procedure SoundBlasterPro.HandleRecordIrq;

Begin
  If RecordLength>0 then Begin
    If CurrentRecord=SoundBuffer1 then CurrentRecord:=SoundBuffer2 Else CurrentRecord:=SoundBuffer1;
    If RecordLength >= 16384 then RecSampleSize:=16384 Else RecSampleSize:=RecordLength;
    Dec (RecordLength,RecSampleSize);
    RecBuffer (CurrentRecord,RecSampleRate,RecSampleSize);
    If CurrentRecord=SoundBuffer1 then OutBuffer (SoundBuffer2^,16384) Else OutBuffer (SoundBuffer1^,16384);
  End
  Else Begin
    If CurrentRecord=SoundBuffer1 then
      OutBuffer (SoundBuffer1^,RecSampleSize)
    Else
      OutBuffer (SoundBuffer2^,RecSampleSize);
    RecordingReady;
  End;
End;

Procedure SoundBlasterPro.HandlePlaybackIrq;

Begin
  If PlayLength>0 then Begin
    If CurrentPlay=SoundBuffer1 then CurrentPlay:=SoundBuffer2 Else CurrentPlay:=SoundBuffer1;
    PlayBuffer (CurrentPlay,PlaySampleRate,PlaySampleSize);
    If PlayLength >= 16384 then PlaySampleSize:=16384 Else PlaySampleSize:=PlayLength;
    Dec (PlayLength,PlaySampleSize);
    If CurrentPlay=SoundBuffer1 then
      InBuffer (SoundBuffer2^,PlaySampleSize)
    Else
      InBuffer (SoundBuffer1^,PlaySampleSize);
  End
  Else Begin
    If PlaySampleSize>0 then Begin
      If CurrentPlay=SoundBuffer1 then CurrentPlay:=SoundBuffer2 Else CurrentPlay:=SoundBuffer1;
      PlayBuffer (CurrentPlay,PlaySampleRate,PlaySampleSize);
      PlaySampleSize:=0;
    End
    Else
      PlaybackReady;
  End;
End;

Procedure SoundBlasterPro.WriteDSP (Value : Byte);

Begin
  While Port[WritePort] > 127 Do ;
  Port[WritePort]:=Value;
end;

Function SoundBlasterPro.ReadDSP : Byte;

Begin
  While Port[PollPort] < 128 Do;
  ReadDSP:=Port[ReadPort];
end;

Procedure SoundBlasterPro.PlayBuffer (Buffer : Pointer; SampleRate : Word; Size : Word);

Var
  SampleRateLimit, Time_constant, Page, Offset : Word;
  l : LongInt;

Begin

  If (Size=0) Or (Size>16384) then Exit;

  Dec (Size);

  { Set up the DMA chip }
  {$IFDEF DPMI}
    l:=GetSelectorBase (LongInt (Buffer) SHR 16);
  {$ELSE}
    l:=LongInt (Seg (Buffer^)) SHL 4+Ofs (Buffer^);
  {$ENDIF}
  Offset:=l MOD 65536;
  Page:=l SHR 16;
  Port[$0A] := DMAStopMask;
  Port[$0C] := 0;
  Port[$0B] := DMAModeReg;
  Port[$02] := Lo(offset);
  Port[$02] := Hi(offset);
  Port[$83] := Page;
  Port[$03] := Lo(size);
  Port[$03] := Hi(size);
  Port[$0A] := DMAStartMask;

  { Set the playback SampleRate }

  If InputMode=Stereo then Begin
    SampleRateLimit:=11025;
    If SampleRate<=SampleRateLimit then Begin
      Time_constant := 256 - (1000000 div (2*SampleRate));
    End
    Else Begin
      Time_constant := Hi (65536-(256000000 DIV (2*SampleRate)));
    End;
  End
  Else Begin
    SampleRateLimit:=22050;
    If SampleRate<=SampleRateLimit then Begin
      Time_constant := 256 - (1000000 div SampleRate);
    End
    Else Begin
      Time_constant := Hi (65536-(256000000 DIV SampleRate));
    End;
  End;
  WriteDSP ($40);
  WriteDSP (Time_constant);

  { Set the playback type (8-bit) }

  If SampleRate<=SampleRateLimit then WriteDSP($14) Else WriteDSP ($48);
  WriteDSP (Lo(size));
  WriteDSP (Hi(size));
  If SampleRate>SampleRateLimit then WriteDSP ($91);

  Port [PICPort]:=Port [PICPort] And IrqStartMask;
  Port [$20]:=$20;

End;

Procedure SoundBlasterPro.RecBuffer (Buffer : Pointer; SampleRate : Word; Size : Word);

Var
  SampleRateLimit, Time_constant, Page, Offset : Word;
  l : LongInt;

Begin

  If (Size=0) Or (Size>16384) then Exit;

  Dec (Size);

  { Set up the DMA chip }
  {$IFDEF DPMI}
    l:=GetSelectorBase (LongInt (Buffer) SHR 16);
  {$ELSE}
    l:=LongInt (Seg (Buffer^)) SHL 4+Ofs (Buffer^);
  {$ENDIF}
  Offset:=l MOD 65536;
  Page:=l SHR 16;
  Port[$0A] := DMAStopMask;
  Port[$0C] := 0;
  Port[$0B] := $45;
  Port[$02] := Lo (Offset);
  Port[$02] := Hi (Offset);
  Port[$83] := Page;
  Port[$03] := Lo (Size);
  Port[$03] := Hi (Size);
  Port[$0A] := DMAStartMask;

  { Set the record SampleRate }
  If InputMode=Stereo then Begin
    SampleRateLimit:=11025;
    If SampleRate<=SampleRateLimit then Begin
      Time_constant := 256 - (1000000 div (2*SampleRate));
    End
    Else Begin
      Time_constant := Hi (65536-(256000000 DIV (2*SampleRate)));
    End;
  End
  Else Begin
    SampleRateLimit:=22050;
    If SampleRate<=SampleRateLimit then Begin
      Time_constant := 256 - (1000000 div SampleRate);
    End
    Else Begin
      Time_constant := Hi (65536-(256000000 DIV SampleRate));
    End;
  End;
  WriteDSP ($40);
  WriteDSP (Time_constant);

  { Set the record type (8-bit) }
  If SampleRate<=SampleRateLimit then WriteDSP ($24) Else WriteDSP ($48);
  WriteDSP (Lo (Size));
  WriteDSP (Hi (Size));
  If SampleRate>SampleRateLimit then WriteDSP ($99);

  Port [PICPort]:=Port [PICPort] And IrqStartMask;
  Port [$20]:=$20;

End;

Procedure SoundBlasterPro.DisableInterrupts; ASSEMBLER; ASM CLI END;
Procedure SoundBlasterPro.EnableInterrupts; ASSEMBLER; ASM STI END;
Procedure SoundBlasterPro.DisableIrq;

Begin
  Port[PICPort]:=Port[PICPort] Or IrqStopMask;
End;

Procedure SoundBlasterPro.EnableIrq;

Begin
  Port[PICPort]:=Port[PICPort] And IrqStartMask;
End;

End.



