add method to play wav files via ISoundOutput, use this instead of System.Media.SoundPlayer
This commit is contained in:
parent
94f6616e74
commit
a38e854a6d
|
@ -15,6 +15,7 @@
|
|||
<PackageReference Include="Vortice.MediaFoundation" Version="2.4.2" />
|
||||
<PackageReference Include="Vortice.XAudio2" Version="2.4.2" />
|
||||
<PackageReference Include="SharpDX.DirectSound" Version="4.2.0" />
|
||||
<PackageReference Include="ppy.SDL2-CS" Version="1.0.630-alpha" ExcludeAssets="native;contentFiles" />
|
||||
<ProjectReference Include="$(ProjectDir)../BizHawk.Client.Common/BizHawk.Client.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -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<byte>.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<byte>.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Disconnect>(_device, out var ext) ? ext : null;
|
||||
_disconnectExt = _alc.TryGetExtension<Disconnect>(_device, out var disconnectExt) ? disconnectExt : null;
|
||||
}
|
||||
|
||||
_floatExt = _al.TryGetExtension<FloatFormat>(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<Disconnect>(_device, out var ext) ? ext : null;
|
||||
_floatExt = _al.TryGetExtension<FloatFormat>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<byte> buffer)
|
||||
{
|
||||
if (_wav == IntPtr.Zero)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(SDL2WavStream));
|
||||
}
|
||||
|
||||
var count = (int)Math.Min(buffer.Length, _len - _pos);
|
||||
new ReadOnlySpan<byte>((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<byte> buffer)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
}
|
|
@ -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<BufferPoolItem> _availableItems = new();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,5 +76,9 @@ namespace BizHawk.Client.Common
|
|||
if (sampleCount == 0) return;
|
||||
_remainingSamples += sampleCount;
|
||||
}
|
||||
|
||||
public void PlayWavFile(string path, double volume)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<string> _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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -214,10 +214,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
InputManager inputManager,
|
||||
ToolManager tools,
|
||||
Func<Config> getConfig,
|
||||
Action<string> 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;
|
||||
|
|
|
@ -47,6 +47,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
InputManager inputManager,
|
||||
ToolManager tools,
|
||||
Func<Config> getConfig,
|
||||
Action<string> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue