diff options
Diffstat (limited to 'macosx/plugins/PeopsSpu109/src/spu.c')
| -rwxr-xr-x | macosx/plugins/PeopsSpu109/src/spu.c | 1359 |
1 files changed, 1359 insertions, 0 deletions
diff --git a/macosx/plugins/PeopsSpu109/src/spu.c b/macosx/plugins/PeopsSpu109/src/spu.c new file mode 100755 index 00000000..4010cb47 --- /dev/null +++ b/macosx/plugins/PeopsSpu109/src/spu.c @@ -0,0 +1,1359 @@ +/*************************************************************************** + spu.c - description + ------------------- + begin : Wed May 15 2002 + copyright : (C) 2002 by Pete Bernert + email : BlackDove@addcom.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. See also the license.txt file for * + * additional informations. * + * * + ***************************************************************************/ + +//*************************************************************************// +// History of changes: +// +// 2004/09/19 - Pete +// - added option: IRQ handling in the decoded sound buffer areas (Crash Team Racing) +// +// 2004/09/18 - Pete +// - changed global channel var handling to local pointers (hopefully it will help LDChen's port) +// +// 2004/04/22 - Pete +// - finally fixed frequency modulation and made some cleanups +// +// 2003/04/07 - Eric +// - adjusted cubic interpolation algorithm +// +// 2003/03/16 - Eric +// - added cubic interpolation +// +// 2003/03/01 - linuzappz +// - libraryName changes using ALSA +// +// 2003/02/28 - Pete +// - added option for type of interpolation +// - adjusted spu irqs again (Thousant Arms, Valkyrie Profile) +// - added MONO support for MSWindows DirectSound +// +// 2003/02/20 - kode54 +// - amended interpolation code, goto GOON could skip initialization of gpos and cause segfault +// +// 2003/02/19 - kode54 +// - moved SPU IRQ handler and changed sample flag processing +// +// 2003/02/18 - kode54 +// - moved ADSR calculation outside of the sample decode loop, somehow I doubt that +// ADSR timing is relative to the frequency at which a sample is played... I guess +// this remains to be seen, and I don't know whether ADSR is applied to noise channels... +// +// 2003/02/09 - kode54 +// - one-shot samples now process the end block before stopping +// - in light of removing fmod hack, now processing ADSR on frequency channel as well +// +// 2003/02/08 - kode54 +// - replaced easy interpolation with gaussian +// - removed fmod averaging hack +// - changed .sinc to be updated from .iRawPitch, no idea why it wasn't done this way already (<- Pete: because I sometimes fail to see the obvious, haharhar :) +// +// 2003/02/08 - linuzappz +// - small bugfix for one usleep that was 1 instead of 1000 +// - added iDisStereo for no stereo (Linux) +// +// 2003/01/22 - Pete +// - added easy interpolation & small noise adjustments +// +// 2003/01/19 - Pete +// - added Neill's reverb +// +// 2003/01/12 - Pete +// - added recording window handlers +// +// 2003/01/06 - Pete +// - added Neill's ADSR timings +// +// 2002/12/28 - Pete +// - adjusted spu irq handling, fmod handling and loop handling +// +// 2002/08/14 - Pete +// - added extra reverb +// +// 2002/06/08 - linuzappz +// - SPUupdate changed for SPUasync +// +// 2002/05/15 - Pete +// - generic cleanup for the Peops release +// +//*************************************************************************// + +#include "stdafx.h" + +#define _IN_SPU + +#include "externals.h" +#include "regs.h" +#include "resource.h" +#include "swap.h" + +//////////////////////////////////////////////////////////////////////// +// spu version infos/name +//////////////////////////////////////////////////////////////////////// + +const unsigned char version = 1; +const unsigned char revision = 1; +const unsigned char build = 9; +#ifdef _WINDOWS +static char * libraryName = "P.E.Op.S. DSound Audio Driver"; +#else +#ifndef USEALSA +static char * libraryName = "P.E.Op.S. OSS Audio Driver"; +#else +static char * libraryName = "P.E.Op.S. ALSA Audio Driver"; +#endif +#endif +static char * libraryInfo = "P.E.Op.S. OSS Driver V1.9\nCoded by Pete Bernert and the P.E.Op.S. team\n"; + +//////////////////////////////////////////////////////////////////////// +// globals +//////////////////////////////////////////////////////////////////////// + +// psx buffer / addresses + +unsigned short regArea[10000]; +unsigned short spuMem[256*1024]; +unsigned char * spuMemC; +unsigned char * pSpuIrq=0; +unsigned char * pSpuBuffer; +unsigned char * pMixIrq=0; + + +// user settings + +int iUseXA=1; +int iVolume=3; +int iXAPitch=1; +int iUseTimer=2; +int iSPUIRQWait=1; +int iDebugMode=0; +int iRecordMode=0; +int iUseReverb=2; +int iUseInterpolation=2; +int iDisStereo=0; +int iUseDBufIrq=0; + +// MAIN infos struct for each channel + +SPUCHAN s_chan[MAXCHAN+1]; // channel + 1 infos (1 is security for fmod handling) +REVERBInfo rvb; + +unsigned long dwNoiseVal=1; // global noise generator + +unsigned short spuCtrl=0; // some vars to store psx reg infos +unsigned short spuStat=0; +unsigned short spuIrq=0; +unsigned long spuAddr=0xffffffff; // address into spu mem +int bEndThread=0; // thread handlers +int bThreadEnded=0; +int bSpuInit=0; +int bSPUIsOpen=0; + +#ifdef _WINDOWS +HWND hWMain=0; // window handle +HWND hWDebug=0; +HWND hWRecord=0; +static HANDLE hMainThread; +#else +// 2003/06/07 - Pete +#ifndef NOTHREADLIB +static pthread_t thread = -1; // thread id (linux) +#endif +#endif + +unsigned long dwNewChannel=0; // flags for faster testing, if new channel starts + +void (CALLBACK *irqCallback)(void)=0; // func of main emu, called on spu irq +void (CALLBACK *cddavCallback)(unsigned short,unsigned short)=0; +void (CALLBACK *irqQSound)(unsigned char *,long *,long)=0; + +// certain globals (were local before, but with the new timeproc I need em global) + +const int f[5][2] = { { 0, 0 }, + { 60, 0 }, + { 115, -52 }, + { 98, -55 }, + { 122, -60 } }; +int SSumR[NSSIZE]; +int SSumL[NSSIZE]; +int iFMod[NSSIZE]; +int iCycle=0; +short * pS; + +static int lastch=-1; // last channel processed on spu irq in timer mode +static int lastns=0; // last ns pos +static int iSecureStart=0; // secure start counter + +//////////////////////////////////////////////////////////////////////// +// CODE AREA +//////////////////////////////////////////////////////////////////////// + +// dirty inline func includes + +#include "reverb.c" +#include "adsr.c" + +//////////////////////////////////////////////////////////////////////// +// helpers for simple interpolation + +// +// easy interpolation on upsampling, no special filter, just "Pete's common sense" tm +// +// instead of having n equal sample values in a row like: +// ____ +// |____ +// +// we compare the current delta change with the next delta change. +// +// if curr_delta is positive, +// +// - and next delta is smaller (or changing direction): +// \. +// -__ +// +// - and next delta significant (at least twice) bigger: +// --_ +// \. +// +// - and next delta is nearly same: +// \. +// \. +// +// +// if curr_delta is negative, +// +// - and next delta is smaller (or changing direction): +// _-- +// / +// +// - and next delta significant (at least twice) bigger: +// / +// __- +// +// - and next delta is nearly same: +// / +// / +// + + +INLINE void InterpolateUp(SPUCHAN * pChannel) +{ + if(pChannel->SB[32]==1) // flag == 1? calc step and set flag... and don't change the value in this pass + { + const int id1=pChannel->SB[30]-pChannel->SB[29]; // curr delta to next val + const int id2=pChannel->SB[31]-pChannel->SB[30]; // and next delta to next-next val :) + + pChannel->SB[32]=0; + + if(id1>0) // curr delta positive + { + if(id2<id1) + {pChannel->SB[28]=id1;pChannel->SB[32]=2;} + else + if(id2<(id1<<1)) + pChannel->SB[28]=(id1*pChannel->sinc)/0x10000L; + else + pChannel->SB[28]=(id1*pChannel->sinc)/0x20000L; + } + else // curr delta negative + { + if(id2>id1) + {pChannel->SB[28]=id1;pChannel->SB[32]=2;} + else + if(id2>(id1<<1)) + pChannel->SB[28]=(id1*pChannel->sinc)/0x10000L; + else + pChannel->SB[28]=(id1*pChannel->sinc)/0x20000L; + } + } + else + if(pChannel->SB[32]==2) // flag 1: calc step and set flag... and don't change the value in this pass + { + pChannel->SB[32]=0; + + pChannel->SB[28]=(pChannel->SB[28]*pChannel->sinc)/0x20000L; + if(pChannel->sinc<=0x8000) + pChannel->SB[29]=pChannel->SB[30]-(pChannel->SB[28]*((0x10000/pChannel->sinc)-1)); + else pChannel->SB[29]+=pChannel->SB[28]; + } + else // no flags? add bigger val (if possible), calc smaller step, set flag1 + pChannel->SB[29]+=pChannel->SB[28]; +} + +// +// even easier interpolation on downsampling, also no special filter, again just "Pete's common sense" tm +// + +INLINE void InterpolateDown(SPUCHAN * pChannel) +{ + if(pChannel->sinc>=0x20000L) // we would skip at least one val? + { + pChannel->SB[29]+=(pChannel->SB[30]-pChannel->SB[29])/2; // add easy weight + if(pChannel->sinc>=0x30000L) // we would skip even more vals? + pChannel->SB[29]+=(pChannel->SB[31]-pChannel->SB[30])/2; // add additional next weight + } +} + +//////////////////////////////////////////////////////////////////////// +// helpers for gauss interpolation + +#define gval0 (((short*)(&pChannel->SB[29]))[gpos]) +#define gval(x) (((short*)(&pChannel->SB[29]))[(gpos+x)&3]) + +#include "gauss_i.h" + +//////////////////////////////////////////////////////////////////////// + +#include "xa.c" + +//////////////////////////////////////////////////////////////////////// +// START SOUND... called by main thread to setup a new sound on a channel +//////////////////////////////////////////////////////////////////////// + +INLINE void StartSound(SPUCHAN * pChannel) +{ + StartADSR(pChannel); + StartREVERB(pChannel); + + pChannel->pCurr=pChannel->pStart; // set sample start + + pChannel->s_1=0; // init mixing vars + pChannel->s_2=0; + pChannel->iSBPos=28; + + pChannel->bNew=0; // init channel flags + pChannel->bStop=0; + pChannel->bOn=1; + + pChannel->SB[29]=0; // init our interpolation helpers + pChannel->SB[30]=0; + + if(iUseInterpolation>=2) // gauss interpolation? + {pChannel->spos=0x30000L;pChannel->SB[28]=0;} // -> start with more decoding + else {pChannel->spos=0x10000L;pChannel->SB[31]=0;} // -> no/simple interpolation starts with one 44100 decoding +} + +//////////////////////////////////////////////////////////////////////// +// ALL KIND OF HELPERS +//////////////////////////////////////////////////////////////////////// + +INLINE void VoiceChangeFrequency(SPUCHAN * pChannel) +{ + pChannel->iUsedFreq=pChannel->iActFreq; // -> take it and calc steps + pChannel->sinc=pChannel->iRawPitch<<4; + if(!pChannel->sinc) pChannel->sinc=1; + if(iUseInterpolation==1) pChannel->SB[32]=1; // -> freq change in simle imterpolation mode: set flag +} + +//////////////////////////////////////////////////////////////////////// + +INLINE void FModChangeFrequency(SPUCHAN * pChannel,int ns) +{ + int NP=pChannel->iRawPitch; + + NP=((32768L+iFMod[ns])*NP)/32768L; + + if(NP>0x3fff) NP=0x3fff; + if(NP<0x1) NP=0x1; + + NP=(44100L*NP)/(4096L); // calc frequency + + pChannel->iActFreq=NP; + pChannel->iUsedFreq=NP; + pChannel->sinc=(((NP/10)<<16)/4410); + if(!pChannel->sinc) pChannel->sinc=1; + if(iUseInterpolation==1) pChannel->SB[32]=1; // freq change in simple interpolation mode + + iFMod[ns]=0; +} + +//////////////////////////////////////////////////////////////////////// + +// noise handler... just produces some noise data +// surely wrong... and no noise frequency (spuCtrl&0x3f00) will be used... +// and sometimes the noise will be used as fmod modulation... pfff + +INLINE int iGetNoiseVal(SPUCHAN * pChannel) +{ + int fa; + + if((dwNoiseVal<<=1)&0x80000000L) + { + dwNoiseVal^=0x0040001L; + fa=((dwNoiseVal>>2)&0x7fff); + fa=-fa; + } + else fa=(dwNoiseVal>>2)&0x7fff; + + // mmm... depending on the noise freq we allow bigger/smaller changes to the previous val + fa=pChannel->iOldNoise+((fa-pChannel->iOldNoise)/((0x001f-((spuCtrl&0x3f00)>>9))+1)); + if(fa>32767L) fa=32767L; + if(fa<-32767L) fa=-32767L; + pChannel->iOldNoise=fa; + + if(iUseInterpolation<2) // no gauss/cubic interpolation? + pChannel->SB[29] = fa; // -> store noise val in "current sample" slot + return fa; +} + +//////////////////////////////////////////////////////////////////////// + +INLINE void StoreInterpolationVal(SPUCHAN * pChannel,int fa) +{ + if(pChannel->bFMod==2) // fmod freq channel + pChannel->SB[29]=fa; + else + { + if((spuCtrl&0x4000)==0) fa=0; // muted? + else // else adjust + { + if(fa>32767L) fa=32767L; + if(fa<-32767L) fa=-32767L; + } + + if(iUseInterpolation>=2) // gauss/cubic interpolation + { + int gpos = pChannel->SB[28]; + gval0 = fa; + gpos = (gpos+1) & 3; + pChannel->SB[28] = gpos; + } + else + if(iUseInterpolation==1) // simple interpolation + { + pChannel->SB[28] = 0; + pChannel->SB[29] = pChannel->SB[30]; // -> helpers for simple linear interpolation: delay real val for two slots, and calc the two deltas, for a 'look at the future behaviour' + pChannel->SB[30] = pChannel->SB[31]; + pChannel->SB[31] = fa; + pChannel->SB[32] = 1; // -> flag: calc new interolation + } + else pChannel->SB[29]=fa; // no interpolation + } +} + +//////////////////////////////////////////////////////////////////////// + +INLINE int iGetInterpolationVal(SPUCHAN * pChannel) +{ + int fa; + + if(pChannel->bFMod==2) return pChannel->SB[29]; + + switch(iUseInterpolation) + { + //--------------------------------------------------// + case 3: // cubic interpolation + { + long xd;int gpos; + xd = ((pChannel->spos) >> 1)+1; + gpos = pChannel->SB[28]; + + fa = gval(3) - 3*gval(2) + 3*gval(1) - gval0; + fa *= (xd - (2<<15)) / 6; + fa >>= 15; + fa += gval(2) - gval(1) - gval(1) + gval0; + fa *= (xd - (1<<15)) >> 1; + fa >>= 15; + fa += gval(1) - gval0; + fa *= xd; + fa >>= 15; + fa = fa + gval0; + + } break; + //--------------------------------------------------// + case 2: // gauss interpolation + { + int vl, vr;int gpos; + vl = (pChannel->spos >> 6) & ~3; + gpos = pChannel->SB[28]; + vr=(gauss[vl]*gval0)&~2047; + vr+=(gauss[vl+1]*gval(1))&~2047; + vr+=(gauss[vl+2]*gval(2))&~2047; + vr+=(gauss[vl+3]*gval(3))&~2047; + fa = vr>>11; + } break; + //--------------------------------------------------// + case 1: // simple interpolation + { + if(pChannel->sinc<0x10000L) // -> upsampling? + InterpolateUp(pChannel); // --> interpolate up + else InterpolateDown(pChannel); // --> else down + fa=pChannel->SB[29]; + } break; + //--------------------------------------------------// + default: // no interpolation + { + fa=pChannel->SB[29]; + } break; + //--------------------------------------------------// + } + + return fa; +} + +//////////////////////////////////////////////////////////////////////// +// MAIN SPU FUNCTION +// here is the main job handler... thread, timer or direct func call +// basically the whole sound processing is done in this fat func! +//////////////////////////////////////////////////////////////////////// + +// 5 ms waiting phase, if buffer is full and no new sound has to get started +// .. can be made smaller (smallest val: 1 ms), but bigger waits give +// better performance + +#define PAUSE_W 5 +#define PAUSE_L 5000 + +//////////////////////////////////////////////////////////////////////// + +int iSpuAsyncWait=0; + +#ifdef _WINDOWS +static VOID CALLBACK MAINProc(UINT nTimerId,UINT msg,DWORD dwUser,DWORD dwParam1, DWORD dwParam2) +#else +static void *MAINThread(void *arg) +#endif +{ + int s_1,s_2,fa,ns; +#ifndef _MACOSX + int voldiv=iVolume; +#else + const int voldiv=1; +#endif + unsigned char * start;unsigned int nSample; + int ch,predict_nr,shift_factor,flags,d,s; + int bIRQReturn=0;SPUCHAN * pChannel; + + + while(!bEndThread) // until we are shutting down + { + //--------------------------------------------------// + // ok, at the beginning we are looking if there is + // enuff free place in the dsound/oss buffer to + // fill in new data, or if there is a new channel to start. + // if not, we wait (thread) or return (timer/spuasync) + // until enuff free place is available/a new channel gets + // started + + if(dwNewChannel) // new channel should start immedately? + { // (at least one bit 0 ... MAXCHANNEL is set?) + iSecureStart++; // -> set iSecure + if(iSecureStart>5) iSecureStart=0; // (if it is set 5 times - that means on 5 tries a new samples has been started - in a row, we will reset it, to give the sound update a chance) + } + else iSecureStart=0; // 0: no new channel should start + + while(!iSecureStart && !bEndThread && // no new start? no thread end? + (SoundGetBytesBuffered()>TESTSIZE)) // and still enuff data in sound buffer? + { + iSecureStart=0; // reset secure + +#ifdef _WINDOWS + if(iUseTimer) // no-thread mode? + { + if(iUseTimer==1) // -> ok, timer mode 1: setup a oneshot timer of x ms to wait + timeSetEvent(PAUSE_W,1,MAINProc,0,TIME_ONESHOT); + return; // -> and done this time (timer mode 1 or 2) + } + // win thread mode: + Sleep(PAUSE_W); // sleep for x ms (win) +#else + if(iUseTimer) return 0; // linux no-thread mode? bye + usleep(PAUSE_L); // else sleep for x ms (linux) +#endif + + if(dwNewChannel) iSecureStart=1; // if a new channel kicks in (or, of course, sound buffer runs low), we will leave the loop + } + + //--------------------------------------------------// continue from irq handling in timer mode? + + if(lastch>=0) // will be -1 if no continue is pending + { + ch=lastch; ns=lastns; lastch=-1; // -> setup all kind of vars to continue + pChannel=&s_chan[ch]; + goto GOON; // -> directly jump to the continue point + } + + //--------------------------------------------------// + //- main channel loop -// + //--------------------------------------------------// + { + pChannel=s_chan; + for(ch=0;ch<MAXCHAN;ch++,pChannel++) // loop em all... we will collect 1 ms of sound of each playing channel + { + if(pChannel->bNew) + { + StartSound(pChannel); // start new sound + dwNewChannel&=~(1<<ch); // clear new channel bit + } + + if(!pChannel->bOn) continue; // channel not playing? next + + if(pChannel->iActFreq!=pChannel->iUsedFreq) // new psx frequency? + VoiceChangeFrequency(pChannel); + + ns=0; + while(ns<NSSIZE) // loop until 1 ms of data is reached + { + if(pChannel->bFMod==1 && iFMod[ns]) // fmod freq channel + FModChangeFrequency(pChannel,ns); + + while(pChannel->spos>=0x10000L) + { + if(pChannel->iSBPos==28) // 28 reached? + { + start=pChannel->pCurr; // set up the current pos + + if (start == (unsigned char*)-1) // special "stop" sign + { + pChannel->bOn=0; // -> turn everything off + pChannel->ADSRX.lVolume=0; + pChannel->ADSRX.EnvelopeVol=0; + goto ENDX; // -> and done for this channel + } + + pChannel->iSBPos=0; + + //////////////////////////////////////////// spu irq handler here? mmm... do it later + + s_1=pChannel->s_1; + s_2=pChannel->s_2; + + predict_nr=(int)*start;start++; + shift_factor=predict_nr&0xf; + predict_nr >>= 4; + flags=(int)*start;start++; + + // -------------------------------------- // + + for (nSample=0;nSample<28;start++) + { + d=(int)*start; + s=((d&0xf)<<12); + if(s&0x8000) s|=0xffff0000; + + fa=(s >> shift_factor); + fa=fa + ((s_1 * f[predict_nr][0])>>6) + ((s_2 * f[predict_nr][1])>>6); + s_2=s_1;s_1=fa; + s=((d & 0xf0) << 8); + + pChannel->SB[nSample++]=fa; + + if(s&0x8000) s|=0xffff0000; + fa=(s>>shift_factor); + fa=fa + ((s_1 * f[predict_nr][0])>>6) + ((s_2 * f[predict_nr][1])>>6); + s_2=s_1;s_1=fa; + + pChannel->SB[nSample++]=fa; + } + + //////////////////////////////////////////// irq check + + if(irqCallback && (spuCtrl&0x40)) // some callback and irq active? + { + if((pSpuIrq > start-16 && // irq address reached? + pSpuIrq <= start) || + ((flags&1) && // special: irq on looping addr, when stop/loop flag is set + (pSpuIrq > pChannel->pLoop-16 && + pSpuIrq <= pChannel->pLoop))) + { + pChannel->iIrqDone=1; // -> debug flag + irqCallback(); // -> call main emu + + if(iSPUIRQWait) // -> option: wait after irq for main emu + { + iSpuAsyncWait=1; + bIRQReturn=1; + } + } + } + + //////////////////////////////////////////// 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... + //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 = (unsigned char*)-1; + } + else + { + start = pChannel->pLoop; + } + } + + pChannel->pCurr=start; // store values for next cycle + pChannel->s_1=s_1; + pChannel->s_2=s_2; + + //////////////////////////////////////////// + + if(bIRQReturn) // special return for "spu irq - wait for cpu action" + { + bIRQReturn=0; + if(iUseTimer!=2) + { + DWORD dwWatchTime=timeGetTime()+2500; + + while(iSpuAsyncWait && !bEndThread && + timeGetTime()<dwWatchTime) +#ifdef _WINDOWS + Sleep(1); +#else + usleep(1000L); +#endif + + } + else + { + lastch=ch; + lastns=ns; + +#ifdef _WINDOWS + return; +#else + return 0; +#endif + } + } + + //////////////////////////////////////////// + +GOON: ; + + } + + fa=pChannel->SB[pChannel->iSBPos++]; // get sample data + + StoreInterpolationVal(pChannel,fa); // store val for later interpolation + + pChannel->spos -= 0x10000L; + } + + //////////////////////////////////////////////// + + if(pChannel->bNoise) + fa=iGetNoiseVal(pChannel); // get noise val + else fa=iGetInterpolationVal(pChannel); // get sample val + + pChannel->sval=(MixADSR(pChannel)*fa)/1024; // mix adsr + + if(pChannel->bFMod==2) // fmod freq channel + iFMod[ns]=pChannel->sval; // -> store 1T sample data, use that to do fmod on next channel + else // no fmod freq channel + { + ////////////////////////////////////////////// + // ok, left/right sound volume (psx volume goes from 0 ... 0x3fff) + + if(pChannel->iMute) + pChannel->sval=0; // debug mute + else + { + SSumL[ns]+=(pChannel->sval*pChannel->iLeftVolume)/0x4000L; + SSumR[ns]+=(pChannel->sval*pChannel->iRightVolume)/0x4000L; + } + + ////////////////////////////////////////////// + // now let us store sound data for reverb + + if(pChannel->bRVBActive) StoreREVERB(pChannel,ns); + } + + //////////////////////////////////////////////// + // ok, go on until 1 ms data of this channel is collected + + ns++; + pChannel->spos += pChannel->sinc; + + } +ENDX: ; + } + } + + //---------------------------------------------------// + //- here we have another 1 ms of sound data + //---------------------------------------------------// + // mix XA infos (if any) + + if(XAPlay!=XAFeed || XARepeat) MixXA(); + + /////////////////////////////////////////////////////// + // mix all channels (including reverb) into one buffer + + if(iDisStereo) // no stereo? + { + int dl,dr; + for(ns=0;ns<NSSIZE;ns++) + { + SSumL[ns]+=MixREVERBLeft(ns); + + dl=SSumL[ns]/voldiv;SSumL[ns]=0; + if(dl<-32767) dl=-32767;if(dl>32767) dl=32767; + + SSumR[ns]+=MixREVERBRight(); + + dr=SSumR[ns]/voldiv;SSumR[ns]=0; + if(dr<-32767) dr=-32767;if(dr>32767) dr=32767; + *pS++=(dl+dr)/2; + } + } + else // stereo: + for(ns=0;ns<NSSIZE;ns++) + { + SSumL[ns]+=MixREVERBLeft(ns); + + d=SSumL[ns]/voldiv;SSumL[ns]=0; + if(d<-32767) d=-32767;if(d>32767) d=32767; + *pS++=d; + + SSumR[ns]+=MixREVERBRight(); + + d=SSumR[ns]/voldiv;SSumR[ns]=0; + if(d<-32767) d=-32767;if(d>32767) d=32767; + *pS++=d; + } + + ////////////////////////////////////////////////////// + // special irq handling in the decode buffers (0x0000-0x1000) + // we know: + // the decode buffers are located in spu memory in the following way: + // 0x0000-0x03ff CD audio left + // 0x0400-0x07ff CD audio right + // 0x0800-0x0bff Voice 1 + // 0x0c00-0x0fff Voice 3 + // and decoded data is 16 bit for one sample + // we assume: + // even if voices 1/3 are off or no cd audio is playing, the internal + // play positions will move on and wrap after 0x400 bytes. + // Therefore: we just need a pointer from spumem+0 to spumem+3ff, and + // increase this pointer on each sample by 2 bytes. If this pointer + // (or 0x400 offsets of this pointer) hits the spuirq address, we generate + // an IRQ. Only problem: the "wait for cpu" option is kinda hard to do here + // in some of Peops timer modes. So: we ignore this option here (for now). + // Also note: we abuse the channel 0-3 irq debug display for those irqs + // (since that's the easiest way to display such irqs in debug mode :)) + + if(pMixIrq && irqCallback) // pMixIRQ will only be set, if the config option is active + { + for(ns=0;ns<NSSIZE;ns++) + { + if((spuCtrl&0x40) && pSpuIrq && pSpuIrq<spuMemC+0x1000) + { + for(ch=0;ch<4;ch++) + { + if(pSpuIrq>=pMixIrq+(ch*0x400) && pSpuIrq<pMixIrq+(ch*0x400)+2) + {irqCallback();s_chan[ch].iIrqDone=1;} + } + } + pMixIrq+=2;if(pMixIrq>spuMemC+0x3ff) pMixIrq=spuMemC; + } + } + + InitREVERB(); + + ////////////////////////////////////////////////////// + // feed the sound + // wanna have around 1/60 sec (16.666 ms) updates + + if(iCycle++>16) + { + //- zn qsound mixer callback ----------------------// + + if(irqQSound) + { + long * pl=(long *)XAPlay; + short * ps=(short *)pSpuBuffer; + int g,iBytes=((unsigned char *)pS)-((unsigned char *)pSpuBuffer); + iBytes/=2; + for(g=0;g<iBytes;g++) {*pl++=*ps++;} + + irqQSound((unsigned char *)pSpuBuffer,(long *)XAPlay,iBytes/2); + } + + //-------------------------------------------------// + + SoundFeedStreamData((unsigned char*)pSpuBuffer, + ((unsigned char *)pS)- + ((unsigned char *)pSpuBuffer)); + pS=(short *)pSpuBuffer; + iCycle=0; + } + } + + // end of big main loop... + + bThreadEnded=1; + +#ifndef _WINDOWS + return 0; +#endif +} + +//////////////////////////////////////////////////////////////////////// +// WINDOWS THREAD... simply calls the timer func and stays forever :) +//////////////////////////////////////////////////////////////////////// + +#ifdef _WINDOWS + +DWORD WINAPI MAINThreadEx(LPVOID lpParameter) +{ + MAINProc(0,0,0,0,0); + return 0; +} + +#endif + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////// +// SPU ASYNC... even newer epsxe func +// 1 time every 'cycle' cycles... harhar +//////////////////////////////////////////////////////////////////////// + +void CALLBACK SPUasync(unsigned long cycle) +{ + + if(iSpuAsyncWait) + { + iSpuAsyncWait++; + if(iSpuAsyncWait<=64) return; + iSpuAsyncWait=0; + } + +#ifdef _WINDOWS + if(iDebugMode==2) + { + if(IsWindow(hWDebug)) DestroyWindow(hWDebug); + hWDebug=0;iDebugMode=0; + } + if(iRecordMode==2) + { + if(IsWindow(hWRecord)) DestroyWindow(hWRecord); + hWRecord=0;iRecordMode=0; + } +#endif + + if(iUseTimer==2) // special mode, only used in Linux by this spu (or if you enable the experimental Windows mode) + { + if(!bSpuInit) return; // -> no init, no call + +#ifdef _WINDOWS + MAINProc(0,0,0,0,0); // -> experimental win mode... not really tested... don't like the drawbacks +#else + MAINThread(0); // -> linux high-compat mode +#endif + } +} + +//////////////////////////////////////////////////////////////////////// +// SPU UPDATE... new epsxe func +// 1 time every 32 hsync lines +// (312/32)x50 in pal +// (262/32)x60 in ntsc +//////////////////////////////////////////////////////////////////////// + +#ifndef _WINDOWS + +// since epsxe 1.5.2 (linux) uses SPUupdate, not SPUasync, I will +// leave that func in the linux port, until epsxe linux is using +// the async function as well + +void CALLBACK SPUupdate(void) +{ + SPUasync(0); +} + +#endif + +//////////////////////////////////////////////////////////////////////// +// XA AUDIO +//////////////////////////////////////////////////////////////////////// + +void CALLBACK SPUplayADPCMchannel(xa_decode_t *xap) +{ + if(!iUseXA) return; // no XA? bye + if(!xap) return; + if(!xap->freq) return; // no xa freq ? bye + + FeedXA(xap); // call main XA feeder +} + +//////////////////////////////////////////////////////////////////////// +// INIT/EXIT STUFF +//////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////// +// SPUINIT: this func will be called first by the main emu +//////////////////////////////////////////////////////////////////////// + +long CALLBACK SPUinit(void) +{ + spuMemC=(unsigned char *)spuMem; // just small setup + memset((void *)s_chan,0,MAXCHAN*sizeof(SPUCHAN)); + memset((void *)&rvb,0,sizeof(REVERBInfo)); + InitADSR(); + return 0; +} + +//////////////////////////////////////////////////////////////////////// +// SETUPTIMER: init of certain buffers and threads/timers +//////////////////////////////////////////////////////////////////////// + +void SetupTimer(void) +{ + memset(SSumR,0,NSSIZE*sizeof(int)); // init some mixing buffers + memset(SSumL,0,NSSIZE*sizeof(int)); + memset(iFMod,0,NSSIZE*sizeof(int)); + + pS=(short *)pSpuBuffer; // setup soundbuffer pointer + + bEndThread=0; // init thread vars + bThreadEnded=0; + bSpuInit=1; // flag: we are inited + +#ifdef _WINDOWS + + if(iUseTimer==1) // windows: use timer + { + timeBeginPeriod(1); + timeSetEvent(1,1,MAINProc,0,TIME_ONESHOT); + } + else + if(iUseTimer==0) // windows: use thread + { + //_beginthread(MAINThread,0,NULL); + DWORD dw; + hMainThread=CreateThread(NULL,0,MAINThreadEx,0,0,&dw); + SetThreadPriority(hMainThread, + //THREAD_PRIORITY_TIME_CRITICAL); + THREAD_PRIORITY_HIGHEST); + } + +#else + +#ifndef NOTHREADLIB + if(!iUseTimer) // linux: use thread + { + pthread_create(&thread, NULL, MAINThread, NULL); + } +#endif + +#endif +} + +//////////////////////////////////////////////////////////////////////// +// REMOVETIMER: kill threads/timers +//////////////////////////////////////////////////////////////////////// + +void RemoveTimer(void) +{ + bEndThread=1; // raise flag to end thread + +#ifdef _WINDOWS + + if(iUseTimer!=2) // windows thread? + { + while(!bThreadEnded) {Sleep(5L);} // -> wait till thread has ended + Sleep(5L); + } + if(iUseTimer==1) timeEndPeriod(1); // windows timer? stop it + +#else + +#ifndef NOTHREADLIB + if(!iUseTimer) // linux tread? + { + int i=0; + while(!bThreadEnded && i<2000) {usleep(1000L);i++;} // -> wait until thread has ended + if(thread!=-1) {pthread_cancel(thread);thread=-1;} // -> cancel thread anyway + } +#endif + +#endif + + bThreadEnded=0; // no more spu is running + bSpuInit=0; +} + +//////////////////////////////////////////////////////////////////////// +// SETUPSTREAMS: init most of the spu buffers +//////////////////////////////////////////////////////////////////////// + +void SetupStreams(void) +{ + int i; + + pSpuBuffer=(unsigned char *)malloc(32768); // alloc mixing buffer + + if(iUseReverb==1) i=88200*2; + else i=NSSIZE*2; + + sRVBStart = (int *)malloc(i*4); // alloc reverb buffer + memset(sRVBStart,0,i*4); + sRVBEnd = sRVBStart + i; + sRVBPlay = sRVBStart; + + XAStart = // alloc xa buffer + (unsigned long *)malloc(44100*4); + XAPlay = XAStart; + XAFeed = XAStart; + XAEnd = XAStart + 44100; + + for(i=0;i<MAXCHAN;i++) // loop sound channels + { +// we don't use mutex sync... not needed, would only +// slow us down: +// s_chan[i].hMutex=CreateMutex(NULL,FALSE,NULL); + s_chan[i].ADSRX.SustainLevel = 0xf<<27; // -> init sustain + s_chan[i].iMute=0; + s_chan[i].iIrqDone=0; + s_chan[i].pLoop=spuMemC; + s_chan[i].pStart=spuMemC; + s_chan[i].pCurr=spuMemC; + } + + if(iUseDBufIrq) pMixIrq=spuMemC; // enable decoded buffer irqs by setting the address +} + +//////////////////////////////////////////////////////////////////////// +// REMOVESTREAMS: free most buffer +//////////////////////////////////////////////////////////////////////// + +void RemoveStreams(void) +{ + free(pSpuBuffer); // free mixing buffer + pSpuBuffer=NULL; + free(sRVBStart); // free reverb buffer + sRVBStart=0; + free(XAStart); // free XA buffer + XAStart=0; + +/* + int i; + for(i=0;i<MAXCHAN;i++) + { + WaitForSingleObject(s_chan[i].hMutex,2000); + ReleaseMutex(s_chan[i].hMutex); + if(s_chan[i].hMutex) + {CloseHandle(s_chan[i].hMutex);s_chan[i].hMutex=0;} + } +*/ +} + + +//////////////////////////////////////////////////////////////////////// +// SPUOPEN: called by main emu after init +//////////////////////////////////////////////////////////////////////// + +#ifdef _WINDOWS +long CALLBACK SPUopen(HWND hW) +#else +long SPUopen(void) +#endif +{ + if(bSPUIsOpen) return 0; // security for some stupid main emus + + iUseXA=1; // just small setup + iVolume=3; + iReverbOff=-1; + spuIrq=0; + spuAddr=0xffffffff; + bEndThread=0; + bThreadEnded=0; + spuMemC=(unsigned char *)spuMem; + pMixIrq=0; + memset((void *)s_chan,0,(MAXCHAN+1)*sizeof(SPUCHAN)); + pSpuIrq=0; + iSPUIRQWait=1; + +#ifdef _WINDOWS + LastWrite=0xffffffff;LastPlay=0; // init some play vars + if(!IsWindow(hW)) hW=GetActiveWindow(); + hWMain = hW; // store hwnd +#endif + + ReadConfig(); // read user stuff + + SetupSound(); // setup sound (before init!) + + SetupStreams(); // prepare streaming + + SetupTimer(); // timer for feeding data + + bSPUIsOpen=1; + +#ifdef _WINDOWS + if(iDebugMode) // windows debug dialog + { + hWDebug=CreateDialog(hInst,MAKEINTRESOURCE(IDD_DEBUG), + NULL,(DLGPROC)DebugDlgProc); + SetWindowPos(hWDebug,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW|SWP_NOACTIVATE); + UpdateWindow(hWDebug); + SetFocus(hWMain); + } + + if(iRecordMode) // windows recording dialog + { + hWRecord=CreateDialog(hInst,MAKEINTRESOURCE(IDD_RECORD), + NULL,(DLGPROC)RecordDlgProc); + SetWindowPos(hWRecord,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW|SWP_NOACTIVATE); + UpdateWindow(hWRecord); + SetFocus(hWMain); + } +#endif + + return PSE_SPU_ERR_SUCCESS; +} + +//////////////////////////////////////////////////////////////////////// + +#ifndef _WINDOWS +void SPUsetConfigFile(char * pCfg) +{ + pConfigFile=pCfg; +} +#endif + +//////////////////////////////////////////////////////////////////////// +// SPUCLOSE: called before shutdown +//////////////////////////////////////////////////////////////////////// + +long CALLBACK SPUclose(void) +{ + if(!bSPUIsOpen) return 0; // some security + + bSPUIsOpen=0; // no more open + +#ifdef _WINDOWS + if(IsWindow(hWDebug)) DestroyWindow(hWDebug); + hWDebug=0; + if(IsWindow(hWRecord)) DestroyWindow(hWRecord); + hWRecord=0; +#endif + + RemoveTimer(); // no more feeding + + RemoveSound(); // no more sound handling + + RemoveStreams(); // no more streaming + + return 0; +} + +//////////////////////////////////////////////////////////////////////// +// SPUSHUTDOWN: called by main emu on final exit +//////////////////////////////////////////////////////////////////////// + +long CALLBACK SPUshutdown(void) +{ + return 0; +} + +//////////////////////////////////////////////////////////////////////// +// SPUTEST: we don't test, we are always fine ;) +//////////////////////////////////////////////////////////////////////// + +long CALLBACK SPUtest(void) +{ + return 0; +} + +//////////////////////////////////////////////////////////////////////// +// SPUCONFIGURE: call config dialog +//////////////////////////////////////////////////////////////////////// + +long CALLBACK SPUconfigure(void) +{ +#ifdef _WINDOWS + DialogBox(hInst,MAKEINTRESOURCE(IDD_CFGDLG), + GetActiveWindow(),(DLGPROC)DSoundDlgProc); +#else +#ifdef _MACOSX + DoConfiguration(); +#else + StartCfgTool("CFG"); +#endif +#endif + return 0; +} + +//////////////////////////////////////////////////////////////////////// +// SPUABOUT: show about window +//////////////////////////////////////////////////////////////////////// + +void CALLBACK SPUabout(void) +{ +#ifdef _WINDOWS + DialogBox(hInst,MAKEINTRESOURCE(IDD_ABOUT), + GetActiveWindow(),(DLGPROC)AboutDlgProc); +#else +#ifdef _MACOSX + DoAbout(); +#else + StartCfgTool("ABOUT"); +#endif +#endif +} + +//////////////////////////////////////////////////////////////////////// +// SETUP CALLBACKS +// this functions will be called once, +// passes a callback that should be called on SPU-IRQ/cdda volume change +//////////////////////////////////////////////////////////////////////// + +void CALLBACK SPUregisterCallback(void (CALLBACK *callback)(void)) +{ + irqCallback = callback; +} + +void CALLBACK SPUregisterCDDAVolume(void (CALLBACK *CDDAVcallback)(unsigned short,unsigned short)) +{ + cddavCallback = CDDAVcallback; +} + +//////////////////////////////////////////////////////////////////////// +// COMMON PLUGIN INFO FUNCS +//////////////////////////////////////////////////////////////////////// + +char * CALLBACK PSEgetLibName(void) +{ + return libraryName; +} + +unsigned long CALLBACK PSEgetLibType(void) +{ + return PSE_LT_SPU; +} + +unsigned long CALLBACK PSEgetLibVersion(void) +{ + return version<<16|revision<<8|build; +} + +char * SPUgetLibInfos(void) +{ + return libraryInfo; +} + +//////////////////////////////////////////////////////////////////////// + |
