#include "sndscape.h" /**************************************************************************** EXTERNAL GLOBAL VARS - hardware config info ... ****************************************************************************/ WORD BasePort; /* Gate Array/MPU-401 base port */ int MidiIrq; /* the MPU-401 IRQ */ WORD WavePort; /* the AD-1848 base port */ int WaveIrq; /* the PCM IRQ */ int DmaChan; /* the PCM DMA channel */ /**************************************************************************** INTERNAL GLOBAL VARS - all kinds of stuff ... ****************************************************************************/ int Windx; /* Wave IRQ index - for reg writes */ int Mindx; /* MIDI IRQ index - for reg writes */ char Buffer[2*BUFFERSIZE]; /* the DMA buffer - get 2x */ char * DmaBuffer; /* pointer to aligned DMA buffer */ DWORD PhysAddr; /* physical addr of aligned buffer */ char * volatile NextBuffer; /* pointer to next buffer to fill */ int volatile FlipFlop; /* int global to track buffer half */ int IcType; /* the Ensoniq chip type */ int CdCfgSav; /* gate array register save area */ int DmaCfgSav; /* gate array register save area */ int IntCfgSav; /* gate array register save area */ int Vector; /* the Wave interrupt vector num */ void (interrupt far * VecSav)(); /* org interrupt vector save area */ WORD PicMaskReg; /* the PIC mask register */ int PicMaskSav; /* save area for mask state */ int Detected; /* suuccessful detection flag */ int DacSavL; /* DAC left volume save */ int DacSavR; /* DAC right volume save */ int CdxSavL; /* CD/Aux left volume save */ int CdxSavR; /* CD/Aux right volume save */ int const SsIrqs[4] = { 9, 5, 7, 10 }; /* Soundscape IRQs */ int const RsIrqs[4] = { 9, 7, 5, 15 }; /* an older IRQ set */ int const * Irqs; /* pointer to one of the IRQ sets */ DMAC_REGS const DmacRegs[4] = { /* the DMAC regs for chans 0-3 ... */ { 0x00, 0x01, 0x08, 0x0a, 0x0b, 0x0c, 0x87 }, { 0x02, 0x03, 0x08, 0x0a, 0x0b, 0x0c, 0x83 }, { 0x04, 0x05, 0x08, 0x0a, 0x0b, 0x0c, 0x81 }, { 0x06, 0x07, 0x08, 0x0a, 0x0b, 0x0c, 0x82 } }; DMAC_REGS const * DmacRegP; /* a pointer to a DMAC reg struct */ /**************************************************************************** EXTERNAL DRIVER FUNCTIONS ****************************************************************************/ /* int DetectSoundscape(void); This function is used to detect the presence of a Soundscape card in a system. It will read the hardware config info from the SNDSCAPE.INI file, the path to which is indicated by the SNDSCAPE environment variable. This config info will be stored in global variable space for the other driver functions to reference. Once the config settings have been determined, a hardware test will be performed to see if the Soundscape card is actually present. If this function is not explicitly called by the application, it it will be called by the OpenSoundscape function. INPUTS: None RETURNS: 0 - if Soundscape detection was successful -1 - if The SNDSCAPE environment variable was not found The SNDSCAPE.INI file cannot be opened The SNDSCAPE.INI file is missing information or is corrupted The Soundscape hardware is not detected */ int DetectSoundscape(void) { char static str[78]; int tmp, status; char *ep; FILE *fp; /* initialize some local vars ... */ status = 0; fp = 0; /* get the environment var and build the filename; then open it */ if( !(ep = getenv("SNDSCAPE")) ) return -1; strcpy(str, ep); if( str[strlen(str) - 1] == '\\' ) str[strlen(str) - 1] = '\0'; strcat(str, "\\SNDSCAPE.INI"); if( !(fp = fopen(str, "r")) ) return -1; /* read all of the necessary config info ... */ if( GetConfigEntry("Product", str, fp) ) { status = -1; goto exit; } /* if an old product name is read, set the IRQs accordingly */ strupr(str); if( strstr(str, "SOUNDFX") || strstr(str, "MEDIA_FX") ) Irqs = RsIrqs; else Irqs = SsIrqs; if( GetConfigEntry("Port", str, fp) ) { status = -1; goto exit; } BasePort = (WORD) strtol(str, NULL, 16); if( GetConfigEntry("WavePort", str, fp) ) { status = -1; goto exit; } WavePort = (WORD) strtol(str, NULL, 16); if( GetConfigEntry("IRQ", str, fp) ) { status = -1; goto exit; } MidiIrq = (int) strtol(str, NULL, 10); if( MidiIrq == 2) MidiIrq = 9; if( GetConfigEntry("SBIRQ", str, fp) ) { status = -1; goto exit; } WaveIrq = (int) strtol(str, NULL, 10); if( WaveIrq == 2 ) WaveIrq = 9; if( GetConfigEntry("DMA", str, fp) ) { status = -1; goto exit; } DmaChan = (int) strtol(str, NULL, 10); /* see if Soundscape is there by reading HW ... */ if( (inp(BasePort + GA_HOSTCTL_OFF) & 0x78) != 0x00 ) { status = -1; goto exit; } if( (inp(BasePort + GA_ADDR_OFF) & 0xf0) == 0xf0 ) { status = -1; goto exit; } outp(BasePort + GA_ADDR_OFF, 0xf5); tmp = inp(BasePort + GA_ADDR_OFF); if( (tmp & 0xf0) == 0xf0) { status = -1; goto exit; } if( (tmp & 0x0f) != 0x05) { status = -1; goto exit; } /* formulate the chip ID */ if( (tmp & 0x80) != 0x00 ) IcType = MMIC; else if((tmp & 0x70) != 0x00) IcType = OPUS; else IcType = ODIE; /* now do a quick check to make sure the CoDec is there too */ if( (inp(WavePort) & 0x80) != 0x00 ) { status = -1; goto exit; } Detected = 0xed; exit: if( fp != 0 ); fclose(fp); return status; } /* int OpenSoundscape(void); This function opens the Soundscape driver. It will setup the Soundscape hardware for Native PCM mode (the default mode is Sound Blaster emulation). It will first call the DetectSoundscape function it it determines that it has not already been called. INPUTS: None. RETURNS: 0 - if successful -1 - if the DetectSoundscape function needed to be called and returned unnsuccessful. */ int OpenSoundscape(void) { /* see if we need to detect first */ if( Detected != 0xed ) if( DetectSoundscape() ) return -1; /* save all volumes that we might use */ DacSavL = CdRead(CD_DACL_REG); DacSavR = CdRead(CD_DACR_REG); CdxSavL = CdRead(CD_CDAUXL_REG); CdxSavR = CdRead(CD_CDAUXL_REG); /* align the DMA buffer pointer and physical address */ DmaBuffer = Buffer; PhysAddr = (DWORD) (char far *) Buffer; PhysAddr = ((PhysAddr >> 12) & 0x000ffff0) + (WORD) PhysAddr; if( PhysAddr >> 16 != (PhysAddr + BUFFERSIZE-1) >> 16 ) { DmaBuffer += BUFFERSIZE; PhysAddr += BUFFERSIZE; } /* build the globals that we'll need ... */ DmacRegP = &DmacRegs[DmaChan]; /* derive the MIDI and Wave IRQ indices (0-3) for reg writes */ for( Mindx = 0; Mindx < 4; ++Mindx ) if( MidiIrq == *(Irqs + Mindx) ) break; for( Windx = 0; Windx < 4; ++Windx ) if( WaveIrq == *(Irqs + Windx) ) break; /* in case the CoDec is running, stop it */ StopCoDec(); /* clear any possible pending ints from Sound Blaster emulation HW */ inp(0x22e); /* and from the CoDec ... */ outp(WavePort + CD_STATUS_OFF, 0x00); /* build some more globals, save necessary stuff, install our int */ Vector = (WaveIrq < 8 ? 0x08 : 0x70) + (WaveIrq & 0x07); VecSav = _dos_getvect(Vector); PicMaskReg = WaveIrq < 8 ? 0x21 : 0xa1; PicMaskSav = inp(PicMaskReg); _dos_setvect(Vector, WaveIsr); /* if necessary, save some regs, do some resource re-routing */ if( IcType != MMIC) { /* setup the CoDec DMA polarity */ GaWrite(GA_DMACFG_REG, 0x50); /* give the CoDec control of the DMA and Wave IRQ resources */ CdCfgSav = GaRead(GA_CDCFG_REG); GaWrite(GA_CDCFG_REG, 0x89 | (DmaChan << 4) | (Windx << 1)); /* pull the Sound Blaster emulation off of those resources */ DmaCfgSav = GaRead(GA_DMAB_REG); GaWrite(GA_DMAB_REG, 0x20); IntCfgSav = GaRead(GA_INTCFG_REG); GaWrite(GA_INTCFG_REG, 0xf0 | (Mindx << 2) | Mindx); } /* put the CoDec into mode change state */ outp(WavePort + CD_ADDR_OFF, 0x40); /* setup CoDec mode - single DMA chan, AutoCal on */ CdWrite(CD_CONFIG_REG, 0x0c); /* select the mic/line input to the record mux */ CdWrite(CD_ADCL_REG, (CdRead(CD_ADCL_REG) & 0x3f) | 0x40); CdWrite(CD_ADCR_REG, (CdRead(CD_ADCR_REG) & 0x3f) | 0x40); /* init the ADC and DAC levels to some nominal values */ SetAdcVol(32, 32); SetDacVol(127, 127); /* enable the CoDec interrupt pin */ CdWrite(CD_PINCTL_REG, CdRead(CD_PINCTL_REG) | 0x02); /* unmask the PC programmable interrupt controller */ outp(PicMaskReg, PicMaskSav & ~(1 << (WaveIrq & 0x07))); return 0; } /* int StartCoDec(WORD srate, int size16bit, int stereo, int direction); This function will start the CoDec auto-restart DMA process. INPUTS: srate - the audio sample rate in Hertz size16bit - 0 for 8-bit unsigned data, 1 for 16-bit signed data stereo - 0 for mono data, 1 for L/R interleaved stereo data direction - 0 for playback, 1 for record RETURNS: 0 - if the CoDec was started successfully -1 - if the sample rate is unacceptable */ int StartCoDec(WORD srate, int size16bit, int stereo, int direction) { WORD i, tmp; /* set format */ if( SetFormat(srate, size16bit, stereo) ) return -1; /* make sure the AD-1848 is not already running */ if( CdRead(CD_CONFIG_REG) & 0x01 ) StopCoDec(); /* pre-fill the audio buffer with silence */ tmp = size16bit ? 0x00 : 0x80; for( i = 0; i < BUFFERSIZE; ++i ) *(DmaBuffer + i) = (char) tmp; /* initialize the int flip-flop and "next buffer to fill" ptr */ FlipFlop = 0; NextBuffer = 0; /* write the CoDec interrupt count - sample frames per half-buffer */ tmp = (BUFFERSIZE/2 >> (size16bit + stereo)) - 1; CdWrite(CD_LCOUNT_REG, tmp); CdWrite(CD_UCOUNT_REG, tmp >> 8); /* Set up the PC DMA Controller */ outp(DmacRegP->mask, 0x04 | DmaChan); outp(DmacRegP->mode, (direction ? DMAC_AUTO_IN : DMAC_AUTO_OUT) | DmaChan); outp(DmacRegP->page, (int) (PhysAddr >> 16)); /* Disable around 2-byte programming sequences */ _disable(); outp(DmacRegP->clrff, 0x00); outp(DmacRegP->addr, (int) PhysAddr); outp(DmacRegP->addr, (int) (PhysAddr >> 8)); outp(DmacRegP->count, (int) (BUFFERSIZE-1)); outp(DmacRegP->count, (int) ((BUFFERSIZE-1) >> 8)); _enable(); /* clear status, unmask the PC DMA Controller */ inp(DmacRegP->status); outp(DmacRegP->mask, DmaChan); /* disable mode change state and start the CoDec */ outp(WavePort + CD_ADDR_OFF, 0x00); CdWrite(CD_CONFIG_REG, direction ? 0x02 : 0x01); return 0; } /* char *GetBuffer(void); This function is used by the application, normally in a polling fashion, to get a pointer to the next buffer half to be filled. INPUTS: None RETURNS: 0 - if the CoDec is not ready for audio data non-0 - a pointer to a BUFFERSIZE/2 byte buffer will be returned if the CoDec is ready for data */ char *GetBuffer(void) { BYTE *tmp; if( (tmp = NextBuffer) != 0 ) NextBuffer = 0; return tmp; } /* void PauseCoDec(void); This function will pause the CoDec auto-restart DMA process. INPUTS: None RETURNS: Nothing */ void PauseCoDec() { CdWrite(CD_CONFIG_REG, CdRead(CD_CONFIG_REG) & 0xfc); return; } /* void ResumeCoDec(int direction); This function will pause the CoDec auto-restart DMA process. INPUTS: direction - 0 for playback, 1 for record RETURNS: Nothing */ void ResumeCoDec(int direction) { CdWrite(CD_CONFIG_REG, direction ? 0x02 : 0x01); return; } /* void StopCoDec(void); This function will stop the CoDec auto-restart DMA process. INPUTS: None RETURNS: Nothing */ void StopCoDec(void) { WORD i; /* only stop it if it's actually running */ if( (CdRead(CD_CONFIG_REG) & 0x03) != 0x00 ) { CdWrite(CD_CONFIG_REG, CdRead(CD_CONFIG_REG) & 0xfc); /* give the CoDec some time to receive its last DACK(s) */ /* the DMAC must not be masked while the CoDec is running */ for( i = 0; i < 256; ++i ) inp(BasePort + GA_ADDR_OFF); } return; } /* void CloseSoundscape(void); This function will make sure the CoDec is stopped, return the Soundscape hardware to it's default Sound Blaster emulation mode, de-install the PCM interrupt, resore the PIC mask, and do other cleanup. INPUTS: None RETURNS: None */ void CloseSoundscape(void) { /* in case the CoDec is running, stop it */ StopCoDec(); /* mask the PC DMA Controller */ outp(DmacRegP->mask, 0x04 | DmaChan); /* disable the CoDec interrupt pin */ CdWrite(CD_PINCTL_REG, CdRead(CD_PINCTL_REG) & 0xfd); /* if necessary, re-assign the DMA and IRQ resources */ if ( IcType != MMIC ) { GaWrite(GA_INTCFG_REG, IntCfgSav); GaWrite(GA_DMAB_REG, DmaCfgSav); GaWrite(GA_CDCFG_REG, CdCfgSav); } /* restore the old interrupt vector and PIC mask */ _dos_setvect(Vector, VecSav); outp(PicMaskReg, PicMaskSav); /* restore all volumes ... */ CdWrite(CD_DACL_REG, DacSavL); CdWrite(CD_DACR_REG, DacSavR); CdWrite(CD_CDAUXL_REG, CdxSavL); CdWrite(CD_CDAUXL_REG, CdxSavR); return; } /* void SetDacVol(int lvol, int rvol); This function sets the left and right DAC output level in the CoDec. INPUTS: lvol - left volume, 0-127 rvol - right volume, 0-127 RETURNS: Nothing */ void SetDacVol(int lvol, int rvol) { CdWrite(CD_DACL_REG, (~lvol & 0x7f) >> 1); CdWrite(CD_DACR_REG, (~rvol & 0x7f) >> 1); return; } /* void SetCdRomVol(int lvol, int rvol); This function sets the left and right CD-ROM output level in the CoDec. INPUTS: lvol - left volume, 0-127 rvol - right volume, 0-127 RETURNS: Nothing */ void SetCdRomVol(int lvol, int rvol) { CdWrite(CD_CDAUXL_REG, (~lvol & 0x7f) >> 2); CdWrite(CD_CDAUXR_REG, (~rvol & 0x7f) >> 2); return; } /* void SetAdcVol(int lvol, int rvol); This function sets the left and right ADC input level in the CoDec. INPUTS: lvol - left volume, 0-127 rvol - right volume, 0-127 RETURNS: Nothing */ void SetAdcVol(int lvol, int rvol) { CdWrite(CD_ADCL_REG, (CdRead(CD_ADCL_REG) & 0xf0) | (lvol & 0x7f) >> 4); CdWrite(CD_ADCR_REG, (CdRead(CD_ADCR_REG) & 0xf0) | (rvol & 0x7f) >> 4); return; } /**************************************************************************** INTERNAL DRIVER FUNCTIONS ****************************************************************************/ /* void interrupt far WaveIsr(); This is the Wave Device Interrupt Service Routine. INPUTS: None RETURNS: Nothing */ void interrupt far WaveIsr() { /* make sure we're not getting garbage IRQ edges ... */ if( WaveIrq == 7 ) { outp(0x20, 0x0b); inp(0x21); if( !(inp(0x20) & 0x80) ) goto exit; } else if( WaveIrq == 15 ) { outp(0xa0, 0x0b); inp(0xa1); if( !(inp(0xa0) & 0x80) ) goto exit; } /* if it's a CoDec interrupt, flip the buffer, clear CoDec int */ if( inp(WavePort + CD_STATUS_OFF) & 0x01 ) { if( (FlipFlop ^= 1 ) != 0 ) NextBuffer = DmaBuffer; else NextBuffer = DmaBuffer + BUFFERSIZE/2; outp(WavePort + CD_STATUS_OFF, 0x00); } /* clear the PC interrupt controller */ if( WaveIrq >= 8 ) outp(0xa0, 0x20); outp(0x20, 0x20); exit: return; } /* int GaRead(WORD rnum); This function is used to read the indirect addressed registers in the Ensoniq Soundscape gate array. INPUTS: rnum - the numner of the indirect register to be read RETURNS: the contents of the indirect register are returned */ int GaRead(WORD rnum) { outp(BasePort + GA_ADDR_OFF, rnum); return inp(BasePort + GA_DATA_OFF); } /* int GaWrite(WORD rnum, int value); This function is used to write the indirect addressed registers in the Ensoniq Soundscape gate array. INPUTS: rnum - the numner of the indirect register to be read value - the byte value to be written to the indirect register RETURNS: Nothing */ void GaWrite(WORD rnum, int value) { outp(BasePort + GA_ADDR_OFF, rnum); outp(BasePort + GA_DATA_OFF, value); return; } /* int CdRead(WORD rnum); This function is used to read the indirect addressed registers in the AD-1848 or compatible CoDec. It will preserve the special function bits in the upper-nibble of the indirect address register. INPUTS: rnum - the numner of the indirect register to be read RETURNS: the contents of the indirect register are returned */ int CdRead(WORD rnum) { outp(WavePort + CD_ADDR_OFF, (inp(WavePort + CD_ADDR_OFF) & 0xf0) | rnum); return inp(WavePort + CD_DATA_OFF); } /* int CdWrite(WORD rnum, int value); This function is used to write the indirect addressed registers in the Ad-1848 or compatible CoDec. It will preserve the special function bits in the upper-nibble of the indirect address register. INPUTS: rnum - the numner of the indirect register to be read value - the byte value to be written to the indirect register RETURNS: Nothing */ void CdWrite(WORD rnum, int value) { outp(WavePort + CD_ADDR_OFF, (inp(WavePort + CD_ADDR_OFF) & 0xf0) | rnum); outp(WavePort + CD_DATA_OFF, value); return; } /* int GetConfigEntry(char *entry, char *dst, FILE *fp); This function parses a file (SNDSCAPE.INI) for a left-hand string and, if found, writes its associated right-hand value to a destination buffer. This function is case-insensitive. INPUTS: fp - a file pointer to the open SNDSCAPE.INI config file dst - the destination buffer pointer lhp - a pointer to the right-hand string RETURNS: 0 - if successful -1 - if the right-hand string is not found or has no equate */ int GetConfigEntry(char *entry, char *dest, FILE *fp) { char static str[83]; char static tokstr[33]; char *p; /* make a local copy of the entry, upper-case it */ strcpy(tokstr, entry); strupr(tokstr); /* rewind the file and try to find it ... */ rewind(fp); for( ;; ) { /* get the next string from the file */ fgets(str, 83, fp); if( feof(fp) ) return -1; /* properly terminate the string */ for( p = str; *p != '\0'; ++p ) { if( *p == ' ' || *p == '\t' || *p == 0x0a || *p == 0x0d ) { *p = '\0'; break; } } /* see if it's an 'equate' string; if so, zero the '=' */ if( !(p = strchr(str, '=')) ) continue; *p = '\0'; /* upper-case the current string and test it */ strupr(str); if( strcmp(str, tokstr) ) continue; /* it's our string - copy the right-hand value to buffer */ for( p = str + strlen(str) + 1; (*dest++ = *p++) != '\0'; ) ; break; } return 0; } /* SetFormat(WORD srate, int size16bit, int stereo); This function sets the CoDec audio data format for record or playback. INPUTS: srate - the audio sample rate in Hertz size16bit - 0 for 8-bit unsigned data, 1 for 16-bit signed data stereo - 0 for mono data, 1 for L/R interleaved stereo data RETURNS: 0 - if successful -1 - if the sample rate is unacceptable */ int SetFormat(WORD srate, int size16bit, int stereo) { int format; DWORD i; /* init the format register value */ format = 0; /* first, find the sample rate ... */ switch(srate) { case 5512U: format = 0x01; break; case 6615U: format = 0x0f; break; case 8000U: format = 0x00; break; case 9600U: format = 0x0e; break; case 11025U: format = 0x03; break; case 16000U: format = 0x02; break; case 18900U: format = 0x05; break; case 22050U: format = 0x07; break; case 27428U: format = 0x04; break; case 32000U: format = 0x06; break; case 33075U: format = 0x0d; break; case 37800U: format = 0x09; break; case 44100U: format = 0x0b; break; case 48000U: format = 0x0c; break; default: return -1; } /* set other format bits ... */ if( size16bit ) format |= 0x40; if( stereo ) format |= 0x10; /* make sure the the CoDec is in mode change state */ outp(WavePort + CD_ADDR_OFF, 0x40); /* and write the format register */ CdWrite(CD_FORMAT_REG, format); /* delay for internal re-synch, then exit mode change state */ for( i = 0; i < 200000UL; ++i ) inp(BasePort + GA_ADDR_OFF); outp(WavePort + CD_ADDR_OFF, 0x00); return 0; }