Metropoli BBS
VIEWER: gus.c MODE: TEXT (ASCII)
/*

  GUS.C

  Michael Chen
  mchen@cs.psu.edu
  4/18/1993

  See the included .TXT file for terms and other information.

  Note that when playing back 16-bit samples, the start and end positions
  are measured in 16-bit words, not bytes!

*/

#include "gus.h"
#include <stdio.h>
#include <dos.h>

int GUSBase;
int GUSMixer;
int GUSStatus;
int GUSTimerControl;
int GUSTimerData;
int GUSIRQDMAControl;
int GUSMIDIControl;
int GUSMIDIData;
int GUSVoice;
int GUSCommand;
int GUSDataLo;
int GUSDataHi;
int GUSDRAMIO;
int GUSCurrentVoice = -1;
int GUSVoices = MINVOICES;

byte GUSFreqDivisorTable[MAXVOICES+1] =
/*
  Divisor table when setting voice frequency, based on number of voices.
  Remember, minimum of 14 active voices (though divisors given for 8 and up).
*/
  { -1, -1, -1, -1, -1, -1, -1, -1, 74, 66, 60, 54, 50, 46, 43, 40, 37,
    35, 33, 31, 30, 28, 27, 26, 25, 24, 23, 22, 21, 20, 20, 19, 18 };


void GUSDelay()
/*
  Waits for GUS. (from Ultradox 2.0)
*/
  {
  int i;

  for (i=0; i<7; i++)
    inportb(0x300);
  }

void GUSReset()
/*
  Resets GUS. (from Ultradox 2.0)
*/
  {
  int i;

  /* Force routines to select voice explicitly first time */
  GUSCurrentVoice = -1;

  /* Put GUS in initialization mode */
  GUS_SetCommand(G_Initialize);
  outportb(GUSDataHi,0);
  GUSDelay();
  GUSDelay();

  /* Take GUS out of initialization mode */
  GUS_SetCommand(G_Initialize);
  outportb(GUSDataHi,1);
  GUSDelay();
  GUSDelay();

  /* Ramp voices to 0 --- doesn't work yet */
/*
  GUSShutUp();
*/

  /* Clear DMA control */
  GUS_SetCommand(G_DMAControl);
  outportb(GUSDataHi,0);

  /* Clear timer control */
  GUS_SetCommand(G_TimerControl);
  outportb(GUSDataHi,0);

  /* Clear sample control */
  GUS_SetCommand(G_SampleControl);
  outportb(GUSDataHi,0);

  /* Set number of voices to minimum. */
  GUSSetVoices(MINVOICES);

  /* Clear pending DMA control */
  GUS_SetCommand(G_DMAControl);
  inportb(GUSDataHi);

  /* Clear pending timer control */
  GUS_SetCommand(G_TimerControl);
  inportb(GUSDataHi);

  /* Clear pending sample control */
  GUS_SetCommand(G_SampleControl);
  inportb(GUSDataHi);

  /* Reset voices */
  for (i=0; i<MAXVOICES; i++)
    {
    /* Select voice */
    GUSSelectVoice(i);

    /* Stop voice */
    GUSStopVoice(i);

    /* Turn off volume ramp */
    GUSStopRamp(i);

    /* Set to zero volume */
    GUSSetVolume(i,0);

    /* Set to center pan */
    GUSSetVoiceBalance(0,P_Center);
    }

  /* Clear pending DMA control */
  GUS_SetCommand(G_DMAControl);
  inportb(GUSDataHi);

  /* Clear pending sample control */
  GUS_SetCommand(G_SampleControl);
  inportb(GUSDataHi);

  /* Unknown read from IRQ status (?) */
  GUS_SetCommand(G_IRQStatus);
  inportb(GUSDataHi);

  /* Do something to initialization register (?) */
  GUS_SetCommand(G_Initialize);
  outportb(GUSDataHi,7);

  /* Set mixer to default (line-in and output on, mic-in off) */
  GUSSetMixer(M_OutputOn|M_LineInOn|M_MicInOff);
  }

byte GUSPeek(longword addr)
/*
  Peeks at value from GUS DRAM (long address).
*/
  {
  return GUS_Peek(addr >> 16,addr & 0xFFFF);
  }

void GUSPoke(longword addr, byte val)
/*
  Pokes value to GUS DRAM (long address).
*/
  {
  GUS_Poke(addr >> 16,addr & 0xFFFF,val);
  }

byte GUSPokePeek(longword addr, byte val)
/*
  Pokes value to GUS DRAM (long address), then immediately peeks same.
*/
  {
  return GUS_PokePeek(addr >> 16,addr & 0xFFFF,val);
  }

static byte GUS_Peek(byte addrhi, word addrlo)
/*
  Peeks at value from GUS DRAM.
*/
  {
  GUS_SetCommand(G_SetDRAMLo);
  outport(GUSDataLo,addrlo);
  GUS_SetCommand(G_SetDRAMHi);
  outportb(GUSDataHi,addrhi);
  return GUS_ReadDRAMIO();
  }

static void GUS_Poke(byte addrhi, word addrlo, byte val)
/*
  Pokes value to GUS DRAM.
*/
  {
  GUS_SetCommand(G_SetDRAMLo);
  outport(GUSDataLo,addrlo);
  GUS_SetCommand(G_SetDRAMHi);
  outportb(GUSDataHi,addrhi);
  GUS_SetDRAMIO(val);
  }

static byte GUS_PokePeek(byte addrhi, word addrlo, byte val)
/*
  Pokes value to GUS DRAM, then immediately peek from same location.
*/
  {
  GUS_SetCommand(G_SetDRAMLo);
  outport(GUSDataLo,addrlo);
  GUS_SetCommand(G_SetDRAMHi);
  outportb(GUSDataHi,addrhi);
  outportb(GUSDRAMIO,val);
  return GUS_ReadDRAMIO();
  }

int ProbeGUS()
/*
  Probes to see if GUS exists at current base address. (from Ultradox 2.0)
*/
  {
  byte testbyte = 0xAA;
  byte test1;

  GUS_SetCommand(G_Initialize);
  outportb(GUSDataHi,0);
  GUSDelay();
  GUSDelay();
  GUS_SetCommand(G_Initialize);
  outportb(GUSDataHi,1);
  GUSDelay();
  GUSDelay();
  GUSPoke(0,testbyte);
  GUSPoke(1,0xFF - testbyte);
  test1 = GUSPeek(0);
  GUS_SetCommand(G_Initialize);
  outportb(GUSDataHi,0);
  return (test1 == testbyte);
  }

int DetectGUS()
/*
  Returns base address of GUS ports, or 0 if no GUS detected.
*/
  {
  for (GUSBase = 0x210; (GUSBase < 0x270); GUSBase += 0x10)
    {
    GUSMixer = GUSBase;
    GUSStatus = GUSBase + 6;
    GUSTimerControl = GUSBase + 8;
    GUSTimerData = GUSBase + 9;
    GUSIRQDMAControl = GUSBase + 0xB;
    GUSMIDIControl = GUSBase + 0x100;
    GUSMIDIData = GUSBase + 0x101;
    GUSVoice = GUSBase + 0x102;
    GUSCommand = GUSBase + 0x103;
    GUSDataLo = GUSBase + 0x104;
    GUSDataHi = GUSBase + 0x105;
    GUSDRAMIO = GUSBase + 0x107;
    if (ProbeGUS()) break;
    }
  if (GUSBase < 0x270)
    return GUSBase;
  else
    return 0;
  }

static void GUS_SelectVoice(byte voice)
/*
  Selects current GUS voice for subsequent commands.
*/
  {
  GUS_SetVoice(voice);
  GUSCurrentVoice = voice;
  }

void GUSSelectVoice(byte voice)
/*
  Changes current GUS voice for subsequent commands if necessary.
*/
  {
  if (GUSCurrentVoice != voice)
    GUS_SelectVoice(voice);
  }

void GUSStopVoice(byte voice)
/*
  Stops GUS voice.
*/
  {
  byte b;

  GUSSelectVoice(voice);
  b = GUSReadVoiceMode(voice);
  if (!(b & V_VoiceStopped))
    {
    b |= 3;
    GUS_SetCommand(G_SetVoiceMode);
    outportb(GUSDataHi,b);
    }
  }

void GUSStartVoice(byte voice)
/*
  Starts GUS voice.
*/
  {
  byte b;

  GUSSelectVoice(voice);
  b = GUSReadVoiceMode(voice);
  if (b & V_VoiceStopped)
    {
    b &= 0xFC;
    GUS_SetCommand(G_SetVoiceMode);
    outportb(GUSDataHi,b);
    }
  }

void GUSSetVoiceMode(byte voice, byte mode)
/*
  Sets GUS voice mode (not start/stop, though) directly.
*/
  {
  byte b;

  GUSSelectVoice(voice);
  GUS_SetCommand(G_ReadVoiceMode);
  b = inportb(GUSDataHi);
  b &= 3;
  b |= (mode & 0xFC);
  GUS_SetCommand(G_SetVoiceMode);
  outportb(GUSDataHi,b);
  }

void GUSSetVoices(byte voices)
/*
  Sets number of voices for GUS.
*/
  {
  GUS_SetCommand(G_SetMaxVoice);
  outportb(GUSDataHi,((voices-1) | G_VoiceMask));
  GUSVoices = voices;
  }

void GUSSetVoiceFreq(byte voice, word freq)
/*
  Sets GUS voice frequency, taking into account number of voices.
*/
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_SetVoiceFreq);
  outport(GUSDataLo,freq/GUSFreqDivisorTable[GUSVoices]);
  }

static void GUS_SetLoopStart(byte voice, word hi, word lo)
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_SetLoopStartLo);
  outport(GUSDataLo,lo);
  GUS_SetCommand(G_SetLoopStartHi);
  outport(GUSDataLo,hi);
  }

static void GUS_SetLoopEnd(byte voice, word hi, word lo)
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_SetLoopEndLo);
  outport(GUSDataLo,lo);
  GUS_SetCommand(G_SetLoopEndHi);
  outport(GUSDataLo,hi);
  }

static void GUS_SetPosition(byte voice, word hi, word lo)
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_SetPositionLo);
  outport(GUSDataLo,lo);
  GUS_SetCommand(G_SetPositionHi);
  outport(GUSDataLo,hi);
  }

void GUSSetLoopStart(byte voice, longword addr)
/*
  Sets loop start of voice.
*/
  {
  GUS_SetLoopStart(voice,addr << 9, addr >> 7);
  }

void GUSSetLoopEnd(byte voice, longword addr)
/*
  Sets loop (sample) end of voice.
*/
  {
  GUS_SetLoopEnd(voice,addr << 9, addr >> 7);
  }

void GUSSetPosition(byte voice, longword addr)
/*
  Sets position (sample begin) of voice.
*/
  {
  GUS_SetPosition(voice,addr << 9, addr >> 7);
  }

longword GUSReadLoopEnd(byte voice)
/*
  Reads loop (sample) end of voice.
*/
  {
  longword l;

  GUSSelectVoice(voice);
  GUS_SetCommand(G_ReadLoopEndLo);
  l = ((longword)((word)inport(GUSDataLo)) & 0x1FFF) << 7;
  GUS_SetCommand(G_ReadLoopEndHi);
  l |= ((word)inport(GUSDataLo)) >> 9;
  return l;
  }

longword GUSReadLoopStart(byte voice)
/*
  Reads loop start of voice.
*/
  {
  longword l;

  GUSSelectVoice(voice);
  GUS_SetCommand(G_ReadLoopStartLo);
  l = ((longword)((word)inport(GUSDataLo)) & 0x1FFF) << 7;
  GUS_SetCommand(G_ReadLoopStartHi);
  l |= ((word)inport(GUSDataLo)) >> 9;
  return l;
  }

longword GUSReadPosition(byte voice)
/*
  Reads position (sample begin) of voice.
*/
  {
  longword l;

  GUSSelectVoice(voice);
  GUS_SetCommand(G_ReadPositionLo);
  l = ((longword)((word)inport(GUSDataLo)) & 0x1FFF) << 7;
  GUS_SetCommand(G_ReadPositionHi);
  l |= ((word)inport(GUSDataLo)) >> 9;
  return l;
  }

byte GUSReadVoiceMode(byte voice)
/*
  Reads mode of voice.
*/
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_ReadVoiceMode);
  return inportb(GUSDataHi);
  }

word GUSReadVoiceFreq(byte voice)
/*
  Reads frequency of voice.
*/
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_ReadVoiceFreq);
  return inport(GUSDataLo) * GUSFreqDivisorTable[GUSVoices];
  }

void GUSSetVolume(byte voice, word vol)
/*
  Sets volume of voice.
*/
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_SetVolume);
  outport(GUSDataLo,vol);
  }

word GUSReadVolume(byte voice)
/*
  Reads volume of voice.
*/
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_ReadVolume);
  return (word)inport(GUSDataLo);
  }

void GUSSetVoiceBalance(byte voice, byte pan)
/*
  Sets pan of voice.
*/
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_SetVoiceBalance);
  outportb(GUSDataHi,pan);
  }

byte GUSReadVoiceBalance(byte voice)
/*
  Reads pan of voice.
*/
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_ReadVoiceBalance);
  return inportb(GUSDataHi);
  }

/*

void GUSSetDRAM(longword addr, void* buf, longword len)

  Set region of GUS DRAM starting at addr, len bytes long to buf.
  Not yet optimized for speed.

  {
  byte* bp;

  bp = buf;
  while (len-- > 0)
    {
    GUSPoke(addr++,*bp++);
    }
  }

*/

void GUSSetDRAM(longword addr, void* buf, longword len)
/*
  Set region of GUS DRAM starting at addr, len bytes long to buf.
  (Supposedly) partially optimized for speed.
*/
  {
  byte* bp;
  byte addrhi;
  word addrlo;

  bp = buf;
  addrhi = addr >> 16;
  addrlo = addr & 0xFFFF;

  GUS_SetCommand(G_SetDRAMHi);
  outportb(GUSDataHi,addrhi);
  GUS_SetCommand(G_SetDRAMLo);

  while (len-- > 0)
    {
    outport(GUSDataLo,addrlo++);
    GUS_SetDRAMIO(*bp++);
    if (!addrlo)
      {
      GUS_SetCommand(G_SetDRAMHi);
      outportb(GUSDataHi,addrhi);
      GUS_SetCommand(G_SetDRAMLo);
      }
    }
  }

/*

void GUSReadDRAM(longword addr, void* buf, longword len)

  Read region of GUS DRAM starting at addr, len bytes long to buf.
  Not yet optimized for speed.

  {
  byte* bp;

  bp = buf;
  while (len-- > 0)
    {
    *bp++ = GUSPeek(addr++);
    }
  }

*/

void GUSReadDRAM(longword addr, void* buf, longword len)
/*
  Read region of GUS DRAM starting at addr, len bytes long to buf.
  (Supposedly) partially optimized for speed.
*/
  {
  byte* bp;
  byte addrhi;
  word addrlo;

  bp = buf;
  addrhi = addr >> 16;
  addrlo = addr & 0xFFFF;

  GUS_SetCommand(G_SetDRAMHi);
  outportb(GUSDataHi,addrhi);
  GUS_SetCommand(G_SetDRAMLo);

  while (len-- > 0)
    {
    outport(GUSDataLo,addrlo++);
    *bp++ = GUS_ReadDRAMIO();
    if (!addrlo)
      {
      GUS_SetCommand(G_SetDRAMHi);
      outportb(GUSDataHi,addrhi);
      GUS_SetCommand(G_SetDRAMLo);
      }
    }
  }

void GUSFillDRAM(longword addr, byte b, longword len)
/*
  Set region of GUS DRAM starting at addr, len bytes long to b.
  (Supposedly) partially optimized for speed.
*/
  {
  byte addrhi;
  word addrlo;

  addrhi = addr >> 16;
  addrlo = addr & 0xFFFF;

  GUS_SetCommand(G_SetDRAMHi);
  outportb(GUSDataHi,addrhi);
  GUS_SetCommand(G_SetDRAMLo);

  while (len-- > 0)
    {
    outport(GUSDataLo,addrlo++);
    GUS_SetDRAMIO(b);
    if (!addrlo)
      {
      GUS_SetCommand(G_SetDRAMHi);
      outportb(GUSDataHi,addrhi);
      GUS_SetCommand(G_SetDRAMLo);
      }
    }
  }

void GUSSetVolumeRampRate(byte voice, byte incr, byte scale)
/*
  Set voice's volume ramp rate.  Scale updates as follows:
      00 - every access
      01 - every 8th access
      10 - every 64th access
      11 - every 512th access
  Advice from Ultradox 2.0: use increments of 8 or less; don't ramp to either
  extreme (below 636, above 4032)
*/
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_SetVolumeRampRate);
  outportb(GUSDataHi,(incr & 0x3f) | ((scale & 3) << 6));
  }

byte GUSReadVolumeRampRate(byte voice)
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_SetVolumeRampRate);
  return inportb(GUSDataHi);
  }

void GUSSetVolumeRampStart(byte voice, word vol)
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_SetVolumeRampStart);
  outportb(GUSDataHi,(vol >> 8));
  }

void GUSSetVolumeRampEnd(byte voice, word vol)
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_SetVolumeRampEnd);
  outportb(GUSDataHi,(vol >> 8));
  }

word GUSReadVolumeRampStart(byte voice)
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_ReadVolumeRampStart);
  return ((word)inportb(GUSDataHi)) << 8;
  }

word GUSReadVolumeRampEnd(byte voice)
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_ReadVolumeRampEnd);
  return ((word)inportb(GUSDataHi)) << 8;
  }

void GUSSetVolumeControl(byte voice, byte mode)
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_SetVolumeControl);
  outportb(GUSDataHi,mode);
  }

byte GUSReadVolumeControl(byte voice)
  {
  GUSSelectVoice(voice);
  GUS_SetCommand(G_ReadVolumeControl);
  return inportb(GUSDataHi);
  }

static int GUSMixerState = 0;

void GUSSetMixer(byte mode)
  {
  GUSMixerState = mode;
  GUS_SetMixer(mode);
  }

byte GUSReadMixer()
  {
  return GUSMixerState;
  }

void GUSStopRamp(byte voice)
/*
  Stops GUS voice ramp.
*/
  {
  byte b;

  GUSSelectVoice(voice);
  b = GUSReadVolumeControl(voice);
  if (!(b & V_RampStopped))
    {
    b |= 3;
    GUS_SetCommand(G_SetVolumeControl);
    outportb(GUSDataHi,b);
    }
  }

void GUSStartRamp(byte voice)
/*
  Starts GUS voice ramp.
*/
  {
  byte b;

  GUSSelectVoice(voice);
  b = GUSReadVolumeControl(voice);
  if (b & V_RampStopped)
    {
    b &= 0xFC;
    GUS_SetCommand(G_SetVolumeControl);
    outportb(GUSDataHi,b);
    }
  }

void GUSShutUp()
  {
  int i;
  word vol;
  byte incr;

  for (i = 0; i < MAXVOICES; i++)
    {
    GUSSelectVoice(i);
    vol = GUSReadVolume(i);
    if (vol > 0)
      {
      GUSSetVolumeRampStart(i,vol);
      GUSSetVolumeRampEnd(i,0);
      GUSSetVolumeRampRate(i,1,R_Every1);
      GUSSetVolumeControl(i,V_StartRamp|V_Decreasing);
      GUSStartRamp(i);

      /* code for ramps seems not to work; no change in volume */
      while ((incr = GUSReadVolumeControl(i) & V_RampStopped)
            != V_RampStopped)
        {
        vol = GUSReadVolume(i);
        if (vol >> 12 == 0) break; /* quiet enough */
        printf("vol 0x%04x mode 0x%02x\r",vol,incr);
        }
      }
    }
  }

[ RETURN TO DIRECTORY ]