/* 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] * * SPU2-X 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 Found- * ation, either version 3 of the License, or (at your option) any later version. * * SPU2-X 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 SPU2-X. If not, see . */ #include "Global.h" 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 ); } class NullOutModule: public SndOutModule { public: s32 Init() { return 0; } void Close() { } s32 Test() const { return 0; } void Configure(uptr parent) { } int GetEmptySampleCount() { return 0; } const wchar_t* GetIdent() const { return L"nullout"; } const wchar_t* GetLongName() const { return L"No Sound (Emulate SPU2 only)"; } void ReadSettings() { } void SetApiSettings(wxString api) { } void WriteSettings() const { } } NullOut; SndOutModule* mods[]= { &NullOut, #ifdef _MSC_VER XAudio2_27_Out, DSoundOut, WaveOut, #endif PortaudioOut, #if defined(SPU2X_SDL) || defined(SPU2X_SDL2) SDLOut, #endif #if defined(__linux__) /* && defined(__ALSA__)*/ AlsaOut, #endif 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; } StereoOut32 *SndBuffer::m_buffer; s32 SndBuffer::m_size; __aligned(4) volatile s32 SndBuffer::m_rpos; __aligned(4) volatile s32 SndBuffer::m_wpos; bool SndBuffer::m_underrun_freeze; StereoOut32* SndBuffer::sndTempBuffer = NULL; StereoOut16* SndBuffer::sndTempBuffer16 = NULL; int SndBuffer::sndTempProgress = 0; int GetAlignedBufferSize( int comp ) { return (comp + SndOutPacketSize-1) & ~(SndOutPacketSize-1); } // 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 ) { quietSampleCount = 0; int data = _GetApproximateDataInBuffer(); if( m_underrun_freeze ) { int toFill = m_size / ( (SynchMode == 2) ? 32 : 400); // TimeStretch and Async off? toFill = GetAlignedBufferSize( toFill ); // toFill is now aligned to a SndOutPacket if( data < toFill ) { quietSampleCount = nSamples; return false; } m_underrun_freeze = false; if( MsgOverruns() ) ConLog(" * SPU2 > Underrun compensation (%d packets buffered)\n", toFill / SndOutPacketSize ); lastPct = 0.0; // normalize timestretcher } else if( data < nSamples ) { nSamples = data; quietSampleCount = SndOutPacketSize - data; m_underrun_freeze = true; if( SynchMode == 0 ) // TimeStrech on timeStretchUnderrun(); return nSamples != 0; } return true; } void SndBuffer::_InitFail() { // 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(); } int SndBuffer::_GetApproximateDataInBuffer() { // WARNING: not necessarily 100% up to date by the time it's used, but it will have to do. return (m_wpos + m_size - m_rpos) % m_size; } void SndBuffer::_WriteSamples_Internal(StereoOut32 *bData, int nSamples) { // WARNING: This assumes the write will NOT wrap around, // and also assumes there's enough free space in the buffer. memcpy(m_buffer + m_wpos, bData, nSamples * sizeof(StereoOut32)); m_wpos = (m_wpos + nSamples) % m_size; } void SndBuffer::_DropSamples_Internal(int nSamples) { m_rpos = (m_rpos + nSamples) % m_size; } void SndBuffer::_ReadSamples_Internal(StereoOut32 *bData, int nSamples) { // WARNING: This assumes the read will NOT wrap around, // and also assumes there's enough data in the buffer. memcpy(bData, m_buffer + m_rpos, nSamples * sizeof(StereoOut32)); _DropSamples_Internal(nSamples); } void SndBuffer::_WriteSamples_Safe(StereoOut32 *bData, int nSamples) { // WARNING: This code assumes there's only ONE writing process. if( (m_size - m_wpos) < nSamples) { int b1 = m_size - m_wpos; int b2 = nSamples - b1; _WriteSamples_Internal(bData, b1); _WriteSamples_Internal(bData+b1, b2); } else { _WriteSamples_Internal(bData, nSamples); } } void SndBuffer::_ReadSamples_Safe(StereoOut32* bData, int nSamples) { // WARNING: This code assumes there's only ONE reading process. if( (m_size - m_rpos) < nSamples) { int b1 = m_size - m_rpos; int b2 = nSamples - b1; _ReadSamples_Internal(bData, b1); _ReadSamples_Internal(bData+b1, b2); } else { _ReadSamples_Internal(bData, nSamples); } } // Note: When using with 32 bit output buffers, the user of this function is responsible // for shifting the values to where they need to be manually. The fixed point depth of // the sample output is determined by the SndOutVolumeShift, which is the number of bits // to shift right to get a 16 bit result. template void SndBuffer::ReadSamples(T* bData) { int nSamples = SndOutPacketSize; // 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. int quietSamples; if( CheckUnderrunStatus( nSamples, quietSamples ) ) { jASSUME( nSamples <= SndOutPacketSize ); // WARNING: This code assumes there's only ONE reading process. int b1 = m_size - m_rpos; if(b1 > nSamples) b1 = nSamples; if (AdvancedVolumeControl) { // First part for (int i = 0; i < b1; i++) bData[i].AdjustFrom(m_buffer[i + m_rpos]); // Second part int b2 = nSamples - b1; for (int i = 0; i < b2; i++) bData[i + b1].AdjustFrom(m_buffer[i]); } else { // First part for (int i = 0; i < b1; i++) bData[i].ResampleFrom(m_buffer[i + m_rpos]); // Second part int b2 = nSamples - b1; for (int i = 0; i < b2; i++) bData[i + b1].ResampleFrom(m_buffer[i]); } _DropSamples_Internal(nSamples); } // If quietSamples != 0 it means we have an underrun... // Let's just dull out some silence, because that's usually the least // painful way of dealing with underruns: memset( bData, 0, quietSamples * sizeof(T) ); } template void SndBuffer::ReadSamples(StereoOut16*); template void SndBuffer::ReadSamples(StereoOut32*); //template void SndBuffer::ReadSamples(StereoOutFloat*); template void SndBuffer::ReadSamples(Stereo21Out16*); template void SndBuffer::ReadSamples(Stereo40Out16*); template void SndBuffer::ReadSamples(Stereo41Out16*); template void SndBuffer::ReadSamples(Stereo51Out16*); template void SndBuffer::ReadSamples(Stereo51Out16Dpl*); template void SndBuffer::ReadSamples(Stereo51Out16DplII*); template void SndBuffer::ReadSamples(Stereo71Out16*); template void SndBuffer::ReadSamples(Stereo20Out32*); template void SndBuffer::ReadSamples(Stereo21Out32*); template void SndBuffer::ReadSamples(Stereo40Out32*); template void SndBuffer::ReadSamples(Stereo41Out32*); template void SndBuffer::ReadSamples(Stereo51Out32*); template void SndBuffer::ReadSamples(Stereo51Out32Dpl*); template void SndBuffer::ReadSamples(Stereo51Out32DplII*); template void SndBuffer::ReadSamples(Stereo71Out32*); void SndBuffer::_WriteSamples(StereoOut32 *bData, int nSamples) { m_predictData = 0; // 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. int free = m_size - _GetApproximateDataInBuffer(); // -1, but the <= handles that if( free <= nSamples ) { // Disabled since the lock-free queue can't handle changing the read end from the write thread #if 0 // Buffer overrun! // Dump samples from the read portion of the buffer instead of dropping // the newly written stuff. s32 comp; if( SynchMode == 0 ) // TimeStrech on { comp = timeStretchOverrun(); } else { // Toss half the buffer plus whatever's being written anew: comp = GetAlignedBufferSize( (m_size + nSamples ) / 16 ); if( comp > (m_size-SndOutPacketSize) ) comp = m_size-SndOutPacketSize; } _DropSamples_Internal(comp); if( MsgOverruns() ) ConLog(" * SPU2 > Overrun Compensation (%d packets tossed)\n", comp / SndOutPacketSize ); lastPct = 0.0; // normalize the timestretcher #else if( MsgOverruns() ) ConLog(" * SPU2 > Overrun! 1 packet tossed)\n"); lastPct = 0.0; // normalize the timestretcher return; #endif } _WriteSamples_Safe(bData, nSamples); } void SndBuffer::Init() { if( mods[OutputModule] == NULL ) { _InitFail(); return; } // initialize sound buffer // Buffer actually attempts to run ~50%, so allocate near double what // the requested latency is: m_rpos = 0; m_wpos = 0; try { const float latencyMS = SndOutLatencyMS * 16; 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 * 2]; // in case of leftovers. } catch( std::bad_alloc& ) { // out of memory exception (most likely) SysMessage( "Out of memory error occurred while initializing SPU2." ); _InitFail(); return; } // clear buffers! // Fixes loopy sounds on emu resets. memset( sndTempBuffer, 0, sizeof(StereoOut32) * SndOutPacketSize ); memset( sndTempBuffer16, 0, sizeof(StereoOut16) * SndOutPacketSize ); sndTempProgress = 0; soundtouchInit(); // initializes the timestretching // initialize module if( mods[OutputModule]->Init() == -1 ) _InitFail(); } void SndBuffer::Cleanup() { mods[OutputModule]->Close(); soundtouchCleanup(); safe_delete_array( m_buffer ); safe_delete_array( sndTempBuffer ); safe_delete_array( sndTempBuffer16 ); } int SndBuffer::m_dsp_progress = 0; int SndBuffer::m_timestretch_progress = 0; int SndBuffer::ssFreeze = 0; void SndBuffer::ClearContents() { SndBuffer::soundtouchClearContents(); SndBuffer::ssFreeze = 256; //Delays sound output for about 1 second. } void SndBuffer::Write( const StereoOut32& Sample ) { // Log final output to wavefile. WaveDump::WriteCore( 1, CoreSrc_External, Sample.DownSample() ); if( WavRecordEnabled ) RecordWrite( Sample.DownSample() ); if(mods[OutputModule] == &NullOut) // null output doesn't need buffering or stretching! :p return; sndTempBuffer[sndTempProgress++] = Sample; // If we haven't accumulated a full packet yet, do nothing more: if(sndTempProgress < SndOutPacketSize) return; sndTempProgress = 0; //Don't play anything directly after loading a savestate, avoids static killing your speakers. if ( ssFreeze > 0 ) { ssFreeze--; memset( sndTempBuffer, 0, sizeof(StereoOut32) * SndOutPacketSize ); // Play silence } #ifndef __POSIX__ if( dspPluginEnabled ) { // Convert in, send to winamp DSP, and convert out. int ei= m_dsp_progress; for( int i=0; i= SndOutPacketSize ) { for( int i=0; i 0 ) { memcpy( sndTempBuffer16, &sndTempBuffer16[ei], sizeof(sndTempBuffer16[0]) * m_dsp_progress ); } } #endif else { if( SynchMode == 0 ) // TimeStrech on timeStretchWrite(); else _WriteSamples(sndTempBuffer, SndOutPacketSize); } } s32 SndBuffer::Test() { if( mods[OutputModule] == NULL ) return -1; return mods[OutputModule]->Test(); }