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

215 lines
6.1 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.Emulation.Common.IEmulatorExtensions;
2015-01-31 17:44:35 +00:00
using BizHawk.Client.Common;
Add preliminary Unix compatibility (using Mono; resolves #1384) (#1380) * Move PlatformSpecificLinkedLibs and implementations to common and rename * Specify file ext. at LoadPlatformSpecific call site * Move Client.Common.Global.RunningOnUnix to PlatformLinkedLibSingleton * Inline var Resolver * Use PlatformLinkedLibManager internally * Move plugin load check to LinkedLibManager, use LinkedLibManager * Interpolate * Return exit code from dlclose/FreeLibrary * Skip all calls to externs in BlipBufDll when using mono * Use PlatformLinkedLibManager in SevenZipLibraryManager * Add expected return value to workaround (from testing on Win32) * Remove ".dll" from DllImport attr, remove temporary workaround, see desc. The library can be built by changing the output file name in `.../blip_buf/Makefile` to `libblip_buf.so`, and running `make`. It will be loaded if placed in the `.../output` folder. * Remove unused code, add TODO (this class is req. for Waterbox.PeWrapper) The TODO is to [rewrite with C#](https://docs.microsoft.com/en-us/dotnet/standard/io/memory-mapped-files) instead of importing from `kernel32.dll`. * Update OpenTK again but better (for #1384) * Add Mono run script * Add libblip_buf.so (temporary) Temporary because it should be a separate package which BizHawk depends on. * Add distro detection, add "already running" and "unknown distro" messages * Gray-out Lua Console on Unix * Extract superclass from EmuLuaLibrary, add shell implementation for Unix * Specify libdl version, Fedora doesn't have the versionless symlink * Remove empty `ToolStripMenuItem`, null `Text` caused crash on Unix * Transform OpenTK keyboard input into a `List<KeyEvent>` and read that Also fixes crash on rebind * Remove debug `using ...;`
2019-01-03 22:50:55 +00:00
using BizHawk.Common;
2015-01-31 17:44:35 +00:00
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;
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 (OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows)
{
2019-05-18 05:51:37 +00:00
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.OpenAL)
_outputDevice = new OpenALSoundOutput(this);
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.DirectSound)
_outputDevice = new DirectSoundSoundOutput(this, mainWindowHandle);
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.XAudio2)
_outputDevice = new XAudio2SoundOutput(this);
}
2019-05-18 05:51:37 +00:00
else _outputDevice = new OpenALSoundOutput(this); // at the moment unix/mono can only support OpenAL (so ignore whatever is set in the config)
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
{
_bufferedAsync.RecalculateMagic(Global.Emulator.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 / (double)Global.Emulator.VsyncRate());
int silenceSamples = Math.Max(samplesNeeded - samplesPerFrame, 0);
_outputDevice.WriteSamples(new short[silenceSamples * 2], silenceSamples);
samplesNeeded -= silenceSamples;
_unjamSoundThrottle = isUnderrun;
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();
if (_unjamSoundThrottle)
{
//may be garbage, but what can we do?
samplesProvided = samplesNeeded;
break;
}
2015-01-31 17:44:35 +00:00
}
_unjamSoundThrottle = false;
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;
}
}
}