/*
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 <dos.h>
#include "quakedef.h"
#include "dosisms.h"

//=============================================================================
// Author(s): Jayeson Lee-Steere

#define INI_STRING_SIZE 0x100

static FILE *ini_fopen(const char *filename, const char *modes);
static int ini_fclose(FILE *f);
static 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];
static byte	extVoices,extCodecVoices; /* FS: GUS clicking sounds during map transitions and pauseing fix */

//***************************************************************************
// Internal routines
//***************************************************************************
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 (Q_toupper(s[0])!=Q_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 (Q_toupper(s[0])!=Q_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);
}

// Opens an .INI file. Works like fopen
static FILE *ini_fopen(const char *filename, const char *modes)
{
	return(fopen(filename,modes));
}

// Closes a .INI file. Works like fclose
static 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="";
static 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 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;
};

//=============================================================================
// GUS-only variables
//=============================================================================

static short *dma_buffer = NULL;
static void  *dma_dosadr = NULL; // sezero: SND_RESTART FIX

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
//=============================================================================
static void SetGf18(BYTE reg,BYTE data)
{
	dos_outportb(Gf1RegisterSelect,reg);
	dos_outportb(Gf1DataHigh,data);
}

static void SetGf116(BYTE reg,WORD data)
{
	dos_outportb(Gf1RegisterSelect,reg);
	dos_outportw(Gf1DataLow,data);
}

static BYTE GetGf18(BYTE reg)
{
	dos_outportb(Gf1RegisterSelect,reg);
	return(dos_inportb(Gf1DataHigh));
}

static WORD GetGf116(BYTE reg)
{
	dos_outportb(Gf1RegisterSelect,reg);
	return(dos_inportw(Gf1DataLow));
}

static void Gf1Delay(void)
{
	int i;

	for (i=0;i<27;i++)
		dos_inportb(Gf1TimerControl);
}

static DWORD ConvertTo16(DWORD Address)
{
	return( ((Address>>1) & 0x0001FFFF) | (Address & 0x000C0000L) );
}

static 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("\x02Sound Card is UltraSound PnP\nIf you experience garbage sound run\nULTRAMID.EXE\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("\x02Sound Card is UltraSound MAX\nIf you experience garbage sound run\nULTRAMID.EXE\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);
		SetGf18(ADLIB_TIMER1,0);
		SetGf116(ADLIB_TIMER1,0);
		SetGf18(ADLIB_TIMER2,0xFF);
		SetGf116(ADLIB_TIMER2,0xFF);		
	}

	// 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("\x02Sound Card is UltraSound\nIf you experience garbage sound run\nULTRAMID.EXE\n");
	return(true);
}


//=============================================================================
// Programs the DMA controller to start DMAing in Auto-init mode
//=============================================================================
static void GUS_StartDMA(BYTE DmaChannel,short *dma_buff,int count)
{
	int mode;
	int RealAddr;

	RealAddr = ptr2real(dma_buff); // sezero

	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;

	if (COM_CheckParm("-nogus"))
		return false; /* FS: Disables GUS, useful if you want to use SB16 instead.  Thanks to sezero for oneliner. */

	// 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) /* FS: Mythical Gravis UltraSound MAX with Crystal CS4231 CODEC chip or an AMD Interwave/Gravis UltraSound PnP */
	{
		// do 11khz sampling rate unless command line parameter wants different
		shm->speed = 11025;
		FSVal = extCodecVoices = 0x03; /* FS: Added extCodecVoices */
		rc = COM_CheckParm("-sspeed");

		if (s_khz.value >= 22050) /* FS: S_KHZ */
		{
			shm->speed = s_khz.value;

			// 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=extCodecVoices=CodecRate->FSVal; /* FS: Added extCodecVoices */
					break;
				}
			}
		}

		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=extCodecVoices=CodecRate->FSVal; /* FS: Added extCodecVoices */
					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_dosadr = dos_getmemory(SND_BUFFER_SIZE*2);
		if (dma_dosadr==NULL)  // sezero
		{
			Con_Printf("Couldn't allocate sound dma buffer");
			return false;
		}

		RealAddr = ptr2real(dma_dosadr);
		RealAddr = (RealAddr + SND_BUFFER_SIZE) & ~(SND_BUFFER_SIZE-1);
		dma_buffer = (short *) real2ptr(RealAddr);

		// Zero off DMA buffer
		memset(dma_buffer, 0, SND_BUFFER_SIZE);

		shm->soundalive = true;
		shm->splitbuffer = false;

		shm->samplepos = 0;
		shm->submission_chunk = 1;
		shm->buffer = (unsigned char *) dma_buffer;
		shm->samples = SND_BUFFER_SIZE/(shm->samplebits/8);

		GUS_StartDMA(DmaChannel,dma_buffer,SND_BUFFER_SIZE);
		GUS_StartCODEC(SND_BUFFER_SIZE,FSVal);
		havegus = GUS_MAXPNP; /* FS: GUS MAX/IWPNP */
	}
	else /* FS: No CODEC?  You must have a Gravis UltraSound "Classic", ACE/SoundBuddy or compatible OEM CLONE! */
	{
		// do 19khz sampling rate unless command line parameter wants different
		shm->speed = 19293; /* FS: Maximum kHZ at 32 Voices.  14 voices gets you 44kHZ. */
		Voices=extVoices=32; /* FS: Added extVoices */
		rc = COM_CheckParm("-sspeed");

		if (s_khz.value >= 19293) /* FS: S_KHZ */
		{
			shm->speed = s_khz.value;

			// 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=extVoices=Gf1Rate->Voices; /* FS: Added extVoices */
					break;
				}
			}
		}

		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=extVoices=Gf1Rate->Voices; /* FS: Added extVoices */
					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_dosadr = dos_getmemory(SND_BUFFER_SIZE*2);
		if (dma_dosadr==NULL)
		{
			Con_Printf("Couldn't allocate sound dma buffer");
			return false;
		}

		RealAddr = ptr2real(dma_dosadr);
		RealAddr = (RealAddr + SND_BUFFER_SIZE) & ~(SND_BUFFER_SIZE-1);
		dma_buffer = (short *) real2ptr(RealAddr);

		// Zero off DMA buffer
		memset(dma_buffer, 0, SND_BUFFER_SIZE);

		shm->soundalive = true;
		shm->splitbuffer = false;

		shm->samplepos = 0;
		shm->submission_chunk = 1;
		shm->buffer = (unsigned char *) dma_buffer;
		shm->samples = SND_BUFFER_SIZE/(shm->samplebits/8);

		GUS_StartDMA(DmaChannel,dma_buffer,SND_BUFFER_SIZE);
		SetGf116(SET_DMA_ADDRESS,0x0000);
		if (DmaChannel<=3)
			SetGf18(DMA_CONTROL,0x41);
		else
			SetGf18(DMA_CONTROL,0x45);
		GUS_StartGf1(SND_BUFFER_SIZE,Voices);
		havegus = GUS_CLASSIC; /* FS: Classic GUS */

	}

	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,SND_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

	dos_freememory (dma_dosadr);
	dma_dosadr = NULL; // sezero
}

void GUS_ClearDMA (void) /* FS: This stops the constant clicking sound during map loads and pause screens */
{
	// Zero off DMA buffer
	memset(dma_buffer, 0, SND_BUFFER_SIZE);

	shm->soundalive = true;
	shm->splitbuffer = false;

	shm->samplepos = 0;
	shm->submission_chunk = 1;
	shm->buffer = (unsigned char *) dma_buffer;
	shm->samples = SND_BUFFER_SIZE/(shm->samplebits/8);

	if (HaveCodec) /* FS: GUS MAX, AMD InterWave/GUS PnP */
	{
		// Stop CODEC
		dos_outportb(CodecRegisterSelect,CODEC_INTERFACE_CONFIG);
		dos_outportb(CodecData,0x01);
	}
	else /* FS: GUS "Classic", ACE, or compatible OEM CLONE */
	{
		// 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);
	}

	GUS_StartDMA(DmaChannel,dma_buffer,SND_BUFFER_SIZE);

	if (HaveCodec) /* FS: GUS MAX, AMD InterWave/GUS PnP */
	{
		GUS_StartCODEC(SND_BUFFER_SIZE,extCodecVoices);
	}
	else /* FS: GUS "Classic", ACE, or compatible OEM CLONE */
	{
		SetGf116(SET_DMA_ADDRESS,0x0000);

		if (DmaChannel<=3)
			SetGf18(DMA_CONTROL,0x41);
		else
			SetGf18(DMA_CONTROL,0x45);

		GUS_StartGf1(SND_BUFFER_SIZE,extVoices);
	}

	Con_DPrintf(DEVELOPER_MSG_SOUND, "Cleared GUS DMA Buffer!\n");
}

