213 lines
5.9 KiB
C#
213 lines
5.9 KiB
C#
using System;
|
|
using System.Threading;
|
|
|
|
using BizHawk.Emulation.Common;
|
|
using BizHawk.Emulation.Common.IEmulatorExtensions;
|
|
using BizHawk.Client.Common;
|
|
using BizHawk.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;
|
|
private bool _unjamSoundThrottle;
|
|
|
|
private readonly ISoundOutput _outputDevice;
|
|
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
|
|
|
|
public Sound(IntPtr mainWindowHandle)
|
|
{
|
|
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.OpenAL)
|
|
_outputDevice = new OpenALSoundOutput(this);
|
|
if (!PlatformLinkedLibSingleton.RunningOnUnix)
|
|
{
|
|
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.DirectSound)
|
|
_outputDevice = new DirectSoundSoundOutput(this, mainWindowHandle);
|
|
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.XAudio2)
|
|
_outputDevice = new XAudio2SoundOutput(this);
|
|
}
|
|
if (_outputDevice == null)
|
|
_outputDevice = new DummySoundOutput(this);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
|
|
StopSound();
|
|
|
|
_outputDevice.Dispose();
|
|
|
|
_disposed = true;
|
|
}
|
|
|
|
public bool IsStarted { get; private set; }
|
|
|
|
public void StartSound()
|
|
{
|
|
if (_disposed) return;
|
|
if (!Global.Config.SoundEnabled) return;
|
|
if (IsStarted) return;
|
|
|
|
_outputDevice.StartSound();
|
|
|
|
_outputProvider.MaxSamplesDeficit = _outputDevice.MaxSamplesDeficit;
|
|
|
|
Global.SoundMaxBufferDeficitMs = (int)Math.Ceiling(SamplesToMilliseconds(_outputDevice.MaxSamplesDeficit));
|
|
|
|
IsStarted = true;
|
|
}
|
|
|
|
public void StopSound()
|
|
{
|
|
if (!IsStarted) return;
|
|
|
|
_outputDevice.StopSound();
|
|
|
|
if (_bufferedProvider != null) _bufferedProvider.DiscardSamples();
|
|
|
|
Global.SoundMaxBufferDeficitMs = 0;
|
|
|
|
IsStarted = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public void SetInputPin(ISoundProvider source)
|
|
{
|
|
if (_bufferedProvider != null)
|
|
{
|
|
_bufferedProvider.BaseSoundProvider = null;
|
|
_bufferedProvider.DiscardSamples();
|
|
_bufferedProvider = null;
|
|
}
|
|
|
|
if (source == null) return;
|
|
|
|
if (source.SyncMode == SyncSoundMode.Sync)
|
|
{
|
|
_bufferedProvider = _outputProvider;
|
|
}
|
|
else if (source.SyncMode == SyncSoundMode.Async)
|
|
{
|
|
_bufferedAsync.RecalculateMagic(Global.Emulator.VsyncRate());
|
|
_bufferedProvider = _bufferedAsync;
|
|
}
|
|
else throw new InvalidOperationException("Unsupported sync mode.");
|
|
|
|
_bufferedProvider.BaseSoundProvider = source;
|
|
}
|
|
|
|
public bool LogUnderruns { get; set; }
|
|
|
|
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 / (double)Global.Emulator.VsyncRate());
|
|
int silenceSamples = Math.Max(samplesNeeded - samplesPerFrame, 0);
|
|
_outputDevice.WriteSamples(new short[silenceSamples * 2], silenceSamples);
|
|
samplesNeeded -= silenceSamples;
|
|
_unjamSoundThrottle = isUnderrun;
|
|
|
|
if (isUnderrun)
|
|
{
|
|
if (LogUnderruns) Console.WriteLine("Sound underrun detected!");
|
|
_outputProvider.OnVolatility();
|
|
}
|
|
}
|
|
|
|
public void UpdateSound(float atten)
|
|
{
|
|
if (!Global.Config.SoundEnabled || !IsStarted || _bufferedProvider == null || _disposed)
|
|
{
|
|
if (_bufferedProvider != null) _bufferedProvider.DiscardSamples();
|
|
return;
|
|
}
|
|
|
|
if (atten < 0) atten = 0;
|
|
if (atten > 1) atten = 1;
|
|
_outputDevice.ApplyVolumeSettings(atten);
|
|
|
|
short[] samples;
|
|
int samplesNeeded = _outputDevice.CalculateSamplesNeeded();
|
|
int samplesProvided;
|
|
|
|
if (atten == 0)
|
|
{
|
|
samples = new short[samplesNeeded * ChannelCount];
|
|
samplesProvided = samplesNeeded;
|
|
|
|
_bufferedProvider.DiscardSamples();
|
|
}
|
|
else if (_bufferedProvider == _outputProvider)
|
|
{
|
|
if (Global.Config.SoundThrottle)
|
|
{
|
|
_outputProvider.BaseSoundProvider.GetSamplesSync(out samples, out samplesProvided);
|
|
|
|
if (Global.DisableSecondaryThrottling && samplesProvided > samplesNeeded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
while (samplesProvided > samplesNeeded)
|
|
{
|
|
Thread.Sleep((samplesProvided - samplesNeeded) / (SampleRate / 1000)); // Let the audio clock control sleep time
|
|
samplesNeeded = _outputDevice.CalculateSamplesNeeded();
|
|
if (_unjamSoundThrottle)
|
|
{
|
|
//may be garbage, but what can we do?
|
|
samplesProvided = samplesNeeded;
|
|
break;
|
|
}
|
|
}
|
|
_unjamSoundThrottle = false;
|
|
}
|
|
else
|
|
{
|
|
if (Global.DisableSecondaryThrottling) // This indicates rewind or fast-forward
|
|
{
|
|
_outputProvider.OnVolatility();
|
|
}
|
|
_outputProvider.GetSamples(samplesNeeded, out samples, out samplesProvided);
|
|
}
|
|
}
|
|
else if (_bufferedProvider == _bufferedAsync)
|
|
{
|
|
samples = new short[samplesNeeded * ChannelCount];
|
|
|
|
_bufferedAsync.GetSamplesAsync(samples);
|
|
|
|
samplesProvided = samplesNeeded;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
_outputDevice.WriteSamples(samples, samplesProvided);
|
|
}
|
|
|
|
public static int MillisecondsToSamples(int milliseconds)
|
|
{
|
|
return milliseconds * SampleRate / 1000;
|
|
}
|
|
|
|
public static double SamplesToMilliseconds(int samples)
|
|
{
|
|
return samples * 1000.0 / SampleRate;
|
|
}
|
|
}
|
|
}
|