home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Audio 4.94 - Over 11,000 Files
/
audio-11000.iso
/
msdos
/
sndbords
/
proaudio
/
tp_int94
/
mvint94.pas
< prev
next >
Wrap
Pascal/Delphi Source File
|
1993-12-12
|
22KB
|
499 lines
UNIT MVInt94;
{$I mvInt94.def}
{-------------------------------------------------------------------------}
{ Media Vision Pro Audio Spectrum routines for Borland Pascal v7. This }
{ is a simple little unit designed to access the Media Vision sound board }
{ through PCM.COM's INT 94h driver. This software has been written from }
{ documentation supplied in the PCM.ZIP file downloaded from the Media }
{ Vision BBS, and a lot of answers from Bart Crane at Media Vision, who }
{ wrote the PCM.COM driver in the first place. }
{ }
{ Copyright 1993, Peter W. Cervasio. }
{ This software may be freely used, so long as my copyright is maintained }
{-------------------------------------------------------------------------}
{ Revision Data: }
{ 1.0 02 Dec 93 pwc Initial coding }
{ 1.1 10 Dec 93 pwc Added SetPCMInfo and .WAV header info }
{-------------------------------------------------------------------------}
{ Written/Translated by Peter W. Cervasio }
{ Contact me via the Media Vision BBS, (Pete Cervasio) }
{ or via CompuServe: 73443,1426 }
{-------------------------------------------------------------------------}
INTERFACE
USES Dos;
CONST
BoardAddress : Word = $0388; { default board address }
ThunderBoard : Word = 0; { ThunderBoard bit. Set to $4000 if }
{ using the Thunderboard or Soundblaster portion of the card. }
{ we really don't use these next two, though they're here in case we do }
{ something with them at a later time. }
IRQ : Byte = $07; { default board irq }
DMA : Byte = $03; { default board dma channel }
{ These are the bits returned by GetHWVersionBits. To use them, AND the }
{ value returned with the specified constant. For example: to find out }
{ if we have the MVA508 mixer or the National one, do the following: }
{ }
{ HWStuff := GetHWVersionBits; }
{ IF HWStuff and bMVA508 = bMVA508 then }
{ Writeln ('We have the MVA508 mixer') }
{ else }
{ Writeln ('We have the National mixer')'; }
{ }
bMVA508 = $0001; { MVA508(1) or National(0) mixer }
bMVPS2 = $0002; { PS2 bus stuff }
bMVSLAVE = $0004; { CDPC Slave device is present }
bMVSCSI = $0008; { SCSI interface }
bMVENHSCSI = $0010; { Enhanced SCSI interface }
bMVSONY = $0020; { Sony 535 interface }
bMVDAC16 = $0040; { 16 bit DAC }
bMVSBEMUL = $0080; { SB h/w emulation }
bMVMPUEMUL = $0100; { MPU h/w emulation }
bMVOPL3 = $0200; { OPL3(1) or 3812(0) }
bMV101 = $0400; { MV101 ASIC }
bMV101_REV = $7800; { MV101 Revision }
TYPE
{-----------------------------------------------------------------------------}
{ The "WaveHeaderType" structure holds the format of a .WAV file's header. }
{ This is for informational purposes only, really. There aren't any routines }
{ in this unit to open .WAV files. Some programs stick their own junk in the }
{ middle of a .WAV's header, too. GoldWave is one. You just have to open }
{ file and search for the various header markers ('RIFF', 'fmt ', 'data', }
{ etc.) before reading the data in. I believe all the Media Vision recording }
{ utilities create a .WAV header in this format, though, so you should be }
{ safe reading the beginning of those as a record. }
{-----------------------------------------------------------------------------}
WaveHeaderType = RECORD
Riff : Array[0..3] of char; { 'RIFF' }
RiffLength : LongInt;
Wave : Array[0..3] of char; { 'WAVE' }
Fmt : Array[0..3] of char; { 'fmt ' }
FmtLength : LongInt; { length of the info block (16) }
FormatTag : Word; { format tag?? }
Channels : Word; { 1 = mono, 2 = stereo }
SampleRate : LongInt; { samples per second }
SampPerSec : LongInt; { channels * sample rate }
BlockAlign : Word; { block alignment (1=byte) }
SampleSize : Word; { bits per sample (8/16) }
DataHeader : Array[0..3] of char; { 'data' }
WaveLength : LongInt; { length of PCM data in the file }
END;
{-------------------------------------------------------------------------}
{ The "MVState" structure holds the hardware state table returned by }
{ InitMVSound(). I don't understand this.. I just translated information }
{ that was in a C header file. }
{-------------------------------------------------------------------------}
MVState = RECORD
SysSpkrTmr : Byte; { 42 System Speaker Timer Address }
SysTmrCllr : Byte; { 43 System Timer Control }
SysSpkrReg : Byte; { 61 System Speaker Register }
Joystick : Byte; { 201 Joystick Register }
LFMAddr : Byte; { 388 Left FM Synth Address }
LFMData : Byte; { 389 Left FM Synth Data }
RFMAddr : Byte; { 38A Right FM Synth Address }
RFMData : Byte; { 38B Right FM Synth Data }
DFMAddr : Byte; { 788 Dual FM Synth Address }
DFMData : Byte; { 789 Dual FM Synth Data }
RESRVD1 : Byte; { reserved }
PAudioMixr : Byte; { 78B Paralllel Audio Mixer Control }
AudioMixr : Byte; { B88 Audio Mixer Control }
IntrCtlrSt : Byte; { B89 Interrupt Status }
AudioFilt : Byte; { B8A Audio Filter Control }
IntrCtlr : Byte; { B8B Interrupt Control }
PcmData : Byte; { F88 PCM Data I/O Register }
RESRVD2 : Byte; { reserved }
CrossChannel: Byte; { F8A Cross Channel }
RESRVD3 : Byte; { reserved }
SampleRate : Word; { 1388 Sample Rate Timer }
SampleCnt : Word; { 1389 Sample Count Register }
SpkrTmr : Word; { 138A Shadow Speaker Timer Count }
TmrCtlr : Byte; { 138B Shadow Speaker Timer Control }
MdIrqVect : Byte; { 1788 MIDI IRQ Vector Register }
MdSysCtlr : Byte; { 1789 MIDI System Control Register }
MdSysStat : Byte; { 178A MIDI IRQ Status Register }
MdIrqClr : Byte; { 178B MIDI IRQ Clear Register }
MdGroup1 : Byte; { 1B88 MIDI Group #1 Register }
MdGroup2 : Byte; { 1B89 MIDI Group #2 Register }
MdGroup3 : Byte; { 1B8A MIDI Group #3 Register }
MdGroup4 : Byte; { 1B8B MIDI Group #4 Register }
END;
MVStatePtr = ^MVState;
{-------------------------------------------------------------------------}
{ The PCMINFOTYPE record is used to set the sample rate, bit size and }
{ the mono/stereo setting. The sample rate can be up to 88200 for mono }
{ and 44100 for stereo. Bit size is either 8 or 16. StereoFlag is set }
{ to 0 for mono, or 1 for stereo. }
{-------------------------------------------------------------------------}
PCMInfoType = RECORD { Passed to PCMInfo() }
SampleRate : LongInt; { 100 - 88200 (200 - 44100 for stereo) }
StereoFlag : Integer; { 0 = mono, 1 = stereo }
{ I added these two... don't know if they're right - pwc }
UnKnown : Integer; { set to 0 }
BitSize : Integer; { 8 or 16 bits per sample }
end;
{-------------------------------------------------------------------------}
{ The DMABUFTYPE record is used to inform the TSR of the DMA buffer's }
{ address, it's size in kilobytes, and the number of partitions it is }
{ to be split into. The DMA buffer must be contained completely within }
{ a 64k block. i.e.: it must not go from 2xxx:xxxx into 3xxx:xxxx. You }
{ WILL experience lockups if it does so. There is a function in the }
{ unit called AllocateDMABuffer that takes a word parameter telling how }
{ many kb you want to allocate. It will return a pointer to a block of }
{ memory that will satisfy the conditions. See the comments near the }
{ function for more information. }
{-------------------------------------------------------------------------}
DMABufType = RECORD { Passed to DMABuffer() }
DmaBuffer : Pointer; { address of DMA buffer }
BufKBSize : Word; { size of DMA buffer in KB }
Partitions : Word; { number of partitions }
end;
{-------------------------------------------------------------------------}
{ The FDMABUFTYPE record is used with the FindDMABuffer function. The }
{ DMABuf element should point to a block of memory that is at least twice }
{ as large as the DMA buffer you desire, and the BufKBSize element should }
{ be set to the size (in kilobytes) of the DMA buffer you desire. The }
{ FindDMABuffer function will return a valid DMA buffer address out of }
{ the block of memory you passed to it. This functionality is in the }
{ Int $94 driver. }
{-------------------------------------------------------------------------}
FDMABufType = RECORD { Passed to FindDMABuffer() }
DmaBuf : Pointer; { address of memory allocated }
BufKBSize : Word; { size of memory in KB }
end;
{ This only sucks up six bytes from your Data Segment... pretty cheap, huh? }
VAR
DMACounter : Word; { Used by EzDMACounter - incremented every time the }
{ DMA Callback routine is called. }
EZDMA : Pointer; { Pointer set to point to EZDMACounter }
{ ------- Functions and Procedures defined in the unit -------- }
FUNCTION InitMVSound: Pointer;
FUNCTION InitPCM: Integer;
FUNCTION SetPCMInfo(DataSize, MonoStereo: Word; SampleRate: LongInt): Integer;
FUNCTION PCMInfo(VAR P_Info: PCMInfoType): Integer;
FUNCTION UserFunc(VAR S): Pointer;
FUNCTION DMABuffer(VAR D_Info: DMABufType): Pointer;
FUNCTION PCMRecord: Integer;
FUNCTION PCMPlay: Integer;
FUNCTION GetHWVersionBits: Word;
FUNCTION FindDMABuffer(VAR S: FDmaBufType): Pointer;
FUNCTION GetDMAAddress: Pointer;
FUNCTION GetDMASize: LongInt;
PROCEDURE ResumePCM;
PROCEDURE PausePCM;
PROCEDURE StopPCM;
PROCEDURE RemovePCM;
PROCEDURE AllocateDMABuffer(VAR TheBuffer: Pointer; Size: Word);
PROCEDURE EzDMACallback;
IMPLEMENTATION
VAR
Int94Ptr : Pointer;
{ In Turbo Pascal, we are free to modify all the microprocessor's registers }
{ with the exception of SS, DS, BP and SP.
{ This call is not really necessary, since it is done by the PCM.COM tsr upon }
{ loading. Returns the address of the hardware state table (MVState above) or }
{ a nil pointer if it fails }
FUNCTION InitMVSound: Pointer; assembler;
asm
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
int $94
end;
{ Initialize PCM State Tables }
{ Returns 0 if failed, otherwise the library version }
FUNCTION InitPCM: Integer; assembler;
asm
mov ax, 1
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ Set sample rate, size, and mono/stereo }
{ Returns 0 if okay, -1 if Sample Rate outside valid range }
FUNCTION SetPCMInfo(DataSize, MonoStereo: Word; SampleRate: LongInt): Integer; assembler;
asm
mov ax, datasize
push ax
mov ax, 0
push ax
mov ax, monostereo
push ax
mov ax, word ptr samplerate[2]
push ax
mov ax, word ptr samplerate[0]
push ax
mov ax, 2
mov bx, ss
mov es, bx
mov bx, sp
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
pop bx
pop bx
pop bx
pop bx
pop bx
end;
{ Same as SetPCMInfo, but using a record to hold the info. }
FUNCTION PCMInfo(VAR P_Info: PCMInfoType): Integer; assembler;
asm
les bx, p_info
mov ax, 2
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ Set DMA Buffer address, size, and partitions }
{ NOTE: The DMA buffer _CANNOT_ cross a 64k boundary. See the sample program }
{ for an easy way of getting it to start at the beginning of one }
{ Returns nil pointer if it failed }
FUNCTION DMABuffer(VAR D_Info: DMABufType): Pointer; assembler;
asm
les bx, d_info
mov ax, 3
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ Set the address of the callback procedure. Note that the DS register will }
{ probably not be set to our data segment when it gets called. Using the one }
{ in this unit, EzDMACallback, and the variable DMACounter is recommended. }
{ Returns nil pointer if it failed to set the address correctly }
FUNCTION UserFunc(VAR S): Pointer; assembler;
asm
les bx, S
mov ax, 4
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ Sets the PCM routines into the Play state. Upon the next call to ResumePCM }
{ the digital audio will start. This function returns 0 if successful }
FUNCTION PCMPlay: Integer; assembler;
asm
mov ax, 5
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ Sets the PCM routines into the Record state. On the next call to ResumePCM }
{ the recording will start. This function returns 0 if successful }
FUNCTION PCMRecord: Integer; assembler;
asm
mov ax, 6
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ This procedure sets the PCM routines into the pause state. Use this before }
{ calling PCMRecord or PCMPlay to avoid the DMA handler starting until you are }
{ ready for it. Also used while recording/playing PCM audio to provide a }
{ pause. }
PROCEDURE PausePCM; assembler;
asm
mov ax, 7
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ This procedure starts the Play / Record of PCM audio data after a call to }
{ PCMPlay or PCMRecord, or it resumes the operation that was paused with a }
{ call to PausePCM. }
PROCEDURE ResumePCM; assembler;
asm
mov ax, 8
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ This procedure stops the Play / Record of PCM data. }
PROCEDURE StopPCM; assembler;
asm
mov ax, 9
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ Remove the PCM hook to Int $94 and reset internal DMA variables }
PROCEDURE RemovePCM; assembler;
asm
mov ax, 10
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ find an area from memory within a 64k block }
{ returns nil if failed, or a valid DMA buffer address }
FUNCTION FindDMABuffer(VAR S: FDmaBufType): Pointer; assembler;
asm
les bx, S
mov ax, 11
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ This function doesn't always work, according to Bart Crane of Media Vision. }
{ It is supposed to return the address of the DMA buffer that was set using }
{ DMABuffer(). Certain versions of PCM.COM have an invalid segment override }
{ in them, though, and return the wrong information. Since the DMABuffer() }
{ routine is supposed to always work, you don't really need this. }
FUNCTION GetDMAAddress: Pointer; assembler;
asm
mov ax, $8000
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ This function doesn't always work, according to Bart Crane of Media Vision. }
{ It is supposed to return the size and # of partitons of the DMA buffer that }
{ was set using DMABuffer(). See GetDMAAddress above for the reasons it may }
{ not work. The number of partitions is returned in the high word of the }
{ long integer while the size of the buffer is returned in the low word. }
FUNCTION GetDMASize: LongInt; assembler;
asm
mov ax, $8001
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ This function returns a word describing the hardware that is detected at }
{ the current board address. The bit meanings are described at the top of }
{ the unit. }
FUNCTION GetHWVersionBits: Word; assembler;
asm
mov ax, 12
mov si, BoardAddress
or si, ThunderBoard
shl si, 4
or si, ax
int $94
end;
{ This is a quick and easy DMA callback routine that you can use. It will }
{ increment the word variable DMACounter once every time a DMA partition is }
{ used. Your main program loop should monitor this variable after calling }
{ ResumePCM to know when a DMA partition should be filled with new data. }
{ See the TESTPCM.PAS program that came with this unit for an example of }
{ what I'm talking about. }
PROCEDURE EzDMACallback; assembler;
asm
push ds
mov ax, SEG @data
mov ds, ax
inc DMACounter
pop ds
end;
{ Allocate memory for the DMA buffer. This memory must _NOT_ cross a 64k }
{ boundary. We take care of that by finding out where the heap pointer is }
{ and how far it is to the next 64k boundary. If there is enough space to }
{ allocate the DMA buffer without crossing the boundary, we go ahead and }
{ put it where it will go. Otherwise, we temporarily suck up enough heap }
{ space to put the heap pointer on the next 64k boundary and allocate the }
{ DMA buffer there, freeing the temporary space after we're done. GetMem }
{ is used for the memory allocation, so you need to call FreeMem with the }
{ correct size if you want to free the DMA buffer for some reason. }
PROCEDURE AllocateDMABuffer(VAR TheBuffer: Pointer; Size: Word);
VAR
J, JJ : Pointer;
K : Array[1..2] of Word absolute J;
M : Word;
BEGIN
{ Take the parameter as either the number of K, or the actual size of }
{ the requested buffer. If it's 64 or less, assume it's in kb, if more }
{ then assume it's the number of bytes requested. }
if Size <= 64 then Size := Size * 1024;
J := HeapPtr; { Find out where our heap pointer is }
M := $1000 - (K[2] AND $FFF); { Take high word of it and find out how }
{ far it is to the next $x000 }
if M = $1000 then M := 0; { deal with it being there already }
if Size < M * 16 then M := 0; { We have room where we are if so. }
if M > 0 then GetMem (JJ, M * 16); { if it isn't, suck some memory up }
GetMem (TheBuffer, Size); { okay, allocate our DMA buffer }
if M > 0 then FreeMem(JJ, M * 16); { and free our temporary stuff }
END;
{ Unit initialization code. This just checks to see that PCM.COM has been }
{ loaded into memory and halts with a message if not. If you want to try }
{ and automatically load it if it isn't found, then it is up to you to get }
{ it working... I think it would be okay to do a SwapVectors and exec PCM }
{ because I don't think SwapVectors bothers with int $94. I don't need it }
{ working that way, so I haven't bothered with it. }
BEGIN
GetIntVec($94, Int94Ptr);
if not assigned(Int94Ptr) then
begin
writeln ('ERROR: Sound driver not installed properly.');
writeln ('Please load PCM.COM and re-run this program.');
Halt(1);
end;
EZDma := @EZDMACallback;
END.