| 1 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,315 +0,0 @@ |
| 1 |
-/* SPU2-X, A plugin for Emulating the Sound Processing Unit of the Playstation 2 |
|
| 2 |
-* Developed and maintained by the Pcsx2 Development Team. |
|
| 3 |
-* |
|
| 4 |
-* Original portions from SPU2ghz are (c) 2008 by David Quintana [gigaherz] |
|
| 5 |
-* |
|
| 6 |
-* SPU2-X is free software: you can redistribute it and/or modify it under the terms |
|
| 7 |
-* of the GNU Lesser General Public License as published by the Free Software Found- |
|
| 8 |
-* ation, either version 3 of the License, or (at your option) any later version. |
|
| 9 |
-* |
|
| 10 |
-* SPU2-X is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; |
|
| 11 |
-* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
|
| 12 |
-* PURPOSE. See the GNU Lesser General Public License for more details. |
|
| 13 |
-* |
|
| 14 |
-* You should have received a copy of the GNU Lesser General Public License |
|
| 15 |
-* along with SPU2-X. If not, see <http://www.gnu.org/licenses/>. |
|
| 16 |
-*/ |
|
| 17 |
- |
|
| 18 |
-#include "XSFCommon.h" |
|
| 19 |
- |
|
| 20 |
-#include "../types.h" |
|
| 21 |
-#include "SoundTouch/SoundTouch.h" |
|
| 22 |
-#include "SndOut.h" |
|
| 23 |
- |
|
| 24 |
-static std::unique_ptr<soundtouch::SoundTouch> pSoundTouch; |
|
| 25 |
-static int ts_stats_stretchblocks = 0; |
|
| 26 |
-static int ts_stats_normalblocks = 0; |
|
| 27 |
-static int ts_stats_logcounter = 0; |
|
| 28 |
- |
|
| 29 |
-// data prediction amount, used to "commit" data that hasn't |
|
| 30 |
-// finished timestretch processing. |
|
| 31 |
-int32_t SndBuffer::m_predictData; |
|
| 32 |
- |
|
| 33 |
-// records last buffer status (fill %, range -100 to 100, with 0 being 50% full) |
|
| 34 |
-float SndBuffer::lastPct; |
|
| 35 |
-float SndBuffer::lastEmergencyAdj; |
|
| 36 |
- |
|
| 37 |
-float SndBuffer::cTempo = 1; |
|
| 38 |
-float SndBuffer::eTempo = 1; |
|
| 39 |
-int SndBuffer::freezeTempo = 0; |
|
| 40 |
- |
|
| 41 |
-void SndBuffer::PredictDataWrite(int samples) |
|
| 42 |
-{
|
|
| 43 |
- m_predictData += samples; |
|
| 44 |
-} |
|
| 45 |
- |
|
| 46 |
-// Calculate the buffer status percentage. |
|
| 47 |
-// Returns range from -1.0 to 1.0 |
|
| 48 |
-// 1.0 = buffer overflow! |
|
| 49 |
-// 0.0 = buffer nominal (50% full) |
|
| 50 |
-// -1.0 = buffer underflow! |
|
| 51 |
-float SndBuffer::GetStatusPct() |
|
| 52 |
-{
|
|
| 53 |
- // Get the buffer status of the output driver too, so that we can |
|
| 54 |
- // obtain a more accurate overall buffer status. |
|
| 55 |
- |
|
| 56 |
- int drvempty = 0; |
|
| 57 |
- //int drvempty = mods[OutputModule]->GetEmptySampleCount(); // / 2; |
|
| 58 |
- //TODO |
|
| 59 |
- |
|
| 60 |
- //ConLog( "Data %d >>> driver: %d predict: %d\n", data, drvempty, predictData ); |
|
| 61 |
- |
|
| 62 |
- float result = static_cast<float>(m_data + m_predictData - drvempty) - (m_size / 2); |
|
| 63 |
- result /= m_size / 2; |
|
| 64 |
- return result; |
|
| 65 |
-} |
|
| 66 |
- |
|
| 67 |
-void SndBuffer::UpdateTempoChange() |
|
| 68 |
-{
|
|
| 69 |
- if (--freezeTempo > 0) |
|
| 70 |
- return; |
|
| 71 |
- |
|
| 72 |
- float statusPct = GetStatusPct(); |
|
| 73 |
- float pctChange = statusPct - lastPct; |
|
| 74 |
- |
|
| 75 |
- float tempoChange; |
|
| 76 |
- float emergencyAdj = 0; |
|
| 77 |
- float newcee = cTempo; // workspace var. for cTempo |
|
| 78 |
- |
|
| 79 |
- // IMPORTANT! |
|
| 80 |
- // If you plan to tweak these values, make sure you're using a release build |
|
| 81 |
- // OUTSIDE THE DEBUGGER to test it! The Visual Studio debugger can really cause |
|
| 82 |
- // erratic behavior in the audio buffers, and makes the timestretcher seem a |
|
| 83 |
- // lot more inconsistent than it really is. |
|
| 84 |
- |
|
| 85 |
- // We have two factors. |
|
| 86 |
- // * Distance from nominal buffer status (50% full) |
|
| 87 |
- // * The change from previous update to this update. |
|
| 88 |
- |
|
| 89 |
- // Prediction based on the buffer change: |
|
| 90 |
- // (linear seems to work better here) |
|
| 91 |
- |
|
| 92 |
- tempoChange = pctChange * 0.75f; |
|
| 93 |
- |
|
| 94 |
- if (statusPct * tempoChange < 0.0f) |
|
| 95 |
- // only apply tempo change if it is in synch with the buffer status. |
|
| 96 |
- // In other words, if the buffer is high (over 0%), and is decreasing, |
|
| 97 |
- // ignore it. It'll just muck things up. |
|
| 98 |
- |
|
| 99 |
- tempoChange = 0; |
|
| 100 |
- |
|
| 101 |
- // Sudden spikes in framerate can cause the nominal buffer status |
|
| 102 |
- // to go critical, in which case we have to enact an emergency |
|
| 103 |
- // stretch. The following cubic formulas do that. Values near |
|
| 104 |
- // the extremeites give much larger results than those near 0. |
|
| 105 |
- // And the value is added only this time, and does not accumulate. |
|
| 106 |
- // (otherwise a large value like this would cause problems down the road) |
|
| 107 |
- |
|
| 108 |
- // Constants: |
|
| 109 |
- // Weight - weights the statusPct's "emergency" consideration. |
|
| 110 |
- // higher values here will make the buffer perform more drastic |
|
| 111 |
- // compensations at the outer edges of the buffer (at -75 or +75% |
|
| 112 |
- // or beyond, for example). |
|
| 113 |
- |
|
| 114 |
- // Range - scales the adjustment to the given range (more or less). |
|
| 115 |
- // The actual range is dependent on the weight used, so if you increase |
|
| 116 |
- // Weight you'll usually want to decrease Range somewhat to compensate. |
|
| 117 |
- |
|
| 118 |
- // Prediction based on the buffer fill status: |
|
| 119 |
- |
|
| 120 |
- float statusWeight = 2.99f; |
|
| 121 |
- float statusRange = 0.068f; |
|
| 122 |
- |
|
| 123 |
- // "non-emergency" deadzone: In this area stretching will be strongly discouraged. |
|
| 124 |
- // Note: due tot he nature of timestretch latency, it's always a wee bit harder to |
|
| 125 |
- // cope with low fps (underruns) than it is high fps (overruns). So to help out a |
|
| 126 |
- // little, the low-end portions of this check are less forgiving than the high-sides. |
|
| 127 |
- |
|
| 128 |
- 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) |
|
| 129 |
- emergencyAdj = std::pow(statusPct * statusWeight, 3.0f) * statusRange; |
|
| 130 |
- |
|
| 131 |
- // Smooth things out by factoring our previous adjustment into this one. |
|
| 132 |
- // It helps make the system 'feel' a little smarter by giving it at least |
|
| 133 |
- // one packet worth of history to help work off of: |
|
| 134 |
- |
|
| 135 |
- emergencyAdj = (emergencyAdj * 0.75f) + (lastEmergencyAdj * 0.25f); |
|
| 136 |
- |
|
| 137 |
- lastEmergencyAdj = emergencyAdj; |
|
| 138 |
- lastPct = statusPct; |
|
| 139 |
- |
|
| 140 |
- // Accumulate a fraction of the tempo change into the tempo itself. |
|
| 141 |
- // This helps the system run "smarter" to games that run consistently |
|
| 142 |
- // fast or slow by altering the base tempo to something closer to the |
|
| 143 |
- // game's active speed. In tests most games normalize within 2 seconds |
|
| 144 |
- // at 100ms latency, which is pretty good (larger buffers normalize even |
|
| 145 |
- // quicker). |
|
| 146 |
- |
|
| 147 |
- newcee += newcee * (tempoChange + emergencyAdj) * 0.03f; |
|
| 148 |
- |
|
| 149 |
- // Apply tempoChange as a scale of cTempo. That way the effect is proportional |
|
| 150 |
- // to the current tempo. (otherwise tempos rate of change at the extremes would |
|
| 151 |
- // be too drastic) |
|
| 152 |
- |
|
| 153 |
- float newTempo = newcee + (emergencyAdj * cTempo); |
|
| 154 |
- |
|
| 155 |
- // ... and as a final optimization, only stretch if the new tempo is outside |
|
| 156 |
- // a nominal threshold. Keep this threshold check small, because it could |
|
| 157 |
- // cause some serious side effects otherwise. (enlarging the cTempo check above |
|
| 158 |
- // is usually better/safer) |
|
| 159 |
- if (newTempo < 0.970f || newTempo > 1.045f) |
|
| 160 |
- {
|
|
| 161 |
- cTempo = newcee; |
|
| 162 |
- |
|
| 163 |
- if (newTempo < 0.10f) |
|
| 164 |
- newTempo = 0.10f; |
|
| 165 |
- else if (newTempo > 10.0f) |
|
| 166 |
- newTempo = 10.0f; |
|
| 167 |
- |
|
| 168 |
- if (cTempo < 0.15f) |
|
| 169 |
- cTempo = 0.15f; |
|
| 170 |
- else if (cTempo > 7.5f) |
|
| 171 |
- cTempo = 7.5f; |
|
| 172 |
- |
|
| 173 |
- pSoundTouch->setTempo(eTempo = newTempo); |
|
| 174 |
- ++ts_stats_stretchblocks; |
|
| 175 |
- |
|
| 176 |
- /*ConLog(" * SPU2: [Nominal %d%%] [Emergency: %d%%] (baseTempo: %d%% ) (newTempo: %d%%) (buffer: %d%%)\n",
|
|
| 177 |
- //(relation < 0.0) ? "Normalize" : "", |
|
| 178 |
- (int)(tempoChange * 100.0 * 0.03), |
|
| 179 |
- (int)(emergencyAdj * 100.0), |
|
| 180 |
- (int)(cTempo * 100.0), |
|
| 181 |
- (int)(newTempo * 100.0), |
|
| 182 |
- (int)(statusPct * 100.0) |
|
| 183 |
- );*/ |
|
| 184 |
- } |
|
| 185 |
- else |
|
| 186 |
- {
|
|
| 187 |
- // Nominal operation -- turn off stretching. |
|
| 188 |
- // note: eTempo 'slides' toward 1.0 for smoother audio and better |
|
| 189 |
- // protection against spikes. |
|
| 190 |
- if (!fEqual(cTempo, 1.0f)) |
|
| 191 |
- {
|
|
| 192 |
- cTempo = 1.0f; |
|
| 193 |
- eTempo = (1.0f + eTempo) * 0.5f; |
|
| 194 |
- pSoundTouch->setTempo(eTempo); |
|
| 195 |
- } |
|
| 196 |
- else |
|
| 197 |
- {
|
|
| 198 |
- if (!fEqual(eTempo, cTempo)) |
|
| 199 |
- pSoundTouch->setTempo(eTempo = cTempo); |
|
| 200 |
- ++ts_stats_normalblocks; |
|
| 201 |
- } |
|
| 202 |
- } |
|
| 203 |
-} |
|
| 204 |
- |
|
| 205 |
-void SndBuffer::timeStretchUnderrun() |
|
| 206 |
-{
|
|
| 207 |
- // timeStretcher failed it's job. We need to slow down the audio some. |
|
| 208 |
- |
|
| 209 |
- cTempo -= cTempo * 0.12f; |
|
| 210 |
- eTempo -= eTempo * 0.30f; |
|
| 211 |
- if (eTempo < 0.1f) |
|
| 212 |
- eTempo = 0.1f; |
|
| 213 |
- pSoundTouch->setTempo(eTempo); |
|
| 214 |
-} |
|
| 215 |
- |
|
| 216 |
-int32_t SndBuffer::timeStretchOverrun() |
|
| 217 |
-{
|
|
| 218 |
- // If we overran it means the timestretcher failed. We need to speed |
|
| 219 |
- // up audio playback. |
|
| 220 |
- cTempo += cTempo * 0.12f; |
|
| 221 |
- eTempo += eTempo * 0.40f; |
|
| 222 |
- if (eTempo > 7.5f) |
|
| 223 |
- eTempo = 7.5f; |
|
| 224 |
- pSoundTouch->setTempo(eTempo); |
|
| 225 |
- |
|
| 226 |
- // Throw out just a little bit (two packets worth) to help |
|
| 227 |
- // give the TS some room to work: |
|
| 228 |
- |
|
| 229 |
- return SndOutPacketSize * 2; |
|
| 230 |
-} |
|
| 231 |
- |
|
| 232 |
-static void CvtPacketToFloat(StereoOut32 *srcdest) |
|
| 233 |
-{
|
|
| 234 |
- StereoOutFloat *dest = reinterpret_cast<StereoOutFloat *>(srcdest); |
|
| 235 |
- const StereoOut32 *src = srcdest; |
|
| 236 |
- for (int i = 0; i < SndOutPacketSize; ++i, ++dest, ++src) |
|
| 237 |
- *dest = static_cast<StereoOutFloat>(*src); |
|
| 238 |
-} |
|
| 239 |
- |
|
| 240 |
-// Parameter note: Size should always be a multiple of 128, thanks! |
|
| 241 |
-static void CvtPacketToInt(StereoOut32 *srcdest, uint32_t size) |
|
| 242 |
-{
|
|
| 243 |
- //jASSUME( (size & 127) == 0 ); |
|
| 244 |
- |
|
| 245 |
- const StereoOutFloat *src = reinterpret_cast<StereoOutFloat *>(srcdest); |
|
| 246 |
- StereoOut32 *dest = srcdest; |
|
| 247 |
- |
|
| 248 |
- for (uint32_t i = 0; i < size; ++i, ++dest, ++src) |
|
| 249 |
- *dest = static_cast<StereoOut32>(*src); |
|
| 250 |
-} |
|
| 251 |
- |
|
| 252 |
-void SndBuffer::timeStretchWrite() |
|
| 253 |
-{
|
|
| 254 |
- bool progress = false; |
|
| 255 |
- |
|
| 256 |
- // data prediction helps keep the tempo adjustments more accurate. |
|
| 257 |
- // The timestretcher returns packets in belated "clump" form. |
|
| 258 |
- // Meaning that most of the time we'll get nothing back, and then |
|
| 259 |
- // suddenly we'll get several chunks back at once. Thus we use |
|
| 260 |
- // data prediction to make the timestretcher more responsive. |
|
| 261 |
- |
|
| 262 |
- PredictDataWrite(SndOutPacketSize / eTempo); |
|
| 263 |
- CvtPacketToFloat(sndTempBuffer.get()); |
|
| 264 |
- |
|
| 265 |
- pSoundTouch->putSamples(reinterpret_cast<float *>(sndTempBuffer.get()), SndOutPacketSize); |
|
| 266 |
- |
|
| 267 |
- int tempProgress; |
|
| 268 |
- while (tempProgress = pSoundTouch->receiveSamples(reinterpret_cast<float *>(sndTempBuffer.get()), SndOutPacketSize), !!tempProgress) |
|
| 269 |
- {
|
|
| 270 |
- // Hint: It's assumed that pSoundTouch will return chunks of 128 bytes (it always does as |
|
| 271 |
- // long as the SSE optimizations are enabled), which means we can do our own SSE opts here. |
|
| 272 |
- |
|
| 273 |
- CvtPacketToInt(sndTempBuffer.get(), tempProgress); |
|
| 274 |
- _WriteSamples(sndTempBuffer.get(), tempProgress); |
|
| 275 |
- progress = true; |
|
| 276 |
- } |
|
| 277 |
- |
|
| 278 |
- UpdateTempoChange(); |
|
| 279 |
- |
|
| 280 |
- //TODO |
|
| 281 |
- //if( MsgOverruns() ) |
|
| 282 |
- {
|
|
| 283 |
- if(progress) |
|
| 284 |
- {
|
|
| 285 |
- if (++ts_stats_logcounter > 300) |
|
| 286 |
- {
|
|
| 287 |
- ts_stats_logcounter = 0; |
|
| 288 |
- printf(" * SPU2 > Timestretch Stats > %d%% of packets stretched.\n", (ts_stats_stretchblocks * 100) / (ts_stats_normalblocks + ts_stats_stretchblocks));
|
|
| 289 |
- ts_stats_normalblocks = ts_stats_stretchblocks = 0; |
|
| 290 |
- } |
|
| 291 |
- } |
|
| 292 |
- } |
|
| 293 |
-} |
|
| 294 |
- |
|
| 295 |
-void SndBuffer::soundtouchInit() |
|
| 296 |
-{
|
|
| 297 |
- pSoundTouch.reset(new soundtouch::SoundTouch()); |
|
| 298 |
- pSoundTouch->setSampleRate(SampleRate); |
|
| 299 |
- pSoundTouch->setChannels(2); |
|
| 300 |
- |
|
| 301 |
- pSoundTouch->setSetting(soundtouch::SETTING_USE_QUICKSEEK, 0); |
|
| 302 |
- pSoundTouch->setSetting(soundtouch::SETTING_USE_AA_FILTER, 0); |
|
| 303 |
- |
|
| 304 |
- pSoundTouch->setTempo(1); |
|
| 305 |
- |
|
| 306 |
- // some timestretch management vars: |
|
| 307 |
- |
|
| 308 |
- cTempo = eTempo = 1.0; |
|
| 309 |
- lastPct = lastEmergencyAdj = 0; |
|
| 310 |
- |
|
| 311 |
- // just freeze tempo changes for a while at startup. |
|
| 312 |
- // the driver buffers are bogus anyway. |
|
| 313 |
- freezeTempo = 16; |
|
| 314 |
- m_predictData = 0; |
|
| 315 |
-} |
| ... | ... |
@@ -259,7 +259,7 @@ void SndBuffer::timeStretchWrite() |
| 259 | 259 |
// suddenly we'll get several chunks back at once. Thus we use |
| 260 | 260 |
// data prediction to make the timestretcher more responsive. |
| 261 | 261 |
|
| 262 |
- PredictDataWrite(static_cast<int>(SndOutPacketSize / eTempo)); |
|
| 262 |
+ PredictDataWrite(SndOutPacketSize / eTempo); |
|
| 263 | 263 |
CvtPacketToFloat(sndTempBuffer.get()); |
| 264 | 264 |
|
| 265 | 265 |
pSoundTouch->putSamples(reinterpret_cast<float *>(sndTempBuffer.get()), SndOutPacketSize); |
| ... | ... |
@@ -17,15 +17,11 @@ |
| 17 | 17 |
|
| 18 | 18 |
#include "XSFCommon.h" |
| 19 | 19 |
|
| 20 |
-//#include "Global.h" |
|
| 21 | 20 |
#include "../types.h" |
| 22 | 21 |
#include "SoundTouch/SoundTouch.h" |
| 23 | 22 |
#include "SndOut.h" |
| 24 |
-//#include "SoundTouch/WavFile.h" |
|
| 25 | 23 |
|
| 26 |
-#include "SoundTouch/Dialogs.h" |
|
| 27 |
- |
|
| 28 |
-static soundtouch::SoundTouch* pSoundTouch = NULL; |
|
| 24 |
+static std::unique_ptr<soundtouch::SoundTouch> pSoundTouch; |
|
| 29 | 25 |
static int ts_stats_stretchblocks = 0; |
| 30 | 26 |
static int ts_stats_normalblocks = 0; |
| 31 | 27 |
static int ts_stats_logcounter = 0; |
| ... | ... |
@@ -42,7 +38,7 @@ float SndBuffer::cTempo = 1; |
| 42 | 38 |
float SndBuffer::eTempo = 1; |
| 43 | 39 |
int SndBuffer::freezeTempo = 0; |
| 44 | 40 |
|
| 45 |
-void SndBuffer::PredictDataWrite( int samples ) |
|
| 41 |
+void SndBuffer::PredictDataWrite(int samples) |
|
| 46 | 42 |
{
|
| 47 | 43 |
m_predictData += samples; |
| 48 | 44 |
} |
| ... | ... |
@@ -63,24 +59,22 @@ float SndBuffer::GetStatusPct() |
| 63 | 59 |
|
| 64 | 60 |
//ConLog( "Data %d >>> driver: %d predict: %d\n", data, drvempty, predictData ); |
| 65 | 61 |
|
| 66 |
- float result = (float)(m_data + m_predictData - drvempty) - (m_size/2); |
|
| 67 |
- result /= (m_size/2); |
|
| 62 |
+ float result = static_cast<float>(m_data + m_predictData - drvempty) - (m_size / 2); |
|
| 63 |
+ result /= m_size / 2; |
|
| 68 | 64 |
return result; |
| 69 | 65 |
} |
| 70 | 66 |
|
| 71 | 67 |
void SndBuffer::UpdateTempoChange() |
| 72 | 68 |
{
|
| 73 |
- if( --freezeTempo > 0 ) |
|
| 74 |
- {
|
|
| 69 |
+ if (--freezeTempo > 0) |
|
| 75 | 70 |
return; |
| 76 |
- } |
|
| 77 | 71 |
|
| 78 | 72 |
float statusPct = GetStatusPct(); |
| 79 | 73 |
float pctChange = statusPct - lastPct; |
| 80 | 74 |
|
| 81 | 75 |
float tempoChange; |
| 82 | 76 |
float emergencyAdj = 0; |
| 83 |
- float newcee = cTempo; // workspace var. for cTempo |
|
| 77 |
+ float newcee = cTempo; // workspace var. for cTempo |
|
| 84 | 78 |
|
| 85 | 79 |
// IMPORTANT! |
| 86 | 80 |
// If you plan to tweak these values, make sure you're using a release build |
| ... | ... |
@@ -97,14 +91,12 @@ void SndBuffer::UpdateTempoChange() |
| 97 | 91 |
|
| 98 | 92 |
tempoChange = pctChange * 0.75f; |
| 99 | 93 |
|
| 100 |
- if( statusPct * tempoChange < 0.0f ) |
|
| 101 |
- {
|
|
| 94 |
+ if (statusPct * tempoChange < 0.0f) |
|
| 102 | 95 |
// only apply tempo change if it is in synch with the buffer status. |
| 103 | 96 |
// In other words, if the buffer is high (over 0%), and is decreasing, |
| 104 | 97 |
// ignore it. It'll just muck things up. |
| 105 | 98 |
|
| 106 | 99 |
tempoChange = 0; |
| 107 |
- } |
|
| 108 | 100 |
|
| 109 | 101 |
// Sudden spikes in framerate can cause the nominal buffer status |
| 110 | 102 |
// to go critical, in which case we have to enact an emergency |
| ... | ... |
@@ -125,27 +117,22 @@ void SndBuffer::UpdateTempoChange() |
| 125 | 117 |
|
| 126 | 118 |
// Prediction based on the buffer fill status: |
| 127 | 119 |
|
| 128 |
- const float statusWeight = 2.99f; |
|
| 129 |
- const float statusRange = 0.068f; |
|
| 120 |
+ float statusWeight = 2.99f; |
|
| 121 |
+ float statusRange = 0.068f; |
|
| 130 | 122 |
|
| 131 | 123 |
// "non-emergency" deadzone: In this area stretching will be strongly discouraged. |
| 132 | 124 |
// Note: due tot he nature of timestretch latency, it's always a wee bit harder to |
| 133 | 125 |
// cope with low fps (underruns) than it is high fps (overruns). So to help out a |
| 134 | 126 |
// little, the low-end portions of this check are less forgiving than the high-sides. |
| 135 | 127 |
|
| 136 |
- if( cTempo < 0.965f || cTempo > 1.060f || |
|
| 137 |
- pctChange < -0.38f || pctChange > 0.54f || |
|
| 138 |
- statusPct < -0.32f || statusPct > 0.39f || |
|
| 139 |
- eTempo < 0.89f || eTempo > 1.19f ) |
|
| 140 |
- {
|
|
| 141 |
- emergencyAdj = ( pow( statusPct*statusWeight, 3.0f ) * statusRange); |
|
| 142 |
- } |
|
| 128 |
+ 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) |
|
| 129 |
+ emergencyAdj = std::pow(statusPct * statusWeight, 3.0f) * statusRange; |
|
| 143 | 130 |
|
| 144 | 131 |
// Smooth things out by factoring our previous adjustment into this one. |
| 145 | 132 |
// It helps make the system 'feel' a little smarter by giving it at least |
| 146 | 133 |
// one packet worth of history to help work off of: |
| 147 | 134 |
|
| 148 |
- emergencyAdj = (emergencyAdj * 0.75f) + (lastEmergencyAdj * 0.25f ); |
|
| 135 |
+ emergencyAdj = (emergencyAdj * 0.75f) + (lastEmergencyAdj * 0.25f); |
|
| 149 | 136 |
|
| 150 | 137 |
lastEmergencyAdj = emergencyAdj; |
| 151 | 138 |
lastPct = statusPct; |
| ... | ... |
@@ -157,30 +144,34 @@ void SndBuffer::UpdateTempoChange() |
| 157 | 144 |
// at 100ms latency, which is pretty good (larger buffers normalize even |
| 158 | 145 |
// quicker). |
| 159 | 146 |
|
| 160 |
- newcee += newcee * (tempoChange+emergencyAdj) * 0.03f; |
|
| 147 |
+ newcee += newcee * (tempoChange + emergencyAdj) * 0.03f; |
|
| 161 | 148 |
|
| 162 | 149 |
// Apply tempoChange as a scale of cTempo. That way the effect is proportional |
| 163 | 150 |
// to the current tempo. (otherwise tempos rate of change at the extremes would |
| 164 | 151 |
// be too drastic) |
| 165 | 152 |
|
| 166 |
- float newTempo = newcee + ( emergencyAdj * cTempo ); |
|
| 153 |
+ float newTempo = newcee + (emergencyAdj * cTempo); |
|
| 167 | 154 |
|
| 168 | 155 |
// ... and as a final optimization, only stretch if the new tempo is outside |
| 169 | 156 |
// a nominal threshold. Keep this threshold check small, because it could |
| 170 | 157 |
// cause some serious side effects otherwise. (enlarging the cTempo check above |
| 171 | 158 |
// is usually better/safer) |
| 172 |
- if( newTempo < 0.970f || newTempo > 1.045f ) |
|
| 159 |
+ if (newTempo < 0.970f || newTempo > 1.045f) |
|
| 173 | 160 |
{
|
| 174 |
- cTempo = (float)newcee; |
|
| 161 |
+ cTempo = newcee; |
|
| 175 | 162 |
|
| 176 |
- if( newTempo < 0.10f ) newTempo = 0.10f; |
|
| 177 |
- else if( newTempo > 10.0f ) newTempo = 10.0f; |
|
| 163 |
+ if (newTempo < 0.10f) |
|
| 164 |
+ newTempo = 0.10f; |
|
| 165 |
+ else if (newTempo > 10.0f) |
|
| 166 |
+ newTempo = 10.0f; |
|
| 178 | 167 |
|
| 179 |
- if( cTempo < 0.15f ) cTempo = 0.15f; |
|
| 180 |
- else if( cTempo > 7.5f ) cTempo = 7.5f; |
|
| 168 |
+ if (cTempo < 0.15f) |
|
| 169 |
+ cTempo = 0.15f; |
|
| 170 |
+ else if (cTempo > 7.5f) |
|
| 171 |
+ cTempo = 7.5f; |
|
| 181 | 172 |
|
| 182 |
- pSoundTouch->setTempo( eTempo = (float)newTempo ); |
|
| 183 |
- ts_stats_stretchblocks++; |
|
| 173 |
+ pSoundTouch->setTempo(eTempo = newTempo); |
|
| 174 |
+ ++ts_stats_stretchblocks; |
|
| 184 | 175 |
|
| 185 | 176 |
/*ConLog(" * SPU2: [Nominal %d%%] [Emergency: %d%%] (baseTempo: %d%% ) (newTempo: %d%%) (buffer: %d%%)\n",
|
| 186 | 177 |
//(relation < 0.0) ? "Normalize" : "", |
| ... | ... |
@@ -199,14 +190,14 @@ void SndBuffer::UpdateTempoChange() |
| 199 | 190 |
if (!fEqual(cTempo, 1.0f)) |
| 200 | 191 |
{
|
| 201 | 192 |
cTempo = 1.0f; |
| 202 |
- eTempo = ( 1.0f + eTempo ) * 0.5f; |
|
| 203 |
- pSoundTouch->setTempo( eTempo ); |
|
| 193 |
+ eTempo = (1.0f + eTempo) * 0.5f; |
|
| 194 |
+ pSoundTouch->setTempo(eTempo); |
|
| 204 | 195 |
} |
| 205 | 196 |
else |
| 206 | 197 |
{
|
| 207 | 198 |
if (!fEqual(eTempo, cTempo)) |
| 208 |
- pSoundTouch->setTempo( eTempo=cTempo ); |
|
| 209 |
- ts_stats_normalblocks++; |
|
| 199 |
+ pSoundTouch->setTempo(eTempo = cTempo); |
|
| 200 |
+ ++ts_stats_normalblocks; |
|
| 210 | 201 |
} |
| 211 | 202 |
} |
| 212 | 203 |
} |
| ... | ... |
@@ -215,10 +206,11 @@ void SndBuffer::timeStretchUnderrun() |
| 215 | 206 |
{
|
| 216 | 207 |
// timeStretcher failed it's job. We need to slow down the audio some. |
| 217 | 208 |
|
| 218 |
- cTempo -= (cTempo * 0.12f); |
|
| 219 |
- eTempo -= (eTempo * 0.30f); |
|
| 220 |
- if( eTempo < 0.1f ) eTempo = 0.1f; |
|
| 221 |
- pSoundTouch->setTempo( eTempo ); |
|
| 209 |
+ cTempo -= cTempo * 0.12f; |
|
| 210 |
+ eTempo -= eTempo * 0.30f; |
|
| 211 |
+ if (eTempo < 0.1f) |
|
| 212 |
+ eTempo = 0.1f; |
|
| 213 |
+ pSoundTouch->setTempo(eTempo); |
|
| 222 | 214 |
} |
| 223 | 215 |
|
| 224 | 216 |
int32_t SndBuffer::timeStretchOverrun() |
| ... | ... |
@@ -227,33 +219,34 @@ int32_t SndBuffer::timeStretchOverrun() |
| 227 | 219 |
// up audio playback. |
| 228 | 220 |
cTempo += cTempo * 0.12f; |
| 229 | 221 |
eTempo += eTempo * 0.40f; |
| 230 |
- if( eTempo > 7.5f ) eTempo = 7.5f; |
|
| 231 |
- pSoundTouch->setTempo( eTempo ); |
|
| 222 |
+ if (eTempo > 7.5f) |
|
| 223 |
+ eTempo = 7.5f; |
|
| 224 |
+ pSoundTouch->setTempo(eTempo); |
|
| 232 | 225 |
|
| 233 | 226 |
// Throw out just a little bit (two packets worth) to help |
| 234 | 227 |
// give the TS some room to work: |
| 235 | 228 |
|
| 236 |
- return SndOutPacketSize*2; |
|
| 229 |
+ return SndOutPacketSize * 2; |
|
| 237 | 230 |
} |
| 238 | 231 |
|
| 239 |
-static void CvtPacketToFloat( StereoOut32* srcdest ) |
|
| 232 |
+static void CvtPacketToFloat(StereoOut32 *srcdest) |
|
| 240 | 233 |
{
|
| 241 |
- StereoOutFloat* dest = (StereoOutFloat*)srcdest; |
|
| 242 |
- const StereoOut32* src = (StereoOut32*)srcdest; |
|
| 243 |
- for( int i=0; i<SndOutPacketSize; ++i, ++dest, ++src ) |
|
| 244 |
- *dest = (StereoOutFloat)*src; |
|
| 234 |
+ StereoOutFloat *dest = reinterpret_cast<StereoOutFloat *>(srcdest); |
|
| 235 |
+ const StereoOut32 *src = srcdest; |
|
| 236 |
+ for (int i = 0; i < SndOutPacketSize; ++i, ++dest, ++src) |
|
| 237 |
+ *dest = static_cast<StereoOutFloat>(*src); |
|
| 245 | 238 |
} |
| 246 | 239 |
|
| 247 | 240 |
// Parameter note: Size should always be a multiple of 128, thanks! |
| 248 |
-static void CvtPacketToInt( StereoOut32* srcdest, uint32_t size ) |
|
| 241 |
+static void CvtPacketToInt(StereoOut32 *srcdest, uint32_t size) |
|
| 249 | 242 |
{
|
| 250 | 243 |
//jASSUME( (size & 127) == 0 ); |
| 251 | 244 |
|
| 252 |
- const StereoOutFloat* src = (StereoOutFloat*)srcdest; |
|
| 253 |
- StereoOut32* dest = srcdest; |
|
| 245 |
+ const StereoOutFloat *src = reinterpret_cast<StereoOutFloat *>(srcdest); |
|
| 246 |
+ StereoOut32 *dest = srcdest; |
|
| 254 | 247 |
|
| 255 |
- for( uint32_t i=0; i<size; ++i, ++dest, ++src ) |
|
| 256 |
- *dest = (StereoOut32)*src; |
|
| 248 |
+ for (uint32_t i = 0; i < size; ++i, ++dest, ++src) |
|
| 249 |
+ *dest = static_cast<StereoOut32>(*src); |
|
| 257 | 250 |
} |
| 258 | 251 |
|
| 259 | 252 |
void SndBuffer::timeStretchWrite() |
| ... | ... |
@@ -266,20 +259,19 @@ void SndBuffer::timeStretchWrite() |
| 266 | 259 |
// suddenly we'll get several chunks back at once. Thus we use |
| 267 | 260 |
// data prediction to make the timestretcher more responsive. |
| 268 | 261 |
|
| 269 |
- PredictDataWrite( (int)( SndOutPacketSize / eTempo ) ); |
|
| 270 |
- CvtPacketToFloat( sndTempBuffer ); |
|
| 262 |
+ PredictDataWrite(static_cast<int>(SndOutPacketSize / eTempo)); |
|
| 263 |
+ CvtPacketToFloat(sndTempBuffer.get()); |
|
| 271 | 264 |
|
| 272 |
- pSoundTouch->putSamples( (float*)sndTempBuffer, SndOutPacketSize ); |
|
| 265 |
+ pSoundTouch->putSamples(reinterpret_cast<float *>(sndTempBuffer.get()), SndOutPacketSize); |
|
| 273 | 266 |
|
| 274 | 267 |
int tempProgress; |
| 275 |
- while( tempProgress = pSoundTouch->receiveSamples( (float*)sndTempBuffer, SndOutPacketSize), |
|
| 276 |
- tempProgress != 0 ) |
|
| 268 |
+ while (tempProgress = pSoundTouch->receiveSamples(reinterpret_cast<float *>(sndTempBuffer.get()), SndOutPacketSize), !!tempProgress) |
|
| 277 | 269 |
{
|
| 278 | 270 |
// Hint: It's assumed that pSoundTouch will return chunks of 128 bytes (it always does as |
| 279 | 271 |
// long as the SSE optimizations are enabled), which means we can do our own SSE opts here. |
| 280 | 272 |
|
| 281 |
- CvtPacketToInt( sndTempBuffer, tempProgress ); |
|
| 282 |
- _WriteSamples( sndTempBuffer, tempProgress ); |
|
| 273 |
+ CvtPacketToInt(sndTempBuffer.get(), tempProgress); |
|
| 274 |
+ _WriteSamples(sndTempBuffer.get(), tempProgress); |
|
| 283 | 275 |
progress = true; |
| 284 | 276 |
} |
| 285 | 277 |
|
| ... | ... |
@@ -288,15 +280,13 @@ void SndBuffer::timeStretchWrite() |
| 288 | 280 |
//TODO |
| 289 | 281 |
//if( MsgOverruns() ) |
| 290 | 282 |
{
|
| 291 |
- if( progress ) |
|
| 283 |
+ if(progress) |
|
| 292 | 284 |
{
|
| 293 |
- if( ++ts_stats_logcounter > 300 ) |
|
| 285 |
+ if (++ts_stats_logcounter > 300) |
|
| 294 | 286 |
{
|
| 295 | 287 |
ts_stats_logcounter = 0; |
| 296 |
- printf( " * SPU2 > Timestretch Stats > %d%% of packets stretched.\n", |
|
| 297 |
- ( ts_stats_stretchblocks * 100 ) / ( ts_stats_normalblocks + ts_stats_stretchblocks ) ); |
|
| 298 |
- ts_stats_normalblocks = 0; |
|
| 299 |
- ts_stats_stretchblocks = 0; |
|
| 288 |
+ printf(" * SPU2 > Timestretch Stats > %d%% of packets stretched.\n", (ts_stats_stretchblocks * 100) / (ts_stats_normalblocks + ts_stats_stretchblocks));
|
|
| 289 |
+ ts_stats_normalblocks = ts_stats_stretchblocks = 0; |
|
| 300 | 290 |
} |
| 301 | 291 |
} |
| 302 | 292 |
} |
| ... | ... |
@@ -304,49 +294,22 @@ void SndBuffer::timeStretchWrite() |
| 304 | 294 |
|
| 305 | 295 |
void SndBuffer::soundtouchInit() |
| 306 | 296 |
{
|
| 307 |
- pSoundTouch = new soundtouch::SoundTouch(); |
|
| 297 |
+ pSoundTouch.reset(new soundtouch::SoundTouch()); |
|
| 308 | 298 |
pSoundTouch->setSampleRate(SampleRate); |
| 309 | 299 |
pSoundTouch->setChannels(2); |
| 310 | 300 |
|
| 311 |
- pSoundTouch->setSetting( soundtouch::SETTING_USE_QUICKSEEK, 0 ); |
|
| 312 |
- pSoundTouch->setSetting( soundtouch::SETTING_USE_AA_FILTER, 0 ); |
|
| 313 |
- |
|
| 314 |
- SoundtouchCfg::ApplySettings( *pSoundTouch ); |
|
| 301 |
+ pSoundTouch->setSetting(soundtouch::SETTING_USE_QUICKSEEK, 0); |
|
| 302 |
+ pSoundTouch->setSetting(soundtouch::SETTING_USE_AA_FILTER, 0); |
|
| 315 | 303 |
|
| 316 | 304 |
pSoundTouch->setTempo(1); |
| 317 | 305 |
|
| 318 | 306 |
// some timestretch management vars: |
| 319 | 307 |
|
| 320 |
- cTempo = 1.0; |
|
| 321 |
- eTempo = 1.0; |
|
| 322 |
- lastPct = 0; |
|
| 323 |
- lastEmergencyAdj = 0; |
|
| 308 |
+ cTempo = eTempo = 1.0; |
|
| 309 |
+ lastPct = lastEmergencyAdj = 0; |
|
| 324 | 310 |
|
| 325 | 311 |
// just freeze tempo changes for a while at startup. |
| 326 | 312 |
// the driver buffers are bogus anyway. |
| 327 | 313 |
freezeTempo = 16; |
| 328 | 314 |
m_predictData = 0; |
| 329 | 315 |
} |
| 330 |
- |
|
| 331 |
-// reset timestretch management vars, and delay updates a bit: |
|
| 332 |
-void SndBuffer::soundtouchClearContents() |
|
| 333 |
-{
|
|
| 334 |
- if( pSoundTouch == NULL ) return; |
|
| 335 |
- |
|
| 336 |
- pSoundTouch->clear(); |
|
| 337 |
- pSoundTouch->setTempo(1); |
|
| 338 |
- |
|
| 339 |
- cTempo = 1.0; |
|
| 340 |
- eTempo = 1.0; |
|
| 341 |
- lastPct = 0; |
|
| 342 |
- lastEmergencyAdj = 0; |
|
| 343 |
- |
|
| 344 |
- freezeTempo = 16; |
|
| 345 |
- m_predictData = 0; |
|
| 346 |
-} |
|
| 347 |
- |
|
| 348 |
-void SndBuffer::soundtouchCleanup() |
|
| 349 |
-{
|
|
| 350 |
- //safe_delete( pSoundTouch ); |
|
| 351 |
- delete pSoundTouch; |
|
| 352 |
-} |
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,352 @@ |
| 1 |
+/* SPU2-X, A plugin for Emulating the Sound Processing Unit of the Playstation 2 |
|
| 2 |
+* Developed and maintained by the Pcsx2 Development Team. |
|
| 3 |
+* |
|
| 4 |
+* Original portions from SPU2ghz are (c) 2008 by David Quintana [gigaherz] |
|
| 5 |
+* |
|
| 6 |
+* SPU2-X is free software: you can redistribute it and/or modify it under the terms |
|
| 7 |
+* of the GNU Lesser General Public License as published by the Free Software Found- |
|
| 8 |
+* ation, either version 3 of the License, or (at your option) any later version. |
|
| 9 |
+* |
|
| 10 |
+* SPU2-X is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; |
|
| 11 |
+* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
|
| 12 |
+* PURPOSE. See the GNU Lesser General Public License for more details. |
|
| 13 |
+* |
|
| 14 |
+* You should have received a copy of the GNU Lesser General Public License |
|
| 15 |
+* along with SPU2-X. If not, see <http://www.gnu.org/licenses/>. |
|
| 16 |
+*/ |
|
| 17 |
+ |
|
| 18 |
+#include "XSFCommon.h" |
|
| 19 |
+ |
|
| 20 |
+//#include "Global.h" |
|
| 21 |
+#include "types.h" |
|
| 22 |
+#include "SoundTouch/SoundTouch.h" |
|
| 23 |
+#include "SndOut.h" |
|
| 24 |
+//#include "SoundTouch/WavFile.h" |
|
| 25 |
+ |
|
| 26 |
+#include "SoundTouch/Dialogs.h" |
|
| 27 |
+ |
|
| 28 |
+static soundtouch::SoundTouch* pSoundTouch = NULL; |
|
| 29 |
+static int ts_stats_stretchblocks = 0; |
|
| 30 |
+static int ts_stats_normalblocks = 0; |
|
| 31 |
+static int ts_stats_logcounter = 0; |
|
| 32 |
+ |
|
| 33 |
+// data prediction amount, used to "commit" data that hasn't |
|
| 34 |
+// finished timestretch processing. |
|
| 35 |
+int32_t SndBuffer::m_predictData; |
|
| 36 |
+ |
|
| 37 |
+// records last buffer status (fill %, range -100 to 100, with 0 being 50% full) |
|
| 38 |
+float SndBuffer::lastPct; |
|
| 39 |
+float SndBuffer::lastEmergencyAdj; |
|
| 40 |
+ |
|
| 41 |
+float SndBuffer::cTempo = 1; |
|
| 42 |
+float SndBuffer::eTempo = 1; |
|
| 43 |
+int SndBuffer::freezeTempo = 0; |
|
| 44 |
+ |
|
| 45 |
+void SndBuffer::PredictDataWrite( int samples ) |
|
| 46 |
+{
|
|
| 47 |
+ m_predictData += samples; |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+// Calculate the buffer status percentage. |
|
| 51 |
+// Returns range from -1.0 to 1.0 |
|
| 52 |
+// 1.0 = buffer overflow! |
|
| 53 |
+// 0.0 = buffer nominal (50% full) |
|
| 54 |
+// -1.0 = buffer underflow! |
|
| 55 |
+float SndBuffer::GetStatusPct() |
|
| 56 |
+{
|
|
| 57 |
+ // Get the buffer status of the output driver too, so that we can |
|
| 58 |
+ // obtain a more accurate overall buffer status. |
|
| 59 |
+ |
|
| 60 |
+ int drvempty = 0; |
|
| 61 |
+ //int drvempty = mods[OutputModule]->GetEmptySampleCount(); // / 2; |
|
| 62 |
+ //TODO |
|
| 63 |
+ |
|
| 64 |
+ //ConLog( "Data %d >>> driver: %d predict: %d\n", data, drvempty, predictData ); |
|
| 65 |
+ |
|
| 66 |
+ float result = (float)(m_data + m_predictData - drvempty) - (m_size/2); |
|
| 67 |
+ result /= (m_size/2); |
|
| 68 |
+ return result; |
|
| 69 |
+} |
|
| 70 |
+ |
|
| 71 |
+void SndBuffer::UpdateTempoChange() |
|
| 72 |
+{
|
|
| 73 |
+ if( --freezeTempo > 0 ) |
|
| 74 |
+ {
|
|
| 75 |
+ return; |
|
| 76 |
+ } |
|
| 77 |
+ |
|
| 78 |
+ float statusPct = GetStatusPct(); |
|
| 79 |
+ float pctChange = statusPct - lastPct; |
|
| 80 |
+ |
|
| 81 |
+ float tempoChange; |
|
| 82 |
+ float emergencyAdj = 0; |
|
| 83 |
+ float newcee = cTempo; // workspace var. for cTempo |
|
| 84 |
+ |
|
| 85 |
+ // IMPORTANT! |
|
| 86 |
+ // If you plan to tweak these values, make sure you're using a release build |
|
| 87 |
+ // OUTSIDE THE DEBUGGER to test it! The Visual Studio debugger can really cause |
|
| 88 |
+ // erratic behavior in the audio buffers, and makes the timestretcher seem a |
|
| 89 |
+ // lot more inconsistent than it really is. |
|
| 90 |
+ |
|
| 91 |
+ // We have two factors. |
|
| 92 |
+ // * Distance from nominal buffer status (50% full) |
|
| 93 |
+ // * The change from previous update to this update. |
|
| 94 |
+ |
|
| 95 |
+ // Prediction based on the buffer change: |
|
| 96 |
+ // (linear seems to work better here) |
|
| 97 |
+ |
|
| 98 |
+ tempoChange = pctChange * 0.75f; |
|
| 99 |
+ |
|
| 100 |
+ if( statusPct * tempoChange < 0.0f ) |
|
| 101 |
+ {
|
|
| 102 |
+ // only apply tempo change if it is in synch with the buffer status. |
|
| 103 |
+ // In other words, if the buffer is high (over 0%), and is decreasing, |
|
| 104 |
+ // ignore it. It'll just muck things up. |
|
| 105 |
+ |
|
| 106 |
+ tempoChange = 0; |
|
| 107 |
+ } |
|
| 108 |
+ |
|
| 109 |
+ // Sudden spikes in framerate can cause the nominal buffer status |
|
| 110 |
+ // to go critical, in which case we have to enact an emergency |
|
| 111 |
+ // stretch. The following cubic formulas do that. Values near |
|
| 112 |
+ // the extremeites give much larger results than those near 0. |
|
| 113 |
+ // And the value is added only this time, and does not accumulate. |
|
| 114 |
+ // (otherwise a large value like this would cause problems down the road) |
|
| 115 |
+ |
|
| 116 |
+ // Constants: |
|
| 117 |
+ // Weight - weights the statusPct's "emergency" consideration. |
|
| 118 |
+ // higher values here will make the buffer perform more drastic |
|
| 119 |
+ // compensations at the outer edges of the buffer (at -75 or +75% |
|
| 120 |
+ // or beyond, for example). |
|
| 121 |
+ |
|
| 122 |
+ // Range - scales the adjustment to the given range (more or less). |
|
| 123 |
+ // The actual range is dependent on the weight used, so if you increase |
|
| 124 |
+ // Weight you'll usually want to decrease Range somewhat to compensate. |
|
| 125 |
+ |
|
| 126 |
+ // Prediction based on the buffer fill status: |
|
| 127 |
+ |
|
| 128 |
+ const float statusWeight = 2.99f; |
|
| 129 |
+ const float statusRange = 0.068f; |
|
| 130 |
+ |
|
| 131 |
+ // "non-emergency" deadzone: In this area stretching will be strongly discouraged. |
|
| 132 |
+ // Note: due tot he nature of timestretch latency, it's always a wee bit harder to |
|
| 133 |
+ // cope with low fps (underruns) than it is high fps (overruns). So to help out a |
|
| 134 |
+ // little, the low-end portions of this check are less forgiving than the high-sides. |
|
| 135 |
+ |
|
| 136 |
+ if( cTempo < 0.965f || cTempo > 1.060f || |
|
| 137 |
+ pctChange < -0.38f || pctChange > 0.54f || |
|
| 138 |
+ statusPct < -0.32f || statusPct > 0.39f || |
|
| 139 |
+ eTempo < 0.89f || eTempo > 1.19f ) |
|
| 140 |
+ {
|
|
| 141 |
+ emergencyAdj = ( pow( statusPct*statusWeight, 3.0f ) * statusRange); |
|
| 142 |
+ } |
|
| 143 |
+ |
|
| 144 |
+ // Smooth things out by factoring our previous adjustment into this one. |
|
| 145 |
+ // It helps make the system 'feel' a little smarter by giving it at least |
|
| 146 |
+ // one packet worth of history to help work off of: |
|
| 147 |
+ |
|
| 148 |
+ emergencyAdj = (emergencyAdj * 0.75f) + (lastEmergencyAdj * 0.25f ); |
|
| 149 |
+ |
|
| 150 |
+ lastEmergencyAdj = emergencyAdj; |
|
| 151 |
+ lastPct = statusPct; |
|
| 152 |
+ |
|
| 153 |
+ // Accumulate a fraction of the tempo change into the tempo itself. |
|
| 154 |
+ // This helps the system run "smarter" to games that run consistently |
|
| 155 |
+ // fast or slow by altering the base tempo to something closer to the |
|
| 156 |
+ // game's active speed. In tests most games normalize within 2 seconds |
|
| 157 |
+ // at 100ms latency, which is pretty good (larger buffers normalize even |
|
| 158 |
+ // quicker). |
|
| 159 |
+ |
|
| 160 |
+ newcee += newcee * (tempoChange+emergencyAdj) * 0.03f; |
|
| 161 |
+ |
|
| 162 |
+ // Apply tempoChange as a scale of cTempo. That way the effect is proportional |
|
| 163 |
+ // to the current tempo. (otherwise tempos rate of change at the extremes would |
|
| 164 |
+ // be too drastic) |
|
| 165 |
+ |
|
| 166 |
+ float newTempo = newcee + ( emergencyAdj * cTempo ); |
|
| 167 |
+ |
|
| 168 |
+ // ... and as a final optimization, only stretch if the new tempo is outside |
|
| 169 |
+ // a nominal threshold. Keep this threshold check small, because it could |
|
| 170 |
+ // cause some serious side effects otherwise. (enlarging the cTempo check above |
|
| 171 |
+ // is usually better/safer) |
|
| 172 |
+ if( newTempo < 0.970f || newTempo > 1.045f ) |
|
| 173 |
+ {
|
|
| 174 |
+ cTempo = (float)newcee; |
|
| 175 |
+ |
|
| 176 |
+ if( newTempo < 0.10f ) newTempo = 0.10f; |
|
| 177 |
+ else if( newTempo > 10.0f ) newTempo = 10.0f; |
|
| 178 |
+ |
|
| 179 |
+ if( cTempo < 0.15f ) cTempo = 0.15f; |
|
| 180 |
+ else if( cTempo > 7.5f ) cTempo = 7.5f; |
|
| 181 |
+ |
|
| 182 |
+ pSoundTouch->setTempo( eTempo = (float)newTempo ); |
|
| 183 |
+ ts_stats_stretchblocks++; |
|
| 184 |
+ |
|
| 185 |
+ /*ConLog(" * SPU2: [Nominal %d%%] [Emergency: %d%%] (baseTempo: %d%% ) (newTempo: %d%%) (buffer: %d%%)\n",
|
|
| 186 |
+ //(relation < 0.0) ? "Normalize" : "", |
|
| 187 |
+ (int)(tempoChange * 100.0 * 0.03), |
|
| 188 |
+ (int)(emergencyAdj * 100.0), |
|
| 189 |
+ (int)(cTempo * 100.0), |
|
| 190 |
+ (int)(newTempo * 100.0), |
|
| 191 |
+ (int)(statusPct * 100.0) |
|
| 192 |
+ );*/ |
|
| 193 |
+ } |
|
| 194 |
+ else |
|
| 195 |
+ {
|
|
| 196 |
+ // Nominal operation -- turn off stretching. |
|
| 197 |
+ // note: eTempo 'slides' toward 1.0 for smoother audio and better |
|
| 198 |
+ // protection against spikes. |
|
| 199 |
+ if (!fEqual(cTempo, 1.0f)) |
|
| 200 |
+ {
|
|
| 201 |
+ cTempo = 1.0f; |
|
| 202 |
+ eTempo = ( 1.0f + eTempo ) * 0.5f; |
|
| 203 |
+ pSoundTouch->setTempo( eTempo ); |
|
| 204 |
+ } |
|
| 205 |
+ else |
|
| 206 |
+ {
|
|
| 207 |
+ if (!fEqual(eTempo, cTempo)) |
|
| 208 |
+ pSoundTouch->setTempo( eTempo=cTempo ); |
|
| 209 |
+ ts_stats_normalblocks++; |
|
| 210 |
+ } |
|
| 211 |
+ } |
|
| 212 |
+} |
|
| 213 |
+ |
|
| 214 |
+void SndBuffer::timeStretchUnderrun() |
|
| 215 |
+{
|
|
| 216 |
+ // timeStretcher failed it's job. We need to slow down the audio some. |
|
| 217 |
+ |
|
| 218 |
+ cTempo -= (cTempo * 0.12f); |
|
| 219 |
+ eTempo -= (eTempo * 0.30f); |
|
| 220 |
+ if( eTempo < 0.1f ) eTempo = 0.1f; |
|
| 221 |
+ pSoundTouch->setTempo( eTempo ); |
|
| 222 |
+} |
|
| 223 |
+ |
|
| 224 |
+int32_t SndBuffer::timeStretchOverrun() |
|
| 225 |
+{
|
|
| 226 |
+ // If we overran it means the timestretcher failed. We need to speed |
|
| 227 |
+ // up audio playback. |
|
| 228 |
+ cTempo += cTempo * 0.12f; |
|
| 229 |
+ eTempo += eTempo * 0.40f; |
|
| 230 |
+ if( eTempo > 7.5f ) eTempo = 7.5f; |
|
| 231 |
+ pSoundTouch->setTempo( eTempo ); |
|
| 232 |
+ |
|
| 233 |
+ // Throw out just a little bit (two packets worth) to help |
|
| 234 |
+ // give the TS some room to work: |
|
| 235 |
+ |
|
| 236 |
+ return SndOutPacketSize*2; |
|
| 237 |
+} |
|
| 238 |
+ |
|
| 239 |
+static void CvtPacketToFloat( StereoOut32* srcdest ) |
|
| 240 |
+{
|
|
| 241 |
+ StereoOutFloat* dest = (StereoOutFloat*)srcdest; |
|
| 242 |
+ const StereoOut32* src = (StereoOut32*)srcdest; |
|
| 243 |
+ for( int i=0; i<SndOutPacketSize; ++i, ++dest, ++src ) |
|
| 244 |
+ *dest = (StereoOutFloat)*src; |
|
| 245 |
+} |
|
| 246 |
+ |
|
| 247 |
+// Parameter note: Size should always be a multiple of 128, thanks! |
|
| 248 |
+static void CvtPacketToInt( StereoOut32* srcdest, uint32_t size ) |
|
| 249 |
+{
|
|
| 250 |
+ //jASSUME( (size & 127) == 0 ); |
|
| 251 |
+ |
|
| 252 |
+ const StereoOutFloat* src = (StereoOutFloat*)srcdest; |
|
| 253 |
+ StereoOut32* dest = srcdest; |
|
| 254 |
+ |
|
| 255 |
+ for( uint32_t i=0; i<size; ++i, ++dest, ++src ) |
|
| 256 |
+ *dest = (StereoOut32)*src; |
|
| 257 |
+} |
|
| 258 |
+ |
|
| 259 |
+void SndBuffer::timeStretchWrite() |
|
| 260 |
+{
|
|
| 261 |
+ bool progress = false; |
|
| 262 |
+ |
|
| 263 |
+ // data prediction helps keep the tempo adjustments more accurate. |
|
| 264 |
+ // The timestretcher returns packets in belated "clump" form. |
|
| 265 |
+ // Meaning that most of the time we'll get nothing back, and then |
|
| 266 |
+ // suddenly we'll get several chunks back at once. Thus we use |
|
| 267 |
+ // data prediction to make the timestretcher more responsive. |
|
| 268 |
+ |
|
| 269 |
+ PredictDataWrite( (int)( SndOutPacketSize / eTempo ) ); |
|
| 270 |
+ CvtPacketToFloat( sndTempBuffer ); |
|
| 271 |
+ |
|
| 272 |
+ pSoundTouch->putSamples( (float*)sndTempBuffer, SndOutPacketSize ); |
|
| 273 |
+ |
|
| 274 |
+ int tempProgress; |
|
| 275 |
+ while( tempProgress = pSoundTouch->receiveSamples( (float*)sndTempBuffer, SndOutPacketSize), |
|
| 276 |
+ tempProgress != 0 ) |
|
| 277 |
+ {
|
|
| 278 |
+ // Hint: It's assumed that pSoundTouch will return chunks of 128 bytes (it always does as |
|
| 279 |
+ // long as the SSE optimizations are enabled), which means we can do our own SSE opts here. |
|
| 280 |
+ |
|
| 281 |
+ CvtPacketToInt( sndTempBuffer, tempProgress ); |
|
| 282 |
+ _WriteSamples( sndTempBuffer, tempProgress ); |
|
| 283 |
+ progress = true; |
|
| 284 |
+ } |
|
| 285 |
+ |
|
| 286 |
+ UpdateTempoChange(); |
|
| 287 |
+ |
|
| 288 |
+ //TODO |
|
| 289 |
+ //if( MsgOverruns() ) |
|
| 290 |
+ {
|
|
| 291 |
+ if( progress ) |
|
| 292 |
+ {
|
|
| 293 |
+ if( ++ts_stats_logcounter > 300 ) |
|
| 294 |
+ {
|
|
| 295 |
+ ts_stats_logcounter = 0; |
|
| 296 |
+ printf( " * SPU2 > Timestretch Stats > %d%% of packets stretched.\n", |
|
| 297 |
+ ( ts_stats_stretchblocks * 100 ) / ( ts_stats_normalblocks + ts_stats_stretchblocks ) ); |
|
| 298 |
+ ts_stats_normalblocks = 0; |
|
| 299 |
+ ts_stats_stretchblocks = 0; |
|
| 300 |
+ } |
|
| 301 |
+ } |
|
| 302 |
+ } |
|
| 303 |
+} |
|
| 304 |
+ |
|
| 305 |
+void SndBuffer::soundtouchInit() |
|
| 306 |
+{
|
|
| 307 |
+ pSoundTouch = new soundtouch::SoundTouch(); |
|
| 308 |
+ pSoundTouch->setSampleRate(SampleRate); |
|
| 309 |
+ pSoundTouch->setChannels(2); |
|
| 310 |
+ |
|
| 311 |
+ pSoundTouch->setSetting( soundtouch::SETTING_USE_QUICKSEEK, 0 ); |
|
| 312 |
+ pSoundTouch->setSetting( soundtouch::SETTING_USE_AA_FILTER, 0 ); |
|
| 313 |
+ |
|
| 314 |
+ SoundtouchCfg::ApplySettings( *pSoundTouch ); |
|
| 315 |
+ |
|
| 316 |
+ pSoundTouch->setTempo(1); |
|
| 317 |
+ |
|
| 318 |
+ // some timestretch management vars: |
|
| 319 |
+ |
|
| 320 |
+ cTempo = 1.0; |
|
| 321 |
+ eTempo = 1.0; |
|
| 322 |
+ lastPct = 0; |
|
| 323 |
+ lastEmergencyAdj = 0; |
|
| 324 |
+ |
|
| 325 |
+ // just freeze tempo changes for a while at startup. |
|
| 326 |
+ // the driver buffers are bogus anyway. |
|
| 327 |
+ freezeTempo = 16; |
|
| 328 |
+ m_predictData = 0; |
|
| 329 |
+} |
|
| 330 |
+ |
|
| 331 |
+// reset timestretch management vars, and delay updates a bit: |
|
| 332 |
+void SndBuffer::soundtouchClearContents() |
|
| 333 |
+{
|
|
| 334 |
+ if( pSoundTouch == NULL ) return; |
|
| 335 |
+ |
|
| 336 |
+ pSoundTouch->clear(); |
|
| 337 |
+ pSoundTouch->setTempo(1); |
|
| 338 |
+ |
|
| 339 |
+ cTempo = 1.0; |
|
| 340 |
+ eTempo = 1.0; |
|
| 341 |
+ lastPct = 0; |
|
| 342 |
+ lastEmergencyAdj = 0; |
|
| 343 |
+ |
|
| 344 |
+ freezeTempo = 16; |
|
| 345 |
+ m_predictData = 0; |
|
| 346 |
+} |
|
| 347 |
+ |
|
| 348 |
+void SndBuffer::soundtouchCleanup() |
|
| 349 |
+{
|
|
| 350 |
+ //safe_delete( pSoundTouch ); |
|
| 351 |
+ delete pSoundTouch; |
|
| 352 |
+} |