/* SPU2null * Copyright (C) 2002-2005 SPU2null 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "SPU2.h" #include #include const unsigned char version = PS2E_SPU2_VERSION; const unsigned char revision = 0; const unsigned char build = 7; // increase that with each version const unsigned int minor = 1; // increase that with each version // ADSR constants #define ATTACK_MS 494L #define DECAYHALF_MS 286L #define DECAY_MS 572L #define SUSTAIN_MS 441L #define RELEASE_MS 437L #ifdef _DEBUG static char *libraryName = "SPU2null (Debug)"; #else static char *libraryName = "SPU2null "; #endif FILE *spu2Log; Config conf; ADMA Adma4; ADMA Adma7; u32 MemAddr[2]; u32 g_nSpuInit = 0; unsigned short interrupt = 0; s8 *spu2regs = NULL; u16* spu2mem = NULL; u16* pSpuIrq[2] = {NULL}; u32 dwEndChannel2[2] = {0}; // keeps track of what channels have ended unsigned long dwNoiseVal=1; // global noise generator int SPUCycles = 0, SPUWorkerCycles = 0; int SPUStartCycle[2]; int SPUTargetCycle[2]; int ADMAS4Write(); int ADMAS7Write(); void InitADSR(); void (*irqCallbackSPU2)(); // func of main emu, called on spu irq void (*irqCallbackDMA4)()=0; // func of main emu, called on spu irq void (*irqCallbackDMA7)()=0; // func of main emu, called on spu irq const int f[5][2] = { { 0, 0 }, { 60, 0 }, { 115, -52 }, { 98, -55 }, { 122, -60 } }; u32 RateTable[160]; // channels and voices VOICE_PROCESSED voices[SPU_NUMBER_VOICES+1]; // +1 for modulation u32 CALLBACK PS2EgetLibType() { return PS2E_LT_SPU2; } char* CALLBACK PS2EgetLibName() { return libraryName; } u32 CALLBACK PS2EgetLibVersion2(u32 type) { return (version<<16)|(revision<<8)|build|(minor<<24); } void __Log(char *fmt, ...) { va_list list; if (!conf.Log || spu2Log == NULL) return; va_start(list, fmt); vfprintf(spu2Log, fmt, list); va_end(list); } s32 CALLBACK SPU2init() { #ifdef SPU2_LOG spu2Log = fopen("logs/spu2.txt", "w"); if (spu2Log) setvbuf(spu2Log, NULL, _IONBF, 0); SPU2_LOG("Spu2 null version %d,%d\n",revision,build); SPU2_LOG("SPU2init\n"); #endif spu2regs = (s8*)malloc(0x10000); if (spu2regs == NULL) { SysMessage("Error allocating Memory\n"); return -1; } memset(spu2regs, 0, 0x10000); spu2mem = (u16*)malloc(0x200000); // 2Mb if (spu2mem == NULL) { SysMessage("Error allocating Memory\n"); return -1; } memset(spu2mem, 0, 0x200000); memset(dwEndChannel2, 0, sizeof(dwEndChannel2)); InitADSR(); memset(voices, 0, sizeof(voices)); // last 24 channels have higher mem offset for(int i = 0; i < 24; ++i) voices[i+24].memoffset = 0x400; // init each channel for(u32 i = 0; i < ARRAYSIZE(voices); ++i) { voices[i].pLoop = voices[i].pStart = voices[i].pCurr = (u8*)spu2mem; voices[i].pvoice = (_SPU_VOICE*)((u8*)spu2regs+voices[i].memoffset)+(i%24); voices[i].ADSRX.SustainLevel = 1024; // -> init sustain } return 0; } s32 CALLBACK SPU2open(void *pDsp) { LoadConfig(); SPUCycles = SPUWorkerCycles = 0; interrupt = 0; SPUStartCycle[0] = SPUStartCycle[1] = 0; SPUTargetCycle[0] = SPUTargetCycle[1] = 0; g_nSpuInit = 1; return 0; } void CALLBACK SPU2close() { g_nSpuInit = 0; } void CALLBACK SPU2shutdown() { free(spu2regs); spu2regs = NULL; free(spu2mem); spu2mem = NULL; #ifdef SPU2_LOG if (spu2Log) fclose(spu2Log); #endif } // simulate SPU2 for 1ms void SPU2Worker(); #define CYCLES_PER_MS (36864000/1000) void CALLBACK SPU2async(u32 cycle) { SPUCycles += cycle; if(interrupt & (1<<2)){ if(SPUCycles - SPUStartCycle[1] >= SPUTargetCycle[1]){ interrupt &= ~(1<<2); irqCallbackDMA7(); } } if(interrupt & (1<<1)){ if(SPUCycles - SPUStartCycle[0] >= SPUTargetCycle[0]){ interrupt &= ~(1<<1); irqCallbackDMA4(); } } if( g_nSpuInit ) { while( SPUCycles-SPUWorkerCycles > 0 && CYCLES_PER_MS < SPUCycles-SPUWorkerCycles ) { SPU2Worker(); SPUWorkerCycles += CYCLES_PER_MS; } } } void InitADSR() // INIT ADSR { unsigned long r,rs,rd; int i; memset(RateTable,0,sizeof(unsigned long)*160); // build the rate table according to Neill's rules (see at bottom of file) r=3;rs=1;rd=0; for(i=32;i<160;i++) // we start at pos 32 with the real values... everything before is 0 { if(r<0x3FFFFFFF) { r+=rs; rd++;if(rd==5) {rd=1;rs*=2;} } if(r>0x3FFFFFFF) r=0x3FFFFFFF; RateTable[i]=r; } } int MixADSR(VOICE_PROCESSED* pvoice) // MIX ADSR { if(pvoice->bStop) // should be stopped: { if(pvoice->bIgnoreLoop==0){ pvoice->ADSRX.EnvelopeVol=0; pvoice->bOn=false; pvoice->pStart= (u8*)(spu2mem+pvoice->iStartAddr); pvoice->pLoop= (u8*)(spu2mem+pvoice->iStartAddr); pvoice->pCurr= (u8*)(spu2mem+pvoice->iStartAddr); pvoice->bStop=true; pvoice->bIgnoreLoop=false; return 0; } if(pvoice->ADSRX.ReleaseModeExp)// do release { switch((pvoice->ADSRX.EnvelopeVol>>28)&0x7) { case 0: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +0 + 32]; break; case 1: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +4 + 32]; break; case 2: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +6 + 32]; break; case 3: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +8 + 32]; break; case 4: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +9 + 32]; break; case 5: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +10+ 32]; break; case 6: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +11+ 32]; break; case 7: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +12+ 32]; break; } } else { pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x0C + 32]; } if(pvoice->ADSRX.EnvelopeVol<0) { pvoice->ADSRX.EnvelopeVol=0; pvoice->bOn=false; pvoice->pStart= (u8*)(spu2mem+pvoice->iStartAddr); pvoice->pLoop= (u8*)(spu2mem+pvoice->iStartAddr); pvoice->pCurr= (u8*)(spu2mem+pvoice->iStartAddr); pvoice->bStop=true; pvoice->bIgnoreLoop=false; //pvoice->bReverb=0; //pvoice->bNoise=0; } pvoice->ADSRX.lVolume=pvoice->ADSRX.EnvelopeVol>>21; pvoice->ADSRX.lVolume=pvoice->ADSRX.EnvelopeVol>>21; return pvoice->ADSRX.lVolume; } else // not stopped yet? { if(pvoice->ADSRX.State==0) // -> attack { if(pvoice->ADSRX.AttackModeExp) { if(pvoice->ADSRX.EnvelopeVol<0x60000000) pvoice->ADSRX.EnvelopeVol+=RateTable[(pvoice->ADSRX.AttackRate^0x7F)-0x10 + 32]; else pvoice->ADSRX.EnvelopeVol+=RateTable[(pvoice->ADSRX.AttackRate^0x7F)-0x18 + 32]; } else { pvoice->ADSRX.EnvelopeVol+=RateTable[(pvoice->ADSRX.AttackRate^0x7F)-0x10 + 32]; } if(pvoice->ADSRX.EnvelopeVol<0) { pvoice->ADSRX.EnvelopeVol=0x7FFFFFFF; pvoice->ADSRX.State=1; } pvoice->ADSRX.lVolume=pvoice->ADSRX.EnvelopeVol>>21; return pvoice->ADSRX.lVolume; } //--------------------------------------------------// if(pvoice->ADSRX.State==1) // -> decay { switch((pvoice->ADSRX.EnvelopeVol>>28)&0x7) { case 0: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+0 + 32]; break; case 1: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+4 + 32]; break; case 2: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+6 + 32]; break; case 3: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+8 + 32]; break; case 4: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+9 + 32]; break; case 5: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+10+ 32]; break; case 6: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+11+ 32]; break; case 7: pvoice->ADSRX.EnvelopeVol-=RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+12+ 32]; break; } if(pvoice->ADSRX.EnvelopeVol<0) pvoice->ADSRX.EnvelopeVol=0; if(((pvoice->ADSRX.EnvelopeVol>>27)&0xF) <= pvoice->ADSRX.SustainLevel) { pvoice->ADSRX.State=2; } pvoice->ADSRX.lVolume=pvoice->ADSRX.EnvelopeVol>>21; return pvoice->ADSRX.lVolume; } //--------------------------------------------------// if(pvoice->ADSRX.State==2) // -> sustain { if(pvoice->ADSRX.SustainIncrease) { if(pvoice->ADSRX.SustainModeExp) { if(pvoice->ADSRX.EnvelopeVol<0x60000000) pvoice->ADSRX.EnvelopeVol+=RateTable[(pvoice->ADSRX.SustainRate^0x7F)-0x10 + 32]; else pvoice->ADSRX.EnvelopeVol+=RateTable[(pvoice->ADSRX.SustainRate^0x7F)-0x18 + 32]; } else { pvoice->ADSRX.EnvelopeVol+=RateTable[(pvoice->ADSRX.SustainRate^0x7F)-0x10 + 32]; } if(pvoice->ADSRX.EnvelopeVol<0) { pvoice->ADSRX.EnvelopeVol=0x7FFFFFFF; } } else { if(pvoice->ADSRX.SustainModeExp) { switch((pvoice->ADSRX.EnvelopeVol>>28)&0x7) { case 0: pvoice->ADSRX.EnvelopeVol-=RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +0 + 32];break; case 1: pvoice->ADSRX.EnvelopeVol-=RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +4 + 32];break; case 2: pvoice->ADSRX.EnvelopeVol-=RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +6 + 32];break; case 3: pvoice->ADSRX.EnvelopeVol-=RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +8 + 32];break; case 4: pvoice->ADSRX.EnvelopeVol-=RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +9 + 32];break; case 5: pvoice->ADSRX.EnvelopeVol-=RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +10+ 32];break; case 6: pvoice->ADSRX.EnvelopeVol-=RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +11+ 32];break; case 7: pvoice->ADSRX.EnvelopeVol-=RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +12+ 32];break; } } else { pvoice->ADSRX.EnvelopeVol-=RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x0F + 32]; } if(pvoice->ADSRX.EnvelopeVol<0) { pvoice->ADSRX.EnvelopeVol=0; } } pvoice->ADSRX.lVolume=pvoice->ADSRX.EnvelopeVol>>21; return pvoice->ADSRX.lVolume; } } return 0; } // simulate SPU2 for 1ms void SPU2Worker() { u8* start; int ch,flags; VOICE_PROCESSED* pChannel=voices; for(ch=0;chbNew) { pChannel->StartSound(); // start new sound dwEndChannel2[ch/24]&=~(1<<(ch%24)); // clear end channel bit } if(!pChannel->bOn) { // fill buffer with empty data continue; } if(pChannel->iActFreq!=pChannel->iUsedFreq) // new psx frequency? pChannel->VoiceChangeFrequency(); // loop until 1 ms of data is reached int ns = 0; while(nsspos >= 0x10000 ) { if(pChannel->iSBPos==28) // 28 reached? { start=pChannel->pCurr; // set up the current pos // special "stop" sign if( start == (u8*)-1 ) //!pChannel->bOn { pChannel->bOn=false; // -> turn everything off pChannel->ADSRX.lVolume=0; pChannel->ADSRX.EnvelopeVol=0; goto ENDX; // -> and done for this channel } pChannel->iSBPos=0; // decode the 16 byte packet flags=(int)start[1]; start += 16; // some callback and irq active? if(pChannel->GetCtrl()->irq) { // if irq address reached or irq on looping addr, when stop/loop flag is set u8* pirq = (u8*)pSpuIrq[ch>=24]; if( (pirq > start-16 && pirq <= start) || ((flags&1) && (pirq > pChannel->pLoop-16 && pirq <= pChannel->pLoop))) { IRQINFO |= 4<<(int)(ch>=24); irqCallbackSPU2(); } } // flag handler if((flags&4) && (!pChannel->bIgnoreLoop)) pChannel->pLoop=start-16; // loop adress if(flags&1) // 1: stop/loop { // We play this block out first... dwEndChannel2[ch/24]|=(1<<(ch%24)); //if(!(flags&2)) // 1+2: do loop... otherwise: stop if(flags!=3 || pChannel->pLoop==NULL) // PETE: if we don't check exactly for 3, loop hang ups will happen (DQ4, for example) { // and checking if pLoop is set avoids crashes, yeah start = (u8*)-1; pChannel->bStop = true; pChannel->bIgnoreLoop = false; } else { start = pChannel->pLoop; } } pChannel->pCurr=start; // store values for next cycle } pChannel->iSBPos++; // get sample data pChannel->spos -= 0x10000; } MixADSR(pChannel); // go to the next packet ns++; pChannel->spos += pChannel->sinc; } ENDX: ; } // mix all channels if( (spu2Ru16(REG_C0_MMIX) & 0xC0) && (spu2Ru16(REG_C0_ADMAS) & 0x1) && !(spu2Ru16(REG_C0_CTRL) & 0x30)) { for(int ns=0;ns0x0fffff) // wrap at 2Mb spuaddr=0; // wrap } spuaddr+=19; //Transfer Local To Host TSAH/L + Data Size + 20 (already +1'd) C0_SPUADDR_SET(spuaddr); // got from J.F. and Kanodin... is it needed? spu2Ru16(REG_C0_SPUSTAT) &=~0x80; // DMA complete SPUStartCycle[0] = SPUCycles; SPUTargetCycle[0] = size; interrupt |= (1<<1); } void CALLBACK SPU2readDMA7Mem(u16* pMem, int size) { u32 spuaddr = C1_SPUADDR; int i; #ifdef SPU2_LOG SPU2_LOG("SPU2 readDMA7Mem size %x, addr: %x\n", size, pMem); #endif for(i=0;i0x0fffff) // wrap at 2Mb spuaddr=0; // wrap } spuaddr+=19; //Transfer Local To Host TSAH/L + Data Size + 20 (already +1'd) C1_SPUADDR_SET(spuaddr); // got from J.F. and Kanodin... is it needed? spu2Ru16(REG_C1_SPUSTAT)&=~0x80; // DMA complete SPUStartCycle[1] = SPUCycles; SPUTargetCycle[1] = size; interrupt |= (1<<2); } // WRITE // AutoDMA's are used to transfer to the DIRECT INPUT area of the spu2 memory // Left and Right channels are always interleaved together in the transfer so // the AutoDMA's deinterleaves them and transfers them. An interrupt is // generated when half of the buffer (256 short-words for left and 256 // short-words for right ) has been transferred. Another interrupt occurs at // the end of the transfer. int ADMAS4Write() { u32 spuaddr; if(interrupt & 0x2) return 0; if(Adma4.AmountLeft <= 0) return 1; spuaddr = C0_SPUADDR; // SPU2 Deinterleaves the Left and Right Channels memcpy((short*)(spu2mem + spuaddr + 0x2000),(short*)Adma4.MemAddr,512); Adma4.MemAddr += 256; memcpy((short*)(spu2mem + spuaddr + 0x2200),(short*)Adma4.MemAddr,512); Adma4.MemAddr += 256; spuaddr = (spuaddr + 256) & 511; C0_SPUADDR_SET(spuaddr); Adma4.AmountLeft-=512; if(Adma4.AmountLeft == 0) { SPUStartCycle[0] = SPUCycles; SPUTargetCycle[0] = 1;//512*48000; spu2Ru16(REG_C0_SPUSTAT)&=~0x80; interrupt |= (1<<1); } return 0; } int ADMAS7Write() { u32 spuaddr; if(interrupt & 0x4) return 0; if(Adma7.AmountLeft <= 0) return 1; spuaddr = C1_SPUADDR; // SPU2 Deinterleaves the Left and Right Channels memcpy((short*)(spu2mem + spuaddr + 0x2400),(short*)Adma7.MemAddr,512); Adma7.MemAddr += 256; memcpy((short*)(spu2mem + spuaddr + 0x2600),(short*)Adma7.MemAddr,512); Adma7.MemAddr += 256; spuaddr = (spuaddr + 256) & 511; C1_SPUADDR_SET(spuaddr); Adma7.AmountLeft-=512; if(Adma7.AmountLeft == 0) { SPUStartCycle[1] = SPUCycles; SPUTargetCycle[1] = 1;//512*48000; spu2Ru16(REG_C1_SPUSTAT)&=~0x80; interrupt |= (1<<2); } return 0; } void CALLBACK SPU2writeDMA4Mem(u16* pMem, int size) { u32 spuaddr; #ifdef SPU2_LOG SPU2_LOG("SPU2 writeDMA4Mem size %x, addr: %x\n", size, pMem); #endif if((spu2Ru16(REG_C0_ADMAS) & 0x1) && (spu2Ru16(REG_C0_CTRL) & 0x30) == 0 && size) { //fwrite(pMem,iSize<<1,1,LogFile); memset(&Adma4,0,sizeof(ADMA)); C0_SPUADDR_SET(0); Adma4.MemAddr = pMem; Adma4.AmountLeft = size; ADMAS4Write(); return; } spuaddr = C0_SPUADDR; memcpy((unsigned char*)(spu2mem + spuaddr),(unsigned char*)pMem,size<<1); spuaddr += size; C0_SPUADDR_SET(spuaddr); if( (spu2Ru16(REG_C0_CTRL)&0x40) && C0_IRQA == spuaddr){ spu2Ru16(SPDIF_OUT) |= 0x4; IRQINFO |= 4; irqCallbackSPU2(); } if(spuaddr>0xFFFFE) spuaddr = 0x2800; C0_SPUADDR_SET(spuaddr); MemAddr[0] += size<<1; spu2Ru16(REG_C0_SPUSTAT)&=~0x80; SPUStartCycle[0] = SPUCycles; SPUTargetCycle[0] = 1;//iSize; interrupt |= (1<<1); } void CALLBACK SPU2writeDMA7Mem(u16* pMem, int size) { u32 spuaddr; #ifdef SPU2_LOG SPU2_LOG("SPU2 writeDMA7Mem size %x, addr: %x\n", size, pMem); #endif if((spu2Ru16(REG_C1_ADMAS) & 0x2) && (spu2Ru16(REG_C1_CTRL) & 0x30) == 0 && size) { //fwrite(pMem,iSize<<1,1,LogFile); memset(&Adma7,0,sizeof(ADMA)); C1_SPUADDR_SET(0); Adma7.MemAddr = pMem; Adma7.AmountLeft = size; ADMAS7Write(); return; } spuaddr = C1_SPUADDR; memcpy((unsigned char*)(spu2mem + spuaddr),(unsigned char*)pMem,size<<1); spuaddr += size; C1_SPUADDR_SET(spuaddr); if( (spu2Ru16(REG_C1_CTRL)&0x40) && C1_IRQA == spuaddr){ spu2Ru16(SPDIF_OUT) |= 0x8; IRQINFO |= 8; irqCallbackSPU2(); } if(spuaddr>0xFFFFE) spuaddr = 0x2800; C1_SPUADDR_SET(spuaddr); MemAddr[1] += size<<1; spu2Ru16(REG_C1_SPUSTAT)&=~0x80; SPUStartCycle[1] = SPUCycles; SPUTargetCycle[1] = 1;//iSize; interrupt |= (1<<2); } void CALLBACK SPU2interruptDMA4() { #ifdef SPU2_LOG SPU2_LOG("SPU2 interruptDMA4\n"); #endif // spu2Rs16(REG_C0_CTRL)&= ~0x30; // spu2Rs16(REG__1B0) = 0; // spu2Rs16(SPU2_STATX_WRDY_M)|= 0x80; spu2Rs16(REG_C0_CTRL)&=~0x30; spu2Ru16(REG_C0_SPUSTAT)|=0x80; } void CALLBACK SPU2interruptDMA7() { #ifdef SPU2_LOG SPU2_LOG("SPU2 interruptDMA7\n"); #endif // spu2Rs16(REG_C1_CTRL)&= ~0x30; // //spu2Rs16(REG__5B0) = 0; // spu2Rs16(SPU2_STATX_DREQ)|= 0x80; spu2Rs16(REG_C1_CTRL)&=~0x30; spu2Ru16(REG_C1_SPUSTAT)|=0x80; } // turn channels on void SoundOn(int start,int end,unsigned short val) // SOUND ON PSX COMAND { for(int ch=start;ch>=1) // loop channels { if((val&1) && voices[ch].pStart) // mmm... start has to be set before key on !?! { voices[ch].bNew=true; voices[ch].bIgnoreLoop = false; } } } // turn channels off void SoundOff(int start,int end,unsigned short val) // SOUND OFF PSX COMMAND { for(int ch=start;ch>=1) // loop channels { if(val&1) // && s_chan[i].bOn) mmm... voices[ch].bStop=true; } } void FModOn(int start,int end,unsigned short val) // FMOD ON PSX COMMAND { int ch; for(ch=start;ch>=1) // loop channels { if(val&1) // -> fmod on/off { if(ch>0) { } } else { // turn fmod off } } } void CALLBACK SPU2write(u32 mem, u16 value) { u32 spuaddr; #ifdef SPU2_LOG SPU2_LOG("SPU2 write mem %x value %x\n", mem, value); #endif assert( C0_SPUADDR < 0x100000); assert( C1_SPUADDR < 0x100000); spu2Ru16(mem) = value; u32 r = mem&0xffff; // channel info if((r>=0x0000 && r<0x0180)||(r>=0x0400 && r<0x0580)) // some channel info? { int ch=0; if(r>=0x400) ch=((r-0x400)>>4)+24; else ch=(r>>4); VOICE_PROCESSED* pvoice = &voices[ch]; switch(r&0x0f) { case 0: case 2: pvoice->SetVolume(mem&0x2); break; case 4: { int NP; if(value>0x3fff) NP=0x3fff; // get pitch val else NP=value; pvoice->pvoice->pitch = NP; NP=(44100L*NP)/4096L; // calc frequency if(NP<1) NP=1; // some security pvoice->iActFreq=NP; // store frequency break; } case 6: { pvoice->ADSRX.AttackModeExp=(value&0x8000)?1:0; pvoice->ADSRX.AttackRate = ((value>>8) & 0x007f); pvoice->ADSRX.DecayRate = (((value>>4) & 0x000f)); pvoice->ADSRX.SustainLevel = (value & 0x000f); break; } case 8: pvoice->ADSRX.SustainModeExp = (value&0x8000)?1:0; pvoice->ADSRX.SustainIncrease= (value&0x4000)?0:1; pvoice->ADSRX.SustainRate = ((value>>6) & 0x007f); pvoice->ADSRX.ReleaseModeExp = (value&0x0020)?1:0; pvoice->ADSRX.ReleaseRate = ((value & 0x001f)); break; } return; } // more channel info if((r>=0x01c0 && r<=0x02E0)||(r>=0x05c0 && r<=0x06E0)) { int ch=0; unsigned long rx=r; if(rx>=0x400) { ch=24; rx-=0x400; } ch+=((rx-0x1c0)/12); rx-=(ch%24)*12; VOICE_PROCESSED* pvoice = &voices[ch]; switch(rx) { case 0x1C0: pvoice->iStartAddr=(((unsigned long)value&0x3f)<<16)|(pvoice->iStartAddr&0xFFFF); pvoice->pStart=(u8*)(spu2mem+pvoice->iStartAddr); break; case 0x1C2: pvoice->iStartAddr=(pvoice->iStartAddr & 0x3f0000) | (value & 0xFFFF); pvoice->pStart=(u8*)(spu2mem+pvoice->iStartAddr); break; case 0x1C4: pvoice->iLoopAddr =(((unsigned long)value&0x3f)<<16)|(pvoice->iLoopAddr&0xFFFF); pvoice->pLoop=(u8*)(spu2mem+pvoice->iLoopAddr); pvoice->bIgnoreLoop=pvoice->iLoopAddr>0; break; case 0x1C6: pvoice->iLoopAddr=(pvoice->iLoopAddr& 0x3f0000) | (value & 0xFFFF); pvoice->pLoop=(u8*)(spu2mem+pvoice->iLoopAddr); pvoice->bIgnoreLoop=pvoice->iLoopAddr>0; break; case 0x1C8: // unused... check if it gets written as well pvoice->iNextAddr=(((unsigned long)value&0x3f)<<16)|(pvoice->iNextAddr&0xFFFF); break; case 0x1CA: // unused... check if it gets written as well pvoice->iNextAddr=(pvoice->iNextAddr & 0x3f0000) | (value & 0xFFFF); break; } return; } // process non-channel data switch(mem&0xffff) { case REG_C0_SPUDATA: spuaddr = C0_SPUADDR; spu2mem[spuaddr] = value; spuaddr++; if( (spu2Ru16(REG_C0_CTRL)&0x40) && C0_IRQA == spuaddr){ spu2Ru16(SPDIF_OUT) |= 0x4; IRQINFO |= 4; irqCallbackSPU2(); } if(spuaddr>0xFFFFE) spuaddr = 0x2800; C0_SPUADDR_SET(spuaddr); spu2Ru16(REG_C0_SPUSTAT)&=~0x80; spu2Ru16(REG_C0_CTRL)&=~0x30; break; case REG_C1_SPUDATA: spuaddr = C1_SPUADDR; spu2mem[spuaddr] = value; spuaddr++; if( (spu2Ru16(REG_C1_CTRL)&0x40) && C1_IRQA == spuaddr){ spu2Ru16(SPDIF_OUT) |= 0x8; IRQINFO |= 8; irqCallbackSPU2(); } if(spuaddr>0xFFFFE) spuaddr = 0x2800; C1_SPUADDR_SET(spuaddr); spu2Ru16(REG_C1_SPUSTAT)&=~0x80; spu2Ru16(REG_C1_CTRL)&=~0x30; break; case REG_C0_IRQA_HI: case REG_C0_IRQA_LO: pSpuIrq[0]=spu2mem+(C0_IRQA<<1); break; case REG_C1_IRQA_HI: case REG_C1_IRQA_LO: pSpuIrq[1]=spu2mem+(C1_IRQA<<1); break; case REG_C0_SPUADDR_HI: case REG_C1_SPUADDR_HI: spu2Ru16(mem) = value&0xf; break; case REG_C0_SPUON1: SoundOn(0,16,value); break; case REG_C0_SPUON2: SoundOn(16,24,value); break; case REG_C1_SPUON1: SoundOn(24,40,value); break; case REG_C1_SPUON2: SoundOn(40,48,value); break; case REG_C0_SPUOFF1: SoundOff(0,16,value); break; case REG_C0_SPUOFF2: SoundOff(16,24,value); break; case REG_C1_SPUOFF1: SoundOff(24,40,value); break; case REG_C1_SPUOFF2: SoundOff(40,48,value); break; // According to manual all bits are cleared by writing an arbitary value case REG_C0_END1: dwEndChannel2[0] = 0; break; case REG_C0_END2: dwEndChannel2[0] = 0; break; case REG_C1_END1: dwEndChannel2[1] = 0; break; case REG_C1_END2: dwEndChannel2[1] = 0; break; case REG_C0_FMOD1: FModOn(0,16,value); break; case REG_C0_FMOD2: FModOn(16,24,value); break; case REG_C1_FMOD1: FModOn(24,40,value); break; case REG_C1_FMOD2: FModOn(40,48,value); break; } assert( C0_SPUADDR < 0x100000); assert( C1_SPUADDR < 0x100000); } u16 CALLBACK SPU2read(u32 mem) { u32 spuaddr; u16 ret; u32 r = mem&0xffff; if((r>=0x0000 && r<=0x0180)||(r>=0x0400 && r<=0x0580)) // some channel info? { int ch=0; if(r>=0x400) ch=((r-0x400)>>4)+24; else ch=(r>>4); VOICE_PROCESSED* pvoice = &voices[ch]; switch(r&0x0f) { case 10: return (unsigned short)(pvoice->ADSRX.EnvelopeVol>>16); } } if((r>0x01c0 && r<=0x02E0)||(r>0x05c0 && r<=0x06E0)) // some channel info? { int ch=0; unsigned long rx=r; if(rx>=0x400) { ch=24; rx-=0x400; } ch+=((rx-0x1c0)/12); rx-=(ch%24)*12; VOICE_PROCESSED* pvoice = &voices[ch]; switch(rx) { case 0x1C0: return (u16)(((pvoice->pStart-(u8*)spu2mem)>>17)&0x3F); case 0x1C2: return (u16)(((pvoice->pStart-(u8*)spu2mem)>>1)&0xFFFF); case 0x1C4: return (u16)(((pvoice->pLoop-(u8*)spu2mem)>>17)&0x3F); case 0x1C6: return (u16)(((pvoice->pLoop-(u8*)spu2mem)>>1)&0xFFFF); case 0x1C8: return (u16)(((pvoice->pCurr-(u8*)spu2mem)>>17)&0x3F); case 0x1CA: return (u16)(((pvoice->pCurr-(u8*)spu2mem)>>1)&0xFFFF); } } switch(mem&0xffff) { case REG_C0_SPUDATA: spuaddr = C0_SPUADDR; ret =spu2mem[spuaddr]; spuaddr++; if(spuaddr>0xfffff) spuaddr=0; C0_SPUADDR_SET(spuaddr); break; case REG_C1_SPUDATA: spuaddr = C1_SPUADDR; ret = spu2mem[spuaddr]; spuaddr++; if(spuaddr>0xfffff) spuaddr=0; C1_SPUADDR_SET(spuaddr); break; case REG_C0_END1: return (dwEndChannel2[0]&0xffff); case REG_C0_END2: return (dwEndChannel2[0]>>16); case REG_C1_END1: return (dwEndChannel2[1]&0xffff); case REG_C1_END2: return (dwEndChannel2[1]>>16); case REG_IRQINFO: ret = IRQINFO; IRQINFO = 0; break; default: ret = spu2Ru16(mem); } #ifdef SPU2_LOG SPU2_LOG("SPU2 read mem %x: %x\n", mem, ret); #endif return ret; } void CALLBACK SPU2WriteMemAddr(int core, u32 value) { MemAddr[core] = value; } u32 CALLBACK SPU2ReadMemAddr(int core) { return MemAddr[core]; } void CALLBACK SPU2irqCallback(void (*SPU2callback)(),void (*DMA4callback)(),void (*DMA7callback)()) { irqCallbackSPU2 = SPU2callback; irqCallbackDMA4 = DMA4callback; irqCallbackDMA7 = DMA7callback; } // VOICE_PROCESSED definitions SPU_CONTROL_* VOICE_PROCESSED::GetCtrl() { return ((SPU_CONTROL_*)(spu2regs+memoffset+REG_C0_CTRL)); } void VOICE_PROCESSED::SetVolume(int iProcessRight) { u16 vol = iProcessRight ? pvoice->right.word : pvoice->left.word; if(vol&0x8000) // sweep not working { short sInc=1; // -> sweep up? if(vol&0x2000) sInc=-1; // -> or down? if(vol&0x1000) vol^=0xffff; // -> mmm... phase inverted? have to investigate this vol=((vol&0x7f)+1)/2; // -> sweep: 0..127 -> 0..64 vol+=vol/(2*sInc); // -> HACK: we don't sweep right now, so we just raise/lower the volume by the half! vol*=128; } else // no sweep: { if(vol&0x4000) // -> mmm... phase inverted? have to investigate this vol=0x3fff-(vol&0x3fff); } vol&=0x3fff; // set volume //if( iProcessRight ) right = vol; //else left = vol; } void VOICE_PROCESSED::StartSound() { ADSRX.lVolume=1; // and init some adsr vars ADSRX.State=0; ADSRX.EnvelopeVol=0; if(bReverb && GetCtrl()->reverb ) { // setup the reverb effects } pCurr=pStart; // set sample start iSBPos=28; bNew=false; // init channel flags bStop=false; bOn=true; spos=0x10000L; } void VOICE_PROCESSED::VoiceChangeFrequency() { iUsedFreq=iActFreq; // -> take it and calc steps sinc=(u32)pvoice->pitch<<4; if(!sinc) sinc=1; } void VOICE_PROCESSED::Stop() { } // GUI Routines s32 CALLBACK SPU2test() { return 0; } typedef struct { u32 version; u8 spu2regs[0x10000]; } SPU2freezeData; s32 CALLBACK SPU2freeze(int mode, freezeData *data){ SPU2freezeData *spud; if (mode == FREEZE_LOAD) { spud = (SPU2freezeData*)data->data; if( spud->version == 0x11223344 ) { memcpy(spu2regs, spud->spu2regs, 0x10000); } else printf("SPU2null wrong format\n"); } else if (mode == FREEZE_SAVE) { spud = (SPU2freezeData*)data->data; spud->version = 0x11223344; memcpy(spud->spu2regs, spu2regs, 0x10000); } else if (mode == FREEZE_SIZE) { data->size = sizeof(SPU2freezeData); } return 0; } #ifndef _WIN32 GtkWidget *MsgDlg; void OnMsg_Ok() { gtk_widget_destroy(MsgDlg); gtk_main_quit(); } void SysMessage(char *fmt, ...) { GtkWidget *Ok,*Txt; GtkWidget *Box,*Box1; va_list list; char msg[512]; va_start(list, fmt); vsprintf(msg, fmt, list); va_end(list); if (msg[strlen(msg)-1] == '\n') msg[strlen(msg)-1] = 0; MsgDlg = gtk_window_new (GTK_WINDOW_POPUP); gtk_window_set_position(GTK_WINDOW(MsgDlg), GTK_WIN_POS_CENTER); gtk_window_set_title(GTK_WINDOW(MsgDlg), "SPU2null Msg"); gtk_container_set_border_width(GTK_CONTAINER(MsgDlg), 5); Box = gtk_vbox_new(5, 0); gtk_container_add(GTK_CONTAINER(MsgDlg), Box); gtk_widget_show(Box); Txt = gtk_label_new(msg); gtk_box_pack_start(GTK_BOX(Box), Txt, FALSE, FALSE, 5); gtk_widget_show(Txt); Box1 = gtk_hbutton_box_new(); gtk_box_pack_start(GTK_BOX(Box), Box1, FALSE, FALSE, 0); gtk_widget_show(Box1); Ok = gtk_button_new_with_label("Ok"); gtk_signal_connect (GTK_OBJECT(Ok), "clicked", GTK_SIGNAL_FUNC(OnMsg_Ok), NULL); gtk_container_add(GTK_CONTAINER(Box1), Ok); GTK_WIDGET_SET_FLAGS(Ok, GTK_CAN_DEFAULT); gtk_widget_show(Ok); gtk_widget_show(MsgDlg); gtk_main(); } void CALLBACK SPU2configure() { SysMessage("Nothing to Configure"); } void CALLBACK SPU2about() { SysMessage("%s %d.%d", libraryName, version, build); } void LoadConfig() { } #endif