added an old version of mikmod for dos
[dosdemo] / libs / oldmik / src / drv_gus.c
diff --git a/libs/oldmik/src/drv_gus.c b/libs/oldmik/src/drv_gus.c
new file mode 100644 (file)
index 0000000..7d65432
--- /dev/null
@@ -0,0 +1,1985 @@
+/*\r
+\r
+Name:\r
+DRV_GUS.C\r
+\r
+Description:\r
+Mikmod driver for output on Gravis Ultrasound (native mode i.e. using\r
+the onboard DRAM)\r
+\r
+Portability:\r
+\r
+MSDOS: BC(y)   Watcom(y)       DJGPP(y)\r
+Win95: n\r
+Os2:   n\r
+Linux: n\r
+\r
+(y) - yes\r
+(n) - no (not possible or not useful)\r
+(?) - may be possible, but not tested\r
+\r
+*/\r
+#include <dos.h>\r
+#include <stdio.h>\r
+#include <stdlib.h>\r
+#include <conio.h>\r
+#include "mikmod.h"\r
+#include "mirq.h"\r
+\r
+/***************************************************************************\r
+>>>>>>>>>>>>>>>>>>>>>>>>> Lowlevel GUS defines <<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
+***************************************************************************/\r
+\r
+/* Special macros for Least-most sig. bytes */\r
+#define MAKE_MSW(x)     ((long)((long)(x)) << 16)\r
+#define LSW(x)          ((unsigned int)(x))\r
+#define MSW(x)          ((unsigned int)(((long)x)>>16))\r
+#define MSB(x)          (unsigned char)((unsigned int)(x)>>8)\r
+#define LSB(x)          ((unsigned char)(x))\r
+\r
+/* Make GF1 address for direct chip i/o. */\r
+#define ADDR_HIGH(x) ((unsigned int)((unsigned int)((x>>7L)&0x1fffL)))\r
+#define ADDR_LOW(x)  ((unsigned int)((unsigned int)((x&0x7fL)<<9L)))\r
+\r
+#define JOYSTICK_TIMER  (GUS_PORT+0x201)                /* 201 */\r
+#define JOYSTICK_DATA   (GUS_PORT+0x201)                /* 201 */\r
+\r
+#define GF1_MIDI_CTRL   (GUS_PORT+0x100)                /* 3X0 */\r
+#define GF1_MIDI_DATA   (GUS_PORT+0x101)                /* 3X1 */\r
+\r
+#define GF1_PAGE        (GUS_PORT+0x102)                /* 3X2 */\r
+#define GF1_REG_SELECT  (GUS_PORT+0x103)                /* 3X3 */\r
+#define GF1_VOICE_SELECT (GUS_PORT+0x102)               /* 3X3 */\r
+#define GF1_DATA_LOW    (GUS_PORT+0x104)                /* 3X4 */\r
+#define GF1_DATA_HI     (GUS_PORT+0x105)                /* 3X5 */\r
+#define GF1_IRQ_STAT    (GUS_PORT+0x006)                /* 2X6 */\r
+#define GF1_DRAM        (GUS_PORT+0x107)                /* 3X7 */\r
+\r
+#define GF1_MIX_CTRL    (GUS_PORT+0x000)                /* 2X0 */\r
+#define GF1_TIMER_CTRL  (GUS_PORT+0x008)                /* 2X8 */\r
+#define GF1_TIMER_DATA  (GUS_PORT+0x009)                /* 2X9 */\r
+#define GF1_IRQ_CTRL    (GUS_PORT+0x00B)                /* 2XB */\r
+\r
+/* The GF1 Hardware clock. */\r
+#define CLOCK_RATE              9878400L\r
+\r
+/* Mixer control bits. */\r
+#define ENABLE_LINE             0x01\r
+#define ENABLE_DAC              0x02\r
+#define ENABLE_MIC              0x04\r
+\r
+/* interrupt controller 1 */\r
+#define CNTRL_8259              0x21\r
+#define OCR_8259                0x20\r
+#define EOI                     0x20\r
+#define REARM3                  0x2F3\r
+#define REARM5                  0x2F5\r
+\r
+/* interrupt controller 2 */\r
+#define CNTRL_M_8259                   0x21\r
+#define CNTRL_M2_8259                          0xA1\r
+#define OCR_2_8259              0xA0\r
+\r
+#define DMA_CONTROL             0x41\r
+#define SET_DMA_ADDRESS         0x42\r
+#define SET_DRAM_LOW            0x43\r
+#define SET_DRAM_HIGH           0x44\r
+\r
+#define TIMER_CONTROL           0x45\r
+#define TIMER1                  0x46\r
+#define TIMER2                  0x47\r
+\r
+#define SET_SAMPLE_RATE         0x48\r
+#define SAMPLE_CONTROL          0x49\r
+\r
+#define SET_JOYSTICK            0x4B\r
+#define MASTER_RESET            0x4C\r
+\r
+/* Voice register mapping. */\r
+#define SET_CONTROL                     0x00\r
+#define SET_FREQUENCY           0x01\r
+#define SET_START_HIGH          0x02\r
+#define SET_START_LOW           0x03\r
+#define SET_END_HIGH            0x04\r
+#define SET_END_LOW                     0x05\r
+#define SET_VOLUME_RATE         0x06\r
+#define SET_VOLUME_START        0x07\r
+#define SET_VOLUME_END          0x08\r
+#define SET_VOLUME                      0x09\r
+#define SET_ACC_HIGH            0x0a\r
+#define SET_ACC_LOW                     0x0b\r
+#define SET_BALANCE                     0x0c\r
+#define SET_VOLUME_CONTROL      0x0d\r
+#define SET_VOICES                      0x0e\r
+\r
+#define GET_CONTROL                     0x80\r
+#define GET_FREQUENCY           0x81\r
+#define GET_START_HIGH          0x82\r
+#define GET_START_LOW           0x83\r
+#define GET_END_HIGH            0x84\r
+#define GET_END_LOW                     0x85\r
+#define GET_VOLUME_RATE         0x86\r
+#define GET_VOLUME_START        0x87\r
+#define GET_VOLUME_END          0x88\r
+#define GET_VOLUME                      0x89\r
+#define GET_ACC_HIGH            0x8a\r
+#define GET_ACC_LOW                     0x8b\r
+#define GET_BALANCE                     0x8c\r
+#define GET_VOLUME_CONTROL      0x8d\r
+#define GET_VOICES                      0x8e\r
+#define GET_IRQV                        0x8f\r
+\r
+/********************************************************************\r
+ *\r
+ * MIDI defines\r
+ *\r
+ *******************************************************************/\r
+\r
+#define MIDI_RESET      0x03\r
+#define MIDI_ENABLE_XMIT        0x20\r
+#define MIDI_ENABLE_RCV         0x80\r
+\r
+#define MIDI_RCV_FULL           0x01\r
+#define MIDI_XMIT_EMPTY         0x02\r
+#define MIDI_FRAME_ERR          0x10\r
+#define MIDI_OVERRUN            0x20\r
+#define MIDI_IRQ_PEND           0x80\r
+\r
+/********************************************************************\r
+ *\r
+ * JOYSTICK defines\r
+ *\r
+ *******************************************************************/\r
+\r
+#define JOY_POSITION            0x0f\r
+#define JOY_BUTTONS                     0xf0\r
+\r
+/********************************************************************\r
+ *\r
+ * GF1 irq/dma programmable latches\r
+ *\r
+ *******************************************************************/\r
+\r
+/* GF1_IRQ_STATUS (port 3X6) */\r
+#define MIDI_TX_IRQ                     0x01            /* pending MIDI xmit IRQ */\r
+#define MIDI_RX_IRQ                     0x02            /* pending MIDI recv IRQ */\r
+#define GF1_TIMER1_IRQ          0x04            /* general purpose timer */\r
+#define GF1_TIMER2_IRQ          0x08            /* general purpose timer */\r
+#define WAVETABLE_IRQ           0x20            /* pending wavetable IRQ */\r
+#define ENVELOPE_IRQ            0x40            /* pending volume envelope IRQ */\r
+#define DMA_TC_IRQ                      0x80            /* pending dma tc IRQ */\r
+\r
+\r
+/* GF1_MIX_CTRL (port 2X0) */\r
+#define ENABLE_LINE_IN          0x01            /* 0=enable */\r
+#define ENABLE_OUTPUT           0x02            /* 0=enable */\r
+#define ENABLE_MIC_IN           0x04            /* 1=enable */\r
+#define ENABLE_GF1_IRQ          0x08            /* 1=enable */\r
+#define GF122                           0x10            /* ?? */\r
+#define ENABLE_MIDI_LOOP        0x20            /* 1=enable loop back */\r
+#define SELECT_GF1_REG          0x40            /* 0=irq latches */\r
+                                                                               /* 1=dma latches */\r
+\r
+/********************************************************************\r
+ *\r
+ * GF1 global registers ($41-$4C)\r
+ *\r
+ *******************************************************************/\r
+\r
+/* DMA control register */\r
+#define DMA_ENABLE                      0x01\r
+#define DMA_READ                        0x02            /* 1=read,0=write */\r
+#define DMA_WIDTH_16            0x04            /* 1=16 bit,0=8 bit (dma chan width)*/\r
+#define DMA_RATE                        0x18            /* 00=fast, 11=slow */\r
+#define DMA_IRQ_ENABLE          0x20            /* 1=enable */\r
+#define DMA_IRQ_PENDING         0x40            /* read */\r
+#define DMA_DATA_16                     0x40            /* write (data width) */\r
+#define DMA_TWOS_COMP           0x80            /* 1=do twos comp */\r
+\r
+/* These are the xfer rate bits ... */\r
+#define DMA_R0          0x00            /* Fastest DMA xfer (~650khz) */\r
+#define DMA_R1          0x08            /* fastest / 2 */\r
+#define DMA_R2          0x10            /* fastest / 4 */\r
+#define DMA_R3          0x18            /* Slowest DMA xfer (fastest / 8) */\r
+\r
+/* SAMPLE control register */\r
+#define ENABLE_ADC                      0x01\r
+#define ADC_MODE                        0x02            /* 0=mono, 1=stereo */\r
+#define ADC_DMA_WIDTH           0x04            /* 0=8 bit, 1=16 bit */\r
+#define ADC_IRQ_ENABLE          0x20            /* 1=enable */\r
+#define ADC_IRQ_PENDING         0x40            /* 1=irq pending */\r
+#define ADC_TWOS_COMP           0x80            /* 1=do twos comp */\r
+\r
+/* RESET control register */\r
+#define GF1_MASTER_RESET        0x01            /* 0=hold in reset */\r
+#define GF1_OUTPUT_ENABLE       0x02            /* enable output */\r
+#define GF1_MASTER_IRQ          0x04            /* master IRQ enable */\r
+\r
+/********************************************************************\r
+ *\r
+ * GF1 voice specific registers ($00 - $0E and $80-$8f)\r
+ *\r
+ *******************************************************************/\r
+\r
+/* ($0,$80) Voice control register */\r
+#define VOICE_STOPPED           0x01            /* voice has stopped */\r
+#define STOP_VOICE                      0x02            /* stop voice */\r
+#define VC_DATA_TYPE            0x04            /* 0=8 bit,1=16 bit */\r
+#define VC_LOOP_ENABLE          0x08            /* 1=enable */\r
+#define VC_BI_LOOP                      0x10            /* 1=bi directional looping */\r
+#define VC_WAVE_IRQ                     0x20            /* 1=enable voice's wave irq */\r
+#define VC_DIRECT                       0x40            /* 0=increasing,1=decreasing */\r
+#define VC_IRQ_PENDING          0x80            /* 1=wavetable irq pending */\r
+\r
+/* ($1,$81) Frequency control */\r
+/* Bit 0  - Unused */\r
+/* Bits 1-9 - Fractional portion */\r
+/* Bits 10-15 - Integer portion */\r
+\r
+/* ($2,$82) Accumulator start address (high) */\r
+/* Bits 0-11 - HIGH 12 bits of address */\r
+/* Bits 12-15 - Unused */\r
+\r
+/* ($3,$83) Accumulator start address (low) */\r
+/* Bits 0-4 - Unused */\r
+/* Bits 5-8 - Fractional portion */\r
+/* Bits 9-15 - Low 7 bits of integer portion */\r
+\r
+/* ($4,$84) Accumulator end address (high) */\r
+/* Bits 0-11 - HIGH 12 bits of address */\r
+/* Bits 12-15 - Unused */\r
+\r
+/* ($5,$85) Accumulator end address (low) */\r
+/* Bits 0-4 - Unused */\r
+/* Bits 5-8 - Fractional portion */\r
+/* Bits 9-15 - Low 7 bits of integer portion */\r
+\r
+\r
+/* ($6,$86) Volume Envelope control register */\r
+#define VL_RATE_MANTISSA                0x3f\r
+#define VL_RATE_RANGE                   0xC0\r
+\r
+/* ($7,$87) Volume envelope start */\r
+#define VL_START_MANT                   0x0F\r
+#define VL_START_EXP                    0xF0\r
+\r
+/* ($8,$88) Volume envelope end */\r
+#define VL_END_MANT                             0x0F\r
+#define VL_END_EXP                              0xF0\r
+\r
+/* ($9,$89) Current volume register */\r
+/* Bits 0-3 are unused */\r
+/* Bits 4-11 - Mantissa of current volume */\r
+/* Bits 10-15 - Exponent of current volume */\r
+\r
+/* ($A,$8A) Accumulator value (high) */\r
+/* Bits 0-12 - HIGH 12 bits of current position (a19-a7) */\r
+\r
+/* ($B,$8B) Accumulator value (low) */\r
+/* Bits 0-8 - Fractional portion */\r
+/* Bits 9-15 - Integer portion of low adress (a6-a0) */\r
+\r
+/* ($C,$8C) Pan (balance) position */\r
+/* Bits 0-3 - Balance position  0=full left, 0x0f=full right */\r
+\r
+/* ($D,$8D) Volume control register */\r
+#define VOLUME_STOPPED          0x01            /* volume has stopped */\r
+#define STOP_VOLUME                     0x02            /* stop volume */\r
+#define VC_ROLLOVER                     0x04            /* Roll PAST end & gen IRQ */\r
+#define VL_LOOP_ENABLE          0x08            /* 1=enable */\r
+#define VL_BI_LOOP                      0x10            /* 1=bi directional looping */\r
+#define VL_WAVE_IRQ                     0x20            /* 1=enable voice's wave irq */\r
+#define VL_DIRECT                       0x40            /* 0=increasing,1=decreasing */\r
+#define VL_IRQ_PENDING          0x80            /* 1=wavetable irq pending */\r
+\r
+/* ($E,$8E) # of Active voices */\r
+/* Bits 0-5 - # of active voices -1 */\r
+\r
+/* ($F,$8F) - Sources of IRQs */\r
+/* Bits 0-4 - interrupting voice number */\r
+/* Bit 5 - Always a 1 */\r
+#define VOICE_VOLUME_IRQ        0x40            /* individual voice irq bit */\r
+#define VOICE_WAVE_IRQ          0x80            /* individual waveform irq bit */\r
+\r
+\r
+/***************************************************************************\r
+>>>>>>>>>>>>>>>>>>>>>>>>> Lowlevel GUS code <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
+***************************************************************************/\r
+\r
+static UWORD GUS_PORT;\r
+static UBYTE GUS_VOICES;\r
+static UBYTE GUS_TIMER_CTRL;\r
+static UBYTE GUS_TIMER_MASK;\r
+static UBYTE GUS_MIX_IMAGE;\r
+\r
+static UWORD GUS_DRAM_DMA;\r
+static UWORD GUS_ADC_DMA;\r
+static UWORD GUS_GF1_IRQ;\r
+static UWORD GUS_MIDI_IRQ;\r
+static ULONG GUS_POOL;         /* dram address of first gusmem pool node */\r
+\r
+static UBYTE GUS_SELECT;          /* currently selected GF1 register */\r
+\r
+static void (*GUS_TIMER1_FUNC)(void);\r
+static void (*GUS_TIMER2_FUNC)(void);\r
+\r
+#define UltraSelect(x) outportb(GF1_REG_SELECT,GUS_SELECT=x)\r
+\r
+#define USE_ROLLOVER 0\r
+\r
+/***************************************************************\r
+ * This function will convert the value read from the GF1 registers\r
+ * back to a 'real' address.\r
+ ***************************************************************/\r
+\r
+#define MAKE_MS_SWORD( x )       ((unsigned long)((unsigned long)(x)) << 16)\r
+\r
+static ULONG make_physical_address(UWORD low,UWORD high,UBYTE mode)\r
+{\r
+       UWORD lower_16, upper_16;\r
+       ULONG ret_address, bit_19_20;\r
+\r
+       upper_16 = high >> 9;\r
+       lower_16 = ((high & 0x01ff) << 7) | ((low >> 9) & 0x007f);\r
+\r
+       ret_address = MAKE_MS_SWORD(upper_16) + lower_16;\r
+\r
+       if (mode & VC_DATA_TYPE)\r
+               {\r
+               bit_19_20 = ret_address & 0xC0000;\r
+               ret_address <<= 1;\r
+               ret_address &= 0x3ffff;\r
+               ret_address |= bit_19_20;\r
+               }\r
+\r
+       return( ret_address );\r
+}\r
+\r
+/***************************************************************\r
+ * This function will translate the address if the dma channel\r
+ * is a 16 bit channel. This translation is not necessary for\r
+ * an 8 bit dma channel.\r
+ ***************************************************************/\r
+\r
+static ULONG convert_to_16bit(ULONG address)\r
+/* unsigned long address;               /* 20 bit ultrasound dram address */\r
+{\r
+       ULONG hold_address;\r
+\r
+       hold_address = address;\r
+\r
+       /* Convert to 16 translated address. */\r
+       address = address >> 1;\r
+\r
+       /* Zero out bit 17. */\r
+       address &= 0x0001ffffL;\r
+\r
+       /* Reset bits 18 and 19. */\r
+       address |= (hold_address & 0x000c0000L);\r
+\r
+       return(address);\r
+}\r
+\r
+\r
+static void GF1OutB(UBYTE x,UBYTE y)\r
+{\r
+       UltraSelect(x);\r
+       outportb(GF1_DATA_HI,y);\r
+}\r
+\r
+\r
+static void GF1OutW(UBYTE x,UWORD y)\r
+{\r
+       UltraSelect(x);\r
+       outport(GF1_DATA_LOW,y);\r
+}\r
+\r
+\r
+static UBYTE GF1InB(UBYTE x)\r
+{\r
+       UltraSelect(x);\r
+       return inportb(GF1_DATA_HI);\r
+}\r
+\r
+\r
+static UWORD GF1InW(UBYTE x)\r
+{\r
+       UltraSelect(x);\r
+       return inport(GF1_DATA_LOW);\r
+}\r
+\r
+\r
+static void gf1_delay(void)\r
+{\r
+       inportb(GF1_DRAM);\r
+       inportb(GF1_DRAM);\r
+       inportb(GF1_DRAM);\r
+       inportb(GF1_DRAM);\r
+       inportb(GF1_DRAM);\r
+       inportb(GF1_DRAM);\r
+       inportb(GF1_DRAM);\r
+}\r
+\r
+\r
+static UBYTE UltraPeek(ULONG address)\r
+{\r
+       GF1OutW(SET_DRAM_LOW,address);\r
+       GF1OutB(SET_DRAM_HIGH,(address>>16)&0xff);      /* 8 bits */\r
+       return(inportb(GF1_DRAM));\r
+}\r
+\r
+\r
+static void UltraPoke(ULONG address,UBYTE data)\r
+{\r
+       GF1OutW(SET_DRAM_LOW,address);\r
+       GF1OutB(SET_DRAM_HIGH,(address>>16)&0xff);\r
+       outportb(GF1_DRAM,data);\r
+}\r
+\r
+\r
+static void UltraPokeFast(ULONG address,UBYTE *src,ULONG size)\r
+/*\r
+       [address,size> doesn't cross 64k page boundary\r
+*/\r
+{\r
+       if(!size) return;\r
+\r
+       UltraSelect(SET_DRAM_HIGH);\r
+       outportb(GF1_DATA_HI,(address>>16)&0xff);       /* 8 bits */\r
+       UltraSelect(SET_DRAM_LOW);\r
+\r
+       while(size--){\r
+               outport(GF1_DATA_LOW,address);\r
+               outportb(GF1_DRAM,*src);\r
+               address++;\r
+               src++;\r
+       }\r
+}\r
+\r
+\r
+static void UltraPokeChunk(ULONG address,UBYTE *src,ULONG size)\r
+{\r
+       ULONG todo;\r
+\r
+       /* first 'todo' is number of bytes 'till first 64k boundary */\r
+\r
+       todo=0x10000-(address&0xffff);\r
+       if(todo>size) todo=size;\r
+\r
+       do{\r
+               UltraPokeFast(address,src,todo);\r
+               address+=todo;\r
+               src+=todo;\r
+               size-=todo;\r
+\r
+               /* next 'todo' is in chunks of max 64k at once. */\r
+               todo=(size>0xffff) ? 0x10000 : size;\r
+\r
+       } while(todo);\r
+}\r
+\r
+\r
+static ULONG UltraPeekLong(ULONG address)\r
+{\r
+       ULONG data;\r
+       char *s=(char *)&data;\r
+       s[0]=UltraPeek(address);\r
+       s[1]=UltraPeek(address+1);\r
+       s[2]=UltraPeek(address+2);\r
+       s[3]=UltraPeek(address+3);\r
+       return data;\r
+}\r
+\r
+\r
+static void UltraPokeLong(ULONG address,ULONG data)\r
+{\r
+        UltraPokeChunk(address,(UBYTE *)&data,4);\r
+}\r
+\r
+\r
+static void UltraEnableOutput(void)\r
+{\r
+       GUS_MIX_IMAGE &= ~ENABLE_OUTPUT;\r
+       outportb(GF1_MIX_CTRL,GUS_MIX_IMAGE);\r
+}\r
+\r
+static void UltraDisableOutput(void)\r
+{\r
+       GUS_MIX_IMAGE |= ENABLE_OUTPUT;\r
+       outportb(GF1_MIX_CTRL,GUS_MIX_IMAGE);\r
+}\r
+\r
+static void UltraEnableLineIn(void)\r
+{\r
+       GUS_MIX_IMAGE &= ~ENABLE_LINE_IN;\r
+       outportb(GF1_MIX_CTRL,GUS_MIX_IMAGE);\r
+}\r
+\r
+static void UltraDisableLineIn(void)\r
+{\r
+       GUS_MIX_IMAGE |= ENABLE_LINE_IN;\r
+       outportb(GF1_MIX_CTRL,GUS_MIX_IMAGE);\r
+}\r
+\r
+static void UltraEnableMicIn(void)\r
+{\r
+       GUS_MIX_IMAGE |= ENABLE_MIC_IN;\r
+       outportb(GF1_MIX_CTRL,GUS_MIX_IMAGE);\r
+}\r
+\r
+\r
+static void UltraDisableMicIn(void)\r
+{\r
+       GUS_MIX_IMAGE &= ~ENABLE_MIC_IN;\r
+       outportb(GF1_MIX_CTRL,GUS_MIX_IMAGE);\r
+}\r
+\r
+\r
+static void UltraReset(int voices)\r
+{\r
+       int v;\r
+\r
+       if(voices<14) voices=14;\r
+       if(voices>32) voices=32;\r
+\r
+       GUS_VOICES=voices;\r
+       GUS_TIMER_CTRL=0;\r
+       GUS_TIMER_MASK=0;\r
+\r
+       UltraPokeLong(0,0);\r
+\r
+       GF1OutB(MASTER_RESET,0x00);\r
+       for(v=0;v<10;v++) gf1_delay();\r
+\r
+       /* Release Reset and wait */\r
+       GF1OutB(MASTER_RESET,GF1_MASTER_RESET);\r
+       for (v=0;v<10;v++) gf1_delay();\r
+\r
+       /* Reset the MIDI port also */\r
+       outportb(GF1_MIDI_CTRL,MIDI_RESET);\r
+       for (v=0;v<10;v++) gf1_delay();\r
+       outportb(GF1_MIDI_CTRL,0x00);\r
+\r
+       /* Clear all interrupts. */\r
+       GF1OutB(DMA_CONTROL,0x00);\r
+       GF1OutB(TIMER_CONTROL,0x00);\r
+       GF1OutB(SAMPLE_CONTROL,0x00);\r
+\r
+       /* Set the number of active voices */\r
+       GF1OutB(SET_VOICES,((voices-1) | 0xC0));\r
+\r
+       /* Clear interrupts on voices. */\r
+       /* Reading the status ports will clear the irqs. */\r
+\r
+       inportb(GF1_IRQ_STAT);\r
+\r
+       GF1InB(DMA_CONTROL);\r
+       GF1InB(SAMPLE_CONTROL);\r
+       GF1InB(GET_IRQV);\r
+\r
+       for(v=0;v<voices;v++){\r
+\r
+               /* Select the proper voice */\r
+               outportb(GF1_PAGE,v);\r
+\r
+               /* Stop the voice and volume */\r
+               GF1OutB(SET_CONTROL,VOICE_STOPPED|STOP_VOICE);\r
+               GF1OutB(SET_VOLUME_CONTROL,VOLUME_STOPPED|STOP_VOLUME);\r
+\r
+               gf1_delay(); /* Wait 4.8 micos. or more. */\r
+\r
+               /* Initialize each voice specific registers. This is not */\r
+               /* really necessary, but is nice for completeness sake .. */\r
+               /* Each application will set up these to whatever values */\r
+               /* it needs. */\r
+\r
+               GF1OutW(SET_FREQUENCY,0x0400);\r
+               GF1OutW(SET_START_HIGH,0);\r
+               GF1OutW(SET_START_LOW,0);\r
+               GF1OutW(SET_END_HIGH,0);\r
+               GF1OutW(SET_END_LOW,0);\r
+               GF1OutB(SET_VOLUME_RATE,0x01);\r
+               GF1OutB(SET_VOLUME_START,0x10);\r
+               GF1OutB(SET_VOLUME_END,0xe0);\r
+               GF1OutW(SET_VOLUME,0x0000);\r
+\r
+               GF1OutW(SET_ACC_HIGH,0);\r
+               GF1OutW(SET_ACC_LOW,0);\r
+               GF1OutB(SET_BALANCE,0x07);\r
+       }\r
+\r
+       inportb(GF1_IRQ_STAT);\r
+\r
+       GF1InB(DMA_CONTROL);\r
+       GF1InB(SAMPLE_CONTROL);\r
+       GF1InB(GET_IRQV);\r
+\r
+       /* Set up GF1 Chip for interrupts & enable DACs. */\r
+/*     GF1OutB(MASTER_RESET,GF1_MASTER_RESET|GF1_OUTPUT_ENABLE); */\r
+       GF1OutB(MASTER_RESET,GF1_MASTER_RESET|GF1_OUTPUT_ENABLE|GF1_MASTER_IRQ);\r
+}\r
+\r
+\r
+static BOOL UltraProbe(void)\r
+{\r
+       UBYTE s1,s2,t1,t2;\r
+\r
+       /* Pull a reset on the GF1 */\r
+\r
+       GF1OutB(MASTER_RESET,0x00);\r
+\r
+       /* Wait a little while ... */\r
+       gf1_delay();\r
+       gf1_delay();\r
+\r
+       /* Release Reset */\r
+       GF1OutB(MASTER_RESET,GF1_MASTER_RESET);\r
+\r
+       gf1_delay();\r
+       gf1_delay();\r
+\r
+       s1=UltraPeek(0); s2=UltraPeek(1);\r
+       UltraPoke(0,0xaa); t1=UltraPeek(0);\r
+       UltraPoke(1,0x55); t2=UltraPeek(1);\r
+       UltraPoke(0,s1); UltraPoke(1,s2);\r
+\r
+       return(t1==0xaa && t2==0x55);\r
+}\r
+\r
+\r
+\r
+static BOOL UltraDetect(void)\r
+{\r
+       char *ptr;\r
+\r
+       if((ptr=getenv("ULTRASND"))==NULL) return 0;\r
+\r
+       if(sscanf(ptr,"%hx,%hd,%hd,%hd,%hd",\r
+                               &GUS_PORT,\r
+                               &GUS_DRAM_DMA,\r
+                               &GUS_ADC_DMA,\r
+                               &GUS_GF1_IRQ,\r
+                               &GUS_MIDI_IRQ)!=5) return 0;\r
+\r
+       return(UltraProbe());\r
+}\r
+\r
+\r
+\r
+\r
+static UBYTE dmalatch[8]       ={ 0,1,0,2,0,3,4,5 };\r
+static UBYTE irqlatch[16]      ={ 0,0,1,3,0,2,0,4,0,0,0,5,6,0,0,7 };\r
+\r
+\r
+static void UltraSetInterface(int dram,int adc,int gf1,int midi)\r
+/* int dram;    /* dram dma chan */\r
+/* int adc;             /* adc dma chan */\r
+/* int gf1;             /* gf1 irq # */\r
+/* int midi;    /* midi irq # */\r
+{\r
+       UBYTE gf1_irq, midi_irq,dram_dma,adc_dma;\r
+       UBYTE irq_control,dma_control;\r
+       UBYTE mix_image;\r
+\r
+       /* Don't need to check for 0 irq #. Its latch entry = 0 */\r
+       gf1_irq =irqlatch[gf1];\r
+       midi_irq=irqlatch[midi];\r
+       midi_irq<<=3;\r
+\r
+       dram_dma=dmalatch[dram];\r
+       adc_dma =dmalatch[adc];\r
+       adc_dma<<=3;\r
+\r
+       irq_control=dma_control=0x0;\r
+\r
+       mix_image=GUS_MIX_IMAGE;\r
+\r
+       irq_control|=gf1_irq;\r
+\r
+       if((gf1==midi) && (gf1!=0))\r
+               irq_control|=0x40;\r
+       else\r
+               irq_control|=midi_irq;\r
+\r
+       dma_control|=dram_dma;\r
+\r
+       if((dram==adc) && (dram!=0))\r
+               dma_control|=0x40;\r
+       else\r
+               dma_control|=adc_dma;\r
+\r
+       /* Set up for Digital ASIC */\r
+       outportb(GUS_PORT+0x0f,0x5);\r
+       outportb(GF1_MIX_CTRL,mix_image);\r
+       outportb(GF1_IRQ_CTRL,0x0);\r
+       outportb(GUS_PORT+0x0f,0x0);\r
+\r
+       /* First do DMA control register */\r
+       outportb(GF1_MIX_CTRL,mix_image);\r
+       outportb(GF1_IRQ_CTRL,dma_control|0x80);\r
+\r
+       /* IRQ CONTROL REG */\r
+       outportb(GF1_MIX_CTRL,mix_image|0x40);\r
+       outportb(GF1_IRQ_CTRL,irq_control);\r
+\r
+       /* First do DMA control register */\r
+       outportb(GF1_MIX_CTRL,mix_image);\r
+       outportb(GF1_IRQ_CTRL,dma_control);\r
+\r
+       /* IRQ CONTROL REG */\r
+       outportb(GF1_MIX_CTRL,mix_image|0x40);\r
+       outportb(GF1_IRQ_CTRL,irq_control);\r
+\r
+       /* IRQ CONTROL, ENABLE IRQ */\r
+       /* just to Lock out writes to irq\dma register ... */\r
+       outportb(GF1_VOICE_SELECT,0);\r
+\r
+       /* enable output & irq, disable line & mic input */\r
+       mix_image|=0x09;\r
+       outportb(GF1_MIX_CTRL,mix_image);\r
+\r
+       /* just to Lock out writes to irq\dma register ... */\r
+       outportb(GF1_VOICE_SELECT,0x0);\r
+\r
+       /* put image back .... */\r
+       GUS_MIX_IMAGE=mix_image;\r
+}\r
+\r
+\r
+static BOOL UltraPP(ULONG address)\r
+{\r
+       UBYTE s,t;\r
+       s=UltraPeek(address);\r
+       UltraPoke(address,0xaa);\r
+       t=UltraPeek(address);\r
+       UltraPoke(address,s);\r
+       return(t==0xaa);\r
+}\r
+\r
+\r
+static UWORD UltraSizeDram(void)\r
+{\r
+       if(!UltraPP(0))      return 0;\r
+       if(!UltraPP(262144)) return 256;\r
+       if(!UltraPP(524288)) return 512;\r
+       if(!UltraPP(786432)) return 768;\r
+       return 1024;\r
+}\r
+\r
+\r
+\r
+\r
+\r
+static ULONG UltraMemTotal(void)\r
+{\r
+       ULONG node=GUS_POOL,nsize,total=0;\r
+\r
+       while(node!=0){\r
+               nsize=UltraPeekLong(node);\r
+               total+=nsize;\r
+               node=UltraPeekLong(node+4);\r
+       }\r
+       return total;\r
+}\r
+\r
+\r
+\r
+static BOOL Mergeable(ULONG a,ULONG b)\r
+{\r
+       return(a && b && (a+UltraPeekLong(a))==b);\r
+}\r
+\r
+\r
+\r
+static ULONG Merge(ULONG a,ULONG b)\r
+{\r
+       UltraPokeLong(a,UltraPeekLong(a)+UltraPeekLong(b));\r
+       UltraPokeLong(a+4,UltraPeekLong(b+4));\r
+       return a;\r
+}\r
+\r
+\r
+\r
+static void UltraFree(ULONG size,ULONG location)\r
+{\r
+       ULONG pred=0,succ=GUS_POOL;\r
+\r
+       if(!size) return;\r
+       size+=31;\r
+       size&=-32L;\r
+\r
+       UltraPokeLong(location,size);\r
+\r
+       while(succ!=0 && succ<=location){\r
+               pred=succ;\r
+               succ=UltraPeekLong(succ+4);\r
+       }\r
+\r
+       if(pred)\r
+               UltraPokeLong(pred+4,location);\r
+       else\r
+               GUS_POOL=location;\r
+\r
+       UltraPokeLong(location+4,succ);\r
+\r
+       if(Mergeable(pred,location)){\r
+               location=Merge(pred,location);\r
+       }\r
+\r
+       if(Mergeable(location,succ)){\r
+               Merge(location,succ);\r
+       }\r
+}\r
+\r
+\r
+/*\r
+void DumpPool(void)\r
+{\r
+       ULONG node=GUS_POOL;\r
+\r
+       while(node!=0){\r
+               printf("Node %ld, size %ld, next %ld\n",node,UltraPeekLong(node),UltraPeekLong(node+4));\r
+               node=UltraPeekLong(node+4);\r
+       }\r
+}\r
+*/\r
+\r
+\r
+\r
+\r
+\r
+\r
+static ULONG UltraMalloc(ULONG reqsize)\r
+{\r
+       ULONG curnode=GUS_POOL,cursize,newnode,newsize,pred,succ;\r
+\r
+       if(!reqsize) return 0;\r
+\r
+       /* round size to 32 bytes */\r
+\r
+       reqsize+=31;\r
+       reqsize&=-32L;\r
+\r
+       /* as long as there are nodes: */\r
+\r
+       pred=0;\r
+\r
+       while(curnode!=0){\r
+\r
+               succ=UltraPeekLong(curnode+4);\r
+\r
+               /* get current node size */\r
+\r
+               cursize=UltraPeekLong(curnode);\r
+\r
+               /* requested block fits? */\r
+\r
+               if(cursize>=reqsize){\r
+\r
+                       /* it fits, so we're allocating the first\r
+                          'size' bytes of this node */\r
+\r
+                       /* find new node position and size */\r
+\r
+                       newnode=curnode+reqsize;\r
+                       newsize=cursize-reqsize;\r
+\r
+                       /* create a new freenode if needed: */\r
+\r
+                       if(newsize>=8){\r
+                               UltraPokeLong(newnode,newsize);\r
+                               UltraPokeLong(newnode+4,succ);\r
+                               succ=newnode;\r
+                       }\r
+\r
+                       /* link prednode & succnode */\r
+\r
+                       if(pred)\r
+                               UltraPokeLong(pred+4,succ);\r
+                       else\r
+                               GUS_POOL=succ;\r
+\r
+                       /* store size of allocated memory block in block itself: */\r
+\r
+                       UltraPokeLong(curnode,reqsize);\r
+                       return curnode;\r
+               }\r
+\r
+               /* doesn't fit, try next node */\r
+               curnode=succ;\r
+       }\r
+       return 0;\r
+}\r
+\r
+\r
+\r
+static ULONG UltraMalloc16(ULONG reqsize)\r
+/*\r
+       Allocates a free block of gus memory, suited for 16 bit samples i.e.\r
+       smaller than 256k and doesn't cross a 256k page.\r
+*/\r
+{\r
+       ULONG p,spage,epage;\r
+\r
+       if(reqsize>262144) return 0;\r
+\r
+       /* round size to 32 bytes */\r
+\r
+       reqsize+=31;\r
+       reqsize&=-32L;\r
+\r
+       p=UltraMalloc(reqsize);\r
+       spage=p>>18;\r
+       epage=(p+reqsize-1)>>18;\r
+\r
+       if(p && spage!=epage){\r
+               ULONG newp,esize;\r
+\r
+               /* free the second part of the block, and try again */\r
+\r
+               esize=(p+reqsize)-(epage<<18);\r
+               UltraFree(esize,epage<<18);\r
+\r
+               newp=UltraMalloc16(reqsize);\r
+\r
+               /* free first part of the previous block */\r
+\r
+               UltraFree(reqsize-esize,p);\r
+               p=newp;\r
+       }\r
+\r
+       return p;\r
+}\r
+\r
+\r
+\r
+static void UltraMemInit(void)\r
+{\r
+       UWORD memsize;\r
+       GUS_POOL=32;\r
+       memsize=UltraSizeDram();\r
+       UltraPokeLong(GUS_POOL,((ULONG)memsize<<10)-32);\r
+       UltraPokeLong(GUS_POOL+4,0);\r
+}\r
+\r
+\r
+static void UltraNumVoices(int voices)\r
+{\r
+       UltraDisableLineIn();\r
+       UltraDisableMicIn();\r
+       UltraDisableOutput();\r
+       UltraReset(voices);\r
+       UltraSetInterface(GUS_DRAM_DMA,GUS_ADC_DMA,GUS_GF1_IRQ,GUS_MIDI_IRQ);\r
+}\r
+\r
+\r
+static void interrupt gf1handler(MIRQARGS)\r
+{\r
+       UBYTE irq_source;\r
+       UBYTE oldselect=GUS_SELECT;\r
+\r
+       while(irq_source=inportb(GF1_IRQ_STAT)){\r
+\r
+/*             if(irq_source & DMA_TC_IRQ){\r
+                       no provisions for DMA-ready irq yet\r
+               }\r
+\r
+               if(irq_source & (MIDI_TX_IRQ|MIDI_RX_IRQ)){\r
+                       no provisions for MIDI-ready irq yet\r
+               }\r
+*/\r
+               if (irq_source & GF1_TIMER1_IRQ){\r
+                       GF1OutB(TIMER_CONTROL,GUS_TIMER_CTRL & ~0x04);\r
+                       GF1OutB(TIMER_CONTROL,GUS_TIMER_CTRL);\r
+                       if(GUS_TIMER1_FUNC!=NULL) GUS_TIMER1_FUNC();\r
+               }\r
+\r
+               if (irq_source & GF1_TIMER2_IRQ){\r
+                       GF1OutB(TIMER_CONTROL,GUS_TIMER_CTRL & ~0x08);\r
+                       GF1OutB(TIMER_CONTROL,GUS_TIMER_CTRL);\r
+                       if(GUS_TIMER2_FUNC!=NULL) GUS_TIMER2_FUNC();\r
+               }\r
+\r
+/*             if (irq_source & (WAVETABLE_IRQ | ENVELOPE_IRQ)){\r
+                       no wavetable or envelope irq provisions yet\r
+               }\r
+*/\r
+       }\r
+\r
+       MIrq_EOI(GUS_GF1_IRQ);\r
+       UltraSelect(oldselect);\r
+}\r
+\r
+\r
+static PVI oldhandler;\r
+typedef void (*PFV)(void);\r
+\r
+\r
+static PFV UltraTimer1Handler(PFV handler)\r
+{\r
+       PFV old=GUS_TIMER1_FUNC;\r
+       GUS_TIMER1_FUNC=handler;\r
+       return old;\r
+}\r
+\r
+\r
+static PFV UltraTimer2Handler(PFV handler)\r
+{\r
+       PFV old=GUS_TIMER1_FUNC;\r
+       GUS_TIMER1_FUNC=handler;\r
+       return old;\r
+}\r
+\r
+\r
+static void UltraOpen(int voices)\r
+{\r
+       GUS_MIX_IMAGE=0x0b;\r
+       GUS_TIMER1_FUNC=NULL;\r
+       GUS_TIMER2_FUNC=NULL;\r
+\r
+       UltraDisableLineIn();\r
+       UltraDisableMicIn();\r
+       UltraDisableOutput();\r
+\r
+       UltraReset(voices);\r
+       UltraSetInterface(GUS_DRAM_DMA,GUS_ADC_DMA,GUS_GF1_IRQ,GUS_MIDI_IRQ);\r
+       UltraMemInit();\r
+       oldhandler=MIrq_SetHandler(GUS_GF1_IRQ,gf1handler);\r
+       MIrq_OnOff(GUS_GF1_IRQ,1);\r
+}\r
+\r
+\r
+static void UltraClose(void)\r
+{\r
+       MIrq_OnOff(GUS_GF1_IRQ,0);\r
+       MIrq_SetHandler(GUS_GF1_IRQ,oldhandler);\r
+       UltraDisableOutput();\r
+       UltraDisableLineIn();\r
+       UltraDisableMicIn();\r
+       UltraReset(14);\r
+}\r
+\r
+\r
+static void UltraSelectVoice(UBYTE voice)\r
+{\r
+       /* Make sure was are talking to proper voice */\r
+       outportb(GF1_VOICE_SELECT,voice);\r
+}\r
+\r
+\r
+\r
+static void UltraSetVoiceEnd(ULONG end)\r
+{\r
+       ULONG phys_end;\r
+       UBYTE data;\r
+\r
+       data=GF1InB(GET_CONTROL);\r
+\r
+       phys_end=(data&VC_DATA_TYPE)?convert_to_16bit(end):end;\r
+\r
+       /* Set end address of buffer */\r
+       GF1OutW(SET_END_LOW,ADDR_LOW(phys_end));\r
+       GF1OutW(SET_END_HIGH,ADDR_HIGH(phys_end));\r
+\r
+       data&=~(VC_IRQ_PENDING|VOICE_STOPPED|STOP_VOICE);\r
+\r
+       GF1OutB(SET_CONTROL,data);\r
+       gf1_delay();\r
+\r
+       GF1OutB(SET_CONTROL,data);\r
+}\r
+\r
+\r
+/* The formula for this table is:\r
+       1,000,000 / (1.619695497 * # of active voices)\r
+\r
+       The 1.619695497 is calculated by knowing that 14 voices\r
+               gives exactly 44.1 Khz. Therefore,\r
+               1,000,000 / (X * 14) = 44100\r
+               X = 1.619695497\r
+*/\r
+\r
+static UWORD freq_divisor[19] = {\r
+       44100,          /* 14 active voices */\r
+       41160,          /* 15 active voices */\r
+       38587,          /* 16 active voices */\r
+       36317,          /* 17 active voices */\r
+       34300,          /* 18 active voices */\r
+       32494,          /* 19 active voices */\r
+       30870,          /* 20 active voices */\r
+       29400,          /* 21 active voices */\r
+       28063,          /* 22 active voices */\r
+       26843,          /* 23 active voices */\r
+       25725,          /* 24 active voices */\r
+       24696,          /* 25 active voices */\r
+       23746,          /* 26 active voices */\r
+       22866,          /* 27 active voices */\r
+       22050,          /* 28 active voices */\r
+       21289,          /* 29 active voices */\r
+       20580,          /* 30 active voices */\r
+       19916,          /* 31 active voices */\r
+       19293}          /* 32 active voices */\r
+;\r
+\r
+static void UltraSetFrequency(ULONG speed_khz)\r
+{\r
+       UWORD fc;\r
+       ULONG temp;\r
+\r
+       /* FC is calculated based on the # of active voices ... */\r
+       temp=freq_divisor[GUS_VOICES-14];\r
+\r
+       fc=(((speed_khz<<9L)+(temp>>1L))/temp);\r
+       GF1OutW(SET_FREQUENCY,fc<<1);\r
+}\r
+\r
+\r
+static void UltraSetLoopMode(UBYTE mode)\r
+{\r
+       UBYTE data;\r
+       UBYTE vmode;\r
+\r
+       /* set/reset the rollover bit as per user request */\r
+\r
+       vmode=GF1InB(GET_VOLUME_CONTROL);\r
+\r
+       if(mode&USE_ROLLOVER)\r
+               vmode|=VC_ROLLOVER;\r
+       else\r
+               vmode&=~VC_ROLLOVER;\r
+\r
+       GF1OutB(SET_VOLUME_CONTROL,vmode);\r
+       gf1_delay();\r
+       GF1OutB(SET_VOLUME_CONTROL,vmode);\r
+\r
+       data=GF1InB(GET_CONTROL);\r
+\r
+       data&=~(VC_WAVE_IRQ|VC_BI_LOOP|VC_LOOP_ENABLE); /* isolate the bits */\r
+       mode&=VC_WAVE_IRQ|VC_BI_LOOP|VC_LOOP_ENABLE;    /* no bad bits passed in */\r
+       data|=mode;             /* turn on proper bits ... */\r
+\r
+       GF1OutB(SET_CONTROL,data);\r
+       gf1_delay();\r
+       GF1OutB(SET_CONTROL,data);\r
+}\r
+\r
+\r
+static ULONG UltraReadVoice(void)\r
+{\r
+       UWORD count_low,count_high;\r
+       ULONG acc;\r
+       UBYTE mode;\r
+\r
+       /* Get the high & low portion of the accumulator */\r
+       count_high = GF1InW(GET_ACC_HIGH);\r
+       count_low = GF1InW(GET_ACC_LOW);\r
+\r
+       /* convert from UltraSound's format to a physical address */\r
+\r
+       mode=GF1InB(GET_CONTROL);\r
+\r
+       acc=make_physical_address(count_low,count_high,mode);\r
+       acc&=0xfffffL;          /* Only 20 bits please ... */\r
+\r
+       return(acc);\r
+}\r
+\r
+\r
+\r
+static void UltraSetVoice(ULONG location)\r
+{\r
+       ULONG phys_loc;\r
+       UBYTE data;\r
+\r
+       data=GF1InB(GET_CONTROL);\r
+\r
+       phys_loc=(data&VC_DATA_TYPE)?convert_to_16bit(location):location;\r
+\r
+       /* First set accumulator to beginning of data */\r
+       GF1OutW(SET_ACC_HIGH,ADDR_HIGH(phys_loc));\r
+       GF1OutW(SET_ACC_LOW,ADDR_LOW(phys_loc));\r
+}\r
+\r
+\r
+\r
+static UBYTE UltraPrimeVoice(ULONG begin,ULONG start,ULONG end,UBYTE mode)\r
+{\r
+       ULONG phys_start,phys_end;\r
+       ULONG phys_begin;\r
+       ULONG temp;\r
+       UBYTE vmode;\r
+\r
+       /* if start is greater than end, flip 'em and turn on */\r
+       /* decrementing addresses */\r
+       if(start>end){\r
+               temp=start;\r
+               start=end;\r
+               end=temp;\r
+               mode|=VC_DIRECT;\r
+       }\r
+\r
+       /* if 16 bit data, must convert addresses */\r
+       if(mode&VC_DATA_TYPE){\r
+               phys_begin = convert_to_16bit(begin);\r
+               phys_start = convert_to_16bit(start);\r
+               phys_end   = convert_to_16bit(end);\r
+       }\r
+       else{\r
+               phys_begin = begin;\r
+               phys_start = start;\r
+               phys_end = end;\r
+       }\r
+\r
+       /* set/reset the rollover bit as per user request */\r
+       vmode=GF1InB(GET_VOLUME_CONTROL);\r
+\r
+       if(mode&USE_ROLLOVER)\r
+               vmode |= VC_ROLLOVER;\r
+       else\r
+               vmode &= ~VC_ROLLOVER;\r
+\r
+       GF1OutB(SET_VOLUME_CONTROL,vmode);\r
+       gf1_delay();\r
+       GF1OutB(SET_VOLUME_CONTROL,vmode);\r
+\r
+       /* First set accumulator to beginning of data */\r
+       GF1OutW(SET_ACC_LOW,ADDR_LOW(phys_begin));\r
+       GF1OutW(SET_ACC_HIGH,ADDR_HIGH(phys_begin));\r
+\r
+       /* Set start loop address of buffer */\r
+       GF1OutW(SET_START_HIGH,ADDR_HIGH(phys_start));\r
+       GF1OutW(SET_START_LOW,ADDR_LOW(phys_start));\r
+\r
+       /* Set end address of buffer */\r
+       GF1OutW(SET_END_HIGH,ADDR_HIGH(phys_end));\r
+       GF1OutW(SET_END_LOW,ADDR_LOW(phys_end));\r
+       return(mode);\r
+}\r
+\r
+\r
+static void UltraGoVoice(UBYTE mode)\r
+{\r
+       mode&=~(VOICE_STOPPED|STOP_VOICE);      /* turn 'stop' bits off ... */\r
+\r
+       /* NOTE: no irq's from the voice ... */\r
+\r
+       GF1OutB(SET_CONTROL,mode);\r
+       gf1_delay();\r
+       GF1OutB(SET_CONTROL,mode);\r
+}\r
+\r
+\r
+/**********************************************************************\r
+ *\r
+ * This function will start playing a wave out of DRAM. It assumes\r
+ * the playback rate, volume & balance have been set up before ...\r
+ *\r
+ *********************************************************************/\r
+\r
+static void UltraStartVoice(ULONG begin,ULONG start,ULONG end,UBYTE mode)\r
+{\r
+       mode=UltraPrimeVoice(begin,start,end,mode);\r
+       UltraGoVoice(mode);\r
+}\r
+\r
+\r
+\r
+/***************************************************************\r
+ * This function will stop a given voices output. Note that a delay\r
+ * is necessary after the stop is issued to ensure the self-\r
+ * modifying bits aren't a problem.\r
+ ***************************************************************/\r
+\r
+static void UltraStopVoice(void)\r
+{\r
+       UBYTE data;\r
+\r
+       /* turn off the roll over bit first ... */\r
+\r
+       data=GF1InB(GET_VOLUME_CONTROL);\r
+       data&=~VC_ROLLOVER;\r
+\r
+       GF1OutB(SET_VOLUME_CONTROL,data);\r
+       gf1_delay();\r
+       GF1OutB(SET_VOLUME_CONTROL,data);\r
+\r
+       /* Now stop the voice  */\r
+\r
+       data=GF1InB(GET_CONTROL);\r
+       data&=~VC_WAVE_IRQ;             /* disable irq's & stop voice .. */\r
+       data|=VOICE_STOPPED|STOP_VOICE;\r
+\r
+       GF1OutB(SET_CONTROL,data);                      /* turn it off */\r
+       gf1_delay();\r
+       GF1OutB(SET_CONTROL,data);\r
+}\r
+\r
+\r
+static int UltraVoiceStopped(void)\r
+{\r
+       return(GF1InB(GET_CONTROL) & (VOICE_STOPPED|STOP_VOICE));\r
+}\r
+\r
+\r
+static void UltraSetBalance(UBYTE pan)\r
+{\r
+       GF1OutB(SET_BALANCE,pan&0xf);\r
+}\r
+\r
+\r
+static void UltraSetVolume(UWORD volume)\r
+{\r
+       GF1OutW(SET_VOLUME,volume<<4);\r
+}\r
+\r
+\r
+static UWORD UltraReadVolume(void)\r
+{\r
+       return(GF1InW(GET_VOLUME)>>4);\r
+}\r
+\r
+\r
+static void UltraStopVolume(void)\r
+{\r
+       UBYTE vmode;\r
+\r
+       vmode=GF1InB(GET_VOLUME_CONTROL);\r
+       vmode|=(VOLUME_STOPPED|STOP_VOLUME);\r
+\r
+       GF1OutB(SET_VOLUME_CONTROL,vmode);\r
+       gf1_delay();\r
+       GF1OutB(SET_VOLUME_CONTROL,vmode);\r
+}\r
+\r
+\r
+static void UltraRampVolume(UWORD start,UWORD end,UBYTE rate,UBYTE mode)\r
+{\r
+       UWORD begin;\r
+       UBYTE vmode;\r
+\r
+       if(start==end) return;\r
+/*********************************************************************\r
+ * If the start volume is greater than the end volume, flip them and\r
+ * turn on decreasing volume. Note that the GF1 requires that the\r
+ * programmed start volume MUST be less than or equal to the end\r
+ * volume.\r
+ *********************************************************************/\r
+       /* Don't let bad bits thru ... */\r
+       mode&=~(VL_IRQ_PENDING|VC_ROLLOVER|STOP_VOLUME|VOLUME_STOPPED);\r
+\r
+       begin = start;\r
+\r
+       if(start>end){\r
+               /* flip start & end if decreasing numbers ... */\r
+               start = end;\r
+               end = begin;\r
+               mode |= VC_DIRECT;              /* decreasing volumes */\r
+       }\r
+\r
+       /* looping below 64 or greater that 4032 can cause strange things */\r
+       if(start<64) start=64;\r
+       if(end>4032) end=4032;\r
+\r
+       GF1OutB(SET_VOLUME_RATE,rate);\r
+       GF1OutB(SET_VOLUME_START,start>>4);\r
+       GF1OutB(SET_VOLUME_END,end>>4);\r
+\r
+       /* Also MUST set the current volume to the start volume ... */\r
+       UltraSetVolume(begin);\r
+\r
+       vmode=GF1InB(GET_VOLUME_CONTROL);\r
+\r
+       if(vmode&VC_ROLLOVER) mode|=VC_ROLLOVER;\r
+\r
+       /* start 'er up !!! */\r
+       GF1OutB(SET_VOLUME_CONTROL,mode);\r
+       gf1_delay();\r
+       GF1OutB(SET_VOLUME_CONTROL,mode);\r
+}\r
+\r
+\r
+static void UltraVectorVolume(UWORD end,UBYTE rate,UBYTE mode)\r
+{\r
+       UltraStopVolume();\r
+       UltraRampVolume(UltraReadVolume(),end,rate,mode);\r
+}\r
+\r
+\r
+static int UltraVolumeStopped(void)\r
+{\r
+       return(GF1InB(GET_VOLUME_CONTROL) & (VOLUME_STOPPED|STOP_VOLUME));\r
+}\r
+\r
+\r
+static UWORD vol_rates[19]={\r
+23,24,26,28,29,31,32,34,36,37,39,40,42,44,45,47,49,50,52\r
+};\r
+\r
+\r
+static UBYTE UltraCalcRate(UWORD start,UWORD end,ULONG mil_secs)\r
+{\r
+       ULONG gap,mic_secs;\r
+       UWORD i,range,increment;\r
+       UBYTE rate_val;\r
+       UWORD value;\r
+\r
+       gap = (start>end) ? (start-end) : (end-start);\r
+       mic_secs = (mil_secs * 1000L)/gap;\r
+\r
+/* OK. We now have the # of microseconds for each update to go from */\r
+/* A to B in X milliseconds. See what the best fit is in the table */\r
+\r
+       range = 4;\r
+       value = vol_rates[GUS_VOICES-14];\r
+\r
+       for(i=0;i<3;i++){\r
+               if(mic_secs<value){\r
+                       range = i;\r
+                       break;\r
+               }\r
+               else value<<=3;\r
+       }\r
+\r
+       if(range==4){\r
+               range = 3;\r
+               increment = 1;\r
+       }\r
+       else{\r
+               /* calculate increment value ... (round it up ?) */\r
+               increment=(unsigned int)((value + (value>>1)) / mic_secs);\r
+       }\r
+\r
+       rate_val=range<<6;\r
+       rate_val|=(increment&0x3F);\r
+       return(rate_val);\r
+}\r
+\r
+\r
+static UWORD _gf1_volumes[512] = {\r
+ 0x0000,\r
+ 0x0700, 0x07ff, 0x0880, 0x08ff, 0x0940, 0x0980, 0x09c0, 0x09ff, 0x0a20,\r
+ 0x0a40, 0x0a60, 0x0a80, 0x0aa0, 0x0ac0, 0x0ae0, 0x0aff, 0x0b10, 0x0b20,\r
+ 0x0b30, 0x0b40, 0x0b50, 0x0b60, 0x0b70, 0x0b80, 0x0b90, 0x0ba0, 0x0bb0,\r
+ 0x0bc0, 0x0bd0, 0x0be0, 0x0bf0, 0x0bff, 0x0c08, 0x0c10, 0x0c18, 0x0c20,\r
+ 0x0c28, 0x0c30, 0x0c38, 0x0c40, 0x0c48, 0x0c50, 0x0c58, 0x0c60, 0x0c68,\r
+ 0x0c70, 0x0c78, 0x0c80, 0x0c88, 0x0c90, 0x0c98, 0x0ca0, 0x0ca8, 0x0cb0,\r
+ 0x0cb8, 0x0cc0, 0x0cc8, 0x0cd0, 0x0cd8, 0x0ce0, 0x0ce8, 0x0cf0, 0x0cf8,\r
+ 0x0cff, 0x0d04, 0x0d08, 0x0d0c, 0x0d10, 0x0d14, 0x0d18, 0x0d1c, 0x0d20,\r
+ 0x0d24, 0x0d28, 0x0d2c, 0x0d30, 0x0d34, 0x0d38, 0x0d3c, 0x0d40, 0x0d44,\r
+ 0x0d48, 0x0d4c, 0x0d50, 0x0d54, 0x0d58, 0x0d5c, 0x0d60, 0x0d64, 0x0d68,\r
+ 0x0d6c, 0x0d70, 0x0d74, 0x0d78, 0x0d7c, 0x0d80, 0x0d84, 0x0d88, 0x0d8c,\r
+ 0x0d90, 0x0d94, 0x0d98, 0x0d9c, 0x0da0, 0x0da4, 0x0da8, 0x0dac, 0x0db0,\r
+ 0x0db4, 0x0db8, 0x0dbc, 0x0dc0, 0x0dc4, 0x0dc8, 0x0dcc, 0x0dd0, 0x0dd4,\r
+ 0x0dd8, 0x0ddc, 0x0de0, 0x0de4, 0x0de8, 0x0dec, 0x0df0, 0x0df4, 0x0df8,\r
+ 0x0dfc, 0x0dff, 0x0e02, 0x0e04, 0x0e06, 0x0e08, 0x0e0a, 0x0e0c, 0x0e0e,\r
+ 0x0e10, 0x0e12, 0x0e14, 0x0e16, 0x0e18, 0x0e1a, 0x0e1c, 0x0e1e, 0x0e20,\r
+ 0x0e22, 0x0e24, 0x0e26, 0x0e28, 0x0e2a, 0x0e2c, 0x0e2e, 0x0e30, 0x0e32,\r
+ 0x0e34, 0x0e36, 0x0e38, 0x0e3a, 0x0e3c, 0x0e3e, 0x0e40, 0x0e42, 0x0e44,\r
+ 0x0e46, 0x0e48, 0x0e4a, 0x0e4c, 0x0e4e, 0x0e50, 0x0e52, 0x0e54, 0x0e56,\r
+ 0x0e58, 0x0e5a, 0x0e5c, 0x0e5e, 0x0e60, 0x0e62, 0x0e64, 0x0e66, 0x0e68,\r
+ 0x0e6a, 0x0e6c, 0x0e6e, 0x0e70, 0x0e72, 0x0e74, 0x0e76, 0x0e78, 0x0e7a,\r
+ 0x0e7c, 0x0e7e, 0x0e80, 0x0e82, 0x0e84, 0x0e86, 0x0e88, 0x0e8a, 0x0e8c,\r
+ 0x0e8e, 0x0e90, 0x0e92, 0x0e94, 0x0e96, 0x0e98, 0x0e9a, 0x0e9c, 0x0e9e,\r
+ 0x0ea0, 0x0ea2, 0x0ea4, 0x0ea6, 0x0ea8, 0x0eaa, 0x0eac, 0x0eae, 0x0eb0,\r
+ 0x0eb2, 0x0eb4, 0x0eb6, 0x0eb8, 0x0eba, 0x0ebc, 0x0ebe, 0x0ec0, 0x0ec2,\r
+ 0x0ec4, 0x0ec6, 0x0ec8, 0x0eca, 0x0ecc, 0x0ece, 0x0ed0, 0x0ed2, 0x0ed4,\r
+ 0x0ed6, 0x0ed8, 0x0eda, 0x0edc, 0x0ede, 0x0ee0, 0x0ee2, 0x0ee4, 0x0ee6,\r
+ 0x0ee8, 0x0eea, 0x0eec, 0x0eee, 0x0ef0, 0x0ef2, 0x0ef4, 0x0ef6, 0x0ef8,\r
+ 0x0efa, 0x0efc, 0x0efe, 0x0eff, 0x0f01, 0x0f02, 0x0f03, 0x0f04, 0x0f05,\r
+ 0x0f06, 0x0f07, 0x0f08, 0x0f09, 0x0f0a, 0x0f0b, 0x0f0c, 0x0f0d, 0x0f0e,\r
+ 0x0f0f, 0x0f10, 0x0f11, 0x0f12, 0x0f13, 0x0f14, 0x0f15, 0x0f16, 0x0f17,\r
+ 0x0f18, 0x0f19, 0x0f1a, 0x0f1b, 0x0f1c, 0x0f1d, 0x0f1e, 0x0f1f, 0x0f20,\r
+ 0x0f21, 0x0f22, 0x0f23, 0x0f24, 0x0f25, 0x0f26, 0x0f27, 0x0f28, 0x0f29,\r
+ 0x0f2a, 0x0f2b, 0x0f2c, 0x0f2d, 0x0f2e, 0x0f2f, 0x0f30, 0x0f31, 0x0f32,\r
+ 0x0f33, 0x0f34, 0x0f35, 0x0f36, 0x0f37, 0x0f38, 0x0f39, 0x0f3a, 0x0f3b,\r
+ 0x0f3c, 0x0f3d, 0x0f3e, 0x0f3f, 0x0f40, 0x0f41, 0x0f42, 0x0f43, 0x0f44,\r
+ 0x0f45, 0x0f46, 0x0f47, 0x0f48, 0x0f49, 0x0f4a, 0x0f4b, 0x0f4c, 0x0f4d,\r
+ 0x0f4e, 0x0f4f, 0x0f50, 0x0f51, 0x0f52, 0x0f53, 0x0f54, 0x0f55, 0x0f56,\r
+ 0x0f57, 0x0f58, 0x0f59, 0x0f5a, 0x0f5b, 0x0f5c, 0x0f5d, 0x0f5e, 0x0f5f,\r
+ 0x0f60, 0x0f61, 0x0f62, 0x0f63, 0x0f64, 0x0f65, 0x0f66, 0x0f67, 0x0f68,\r
+ 0x0f69, 0x0f6a, 0x0f6b, 0x0f6c, 0x0f6d, 0x0f6e, 0x0f6f, 0x0f70, 0x0f71,\r
+ 0x0f72, 0x0f73, 0x0f74, 0x0f75, 0x0f76, 0x0f77, 0x0f78, 0x0f79, 0x0f7a,\r
+ 0x0f7b, 0x0f7c, 0x0f7d, 0x0f7e, 0x0f7f, 0x0f80, 0x0f81, 0x0f82, 0x0f83,\r
+ 0x0f84, 0x0f85, 0x0f86, 0x0f87, 0x0f88, 0x0f89, 0x0f8a, 0x0f8b, 0x0f8c,\r
+ 0x0f8d, 0x0f8e, 0x0f8f, 0x0f90, 0x0f91, 0x0f92, 0x0f93, 0x0f94, 0x0f95,\r
+ 0x0f96, 0x0f97, 0x0f98, 0x0f99, 0x0f9a, 0x0f9b, 0x0f9c, 0x0f9d, 0x0f9e,\r
+ 0x0f9f, 0x0fa0, 0x0fa1, 0x0fa2, 0x0fa3, 0x0fa4, 0x0fa5, 0x0fa6, 0x0fa7,\r
+ 0x0fa8, 0x0fa9, 0x0faa, 0x0fab, 0x0fac, 0x0fad, 0x0fae, 0x0faf, 0x0fb0,\r
+ 0x0fb1, 0x0fb2, 0x0fb3, 0x0fb4, 0x0fb5, 0x0fb6, 0x0fb7, 0x0fb8, 0x0fb9,\r
+ 0x0fba, 0x0fbb, 0x0fbc, 0x0fbd, 0x0fbe, 0x0fbf, 0x0fc0, 0x0fc1, 0x0fc2,\r
+ 0x0fc3, 0x0fc4, 0x0fc5, 0x0fc6, 0x0fc7, 0x0fc8, 0x0fc9, 0x0fca, 0x0fcb,\r
+ 0x0fcc, 0x0fcd, 0x0fce, 0x0fcf, 0x0fd0, 0x0fd1, 0x0fd2, 0x0fd3, 0x0fd4,\r
+ 0x0fd5, 0x0fd6, 0x0fd7, 0x0fd8, 0x0fd9, 0x0fda, 0x0fdb, 0x0fdc, 0x0fdd,\r
+ 0x0fde, 0x0fdf, 0x0fe0, 0x0fe1, 0x0fe2, 0x0fe3, 0x0fe4, 0x0fe5, 0x0fe6,\r
+ 0x0fe7, 0x0fe8, 0x0fe9, 0x0fea, 0x0feb, 0x0fec, 0x0fed, 0x0fee, 0x0fef,\r
+ 0x0ff0, 0x0ff1, 0x0ff2, 0x0ff3, 0x0ff4, 0x0ff5, 0x0ff6, 0x0ff7, 0x0ff8,\r
+ 0x0ff9, 0x0ffa, 0x0ffb, 0x0ffc, 0x0ffd, 0x0ffe, 0x0fff\r
+};\r
+\r
+\r
+static void UltraSetLinearVolume(UWORD index)\r
+{\r
+       UltraSetVolume(_gf1_volumes[index]);\r
+}\r
+\r
+\r
+static void UltraRampLinearVolume(UWORD start_idx,UWORD end_idx,ULONG msecs,UBYTE mode)\r
+{\r
+       UWORD start,end;\r
+       UBYTE rate;\r
+\r
+       /* Ramp from start to end in x milliseconds */\r
+\r
+       start = _gf1_volumes[start_idx];\r
+       end   = _gf1_volumes[end_idx];\r
+\r
+       /* calculate a rate to get from start to end in msec milliseconds .. */\r
+       rate=UltraCalcRate(start,end,msecs);\r
+       UltraRampVolume(start,end,rate,mode);\r
+}\r
+\r
+\r
+static void UltraVectorLinearVolume(UWORD end_idx,UBYTE rate,UBYTE mode)\r
+{\r
+       UltraStopVolume();\r
+       UltraRampVolume(UltraReadVolume(),_gf1_volumes[end_idx],rate,mode);\r
+}\r
+\r
+\r
+static void UltraStartTimer(UBYTE timer,UBYTE time)\r
+{\r
+       UBYTE temp;\r
+\r
+       if (timer == 1){\r
+               GUS_TIMER_CTRL |= 0x04;\r
+               GUS_TIMER_MASK |= 0x01;\r
+               temp = TIMER1;\r
+       }\r
+       else{\r
+               GUS_TIMER_CTRL |= 0x08;\r
+               GUS_TIMER_MASK |= 0x02;\r
+               temp = TIMER2;\r
+       }\r
+\r
+/*     ENTER_CRITICAL; */\r
+\r
+       time = 256 - time;\r
+\r
+       GF1OutB(temp,time);                                                     /* set timer speed */\r
+       GF1OutB(TIMER_CONTROL,GUS_TIMER_CTRL);          /* enable timer interrupt on gf1 */\r
+       outportb(GF1_TIMER_CTRL,0x04);                          /* select timer stuff */\r
+       outportb(GF1_TIMER_DATA,GUS_TIMER_MASK);        /* start the timers */\r
+\r
+/*     LEAVE_CRITICAL; */\r
+}\r
+\r
+\r
+static void UltraStopTimer(int timer)\r
+{\r
+       if (timer == 1){\r
+               GUS_TIMER_CTRL &= ~0x04;\r
+               GUS_TIMER_MASK &= ~0x01;\r
+       }\r
+       else{\r
+               GUS_TIMER_CTRL &= ~0x08;\r
+               GUS_TIMER_MASK &= ~0x02;\r
+       }\r
+\r
+/*     ENTER_CRITICAL; */\r
+\r
+       GF1OutB(TIMER_CONTROL,GUS_TIMER_CTRL);          /* disable timer interrupts */\r
+       outportb(GF1_TIMER_CTRL,0x04);                          /* select timer stuff */\r
+       outportb(GF1_TIMER_DATA,GUS_TIMER_MASK | 0x80);\r
+\r
+/*     LEAVE_CRITICAL; */\r
+}\r
+\r
+\r
+static BOOL UltraTimerStopped(UBYTE timer)\r
+{\r
+       UBYTE value;\r
+       UBYTE temp;\r
+\r
+       if (timer == 1)\r
+               temp = 0x40;\r
+       else\r
+               temp = 0x20;\r
+\r
+       value = (inportb(GF1_TIMER_CTRL)) & temp;\r
+\r
+       return(value);\r
+}\r
+\r
+\r
+/***************************************************************************\r
+>>>>>>>>>>>>>>>>>>>>>>>>> The actual GUS driver <<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
+***************************************************************************/\r
+\r
+\r
+static ULONG Ultra[MAXSAMPLEHANDLES];\r
+static ULONG Ultrs[MAXSAMPLEHANDLES];\r
+\r
+/* Ultra[] holds the sample dram adresses\r
+   of the samples of a module */\r
+\r
+typedef struct{\r
+       UBYTE kick;                     /* =1 -> sample has to be restarted */\r
+       UBYTE active;                   /* =1 -> sample is playing */\r
+       UWORD flags;                    /* 16/8 bits looping/one-shot */\r
+       SWORD handle;                  /* identifies the sample */\r
+       ULONG start;                    /* start index */\r
+       ULONG size;                     /* samplesize */\r
+       ULONG reppos;                   /* loop start */\r
+       ULONG repend;                   /* loop end */\r
+       ULONG frq;                      /* current frequency */\r
+       UBYTE vol;                      /* current volume */\r
+       UBYTE pan;                                              /* current panning position */\r
+} GHOLD;\r
+\r
+static GHOLD ghld[32];\r
+\r
+\r
+static UBYTE timeskip;\r
+static UBYTE timecount;\r
+static UBYTE GUS_BPM;\r
+\r
+\r
+void UltraSetBPM(UBYTE bpm)\r
+{\r
+       /* The player routine has to be called (bpm*50)/125 times a second,\r
+          so the interval between calls takes 125/(bpm*50) seconds (amazing!).\r
+\r
+          The Timer1 handler has a resolution of 80 microseconds.\r
+\r
+          So the timer value to program:\r
+\r
+          (125/(bpm*50)) / 80e-6 = 31250/bpm\r
+       */\r
+       UWORD rate=31250/bpm;\r
+\r
+       timeskip=0;\r
+       timecount=0;\r
+\r
+       while(rate>255){\r
+               rate>>=1;\r
+               timeskip++;\r
+       }\r
+       UltraStartTimer(1,rate);\r
+}\r
+\r
+\r
+\r
+void GUS_Update(void)\r
+{\r
+       UBYTE t;\r
+       GHOLD *aud;\r
+       UWORD vol;\r
+       ULONG base,start,size,reppos,repend;\r
+\r
+       UWORD curvol,bigvol=0,bigvoc=0;\r
+\r
+       if(timecount<timeskip){\r
+               timecount++;\r
+               return;\r
+       }\r
+       timecount=0;\r
+\r
+       md_player();\r
+\r
+       if(GUS_BPM != md_bpm){\r
+               UltraSetBPM(md_bpm);\r
+               GUS_BPM=md_bpm;\r
+       }\r
+\r
+       /* ramp down voices that need to be started next */\r
+\r
+       for(t=0;t<md_numchn;t++){\r
+               UltraSelectVoice(t);\r
+               aud=&ghld[t];\r
+               if(aud->kick){\r
+                       curvol=UltraReadVolume();\r
+                       if(bigvol<=curvol){\r
+                               bigvol=curvol;\r
+                               bigvoc=t;\r
+                       }\r
+                       UltraVectorLinearVolume(0,0x3f,0);\r
+               }\r
+       }\r
+\r
+/*      while(!UltraVolumeStopped(bigvoc)); */\r
+\r
+       for(t=0;t<md_numchn;t++){\r
+               UltraSelectVoice(t);\r
+               aud=&ghld[t];\r
+\r
+               if(aud->kick){\r
+                       aud->kick=0;\r
+\r
+                       base=Ultra[aud->handle];\r
+\r
+                       start=aud->start;\r
+                       reppos=aud->reppos;\r
+                       repend=aud->repend;\r
+                       size=aud->size;\r
+\r
+                       if(aud->flags&SF_16BITS){\r
+                               start<<=1;\r
+                               reppos<<=1;\r
+                               repend<<=1;\r
+                               size<<=1;\r
+                       }\r
+\r
+                       /* Stop current sample and start a new one */\r
+\r
+                       UltraStopVoice();\r
+\r
+                       UltraSetFrequency(aud->frq);\r
+                       UltraVectorLinearVolume(6U*aud->vol,0x3f,0);\r
+                       UltraSetBalance(aud->pan>>4);\r
+\r
+                       if(aud->flags&SF_LOOP){\r
+\r
+                               /* Start a looping sample */\r
+\r
+                               UltraStartVoice(base+start,\r
+                                                               base+reppos,\r
+                                                               base+repend,0x8|((aud->flags&SF_16BITS)?4:0)|\r
+                                                               ((aud->flags&SF_BIDI)?16:0));\r
+                       }\r
+                       else{\r
+\r
+                               /* Start a one-shot sample */\r
+\r
+                               UltraStartVoice(base+start,\r
+                                                               base+start,\r
+                                                               base+size+2,(aud->flags&SF_16BITS)?4:0);\r
+                       }\r
+               }\r
+               else{\r
+                       UltraSetFrequency(aud->frq);\r
+                       UltraVectorLinearVolume(6U*aud->vol,0x3f,0);\r
+                       UltraSetBalance(aud->pan>>4);\r
+               }\r
+       }\r
+}\r
+\r
+\r
+SWORD GUS_Load(FILE *fp,ULONG length,ULONG loopstart,ULONG loopend,UWORD flags)\r
+/*\r
+       callback routine for the MODLOAD module.\r
+*/\r
+{\r
+       int handle,t;\r
+       long p,l;\r
+\r
+       SL_Init(fp,flags,flags|SF_SIGNED);\r
+\r
+       /* Find empty slot to put sample address in */\r
+\r
+       for(handle=0;handle<MAXSAMPLEHANDLES;handle++){\r
+               if(Ultra[handle]==0) break;\r
+       }\r
+\r
+       if(handle==MAXSAMPLEHANDLES){\r
+               myerr=ERROR_OUT_OF_HANDLES;\r
+               return -1;\r
+       }\r
+\r
+       if(flags&SF_16BITS){\r
+               length<<=1;\r
+               loopstart<<=1;\r
+               loopend<<=1;\r
+       }\r
+\r
+       /* Allocate GUS dram and store the address in Ultra[handle] */\r
+       /* Alloc 8 bytes more for anticlick measures. see below. */\r
+\r
+       /* 2.04: use UltraMalloc16 to allocate 16 bit samples */\r
+\r
+       if(!(Ultra[handle]=(flags&SF_16BITS) ? UltraMalloc16(length+8) : UltraMalloc(length+8) )){\r
+               myerr=ERROR_SAMPLE_TOO_BIG;\r
+               return -1;\r
+       }\r
+\r
+       /* Load the sample */\r
+\r
+       Ultrs[handle]=length+8;\r
+       p=Ultra[handle];\r
+       l=length;\r
+\r
+       while(l>0){\r
+                static UBYTE buffer[1024];\r
+               long todo;\r
+\r
+               todo=(l>1024) ? 1024 : l;\r
+\r
+               SL_Load(buffer,todo);\r
+\r
+               UltraPokeChunk(p,buffer,todo);\r
+\r
+               p+=todo;\r
+               l-=todo;\r
+       }\r
+\r
+       if(flags&SF_LOOP && !(flags&SF_BIDI)){  /* looping sample ? */\r
+\r
+               /*      Anticlick for looping samples:\r
+                       Copy the first bytes in the loop\r
+                       beyond the end of the loop */\r
+\r
+               for(t=0;t<8;t++){\r
+                       UltraPoke(Ultra[handle]+loopend+t,\r
+                                         UltraPeek(Ultra[handle]+loopstart+t));\r
+               }\r
+       }\r
+       else{\r
+\r
+               /*      Anticlick for one-shot samples:\r
+                       Zero the bytes beyond the end of the sample.\r
+               */\r
+\r
+               for(t=0;t<8;t++){\r
+                       UltraPoke(Ultra[handle]+length+t,0);\r
+               }\r
+       }\r
+\r
+       return handle;\r
+}\r
+\r
+\r
+\r
+void GUS_UnLoad(SWORD handle)\r
+/*\r
+       callback routine to unload samples\r
+\r
+       smp                     :sampleinfo of sample that is being freed\r
+*/\r
+{\r
+       UltraFree(Ultrs[handle],Ultra[handle]);\r
+       Ultra[handle]=0;\r
+}\r
+\r
+\r
+\r
+\r
+BOOL GUS_Init(void)\r
+{\r
+       ULONG p1,p2;\r
+       int irq;\r
+\r
+       if(!(md_mode&DMODE_16BITS)){\r
+               md_mode|=DMODE_16BITS;          /* gus can't do 8 bit mixing */\r
+       }\r
+\r
+       if(!(md_mode&DMODE_STEREO)){\r
+               md_mode|=DMODE_STEREO;          /* gus can't do mono mixing */\r
+       }\r
+\r
+       if(!UltraDetect()){\r
+               myerr="Couldn't detect gus, please check env. string";\r
+               return 0;\r
+       }\r
+\r
+       UltraOpen(14);\r
+       UltraTimer1Handler(GUS_Update);\r
+\r
+       return 1;\r
+}\r
+\r
+\r
+\r
+void GUS_Exit(void)\r
+{\r
+       UltraClose();\r
+}\r
+\r
+\r
+\r
+void GUS_PlayStart(void)\r
+{\r
+       int t;\r
+       for(t=0;t<md_numchn;t++){\r
+               ghld[t].flags=0;\r
+               ghld[t].handle=0;\r
+               ghld[t].kick=0;\r
+               ghld[t].active=0;\r
+               ghld[t].frq=10000;\r
+               ghld[t].vol=0;\r
+               ghld[t].pan=(t&1)?0:255;\r
+       }\r
+       UltraNumVoices(md_numchn);\r
+       UltraEnableOutput();\r
+       GUS_BPM=125;\r
+       UltraSetBPM(125);\r
+}\r
+\r
+\r
+\r
+void GUS_PlayStop(void)\r
+{\r
+       UltraStopTimer(1);\r
+       UltraDisableOutput();\r
+}\r
+\r
+\r
+BOOL GUS_IsThere(void)\r
+{\r
+       return(getenv("ULTRASND")!=NULL);\r
+}\r
+\r
+\r
+void GUS_VoiceSetVolume(UBYTE voice,UBYTE vol)\r
+{\r
+       ghld[voice].vol=vol;\r
+}\r
+\r
+\r
+void GUS_VoiceSetFrequency(UBYTE voice,ULONG frq)\r
+{\r
+       ghld[voice].frq=frq;\r
+}\r
+\r
+void GUS_VoiceSetPanning(UBYTE voice,UBYTE pan)\r
+{\r
+       ghld[voice].pan=pan;\r
+}\r
+\r
+void GUS_VoicePlay(UBYTE voice,SWORD handle,ULONG start,ULONG size,ULONG reppos,ULONG repend,UWORD flags)\r
+{\r
+       if(start>=size) return;\r
+\r
+       if(flags&SF_LOOP){\r
+               if(repend>size) repend=size;    /* repend can't be bigger than size */\r
+       }\r
+\r
+       ghld[voice].flags=flags;\r
+       ghld[voice].handle=handle;\r
+       ghld[voice].start=start;\r
+       ghld[voice].size=size;\r
+       ghld[voice].reppos=reppos;\r
+       ghld[voice].repend=repend;\r
+       ghld[voice].kick=1;\r
+}\r
+\r
+void GUS_Dummy(void)\r
+{\r
+}\r
+\r
+\r
+DRIVER drv_gus={\r
+       NULL,\r
+       "Gravis Ultrasound",\r
+       "MikMod GUS Driver v2.1 (uses gus timer interrupt)",\r
+       GUS_IsThere,\r
+       GUS_Load,\r
+       GUS_UnLoad,\r
+       GUS_Init,\r
+       GUS_Exit,\r
+       GUS_PlayStart,\r
+       GUS_PlayStop,\r
+       GUS_Dummy,\r
+       GUS_VoiceSetVolume,\r
+       GUS_VoiceSetFrequency,\r
+       GUS_VoiceSetPanning,\r
+       GUS_VoicePlay\r
+};\r