/* 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 #include 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> 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); } } } }