2008-10-12 17:29:15 +00:00
|
|
|
//GiGaHeRz's SPU2 Driver
|
|
|
|
//Copyright (c) 2003-2008, David Quintana <gigaherz@gmail.com>
|
|
|
|
//
|
|
|
|
//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*);
|
|
|
|
|
2008-10-14 14:57:05 +00:00
|
|
|
//usefull when timestretch isn't available
|
2008-10-12 17:29:15 +00:00
|
|
|
//#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(free<nSamples)
|
|
|
|
writewaits++;
|
|
|
|
#endif
|
|
|
|
while((free<nSamples)&&(pw))
|
|
|
|
{
|
2008-10-16 14:12:40 +00:00
|
|
|
//isWaiting=true;
|
2008-10-12 17:29:15 +00:00
|
|
|
LeaveCriticalSection(&cs);
|
2008-10-23 09:22:20 +00:00
|
|
|
//ConLog( " * SPU2 : Waiting for object... " );
|
2008-10-12 17:29:15 +00:00
|
|
|
WaitForSingleObject(hSyncEvent,1000);
|
2008-10-23 09:22:20 +00:00
|
|
|
//ConLog( " Signaled! \n" );
|
2008-10-12 17:29:15 +00:00
|
|
|
EnterCriticalSection(&cs);
|
|
|
|
#ifdef DYNAMIC_BUFFER_LIMITING
|
|
|
|
free = buffer_limit-data;
|
|
|
|
#else
|
|
|
|
free = size-data;
|
|
|
|
#endif
|
2008-10-16 14:12:40 +00:00
|
|
|
//isWaiting=false;
|
2008-10-12 17:29:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// either pw=false or free>nSamples
|
2008-10-22 16:39:31 +00:00
|
|
|
|
|
|
|
// 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:
|
2008-10-23 09:22:20 +00:00
|
|
|
// 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.
|
2008-10-22 16:39:31 +00:00
|
|
|
|
|
|
|
#ifndef DYNAMIC_BUFFER_LIMITING
|
2008-10-23 09:22:20 +00:00
|
|
|
|
|
|
|
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;
|
2008-10-23 14:01:42 +00:00
|
|
|
timeStretchEnabled = true;
|
2008-10-23 09:22:20 +00:00
|
|
|
ConLog(" * SPU2 > Overrun Compensation (%d samples tossed)\n", size );
|
|
|
|
}
|
|
|
|
|
2008-10-22 16:39:31 +00:00
|
|
|
while(data<size && nSamples>0)
|
|
|
|
{
|
|
|
|
buffer[wpos] = *(bData++);
|
|
|
|
wpos=(wpos+1)%size;
|
|
|
|
data++;
|
|
|
|
nSamples--;
|
|
|
|
}
|
|
|
|
|
|
|
|
#elif defined( DYNAMIC_BUFFER_LIMITING )
|
|
|
|
|
2008-10-12 17:29:15 +00:00
|
|
|
while(nSamples>0)
|
|
|
|
{
|
|
|
|
buffer[wpos] = *(bData++);
|
|
|
|
wpos=(wpos+1)%size;
|
|
|
|
data++;
|
|
|
|
nSamples--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(data>size)
|
|
|
|
{
|
|
|
|
do {
|
|
|
|
data-=size;
|
|
|
|
}
|
|
|
|
while(data>size);
|
|
|
|
overflows++;
|
|
|
|
}
|
2008-10-22 16:39:31 +00:00
|
|
|
#endif
|
2008-10-12 17:29:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
LeaveCriticalSection(&cs);
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void ReadSamples (s32 *bData, s32 nSamples)
|
|
|
|
{
|
2008-10-22 16:39:31 +00:00
|
|
|
static bool underrun_freeze = false;
|
|
|
|
|
2008-10-12 17:29:15 +00:00
|
|
|
EnterCriticalSection(&cs);
|
|
|
|
dataread+=nSamples;
|
|
|
|
|
2008-10-22 16:39:31 +00:00
|
|
|
// 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 )
|
|
|
|
{
|
2008-10-23 09:22:20 +00:00
|
|
|
// 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 )
|
2008-10-22 16:39:31 +00:00
|
|
|
{
|
|
|
|
while( nSamples>0 )
|
|
|
|
{
|
|
|
|
*(bData++) = 0;
|
|
|
|
nSamples--;
|
|
|
|
}
|
|
|
|
LeaveCriticalSection(&cs);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
underrun_freeze = false;
|
2008-10-23 14:01:42 +00:00
|
|
|
timeStretchEnabled = true;
|
2008-10-23 09:22:20 +00:00
|
|
|
ConLog( " * SPU2 > Underrun compensation (%d samples buffered)\n", toFill );
|
2008-10-22 16:39:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 )
|
|
|
|
|
2008-10-12 17:29:15 +00:00
|
|
|
while(nSamples>0)
|
|
|
|
{
|
|
|
|
*(bData++) = buffer[rpos];
|
|
|
|
rpos=(rpos+1)%size;
|
|
|
|
data--;
|
|
|
|
nSamples--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(data<0)
|
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
|
|
|
data+=buffer_limit;
|
|
|
|
}
|
|
|
|
while(data<0);
|
|
|
|
underflows++;
|
|
|
|
}
|
|
|
|
#else
|
2008-10-16 14:12:40 +00:00
|
|
|
bool uflow = false;
|
2008-10-12 17:29:15 +00:00
|
|
|
while(data<0)
|
|
|
|
{
|
|
|
|
data+=size;
|
2008-10-16 14:12:40 +00:00
|
|
|
uflow = true;
|
2008-10-12 17:29:15 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2008-10-22 16:39:31 +00:00
|
|
|
PulseEvent(hSyncEvent);
|
2008-10-12 17:29:15 +00:00
|
|
|
|
|
|
|
#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_limit<size)
|
|
|
|
{
|
|
|
|
buffer_limit=min(size,buffer_limit*2);
|
|
|
|
underflows=0;
|
|
|
|
}
|
|
|
|
if(underflows>3) underflows=3;
|
|
|
|
}
|
|
|
|
if(writewaits>0)
|
|
|
|
{
|
2008-10-13 13:55:55 +00:00
|
|
|
if(buffer_limit>(3*CurBufferSize))
|
2008-10-12 17:29:15 +00:00
|
|
|
{
|
2008-10-13 13:55:55 +00:00
|
|
|
buffer_limit=max(3*CurBufferSize,buffer_limit*3/4);
|
2008-10-12 17:29:15 +00:00
|
|
|
writewaits=0;
|
|
|
|
}
|
|
|
|
if(writewaits>10) writewaits=10;
|
|
|
|
}
|
|
|
|
if(overflows>0)
|
|
|
|
{
|
2008-10-13 13:55:55 +00:00
|
|
|
if(buffer_limit>(3*CurBufferSize))
|
2008-10-12 17:29:15 +00:00
|
|
|
{
|
2008-10-13 13:55:55 +00:00
|
|
|
buffer_limit=max(3*CurBufferSize,buffer_limit*3/4);
|
2008-10-12 17:29:15 +00:00
|
|
|
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;
|
|
|
|
|
2008-10-14 14:57:05 +00:00
|
|
|
float valAccum1 = 1.0f;
|
|
|
|
float valAccum2 = 1.0f;
|
|
|
|
u32 numAccum = 1;
|
2008-10-13 13:55:55 +00:00
|
|
|
|
2008-10-23 14:01:42 +00:00
|
|
|
const u32 numUpdates = 160;
|
2008-10-12 17:29:15 +00:00
|
|
|
|
|
|
|
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();
|
2008-10-14 10:47:02 +00:00
|
|
|
//Emergency stretch to compensate for FPS fluctuations and keep the buffers happy
|
2008-10-17 12:35:06 +00:00
|
|
|
bool a=(bufferUsage < CurBufferSize * 4);
|
|
|
|
bool b=(bufferUsage >= (bufferSize - CurBufferSize * 4));
|
2008-10-14 10:47:02 +00:00
|
|
|
|
2008-10-12 17:29:15 +00:00
|
|
|
if(a!=b)
|
|
|
|
{
|
2008-10-17 12:35:06 +00:00
|
|
|
if (bufferUsage < CurBufferSize * 2) { cTempo*=0.80f; }
|
|
|
|
else if(bufferUsage < CurBufferSize * 3) { cTempo*=0.90f; }
|
|
|
|
else if(bufferUsage < CurBufferSize * 4) { cTempo*=0.96f; }
|
2008-10-12 17:29:15 +00:00
|
|
|
|
2008-10-17 12:35:06 +00:00
|
|
|
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; }
|
2008-10-14 14:57:05 +00:00
|
|
|
|
|
|
|
if (cTempo != lastTempo) {
|
|
|
|
pSoundTouch->setTempo(cTempo);
|
|
|
|
}
|
2008-10-12 17:29:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
cTempo = cTempo * 0.9f + lastTempo * 0.1f;
|
|
|
|
}
|
2008-10-14 10:47:02 +00:00
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
2008-10-12 17:29:15 +00:00
|
|
|
|
|
|
|
sndBuffer->GetStats(cWritten,cRead,false);
|
|
|
|
|
2008-10-14 10:47:02 +00:00
|
|
|
valAccum1 += (cRead-oldRead);
|
|
|
|
valAccum2 += (cInput-oldInput);
|
|
|
|
numAccum++;
|
2008-10-12 17:29:15 +00:00
|
|
|
|
2008-10-14 10:47:02 +00:00
|
|
|
oldRead = cRead;
|
|
|
|
oldInput = cInput;
|
|
|
|
|
|
|
|
//normal stretch, scales sound to game speed
|
|
|
|
if(numAccum >= numUpdates)
|
2008-10-12 17:29:15 +00:00
|
|
|
{
|
2008-10-14 14:57:05 +00:00
|
|
|
float valAccum = 1.0f;
|
|
|
|
|
|
|
|
valAccum = valAccum2 / valAccum1;
|
2008-10-23 14:01:42 +00:00
|
|
|
|
|
|
|
//ConLog( " * SPU2 Timestretch: ENABLED > tempo = %f\n",lastTempo);
|
|
|
|
|
2008-10-17 12:35:06 +00:00
|
|
|
if((valAccum < 1.04f) && (valAccum > 0.96f) /*&& (valAccum != 1)*/)
|
2008-10-12 17:29:15 +00:00
|
|
|
{
|
2008-10-17 12:35:06 +00:00
|
|
|
// printf("Timestretch Debug > Playbackpeed: %f (difference disregarded, using 1.0).\n",valAccum);
|
2008-10-14 14:57:05 +00:00
|
|
|
valAccum = 1.0f;
|
2008-10-12 17:29:15 +00:00
|
|
|
}
|
2008-10-17 12:35:06 +00:00
|
|
|
/*else
|
2008-10-12 17:29:15 +00:00
|
|
|
{
|
2008-10-16 14:10:31 +00:00
|
|
|
printf("Timestretch Debug > Playbackpeed: %f\n",valAccum);
|
2008-10-17 12:35:06 +00:00
|
|
|
}*/
|
2008-10-14 10:47:02 +00:00
|
|
|
|
|
|
|
if (valAccum != lastTempo) //only update soundtouch object when needed
|
|
|
|
pSoundTouch->setTempo(valAccum);
|
2008-10-12 17:29:15 +00:00
|
|
|
|
2008-10-14 10:47:02 +00:00
|
|
|
lastTempo = valAccum;
|
2008-10-14 14:57:05 +00:00
|
|
|
cTempo = valAccum;
|
2008-10-23 14:01:42 +00:00
|
|
|
|
|
|
|
if (lastTempo == 1) {
|
|
|
|
timeStretchEnabled = false;
|
|
|
|
// ConLog( " * SPU2 Timestretch: DISABLED > tempo = %f\n",lastTempo);
|
|
|
|
}
|
|
|
|
|
2008-10-14 14:57:05 +00:00
|
|
|
valAccum1 = 1.0f;
|
|
|
|
valAccum2 = 1.0f;
|
|
|
|
numAccum = 0;
|
2008-10-12 17:29:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2008-10-13 13:55:55 +00:00
|
|
|
sndBuffer = new SndBufferImpl(CurBufferSize * MaxBufferCount * 2);
|
2008-10-12 17:29:15 +00:00
|
|
|
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()
|
|
|
|
{
|
2008-10-22 16:39:31 +00:00
|
|
|
//sndBuffer->PauseOnWrite(LimitMode!=0);
|
2008-10-12 17:29:15 +00:00
|
|
|
|
2008-10-22 16:39:31 +00:00
|
|
|
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;
|
|
|
|
}
|
2008-10-12 17:29:15 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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<sndTempProgress;i++) { sndTempBuffer16[i] = sndTempBuffer[i]>>8; }
|
|
|
|
|
|
|
|
// send to winamp DSP
|
|
|
|
sndTempProgress = DspProcess(sndTempBuffer16,sndTempProgress>>1)<<1;
|
|
|
|
|
|
|
|
for(int i=0;i<sndTempProgress;i++) { sndTempBuffer[i] = sndTempBuffer16[i]<<8; }
|
|
|
|
}
|
|
|
|
|
|
|
|
if(timeStretchEnabled)
|
|
|
|
{
|
|
|
|
for(int i=0;i<sndTempProgress;i++) { sndTempBufferFloat[i] = sndTempBuffer[i]/2147483648.0f; }
|
|
|
|
|
|
|
|
// send to timestretcher
|
|
|
|
pSoundTouch->putSamples(sndTempBufferFloat, sndTempProgress>>1);
|
|
|
|
UpdateTempoChange();
|
|
|
|
do
|
|
|
|
{
|
|
|
|
sndTempProgress = pSoundTouch->receiveSamples(sndTempBufferFloat, sndTempSize>>1)<<1;
|
|
|
|
|
|
|
|
if(sndTempProgress>0)
|
|
|
|
{
|
|
|
|
|
|
|
|
for(int i=0;i<sndTempProgress;i++) { sndTempBuffer[i] = (s32)(sndTempBufferFloat[i]*2147483648.0f); }
|
|
|
|
sndBuffer->WriteSamples(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<stretchOutputSize;i++)
|
|
|
|
{
|
|
|
|
stretchBufferL[i+K]=stretchBufferL[i];
|
|
|
|
stretchBufferR[i+K]=stretchBufferR[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
// blend along J samples from K to stretchbuffersize
|
|
|
|
for(int i=K;i<StretchBufferSize;i++)
|
|
|
|
{
|
|
|
|
int QL = stretchBufferL[i-K] - stretchBufferL[i];
|
|
|
|
stretchBufferL[i] = stretchBufferL[i] + MulDiv(QL,(i-K),J);
|
|
|
|
|
|
|
|
int QR = stretchBufferR[i-K] - stretchBufferR[i];
|
|
|
|
stretchBufferR[i] = stretchBufferR[i] + MulDiv(QR,(i-K),J);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else if( stretchOutputSize < StretchBufferSize)
|
|
|
|
{
|
|
|
|
int K = (StretchBufferSize-stretchOutputSize);
|
|
|
|
|
|
|
|
// blend along K samples from 0 to stretchoutputsize
|
|
|
|
for(int i=0;i<stretchOutputSize;i++)
|
|
|
|
{
|
|
|
|
int QL = stretchBufferL[i+K] - stretchBufferL[i];
|
|
|
|
stretchBufferL[i] = stretchBufferL[i] + MulDiv(QL,i,stretchOutputSize);
|
|
|
|
|
|
|
|
int QR = stretchBufferR[i+K] - stretchBufferR[i];
|
|
|
|
stretchBufferR[i] = stretchBufferR[i] + MulDiv(QR,i,stretchOutputSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int K=stretchOutputSize; // stretchOutputSize might be modified in the middle of writing!
|
|
|
|
for(int i=0;i<K;i++)
|
|
|
|
{
|
|
|
|
int t = SndWriteOut(stretchBufferL[i],stretchBufferR[i]);
|
|
|
|
if(t) return t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|