Sound - mostly just cleanup and preparation for some future changes.

This commit is contained in:
jdpurcell 2015-01-23 02:09:25 +00:00
parent 308b6ec256
commit 3b524cdbf2
1 changed files with 66 additions and 47 deletions

View File

@ -43,8 +43,10 @@ namespace BizHawk.Client.EmuHawk
private const int BytesPerSample = 2;
private const int ChannelCount = 2;
private const int BlockAlign = BytesPerSample * ChannelCount;
private const int BufferSize = (SampleRate / 10) * BlockAlign; // 1/10th of a second
private const double BufferDuration = (double)(BufferSize / BlockAlign) / SampleRate;
private const int BufferSizeMilliseconds = 100;
private const int BufferSizeSamples = SampleRate * BufferSizeMilliseconds / 1000;
private const int BufferSizeBytes = BufferSizeSamples * BlockAlign;
private const double BufferSizeSeconds = (double)(BufferSizeBytes / BlockAlign) / SampleRate;
private bool _muted;
private bool _disposed;
@ -52,8 +54,8 @@ namespace BizHawk.Client.EmuHawk
private readonly BufferedAsync _semiSync = new BufferedAsync();
private ISoundProvider _asyncSoundProvider;
private ISyncSoundProvider _syncSoundProvider;
private int _actualWriteOffset = -1;
private int _filledBufferSize;
private int _actualWriteOffsetBytes = -1;
private int _filledBufferSizeBytes;
private long _lastWriteTime;
private int _lastWriteCursor;
@ -78,7 +80,7 @@ namespace BizHawk.Client.EmuHawk
Format = format,
Flags =
BufferFlags.GlobalFocus | BufferFlags.Software | BufferFlags.GetCurrentPosition2 | BufferFlags.ControlVolume,
SizeInBytes = BufferSize
SizeInBytes = BufferSizeBytes
};
_deviceBuffer = new SecondarySoundBuffer(device, desc);
@ -93,7 +95,7 @@ namespace BizHawk.Client.EmuHawk
if (_deviceBuffer == null) return;
if (IsPlaying) return;
_deviceBuffer.Write(new byte[BufferSize], 0, LockFlags.EntireBuffer);
_deviceBuffer.Write(new byte[BufferSizeBytes], 0, LockFlags.EntireBuffer);
_deviceBuffer.CurrentPlayPosition = 0;
_deviceBuffer.Play(0, PlayFlags.Looping);
}
@ -112,7 +114,7 @@ namespace BizHawk.Client.EmuHawk
{
if (!IsPlaying) return;
_deviceBuffer.Write(new byte[BufferSize], 0, LockFlags.EntireBuffer);
_deviceBuffer.Write(new byte[BufferSizeBytes], 0, LockFlags.EntireBuffer);
_deviceBuffer.Stop();
}
@ -142,33 +144,54 @@ namespace BizHawk.Client.EmuHawk
_semiSync.RecalculateMagic(Global.CoreComm.VsyncRate);
}
public bool InitializeBufferWithSilence
{
get { return Global.Config.SoundThrottle; }
}
public bool RecoverFromUnderrunsWithSilence
{
get { return Global.Config.SoundThrottle; }
}
public bool LogUnderruns { get; set; }
private int CalculateSamplesNeeded()
{
long currentWriteTime = Stopwatch.GetTimestamp();
int playCursor = _deviceBuffer.CurrentPlayPosition;
int writeCursor = _deviceBuffer.CurrentWritePosition;
if (_actualWriteOffset != -1)
bool detectedUnderrun = false;
if (_actualWriteOffsetBytes != -1)
{
double elapsedTime = (currentWriteTime - _lastWriteTime) / (double)Stopwatch.Frequency; // Seconds
int cursorDelta = CircularDistance(_lastWriteCursor, writeCursor, BufferSize);
cursorDelta += BufferSize * (int)Math.Round((elapsedTime - (cursorDelta / (double)(SampleRate * BlockAlign))) / BufferDuration);
_filledBufferSize -= cursorDelta;
if (_filledBufferSize < 0)
double elapsedSeconds = (currentWriteTime - _lastWriteTime) / (double)Stopwatch.Frequency;
int cursorDelta = CircularDistance(_lastWriteCursor, writeCursor, BufferSizeBytes);
cursorDelta += BufferSizeBytes * (int)Math.Round((elapsedSeconds - (cursorDelta / (double)(SampleRate * BlockAlign))) / BufferSizeSeconds);
_filledBufferSizeBytes -= cursorDelta;
if (_filledBufferSizeBytes < 0)
{
// Buffer underflow
_actualWriteOffset = -1;
if (LogUnderruns) Console.WriteLine("DirectSound underrun detected!");
detectedUnderrun = true;
}
}
if (_actualWriteOffset == -1)
bool isInitializing = _actualWriteOffsetBytes == -1;
if (isInitializing || detectedUnderrun)
{
_actualWriteOffset = writeCursor;
_filledBufferSize = 0;
_actualWriteOffsetBytes = writeCursor;
_filledBufferSizeBytes = 0;
}
int samplesNeeded = CircularDistance(_actualWriteOffsetBytes, playCursor, BufferSizeBytes) / BlockAlign;
if ((isInitializing && InitializeBufferWithSilence) || (detectedUnderrun && RecoverFromUnderrunsWithSilence))
{
// Fill the buffer with silence but leave enough empty for one frame's audio
int samplesPerFrame = (int)Math.Round(SampleRate / Global.Emulator.CoreComm.VsyncRate);
int silenceSamples = Math.Max(samplesNeeded - samplesPerFrame, 0);
WriteSamples(new short[silenceSamples * 2], silenceSamples);
samplesNeeded -= silenceSamples;
}
_lastWriteTime = currentWriteTime;
_lastWriteCursor = writeCursor;
int bytesNeeded = CircularDistance(_actualWriteOffset, playCursor, BufferSize);
return bytesNeeded / BlockAlign;
return samplesNeeded;
}
private int CircularDistance(int start, int end, int size)
@ -176,6 +199,19 @@ namespace BizHawk.Client.EmuHawk
return (end - start + size) % size;
}
private void WriteSamples(short[] samples, int sampleCount)
{
if (sampleCount == 0) return;
_deviceBuffer.Write(samples, 0, sampleCount * ChannelCount, _actualWriteOffsetBytes, LockFlags.None);
AdvanceWriteOffset(sampleCount);
}
private void AdvanceWriteOffset(int sampleCount)
{
_actualWriteOffsetBytes = (_actualWriteOffsetBytes + (sampleCount * BlockAlign)) % BufferSizeBytes;
_filledBufferSizeBytes += sampleCount * BlockAlign;
}
public void UpdateSilence()
{
_muted = true;
@ -192,15 +228,12 @@ namespace BizHawk.Client.EmuHawk
return;
}
int samplesNeeded = CalculateSamplesNeeded();
short[] samples;
int samplesNeeded = CalculateSamplesNeeded();
int samplesProvided;
if (_muted)
{
if (samplesNeeded == 0) return;
samples = new short[samplesNeeded * ChannelCount];
samplesProvided = samplesNeeded;
@ -209,40 +242,26 @@ namespace BizHawk.Client.EmuHawk
}
else if (_syncSoundProvider != null)
{
if (_deviceBuffer == null) return; // can cause CalculateSamplesNeeded() = 0
int nsampgot;
_syncSoundProvider.GetSamples(out samples, out samplesProvided);
_syncSoundProvider.GetSamples(out samples, out nsampgot);
samplesProvided = nsampgot;
if (!Global.DisableSecondaryThrottling)
while (samplesNeeded < samplesProvided)
{
Thread.Sleep((samplesProvided - samplesNeeded) / (SampleRate / 1000)); // let audio clock control sleep time
samplesNeeded = CalculateSamplesNeeded();
}
while (samplesNeeded < samplesProvided && !Global.DisableSecondaryThrottling)
{
Thread.Sleep((samplesProvided - samplesNeeded) / (SampleRate / 1000)); // let audio clock control sleep time
samplesNeeded = CalculateSamplesNeeded();
}
}
else if (_asyncSoundProvider != null)
{
samples = new short[samplesNeeded * ChannelCount];
//if (asyncsoundProvider != null && Muted == false)
//{
_semiSync.BaseSoundProvider = _asyncSoundProvider;
_semiSync.GetSamples(samples);
//}
//else asyncsoundProvider.DiscardSamples();
if (samplesNeeded == 0) return;
_semiSync.GetSamples(samples);
samplesProvided = samplesNeeded;
}
else
return;
_deviceBuffer.Write(samples, 0, samplesProvided * ChannelCount, _actualWriteOffset, LockFlags.None);
_actualWriteOffset = (_actualWriteOffset + (samplesProvided * BlockAlign)) % BufferSize;
_filledBufferSize += samplesProvided * BlockAlign;
WriteSamples(samples, samplesProvided);
}
/// <summary>