2009-02-15 05:15:39 +00:00
|
|
|
/* SPU2-X, A plugin for Emulating the Sound Processing Unit of the Playstation 2
|
|
|
|
* Developed and maintained by the Pcsx2 Development Team.
|
|
|
|
*
|
|
|
|
* Original portions from SPU2ghz are (c) 2008 by David Quintana [gigaherz]
|
|
|
|
*
|
|
|
|
* 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 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
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2009-02-20 21:48:59 +00:00
|
|
|
#include "Spu2.h"
|
2009-02-15 05:15:39 +00:00
|
|
|
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
StereoOut32 StereoOut32::Empty( 0, 0 );
|
|
|
|
|
|
|
|
StereoOut32::StereoOut32( const StereoOut16& src ) :
|
|
|
|
Left( src.Left ),
|
|
|
|
Right( src.Right )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
StereoOut32::StereoOut32( const StereoOutFloat& src ) :
|
|
|
|
Left( (s32)(src.Left * 2147483647.0f) ),
|
|
|
|
Right( (s32)(src.Right * 2147483647.0f) )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
StereoOut16 StereoOut32::DownSample() const
|
|
|
|
{
|
|
|
|
return StereoOut16(
|
|
|
|
Left >> SndOutVolumeShift,
|
|
|
|
Right >> SndOutVolumeShift
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
StereoOut32 StereoOut16::UpSample() const
|
|
|
|
{
|
|
|
|
return StereoOut32(
|
|
|
|
Left << SndOutVolumeShift,
|
|
|
|
Right << SndOutVolumeShift
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2009-02-15 05:15:39 +00:00
|
|
|
|
|
|
|
class NullOutModule: public SndOutModule
|
|
|
|
{
|
|
|
|
public:
|
2009-02-18 13:36:20 +00:00
|
|
|
s32 Init() { return 0; }
|
2009-02-15 05:15:39 +00:00
|
|
|
void Close() { }
|
|
|
|
s32 Test() const { return 0; }
|
2009-02-20 21:48:59 +00:00
|
|
|
#ifdef _MSC_VER
|
2009-02-15 05:15:39 +00:00
|
|
|
void Configure(HWND parent) { }
|
2009-02-20 21:48:59 +00:00
|
|
|
#else
|
|
|
|
void Configure(uptr parent) { }
|
|
|
|
#endif
|
2009-02-15 05:15:39 +00:00
|
|
|
bool Is51Out() const { return false; }
|
|
|
|
int GetEmptySampleCount() const { return 0; }
|
|
|
|
|
|
|
|
const wchar_t* GetIdent() const
|
|
|
|
{
|
2009-02-21 04:20:34 +00:00
|
|
|
return L"nullout";
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const wchar_t* GetLongName() const
|
|
|
|
{
|
2009-02-21 04:20:34 +00:00
|
|
|
return L"No Sound (Emulate SPU2 only)";
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2009-02-27 01:46:01 +00:00
|
|
|
void ReadSettings()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void WriteSettings() const
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2009-02-15 05:15:39 +00:00
|
|
|
} NullOut;
|
|
|
|
|
|
|
|
SndOutModule* mods[]=
|
|
|
|
{
|
|
|
|
&NullOut,
|
2009-02-28 08:00:56 +00:00
|
|
|
#ifdef _MSC_VER
|
2009-02-15 05:15:39 +00:00
|
|
|
XAudio2Out,
|
|
|
|
DSoundOut,
|
|
|
|
WaveOut,
|
2009-02-28 08:00:56 +00:00
|
|
|
#endif
|
2009-02-15 05:15:39 +00:00
|
|
|
NULL // signals the end of our list
|
|
|
|
};
|
|
|
|
|
|
|
|
int FindOutputModuleById( const wchar_t* omodid )
|
|
|
|
{
|
|
|
|
int modcnt = 0;
|
|
|
|
while( mods[modcnt] != NULL )
|
|
|
|
{
|
|
|
|
if( wcscmp( mods[modcnt]->GetIdent(), omodid ) == 0 )
|
|
|
|
break;
|
|
|
|
++modcnt;
|
|
|
|
}
|
|
|
|
return modcnt;
|
|
|
|
}
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
StereoOut32 *SndBuffer::m_buffer;
|
|
|
|
s32 SndBuffer::m_size;
|
|
|
|
s32 SndBuffer::m_rpos;
|
|
|
|
s32 SndBuffer::m_wpos;
|
|
|
|
s32 SndBuffer::m_data;
|
|
|
|
|
|
|
|
bool SndBuffer::m_underrun_freeze;
|
|
|
|
StereoOut32* SndBuffer::sndTempBuffer = NULL;
|
|
|
|
StereoOut16* SndBuffer::sndTempBuffer16 = NULL;
|
|
|
|
int SndBuffer::sndTempProgress = 0;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
int GetAlignedBufferSize( int comp )
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
return (comp + SndOutPacketSize-1) & ~(SndOutPacketSize-1);
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
// Returns TRUE if there is data to be output, or false if no data
|
|
|
|
// is available to be copied.
|
|
|
|
bool SndBuffer::CheckUnderrunStatus( int& nSamples, int& quietSampleCount )
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
quietSampleCount = 0;
|
|
|
|
if( m_underrun_freeze )
|
|
|
|
{
|
|
|
|
int toFill = (int)(m_size * ( timeStretchDisabled ? 0.50f : 0.1f ) );
|
|
|
|
toFill = GetAlignedBufferSize( toFill );
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
// toFill is now aligned to a SndOutPacket
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
if( m_data < toFill )
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
quietSampleCount = nSamples;
|
|
|
|
return false;
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
m_underrun_freeze = false;
|
|
|
|
if( MsgOverruns() )
|
|
|
|
ConLog(" * SPU2 > Underrun compensation (%d packets buffered)\n", toFill / SndOutPacketSize );
|
|
|
|
lastPct = 0.0; // normalize timestretcher
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
2009-02-18 13:36:20 +00:00
|
|
|
else if( m_data < nSamples )
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
nSamples = m_data;
|
|
|
|
quietSampleCount = SndOutPacketSize - m_data;
|
|
|
|
m_underrun_freeze = true;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
if( !timeStretchDisabled )
|
|
|
|
timeStretchUnderrun();
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
return nSamples != 0;
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
return true;
|
|
|
|
}
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
void SndBuffer::_InitFail()
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
// If a failure occurs, just initialize the NoSound driver. This'll allow
|
|
|
|
// the game to emulate properly (hopefully), albeit without sound.
|
|
|
|
OutputModule = FindOutputModuleById( NullOut.GetIdent() );
|
|
|
|
mods[OutputModule]->Init();
|
|
|
|
}
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
void SndBuffer::_WriteSamples(StereoOut32 *bData, int nSamples)
|
|
|
|
{
|
|
|
|
int free = m_size-m_data;
|
|
|
|
m_predictData = 0;
|
|
|
|
|
|
|
|
jASSUME( m_data <= m_size );
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
if( free < nSamples )
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
// Buffer overrun!
|
|
|
|
// Dump samples from the read portion of the buffer instead of dropping
|
|
|
|
// the newly written stuff.
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
s32 comp;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
if( !timeStretchDisabled )
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
comp = timeStretchOverrun();
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
// Toss half the buffer plus whatever's being written anew:
|
|
|
|
comp = GetAlignedBufferSize( (m_size + nSamples ) / 2 );
|
|
|
|
if( comp > (m_size-SndOutPacketSize) ) comp = m_size-SndOutPacketSize;
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
m_data -= comp;
|
|
|
|
m_rpos = (m_rpos+comp) % m_size;
|
|
|
|
if( MsgOverruns() )
|
|
|
|
ConLog(" * SPU2 > Overrun Compensation (%d packets tossed)\n", comp / SndOutPacketSize );
|
|
|
|
lastPct = 0.0; // normalize the timestretcher
|
|
|
|
}
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
// copy in two phases, since there's a chance the packet
|
|
|
|
// wraps around the buffer (it'd be nice to deal in packets only, but
|
|
|
|
// the timestretcher and DSP options require flexibility).
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
const int endPos = m_wpos + nSamples;
|
|
|
|
const int secondCopyLen = endPos - m_size;
|
|
|
|
StereoOut32* wposbuffer = &m_buffer[m_wpos];
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
m_data += nSamples;
|
|
|
|
if( secondCopyLen > 0 )
|
|
|
|
{
|
|
|
|
nSamples -= secondCopyLen;
|
|
|
|
memcpy( m_buffer, &bData[nSamples], secondCopyLen * sizeof( *bData ) );
|
|
|
|
m_wpos = secondCopyLen;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
m_wpos += nSamples;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
memcpy( wposbuffer, bData, nSamples * sizeof( *bData ) );
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
void SndBuffer::Init()
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
|
|
|
if( mods[OutputModule] == NULL )
|
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
_InitFail();
|
|
|
|
return;
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// initialize sound buffer
|
|
|
|
// Buffer actually attempts to run ~50%, so allocate near double what
|
|
|
|
// the requested latency is:
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
|
|
|
|
m_rpos = 0;
|
|
|
|
m_wpos = 0;
|
|
|
|
m_data = 0;
|
|
|
|
|
2009-02-15 05:15:39 +00:00
|
|
|
try
|
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
const float latencyMS = SndOutLatencyMS * (timeStretchDisabled ? 1.5f : 2.0f );
|
|
|
|
m_size = GetAlignedBufferSize( (int)(latencyMS * SampleRate / 1000.0f ) );
|
|
|
|
m_buffer = new StereoOut32[m_size];
|
|
|
|
m_underrun_freeze = false;
|
|
|
|
|
|
|
|
sndTempBuffer = new StereoOut32[SndOutPacketSize];
|
|
|
|
sndTempBuffer16 = new StereoOut16[SndOutPacketSize];
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
catch( std::bad_alloc& )
|
|
|
|
{
|
|
|
|
// out of memory exception (most likely)
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
SysMessage( "Out of memory error occurred while initializing SPU2." );
|
|
|
|
_InitFail();
|
|
|
|
return;
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// clear buffers!
|
|
|
|
// Fixes loopy sounds on emu resets.
|
2009-02-18 13:36:20 +00:00
|
|
|
memset( sndTempBuffer, 0, sizeof(StereoOut32) * SndOutPacketSize );
|
|
|
|
memset( sndTempBuffer16, 0, sizeof(StereoOut16) * SndOutPacketSize );
|
2009-02-15 05:15:39 +00:00
|
|
|
|
|
|
|
sndTempProgress = 0;
|
|
|
|
|
|
|
|
soundtouchInit(); // initializes the timestretching
|
|
|
|
|
|
|
|
// some crap
|
|
|
|
spdif_set51(mods[OutputModule]->Is51Out());
|
|
|
|
|
|
|
|
// initialize module
|
2009-02-18 13:36:20 +00:00
|
|
|
if( mods[OutputModule]->Init() == -1 ) _InitFail();
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
void SndBuffer::Cleanup()
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
|
|
|
mods[OutputModule]->Close();
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
SAFE_DELETE_ARRAY( m_buffer );
|
2009-02-15 05:15:39 +00:00
|
|
|
SAFE_DELETE_ARRAY( sndTempBuffer );
|
|
|
|
SAFE_DELETE_ARRAY( sndTempBuffer16 );
|
|
|
|
}
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
int SndBuffer::m_dsp_progress = 0;
|
|
|
|
int SndBuffer::m_dsp_writepos = 0;
|
|
|
|
|
|
|
|
int SndBuffer::m_timestretch_progress = 0;
|
|
|
|
|
|
|
|
void SndBuffer::Write( const StereoOut32& Sample )
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
|
|
|
// Log final output to wavefile.
|
2009-02-18 13:36:20 +00:00
|
|
|
WaveDump::WriteCore( 1, CoreSrc_External, Sample.DownSample() );
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
RecordWrite( Sample.DownSample() );
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
if(mods[OutputModule] == &NullOut) // null output doesn't need buffering or stretching! :p
|
|
|
|
return;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
sndTempBuffer[sndTempProgress++] = Sample;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
// If we haven't accumulated a full packet yet, do nothing more:
|
|
|
|
if(sndTempProgress < SndOutPacketSize) return;
|
|
|
|
sndTempProgress = 0;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
if( dspPluginEnabled )
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
// Convert in, send to winamp DSP, and convert out.
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
for( int i=0; i<SndOutPacketSize; ++i, ++m_dsp_writepos ) { sndTempBuffer16[m_dsp_writepos] = sndTempBuffer[i].DownSample(); }
|
|
|
|
m_dsp_progress += DspProcess( (s16*)sndTempBuffer16, SndOutPacketSize );
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
// Some ugly code to ensure full packet handling:
|
|
|
|
int ei = 0;
|
|
|
|
while( m_dsp_progress >= SndOutPacketSize )
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
for( int i=0; i<SndOutPacketSize; ++i, ++ei ) { sndTempBuffer[i] = sndTempBuffer16[ei].UpSample(); }
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
if( !timeStretchDisabled )
|
|
|
|
timeStretchWrite();
|
|
|
|
else
|
|
|
|
_WriteSamples(sndTempBuffer, sndTempProgress);
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
m_dsp_progress -= SndOutPacketSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
// copy any leftovers to the front of the dsp buffer.
|
|
|
|
if( m_dsp_progress > 0 )
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
memcpy( &sndTempBuffer16[ei], sndTempBuffer16,
|
|
|
|
sizeof(sndTempBuffer16[0]) * m_dsp_progress
|
|
|
|
);
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2009-02-18 13:36:20 +00:00
|
|
|
if( !timeStretchDisabled )
|
|
|
|
timeStretchWrite();
|
|
|
|
else
|
|
|
|
_WriteSamples(sndTempBuffer, SndOutPacketSize);
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
s32 SndBuffer::Test()
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
|
|
|
if( mods[OutputModule] == NULL )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return mods[OutputModule]->Test();
|
|
|
|
}
|
|
|
|
|
2009-02-20 21:48:59 +00:00
|
|
|
#ifdef _MSC_VER
|
2009-02-18 13:36:20 +00:00
|
|
|
void SndBuffer::Configure(HWND parent, u32 module )
|
2009-02-20 21:48:59 +00:00
|
|
|
#else
|
|
|
|
void SndBuffer::Configure(uptr parent, u32 module )
|
|
|
|
#endif
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
|
|
|
if( mods[module] == NULL )
|
|
|
|
return;
|
|
|
|
|
|
|
|
mods[module]->Configure(parent);
|
|
|
|
}
|
2009-02-18 13:36:20 +00:00
|
|
|
|