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