aboutsummaryrefslogtreecommitdiffstats
path: root/WinQuake/snd_gus.c
diff options
context:
space:
mode:
Diffstat (limited to 'WinQuake/snd_gus.c')
-rw-r--r--WinQuake/snd_gus.c1293
1 files changed, 1293 insertions, 0 deletions
diff --git a/WinQuake/snd_gus.c b/WinQuake/snd_gus.c
new file mode 100644
index 0000000..dd1376f
--- /dev/null
+++ b/WinQuake/snd_gus.c
@@ -0,0 +1,1293 @@
+/*
+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 the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+//=============================================================================
+// Routines for GUS support in QUAKE
+//
+// Author(s): Jayeson Lee-Steere
+//=============================================================================
+
+#include "quakedef.h"
+#include "dosisms.h"
+
+//=============================================================================
+// Author(s): Jayeson Lee-Steere
+
+#define INI_STRING_SIZE 0x100
+
+FILE *ini_fopen(const char *filename, const char *modes);
+int ini_fclose(FILE *f);
+void ini_fgets(FILE *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 FILE *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(FILE *f)
+{
+ int i;
+
+ for (i=0;i<NUM_SECTION_BUFFERS;i++)
+ section_buffers[i].name[0]=0;
+ for (i=0;i<NUM_FIELD_BUFFERS;i++)
+ field_buffers[i].name[0]=0;
+
+ current_file=f;
+}
+
+// Sees if the current string is section "name" (i.e. ["name"]).
+// If "name"=="*", sees if the current string is any section
+// (i.e. [....]). Returns 1 if true else 0 if false.
+static int is_section(char *s,const char *name)
+{
+ int wild=0;
+
+ // See if wild search
+ if (strcmp("*",name)==0)
+ wild=1;
+
+ // Skip leading spaces
+ while (s[0]==' ')
+ s++;
+ // Look for leading "["
+ if (s[0]!='[')
+ return(0);
+ s++;
+ // Make sure name matches
+ while (s[0]!=']' && s[0]!=13 && s[0]!=10 && s[0]!=0 && name[0]!=0)
+ {
+ if (!wild)
+ if (toupper(s[0])!=toupper(name[0]))
+ return(0);
+ s++;
+ if (!wild)
+ name++;
+ }
+ if (!wild)
+ if (name[0]!=0)
+ return(0);
+ // Skip trailing spaces
+ while (s[0]==' ')
+ s++;
+ // Make sure we have trailing "]"
+ if (s[0]!=']')
+ return(0);
+ return(1);
+}
+
+// Sees if the current string is field "name" (i.e. "name"=...).
+// If "name"=="*", sees if the current string is any field
+// (i.e. ...=...). Returns 1 if true else 0 if false.
+static int is_field(char *s,const char *name)
+{
+ int wild=0;
+
+ // See if wild search
+ if (strcmp("*",name)==0)
+ wild=1;
+
+ // Skip leading spaces
+ while (s[0]==' ')
+ s++;
+
+ // Make sure name matches
+ while (s[0]!='=' && s[0]!=13 && s[0]!=10 && s[0]!=0 && name[0]!=0)
+ {
+ if (!wild)
+ if (toupper(s[0])!=toupper(name[0]))
+ return(0);
+ s++;
+ if (!wild)
+ name++;
+ }
+ if (!wild)
+ if (name[0]!=0)
+ return(0);
+ // Skip trailing spaces
+ while (s[0]==' ')
+ s++;
+ // Make sure we have an "="
+ if (s[0]!='=')
+ return(0);
+
+ return(1);
+}
+
+// Extracts the section name from a section heading
+// e.g. in="[hey man]" gives out="hey man"
+static void get_section_name(char *out, char *in)
+{
+ int i=0;
+
+ // Skip spaces before '['
+ while (in[0]==' ')
+ in++;
+ // Make sure there is a '['
+ if (in[0]!='[')
+ {
+ out[0]=0;
+ return;
+ }
+ // Skip past '['
+ in++;
+ // Copy string if any to output string.
+ while (in[0]!=']' && in[0]!=13 && in[0]!=10 && in[0]!=0)
+ {
+ if (i<MAX_SECTION_WIDTH)
+ {
+ out[i]=in[0];
+ i++;
+ }
+ in++;
+ }
+ // Make sure string was terminated with ']'
+ if (in[0]!=']')
+ {
+ out[0]=0;
+ return;
+ }
+ // Remove trailing spaces
+ while (i>0 && 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 (i<MAX_FIELD_WIDTH)
+ {
+ out[i]=in[0];
+ i++;
+ }
+ in++;
+ }
+ // Make sure we stopped on "="
+ if (in[0]!='=')
+ {
+ out[0]=0;
+ return;
+ }
+ // Remove trailing spaces
+ while (i>0 && out[i-1]==' ')
+ i--;
+ // Null terminate the output string.
+ out[i]=0;
+}
+
+// Returns the field data from string s.
+// e.g. in="wally = golly man" gives out="golly man"
+static void get_field_string(char *out, char *in)
+{
+ int i=0;
+
+ // Find '=' if it exists
+ while (in[0]!='=' && in[0]!=13 && in[0]!=10 && in[0]!=0)
+ in++;
+ // If there is an '=', skip past it.
+ if (in[0]=='=')
+ in++;
+ // Skip any spaces between the '=' and string.
+ while (in[0]==' ' || in[0]=='[')
+ in++;
+ // Copy string, if there is one, to the output string.
+ while (in[0]!=13 && in[0]!=10 && in[0]!=0 && i<(INI_STRING_SIZE-1))
+ {
+ out[i]=in[0];
+ in++;
+ i++;
+ }
+ // Null terminate the output string.
+ out[i]=0;
+}
+
+// Adds a section to the buffer
+static int add_section(char *instring, long offset)
+{
+ int i;
+ char section[MAX_SECTION_WIDTH+1];
+
+ // Extract section name
+ get_section_name(section,instring);
+ // See if section already exists.
+ for (i=0;i<NUM_SECTION_BUFFERS;i++)
+ if (stricmp(section,section_buffers[i].name)==0)
+ return(i);
+ // Increment current_section_buffer
+ current_section_buffer++;
+ if (current_section_buffer>NUM_SECTION_BUFFERS)
+ current_section_buffer=0;
+ // Delete any field buffers that correspond to this section
+ for (i=0;i<NUM_FIELD_BUFFERS;i++)
+ if (field_buffers[i].section==current_section_buffer)
+ field_buffers[i].name[0]=0;
+ // Set buffer information
+ strcpy(section_buffers[current_section_buffer].name,section);
+ section_buffers[current_section_buffer].offset=offset;
+ return(current_section_buffer);
+}
+
+// Adds a field to the buffer
+static void add_field(char *instring, int section, long offset)
+{
+ int i;
+ char field[MAX_FIELD_WIDTH+1];
+
+ // Extract field name
+ get_field_name(field,instring);
+ // See if field already exists
+ for (i=0;i<NUM_FIELD_BUFFERS;i++)
+ if (field_buffers[i].section==section)
+ if (stricmp(field_buffers[i].name,field)==0)
+ return;
+ // Increment current_field_buffer
+ current_field_buffer++;
+ if (current_field_buffer>NUM_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, FILE *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
+FILE *ini_fopen(const char *filename, const char *modes)
+{
+ return(fopen(filename,modes));
+}
+
+// Closes a .INI file. Works like fclose
+int ini_fclose(FILE *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(FILE *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<NUM_SECTION_BUFFERS;i++)
+ if (strnicmp(section_buffers[i].name,section,MAX_SECTION_WIDTH)==0)
+ break;
+
+ // If section is in buffer, seek to it if necessary
+ if (i<NUM_SECTION_BUFFERS)
+ {
+ if (i!=current_section)
+ {
+ current_section=i;
+ fseek(f,section_buffers[i].offset,SEEK_SET);
+ }
+ }
+ // else look through .ini file for it.
+ else
+ {
+ // Make sure we are not at eof or this will cause trouble.
+ if (feof(f))
+ rewind(f);
+ start_pos=ftell(f);
+ while (1)
+ {
+ stripped_fgets(ts,INI_STRING_SIZE*2,f);
+ // If it is a section, add it to the section buffer
+ if (is_section(ts,"*"))
+ current_section=add_section(ts,ftell(f));
+ // If it is the section we are looking for, break.
+ if (is_section(ts,section))
+ break;
+ // If we reach the end of the file, rewind to the start.
+ if (feof(f))
+ rewind(f);
+ if (ftell(f)==start_pos)
+ return;
+ }
+ }
+
+ // See if field is in buffer
+ for (i=0;i<NUM_FIELD_BUFFERS;i++)
+ if (field_buffers[i].section==current_section)
+ if (strnicmp(field_buffers[i].name,field,MAX_FIELD_WIDTH)==0)
+ break;
+
+ // If field is in buffer, seek to it and read it
+ if (i<NUM_FIELD_BUFFERS)
+ {
+ fseek(f,field_buffers[i].offset,SEEK_SET);
+ stripped_fgets(ts,INI_STRING_SIZE*2,f);
+ get_field_string(s,ts);
+ }
+ else
+ // else search through section for field.
+ {
+ // Make sure we do not start at eof or this will cause problems.
+ if (feof(f))
+ fseek(f,section_buffers[current_section].offset,SEEK_SET);
+ start_pos=ftell(f);
+ while (1)
+ {
+ string_start_pos=ftell(f);
+ stripped_fgets(ts,INI_STRING_SIZE*2,f);
+ // If it is a field, add it to the buffer
+ if (is_field(ts,"*"))
+ add_field(ts,current_section,string_start_pos);
+ // If it is the field we are looking for, save it
+ if (is_field(ts,field))
+ {
+ get_field_string(s,ts);
+ break;
+ }
+ // If we reach the end of the section, start over
+ if (feof(f) || is_section(ts,"*"))
+ fseek(f,section_buffers[current_section].offset,SEEK_SET);
+ if (ftell(f)==start_pos)
+ return;
+ }
+ }
+}
+
+//=============================================================================
+
+#define BYTE unsigned char
+#define WORD unsigned short
+#define DWORD unsigned long
+
+#define BUFFER_SIZE 4096
+
+#define CODEC_ADC_INPUT_CONTROL_LEFT 0x00
+#define CODEC_ADC_INPUT_CONTROL_RIGHT 0x01
+#define CODEC_AUX1_INPUT_CONTROL_LEFT 0x02
+#define CODEC_AUX1_INPUT_CONTROL_RIGHT 0x03
+#define CODEC_AUX2_INPUT_CONTROL_LEFT 0x04
+#define CODEC_AUX2_INPUT_CONTROL_RIGHT 0x05
+#define CODEC_DAC_OUTPUT_CONTROL_LEFT 0x06
+#define CODEC_DAC_OUTPUT_CONTROL_RIGHT 0x07
+#define CODEC_FS_FORMAT 0x08
+#define CODEC_INTERFACE_CONFIG 0x09
+#define CODEC_PIN_CONTROL 0x0A
+#define CODEC_ERROR_STATUS_AND_INIT 0x0B
+#define CODEC_MODE_AND_ID 0x0C
+#define CODEC_LOOPBACK_CONTROL 0x0D
+#define CODEC_PLAYBACK_UPPER_BASE_COUNT 0x0E
+#define CODEC_PLAYBACK_LOWER_BASE_COUNT 0x0F
+
+#define SET_CONTROL 0x00
+#define SET_FREQUENCY 0x01
+#define SET_START_HIGH 0x02
+#define SET_START_LOW 0x03
+#define SET_END_HIGH 0x04
+#define SET_END_LOW 0x05
+#define SET_VOLUME_RATE 0x06
+#define SET_VOLUME_START 0x07
+#define SET_VOLUME_END 0x08
+#define SET_CURR_VOLUME 0x09
+#define SET_VOLUME 0x09
+#define SET_ACC_HIGH 0x0A
+#define SET_ACC_LOW 0x0B
+#define SET_BALANCE 0x0C
+#define SET_VOLUME_CONTROL 0x0D
+#define SET_VOICES 0x0E
+
+#define DMA_CONTROL 0x41
+#define SET_DMA_ADDRESS 0x42
+#define SET_DRAM_LOW 0x43
+#define SET_DRAM_HIGH 0x44
+#define ADLIB_CONTROL 0x45
+#define ADLIB_TIMER1 0x46
+#define ADLIB_TIMER2 0x47
+#define SET_RECORD_RATE 0x48
+#define RECORD_CONTROL 0x49
+#define SET_JOYSTICK 0x4B
+#define MASTER_RESET 0x4C
+
+#define GET_CONTROL 0x80
+#define GET_FREQUENCY 0x81
+#define GET_START_HIGH 0x82
+#define GET_START_LOW 0x83
+#define GET_END_HIGH 0x84
+#define GET_END_LOW 0x85
+#define GET_VOLUME_RATE 0x86
+#define GET_VOLUME_START 0x87
+#define GET_VOLUME_END 0x88
+#define GET_VOLUME 0x89
+#define GET_ACC_HIGH 0x8A
+#define GET_ACC_LOW 0x8B
+#define GET_BALANCE 0x8C
+#define GET_VOLUME_CONTROL 0x8D
+#define GET_VOICES 0x8E
+#define GET_IRQV 0x8F
+
+struct CodecRateStruct
+{
+ WORD Rate;
+ BYTE FSVal;
+};
+
+struct Gf1RateStruct
+{
+ WORD Rate;
+ BYTE Voices;
+};
+
+//=============================================================================
+// Reference variables in SND_DOS.C
+//=============================================================================
+extern short *dma_buffer;
+
+//=============================================================================
+// GUS-only variables
+//=============================================================================
+static BYTE HaveCodec=0;
+
+static WORD CodecRegisterSelect;
+static WORD CodecData;
+static WORD CodecStatus;
+static WORD Gf1TimerControl;
+static WORD Gf1PageRegister;
+static WORD Gf1RegisterSelect;
+static WORD Gf1DataLow;
+static WORD Gf1DataHigh;
+
+static BYTE DmaChannel;
+
+static BYTE PageRegs[] = { 0x87, 0x83, 0x81, 0x82, 0x8f, 0x8b, 0x89, 0x8a };
+static BYTE AddrRegs[] = { 0, 2, 4, 6, 0xc0, 0xc4, 0xc8, 0xcc };
+static BYTE CountRegs[] = { 1, 3, 5, 7, 0xc2, 0xc6, 0xca, 0xce };
+
+static WORD AddrReg;
+static WORD CountReg;
+static WORD ModeReg;
+static WORD DisableReg;
+static WORD ClearReg;
+
+static struct CodecRateStruct CodecRates[]=
+{
+ { 5512,0x01},
+ { 6620,0x0F},
+ { 8000,0x00},
+ { 9600,0x0E},
+ {11025,0x03},
+ {16000,0x02},
+ {18900,0x05},
+ {22050,0x07},
+ {27420,0x04},
+ {32000,0x06},
+ {33075,0x0D},
+ {37800,0x09},
+ {44100,0x0B},
+ {48000,0x0C},
+ { 0,0x00} // End marker
+};
+
+static struct Gf1RateStruct Gf1Rates[]=
+{
+ {19293,32},
+ {19916,31},
+ {20580,30},
+ {21289,29},
+ {22050,28},
+ {22866,27},
+ {23746,26},
+ {24696,25},
+ {25725,24},
+ {26843,23},
+ {28063,22},
+ {29400,21},
+ {30870,20},
+ {32494,19},
+ {34300,18},
+ {36317,17},
+ {38587,16},
+ {41160,15},
+ {44100,14},
+ {0,0}
+};
+
+//=============================================================================
+// Basic GF1 functions
+//=============================================================================
+void SetGf18(BYTE reg,BYTE data)
+{
+ dos_outportb(Gf1RegisterSelect,reg);
+ dos_outportb(Gf1DataHigh,data);
+}
+
+void SetGf116(BYTE reg,WORD data)
+{
+ dos_outportb(Gf1RegisterSelect,reg);
+ dos_outportw(Gf1DataLow,data);
+}
+
+BYTE GetGf18(BYTE reg)
+{
+ dos_outportb(Gf1RegisterSelect,reg);
+ return(dos_inportb(Gf1DataHigh));
+}
+
+WORD GetGf116(BYTE reg)
+{
+ dos_outportb(Gf1RegisterSelect,reg);
+ return(dos_inportw(Gf1DataLow));
+}
+
+void Gf1Delay(void)
+{
+ int i;
+
+ for (i=0;i<27;i++)
+ dos_inportb(Gf1TimerControl);
+}
+
+DWORD ConvertTo16(DWORD Address)
+{
+ return( ((Address>>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];
+ FILE *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
+}