339 lines
7.9 KiB
C#
339 lines
7.9 KiB
C#
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<string> DeviceNames()
|
|
{
|
|
var ret = new List<string>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Range: 0-100
|
|
/// </summary>
|
|
/// <param name="vol"></param>
|
|
public void ChangeVolume(int vol)
|
|
{
|
|
if (vol > 100)
|
|
vol = 100;
|
|
if (vol < 0)
|
|
vol = 0;
|
|
Global.Config.SoundVolume = vol;
|
|
UpdateSoundSettings();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uses Global.Config.SoundEnabled, this just notifies the object to read it
|
|
/// </summary>
|
|
public void UpdateSoundSettings()
|
|
{
|
|
if (!Global.Config.SoundEnabled || Global.Config.SoundVolume == 0)
|
|
DSoundBuffer.Volume = -5000;
|
|
else
|
|
DSoundBuffer.Volume = 0 - ((100 - Global.Config.SoundVolume) * 15);
|
|
}
|
|
}
|
|
#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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Range: 0-100
|
|
/// </summary>
|
|
/// <param name="vol"></param>
|
|
public void ChangeVolume(int vol)
|
|
{
|
|
Global.Config.SoundVolume = vol;
|
|
UpdateSoundSettings();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uses Global.Config.SoundEnabled, this just notifies the object to read it
|
|
/// </summary>
|
|
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
|
|
}
|