void SndBuffer::UpdateTempoChangeAsyncMixing() { float statusPct = GetStatusPct(); lastPct = statusPct; if( statusPct < -0.4f ) { TickInterval -= 4; if( TickInterval <= 200 ) TickInterval = 200; //printf("-- %d, %f\n",TickInterval,statusPct); } else if( statusPct > 0.5f ) { TickInterval += 1; if( TickInterval >= 7000 ) TickInterval = 7000; //printf("++ %d, %f\n",TickInterval,statusPct); } else TickInterval = 768; }
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) than 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++; } } }