quakeforge-old/common/snd_gus.c
2000-08-20 13:33:48 +00:00

1341 lines
32 KiB
C

/*
snd_gus.c
(description)
Copyright (C) 1996-1997 Id Software, Inc.
Copyright (C) 1999,2000 contributors of the QuakeForge project
Please see the file "AUTHORS" for a list of contributors
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$
*/
//=============================================================================
// Routines for GUS support in QUAKE
//
// Author(s): Jayeson Lee-Steere
//=============================================================================
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dosisms.h>
#include <qtypes.h>
#include <sound.h>
#include <console.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;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, QFile *f )
{
int i=0;
if (Qgets(f, s, n)==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(Qopen(filename,modes));
}
// Closes a .INI file. Works like fclose
int
ini_fclose ( QFile *f )
{
if (f==current_file)
reset_buffer(NULL);
Qclose(f);
return 0;
}
// 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<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;
Qseek(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 (Qeof(f))
/*rewind(f);*/
Qseek(f, 0, SEEK_SET);
start_pos=Qtell(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,Qtell(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 (Qeof(f))
/*rewind(f);*/
Qseek(f, 0, SEEK_SET);
if (Qtell(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)
{
Qseek(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 (Qeof(f))
Qseek(f,section_buffers[current_section].offset,SEEK_SET);
start_pos=Qtell(f);
while (1)
{
string_start_pos=Qtell(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 (Qeof(f) || is_section(ts,"*"))
Qseek(f,section_buffers[current_section].offset,SEEK_SET);
if (Qtell(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];
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
}