SPU-X: Major code cleanups across the board, and optimizations to the reverb effects generator (possibly buggy yet)

git-svn-id: http://pcsx2.googlecode.com/svn/trunk@525 96395faa-99c1-11dd-bbfe-3dabce05a288
This commit is contained in:
Jake.Stine 2009-02-18 13:36:20 +00:00
parent bee40a2948
commit 0ca2f4d2be
24 changed files with 1743 additions and 1257 deletions

View File

@ -39,7 +39,7 @@
void * memalign (size_t align, size_t size); void * memalign (size_t align, size_t size);
#else #else
/* assume malloc alignment is sufficient */ /* assume malloc alignment is sufficient */
#define memalign(align,size) malloc (size) #define memalign(align,m_size) malloc (m_size)
#endif #endif
typedef struct { typedef struct {

View File

@ -216,7 +216,7 @@ bool V_ADSR::Calculate()
#define VOLFLAG_EXPONENTIAL (1ul<<2) #define VOLFLAG_EXPONENTIAL (1ul<<2)
#define VOLFLAG_SLIDE_ENABLE (1ul<<3) #define VOLFLAG_SLIDE_ENABLE (1ul<<3)
void V_Volume::Update() void V_VolumeSlide::Update()
{ {
if( !(Mode & VOLFLAG_SLIDE_ENABLE) ) return; if( !(Mode & VOLFLAG_SLIDE_ENABLE) ) return;

View File

@ -27,6 +27,8 @@
using std::string; using std::string;
using std::wstring; using std::wstring;
#include "PS2Edefs.h"
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Override Win32 min/max macros with the STL's type safe and macro // Override Win32 min/max macros with the STL's type safe and macro
// free varieties (much safer!) // free varieties (much safer!)
@ -84,4 +86,40 @@ static const bool IsDebugBuild = false;
#endif #endif
struct StereoOut16;
struct StereoOutFloat;
struct StereoOut32
{
static StereoOut32 Empty;
s32 Left;
s32 Right;
StereoOut32() :
Left( 0 ),
Right( 0 )
{
}
StereoOut32( s32 left, s32 right ) :
Left( left ),
Right( right )
{
}
StereoOut32( const StereoOut16& src );
explicit StereoOut32( const StereoOutFloat& src );
StereoOut16 DownSample() const;
StereoOut32 operator+( const StereoOut32& right )
{
return StereoOut32(
Left + right.Left,
Right + right.Right
);
}
};
#endif #endif

View File

@ -71,6 +71,27 @@ void ConLog(const char *fmt, ...) {
#endif #endif
} }
void V_VolumeSlide::DebugDump( FILE* dump, const char* title, const char* nameLR )
{
fprintf( dump, "%s Volume for %s Channel:\t%x\n"
" - Value: %x\n"
" - Mode: %x\n"
" - Increment: %x\n",
title, nameLR, Reg_VOL, Value, Mode, Increment);
}
void V_VolumeSlideLR::DebugDump( FILE* dump, const char* title )
{
Left.DebugDump( dump, title, "Left" );
Right.DebugDump( dump, title, "Right" );
}
void V_VolumeLR::DebugDump( FILE* dump, const char* title )
{
fprintf( dump, "Volume for %s (%s Channel):\t%x\n", title, "Left", Left );
fprintf( dump, "Volume for %s (%s Channel):\t%x\n", title, "Right", Right );
}
void DoFullDump() void DoFullDump()
{ {
#ifdef SPU2_LOG #ifdef SPU2_LOG
@ -98,32 +119,18 @@ void DoFullDump()
if(!CoresDump()) return; if(!CoresDump()) return;
dump = _wfopen( CoresDumpFileName, _T("wt") ); dump = _wfopen( CoresDumpFileName, _T("wt") );
if (dump) { if (dump)
{
for(c=0;c<2;c++) for(c=0;c<2;c++)
{ {
fprintf(dump,"#### CORE %d DUMP.\n",c); fprintf(dump,"#### CORE %d DUMP.\n",c);
fprintf(dump,"Master Volume for Left Channel: %x\n"
" - Value: %x\n" Cores[c].MasterVol.DebugDump( dump, "Master" );
" - Mode: %x\n"
" - Increment: %x\n", Cores[c].ExtVol.DebugDump( dump, "External Data Input" );
Cores[c].MasterL.Reg_VOL, Cores[c].InpVol.DebugDump( dump, "Voice Data Input [dry]" );
Cores[c].MasterL.Value, Cores[c].FxVol.DebugDump( dump, "Effects/Reverb [wet]" );
Cores[c].MasterL.Mode,
Cores[c].MasterL.Increment);
fprintf(dump,"Master Volume for Right Channel: %x\n"
" - Value: %x\n"
" - Mode: %x\n"
" - Increment: %x\n",
Cores[c].MasterR.Reg_VOL,
Cores[c].MasterR.Value,
Cores[c].MasterR.Mode,
Cores[c].MasterR.Increment);
fprintf(dump,"Volume for External Data Input (Left Channel): %x\n",Cores[c].ExtL);
fprintf(dump,"Volume for External Data Input (Right Channel): %x\n",Cores[c].ExtR);
fprintf(dump,"Volume for Sound Data Input (Left Channel): %x\n",Cores[c].InpL);
fprintf(dump,"Volume for Sound Data Input (Right Channel): %x\n",Cores[c].InpR);
fprintf(dump,"Volume for Output from Effects (Left Channel): %x\n",Cores[c].FxL);
fprintf(dump,"Volume for Output from Effects (Right Channel): %x\n",Cores[c].FxR);
fprintf(dump,"Interrupt Address: %x\n",Cores[c].IRQA); fprintf(dump,"Interrupt Address: %x\n",Cores[c].IRQA);
fprintf(dump,"DMA Transfer Start Address: %x\n",Cores[c].TSA); fprintf(dump,"DMA Transfer Start Address: %x\n",Cores[c].TSA);
fprintf(dump,"External Input to Direct Output (Left): %s\n",Cores[c].ExtDryL?"Yes":"No"); fprintf(dump,"External Input to Direct Output (Left): %s\n",Cores[c].ExtDryL?"Yes":"No");
@ -156,24 +163,11 @@ void DoFullDump()
fprintf(dump," - ENDX: %x\n",Cores[c].Regs.VMIXER); fprintf(dump," - ENDX: %x\n",Cores[c].Regs.VMIXER);
fprintf(dump," - STATX: %x\n",Cores[c].Regs.VMIXEL); fprintf(dump," - STATX: %x\n",Cores[c].Regs.VMIXEL);
fprintf(dump," - ATTR: %x\n",Cores[c].Regs.VMIXER); fprintf(dump," - ATTR: %x\n",Cores[c].Regs.VMIXER);
for(v=0;v<24;v++) { for(v=0;v<24;v++)
{
fprintf(dump,"Voice %d:\n",v); fprintf(dump,"Voice %d:\n",v);
fprintf(dump," - Volume for Left Channel: %x\n" Cores[c].Voices[v].Volume.DebugDump( dump, "" );
" - Value: %x\n"
" - Mode: %x\n"
" - Increment: %x\n",
Cores[c].Voices[v].VolumeL.Reg_VOL,
Cores[c].Voices[v].VolumeL.Value,
Cores[c].Voices[v].VolumeL.Mode,
Cores[c].Voices[v].VolumeL.Increment);
fprintf(dump," - Volume for Right Channel: %x\n"
" - Value: %x\n"
" - Mode: %x\n"
" - Increment: %x\n",
Cores[c].Voices[v].VolumeR.Reg_VOL,
Cores[c].Voices[v].VolumeR.Value,
Cores[c].Voices[v].VolumeR.Mode,
Cores[c].Voices[v].VolumeR.Increment);
fprintf(dump," - ADSR Envelope: %x & %x\n" fprintf(dump," - ADSR Envelope: %x & %x\n"
" - Ar: %x\n" " - Ar: %x\n"
" - Am: %x\n" " - Am: %x\n"
@ -197,6 +191,7 @@ void DoFullDump()
Cores[c].Voices[v].ADSR.ReleaseMode, Cores[c].Voices[v].ADSR.ReleaseMode,
Cores[c].Voices[v].ADSR.Phase, Cores[c].Voices[v].ADSR.Phase,
Cores[c].Voices[v].ADSR.Value); Cores[c].Voices[v].ADSR.Value);
fprintf(dump," - Pitch: %x\n",Cores[c].Voices[v].Pitch); fprintf(dump," - Pitch: %x\n",Cores[c].Voices[v].Pitch);
fprintf(dump," - Modulated: %s\n",Cores[c].Voices[v].Modulated?"Yes":"No"); fprintf(dump," - Modulated: %s\n",Cores[c].Voices[v].Modulated?"Yes":"No");
fprintf(dump," - Source: %s\n",Cores[c].Voices[v].Noise?"Noise":"Wave"); fprintf(dump," - Source: %s\n",Cores[c].Voices[v].Noise?"Noise":"Wave");
@ -204,12 +199,12 @@ void DoFullDump()
fprintf(dump," - Direct Output for Right Channel: %s\n",Cores[c].Voices[v].DryR?"Yes":"No"); fprintf(dump," - Direct Output for Right Channel: %s\n",Cores[c].Voices[v].DryR?"Yes":"No");
fprintf(dump," - Effects Output for Left Channel: %s\n",Cores[c].Voices[v].WetL?"Yes":"No"); fprintf(dump," - Effects Output for Left Channel: %s\n",Cores[c].Voices[v].WetL?"Yes":"No");
fprintf(dump," - Effects Output for Right Channel: %s\n",Cores[c].Voices[v].WetR?"Yes":"No"); fprintf(dump," - Effects Output for Right Channel: %s\n",Cores[c].Voices[v].WetR?"Yes":"No");
fprintf(dump," - Loop Start Adress: %x\n",Cores[c].Voices[v].LoopStartA); fprintf(dump," - Loop Start Address: %x\n",Cores[c].Voices[v].LoopStartA);
fprintf(dump," - Sound Start Adress: %x\n",Cores[c].Voices[v].StartA); fprintf(dump," - Sound Start Address: %x\n",Cores[c].Voices[v].StartA);
fprintf(dump," - Next Data Adress: %x\n",Cores[c].Voices[v].NextA); fprintf(dump," - Next Data Address: %x\n",Cores[c].Voices[v].NextA);
fprintf(dump," - Play Start Cycle: %d\n",Cores[c].Voices[v].PlayCycle); fprintf(dump," - Play Start Cycle: %d\n",Cores[c].Voices[v].PlayCycle);
fprintf(dump," - Play Status: %s\n",(Cores[c].Voices[v].ADSR.Phase>0)?"Playing":"Not Playing"); fprintf(dump," - Play Status: %s\n",(Cores[c].Voices[v].ADSR.Phase>0)?"Playing":"Not Playing");
fprintf(dump," - Block Sample: %d\n",Cores[c].Voices[v].SCurrent); fprintf(dump," - Block Sample: %d\n",Cores[c].Voices[v].SCurrent);
} }
fprintf(dump,"#### END OF DUMP.\n\n"); fprintf(dump,"#### END OF DUMP.\n\n");
} }

View File

@ -52,9 +52,10 @@ namespace WaveDump
, CoreSrc_Count , CoreSrc_Count
}; };
void Open(); extern void Open();
void Close(); extern void Close();
void WriteCore( uint coreidx, CoreSourceType src, s16 left, s16 right ); extern void WriteCore( uint coreidx, CoreSourceType src, s16 left, s16 right );
extern void WriteCore( uint coreidx, CoreSourceType src, const StereoOut16& sample );
} }
using WaveDump::CoreSrc_Input; using WaveDump::CoreSrc_Input;

View File

@ -58,7 +58,6 @@ int state=0;
FILE *fSpdifDump; FILE *fSpdifDump;
extern u32 core; extern u32 core;
void __fastcall ReadInput(V_Core& thiscore, s32& PDataL,s32& PDataR);
union spdif_frame { // total size: 32bits union spdif_frame { // total size: 32bits
struct { struct {
@ -132,22 +131,23 @@ s32 stoi(sample_t n) //input: [-1..1]
void spdif_update() void spdif_update()
{ {
s32 Data,Zero; StereoOut32 Data;
core=0; core=0;
V_Core& thiscore( Cores[core] ); V_Core& thiscore( Cores[core] );
for(int i=0;i<data_rate;i++) for(int i=0;i<data_rate;i++)
{ {
ReadInput(thiscore, Data,Zero); // Right side data should be zero / ignored
ReadInput( thiscore, Data );
if(fSpdifDump) if(fSpdifDump)
{ {
fwrite(&Data,4,1,fSpdifDump); fwrite(&Data.Left,4,1,fSpdifDump);
fwrite(&Zero,4,1,fSpdifDump); fwrite(&Data.Right,4,1,fSpdifDump); // zero side.
} }
if(ac3dec) if(ac3dec)
spdif_Write(Data); spdif_Write(Data.Left);
} }
if(!ac3dec) return; if(!ac3dec) return;

View File

@ -120,7 +120,7 @@ EXPORT_C_(void) SPU2about()
EXPORT_C_(s32) SPU2test() EXPORT_C_(s32) SPU2test()
{ {
return SndTest(); return SndBuffer::Test();
} }
EXPORT_C_(s32) SPU2init() EXPORT_C_(s32) SPU2init()
@ -228,22 +228,20 @@ EXPORT_C_(s32) SPU2open(void *pDsp)
debugDialogOpen=1; debugDialogOpen=1;
}*/ }*/
spu2open=true; spu2open = true;
if (!SndInit()) try
{ {
SndBuffer::Init();
spdif_init(); spdif_init();
DspLoadLibrary(dspPlugin,dspPluginModule); DspLoadLibrary(dspPlugin,dspPluginModule);
WaveDump::Open(); WaveDump::Open();
return 0;
} }
else catch( ... )
{ {
SPU2close(); SPU2close();
return -1; return -1;
}; }
return 0;
} }
EXPORT_C_(void) SPU2close() EXPORT_C_(void) SPU2close()
@ -253,7 +251,7 @@ EXPORT_C_(void) SPU2close()
DspCloseLibrary(); DspCloseLibrary();
spdif_shutdown(); spdif_shutdown();
SndClose(); SndBuffer::Cleanup();
spu2open = false; spu2open = false;
} }

View File

@ -61,11 +61,17 @@ __forceinline s32 MulShr32( s32 srcval, s32 mulval )
// It won't fly on big endian machines though... :) // It won't fly on big endian machines though... :)
} }
__forceinline s32 clamp_mix(s32 x, u8 bitshift) __forceinline s32 clamp_mix( s32 x, u8 bitshift )
{ {
return GetClamped( x, -0x8000<<bitshift, 0x7fff<<bitshift ); return GetClamped( x, -0x8000<<bitshift, 0x7fff<<bitshift );
} }
__forceinline void clamp_mix( StereoOut32& sample, u8 bitshift )
{
Clampify( sample.Left, -0x8000<<bitshift, 0x7fff<<bitshift );
Clampify( sample.Right, -0x8000<<bitshift, 0x7fff<<bitshift );
}
static void __forceinline XA_decode_block(s16* buffer, const s16* block, s32& prev1, s32& prev2) static void __forceinline XA_decode_block(s16* buffer, const s16* block, s32& prev1, s32& prev2)
{ {
const s32 header = *block; const s32 header = *block;
@ -171,7 +177,7 @@ int g_counter_cache_ignores = 0;
#define XAFLAG_LOOP (1ul<<1) #define XAFLAG_LOOP (1ul<<1)
#define XAFLAG_LOOP_START (1ul<<2) #define XAFLAG_LOOP_START (1ul<<2)
static void __forceinline __fastcall GetNextDataBuffered( V_Core& thiscore, V_Voice& vc, s32& Data) static s32 __forceinline __fastcall GetNextDataBuffered( V_Core& thiscore, V_Voice& vc )
{ {
if (vc.SCurrent<28) if (vc.SCurrent<28)
{ {
@ -259,19 +265,19 @@ static void __forceinline __fastcall GetNextDataBuffered( V_Core& thiscore, V_Vo
IncrementNextA( thiscore, vc ); IncrementNextA( thiscore, vc );
_skipIncrement: _skipIncrement:
Data = vc.SBuffer[vc.SCurrent++]; return vc.SBuffer[vc.SCurrent++];
} }
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
// // // //
static void __forceinline GetNoiseValues(s32& VD) static s32 __forceinline GetNoiseValues()
{ {
static s32 Seed = 0x41595321; static s32 Seed = 0x41595321;
s32 retval = 0x8000;
if(Seed&0x100) VD = (s32)((Seed&0xff)<<8); if( Seed&0x100 ) retval = (Seed&0xff) << 8;
else if(!(Seed&0xffff)) VD = (s32)0x8000; else if( Seed&0xffff ) retval = 0x7fff;
else VD = (s32)0x7fff;
__asm { __asm {
MOV eax,Seed MOV eax,Seed
@ -284,6 +290,7 @@ static void __forceinline GetNoiseValues(s32& VD)
ROR eax,3 ROR eax,3
MOV Seed,eax MOV Seed,eax
} }
return retval;
} }
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
@ -299,6 +306,22 @@ static __forceinline s32 ApplyVolume(s32 data, s32 volume)
return MulShr32( data<<1, volume ); return MulShr32( data<<1, volume );
} }
static __forceinline StereoOut32 ApplyVolume( const StereoOut32& data, const V_VolumeLR& volume )
{
return StereoOut32(
ApplyVolume( data.Left, volume.Left ),
ApplyVolume( data.Right, volume.Right )
);
}
static __forceinline StereoOut32 ApplyVolume( const StereoOut32& data, const V_VolumeSlideLR& volume )
{
return StereoOut32(
ApplyVolume( data.Left, volume.Left.Value ),
ApplyVolume( data.Right, volume.Right.Value )
);
}
static void __forceinline UpdatePitch( V_Voice& vc ) static void __forceinline UpdatePitch( V_Voice& vc )
{ {
s32 pitch; s32 pitch;
@ -339,14 +362,12 @@ static __forceinline void CalculateADSR( V_Core& thiscore, V_Voice& vc )
} }
// Returns a 16 bit result in Value. // Returns a 16 bit result in Value.
static void __forceinline GetVoiceValues_Linear(V_Core& thiscore, V_Voice& vc, s32& Value) static s32 __forceinline GetVoiceValues_Linear( V_Core& thiscore, V_Voice& vc )
{ {
while( vc.SP > 0 ) while( vc.SP > 0 )
{ {
vc.PV2 = vc.PV1; vc.PV2 = vc.PV1;
vc.PV1 = GetNextDataBuffered( thiscore, vc );
GetNextDataBuffered( thiscore, vc, vc.PV1 );
vc.SP -= 4096; vc.SP -= 4096;
} }
@ -358,28 +379,28 @@ static void __forceinline GetVoiceValues_Linear(V_Core& thiscore, V_Voice& vc, s
if(Interpolation==0) if(Interpolation==0)
{ {
Value = ApplyVolume( vc.PV1, vc.ADSR.Value ); return ApplyVolume( vc.PV1, vc.ADSR.Value );
} }
else //if(Interpolation==1) //must be linear else //if(Interpolation==1) //must be linear
{ {
s32 t0 = vc.PV2 - vc.PV1; s32 t0 = vc.PV2 - vc.PV1;
Value = MulShr32( (vc.PV1<<1) - ((t0*vc.SP)>>11), vc.ADSR.Value ); return MulShr32( (vc.PV1<<1) - ((t0*vc.SP)>>11), vc.ADSR.Value );
} }
} }
// Returns a 16 bit result in Value. // Returns a 16 bit result in Value.
static void __forceinline GetVoiceValues_Cubic(V_Core& thiscore, V_Voice& vc, s32& Value) static s32 __forceinline GetVoiceValues_Cubic( V_Core& thiscore, V_Voice& vc )
{ {
while( vc.SP > 0 ) while( vc.SP > 0 )
{ {
vc.PV4=vc.PV3; vc.PV4 = vc.PV3;
vc.PV3=vc.PV2; vc.PV3 = vc.PV2;
vc.PV2=vc.PV1; vc.PV2 = vc.PV1;
GetNextDataBuffered( thiscore, vc, vc.PV1 ); vc.PV1 = GetNextDataBuffered( thiscore, vc );
vc.PV1<<=2; vc.PV1 <<= 2;
vc.SPc = vc.SP&4095; // just the fractional part, please! vc.SPc = vc.SP&4095; // just the fractional part, please!
vc.SP-=4096; vc.SP -= 4096;
} }
CalculateADSR( thiscore, vc ); CalculateADSR( thiscore, vc );
@ -398,19 +419,21 @@ static void __forceinline GetVoiceValues_Cubic(V_Core& thiscore, V_Voice& vc, s3
// Note! It's very important that ADSR stay as accurate as possible. By the way // Note! It's very important that ADSR stay as accurate as possible. By the way
// it is used, various sound effects can end prematurely if we truncate more than // it is used, various sound effects can end prematurely if we truncate more than
// one or two bits. // one or two bits.
Value = MulShr32( val, vc.ADSR.Value>>1 ); return MulShr32( val, vc.ADSR.Value>>1 );
} }
// Noise values need to be mixed without going through interpolation, since it // Noise values need to be mixed without going through interpolation, since it
// can wreak havoc on the noise (causing muffling or popping). Not that this noise // can wreak havoc on the noise (causing muffling or popping). Not that this noise
// generator is accurate in its own right.. but eh, ah well :) // generator is accurate in its own right.. but eh, ah well :)
static void __forceinline __fastcall GetNoiseValues(V_Core& thiscore, V_Voice& vc, s32& Data) static s32 __forceinline __fastcall GetNoiseValues( V_Core& thiscore, V_Voice& vc )
{ {
while(vc.SP>=4096) s32 retval = GetNoiseValues();
/*while(vc.SP>=4096)
{ {
GetNoiseValues( Data ); retval = GetNoiseValues();
vc.SP-=4096; vc.SP-=4096;
} }*/
// GetNoiseValues can't set the phase zero on us unexpectedly // GetNoiseValues can't set the phase zero on us unexpectedly
// like GetVoiceValues can. Better assert just in case though.. // like GetVoiceValues can. Better assert just in case though..
@ -419,14 +442,14 @@ static void __forceinline __fastcall GetNoiseValues(V_Core& thiscore, V_Voice& v
CalculateADSR( thiscore, vc ); CalculateADSR( thiscore, vc );
// Yup, ADSR applies even to noise sources... // Yup, ADSR applies even to noise sources...
Data = MulShr32( Data, vc.ADSR.Value ); return ApplyVolume( retval, vc.ADSR.Value );
} }
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
// // // //
void __fastcall ReadInput(V_Core& thiscore, s32& PDataL,s32& PDataR) void __fastcall ReadInput( V_Core& thiscore, StereoOut32& PData )
{ {
if((thiscore.AutoDMACtrl&(core+1))==(core+1)) if((thiscore.AutoDMACtrl&(core+1))==(core+1))
{ {
@ -442,17 +465,17 @@ void __fastcall ReadInput(V_Core& thiscore, s32& PDataL,s32& PDataR)
// so we just downgrade it to 16 bits for now. // so we just downgrade it to 16 bits for now.
#ifdef PCM24_S1_INTERLEAVE #ifdef PCM24_S1_INTERLEAVE
*PDataL=*(((s32*)(thiscore.ADMATempBuffer+(thiscore.InputPos<<1)))); *PData.Left=*(((s32*)(thiscore.ADMATempBuffer+(thiscore.InputPos<<1))));
*PDataR=*(((s32*)(thiscore.ADMATempBuffer+(thiscore.InputPos<<1)+2))); *PData.Right=*(((s32*)(thiscore.ADMATempBuffer+(thiscore.InputPos<<1)+2)));
#else #else
s32 *pl=(s32*)&(thiscore.ADMATempBuffer[thiscore.InputPos]); s32 *pl=(s32*)&(thiscore.ADMATempBuffer[thiscore.InputPos]);
s32 *pr=(s32*)&(thiscore.ADMATempBuffer[thiscore.InputPos+0x200]); s32 *pr=(s32*)&(thiscore.ADMATempBuffer[thiscore.InputPos+0x200]);
PDataL=*pl; PData.Left = *pl;
PDataR=*pr; PData.Right = *pr;
#endif #endif
PDataL>>=1; //give 31 bit data (SndOut downsamples the rest of the way) PData.Left >>= 2; //give 30 bit data (SndOut downsamples the rest of the way)
PDataR>>=1; PData.Right >>= 2;
thiscore.InputPos+=2; thiscore.InputPos+=2;
if((thiscore.InputPos==0x100)||(thiscore.InputPos>=0x200)) { if((thiscore.InputPos==0x100)||(thiscore.InputPos>=0x200)) {
@ -495,8 +518,8 @@ void __fastcall ReadInput(V_Core& thiscore, s32& PDataL,s32& PDataR)
s32 *pl=(s32*)&(thiscore.ADMATempBuffer[thiscore.InputPos]); s32 *pl=(s32*)&(thiscore.ADMATempBuffer[thiscore.InputPos]);
s32 *pr=(s32*)&(thiscore.ADMATempBuffer[thiscore.InputPos+0x200]); s32 *pr=(s32*)&(thiscore.ADMATempBuffer[thiscore.InputPos+0x200]);
PDataL=*pl; PData.Left = *pl;
PDataR=*pr; PData.Right = *pr;
thiscore.InputPos+=2; thiscore.InputPos+=2;
if(thiscore.InputPos>=0x200) { if(thiscore.InputPos>=0x200) {
@ -540,16 +563,16 @@ void __fastcall ReadInput(V_Core& thiscore, s32& PDataL,s32& PDataR)
else else
{ {
// Using the temporary buffer because this area gets overwritten by some other code. // Using the temporary buffer because this area gets overwritten by some other code.
//*PDataL=(s32)*(s16*)(spu2mem+0x2000+(core<<10)+thiscore.InputPos); //*PData.Left = (s32)*(s16*)(spu2mem+0x2000+(core<<10)+thiscore.InputPos);
//*PDataR=(s32)*(s16*)(spu2mem+0x2200+(core<<10)+thiscore.InputPos); //*PData.Right = (s32)*(s16*)(spu2mem+0x2200+(core<<10)+thiscore.InputPos);
tl=(s32)thiscore.ADMATempBuffer[thiscore.InputPos]; tl = (s32)thiscore.ADMATempBuffer[thiscore.InputPos];
tr=(s32)thiscore.ADMATempBuffer[thiscore.InputPos+0x200]; tr = (s32)thiscore.ADMATempBuffer[thiscore.InputPos+0x200];
} }
PDataL=tl; PData.Left = tl;
PDataR=tr; PData.Right = tr;
thiscore.InputPos++; thiscore.InputPos++;
if((thiscore.InputPos==0x100)||(thiscore.InputPos>=0x200)) { if((thiscore.InputPos==0x100)||(thiscore.InputPos>=0x200)) {
@ -585,9 +608,10 @@ void __fastcall ReadInput(V_Core& thiscore, s32& PDataL,s32& PDataR)
} }
} }
} }
else { else
PDataL=0; {
PDataR=0; PData.Left = 0;
PData.Right = 0;
} }
} }
@ -595,29 +619,21 @@ void __fastcall ReadInput(V_Core& thiscore, s32& PDataL,s32& PDataR)
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
// // // //
static void __forceinline __fastcall ReadInputPV(V_Core& thiscore, s32& ValL,s32& ValR) static __forceinline StereoOut32 ReadInputPV( V_Core& thiscore )
{ {
s32 DL=0, DR=0;
u32 pitch=AutoDMAPlayRate[core]; u32 pitch=AutoDMAPlayRate[core];
if(pitch==0) pitch=48000; if(pitch==0) pitch=48000;
thiscore.ADMAPV+=pitch; thiscore.ADMAPV += pitch;
while(thiscore.ADMAPV>=48000) while(thiscore.ADMAPV>=48000)
{ {
ReadInput(thiscore, DL,DR); ReadInput( thiscore, thiscore.ADMAP );
thiscore.ADMAPV-=48000; thiscore.ADMAPV -= 48000;
thiscore.ADMAPL=DL;
thiscore.ADMAPR=DR;
} }
ValL=thiscore.ADMAPL;
ValR=thiscore.ADMAPR;
// Apply volumes: // Apply volumes:
ValL = ApplyVolume( ValL, thiscore.InpL ); return ApplyVolume( thiscore.ADMAP, thiscore.InpVol );
ValR = ApplyVolume( ValR, thiscore.InpR );
} }
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
@ -637,108 +653,107 @@ static __forceinline void spu2M_WriteFast( u32 addr, s16 value )
} }
static __forceinline void MixVoice( V_Core& thiscore, V_Voice& vc, s32& VValL, s32& VValR ) static __forceinline StereoOut32 MixVoice( V_Core& thiscore, V_Voice& vc )
{ {
s32 Value=0;
VValL = 0;
VValR = 0;
// Most games don't use much volume slide effects. So only call the UpdateVolume // Most games don't use much volume slide effects. So only call the UpdateVolume
// methods when needed by checking the flag outside the method here... // methods when needed by checking the flag outside the method here...
vc.VolumeL.Update(); vc.Volume.Update();
vc.VolumeR.Update();
// SPU2 Note: The spu2 continues to process voices for eternity, always, so we
// have to run through all the motions of updating the voice regardless of it's
// audible status. Otherwise IRQs might not trigger and emulation might fail.
if( vc.ADSR.Phase > 0 ) if( vc.ADSR.Phase > 0 )
{ {
UpdatePitch( vc ); UpdatePitch( vc );
s32 Value;
if( vc.Noise ) if( vc.Noise )
GetNoiseValues( thiscore, vc, Value ); Value = GetNoiseValues( thiscore, vc );
else else
{ {
if( Interpolation == 2 ) if( Interpolation == 2 )
GetVoiceValues_Cubic( thiscore, vc, Value ); Value = GetVoiceValues_Cubic( thiscore, vc );
else else
GetVoiceValues_Linear( thiscore, vc, Value ); Value = GetVoiceValues_Linear( thiscore, vc );
} }
// Record the output (used for modulation effects) // Note: All values recorded into OutX (may be used for modulation later)
vc.OutX = Value; vc.OutX = Value;
if( IsDevBuild ) if( IsDevBuild )
DebugCores[core].Voices[voice].displayPeak = max(DebugCores[core].Voices[voice].displayPeak,abs(Value)); DebugCores[core].Voices[voice].displayPeak = max(DebugCores[core].Voices[voice].displayPeak,abs(vc.OutX));
// TODO : Implement this using high-def MulShr32. // Write-back of raw voice data (post ADSR applied)
// vc.VolumeL/R are 15 bits. Value should be 32 bits (but is currently 16)
VValL = ApplyVolume(Value,vc.VolumeL.Value); if (voice==1) spu2M_WriteFast( 0x400 + (core<<12) + OutPos, vc.OutX );
VValR = ApplyVolume(Value,vc.VolumeR.Value); else if (voice==3) spu2M_WriteFast( 0x600 + (core<<12) + OutPos, vc.OutX );
return ApplyVolume( StereoOut32( Value, Value ), vc.Volume );
} }
else
{
// Write-back of raw voice data (some zeros since the voice is "dead")
// Write-back of raw voice data (post ADSR applied) if (voice==1) spu2M_WriteFast( 0x400 + (core<<12) + OutPos, 0 );
else if (voice==3) spu2M_WriteFast( 0x600 + (core<<12) + OutPos, 0 );
if (voice==1) spu2M_WriteFast( 0x400 + (core<<12) + OutPos, (s16)Value );
else if (voice==3) spu2M_WriteFast( 0x600 + (core<<12) + OutPos, (s16)Value );
return StereoOut32( 0, 0 );
}
} }
static void __fastcall MixCore(s32& OutL, s32& OutR, s32 ExtL, s32 ExtR) static StereoOut32 __fastcall MixCore( const StereoOut32& Input, const StereoOut32& Ext )
{ {
s32 RVL,RVR;
s32 SDL=0,SDR=0;
s32 SWL=0,SWR=0;
V_Core& thiscore( Cores[core] ); V_Core& thiscore( Cores[core] );
thiscore.MasterVol.Update();
StereoOut32 Dry(0,0), Wet(0,0);
for( voice=0; voice<24; ++voice ) for( voice=0; voice<24; ++voice )
{ {
s32 VValL,VValR;
V_Voice& vc( thiscore.Voices[voice] ); V_Voice& vc( thiscore.Voices[voice] );
MixVoice( thiscore, vc, VValL, VValR ); StereoOut32 VVal( MixVoice( thiscore, vc ) );
// Note: Results from MixVoice are ranged at 16 bits. // Note: Results from MixVoice are ranged at 16 bits.
// Following muls are toggles only (0 or 1)
SDL += VValL & vc.DryL; Dry.Left += VVal.Left & vc.DryL;
SDR += VValR & vc.DryR; Dry.Right += VVal.Right & vc.DryR;
SWL += VValL & vc.WetL; Wet.Left += VVal.Left & vc.WetL;
SWR += VValR & vc.WetR; Wet.Right += VVal.Right & vc.WetR;
} }
// Saturate final result to standard 16 bit range. // Saturate final result to standard 16 bit range.
SDL = clamp_mix( SDL ); clamp_mix( Dry );
SDR = clamp_mix( SDR ); clamp_mix( Wet );
SWL = clamp_mix( SWL );
SWR = clamp_mix( SWR );
// Write Mixed results To Output Area // Write Mixed results To Output Area
spu2M_WriteFast( 0x1000 + (core<<12) + OutPos, (s16)SDL ); spu2M_WriteFast( 0x1000 + (core<<12) + OutPos, Dry.Left );
spu2M_WriteFast( 0x1200 + (core<<12) + OutPos, (s16)SDR ); spu2M_WriteFast( 0x1200 + (core<<12) + OutPos, Dry.Right );
spu2M_WriteFast( 0x1400 + (core<<12) + OutPos, (s16)SWL ); spu2M_WriteFast( 0x1400 + (core<<12) + OutPos, Wet.Left );
spu2M_WriteFast( 0x1600 + (core<<12) + OutPos, (s16)SWR ); spu2M_WriteFast( 0x1600 + (core<<12) + OutPos, Wet.Right );
// Write mixed results to logfile (if enabled) // Write mixed results to logfile (if enabled)
WaveDump::WriteCore( core, CoreSrc_DryVoiceMix, SDL, SDR ); WaveDump::WriteCore( core, CoreSrc_DryVoiceMix, Dry );
WaveDump::WriteCore( core, CoreSrc_WetVoiceMix, SWL, SWR ); WaveDump::WriteCore( core, CoreSrc_WetVoiceMix, Wet );
s32 TDL,TDR;
// Mix in the Input data // Mix in the Input data
TDL = OutL & thiscore.InpDryL;
TDR = OutR & thiscore.InpDryR; StereoOut32 TD(
Input.Left & thiscore.InpDryL,
Input.Right & thiscore.InpDryR
);
// Mix in the Voice data // Mix in the Voice data
TDL += SDL & thiscore.SndDryL; TD.Left += Dry.Left & thiscore.SndDryL;
TDR += SDR & thiscore.SndDryR; TD.Right += Dry.Right & thiscore.SndDryR;
// Mix in the External (nothing/core0) data // Mix in the External (nothing/core0) data
TDL += ExtL & thiscore.ExtDryL; TD.Left += Ext.Left & thiscore.ExtDryL;
TDR += ExtR & thiscore.ExtDryR; TD.Right += Ext.Right & thiscore.ExtDryR;
if( !EffectsDisabled ) if( !EffectsDisabled )
{ {
@ -747,138 +762,106 @@ static void __fastcall MixCore(s32& OutL, s32& OutR, s32 ExtL, s32 ExtR)
if( thiscore.FxEnable ) if( thiscore.FxEnable )
{ {
s32 TWL,TWR;
// Mix Input, Voice, and External data: // Mix Input, Voice, and External data:
TWL = OutL & thiscore.InpWetL; StereoOut32 TW(
TWR = OutR & thiscore.InpWetR; Input.Left & thiscore.InpWetL,
TWL += SWL & thiscore.SndWetL; Input.Right & thiscore.InpWetR
TWR += SWR & thiscore.SndWetR; );
TWL += ExtL & thiscore.ExtWetL;
TWR += ExtR & thiscore.ExtWetR;
WaveDump::WriteCore( core, CoreSrc_PreReverb, TWL, TWR ); TW.Left += Wet.Left & thiscore.SndWetL;
TW.Right += Wet.Right & thiscore.SndWetR;
TW.Left += Ext.Left & thiscore.ExtWetL;
TW.Right += Ext.Right & thiscore.ExtWetR;
DoReverb( thiscore, RVL, RVR, TWL, TWR ); WaveDump::WriteCore( core, CoreSrc_PreReverb, TW );
StereoOut32 RV( DoReverb( thiscore, TW ) );
// Volume boost after effects application. Boosting volume prior to effects // Volume boost after effects application. Boosting volume prior to effects
// causes slight overflows in some games, and the volume boost is required. // causes slight overflows in some games, and the volume boost is required.
// (like all over volumes on SPU2, reverb coefficients and stuff are signed, // (like all over volumes on SPU2, reverb coefficients and stuff are signed,
// range -50% to 50%, thus *2 is needed) // range -50% to 50%, thus *2 is needed)
RVL *= 2; RV.Left *= 2;
RVR *= 2; RV.Right *= 2;
WaveDump::WriteCore( core, CoreSrc_PostReverb, RVL, RVR ); WaveDump::WriteCore( core, CoreSrc_PostReverb, RV );
TWL = ApplyVolume(RVL,thiscore.FxL);
TWR = ApplyVolume(RVR,thiscore.FxR);
// Mix Dry+Wet // Mix Dry+Wet
OutL = TDL + TWL; return StereoOut32( TD + ApplyVolume( RV, thiscore.FxVol ) );
OutR = TDR + TWR;
} }
else else
{ {
WaveDump::WriteCore( core, CoreSrc_PreReverb, 0, 0 ); WaveDump::WriteCore( core, CoreSrc_PreReverb, 0, 0 );
WaveDump::WriteCore( core, CoreSrc_PostReverb, 0, 0 ); WaveDump::WriteCore( core, CoreSrc_PostReverb, 0, 0 );
OutL = TDL;
OutR = TDR;
} }
} }
else return TD;
{
OutL = TDL;
OutR = TDR;
}
// Apply Master Volume. The core will need this when the function returns.
thiscore.MasterL.Update();
thiscore.MasterR.Update();
} }
// used to throttle the output rate of cache stat reports // used to throttle the output rate of cache stat reports
static int p_cachestat_counter=0; static int p_cachestat_counter=0;
void Mix() __forceinline void Mix()
{ {
s32 ExtL=0, ExtR=0, OutL, OutR;
// **** CORE ZERO **** // **** CORE ZERO ****
core = 0;
core=0; // Note: Playmode 4 is SPDIF, which overrides other inputs.
if( (PlayMode&4) == 0 ) StereoOut32 Ext( (PlayMode&4) ? StereoOut32::Empty : ReadInputPV( Cores[0] ) );
{ WaveDump::WriteCore( 0, CoreSrc_Input, Ext );
// get input data from input buffers
ReadInputPV(Cores[0], ExtL, ExtR);
WaveDump::WriteCore( 0, CoreSrc_Input, ExtL, ExtR );
}
MixCore( ExtL, ExtR, 0, 0 ); Ext = MixCore( Ext, StereoOut32::Empty );
if( (PlayMode & 4) || (Cores[0].Mute!=0) ) if( (PlayMode & 4) || (Cores[0].Mute!=0) )
{ Ext = StereoOut32( 0, 0 );
ExtL=0;
ExtR=0;
}
else else
{ {
ExtL = ApplyVolume( ExtL, Cores[0].MasterL.Value ); Ext = ApplyVolume( Ext, Cores[0].MasterVol );
ExtR = ApplyVolume( ExtR, Cores[0].MasterR.Value ); clamp_mix( Ext );
} }
// Commit Core 0 output to ram before mixing Core 1: // Commit Core 0 output to ram before mixing Core 1:
ExtL = clamp_mix( ExtL ); spu2M_WriteFast( 0x800 + OutPos, Ext.Left );
ExtR = clamp_mix( ExtR ); spu2M_WriteFast( 0xA00 + OutPos, Ext.Right );
WaveDump::WriteCore( 0, CoreSrc_External, Ext );
spu2M_WriteFast( 0x800 + OutPos, ExtL );
spu2M_WriteFast( 0xA00 + OutPos, ExtR );
WaveDump::WriteCore( 0, CoreSrc_External, ExtL, ExtR );
// **** CORE ONE **** // **** CORE ONE ****
core = 1; core = 1;
if( (PlayMode&8) != 8 ) StereoOut32 Out( (PlayMode&8) ? StereoOut32::Empty : ReadInputPV( Cores[1] ) );
{ WaveDump::WriteCore( 1, CoreSrc_Input, Out );
ReadInputPV(Cores[1], OutL, OutR); // get input data from input buffers
WaveDump::WriteCore( 1, CoreSrc_Input, OutL, OutR );
}
// Apply volume to the external (Core 0) input data. ApplyVolume( Ext, Cores[1].ExtVol );
Out = MixCore( Out, Ext );
MixCore( OutL, OutR, ApplyVolume( ExtL, Cores[1].ExtL), ApplyVolume( ExtR, Cores[1].ExtR) );
if( PlayMode & 8 ) if( PlayMode & 8 )
{ {
// Experimental CDDA support // Experimental CDDA support
// The CDDA overrides all other mixer output. It's a direct feed! // The CDDA overrides all other mixer output. It's a direct feed!
ReadInput(Cores[1], OutL, OutR); ReadInput( Cores[1], Out );
//WaveLog::WriteCore( 1, "CDDA-32", OutL, OutR ); //WaveLog::WriteCore( 1, "CDDA-32", OutL, OutR );
} }
else else
{ {
OutL = MulShr32( OutL<<10, Cores[1].MasterL.Value ); Out.Left = MulShr32( Out.Left<<SndOutVolumeShift, Cores[1].MasterVol.Left.Value );
OutR = MulShr32( OutR<<10, Cores[1].MasterR.Value ); Out.Right = MulShr32( Out.Right<<SndOutVolumeShift, Cores[1].MasterVol.Right.Value );
// Final Clamp. // Final Clamp!
// This could be circumvented by using 1/2th total output volume, although // This could be circumvented by using 1/2th total output volume, although
// I suspect clamping at the higher volume is more true to the PS2's real // I suspect this approach (clamping at the higher volume) is more true to the
// implementation. // PS2's real implementation.
OutL = clamp_mix( OutL, SndOutVolumeShift ); clamp_mix( Out, SndOutVolumeShift );
OutR = clamp_mix( OutR, SndOutVolumeShift );
} }
// Update spdif (called each sample) // Update spdif (called each sample)
if(PlayMode&4) if(PlayMode&4)
spdif_update(); spdif_update();
// AddToBuffer SndBuffer::Write( Out );
SndWrite(OutL, OutR);
// Update AutoDMA output positioning // Update AutoDMA output positioning
OutPos++; OutPos++;

View File

@ -31,14 +31,14 @@ const u16 zero=0;
PCORE(c,Voices[v].##p) PCORE(c,Voices[v].##p)
#define PVC(c,v) \ #define PVC(c,v) \
PVCP(c,v,VolumeL.Reg_VOL), \ PVCP(c,v,Volume.Left.Reg_VOL), \
PVCP(c,v,VolumeR.Reg_VOL), \ PVCP(c,v,Volume.Right.Reg_VOL), \
PVCP(c,v,Pitch), \ PVCP(c,v,Pitch), \
PVCP(c,v,ADSR.Reg_ADSR1), \ PVCP(c,v,ADSR.Reg_ADSR1), \
PVCP(c,v,ADSR.Reg_ADSR2), \ PVCP(c,v,ADSR.Reg_ADSR2), \
PVCP(c,v,ADSR.Value)+1, \ PVCP(c,v,ADSR.Value)+1, \
PVCP(c,v,VolumeL.Value)+1, \ PVCP(c,v,Volume.Left.Value)+1, \
PVCP(c,v,VolumeR.Value)+1 PVCP(c,v,Volume.Right.Value)+1
#define PVCA(c,v) \ #define PVCA(c,v) \
PVCP(c,v,StartA)+1, \ PVCP(c,v,StartA)+1, \
@ -247,16 +247,16 @@ u16* regtable[0x800] =
PRAW(0x758),PRAW(0x75A),PRAW(0x75C),PRAW(0x75E), PRAW(0x758),PRAW(0x75A),PRAW(0x75C),PRAW(0x75E),
//0x760: weird area //0x760: weird area
PCORE(0,MasterL.Reg_VOL), PCORE(0,MasterVol.Left.Reg_VOL),
PCORE(0,MasterR.Reg_VOL), PCORE(0,MasterVol.Right.Reg_VOL),
PCORE(0,FxL)+1, PCORE(0,FxVol.Left)+1,
PCORE(0,FxR)+1, PCORE(0,FxVol.Right)+1,
PCORE(0,ExtL)+1, PCORE(0,ExtVol.Left)+1,
PCORE(0,ExtR)+1, PCORE(0,ExtVol.Right)+1,
PCORE(0,InpL)+1, PCORE(0,InpVol.Left)+1,
PCORE(0,InpR)+1, PCORE(0,InpVol.Right)+1,
PCORE(0,MasterL.Value)+1, PCORE(0,MasterVol.Left.Value)+1,
PCORE(0,MasterR.Value)+1, PCORE(0,MasterVol.Right.Value)+1,
PCORE(0,Revb.IIR_ALPHA), PCORE(0,Revb.IIR_ALPHA),
PCORE(0,Revb.ACC_COEF_A), PCORE(0,Revb.ACC_COEF_A),
PCORE(0,Revb.ACC_COEF_B), PCORE(0,Revb.ACC_COEF_B),
@ -268,16 +268,16 @@ u16* regtable[0x800] =
PCORE(0,Revb.IN_COEF_L), PCORE(0,Revb.IN_COEF_L),
PCORE(0,Revb.IN_COEF_R), PCORE(0,Revb.IN_COEF_R),
PCORE(1,MasterL.Reg_VOL), PCORE(1,MasterVol.Left.Reg_VOL),
PCORE(1,MasterR.Reg_VOL), PCORE(1,MasterVol.Right.Reg_VOL),
PCORE(1,FxL)+1, PCORE(1,FxVol.Left)+1,
PCORE(1,FxR)+1, PCORE(1,FxVol.Right)+1,
PCORE(1,ExtL)+1, PCORE(1,ExtVol.Left)+1,
PCORE(1,ExtR)+1, PCORE(1,ExtVol.Right)+1,
PCORE(1,InpL)+1, PCORE(1,InpVol.Left)+1,
PCORE(1,InpR)+1, PCORE(1,InpVol.Right)+1,
PCORE(1,MasterL.Value)+1, PCORE(1,MasterVol.Left.Value)+1,
PCORE(1,MasterR.Value)+1, PCORE(1,MasterVol.Right.Value)+1,
PCORE(1,Revb.IIR_ALPHA), PCORE(1,Revb.IIR_ALPHA),
PCORE(1,Revb.ACC_COEF_A), PCORE(1,Revb.ACC_COEF_A),
PCORE(1,Revb.ACC_COEF_B), PCORE(1,Revb.ACC_COEF_B),

View File

@ -24,20 +24,18 @@
static LPF_data lowpass_left( 11000, SampleRate ); static LPF_data lowpass_left( 11000, SampleRate );
static LPF_data lowpass_right( 11000, SampleRate ); static LPF_data lowpass_right( 11000, SampleRate );
static s32 EffectsBufferIndexer( V_Core& thiscore, s32 offset ) static __forceinline s32 RevbGetIndexer( V_Core& thiscore, s32 offset )
{ {
u32 pos = thiscore.EffectsStartA + thiscore.ReverbX + offset; u32 pos = thiscore.ReverbX + offset;
// Need to use modulus here, because games can and will drop the buffer size // Need to use modulus here, because games can and will drop the buffer size
// without notice, and it leads to offsets several times past the end of the buffer. // without notice, and it leads to offsets several times past the end of the buffer.
if( pos > thiscore.EffectsEndA ) if( pos > thiscore.EffectsEndA )
{ {
pos = thiscore.EffectsStartA + ((thiscore.ReverbX + offset) % (u32)thiscore.EffectsBufferSize); //pos = thiscore.EffectsStartA + ((thiscore.ReverbX + offset) % (u32)thiscore.EffectsBufferSize);
} pos -= thiscore.EffectsEndA+1;
else if( pos < thiscore.EffectsStartA ) pos += thiscore.EffectsStartA;
{
pos = thiscore.EffectsEndA+1 - ((thiscore.ReverbX + offset) % (u32)thiscore.EffectsBufferSize );
} }
return pos; return pos;
} }
@ -52,15 +50,16 @@ void Reverb_AdvanceBuffer( V_Core& thiscore )
{ {
if( (Cycles & 1) && (thiscore.EffectsBufferSize > 0) ) if( (Cycles & 1) && (thiscore.EffectsBufferSize > 0) )
{ {
thiscore.ReverbX += 1; thiscore.ReverbX = RevbGetIndexer( thiscore, 1 );
if(thiscore.ReverbX >= (u32)thiscore.EffectsBufferSize ) //thiscore.ReverbX += 1;
thiscore.ReverbX %= (u32)thiscore.EffectsBufferSize; //if(thiscore.ReverbX >= (u32)thiscore.EffectsBufferSize )
// thiscore.ReverbX %= (u32)thiscore.EffectsBufferSize;
} }
} }
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
void DoReverb( V_Core& thiscore, s32& OutL, s32& OutR, s32 InL, s32 InR) StereoOut32 DoReverb( V_Core& thiscore, const StereoOut32& Input )
{ {
// Reverb processing occurs at 24khz, so we skip processing every other sample, // Reverb processing occurs at 24khz, so we skip processing every other sample,
// and use the previous calculation for this core instead. // and use the previous calculation for this core instead.
@ -68,84 +67,90 @@ void DoReverb( V_Core& thiscore, s32& OutL, s32& OutR, s32 InL, s32 InR)
if( thiscore.EffectsBufferSize <= 0 ) if( thiscore.EffectsBufferSize <= 0 )
{ {
// StartA is past EndA, so effects are disabled. // StartA is past EndA, so effects are disabled.
OutL = InL;
OutR = InR;
//ConLog( " * SPU2: Effects disabled due to leapfrogged EffectsStart." ); //ConLog( " * SPU2: Effects disabled due to leapfrogged EffectsStart." );
return; return Input;
} }
if((Cycles&1)==0) if( (Cycles&1)==0 )
{ {
OutL = thiscore.LastEffectL; StereoOut32 retval( thiscore.LastEffect );
OutR = thiscore.LastEffectR; thiscore.LastEffect = Input;
return retval;
thiscore.LastEffectL = InL;
thiscore.LastEffectR = InR;
} }
else else
{ {
if( thiscore.RevBuffers.NeedsUpdated )
thiscore.UpdateEffectsBufferSize();
// Advance the current reverb buffer pointer, and cache the read/write addresses we'll be // Advance the current reverb buffer pointer, and cache the read/write addresses we'll be
// needing for this session of reverb. // needing for this session of reverb.
const u32 src_a0 = EffectsBufferIndexer( thiscore, thiscore.Revb.IIR_SRC_A0 ); const u32 src_a0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.IIR_SRC_A0 );
const u32 src_a1 = EffectsBufferIndexer( thiscore, thiscore.Revb.IIR_SRC_A1 ); const u32 src_a1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.IIR_SRC_A1 );
const u32 src_b0 = EffectsBufferIndexer( thiscore, thiscore.Revb.IIR_SRC_B0 ); const u32 src_b0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.IIR_SRC_B0 );
const u32 src_b1 = EffectsBufferIndexer( thiscore, thiscore.Revb.IIR_SRC_B1 ); const u32 src_b1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.IIR_SRC_B1 );
const u32 dest_a0 = EffectsBufferIndexer( thiscore, thiscore.Revb.IIR_DEST_A0 ); const u32 dest_a0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.IIR_DEST_A0 );
const u32 dest_a1 = EffectsBufferIndexer( thiscore, thiscore.Revb.IIR_DEST_A1 ); const u32 dest_a1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.IIR_DEST_A1 );
const u32 dest_b0 = EffectsBufferIndexer( thiscore, thiscore.Revb.IIR_DEST_B0 ); const u32 dest_b0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.IIR_DEST_B0 );
const u32 dest_b1 = EffectsBufferIndexer( thiscore, thiscore.Revb.IIR_DEST_B1 ); const u32 dest_b1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.IIR_DEST_B1 );
const u32 dest2_a0 = EffectsBufferIndexer( thiscore, thiscore.Revb.IIR_DEST_A0 + 1 ); const u32 dest2_a0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.IIR_DEST_A0 + 1 );
const u32 dest2_a1 = EffectsBufferIndexer( thiscore, thiscore.Revb.IIR_DEST_A1 + 1 ); const u32 dest2_a1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.IIR_DEST_A1 + 1 );
const u32 dest2_b0 = EffectsBufferIndexer( thiscore, thiscore.Revb.IIR_DEST_B0 + 1 ); const u32 dest2_b0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.IIR_DEST_B0 + 1 );
const u32 dest2_b1 = EffectsBufferIndexer( thiscore, thiscore.Revb.IIR_DEST_B1 + 1 ); const u32 dest2_b1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.IIR_DEST_B1 + 1 );
const u32 acc_src_a0 = EffectsBufferIndexer( thiscore, thiscore.Revb.ACC_SRC_A0 ); const u32 acc_src_a0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.ACC_SRC_A0 );
const u32 acc_src_b0 = EffectsBufferIndexer( thiscore, thiscore.Revb.ACC_SRC_B0 ); const u32 acc_src_b0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.ACC_SRC_B0 );
const u32 acc_src_c0 = EffectsBufferIndexer( thiscore, thiscore.Revb.ACC_SRC_C0 ); const u32 acc_src_c0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.ACC_SRC_C0 );
const u32 acc_src_d0 = EffectsBufferIndexer( thiscore, thiscore.Revb.ACC_SRC_D0 ); const u32 acc_src_d0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.ACC_SRC_D0 );
const u32 acc_src_a1 = EffectsBufferIndexer( thiscore, thiscore.Revb.ACC_SRC_A1 ); const u32 acc_src_a1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.ACC_SRC_A1 );
const u32 acc_src_b1 = EffectsBufferIndexer( thiscore, thiscore.Revb.ACC_SRC_B1 ); const u32 acc_src_b1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.ACC_SRC_B1 );
const u32 acc_src_c1 = EffectsBufferIndexer( thiscore, thiscore.Revb.ACC_SRC_C1 ); const u32 acc_src_c1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.ACC_SRC_C1 );
const u32 acc_src_d1 = EffectsBufferIndexer( thiscore, thiscore.Revb.ACC_SRC_D1 ); const u32 acc_src_d1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.ACC_SRC_D1 );
const u32 fb_src_a0 = EffectsBufferIndexer( thiscore, thiscore.Revb.MIX_DEST_A0 - thiscore.Revb.FB_SRC_A ); const u32 fb_src_a0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.FB_SRC_A0 );
const u32 fb_src_a1 = EffectsBufferIndexer( thiscore, thiscore.Revb.MIX_DEST_A1 - thiscore.Revb.FB_SRC_A ); const u32 fb_src_a1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.FB_SRC_A1 );
const u32 fb_src_b0 = EffectsBufferIndexer( thiscore, thiscore.Revb.MIX_DEST_B0 - thiscore.Revb.FB_SRC_B ); const u32 fb_src_b0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.FB_SRC_B0 );
const u32 fb_src_b1 = EffectsBufferIndexer( thiscore, thiscore.Revb.MIX_DEST_B1 - thiscore.Revb.FB_SRC_B ); const u32 fb_src_b1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.FB_SRC_B1 );
const u32 mix_dest_a0 = EffectsBufferIndexer( thiscore, thiscore.Revb.MIX_DEST_A0 ); const u32 mix_dest_a0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.MIX_DEST_A0 );
const u32 mix_dest_a1 = EffectsBufferIndexer( thiscore, thiscore.Revb.MIX_DEST_A1 ); const u32 mix_dest_a1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.MIX_DEST_A1 );
const u32 mix_dest_b0 = EffectsBufferIndexer( thiscore, thiscore.Revb.MIX_DEST_B0 ); const u32 mix_dest_b0 = RevbGetIndexer( thiscore, thiscore.RevBuffers.MIX_DEST_B0 );
const u32 mix_dest_b1 = EffectsBufferIndexer( thiscore, thiscore.Revb.MIX_DEST_B1 ); const u32 mix_dest_b1 = RevbGetIndexer( thiscore, thiscore.RevBuffers.MIX_DEST_B1 );
// ----------------------------------------- // -----------------------------------------
// End Buffer Pointers, Begin Reverb! // End Buffer Pointers, Begin Reverb!
// ----------------------------------------- // -----------------------------------------
const s32 INPUT_SAMPLE_L = (thiscore.LastEffectL+InL); StereoOut32 INPUT_SAMPLE( thiscore.LastEffect + Input );
const s32 INPUT_SAMPLE_R = (thiscore.LastEffectR+InR);
//const s32 INPUT_SAMPLE_L = (s32)( lowpass_left.sample( (thiscore.LastEffectL+InL)/65536.0 ) * 65536.0 ); const s32 IIR_INPUT_A0 = ((_spu2mem[src_a0] * thiscore.Revb.IIR_COEF) + (INPUT_SAMPLE.Left * thiscore.Revb.IN_COEF_L))>>16;
//const s32 INPUT_SAMPLE_R = (s32)( lowpass_right.sample( (thiscore.LastEffectR+InR)/65536.0 ) * 65536.0 ); const s32 IIR_INPUT_A1 = ((_spu2mem[src_a1] * thiscore.Revb.IIR_COEF) + (INPUT_SAMPLE.Right * thiscore.Revb.IN_COEF_R))>>16;
const s32 IIR_INPUT_B0 = ((_spu2mem[src_b0] * thiscore.Revb.IIR_COEF) + (INPUT_SAMPLE.Left * thiscore.Revb.IN_COEF_L))>>16;
const s32 IIR_INPUT_B1 = ((_spu2mem[src_b1] * thiscore.Revb.IIR_COEF) + (INPUT_SAMPLE.Right * thiscore.Revb.IN_COEF_R))>>16;
const s32 IIR_INPUT_A0 = ((_spu2mem[src_a0] * thiscore.Revb.IIR_COEF) + (INPUT_SAMPLE_L * thiscore.Revb.IN_COEF_L))>>16; //const s32 IIR_A0 = (IIR_INPUT_A0 * thiscore.Revb.IIR_ALPHA) + (_spu2mem[dest_a0] * (0x7fff - thiscore.Revb.IIR_ALPHA));
const s32 IIR_INPUT_A1 = ((_spu2mem[src_a1] * thiscore.Revb.IIR_COEF) + (INPUT_SAMPLE_R * thiscore.Revb.IN_COEF_R))>>16; //const s32 IIR_A1 = (IIR_INPUT_A1 * thiscore.Revb.IIR_ALPHA) + (_spu2mem[dest_a1] * (0x7fff - thiscore.Revb.IIR_ALPHA));
const s32 IIR_INPUT_B0 = ((_spu2mem[src_b0] * thiscore.Revb.IIR_COEF) + (INPUT_SAMPLE_L * thiscore.Revb.IN_COEF_L))>>16; //const s32 IIR_B0 = (IIR_INPUT_B0 * thiscore.Revb.IIR_ALPHA) + (_spu2mem[dest_b0] * (0x7fff - thiscore.Revb.IIR_ALPHA));
const s32 IIR_INPUT_B1 = ((_spu2mem[src_b1] * thiscore.Revb.IIR_COEF) + (INPUT_SAMPLE_R * thiscore.Revb.IN_COEF_R))>>16; //const s32 IIR_B1 = (IIR_INPUT_B1 * thiscore.Revb.IIR_ALPHA) + (_spu2mem[dest_b1] * (0x7fff - thiscore.Revb.IIR_ALPHA));
const s32 IIR_A0 = (IIR_INPUT_A0 * thiscore.Revb.IIR_ALPHA) + (_spu2mem[dest_a0] * (0x7fff - thiscore.Revb.IIR_ALPHA)); //_spu2mem[dest2_a0] = clamp_mix( IIR_A0 >> 16 );
const s32 IIR_A1 = (IIR_INPUT_A1 * thiscore.Revb.IIR_ALPHA) + (_spu2mem[dest_a1] * (0x7fff - thiscore.Revb.IIR_ALPHA)); //_spu2mem[dest2_a1] = clamp_mix( IIR_A1 >> 16 );
const s32 IIR_B0 = (IIR_INPUT_B0 * thiscore.Revb.IIR_ALPHA) + (_spu2mem[dest_b0] * (0x7fff - thiscore.Revb.IIR_ALPHA)); //_spu2mem[dest2_b0] = clamp_mix( IIR_B0 >> 16 );
const s32 IIR_B1 = (IIR_INPUT_B1 * thiscore.Revb.IIR_ALPHA) + (_spu2mem[dest_b1] * (0x7fff - thiscore.Revb.IIR_ALPHA)); //_spu2mem[dest2_b1] = clamp_mix( IIR_B1 >> 16 );
_spu2mem[dest2_a0] = clamp_mix( IIR_A0 >> 16 ); // Faster single-mul approach to interpolation:
_spu2mem[dest2_a1] = clamp_mix( IIR_A1 >> 16 ); const s32 IIR_A0 = IIR_INPUT_A0 + ((_spu2mem[dest_a0]-IIR_INPUT_A0) * thiscore.Revb.IIR_ALPHA)>>16;
_spu2mem[dest2_b0] = clamp_mix( IIR_B0 >> 16 ); const s32 IIR_A1 = IIR_INPUT_A1 + ((_spu2mem[dest_a1]-IIR_INPUT_A1) * thiscore.Revb.IIR_ALPHA)>>16;
_spu2mem[dest2_b1] = clamp_mix( IIR_B1 >> 16 ); const s32 IIR_B0 = IIR_INPUT_B0 + ((_spu2mem[dest_b0]-IIR_INPUT_B0) * thiscore.Revb.IIR_ALPHA)>>16;
const s32 IIR_B1 = IIR_INPUT_B1 + ((_spu2mem[dest_b1]-IIR_INPUT_B1) * thiscore.Revb.IIR_ALPHA)>>16;
_spu2mem[dest2_a0] = clamp_mix( IIR_A0 );
_spu2mem[dest2_a1] = clamp_mix( IIR_A1 );
_spu2mem[dest2_b0] = clamp_mix( IIR_B0 );
_spu2mem[dest2_b1] = clamp_mix( IIR_B1 );
const s32 ACC0 = const s32 ACC0 =
((_spu2mem[acc_src_a0] * thiscore.Revb.ACC_COEF_A)) + ((_spu2mem[acc_src_a0] * thiscore.Revb.ACC_COEF_A)) +
@ -161,8 +166,6 @@ void DoReverb( V_Core& thiscore, s32& OutL, s32& OutR, s32 InL, s32 InR)
const s32 FB_A0 = (_spu2mem[fb_src_a0] * thiscore.Revb.FB_ALPHA); const s32 FB_A0 = (_spu2mem[fb_src_a0] * thiscore.Revb.FB_ALPHA);
const s32 FB_A1 = (_spu2mem[fb_src_a1] * thiscore.Revb.FB_ALPHA); const s32 FB_A1 = (_spu2mem[fb_src_a1] * thiscore.Revb.FB_ALPHA);
const s32 FB_B0 = (_spu2mem[fb_src_b0] * (0x7fff - thiscore.Revb.FB_ALPHA)); //>>16;
const s32 FB_B1 = (_spu2mem[fb_src_b1] * (0x7fff - thiscore.Revb.FB_ALPHA)); //>>16;
const s32 fb_xor_a0 = (_spu2mem[fb_src_a0] * ( thiscore.Revb.FB_ALPHA ^ 0x8000 ))>>2; const s32 fb_xor_a0 = (_spu2mem[fb_src_a0] * ( thiscore.Revb.FB_ALPHA ^ 0x8000 ))>>2;
const s32 fb_xor_a1 = (_spu2mem[fb_src_a1] * ( thiscore.Revb.FB_ALPHA ^ 0x8000 ))>>2; const s32 fb_xor_a1 = (_spu2mem[fb_src_a1] * ( thiscore.Revb.FB_ALPHA ^ 0x8000 ))>>2;
@ -172,12 +175,13 @@ void DoReverb( V_Core& thiscore, s32& OutL, s32& OutR, s32 InL, s32 InR)
_spu2mem[mix_dest_b0] = clamp_mix( (MulShr32(thiscore.Revb.FB_ALPHA<<14, ACC0) - fb_xor_a0 - ((_spu2mem[fb_src_b0] * thiscore.Revb.FB_X)>>2)) >> 14 ); _spu2mem[mix_dest_b0] = clamp_mix( (MulShr32(thiscore.Revb.FB_ALPHA<<14, ACC0) - fb_xor_a0 - ((_spu2mem[fb_src_b0] * thiscore.Revb.FB_X)>>2)) >> 14 );
_spu2mem[mix_dest_b1] = clamp_mix( (MulShr32(thiscore.Revb.FB_ALPHA<<14, ACC1) - fb_xor_a1 - ((_spu2mem[fb_src_b1] * thiscore.Revb.FB_X)>>2)) >> 14 ); _spu2mem[mix_dest_b1] = clamp_mix( (MulShr32(thiscore.Revb.FB_ALPHA<<14, ACC1) - fb_xor_a1 - ((_spu2mem[fb_src_b1] * thiscore.Revb.FB_X)>>2)) >> 14 );
thiscore.LastEffectL = clamp_mix(_spu2mem[mix_dest_a0] + _spu2mem[mix_dest_b0]); thiscore.LastEffect.Left = _spu2mem[mix_dest_a0] + _spu2mem[mix_dest_b0];
thiscore.LastEffectR = clamp_mix(_spu2mem[mix_dest_a1] + _spu2mem[mix_dest_b1]); thiscore.LastEffect.Right = _spu2mem[mix_dest_a1] + _spu2mem[mix_dest_b1];
clamp_mix( thiscore.LastEffect );
//OutL = thiscore.LastEffectL; thiscore.LastEffect.Left = (s32)(lowpass_left.sample( thiscore.LastEffect.Left / 32768.0 ) * 32768.0);
//OutR = thiscore.LastEffectR; thiscore.LastEffect.Right = (s32)(lowpass_right.sample( thiscore.LastEffect.Right / 32768.0 ) * 32768.0);
OutL = (s32)(lowpass_left.sample( thiscore.LastEffectL / 32768.0 ) * 32768.0);
OutR = (s32)(lowpass_right.sample( thiscore.LastEffectR / 32768.0 ) * 32768.0); return thiscore.LastEffect;
} }
} }

View File

@ -19,24 +19,45 @@
* *
*/ */
// [TODO] : The layout of this code file is now a complete hackish mess after
// numerous timestretch-related additions. The whole thing should really be
// rethought and redone at this point.
#include "spu2.h" #include "spu2.h"
#include "SoundTouch/SoundTouch.h"
#include "SoundTouch/WavFile.h"
#include <new>
static int ts_stats_stretchblocks = 0; StereoOut32 StereoOut32::Empty( 0, 0 );
static int ts_stats_normalblocks = 0;
static int ts_stats_logcounter = 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 class NullOutModule: public SndOutModule
{ {
public: public:
s32 Init(SndBuffer *) { return 0; } s32 Init() { return 0; }
void Close() { } void Close() { }
s32 Test() const { return 0; } s32 Test() const { return 0; }
void Configure(HWND parent) { } void Configure(HWND parent) { }
@ -61,7 +82,6 @@ SndOutModule* mods[]=
XAudio2Out, XAudio2Out,
DSoundOut, DSoundOut,
WaveOut, WaveOut,
//ASIOOut,
NULL // signals the end of our list NULL // signals the end of our list
}; };
@ -77,528 +97,173 @@ int FindOutputModuleById( const wchar_t* omodid )
return modcnt; return modcnt;
} }
StereoOut32 *SndBuffer::m_buffer;
s32 SndBuffer::m_size;
s32 SndBuffer::m_rpos;
s32 SndBuffer::m_wpos;
s32 SndBuffer::m_data;
__forceinline s16 SndScaleVol( s32 inval ) bool SndBuffer::m_underrun_freeze;
StereoOut32* SndBuffer::sndTempBuffer = NULL;
StereoOut16* SndBuffer::sndTempBuffer16 = NULL;
int SndBuffer::sndTempProgress = 0;
int GetAlignedBufferSize( int comp )
{ {
return inval >> SndOutVolumeShift; return (comp + SndOutPacketSize-1) & ~(SndOutPacketSize-1);
} }
// Returns TRUE if there is data to be output, or false if no data
// records last buffer status (fill %, range -100 to 100, with 0 being 50% full) // is available to be copied.
float lastPct; bool SndBuffer::CheckUnderrunStatus( int& nSamples, int& quietSampleCount )
float lastEmergencyAdj;
float cTempo=1;
float eTempo = 1;
int freezeTempo = 0;
soundtouch::SoundTouch* pSoundTouch=NULL;
//usefull when timestretch isn't available
class SndBufferImpl: public SndBuffer
{ {
private: quietSampleCount = 0;
s32 *buffer; if( m_underrun_freeze )
s32 size;
s32 rpos;
s32 wpos;
s32 data;
// data prediction amount, used to "commit" data that hasn't
// finished timestretch processing.
s32 predictData;
bool pw;
bool underrun_freeze;
protected:
int GetAlignedBufferSize( int comp )
{ {
return (comp + SndOutPacketSize-1) & ~(SndOutPacketSize-1); int toFill = (int)(m_size * ( timeStretchDisabled ? 0.50f : 0.1f ) );
} toFill = GetAlignedBufferSize( toFill );
public: // toFill is now aligned to a SndOutPacket
SndBufferImpl( float latencyMS )
{
rpos=0;
wpos=0;
data=0;
size=GetAlignedBufferSize( (int)(latencyMS * SampleRate / 500.0f ) );
buffer = new s32[size];
pw=false;
underrun_freeze = false;
predictData = 0;
}
virtual ~SndBufferImpl() if( m_data < toFill )
{
delete buffer;
}
virtual void WriteSamples(s32 *bData, int nSamples)
{
int free = size-data;
predictData = 0;
jASSUME( data <= 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 )
{ {
// Buffer overrun! quietSampleCount = nSamples;
// Dump samples from the read portion of the buffer instead of dropping return false;
// the newly written stuff.
s32 comp;
if( !timeStretchDisabled )
{
// If we overran it means the timestretcher failed. We need to speed
// up audio playback.
cTempo += cTempo * 0.12f;
eTempo += eTempo * 0.40f;
if( eTempo > 7.5f ) eTempo = 7.5f;
pSoundTouch->setTempo( eTempo );
// Throw out just a little bit (two packets worth) to help
// give the TS some room to work:
comp = SndOutPacketSize*2;
}
else
{
// Toss half the buffer plus whatever's being written anew:
comp = GetAlignedBufferSize( (size + nSamples ) / 2 );
if( comp > (size-SndOutPacketSize) ) comp = size-SndOutPacketSize;
}
data -= comp;
rpos = (rpos+comp)%size;
if( MsgOverruns() )
ConLog(" * SPU2 > Overrun Compensation (%d packets tossed)\n", comp / SndOutPacketSize );
lastPct = 0.0; // normalize the timestretcher
} }
// copy in two phases, since there's a chance the packet m_underrun_freeze = false;
// wraps around the buffer (it'd be nice to deal in packets only, but if( MsgOverruns() )
// the timestretcher and DSP options require flexibility). ConLog(" * SPU2 > Underrun compensation (%d packets buffered)\n", toFill / SndOutPacketSize );
lastPct = 0.0; // normalize timestretcher
const int endPos = wpos + nSamples;
const int secondCopyLen = endPos - size;
s32* wposbuffer = &buffer[wpos];
data += nSamples;
if( secondCopyLen > 0 )
{
nSamples -= secondCopyLen;
memcpy( buffer, &bData[nSamples], secondCopyLen * sizeof( *bData ) );
wpos = secondCopyLen;
}
else
wpos += nSamples;
memcpy( wposbuffer, bData, nSamples * sizeof( *bData ) );
} }
else if( m_data < nSamples )
protected:
// Returns TRUE if there is data to be output, or false if no data
// is available to be copied.
bool CheckUnderrunStatus( int& nSamples, int& quietSampleCount )
{ {
quietSampleCount = 0; nSamples = m_data;
if( underrun_freeze ) quietSampleCount = SndOutPacketSize - m_data;
{ m_underrun_freeze = true;
int toFill = (int)(size * ( timeStretchDisabled ? 0.50f : 0.1f ) );
toFill = GetAlignedBufferSize( toFill );
// toFill is now aligned to a SndOutPacket if( !timeStretchDisabled )
timeStretchUnderrun();
if( data < toFill ) return nSamples != 0;
{
quietSampleCount = nSamples;
return false;
}
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;
underrun_freeze = true;
if( !timeStretchDisabled )
{
// timeStretcher failed it's job. We need to slow down the audio some.
cTempo -= (cTempo * 0.12f);
eTempo -= (eTempo * 0.30f);
if( eTempo < 0.1f ) eTempo = 0.1f;
pSoundTouch->setTempo( eTempo );
}
return nSamples != 0;
}
return true;
} }
public: return true;
void ReadSamples( s16* 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 );
// [Air] [TODO]: This loop is probably a candidiate for SSE2 optimization.
const int endPos = rpos + nSamples;
const int secondCopyLen = endPos - size;
const s32* rposbuffer = &buffer[rpos];
data -= nSamples;
if( secondCopyLen > 0 )
{
nSamples -= secondCopyLen;
for( int i=0; i<secondCopyLen; i++ )
bData[nSamples+i] = SndScaleVol( buffer[i] );
rpos = secondCopyLen;
}
else
rpos += nSamples;
for( int i=0; i<nSamples; i++ )
bData[i] = SndScaleVol( rposbuffer[i] );
}
// 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(*bData) );
}
void ReadSamples( s32* 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 ) )
{
// nSamples is garaunteed non-zero if CheckUnderrunStatus
// returned true.
const int endPos = rpos + nSamples;
const int secondCopyLen = endPos - size;
const int oldrpos = rpos;
data -= nSamples;
if( secondCopyLen > 0 )
{
nSamples -= secondCopyLen;
memcpy( &bData[nSamples], buffer, secondCopyLen * sizeof( *bData ) );
rpos = secondCopyLen;
}
else
rpos += nSamples;
memcpy( bData, &buffer[oldrpos], nSamples * sizeof( *bData ) );
}
// 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(*bData) );
}
void PredictDataWrite( int samples )
{
predictData += samples;
}
virtual void PauseOnWrite(bool doPause) { pw = doPause; }
// Calculate the buffer status percentage.
// Returns range from -1.0 to 1.0
// 1.0 = buffer overflow!
// 0.0 = buffer nominal (50% full)
// -1.0 = buffer underflow!
float GetStatusPct()
{
// Get the buffer status of the output driver too, so that we can
// obtain a more accurate overall buffer status.
int drvempty = mods[OutputModule]->GetEmptySampleCount(); // / 2;
//ConLog( "Data %d >>> driver: %d predict: %d\n", data, drvempty, predictData );
float result = (float)(data + predictData - drvempty) - (size/2);
result /= (size/2);
return result;
}
};
SndBufferImpl *sndBuffer=NULL;
s32* sndTempBuffer=NULL;
s32 sndTempProgress=NULL;
s16* sndTempBuffer16=NULL;
void UpdateTempoChange()
{
if( --freezeTempo > 0 )
{
return;
}
float statusPct = sndBuffer->GetStatusPct();
float pctChange = statusPct - lastPct;
float tempoChange;
float emergencyAdj = 0;
float newcee = cTempo; // workspace var. for cTempo
// IMPORTANT!
// If you plan to tweak these values, make sure you're using a release build
// OUTSIDE THE DEBUGGER to test it! The Visual Studio debugger can really cause
// erratic behavior in the audio buffers, and makes the timestretcher seem a
// lot more inconsistent than it really is.
// We have two factors.
// * Distance from nominal buffer status (50% full)
// * The change from previous update to this update.
// Prediction based on the buffer change:
// (linear seems to work better here)
tempoChange = pctChange * 0.75f;
if( statusPct * tempoChange < 0.0f )
{
// only apply tempo change if it is in synch with the buffer status.
// In other words, if the buffer is high (over 0%), and is decreasing,
// ignore it. It'll just muck things up.
tempoChange = 0;
}
// Sudden spikes in framerate can cause the nominal buffer status
// to go critical, in which case we have to enact an emergency
// stretch. The following cubic formulas do that. Values near
// the extremeites give much larger results than those near 0.
// And the value is added only this time, and does not accumulate.
// (otherwise a large value like this would cause problems down the road)
// Constants:
// Weight - weights the statusPct's "emergency" consideration.
// higher values here will make the buffer perform more drastic
// compensations at the outer edges of the buffer (at -75 or +75%
// or beyond, for example).
// Range - scales the adjustment to the given range (more or less).
// The actual range is dependent on the weight used, so if you increase
// Weight you'll usually want to decrease Range somewhat to compensate.
// Prediction based on the buffer fill status:
const float statusWeight = 2.99f;
const float statusRange = 0.068f;
// "non-emergency" deadzone: In this area stretching will be strongly discouraged.
// Note: due tot he nature of timestretch latency, it's always a wee bit harder to
// cope with low fps (underruns) tha it is high fps (overruns). So to help out a
// little, the low-end portions of this check are less forgiving than the high-sides.
if( cTempo < 0.965f || cTempo > 1.060f ||
pctChange < -0.38f || pctChange > 0.54f ||
statusPct < -0.32f || statusPct > 0.39f ||
eTempo < 0.89f || eTempo > 1.19f )
{
emergencyAdj = ( pow( statusPct*statusWeight, 3.0f ) * statusRange);
}
// Smooth things out by factoring our previous adjustment into this one.
// It helps make the system 'feel' a little smarter by giving it at least
// one packet worth of history to help work off of:
emergencyAdj = (emergencyAdj * 0.75f) + (lastEmergencyAdj * 0.25f );
lastEmergencyAdj = emergencyAdj;
lastPct = statusPct;
// Accumulate a fraction of the tempo change into the tempo itself.
// This helps the system run "smarter" to games that run consistently
// fast or slow by altering the base tempo to something closer to the
// game's active speed. In tests most games normalize within 2 seconds
// at 100ms latency, which is pretty good (larger buffers normalize even
// quicker).
newcee += newcee * (tempoChange+emergencyAdj) * 0.03f;
// Apply tempoChange as a scale of cTempo. That way the effect is proportional
// to the current tempo. (otherwise tempos rate of change at the extremes would
// be too drastic)
float newTempo = newcee + ( emergencyAdj * cTempo );
// ... and as a final optimization, only stretch if the new tempo is outside
// a nominal threshold. Keep this threshold check small, because it could
// cause some serious side effects otherwise. (enlarging the cTempo check above
// is usually better/safer)
if( newTempo < 0.970f || newTempo > 1.045f )
{
cTempo = (float)newcee;
if( newTempo < 0.10f ) newTempo = 0.10f;
else if( newTempo > 10.0f ) newTempo = 10.0f;
if( cTempo < 0.15f ) cTempo = 0.15f;
else if( cTempo > 7.5f ) cTempo = 7.5f;
pSoundTouch->setTempo( eTempo = (float)newTempo );
ts_stats_stretchblocks++;
/*ConLog(" * SPU2: [Nominal %d%%] [Emergency: %d%%] (baseTempo: %d%% ) (newTempo: %d%%) (buffer: %d%%)\n",
//(relation < 0.0) ? "Normalize" : "",
(int)(tempoChange * 100.0 * 0.03),
(int)(emergencyAdj * 100.0),
(int)(cTempo * 100.0),
(int)(newTempo * 100.0),
(int)(statusPct * 100.0)
);*/
}
else
{
// Nominal operation -- turn off stretching.
// note: eTempo 'slides' toward 1.0 for smoother audio and better
// protection against spikes.
if( cTempo != 1.0f )
{
cTempo = 1.0f;
eTempo = ( 1.0f + eTempo ) * 0.5f;
pSoundTouch->setTempo( eTempo );
}
else
{
if( eTempo != cTempo )
pSoundTouch->setTempo( eTempo=cTempo );
ts_stats_normalblocks++;
}
}
} }
void soundtouchInit() void SndBuffer::_InitFail()
{
pSoundTouch = new soundtouch::SoundTouch();
pSoundTouch->setSampleRate(SampleRate);
pSoundTouch->setChannels(2);
pSoundTouch->setSetting( SETTING_USE_QUICKSEEK, 0 );
pSoundTouch->setSetting( SETTING_USE_AA_FILTER, 0 );
pSoundTouch->setSetting( SETTING_SEQUENCE_MS, SoundtouchCfg::SequenceLenMS );
pSoundTouch->setSetting( SETTING_SEEKWINDOW_MS, SoundtouchCfg::SeekWindowMS );
pSoundTouch->setSetting( SETTING_OVERLAP_MS, SoundtouchCfg::OverlapMS );
pSoundTouch->setTempo(1);
// some timestretch management vars:
cTempo = 1.0;
eTempo = 1.0;
lastPct = 0;
lastEmergencyAdj = 0;
// just freeze tempo changes for a while at startup.
// the driver buffers are bogus anyway.
freezeTempo = 8;
}
static void _sndInitFail()
{ {
// If a failure occurs, just initialize the NoSound driver. This'll allow // If a failure occurs, just initialize the NoSound driver. This'll allow
// the game to emulate properly (hopefully), albeit without sound. // the game to emulate properly (hopefully), albeit without sound.
OutputModule = FindOutputModuleById( NullOut.GetIdent() ); OutputModule = FindOutputModuleById( NullOut.GetIdent() );
mods[OutputModule]->Init( sndBuffer ); mods[OutputModule]->Init();
} }
s32 SndInit() 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 )
{
// Buffer overrun!
// Dump samples from the read portion of the buffer instead of dropping
// the newly written stuff.
s32 comp;
if( !timeStretchDisabled )
{
comp = timeStretchOverrun();
}
else
{
// 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;
}
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
}
// 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).
const int endPos = m_wpos + nSamples;
const int secondCopyLen = endPos - m_size;
StereoOut32* wposbuffer = &m_buffer[m_wpos];
m_data += nSamples;
if( secondCopyLen > 0 )
{
nSamples -= secondCopyLen;
memcpy( m_buffer, &bData[nSamples], secondCopyLen * sizeof( *bData ) );
m_wpos = secondCopyLen;
}
else
m_wpos += nSamples;
memcpy( wposbuffer, bData, nSamples * sizeof( *bData ) );
}
void SndBuffer::Init()
{ {
if( mods[OutputModule] == NULL ) if( mods[OutputModule] == NULL )
{ {
_sndInitFail(); _InitFail();
return 0; return;
} }
// initialize sound buffer // initialize sound buffer
// Buffer actually attempts to run ~50%, so allocate near double what // Buffer actually attempts to run ~50%, so allocate near double what
// the requested latency is: // the requested latency is:
m_rpos = 0;
m_wpos = 0;
m_data = 0;
try try
{ {
sndBuffer = new SndBufferImpl( SndOutLatencyMS * (timeStretchDisabled ? 1.5f : 2.0f ) ); const float latencyMS = SndOutLatencyMS * (timeStretchDisabled ? 1.5f : 2.0f );
sndTempBuffer = new s32[SndOutPacketSize]; m_size = GetAlignedBufferSize( (int)(latencyMS * SampleRate / 1000.0f ) );
sndTempBuffer16 = new s16[SndOutPacketSize]; m_buffer = new StereoOut32[m_size];
m_underrun_freeze = false;
sndTempBuffer = new StereoOut32[SndOutPacketSize];
sndTempBuffer16 = new StereoOut16[SndOutPacketSize];
} }
catch( std::bad_alloc& ) catch( std::bad_alloc& )
{ {
// out of memory exception (most likely) // out of memory exception (most likely)
SysMessage( "Out of memory error occured while initializing SPU2." ); SysMessage( "Out of memory error occurred while initializing SPU2." );
_sndInitFail(); _InitFail();
return 0; return;
} }
// clear buffers! // clear buffers!
// Fixes loopy sounds on emu resets. // Fixes loopy sounds on emu resets.
memset( sndTempBuffer, 0, sizeof(s32) * SndOutPacketSize ); memset( sndTempBuffer, 0, sizeof(StereoOut32) * SndOutPacketSize );
memset( sndTempBuffer16, 0, sizeof(s16) * SndOutPacketSize ); memset( sndTempBuffer16, 0, sizeof(StereoOut16) * SndOutPacketSize );
sndTempProgress = 0; sndTempProgress = 0;
@ -608,104 +273,78 @@ s32 SndInit()
spdif_set51(mods[OutputModule]->Is51Out()); spdif_set51(mods[OutputModule]->Is51Out());
// initialize module // initialize module
if( mods[OutputModule]->Init(sndBuffer) == -1 ) if( mods[OutputModule]->Init() == -1 ) _InitFail();
{
_sndInitFail();
}
return 0;
} }
void SndClose() void SndBuffer::Cleanup()
{ {
mods[OutputModule]->Close(); mods[OutputModule]->Close();
SAFE_DELETE_OBJ( sndBuffer ); SAFE_DELETE_ARRAY( m_buffer );
SAFE_DELETE_ARRAY( sndTempBuffer ); SAFE_DELETE_ARRAY( sndTempBuffer );
SAFE_DELETE_ARRAY( sndTempBuffer16 ); SAFE_DELETE_ARRAY( sndTempBuffer16 );
SAFE_DELETE_OBJ( pSoundTouch );
} }
s32 SndWrite(s32 ValL, s32 ValR) int SndBuffer::m_dsp_progress = 0;
int SndBuffer::m_dsp_writepos = 0;
int SndBuffer::m_timestretch_progress = 0;
void SndBuffer::Write( const StereoOut32& Sample )
{ {
// Log final output to wavefile. // Log final output to wavefile.
WaveDump::WriteCore( 1, CoreSrc_External, SndScaleVol(ValL), SndScaleVol(ValR) ); WaveDump::WriteCore( 1, CoreSrc_External, Sample.DownSample() );
RecordWrite(SndScaleVol(ValL),SndScaleVol(ValR)); RecordWrite( Sample.DownSample() );
if(mods[OutputModule] == &NullOut) // null output doesn't need buffering or stretching! :p if(mods[OutputModule] == &NullOut) // null output doesn't need buffering or stretching! :p
return 0; return;
sndTempBuffer[sndTempProgress++] = ValL; sndTempBuffer[sndTempProgress++] = Sample;
sndTempBuffer[sndTempProgress++] = ValR;
// If we haven't accumulated a full packet yet, do nothing more: // If we haven't accumulated a full packet yet, do nothing more:
if(sndTempProgress < SndOutPacketSize) return 1; if(sndTempProgress < SndOutPacketSize) return;
sndTempProgress = 0;
if(dspPluginEnabled) if( dspPluginEnabled )
{ {
for(int i=0;i<SndOutPacketSize;i++) { sndTempBuffer16[i] = SndScaleVol( sndTempBuffer[i] ); } // Convert in, send to winamp DSP, and convert out.
// send to winamp DSP for( int i=0; i<SndOutPacketSize; ++i, ++m_dsp_writepos ) { sndTempBuffer16[m_dsp_writepos] = sndTempBuffer[i].DownSample(); }
sndTempProgress = DspProcess(sndTempBuffer16,sndTempProgress>>1)<<1; m_dsp_progress += DspProcess( (s16*)sndTempBuffer16, SndOutPacketSize );
for(int i=0;i<sndTempProgress;i++) { sndTempBuffer[i] = sndTempBuffer16[i]<<SndOutVolumeShift; } // Some ugly code to ensure full packet handling:
} int ei = 0;
while( m_dsp_progress >= SndOutPacketSize )
static int equalized = 0;
if( !timeStretchDisabled )
{
bool progress = false;
// data prediction helps keep the tempo adjustments more accurate.
// The timestretcher returns packets in belated "clump" form.
// Meaning that most of the time we'll get nothing back, and then
// suddenly we'll get several chunks back at once. Thus we use
// data prediction to make the timestretcher more responsive.
sndBuffer->PredictDataWrite( (int)( sndTempProgress / eTempo ) );
for(int i=0;i<sndTempProgress;i++) { ((float*)sndTempBuffer)[i] = sndTempBuffer[i]/2147483648.0f; }
pSoundTouch->putSamples((float*)sndTempBuffer, sndTempProgress>>1);
while( ( sndTempProgress = pSoundTouch->receiveSamples((float*)sndTempBuffer, sndTempProgress>>1)<<1 ) != 0 )
{ {
// [Air] [TODO] : Implement an SSE downsampler to int. for( int i=0; i<SndOutPacketSize; ++i, ++ei ) { sndTempBuffer[i] = sndTempBuffer16[ei].UpSample(); }
for(int i=0;i<sndTempProgress;i++)
{ if( !timeStretchDisabled )
sndTempBuffer[i] = (s32)(((float*)sndTempBuffer)[i]*2147483648.0f); timeStretchWrite();
} else
sndBuffer->WriteSamples(sndTempBuffer, sndTempProgress); _WriteSamples(sndTempBuffer, sndTempProgress);
progress = true;
m_dsp_progress -= SndOutPacketSize;
} }
UpdateTempoChange(); // copy any leftovers to the front of the dsp buffer.
if( m_dsp_progress > 0 )
if( MsgOverruns() )
{ {
if( progress ) memcpy( &sndTempBuffer16[ei], sndTempBuffer16,
{ sizeof(sndTempBuffer16[0]) * m_dsp_progress
if( ++ts_stats_logcounter > 300 ) );
{
ts_stats_logcounter = 0;
ConLog( " * SPU2 > Timestretch Stats > %d%% of packets stretched.\n",
( ts_stats_stretchblocks * 100 ) / ( ts_stats_normalblocks + ts_stats_stretchblocks ) );
ts_stats_normalblocks = 0;
ts_stats_stretchblocks = 0;
}
}
} }
} }
else else
{ {
sndBuffer->WriteSamples(sndTempBuffer, sndTempProgress); if( !timeStretchDisabled )
sndTempProgress=0; timeStretchWrite();
else
_WriteSamples(sndTempBuffer, SndOutPacketSize);
} }
return 1;
} }
s32 SndTest() s32 SndBuffer::Test()
{ {
if( mods[OutputModule] == NULL ) if( mods[OutputModule] == NULL )
return -1; return -1;
@ -713,10 +352,11 @@ s32 SndTest()
return mods[OutputModule]->Test(); return mods[OutputModule]->Test();
} }
void SndConfigure(HWND parent, u32 module ) void SndBuffer::Configure(HWND parent, u32 module )
{ {
if( mods[module] == NULL ) if( mods[module] == NULL )
return; return;
mods[module]->Configure(parent); mods[module]->Configure(parent);
} }

View File

@ -24,40 +24,310 @@
// Number of stereo samples per SndOut block. // Number of stereo samples per SndOut block.
// All drivers must work in units of this size when communicating with // All drivers must work in units of this size when communicating with
// SndOut. // SndOut.
static const int SndOutPacketSize = 1024; static const int SndOutPacketSize = 512;
// Overall master volume shift. // Overall master volume shift.
// Converts the mixer's 32 bit value into a 16 bit value. // Converts the mixer's 32 bit value into a 16 bit value.
static const int SndOutVolumeShift = 10; static const int SndOutVolumeShift = 13;
// Samplerate of the SPU2. For accurate playback we need to match this // Samplerate of the SPU2. For accurate playback we need to match this
// exactly. Trying to scale samplerates and maintain SPU2's Ts timing accuracy // exactly. Trying to scale samplerates and maintain SPU2's Ts timing accuracy
// is too problematic. :) // is too problematic. :)
static const int SampleRate = 48000; static const int SampleRate = 48000;
extern s32 SndInit();
extern void SndClose();
extern s32 SndWrite(s32 ValL, s32 ValR);
extern s32 SndTest();
extern void SndConfigure(HWND parent, u32 outmodidx );
extern bool SndGetStats(u32 *written, u32 *played);
extern s16 SndScaleVol( s32 inval );
int FindOutputModuleById( const wchar_t* omodid ); int FindOutputModuleById( const wchar_t* omodid );
struct StereoOut16
{
s16 Left;
s16 Right;
StereoOut16() :
Left( 0 ),
Right( 0 )
{
}
StereoOut16( const StereoOut32& src ) :
Left( (s16)src.Left ),
Right( (s16)src.Right )
{
}
StereoOut16( s16 left, s16 right ) :
Left( left ),
Right( right )
{
}
StereoOut32 UpSample() const;
void ResampleFrom( const StereoOut32& src )
{
// Use StereoOut32's built in conversion
*this = src.DownSample();
}
};
struct StereoOutFloat
{
float Left;
float Right;
StereoOutFloat() :
Left( 0 ),
Right( 0 )
{
}
explicit StereoOutFloat( const StereoOut32& src ) :
Left( src.Left / 2147483647.0f ),
Right( src.Right / 2147483647.0f )
{
}
explicit StereoOutFloat( s32 left, s32 right ) :
Left( left / 2147483647.0f ),
Right( right / 2147483647.0f )
{
}
StereoOutFloat( float left, float right ) :
Left( left ),
Right( right )
{
}
};
struct Stereo21Out16
{
s16 Left;
s16 Right;
s16 LFE;
void ResampleFrom( const StereoOut32& src )
{
Left = src.Left >> SndOutVolumeShift;
Right = src.Right >> SndOutVolumeShift;
LFE = (src.Left + src.Right) >> (SndOutVolumeShift + 1);
}
};
struct StereoQuadOut16
{
s16 Left;
s16 Right;
s16 LeftBack;
s16 RightBack;
void ResampleFrom( const StereoOut32& src )
{
Left = src.Left >> SndOutVolumeShift;
Right = src.Right >> SndOutVolumeShift;
LeftBack = src.Left >> SndOutVolumeShift;
RightBack = src.Right >> SndOutVolumeShift;
}
};
struct Stereo41Out16
{
s16 Left;
s16 Right;
s16 LFE;
s16 LeftBack;
s16 RightBack;
void ResampleFrom( const StereoOut32& src )
{
Left = src.Left >> SndOutVolumeShift;
Right = src.Right >> SndOutVolumeShift;
LFE = (src.Left + src.Right) >> (SndOutVolumeShift + 1);
LeftBack = src.Left >> SndOutVolumeShift;
RightBack = src.Right >> SndOutVolumeShift;
}
};
struct Stereo51Out16
{
s16 Left;
s16 Right;
s16 Center;
s16 LFE;
s16 LeftBack;
s16 RightBack;
// Implementation Note: Center and Subwoofer/LFE -->
// This method is simple and sounds nice. It relies on the speaker/soundcard
// systems do to their own low pass / crossover. Manual lowpass is wasted effort
// and can't match solid state results anyway.
void ResampleFrom( const StereoOut32& src )
{
Left = src.Left >> SndOutVolumeShift;
Right = src.Right >> SndOutVolumeShift;
Center = (src.Left + src.Right) >> (SndOutVolumeShift + 1);
LFE = Center;
LeftBack = src.Left >> SndOutVolumeShift;
RightBack = src.Right >> SndOutVolumeShift;
}
};
struct Stereo71Out16
{
s16 Left;
s16 Right;
s16 Center;
s16 LFE;
s16 LeftBack;
s16 RightBack;
s16 LeftSide;
s16 RightSide;
void ResampleFrom( const StereoOut32& src )
{
Left = src.Left >> SndOutVolumeShift;
Right = src.Right >> SndOutVolumeShift;
Center = (src.Left + src.Right) >> (SndOutVolumeShift + 1);
LFE = Center;
LeftBack = src.Left >> SndOutVolumeShift;
RightBack = src.Right >> SndOutVolumeShift;
LeftSide = src.Left >> (SndOutVolumeShift+1);
RightSide = src.Right >> (SndOutVolumeShift+1);
}
};
struct Stereo21Out32
{
s32 Left;
s32 Right;
s32 LFE;
};
struct Stereo41Out32
{
s32 Left;
s32 Right;
s32 LFE;
s32 LeftBack;
s32 RightBack;
};
struct Stereo51Out32
{
s32 Left;
s32 Right;
s32 Center;
s32 LFE;
s32 LeftBack;
s32 RightBack;
};
// Developer Note: This is a static class only (all static members).
class SndBuffer class SndBuffer
{ {
private:
static bool m_underrun_freeze;
static s32 m_predictData;
static float lastPct;
static StereoOut32* sndTempBuffer;
static StereoOut16* sndTempBuffer16;
static int sndTempProgress;
static int m_dsp_progress;
static int m_dsp_writepos;
static int m_timestretch_progress;
static int m_timestretch_writepos;
static StereoOut32 *m_buffer;
static s32 m_size;
static s32 m_rpos;
static s32 m_wpos;
static s32 m_data;
static float lastEmergencyAdj;
static float cTempo;
static float eTempo;
static int freezeTempo;
static void _InitFail();
static void _WriteSamples(StereoOut32* bData, int nSamples);
static bool CheckUnderrunStatus( int& nSamples, int& quietSampleCount );
static void soundtouchInit();
static void soundtouchCleanup();
static void timeStretchWrite();
static void timeStretchUnderrun();
static s32 timeStretchOverrun();
static void PredictDataWrite( int samples );
static float GetStatusPct();
static void UpdateTempoChange();
public: public:
virtual ~SndBuffer() {} static void Init();
static void Cleanup();
static void Write( const StereoOut32& Sample );
static s32 Test();
static void Configure(HWND parent, u32 module );
virtual void WriteSamples(s32 *buffer, int nSamples)=0; // Note: When using with 32 bit output buffers, the user of this function is responsible
virtual void PauseOnWrite(bool doPause)=0; // 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< typename T >
static void ReadSamples( T* bData )
{
int nSamples = SndOutPacketSize;
virtual void ReadSamples( s16* bData )=0; // Problem:
virtual void ReadSamples( s32* bData )=0; // 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.
//virtual s32 GetBufferUsage()=0; int quietSamples;
//virtual s32 GetBufferSize()=0; if( CheckUnderrunStatus( nSamples, quietSamples ) )
{
jASSUME( nSamples <= SndOutPacketSize );
// [Air] [TODO]: This loop is probably a candidate for SSE2 optimization.
const int endPos = m_rpos + nSamples;
const int secondCopyLen = endPos - m_size;
const StereoOut32* rposbuffer = &m_buffer[m_rpos];
m_data -= nSamples;
if( secondCopyLen > 0 )
{
nSamples -= secondCopyLen;
for( int i=0; i<secondCopyLen; i++ )
bData[nSamples+i].ResampleFrom( m_buffer[i] );
m_rpos = secondCopyLen;
}
else
m_rpos += nSamples;
for( int i=0; i<nSamples; i++ )
bData[i].ResampleFrom( rposbuffer[i] );
}
// 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) );
}
}; };
class SndOutModule class SndOutModule
@ -74,7 +344,7 @@ public:
// (for use in configuration screen) // (for use in configuration screen)
virtual const wchar_t* GetLongName() const=0; virtual const wchar_t* GetLongName() const=0;
virtual s32 Init(SndBuffer *buffer)=0; virtual s32 Init()=0;
virtual void Close()=0; virtual void Close()=0;
virtual s32 Test() const=0; virtual s32 Test() const=0;
virtual void Configure(HWND parent)=0; virtual void Configure(HWND parent)=0;
@ -87,12 +357,9 @@ public:
//internal //internal
extern SndOutModule *WaveOut; extern SndOutModule* WaveOut;
extern SndOutModule *DSoundOut; extern SndOutModule* DSoundOut;
extern SndOutModule *FModOut; extern SndOutModule* XAudio2Out;
extern SndOutModule *ASIOOut;
extern SndOutModule *XAudio2Out;
extern SndOutModule *DSound51Out;
extern SndOutModule* mods[]; extern SndOutModule* mods[];

View File

@ -133,6 +133,13 @@ __inline void __fastcall spu2M_Write( u32 addr, u16 value )
spu2M_Write( addr, (s16)value ); spu2M_Write( addr, (s16)value );
} }
V_VolumeLR V_VolumeLR::Max( 0x7FFFFFFF );
V_VolumeSlideLR V_VolumeSlideLR::Max( 0x3FFF, 0x7FFFFFFF );
V_Core::V_Core()
{
}
void V_Core::Reset() void V_Core::Reset()
{ {
memset( this, 0, sizeof(V_Core) ); memset( this, 0, sizeof(V_Core) );
@ -141,16 +148,12 @@ void V_Core::Reset()
Regs.STATX=0; Regs.STATX=0;
Regs.ATTR=0; Regs.ATTR=0;
ExtL = 0x7FFFFFFF; ExtVol = V_VolumeLR::Max;
ExtR = 0x7FFFFFFF; InpVol = V_VolumeLR::Max;
InpL = 0x7FFFFFFF; FxVol = V_VolumeLR::Max;
InpR = 0x7FFFFFFF;
FxL = 0x7FFFFFFF; MasterVol = V_VolumeSlideLR::Max;
FxR = 0x7FFFFFFF;
MasterL.Reg_VOL= 0x3FFF;
MasterR.Reg_VOL= 0x3FFF;
MasterL.Value = 0x7FFFFFFF;
MasterR.Value = 0x7FFFFFFF;
ExtWetR = -1; ExtWetR = -1;
ExtWetL = -1; ExtWetL = -1;
ExtDryR = -1; ExtDryR = -1;
@ -176,32 +179,94 @@ void V_Core::Reset()
for( uint v=0; v<24; ++v ) for( uint v=0; v<24; ++v )
{ {
Voices[v].VolumeL.Reg_VOL = 0x3FFF; Voices[v].Volume = V_VolumeSlideLR::Max;
Voices[v].VolumeR.Reg_VOL = 0x3FFF;
Voices[v].VolumeL.Value = 0x7FFFFFFF; Voices[v].ADSR.Value = 0;
Voices[v].VolumeR.Value = 0x7FFFFFFF; Voices[v].ADSR.Phase = 0;
Voices[v].Pitch = 0x3FFF;
Voices[v].ADSR.Value=0;
Voices[v].ADSR.Phase=0;
Voices[v].Pitch=0x3FFF;
Voices[v].DryL = -1; Voices[v].DryL = -1;
Voices[v].DryR = -1; Voices[v].DryR = -1;
Voices[v].WetL = -1; Voices[v].WetL = -1;
Voices[v].WetR = -1; Voices[v].WetR = -1;
Voices[v].NextA=2800; Voices[v].NextA = 2800;
Voices[v].StartA=2800; Voices[v].StartA = 2800;
Voices[v].LoopStartA=2800; Voices[v].LoopStartA = 2800;
} }
DMAICounter=0; DMAICounter = 0;
AdmaInProgress=0; AdmaInProgress = 0;
Regs.STATX=0x80; Regs.STATX = 0x80;
} }
s32 V_Core::EffectsBufferIndexer( s32 offset ) const
{
u32 pos = EffectsStartA + ReverbX + offset;
// Need to use modulus here, because games can and will drop the buffer size
// without notice, and it leads to offsets several times past the end of the buffer.
if( pos > EffectsEndA )
{
pos = EffectsStartA + ((ReverbX + offset) % (u32)EffectsBufferSize);
}
else if( pos < EffectsStartA )
{
pos = EffectsEndA+1 - ((ReverbX + offset) % (u32)EffectsBufferSize );
}
return pos;
}
void V_Core::UpdateFeedbackBuffersA()
{
RevBuffers.FB_SRC_A0 = EffectsBufferIndexer( Revb.MIX_DEST_A0 - Revb.FB_SRC_A );
RevBuffers.FB_SRC_A1 = EffectsBufferIndexer( Revb.MIX_DEST_A1 - Revb.FB_SRC_A );
}
void V_Core::UpdateFeedbackBuffersB()
{
RevBuffers.FB_SRC_B0 = EffectsBufferIndexer( Revb.MIX_DEST_B0 - Revb.FB_SRC_B );
RevBuffers.FB_SRC_B1 = EffectsBufferIndexer( Revb.MIX_DEST_B1 - Revb.FB_SRC_B );
}
void V_Core::UpdateEffectsBufferSize() void V_Core::UpdateEffectsBufferSize()
{ {
EffectsBufferSize = EffectsEndA - EffectsStartA + 1; ReverbX = 0;
const s32 newbufsize = EffectsEndA - EffectsStartA + 1;
if( !RevBuffers.NeedsUpdated && newbufsize == EffectsBufferSize ) return;
RevBuffers.NeedsUpdated = false;
if( EffectsBufferSize == 0 ) return;
// Rebuild buffer indexers.
RevBuffers.ACC_SRC_A0 = EffectsBufferIndexer( Revb.ACC_SRC_A0 );
RevBuffers.ACC_SRC_A1 = EffectsBufferIndexer( Revb.ACC_SRC_A1 );
RevBuffers.ACC_SRC_B0 = EffectsBufferIndexer( Revb.ACC_SRC_B0 );
RevBuffers.ACC_SRC_B1 = EffectsBufferIndexer( Revb.ACC_SRC_B1 );
RevBuffers.ACC_SRC_C0 = EffectsBufferIndexer( Revb.ACC_SRC_C0 );
RevBuffers.ACC_SRC_C1 = EffectsBufferIndexer( Revb.ACC_SRC_C1 );
RevBuffers.ACC_SRC_D0 = EffectsBufferIndexer( Revb.ACC_SRC_D0 );
RevBuffers.ACC_SRC_D1 = EffectsBufferIndexer( Revb.ACC_SRC_D1 );
UpdateFeedbackBuffersA();
UpdateFeedbackBuffersB();
RevBuffers.IIR_DEST_A0 = EffectsBufferIndexer( Revb.IIR_DEST_A0 );
RevBuffers.IIR_DEST_A1 = EffectsBufferIndexer( Revb.IIR_DEST_A1 );
RevBuffers.IIR_DEST_B0 = EffectsBufferIndexer( Revb.IIR_DEST_B0 );
RevBuffers.IIR_DEST_B1 = EffectsBufferIndexer( Revb.IIR_DEST_B1 );
RevBuffers.IIR_SRC_A0 = EffectsBufferIndexer( Revb.IIR_SRC_A0 );
RevBuffers.IIR_SRC_A1 = EffectsBufferIndexer( Revb.IIR_SRC_A1 );
RevBuffers.IIR_SRC_B0 = EffectsBufferIndexer( Revb.IIR_SRC_B0 );
RevBuffers.IIR_SRC_B1 = EffectsBufferIndexer( Revb.IIR_SRC_B1 );
RevBuffers.MIX_DEST_A0 = EffectsBufferIndexer( Revb.MIX_DEST_A0 );
RevBuffers.MIX_DEST_A1 = EffectsBufferIndexer( Revb.MIX_DEST_A1 );
RevBuffers.MIX_DEST_B0 = EffectsBufferIndexer( Revb.MIX_DEST_B0 );
RevBuffers.MIX_DEST_B1 = EffectsBufferIndexer( Revb.MIX_DEST_B1 );
} }
void V_Voice::Start() void V_Voice::Start()
@ -379,6 +444,11 @@ static s32 GetVol32( u16 src )
return (((s32)src) << 16 ) | ((src<<1) & 0xffff); return (((s32)src) << 16 ) | ((src<<1) & 0xffff);
} }
void V_VolumeSlide::RegSet( u16 src )
{
Value = GetVol32( src );
}
void SPU_ps1_write(u32 mem, u16 value) void SPU_ps1_write(u32 mem, u16 value)
{ {
bool show=true; bool show=true;
@ -393,15 +463,15 @@ void SPU_ps1_write(u32 mem, u16 value)
switch(vval) switch(vval)
{ {
case 0: //VOLL (Volume L) case 0: //VOLL (Volume L)
Cores[0].Voices[voice].VolumeL.Mode = 0; Cores[0].Voices[voice].Volume.Left.Mode = 0;
Cores[0].Voices[voice].VolumeL.Value = GetVol32( value<<1 ); Cores[0].Voices[voice].Volume.Left.RegSet( value << 1 );
Cores[0].Voices[voice].VolumeL.Reg_VOL = value; Cores[0].Voices[voice].Volume.Left.Reg_VOL = value;
break; break;
case 1: //VOLR (Volume R) case 1: //VOLR (Volume R)
Cores[0].Voices[voice].VolumeR.Mode = 0; Cores[0].Voices[voice].Volume.Right.Mode = 0;
Cores[0].Voices[voice].VolumeR.Value = GetVol32( value<<1 ); Cores[0].Voices[voice].Volume.Right.RegSet( value << 1 );
Cores[0].Voices[voice].VolumeR.Reg_VOL = value; Cores[0].Voices[voice].Volume.Right.Reg_VOL = value;
break; break;
case 2: Cores[0].Voices[voice].Pitch = value; break; case 2: Cores[0].Voices[voice].Pitch = value; break;
@ -437,19 +507,22 @@ void SPU_ps1_write(u32 mem, u16 value)
else switch(reg) else switch(reg)
{ {
case 0x1d80:// Mainvolume left case 0x1d80:// Mainvolume left
Cores[0].MasterL.Mode = 0; Cores[0].MasterVol.Left.Mode = 0;
Cores[0].MasterL.Value = GetVol32( value ); Cores[0].MasterVol.Left.RegSet( value );
break; break;
case 0x1d82:// Mainvolume right case 0x1d82:// Mainvolume right
Cores[0].MasterL.Mode = 0; Cores[0].MasterVol.Right.Mode = 0;
Cores[0].MasterR.Value = GetVol32( value ); Cores[0].MasterVol.Right.RegSet( value );
break; break;
case 0x1d84:// Reverberation depth left case 0x1d84:// Reverberation depth left
Cores[0].FxL = GetVol32( value ); Cores[0].FxVol.Left = GetVol32( value );
break; break;
case 0x1d86:// Reverberation depth right case 0x1d86:// Reverberation depth right
Cores[0].FxR = GetVol32( value ); Cores[0].FxVol.Right = GetVol32( value );
break; break;
case 0x1d88:// Voice ON (0-15) case 0x1d88:// Voice ON (0-15)
SPU2_FastWrite(REG_S_KON,value); SPU2_FastWrite(REG_S_KON,value);
@ -463,65 +536,74 @@ void SPU_ps1_write(u32 mem, u16 value)
break; break;
case 0x1d8e:// Voice OFF (16-23) case 0x1d8e:// Voice OFF (16-23)
SPU2_FastWrite(REG_S_KOFF+2,value); SPU2_FastWrite(REG_S_KOFF+2,value);
break; break;
case 0x1d90:// Channel FM (pitch lfo) mode (0-15) case 0x1d90:// Channel FM (pitch lfo) mode (0-15)
SPU2_FastWrite(REG_S_PMON,value); SPU2_FastWrite(REG_S_PMON,value);
break; break;
case 0x1d92:// Channel FM (pitch lfo) mode (16-23) case 0x1d92:// Channel FM (pitch lfo) mode (16-23)
SPU2_FastWrite(REG_S_PMON+2,value); SPU2_FastWrite(REG_S_PMON+2,value);
break; break;
case 0x1d94:// Channel Noise mode (0-15) case 0x1d94:// Channel Noise mode (0-15)
SPU2_FastWrite(REG_S_NON,value); SPU2_FastWrite(REG_S_NON,value);
break; break;
case 0x1d96:// Channel Noise mode (16-23) case 0x1d96:// Channel Noise mode (16-23)
SPU2_FastWrite(REG_S_NON+2,value); SPU2_FastWrite(REG_S_NON+2,value);
break; break;
case 0x1d98:// Channel Reverb mode (0-15) case 0x1d98:// Channel Reverb mode (0-15)
SPU2_FastWrite(REG_S_VMIXEL,value); SPU2_FastWrite(REG_S_VMIXEL,value);
SPU2_FastWrite(REG_S_VMIXER,value); SPU2_FastWrite(REG_S_VMIXER,value);
break; break;
case 0x1d9a:// Channel Reverb mode (16-23) case 0x1d9a:// Channel Reverb mode (16-23)
SPU2_FastWrite(REG_S_VMIXEL+2,value); SPU2_FastWrite(REG_S_VMIXEL+2,value);
SPU2_FastWrite(REG_S_VMIXER+2,value); SPU2_FastWrite(REG_S_VMIXER+2,value);
break; break;
case 0x1d9c:// Channel Reverb mode (0-15) case 0x1d9c:// Channel Reverb mode (0-15)
SPU2_FastWrite(REG_S_VMIXL,value); SPU2_FastWrite(REG_S_VMIXL,value);
SPU2_FastWrite(REG_S_VMIXR,value); SPU2_FastWrite(REG_S_VMIXR,value);
break; break;
case 0x1d9e:// Channel Reverb mode (16-23) case 0x1d9e:// Channel Reverb mode (16-23)
SPU2_FastWrite(REG_S_VMIXL+2,value); SPU2_FastWrite(REG_S_VMIXL+2,value);
SPU2_FastWrite(REG_S_VMIXR+2,value); SPU2_FastWrite(REG_S_VMIXR+2,value);
break; break;
case 0x1da2:// Reverb work area start case 0x1da2:// Reverb work area start
{ {
u32 val=(u32)value <<8; u32 val = (u32)value << 8;
SPU2_FastWrite(REG_A_ESA, val&0xFFFF);
SPU2_FastWrite(REG_A_ESA+2,val>>16);
}
break;
SPU2_FastWrite(REG_A_ESA, val&0xFFFF);
SPU2_FastWrite(REG_A_ESA+2,val>>16);
}
break;
case 0x1da4: case 0x1da4:
Cores[0].IRQA=(u32)value<<8; Cores[0].IRQA=(u32)value<<8;
break; break;
case 0x1da6: case 0x1da6:
Cores[0].TSA=(u32)value<<8; Cores[0].TSA=(u32)value<<8;
break; break;
case 0x1daa: case 0x1daa:
SPU2_FastWrite(REG_C_ATTR,value); SPU2_FastWrite(REG_C_ATTR,value);
break; break;
case 0x1dae: case 0x1dae:
SPU2_FastWrite(REG_P_STATX,value); SPU2_FastWrite(REG_P_STATX,value);
break; break;
case 0x1da8:// Spu Write to Memory case 0x1da8:// Spu Write to Memory
DmaWrite(0,value); DmaWrite(0,value);
show=false; show=false;
break; break;
} }
if(show) FileLog("[%10d] (!) SPU write mem %08x value %04x\n",Cycles,mem,value); if(show) FileLog("[%10d] (!) SPU write mem %08x value %04x\n",Cycles,mem,value);
@ -546,27 +628,31 @@ u16 SPU_ps1_read(u32 mem)
case 0: //VOLL (Volume L) case 0: //VOLL (Volume L)
//value=Cores[0].Voices[voice].VolumeL.Mode; //value=Cores[0].Voices[voice].VolumeL.Mode;
//value=Cores[0].Voices[voice].VolumeL.Value; //value=Cores[0].Voices[voice].VolumeL.Value;
value=Cores[0].Voices[voice].VolumeL.Reg_VOL; break; value = Cores[0].Voices[voice].Volume.Left.Reg_VOL;
break;
case 1: //VOLR (Volume R) case 1: //VOLR (Volume R)
//value=Cores[0].Voices[voice].VolumeR.Mode; //value=Cores[0].Voices[voice].VolumeR.Mode;
//value=Cores[0].Voices[voice].VolumeR.Value; //value=Cores[0].Voices[voice].VolumeR.Value;
value=Cores[0].Voices[voice].VolumeR.Reg_VOL; break; value = Cores[0].Voices[voice].Volume.Right.Reg_VOL;
case 2: value=Cores[0].Voices[voice].Pitch; break; break;
case 3: value=Cores[0].Voices[voice].StartA; break;
case 4: value=Cores[0].Voices[voice].ADSR.Reg_ADSR1; break; case 2: value = Cores[0].Voices[voice].Pitch; break;
case 5: value=Cores[0].Voices[voice].ADSR.Reg_ADSR2; break; case 3: value = Cores[0].Voices[voice].StartA; break;
case 6: value=Cores[0].Voices[voice].ADSR.Value >> 16; break; case 4: value = Cores[0].Voices[voice].ADSR.Reg_ADSR1; break;
case 7: value=Cores[0].Voices[voice].LoopStartA; break; case 5: value = Cores[0].Voices[voice].ADSR.Reg_ADSR2; break;
case 6: value = Cores[0].Voices[voice].ADSR.Value >> 16; break;
case 7: value = Cores[0].Voices[voice].LoopStartA; break;
jNO_DEFAULT; jNO_DEFAULT;
} }
} }
else switch(reg) else switch(reg)
{ {
case 0x1d80: value = Cores[0].MasterL.Value>>16; break; case 0x1d80: value = Cores[0].MasterVol.Left.Value >> 16; break;
case 0x1d82: value = Cores[0].MasterR.Value>>16; break; case 0x1d82: value = Cores[0].MasterVol.Right.Value >> 16; break;
case 0x1d84: value = Cores[0].FxL>>16; break; case 0x1d84: value = Cores[0].FxVol.Left >> 16; break;
case 0x1d86: value = Cores[0].FxR>>16; break; case 0x1d86: value = Cores[0].FxVol.Right >> 16; break;
case 0x1d88: value = 0; break; case 0x1d88: value = 0; break;
case 0x1d8a: value = 0; break; case 0x1d8a: value = 0; break;
@ -585,8 +671,11 @@ u16 SPU_ps1_read(u32 mem)
case 0x1d9e: value = Cores[0].Regs.VMIXL>>16; break; case 0x1d9e: value = Cores[0].Regs.VMIXL>>16; break;
case 0x1da2: case 0x1da2:
value = Cores[0].EffectsStartA>>3; if( value != Cores[0].EffectsStartA>>3 )
Cores[0].UpdateEffectsBufferSize(); {
value = Cores[0].EffectsStartA>>3;
Cores[0].UpdateEffectsBufferSize();
}
break; break;
case 0x1da4: value = Cores[0].IRQA>>3; break; case 0x1da4: value = Cores[0].IRQA>>3; break;
case 0x1da6: value = Cores[0].TSA>>3; break; case 0x1da6: value = Cores[0].TSA>>3; break;
@ -607,15 +696,49 @@ u16 SPU_ps1_read(u32 mem)
return value; return value;
} }
static u32 SetLoWord( u32 var, u16 writeval ) // Ah the joys of endian-specific code! :D
static __forceinline u32 SetHiWord( u32& src, u16 value )
{ {
return (var & 0xFFFF0000) | writeval; ((u16*)&src)[1] = value;
return src;
} }
static __forceinline u32 SetLoWord( u32& src, u16 value )
static u32 SetHiWord( u32 var, u16 writeval )
{ {
return (var & 0x0000FFFF) | (writeval<<16); ((u16*)&src)[0] = value;
return src;
}
static __forceinline s32 SetHiWord( s32& src, u16 value )
{
((u16*)&src)[1] = value;
return src;
}
static __forceinline s32 SetLoWord( s32& src, u16 value )
{
((u16*)&src)[0] = value;
return src;
}
static __forceinline u16 GetHiWord( u32& src )
{
return ((u16*)&src)[1];
}
static __forceinline u16 GetLoWord( u32& src )
{
return ((u16*)&src)[0];
}
static __forceinline u16 GetHiWord( s32& src )
{
return ((u16*)&src)[1];
}
static __forceinline u16 GetLoWord( s32& src )
{
return ((u16*)&src)[0];
} }
__forceinline void SPU2_FastWrite( u32 rmem, u16 value ) __forceinline void SPU2_FastWrite( u32 rmem, u16 value )
@ -637,7 +760,9 @@ __forceinline void SPU2_FastWrite( u32 rmem, u16 value )
case 0: //VOLL (Volume L) case 0: //VOLL (Volume L)
case 1: //VOLR (Volume R) case 1: //VOLR (Volume R)
{ {
V_Volume& thisvol = (param==0) ? thisvoice.VolumeL : thisvoice.VolumeR; V_VolumeSlide& thisvol = (param==0) ? thisvoice.Volume.Left : thisvoice.Volume.Right;
thisvol.Reg_VOL = value;
if (value & 0x8000) // +Lin/-Lin/+Exp/-Exp if (value & 0x8000) // +Lin/-Lin/+Exp/-Exp
{ {
thisvol.Mode = (value & 0xF000)>>12; thisvol.Mode = (value & 0xF000)>>12;
@ -649,11 +774,10 @@ __forceinline void SPU2_FastWrite( u32 rmem, u16 value )
// Volumes range from 0x3fff to 0x7fff, with 0x4000 serving as // Volumes range from 0x3fff to 0x7fff, with 0x4000 serving as
// the "sign" bit, so a simple bitwise extension will do the trick: // the "sign" bit, so a simple bitwise extension will do the trick:
thisvol.Value = GetVol32( value<<1 ); thisvol.RegSet( value<<1 );
thisvol.Mode = 0; thisvol.Mode = 0;
thisvol.Increment = 0; thisvol.Increment = 0;
} }
thisvol.Reg_VOL = value;
} }
break; break;
@ -677,8 +801,8 @@ __forceinline void SPU2_FastWrite( u32 rmem, u16 value )
ConLog( "* SPU2: Mysterious ADSR Volume Set to 0x%x", value ); ConLog( "* SPU2: Mysterious ADSR Volume Set to 0x%x", value );
break; break;
case 6: thisvoice.VolumeL.Value = GetVol32( value ); break; case 6: thisvoice.Volume.Left.RegSet( value ); break;
case 7: thisvoice.VolumeR.Value = GetVol32( value ); break; case 7: thisvoice.Volume.Right.RegSet( value ); break;
jNO_DEFAULT; jNO_DEFAULT;
} }
@ -727,6 +851,15 @@ __forceinline void SPU2_FastWrite( u32 rmem, u16 value )
*(regtable[mem>>1]) = value; *(regtable[mem>>1]) = value;
UpdateSpdifMode(); UpdateSpdifMode();
} }
else if( mem >= R_FB_SRC_A && mem < REG_A_EEA )
{
// Signal to the Reverb code that the effects buffers need to be re-aligned.
// This is both simple, efficient, and safe, since we only want to re-align
// buffers after both hi and lo words have been written.
*(regtable[mem>>1]) = value;
Cores[core].RevBuffers.NeedsUpdated = true;
}
else else
{ {
switch(omem) switch(omem)
@ -783,22 +916,22 @@ __forceinline void SPU2_FastWrite( u32 rmem, u16 value )
case REG_S_PMON: case REG_S_PMON:
vx=2; for (vc=1;vc<16;vc++) { Cores[core].Voices[vc].Modulated=(s8)((value & vx)/vx); vx<<=1; } vx=2; for (vc=1;vc<16;vc++) { Cores[core].Voices[vc].Modulated=(s8)((value & vx)/vx); vx<<=1; }
Cores[core].Regs.PMON = SetLoWord( Cores[core].Regs.PMON, value ); SetLoWord( Cores[core].Regs.PMON, value );
break; break;
case (REG_S_PMON + 2): case (REG_S_PMON + 2):
vx=1; for (vc=16;vc<24;vc++) { Cores[core].Voices[vc].Modulated=(s8)((value & vx)/vx); vx<<=1; } vx=1; for (vc=16;vc<24;vc++) { Cores[core].Voices[vc].Modulated=(s8)((value & vx)/vx); vx<<=1; }
Cores[core].Regs.PMON = SetHiWord( Cores[core].Regs.PMON, value ); SetHiWord( Cores[core].Regs.PMON, value );
break; break;
case REG_S_NON: case REG_S_NON:
vx=1; for (vc=0;vc<16;vc++) { Cores[core].Voices[vc].Noise=(s8)((value & vx)/vx); vx<<=1; } vx=1; for (vc=0;vc<16;vc++) { Cores[core].Voices[vc].Noise=(s8)((value & vx)/vx); vx<<=1; }
Cores[core].Regs.NON = SetLoWord( Cores[core].Regs.NON, value ); SetLoWord( Cores[core].Regs.NON, value );
break; break;
case (REG_S_NON + 2): case (REG_S_NON + 2):
vx=1; for (vc=16;vc<24;vc++) { Cores[core].Voices[vc].Noise=(s8)((value & vx)/vx); vx<<=1; } vx=1; for (vc=16;vc<24;vc++) { Cores[core].Voices[vc].Noise=(s8)((value & vx)/vx); vx<<=1; }
Cores[core].Regs.NON = SetHiWord( Cores[core].Regs.NON, value ); SetHiWord( Cores[core].Regs.NON, value );
break; break;
// Games like to repeatedly write these regs over and over with the same value, hence // Games like to repeatedly write these regs over and over with the same value, hence
@ -895,26 +1028,23 @@ __forceinline void SPU2_FastWrite( u32 rmem, u16 value )
// Reverb Start and End Address Writes! // Reverb Start and End Address Writes!
// * Yes, these are backwards from all the volumes -- the hiword comes FIRST (wtf!) // * Yes, these are backwards from all the volumes -- the hiword comes FIRST (wtf!)
// * End position is a hiword only! Lowword is always ffff. // * End position is a hiword only! Loword is always ffff.
// * The Reverb buffer position resets on writes to StartA. It probably resets // * The Reverb buffer position resets on writes to StartA. It probably resets
// on writes to End too. Docs don't say, but they're for PSX, which couldn't // on writes to End too. Docs don't say, but they're for PSX, which couldn't
// change the end address anyway. // change the end address anyway.
case REG_A_ESA: case REG_A_ESA:
Cores[core].EffectsStartA = (Cores[core].EffectsStartA & 0x0000FFFF) | (value<<16); SetHiWord( Cores[core].EffectsStartA, value );
Cores[core].ReverbX = 0;
Cores[core].UpdateEffectsBufferSize(); Cores[core].UpdateEffectsBufferSize();
break; break;
case (REG_A_ESA + 2): case (REG_A_ESA + 2):
Cores[core].EffectsStartA = (Cores[core].EffectsStartA & 0xFFFF0000) | value; SetLoWord( Cores[core].EffectsStartA, value );
Cores[core].ReverbX = 0;
Cores[core].UpdateEffectsBufferSize(); Cores[core].UpdateEffectsBufferSize();
break; break;
case REG_A_EEA: case REG_A_EEA:
Cores[core].EffectsEndA = ((u32)value<<16) | 0xFFFF; Cores[core].EffectsEndA = ((u32)value<<16) | 0xFFFF;
Cores[core].ReverbX = 0;
Cores[core].UpdateEffectsBufferSize(); Cores[core].UpdateEffectsBufferSize();
break; break;
@ -923,7 +1053,7 @@ __forceinline void SPU2_FastWrite( u32 rmem, u16 value )
case REG_P_MVOLL: case REG_P_MVOLL:
case REG_P_MVOLR: case REG_P_MVOLR:
{ {
V_Volume& thisvol = (omem==REG_P_MVOLL) ? Cores[core].MasterL : Cores[core].MasterR; V_VolumeSlide& thisvol = (omem==REG_P_MVOLL) ? Cores[core].MasterVol.Left : Cores[core].MasterVol.Right;
if( value & 0x8000 ) // +Lin/-Lin/+Exp/-Exp if( value & 0x8000 ) // +Lin/-Lin/+Exp/-Exp
{ {
@ -945,27 +1075,27 @@ __forceinline void SPU2_FastWrite( u32 rmem, u16 value )
break; break;
case REG_P_EVOLL: case REG_P_EVOLL:
Cores[core].FxL = GetVol32( value ); Cores[core].FxVol.Left = GetVol32( value );
break; break;
case REG_P_EVOLR: case REG_P_EVOLR:
Cores[core].FxR = GetVol32( value ); Cores[core].FxVol.Right = GetVol32( value );
break; break;
case REG_P_AVOLL: case REG_P_AVOLL:
Cores[core].ExtL = GetVol32( value ); Cores[core].ExtVol.Left = GetVol32( value );
break; break;
case REG_P_AVOLR: case REG_P_AVOLR:
Cores[core].ExtR = GetVol32( value ); Cores[core].ExtVol.Right = GetVol32( value );
break; break;
case REG_P_BVOLL: case REG_P_BVOLL:
Cores[core].InpL = GetVol32( value ); Cores[core].InpVol.Left = GetVol32( value );
break; break;
case REG_P_BVOLR: case REG_P_BVOLR:
Cores[core].InpR = GetVol32( value ); Cores[core].InpVol.Right = GetVol32( value );
break; break;
case REG_S_ADMAS: case REG_S_ADMAS:
@ -1012,7 +1142,7 @@ void StartVoices(int core, u32 value)
(thisvc.WetL)?"+":"-",(thisvc.WetR)?"+":"-", (thisvc.WetL)?"+":"-",(thisvc.WetR)?"+":"-",
*(u8*)GetMemPtr(thisvc.StartA),*(u8 *)GetMemPtr((thisvc.StartA)+1), *(u8*)GetMemPtr(thisvc.StartA),*(u8 *)GetMemPtr((thisvc.StartA)+1),
thisvc.Pitch, thisvc.Pitch,
thisvc.VolumeL.Value,thisvc.VolumeR.Value, thisvc.Volume.Left.Value,thisvc.Volume.Right.Value,
thisvc.ADSR.Reg_ADSR1,thisvc.ADSR.Reg_ADSR2); thisvc.ADSR.Reg_ADSR1,thisvc.ADSR.Reg_ADSR2);
} }
} }

View File

@ -182,21 +182,25 @@ extern void DspUpdate(); // to let the Dsp process window messages
extern void RecordStart(); extern void RecordStart();
extern void RecordStop(); extern void RecordStop();
extern void RecordWrite(s16 left, s16 right); extern void RecordWrite( const StereoOut16& sample );
extern void UpdateSpdifMode(); extern void UpdateSpdifMode();
extern void LowPassFilterInit(); extern void LowPassFilterInit();
extern void InitADSR(); extern void InitADSR();
extern void CalculateADSR( V_Voice& vc ); extern void CalculateADSR( V_Voice& vc );
extern void __fastcall ReadInput( V_Core& thiscore, StereoOut32& PData );
////////////////////////////// //////////////////////////////
// The Mixer Section // // The Mixer Section //
////////////////////////////// //////////////////////////////
extern void Mix(); extern void Mix();
extern s32 clamp_mix(s32 x, u8 bitshift=0); extern s32 clamp_mix( s32 x, u8 bitshift=0 );
extern void clamp_mix( StereoOut32& sample, u8 bitshift=0 );
extern void Reverb_AdvanceBuffer( V_Core& thiscore ); extern void Reverb_AdvanceBuffer( V_Core& thiscore );
extern void DoReverb( V_Core& thiscore, s32& OutL, s32& OutR, s32 InL, s32 InR); extern StereoOut32 DoReverb( V_Core& thiscore, const StereoOut32& Input );
extern s32 MulShr32( s32 srcval, s32 mulval ); extern s32 MulShr32( s32 srcval, s32 mulval );
//#define PCM24_S1_INTERLEAVE //#define PCM24_S1_INTERLEAVE

View File

@ -0,0 +1,333 @@
/* 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
*
*/
#include "spu2.h"
#include "SoundTouch/SoundTouch.h"
#include "SoundTouch/WavFile.h"
static soundtouch::SoundTouch* pSoundTouch = NULL;
static int ts_stats_stretchblocks = 0;
static int ts_stats_normalblocks = 0;
static int ts_stats_logcounter = 0;
// data prediction amount, used to "commit" data that hasn't
// finished timestretch processing.
s32 SndBuffer::m_predictData;
// records last buffer status (fill %, range -100 to 100, with 0 being 50% full)
float SndBuffer::lastPct;
float SndBuffer::lastEmergencyAdj;
float SndBuffer::cTempo = 1;
float SndBuffer::eTempo = 1;
int SndBuffer::freezeTempo = 0;
void SndBuffer::PredictDataWrite( int samples )
{
m_predictData += samples;
}
// Calculate the buffer status percentage.
// Returns range from -1.0 to 1.0
// 1.0 = buffer overflow!
// 0.0 = buffer nominal (50% full)
// -1.0 = buffer underflow!
float SndBuffer::GetStatusPct()
{
// Get the buffer status of the output driver too, so that we can
// obtain a more accurate overall buffer status.
int drvempty = mods[OutputModule]->GetEmptySampleCount(); // / 2;
//ConLog( "Data %d >>> driver: %d predict: %d\n", data, drvempty, predictData );
float result = (float)(m_data + m_predictData - drvempty) - (m_size/2);
result /= (m_size/2);
return result;
}
void SndBuffer::UpdateTempoChange()
{
if( --freezeTempo > 0 )
{
return;
}
float statusPct = GetStatusPct();
float pctChange = statusPct - lastPct;
float tempoChange;
float emergencyAdj = 0;
float newcee = cTempo; // workspace var. for cTempo
// IMPORTANT!
// If you plan to tweak these values, make sure you're using a release build
// OUTSIDE THE DEBUGGER to test it! The Visual Studio debugger can really cause
// erratic behavior in the audio buffers, and makes the timestretcher seem a
// lot more inconsistent than it really is.
// We have two factors.
// * Distance from nominal buffer status (50% full)
// * The change from previous update to this update.
// Prediction based on the buffer change:
// (linear seems to work better here)
tempoChange = pctChange * 0.75f;
if( statusPct * tempoChange < 0.0f )
{
// only apply tempo change if it is in synch with the buffer status.
// In other words, if the buffer is high (over 0%), and is decreasing,
// ignore it. It'll just muck things up.
tempoChange = 0;
}
// Sudden spikes in framerate can cause the nominal buffer status
// to go critical, in which case we have to enact an emergency
// stretch. The following cubic formulas do that. Values near
// the extremeites give much larger results than those near 0.
// And the value is added only this time, and does not accumulate.
// (otherwise a large value like this would cause problems down the road)
// Constants:
// Weight - weights the statusPct's "emergency" consideration.
// higher values here will make the buffer perform more drastic
// compensations at the outer edges of the buffer (at -75 or +75%
// or beyond, for example).
// Range - scales the adjustment to the given range (more or less).
// The actual range is dependent on the weight used, so if you increase
// Weight you'll usually want to decrease Range somewhat to compensate.
// Prediction based on the buffer fill status:
const float statusWeight = 2.99f;
const float statusRange = 0.068f;
// "non-emergency" deadzone: In this area stretching will be strongly discouraged.
// Note: due tot he nature of timestretch latency, it's always a wee bit harder to
// cope with low fps (underruns) tha it is high fps (overruns). So to help out a
// little, the low-end portions of this check are less forgiving than the high-sides.
if( cTempo < 0.965f || cTempo > 1.060f ||
pctChange < -0.38f || pctChange > 0.54f ||
statusPct < -0.32f || statusPct > 0.39f ||
eTempo < 0.89f || eTempo > 1.19f )
{
emergencyAdj = ( pow( statusPct*statusWeight, 3.0f ) * statusRange);
}
// Smooth things out by factoring our previous adjustment into this one.
// It helps make the system 'feel' a little smarter by giving it at least
// one packet worth of history to help work off of:
emergencyAdj = (emergencyAdj * 0.75f) + (lastEmergencyAdj * 0.25f );
lastEmergencyAdj = emergencyAdj;
lastPct = statusPct;
// Accumulate a fraction of the tempo change into the tempo itself.
// This helps the system run "smarter" to games that run consistently
// fast or slow by altering the base tempo to something closer to the
// game's active speed. In tests most games normalize within 2 seconds
// at 100ms latency, which is pretty good (larger buffers normalize even
// quicker).
newcee += newcee * (tempoChange+emergencyAdj) * 0.03f;
// Apply tempoChange as a scale of cTempo. That way the effect is proportional
// to the current tempo. (otherwise tempos rate of change at the extremes would
// be too drastic)
float newTempo = newcee + ( emergencyAdj * cTempo );
// ... and as a final optimization, only stretch if the new tempo is outside
// a nominal threshold. Keep this threshold check small, because it could
// cause some serious side effects otherwise. (enlarging the cTempo check above
// is usually better/safer)
if( newTempo < 0.970f || newTempo > 1.045f )
{
cTempo = (float)newcee;
if( newTempo < 0.10f ) newTempo = 0.10f;
else if( newTempo > 10.0f ) newTempo = 10.0f;
if( cTempo < 0.15f ) cTempo = 0.15f;
else if( cTempo > 7.5f ) cTempo = 7.5f;
pSoundTouch->setTempo( eTempo = (float)newTempo );
ts_stats_stretchblocks++;
/*ConLog(" * SPU2: [Nominal %d%%] [Emergency: %d%%] (baseTempo: %d%% ) (newTempo: %d%%) (buffer: %d%%)\n",
//(relation < 0.0) ? "Normalize" : "",
(int)(tempoChange * 100.0 * 0.03),
(int)(emergencyAdj * 100.0),
(int)(cTempo * 100.0),
(int)(newTempo * 100.0),
(int)(statusPct * 100.0)
);*/
}
else
{
// Nominal operation -- turn off stretching.
// note: eTempo 'slides' toward 1.0 for smoother audio and better
// protection against spikes.
if( cTempo != 1.0f )
{
cTempo = 1.0f;
eTempo = ( 1.0f + eTempo ) * 0.5f;
pSoundTouch->setTempo( eTempo );
}
else
{
if( eTempo != cTempo )
pSoundTouch->setTempo( eTempo=cTempo );
ts_stats_normalblocks++;
}
}
}
void SndBuffer::timeStretchUnderrun()
{
// timeStretcher failed it's job. We need to slow down the audio some.
cTempo -= (cTempo * 0.12f);
eTempo -= (eTempo * 0.30f);
if( eTempo < 0.1f ) eTempo = 0.1f;
pSoundTouch->setTempo( eTempo );
}
s32 SndBuffer::timeStretchOverrun()
{
// If we overran it means the timestretcher failed. We need to speed
// up audio playback.
cTempo += cTempo * 0.12f;
eTempo += eTempo * 0.40f;
if( eTempo > 7.5f ) eTempo = 7.5f;
pSoundTouch->setTempo( eTempo );
// Throw out just a little bit (two packets worth) to help
// give the TS some room to work:
return SndOutPacketSize*2;
}
static void CvtPacketToFloat( StereoOut32* srcdest )
{
StereoOutFloat* dest = (StereoOutFloat*)srcdest;
const StereoOut32* src = (StereoOut32*)srcdest;
for( uint i=0; i<SndOutPacketSize; ++i, ++dest, ++src )
*dest = (StereoOutFloat)*src;
}
// Parameter note: Size should always be a multiple of 128, thanks!
static void CvtPacketToInt( StereoOut32* srcdest, uint size )
{
jASSUME( (size & 127) == 0 );
const StereoOutFloat* src = (StereoOutFloat*)srcdest;
StereoOut32* dest = srcdest;
for( uint i=0; i<size; ++i, ++dest, ++src )
*dest = (StereoOut32)*src;
}
void SndBuffer::timeStretchWrite()
{
bool progress = false;
// data prediction helps keep the tempo adjustments more accurate.
// The timestretcher returns packets in belated "clump" form.
// Meaning that most of the time we'll get nothing back, and then
// suddenly we'll get several chunks back at once. Thus we use
// data prediction to make the timestretcher more responsive.
PredictDataWrite( (int)( SndOutPacketSize / eTempo ) );
CvtPacketToFloat( sndTempBuffer );
pSoundTouch->putSamples( (float*)sndTempBuffer, SndOutPacketSize );
int tempProgress;
while( tempProgress = pSoundTouch->receiveSamples( (float*)sndTempBuffer, SndOutPacketSize),
tempProgress != 0 )
{
// Hint: It's assumed that pSoundTouch will return chunks of 128 bytes (it always does as
// long as the SSE optimizations are enabled), which means we can do our own SSE opts here.
CvtPacketToInt( sndTempBuffer, tempProgress );
_WriteSamples( sndTempBuffer, tempProgress );
progress = true;
}
UpdateTempoChange();
if( MsgOverruns() )
{
if( progress )
{
if( ++ts_stats_logcounter > 300 )
{
ts_stats_logcounter = 0;
ConLog( " * SPU2 > Timestretch Stats > %d%% of packets stretched.\n",
( ts_stats_stretchblocks * 100 ) / ( ts_stats_normalblocks + ts_stats_stretchblocks ) );
ts_stats_normalblocks = 0;
ts_stats_stretchblocks = 0;
}
}
}
}
void SndBuffer::soundtouchInit()
{
pSoundTouch = new soundtouch::SoundTouch();
pSoundTouch->setSampleRate(SampleRate);
pSoundTouch->setChannels(2);
pSoundTouch->setSetting( SETTING_USE_QUICKSEEK, 0 );
pSoundTouch->setSetting( SETTING_USE_AA_FILTER, 0 );
pSoundTouch->setSetting( SETTING_SEQUENCE_MS, SoundtouchCfg::SequenceLenMS );
pSoundTouch->setSetting( SETTING_SEEKWINDOW_MS, SoundtouchCfg::SeekWindowMS );
pSoundTouch->setSetting( SETTING_OVERLAP_MS, SoundtouchCfg::OverlapMS );
pSoundTouch->setTempo(1);
// some timestretch management vars:
cTempo = 1.0;
eTempo = 1.0;
lastPct = 0;
lastEmergencyAdj = 0;
// just freeze tempo changes for a while at startup.
// the driver buffers are bogus anyway.
freezeTempo = 8;
m_predictData = 0;
}
void SndBuffer::soundtouchCleanup()
{
SAFE_DELETE_OBJ( pSoundTouch );
}

View File

@ -83,14 +83,16 @@ namespace WaveDump
} }
} }
void WriteCore( uint coreidx, CoreSourceType src, s16 left, s16 right ) void WriteCore( uint coreidx, CoreSourceType src, const StereoOut16& sample )
{ {
if( !IsDevBuild ) return; if( !IsDevBuild ) return;
if( m_CoreWav[coreidx][src] != NULL ) if( m_CoreWav[coreidx][src] != NULL )
{ m_CoreWav[coreidx][src]->write( (s16*)&sample, 2 );
s16 buffer[2] = { left, right }; }
m_CoreWav[coreidx][src]->write( buffer, 2 );
} void WriteCore( uint coreidx, CoreSourceType src, s16 left, s16 right )
{
WriteCore( coreidx, src, StereoOut16( left, right ) );
} }
} }
@ -116,10 +118,8 @@ void RecordStop()
SAFE_DELETE_OBJ( m_wavrecord ); SAFE_DELETE_OBJ( m_wavrecord );
} }
void RecordWrite(s16 left, s16 right) void RecordWrite( const StereoOut16& sample )
{ {
if( m_wavrecord == NULL ) return; if( m_wavrecord == NULL ) return;
m_wavrecord->write( (s16*)&sample, 2 );
s16 buffer[2] = { left, right };
m_wavrecord->write( buffer, 2 );
} }

View File

@ -33,30 +33,32 @@ static const int LATENCY_MIN = 40;
int AutoDMAPlayRate[2] = {0,0}; int AutoDMAPlayRate[2] = {0,0};
// MIXING // MIXING
int Interpolation=1; int Interpolation = 1;
/* values: /* values:
0: no interpolation (use nearest) 0: no interpolation (use nearest)
1. linear interpolation 1. linear interpolation
2. cubic interpolation 2. cubic interpolation
*/ */
bool EffectsDisabled=false; bool EffectsDisabled = false;
// OUTPUT // OUTPUT
int SndOutLatencyMS=160; int SndOutLatencyMS = 160;
bool timeStretchDisabled=false; bool timeStretchDisabled = false;
u32 OutputModule=0; //OUTPUT_DSOUND; u32 OutputModule = 0;
CONFIG_DSOUNDOUT Config_DSoundOut; CONFIG_DSOUNDOUT Config_DSoundOut;
CONFIG_WAVEOUT Config_WaveOut; CONFIG_WAVEOUT Config_WaveOut;
CONFIG_XAUDIO2 Config_XAudio2; CONFIG_XAUDIO2 Config_XAudio2;
// DSP // DSP
bool dspPluginEnabled=false; bool dspPluginEnabled = false;
int dspPluginModule=0; int dspPluginModule = 0;
wchar_t dspPlugin[256]; wchar_t dspPlugin[256];
bool StereoExpansionDisabled = true;
/*****************************************************************************/ /*****************************************************************************/
void ReadSettings() void ReadSettings()
@ -69,7 +71,8 @@ void ReadSettings()
timeStretchDisabled = CfgReadBool( _T("OUTPUT"), _T("Disable_Timestretch"), false ); timeStretchDisabled = CfgReadBool( _T("OUTPUT"), _T("Disable_Timestretch"), false );
EffectsDisabled = CfgReadBool( _T("MIXING"), _T("Disable_Effects"), false ); EffectsDisabled = CfgReadBool( _T("MIXING"), _T("Disable_Effects"), false );
SndOutLatencyMS=CfgReadInt(_T("OUTPUT"),_T("Latency"), 160); StereoExpansionDisabled = CfgReadBool( _T("OUTPUT"), _T("Disable_StereoExpansion"), false );
SndOutLatencyMS = CfgReadInt(_T("OUTPUT"),_T("Latency"), 160);
wchar_t omodid[128]; wchar_t omodid[128];
CfgReadStr( _T("OUTPUT"), _T("Output_Module"), omodid, 127, XAudio2Out->GetIdent() ); CfgReadStr( _T("OUTPUT"), _T("Output_Module"), omodid, 127, XAudio2Out->GetIdent() );
@ -118,9 +121,10 @@ void WriteSettings()
CfgWriteBool(_T("MIXING"),_T("Disable_Effects"),EffectsDisabled); CfgWriteBool(_T("MIXING"),_T("Disable_Effects"),EffectsDisabled);
CfgWriteStr(_T("OUTPUT"),_T("Output_Module"),mods[OutputModule]->GetIdent() ); CfgWriteStr(_T("OUTPUT"),_T("Output_Module"), mods[OutputModule]->GetIdent() );
CfgWriteInt(_T("OUTPUT"),_T("Latency"),SndOutLatencyMS); CfgWriteInt(_T("OUTPUT"),_T("Latency"), SndOutLatencyMS);
CfgWriteBool(_T("OUTPUT"),_T("Disable_Timestretch"),timeStretchDisabled); CfgWriteBool(_T("OUTPUT"),_T("Disable_Timestretch"), timeStretchDisabled);
CfgWriteBool(_T("OUTPUT"),_T("Disable_StereoExpansion"), StereoExpansionDisabled);
if( Config_DSoundOut.Device.empty() ) Config_DSoundOut.Device = _T("default"); if( Config_DSoundOut.Device.empty() ) Config_DSoundOut.Device = _T("default");
if( Config_WaveOut.Device.empty() ) Config_WaveOut.Device = _T("default"); if( Config_WaveOut.Device.empty() ) Config_WaveOut.Device = _T("default");
@ -181,6 +185,7 @@ BOOL CALLBACK ConfigProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
EnableWindow( GetDlgItem( hWnd, IDC_OPEN_CONFIG_DEBUG ), DebugEnabled ); EnableWindow( GetDlgItem( hWnd, IDC_OPEN_CONFIG_DEBUG ), DebugEnabled );
SET_CHECK(IDC_EFFECTS_DISABLE, EffectsDisabled); SET_CHECK(IDC_EFFECTS_DISABLE, EffectsDisabled);
SET_CHECK(IDC_EXPANSION_DISABLE,StereoExpansionDisabled);
SET_CHECK(IDC_TS_DISABLE, timeStretchDisabled); SET_CHECK(IDC_TS_DISABLE, timeStretchDisabled);
SET_CHECK(IDC_DEBUG_ENABLE, DebugEnabled); SET_CHECK(IDC_DEBUG_ENABLE, DebugEnabled);
SET_CHECK(IDC_DSP_ENABLE, dspPluginEnabled); SET_CHECK(IDC_DSP_ENABLE, dspPluginEnabled);
@ -212,7 +217,7 @@ BOOL CALLBACK ConfigProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
break; break;
case IDC_OUTCONF: case IDC_OUTCONF:
SndConfigure( hWnd, SndBuffer::Configure( hWnd,
(int)SendMessage(GetDlgItem(hWnd,IDC_OUTPUT),CB_GETCURSEL,0,0) (int)SendMessage(GetDlgItem(hWnd,IDC_OUTPUT),CB_GETCURSEL,0,0)
); );
break; break;
@ -234,6 +239,7 @@ BOOL CALLBACK ConfigProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
HANDLE_CHECK(IDC_EFFECTS_DISABLE,EffectsDisabled); HANDLE_CHECK(IDC_EFFECTS_DISABLE,EffectsDisabled);
HANDLE_CHECK(IDC_DSP_ENABLE,dspPluginEnabled); HANDLE_CHECK(IDC_DSP_ENABLE,dspPluginEnabled);
HANDLE_CHECK(IDC_EXPANSION_DISABLE,StereoExpansionDisabled);
HANDLE_CHECKNB(IDC_TS_DISABLE,timeStretchDisabled); HANDLE_CHECKNB(IDC_TS_DISABLE,timeStretchDisabled);
EnableWindow( GetDlgItem( hWnd, IDC_OPEN_CONFIG_SOUNDTOUCH ), !timeStretchDisabled ); EnableWindow( GetDlgItem( hWnd, IDC_OPEN_CONFIG_SOUNDTOUCH ), !timeStretchDisabled );
break; break;

View File

@ -82,6 +82,7 @@ extern int dspPluginModule;
extern bool dspPluginEnabled; extern bool dspPluginEnabled;
extern bool timeStretchDisabled; extern bool timeStretchDisabled;
extern bool StereoExpansionDisabled;
class SoundtouchCfg class SoundtouchCfg
{ {
@ -120,12 +121,9 @@ struct CONFIG_XAUDIO2
std::wstring Device; std::wstring Device;
s8 NumBuffers; s8 NumBuffers;
bool ExpandTo51;
CONFIG_XAUDIO2() : CONFIG_XAUDIO2() :
Device(), Device(),
NumBuffers( 2 ), NumBuffers( 2 )
ExpandTo51( true )
{ {
} }
}; };

View File

@ -144,8 +144,8 @@ void UpdateDebugDialog()
SetDCBrushColor (hdc,RGB( 0,255, 0)); SetDCBrushColor (hdc,RGB( 0,255, 0));
int vl = abs(((vc.VolumeL.Value >> 16) * 24) >> 15); int vl = abs(((vc.Volume.Left.Value >> 16) * 24) >> 15);
int vr = abs(((vc.VolumeR.Value >> 16) * 24) >> 15); int vr = abs(((vc.Volume.Right.Value >> 16) * 24) >> 15);
FillRectangle(hdc,IX+38,IY+26 - vl, 4, vl); FillRectangle(hdc,IX+38,IY+26 - vl, 4, vl);
FillRectangle(hdc,IX+42,IY+26 - vr, 4, vr); FillRectangle(hdc,IX+42,IY+26 - vr, 4, vr);

View File

@ -23,6 +23,7 @@
#include "spu2.h" #include "spu2.h"
#include "dialogs.h" #include "dialogs.h"
#define DIRECTSOUND_VERSION 0x1000
#include <dsound.h> #include <dsound.h>
static ds_device_data devices[32]; static ds_device_data devices[32];
@ -37,7 +38,6 @@ private:
static const int PacketsPerBuffer = 1; static const int PacketsPerBuffer = 1;
static const int BufferSize = SndOutPacketSize * PacketsPerBuffer; static const int BufferSize = SndOutPacketSize * PacketsPerBuffer;
static const int BufferSizeBytes = BufferSize << 1;
u32 numBuffers; // cached copy of our configuration setting. u32 numBuffers; // cached copy of our configuration setting.
@ -57,25 +57,26 @@ private:
HANDLE waitEvent; HANDLE waitEvent;
SndBuffer *buff; template< typename T >
static DWORD CALLBACK RThread( DSound* obj )
static DWORD CALLBACK RThread(DSound*obj)
{ {
return obj->Thread(); return obj->Thread<T>();
} }
template< typename T >
DWORD CALLBACK Thread() DWORD CALLBACK Thread()
{ {
static const int BufferSizeBytes = BufferSize * sizeof( T );
while( dsound_running ) while( dsound_running )
{ {
u32 rv = WaitForMultipleObjects(numBuffers,buffer_events,FALSE,200); u32 rv = WaitForMultipleObjects(numBuffers,buffer_events,FALSE,200);
s16* p1, *oldp1; T* p1, *oldp1;
LPVOID p2; LPVOID p2;
DWORD s1,s2; DWORD s1,s2;
u32 poffset=BufferSizeBytes * rv; u32 poffset = BufferSizeBytes * rv;
if( FAILED(buffer->Lock(poffset,BufferSizeBytes,(LPVOID*)&p1,&s1,&p2,&s2,0) ) ) if( FAILED(buffer->Lock(poffset,BufferSizeBytes,(LPVOID*)&p1,&s1,&p2,&s2,0) ) )
{ {
@ -86,9 +87,9 @@ private:
oldp1 = p1; oldp1 = p1;
for(int p=0; p<PacketsPerBuffer; p++, p1+=SndOutPacketSize ) for(int p=0; p<PacketsPerBuffer; p++, p1+=SndOutPacketSize )
buff->ReadSamples( p1 ); SndBuffer::ReadSamples( p1 );
buffer->Unlock(oldp1,s1,p2,s2); buffer->Unlock( oldp1, s1, p2, s2 );
// Set the write pointer to the beginning of the next block. // Set the write pointer to the beginning of the next block.
myLastWrite = (poffset + BufferSizeBytes) & ~BufferSizeBytes; myLastWrite = (poffset + BufferSizeBytes) & ~BufferSizeBytes;
@ -97,9 +98,8 @@ private:
} }
public: public:
s32 Init(SndBuffer *sb) s32 Init()
{ {
buff = sb;
numBuffers = Config_DSoundOut.NumBuffers; numBuffers = Config_DSoundOut.NumBuffers;
// //
@ -130,19 +130,27 @@ public:
if( FAILED(dsound->SetCooperativeLevel(GetDesktopWindow(),DSSCL_PRIORITY)) ) if( FAILED(dsound->SetCooperativeLevel(GetDesktopWindow(),DSSCL_PRIORITY)) )
throw std::runtime_error( "DirectSound Error: Cooperative level could not be set." ); throw std::runtime_error( "DirectSound Error: Cooperative level could not be set." );
// Determine the user's speaker configuration, and select an expansion option as needed.
// FAIL : Directsound doesn't appear to support audio expansion >_<
DWORD speakerConfig = 2;
//dsound->GetSpeakerConfig( &speakerConfig );
IDirectSoundBuffer* buffer_; IDirectSoundBuffer* buffer_;
DSBUFFERDESC desc; DSBUFFERDESC desc;
// Set up WAV format structure. // Set up WAV format structure.
memset(&wfx, 0, sizeof(WAVEFORMATEX)); memset(&wfx, 0, sizeof(WAVEFORMATEX));
wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nSamplesPerSec = SampleRate; wfx.nSamplesPerSec = SampleRate;
wfx.nChannels=2; wfx.nChannels = speakerConfig;
wfx.wBitsPerSample = 16; wfx.wBitsPerSample = 16;
wfx.nBlockAlign = 2*2; wfx.nBlockAlign = 2*speakerConfig;
wfx.nAvgBytesPerSec = SampleRate * wfx.nBlockAlign; wfx.nAvgBytesPerSec = SampleRate * wfx.nBlockAlign;
wfx.cbSize=0; wfx.cbSize = 0;
uint BufferSizeBytes = BufferSize * wfx.nBlockAlign;
// Set up DSBUFFERDESC structure. // Set up DSBUFFERDESC structure.
@ -155,12 +163,13 @@ public:
desc.dwFlags |= DSBCAPS_LOCSOFTWARE; desc.dwFlags |= DSBCAPS_LOCSOFTWARE;
desc.dwFlags |= DSBCAPS_GLOBALFOCUS; desc.dwFlags |= DSBCAPS_GLOBALFOCUS;
if( FAILED(dsound->CreateSoundBuffer(&desc,&buffer_,0) ) || if( FAILED(dsound->CreateSoundBuffer(&desc,&buffer_,0) ) )
FAILED(buffer_->QueryInterface(IID_IDirectSoundBuffer8,(void**)&buffer)) ) throw std::runtime_error( "DirectSound Error: Interface could not be queried." );
if( FAILED(buffer_->QueryInterface(IID_IDirectSoundBuffer8,(void**)&buffer)) )
throw std::runtime_error( "DirectSound Error: Interface could not be queried." ); throw std::runtime_error( "DirectSound Error: Interface could not be queried." );
buffer_->Release(); buffer_->Release();
verifyc( buffer->QueryInterface(IID_IDirectSoundNotify8,(void**)&buffer_notify) ); verifyc( buffer->QueryInterface(IID_IDirectSoundNotify8,(void**)&buffer_notify) );
DSBPOSITIONNOTIFY not[MAX_BUFFER_COUNT]; DSBPOSITIONNOTIFY not[MAX_BUFFER_COUNT];
@ -171,9 +180,9 @@ public:
// it was needed for some quirky driver? Theoretically we want the notification as soon // it was needed for some quirky driver? Theoretically we want the notification as soon
// as possible after the buffer has finished playing. // as possible after the buffer has finished playing.
buffer_events[i]=CreateEvent(NULL,FALSE,FALSE,NULL); buffer_events[i] = CreateEvent(NULL,FALSE,FALSE,NULL);
not[i].dwOffset=(wfx.nBlockAlign*2 + BufferSizeBytes*(i+1))%desc.dwBufferBytes; not[i].dwOffset = (wfx.nBlockAlign + BufferSizeBytes*(i+1)) % desc.dwBufferBytes;
not[i].hEventNotify=buffer_events[i]; not[i].hEventNotify = buffer_events[i];
} }
buffer_notify->SetNotificationPositions(numBuffers,not); buffer_notify->SetNotificationPositions(numBuffers,not);
@ -191,9 +200,9 @@ public:
// Start Thread // Start Thread
myLastWrite = 0; myLastWrite = 0;
dsound_running=true; dsound_running = true;
thread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)RThread,this,0,&tid); thread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)RThread<StereoOut16>,this,0,&tid);
SetThreadPriority(thread,THREAD_PRIORITY_TIME_CRITICAL); SetThreadPriority(thread,THREAD_PRIORITY_ABOVE_NORMAL);
return 0; return 0;
} }

View File

@ -38,7 +38,6 @@ private:
class BaseStreamingVoice : public IXAudio2VoiceCallback class BaseStreamingVoice : public IXAudio2VoiceCallback
{ {
protected: protected:
SndBuffer* m_sndout;
IXAudio2SourceVoice* pSourceVoice; IXAudio2SourceVoice* pSourceVoice;
s16* qbuffer; s16* qbuffer;
@ -69,11 +68,10 @@ private:
DeleteCriticalSection( &cs ); DeleteCriticalSection( &cs );
} }
BaseStreamingVoice( SndBuffer* sb, uint numChannels ) : BaseStreamingVoice( uint numChannels ) :
m_sndout( sb ),
m_nBuffers( Config_XAudio2.NumBuffers ), m_nBuffers( Config_XAudio2.NumBuffers ),
m_nChannels( numChannels ), m_nChannels( numChannels ),
m_BufferSize( SndOutPacketSize/2 * m_nChannels * PacketsPerBuffer ), m_BufferSize( SndOutPacketSize * m_nChannels * PacketsPerBuffer ),
m_BufferSizeBytes( m_BufferSize * sizeof(s16) ) m_BufferSizeBytes( m_BufferSize * sizeof(s16) )
{ {
} }
@ -133,18 +131,25 @@ private:
LeaveCriticalSection( &cs ); LeaveCriticalSection( &cs );
} }
STDMETHOD_(void, OnVoiceProcessingPassStart) () {}
STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) { };
STDMETHOD_(void, OnVoiceProcessingPassEnd) () {}
STDMETHOD_(void, OnStreamEnd) () {}
STDMETHOD_(void, OnBufferStart) ( void* ) {}
STDMETHOD_(void, OnLoopEnd) ( void* ) {}
STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) { };
}; };
template< typename T >
class StreamingVoice_Stereo : public BaseStreamingVoice class StreamingVoice : public BaseStreamingVoice
{ {
public: public:
StreamingVoice_Stereo( SndBuffer* sb, IXAudio2* pXAudio2 ) : StreamingVoice( IXAudio2* pXAudio2 ) :
BaseStreamingVoice( sb, 2 ) BaseStreamingVoice( sizeof(T) / sizeof( s16 ) )
{ {
} }
virtual ~StreamingVoice_Stereo() {} virtual ~StreamingVoice() {}
void Init( IXAudio2* pXAudio2 ) void Init( IXAudio2* pXAudio2 )
{ {
@ -152,11 +157,6 @@ private:
} }
protected: protected:
STDMETHOD_(void, OnVoiceProcessingPassStart) () {}
STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) { };
STDMETHOD_(void, OnVoiceProcessingPassEnd) () {}
STDMETHOD_(void, OnStreamEnd) () {}
STDMETHOD_(void, OnBufferStart) ( void* ) {}
STDMETHOD_(void, OnBufferEnd) ( void* context ) STDMETHOD_(void, OnBufferEnd) ( void* context )
{ {
EnterCriticalSection( &cs ); EnterCriticalSection( &cs );
@ -164,10 +164,10 @@ private:
// All of these checks are necessary because XAudio2 is wonky shizat. // All of these checks are necessary because XAudio2 is wonky shizat.
if( pSourceVoice == NULL || context == NULL ) return; if( pSourceVoice == NULL || context == NULL ) return;
s16* qb = (s16*)context; T* qb = (T*)context;
for(int p=0; p<PacketsPerBuffer; p++, qb+=SndOutPacketSize ) for(int p=0; p<PacketsPerBuffer; p++, qb+=SndOutPacketSize )
m_sndout->ReadSamples( qb ); SndBuffer::ReadSamples( qb );
XAUDIO2_BUFFER buf = {0}; XAUDIO2_BUFFER buf = {0};
buf.AudioBytes = m_BufferSizeBytes; buf.AudioBytes = m_BufferSizeBytes;
@ -177,83 +177,6 @@ private:
pSourceVoice->SubmitSourceBuffer( &buf ); pSourceVoice->SubmitSourceBuffer( &buf );
LeaveCriticalSection( &cs ); LeaveCriticalSection( &cs );
} }
STDMETHOD_(void, OnLoopEnd) ( void* ) {}
STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) { };
};
class StreamingVoice_Surround51 : public BaseStreamingVoice
{
public:
//LPF_data m_lpf_left;
//LPF_data m_lpf_right;
s32 buffer[2 * SndOutPacketSize * PacketsPerBuffer];
StreamingVoice_Surround51( SndBuffer* sb, IXAudio2* pXAudio2 ) :
BaseStreamingVoice( sb, 6 )
//m_lpf_left( Config_XAudio2.LowpassLFE, SampleRate ),
//m_lpf_right( Config_XAudio2.LowpassLFE, SampleRate )
{
}
virtual ~StreamingVoice_Surround51() {}
void Init( IXAudio2* pXAudio2 )
{
_init( pXAudio2, SPEAKER_5POINT1 );
}
protected:
STDMETHOD_(void, OnVoiceProcessingPassStart) () {}
STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) { };
STDMETHOD_(void, OnVoiceProcessingPassEnd) () {}
STDMETHOD_(void, OnStreamEnd) () {}
STDMETHOD_(void, OnBufferStart) ( void* ) {}
STDMETHOD_(void, OnBufferEnd) ( void* context )
{
EnterCriticalSection( &cs );
// All of these checks are necessary because XAudio2 is wonky shizat.
if( pSourceVoice == NULL || context == NULL ) return;
s16* qb = (s16*)context;
for(int p=0; p<PacketsPerBuffer; p++ )
{
m_sndout->ReadSamples( buffer );
const s32* src = buffer;
for( int i=0; i<SndOutPacketSize/2; i++, qb+=6, src+=2 )
{
// Left and right Front!
qb[0] = SndScaleVol( src[0] );
qb[1] = SndScaleVol( src[1] );
// Center and Subwoofer/LFE -->
// This method is simple and sounds nice. It relies on the speaker/soundcard
// systems do to their own low pass / crossover. Manual lowpass is wasted effort
// and can't match solid state results anyway.
qb[2] = qb[3] = (src[0] + src[1]) >> (SndOutVolumeShift+1);
// Left and right rear!
qb[4] = SndScaleVol( src[0] );
qb[5] = SndScaleVol( src[1] );
}
}
XAUDIO2_BUFFER buf = { 0 };
buf.AudioBytes = m_BufferSizeBytes;
buf.pAudioData = (BYTE*)context;
buf.pContext = context;
pSourceVoice->SubmitSourceBuffer( &buf );
LeaveCriticalSection( &cs );
}
STDMETHOD_(void, OnLoopEnd) ( void* ) {}
STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) { };
}; };
@ -263,7 +186,7 @@ private:
public: public:
s32 Init( SndBuffer *sb ) s32 Init()
{ {
HRESULT hr; HRESULT hr;
@ -273,9 +196,8 @@ public:
CoInitializeEx( NULL, COINIT_MULTITHREADED ); CoInitializeEx( NULL, COINIT_MULTITHREADED );
UINT32 flags = 0; UINT32 flags = 0;
#ifdef _DEBUG if( IsDebugBuild )
flags |= XAUDIO2_DEBUG_ENGINE; flags |= XAUDIO2_DEBUG_ENGINE;
#endif
if ( FAILED(hr = XAudio2Create( &pXAudio2, flags ) ) ) if ( FAILED(hr = XAudio2Create( &pXAudio2, flags ) ) )
{ {
@ -298,18 +220,47 @@ public:
return -1; return -1;
} }
if( Config_XAudio2.ExpandTo51 && deviceDetails.OutputFormat.Format.nChannels >= 6 ) if( StereoExpansionDisabled )
deviceDetails.OutputFormat.Format.nChannels = 2;
// Any windows driver should support stereo at the software level, I should think!
jASSUME( deviceDetails.OutputFormat.Format.nChannels > 1 );
switch( deviceDetails.OutputFormat.Format.nChannels )
{ {
ConLog( "* SPU2 > 5.1 speaker expansion enabled." ); case 2:
voiceContext = new StreamingVoice_Surround51( sb, pXAudio2 ); ConLog( "* SPU2 > Using normal 2 speaker stereo output." );
} voiceContext = new StreamingVoice<StereoOut16>( pXAudio2 );
else break;
{
voiceContext = new StreamingVoice_Stereo( sb, pXAudio2 ); case 3:
ConLog( "* SPU2 > 2.1 speaker expansion enabled." );
voiceContext = new StreamingVoice<Stereo21Out16>( pXAudio2 );
break;
case 4:
ConLog( "* SPU2 > 4 speaker expansion enabled [quadraphenia]" );
voiceContext = new StreamingVoice<StereoQuadOut16>( pXAudio2 );
break;
case 5:
ConLog( "* SPU2 > 4.1 speaker expansion enabled." );
voiceContext = new StreamingVoice<Stereo41Out16>( pXAudio2 );
break;
case 6:
case 7:
ConLog( "* SPU2 > 5.1 speaker expansion enabled." );
voiceContext = new StreamingVoice<Stereo51Out16>( pXAudio2 );
break;
default: // anything 8 or more gets the 7.1 treatment!
ConLog( "* SPU2 > 7.1 speaker expansion enabled." );
voiceContext = new StreamingVoice<Stereo51Out16>( pXAudio2 );
break;
} }
voiceContext->Init( pXAudio2 ); voiceContext->Init( pXAudio2 );
return 0; return 0;
} }

View File

@ -31,14 +31,13 @@ private:
static const int PacketsPerBuffer = (1024 / SndOutPacketSize); static const int PacketsPerBuffer = (1024 / SndOutPacketSize);
static const int BufferSize = SndOutPacketSize*PacketsPerBuffer; static const int BufferSize = SndOutPacketSize*PacketsPerBuffer;
static const int BufferSizeBytes = BufferSize << 1;
u32 numBuffers; u32 numBuffers;
HWAVEOUT hwodevice; HWAVEOUT hwodevice;
WAVEFORMATEX wformat; WAVEFORMATEX wformat;
WAVEHDR whbuffer[MAX_BUFFER_COUNT]; WAVEHDR whbuffer[MAX_BUFFER_COUNT];
s16* qbuffer; StereoOut16* qbuffer;
#define QBUFFER(x) (qbuffer + BufferSize * (x)) #define QBUFFER(x) (qbuffer + BufferSize * (x))
@ -46,17 +45,13 @@ private:
HANDLE thread; HANDLE thread;
DWORD tid; DWORD tid;
SndBuffer *buff;
wchar_t ErrText[256]; wchar_t ErrText[256];
static DWORD CALLBACK RThread(WaveOutModule*obj) template< typename T >
{
return obj->Thread();
}
DWORD CALLBACK Thread() DWORD CALLBACK Thread()
{ {
static const int BufferSizeBytes = BufferSize * sizeof( T );
while( waveout_running ) while( waveout_running )
{ {
bool didsomething = false; bool didsomething = false;
@ -64,16 +59,16 @@ private:
{ {
if(!(whbuffer[i].dwFlags & WHDR_DONE) ) continue; if(!(whbuffer[i].dwFlags & WHDR_DONE) ) continue;
WAVEHDR *buf=whbuffer+i; WAVEHDR *buf = whbuffer+i;
buf->dwBytesRecorded = buf->dwBufferLength; buf->dwBytesRecorded = buf->dwBufferLength;
s16 *t = (s16*)buf->lpData; T* t = (T*)buf->lpData;
for(int p=0; p<PacketsPerBuffer; p++, t+=SndOutPacketSize ) for(int p=0; p<PacketsPerBuffer; p++, t+=SndOutPacketSize )
buff->ReadSamples( t ); SndBuffer::ReadSamples( t );
whbuffer[i].dwFlags&=~WHDR_DONE; whbuffer[i].dwFlags &= ~WHDR_DONE;
waveOutWrite(hwodevice,buf,sizeof(WAVEHDR)); waveOutWrite( hwodevice, buf, sizeof(WAVEHDR) );
didsomething = true; didsomething = true;
} }
@ -85,25 +80,71 @@ private:
return 0; return 0;
} }
public: template< typename T >
s32 Init(SndBuffer *sb) static DWORD CALLBACK RThread(WaveOutModule*obj)
{
return obj->Thread<T>();
}
public:
s32 Init()
{ {
buff = sb;
numBuffers = Config_WaveOut.NumBuffers; numBuffers = Config_WaveOut.NumBuffers;
MMRESULT woores; MMRESULT woores;
if (Test()) return -1; if (Test()) return -1;
wformat.wFormatTag=WAVE_FORMAT_PCM; // TODO : Use dsound to determine the speaker configuration, and expand audio from there.
wformat.nSamplesPerSec=SampleRate;
wformat.wBitsPerSample=16;
wformat.nChannels=2;
wformat.nBlockAlign=((wformat.wBitsPerSample * wformat.nChannels) / 8);
wformat.nAvgBytesPerSec=(wformat.nSamplesPerSec * wformat.nBlockAlign);
wformat.cbSize=0;
qbuffer=new s16[BufferSize*numBuffers]; #if 0
int speakerConfig;
if( StereoExpansionDisabled )
speakerConfig = 2;
// Any windows driver should support stereo at the software level, I should think!
jASSUME( speakerConfig > 1 );
LPTHREAD_START_ROUTINE threadproc;
switch( speakerConfig )
{
case 2:
ConLog( "* SPU2 > Using normal 2 speaker stereo output." );
threadproc = (LPTHREAD_START_ROUTINE)&RThread<StereoOut16>;
speakerConfig = 2;
break;
case 4:
ConLog( "* SPU2 > 4 speaker expansion enabled [quadraphenia]" );
threadproc = (LPTHREAD_START_ROUTINE)&RThread<StereoQuadOut16>;
speakerConfig = 4;
break;
case 6:
case 7:
ConLog( "* SPU2 > 5.1 speaker expansion enabled." );
threadproc = (LPTHREAD_START_ROUTINE)&RThread<Stereo51Out16>;
speakerConfig = 6;
break;
default:
ConLog( "* SPU2 > 7.1 speaker expansion enabled." );
threadproc = (LPTHREAD_START_ROUTINE)&RThread<Stereo51Out16>;
speakerConfig = 8;
break;
}
#endif
wformat.wFormatTag = WAVE_FORMAT_PCM;
wformat.nSamplesPerSec = SampleRate;
wformat.wBitsPerSample = 16;
wformat.nChannels = 2;
wformat.nBlockAlign = ((wformat.wBitsPerSample * wformat.nChannels) / 8);
wformat.nAvgBytesPerSec = (wformat.nSamplesPerSec * wformat.nBlockAlign);
wformat.cbSize = 0;
qbuffer = new StereoOut16[BufferSize*numBuffers];
woores = waveOutOpen(&hwodevice,WAVE_MAPPER,&wformat,0,0,0); woores = waveOutOpen(&hwodevice,WAVE_MAPPER,&wformat,0,0,0);
if (woores != MMSYSERR_NOERROR) if (woores != MMSYSERR_NOERROR)
@ -113,6 +154,8 @@ public:
return -1; return -1;
} }
const int BufferSizeBytes = wformat.nBlockAlign * BufferSize;
for(u32 i=0;i<numBuffers;i++) for(u32 i=0;i<numBuffers;i++)
{ {
whbuffer[i].dwBufferLength=BufferSizeBytes; whbuffer[i].dwBufferLength=BufferSizeBytes;
@ -133,7 +176,7 @@ public:
// love it needs and won't suck resources idling pointlessly. Just don't try to // love it needs and won't suck resources idling pointlessly. Just don't try to
// run it in uber-low-latency mode. // run it in uber-low-latency mode.
waveout_running = true; waveout_running = true;
thread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)RThread,this,0,&tid); thread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)RThread<StereoOut16>,this,0,&tid);
return 0; return 0;
} }
@ -276,4 +319,4 @@ public:
} WO; } WO;
SndOutModule *WaveOut=&WO; SndOutModule *WaveOut = &WO;

View File

@ -53,6 +53,7 @@
FavorSizeOrSpeed="1" FavorSizeOrSpeed="1"
OmitFramePointers="true" OmitFramePointers="true"
EnableFiberSafeOptimizations="true" EnableFiberSafeOptimizations="true"
AdditionalIncludeDirectories=""
PreprocessorDefinitions="SPU2X_DEVBUILD;FLOAT_SAMPLES;NDEBUG;_USRDLL" PreprocessorDefinitions="SPU2X_DEVBUILD;FLOAT_SAMPLES;NDEBUG;_USRDLL"
StringPooling="true" StringPooling="true"
RuntimeLibrary="0" RuntimeLibrary="0"
@ -608,6 +609,10 @@
RelativePath=".\SndOut_XAudio2.cpp" RelativePath=".\SndOut_XAudio2.cpp"
> >
</File> </File>
<File
RelativePath="..\Timestretcher.cpp"
>
</File>
</Filter> </Filter>
<Filter <Filter
Name="decoder" Name="decoder"

View File

@ -22,7 +22,24 @@
#ifndef DEFS_H_INCLUDED #ifndef DEFS_H_INCLUDED
#define DEFS_H_INCLUDED #define DEFS_H_INCLUDED
struct V_Volume struct V_VolumeLR
{
static V_VolumeLR Max;
s32 Left;
s32 Right;
V_VolumeLR() {}
V_VolumeLR( s32 both ) :
Left( both ),
Right( both )
{
}
void DebugDump( FILE* dump, const char* title );
};
struct V_VolumeSlide
{ {
// Holds the "original" value of the volume for this voice, prior to slides. // Holds the "original" value of the volume for this voice, prior to slides.
// (ie, the volume as written to the register) // (ie, the volume as written to the register)
@ -33,9 +50,47 @@ struct V_Volume
s8 Mode; s8 Mode;
public: public:
V_VolumeSlide() {}
V_VolumeSlide( s16 regval, s32 fullvol ) :
Reg_VOL( regval ),
Value( fullvol ),
Increment( 0 ),
Mode( 0 )
{
}
void Update(); void Update();
void RegSet( u16 src ); // used to set the volume from a register source (16 bit signed)
void DebugDump( FILE* dump, const char* title, const char* nameLR );
}; };
struct V_VolumeSlideLR
{
static V_VolumeSlideLR Max;
V_VolumeSlide Left;
V_VolumeSlide Right;
public:
V_VolumeSlideLR() {}
V_VolumeSlideLR( s16 regval, s32 bothval ) :
Left( regval, bothval ),
Right( regval, bothval )
{
}
void Update()
{
Left.Update();
Right.Update();
}
void DebugDump( FILE* dump, const char* title );
};
struct V_ADSR struct V_ADSR
{ {
u16 Reg_ADSR1; u16 Reg_ADSR1;
@ -61,12 +116,10 @@ public:
struct V_Voice struct V_Voice
{ {
// SPU2 cycle where the Playing started u32 PlayCycle; // SPU2 cycle where the Playing started
u32 PlayCycle;
// Left Volume V_VolumeSlideLR Volume;
V_Volume VolumeL;
// Right Volume
V_Volume VolumeR;
// Envelope // Envelope
V_ADSR ADSR; V_ADSR ADSR;
// Pitch (also Reg_PITCH) // Pitch (also Reg_PITCH)
@ -198,6 +251,39 @@ struct V_Reverb
u32 MIX_DEST_B1; u32 MIX_DEST_B1;
}; };
struct V_ReverbBuffers
{
s32 FB_SRC_A0;
s32 FB_SRC_B0;
s32 FB_SRC_A1;
s32 FB_SRC_B1;
s32 IIR_SRC_A0;
s32 IIR_SRC_A1;
s32 IIR_SRC_B1;
s32 IIR_SRC_B0;
s32 IIR_DEST_A0;
s32 IIR_DEST_A1;
s32 IIR_DEST_B0;
s32 IIR_DEST_B1;
s32 ACC_SRC_A0;
s32 ACC_SRC_A1;
s32 ACC_SRC_B0;
s32 ACC_SRC_B1;
s32 ACC_SRC_C0;
s32 ACC_SRC_C1;
s32 ACC_SRC_D0;
s32 ACC_SRC_D1;
s32 MIX_DEST_A0;
s32 MIX_DEST_A1;
s32 MIX_DEST_B0;
s32 MIX_DEST_B1;
bool NeedsUpdated;
};
struct V_SPDIF struct V_SPDIF
{ {
u16 Out; u16 Out;
@ -228,22 +314,14 @@ struct V_Core
{ {
// Core Voices // Core Voices
V_Voice Voices[24]; V_Voice Voices[24];
// Master Volume for Left Channel
V_Volume MasterL;
// Master Volume for Right Channel V_VolumeSlideLR MasterVol;// Master Volume
V_Volume MasterR;
// Volume for External Data Input (Left Channel) V_VolumeLR ExtVol; // Volume for External Data Input
s32 ExtL; V_VolumeLR InpVol; // Volume for Sound Data Input
// Volume for External Data Input (Right Channel) V_VolumeLR FxVol; // Volume for Output from Effects
s32 ExtR;
// Volume for Sound Data Input (Left Channel)
s32 InpL;
// Volume for Sound Data Input (Right Channel)
s32 InpR;
// Volume for Output from Effects (Left Channel)
s32 FxL;
// Volume for Output from Effects (Right Channel)
s32 FxR;
// Interrupt Address // Interrupt Address
u32 IRQA; u32 IRQA;
// DMA Transfer Start Address // DMA Transfer Start Address
@ -296,6 +374,7 @@ struct V_Core
// Reverb // Reverb
V_Reverb Revb; V_Reverb Revb;
V_ReverbBuffers RevBuffers; // buffer pointers for reverb, pre-calculated and pre-clipped.
u32 EffectsStartA; u32 EffectsStartA;
u32 EffectsEndA; u32 EffectsEndA;
u32 ReverbX; u32 ReverbX;
@ -311,8 +390,7 @@ struct V_Core
// Last samples to pass through the effects processor. // Last samples to pass through the effects processor.
// Used because the effects processor works at 24khz and just pulls // Used because the effects processor works at 24khz and just pulls
// from this for the odd Ts. // from this for the odd Ts.
s16 LastEffectL; StereoOut32 LastEffect;
s16 LastEffectR;
u8 InitDelay; u8 InitDelay;
@ -329,12 +407,15 @@ struct V_Core
s16 ADMATempBuffer[0x1000]; s16 ADMATempBuffer[0x1000];
u32 ADMAPV; u32 ADMAPV;
u32 ADMAPL; StereoOut32 ADMAP;
u32 ADMAPR;
void Reset(); void Reset();
void UpdateEffectsBufferSize(); void UpdateEffectsBufferSize();
V_Core(); // our badass constructor
s32 EffectsBufferIndexer( s32 offset ) const;
void UpdateFeedbackBuffersA();
void UpdateFeedbackBuffersB();
}; };
extern V_Core Cores[2]; extern V_Core Cores[2];