mirror of https://github.com/PCSX2/pcsx2.git
SPU2X: New stretcher: hopefully works better than r4834 (same goals as r4834: better resiliance to very high/low fps, compared to r4820).
I'm not fully happy with it, as it's a bit complicated for my taste, so feedback about how it performs will be appreciated. git-svn-id: http://pcsx2.googlecode.com/svn/trunk@4840 96395faa-99c1-11dd-bbfe-3dabce05a288
This commit is contained in:
parent
34569047b4
commit
36fc051238
|
@ -81,19 +81,22 @@ float SndBuffer::GetStatusPct()
|
||||||
// These params were tested to show good respond and stability, on all audio systems (dsound, wav, port audio, xaudio2),
|
// These params were tested to show good respond and stability, on all audio systems (dsound, wav, port audio, xaudio2),
|
||||||
// even at extreme small latency of 50ms which can handle 50%-100% variations without audible glitches.
|
// even at extreme small latency of 50ms which can handle 50%-100% variations without audible glitches.
|
||||||
|
|
||||||
|
int targetIPS=750;
|
||||||
|
|
||||||
|
|
||||||
//running average can be implemented in O(1) time.
|
//running average can be implemented in O(1) time.
|
||||||
//For the sake of simplicity, this average is calculated in O(<buffer-size>). Possibly improve later.
|
//For the sake of simplicity, this average is calculated in O(<buffer-size>). Possibly improve later.
|
||||||
#define STRETCH_AVERAGE_LEN 50
|
#define MAX_STRETCH_AVERAGE_LEN 100
|
||||||
|
int STRETCH_AVERAGE_LEN=50.0 *targetIPS/750;
|
||||||
//adds a value to the running average buffer, and return new running average.
|
//adds a value to the running average buffer, and return new running average.
|
||||||
float addToAvg(float val){
|
float addToAvg(float val){
|
||||||
static float avg_fullness[STRETCH_AVERAGE_LEN]={0};
|
static float avg_fullness[MAX_STRETCH_AVERAGE_LEN]={0};
|
||||||
static int nextAvgPos=0;
|
static int nextAvgPos=0;
|
||||||
|
|
||||||
avg_fullness[nextAvgPos]=val;
|
avg_fullness[nextAvgPos]=val;
|
||||||
nextAvgPos=(nextAvgPos+1)%STRETCH_AVERAGE_LEN;
|
nextAvgPos=(nextAvgPos+1)%STRETCH_AVERAGE_LEN;
|
||||||
float sum=0;
|
float sum=0;
|
||||||
for(int i=0; i<STRETCH_AVERAGE_LEN; i++)
|
for(int c=0, i=(nextAvgPos-1)%STRETCH_AVERAGE_LEN; c<STRETCH_AVERAGE_LEN; c++, i=(i+STRETCH_AVERAGE_LEN-1)%STRETCH_AVERAGE_LEN)
|
||||||
sum+=avg_fullness[i];
|
sum+=avg_fullness[i];
|
||||||
|
|
||||||
sum= (float)sum/(float)STRETCH_AVERAGE_LEN;
|
sum= (float)sum/(float)STRETCH_AVERAGE_LEN;
|
||||||
|
@ -110,91 +113,107 @@ T clamp(T val, T min, T max){
|
||||||
//actual stretch algorithm implementation
|
//actual stretch algorithm implementation
|
||||||
void SndBuffer::UpdateTempoChangeSoundTouch2()
|
void SndBuffer::UpdateTempoChangeSoundTouch2()
|
||||||
{
|
{
|
||||||
//base aim at buffer filled %
|
|
||||||
float baseTargetFullness=0.05;
|
|
||||||
|
|
||||||
//threshold params (hysteresis)
|
long targetSamplesReservoir=48*SndOutLatencyMS;//48000*SndOutLatencyMS/1000
|
||||||
static const float hys_ok_factor=1.03;
|
//base aim at buffer filled %
|
||||||
static const int hys_min_ok_count=100; //consecutive iterations within hys_ok before going to 1:1 mode
|
float baseTargetFullness=(double)targetSamplesReservoir;///(double)m_size;//0.05;
|
||||||
static const float hys_bad_factor=1.2;
|
|
||||||
|
|
||||||
//state vars
|
//state vars
|
||||||
static bool inside_hysteresis=false;
|
static bool inside_hysteresis=false;
|
||||||
static int hys_ok_count=0;
|
static int hys_ok_count=0;
|
||||||
static float dynamicTargetFullness=baseTargetFullness;
|
static float dynamicTargetFullness=baseTargetFullness;
|
||||||
|
|
||||||
//some precalculated values
|
float bufferFullness=(float)m_data;///(float)m_size;
|
||||||
static const float hys_ok_min=1.0/hys_ok_factor;
|
|
||||||
static const float hys_ok_max=hys_ok_factor;
|
|
||||||
static const float hys_bad_min=1.0/hys_bad_factor;
|
|
||||||
static const float hys_bad_max=hys_bad_factor;
|
|
||||||
|
|
||||||
float bufferFullness=(float)m_data/(float)m_size;
|
{//test current iterations/sec every 0.5s, and change algo params accordingly if different than previous IPS more than 30%
|
||||||
static float last_bufferFullness=0;
|
static long iters=0;
|
||||||
if(last_bufferFullness != bufferFullness){// only recalculate if buffer changes
|
static wxDateTime last=wxDateTime::UNow();
|
||||||
last_bufferFullness = bufferFullness;
|
wxDateTime unow=wxDateTime::UNow();
|
||||||
|
wxTimeSpan delta = unow.Subtract(last);
|
||||||
float tempoAdjust=bufferFullness/dynamicTargetFullness;
|
if( delta.GetMilliseconds()>500 ){
|
||||||
float avgerage = addToAvg(tempoAdjust);
|
int pot_targetIPS=1000.0/delta.GetMilliseconds().ToDouble()*iters;
|
||||||
tempoAdjust = avgerage;
|
if(pot_targetIPS != clamp(pot_targetIPS, int((float)targetIPS/1.3f), int((float)targetIPS*1.3f)) ){
|
||||||
tempoAdjust = clamp( tempoAdjust, 0.05f, 10.0f);
|
if(MsgOverruns()) printf("Stretcher: setting iters/sec from %d to %d\n", targetIPS, pot_targetIPS);
|
||||||
dynamicTargetFullness += (baseTargetFullness/tempoAdjust - dynamicTargetFullness)/50.0;
|
targetIPS=pot_targetIPS;
|
||||||
if(
|
STRETCH_AVERAGE_LEN=clamp((int)(50.0f *(float)targetIPS/750.0f), 3, MAX_STRETCH_AVERAGE_LEN);
|
||||||
tempoAdjust == clamp(tempoAdjust, 0.9f, 1.1f)
|
|
||||||
&& dynamicTargetFullness == clamp( dynamicTargetFullness, baseTargetFullness*0.9f, baseTargetFullness*1.1f)
|
|
||||||
)
|
|
||||||
dynamicTargetFullness=baseTargetFullness;
|
|
||||||
|
|
||||||
if( !inside_hysteresis )
|
|
||||||
{
|
|
||||||
if( tempoAdjust == clamp( tempoAdjust, hys_ok_min, hys_ok_max ) )
|
|
||||||
hys_ok_count++;
|
|
||||||
else
|
|
||||||
hys_ok_count=0;
|
|
||||||
|
|
||||||
if( hys_ok_count >= hys_min_ok_count ){
|
|
||||||
inside_hysteresis=true;
|
|
||||||
if(MsgOverruns()) printf("======> stretch: None (1:1)\n");
|
|
||||||
}
|
}
|
||||||
|
last=unow;
|
||||||
}
|
iters=0;
|
||||||
else if( tempoAdjust != clamp( tempoAdjust, hys_bad_min, hys_bad_max ) ){
|
|
||||||
if(MsgOverruns()) printf("~~~~~~> stretch: Dynamic\n");
|
|
||||||
inside_hysteresis=false;
|
|
||||||
hys_ok_count=0;
|
|
||||||
}
|
}
|
||||||
|
iters++;
|
||||||
|
}
|
||||||
|
|
||||||
if(inside_hysteresis)
|
//Algorithm params: (threshold params (hysteresis), etc)
|
||||||
tempoAdjust=1.0;
|
const float hys_ok_factor=1.04;
|
||||||
|
int hys_min_ok_count=clamp((int)(50.0 *(float)targetIPS/750.0), 2, 100); //consecutive iterations within hys_ok before going to 1:1 mode
|
||||||
|
const float hys_bad_factor=1.2;
|
||||||
|
int compensationDivider=clamp((int)(100.0 *(float)targetIPS/750), 15, 150);
|
||||||
|
|
||||||
if(MsgOverruns()){
|
float tempoAdjust=bufferFullness/dynamicTargetFullness;
|
||||||
static int iters=0;
|
float avgerage = addToAvg(tempoAdjust);
|
||||||
static wxDateTime last=wxDateTime::UNow();
|
tempoAdjust = avgerage;
|
||||||
wxDateTime unow=wxDateTime::UNow();
|
tempoAdjust = clamp( tempoAdjust, 0.05f, 10.0f);
|
||||||
wxTimeSpan delta = unow.Subtract(last);
|
|
||||||
|
|
||||||
if(delta.GetMilliseconds()>1000){//report buffers state and tempo adjust every second
|
|
||||||
printf("buffers[->%.2f]: %.3f (%3.0f%%), tempo adjust: %f, compensated target: %.3f, iterations: %d\n",
|
|
||||||
(double)baseTargetFullness, (double)bufferFullness, (double)(100.0*bufferFullness/baseTargetFullness), (double)tempoAdjust, (double)dynamicTargetFullness, iters);
|
|
||||||
last=unow;
|
|
||||||
iters=0;
|
|
||||||
}
|
|
||||||
iters++;
|
|
||||||
}
|
|
||||||
|
|
||||||
pSoundTouch->setTempo(tempoAdjust);
|
dynamicTargetFullness += (baseTargetFullness/tempoAdjust - dynamicTargetFullness)/(double)compensationDivider;
|
||||||
|
if(
|
||||||
|
tempoAdjust == clamp(tempoAdjust, 0.9f, 1.1f)
|
||||||
|
&& dynamicTargetFullness == clamp( dynamicTargetFullness, baseTargetFullness*0.9f, baseTargetFullness*1.1f)
|
||||||
|
)
|
||||||
|
dynamicTargetFullness=baseTargetFullness;
|
||||||
|
|
||||||
//collect some stats...
|
if( !inside_hysteresis )
|
||||||
if(tempoAdjust==1.0)
|
{
|
||||||
ts_stats_normalblocks++;
|
if( tempoAdjust == clamp( tempoAdjust, 1.0f/hys_ok_factor, hys_ok_factor ) )
|
||||||
|
hys_ok_count++;
|
||||||
else
|
else
|
||||||
ts_stats_stretchblocks++;
|
hys_ok_count=0;
|
||||||
|
|
||||||
|
if( hys_ok_count >= hys_min_ok_count ){
|
||||||
|
inside_hysteresis=true;
|
||||||
|
if(MsgOverruns()) printf("======> stretch: None (1:1)\n");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
else if( tempoAdjust != clamp( tempoAdjust, 1.0f/hys_bad_factor, hys_bad_factor ) ){
|
||||||
|
if(MsgOverruns()) printf("~~~~~~> stretch: Dynamic\n");
|
||||||
|
inside_hysteresis=false;
|
||||||
|
hys_ok_count=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(inside_hysteresis)
|
||||||
|
tempoAdjust=1.0;
|
||||||
|
|
||||||
|
if(MsgOverruns()){
|
||||||
|
static int iters=0;
|
||||||
|
static wxDateTime last=wxDateTime::UNow();
|
||||||
|
wxDateTime unow=wxDateTime::UNow();
|
||||||
|
wxTimeSpan delta = unow.Subtract(last);
|
||||||
|
|
||||||
|
if(delta.GetMilliseconds()>1000){//report buffers state and tempo adjust every second
|
||||||
|
printf("buffers: %4d ms (%3.0f%%), tempo: %f, comp: %2.3f, iters: %d, (N-IPS:%d -> avg:%d, minokc:%d, div:%d)\n",
|
||||||
|
(int)(m_data/48), (double)(100.0*bufferFullness/baseTargetFullness), (double)tempoAdjust, (double)(dynamicTargetFullness/baseTargetFullness), iters, (int)targetIPS
|
||||||
|
, STRETCH_AVERAGE_LEN, hys_min_ok_count, compensationDivider
|
||||||
|
);
|
||||||
|
last=unow;
|
||||||
|
iters=0;
|
||||||
|
}
|
||||||
|
iters++;
|
||||||
|
}
|
||||||
|
|
||||||
|
pSoundTouch->setTempo(tempoAdjust);
|
||||||
|
|
||||||
|
//collect some unuseful stats...
|
||||||
|
if(tempoAdjust==1.0)
|
||||||
|
ts_stats_normalblocks++;
|
||||||
|
else
|
||||||
|
ts_stats_stretchblocks++;
|
||||||
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void SndBuffer::UpdateTempoChangeSoundTouch()
|
void SndBuffer::UpdateTempoChangeSoundTouch()
|
||||||
{
|
{
|
||||||
float statusPct = GetStatusPct();
|
float statusPct = GetStatusPct();
|
||||||
|
@ -436,7 +455,7 @@ void SndBuffer::timeStretchWrite()
|
||||||
|
|
||||||
if( MsgOverruns() )
|
if( MsgOverruns() )
|
||||||
{
|
{
|
||||||
if( progress )
|
if( progress && (ts_stats_normalblocks + ts_stats_stretchblocks))
|
||||||
{
|
{
|
||||||
if( ++ts_stats_logcounter > 150 )
|
if( ++ts_stats_logcounter > 150 )
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue