From a38e854a6d8266be4a6c7932756593f8df5c71cc Mon Sep 17 00:00:00 2001
From: CasualPokePlayer <50538166+CasualPokePlayer@users.noreply.github.com>
Date: Sat, 30 Dec 2023 04:49:26 -0800
Subject: [PATCH] add method to play wav files via ISoundOutput, use this
instead of System.Media.SoundPlayer
---
.../BizHawk.Bizware.Audio.csproj | 1 +
.../DirectSoundSoundOutput.cs | 106 ++++++++++++++-
.../OpenALSoundOutput.cs | 76 +++++++++--
src/BizHawk.Bizware.Audio/SDL2WavStream.cs | 125 ++++++++++++++++++
.../XAudio2SoundOutput.cs | 41 +++++-
.../Sound/Interfaces/ISoundOutput.cs | 1 +
.../Sound/Output/DummySoundOutput.cs | 4 +
src/BizHawk.Client.EmuHawk/MainForm.cs | 21 ++-
.../RetroAchievements/RCheevos.Login.cs | 6 +-
.../RetroAchievements/RCheevos.Sound.cs | 30 ++---
.../RetroAchievements/RCheevos.cs | 17 ++-
.../RetroAchievements/RetroAchievements.cs | 3 +-
src/BizHawk.Client.EmuHawk/Sound/Sound.cs | 13 ++
13 files changed, 396 insertions(+), 48 deletions(-)
create mode 100644 src/BizHawk.Bizware.Audio/SDL2WavStream.cs
diff --git a/src/BizHawk.Bizware.Audio/BizHawk.Bizware.Audio.csproj b/src/BizHawk.Bizware.Audio/BizHawk.Bizware.Audio.csproj
index a0f03231d4..f8daea5b87 100644
--- a/src/BizHawk.Bizware.Audio/BizHawk.Bizware.Audio.csproj
+++ b/src/BizHawk.Bizware.Audio/BizHawk.Bizware.Audio.csproj
@@ -15,6 +15,7 @@
+
diff --git a/src/BizHawk.Bizware.Audio/DirectSoundSoundOutput.cs b/src/BizHawk.Bizware.Audio/DirectSoundSoundOutput.cs
index 768062b9d9..c67b55f2b8 100644
--- a/src/BizHawk.Bizware.Audio/DirectSoundSoundOutput.cs
+++ b/src/BizHawk.Bizware.Audio/DirectSoundSoundOutput.cs
@@ -1,10 +1,12 @@
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using BizHawk.Client.Common;
+using BizHawk.Common;
using SharpDX;
using SharpDX.DirectSound;
@@ -18,7 +20,7 @@ namespace BizHawk.Bizware.Audio
private readonly IntPtr _mainWindowHandle;
private bool _disposed;
private DirectSound _device;
- private SecondarySoundBuffer _deviceBuffer;
+ private SecondarySoundBuffer _deviceBuffer, _wavDeviceBuffer;
private int _actualWriteOffsetBytes = -1;
private int _filledBufferSizeBytes;
private long _lastWriteTime;
@@ -40,6 +42,7 @@ namespace BizHawk.Bizware.Audio
{
if (_disposed) return;
+ StopWav(throwOnInvalidDevice: false);
_device.Dispose();
_device = null;
@@ -63,6 +66,8 @@ namespace BizHawk.Bizware.Audio
_deviceBuffer.Dispose();
_deviceBuffer = null;
+ StopWav(throwOnInvalidDevice: false);
+
_device.Dispose();
_device = new();
_device.SetCooperativeLevel(_mainWindowHandle, CooperativeLevel.Priority);
@@ -273,5 +278,104 @@ namespace BizHawk.Bizware.Audio
StartPlaying();
}
}
+
+ private bool IsWavPlaying
+ {
+ get
+ {
+ if (_wavDeviceBuffer == null)
+ {
+ return false;
+ }
+
+ var status = (BufferStatus)_wavDeviceBuffer.Status;
+ return (status & BufferStatus.BufferLost) == 0 &&
+ (status & BufferStatus.Playing) == BufferStatus.Playing;
+ }
+ }
+
+ private void StopWav(bool throwOnInvalidDevice = true)
+ {
+ bool isPlaying;
+ try
+ {
+ isPlaying = IsWavPlaying;
+ }
+ catch (SharpDXException)
+ {
+ if (throwOnInvalidDevice)
+ {
+ throw;
+ }
+
+ isPlaying = false;
+ }
+
+ if (isPlaying)
+ {
+ try
+ {
+ _wavDeviceBuffer.Stop();
+ }
+ catch (SharpDXException)
+ {
+ }
+ }
+
+ _wavDeviceBuffer?.Dispose();
+ _wavDeviceBuffer = null;
+ }
+
+ public void PlayWavFile(string path, double volume)
+ {
+ using var wavStream = new SDL2WavStream(path);
+ var format = wavStream.Format == SDL2WavStream.AudioFormat.F32LSB
+ ? WaveFormat.CreateIeeeFloatWaveFormat(wavStream.Frequency, wavStream.Channels)
+ : new(wavStream.Frequency, wavStream.BitsPerSample, wavStream.Channels);
+
+ var desc = new SoundBufferDescription
+ {
+ Format = format,
+ Flags =
+ BufferFlags.GlobalFocus |
+ BufferFlags.Software |
+ BufferFlags.GetCurrentPosition2 |
+ BufferFlags.ControlVolume,
+ BufferBytes = unchecked((int)wavStream.Length)
+ };
+
+ StopWav();
+ _wavDeviceBuffer = new(_device, desc);
+ const int TEMP_BUFFER_LENGTH = 65536;
+ var tempBuffer = ArrayPool.Shared.Rent(TEMP_BUFFER_LENGTH);
+ try
+ {
+ var bufferOffset = 0;
+ while (true)
+ {
+ var numRead = wavStream.Read(tempBuffer, 0, TEMP_BUFFER_LENGTH);
+ if (numRead == 0)
+ {
+ break;
+ }
+
+ if (wavStream.Format == SDL2WavStream.AudioFormat.S16MSB)
+ {
+ EndiannessUtils.MutatingByteSwap16(tempBuffer.AsSpan()[..numRead]);
+ }
+
+ _wavDeviceBuffer.Write(tempBuffer, 0, numRead, bufferOffset, LockFlags.None);
+ bufferOffset += numRead;
+ }
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(tempBuffer);
+ }
+
+ const int range = Volume.Maximum - Volume.Minimum;
+ _wavDeviceBuffer.Volume = (int)(Math.Pow(volume, 0.1) * range) + Volume.Minimum;
+ _wavDeviceBuffer.Play(0, PlayFlags.None);
+ }
}
}
diff --git a/src/BizHawk.Bizware.Audio/OpenALSoundOutput.cs b/src/BizHawk.Bizware.Audio/OpenALSoundOutput.cs
index 81442268e3..0961a08fa4 100644
--- a/src/BizHawk.Bizware.Audio/OpenALSoundOutput.cs
+++ b/src/BizHawk.Bizware.Audio/OpenALSoundOutput.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using BizHawk.Client.Common;
+using BizHawk.Common;
using Silk.NET.Core.Native;
using Silk.NET.OpenAL;
@@ -26,12 +27,14 @@ namespace BizHawk.Bizware.Audio
private bool _disposed;
private readonly IHostAudioManager _sound;
private AudioContext _context;
- private uint _sourceID;
+ private uint _sourceID, _wavSourceID;
private BufferPool _bufferPool;
+ private uint _wavBufferID;
private int _currentSamplesQueued;
private short[] _tempSampleBuffer;
private unsafe Device* _device;
private Disconnect _disconnectExt;
+ private FloatFormat _floatExt;
public OpenALSoundOutput(IHostAudioManager sound, string chosenDeviceName)
{
@@ -44,14 +47,18 @@ namespace BizHawk.Bizware.Audio
unsafe
{
_device = _alc.GetContextsDevice(_alc.GetCurrentContext());
- _disconnectExt = _alc.TryGetExtension(_device, out var ext) ? ext : null;
+ _disconnectExt = _alc.TryGetExtension(_device, out var disconnectExt) ? disconnectExt : null;
}
+
+ _floatExt = _al.TryGetExtension(out var floatFormatExt) ? floatFormatExt : null;
}
public void Dispose()
{
if (_disposed) return;
+ StopWav();
+
_context.Dispose();
_context = null;
@@ -104,11 +111,13 @@ namespace BizHawk.Bizware.Audio
}
StopSound();
+ StopWav();
_context.Dispose();
_context = new(device: null, _sound.SampleRate);
_device = _alc.GetContextsDevice(_alc.GetCurrentContext());
_disconnectExt = _alc.TryGetExtension(_device, out var ext) ? ext : null;
+ _floatExt = _al.TryGetExtension(out var floatFormatExt) ? floatFormatExt : null;
StartSound();
}
@@ -161,6 +170,62 @@ namespace BizHawk.Bizware.Audio
}
}
+ private void StopWav()
+ {
+ if (_wavSourceID != 0)
+ {
+ _al.SourceStop(_wavSourceID);
+ _al.DeleteSource(_wavSourceID);
+ _wavSourceID = 0;
+ }
+
+ if (_wavBufferID != 0)
+ {
+ _al.DeleteBuffer(_wavBufferID);
+ _wavBufferID = 0;
+ }
+ }
+
+ public void PlayWavFile(string path, double volume)
+ {
+ using var wavStream = new SDL2WavStream(path);
+ if (wavStream.Channels > 2)
+ {
+ throw new NotSupportedException("OpenAL does not support more than 2 channels");
+ }
+
+ var format = wavStream.Format switch
+ {
+ SDL2WavStream.AudioFormat.U8 => wavStream.Channels == 1 ? BufferFormat.Mono8 : BufferFormat.Stereo8,
+ SDL2WavStream.AudioFormat.S16LSB or SDL2WavStream.AudioFormat.S16MSB => wavStream.Channels == 1 ? BufferFormat.Mono16 : BufferFormat.Stereo16,
+ SDL2WavStream.AudioFormat.S32LSB => throw new NotSupportedException("OpenAL does not support s32 samples"),
+ SDL2WavStream.AudioFormat.F32LSB when _floatExt == null => throw new NotSupportedException("This OpenAL implementation does not support f32 samples"),
+ SDL2WavStream.AudioFormat.F32LSB => (BufferFormat)(wavStream.Channels == 1 ? FloatBufferFormat.Mono : FloatBufferFormat.Stereo),
+ _ => throw new InvalidOperationException(),
+ };
+
+ StopWav();
+ _wavSourceID = _al.GenSource();
+ _wavBufferID = _al.GenBuffer();
+
+ var tempBuffer = new byte[wavStream.Length];
+ wavStream.Read(tempBuffer);
+
+ if (wavStream.Format == SDL2WavStream.AudioFormat.S16MSB)
+ {
+ EndiannessUtils.MutatingByteSwap16(tempBuffer);
+ }
+
+ _al.BufferData(_wavBufferID, format, tempBuffer, wavStream.Frequency);
+ _al.SetSourceProperty(_wavSourceID, SourceFloat.Gain, (float)volume);
+ unsafe
+ {
+ var bid = _wavBufferID;
+ _al.SourceQueueBuffers(_wavSourceID, 1, &bid);
+ }
+ _al.SourcePlay(_wavSourceID);
+ }
+
private unsafe void UnqueueProcessedBuffers()
{
var releaseCount = GetSource(GetSourceInteger.BuffersProcessed);
@@ -223,13 +288,8 @@ namespace BizHawk.Bizware.Audio
public class BufferPoolItem
{
- public uint BufferID { get; }
+ public uint BufferID { get; } = _al.GenBuffer();
public int Length { get; set; }
-
- public BufferPoolItem()
- {
- BufferID = _al.GenBuffer();
- }
}
}
}
diff --git a/src/BizHawk.Bizware.Audio/SDL2WavStream.cs b/src/BizHawk.Bizware.Audio/SDL2WavStream.cs
new file mode 100644
index 0000000000..ea169e4af6
--- /dev/null
+++ b/src/BizHawk.Bizware.Audio/SDL2WavStream.cs
@@ -0,0 +1,125 @@
+using System;
+using System.IO;
+
+using BizHawk.Common;
+
+using static SDL2.SDL;
+
+namespace BizHawk.Bizware.Audio
+{
+ internal sealed class SDL2WavStream : Stream, ISpanStream
+ {
+ private IntPtr _wav;
+ private readonly uint _len;
+ private uint _pos;
+
+ // These are the only formats SDL2's wav loadder will output
+ public enum AudioFormat : ushort
+ {
+ U8 = 0x0008,
+ S16LSB = 0x8010,
+ S32LSB = 0x8020,
+ F32LSB = 0x8120,
+ S16MSB = 0x9010,
+ }
+
+ public int Frequency { get; }
+ public AudioFormat Format { get; }
+ public byte Channels { get; }
+
+ public int BitsPerSample => Format switch
+ {
+ AudioFormat.U8 => 8,
+ AudioFormat.S16LSB or AudioFormat.S16MSB => 16,
+ AudioFormat.S32LSB or AudioFormat.F32LSB => 32,
+ _ => throw new InvalidOperationException(),
+ };
+
+ public SDL2WavStream(string path)
+ {
+ // TODO: Perhaps this should just take a Stream?
+ // need to update SDL2-CS since the version we're on doesn't expose SDL_LoadWAV_RW :(
+ if (SDL_LoadWAV(path, out var spec, out var wav, out var len) == IntPtr.Zero)
+ {
+ throw new($"Could not load WAV file! SDL error: {SDL_GetError()}");
+ }
+
+ Frequency = spec.freq;
+ Format = (AudioFormat)spec.format;
+ Channels = spec.channels;
+
+ _wav = wav;
+ _len = len;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ SDL_FreeWAV(_wav);
+ _wav = IntPtr.Zero;
+
+ base.Dispose(disposing);
+ }
+
+ public override bool CanRead => true;
+ public override bool CanSeek => true;
+ public override bool CanWrite => false;
+ public override long Length => _len;
+
+ public override long Position
+ {
+ get => _pos;
+ set
+ {
+ if (value < 0 || value > _len)
+ {
+ throw new ArgumentOutOfRangeException(paramName: nameof(value), value, message: "index out of range");
+ }
+
+ _pos = (uint)value;
+ }
+ }
+
+ public override void Flush()
+ {
+ }
+
+ public unsafe int Read(Span buffer)
+ {
+ if (_wav == IntPtr.Zero)
+ {
+ throw new ObjectDisposedException(nameof(SDL2WavStream));
+ }
+
+ var count = (int)Math.Min(buffer.Length, _len - _pos);
+ new ReadOnlySpan((void*)((nint)_wav + _pos), count).CopyTo(buffer);
+ _pos += (uint)count;
+ return count;
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ => Read(new(buffer, offset, count));
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ var newpos = origin switch
+ {
+ SeekOrigin.Begin => offset,
+ SeekOrigin.Current => _pos + offset,
+ SeekOrigin.End => _len + offset,
+ _ => offset
+ };
+
+ Position = newpos;
+ return newpos;
+ }
+
+ public override void SetLength(long value)
+ => throw new NotSupportedException();
+
+ public void Write(ReadOnlySpan buffer)
+ => throw new NotSupportedException();
+
+ public override void Write(byte[] buffer, int offset, int count)
+ => throw new NotSupportedException();
+ }
+}
diff --git a/src/BizHawk.Bizware.Audio/XAudio2SoundOutput.cs b/src/BizHawk.Bizware.Audio/XAudio2SoundOutput.cs
index 2ea5b16ce4..cf6888e56c 100644
--- a/src/BizHawk.Bizware.Audio/XAudio2SoundOutput.cs
+++ b/src/BizHawk.Bizware.Audio/XAudio2SoundOutput.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using BizHawk.Client.Common;
+using BizHawk.Common;
using Vortice.MediaFoundation;
using Vortice.Multimedia;
@@ -17,8 +18,9 @@ namespace BizHawk.Bizware.Audio
private volatile bool _deviceResetRequired;
private IXAudio2 _device;
private IXAudio2MasteringVoice _masteringVoice;
- private IXAudio2SourceVoice _sourceVoice;
+ private IXAudio2SourceVoice _sourceVoice, _wavVoice;
private BufferPool _bufferPool;
+ private AudioBuffer _wavBuffer;
private long _runningSamplesQueued;
private static string GetDeviceId(string deviceName)
@@ -49,7 +51,7 @@ namespace BizHawk.Bizware.Audio
// note that this won't be called on the main thread, so we'll defer the reset to the main thread
_device.CriticalError += (_, _) => _deviceResetRequired = true;
_masteringVoice = _device.CreateMasteringVoice(
- inputChannels: _sound.ChannelCount,
+ inputChannels: _sound.ChannelCount,
inputSampleRate: _sound.SampleRate,
deviceId: GetDeviceId(chosenDeviceName));
}
@@ -58,6 +60,7 @@ namespace BizHawk.Bizware.Audio
{
if (_disposed) return;
+ StopWav();
_masteringVoice.Dispose();
_device.Dispose();
@@ -113,6 +116,7 @@ namespace BizHawk.Bizware.Audio
_deviceResetRequired = false;
StopSound();
+ StopWav();
_masteringVoice.Dispose();
_device.Dispose();
@@ -138,6 +142,7 @@ namespace BizHawk.Bizware.Audio
{
_sound.HandleInitializationOrUnderrun(detectedUnderrun, ref samplesNeeded);
}
+
return samplesNeeded;
}
@@ -154,6 +159,38 @@ namespace BizHawk.Bizware.Audio
_runningSamplesQueued += sampleCount;
}
+ private void StopWav()
+ {
+ _wavVoice?.Stop();
+ _wavVoice?.Dispose();
+ _wavVoice = null;
+
+ _wavBuffer?.Dispose();
+ _wavBuffer = null;
+ }
+
+ public void PlayWavFile(string path, double volume)
+ {
+ using var wavStream = new SDL2WavStream(path);
+ var format = wavStream.Format == SDL2WavStream.AudioFormat.F32LSB
+ ? WaveFormat.CreateIeeeFloatWaveFormat(wavStream.Frequency, wavStream.Channels)
+ : new(wavStream.Frequency, wavStream.BitsPerSample, wavStream.Channels);
+
+ StopWav();
+ _wavVoice = _device.CreateSourceVoice(format);
+ _wavBuffer = new(unchecked((int)wavStream.Length));
+
+ wavStream.Read(_wavBuffer.AsSpan());
+ if (wavStream.Format == SDL2WavStream.AudioFormat.S16MSB)
+ {
+ EndiannessUtils.MutatingByteSwap16(_wavBuffer.AsSpan());
+ }
+
+ _wavVoice.SubmitSourceBuffer(_wavBuffer);
+ _wavVoice.Volume = (float)volume;
+ _wavVoice.Start();
+ }
+
private class BufferPool : IDisposable
{
private readonly List _availableItems = new();
diff --git a/src/BizHawk.Client.Common/Sound/Interfaces/ISoundOutput.cs b/src/BizHawk.Client.Common/Sound/Interfaces/ISoundOutput.cs
index 2b2ab9bbe2..8e2b2ecbdb 100644
--- a/src/BizHawk.Client.Common/Sound/Interfaces/ISoundOutput.cs
+++ b/src/BizHawk.Client.Common/Sound/Interfaces/ISoundOutput.cs
@@ -10,5 +10,6 @@ namespace BizHawk.Client.Common
int MaxSamplesDeficit { get; }
int CalculateSamplesNeeded();
void WriteSamples(short[] samples, int sampleOffset, int sampleCount);
+ void PlayWavFile(string path, double volume);
}
}
diff --git a/src/BizHawk.Client.Common/Sound/Output/DummySoundOutput.cs b/src/BizHawk.Client.Common/Sound/Output/DummySoundOutput.cs
index 31d5839e55..9de7e51059 100644
--- a/src/BizHawk.Client.Common/Sound/Output/DummySoundOutput.cs
+++ b/src/BizHawk.Client.Common/Sound/Output/DummySoundOutput.cs
@@ -76,5 +76,9 @@ namespace BizHawk.Client.Common
if (sampleCount == 0) return;
_remainingSamples += sampleCount;
}
+
+ public void PlayWavFile(string path, double volume)
+ {
+ }
}
}
diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs
index ac8049d399..872a4556cb 100644
--- a/src/BizHawk.Client.EmuHawk/MainForm.cs
+++ b/src/BizHawk.Client.EmuHawk/MainForm.cs
@@ -4895,13 +4895,20 @@ namespace BizHawk.Client.EmuHawk
private void OpenRetroAchievements()
{
- RA = RetroAchievements.CreateImpl(this, InputManager, Tools, () => Config, RetroAchievementsMenuItem.DropDownItems, () =>
- {
- RA.Dispose();
- RA = null;
- RetroAchievementsMenuItem.DropDownItems.Clear();
- RetroAchievementsMenuItem.DropDownItems.Add(StartRetroAchievementsMenuItem);
- });
+ RA = RetroAchievements.CreateImpl(
+ this,
+ InputManager,
+ Tools,
+ () => Config,
+ path => Sound.PlayWavFile(path, 1), // TODO: Make this configurable
+ RetroAchievementsMenuItem.DropDownItems,
+ () =>
+ {
+ RA.Dispose();
+ RA = null;
+ RetroAchievementsMenuItem.DropDownItems.Clear();
+ RetroAchievementsMenuItem.DropDownItems.Add(StartRetroAchievementsMenuItem);
+ });
RA?.Restart();
}
diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Login.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Login.cs
index 563a9e6c03..c54a4eefd0 100644
--- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Login.cs
+++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Login.cs
@@ -68,7 +68,7 @@ namespace BizHawk.Client.EmuHawk
{
config.RAUsername = Username;
config.RAToken = ApiToken;
- if (EnableSoundEffects) _loginSound.PlayNoExceptions();
+ PlaySound(_loginSound);
return;
}
}
@@ -79,9 +79,9 @@ namespace BizHawk.Client.EmuHawk
config.RAUsername = Username;
config.RAToken = ApiToken;
- if (LoggedIn && EnableSoundEffects)
+ if (LoggedIn)
{
- _loginSound.PlayNoExceptions();
+ PlaySound(_loginSound);
}
}
diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Sound.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Sound.cs
index 6c1a577d6f..0ff6655cbe 100644
--- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Sound.cs
+++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Sound.cs
@@ -1,5 +1,5 @@
+using System;
using System.IO;
-using System.Media;
using BizHawk.Common.PathExtensions;
@@ -7,29 +7,21 @@ namespace BizHawk.Client.EmuHawk
{
public partial class RCheevos
{
- // NOTE: these are net framework only...
- // this logic should probably be the main sound class
- // this shouldn't be a blocker to moving to net core anyways
- private static readonly SoundPlayer _loginSound = new(Path.Combine(PathUtils.ExeDirectoryPath, "overlay/login.wav"));
- private static readonly SoundPlayer _unlockSound = new(Path.Combine(PathUtils.ExeDirectoryPath, "overlay/unlock.wav"));
- private static readonly SoundPlayer _lboardStartSound = new(Path.Combine(PathUtils.ExeDirectoryPath, "overlay/lb.wav"));
- private static readonly SoundPlayer _lboardFailedSound = new(Path.Combine(PathUtils.ExeDirectoryPath, "overlay/lbcancel.wav"));
- private static readonly SoundPlayer _infoSound = new(Path.Combine(PathUtils.ExeDirectoryPath, "overlay/info.wav"));
+ private readonly Action _playWavFileCallback;
+
+ private static readonly string _loginSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/login.wav");
+ private static readonly string _unlockSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/unlock.wav");
+ private static readonly string _lboardStartSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/lb.wav");
+ private static readonly string _lboardFailedSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/lbcancel.wav");
+ private static readonly string _infoSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/info.wav");
private bool EnableSoundEffects { get; set; }
- }
- public static class SoundPlayerExtensions
- {
- public static void PlayNoExceptions(this SoundPlayer sound)
+ private void PlaySound(string path)
{
- try
+ if (EnableSoundEffects)
{
- sound.Play();
- }
- catch
- {
- // ignored
+ _playWavFileCallback(path);
}
}
}
diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.cs
index 433797f29a..1132117e07 100644
--- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.cs
+++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.cs
@@ -214,10 +214,13 @@ namespace BizHawk.Client.EmuHawk
InputManager inputManager,
ToolManager tools,
Func getConfig,
+ Action playWavFile,
ToolStripItemCollection raDropDownItems,
Action shutdownRACallback)
: base(mainForm, inputManager, tools, getConfig, raDropDownItems, shutdownRACallback)
{
+ _playWavFileCallback = playWavFile;
+
_isActive = true;
_httpThread = new(HttpRequestThreadProc) { IsBackground = true, Priority = ThreadPriority.BelowNormal, Name = "RCheevos HTTP Thread" };
_httpThread.Start();
@@ -227,7 +230,6 @@ namespace BizHawk.Client.EmuHawk
{
throw new("rc_runtime_alloc returned NULL!");
}
- Login();
_eventcb = EventHandlerCallback;
_peekcb = PeekCallback;
@@ -241,6 +243,7 @@ namespace BizHawk.Client.EmuHawk
EnableSoundEffects = config.RASoundEffects;
AllowUnofficialCheevos = config.RAAllowUnofficialCheevos;
+ Login();
BuildMenu(raDropDownItems);
}
@@ -488,7 +491,7 @@ namespace BizHawk.Client.EmuHawk
var prefix = HardcoreMode ? "[HARDCORE] " : "";
_dialogParent.AddOnScreenMessage($"{prefix}Achievement Unlocked!");
_dialogParent.AddOnScreenMessage(cheevo.Description);
- if (EnableSoundEffects) _unlockSound.PlayNoExceptions();
+ PlaySound(_unlockSound);
if (cheevo.IsOfficial)
{
@@ -509,7 +512,7 @@ namespace BizHawk.Client.EmuHawk
var prefix = HardcoreMode ? "[HARDCORE] " : "";
_dialogParent.AddOnScreenMessage($"{prefix}Achievement Primed!");
_dialogParent.AddOnScreenMessage(cheevo.Description);
- if (EnableSoundEffects) _infoSound.PlayNoExceptions();
+ PlaySound(_infoSound);
}
break;
@@ -528,7 +531,7 @@ namespace BizHawk.Client.EmuHawk
CurrentLboard = lboard;
_dialogParent.AddOnScreenMessage($"Leaderboard Attempt Started!");
_dialogParent.AddOnScreenMessage(lboard.Description);
- if (EnableSoundEffects) _lboardStartSound.PlayNoExceptions();
+ PlaySound(_lboardStartSound);
}
}
@@ -550,7 +553,7 @@ namespace BizHawk.Client.EmuHawk
_dialogParent.AddOnScreenMessage($"Leaderboard Attempt Failed! ({lboard.Score})");
_dialogParent.AddOnScreenMessage(lboard.Description);
- if (EnableSoundEffects) _lboardFailedSound.PlayNoExceptions();
+ PlaySound(_lboardFailedSound);
}
lboard.SetScore(0);
@@ -588,7 +591,7 @@ namespace BizHawk.Client.EmuHawk
_dialogParent.AddOnScreenMessage($"Leaderboard Attempt Complete! ({lboard.Score})");
_dialogParent.AddOnScreenMessage(lboard.Description);
- if (EnableSoundEffects) _unlockSound.PlayNoExceptions();
+ PlaySound(_unlockSound);
}
}
@@ -615,7 +618,7 @@ namespace BizHawk.Client.EmuHawk
var prefix = HardcoreMode ? "[HARDCORE] " : "";
_dialogParent.AddOnScreenMessage($"{prefix}Achievement Unprimed!");
_dialogParent.AddOnScreenMessage(cheevo.Description);
- if (EnableSoundEffects) _infoSound.PlayNoExceptions();
+ PlaySound(_infoSound);
}
break;
diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.cs
index 0fd65c939d..bc23153026 100644
--- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.cs
+++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.cs
@@ -47,6 +47,7 @@ namespace BizHawk.Client.EmuHawk
InputManager inputManager,
ToolManager tools,
Func getConfig,
+ Action playWavFile,
ToolStripItemCollection raDropDownItems,
Action shutdownRACallback)
{
@@ -72,7 +73,7 @@ namespace BizHawk.Client.EmuHawk
}
else
{
- return new RCheevos(mainForm, inputManager, tools, getConfig, raDropDownItems, shutdownRACallback);
+ return new RCheevos(mainForm, inputManager, tools, getConfig, playWavFile, raDropDownItems, shutdownRACallback);
}
}
diff --git a/src/BizHawk.Client.EmuHawk/Sound/Sound.cs b/src/BizHawk.Client.EmuHawk/Sound/Sound.cs
index 782d8f2b92..702190da21 100644
--- a/src/BizHawk.Client.EmuHawk/Sound/Sound.cs
+++ b/src/BizHawk.Client.EmuHawk/Sound/Sound.cs
@@ -235,5 +235,18 @@ namespace BizHawk.Client.EmuHawk
_outputDevice.WriteSamples(samples, sampleOffset, sampleCount);
}
+
+ public void PlayWavFile(string path, float atten)
+ {
+ if (atten <= 0) return;
+ try
+ {
+ _outputDevice.PlayWavFile(path, Math.Min(atten, 1));
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ }
+ }
}
}