/* * Copyright (C) 2002-2019 The DOSBox Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "dosbox.h" #include "inout.h" #include "mixer.h" #include "dma.h" #include "pic.h" #include "setup.h" #include "shell.h" #include "math.h" #include "regs.h" using namespace std; //Extra bits of precision over normal gus #define WAVE_FRACT 9 #define WAVE_FRACT_MASK ((1 << WAVE_FRACT)-1) #define WAVE_MSWMASK ((1 << 16)-1) #define WAVE_LSWMASK (0xffffffff ^ WAVE_MSWMASK) //Amount of precision the volume has #define RAMP_FRACT (10) #define RAMP_FRACT_MASK ((1 << RAMP_FRACT)-1) #define GUS_BASE myGUS.portbase #define GUS_RATE myGUS.rate #define LOG_GUS 0 #define VOL_SHIFT 14 #define WCTRL_STOPPED 0x01 #define WCTRL_STOP 0x02 #define WCTRL_16BIT 0x04 #define WCTRL_LOOP 0x08 #define WCTRL_BIDIRECTIONAL 0x10 #define WCTRL_IRQENABLED 0x20 #define WCTRL_DECREASING 0x40 #define WCTRL_IRQPENDING 0x80 Bit8u adlib_commandreg; static MixerChannel * gus_chan; static Bit8u irqtable[8] = { 0, 2, 5, 3, 7, 11, 12, 15 }; static Bit8u dmatable[8] = { 0, 1, 3, 5, 6, 7, 0, 0 }; static Bit8u GUSRam[1024*1024]; // 1024K of GUS Ram static Bit16u vol16bit[4096]; static Bit32u pantable[16]; class GUSChannels; static void CheckVoiceIrq(void); struct GFGus { Bit8u gRegSelect; Bit16u gRegData; Bit32u gDramAddr; Bit16u gCurChannel; Bit8u DMAControl; Bit16u dmaAddr; Bit8u TimerControl; Bit8u SampControl; Bit8u mixControl; Bit8u ActiveChannels; Bit32u basefreq; struct GusTimer { Bit8u value; bool reached; bool raiseirq; bool masked; bool running; float delay; } timers[2]; Bit32u rate; Bitu portbase; Bit8u dma1; Bit8u dma2; Bit8u irq1; Bit8u irq2; bool irqenabled; bool ChangeIRQDMA; // IRQ status register values Bit8u IRQStatus; Bit32u ActiveMask; Bit8u IRQChan; Bit32u RampIRQ; Bit32u WaveIRQ; } myGUS; Bitu DEBUG_EnableDebugger(void); static void GUS_DMA_Callback(DmaChannel * chan,DMAEvent event); class GUSChannels { public: Bit32u WaveStart; Bit32u WaveEnd; Bit32u WaveAddr; Bit32u WaveAdd; Bit8u WaveCtrl; Bit16u WaveFreq; Bit32u RampStart; Bit32u RampEnd; Bit32u RampVol; Bit32u RampAdd; Bit32u RampAddReal; Bit8u RampRate; Bit8u RampCtrl; Bit8u PanPot; Bit8u channum; Bit32u irqmask; Bit32u PanLeft; Bit32u PanRight; Bit32s VolLeft; Bit32s VolRight; GUSChannels(Bit8u num) { channum = num; irqmask = 1 << num; WaveStart = 0; WaveEnd = 0; WaveAddr = 0; WaveAdd = 0; WaveFreq = 0; WaveCtrl = 3; RampRate = 0; RampStart = 0; RampEnd = 0; RampCtrl = 3; RampAdd = 0; RampVol = 0; VolLeft = 0; VolRight = 0; PanLeft = 0; PanRight = 0; PanPot = 0x7; } // Returns a single 16-bit sample from the Gravis's RAM INLINE Bit32s GetSample8() const { Bit32u useAddr = WaveAddr >> WAVE_FRACT; if (WaveAdd >= (1 << WAVE_FRACT)) { Bit32s tmpsmall = (Bit8s)GUSRam[useAddr]; return tmpsmall << 8; } else { Bit32u nextAddr = (useAddr + 1) & ( 1024 * 1024 - 1 ); // Interpolate Bit32s w1 = ((Bit8s)GUSRam[useAddr]) << 8; Bit32s w2 = ((Bit8s)GUSRam[nextAddr]) << 8; Bit32s diff = w2 - w1; Bit32s scale = (Bit32s)(WaveAddr&WAVE_FRACT_MASK); return (w1 + ((diff*scale) >> WAVE_FRACT)); } } INLINE Bit32s GetSample16() const { Bit32u useAddr = WaveAddr >> WAVE_FRACT; // Formula used to convert addresses for use with 16-bit samples Bit32u holdAddr = useAddr & 0xc0000L; useAddr = useAddr & 0x1ffffL; useAddr = useAddr << 1; useAddr = (holdAddr | useAddr); if (WaveAdd >= (1 << WAVE_FRACT)) { return (GUSRam[useAddr + 0] | (((Bit8s)GUSRam[useAddr + 1]) << 8)); } else { // Interpolate Bit32s w1 = (GUSRam[useAddr + 0] | (((Bit8s)GUSRam[useAddr + 1]) << 8)); Bit32s w2 = (GUSRam[useAddr + 2] | (((Bit8s)GUSRam[useAddr + 3]) << 8)); Bit32s diff = w2 - w1; Bit32s scale = (Bit32s)(WaveAddr&WAVE_FRACT_MASK); return (w1 + ((diff*scale) >> WAVE_FRACT)); } } void WriteWaveFreq(Bit16u val) { WaveFreq = val; double frameadd = double(val >> 1)/512.0; //Samples / original gus frame double realadd = (frameadd*(double)myGUS.basefreq/(double)GUS_RATE) * (double)(1 << WAVE_FRACT); WaveAdd = (Bit32u)realadd; } void WriteWaveCtrl(Bit8u val) { Bit32u oldirq=myGUS.WaveIRQ; WaveCtrl = val & 0x7f; if ((val & 0xa0)==0xa0) myGUS.WaveIRQ|=irqmask; else myGUS.WaveIRQ&=~irqmask; if (oldirq != myGUS.WaveIRQ) CheckVoiceIrq(); } INLINE Bit8u ReadWaveCtrl(void) { Bit8u ret=WaveCtrl; if (myGUS.WaveIRQ & irqmask) ret|=0x80; return ret; } void UpdateWaveRamp(void) { WriteWaveFreq(WaveFreq); WriteRampRate(RampRate); } void WritePanPot(Bit8u val) { PanPot = val; PanLeft = pantable[0x0f-(val & 0xf)]; PanRight = pantable[(val & 0xf)]; UpdateVolumes(); } Bit8u ReadPanPot(void) { return PanPot; } void WriteRampCtrl(Bit8u val) { Bit32u old=myGUS.RampIRQ; RampCtrl = val & 0x7f; //Manually set the irq if ((val & 0xa0)==0xa0) myGUS.RampIRQ|=irqmask; else myGUS.RampIRQ&=~irqmask; if (old != myGUS.RampIRQ) CheckVoiceIrq(); } INLINE Bit8u ReadRampCtrl(void) { Bit8u ret=RampCtrl; if (myGUS.RampIRQ & irqmask) ret |= 0x80; return ret; } void WriteRampRate(Bit8u val) { RampRate = val; double frameadd = (double)(RampRate & 63)/(double)(1 << (3*(val >> 6))); double realadd = (frameadd*(double)myGUS.basefreq/(double)GUS_RATE) * (double)(1 << RAMP_FRACT); RampAdd = (Bit32u)realadd; } INLINE void WaveUpdate(void) { if (WaveCtrl & ( WCTRL_STOP | WCTRL_STOPPED)) return; Bit32s WaveLeft; if (WaveCtrl & WCTRL_DECREASING) { WaveAddr -= WaveAdd; WaveLeft = WaveStart-WaveAddr; } else { WaveAddr += WaveAdd; WaveLeft = WaveAddr-WaveEnd; } //Not yet reaching a boundary if (WaveLeft<0) return; /* Generate an IRQ if needed */ if (WaveCtrl & 0x20) { myGUS.WaveIRQ|=irqmask; } /* Check for not being in PCM operation */ if (RampCtrl & 0x04) return; /* Check for looping */ if (WaveCtrl & WCTRL_LOOP) { /* Bi-directional looping */ if (WaveCtrl & WCTRL_BIDIRECTIONAL) WaveCtrl^= WCTRL_DECREASING; WaveAddr = (WaveCtrl & WCTRL_DECREASING) ? (WaveEnd-WaveLeft) : (WaveStart+WaveLeft); } else { WaveCtrl|=1; //Stop the channel WaveAddr = (WaveCtrl & WCTRL_DECREASING) ? WaveStart : WaveEnd; } } INLINE void UpdateVolumes(void) { Bit32s templeft=RampVol - PanLeft; templeft&=~(templeft >> 31); Bit32s tempright=RampVol - PanRight; tempright&=~(tempright >> 31); VolLeft=vol16bit[templeft >> RAMP_FRACT]; VolRight=vol16bit[tempright >> RAMP_FRACT]; } INLINE void RampUpdate(void) { /* Check if ramping enabled */ if (RampCtrl & 0x3) return; Bit32s RampLeft; if (RampCtrl & 0x40) { RampVol-=RampAdd; RampLeft=RampStart-RampVol; } else { RampVol+=RampAdd; RampLeft=RampVol-RampEnd; } if (RampLeft<0) { UpdateVolumes(); return; } /* Generate an IRQ if needed */ if (RampCtrl & 0x20) { myGUS.RampIRQ|=irqmask; } /* Check for looping */ if (RampCtrl & 0x08) { /* Bi-directional looping */ if (RampCtrl & 0x10) RampCtrl^=0x40; RampVol = (RampCtrl & 0x40) ? (RampEnd-RampLeft) : (RampStart+RampLeft); } else { RampCtrl|=1; //Stop the channel RampVol = (RampCtrl & 0x40) ? RampStart : RampEnd; } UpdateVolumes(); } void generateSamples(Bit32s * stream,Bit32u len) { //Disabled channel if (RampCtrl & WaveCtrl & 3) return; if (WaveCtrl & WCTRL_16BIT) { for (int i = 0; i < (int)len; i++) { // Get sample Bit32s tmpsamp = GetSample16(); // Output stereo sample stream[i << 1] += tmpsamp * VolLeft; stream[(i << 1) + 1] += tmpsamp * VolRight; WaveUpdate(); RampUpdate(); } } else { for (int i = 0; i < (int)len; i++) { // Get sample Bit32s tmpsamp = GetSample8(); // Output stereo sample stream[i << 1] += tmpsamp * VolLeft; stream[(i << 1) + 1] += tmpsamp * VolRight; WaveUpdate(); RampUpdate(); } } } }; static GUSChannels *guschan[32]; static GUSChannels *curchan; static void GUSReset(void) { if((myGUS.gRegData & 0x1) == 0x1) { // Reset adlib_commandreg = 85; myGUS.IRQStatus = 0; myGUS.timers[0].raiseirq = false; myGUS.timers[1].raiseirq = false; myGUS.timers[0].reached = false; myGUS.timers[1].reached = false; myGUS.timers[0].running = false; myGUS.timers[1].running = false; myGUS.timers[0].value = 0xff; myGUS.timers[1].value = 0xff; myGUS.timers[0].delay = 0.080f; myGUS.timers[1].delay = 0.320f; myGUS.ChangeIRQDMA = false; myGUS.mixControl = 0x0b; // latches enabled by default LINEs disabled // Stop all channels int i; for(i=0;i<32;i++) { guschan[i]->RampVol=0; guschan[i]->WriteWaveCtrl(0x1); guschan[i]->WriteRampCtrl(0x1); guschan[i]->WritePanPot(0x7); } myGUS.IRQChan = 0; } if ((myGUS.gRegData & 0x4) != 0) { myGUS.irqenabled = true; } else { myGUS.irqenabled = false; } } static INLINE void GUS_CheckIRQ(void) { if (myGUS.IRQStatus && (myGUS.mixControl & 0x08)) PIC_ActivateIRQ(myGUS.irq1); } static void CheckVoiceIrq(void) { myGUS.IRQStatus&=0x9f; Bitu totalmask=(myGUS.RampIRQ|myGUS.WaveIRQ) & myGUS.ActiveMask; if (!totalmask) return; if (myGUS.RampIRQ) myGUS.IRQStatus|=0x40; if (myGUS.WaveIRQ) myGUS.IRQStatus|=0x20; GUS_CheckIRQ(); for (;;) { Bit32u check=(1 << myGUS.IRQChan); if (totalmask & check) return; myGUS.IRQChan++; if (myGUS.IRQChan>=myGUS.ActiveChannels) myGUS.IRQChan=0; } } static Bit16u ExecuteReadRegister(void) { Bit8u tmpreg; // LOG_MSG("Read global reg %x",myGUS.gRegSelect); switch (myGUS.gRegSelect) { case 0x41: // Dma control register - read acknowledges DMA IRQ tmpreg = myGUS.DMAControl & 0xbf; tmpreg |= (myGUS.IRQStatus & 0x80) >> 1; myGUS.IRQStatus&=0x7f; return (Bit16u)(tmpreg << 8); case 0x42: // Dma address register return myGUS.dmaAddr; case 0x45: // Timer control register. Identical in operation to Adlib's timer return (Bit16u)(myGUS.TimerControl << 8); break; case 0x49: // Dma sample register tmpreg = myGUS.DMAControl & 0xbf; tmpreg |= (myGUS.IRQStatus & 0x80) >> 1; return (Bit16u)(tmpreg << 8); case 0x80: // Channel voice control read register if (curchan) return curchan->ReadWaveCtrl() << 8; else return 0x0300; case 0x82: // Channel MSB start address register if (curchan) return (Bit16u)(curchan->WaveStart >> 16); else return 0x0000; case 0x83: // Channel LSW start address register if (curchan) return (Bit16u)(curchan->WaveStart ); else return 0x0000; case 0x89: // Channel volume register if (curchan) return (Bit16u)((curchan->RampVol >> RAMP_FRACT) << 4); else return 0x0000; case 0x8a: // Channel MSB current address register if (curchan) return (Bit16u)(curchan->WaveAddr >> 16); else return 0x0000; case 0x8b: // Channel LSW current address register if (curchan) return (Bit16u)(curchan->WaveAddr ); else return 0x0000; case 0x8d: // Channel volume control register if (curchan) return curchan->ReadRampCtrl() << 8; else return 0x0300; case 0x8f: // General channel IRQ status register tmpreg=myGUS.IRQChan|0x20; Bit32u mask; mask=1 << myGUS.IRQChan; if (!(myGUS.RampIRQ & mask)) tmpreg|=0x40; if (!(myGUS.WaveIRQ & mask)) tmpreg|=0x80; myGUS.RampIRQ&=~mask; myGUS.WaveIRQ&=~mask; CheckVoiceIrq(); return (Bit16u)(tmpreg << 8); default: #if LOG_GUS LOG_MSG("Read Register num 0x%x", myGUS.gRegSelect); #endif return myGUS.gRegData; } } static void GUS_TimerEvent(Bitu val) { if (!myGUS.timers[val].masked) myGUS.timers[val].reached=true; if (myGUS.timers[val].raiseirq) { myGUS.IRQStatus|=0x4 << val; GUS_CheckIRQ(); } if (myGUS.timers[val].running) PIC_AddEvent(GUS_TimerEvent,myGUS.timers[val].delay,val); } static void ExecuteGlobRegister(void) { int i; // if (myGUS.gRegSelect|1!=0x44) LOG_MSG("write global register %x with %x", myGUS.gRegSelect, myGUS.gRegData); switch(myGUS.gRegSelect) { case 0x0: // Channel voice control register if(curchan) curchan->WriteWaveCtrl((Bit16u)myGUS.gRegData>>8); break; case 0x1: // Channel frequency control register if(curchan) curchan->WriteWaveFreq(myGUS.gRegData); break; case 0x2: // Channel MSW start address register if (curchan) { Bit32u tmpaddr = (Bit32u)((myGUS.gRegData & 0x1fff) << 16); curchan->WaveStart = (curchan->WaveStart & WAVE_MSWMASK) | tmpaddr; } break; case 0x3: // Channel LSW start address register if(curchan != NULL) { Bit32u tmpaddr = (Bit32u)(myGUS.gRegData); curchan->WaveStart = (curchan->WaveStart & WAVE_LSWMASK) | tmpaddr; } break; case 0x4: // Channel MSW end address register if(curchan != NULL) { Bit32u tmpaddr = (Bit32u)(myGUS.gRegData & 0x1fff) << 16; curchan->WaveEnd = (curchan->WaveEnd & WAVE_MSWMASK) | tmpaddr; } break; case 0x5: // Channel MSW end address register if(curchan != NULL) { Bit32u tmpaddr = (Bit32u)(myGUS.gRegData); curchan->WaveEnd = (curchan->WaveEnd & WAVE_LSWMASK) | tmpaddr; } break; case 0x6: // Channel volume ramp rate register if(curchan != NULL) { Bit8u tmpdata = (Bit16u)myGUS.gRegData>>8; curchan->WriteRampRate(tmpdata); } break; case 0x7: // Channel volume ramp start register EEEEMMMM if(curchan != NULL) { Bit8u tmpdata = (Bit16u)myGUS.gRegData >> 8; curchan->RampStart = tmpdata << (4+RAMP_FRACT); } break; case 0x8: // Channel volume ramp end register EEEEMMMM if(curchan != NULL) { Bit8u tmpdata = (Bit16u)myGUS.gRegData >> 8; curchan->RampEnd = tmpdata << (4+RAMP_FRACT); } break; case 0x9: // Channel current volume register if(curchan != NULL) { Bit16u tmpdata = (Bit16u)myGUS.gRegData >> 4; curchan->RampVol = tmpdata << RAMP_FRACT; curchan->UpdateVolumes(); } break; case 0xA: // Channel MSW current address register if(curchan != NULL) { Bit32u tmpaddr = (Bit32u)(myGUS.gRegData & 0x1fff) << 16; curchan->WaveAddr = (curchan->WaveAddr & WAVE_MSWMASK) | tmpaddr; } break; case 0xB: // Channel LSW current address register if(curchan != NULL) { Bit32u tmpaddr = (Bit32u)(myGUS.gRegData); curchan->WaveAddr = (curchan->WaveAddr & WAVE_LSWMASK) | tmpaddr; } break; case 0xC: // Channel pan pot register if(curchan) curchan->WritePanPot((Bit16u)myGUS.gRegData>>8); break; case 0xD: // Channel volume control register if(curchan) curchan->WriteRampCtrl((Bit16u)myGUS.gRegData>>8); break; case 0xE: // Set active channel register myGUS.gRegSelect = myGUS.gRegData>>8; //JAZZ Jackrabbit seems to assume this? myGUS.ActiveChannels = 1+((myGUS.gRegData>>8) & 63); if(myGUS.ActiveChannels < 14) myGUS.ActiveChannels = 14; if(myGUS.ActiveChannels > 32) myGUS.ActiveChannels = 32; myGUS.ActiveMask=0xffffffffU >> (32-myGUS.ActiveChannels); gus_chan->Enable(true); myGUS.basefreq = (Bit32u)(0.5 + 1000000.0/(1.619695497*(double)(myGUS.ActiveChannels))); #if LOG_GUS LOG_MSG("GUS set to %d channels, freq %d", myGUS.ActiveChannels, myGUS.basefreq); #endif for (i=0;iUpdateWaveRamp(); break; case 0x10: // Undocumented register used in Fast Tracker 2 break; case 0x41: // Dma control register myGUS.DMAControl = (Bit8u)(myGUS.gRegData>>8); GetDMAChannel(myGUS.dma1)->Register_Callback( (myGUS.DMAControl & 0x1) ? GUS_DMA_Callback : 0); break; case 0x42: // Gravis DRAM DMA address register myGUS.dmaAddr = myGUS.gRegData; break; case 0x43: // MSB Peek/poke DRAM position myGUS.gDramAddr = (0xff0000 & myGUS.gDramAddr) | ((Bit32u)myGUS.gRegData); break; case 0x44: // LSW Peek/poke DRAM position myGUS.gDramAddr = (0xffff & myGUS.gDramAddr) | ((Bit32u)myGUS.gRegData>>8) << 16; break; case 0x45: // Timer control register. Identical in operation to Adlib's timer myGUS.TimerControl = (Bit8u)(myGUS.gRegData>>8); myGUS.timers[0].raiseirq=(myGUS.TimerControl & 0x04)>0; if (!myGUS.timers[0].raiseirq) myGUS.IRQStatus&=~0x04; myGUS.timers[1].raiseirq=(myGUS.TimerControl & 0x08)>0; if (!myGUS.timers[1].raiseirq) myGUS.IRQStatus&=~0x08; break; case 0x46: // Timer 1 control myGUS.timers[0].value = (Bit8u)(myGUS.gRegData>>8); myGUS.timers[0].delay = (0x100 - myGUS.timers[0].value) * 0.080f; break; case 0x47: // Timer 2 control myGUS.timers[1].value = (Bit8u)(myGUS.gRegData>>8); myGUS.timers[1].delay = (0x100 - myGUS.timers[1].value) * 0.320f; break; case 0x49: // DMA sampling control register myGUS.SampControl = (Bit8u)(myGUS.gRegData>>8); GetDMAChannel(myGUS.dma1)->Register_Callback( (myGUS.SampControl & 0x1) ? GUS_DMA_Callback : 0); break; case 0x4c: // GUS reset register GUSReset(); break; default: #if LOG_GUS LOG_MSG("Unimplemented global register %x -- %x", myGUS.gRegSelect, myGUS.gRegData); #endif break; } return; } static Bitu read_gus(Bitu port,Bitu iolen) { // LOG_MSG("read from gus port %x",port); switch(port - GUS_BASE) { case 0x206: return myGUS.IRQStatus; case 0x208: Bit8u tmptime; tmptime = 0; if (myGUS.timers[0].reached) tmptime |= (1 << 6); if (myGUS.timers[1].reached) tmptime |= (1 << 5); if (tmptime & 0x60) tmptime |= (1 << 7); if (myGUS.IRQStatus & 0x04) tmptime|=(1 << 2); if (myGUS.IRQStatus & 0x08) tmptime|=(1 << 1); return tmptime; case 0x20a: return adlib_commandreg; case 0x302: return (Bit8u)myGUS.gCurChannel; case 0x303: return myGUS.gRegSelect; case 0x304: if (iolen==2) return ExecuteReadRegister() & 0xffff; else return ExecuteReadRegister() & 0xff; case 0x305: return ExecuteReadRegister() >> 8; case 0x307: if(myGUS.gDramAddr < sizeof(GUSRam)) { return GUSRam[myGUS.gDramAddr]; } else { return 0; } default: #if LOG_GUS LOG_MSG("Read GUS at port 0x%x", port); #endif break; } return 0xff; } static void write_gus(Bitu port,Bitu val,Bitu iolen) { // LOG_MSG("Write gus port %x val %x",port,val); switch(port - GUS_BASE) { case 0x200: myGUS.mixControl = (Bit8u)val; myGUS.ChangeIRQDMA = true; return; case 0x208: adlib_commandreg = (Bit8u)val; break; case 0x209: //TODO adlib_commandreg should be 4 for this to work else it should just latch the value if (val & 0x80) { myGUS.timers[0].reached=false; myGUS.timers[1].reached=false; return; } myGUS.timers[0].masked=(val & 0x40)>0; myGUS.timers[1].masked=(val & 0x20)>0; if (val & 0x1) { if (!myGUS.timers[0].running) { PIC_AddEvent(GUS_TimerEvent,myGUS.timers[0].delay,0); myGUS.timers[0].running=true; } } else myGUS.timers[0].running=false; if (val & 0x2) { if (!myGUS.timers[1].running) { PIC_AddEvent(GUS_TimerEvent,myGUS.timers[1].delay,1); myGUS.timers[1].running=true; } } else myGUS.timers[1].running=false; break; //TODO Check if 0x20a register is also available on the gus like on the interwave case 0x20b: if (!myGUS.ChangeIRQDMA) break; myGUS.ChangeIRQDMA=false; if (myGUS.mixControl & 0x40) { // IRQ configuration, only use low bits for irq 1 if (irqtable[val & 0x7]) myGUS.irq1=irqtable[val & 0x7]; #if LOG_GUS LOG_MSG("Assigned GUS to IRQ %d", myGUS.irq1); #endif } else { // DMA configuration, only use low bits for dma 1 if (dmatable[val & 0x7]) myGUS.dma1=dmatable[val & 0x7]; #if LOG_GUS LOG_MSG("Assigned GUS to DMA %d", myGUS.dma1); #endif } break; case 0x302: myGUS.gCurChannel = val & 31 ; curchan = guschan[myGUS.gCurChannel]; break; case 0x303: myGUS.gRegSelect = (Bit8u)val; myGUS.gRegData = 0; break; case 0x304: if (iolen==2) { myGUS.gRegData=(Bit16u)val; ExecuteGlobRegister(); } else myGUS.gRegData = (Bit16u)val; break; case 0x305: myGUS.gRegData = (Bit16u)((0x00ff & myGUS.gRegData) | val << 8); ExecuteGlobRegister(); break; case 0x307: if(myGUS.gDramAddr < sizeof(GUSRam)) GUSRam[myGUS.gDramAddr] = (Bit8u)val; break; default: #if LOG_GUS LOG_MSG("Write GUS at port 0x%x with %x", port, val); #endif break; } } static void GUS_DMA_Callback(DmaChannel * chan,DMAEvent event) { if (event!=DMA_UNMASKED) return; Bitu dmaaddr; //Calculate the dma address //DMA transfers can't cross 256k boundaries, so you should be safe to just determine the start once and go from there //Bit 2 - 0 = if DMA channel is an 8 bit channel(0 - 3). if (myGUS.DMAControl & 0x4) dmaaddr = (((myGUS.dmaAddr & 0x1fff) << 1) | (myGUS.dmaAddr & 0xc000)) << 4; else dmaaddr = myGUS.dmaAddr << 4; //Reading from dma? if((myGUS.DMAControl & 0x2) == 0) { Bitu read=chan->Read(chan->currcnt+1,&GUSRam[dmaaddr]); //Check for 16 or 8bit channel read*=(chan->DMA16+1); if((myGUS.DMAControl & 0x80) != 0) { //Invert the MSB to convert twos compliment form Bitu i; if((myGUS.DMAControl & 0x40) == 0) { // 8-bit data for(i=dmaaddr;i<(dmaaddr+read);i++) GUSRam[i] ^= 0x80; } else { // 16-bit data for(i=dmaaddr+1;i<(dmaaddr+read);i+=2) GUSRam[i] ^= 0x80; } } //Writing to dma } else { chan->Write(chan->currcnt+1,&GUSRam[dmaaddr]); } /* Raise the TC irq if needed */ if((myGUS.DMAControl & 0x20) != 0) { myGUS.IRQStatus |= 0x80; GUS_CheckIRQ(); } chan->Register_Callback(0); } static void GUS_CallBack(Bitu len) { Bit32s buffer[MIXER_BUFSIZE][2]; memset(buffer, 0, len * sizeof(buffer[0])); for (Bitu i = 0; i < myGUS.ActiveChannels; i++) { guschan[i]->generateSamples(buffer[0], len); } for (Bitu i = 0; i < len; i++) { buffer[i][0] >>= VOL_SHIFT; buffer[i][1] >>= VOL_SHIFT; } gus_chan->AddSamples_s32(len, buffer[0]); CheckVoiceIrq(); } // Generate logarithmic to linear volume conversion tables static void MakeTables(void) { int i; double out = (double)(1 << 13); for (i=4095;i>=0;i--) { vol16bit[i]=(Bit16s)out; out/=1.002709201; /* 0.0235 dB Steps */ //Original amplification routine in the hardware //vol16bit[i] = ((256 + i & 0xff) << VOL_SHIFT) / (1 << (24 - (i >> 8))); } pantable[0] = 4095 << RAMP_FRACT; for (i=1;i<16;i++) { pantable[i]=(Bit32u)(0.5-128.0*(log((double)i/15.0)/log(2.0))*(double)(1 << RAMP_FRACT)); } } class GUS:public Module_base{ private: IO_ReadHandleObject ReadHandler[8]; IO_WriteHandleObject WriteHandler[9]; AutoexecObject autoexecline[2]; MixerObject MixerChan; public: GUS(Section* configuration):Module_base(configuration){ if(!IS_EGAVGA_ARCH) return; Section_prop * section=static_cast(configuration); if(!section->Get_bool("gus")) return; memset(&myGUS,0,sizeof(myGUS)); memset(GUSRam,0,1024*1024); myGUS.rate=section->Get_int("gusrate"); myGUS.portbase = section->Get_hex("gusbase") - 0x200; int dma_val = section->Get_int("gusdma"); if ((dma_val<0) || (dma_val>255)) dma_val = 3; // sensible default int irq_val = section->Get_int("gusirq"); if ((irq_val<0) || (irq_val>255)) irq_val = 5; // sensible default myGUS.dma1 = (Bit8u)dma_val; myGUS.dma2 = (Bit8u)dma_val; myGUS.irq1 = (Bit8u)irq_val; myGUS.irq2 = (Bit8u)irq_val; // We'll leave the MIDI interface to the MPU-401 // Ditto for the Joystick // GF1 Synthesizer ReadHandler[0].Install(0x302 + GUS_BASE,read_gus,IO_MB); WriteHandler[0].Install(0x302 + GUS_BASE,write_gus,IO_MB); WriteHandler[1].Install(0x303 + GUS_BASE,write_gus,IO_MB); ReadHandler[1].Install(0x303 + GUS_BASE,read_gus,IO_MB); WriteHandler[2].Install(0x304 + GUS_BASE,write_gus,IO_MB|IO_MW); ReadHandler[2].Install(0x304 + GUS_BASE,read_gus,IO_MB|IO_MW); WriteHandler[3].Install(0x305 + GUS_BASE,write_gus,IO_MB); ReadHandler[3].Install(0x305 + GUS_BASE,read_gus,IO_MB); ReadHandler[4].Install(0x206 + GUS_BASE,read_gus,IO_MB); WriteHandler[4].Install(0x208 + GUS_BASE,write_gus,IO_MB); ReadHandler[5].Install(0x208 + GUS_BASE,read_gus,IO_MB); WriteHandler[5].Install(0x209 + GUS_BASE,write_gus,IO_MB); WriteHandler[6].Install(0x307 + GUS_BASE,write_gus,IO_MB); ReadHandler[6].Install(0x307 + GUS_BASE,read_gus,IO_MB); // Board Only WriteHandler[7].Install(0x200 + GUS_BASE,write_gus,IO_MB); ReadHandler[7].Install(0x20A + GUS_BASE,read_gus,IO_MB); WriteHandler[8].Install(0x20B + GUS_BASE,write_gus,IO_MB); // DmaChannels[myGUS.dma1]->Register_TC_Callback(GUS_DMA_TC_Callback); MakeTables(); for (Bit8u chan_ct=0; chan_ct<32; chan_ct++) { guschan[chan_ct] = new GUSChannels(chan_ct); } // Register the Mixer CallBack gus_chan=MixerChan.Install(GUS_CallBack,GUS_RATE,"GUS"); myGUS.gRegData=0x1; GUSReset(); myGUS.gRegData=0x0; int portat = 0x200+GUS_BASE; // ULTRASND=Port,DMA1,DMA2,IRQ1,IRQ2 // [GUS port], [GUS DMA (recording)], [GUS DMA (playback)], [GUS IRQ (playback)], [GUS IRQ (MIDI)] ostringstream temp; temp << "SET ULTRASND=" << hex << setw(3) << portat << "," << dec << (Bitu)myGUS.dma1 << "," << (Bitu)myGUS.dma2 << "," << (Bitu)myGUS.irq1 << "," << (Bitu)myGUS.irq2 << ends; // Create autoexec.bat lines autoexecline[0].Install(temp.str()); autoexecline[1].Install(std::string("SET ULTRADIR=") + section->Get_string("ultradir")); } ~GUS() { if(!IS_EGAVGA_ARCH) return; Section_prop * section=static_cast(m_configuration); if(!section->Get_bool("gus")) return; myGUS.gRegData=0x1; GUSReset(); myGUS.gRegData=0x0; for(Bitu i=0;i<32;i++) { delete guschan[i]; } memset(&myGUS,0,sizeof(myGUS)); memset(GUSRam,0,1024*1024); } }; static GUS* test; void GUS_ShutDown(Section* /*sec*/) { delete test; } void GUS_Init(Section* sec) { test = new GUS(sec); sec->AddDestroyFunction(&GUS_ShutDown,true); }