make SDL2WavStream consume a generic Stream rather than passing in a file path
this now should make it functionally the same as a System.Media.SoundPlayer, letting us only doing disk access one time per wav file (and allows some embbed wav to be played)
This commit is contained in:
parent
a38e854a6d
commit
51925e7cc9
|
@ -2,6 +2,7 @@
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
|
@ -326,9 +327,9 @@ namespace BizHawk.Bizware.Audio
|
||||||
_wavDeviceBuffer = null;
|
_wavDeviceBuffer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PlayWavFile(string path, double volume)
|
public void PlayWavFile(Stream wavFile, double volume)
|
||||||
{
|
{
|
||||||
using var wavStream = new SDL2WavStream(path);
|
using var wavStream = new SDL2WavStream(wavFile);
|
||||||
var format = wavStream.Format == SDL2WavStream.AudioFormat.F32LSB
|
var format = wavStream.Format == SDL2WavStream.AudioFormat.F32LSB
|
||||||
? WaveFormat.CreateIeeeFloatWaveFormat(wavStream.Frequency, wavStream.Channels)
|
? WaveFormat.CreateIeeeFloatWaveFormat(wavStream.Frequency, wavStream.Channels)
|
||||||
: new(wavStream.Frequency, wavStream.BitsPerSample, wavStream.Channels);
|
: new(wavStream.Frequency, wavStream.BitsPerSample, wavStream.Channels);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
using BizHawk.Client.Common;
|
using BizHawk.Client.Common;
|
||||||
|
@ -186,9 +187,9 @@ namespace BizHawk.Bizware.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PlayWavFile(string path, double volume)
|
public void PlayWavFile(Stream wavFile, double volume)
|
||||||
{
|
{
|
||||||
using var wavStream = new SDL2WavStream(path);
|
using var wavStream = new SDL2WavStream(wavFile);
|
||||||
if (wavStream.Channels > 2)
|
if (wavStream.Channels > 2)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("OpenAL does not support more than 2 channels");
|
throw new NotSupportedException("OpenAL does not support more than 2 channels");
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using BizHawk.Common;
|
using BizHawk.Common;
|
||||||
|
|
||||||
|
@ -16,11 +18,11 @@ namespace BizHawk.Bizware.Audio
|
||||||
// These are the only formats SDL2's wav loadder will output
|
// These are the only formats SDL2's wav loadder will output
|
||||||
public enum AudioFormat : ushort
|
public enum AudioFormat : ushort
|
||||||
{
|
{
|
||||||
U8 = 0x0008,
|
U8 = AUDIO_U8,
|
||||||
S16LSB = 0x8010,
|
S16LSB = AUDIO_S16LSB,
|
||||||
S32LSB = 0x8020,
|
S32LSB = AUDIO_S32LSB,
|
||||||
F32LSB = 0x8120,
|
F32LSB = AUDIO_F32LSB,
|
||||||
S16MSB = 0x9010,
|
S16MSB = AUDIO_S16MSB,
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Frequency { get; }
|
public int Frequency { get; }
|
||||||
|
@ -34,12 +36,19 @@ namespace BizHawk.Bizware.Audio
|
||||||
AudioFormat.S32LSB or AudioFormat.F32LSB => 32,
|
AudioFormat.S32LSB or AudioFormat.F32LSB => 32,
|
||||||
_ => throw new InvalidOperationException(),
|
_ => throw new InvalidOperationException(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[DllImport("SDL2", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
private static extern IntPtr SDL_LoadWAV_RW(
|
||||||
|
IntPtr src,
|
||||||
|
int freesrc,
|
||||||
|
out SDL_AudioSpec spec,
|
||||||
|
out IntPtr audio_buf,
|
||||||
|
out uint audio_len);
|
||||||
|
|
||||||
public SDL2WavStream(string path)
|
public SDL2WavStream(Stream wavFile)
|
||||||
{
|
{
|
||||||
// TODO: Perhaps this should just take a Stream?
|
using var rwOpWrapper = new SDLRwOpsStreamWrapper(wavFile);
|
||||||
// need to update SDL2-CS since the version we're on doesn't expose SDL_LoadWAV_RW :(
|
if (SDL_LoadWAV_RW(rwOpWrapper.Rw, 0, out var spec, out var wav, out var len) == IntPtr.Zero)
|
||||||
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()}");
|
throw new($"Could not load WAV file! SDL error: {SDL_GetError()}");
|
||||||
}
|
}
|
||||||
|
@ -121,5 +130,94 @@ namespace BizHawk.Bizware.Audio
|
||||||
|
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
=> throw new NotSupportedException();
|
=> throw new NotSupportedException();
|
||||||
|
|
||||||
|
private unsafe class SDLRwOpsStreamWrapper : IDisposable
|
||||||
|
{
|
||||||
|
public IntPtr Rw { get; private set; }
|
||||||
|
private readonly Stream _s;
|
||||||
|
|
||||||
|
private readonly SDLRWopsSizeCallback _sizeCallback;
|
||||||
|
private readonly SDLRWopsSeekCallback _seekCallback;
|
||||||
|
private readonly SDLRWopsReadCallback _readCallback;
|
||||||
|
private readonly SDLRWopsWriteCallback _writeCallback;
|
||||||
|
private readonly SDLRWopsCloseCallback _closeCallback;
|
||||||
|
|
||||||
|
public SDLRwOpsStreamWrapper(Stream s)
|
||||||
|
{
|
||||||
|
Rw = SDL_AllocRW();
|
||||||
|
if (Rw == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
throw new($"Could not allocate SDL_RWops! SDL error: {SDL_GetError()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_s = s;
|
||||||
|
_sizeCallback = SizeCallback;
|
||||||
|
_seekCallback = SeekCallback;
|
||||||
|
_readCallback = ReadCallback;
|
||||||
|
_writeCallback = WriteCallback;
|
||||||
|
_closeCallback = CloseCallback;
|
||||||
|
|
||||||
|
var rw = (SDL_RWops*)Rw;
|
||||||
|
rw->size = Marshal.GetFunctionPointerForDelegate(_sizeCallback);
|
||||||
|
rw->seek = Marshal.GetFunctionPointerForDelegate(_seekCallback);
|
||||||
|
rw->read = Marshal.GetFunctionPointerForDelegate(_readCallback);
|
||||||
|
rw->write = Marshal.GetFunctionPointerForDelegate(_writeCallback);
|
||||||
|
rw->close = Marshal.GetFunctionPointerForDelegate(_closeCallback);
|
||||||
|
rw->type = SDL_RWOPS_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long SizeCallback(IntPtr ctx)
|
||||||
|
=> _s.Length;
|
||||||
|
|
||||||
|
private long SeekCallback(IntPtr ctx, long offset, int whence)
|
||||||
|
=> _s.Seek(offset, (SeekOrigin)whence);
|
||||||
|
|
||||||
|
private nint ReadCallback(IntPtr ctx, IntPtr ptr, nint size, nint num)
|
||||||
|
{
|
||||||
|
const int TEMP_BUFFER_LENGTH = 65536;
|
||||||
|
var tempBuffer = ArrayPool<byte>.Shared.Rent(TEMP_BUFFER_LENGTH);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var numBytes = (nuint)size * (nuint)num;
|
||||||
|
var remainingBytes = numBytes;
|
||||||
|
while (remainingBytes != 0)
|
||||||
|
{
|
||||||
|
var numRead = _s.Read(tempBuffer, 0, (int)Math.Min(remainingBytes, TEMP_BUFFER_LENGTH));
|
||||||
|
if (numRead == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Marshal.Copy(tempBuffer, 0, ptr, numRead);
|
||||||
|
ptr += numRead;
|
||||||
|
remainingBytes -= (uint)numRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (nint)((numBytes - remainingBytes) / (nuint)size);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(tempBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static nint WriteCallback(IntPtr ctx, IntPtr ptr, nint size, nint num)
|
||||||
|
=> 0;
|
||||||
|
|
||||||
|
private int CloseCallback(IntPtr ctx)
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Rw != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
SDL_FreeRW(Rw);
|
||||||
|
Rw = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
using BizHawk.Client.Common;
|
using BizHawk.Client.Common;
|
||||||
|
@ -169,9 +170,9 @@ namespace BizHawk.Bizware.Audio
|
||||||
_wavBuffer = null;
|
_wavBuffer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PlayWavFile(string path, double volume)
|
public void PlayWavFile(Stream wavFile, double volume)
|
||||||
{
|
{
|
||||||
using var wavStream = new SDL2WavStream(path);
|
using var wavStream = new SDL2WavStream(wavFile);
|
||||||
var format = wavStream.Format == SDL2WavStream.AudioFormat.F32LSB
|
var format = wavStream.Format == SDL2WavStream.AudioFormat.F32LSB
|
||||||
? WaveFormat.CreateIeeeFloatWaveFormat(wavStream.Frequency, wavStream.Channels)
|
? WaveFormat.CreateIeeeFloatWaveFormat(wavStream.Frequency, wavStream.Channels)
|
||||||
: new(wavStream.Frequency, wavStream.BitsPerSample, wavStream.Channels);
|
: new(wavStream.Frequency, wavStream.BitsPerSample, wavStream.Channels);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace BizHawk.Client.Common
|
namespace BizHawk.Client.Common
|
||||||
{
|
{
|
||||||
|
@ -10,6 +11,6 @@ namespace BizHawk.Client.Common
|
||||||
int MaxSamplesDeficit { get; }
|
int MaxSamplesDeficit { get; }
|
||||||
int CalculateSamplesNeeded();
|
int CalculateSamplesNeeded();
|
||||||
void WriteSamples(short[] samples, int sampleOffset, int sampleCount);
|
void WriteSamples(short[] samples, int sampleOffset, int sampleCount);
|
||||||
void PlayWavFile(string path, double volume);
|
void PlayWavFile(Stream wavFile, double volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace BizHawk.Client.Common
|
namespace BizHawk.Client.Common
|
||||||
{
|
{
|
||||||
|
@ -77,7 +78,7 @@ namespace BizHawk.Client.Common
|
||||||
_remainingSamples += sampleCount;
|
_remainingSamples += sampleCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PlayWavFile(string path, double volume)
|
public void PlayWavFile(Stream wavFile, double volume)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4900,7 +4900,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
InputManager,
|
InputManager,
|
||||||
Tools,
|
Tools,
|
||||||
() => Config,
|
() => Config,
|
||||||
path => Sound.PlayWavFile(path, 1), // TODO: Make this configurable
|
wavFile => Sound.PlayWavFile(wavFile, 1), // TODO: Make this configurable
|
||||||
RetroAchievementsMenuItem.DropDownItems,
|
RetroAchievementsMenuItem.DropDownItems,
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,21 +7,34 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
public partial class RCheevos
|
public partial class RCheevos
|
||||||
{
|
{
|
||||||
private readonly Action<string> _playWavFileCallback;
|
private static MemoryStream ReadWavFile(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new(File.ReadAllBytes(Path.Combine(PathUtils.ExeDirectoryPath, path)), false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly string _loginSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/login.wav");
|
private static readonly MemoryStream _loginSound = ReadWavFile("overlay/login.wav");
|
||||||
private static readonly string _unlockSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/unlock.wav");
|
private static readonly MemoryStream _unlockSound = ReadWavFile( "overlay/unlock.wav");
|
||||||
private static readonly string _lboardStartSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/lb.wav");
|
private static readonly MemoryStream _lboardStartSound = ReadWavFile("overlay/lb.wav");
|
||||||
private static readonly string _lboardFailedSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/lbcancel.wav");
|
private static readonly MemoryStream _lboardFailedSound = ReadWavFile("overlay/lbcancel.wav");
|
||||||
private static readonly string _infoSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/info.wav");
|
private static readonly MemoryStream _infoSound = ReadWavFile("overlay/info.wav");
|
||||||
|
|
||||||
|
private readonly Action<Stream> _playWavFileCallback;
|
||||||
|
|
||||||
private bool EnableSoundEffects { get; set; }
|
private bool EnableSoundEffects { get; set; }
|
||||||
|
|
||||||
private void PlaySound(string path)
|
private void PlaySound(Stream wavFile)
|
||||||
{
|
{
|
||||||
if (EnableSoundEffects)
|
if (EnableSoundEffects)
|
||||||
{
|
{
|
||||||
_playWavFileCallback(path);
|
wavFile.Position = 0;
|
||||||
|
_playWavFileCallback(wavFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
using BizHawk.Client.Common;
|
using BizHawk.Client.Common;
|
||||||
|
@ -47,7 +48,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
InputManager inputManager,
|
InputManager inputManager,
|
||||||
ToolManager tools,
|
ToolManager tools,
|
||||||
Func<Config> getConfig,
|
Func<Config> getConfig,
|
||||||
Action<string> playWavFile,
|
Action<Stream> playWavFile,
|
||||||
ToolStripItemCollection raDropDownItems,
|
ToolStripItemCollection raDropDownItems,
|
||||||
Action shutdownRACallback)
|
Action shutdownRACallback)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
using BizHawk.Bizware.Audio;
|
using BizHawk.Bizware.Audio;
|
||||||
|
@ -236,12 +237,12 @@ namespace BizHawk.Client.EmuHawk
|
||||||
_outputDevice.WriteSamples(samples, sampleOffset, sampleCount);
|
_outputDevice.WriteSamples(samples, sampleOffset, sampleCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PlayWavFile(string path, float atten)
|
public void PlayWavFile(Stream wavFile, float atten)
|
||||||
{
|
{
|
||||||
if (atten <= 0) return;
|
if (atten <= 0) return;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_outputDevice.PlayWavFile(path, Math.Min(atten, 1));
|
_outputDevice.PlayWavFile(wavFile, Math.Min(atten, 1));
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue