//GiGaHeRz's SPU2 Driver //Copyright (c) 2003-2008, David Quintana // //This library is free software; you can redistribute it and/or //modify it under the terms of the GNU Lesser General Public //License as published by the Free Software Foundation; either //version 2.1 of the License, or (at your option) any later version. // //This library 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 //Lesser General Public License for more details. // //You should have received a copy of the GNU Lesser General Public //License along with this library; if not, write to the Free Software //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // #include "spu2.h" #include "SoundTouch/SoundTouch.h" #include "SoundTouch/WavFile.h" class NullOutModule: public SndOutModule { s32 Init(SndBuffer *) { return 0; } void Close() { } s32 Test() { return 0; } void Configure(HWND parent) { } bool Is51Out() { return false; } } NullOut; SndOutModule* mods[]= { &NullOut, WaveOut, DSoundOut, DSound51Out, ASIOOut, XAudio2Out, }; const u32 mods_count=sizeof(mods)/sizeof(SndOutModule*); //usefull when timestretch isn't available //#define DYNAMIC_BUFFER_LIMITING class SndBufferImpl: public SndBuffer { private: s32 *buffer; s32 size; s32 rpos; s32 wpos; s32 data; #ifdef DYNAMIC_BUFFER_LIMITING s32 buffer_limit; #endif bool pr; bool pw; int overflows; int underflows; int writewaits; bool isWaiting; HANDLE hSyncEvent; CRITICAL_SECTION cs; u32 datawritten; u32 dataread; public: SndBufferImpl(s32 _size) { rpos=0; wpos=0; data=0; size=(_size+1)&(~1); buffer = new s32[size]; pr=false; pw=false; isWaiting=false; #ifdef DYNAMIC_BUFFER_LIMITING overflows=0; underflows=0; writewaits=0; buffer_limit=size; #endif datawritten=0; dataread=0; InitializeCriticalSection(&cs); hSyncEvent = CreateEvent(NULL,FALSE,FALSE,NULL); } virtual ~SndBufferImpl() { pw=false; if(isWaiting) PulseEvent(hSyncEvent); Sleep(10); EnterCriticalSection(&cs); LeaveCriticalSection(&cs); DeleteCriticalSection(&cs); CloseHandle(hSyncEvent); delete buffer; } virtual void WriteSamples(s32 *bData, s32 nSamples) { EnterCriticalSection(&cs); datawritten+=nSamples; #ifdef DYNAMIC_BUFFER_LIMITING int free = buffer_limit-data; #else int free = size-data; #endif if(pw) { #ifdef DYNAMIC_BUFFER_LIMITING if(freenSamples // Problem: // If the SPU2 gets out of sync with the SndOut device, the writepos of the // circular buffer will overtake the readpos, leading to a prolonged period // of hopscotching read/write accesses (ie, lots of staticy crap sound for // several seconds). // // Compromise: // When an overrun occurs, we adapt by discarding a portion of the buffer. // The older portion of the buffer is discarded rather than incoming data, // so that the overall audio synchronization is better. #ifndef DYNAMIC_BUFFER_LIMITING if( data+nSamples > size ) { // Buffer overrun! // Dump samples from the read portion of the buffer instead of dropping // the newly written stuff. // Toss half the buffer plus whatever's being written anew: s32 comp = (size / 2) + nSamples; comp = (comp + 128) & ~127; // probably not important but it makes the log look nicer. :P if( comp > size ) comp = size; data-=comp; rpos=(rpos+comp)%size; timeStretchEnabled = true; ConLog(" * SPU2 > Overrun Compensation (%d samples tossed)\n", size ); } while(data0) { buffer[wpos] = *(bData++); wpos=(wpos+1)%size; data++; nSamples--; } #elif defined( DYNAMIC_BUFFER_LIMITING ) while(nSamples>0) { buffer[wpos] = *(bData++); wpos=(wpos+1)%size; data++; nSamples--; } if(data>size) { do { data-=size; } while(data>size); overflows++; } #endif LeaveCriticalSection(&cs); } virtual void ReadSamples (s32 *bData, s32 nSamples) { static bool underrun_freeze = false; EnterCriticalSection(&cs); dataread+=nSamples; // Problem: // If the SPU2 gets even the least bit out of sync with the SndOut device, // the readpos of the circular buffer will overtake the writepos, // leading to a prolonged period of hopscotching read/write accesses (ie, // lots of staticy crap sound for several seconds). // // Fix: // If the read position overtakes the write position, abort the // transfer immediately and force the SndOut driver to wait until // the read buffer has filled up again before proceeding. // This will cause one brief hiccup that can never exceed the user's // set buffer length in duration. #ifndef DYNAMIC_BUFFER_LIMITING if( underrun_freeze ) { // Let's fill up 80% of our buffer. // (I just picked 80% arbitrarily.. there might be a better value) int toFill = (int)(size * 0.80); toFill = (toFill + 128) & ~127; // probably not important but it makes the log look nicer. :P if( data < toFill ) { while( nSamples>0 ) { *(bData++) = 0; nSamples--; } LeaveCriticalSection(&cs); return; } underrun_freeze = false; timeStretchEnabled = true; ConLog( " * SPU2 > Underrun compensation (%d samples buffered)\n", toFill ); } while(data>0 && nSamples>0) { *(bData++) = buffer[rpos]; rpos=(rpos+1)%size; data--; nSamples--; } while( nSamples>0 ) { // buffer underrun code: // the contents of this loop only get run if data reached zero // before nSamples. // Let's just dull out some silence, because that's usually the least // painful way of dealing with underruns. *(bData++) = 0; nSamples--; } if( data == 0 && !pw ) { underrun_freeze = true; } #elif defined( DYNAMIC_BUFFER_LIMITING ) while(nSamples>0) { *(bData++) = buffer[rpos]; rpos=(rpos+1)%size; data--; nSamples--; } if(data<0) { do { data+=buffer_limit; } while(data<0); underflows++; } #else bool uflow = false; while(data<0) { data+=size; uflow = true; } #endif PulseEvent(hSyncEvent); #ifdef DYNAMIC_BUFFER_LIMITING // will never have BOTH overflows and write waits ;) while((overflows>0)&&(underflows>0)) { overflows--; underflows--; } while((writewaits>0)&&(underflows>0)) { writewaits--; underflows--; } int t=buffer_limit; if(underflows>0) { if(buffer_limit3) underflows=3; } if(writewaits>0) { if(buffer_limit>(3*CurBufferSize)) { buffer_limit=max(3*CurBufferSize,buffer_limit*3/4); writewaits=0; } if(writewaits>10) writewaits=10; } if(overflows>0) { if(buffer_limit>(3*CurBufferSize)) { buffer_limit=max(3*CurBufferSize,buffer_limit*3/4); overflows=0; } if(overflows>3) overflows=3; } //printf(" ** SPU2 Dynamic limiter update: Buffer limit set to %d\n",buffer_limit); #endif LeaveCriticalSection(&cs); } virtual void PauseOnWrite(bool doPause) { pw = doPause; } virtual s32 GetBufferUsage() { return data; } virtual s32 GetBufferSize() { return size; } bool GetStats(u32 &w, u32 &r, bool reset) { EnterCriticalSection(&cs); w = datawritten; r = dataread; if(reset) { datawritten=dataread=0; } LeaveCriticalSection(&cs); return true; } } *sndBuffer; s32* sndTempBuffer; s32 sndTempProgress; s32 sndTempSize; s16* sndTempBuffer16; float* sndTempBufferFloat; s32 buffersize=0; soundtouch::SoundTouch* pSoundTouch=NULL; u32 inputSamples=0; u32 oldWritten=0; u32 oldRead=0; u32 oldInput=0; float valAccum1 = 1.0f; float valAccum2 = 1.0f; u32 numAccum = 1; const u32 numUpdates = 160; float lastTempo=1; float cTempo=1; void ResetTempoChange() { u32 cWritten; u32 cRead; u32 cInput = inputSamples; sndBuffer->GetStats(cWritten,cRead,false); oldWritten=cWritten; oldRead=cRead; oldInput=cInput; pSoundTouch->setTempo(1); } void UpdateTempoChange() { u32 cWritten; u32 cRead; u32 cInput = inputSamples; s32 bufferUsage = sndBuffer->GetBufferUsage(); s32 bufferSize = sndBuffer->GetBufferSize(); //Emergency stretch to compensate for FPS fluctuations and keep the buffers happy bool a=(bufferUsage < CurBufferSize * 4); bool b=(bufferUsage >= (bufferSize - CurBufferSize * 4)); if(a!=b) { if (bufferUsage < CurBufferSize * 2) { cTempo*=0.80f; } else if(bufferUsage < CurBufferSize * 3) { cTempo*=0.90f; } else if(bufferUsage < CurBufferSize * 4) { cTempo*=0.96f; } if (bufferUsage > (bufferSize - CurBufferSize * 2)) { cTempo*=1.20f; } else if(bufferUsage > (bufferSize - CurBufferSize * 3)) { cTempo*=1.10f; } else if(bufferUsage >= (bufferSize - CurBufferSize * 4)) { cTempo*=1.04f; } if (cTempo != lastTempo) { pSoundTouch->setTempo(cTempo); } } else { cTempo = cTempo * 0.9f + lastTempo * 0.1f; } ////////////////////////////////////////////////////////////////////////////// sndBuffer->GetStats(cWritten,cRead,false); valAccum1 += (cRead-oldRead); valAccum2 += (cInput-oldInput); numAccum++; oldRead = cRead; oldInput = cInput; //normal stretch, scales sound to game speed if(numAccum >= numUpdates) { float valAccum = 1.0f; valAccum = valAccum2 / valAccum1; //ConLog( " * SPU2 Timestretch: ENABLED > tempo = %f\n",lastTempo); if((valAccum < 1.04f) && (valAccum > 0.96f) /*&& (valAccum != 1)*/) { // printf("Timestretch Debug > Playbackpeed: %f (difference disregarded, using 1.0).\n",valAccum); valAccum = 1.0f; } /*else { printf("Timestretch Debug > Playbackpeed: %f\n",valAccum); }*/ if (valAccum != lastTempo) //only update soundtouch object when needed pSoundTouch->setTempo(valAccum); lastTempo = valAccum; cTempo = valAccum; if (lastTempo == 1) { timeStretchEnabled = false; // ConLog( " * SPU2 Timestretch: DISABLED > tempo = %f\n",lastTempo); } valAccum1 = 1.0f; valAccum2 = 1.0f; numAccum = 0; } } void soundtouchInit() { pSoundTouch = new soundtouch::SoundTouch(); pSoundTouch->setSampleRate(SampleRate); pSoundTouch->setChannels(2); pSoundTouch->setSetting(SETTING_USE_QUICKSEEK, 0); pSoundTouch->setSetting(SETTING_USE_AA_FILTER, 0); } s32 SndInit() { if(OutputModule>=mods_count) return -1; // initialize sound buffer sndBuffer = new SndBufferImpl(CurBufferSize * MaxBufferCount * 2); sndTempSize = 512; sndTempProgress = 0; sndTempBuffer = new s32[sndTempSize]; sndTempBuffer16 = new s16[sndTempSize]; sndTempBufferFloat = new float[sndTempSize]; buffersize=sndBuffer->GetBufferSize(); soundtouchInit(); ResetTempoChange(); if(LimitMode!=0) { sndBuffer->PauseOnWrite(true); } // some crap spdif_set51(mods[OutputModule]->Is51Out()); // initialize module return mods[OutputModule]->Init(sndBuffer); } void SndClose() { if(OutputModule>=mods_count) return; mods[OutputModule]->Close(); delete sndBuffer; delete sndTempBuffer; delete sndTempBuffer16; delete sndTempBufferFloat; delete pSoundTouch; } void SndUpdateLimitMode() { //sndBuffer->PauseOnWrite(LimitMode!=0); if(LimitMode!=0) { timeStretchEnabled = true; //printf(" * SPU2 limiter is now ON.\n"); printf(" * SPU2 timestretch is now ON.\n"); } else { //printf(" * SPU2 limiter is now OFF.\n"); printf(" * SPU2 timestretch is now OFF.\n"); timeStretchEnabled = false; } } bool SndGetStats(u32 *written, u32 *played) { return sndBuffer->GetStats(*written,*played,false); } s32 SndWrite(s32 ValL, s32 ValR) { if(WaveLog && wavedump_ok) { wavedump_write(ValL>>8,ValR>>8); } if(recording!=0) RecordWrite(ValL>>8,ValR>>8); if(OutputModule>=mods_count) return -1; if(mods[OutputModule] == &NullOut) // null output doesn't need buffering or stretching! :p return 0; inputSamples+=2; sndTempBuffer[sndTempProgress++] = ValL; sndTempBuffer[sndTempProgress++] = ValR; if(sndTempProgress>=sndTempSize) { if(dspPluginEnabled) { for(int i=0;i>8; } // send to winamp DSP sndTempProgress = DspProcess(sndTempBuffer16,sndTempProgress>>1)<<1; for(int i=0;iputSamples(sndTempBufferFloat, sndTempProgress>>1); UpdateTempoChange(); do { sndTempProgress = pSoundTouch->receiveSamples(sndTempBufferFloat, sndTempSize>>1)<<1; if(sndTempProgress>0) { for(int i=0;iWriteSamples(sndTempBuffer,sndTempProgress); } } while (sndTempProgress != 0 ); } else { sndBuffer->WriteSamples(sndTempBuffer,sndTempProgress); sndTempProgress=0; } } return 1; } s32 SndTest() { if(OutputModule>=mods_count) return -1; return mods[OutputModule]->Test(); } void SndConfigure(HWND parent) { if(OutputModule>=mods_count) return; mods[OutputModule]->Configure(parent); } #if 0 ////////////////////////////////////////////////////////////// // Basic Timestretcher (50% to 150%) const s32 StretchBufferSize = 2048; s32 stretchBufferL[StretchBufferSize*2]; s32 stretchBufferR[StretchBufferSize*2]; s32 stretchPosition=0; s32 stretchOutputSize = 2048; // valid values from 1024 to 3072 s32 blah; extern float cspeed; void TimestretchUpdate(int bufferusage,int buffersize) { if(cspeed>1.01) { stretchOutputSize+=10; } else if (cspeed<0.99) { stretchOutputSize-=10; } blah++; if(blah>=2) { blah=0; printf(" * Stretch = %d of %d\n",stretchOutputSize,StretchBufferSize); } } s32 SndWriteStretch(s32 ValL, s32 ValR) { // TODO: update stretchOutputSize according to speed :P stretchBufferL[stretchPosition] = ValL; stretchBufferR[stretchPosition] = ValR; stretchPosition++; if(stretchPosition>=StretchBufferSize) { stretchPosition=0; if(stretchOutputSize < (StretchBufferSize/2)) stretchOutputSize=(StretchBufferSize/2); if(stretchOutputSize > (StretchBufferSize*3/2)) stretchOutputSize=(StretchBufferSize*3/2); if(stretchOutputSize>StretchBufferSize) { int K = (stretchOutputSize-StretchBufferSize); int J = StretchBufferSize - K; // K samples offset for(int i=StretchBufferSize;i