BizHawk/BizHawk.Client.EmuHawk/Sound/Sound.cs

203 lines
5.6 KiB
C#
Raw Normal View History

2015-01-31 17:44:35 +00:00
using System;
using System.Threading;
using BizHawk.Emulation.Common;
using BizHawk.Client.Common;
namespace BizHawk.Client.EmuHawk
{
public class Sound : IDisposable
{
public const int SampleRate = 44100;
public const int BytesPerSample = 2;
public const int ChannelCount = 2;
public const int BlockAlign = BytesPerSample * ChannelCount;
private bool _disposed;
2016-12-15 03:03:25 +00:00
private readonly ISoundOutput _outputDevice;
2016-12-15 04:26:01 +00:00
private readonly SoundOutputProvider _outputProvider = new SoundOutputProvider(); // Buffer for Sync sources
private readonly BufferedAsync _bufferedAsync = new BufferedAsync(); // Buffer for Async sources
private IBufferedSoundProvider _bufferedProvider; // One of the preceding buffers, or null if no source is set
2015-01-31 17:44:35 +00:00
public Sound(IntPtr mainWindowHandle)
{
#if WINDOWS
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.DirectSound)
2016-12-15 03:03:25 +00:00
_outputDevice = new DirectSoundSoundOutput(this, mainWindowHandle);
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.XAudio2)
2016-12-15 03:03:25 +00:00
_outputDevice = new XAudio2SoundOutput(this);
2015-01-31 17:44:35 +00:00
#endif
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.OpenAL)
2016-12-15 03:03:25 +00:00
_outputDevice = new OpenALSoundOutput(this);
2016-12-15 03:03:25 +00:00
if (_outputDevice == null)
_outputDevice = new DummySoundOutput(this);
2015-01-31 17:44:35 +00:00
}
public void Dispose()
{
if (_disposed) return;
StopSound();
2016-12-15 03:03:25 +00:00
_outputDevice.Dispose();
2015-01-31 17:44:35 +00:00
_disposed = true;
}
public bool IsStarted { get; private set; }
public void StartSound()
{
if (_disposed) return;
if (!Global.Config.SoundEnabled) return;
if (IsStarted) return;
2016-12-15 03:03:25 +00:00
_outputDevice.StartSound();
2015-01-31 17:44:35 +00:00
2016-12-15 03:03:25 +00:00
_outputProvider.MaxSamplesDeficit = _outputDevice.MaxSamplesDeficit;
2015-01-31 17:44:35 +00:00
2016-12-15 03:03:25 +00:00
Global.SoundMaxBufferDeficitMs = (int)Math.Ceiling(SamplesToMilliseconds(_outputDevice.MaxSamplesDeficit));
2015-01-31 17:44:35 +00:00
IsStarted = true;
}
public void StopSound()
{
if (!IsStarted) return;
2016-12-15 03:03:25 +00:00
_outputDevice.StopSound();
2015-01-31 17:44:35 +00:00
2016-12-15 04:26:01 +00:00
if (_bufferedProvider != null) _bufferedProvider.DiscardSamples();
2015-01-31 17:44:35 +00:00
Global.SoundMaxBufferDeficitMs = 0;
IsStarted = false;
}
2016-12-11 19:16:25 +00:00
/// <summary>
2016-12-15 02:19:46 +00:00
/// Attaches a new input pin which will run either in sync or async mode depending
/// on its SyncMode property. Once attached, the sync mode must not change unless
/// the pin is re-attached.
2016-12-11 19:16:25 +00:00
/// </summary>
2016-12-15 02:19:46 +00:00
public void SetInputPin(ISoundProvider source)
2015-01-31 17:44:35 +00:00
{
2016-12-15 04:26:01 +00:00
if (_bufferedProvider != null)
2015-01-31 17:44:35 +00:00
{
2016-12-15 04:26:01 +00:00
_bufferedProvider.BaseSoundProvider = null;
_bufferedProvider.DiscardSamples();
_bufferedProvider = null;
2015-01-31 17:44:35 +00:00
}
2016-12-15 04:26:01 +00:00
if (source == null) return;
if (source.SyncMode == SyncSoundMode.Sync)
2015-01-31 17:44:35 +00:00
{
2016-12-15 04:26:01 +00:00
_bufferedProvider = _outputProvider;
2016-12-15 02:19:46 +00:00
}
2016-12-15 04:26:01 +00:00
else if (source.SyncMode == SyncSoundMode.Async)
2016-12-15 02:19:46 +00:00
{
2016-12-15 03:03:25 +00:00
_bufferedAsync.RecalculateMagic(Global.Emulator.CoreComm.VsyncRate);
2016-12-15 04:26:01 +00:00
_bufferedProvider = _bufferedAsync;
2015-01-31 17:44:35 +00:00
}
2016-12-15 04:26:01 +00:00
else throw new InvalidOperationException("Unsupported sync mode.");
_bufferedProvider.BaseSoundProvider = source;
2015-01-31 17:44:35 +00:00
}
2015-02-01 01:09:48 +00:00
public bool LogUnderruns { get; set; }
2015-01-31 17:44:35 +00:00
internal 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 / Global.Emulator.CoreComm.VsyncRate);
int silenceSamples = Math.Max(samplesNeeded - samplesPerFrame, 0);
_outputDevice.WriteSamples(new short[silenceSamples * 2], silenceSamples);
samplesNeeded -= silenceSamples;
if (isUnderrun)
2015-01-31 17:44:35 +00:00
{
if (LogUnderruns) Console.WriteLine("Sound underrun detected!");
_outputProvider.OnVolatility();
2015-01-31 17:44:35 +00:00
}
}
public void UpdateSound(float atten)
2015-01-31 17:44:35 +00:00
{
2016-12-15 04:26:01 +00:00
if (!Global.Config.SoundEnabled || !IsStarted || _bufferedProvider == null || _disposed)
2015-01-31 17:44:35 +00:00
{
2016-12-15 04:26:01 +00:00
if (_bufferedProvider != null) _bufferedProvider.DiscardSamples();
2015-01-31 17:44:35 +00:00
return;
}
if (atten < 0) atten = 0;
if (atten > 1) atten = 1;
2016-12-15 03:03:25 +00:00
_outputDevice.ApplyVolumeSettings(atten);
2015-01-31 17:44:35 +00:00
short[] samples;
2016-12-15 03:03:25 +00:00
int samplesNeeded = _outputDevice.CalculateSamplesNeeded();
2015-01-31 17:44:35 +00:00
int samplesProvided;
2016-12-11 19:16:25 +00:00
if (atten == 0)
2015-01-31 17:44:35 +00:00
{
samples = new short[samplesNeeded * ChannelCount];
samplesProvided = samplesNeeded;
2016-12-15 04:26:01 +00:00
_bufferedProvider.DiscardSamples();
2015-01-31 17:44:35 +00:00
}
2016-12-15 04:26:01 +00:00
else if (_bufferedProvider == _outputProvider)
2015-01-31 17:44:35 +00:00
{
if (Global.Config.SoundThrottle)
{
2016-12-15 04:26:01 +00:00
_outputProvider.BaseSoundProvider.GetSamplesSync(out samples, out samplesProvided);
2015-01-31 17:44:35 +00:00
if (Global.DisableSecondaryThrottling && samplesProvided > samplesNeeded)
{
return;
}
while (samplesProvided > samplesNeeded)
2015-01-31 17:44:35 +00:00
{
Thread.Sleep((samplesProvided - samplesNeeded) / (SampleRate / 1000)); // Let the audio clock control sleep time
2016-12-15 03:03:25 +00:00
samplesNeeded = _outputDevice.CalculateSamplesNeeded();
2015-01-31 17:44:35 +00:00
}
}
else
{
if (Global.DisableSecondaryThrottling) // This indicates rewind or fast-forward
{
_outputProvider.OnVolatility();
}
_outputProvider.GetSamples(samplesNeeded, out samples, out samplesProvided);
}
}
2016-12-15 04:26:01 +00:00
else if (_bufferedProvider == _bufferedAsync)
2015-01-31 17:44:35 +00:00
{
samples = new short[samplesNeeded * ChannelCount];
2016-12-15 03:03:25 +00:00
_bufferedAsync.GetSamplesAsync(samples);
2015-01-31 17:44:35 +00:00
samplesProvided = samplesNeeded;
}
else
{
return;
}
2016-12-15 03:03:25 +00:00
_outputDevice.WriteSamples(samples, samplesProvided);
2015-01-31 17:44:35 +00:00
}
public static int MillisecondsToSamples(int milliseconds)
{
return milliseconds * SampleRate / 1000;
}
public static double SamplesToMilliseconds(int samples)
{
return samples * 1000.0 / SampleRate;
}
}
}