From 83e8abc963a091ffb8762b91503fdcef9d6f6569 Mon Sep 17 00:00:00 2001 From: jdpurcell Date: Sat, 31 Jan 2015 04:49:53 +0000 Subject: [PATCH] XAudio2! There's no UI for it yet. --- BizHawk.Client.Common/Global.cs | 2 +- BizHawk.Client.EmuHawk/GlobalWin.cs | 3 - BizHawk.Client.EmuHawk/MainForm.cs | 16 +- BizHawk.Client.EmuHawk/Program.cs | 15 +- BizHawk.Client.EmuHawk/Sound.cs | 522 ++++++++++++------- BizHawk.Client.EmuHawk/config/SoundConfig.cs | 9 +- 6 files changed, 355 insertions(+), 212 deletions(-) diff --git a/BizHawk.Client.Common/Global.cs b/BizHawk.Client.Common/Global.cs index 70a638b9f7..6b2207ca51 100644 --- a/BizHawk.Client.Common/Global.cs +++ b/BizHawk.Client.Common/Global.cs @@ -23,7 +23,7 @@ namespace BizHawk.Client.Common public static bool DisableSecondaryThrottling; /// - /// How far the sound output buffer can go below full before drastic corrective measures are taken. + /// The maximum number of millseconds the sound output buffer can go below full before causing a noticable sound interruption. /// public static int SoundMaxBufferDeficitMs; diff --git a/BizHawk.Client.EmuHawk/GlobalWin.cs b/BizHawk.Client.EmuHawk/GlobalWin.cs index 97b732fe5a..8b49300aae 100644 --- a/BizHawk.Client.EmuHawk/GlobalWin.cs +++ b/BizHawk.Client.EmuHawk/GlobalWin.cs @@ -8,9 +8,6 @@ namespace BizHawk.Client.EmuHawk { public static MainForm MainForm; public static ToolManager Tools; -#if WINDOWS - public static DirectSound DSound; -#endif public static IGL GL; public static Bizware.BizwareGL.Drivers.OpenTK.IGL_TK IGL_GL; public static GLManager.ContextRef CR_GL; diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 541bc2fa71..5d67465c85 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -278,7 +278,11 @@ namespace BizHawk.Client.EmuHawk Global.AutoFireController = Global.AutofireNullControls; Global.AutofireStickyXORAdapter.SetOnOffPatternFromConfig(); #if WINDOWS - GlobalWin.Sound = new Sound(Handle, GlobalWin.DSound); + try { GlobalWin.Sound = new Sound(Handle); } + catch + { + MessageBox.Show("Couldn't initialize DirectSound! Things may go poorly for you. Try changing your sound driver to 41khz instead of 48khz in mmsys.cpl.", "Initialization Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } #else Global.Sound = new Sound(); #endif @@ -2886,14 +2890,8 @@ namespace BizHawk.Client.EmuHawk UpdateFrame = false; } - if (genSound && !coreskipaudio) - { - GlobalWin.Sound.UpdateSound(); - } - else - { - GlobalWin.Sound.UpdateSilence(); - } + bool outputSilence = !genSound || coreskipaudio; + GlobalWin.Sound.UpdateSound(outputSilence); } #endregion diff --git a/BizHawk.Client.EmuHawk/Program.cs b/BizHawk.Client.EmuHawk/Program.cs index 77c1979b16..10c665e979 100644 --- a/BizHawk.Client.EmuHawk/Program.cs +++ b/BizHawk.Client.EmuHawk/Program.cs @@ -77,14 +77,6 @@ namespace BizHawk.Client.EmuHawk } } -#if WINDOWS - try { GlobalWin.DSound = SoundEnumeration.Create(); } - catch - { - MessageBox.Show("Couldn't initialize DirectSound! Things may go poorly for you. Try changing your sound driver to 41khz instead of 48khz in mmsys.cpl.", "Initialization Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } -#endif - //create IGL context. we do this whether or not the user has selected OpenGL, so that we can run opengl-based emulator cores GlobalWin.IGL_GL = new Bizware.BizwareGL.Drivers.OpenTK.IGL_TK(); @@ -183,8 +175,11 @@ REDO_DISPMETHOD: #if WINDOWS finally { - if (GlobalWin.DSound != null && GlobalWin.DSound.Disposed == false) - GlobalWin.DSound.Dispose(); + if (GlobalWin.Sound != null) + { + GlobalWin.Sound.Dispose(); + GlobalWin.Sound = null; + } GlobalWin.GL.Dispose(); GamePad.CloseAll(); } diff --git a/BizHawk.Client.EmuHawk/Sound.cs b/BizHawk.Client.EmuHawk/Sound.cs index 6807beeba6..97e6995fc0 100644 --- a/BizHawk.Client.EmuHawk/Sound.cs +++ b/BizHawk.Client.EmuHawk/Sound.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; #if WINDOWS +using SlimDX; using SlimDX.DirectSound; using SlimDX.Multimedia; +using SlimDX.XAudio2; #endif using BizHawk.Emulation.Common; @@ -13,130 +16,68 @@ using BizHawk.Client.Common; namespace BizHawk.Client.EmuHawk { -#if WINDOWS - public static class SoundEnumeration + public interface ISoundOutput : IDisposable { - public static DirectSound Create() - { - var dc = DirectSound.GetDevices(); - foreach (var dev in dc) - { - if (dev.Description == Global.Config.SoundDevice) - return new DirectSound(dev.DriverGuid); - } - return new DirectSound(); - } - - public static IEnumerable DeviceNames() - { - var ret = new List(); - var dc = DirectSound.GetDevices(); - foreach (var dev in dc) - ret.Add(dev.Description); - return ret; - } + void StartSound(); + void StopSound(); + void ApplyVolumeSettings(double volume); + int MaxSamplesDeficit { get; } + int CalculateSamplesNeeded(); + void WriteSamples(short[] samples, int sampleCount); } +#if WINDOWS public class Sound : IDisposable { - private const int SampleRate = 44100; - private const int BytesPerSample = 2; - private const int ChannelCount = 2; - private const int BlockAlign = BytesPerSample * ChannelCount; + public const int SampleRate = 44100; + public const int BytesPerSample = 2; + public const int ChannelCount = 2; + public const int BlockAlign = BytesPerSample * ChannelCount; - private bool _muted; private bool _disposed; - private DirectSound _device; - private SecondarySoundBuffer _deviceBuffer; - private readonly BufferedAsync _semiSync = new BufferedAsync(); - private SoundOutputProvider _outputProvider; - private ISoundProvider _asyncSoundProvider; + private ISoundOutput _soundOutput; private ISyncSoundProvider _syncSoundProvider; - private int _actualWriteOffsetBytes = -1; - private int _filledBufferSizeBytes; - private long _lastWriteTime; - private int _lastWriteCursor; + private ISoundProvider _asyncSoundProvider; + private SoundOutputProvider _outputProvider; + private readonly BufferedAsync _semiSync = new BufferedAsync(); - public Sound(IntPtr handle, DirectSound device) + public Sound(IntPtr mainWindowHandle) { - if (device == null) return; - - device.SetCooperativeLevel(handle, CooperativeLevel.Priority); - _device = device; + _soundOutput = new DirectSoundSoundOutput(this, mainWindowHandle); + //_soundOutput = new XAudio2SoundOutput(this); } - private int BufferSizeSamples { get; set; } - - private int BufferSizeBytes + public void Dispose() { - get { return BufferSizeSamples * BlockAlign; } + if (_disposed) return; + + StopSound(); + + _soundOutput.Dispose(); + _soundOutput = null; + + _disposed = true; } - private void CreateDeviceBuffer() - { - BufferSizeSamples = MillisecondsToSamples(Global.Config.SoundBufferSizeMs); - - var format = new WaveFormat - { - SamplesPerSecond = SampleRate, - BitsPerSample = BytesPerSample * 8, - Channels = ChannelCount, - FormatTag = WaveFormatTag.Pcm, - BlockAlignment = BlockAlign, - AverageBytesPerSecond = SampleRate * BlockAlign - }; - - var desc = new SoundBufferDescription - { - Format = format, - Flags = BufferFlags.GlobalFocus | BufferFlags.Software | BufferFlags.GetCurrentPosition2 | BufferFlags.ControlVolume, - SizeInBytes = BufferSizeBytes - }; - - _deviceBuffer = new SecondarySoundBuffer(_device, desc); - } - - public void ApplyVolumeSettings() - { - if (_deviceBuffer == null) return; - - double volume = Global.Config.SoundVolume / 100.0; - if (volume < 0.0) volume = 0.0; - if (volume > 1.0) volume = 1.0; - _deviceBuffer.Volume = CalculateDirectSoundVolumeLevel(volume); - } + public bool IsStarted { get; private set; } public void StartSound() { - if (_disposed) throw new ObjectDisposedException("Sound"); + if (_disposed) return; if (!Global.Config.SoundEnabled) return; - if (_deviceBuffer != null) return; + if (IsStarted) return; - CreateDeviceBuffer(); - ApplyVolumeSettings(); - - _deviceBuffer.Write(new byte[BufferSizeBytes], 0, LockFlags.EntireBuffer); - _deviceBuffer.CurrentPlayPosition = 0; - _deviceBuffer.Play(0, PlayFlags.Looping); - - _actualWriteOffsetBytes = -1; - _filledBufferSizeBytes = 0; - _lastWriteTime = 0; - _lastWriteCursor = 0; - - // 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 - // they happen and filling the buffer with silence) or less frequent but more - // severe glitches. At least on my Windows 8 machines, the distance between the - // 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 + ((Global.Config.SoundBufferSizeMs - 60) / 2), 65); + _soundOutput.StartSound(); _outputProvider = new SoundOutputProvider(); - _outputProvider.MaxSamplesDeficit = BufferSizeSamples - MillisecondsToSamples(minBufferFullnessMs); + _outputProvider.MaxSamplesDeficit = _soundOutput.MaxSamplesDeficit; _outputProvider.BaseSoundProvider = _syncSoundProvider; - Global.SoundMaxBufferDeficitMs = Global.Config.SoundBufferSizeMs - minBufferFullnessMs; + Global.SoundMaxBufferDeficitMs = (int)Math.Ceiling(SamplesToMilliseconds(_soundOutput.MaxSamplesDeficit)); + + IsStarted = true; + + ApplyVolumeSettings(); //LogUnderruns = true; //_outputProvider.LogDebug = true; @@ -144,25 +85,25 @@ namespace BizHawk.Client.EmuHawk public void StopSound() { - if (_deviceBuffer == null) return; + if (!IsStarted) return; - _deviceBuffer.Write(new byte[BufferSizeBytes], 0, LockFlags.EntireBuffer); - _deviceBuffer.Stop(); - _deviceBuffer.Dispose(); - _deviceBuffer = null; + _soundOutput.StopSound(); _outputProvider = null; Global.SoundMaxBufferDeficitMs = 0; - BufferSizeSamples = 0; + IsStarted = false; } - public void Dispose() + public void ApplyVolumeSettings() { - if (_disposed) return; - StopSound(); - _disposed = true; + if (!IsStarted) return; + + double volume = Global.Config.SoundVolume / 100.0; + if (volume < 0.0) volume = 0.0; + if (volume > 1.0) volume = 1.0; + _soundOutput.ApplyVolumeSettings(volume); } public void SetSyncInputPin(ISyncSoundProvider source) @@ -198,90 +139,34 @@ namespace BizHawk.Client.EmuHawk _semiSync.RecalculateMagic(Global.CoreComm.VsyncRate); } - private bool InitializeBufferWithSilence + public bool InitializeBufferWithSilence { get { return true; } } - private bool RecoverFromUnderrunsWithSilence + public bool RecoverFromUnderrunsWithSilence { get { return true; } } - private int SilenceLeaveRoomForFrameCount + public int SilenceLeaveRoomForFrameCount { get { return Global.Config.SoundThrottle ? 1 : 2; } // Why 2? I don't know, but it seems to work well with the clock throttle's behavior. } public bool LogUnderruns { get; set; } - private int CalculateSamplesNeeded() + public void OnUnderrun() { - long currentWriteTime = Stopwatch.GetTimestamp(); - int playCursor = _deviceBuffer.CurrentPlayPosition; - int writeCursor = _deviceBuffer.CurrentWritePosition; - bool detectedUnderrun = false; - if (_actualWriteOffsetBytes != -1) - { - double elapsedSeconds = (currentWriteTime - _lastWriteTime) / (double)Stopwatch.Frequency; - double bufferSizeSeconds = (double)BufferSizeSamples / SampleRate; - int cursorDelta = CircularDistance(_lastWriteCursor, writeCursor, BufferSizeBytes); - cursorDelta += BufferSizeBytes * (int)Math.Round((elapsedSeconds - (cursorDelta / (double)(SampleRate * BlockAlign))) / bufferSizeSeconds); - _filledBufferSizeBytes -= cursorDelta; - if (_filledBufferSizeBytes < 0) - { - if (LogUnderruns) Console.WriteLine("DirectSound underrun detected!"); - detectedUnderrun = true; - _outputProvider.OnVolatility(); - } - } - bool isInitializing = _actualWriteOffsetBytes == -1; - if (isInitializing || detectedUnderrun) - { - _actualWriteOffsetBytes = writeCursor; - _filledBufferSizeBytes = 0; - } - int samplesNeeded = CircularDistance(_actualWriteOffsetBytes, playCursor, BufferSizeBytes) / BlockAlign; - if ((isInitializing && InitializeBufferWithSilence) || (detectedUnderrun && RecoverFromUnderrunsWithSilence)) - { - int samplesPerFrame = (int)Math.Round(SampleRate / Global.Emulator.CoreComm.VsyncRate); - int silenceSamples = Math.Max(samplesNeeded - (SilenceLeaveRoomForFrameCount * samplesPerFrame), 0); - WriteSamples(new short[silenceSamples * 2], silenceSamples); - samplesNeeded -= silenceSamples; - } - _lastWriteTime = currentWriteTime; - _lastWriteCursor = writeCursor; - return samplesNeeded; + if (!IsStarted) return; + + if (LogUnderruns) Console.WriteLine("Sound underrun detected!"); + _outputProvider.OnVolatility(); } - private int CircularDistance(int start, int end, int size) + public void UpdateSound(bool outputSilence) { - 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; - UpdateSound(); - _muted = false; - } - - public void UpdateSound() - { - if (!Global.Config.SoundEnabled || _deviceBuffer == null || _disposed) + if (!Global.Config.SoundEnabled || !IsStarted || _disposed) { if (_asyncSoundProvider != null) _asyncSoundProvider.DiscardSamples(); if (_syncSoundProvider != null) _syncSoundProvider.DiscardSamples(); @@ -290,10 +175,10 @@ namespace BizHawk.Client.EmuHawk } short[] samples; - int samplesNeeded = CalculateSamplesNeeded(); + int samplesNeeded = _soundOutput.CalculateSamplesNeeded(); int samplesProvided; - if (_muted) + if (outputSilence) { samples = new short[samplesNeeded * ChannelCount]; samplesProvided = samplesNeeded; @@ -311,7 +196,7 @@ namespace BizHawk.Client.EmuHawk while (samplesNeeded < samplesProvided && !Global.DisableSecondaryThrottling) { Thread.Sleep((samplesProvided - samplesNeeded) / (SampleRate / 1000)); // Let the audio clock control sleep time - samplesNeeded = CalculateSamplesNeeded(); + samplesNeeded = _soundOutput.CalculateSamplesNeeded(); } } else @@ -336,20 +221,289 @@ namespace BizHawk.Client.EmuHawk return; } - WriteSamples(samples, samplesProvided); + _soundOutput.WriteSamples(samples, samplesProvided); } - private static int MillisecondsToSamples(int milliseconds) + public static int MillisecondsToSamples(int milliseconds) { return milliseconds * SampleRate / 1000; } - /// Percent volume level from 0.0 to 1.0. - private static int CalculateDirectSoundVolumeLevel(double level) + public static double SamplesToMilliseconds(int samples) + { + return samples * 1000.0 / SampleRate; + } + } + + public class DirectSoundSoundOutput : ISoundOutput + { + private bool _disposed; + private Sound _sound; + private DirectSound _device; + private SecondarySoundBuffer _deviceBuffer; + private int _actualWriteOffsetBytes = -1; + private int _filledBufferSizeBytes; + private long _lastWriteTime; + private int _lastWriteCursor; + + public DirectSoundSoundOutput(Sound sound, IntPtr mainWindowHandle) + { + _sound = sound; + + var deviceInfo = DirectSound.GetDevices().FirstOrDefault(d => d.Description == Global.Config.SoundDevice); + _device = deviceInfo != null ? new DirectSound(deviceInfo.DriverGuid) : new DirectSound(); + _device.SetCooperativeLevel(mainWindowHandle, CooperativeLevel.Priority); + } + + public void Dispose() + { + if (_disposed) return; + + _device.Dispose(); + _device = null; + + _disposed = true; + } + + public static IEnumerable GetDeviceNames() + { + return DirectSound.GetDevices().Select(d => d.Description); + } + + private int BufferSizeSamples { get; set; } + + private int BufferSizeBytes + { + get { return BufferSizeSamples * Sound.BlockAlign; } + } + + public int MaxSamplesDeficit { get; private set; } + + public void ApplyVolumeSettings(double volume) { // I'm not sure if this is "technically" correct but it works okay int range = (int)Volume.Maximum - (int)Volume.Minimum; - return (int)(Math.Pow(level, 0.15) * range) + (int)Volume.Minimum; + _deviceBuffer.Volume = (int)(Math.Pow(volume, 0.15) * range) + (int)Volume.Minimum; + } + + public void StartSound() + { + BufferSizeSamples = Sound.MillisecondsToSamples(Global.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 + // they happen and filling the buffer with silence) or less frequent but more + // severe glitches. At least on my Windows 8 machines, the distance between the + // 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 + ((Global.Config.SoundBufferSizeMs - 60) / 2), 65); + MaxSamplesDeficit = BufferSizeSamples - Sound.MillisecondsToSamples(minBufferFullnessMs); + + var format = new WaveFormat + { + SamplesPerSecond = Sound.SampleRate, + BitsPerSample = Sound.BytesPerSample * 8, + Channels = Sound.ChannelCount, + FormatTag = WaveFormatTag.Pcm, + BlockAlignment = Sound.BlockAlign, + AverageBytesPerSecond = Sound.SampleRate * Sound.BlockAlign + }; + + var desc = new SoundBufferDescription + { + Format = format, + Flags = + SlimDX.DirectSound.BufferFlags.GlobalFocus | + SlimDX.DirectSound.BufferFlags.Software | + SlimDX.DirectSound.BufferFlags.GetCurrentPosition2 | + SlimDX.DirectSound.BufferFlags.ControlVolume, + SizeInBytes = BufferSizeBytes + }; + + _deviceBuffer = new SecondarySoundBuffer(_device, desc); + + _actualWriteOffsetBytes = -1; + _filledBufferSizeBytes = 0; + _lastWriteTime = 0; + _lastWriteCursor = 0; + + _deviceBuffer.Play(0, SlimDX.DirectSound.PlayFlags.Looping); + } + + public void StopSound() + { + _deviceBuffer.Stop(); + _deviceBuffer.Dispose(); + _deviceBuffer = null; + + BufferSizeSamples = 0; + } + + public int CalculateSamplesNeeded() + { + long currentWriteTime = Stopwatch.GetTimestamp(); + int playCursor = _deviceBuffer.CurrentPlayPosition; + int writeCursor = _deviceBuffer.CurrentWritePosition; + bool detectedUnderrun = false; + if (_actualWriteOffsetBytes != -1) + { + double elapsedSeconds = (currentWriteTime - _lastWriteTime) / (double)Stopwatch.Frequency; + 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); + _filledBufferSizeBytes -= cursorDelta; + if (_filledBufferSizeBytes < 0) + { + _sound.OnUnderrun(); + detectedUnderrun = true; + } + } + bool isInitializing = _actualWriteOffsetBytes == -1; + if (isInitializing || detectedUnderrun) + { + _actualWriteOffsetBytes = writeCursor; + _filledBufferSizeBytes = 0; + } + int samplesNeeded = CircularDistance(_actualWriteOffsetBytes, playCursor, BufferSizeBytes) / Sound.BlockAlign; + if ((isInitializing && _sound.InitializeBufferWithSilence) || (detectedUnderrun && _sound.RecoverFromUnderrunsWithSilence)) + { + int samplesPerFrame = (int)Math.Round(Sound.SampleRate / Global.Emulator.CoreComm.VsyncRate); + int silenceSamples = Math.Max(samplesNeeded - (_sound.SilenceLeaveRoomForFrameCount * samplesPerFrame), 0); + WriteSamples(new short[silenceSamples * 2], silenceSamples); + samplesNeeded -= silenceSamples; + } + _lastWriteTime = currentWriteTime; + _lastWriteCursor = writeCursor; + return samplesNeeded; + } + + private int CircularDistance(int start, int end, int size) + { + return (end - start + size) % size; + } + + public void WriteSamples(short[] samples, int sampleCount) + { + if (sampleCount == 0) return; + _deviceBuffer.Write(samples, 0, sampleCount * Sound.ChannelCount, _actualWriteOffsetBytes, LockFlags.None); + _actualWriteOffsetBytes = (_actualWriteOffsetBytes + (sampleCount * Sound.BlockAlign)) % BufferSizeBytes; + _filledBufferSizeBytes += sampleCount * Sound.BlockAlign; + } + } + + public class XAudio2SoundOutput : ISoundOutput + { + private bool _disposed; + private Sound _sound; + private XAudio2 _device; + private MasteringVoice _masteringVoice; + private SourceVoice _sourceVoice; + private long _runningSamplesQueued; + + public XAudio2SoundOutput(Sound sound) + { + _sound = sound; + _device = new XAudio2(); + int? deviceIndex = Enumerable.Range(0, _device.DeviceCount) + .Select(n => (int?)n) + .FirstOrDefault(n => _device.GetDeviceDetails(n.Value).DisplayName == Global.Config.SoundDevice); + _masteringVoice = deviceIndex == null ? + new MasteringVoice(_device, Sound.ChannelCount, Sound.SampleRate) : + new MasteringVoice(_device, Sound.ChannelCount, Sound.SampleRate, deviceIndex.Value); + } + + public void Dispose() + { + if (_disposed) return; + + _masteringVoice.Dispose(); + _masteringVoice = null; + + _device.Dispose(); + _device = null; + + _disposed = true; + } + + public static IEnumerable GetDeviceNames() + { + using (XAudio2 device = new XAudio2()) + { + return Enumerable.Range(0, device.DeviceCount).Select(n => device.GetDeviceDetails(n).DisplayName); + } + } + + private int BufferSizeSamples { get; set; } + + public int MaxSamplesDeficit { get; private set; } + + public void ApplyVolumeSettings(double volume) + { + _sourceVoice.Volume = (float)volume; + } + + public void StartSound() + { + BufferSizeSamples = Sound.MillisecondsToSamples(Global.Config.SoundBufferSizeMs); + MaxSamplesDeficit = BufferSizeSamples; + + var format = new WaveFormat + { + SamplesPerSecond = Sound.SampleRate, + BitsPerSample = Sound.BytesPerSample * 8, + Channels = Sound.ChannelCount, + FormatTag = WaveFormatTag.Pcm, + BlockAlignment = Sound.BlockAlign, + AverageBytesPerSecond = Sound.SampleRate * Sound.BlockAlign + }; + + _sourceVoice = new SourceVoice(_device, format); + + _runningSamplesQueued = 0; + + _sourceVoice.Start(); + } + + public void StopSound() + { + _sourceVoice.Stop(); + _sourceVoice.Dispose(); + _sourceVoice = null; + + BufferSizeSamples = 0; + } + + public int CalculateSamplesNeeded() + { + long samplesAwaitingPlayback = _runningSamplesQueued - _sourceVoice.State.SamplesPlayed; + bool isInitializing = _runningSamplesQueued == 0; + bool detectedUnderrun = !isInitializing && _sourceVoice.State.BuffersQueued == 0; + if (detectedUnderrun) + { + _sound.OnUnderrun(); + } + int samplesNeeded = (int)Math.Max(BufferSizeSamples - samplesAwaitingPlayback, 0); + if ((isInitializing && _sound.InitializeBufferWithSilence) || (detectedUnderrun && _sound.RecoverFromUnderrunsWithSilence)) + { + int samplesPerFrame = (int)Math.Round(Sound.SampleRate / Global.Emulator.CoreComm.VsyncRate); + int silenceSamples = Math.Max(samplesNeeded - (_sound.SilenceLeaveRoomForFrameCount * samplesPerFrame), 0); + WriteSamples(new short[silenceSamples * 2], silenceSamples); + samplesNeeded -= silenceSamples; + } + return samplesNeeded; + } + + public void WriteSamples(short[] samples, int sampleCount) + { + if (sampleCount == 0) return; + // TODO: Re-use these buffers + byte[] bytes = new byte[sampleCount * Sound.BlockAlign]; + Buffer.BlockCopy(samples, 0, bytes, 0, bytes.Length); + _sourceVoice.SubmitSourceBuffer(new AudioBuffer { + AudioBytes = bytes.Length, + AudioData = new DataStream(bytes, true, false) + }); + _runningSamplesQueued += sampleCount; } } #else diff --git a/BizHawk.Client.EmuHawk/config/SoundConfig.cs b/BizHawk.Client.EmuHawk/config/SoundConfig.cs index 956d7aca89..ce94847529 100644 --- a/BizHawk.Client.EmuHawk/config/SoundConfig.cs +++ b/BizHawk.Client.EmuHawk/config/SoundConfig.cs @@ -26,15 +26,14 @@ namespace BizHawk.Client.EmuHawk SoundVolNumeric.Value = Global.Config.SoundVolume; UpdateSoundDialog(); - var dd = SoundEnumeration.DeviceNames(); listBoxSoundDevices.Items.Add(""); listBoxSoundDevices.SelectedIndex = 0; - foreach (var d in dd) + foreach (var name in DirectSoundSoundOutput.GetDeviceNames()) { - listBoxSoundDevices.Items.Add(d); - if (d == Global.Config.SoundDevice) + listBoxSoundDevices.Items.Add(name); + if (name == Global.Config.SoundDevice) { - listBoxSoundDevices.SelectedItem = d; + listBoxSoundDevices.SelectedItem = name; } }