/* snd_gus.c @description@ Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA $Id$ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "dosisms.h" //============================================================================= // Author(s): Jayeson Lee-Steere #define INI_STRING_SIZE 0x100 QFile *ini_fopen(const char *filename, const char *modes); int ini_fclose(QFile *f); void ini_fgets(QFile *f, const char *section, const char *field, char *s); // Routines for reading from .INI files // The read routines are fairly efficient. // // Author(s): Jayeson Lee-Steere #define MAX_SECTION_WIDTH 20 #define MAX_FIELD_WIDTH 20 #define NUM_SECTION_BUFFERS 10 #define NUM_FIELD_BUFFERS 20 struct section_buffer { long offset; char name[MAX_SECTION_WIDTH+1]; }; struct field_buffer { long offset; int section; char name[MAX_FIELD_WIDTH+1]; }; static QFile *current_file=NULL; static int current_section; static int current_section_buffer=0; static int current_field_buffer=0; static struct section_buffer section_buffers[NUM_SECTION_BUFFERS]; static struct field_buffer field_buffers[NUM_FIELD_BUFFERS]; //*************************************************************************** // Internal routines //*************************************************************************** static char toupper(char c) { if (c>='a' && c<='z') c-=('a'-'A'); return(c); } static void reset_buffer(QFile *f) { int i; for (i=0;i0 && out[i-1]==' ') i--; // Null terminate the output string. out[i]=0; } // Extracts the field name from a field line // e.g. in="sooty=life be in it" gives out="sooty" static void get_field_name(char *out, char *in) { int i=0; // Skip leading spaces while (in[0]==' ') in++; // Copy name to output string while (in[0]!='=' && in[0]!=13 && in[0]!=10 && in[0]!=0) { if (iNUM_SECTION_BUFFERS) current_section_buffer=0; // Delete any field buffers that correspond to this section for (i=0;iNUM_FIELD_BUFFERS) current_field_buffer=0; // Set buffer information strcpy(field_buffers[current_field_buffer].name,field); field_buffers[current_field_buffer].section=section; field_buffers[current_field_buffer].offset=offset; } // Identical to fgets except the string is trucated at the first ';', // carriage return or line feed. static char *stripped_fgets(char *s, int n, QFile *f) { int i=0; if (fgets(s,n,f)==NULL) return(NULL); while (s[i]!=';' && s[i]!=13 && s[i]!=10 && s[i]!=0) i++; s[i]=0; return(s); } //*************************************************************************** // Externally accessable routines //*************************************************************************** // Opens an .INI file. Works like fopen QFile *ini_fopen(const char *filename, const char *modes) { return(fopen(filename,modes)); } // Closes a .INI file. Works like fclose int ini_fclose(QFile *f) { if (f==current_file) reset_buffer(NULL); return(fclose(f)); } // Puts "field" from "section" from .ini file "f" into "s". // If "section" does not exist or "field" does not exist in // section then s=""; void ini_fgets(QFile *f, const char *section, const char *field, char *s) { int i; long start_pos,string_start_pos; char ts[INI_STRING_SIZE*2]; if (f!=current_file) reset_buffer(f); // Default to "Not found" s[0]=0; // See if section is in buffer for (i=0;i>1) & 0x0001FFFF) | (Address & 0x000C0000L) ); } void ClearGf1Ints(void) { int i; SetGf18(DMA_CONTROL,0x00); SetGf18(ADLIB_CONTROL,0x00); SetGf18(RECORD_CONTROL,0x00); GetGf18(DMA_CONTROL); GetGf18(RECORD_CONTROL); for (i=0;i<32;i++); GetGf18(GET_IRQV); } //============================================================================= // Get Interwave (UltraSound PnP) configuration if any //============================================================================= static qboolean GUS_GetIWData(void) { char *Interwave,s[INI_STRING_SIZE]; QFile *IwFile; int CodecBase,CodecDma,i; Interwave=getenv("INTERWAVE"); if (Interwave==NULL) return(false); // Open IW.INI IwFile=ini_fopen(Interwave,"rt"); if (IwFile==NULL) return(false); // Read codec base and codec DMA ini_fgets(IwFile,"setup 0","CodecBase",s); sscanf(s,"%X",&CodecBase); ini_fgets(IwFile,"setup 0","DMA2",s); sscanf(s,"%i",&CodecDma); ini_fclose(IwFile); // Make sure numbers OK if (CodecBase==0 || CodecDma==0) return(false); CodecRegisterSelect=CodecBase; CodecData=CodecBase+1; CodecStatus=CodecBase+2; DmaChannel=CodecDma; // Make sure there is a CODEC at the CODEC base // Clear any pending IRQs dos_inportb(CodecStatus); dos_outportb(CodecStatus,0); // Wait for 'INIT' bit to clear for (i=0;i<0xFFFF;i++) if ((dos_inportb(CodecRegisterSelect) & 0x80) == 0) break; if (i==0xFFFF) return(false); // Get chip revision - can not be zero dos_outportb(CodecRegisterSelect,CODEC_MODE_AND_ID); if ((dos_inportb(CodecRegisterSelect) & 0x7F) != CODEC_MODE_AND_ID) return(false); if ((dos_inportb(CodecData) & 0x0F) == 0) return(false); HaveCodec=1; Con_Printf("Sound Card is UltraSound PnP\n"); return(true); } //============================================================================= // Get UltraSound MAX configuration if any //============================================================================= static qboolean GUS_GetMAXData(void) { char *Ultrasnd,*Ultra16; int i; int GusBase,Dma1,Dma2,Irq1,Irq2; int CodecBase,CodecDma,CodecIrq,CodecType; BYTE MaxVal; Ultrasnd=getenv("ULTRASND"); Ultra16=getenv("ULTRA16"); if (Ultrasnd==NULL || Ultra16==NULL) return(false); sscanf(Ultrasnd,"%x,%i,%i,%i,%i",&GusBase,&Dma1,&Dma2,&Irq1,&Irq2); sscanf(Ultra16,"%x,%i,%i,%i",&CodecBase,&CodecDma,&CodecIrq,&CodecType); if (CodecType==0 && CodecDma!=0) DmaChannel=CodecDma & 0x07; else DmaChannel=Dma2 & 0x07; // Make sure there is a GUS at GUS base dos_outportb(GusBase+0x08,0x55); if (dos_inportb(GusBase+0x0A)!=0x55) return(false); dos_outportb(GusBase+0x08,0xAA); if (dos_inportb(GusBase+0x0A)!=0xAA) return(false); // Program CODEC control register MaxVal=((CodecBase & 0xF0)>>4) | 0x40; if (Dma1 > 3) MaxVal|=0x10; if (Dma2 > 3) MaxVal|=0x20; dos_outportb(GusBase+0x106,MaxVal); CodecRegisterSelect=CodecBase; CodecData=CodecBase+1; CodecStatus=CodecBase+2; // Make sure there is a CODEC at the CODEC base // Clear any pending IRQs dos_inportb(CodecStatus); dos_outportb(CodecStatus,0); // Wait for 'INIT' bit to clear for (i=0;i<0xFFFF;i++) if ((dos_inportb(CodecRegisterSelect) & 0x80) == 0) break; if (i==0xFFFF) return(false); // Get chip revision - can not be zero dos_outportb(CodecRegisterSelect,CODEC_MODE_AND_ID); if ((dos_inportb(CodecRegisterSelect) & 0x7F) != CODEC_MODE_AND_ID) return(false); if ((dos_inportb(CodecData) & 0x0F) == 0) return(false); HaveCodec=1; Con_Printf("Sound Card is UltraSound MAX\n"); return(true); } //============================================================================= // Get regular UltraSound configuration if any //============================================================================= static qboolean GUS_GetGUSData(void) { char *Ultrasnd; int GusBase,Dma1,Dma2,Irq1,Irq2,i; Ultrasnd=getenv("ULTRASND"); if (Ultrasnd==NULL) return(false); sscanf(Ultrasnd,"%x,%i,%i,%i,%i",&GusBase,&Dma1,&Dma2,&Irq1,&Irq2); DmaChannel=Dma1 & 0x07; // Make sure there is a GUS at GUS base dos_outportb(GusBase+0x08,0x55); if (dos_inportb(GusBase+0x0A)!=0x55) return(false); dos_outportb(GusBase+0x08,0xAA); if (dos_inportb(GusBase+0x0A)!=0xAA) return(false); Gf1TimerControl = GusBase+0x008; Gf1PageRegister = GusBase+0x102; Gf1RegisterSelect = GusBase+0x103; Gf1DataLow = GusBase+0x104; Gf1DataHigh = GusBase+0x105; // Reset the GUS SetGf18(MASTER_RESET,0x00); Gf1Delay(); Gf1Delay(); SetGf18(MASTER_RESET,0x01); Gf1Delay(); Gf1Delay(); // Set to max (32) voices SetGf18(SET_VOICES,0xDF); // Clear any pending IRQ's ClearGf1Ints(); // Set all registers to known values for (i=0;i<32;i++) { dos_outportb(Gf1PageRegister,i); SetGf18(SET_CONTROL,0x03); SetGf18(SET_VOLUME_CONTROL,0x03); Gf1Delay(); SetGf18(SET_CONTROL,0x03); SetGf18(SET_VOLUME_CONTROL,0x03); SetGf116(SET_START_HIGH,0); SetGf116(SET_START_LOW,0); SetGf116(SET_END_HIGH,0); SetGf116(SET_END_LOW,0); SetGf116(SET_ACC_HIGH,0); SetGf116(SET_ACC_LOW,0); SetGf18(SET_VOLUME_RATE,63); SetGf18(SET_VOLUME_START,5); SetGf18(SET_VOLUME_END,251); SetGf116(SET_VOLUME,5<<8); } // Clear any pending IRQ's ClearGf1Ints(); // Enable DAC etc. SetGf18(MASTER_RESET,0x07); // Enable line output so we can hear something dos_outportb(GusBase,0x08); HaveCodec=0; Con_Printf("Sound Card is UltraSound\n"); return(true); } //============================================================================= // Programs the DMA controller to start DMAing in Auto-init mode //============================================================================= static void GUS_StartDMA(BYTE DmaChannel,short *dma_buffer,int count) { int mode; int RealAddr; RealAddr = ptr2real(dma_buffer); if (DmaChannel <= 3) { ModeReg = 0x0B; DisableReg = 0x0A; ClearReg = 0x0E; } else { ModeReg = 0xD6; DisableReg = 0xD4; ClearReg = 0xDC; } CountReg=CountRegs[DmaChannel]; AddrReg=AddrRegs[DmaChannel]; dos_outportb(DisableReg, DmaChannel | 4); // disable channel // set mode- see "undocumented pc", p.876 mode = (1<<6) // single-cycle +(0<<5) // address increment +(1<<4) // auto-init dma +(2<<2) // read +(DmaChannel & 0x03); // channel # dos_outportb(ModeReg, mode); // set page dos_outportb(PageRegs[DmaChannel], RealAddr >> 16); if (DmaChannel <= 3) { // address is in bytes dos_outportb(0x0C, 0); // prepare to send 16-bit value dos_outportb(AddrReg, RealAddr & 0xff); dos_outportb(AddrReg, (RealAddr>>8) & 0xff); dos_outportb(0x0C, 0); // prepare to send 16-bit value dos_outportb(CountReg, (count-1) & 0xff); dos_outportb(CountReg, (count-1) >> 8); } else { // address is in words dos_outportb(0xD8, 0); // prepare to send 16-bit value dos_outportb(AddrReg, (RealAddr>>1) & 0xff); dos_outportb(AddrReg, (RealAddr>>9) & 0xff); dos_outportb(0xD8, 0); // prepare to send 16-bit value dos_outportb(CountReg, ((count>>1)-1) & 0xff); dos_outportb(CountReg, ((count>>1)-1) >> 8); } dos_outportb(ClearReg, 0); // clear write mask dos_outportb(DisableReg, DmaChannel & ~4); } //============================================================================= // Starts the CODEC playing //============================================================================= static void GUS_StartCODEC(int count,BYTE FSVal) { int i,j; // Clear any pending IRQs dos_inportb(CodecStatus); dos_outportb(CodecStatus,0); // Set mode to 2 dos_outportb(CodecRegisterSelect,CODEC_MODE_AND_ID); dos_outportb(CodecData,0xC0); // Stop any playback or capture which may be happening dos_outportb(CodecRegisterSelect,CODEC_INTERFACE_CONFIG); dos_outportb(CodecData,dos_inportb(CodecData) & 0xFC); // Set FS dos_outportb(CodecRegisterSelect,CODEC_FS_FORMAT | 0x40); dos_outportb(CodecData,FSVal | 0x50); // Or in stereo and 16 bit bits // Wait a bit for (i=0;i<10;i++) dos_inportb(CodecData); // Routine 1 to counter CODEC bug - wait for init bit to clear and then a // bit longer (i=min loop count, j=timeout for (i=0,j=0;i<1000 && j<0x7FFFF;j++) if ((dos_inportb(CodecRegisterSelect) & 0x80)==0) i++; // Routine 2 to counter CODEC bug - this is from Forte's code. For me it // does not seem to cure the problem, but is added security // Waits till we can modify index register for (j=0;j<0x7FFFF;j++) { dos_outportb(CodecRegisterSelect,CODEC_INTERFACE_CONFIG | 0x40); if (dos_inportb(CodecRegisterSelect)==(CODEC_INTERFACE_CONFIG | 0x40)) break; } // Perform ACAL dos_outportb(CodecRegisterSelect,CODEC_INTERFACE_CONFIG | 0x40); dos_outportb(CodecData,0x08); // Clear MCE bit - this makes ACAL happen dos_outportb(CodecRegisterSelect,CODEC_INTERFACE_CONFIG); // Wait for ACAL to finish for (j=0;j<0x7FFFF;j++) { if ((dos_inportb(CodecRegisterSelect) & 0x80) != 0) continue; dos_outportb(CodecRegisterSelect,CODEC_ERROR_STATUS_AND_INIT); if ((dos_inportb(CodecData) & 0x20) == 0) break; } // Clear ACAL bit dos_outportb(CodecRegisterSelect,CODEC_INTERFACE_CONFIG | 0x40); dos_outportb(CodecData,0x00); dos_outportb(CodecRegisterSelect,CODEC_INTERFACE_CONFIG); // Set some other junk dos_outportb(CodecRegisterSelect,CODEC_LOOPBACK_CONTROL); dos_outportb(CodecData,0x00); dos_outportb(CodecRegisterSelect,CODEC_PIN_CONTROL); dos_outportb(CodecData,0x08); // IRQ is disabled in PIN control // Set count (it doesn't really matter what value we stuff in here dos_outportb(CodecRegisterSelect,CODEC_PLAYBACK_LOWER_BASE_COUNT); dos_outportb(CodecData,count & 0xFF); dos_outportb(CodecRegisterSelect,CODEC_PLAYBACK_UPPER_BASE_COUNT); dos_outportb(CodecData,count >> 8); // Start playback dos_outportb(CodecRegisterSelect,CODEC_INTERFACE_CONFIG); dos_outportb(CodecData,0x01); } //============================================================================= // Starts the GF1 playing //============================================================================= static void GUS_StartGf1(int count,BYTE Voices) { DWORD StartAddressL,EndAddressL,StartAddressR,EndAddressR; // Set number of voices to give us the sampling rate we want SetGf18(SET_VOICES,0xC0 | (Voices-1)); // Figure out addresses StartAddressL=ConvertTo16(0); EndAddressL=ConvertTo16(count-2-2); StartAddressR=ConvertTo16(2); EndAddressR=ConvertTo16(count-2); // Set left voice addresses dos_outportb(Gf1PageRegister,0); SetGf116(SET_START_LOW,StartAddressL<<9); SetGf116(SET_START_HIGH,StartAddressL>>7); SetGf116(SET_ACC_LOW,StartAddressL<<9); SetGf116(SET_ACC_HIGH,StartAddressL>>7); SetGf116(SET_END_LOW,EndAddressL<<9); SetGf116(SET_END_HIGH,EndAddressL>>7); // Set balance to full left SetGf18(SET_BALANCE,0); // Set volume to full SetGf116(SET_VOLUME,0xFFF0); // Set FC to 2 (so we play every second sample) SetGf116(SET_FREQUENCY,0x0800); // Set right voice addresses dos_outportb(Gf1PageRegister,1); SetGf116(SET_START_LOW,StartAddressR<<9); SetGf116(SET_START_HIGH,StartAddressR>>7); SetGf116(SET_ACC_LOW,StartAddressR<<9); SetGf116(SET_ACC_HIGH,StartAddressR>>7); SetGf116(SET_END_LOW,EndAddressR<<9); SetGf116(SET_END_HIGH,EndAddressR>>7); // Set balance to full right SetGf18(SET_BALANCE,15); // Set volume to full SetGf116(SET_VOLUME,0xFFF0); // Set FC to 2 (so we play every second sample) SetGf116(SET_FREQUENCY,0x0800); // Start voices dos_outportb(Gf1PageRegister,0); SetGf18(SET_CONTROL,0x0C); dos_outportb(Gf1PageRegister,1); SetGf18(SET_CONTROL,0x0C); Gf1Delay(); dos_outportb(Gf1PageRegister,0); SetGf18(SET_CONTROL,0x0C); dos_outportb(Gf1PageRegister,1); SetGf18(SET_CONTROL,0x0C); } //============================================================================= // Figures out what kind of UltraSound we have, if any, and starts it playing //============================================================================= qboolean GUS_Init(void) { int rc; int RealAddr; BYTE FSVal,Voices; struct CodecRateStruct *CodecRate; struct Gf1RateStruct *Gf1Rate; // See what kind of UltraSound we have, if any if (GUS_GetIWData()==false) if (GUS_GetMAXData()==false) if (GUS_GetGUSData()==false) return(false); shm = &sn; if (HaveCodec) { // do 11khz sampling rate unless command line parameter wants different shm->speed = 11025; FSVal = 0x03; rc = COM_CheckParm("-sspeed"); if (rc) { shm->speed = Q_atoi(com_argv[rc+1]); // Make sure rate not too high if (shm->speed>48000) shm->speed=48000; // Adjust speed to match one of the possible CODEC rates for (CodecRate=CodecRates;CodecRate->Rate!=0;CodecRate++) { if (shm->speed <= CodecRate->Rate) { shm->speed=CodecRate->Rate; FSVal=CodecRate->FSVal; break; } } } // Always do 16 bit stereo shm->channels = 2; shm->samplebits = 16; // allocate buffer twice the size we need so we can get aligned buffer dma_buffer = dos_getmemory(BUFFER_SIZE*2); if (dma_buffer==NULL) { Con_Printf("Couldn't allocate sound dma buffer"); return false; } RealAddr = ptr2real(dma_buffer); RealAddr = (RealAddr + BUFFER_SIZE) & ~(BUFFER_SIZE-1); dma_buffer = (short *) real2ptr(RealAddr); // Zero off DMA buffer memset(dma_buffer, 0, BUFFER_SIZE); shm->soundalive = true; shm->splitbuffer = false; shm->samplepos = 0; shm->submission_chunk = 1; shm->buffer = (unsigned char *) dma_buffer; shm->samples = BUFFER_SIZE/(shm->samplebits/8); GUS_StartDMA(DmaChannel,dma_buffer,BUFFER_SIZE); GUS_StartCODEC(BUFFER_SIZE,FSVal); } else { // do 19khz sampling rate unless command line parameter wants different shm->speed = 19293; Voices=32; rc = COM_CheckParm("-sspeed"); if (rc) { shm->speed = Q_atoi(com_argv[rc+1]); // Make sure rate not too high if (shm->speed>44100) shm->speed=44100; // Adjust speed to match one of the possible GF1 rates for (Gf1Rate=Gf1Rates;Gf1Rate->Rate!=0;Gf1Rate++) { if (shm->speed <= Gf1Rate->Rate) { shm->speed=Gf1Rate->Rate; Voices=Gf1Rate->Voices; break; } } } // Always do 16 bit stereo shm->channels = 2; shm->samplebits = 16; // allocate buffer twice the size we need so we can get aligned buffer dma_buffer = dos_getmemory(BUFFER_SIZE*2); if (dma_buffer==NULL) { Con_Printf("Couldn't allocate sound dma buffer"); return false; } RealAddr = ptr2real(dma_buffer); RealAddr = (RealAddr + BUFFER_SIZE) & ~(BUFFER_SIZE-1); dma_buffer = (short *) real2ptr(RealAddr); // Zero off DMA buffer memset(dma_buffer, 0, BUFFER_SIZE); shm->soundalive = true; shm->splitbuffer = false; shm->samplepos = 0; shm->submission_chunk = 1; shm->buffer = (unsigned char *) dma_buffer; shm->samples = BUFFER_SIZE/(shm->samplebits/8); GUS_StartDMA(DmaChannel,dma_buffer,BUFFER_SIZE); SetGf116(SET_DMA_ADDRESS,0x0000); if (DmaChannel<=3) SetGf18(DMA_CONTROL,0x41); else SetGf18(DMA_CONTROL,0x45); GUS_StartGf1(BUFFER_SIZE,Voices); } return(true); } //============================================================================= // Returns the current playback position //============================================================================= int GUS_GetDMAPos(void) { int count; if (HaveCodec) { // clear 16-bit reg flip-flop // load the current dma count register if (DmaChannel < 4) { dos_outportb(0x0C, 0); count = dos_inportb(CountReg); count += dos_inportb(CountReg) << 8; if (shm->samplebits == 16) count /= 2; count = shm->samples - (count+1); } else { dos_outportb(0xD8, 0); count = dos_inportb(CountReg); count += dos_inportb(CountReg) << 8; if (shm->samplebits == 8) count *= 2; count = shm->samples - (count+1); } } else { // Read current position from GF1 dos_outportb(Gf1PageRegister,0); count=(GetGf116(GET_ACC_HIGH)<<7) & 0xFFFF; // See which half of buffer we are in. Note that since this is 16 bit // data we are playing, position is in 16 bit samples if (GetGf18(DMA_CONTROL) & 0x40) { GUS_StartDMA(DmaChannel,dma_buffer,BUFFER_SIZE); SetGf116(SET_DMA_ADDRESS,0x0000); if (DmaChannel<=3) SetGf18(DMA_CONTROL,0x41); else SetGf18(DMA_CONTROL,0x45); } } shm->samplepos = count & (shm->samples-1); return(shm->samplepos); } //============================================================================= // Stops the UltraSound playback //============================================================================= void GUS_Shutdown (void) { if (HaveCodec) { // Stop CODEC dos_outportb(CodecRegisterSelect,CODEC_INTERFACE_CONFIG); dos_outportb(CodecData,0x01); } else { // Stop Voices dos_outportb(Gf1PageRegister,0); SetGf18(SET_CONTROL,0x03); dos_outportb(Gf1PageRegister,1); SetGf18(SET_CONTROL,0x03); Gf1Delay(); dos_outportb(Gf1PageRegister,0); SetGf18(SET_CONTROL,0x03); dos_outportb(Gf1PageRegister,1); SetGf18(SET_CONTROL,0x03); // Stop any DMA SetGf18(DMA_CONTROL,0x00); GetGf18(DMA_CONTROL); } dos_outportb(DisableReg, DmaChannel | 4); // disable dma channel }