/* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "SPU2.h" #include #include #include using namespace std; const u8 version = PS2E_SPU2_VERSION; const u8 revision = 0; const u8 build = 8; // increase that with each version const u32 minor = 0; // 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 PCSX2_DEBUG char *libraryName = "SPU2null (Debug)"; #else char *libraryName = "SPU2null "; #endif string s_strIniPath = "inis/"; string s_strLogPath = "logs/"; FILE *spu2Log; Config conf; ADMA Adma4; ADMA Adma7; u32 MemAddr[2]; u32 g_nSpuInit = 0; u16 interrupt = 0; s8 *spu2regs = NULL; u16 *spu2mem = NULL; u16 *pSpuIrq[2] = {NULL}; u32 dwEndChannel2[2] = {0}; // keeps track of what channels have ended u32 dwNoiseVal = 1; // global noise generator s32 SPUCycles = 0, SPUWorkerCycles = 0; s32 SPUStartCycle[2]; s32 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 s32 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 EXPORT_C_(u32) PS2EgetLibType() { return PS2E_LT_SPU2; } EXPORT_C_(char *) PS2EgetLibName() { return libraryName; } EXPORT_C_(u32) 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); } EXPORT_C_(void) SPU2setSettingsDir(const char *dir) { s_strIniPath = (dir == NULL) ? "inis/" : dir; } bool OpenLog() { bool result = true; #ifdef SPU2_LOG if (spu2Log) return result; const std::string LogFile(s_strLogPath + "/spu2null.log"); spu2Log = fopen(LogFile.c_str(), "w"); if (spu2Log != NULL) setvbuf(spu2Log, NULL, _IONBF, 0); else { SysMessage("Can't create log file %s\n", LogFile.c_str()); result = false; } SPU2_LOG("Spu2 null version %d,%d\n", revision, build); SPU2_LOG("SPU2init\n"); #endif return result; } EXPORT_C_(void) SPU2setLogDir(const char *dir) { // Get the path to the log directory. s_strLogPath = (dir == NULL) ? "logs/" : dir; // Reload the log file after updated the path if (spu2Log) { fclose(spu2Log); spu2Log = NULL; } OpenLog(); } EXPORT_C_(s32) SPU2init() { OpenLog(); 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; } EXPORT_C_(s32) SPU2open(void *pDsp) { LoadConfig(); SPUCycles = SPUWorkerCycles = 0; interrupt = 0; SPUStartCycle[0] = SPUStartCycle[1] = 0; SPUTargetCycle[0] = SPUTargetCycle[1] = 0; g_nSpuInit = 1; return 0; } EXPORT_C_(void) SPU2close() { g_nSpuInit = 0; } EXPORT_C_(void) SPU2shutdown() { free(spu2regs); spu2regs = NULL; free(spu2mem); spu2mem = NULL; #ifdef SPU2_LOG if (spu2Log) { fclose(spu2Log); spu2Log = NULL; } #endif } // simulate SPU2 for 1ms void SPU2Worker(); #define CYCLES_PER_MS (36864000 / 1000) EXPORT_C_(void) 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 { u32 r, rs, rd; s32 i; memset(RateTable, 0, sizeof(u32) * 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; ch < SPU_NUMBER_VOICES; ch++, pChannel++) // loop em all... we will collect 1 ms of sound of each playing channel { if (pChannel->bNew) { 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 (ns < NSSIZE) { while (pChannel->spos >= 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; ns < NSSIZE; ns++) { Adma4.Index += 1; if (Adma4.Index == 128 || Adma4.Index == 384) { if (ADMAS4Write()) { spu2Ru16(REG_C0_SPUSTAT) &= ~0x80; irqCallbackDMA4(); } else MemAddr[0] += 1024; } if (Adma4.Index == 512) { Adma4.Index = 0; } } } if ((spu2Ru16(REG_C1_MMIX) & 0xC0) && (spu2Ru16(REG_C1_ADMAS) & 0x2) && !(spu2Ru16(REG_C1_CTRL) & 0x30)) { for (int ns = 0; ns < NSSIZE; ns++) { Adma7.Index += 1; if (Adma7.Index == 128 || Adma7.Index == 384) { if (ADMAS7Write()) { spu2Ru16(REG_C1_SPUSTAT) &= ~0x80; irqCallbackDMA7(); } else MemAddr[1] += 1024; } if (Adma7.Index == 512) Adma7.Index = 0; } } } EXPORT_C_(void) SPU2readDMA4Mem(u16 *pMem, int size) { u32 spuaddr = C0_SPUADDR; int i; SPU2_LOG("SPU2 readDMA4Mem size %x, addr: %x\n", size, pMem); for (i = 0; i < size; i++) { *pMem++ = *(u16 *)(spu2mem + spuaddr); if ((spu2Rs16(REG_C0_CTRL) & 0x40) && C0_IRQA == spuaddr) { spu2Ru16(SPDIF_OUT) |= 0x4; C0_SPUADDR_SET(spuaddr); IRQINFO |= 4; irqCallbackSPU2(); } spuaddr++; // inc spu addr if (spuaddr > 0x0fffff) // 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); } EXPORT_C_(void) SPU2readDMA7Mem(u16 *pMem, int size) { u32 spuaddr = C1_SPUADDR; int i; SPU2_LOG("SPU2 readDMA7Mem size %x, addr: %x\n", size, pMem); for (i = 0; i < size; i++) { *pMem++ = *(u16 *)(spu2mem + spuaddr); if ((spu2Rs16(REG_C1_CTRL) & 0x40) && C1_IRQA == spuaddr) { spu2Ru16(SPDIF_OUT) |= 0x8; C1_SPUADDR_SET(spuaddr); IRQINFO |= 8; irqCallbackSPU2(); } spuaddr++; // inc spu addr if (spuaddr > 0x0fffff) // 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((s16 *)(spu2mem + spuaddr + 0x2000), (s16 *)Adma4.MemAddr, 512); Adma4.MemAddr += 256; memcpy((s16 *)(spu2mem + spuaddr + 0x2200), (s16 *)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((s16 *)(spu2mem + spuaddr + 0x2400), (s16 *)Adma7.MemAddr, 512); Adma7.MemAddr += 256; memcpy((s16 *)(spu2mem + spuaddr + 0x2600), (s16 *)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; } EXPORT_C_(void) SPU2writeDMA4Mem(u16 *pMem, int size) { u32 spuaddr; SPU2_LOG("SPU2 writeDMA4Mem size %x, addr: %x\n", size, pMem); 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((u8 *)(spu2mem + spuaddr), (u8 *)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); } EXPORT_C_(void) SPU2writeDMA7Mem(u16 *pMem, int size) { u32 spuaddr; SPU2_LOG("SPU2 writeDMA7Mem size %x, addr: %x\n", size, pMem); 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((u8 *)(spu2mem + spuaddr), (u8 *)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); } EXPORT_C_(void) SPU2interruptDMA4() { SPU2_LOG("SPU2 interruptDMA4\n"); spu2Rs16(REG_C0_CTRL) &= ~0x30; spu2Ru16(REG_C0_SPUSTAT) |= 0x80; } EXPORT_C_(void) SPU2interruptDMA7() { SPU2_LOG("SPU2 interruptDMA7\n"); // 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(s32 start, s32 end, u16 val) // SOUND ON PSX COMAND { for (s32 ch = start; ch < end; ch++, val >>= 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(s32 start, s32 end, u16 val) // SOUND OFF PSX COMMAND { for (s32 ch = start; ch < end; ch++, val >>= 1) // loop channels { if (val & 1) // && s_chan[i].bOn) mmm... voices[ch].bStop = true; } } void FModOn(s32 start, s32 end, u16 val) // FMOD ON PSX COMMAND { int ch; for (ch = start; ch < end; ch++, val >>= 1) // loop channels { if (val & 1) // -> fmod on/off { if (ch > 0) { } } else { // turn fmod off } } } EXPORT_C_(void) SPU2write(u32 mem, u16 value) { u32 spuaddr; SPU2_LOG("SPU2 write mem %x value %x\n", mem, value); 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)) { s32 ch = 0; u32 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 = (((u32)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 = (((u32)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 = (((u32)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); } EXPORT_C_(u16) SPU2read(u32 mem) { u32 spuaddr; u16 ret; u32 r = mem & 0xffff; if ((r >= 0x0000 && r <= 0x0180) || (r >= 0x0400 && r <= 0x0580)) // some channel info? { s32 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 (u16)(pvoice->ADSRX.EnvelopeVol >> 16); } } if ((r > 0x01c0 && r <= 0x02E0) || (r > 0x05c0 && r <= 0x06E0)) // some channel info? { s32 ch = 0; u32 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); } SPU2_LOG("SPU2 read mem %x: %x\n", mem, ret); return ret; } EXPORT_C_(void) SPU2WriteMemAddr(int core, u32 value) { MemAddr[core] = value; } EXPORT_C_(u32) SPU2ReadMemAddr(int core) { return MemAddr[core]; } EXPORT_C_(void) 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 { s16 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 EXPORT_C_(s32) SPU2test() { return 0; } typedef struct { u32 version; u8 spu2regs[0x10000]; } SPU2freezeData; EXPORT_C_(s32) 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; }