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