XAudio2! There's no UI for it yet.

This commit is contained in:
jdpurcell 2015-01-31 04:49:53 +00:00
parent 22c62d16a9
commit 83e8abc963
6 changed files with 355 additions and 212 deletions

View File

@ -23,7 +23,7 @@ namespace BizHawk.Client.Common
public static bool DisableSecondaryThrottling; public static bool DisableSecondaryThrottling;
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public static int SoundMaxBufferDeficitMs; public static int SoundMaxBufferDeficitMs;

View File

@ -8,9 +8,6 @@ namespace BizHawk.Client.EmuHawk
{ {
public static MainForm MainForm; public static MainForm MainForm;
public static ToolManager Tools; public static ToolManager Tools;
#if WINDOWS
public static DirectSound DSound;
#endif
public static IGL GL; public static IGL GL;
public static Bizware.BizwareGL.Drivers.OpenTK.IGL_TK IGL_GL; public static Bizware.BizwareGL.Drivers.OpenTK.IGL_TK IGL_GL;
public static GLManager.ContextRef CR_GL; public static GLManager.ContextRef CR_GL;

View File

@ -278,7 +278,11 @@ namespace BizHawk.Client.EmuHawk
Global.AutoFireController = Global.AutofireNullControls; Global.AutoFireController = Global.AutofireNullControls;
Global.AutofireStickyXORAdapter.SetOnOffPatternFromConfig(); Global.AutofireStickyXORAdapter.SetOnOffPatternFromConfig();
#if WINDOWS #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 #else
Global.Sound = new Sound(); Global.Sound = new Sound();
#endif #endif
@ -2886,14 +2890,8 @@ namespace BizHawk.Client.EmuHawk
UpdateFrame = false; UpdateFrame = false;
} }
if (genSound && !coreskipaudio) bool outputSilence = !genSound || coreskipaudio;
{ GlobalWin.Sound.UpdateSound(outputSilence);
GlobalWin.Sound.UpdateSound();
}
else
{
GlobalWin.Sound.UpdateSilence();
}
} }
#endregion #endregion

View File

@ -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 //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(); GlobalWin.IGL_GL = new Bizware.BizwareGL.Drivers.OpenTK.IGL_TK();
@ -183,8 +175,11 @@ REDO_DISPMETHOD:
#if WINDOWS #if WINDOWS
finally finally
{ {
if (GlobalWin.DSound != null && GlobalWin.DSound.Disposed == false) if (GlobalWin.Sound != null)
GlobalWin.DSound.Dispose(); {
GlobalWin.Sound.Dispose();
GlobalWin.Sound = null;
}
GlobalWin.GL.Dispose(); GlobalWin.GL.Dispose();
GamePad.CloseAll(); GamePad.CloseAll();
} }

View File

@ -1,11 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Threading; using System.Threading;
#if WINDOWS #if WINDOWS
using SlimDX;
using SlimDX.DirectSound; using SlimDX.DirectSound;
using SlimDX.Multimedia; using SlimDX.Multimedia;
using SlimDX.XAudio2;
#endif #endif
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
@ -13,130 +16,68 @@ using BizHawk.Client.Common;
namespace BizHawk.Client.EmuHawk namespace BizHawk.Client.EmuHawk
{ {
#if WINDOWS public interface ISoundOutput : IDisposable
public static class SoundEnumeration
{ {
public static DirectSound Create() void StartSound();
{ void StopSound();
var dc = DirectSound.GetDevices(); void ApplyVolumeSettings(double volume);
foreach (var dev in dc) int MaxSamplesDeficit { get; }
{ int CalculateSamplesNeeded();
if (dev.Description == Global.Config.SoundDevice) void WriteSamples(short[] samples, int sampleCount);
return new DirectSound(dev.DriverGuid);
}
return new DirectSound();
}
public static IEnumerable<string> DeviceNames()
{
var ret = new List<string>();
var dc = DirectSound.GetDevices();
foreach (var dev in dc)
ret.Add(dev.Description);
return ret;
}
} }
#if WINDOWS
public class Sound : IDisposable public class Sound : IDisposable
{ {
private const int SampleRate = 44100; public const int SampleRate = 44100;
private const int BytesPerSample = 2; public const int BytesPerSample = 2;
private const int ChannelCount = 2; public const int ChannelCount = 2;
private const int BlockAlign = BytesPerSample * ChannelCount; public const int BlockAlign = BytesPerSample * ChannelCount;
private bool _muted;
private bool _disposed; private bool _disposed;
private DirectSound _device; private ISoundOutput _soundOutput;
private SecondarySoundBuffer _deviceBuffer;
private readonly BufferedAsync _semiSync = new BufferedAsync();
private SoundOutputProvider _outputProvider;
private ISoundProvider _asyncSoundProvider;
private ISyncSoundProvider _syncSoundProvider; private ISyncSoundProvider _syncSoundProvider;
private int _actualWriteOffsetBytes = -1; private ISoundProvider _asyncSoundProvider;
private int _filledBufferSizeBytes; private SoundOutputProvider _outputProvider;
private long _lastWriteTime; private readonly BufferedAsync _semiSync = new BufferedAsync();
private int _lastWriteCursor;
public Sound(IntPtr handle, DirectSound device) public Sound(IntPtr mainWindowHandle)
{ {
if (device == null) return; _soundOutput = new DirectSoundSoundOutput(this, mainWindowHandle);
//_soundOutput = new XAudio2SoundOutput(this);
device.SetCooperativeLevel(handle, CooperativeLevel.Priority);
_device = device;
} }
private int BufferSizeSamples { get; set; } public void Dispose()
private int BufferSizeBytes
{ {
get { return BufferSizeSamples * BlockAlign; } if (_disposed) return;
StopSound();
_soundOutput.Dispose();
_soundOutput = null;
_disposed = true;
} }
private void CreateDeviceBuffer() public bool IsStarted { get; private set; }
{
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 void StartSound() public void StartSound()
{ {
if (_disposed) throw new ObjectDisposedException("Sound"); if (_disposed) return;
if (!Global.Config.SoundEnabled) return; if (!Global.Config.SoundEnabled) return;
if (_deviceBuffer != null) return; if (IsStarted) return;
CreateDeviceBuffer(); _soundOutput.StartSound();
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);
_outputProvider = new SoundOutputProvider(); _outputProvider = new SoundOutputProvider();
_outputProvider.MaxSamplesDeficit = BufferSizeSamples - MillisecondsToSamples(minBufferFullnessMs); _outputProvider.MaxSamplesDeficit = _soundOutput.MaxSamplesDeficit;
_outputProvider.BaseSoundProvider = _syncSoundProvider; _outputProvider.BaseSoundProvider = _syncSoundProvider;
Global.SoundMaxBufferDeficitMs = Global.Config.SoundBufferSizeMs - minBufferFullnessMs; Global.SoundMaxBufferDeficitMs = (int)Math.Ceiling(SamplesToMilliseconds(_soundOutput.MaxSamplesDeficit));
IsStarted = true;
ApplyVolumeSettings();
//LogUnderruns = true; //LogUnderruns = true;
//_outputProvider.LogDebug = true; //_outputProvider.LogDebug = true;
@ -144,25 +85,25 @@ namespace BizHawk.Client.EmuHawk
public void StopSound() public void StopSound()
{ {
if (_deviceBuffer == null) return; if (!IsStarted) return;
_deviceBuffer.Write(new byte[BufferSizeBytes], 0, LockFlags.EntireBuffer); _soundOutput.StopSound();
_deviceBuffer.Stop();
_deviceBuffer.Dispose();
_deviceBuffer = null;
_outputProvider = null; _outputProvider = null;
Global.SoundMaxBufferDeficitMs = 0; Global.SoundMaxBufferDeficitMs = 0;
BufferSizeSamples = 0; IsStarted = false;
} }
public void Dispose() public void ApplyVolumeSettings()
{ {
if (_disposed) return; if (!IsStarted) return;
StopSound();
_disposed = true; 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) public void SetSyncInputPin(ISyncSoundProvider source)
@ -198,90 +139,34 @@ namespace BizHawk.Client.EmuHawk
_semiSync.RecalculateMagic(Global.CoreComm.VsyncRate); _semiSync.RecalculateMagic(Global.CoreComm.VsyncRate);
} }
private bool InitializeBufferWithSilence public bool InitializeBufferWithSilence
{ {
get { return true; } get { return true; }
} }
private bool RecoverFromUnderrunsWithSilence public bool RecoverFromUnderrunsWithSilence
{ {
get { return true; } 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. 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; } public bool LogUnderruns { get; set; }
private int CalculateSamplesNeeded() public void OnUnderrun()
{ {
long currentWriteTime = Stopwatch.GetTimestamp(); if (!IsStarted) return;
int playCursor = _deviceBuffer.CurrentPlayPosition;
int writeCursor = _deviceBuffer.CurrentWritePosition; if (LogUnderruns) Console.WriteLine("Sound underrun detected!");
bool detectedUnderrun = false; _outputProvider.OnVolatility();
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;
} }
private int CircularDistance(int start, int end, int size) public void UpdateSound(bool outputSilence)
{ {
return (end - start + size) % size; if (!Global.Config.SoundEnabled || !IsStarted || _disposed)
}
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 (_asyncSoundProvider != null) _asyncSoundProvider.DiscardSamples(); if (_asyncSoundProvider != null) _asyncSoundProvider.DiscardSamples();
if (_syncSoundProvider != null) _syncSoundProvider.DiscardSamples(); if (_syncSoundProvider != null) _syncSoundProvider.DiscardSamples();
@ -290,10 +175,10 @@ namespace BizHawk.Client.EmuHawk
} }
short[] samples; short[] samples;
int samplesNeeded = CalculateSamplesNeeded(); int samplesNeeded = _soundOutput.CalculateSamplesNeeded();
int samplesProvided; int samplesProvided;
if (_muted) if (outputSilence)
{ {
samples = new short[samplesNeeded * ChannelCount]; samples = new short[samplesNeeded * ChannelCount];
samplesProvided = samplesNeeded; samplesProvided = samplesNeeded;
@ -311,7 +196,7 @@ namespace BizHawk.Client.EmuHawk
while (samplesNeeded < samplesProvided && !Global.DisableSecondaryThrottling) while (samplesNeeded < samplesProvided && !Global.DisableSecondaryThrottling)
{ {
Thread.Sleep((samplesProvided - samplesNeeded) / (SampleRate / 1000)); // Let the audio clock control sleep time Thread.Sleep((samplesProvided - samplesNeeded) / (SampleRate / 1000)); // Let the audio clock control sleep time
samplesNeeded = CalculateSamplesNeeded(); samplesNeeded = _soundOutput.CalculateSamplesNeeded();
} }
} }
else else
@ -336,20 +221,289 @@ namespace BizHawk.Client.EmuHawk
return; return;
} }
WriteSamples(samples, samplesProvided); _soundOutput.WriteSamples(samples, samplesProvided);
} }
private static int MillisecondsToSamples(int milliseconds) public static int MillisecondsToSamples(int milliseconds)
{ {
return milliseconds * SampleRate / 1000; return milliseconds * SampleRate / 1000;
} }
/// <param name="level">Percent volume level from 0.0 to 1.0.</param> public static double SamplesToMilliseconds(int samples)
private static int CalculateDirectSoundVolumeLevel(double level) {
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<string> 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 // I'm not sure if this is "technically" correct but it works okay
int range = (int)Volume.Maximum - (int)Volume.Minimum; 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<string> 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 #else

View File

@ -26,15 +26,14 @@ namespace BizHawk.Client.EmuHawk
SoundVolNumeric.Value = Global.Config.SoundVolume; SoundVolNumeric.Value = Global.Config.SoundVolume;
UpdateSoundDialog(); UpdateSoundDialog();
var dd = SoundEnumeration.DeviceNames();
listBoxSoundDevices.Items.Add("<default>"); listBoxSoundDevices.Items.Add("<default>");
listBoxSoundDevices.SelectedIndex = 0; listBoxSoundDevices.SelectedIndex = 0;
foreach (var d in dd) foreach (var name in DirectSoundSoundOutput.GetDeviceNames())
{ {
listBoxSoundDevices.Items.Add(d); listBoxSoundDevices.Items.Add(name);
if (d == Global.Config.SoundDevice) if (name == Global.Config.SoundDevice)
{ {
listBoxSoundDevices.SelectedItem = d; listBoxSoundDevices.SelectedItem = name;
} }
} }