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.
This commit is contained in:
YoshiRulz 2020-10-06 07:32:43 +10:00 committed by James Groom
parent 447ae737f3
commit e1daa7efd3
7 changed files with 84 additions and 58 deletions

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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)
{

View File

@ -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;

View File

@ -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];

View File

@ -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;

View File

@ -7,12 +7,16 @@ using BizHawk.Common;
namespace BizHawk.Client.EmuHawk
{
public class Sound : IDisposable
/// <remarks>TODO rename to <c>HostAudioManager</c></remarks>
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;
}
}
}