using System; using System.Collections.Generic; #if WINDOWS using SlimDX.DirectSound; using SlimDX.Multimedia; #endif using BizHawk.Emulation.Common; using BizHawk.Client.Common; namespace BizHawk.Client.EmuHawk { #if WINDOWS public static class SoundEnumeration { public static DirectSound Create() { var dc = DirectSound.GetDevices(); foreach (var dev in dc) { if (dev.Description == Global.Config.SoundDevice) return new DirectSound(dev.DriverGuid); } return new DirectSound(); } public static IEnumerable DeviceNames() { var ret = new List(); var dc = DirectSound.GetDevices(); foreach (var dev in dc) ret.Add(dev.Description); return ret; } } public class Sound : IDisposable { public bool needDiscard; private bool Muted; private readonly bool disposed; private SecondarySoundBuffer DSoundBuffer; private readonly byte[] SoundBuffer; private const int BufferSize = 4410 * 2 * 2; // 1/10th of a second, 2 bytes per sample, 2 channels; //private int SoundBufferPosition; //TODO: use this private readonly BufferedAsync semisync = new BufferedAsync(); private ISoundProvider asyncsoundProvider; private ISyncSoundProvider syncsoundProvider; public Sound(IntPtr handle, DirectSound device) { if (device != null) { device.SetCooperativeLevel(handle, CooperativeLevel.Priority); var format = new WaveFormat { SamplesPerSecond = 44100, BitsPerSample = 16, Channels = 2, FormatTag = WaveFormatTag.Pcm, BlockAlignment = 4 }; format.AverageBytesPerSecond = format.SamplesPerSecond * format.Channels * (format.BitsPerSample / 8); var desc = new SoundBufferDescription { Format = format, Flags = BufferFlags.GlobalFocus | BufferFlags.Software | BufferFlags.GetCurrentPosition2 | BufferFlags.ControlVolume, SizeInBytes = BufferSize }; DSoundBuffer = new SecondarySoundBuffer(device, desc); ChangeVolume(Global.Config.SoundVolume); } SoundBuffer = new byte[BufferSize]; disposed = false; } public void StartSound() { if (disposed) throw new ObjectDisposedException("Sound"); if (Global.Config.SoundEnabled == false) return; if (DSoundBuffer == null) return; if (IsPlaying) return; needDiscard = true; DSoundBuffer.Write(SoundBuffer, 0, LockFlags.EntireBuffer); DSoundBuffer.CurrentPlayPosition = 0; DSoundBuffer.Play(0, PlayFlags.Looping); } bool IsPlaying { get { if (DSoundBuffer == null) return false; if ((DSoundBuffer.Status & BufferStatus.Playing) != 0) return true; return false; } } public void StopSound() { if (!IsPlaying) return; for (int i = 0; i < SoundBuffer.Length; i++) SoundBuffer[i] = 0; DSoundBuffer.Write(SoundBuffer, 0, LockFlags.EntireBuffer); DSoundBuffer.Stop(); } public void Dispose() { if (disposed) return; if (DSoundBuffer != null && DSoundBuffer.Disposed == false) { DSoundBuffer.Dispose(); DSoundBuffer = null; } } public void SetSyncInputPin(ISyncSoundProvider source) { syncsoundProvider = source; asyncsoundProvider = null; semisync.DiscardSamples(); } public void SetAsyncInputPin(ISoundProvider source) { syncsoundProvider = null; asyncsoundProvider = source; semisync.BaseSoundProvider = source; semisync.RecalculateMagic(Global.CoreComm.VsyncRate); } static int circularDist(int from, int to, int size) { if (size == 0) return 0; int diff = (to - from); while (diff < 0) diff += size; return diff; } int soundoffset; int SNDDXGetAudioSpace() { if (DSoundBuffer == null) return 0; int playcursor = DSoundBuffer.CurrentPlayPosition; int writecursor = DSoundBuffer.CurrentWritePosition; int curToWrite = circularDist(soundoffset, writecursor, BufferSize); int curToPlay = circularDist(soundoffset, playcursor, BufferSize); if (curToWrite < curToPlay) return 0; // in-between the two cursors. we shouldn't write anything during this time. return curToPlay / 4; } public void UpdateSilence() { Muted = true; UpdateSound(); Muted = false; } public void UpdateSound() { if (Global.Config.SoundEnabled == false || disposed) { if (asyncsoundProvider != null) asyncsoundProvider.DiscardSamples(); if (syncsoundProvider != null) syncsoundProvider.DiscardSamples(); return; } int samplesNeeded = SNDDXGetAudioSpace() * 2; short[] samples; int samplesProvided; if (Muted) { if (samplesNeeded == 0) return; samples = new short[samplesNeeded]; samplesProvided = samplesNeeded; if (asyncsoundProvider != null) asyncsoundProvider.DiscardSamples(); if (syncsoundProvider != null) syncsoundProvider.DiscardSamples(); } else if (syncsoundProvider != null) { if (DSoundBuffer == null) return; // can cause SNDDXGetAudioSpace() = 0 int nsampgot; syncsoundProvider.GetSamples(out samples, out nsampgot); samplesProvided = 2 * nsampgot; if (!Global.ForceNoThrottle) while (samplesNeeded < samplesProvided) { System.Threading.Thread.Sleep((samplesProvided - samplesNeeded) / 88); // let audio clock control sleep time samplesNeeded = SNDDXGetAudioSpace() * 2; } } else if (asyncsoundProvider != null) { if (samplesNeeded == 0) return; samples = new short[samplesNeeded]; //if (asyncsoundProvider != null && Muted == false) //{ semisync.BaseSoundProvider = asyncsoundProvider; semisync.GetSamples(samples); //} //else asyncsoundProvider.DiscardSamples(); samplesProvided = samplesNeeded; } else return; int cursor = soundoffset; for (int i = 0; i < samplesProvided; i++) { short s = samples[i]; SoundBuffer[cursor++] = (byte)(s & 0xFF); SoundBuffer[cursor++] = (byte)(s >> 8); if (cursor >= SoundBuffer.Length) cursor = 0; } DSoundBuffer.Write(SoundBuffer, 0, LockFlags.EntireBuffer); soundoffset += samplesProvided * 2; soundoffset %= BufferSize; } /// /// Range: 0-100 /// /// public void ChangeVolume(int vol) { if (vol > 100) vol = 100; if (vol < 0) vol = 0; Global.Config.SoundVolume = vol; UpdateSoundSettings(); } /// /// Uses Global.Config.SoundEnabled, this just notifies the object to read it /// public void UpdateSoundSettings() { if (!Global.Config.SoundEnabled || Global.Config.SoundVolume == 0) DSoundBuffer.Volume = -5000; else DSoundBuffer.Volume = 0 - ((100 - Global.Config.SoundVolume) * 45); } } #else // Dummy implementation for non-Windows platforms for now. public class Sound { public bool Muted = false; public bool needDiscard; public Sound() { } public void StartSound() { } public bool IsPlaying = false; public void StopSound() { } public void Dispose() { } int SNDDXGetAudioSpace() { return 0; } public void UpdateSound(ISoundProvider soundProvider) { soundProvider.DiscardSamples(); } /// /// Range: 0-100 /// /// public void ChangeVolume(int vol) { Global.Config.SoundVolume = vol; UpdateSoundSettings(); } /// /// Uses Global.Config.SoundEnabled, this just notifies the object to read it /// public void UpdateSoundSettings() { if (Global.Emulator is NES) { NES n = Global.Emulator as NES; if (Global.Config.SoundEnabled == false) n.SoundOn = false; else n.SoundOn = true; } } } #endif }