diff --git a/BizHawk.Client.Common/Global.cs b/BizHawk.Client.Common/Global.cs
index fba235b924..70a638b9f7 100644
--- a/BizHawk.Client.Common/Global.cs
+++ b/BizHawk.Client.Common/Global.cs
@@ -22,6 +22,11 @@ namespace BizHawk.Client.Common
///
public static bool DisableSecondaryThrottling;
+ ///
+ /// How far the sound output buffer can go below full before drastic corrective measures are taken.
+ ///
+ public static int SoundMaxBufferDeficitMs;
+
public static Controller NullControls;
public static AutofireController AutofireNullControls;
diff --git a/BizHawk.Client.EmuHawk/Sound.cs b/BizHawk.Client.EmuHawk/Sound.cs
index f7c2e00d3a..8412ccd3bc 100644
--- a/BizHawk.Client.EmuHawk/Sound.cs
+++ b/BizHawk.Client.EmuHawk/Sound.cs
@@ -124,14 +124,17 @@ namespace BizHawk.Client.EmuHawk
_lastWriteTime = 0;
_lastWriteCursor = 0;
- int minBufferFullnessSamples = MillisecondsToSamples(
+ int minBufferFullnessMs =
Global.Config.SoundBufferSizeMs < 80 ? 35 :
Global.Config.SoundBufferSizeMs < 100 ? 45 :
- 55);
+ 55;
+
_outputProvider = new SoundOutputProvider();
- _outputProvider.MaxSamplesDeficit = BufferSizeSamples - minBufferFullnessSamples;
+ _outputProvider.MaxSamplesDeficit = BufferSizeSamples - MillisecondsToSamples(minBufferFullnessMs);
_outputProvider.BaseSoundProvider = _syncSoundProvider;
+ Global.SoundMaxBufferDeficitMs = Global.Config.SoundBufferSizeMs - minBufferFullnessMs;
+
//LogUnderruns = true;
//_outputProvider.LogDebug = true;
}
@@ -147,6 +150,8 @@ namespace BizHawk.Client.EmuHawk
_outputProvider = null;
+ Global.SoundMaxBufferDeficitMs = 0;
+
BufferSizeSamples = 0;
}
@@ -224,6 +229,7 @@ namespace BizHawk.Client.EmuHawk
{
if (LogUnderruns) Console.WriteLine("DirectSound underrun detected!");
detectedUnderrun = true;
+ _outputProvider.OnUnderrun();
}
}
bool isInitializing = _actualWriteOffsetBytes == -1;
diff --git a/BizHawk.Client.EmuHawk/SoundOutputProvider.cs b/BizHawk.Client.EmuHawk/SoundOutputProvider.cs
index fbda4e391d..b4ff27d26b 100644
--- a/BizHawk.Client.EmuHawk/SoundOutputProvider.cs
+++ b/BizHawk.Client.EmuHawk/SoundOutputProvider.cs
@@ -21,15 +21,12 @@ namespace BizHawk.Client.EmuHawk
private const int SampleRate = 44100;
private const int ChannelCount = 2;
private const int SoftCorrectionThresholdSamples = 5 * SampleRate / 1000;
- private const int StartupHardCorrectionThresholdSamples = 10 * SampleRate / 1000;
- // Can't go more than 50 ms or so even if there's enough buffered up in the device
- // because the clock throttle loses its "rubber band" effect and won't replenish
- // our buffer. At that point it's better to hard correct than to let the soft
- // correction cause a major pitch shift.
- private const int NormalHardCorrectionThresholdSamples = 50 * SampleRate / 1000;
+ private const int StartupMaxSamplesSurplusDeficit = 10 * SampleRate / 1000;
+ private const int MaxSamplesSurplus = 50 * SampleRate / 1000;
private const int UsableHistoryLength = 20;
private const int MaxHistoryLength = 60;
private const int SoftCorrectionLength = 240;
+ private const int BaseMaxConsecutiveEmptyFrames = 1;
private const int BaseSampleRateUsableHistoryLength = 60;
private const int BaseSampleRateMaxHistoryLength = 300;
private const int MinResamplingDistanceSamples = 3;
@@ -40,6 +37,9 @@ namespace BizHawk.Client.EmuHawk
private Queue _outputCountHistory = new Queue();
private Queue _hardCorrectionHistory = new Queue();
+ private int _baseConsecutiveEmptyFrames;
+ private Queue _baseEmptyFrameCorrectionHistory = new Queue();
+
private double _lastAdvertisedSamplesPerFrame;
private Queue _baseSamplesPerFrame = new Queue();
@@ -62,6 +62,8 @@ namespace BizHawk.Client.EmuHawk
_extraCountHistory.Clear();
_outputCountHistory.Clear();
_hardCorrectionHistory.Clear();
+ _baseConsecutiveEmptyFrames = 0;
+ _baseEmptyFrameCorrectionHistory.Clear();
_lastAdvertisedSamplesPerFrame = 0.0;
_baseSamplesPerFrame.Clear();
_outputBuffer = new short[0];
@@ -74,6 +76,13 @@ namespace BizHawk.Client.EmuHawk
}
}
+ public void OnUnderrun()
+ {
+ _extraCountHistory.Clear();
+ _outputCountHistory.Clear();
+ _hardCorrectionHistory.Clear();
+ }
+
public bool LogDebug { get; set; }
private double AdvertisedSamplesPerFrame
@@ -87,7 +96,7 @@ namespace BizHawk.Client.EmuHawk
if (_extraCountHistory.Count >= UsableHistoryLength && !_hardCorrectionHistory.Any(c => c))
{
- double offsetFromTarget = _extraCountHistory.Average();
+ double offsetFromTarget = CalculatePowerMean(_extraCountHistory, 0.6);
if (Math.Abs(offsetFromTarget) > SoftCorrectionThresholdSamples)
{
double correctionSpan = _outputCountHistory.Average() * SoftCorrectionLength;
@@ -99,12 +108,13 @@ namespace BizHawk.Client.EmuHawk
int bufferSampleCount = _buffer.Count / ChannelCount;
int extraSampleCount = bufferSampleCount - idealSampleCount;
- int hardCorrectionThresholdSamples = _extraCountHistory.Count >= UsableHistoryLength ?
- Math.Min(NormalHardCorrectionThresholdSamples, MaxSamplesDeficit) :
- Math.Min(StartupHardCorrectionThresholdSamples, MaxSamplesDeficit);
+ int maxSamplesDeficit = _extraCountHistory.Count >= UsableHistoryLength ?
+ MaxSamplesDeficit : Math.Min(StartupMaxSamplesSurplusDeficit, MaxSamplesDeficit);
+ int maxSamplesSurplus = _extraCountHistory.Count >= UsableHistoryLength ?
+ MaxSamplesSurplus : Math.Min(StartupMaxSamplesSurplusDeficit, MaxSamplesSurplus);
bool hardCorrected = false;
- if (extraSampleCount < -hardCorrectionThresholdSamples)
+ if (extraSampleCount < -maxSamplesDeficit)
{
int generateSampleCount = -extraSampleCount;
if (LogDebug) Console.WriteLine("Generating " + generateSampleCount + " samples");
@@ -114,7 +124,7 @@ namespace BizHawk.Client.EmuHawk
}
hardCorrected = true;
}
- else if (extraSampleCount > hardCorrectionThresholdSamples)
+ else if (extraSampleCount > maxSamplesSurplus)
{
int discardSampleCount = extraSampleCount;
if (LogDebug) Console.WriteLine("Discarding " + discardSampleCount + " samples");
@@ -136,8 +146,8 @@ namespace BizHawk.Client.EmuHawk
if (LogDebug)
{
- Console.WriteLine("Avg: {0:0.0} ms, Min: {1:0.0} ms, Max: {2:0.0} ms, Scale: {3:0.0000}",
- _extraCountHistory.Average() * 1000.0 / SampleRate,
+ Console.WriteLine("Avg: {0:0.0} ms, Min: {1:0.0}, Max: {2:0.0}, Scale: {3:0.0000}",
+ CalculatePowerMean(_extraCountHistory, 0.6) * 1000.0 / SampleRate,
_extraCountHistory.Min() * 1000.0 / SampleRate,
_extraCountHistory.Max() * 1000.0 / SampleRate,
scaleFactor);
@@ -155,6 +165,24 @@ namespace BizHawk.Client.EmuHawk
BaseSoundProvider.GetSamples(out samples, out count);
+ bool correctedEmptyFrame = false;
+ if (count == 0)
+ {
+ _baseConsecutiveEmptyFrames++;
+ if (_baseConsecutiveEmptyFrames > BaseMaxConsecutiveEmptyFrames)
+ {
+ int silenceCount = (int)Math.Round(AdvertisedSamplesPerFrame);
+ samples = Resample(samples, count, silenceCount);
+ count = silenceCount;
+ correctedEmptyFrame = true;
+ }
+ }
+ else if (_baseConsecutiveEmptyFrames != 0)
+ {
+ _baseConsecutiveEmptyFrames = 0;
+ }
+ UpdateHistory(_baseEmptyFrameCorrectionHistory, correctedEmptyFrame, MaxHistoryLength);
+
if (AdvertisedSamplesPerFrame != _lastAdvertisedSamplesPerFrame)
{
_baseSamplesPerFrame.Clear();
@@ -162,7 +190,8 @@ namespace BizHawk.Client.EmuHawk
}
UpdateHistory(_baseSamplesPerFrame, count, BaseSampleRateMaxHistoryLength);
- if (_baseSamplesPerFrame.Count >= BaseSampleRateUsableHistoryLength && !_baseSamplesPerFrame.Any(n => n == 0))
+ if (_baseSamplesPerFrame.Count >= BaseSampleRateUsableHistoryLength &&
+ !_baseEmptyFrameCorrectionHistory.Any(n => n))
{
double baseAverageSamplesPerFrame = _baseSamplesPerFrame.Average();
if (baseAverageSamplesPerFrame != 0.0)
@@ -192,7 +221,13 @@ namespace BizHawk.Client.EmuHawk
AddSamplesToBuffer(samples, count);
}
- private void UpdateHistory(Queue queue, T value, int maxLength)
+ private static double CalculatePowerMean(IEnumerable values, double power)
+ {
+ double x = values.Average(n => Math.Pow(Math.Abs(n), power) * Math.Sign(n));
+ return Math.Pow(Math.Abs(x), 1.0 / power) * Math.Sign(x);
+ }
+
+ private static void UpdateHistory(Queue queue, T value, int maxLength)
{
queue.Enqueue(value);
while (queue.Count > maxLength)
diff --git a/BizHawk.Client.EmuHawk/Throttle.cs b/BizHawk.Client.EmuHawk/Throttle.cs
index 89577e8e22..485ddcb330 100644
--- a/BizHawk.Client.EmuHawk/Throttle.cs
+++ b/BizHawk.Client.EmuHawk/Throttle.cs
@@ -321,7 +321,11 @@ namespace BizHawk.Client.EmuHawk
if (elapsedTime >= timePerFrame)
{
- if (elapsedTime >= timePerFrame * 4)
+ int maxMissedFrames = (int)Math.Ceiling((Global.SoundMaxBufferDeficitMs / 1000.0) / ((double)timePerFrame / afsfreq));
+ if (maxMissedFrames < 3)
+ maxMissedFrames = 3;
+
+ if (elapsedTime > timePerFrame * (ulong)(1 + maxMissedFrames))
ltime = ttime;
else
ltime += timePerFrame;