/*

Name: DRV_GUS.C

Description:
  Basic GUS driver routines used by all GUS modes.

Portability:

MSDOS:  BC(y)   Watcom(y)       DJGPP(y)
Win95:  n
Os2:    n
Linux:  n

(y) - yes
(n) - no (not possible or not useful)
(?) - may be possible, but not tested

*/

#include <dos.h>
#include <conio.h>
#include "gus.h"
#include "mirq.h"


static CHAR *ERROR_DETECTING_GUS = "Ultrasound not found.  Check your ULTRASND env. variable";


// ***************************************************************************
// >>>>>>>>>>>>>>>>>>>>>>>>> Lowlevel GUS defines <<<<<<<<<<<<<<<<<<<<<<<<<<<<
// ***************************************************************************

// Special macros for Least-most sig. bytes
#define MAKE_MSW(x)     ((long)((long)(x)) << 16)
#define LSW(x)          ((unsigned int)(x))
#define MSW(x)          ((unsigned int)(((long)x)>>16))
#define MSB(x)          (unsigned char)((unsigned int)(x)>>8)
#define LSB(x)          ((unsigned char)(x))

// Make GF1 address for direct chip i/o.
#define ADDR_HIGH(x) ((unsigned int)((unsigned int)((x>>7L)&0x1fffL)))
#define ADDR_LOW(x)  ((unsigned int)((unsigned int)((x&0x7fL)<<9L)))

#define JOYSTICK_TIMER   (GUS_PORT+0x201)                // 201
#define JOYSTICK_DATA    (GUS_PORT+0x201)                // 201

#define GF1_MIDI_CTRL    (GUS_PORT+0x100)                // 3X0
#define GF1_MIDI_DATA    (GUS_PORT+0x101)                // 3X1

#define GF1_PAGE         (GUS_PORT+0x102)                // 3X2
#define GF1_REG_SELECT   (GUS_PORT+0x103)                // 3X3
#define GF1_VOICE_SELECT (GUS_PORT+0x102)                // 3X3
#define GF1_DATA_LOW     (GUS_PORT+0x104)                // 3X4
#define GF1_DATA_HI      (GUS_PORT+0x105)                // 3X5
#define GF1_IRQ_STAT     (GUS_PORT+0x006)                // 2X6
#define GF1_DRAM         (GUS_PORT+0x107)                // 3X7

#define GF1_MIX_CTRL     (GUS_PORT+0x000)                // 2X0
#define GF1_TIMER_CTRL   (GUS_PORT+0x008)                // 2X8
#define GF1_TIMER_DATA   (GUS_PORT+0x009)                // 2X9
#define GF1_IRQ_CTRL     (GUS_PORT+0x00B)                // 2XB

// The GF1 Hardware clock.
#define CLOCK_RATE              9878400L

// Mixer control bits.
#define ENABLE_LINE             0x01
#define ENABLE_DAC              0x02
#define ENABLE_MIC              0x04

// interrupt controller 1
#define CNTRL_8259              0x21
#define OCR_8259                0x20
#define EOI                     0x20
#define REARM3                  0x2F3
#define REARM5                  0x2F5

// interrupt controller 2
#define CNTRL_M_8259            0x21
#define CNTRL_M2_8259           0xA1
#define OCR_2_8259              0xA0

#define DMA_CONTROL             0x41
#define SET_DMA_ADDRESS         0x42
#define SET_DRAM_LOW            0x43
#define SET_DRAM_HIGH           0x44

#define TIMER_CONTROL           0x45
#define TIMER1                  0x46
#define TIMER2                  0x47

#define SET_SAMPLE_RATE         0x48
#define SAMPLE_CONTROL          0x49

#define SET_JOYSTICK            0x4B
#define MASTER_RESET            0x4C

// Voice register mapping.
#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_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 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

/********************************************************************
 *
 * MIDI defines
 *
 *******************************************************************/

#define MIDI_RESET      0x03
#define MIDI_ENABLE_XMIT        0x20
#define MIDI_ENABLE_RCV         0x80

#define MIDI_RCV_FULL           0x01
#define MIDI_XMIT_EMPTY         0x02
#define MIDI_FRAME_ERR          0x10
#define MIDI_OVERRUN            0x20
#define MIDI_IRQ_PEND           0x80

/********************************************************************
 *
 * JOYSTICK defines
 *
 *******************************************************************/

#define JOY_POSITION            0x0f
#define JOY_BUTTONS             0xf0

/********************************************************************
 *
 * GF1 irq/dma programmable latches
 *
 *******************************************************************/

// GF1_IRQ_STATUS (port 3X6)
#define MIDI_TX_IRQ             0x01            // pending MIDI xmit IRQ
#define MIDI_RX_IRQ             0x02            // pending MIDI recv IRQ
#define GF1_TIMER1_IRQ          0x04            // general purpose timer
#define GF1_TIMER2_IRQ          0x08            // general purpose timer
#define WAVETABLE_IRQ           0x20            // pending wavetable IRQ
#define ENVELOPE_IRQ            0x40            // pending volume envelope IRQ
#define DMA_TC_IRQ              0x80            // pending dma tc IRQ


// GF1_MIX_CTRL (port 2X0)
#define ENABLE_LINE_IN          0x01            // 0=enable
#define ENABLE_OUTPUT           0x02            // 0=enable
#define ENABLE_MIC_IN           0x04            // 1=enable
#define ENABLE_GF1_IRQ          0x08            // 1=enable
#define GF122                   0x10            // ??
#define ENABLE_MIDI_LOOP        0x20            // 1=enable loop back
#define SELECT_GF1_REG          0x40            // 0=irq latches
                                                                                /* 1=dma latches */

/********************************************************************
 *
 * GF1 global registers ($41-$4C)
 *
 *******************************************************************/

// DMA control register
#define DMA_ENABLE              0x01
#define DMA_READ                0x02            // 1=read,0=write
#define DMA_WIDTH_16            0x04            // 1=16 bit,0=8 bit (dma chan width)
#define DMA_RATE                0x18            // 00=fast, 11=slow
#define DMA_IRQ_ENABLE          0x20            // 1=enable
#define DMA_IRQ_PENDING         0x40            // read
#define DMA_DATA_16             0x40            // write (data width)
#define DMA_TWOS_COMP           0x80            // 1=do twos comp

// These are the xfer rate bits ...
#define DMA_R0          0x00            // Fastest DMA xfer (~650khz)
#define DMA_R1          0x08            // fastest / 2
#define DMA_R2          0x10            // fastest / 4
#define DMA_R3          0x18            // Slowest DMA xfer (fastest / 8)

// SAMPLE control register 
#define ENABLE_ADC              0x01
#define ADC_MODE                0x02            // 0=mono, 1=stereo
#define ADC_DMA_WIDTH           0x04            // 0=8 bit, 1=16 bit
#define ADC_IRQ_ENABLE          0x20            // 1=enable
#define ADC_IRQ_PENDING         0x40            // 1=irq pending
#define ADC_TWOS_COMP           0x80            // 1=do twos comp

// RESET control register
#define GF1_MASTER_RESET        0x01            // 0=hold in reset
#define GF1_OUTPUT_ENABLE       0x02            // enable output
#define GF1_MASTER_IRQ          0x04            // master IRQ enable

/********************************************************************
 *
 * GF1 voice specific registers ($00 - $0E and $80-$8f)
 *
 *******************************************************************/

// ($0,$80) Voice control register
#define VOICE_STOPPED           0x01            // voice has stopped
#define STOP_VOICE              0x02            // stop voice
#define VC_DATA_TYPE            0x04            // 0=8 bit,1=16 bit
#define VC_LOOP_ENABLE          0x08            // 1=enable
#define VC_BI_LOOP              0x10            // 1=bi directional looping
#define VC_WAVE_IRQ             0x20            // 1=enable voice's wave irq
#define VC_DIRECT               0x40            // 0=increasing,1=decreasing
#define VC_IRQ_PENDING          0x80            // 1=wavetable irq pending

// ($1,$81) Frequency control
// Bit 0  - Unused
// Bits 1-9 - Fractional portion
// Bits 10-15 - Integer portion

// ($2,$82) Accumulator start address (high)
// Bits 0-11 - HIGH 12 bits of address
// Bits 12-15 - Unused

// ($3,$83) Accumulator start address (low)
// Bits 0-4 - Unused
// Bits 5-8 - Fractional portion
// Bits 9-15 - Low 7 bits of integer portion

// ($4,$84) Accumulator end address (high)
// Bits 0-11 - HIGH 12 bits of address
// Bits 12-15 - Unused

// ($5,$85) Accumulator end address (low)
// Bits 0-4 - Unused
// Bits 5-8 - Fractional portion
// Bits 9-15 - Low 7 bits of integer portion


// ($6,$86) Volume Envelope control register
#define VL_RATE_MANTISSA                0x3f
#define VL_RATE_RANGE                   0xC0

// ($7,$87) Volume envelope start
#define VL_START_MANT                   0x0F
#define VL_START_EXP                    0xF0

// ($8,$88) Volume envelope end
#define VL_END_MANT                     0x0F
#define VL_END_EXP                      0xF0

// ($9,$89) Current volume register
// Bits 0-3 are unused
// Bits 4-11 - Mantissa of current volume
// Bits 10-15 - Exponent of current volume

// ($A,$8A) Accumulator value (high)
// Bits 0-12 - HIGH 12 bits of current position (a19-a7)

// ($B,$8B) Accumulator value (low)
// Bits 0-8 - Fractional portion
// Bits 9-15 - Integer portion of low adress (a6-a0)

// ($C,$8C) Pan (balance) position
// Bits 0-3 - Balance position  0=full left, 0x0f=full right

// ($D,$8D) Volume control register
#define VOLUME_STOPPED          0x01            // volume has stopped
#define STOP_VOLUME             0x02            // stop volume
#define VC_ROLLOVER             0x04            // Roll PAST end & gen IRQ
#define VL_LOOP_ENABLE          0x08            // 1=enable
#define VL_BI_LOOP              0x10            // 1=bi directional looping
#define VL_WAVE_IRQ             0x20            // 1=enable voice's wave irq
#define VL_DIRECT               0x40            // 0=increasing,1=decreasing
#define VL_IRQ_PENDING          0x80            // 1=wavetable irq pending

// ($E,$8E) # of Active voices
// Bits 0-5 - # of active voices -1

// ($F,$8F) - Sources of IRQs
// Bits 0-4 - interrupting voice number
// Bit 5 - Always a 1
#define VOICE_VOLUME_IRQ        0x40            // individual voice irq bit
#define VOICE_WAVE_IRQ          0x80            // individual waveform irq bit


/***************************************************************************
>>>>>>>>>>>>>>>>>>>>>>>>> Lowlevel GUS code <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
***************************************************************************/

static UWORD GUS_PORT;
static UBYTE GUS_VOICES;
static UBYTE GUS_TIMER_CTRL;
static UBYTE GUS_TIMER_MASK;
static UBYTE GUS_MIX_IMAGE;

UWORD GUS_DRAM_DMA;
static UWORD GUS_ADC_DMA;
static UWORD GUS_GF1_IRQ;
static UWORD GUS_MIDI_IRQ;
static ULONG GUS_POOL;           // dram address of first gusmem pool node

static UBYTE GUS_SELECT;         // currently selected GF1 register

static volatile BOOL DMA_DRAM_BUSY = 0;

static void (*GUS_TIMER1_FUNC)(void);
static void (*GUS_TIMER2_FUNC)(void);
static void (*GUS_DMA_FUNC)(void);

#define UltraSelect(x) outportb(GF1_REG_SELECT,GUS_SELECT=x)

#define USE_ROLLOVER 0

/***************************************************************
 * This function will convert the value read from the GF1 registers
 * back to a 'real' address.
 ***************************************************************/

#define MAKE_MS_SWORD( x )       ((unsigned long)((unsigned long)(x)) << 16)

static ULONG make_physical_address(UWORD low,UWORD high,UBYTE mode)
{
        UWORD lower_16, upper_16;
        ULONG ret_address, bit_19_20;

        upper_16 = high >> 9;
        lower_16 = ((high & 0x01ff) << 7) | ((low >> 9) & 0x007f);

        ret_address = MAKE_MS_SWORD(upper_16) + lower_16;

        if (mode & VC_DATA_TYPE)
        {   bit_19_20 = ret_address & 0xC0000;
            ret_address <<= 1;
            ret_address &= 0x3ffff;
            ret_address |= bit_19_20;
        }

        return( ret_address );
}

/***************************************************************
 * This function will translate the address if the dma channel
 * is a 16 bit channel. This translation is not necessary for
 * an 8 bit dma channel.
 ***************************************************************/

static ULONG convert_to_16bit(ULONG address)
// unsigned long address;               // 20 bit ultrasound dram address
{
    ULONG hold_address;

    hold_address = address;

    // Convert to 16 translated address.
    address = address >> 1;

    // Zero out bit 17.
    address &= 0x0001ffffL;

    // Reset bits 18 and 19.
    address |= (hold_address & 0x000c0000L);

    return(address);
}


static void GF1OutB(UBYTE x, UBYTE y)
{
    UltraSelect(x);
    outportb(GF1_DATA_HI,y);
}


static void GF1OutW(UBYTE x, UWORD y)
{
    UltraSelect(x);
    outport(GF1_DATA_LOW,y);
}


static UBYTE GF1InB(UBYTE x)
{
    UltraSelect(x);
    return inportb(GF1_DATA_HI);
}


static UWORD GF1InW(UBYTE x)
{
    UltraSelect(x);
    return inport(GF1_DATA_LOW);
}


static void gf1_delay(void)
{
    inportb(GF1_DRAM);  inportb(GF1_DRAM);
    inportb(GF1_DRAM);  inportb(GF1_DRAM);
    inportb(GF1_DRAM);  inportb(GF1_DRAM);
    inportb(GF1_DRAM);
}


static UBYTE UltraPeek(ULONG address)
{
    GF1OutW(SET_DRAM_LOW,address);
    GF1OutB(SET_DRAM_HIGH,(address>>16)&0xff);      // 8 bits
    return(inportb(GF1_DRAM));
}


static void UltraPoke(ULONG address,UBYTE data)
{
    GF1OutW(SET_DRAM_LOW,address);
    GF1OutB(SET_DRAM_HIGH,(address>>16)&0xff);
    outportb(GF1_DRAM,data);
}


static void UltraPokeFast(ULONG address, UBYTE *src,ULONG size)

//  [address,size> doesn't cross 64k page boundary

{
    if(!size) return;

    UltraSelect(SET_DRAM_HIGH);
    outportb(GF1_DATA_HI,(address>>16)&0xff);       // 8 bits
    UltraSelect(SET_DRAM_LOW);

    while(size--)
    {   outport(GF1_DATA_LOW,address);
        outportb(GF1_DRAM,*src);
        address++;
        src++;
    }
}


static void UltraPokeChunk(ULONG address, UBYTE *src, ULONG size)
{
    ULONG todo;

    // first 'todo' is number of bytes 'till first 64k boundary

    todo=0x10000-(address&0xffff);
    if(todo>size) todo=size;

    do
    {   UltraPokeFast(address,src,todo);
        address += todo;
        src     += todo;
        size    -= todo;

        // next 'todo' is in chunks of max 64k at once
        todo=(size>0xffff) ? 0x10000 : size;

    } while(todo);
}


static ULONG UltraPeekLong(ULONG address)
{
    ULONG data;
    char *s = (char *)&data;

    s[0] = UltraPeek(address);
    s[1] = UltraPeek(address+1);
    s[2] = UltraPeek(address+2);
    s[3] = UltraPeek(address+3);
    return data;
}


static void UltraPokeLong(ULONG address,ULONG data)
{
    UltraPokeChunk(address,(UBYTE *)&data,4);
}


static void UltraEnableOutput(void)
{
    GUS_MIX_IMAGE &= ~ENABLE_OUTPUT;
    outportb(GF1_MIX_CTRL,GUS_MIX_IMAGE);
}

void UltraDisableOutput(void)
{
    GUS_MIX_IMAGE |= ENABLE_OUTPUT;
    outportb(GF1_MIX_CTRL,GUS_MIX_IMAGE);
}

/*
static void UltraEnableLineIn(void)
{
    GUS_MIX_IMAGE &= ~ENABLE_LINE_IN;
    outportb(GF1_MIX_CTRL,GUS_MIX_IMAGE);
}
*/
static void UltraDisableLineIn(void)
{
    GUS_MIX_IMAGE |= ENABLE_LINE_IN;
    outportb(GF1_MIX_CTRL,GUS_MIX_IMAGE);
}

/*
static void UltraEnableMicIn(void)
{
    GUS_MIX_IMAGE |= ENABLE_MIC_IN;
    outportb(GF1_MIX_CTRL,GUS_MIX_IMAGE);
}
*/

static void UltraDisableMicIn(void)
{
    GUS_MIX_IMAGE &= ~ENABLE_MIC_IN;
    outportb(GF1_MIX_CTRL,GUS_MIX_IMAGE);
}


static void UltraReset(int voices)
{
    int v;

    if(voices<14) voices = 14;
    if(voices>32) voices = 32;

    GUS_VOICES     = voices;
    GUS_TIMER_CTRL = 0;
    GUS_TIMER_MASK = 0;

    UltraPokeLong(0,0);

    GF1OutB(MASTER_RESET,0x00);
    for(v=0;v<10;v++) gf1_delay();

    // Release Reset and wait
    GF1OutB(MASTER_RESET,GF1_MASTER_RESET);
    for (v=0;v<10;v++) gf1_delay();

    // Reset the MIDI port also
    outportb(GF1_MIDI_CTRL,MIDI_RESET);
    for (v=0;v<10;v++) gf1_delay();
    outportb(GF1_MIDI_CTRL,0x00);

    // Clear all interrupts.
    GF1OutB(DMA_CONTROL,0x00);
    GF1OutB(TIMER_CONTROL,0x00);
    GF1OutB(SAMPLE_CONTROL,0x00);

    // Set the number of active voices
    GF1OutB(SET_VOICES,((voices-1) | 0xC0));

    // Clear interrupts on voices.
    // Reading the status ports will clear the irqs.

    inportb(GF1_IRQ_STAT);

    GF1InB(DMA_CONTROL);
    GF1InB(SAMPLE_CONTROL);
    GF1InB(GET_IRQV);

    for(v=0; v<voices; v++)
    {   // Select the proper voice
        outportb(GF1_PAGE,v);

        // Stop the voice and volume
        GF1OutB(SET_CONTROL,VOICE_STOPPED|STOP_VOICE);
        GF1OutB(SET_VOLUME_CONTROL,VOLUME_STOPPED|STOP_VOLUME);
         
        gf1_delay(); // Wait 4.8 micos. or more

        // Initialize each voice specific registers. This is not
        // really necessary, but is nice for completeness sake ..
        // Each application will set up these to whatever values
        // it needs.

        GF1OutW(SET_FREQUENCY,0x0400);
        GF1OutW(SET_START_HIGH,0);
        GF1OutW(SET_START_LOW,0);
        GF1OutW(SET_END_HIGH,0);
        GF1OutW(SET_END_LOW,0);
        GF1OutB(SET_VOLUME_RATE,0x01);
        GF1OutB(SET_VOLUME_START,0x10);
        GF1OutB(SET_VOLUME_END,0xe0);
        GF1OutW(SET_VOLUME,0x0000);

        GF1OutW(SET_ACC_HIGH,0);
        GF1OutW(SET_ACC_LOW,0);
        GF1OutB(SET_BALANCE,0x07);
    }

    inportb(GF1_IRQ_STAT);

    GF1InB(DMA_CONTROL);
    GF1InB(SAMPLE_CONTROL);
    GF1InB(GET_IRQV);

    // Set up GF1 Chip for interrupts & enable DACs.
/*      GF1OutB(MASTER_RESET,GF1_MASTER_RESET|GF1_OUTPUT_ENABLE); */
    GF1OutB(MASTER_RESET,GF1_MASTER_RESET|GF1_OUTPUT_ENABLE|GF1_MASTER_IRQ);
}


static BOOL UltraProbe(void)
{
    UBYTE s1,s2,t1,t2;

    // Pull a reset on the GF1
    GF1OutB(MASTER_RESET,0x00);

    // Wait a little while ...
    gf1_delay();
    gf1_delay();

    // Release Reset
    GF1OutB(MASTER_RESET,GF1_MASTER_RESET);

    gf1_delay();
    gf1_delay();

    s1=UltraPeek(0);   s2 = UltraPeek(1);
    UltraPoke(0,0xaa); t1 = UltraPeek(0);
    UltraPoke(1,0x55); t2 = UltraPeek(1);
    UltraPoke(0,s1);   UltraPoke(1,s2);

    return(t1==0xaa && t2==0x55);
}



static BOOL UltraDetect(void)
{
    CHAR *ptr;

    if((ptr=getenv("ULTRASND"))==NULL) return 0;

    if(sscanf(ptr,"%hx,%hd,%hd,%hd,%hd",
                  &GUS_PORT,
                  &GUS_DRAM_DMA,
                  &GUS_ADC_DMA,
                  &GUS_GF1_IRQ,
                  &GUS_MIDI_IRQ)!=5) return 0;

    return(UltraProbe());
}




static UBYTE dmalatch[8]      = { 0,1,0,2,0,3,4,5 };
static UBYTE irqlatch[16]     = { 0,0,1,3,0,2,0,4,0,0,0,5,6,0,0,7 };


static void UltraSetInterface(int dram,int adc,int gf1,int midi)
/* int dram;    // dram dma chan */
/* int adc;             // adc dma chan */
/* int gf1;             // gf1 irq # */
/* int midi;    // midi irq # */
{
    UBYTE gf1_irq, midi_irq,dram_dma,adc_dma;
    UBYTE irq_control,dma_control;
    UBYTE mix_image;

    /* Don't need to check for 0 irq #. Its latch entry = 0 */
    gf1_irq =irqlatch[gf1];
    midi_irq=irqlatch[midi];
    midi_irq<<=3;

    dram_dma = dmalatch[dram];
    adc_dma  = dmalatch[adc];
    adc_dma<<=3;

    irq_control = dma_control = 0x0;

    mix_image   = GUS_MIX_IMAGE;

    irq_control|=gf1_irq;

    if((gf1==midi) && (gf1!=0))
       irq_control|=0x40;
    else
      irq_control|=midi_irq;

    dma_control|=dram_dma;

    if((dram==adc) && (dram!=0))
       dma_control|=0x40;
    else
       dma_control|=adc_dma;

    // Set up for Digital ASIC
    outportb(GUS_PORT+0x0f,0x5);
    outportb(GF1_MIX_CTRL,mix_image);
    outportb(GF1_IRQ_CTRL,0x0);
    outportb(GUS_PORT+0x0f,0x0);

    // First do DMA control register
    outportb(GF1_MIX_CTRL,mix_image);
    outportb(GF1_IRQ_CTRL,dma_control|0x80);

    // IRQ CONTROL REG
    outportb(GF1_MIX_CTRL,mix_image|0x40);
    outportb(GF1_IRQ_CTRL,irq_control);

    // First do DMA control register
    outportb(GF1_MIX_CTRL,mix_image);
    outportb(GF1_IRQ_CTRL,dma_control);

    // IRQ CONTROL REG
    outportb(GF1_MIX_CTRL,mix_image|0x40);
    outportb(GF1_IRQ_CTRL,irq_control);

    // IRQ CONTROL, ENABLE IRQ
    // just to Lock out writes to irq\dma register ...
    outportb(GF1_VOICE_SELECT,0);

    // enable output & irq, disable line & mic input
    mix_image|=0x09;
    outportb(GF1_MIX_CTRL,mix_image);

    // just to Lock out writes to irq\dma register ...
    outportb(GF1_VOICE_SELECT,0x0);

    // put image back ....
    GUS_MIX_IMAGE=mix_image;
}


static BOOL UltraPP(ULONG address)
{
    UBYTE s,t;
    
    s = UltraPeek(address);
    UltraPoke(address,0xaa);
    t = UltraPeek(address);
    UltraPoke(address,s);

    return(t==0xaa);
}


static UWORD UltraSizeDram(void)
{
    if(!UltraPP(0))      return 0;
    if(!UltraPP(262144)) return 256;
    if(!UltraPP(524288)) return 512;
    if(!UltraPP(786432)) return 768;
    return 1024;
}

ULONG UltraMemTotal(void)
{
    ULONG node = GUS_POOL,nsize,total=0;

    while(node!=0)
    {   nsize  = UltraPeekLong(node);
        total += nsize;
        node   = UltraPeekLong(node+4);
    }
    return total;
}


static BOOL Mergeable(ULONG a,ULONG b)
{
    return(a && b && (a+UltraPeekLong(a))==b);
}


static ULONG Merge(ULONG a,ULONG b)
{
    UltraPokeLong(a,UltraPeekLong(a)+UltraPeekLong(b));
    UltraPokeLong(a+4,UltraPeekLong(b+4));
    return a;
}


void UltraFree(ULONG size, ULONG location)
{
    ULONG pred=0,succ=GUS_POOL;

    if(!size) return;
    size+=31;
    size&=-32L;

    UltraPokeLong(location,size);

    while(succ!=0 && succ<=location)
    {  pred = succ;
       succ = UltraPeekLong(succ+4);
    }

    if(pred)
       UltraPokeLong(pred+4,location);
    else
       GUS_POOL = location;

    UltraPokeLong(location+4,succ);

    if(Mergeable(pred,location))
       location = Merge(pred,location);

    if(Mergeable(location,succ))
       Merge(location,succ);
}


/*
void DumpPool(void)
{
    ULONG node=GUS_POOL;

    while(node!=0)
    {   printf("Node %ld, size %ld, next %ld\n",node,UltraPeekLong(node),UltraPeekLong(node+4));
        node = UltraPeekLong(node+4);
    }
}
*/

ULONG UltraMalloc(ULONG reqsize)
{
    ULONG curnode = GUS_POOL,cursize,newnode,newsize,pred,succ;

    if(!reqsize) return 0;

    // round size to 32 bytes

    reqsize+=31;
    reqsize&=-32L;

    // as long as there are nodes:

    pred = 0;

    while(curnode!=0)
    {   succ = UltraPeekLong(curnode+4);

        // get current node size
        cursize = UltraPeekLong(curnode);

        // requested block fits?
        if(cursize>=reqsize)
        {   // it fits, so we're allocating the first
            // 'size' bytes of this node

            // find new node position and size
            newnode = curnode+reqsize;
            newsize = cursize-reqsize;

            // create a new freenode if needed:
            if(newsize>=8)
            {   UltraPokeLong(newnode,newsize);
                UltraPokeLong(newnode+4,succ);
                succ = newnode;
            }

            // link prednode & succnode
            if(pred)
               UltraPokeLong(pred+4,succ);
            else
               GUS_POOL = succ;

            // store size of allocated memory block in block itself:
            UltraPokeLong(curnode,reqsize);
            return curnode;
        }

        // doesn't fit, try next node
        pred = curnode;
        curnode = succ;
    }
    return 0;
}



ULONG UltraMalloc16(ULONG reqsize)
//  Allocates a free block of gus memory, suited for 16 bit samples i.e.
//  smaller than 256k and doesn't cross a 256k page.
{
    ULONG p,spage,epage;

    if(reqsize>262144) return 0;

    // round size to 32 bytes
    reqsize+=31;
    reqsize&=-32L;

    p     = UltraMalloc(reqsize);
    spage = p>>18;
    epage = (p+reqsize-1)>>18;

    if(p && spage!=epage)
    {   ULONG newp, esize;

        // free the second part of the block, and try again
        esize = (p+reqsize)-(epage<<18);
        UltraFree(esize,epage<<18);

        newp = UltraMalloc16(reqsize);

        // free first part of the previous block
        UltraFree(reqsize-esize, p);

        p = newp;
    }

    return p;
}


static void UltraMemInit(void)
{
    UWORD memsize;

    GUS_POOL = 32;
    memsize  = UltraSizeDram();
    UltraPokeLong(GUS_POOL,((ULONG)memsize<<10)-32);
    UltraPokeLong(GUS_POOL+4,0);
}


static void UltraNumVoices(int voices)
{
    UltraDisableLineIn();
    UltraDisableMicIn();
    UltraDisableOutput();
    UltraReset(voices);
    UltraSetInterface(GUS_DRAM_DMA,GUS_ADC_DMA,GUS_GF1_IRQ,GUS_MIDI_IRQ);
}


static UBYTE dma_dram_control;
static volatile BOOL dma_dram_active=0;
static PVI oldhandler;


static void interrupt gf1handler(MIRQARGS)
{
    UBYTE irq_source;
    UBYTE oldselect = GUS_SELECT;

    while(irq_source=inportb(GF1_IRQ_STAT))
    {   //if(irq_source & DMA_TC_IRQ)
        //{   GF1InB(DMA_CONTROL);    // reset the IRQ pending bit
        //    dma_dram_active = 0;
        //    if(GUS_DMA_FUNC!=NULL) GUS_DMA_FUNC();
        //}

/*              if(irq_source & (MIDI_TX_IRQ|MIDI_RX_IRQ)){
                    no provisions for MIDI-ready irq yet
            }
*/
         if(irq_source & GF1_TIMER1_IRQ)
         {   GF1OutB(TIMER_CONTROL,GUS_TIMER_CTRL & ~0x04);
             GF1OutB(TIMER_CONTROL,GUS_TIMER_CTRL);
             if(GUS_TIMER1_FUNC!=NULL) GUS_TIMER1_FUNC();
         }

         if(irq_source & GF1_TIMER2_IRQ)
         {   GF1OutB(TIMER_CONTROL,GUS_TIMER_CTRL & ~0x08);
             GF1OutB(TIMER_CONTROL,GUS_TIMER_CTRL);
             if(GUS_TIMER2_FUNC!=NULL) GUS_TIMER2_FUNC();
         }

/*              if (irq_source & (WAVETABLE_IRQ | ENVELOPE_IRQ)){
                    no wavetable or envelope irq provisions yet
            }
*/
    }

    MIrq_EOI(GUS_GF1_IRQ);
    UltraSelect(oldselect);
}


PFV UltraTimer1Handler(PFV handler)
{
    PFV old = GUS_TIMER1_FUNC;
    GUS_TIMER1_FUNC = handler;
    return old;
}


PFV UltraDMAHandler(PFV handler)
{
    PFV old = GUS_DMA_FUNC;
    GUS_DMA_FUNC = handler;
    return old;
}


/*static PFV UltraTimer2Handler(PFV handler)
{
    PFV old=GUS_TIMER2_FUNC;
    GUS_TIMER2_FUNC=handler;
    return old;
}
*/

static void UltraSetDramDma(UBYTE control)
{
    dma_dram_control = control;
}

void UltraWaitDramDma(void)
{
    while(dma_dram_active);
}

BOOL UltraDramDmaActive(void)
{
    return dma_dram_active;
}

void UltraStartDramDma(void)
{
    GF1OutB(DMA_CONTROL, dma_dram_control | DMA_ENABLE);
    dma_dram_active = 1;
}

void UltraStopDramDma(void)
{
    GF1OutB(DMA_CONTROL, dma_dram_control & ~DMA_ENABLE);
    dma_dram_active = 0;
}

void UltraDownloadDma(ULONG dest)
// Uploads the given amount of data to the given location.
// If the PC's DMA is not yet programmed, the GUS will start the transfer
// as soon as it is.
{
    dma_dram_control &= ~DMA_READ;
    if(GUS_DRAM_DMA >= 4) dest = convert_to_16bit(dest);
    GF1OutW(SET_DMA_ADDRESS,dest>>4);

    DMA_DRAM_BUSY = 1;
    UltraStartDramDma();
}


static void UltraOpen(int voices)
{
    GUS_MIX_IMAGE   = 0x0b;
    GUS_TIMER1_FUNC = NULL;
    GUS_TIMER2_FUNC = NULL;
    GUS_DMA_FUNC    = NULL;

    DMA_DRAM_BUSY   = 0;

    UltraDisableLineIn();
    UltraDisableMicIn();
    UltraDisableOutput();

    UltraReset(voices);
    UltraSetInterface(GUS_DRAM_DMA,GUS_ADC_DMA,GUS_GF1_IRQ,GUS_MIDI_IRQ);
    UltraMemInit();
    oldhandler = MIrq_SetHandler(GUS_GF1_IRQ,gf1handler);
    MIrq_OnOff(GUS_GF1_IRQ,1);    
}


void UltraClose(void)
{
    UltraStopDramDma();
    UltraDisableOutput();
    UltraDisableLineIn();
    UltraDisableMicIn();
    UltraReset(14);
}

static void UltraSelectVoice(UBYTE voice)
{
    // Make sure was are talking to proper voice
    outportb(GF1_VOICE_SELECT,voice);
}


/*
static void UltraSetVoiceEnd(ULONG end)
{
        ULONG phys_end;
        UBYTE data;

        data=GF1InB(GET_CONTROL);

        phys_end=(data&VC_DATA_TYPE)?convert_to_16bit(end):end;

        // Set end address of buffer
        GF1OutW(SET_END_LOW,ADDR_LOW(phys_end));
        GF1OutW(SET_END_HIGH,ADDR_HIGH(phys_end));

        data&=~(VC_IRQ_PENDING|VOICE_STOPPED|STOP_VOICE);

        GF1OutB(SET_CONTROL,data);
        gf1_delay();

        GF1OutB(SET_CONTROL,data);
}
*/

/* The formula for this table is:
        1,000,000 / (1.619695497 * # of active voices)

        The 1.619695497 is calculated by knowing that 14 voices
                gives exactly 44.1 Khz. Therefore,
                1,000,000 / (X * 14) = 44100
                X = 1.619695497
*/

static UWORD freq_divisor[19] =
{       44100,          // 14 active voices
        41160,          // 15 active voices
        38587,          // 16 active voices
        36317,          // 17 active voices
        34300,          // 18 active voices
        32494,          // 19 active voices
        30870,          // 20 active voices
        29400,          // 21 active voices
        28063,          // 22 active voices
        26843,          // 23 active voices
        25725,          // 24 active voices
        24696,          // 25 active voices
        23746,          // 26 active voices
        22866,          // 27 active voices
        22050,          // 28 active voices
        21289,          // 29 active voices
        20580,          // 30 active voices
        19916,          // 31 active voices
        19293           // 32 active voices
};

static void UltraSetFrequency(ULONG speed_khz)
{
        UWORD fc;
        ULONG temp;

        // FC is calculated based on the # of active voices ...
        temp=freq_divisor[GUS_VOICES-14];

        fc=(((speed_khz<<9L)+(temp>>1L))/temp);
        GF1OutW(SET_FREQUENCY,fc<<1);
}

/*
static void UltraSetLoopMode(UBYTE mode)
{
        UBYTE data;
        UBYTE vmode;

        // set/reset the rollover bit as per user request

        vmode=GF1InB(GET_VOLUME_CONTROL);

        if(mode&USE_ROLLOVER)
                vmode|=VC_ROLLOVER;
        else
                vmode&=~VC_ROLLOVER;

        GF1OutB(SET_VOLUME_CONTROL,vmode);
        gf1_delay();
        GF1OutB(SET_VOLUME_CONTROL,vmode);

        data=GF1InB(GET_CONTROL);

        data&=~(VC_WAVE_IRQ|VC_BI_LOOP|VC_LOOP_ENABLE); // isolate the bits 
        mode&=VC_WAVE_IRQ|VC_BI_LOOP|VC_LOOP_ENABLE;    // no bad bits passed in
        data|=mode;             // turn on proper bits ... 

        GF1OutB(SET_CONTROL,data);
        gf1_delay();
        GF1OutB(SET_CONTROL,data);
}
*/

static ULONG UltraReadVoice(void)
{
        UWORD count_low,count_high;
        ULONG acc;
        UBYTE mode;

        // Get the high & low portion of the accumulator
        count_high = GF1InW(GET_ACC_HIGH);
        count_low = GF1InW(GET_ACC_LOW);

        // convert from UltraSound's format to a physical address
        mode = GF1InB(GET_CONTROL);

        acc = make_physical_address(count_low,count_high,mode);
        acc&=0xfffffL;          // Only 20 bits please ...

        return(acc);
}



/*static void UltraSetVoice(ULONG location)
{
        ULONG phys_loc;
        UBYTE data;

        data=GF1InB(GET_CONTROL);

        phys_loc=(data&VC_DATA_TYPE)?convert_to_16bit(location):location;

        // First set accumulator to beginning of data
        GF1OutW(SET_ACC_HIGH,ADDR_HIGH(phys_loc));
        GF1OutW(SET_ACC_LOW,ADDR_LOW(phys_loc));
}
*/


static UBYTE UltraPrimeVoice(ULONG begin,ULONG start,ULONG end,UBYTE mode)
{
        ULONG phys_start,phys_end;
        ULONG phys_begin;
        ULONG temp;
        UBYTE vmode;

        // if start is greater than end, flip 'em and turn on
        // decrementing addresses
        if(start>end)
        {   temp  = start;
            start = end;
            end   = temp;
            mode|=VC_DIRECT;
        }

        // if 16 bit data, must convert addresses
        if(mode&VC_DATA_TYPE)
        {   phys_begin = convert_to_16bit(begin);
            phys_start = convert_to_16bit(start);          
            phys_end   = convert_to_16bit(end);
        } else
        {   phys_begin = begin;
            phys_start = start;
            phys_end   = end;
        }

        // set/reset the rollover bit as per user request
        vmode = GF1InB(GET_VOLUME_CONTROL);

        if(mode&USE_ROLLOVER)
            vmode |= VC_ROLLOVER;
        else
            vmode &= ~VC_ROLLOVER;

        GF1OutB(SET_VOLUME_CONTROL,vmode);
        gf1_delay();
        GF1OutB(SET_VOLUME_CONTROL,vmode);

        // First set accumulator to beginning of data
        GF1OutW(SET_ACC_LOW,ADDR_LOW(phys_begin));
        GF1OutW(SET_ACC_HIGH,ADDR_HIGH(phys_begin));

        // Set start loop address of buffer
        GF1OutW(SET_START_HIGH,ADDR_HIGH(phys_start));
        GF1OutW(SET_START_LOW,ADDR_LOW(phys_start));

        // Set end address of buffer
        GF1OutW(SET_END_HIGH,ADDR_HIGH(phys_end));
        GF1OutW(SET_END_LOW,ADDR_LOW(phys_end));
        return(mode);
}


static void UltraGoVoice(UBYTE mode)
{
        mode &= ~(VOICE_STOPPED|STOP_VOICE);   // turn 'stop' bits off ...

        // NOTE: no irq's from the voice ...

        GF1OutB(SET_CONTROL,mode);
        gf1_delay();
        GF1OutB(SET_CONTROL,mode);
}


/**********************************************************************
 *
 * This function will start playing a wave out of DRAM. It assumes
 * the playback rate, volume & balance have been set up before ...
 *
 *********************************************************************/

static void UltraStartVoice(ULONG begin,ULONG start,ULONG end,UBYTE mode)
{
        mode = UltraPrimeVoice(begin,start,end,mode);
        UltraGoVoice(mode);
}



/***************************************************************
 * This function will stop a given voices output. Note that a delay
 * is necessary after the stop is issued to ensure the self-
 * modifying bits aren't a problem.
 ***************************************************************/

static void UltraStopVoice(void)
{
        UBYTE data;

        // turn off the roll over bit first ...

        data=GF1InB(GET_VOLUME_CONTROL);
        data&=~VC_ROLLOVER;

        GF1OutB(SET_VOLUME_CONTROL,data);
        gf1_delay();
        GF1OutB(SET_VOLUME_CONTROL,data);

        // Now stop the voice 

        data=GF1InB(GET_CONTROL);
        data&=~VC_WAVE_IRQ;                // disable irq's & stop voice ..
        data|=VOICE_STOPPED|STOP_VOICE;

        GF1OutB(SET_CONTROL,data);         // turn it off
        gf1_delay();
        GF1OutB(SET_CONTROL,data);
}


static int UltraVoiceStopped(void)
{
        return(GF1InB(GET_CONTROL) & (VOICE_STOPPED|STOP_VOICE));
}


static void UltraSetBalance(UBYTE pan)
{
        GF1OutB(SET_BALANCE,pan&0xf);
}


static void UltraSetVolume(UWORD volume)
{
        GF1OutW(SET_VOLUME,volume<<4);
}


static UWORD UltraReadVolume(void)
{
        return(GF1InW(GET_VOLUME)>>4);
}


static void UltraStopVolume(void)
{
        UBYTE vmode;

        vmode=GF1InB(GET_VOLUME_CONTROL);
        vmode|=(VOLUME_STOPPED|STOP_VOLUME);

        GF1OutB(SET_VOLUME_CONTROL,vmode);
        gf1_delay();
        GF1OutB(SET_VOLUME_CONTROL,vmode);
}


static void UltraRampVolume(UWORD start,UWORD end,UBYTE rate,UBYTE mode)
{
        UWORD begin;
        UBYTE vmode;

        if(start==end) return;
/*********************************************************************
 * If the start volume is greater than the end volume, flip them and
 * turn on decreasing volume. Note that the GF1 requires that the
 * programmed start volume MUST be less than or equal to the end
 * volume.
 *********************************************************************/
        // Don't let bad bits thru ...
        mode&=~(VL_IRQ_PENDING|VC_ROLLOVER|STOP_VOLUME|VOLUME_STOPPED);

        begin = start;

        if(start>end)
        {   // flip start & end if decreasing numbers ...
            start = end;
            end   = begin;
            mode |= VC_DIRECT;              // decreasing volumes
        }

        // looping below 64 or greater that 4032 can cause strange things
        if(start<64) start = 64;
        if(end>4032) end   = 4032;

        GF1OutB(SET_VOLUME_RATE,rate);
        GF1OutB(SET_VOLUME_START,start>>4);
        GF1OutB(SET_VOLUME_END,end>>4);

        // Also MUST set the current volume to the start volume ...
        UltraSetVolume(begin);

        vmode = GF1InB(GET_VOLUME_CONTROL);

        if(vmode&VC_ROLLOVER) mode|=VC_ROLLOVER;

        // start 'er up !!!
        GF1OutB(SET_VOLUME_CONTROL,mode);
        gf1_delay();
        GF1OutB(SET_VOLUME_CONTROL,mode);
}


/*static void UltraVectorVolume(UWORD end,UBYTE rate,UBYTE mode)
{
        UltraStopVolume();
        UltraRampVolume(UltraReadVolume(),end,rate,mode);
}
*/

/*static int UltraVolumeStopped(void)
{
   return(GF1InB(GET_VOLUME_CONTROL) & (VOLUME_STOPPED|STOP_VOLUME));
}*/


/*
static UWORD vol_rates[19]={
23,24,26,28,29,31,32,34,36,37,39,40,42,44,45,47,49,50,52
};
*/

/*static UBYTE UltraCalcRate(UWORD start,UWORD end,ULONG mil_secs)
{
        ULONG gap,mic_secs;
        UWORD i,range,increment;
        UBYTE rate_val;
        UWORD value;

        gap = (start>end) ? (start-end) : (end-start);
        mic_secs = (mil_secs * 1000L)/gap;

// OK. We now have the # of microseconds for each update to go from
// A to B in X milliseconds. See what the best fit is in the table

        range = 4;
        value = vol_rates[GUS_VOICES-14];

        for(i=0;i<3;i++){
                if(mic_secs<value){
                        range = i;
                        break;
                }
                else value<<=3;
        }

        if(range==4){
                range = 3;
                increment = 1;
        }
        else{
                // calculate increment value ... (round it up ?)
                increment=(unsigned int)((value + (value>>1)) / mic_secs);
        }

        rate_val=range<<6;
        rate_val|=(increment&0x3F);
        return(rate_val);
}
*/

static UWORD _gf1_volumes[512] =
{  0x0000,
   0x0700, 0x07ff, 0x0880, 0x08ff, 0x0940, 0x0980, 0x09c0, 0x09ff, 0x0a20,
   0x0a40, 0x0a60, 0x0a80, 0x0aa0, 0x0ac0, 0x0ae0, 0x0aff, 0x0b10, 0x0b20,
   0x0b30, 0x0b40, 0x0b50, 0x0b60, 0x0b70, 0x0b80, 0x0b90, 0x0ba0, 0x0bb0,
   0x0bc0, 0x0bd0, 0x0be0, 0x0bf0, 0x0bff, 0x0c08, 0x0c10, 0x0c18, 0x0c20,
   0x0c28, 0x0c30, 0x0c38, 0x0c40, 0x0c48, 0x0c50, 0x0c58, 0x0c60, 0x0c68,
   0x0c70, 0x0c78, 0x0c80, 0x0c88, 0x0c90, 0x0c98, 0x0ca0, 0x0ca8, 0x0cb0,
   0x0cb8, 0x0cc0, 0x0cc8, 0x0cd0, 0x0cd8, 0x0ce0, 0x0ce8, 0x0cf0, 0x0cf8,
   0x0cff, 0x0d04, 0x0d08, 0x0d0c, 0x0d10, 0x0d14, 0x0d18, 0x0d1c, 0x0d20,
   0x0d24, 0x0d28, 0x0d2c, 0x0d30, 0x0d34, 0x0d38, 0x0d3c, 0x0d40, 0x0d44,
   0x0d48, 0x0d4c, 0x0d50, 0x0d54, 0x0d58, 0x0d5c, 0x0d60, 0x0d64, 0x0d68,
   0x0d6c, 0x0d70, 0x0d74, 0x0d78, 0x0d7c, 0x0d80, 0x0d84, 0x0d88, 0x0d8c,
   0x0d90, 0x0d94, 0x0d98, 0x0d9c, 0x0da0, 0x0da4, 0x0da8, 0x0dac, 0x0db0,
   0x0db4, 0x0db8, 0x0dbc, 0x0dc0, 0x0dc4, 0x0dc8, 0x0dcc, 0x0dd0, 0x0dd4,
   0x0dd8, 0x0ddc, 0x0de0, 0x0de4, 0x0de8, 0x0dec, 0x0df0, 0x0df4, 0x0df8,
   0x0dfc, 0x0dff, 0x0e02, 0x0e04, 0x0e06, 0x0e08, 0x0e0a, 0x0e0c, 0x0e0e,
   0x0e10, 0x0e12, 0x0e14, 0x0e16, 0x0e18, 0x0e1a, 0x0e1c, 0x0e1e, 0x0e20,
   0x0e22, 0x0e24, 0x0e26, 0x0e28, 0x0e2a, 0x0e2c, 0x0e2e, 0x0e30, 0x0e32,
   0x0e34, 0x0e36, 0x0e38, 0x0e3a, 0x0e3c, 0x0e3e, 0x0e40, 0x0e42, 0x0e44,
   0x0e46, 0x0e48, 0x0e4a, 0x0e4c, 0x0e4e, 0x0e50, 0x0e52, 0x0e54, 0x0e56,
   0x0e58, 0x0e5a, 0x0e5c, 0x0e5e, 0x0e60, 0x0e62, 0x0e64, 0x0e66, 0x0e68,
   0x0e6a, 0x0e6c, 0x0e6e, 0x0e70, 0x0e72, 0x0e74, 0x0e76, 0x0e78, 0x0e7a,
   0x0e7c, 0x0e7e, 0x0e80, 0x0e82, 0x0e84, 0x0e86, 0x0e88, 0x0e8a, 0x0e8c,
   0x0e8e, 0x0e90, 0x0e92, 0x0e94, 0x0e96, 0x0e98, 0x0e9a, 0x0e9c, 0x0e9e,
   0x0ea0, 0x0ea2, 0x0ea4, 0x0ea6, 0x0ea8, 0x0eaa, 0x0eac, 0x0eae, 0x0eb0,
   0x0eb2, 0x0eb4, 0x0eb6, 0x0eb8, 0x0eba, 0x0ebc, 0x0ebe, 0x0ec0, 0x0ec2,
   0x0ec4, 0x0ec6, 0x0ec8, 0x0eca, 0x0ecc, 0x0ece, 0x0ed0, 0x0ed2, 0x0ed4,
   0x0ed6, 0x0ed8, 0x0eda, 0x0edc, 0x0ede, 0x0ee0, 0x0ee2, 0x0ee4, 0x0ee6,
   0x0ee8, 0x0eea, 0x0eec, 0x0eee, 0x0ef0, 0x0ef2, 0x0ef4, 0x0ef6, 0x0ef8,
   0x0efa, 0x0efc, 0x0efe, 0x0eff, 0x0f01, 0x0f02, 0x0f03, 0x0f04, 0x0f05,
   0x0f06, 0x0f07, 0x0f08, 0x0f09, 0x0f0a, 0x0f0b, 0x0f0c, 0x0f0d, 0x0f0e,
   0x0f0f, 0x0f10, 0x0f11, 0x0f12, 0x0f13, 0x0f14, 0x0f15, 0x0f16, 0x0f17,
   0x0f18, 0x0f19, 0x0f1a, 0x0f1b, 0x0f1c, 0x0f1d, 0x0f1e, 0x0f1f, 0x0f20,
   0x0f21, 0x0f22, 0x0f23, 0x0f24, 0x0f25, 0x0f26, 0x0f27, 0x0f28, 0x0f29,
   0x0f2a, 0x0f2b, 0x0f2c, 0x0f2d, 0x0f2e, 0x0f2f, 0x0f30, 0x0f31, 0x0f32,
   0x0f33, 0x0f34, 0x0f35, 0x0f36, 0x0f37, 0x0f38, 0x0f39, 0x0f3a, 0x0f3b,
   0x0f3c, 0x0f3d, 0x0f3e, 0x0f3f, 0x0f40, 0x0f41, 0x0f42, 0x0f43, 0x0f44,
   0x0f45, 0x0f46, 0x0f47, 0x0f48, 0x0f49, 0x0f4a, 0x0f4b, 0x0f4c, 0x0f4d,
   0x0f4e, 0x0f4f, 0x0f50, 0x0f51, 0x0f52, 0x0f53, 0x0f54, 0x0f55, 0x0f56,
   0x0f57, 0x0f58, 0x0f59, 0x0f5a, 0x0f5b, 0x0f5c, 0x0f5d, 0x0f5e, 0x0f5f,
   0x0f60, 0x0f61, 0x0f62, 0x0f63, 0x0f64, 0x0f65, 0x0f66, 0x0f67, 0x0f68,
   0x0f69, 0x0f6a, 0x0f6b, 0x0f6c, 0x0f6d, 0x0f6e, 0x0f6f, 0x0f70, 0x0f71,
   0x0f72, 0x0f73, 0x0f74, 0x0f75, 0x0f76, 0x0f77, 0x0f78, 0x0f79, 0x0f7a,
   0x0f7b, 0x0f7c, 0x0f7d, 0x0f7e, 0x0f7f, 0x0f80, 0x0f81, 0x0f82, 0x0f83,
   0x0f84, 0x0f85, 0x0f86, 0x0f87, 0x0f88, 0x0f89, 0x0f8a, 0x0f8b, 0x0f8c,
   0x0f8d, 0x0f8e, 0x0f8f, 0x0f90, 0x0f91, 0x0f92, 0x0f93, 0x0f94, 0x0f95,
   0x0f96, 0x0f97, 0x0f98, 0x0f99, 0x0f9a, 0x0f9b, 0x0f9c, 0x0f9d, 0x0f9e,
   0x0f9f, 0x0fa0, 0x0fa1, 0x0fa2, 0x0fa3, 0x0fa4, 0x0fa5, 0x0fa6, 0x0fa7,
   0x0fa8, 0x0fa9, 0x0faa, 0x0fab, 0x0fac, 0x0fad, 0x0fae, 0x0faf, 0x0fb0,
   0x0fb1, 0x0fb2, 0x0fb3, 0x0fb4, 0x0fb5, 0x0fb6, 0x0fb7, 0x0fb8, 0x0fb9,
   0x0fba, 0x0fbb, 0x0fbc, 0x0fbd, 0x0fbe, 0x0fbf, 0x0fc0, 0x0fc1, 0x0fc2,
   0x0fc3, 0x0fc4, 0x0fc5, 0x0fc6, 0x0fc7, 0x0fc8, 0x0fc9, 0x0fca, 0x0fcb,
   0x0fcc, 0x0fcd, 0x0fce, 0x0fcf, 0x0fd0, 0x0fd1, 0x0fd2, 0x0fd3, 0x0fd4,
   0x0fd5, 0x0fd6, 0x0fd7, 0x0fd8, 0x0fd9, 0x0fda, 0x0fdb, 0x0fdc, 0x0fdd,
   0x0fde, 0x0fdf, 0x0fe0, 0x0fe1, 0x0fe2, 0x0fe3, 0x0fe4, 0x0fe5, 0x0fe6,
   0x0fe7, 0x0fe8, 0x0fe9, 0x0fea, 0x0feb, 0x0fec, 0x0fed, 0x0fee, 0x0fef,
   0x0ff0, 0x0ff1, 0x0ff2, 0x0ff3, 0x0ff4, 0x0ff5, 0x0ff6, 0x0ff7, 0x0ff8,
   0x0ff9, 0x0ffa, 0x0ffb, 0x0ffc, 0x0ffd, 0x0ffe, 0x0fff
};


/*
static void UltraSetLinearVolume(UWORD index)
{
        UltraSetVolume(_gf1_volumes[index]);
}


static void UltraRampLinearVolume(UWORD start_idx,UWORD end_idx,ULONG msecs,UBYTE mode)
{
        UWORD start,end;
        UBYTE rate;

        // Ramp from start to end in x milliseconds

        start = _gf1_volumes[start_idx];
        end   = _gf1_volumes[end_idx];

        // calculate a rate to get from start to end in msec milliseconds ..
        rate=UltraCalcRate(start,end,msecs);
        UltraRampVolume(start,end,rate,mode);
}
*/

static void UltraVectorLinearVolume(UWORD end_idx,UBYTE rate,UBYTE mode)
{
    UltraStopVolume();
    UltraRampVolume(UltraReadVolume(),_gf1_volumes[end_idx],rate,mode);
}


static void UltraStartTimer(UBYTE timer,UBYTE time)
{
        UBYTE temp;

        if (timer == 1)
        {   GUS_TIMER_CTRL |= 0x04;
            GUS_TIMER_MASK |= 0x01;
            temp = TIMER1;
        } else
        {   GUS_TIMER_CTRL |= 0x08;
            GUS_TIMER_MASK |= 0x02;
            temp = TIMER2;
        }                        

//      ENTER_CRITICAL;

        time = 256 - time;

        GF1OutB(temp,time);                             // set timer speed
        GF1OutB(TIMER_CONTROL,GUS_TIMER_CTRL);          // enable timer interrupt on gf1
        outportb(GF1_TIMER_CTRL,0x04);                  // select timer stuff
        outportb(GF1_TIMER_DATA,GUS_TIMER_MASK);        // start the timers

//      LEAVE_CRITICAL;
}


void UltraStopTimer(int timer)
{
        if (timer == 1)
        {   GUS_TIMER_CTRL &= ~0x04;
            GUS_TIMER_MASK &= ~0x01;
        } else
        {   GUS_TIMER_CTRL &= ~0x08;
            GUS_TIMER_MASK &= ~0x02;
        }

//      ENTER_CRITICAL;

        GF1OutB(TIMER_CONTROL,GUS_TIMER_CTRL);          // disable timer interrupts
        outportb(GF1_TIMER_CTRL,0x04);                  // select timer stuff
        outportb(GF1_TIMER_DATA,GUS_TIMER_MASK | 0x80);

//      LEAVE_CRITICAL;
}

/*
static BOOL UltraTimerStopped(UBYTE timer)
{
        UBYTE value;
        UBYTE temp;

        if (timer == 1)
                temp = 0x40;
        else
                temp = 0x20;

        value = (inportb(GF1_TIMER_CTRL)) & temp;

        return(value);
}
*/

/***************************************************************************
>>>>>>>>>>>>>>>>>>>>>>>>> The actual GUS driver <<<<<<<<<<<<<<<<<<<<<<<<<<<<
***************************************************************************/


static ULONG Ultra[MAXSAMPLEHANDLES];
static ULONG Ultrs[MAXSAMPLEHANDLES];

// Ultra[] holds the sample dram adresses
// of the samples of a module

GHOLD *ghld = NULL;


UBYTE timeskip;
UBYTE timecount;
UBYTE GUS_BPM;


void UltraSetBPM(UBYTE bpm)
{
   // The player routine has to be called (bpm*50)/125 times a second,
   // so the interval between calls takes 125/(bpm*50) seconds (amazing!).

   // The Timer1 handler has a resolution of 80 microseconds.
   // So the timer value to program:

   // (125/(bpm*50)) / 80e-6 = 31250/bpm
   
   UWORD rate = 31250/bpm;

   timeskip  = 1;
   timecount = 1;

   while(rate>255)
   {  rate>>=1;
      timeskip<<=1;
   }
   UltraStartTimer(1,rate);
}


void GUS_Common_Update(void)
{
    UBYTE t;
    GHOLD *aud;
    ULONG base,start,size,reppos,repend;
    UWORD curvol, bigvol=0, bigvoc=0;

    // ramp down voices that need to be started next
    for(t=0; t<md_numchn; t++)
    {   UltraSelectVoice(t);
        aud = &ghld[t];
        if(aud->kick)
        {   curvol = UltraReadVolume();
            if(bigvol<=curvol)
            {   bigvol = curvol;
                bigvoc = t;
            }
            UltraVectorLinearVolume(0,0x3f,0);
        } else
        {   // stop inactive voices [voices stopped by GUS_VoiceStop()]           
            if(!aud->active) UltraStopVoice();
            // check for any voices stopped by the hardware
            if(UltraVoiceStopped()) aud->active = 0;
        }
    }  

    for(t=0; t<md_numchn; t++)
    {   UltraSelectVoice(t);
        aud = &ghld[t];

        if(aud->kick)
        {   aud->kick = 0;

            base   = Ultra[aud->handle];
            start  = aud->start;
            reppos = aud->reppos;
            repend = aud->repend;
            size   = aud->size;

            if(aud->flags&SF_16BITS)
            {   start  <<= 1;
                reppos <<= 1;
                repend <<= 1;
                size   <<= 1;
            }

            // Stop current sample and start a new one
            UltraStopVoice();

            UltraSetFrequency(aud->frq);
            UltraVectorLinearVolume(aud->vol,0x3f,0);
            UltraSetBalance(aud->pan>>4);

            if(aud->flags&SF_LOOP)
            {   // Start a looping sample

                UltraStartVoice(base+start,
                                base+reppos,
                                base+repend,0x8|((aud->flags&SF_16BITS)?4:0)|
                                ((aud->flags&SF_BIDI)?16:0));
            } else
            {   // Start a one-shot sample

                UltraStartVoice(base+start,
                                base+start,
                                base+size+2,(aud->flags&SF_16BITS) ? 4 : 0);
            }
        } else if(aud->active)
        {    UltraSetFrequency(aud->frq);
             UltraVectorLinearVolume(aud->vol,0x3f,0);
             UltraSetBalance(aud->pan>>4);
        }
    }
}


SWORD GUS_Load(SAMPLOAD *sload, FILE *fp)
{
    SAMPLE  *s = sload->sample;
    int     handle,t;
    long    p,l;
    ULONG   length, loopstart, loopend;

    length    = s->length;
    loopstart = s->loopstart;
    loopend   = s->loopend;

    // Find empty slot to put sample address in

    for(handle=0; handle<MAXSAMPLEHANDLES; handle++)
       if(Ultra[handle]==0) break;

    if(handle==MAXSAMPLEHANDLES)
    {   _mm_error = MDERR_OUT_OF_HANDLES;
        return -1;
    }

    //SL_Sample16to8(sload);
    if(s->flags&SF_16BITS)
    {   if(length > 131000)    // GUS can't handle large 16bit samples
        {   SL_Sample16to8(sload);
        } else
        {   length    <<= 1;
            loopstart <<= 1;
            loopend   <<= 1;
        }
    }

    // Allocate GUS dram and store the address in Ultra[handle]
    // Alloc 8 bytes more for anticlick measures. see below.

    // 2.04: use UltraMalloc16 to allocate 16 bit samples

    if(!(Ultra[handle]=(s->flags&SF_16BITS) ? UltraMalloc16(length+8) : UltraMalloc(length+8) ))
    {   _mm_error = MDERR_SAMPLE_TOO_BIG;
        return -1;
    }

    // Load the sample

    Ultrs[handle] = length+8;
    p = Ultra[handle];
    l = length;

    SL_SampleSigned(sload);

    while(l>0)
    {   static UBYTE  buffer[2048];
        long          todo;

        todo = (l>2048) ? 2048 : l;
        SL_Load(buffer,sload, todo);
        UltraPokeChunk(p,buffer,todo);

        p+=todo;
        l-=todo;
    }

    if(s->flags&SF_LOOP && !(s->flags&SF_BIDI))  // looping sample ?
    {   //  Anticlick for looping samples:  Copy the first bytes in the loop
        //                                  beyond the end of the loop

        for(t=0; t<8; t++)
        {   UltraPoke(Ultra[handle]+loopend+t,
                      UltraPeek(Ultra[handle]+loopstart+t));
        }
    } else
    {   //  Anticlick for one-shot samples: Zero the bytes beyond the end
        //                                  of the sample.

        for(t=0; t<8; t++)
            UltraPoke(Ultra[handle]+length+t,0);
    }
    return handle;
}


void GUS_UnLoad(SWORD handle)
//   callback routine to unload samples
//   smp  =  sampleinfo of sample that is being freed
{
    UltraFree(Ultrs[handle],Ultra[handle]);
    Ultra[handle] = 0;
}


BOOL GUS_Common_Init(void)
{
    if(!UltraDetect())
    {   _mm_error = ERROR_DETECTING_GUS;
        return 1;
    }

    //UltraSetDramDma(DMA_TWOS_COMP | DMA_IRQ_ENABLE |
    //                ((GUS_DRAM_DMA >=4) ? DMA_WIDTH_16 : 0) |
    //                (md_mode&DMODE_16BITS ? DMA_DATA_16 : 0) );

    UltraOpen(14);
    return 0;
}

BOOL GUS_SetNumChannels(void)
{
    if(md_numchn > 32) md_numchn = 32;
    if(ghld!=NULL) free(ghld);
    if((ghld = _mm_calloc(sizeof(GHOLD),md_numchn)) == NULL) return 1;

    return 0;
}    

void GUS_Common_Start(void)
{
    int t;
    for(t=0; t<md_numchn; t++)
    {   ghld[t].flags  = 0;
        ghld[t].handle = 0;
        ghld[t].kick   = 0;
        ghld[t].active = 0;
        ghld[t].frq    = 10000;
        ghld[t].vol    = 0;
        ghld[t].pan    = (t&1) ? 0 : 255;
    }
    UltraNumVoices(md_numchn);
    UltraEnableOutput();
    GUS_BPM = 125;
}

BOOL GUS_IsThere(void)
{
    return(getenv("ULTRASND")!=NULL);
}


void GUS_VoiceSetVolume(UBYTE voice, UWORD vol)
{
    ghld[voice].vol = (vol*6)/4;
}


void GUS_VoiceSetFrequency(UBYTE voice, ULONG frq)
{
    ghld[voice].frq = frq;
}


void GUS_VoiceSetPanning(UBYTE voice, ULONG pan)
{
    if(pan == PAN_SURROUND) pan = 128;
    ghld[voice].pan = pan;
}


void GUS_VoicePlay(UBYTE voice,SWORD handle,ULONG start,ULONG size,ULONG reppos,ULONG repend,UWORD flags)
{
    if(start>=size) return;

    if(flags&SF_LOOP)
       if(repend > size) repend = size;    // repend can't be bigger than size
   
    ghld[voice].flags  = flags;
    ghld[voice].handle = handle;
    ghld[voice].start  = start;
    ghld[voice].size   = size;
    ghld[voice].reppos = reppos;
    ghld[voice].repend = repend;
    ghld[voice].kick   = 1;
    ghld[voice].active = 1;
}


void GUS_VoiceStop(UBYTE voice)
{
    UltraSelectVoice(voice);
    UltraVectorLinearVolume(0,0x3f,0);
    //while(!UltraVolumeStopped());
    //UltraStopVoice();
    ghld[voice].active = 0;
}


BOOL GUS_VoiceStopped(UBYTE voice)
{
    return !ghld[voice].active;
}


SLONG GUS_VoiceGetPosition(UBYTE voice)
{
    UltraSelectVoice(voice);
    return(UltraReadVoice()-Ultra[ghld[voice].handle]);
}


void GUS_VoiceReleaseSustain(UBYTE voice)
{

}


ULONG GUS_RealVolume(UBYTE voice)
{
    return(0);
}


ULONG GUS_SampleSpace(int type)
{
    return UltraSizeDram()-32;
}


ULONG GUS_SampleLength(int type, SAMPLE *s)
{
    int retval;

    retval = s->length + 64;
    if(s->flags&SF_16BITS)
    {   if(s->length < 131000)
            retval = (retval * 2) + (16*1024);
    }
    return retval;
}


void GUS_Dummy(void)
{
}


