示例#1
0
void SndBuffer::_WriteSamples(StereoOut32 *bData, int nSamples)
{
    m_predictData = 0;

    // Problem:
    //  If the SPU2 gets out of sync with the SndOut device, the writepos of the
    //  circular buffer will overtake the readpos, leading to a prolonged period
    //  of hopscotching read/write accesses (ie, lots of staticy crap sound for
    //  several seconds).
    //
    // Compromise:
    //  When an overrun occurs, we adapt by discarding a portion of the buffer.
    //  The older portion of the buffer is discarded rather than incoming data,
    //  so that the overall audio synchronization is better.

    int free = m_size - _GetApproximateDataInBuffer(); // -1, but the <= handles that
    if( free <= nSamples )
    {
        // Disabled since the lock-free queue can't handle changing the read end from the write thread
#if 0
        // Buffer overrun!
        // Dump samples from the read portion of the buffer instead of dropping
        // the newly written stuff.

        s32 comp;

        if( SynchMode == 0 ) // TimeStrech on
        {
            comp = timeStretchOverrun();
        }
        else
        {
            // Toss half the buffer plus whatever's being written anew:
            comp = GetAlignedBufferSize( (m_size + nSamples ) / 16 );
            if( comp > (m_size-SndOutPacketSize) ) comp = m_size-SndOutPacketSize;
        }

        _DropSamples_Internal(comp);

        if( MsgOverruns() )
            ConLog(" * SPU2 > Overrun Compensation (%d packets tossed)\n", comp / SndOutPacketSize );
        lastPct = 0.0;		// normalize the timestretcher
#else
        if( MsgOverruns() )
            ConLog(" * SPU2 > Overrun! 1 packet tossed)\n");
        lastPct = 0.0;		// normalize the timestretcher
        return;
#endif
    }

    _WriteSamples_Safe(bData, nSamples);
}
示例#2
0
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;
	}

#ifdef SPU2X_USE_OLD_STRETCHER
	UpdateTempoChangeSoundTouch();
#else
	UpdateTempoChangeSoundTouch2();
#endif

	if( MsgOverruns() )
	{
		if( progress )
		{
			if( ++ts_stats_logcounter > 150 )
			{
				ts_stats_logcounter = 0;
				ConLog( " * SPU2 > Timestretch Stats > %d percent stretched. Total stretchblocks = %d.\n",
					( ts_stats_stretchblocks * 100 ) / ( ts_stats_normalblocks + ts_stats_stretchblocks ),
					ts_stats_stretchblocks);
				ts_stats_normalblocks = 0;
				ts_stats_stretchblocks = 0;
			}
		}
	}
}
示例#3
0
// Returns TRUE if there is data to be output, or false if no data
// is available to be copied.
bool SndBuffer::CheckUnderrunStatus( int& nSamples, int& quietSampleCount )
{
    quietSampleCount = 0;

    int data = _GetApproximateDataInBuffer();
    if( m_underrun_freeze )
    {
        int toFill = m_size / ( (SynchMode == 2) ? 32 : 400); // TimeStretch and Async off?
        toFill = GetAlignedBufferSize( toFill );

        // toFill is now aligned to a SndOutPacket

        if( data < toFill )
        {
            quietSampleCount = nSamples;
            return false;
        }

        m_underrun_freeze = false;
        if( MsgOverruns() )
            ConLog(" * SPU2 > Underrun compensation (%d packets buffered)\n", toFill / SndOutPacketSize );
        lastPct = 0.0;		// normalize timestretcher
    }
    else if( data < nSamples )
    {
        nSamples = data;
        quietSampleCount = SndOutPacketSize - data;
        m_underrun_freeze = true;

        if( SynchMode == 0 ) // TimeStrech on
            timeStretchUnderrun();

        return nSamples != 0;
    }

    return true;
}
示例#4
0
//actual stretch algorithm implementation
void SndBuffer::UpdateTempoChangeSoundTouch2()
{

    long targetSamplesReservoir = 48 * SndOutLatencyMS;  //48000*SndOutLatencyMS/1000
    //base aim at buffer filled %
    float baseTargetFullness = (double)targetSamplesReservoir;  ///(double)m_size;//0.05;

    //state vars
    static bool inside_hysteresis;       //=false;
    static int hys_ok_count;             //=0;
    static float dynamicTargetFullness;  //=baseTargetFullness;
    if (gRequestStretcherReset >= STRETCHER_RESET_THRESHOLD) {
        ConLog("______> stretch: Reset.\n");
        inside_hysteresis = false;
        hys_ok_count = 0;
        dynamicTargetFullness = baseTargetFullness;
    }

    int data = _GetApproximateDataInBuffer();
    float bufferFullness = (float)data;  ///(float)m_size;

#ifdef NEWSTRETCHER_USE_DYNAMIC_TUNING
    {  //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 (!IsInRange(pot_targetIPS, int((float)targetIPS / 1.3f), int((float)targetIPS * 1.3f))) {
                if (MsgOverruns())
                    ConLog("Stretcher: setting iters/sec from %d to %d\n", targetIPS, pot_targetIPS);
                targetIPS = pot_targetIPS;
                AVERAGING_WINDOW = GetClamped((int)(50.0f * (float)targetIPS / 750.0f), 3, (int)AVERAGING_BUFFER_SIZE);
            }
            last = unow;
            iters = 0;
        }
        iters++;
    }
#endif

    //Algorithm params: (threshold params (hysteresis), etc)
    const float hys_ok_factor = 1.04f;
    const float hys_bad_factor = 1.2f;
    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
    int compensationDivider = GetClamped((int)(100.0 * (float)targetIPS / 750), 15, 150);

    float tempoAdjust = bufferFullness / dynamicTargetFullness;
    float avgerage = addToAvg(tempoAdjust);
    tempoAdjust = avgerage;

    // Dampen the adjustment to avoid overshoots (this means the average will compensate to the other side).
    // This is different than simply bigger averaging window since bigger window also has bigger "momentum",
    // so it's slower to slow down when it gets close to the equilibrium state and can therefore resonate.
    // The dampening (sqrt was chosen for no very good reason) manages to mostly prevent that.
    tempoAdjust = sqrt(tempoAdjust);

    tempoAdjust = GetClamped(tempoAdjust, 0.05f, 10.0f);

    if (tempoAdjust < 1)
        baseTargetFullness /= sqrt(tempoAdjust);  // slightly increase latency when running slow.

    dynamicTargetFullness += (baseTargetFullness / tempoAdjust - dynamicTargetFullness) / (double)compensationDivider;
    if (IsInRange(tempoAdjust, 0.9f, 1.1f) && IsInRange(dynamicTargetFullness, baseTargetFullness * 0.9f, baseTargetFullness * 1.1f))
        dynamicTargetFullness = baseTargetFullness;

    if (!inside_hysteresis) {
        if (IsInRange(tempoAdjust, 1.0f / hys_ok_factor, hys_ok_factor))
            hys_ok_count++;
        else
            hys_ok_count = 0;

        if (hys_ok_count >= hys_min_ok_count) {
            inside_hysteresis = true;
            if (MsgOverruns())
                ConLog("======> stretch: None (1:1)\n");
        }

    } else if (!IsInRange(tempoAdjust, 1.0f / hys_bad_factor, hys_bad_factor)) {
        if (MsgOverruns())
            ConLog("~~~~~~> 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
            ConLog("buffers: %4d ms (%3.0f%%), tempo: %f, comp: %2.3f, iters: %d, (N-IPS:%d -> avg:%d, minokc:%d, div:%d) reset:%d\n",
                   (int)(data / 48), (double)(100.0 * bufferFullness / baseTargetFullness), (double)tempoAdjust, (double)(dynamicTargetFullness / baseTargetFullness), iters, (int)targetIPS, AVERAGING_WINDOW, hys_min_ok_count, compensationDivider, gRequestStretcherReset);
            last = unow;
            iters = 0;
        }
        iters++;
    }

    pSoundTouch->setTempo(tempoAdjust);
    if (gRequestStretcherReset >= STRETCHER_RESET_THRESHOLD)
        gRequestStretcherReset = 0;

    return;
}
示例#5
0
//actual stretch algorithm implementation
void SndBuffer::UpdateTempoChangeSoundTouch2()
{
	//base aim at buffer filled %
	float targetFullness=0.1;

	//threshold params (hysteresis)
	static const float hys_ok_factor=1.03;
	static const int hys_min_ok_count=100; //consecutive iterations within hys_ok before going to 1:1 mode
	static const float hys_bad_factor=1.2;

	//state vars
	static bool inside_hysteresis=false;
	static int hys_ok_count=0;

	//some precalculated values
	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;
	static float last_bufferFullness=0;
	if(last_bufferFullness != bufferFullness){// only recalculate if buffer changes
		last_bufferFullness = bufferFullness;
		
		float tempoAdjust=bufferFullness/targetFullness;
		float avgerage = addToAvg(tempoAdjust);
		tempoAdjust = avgerage;
		if( tempoAdjust>1.2 ) tempoAdjust=0.2+pow(tempoAdjust-0.2f, 3);//reduce latency for faster speeds only
		tempoAdjust = clamp( tempoAdjust, 0.1f, 10.0f);

		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");
			}

		}
		else if( tempoAdjust != clamp( tempoAdjust, hys_bad_min, hys_bad_max ) ){
			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: %f, actual adjust: %f, iterations: %d\n", bufferFullness, tempoAdjust, iters);
				last=unow;
				iters=0;
			}
			iters++;
		}

		pSoundTouch->setTempo(tempoAdjust);
		
		//collect some stats...
		if(tempoAdjust==1.0)
			ts_stats_normalblocks++;
		else
			ts_stats_stretchblocks++;

	}

	return;
}