2009-02-18 13:36:20 +00:00
|
|
|
/* SPU2-X, A plugin for Emulating the Sound Processing Unit of the Playstation 2
|
|
|
|
* Developed and maintained by the Pcsx2 Development Team.
|
2010-04-25 00:31:27 +00:00
|
|
|
*
|
2009-02-18 13:36:20 +00:00
|
|
|
* Original portions from SPU2ghz are (c) 2008 by David Quintana [gigaherz]
|
|
|
|
*
|
2009-06-21 11:00:53 +00:00
|
|
|
* SPU2-X is free software: you can redistribute it and/or modify it under the terms
|
|
|
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
|
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* SPU2-X is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
|
|
* PURPOSE. See the GNU Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
|
|
* along with SPU2-X. If not, see <http://www.gnu.org/licenses/>.
|
2009-02-18 13:36:20 +00:00
|
|
|
*/
|
|
|
|
|
2009-09-29 19:18:50 +00:00
|
|
|
#include "Global.h"
|
2010-06-11 13:51:43 +00:00
|
|
|
#include "soundtouch/SoundTouch.h"
|
2011-07-24 10:58:42 +00:00
|
|
|
#include <wx/datetime.h>
|
|
|
|
|
|
|
|
//Uncomment the next line to use the old time stretcher
|
|
|
|
//#define SPU2X_USE_OLD_STRETCHER
|
2009-02-18 13:36:20 +00:00
|
|
|
|
|
|
|
static soundtouch::SoundTouch* pSoundTouch = NULL;
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2010-03-19 20:28:10 +00:00
|
|
|
//ConLog( "Data %d >>> driver: %d predict: %d\n", m_data, drvempty, m_predictData );
|
2009-02-18 13:36:20 +00:00
|
|
|
|
2011-08-14 22:06:18 +00:00
|
|
|
int data = _GetApproximateDataInBuffer();
|
|
|
|
float result = (float)( data + m_predictData - drvempty) - (m_size/16);
|
2010-04-12 17:13:30 +00:00
|
|
|
result /= (m_size/16);
|
2009-02-18 13:36:20 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2011-07-24 10:58:42 +00:00
|
|
|
|
|
|
|
//Alternative simple tempo adjustment. Based only on the soundtouch buffer state.
|
SPUX: New stretcher: Should be slightly more stable now, compared to r4840.
On 4840 I added dynamic tuning (which may have slight negative effect on stability) to adapt to different iterations/sec of different systems, but I didn't like this system too much. On 4840 I also made a small change which, I now understand, factors out the differences between systems in this regard, essentially making dynamic tuning not required anymore. So now dynamic tuning is disabled.
If you enable the SPU2X messages (and the overrun messages), you would see log messages similar to this:
buffers: 44 ms ( 89%), tempo: 1.000000, comp: 1.000, iters: 764, (N-IPS:750 -> avg:50, minokc:50, div:100)
If the iters value (764 at this example) on your system when running at 100% speed (50/60fps) is higher than 1000 or lower than 500, then please comment here and post few of the log lines.
git-svn-id: http://pcsx2.googlecode.com/svn/trunk@4843 96395faa-99c1-11dd-bbfe-3dabce05a288
2011-08-04 18:21:32 +00:00
|
|
|
//Base algorithm: aim at specific average number of samples at the buffer (by GUI), and adjust tempo simply by current/target.
|
2011-07-24 10:58:42 +00:00
|
|
|
//An extra mechanism is added to keep adjustment at perfect 1:1 ratio (when emulation speed is stable around 100%)
|
|
|
|
// to prevent constant stretching/shrinking of packets if possible.
|
|
|
|
// This mechanism is triggered when the adjustment is close to 1:1 for long enough (defaults to 100 iterations within hys_ok_factor - defaults to 3%).
|
|
|
|
// 1:1 state is aborted when required adjustment goes beyond hys_bad_factor (defaults to 20%).
|
|
|
|
//
|
SPUX: New stretcher: Should be slightly more stable now, compared to r4840.
On 4840 I added dynamic tuning (which may have slight negative effect on stability) to adapt to different iterations/sec of different systems, but I didn't like this system too much. On 4840 I also made a small change which, I now understand, factors out the differences between systems in this regard, essentially making dynamic tuning not required anymore. So now dynamic tuning is disabled.
If you enable the SPU2X messages (and the overrun messages), you would see log messages similar to this:
buffers: 44 ms ( 89%), tempo: 1.000000, comp: 1.000, iters: 764, (N-IPS:750 -> avg:50, minokc:50, div:100)
If the iters value (764 at this example) on your system when running at 100% speed (50/60fps) is higher than 1000 or lower than 500, then please comment here and post few of the log lines.
git-svn-id: http://pcsx2.googlecode.com/svn/trunk@4843 96395faa-99c1-11dd-bbfe-3dabce05a288
2011-08-04 18:21:32 +00:00
|
|
|
//To compensate for wide variation of the <num of samples> ratio due to relatively small size of the buffer,
|
2011-07-24 10:58:42 +00:00
|
|
|
// The required tempo is a running average of STRETCH_AVERAGE_LEN (defaults to 50) last calculations.
|
|
|
|
// This averaging slows down the respons time of the algorithm, but greatly stablize it towards steady stretching.
|
|
|
|
//
|
SPUX: New stretcher: Should be slightly more stable now, compared to r4840.
On 4840 I added dynamic tuning (which may have slight negative effect on stability) to adapt to different iterations/sec of different systems, but I didn't like this system too much. On 4840 I also made a small change which, I now understand, factors out the differences between systems in this regard, essentially making dynamic tuning not required anymore. So now dynamic tuning is disabled.
If you enable the SPU2X messages (and the overrun messages), you would see log messages similar to this:
buffers: 44 ms ( 89%), tempo: 1.000000, comp: 1.000, iters: 764, (N-IPS:750 -> avg:50, minokc:50, div:100)
If the iters value (764 at this example) on your system when running at 100% speed (50/60fps) is higher than 1000 or lower than 500, then please comment here and post few of the log lines.
git-svn-id: http://pcsx2.googlecode.com/svn/trunk@4843 96395faa-99c1-11dd-bbfe-3dabce05a288
2011-08-04 18:21:32 +00:00
|
|
|
//Keeping the buffer at required latency:
|
|
|
|
// This algorithm stabilises when the actual latency is <speed>*<required_latency>. While this is just fine at 100% speed,
|
|
|
|
// it's problematic especially for slow speeds, as the number of actual samples at the buffer gets very small on that case,
|
|
|
|
// which may lead to underruns (or just too much latency when running very fast fast).
|
|
|
|
//To compensate for that, the algorithm has a slowly moving compensation factor which will eventually bring the actual latency to the required one.
|
|
|
|
//compensationDivider defines how slow this compensation changes. By default it's set to 100,
|
|
|
|
// which will finalize the compensation after about 200 iterations.
|
|
|
|
//
|
2011-07-24 10:58:42 +00:00
|
|
|
// Note, this algorithm is intentionally simplified by not taking extreme actions at extreme scenarios (mostly underruns when speed dtops sharply),
|
|
|
|
// and let's the overrun/underrun protections do what they should (doesn't happen much though in practice, even at big FPS variations).
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2011-08-02 18:48:48 +00:00
|
|
|
int targetIPS=750;
|
|
|
|
|
SPUX: New stretcher: Should be slightly more stable now, compared to r4840.
On 4840 I added dynamic tuning (which may have slight negative effect on stability) to adapt to different iterations/sec of different systems, but I didn't like this system too much. On 4840 I also made a small change which, I now understand, factors out the differences between systems in this regard, essentially making dynamic tuning not required anymore. So now dynamic tuning is disabled.
If you enable the SPU2X messages (and the overrun messages), you would see log messages similar to this:
buffers: 44 ms ( 89%), tempo: 1.000000, comp: 1.000, iters: 764, (N-IPS:750 -> avg:50, minokc:50, div:100)
If the iters value (764 at this example) on your system when running at 100% speed (50/60fps) is higher than 1000 or lower than 500, then please comment here and post few of the log lines.
git-svn-id: http://pcsx2.googlecode.com/svn/trunk@4843 96395faa-99c1-11dd-bbfe-3dabce05a288
2011-08-04 18:21:32 +00:00
|
|
|
//Dynamic tuning changes the values of the base algorithm parameters (derived from targetIPS) to adapt, in real time, to
|
|
|
|
// diferent number of invocations/sec (mostly affects number of iterations to average).
|
|
|
|
// Dynamic tuning can have a slight negative effect on the behavior of the algorithm, so it's preferred to have it off.
|
|
|
|
//Currently it looks like it's around 750/sec on all systems when playing at 100% speed (50/60fps),
|
|
|
|
// and proportional to that speed otherwise.
|
|
|
|
//If changes are made to SPU2X which affects this number (but it's still the same on all systems), then just change targetIPS.
|
|
|
|
//If we find out that some systems are very different, we can turn on dynamic tuning by uncommenting the next line.
|
|
|
|
//#define NEWSTRETCHER_USE_DYNAMIC_TUNING
|
|
|
|
|
2011-07-24 10:58:42 +00:00
|
|
|
|
2011-08-07 09:33:27 +00:00
|
|
|
//running average can be implemented in O(1) time.
|
2011-07-24 10:58:42 +00:00
|
|
|
//For the sake of simplicity, this average is calculated in O(<buffer-size>). Possibly improve later.
|
2011-08-02 18:48:48 +00:00
|
|
|
#define MAX_STRETCH_AVERAGE_LEN 100
|
|
|
|
int STRETCH_AVERAGE_LEN=50.0 *targetIPS/750;
|
2011-07-24 10:58:42 +00:00
|
|
|
//adds a value to the running average buffer, and return new running average.
|
|
|
|
float addToAvg(float val){
|
2011-08-02 18:48:48 +00:00
|
|
|
static float avg_fullness[MAX_STRETCH_AVERAGE_LEN]={0};
|
2011-07-24 10:58:42 +00:00
|
|
|
static int nextAvgPos=0;
|
|
|
|
|
|
|
|
avg_fullness[nextAvgPos]=val;
|
|
|
|
nextAvgPos=(nextAvgPos+1)%STRETCH_AVERAGE_LEN;
|
|
|
|
float sum=0;
|
2011-08-02 18:48:48 +00:00
|
|
|
for(int c=0, i=(nextAvgPos-1)%STRETCH_AVERAGE_LEN; c<STRETCH_AVERAGE_LEN; c++, i=(i+STRETCH_AVERAGE_LEN-1)%STRETCH_AVERAGE_LEN)
|
2011-07-24 10:58:42 +00:00
|
|
|
sum+=avg_fullness[i];
|
|
|
|
|
|
|
|
sum= (float)sum/(float)STRETCH_AVERAGE_LEN;
|
|
|
|
return sum?sum:1;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
2011-08-15 16:55:23 +00:00
|
|
|
bool IsInRange(const T& val, const T& min, const T& max)
|
|
|
|
{
|
|
|
|
return ( min <= val && val <= max );
|
2011-07-24 10:58:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//actual stretch algorithm implementation
|
|
|
|
void SndBuffer::UpdateTempoChangeSoundTouch2()
|
|
|
|
{
|
|
|
|
|
2011-08-02 18:48:48 +00:00
|
|
|
long targetSamplesReservoir=48*SndOutLatencyMS;//48000*SndOutLatencyMS/1000
|
|
|
|
//base aim at buffer filled %
|
|
|
|
float baseTargetFullness=(double)targetSamplesReservoir;///(double)m_size;//0.05;
|
2011-07-24 10:58:42 +00:00
|
|
|
|
|
|
|
//state vars
|
|
|
|
static bool inside_hysteresis=false;
|
|
|
|
static int hys_ok_count=0;
|
2011-07-31 11:27:50 +00:00
|
|
|
static float dynamicTargetFullness=baseTargetFullness;
|
2011-07-24 10:58:42 +00:00
|
|
|
|
2011-08-14 22:06:18 +00:00
|
|
|
int data = _GetApproximateDataInBuffer();
|
|
|
|
float bufferFullness=(float)data;///(float)m_size;
|
2011-08-02 18:48:48 +00:00
|
|
|
|
SPUX: New stretcher: Should be slightly more stable now, compared to r4840.
On 4840 I added dynamic tuning (which may have slight negative effect on stability) to adapt to different iterations/sec of different systems, but I didn't like this system too much. On 4840 I also made a small change which, I now understand, factors out the differences between systems in this regard, essentially making dynamic tuning not required anymore. So now dynamic tuning is disabled.
If you enable the SPU2X messages (and the overrun messages), you would see log messages similar to this:
buffers: 44 ms ( 89%), tempo: 1.000000, comp: 1.000, iters: 764, (N-IPS:750 -> avg:50, minokc:50, div:100)
If the iters value (764 at this example) on your system when running at 100% speed (50/60fps) is higher than 1000 or lower than 500, then please comment here and post few of the log lines.
git-svn-id: http://pcsx2.googlecode.com/svn/trunk@4843 96395faa-99c1-11dd-bbfe-3dabce05a288
2011-08-04 18:21:32 +00:00
|
|
|
#ifdef NEWSTRETCHER_USE_DYNAMIC_TUNING
|
2011-08-02 18:48:48 +00:00
|
|
|
{//test current iterations/sec every 0.5s, and change algo params accordingly if different than previous IPS more than 30%
|
|
|
|
static long iters=0;
|
|
|
|
static wxDateTime last=wxDateTime::UNow();
|
|
|
|
wxDateTime unow=wxDateTime::UNow();
|
|
|
|
wxTimeSpan delta = unow.Subtract(last);
|
|
|
|
if( delta.GetMilliseconds()>500 ){
|
|
|
|
int pot_targetIPS=1000.0/delta.GetMilliseconds().ToDouble()*iters;
|
|
|
|
if(pot_targetIPS != clamp(pot_targetIPS, int((float)targetIPS/1.3f), int((float)targetIPS*1.3f)) ){
|
2011-08-04 14:27:21 +00:00
|
|
|
if(MsgOverruns()) ConLog("Stretcher: setting iters/sec from %d to %d\n", targetIPS, pot_targetIPS);
|
2011-08-02 18:48:48 +00:00
|
|
|
targetIPS=pot_targetIPS;
|
|
|
|
STRETCH_AVERAGE_LEN=clamp((int)(50.0f *(float)targetIPS/750.0f), 3, MAX_STRETCH_AVERAGE_LEN);
|
2011-07-24 10:58:42 +00:00
|
|
|
}
|
2011-08-02 18:48:48 +00:00
|
|
|
last=unow;
|
|
|
|
iters=0;
|
2011-07-24 10:58:42 +00:00
|
|
|
}
|
2011-08-02 18:48:48 +00:00
|
|
|
iters++;
|
|
|
|
}
|
SPUX: New stretcher: Should be slightly more stable now, compared to r4840.
On 4840 I added dynamic tuning (which may have slight negative effect on stability) to adapt to different iterations/sec of different systems, but I didn't like this system too much. On 4840 I also made a small change which, I now understand, factors out the differences between systems in this regard, essentially making dynamic tuning not required anymore. So now dynamic tuning is disabled.
If you enable the SPU2X messages (and the overrun messages), you would see log messages similar to this:
buffers: 44 ms ( 89%), tempo: 1.000000, comp: 1.000, iters: 764, (N-IPS:750 -> avg:50, minokc:50, div:100)
If the iters value (764 at this example) on your system when running at 100% speed (50/60fps) is higher than 1000 or lower than 500, then please comment here and post few of the log lines.
git-svn-id: http://pcsx2.googlecode.com/svn/trunk@4843 96395faa-99c1-11dd-bbfe-3dabce05a288
2011-08-04 18:21:32 +00:00
|
|
|
#endif
|
2011-07-24 10:58:42 +00:00
|
|
|
|
2011-08-02 18:48:48 +00:00
|
|
|
//Algorithm params: (threshold params (hysteresis), etc)
|
|
|
|
const float hys_ok_factor=1.04;
|
2011-08-15 16:55:23 +00:00
|
|
|
int hys_min_ok_count = GetClamped((int)(50.0 *(float)targetIPS/750.0), 2, 100); //consecutive iterations within hys_ok before going to 1:1 mode
|
2011-08-02 18:48:48 +00:00
|
|
|
const float hys_bad_factor=1.2;
|
2011-08-15 16:55:23 +00:00
|
|
|
int compensationDivider = GetClamped((int)(100.0 *(float)targetIPS/750), 15, 150);
|
2011-07-24 10:58:42 +00:00
|
|
|
|
2011-08-02 18:48:48 +00:00
|
|
|
float tempoAdjust=bufferFullness/dynamicTargetFullness;
|
|
|
|
float avgerage = addToAvg(tempoAdjust);
|
|
|
|
tempoAdjust = avgerage;
|
2011-08-15 16:55:23 +00:00
|
|
|
tempoAdjust = GetClamped( tempoAdjust, 0.05f, 10.0f);
|
2011-07-24 10:58:42 +00:00
|
|
|
|
|
|
|
|
2011-08-02 18:48:48 +00:00
|
|
|
dynamicTargetFullness += (baseTargetFullness/tempoAdjust - dynamicTargetFullness)/(double)compensationDivider;
|
2011-08-15 16:55:23 +00:00
|
|
|
if( IsInRange(tempoAdjust, 0.9f, 1.1f) && IsInRange( dynamicTargetFullness, baseTargetFullness*0.9f, baseTargetFullness*1.1f) )
|
2011-08-02 18:48:48 +00:00
|
|
|
dynamicTargetFullness=baseTargetFullness;
|
|
|
|
|
|
|
|
if( !inside_hysteresis )
|
|
|
|
{
|
2011-08-15 16:55:23 +00:00
|
|
|
if( IsInRange( tempoAdjust, 1.0f/hys_ok_factor, hys_ok_factor ) )
|
2011-08-02 18:48:48 +00:00
|
|
|
hys_ok_count++;
|
2011-07-24 10:58:42 +00:00
|
|
|
else
|
2011-08-02 18:48:48 +00:00
|
|
|
hys_ok_count=0;
|
|
|
|
|
|
|
|
if( hys_ok_count >= hys_min_ok_count ){
|
|
|
|
inside_hysteresis=true;
|
2011-08-04 14:27:21 +00:00
|
|
|
if(MsgOverruns()) ConLog("======> stretch: None (1:1)\n");
|
2011-08-02 18:48:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2011-08-15 16:55:23 +00:00
|
|
|
else if( !IsInRange( tempoAdjust, 1.0f/hys_bad_factor, hys_bad_factor ) ){
|
2011-08-04 14:27:21 +00:00
|
|
|
if(MsgOverruns()) ConLog("~~~~~~> stretch: Dynamic\n");
|
2011-08-02 18:48:48 +00:00
|
|
|
inside_hysteresis=false;
|
|
|
|
hys_ok_count=0;
|
|
|
|
}
|
2011-07-24 10:58:42 +00:00
|
|
|
|
2011-08-02 18:48:48 +00:00
|
|
|
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
|
2011-08-04 14:27:21 +00:00
|
|
|
ConLog("buffers: %4d ms (%3.0f%%), tempo: %f, comp: %2.3f, iters: %d, (N-IPS:%d -> avg:%d, minokc:%d, div:%d)\n",
|
2011-08-14 22:06:18 +00:00
|
|
|
(int)(data/48), (double)(100.0*bufferFullness/baseTargetFullness), (double)tempoAdjust, (double)(dynamicTargetFullness/baseTargetFullness), iters, (int)targetIPS
|
2011-08-02 18:48:48 +00:00
|
|
|
, STRETCH_AVERAGE_LEN, hys_min_ok_count, compensationDivider
|
|
|
|
);
|
|
|
|
last=unow;
|
|
|
|
iters=0;
|
|
|
|
}
|
|
|
|
iters++;
|
2011-07-24 10:58:42 +00:00
|
|
|
}
|
|
|
|
|
2011-08-02 18:48:48 +00:00
|
|
|
pSoundTouch->setTempo(tempoAdjust);
|
|
|
|
|
2011-07-24 10:58:42 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-08-02 18:48:48 +00:00
|
|
|
|
2010-04-11 22:08:32 +00:00
|
|
|
void SndBuffer::UpdateTempoChangeSoundTouch()
|
2009-02-18 13:36:20 +00:00
|
|
|
{
|
|
|
|
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
|
2009-04-12 21:19:29 +00:00
|
|
|
// cope with low fps (underruns) than it is high fps (overruns). So to help out a
|
2009-02-18 13:36:20 +00:00
|
|
|
// 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 ||
|
2010-04-12 18:38:29 +00:00
|
|
|
statusPct < -0.42f || statusPct > 0.70f ||
|
2009-02-18 13:36:20 +00:00
|
|
|
eTempo < 0.89f || eTempo > 1.19f )
|
|
|
|
{
|
2009-12-22 17:05:40 +00:00
|
|
|
//printf("Emergency stretch: cTempo = %f eTempo = %f pctChange = %f statusPct = %f\n",cTempo,eTempo,pctChange,statusPct);
|
2009-02-18 13:36:20 +00:00
|
|
|
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 );
|
|
|
|
|
2010-05-31 15:18:49 +00:00
|
|
|
/*ConLog("* SPU2-X: [Nominal %d%%] [Emergency: %d%%] (baseTempo: %d%% ) (newTempo: %d%%) (buffer: %d%%)\n",
|
2009-02-18 13:36:20 +00:00
|
|
|
//(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 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-20 22:29:30 +00:00
|
|
|
extern uint TickInterval;
|
2010-04-11 22:08:32 +00:00
|
|
|
void SndBuffer::UpdateTempoChangeAsyncMixing()
|
|
|
|
{
|
|
|
|
float statusPct = GetStatusPct();
|
|
|
|
|
|
|
|
lastPct = statusPct;
|
2011-03-28 00:03:06 +00:00
|
|
|
if( statusPct < -0.1f )
|
2010-04-11 22:08:32 +00:00
|
|
|
{
|
2010-04-12 18:38:29 +00:00
|
|
|
TickInterval -= 4;
|
2011-03-28 00:03:06 +00:00
|
|
|
if( statusPct < -0.3f ) TickInterval = 64;
|
|
|
|
if( TickInterval < 64 ) TickInterval = 64;
|
2010-04-12 18:38:29 +00:00
|
|
|
//printf("-- %d, %f\n",TickInterval,statusPct);
|
2010-04-11 22:08:32 +00:00
|
|
|
}
|
2011-03-28 00:03:06 +00:00
|
|
|
else if( statusPct > 0.2f )
|
2010-04-11 22:08:32 +00:00
|
|
|
{
|
2010-04-12 18:38:29 +00:00
|
|
|
TickInterval += 1;
|
2010-04-12 15:12:31 +00:00
|
|
|
if( TickInterval >= 7000 ) TickInterval = 7000;
|
2010-04-12 18:38:29 +00:00
|
|
|
//printf("++ %d, %f\n",TickInterval,statusPct);
|
2010-04-11 22:08:32 +00:00
|
|
|
}
|
|
|
|
else TickInterval = 768;
|
|
|
|
}
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
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 )
|
|
|
|
{
|
2009-10-18 14:56:48 +00:00
|
|
|
//jASSUME( (size & 127) == 0 );
|
2010-04-25 00:31:27 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
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.
|
2010-04-25 00:31:27 +00:00
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
CvtPacketToInt( sndTempBuffer, tempProgress );
|
|
|
|
_WriteSamples( sndTempBuffer, tempProgress );
|
|
|
|
progress = true;
|
|
|
|
}
|
|
|
|
|
2011-07-24 10:58:42 +00:00
|
|
|
#ifdef SPU2X_USE_OLD_STRETCHER
|
2010-04-11 22:08:32 +00:00
|
|
|
UpdateTempoChangeSoundTouch();
|
2011-07-24 10:58:42 +00:00
|
|
|
#else
|
|
|
|
UpdateTempoChangeSoundTouch2();
|
|
|
|
#endif
|
2009-02-18 13:36:20 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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 );
|
|
|
|
|
2009-09-29 19:18:50 +00:00
|
|
|
SoundtouchCfg::ApplySettings( *pSoundTouch );
|
2009-02-18 13:36:20 +00:00
|
|
|
|
|
|
|
pSoundTouch->setTempo(1);
|
|
|
|
|
|
|
|
// some timestretch management vars:
|
|
|
|
|
|
|
|
cTempo = 1.0;
|
|
|
|
eTempo = 1.0;
|
|
|
|
lastPct = 0;
|
|
|
|
lastEmergencyAdj = 0;
|
|
|
|
|
2009-04-12 21:19:29 +00:00
|
|
|
m_predictData = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset timestretch management vars, and delay updates a bit:
|
|
|
|
void SndBuffer::soundtouchClearContents()
|
|
|
|
{
|
2009-10-09 17:28:17 +00:00
|
|
|
if( pSoundTouch == NULL ) return;
|
|
|
|
|
2009-04-12 21:19:29 +00:00
|
|
|
pSoundTouch->clear();
|
|
|
|
pSoundTouch->setTempo(1);
|
|
|
|
|
|
|
|
cTempo = 1.0;
|
|
|
|
eTempo = 1.0;
|
|
|
|
lastPct = 0;
|
|
|
|
lastEmergencyAdj = 0;
|
|
|
|
|
2009-02-18 13:36:20 +00:00
|
|
|
m_predictData = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SndBuffer::soundtouchCleanup()
|
|
|
|
{
|
2009-09-29 19:18:50 +00:00
|
|
|
safe_delete( pSoundTouch );
|
2010-04-25 00:31:27 +00:00
|
|
|
}
|