pcsx2/plugins/SPU2null/Src/SPU2.cpp

1214 lines
37 KiB
C++
Raw Normal View History

/* 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 <assert.h>
#include <stdlib.h>
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;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;
}
}
}
void CALLBACK SPU2readDMA4Mem(u16 *pMem, int size)
{
u32 spuaddr = C0_SPUADDR;
int i;
#ifdef SPU2_LOG
SPU2_LOG("SPU2 readDMA4Mem size %x, addr: %x\n", size, pMem);
#endif
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);
}
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;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((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<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(int start,int end,unsigned short val) // SOUND OFF PSX COMMAND
{
for(int ch=start;ch<end;ch++,val>>=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<end;ch++,val>>=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