From e1daa7efd38fbd7ef4add7a60f63fbd4443af3fa Mon Sep 17 00:00:00 2001 From: YoshiRulz Date: Tue, 6 Oct 2020 07:32:43 +1000 Subject: [PATCH] Extract interface from Sound The interface is a single method plus the four constants from the top of Sound, which are now properties. The static methods in Sound are moved to extension methods on the interface. --- .../Sound/HostAudioManagerExtensions.cs | 15 +++++++++ .../Sound/IHostAudioManager.cs | 15 +++++++++ .../Sound/Output/DirectSoundSoundOutput.cs | 32 +++++++++---------- .../Sound/Output/DummySoundOutput.cs | 8 ++--- .../Sound/Output/OpenALSoundOutput.cs | 18 +++++------ .../Sound/Output/XAudio2SoundOutput.cs | 24 +++++++------- src/BizHawk.Client.EmuHawk/Sound/Sound.cs | 30 ++++++++--------- 7 files changed, 84 insertions(+), 58 deletions(-) create mode 100644 src/BizHawk.Client.Common/Sound/HostAudioManagerExtensions.cs create mode 100644 src/BizHawk.Client.Common/Sound/IHostAudioManager.cs diff --git a/src/BizHawk.Client.Common/Sound/HostAudioManagerExtensions.cs b/src/BizHawk.Client.Common/Sound/HostAudioManagerExtensions.cs new file mode 100644 index 0000000000..53453c6ac0 --- /dev/null +++ b/src/BizHawk.Client.Common/Sound/HostAudioManagerExtensions.cs @@ -0,0 +1,15 @@ +namespace BizHawk.Client.Common +{ + public static class HostAudioManagerExtensions + { + public static int MillisecondsToSamples(this IHostAudioManager audioMan, int milliseconds) + { + return milliseconds * audioMan.SampleRate / 1000; + } + + public static double SamplesToMilliseconds(this IHostAudioManager audioMan, int samples) + { + return samples * 1000.0 / audioMan.SampleRate; + } + } +} diff --git a/src/BizHawk.Client.Common/Sound/IHostAudioManager.cs b/src/BizHawk.Client.Common/Sound/IHostAudioManager.cs new file mode 100644 index 0000000000..62f130324c --- /dev/null +++ b/src/BizHawk.Client.Common/Sound/IHostAudioManager.cs @@ -0,0 +1,15 @@ +namespace BizHawk.Client.Common +{ + public interface IHostAudioManager + { + int BlockAlign { get; } + + int BytesPerSample { get; } + + int ChannelCount { get; } + + int SampleRate { get; } + + void HandleInitializationOrUnderrun(bool isUnderrun, ref int samplesNeeded); + } +} diff --git a/src/BizHawk.Client.EmuHawk/Sound/Output/DirectSoundSoundOutput.cs b/src/BizHawk.Client.EmuHawk/Sound/Output/DirectSoundSoundOutput.cs index c6a29d38f7..fa70abde49 100644 --- a/src/BizHawk.Client.EmuHawk/Sound/Output/DirectSoundSoundOutput.cs +++ b/src/BizHawk.Client.EmuHawk/Sound/Output/DirectSoundSoundOutput.cs @@ -12,7 +12,7 @@ namespace BizHawk.Client.EmuHawk { public class DirectSoundSoundOutput : ISoundOutput { - private readonly Sound _sound; + private readonly IHostAudioManager _sound; private bool _disposed; private DirectSound _device; private SecondarySoundBuffer _deviceBuffer; @@ -22,7 +22,7 @@ namespace BizHawk.Client.EmuHawk private int _lastWriteCursor; private int _retryCounter; - public DirectSoundSoundOutput(Sound sound, IntPtr mainWindowHandle, string soundDevice) + public DirectSoundSoundOutput(IHostAudioManager sound, IntPtr mainWindowHandle, string soundDevice) { _sound = sound; _retryCounter = 5; @@ -49,7 +49,7 @@ namespace BizHawk.Client.EmuHawk private int BufferSizeSamples { get; set; } - private int BufferSizeBytes => BufferSizeSamples * Sound.BlockAlign; + private int BufferSizeBytes => BufferSizeSamples * _sound.BlockAlign; public int MaxSamplesDeficit { get; private set; } @@ -71,12 +71,12 @@ namespace BizHawk.Client.EmuHawk { var format = new WaveFormat { - SamplesPerSecond = Sound.SampleRate, - BitsPerSample = Sound.BytesPerSample * 8, - Channels = Sound.ChannelCount, + SamplesPerSecond = _sound.SampleRate, + BitsPerSample = (short) (_sound.BytesPerSample * 8), + Channels = (short) _sound.ChannelCount, FormatTag = WaveFormatTag.Pcm, - BlockAlignment = Sound.BlockAlign, - AverageBytesPerSecond = Sound.SampleRate * Sound.BlockAlign + BlockAlignment = (short) _sound.BlockAlign, + AverageBytesPerSecond = _sound.SampleRate * _sound.BlockAlign }; var desc = new SoundBufferDescription @@ -136,7 +136,7 @@ namespace BizHawk.Client.EmuHawk public void StartSound() { - BufferSizeSamples = Sound.MillisecondsToSamples(GlobalWin.Config.SoundBufferSizeMs); + BufferSizeSamples = _sound.MillisecondsToSamples(GlobalWin.Config.SoundBufferSizeMs); // 35 to 65 milliseconds depending on how big the buffer is. This is a trade-off // between more frequent but less severe glitches (i.e. catching underruns before @@ -145,7 +145,7 @@ namespace BizHawk.Client.EmuHawk // play and write cursors can be up to 30 milliseconds, so that would be the // absolute minimum we could use here. int minBufferFullnessMs = Math.Min(35 + ((GlobalWin.Config.SoundBufferSizeMs - 60) / 2), 65); - MaxSamplesDeficit = BufferSizeSamples - Sound.MillisecondsToSamples(minBufferFullnessMs); + MaxSamplesDeficit = BufferSizeSamples - _sound.MillisecondsToSamples(minBufferFullnessMs); StartPlaying(); } @@ -182,9 +182,9 @@ namespace BizHawk.Client.EmuHawk if (!isInitializing) { double elapsedSeconds = (currentWriteTime - _lastWriteTime) / (double)Stopwatch.Frequency; - double bufferSizeSeconds = (double)BufferSizeSamples / Sound.SampleRate; + double bufferSizeSeconds = (double) BufferSizeSamples / _sound.SampleRate; int cursorDelta = CircularDistance(_lastWriteCursor, writeCursor, BufferSizeBytes); - cursorDelta += BufferSizeBytes * (int)Math.Round((elapsedSeconds - (cursorDelta / (double)(Sound.SampleRate * Sound.BlockAlign))) / bufferSizeSeconds); + cursorDelta += BufferSizeBytes * (int) Math.Round((elapsedSeconds - (cursorDelta / (double) (_sound.SampleRate * _sound.BlockAlign))) / bufferSizeSeconds); _filledBufferSizeBytes -= cursorDelta; detectedUnderrun = _filledBufferSizeBytes < 0; } @@ -193,7 +193,7 @@ namespace BizHawk.Client.EmuHawk _actualWriteOffsetBytes = writeCursor; _filledBufferSizeBytes = 0; } - samplesNeeded = CircularDistance(_actualWriteOffsetBytes, playCursor, BufferSizeBytes) / Sound.BlockAlign; + samplesNeeded = CircularDistance(_actualWriteOffsetBytes, playCursor, BufferSizeBytes) / _sound.BlockAlign; if (isInitializing || detectedUnderrun) { _sound.HandleInitializationOrUnderrun(detectedUnderrun, ref samplesNeeded); @@ -223,9 +223,9 @@ namespace BizHawk.Client.EmuHawk if (sampleCount == 0) return; try { - _deviceBuffer.Write(samples, sampleOffset * Sound.ChannelCount, sampleCount * Sound.ChannelCount, _actualWriteOffsetBytes, LockFlags.None); - _actualWriteOffsetBytes = (_actualWriteOffsetBytes + (sampleCount * Sound.BlockAlign)) % BufferSizeBytes; - _filledBufferSizeBytes += sampleCount * Sound.BlockAlign; + _deviceBuffer.Write(samples, sampleOffset * _sound.ChannelCount, sampleCount * _sound.ChannelCount, _actualWriteOffsetBytes, LockFlags.None); + _actualWriteOffsetBytes = (_actualWriteOffsetBytes + (sampleCount * _sound.BlockAlign)) % BufferSizeBytes; + _filledBufferSizeBytes += sampleCount * _sound.BlockAlign; } catch (DirectSoundException) { diff --git a/src/BizHawk.Client.EmuHawk/Sound/Output/DummySoundOutput.cs b/src/BizHawk.Client.EmuHawk/Sound/Output/DummySoundOutput.cs index 92934744db..ae24a45f56 100644 --- a/src/BizHawk.Client.EmuHawk/Sound/Output/DummySoundOutput.cs +++ b/src/BizHawk.Client.EmuHawk/Sound/Output/DummySoundOutput.cs @@ -7,11 +7,11 @@ namespace BizHawk.Client.EmuHawk { public class DummySoundOutput : ISoundOutput { - private Sound _sound; + private readonly IHostAudioManager _sound; private int _remainingSamples; private long _lastWriteTime; - public DummySoundOutput(Sound sound) + public DummySoundOutput(IHostAudioManager sound) { _sound = sound; } @@ -30,7 +30,7 @@ namespace BizHawk.Client.EmuHawk public void StartSound() { - BufferSizeSamples = Sound.MillisecondsToSamples(GlobalWin.Config.SoundBufferSizeMs); + BufferSizeSamples = _sound.MillisecondsToSamples(GlobalWin.Config.SoundBufferSizeMs); MaxSamplesDeficit = BufferSizeSamples; _lastWriteTime = 0; @@ -52,7 +52,7 @@ namespace BizHawk.Client.EmuHawk // Due to rounding errors this doesn't work well in audio throttle mode unless enough time has passed if (elapsedSeconds >= 0.001) { - _remainingSamples -= (int)Math.Round(elapsedSeconds * Sound.SampleRate); + _remainingSamples -= (int) Math.Round(elapsedSeconds * _sound.SampleRate); if (_remainingSamples < 0) { _remainingSamples = 0; diff --git a/src/BizHawk.Client.EmuHawk/Sound/Output/OpenALSoundOutput.cs b/src/BizHawk.Client.EmuHawk/Sound/Output/OpenALSoundOutput.cs index cd0041e8f3..c0f6b6a265 100644 --- a/src/BizHawk.Client.EmuHawk/Sound/Output/OpenALSoundOutput.cs +++ b/src/BizHawk.Client.EmuHawk/Sound/Output/OpenALSoundOutput.cs @@ -12,18 +12,18 @@ namespace BizHawk.Client.EmuHawk public class OpenALSoundOutput : ISoundOutput { private bool _disposed; - private Sound _sound; + private readonly IHostAudioManager _sound; private AudioContext _context; private int _sourceID; private BufferPool _bufferPool; private int _currentSamplesQueued; private short[] _tempSampleBuffer; - public OpenALSoundOutput(Sound sound) + public OpenALSoundOutput(IHostAudioManager sound) { _sound = sound; string deviceName = GetDeviceNames().FirstOrDefault(n => n == GlobalWin.Config.SoundDevice); - _context = new AudioContext(deviceName, Sound.SampleRate); + _context = new AudioContext(deviceName, _sound.SampleRate); } public void Dispose() @@ -54,7 +54,7 @@ namespace BizHawk.Client.EmuHawk public void StartSound() { - BufferSizeSamples = Sound.MillisecondsToSamples(GlobalWin.Config.SoundBufferSizeMs); + BufferSizeSamples = _sound.MillisecondsToSamples(GlobalWin.Config.SoundBufferSizeMs); MaxSamplesDeficit = BufferSizeSamples; _sourceID = AL.GenSource(); @@ -100,15 +100,15 @@ namespace BizHawk.Client.EmuHawk { if (sampleCount == 0) return; UnqueueProcessedBuffers(); - int byteCount = sampleCount * Sound.BlockAlign; + int byteCount = sampleCount * _sound.BlockAlign; if (sampleOffset != 0) { AllocateTempSampleBuffer(sampleCount); - Buffer.BlockCopy(samples, sampleOffset * Sound.BlockAlign, _tempSampleBuffer, 0, byteCount); + Buffer.BlockCopy(samples, sampleOffset * _sound.BlockAlign, _tempSampleBuffer, 0, byteCount); samples = _tempSampleBuffer; } var buffer = _bufferPool.Obtain(byteCount); - AL.BufferData(buffer.BufferID, ALFormat.Stereo16, samples, byteCount, Sound.SampleRate); + AL.BufferData(buffer.BufferID, ALFormat.Stereo16, samples, byteCount, _sound.SampleRate); AL.SourceQueueBuffer(_sourceID, buffer.BufferID); _currentSamplesQueued += sampleCount; if (AL.GetSourceState(_sourceID) != ALSourceState.Playing) @@ -124,7 +124,7 @@ namespace BizHawk.Client.EmuHawk { AL.SourceUnqueueBuffer(_sourceID); var releasedBuffer = _bufferPool.ReleaseOne(); - _currentSamplesQueued -= releasedBuffer.Length / Sound.BlockAlign; + _currentSamplesQueued -= releasedBuffer.Length / _sound.BlockAlign; } } @@ -136,7 +136,7 @@ namespace BizHawk.Client.EmuHawk private void AllocateTempSampleBuffer(int sampleCount) { - int length = sampleCount * Sound.ChannelCount; + int length = sampleCount * _sound.ChannelCount; if (_tempSampleBuffer == null || _tempSampleBuffer.Length < length) { _tempSampleBuffer = new short[length]; diff --git a/src/BizHawk.Client.EmuHawk/Sound/Output/XAudio2SoundOutput.cs b/src/BizHawk.Client.EmuHawk/Sound/Output/XAudio2SoundOutput.cs index 21d10235c5..3e2156198a 100644 --- a/src/BizHawk.Client.EmuHawk/Sound/Output/XAudio2SoundOutput.cs +++ b/src/BizHawk.Client.EmuHawk/Sound/Output/XAudio2SoundOutput.cs @@ -13,14 +13,14 @@ namespace BizHawk.Client.EmuHawk public class XAudio2SoundOutput : ISoundOutput { private bool _disposed; - private Sound _sound; + private readonly IHostAudioManager _sound; private XAudio2 _device; private MasteringVoice _masteringVoice; private SourceVoice _sourceVoice; private BufferPool _bufferPool; private long _runningSamplesQueued; - public XAudio2SoundOutput(Sound sound) + public XAudio2SoundOutput(IHostAudioManager sound) { _sound = sound; _device = new XAudio2(); @@ -28,8 +28,8 @@ namespace BizHawk.Client.EmuHawk .Select(n => (int?)n) .FirstOrDefault(n => _device.GetDeviceDetails(n.Value).DisplayName == GlobalWin.Config.SoundDevice); _masteringVoice = deviceIndex == null ? - new MasteringVoice(_device, Sound.ChannelCount, Sound.SampleRate) : - new MasteringVoice(_device, Sound.ChannelCount, Sound.SampleRate, deviceIndex.Value); + new MasteringVoice(_device, _sound.ChannelCount, _sound.SampleRate) : + new MasteringVoice(_device, _sound.ChannelCount, _sound.SampleRate, deviceIndex.Value); } public void Dispose() @@ -64,17 +64,17 @@ namespace BizHawk.Client.EmuHawk public void StartSound() { - BufferSizeSamples = Sound.MillisecondsToSamples(GlobalWin.Config.SoundBufferSizeMs); + BufferSizeSamples = _sound.MillisecondsToSamples(GlobalWin.Config.SoundBufferSizeMs); MaxSamplesDeficit = BufferSizeSamples; var format = new WaveFormat { - SamplesPerSecond = Sound.SampleRate, - BitsPerSample = Sound.BytesPerSample * 8, - Channels = Sound.ChannelCount, + SamplesPerSecond = _sound.SampleRate, + BitsPerSample = (short) (_sound.BytesPerSample * 8), + Channels = (short) _sound.ChannelCount, FormatTag = WaveFormatTag.Pcm, - BlockAlignment = Sound.BlockAlign, - AverageBytesPerSecond = Sound.SampleRate * Sound.BlockAlign + BlockAlignment = (short) _sound.BlockAlign, + AverageBytesPerSecond = _sound.SampleRate * _sound.BlockAlign }; _sourceVoice = new SourceVoice(_device, format); @@ -114,9 +114,9 @@ namespace BizHawk.Client.EmuHawk { if (sampleCount == 0) return; _bufferPool.Release(_sourceVoice.State.BuffersQueued); - int byteCount = sampleCount * Sound.BlockAlign; + int byteCount = sampleCount * _sound.BlockAlign; var item = _bufferPool.Obtain(byteCount); - Buffer.BlockCopy(samples, sampleOffset * Sound.BlockAlign, item.Bytes, 0, byteCount); + Buffer.BlockCopy(samples, sampleOffset * _sound.BlockAlign, item.Bytes, 0, byteCount); item.AudioBuffer.AudioBytes = byteCount; _sourceVoice.SubmitSourceBuffer(item.AudioBuffer); _runningSamplesQueued += sampleCount; diff --git a/src/BizHawk.Client.EmuHawk/Sound/Sound.cs b/src/BizHawk.Client.EmuHawk/Sound/Sound.cs index 886df24706..055ac329c6 100644 --- a/src/BizHawk.Client.EmuHawk/Sound/Sound.cs +++ b/src/BizHawk.Client.EmuHawk/Sound/Sound.cs @@ -7,12 +7,16 @@ using BizHawk.Common; namespace BizHawk.Client.EmuHawk { - public class Sound : IDisposable + /// TODO rename to HostAudioManager + public class Sound : IHostAudioManager, IDisposable { - public const int SampleRate = 44100; - public const int BytesPerSample = 2; - public const int ChannelCount = 2; - public const int BlockAlign = BytesPerSample * ChannelCount; + public int SampleRate { get; } = 44100; + + public int BytesPerSample { get; } = 2; + + public int ChannelCount { get; } = 2; + + public int BlockAlign { get; } private bool _disposed; private readonly ISoundOutput _outputDevice; @@ -22,6 +26,8 @@ namespace BizHawk.Client.EmuHawk public Sound(IntPtr mainWindowHandle) { + BlockAlign = BytesPerSample * ChannelCount; + if (OSTailoredCode.IsUnixHost) { // if DirectSound or XAudio is chosen, use OpenAL, otherwise comply with the user's choice @@ -69,7 +75,7 @@ namespace BizHawk.Client.EmuHawk _outputProvider.MaxSamplesDeficit = _outputDevice.MaxSamplesDeficit; - SoundMaxBufferDeficitMs = (int)Math.Ceiling(SamplesToMilliseconds(_outputDevice.MaxSamplesDeficit)); + SoundMaxBufferDeficitMs = (int) Math.Ceiling(this.SamplesToMilliseconds(_outputDevice.MaxSamplesDeficit)); IsStarted = true; } @@ -119,7 +125,7 @@ namespace BizHawk.Client.EmuHawk public bool LogUnderruns { get; set; } - internal void HandleInitializationOrUnderrun(bool isUnderrun, ref int samplesNeeded) + public void HandleInitializationOrUnderrun(bool isUnderrun, ref int samplesNeeded) { // Fill device buffer with silence but leave enough room for one frame int samplesPerFrame = (int)Math.Round(SampleRate / (double)GlobalWin.Emulator.VsyncRate()); @@ -218,15 +224,5 @@ namespace BizHawk.Client.EmuHawk _outputDevice.WriteSamples(samples, sampleOffset, sampleCount); } - - public static int MillisecondsToSamples(int milliseconds) - { - return milliseconds * SampleRate / 1000; - } - - public static double SamplesToMilliseconds(int samples) - { - return samples * 1000.0 / SampleRate; - } } }