/* | |
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 | |
} |