/* snd_gus.c @description@ 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: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA $Id$ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "dosisms.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 (strcasecmp (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 (strcasecmp (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 (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 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); return (Qclose (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 (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; 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]; 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 }