aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Audio.Backends.SoundIo
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-08 01:22:00 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commitcee712105850ac3385cd0091a923438167433f9f (patch)
tree4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.Audio.Backends.SoundIo
parentcd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff)
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.Audio.Backends.SoundIo')
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs178
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs13
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs75
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs107
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs8
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs49
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs22
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs11
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs25
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs164
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dllbin0 -> 85504 bytes
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylibbin0 -> 54976 bytes
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.sobin0 -> 88584 bytes
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj28
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs16
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs247
-rw-r--r--src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs438
17 files changed, 1381 insertions, 0 deletions
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs
new file mode 100644
index 00000000..9c3e686d
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs
@@ -0,0 +1,178 @@
+using Ryujinx.Common.Memory;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Backends.SoundIo.Native
+{
+ public static partial class SoundIo
+ {
+ private const string LibraryName = "libsoundio";
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public unsafe delegate void OnDeviceChangeNativeDelegate(IntPtr ctx);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public unsafe delegate void OnBackendDisconnectedDelegate(IntPtr ctx, SoundIoError err);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public unsafe delegate void OnEventsSignalDelegate(IntPtr ctx);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public unsafe delegate void EmitRtPrioWarningDelegate();
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public unsafe delegate void JackCallbackDelegate(IntPtr msg);
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SoundIoStruct
+ {
+ public IntPtr UserData;
+ public IntPtr OnDeviceChange;
+ public IntPtr OnBackendDisconnected;
+ public IntPtr OnEventsSignal;
+ public SoundIoBackend CurrentBackend;
+ public IntPtr ApplicationName;
+ public IntPtr EmitRtPrioWarning;
+ public IntPtr JackInfoCallback;
+ public IntPtr JackErrorCallback;
+ }
+
+ public struct SoundIoChannelLayout
+ {
+ public IntPtr Name;
+ public int ChannelCount;
+ public Array24<SoundIoChannelId> Channels;
+
+ public static IntPtr GetDefault(int channelCount)
+ {
+ return soundio_channel_layout_get_default(channelCount);
+ }
+
+ public static unsafe SoundIoChannelLayout GetDefaultValue(int channelCount)
+ {
+ return Unsafe.AsRef<SoundIoChannelLayout>((SoundIoChannelLayout*)GetDefault(channelCount));
+ }
+ }
+
+ public struct SoundIoSampleRateRange
+ {
+ public int Min;
+ public int Max;
+ }
+
+ public struct SoundIoDevice
+ {
+ public IntPtr SoundIo;
+ public IntPtr Id;
+ public IntPtr Name;
+ public SoundIoDeviceAim Aim;
+ public IntPtr Layouts;
+ public int LayoutCount;
+ public SoundIoChannelLayout CurrentLayout;
+ public IntPtr Formats;
+ public int FormatCount;
+ public SoundIoFormat CurrentFormat;
+ public IntPtr SampleRates;
+ public int SampleRateCount;
+ public int SampleRateCurrent;
+ public double SoftwareLatencyMin;
+ public double SoftwareLatencyMax;
+ public double SoftwareLatencyCurrent;
+ public bool IsRaw;
+ public int RefCount;
+ public SoundIoError ProbeError;
+ }
+
+ public struct SoundIoOutStream
+ {
+ public IntPtr Device;
+ public SoundIoFormat Format;
+ public int SampleRate;
+ public SoundIoChannelLayout Layout;
+ public double SoftwareLatency;
+ public float Volume;
+ public IntPtr UserData;
+ public IntPtr WriteCallback;
+ public IntPtr UnderflowCallback;
+ public IntPtr ErrorCallback;
+ public IntPtr Name;
+ public bool NonTerminalHint;
+ public int BytesPerFrame;
+ public int BytesPerSample;
+ public SoundIoError LayoutError;
+ }
+
+ public struct SoundIoChannelArea
+ {
+ public IntPtr Pointer;
+ public int Step;
+ }
+
+ [LibraryImport(LibraryName)]
+ public static partial IntPtr soundio_create();
+
+ [LibraryImport(LibraryName)]
+ public static partial SoundIoError soundio_connect(IntPtr ctx);
+
+ [LibraryImport(LibraryName)]
+ public static partial void soundio_disconnect(IntPtr ctx);
+
+ [LibraryImport(LibraryName)]
+ public static partial void soundio_flush_events(IntPtr ctx);
+
+ [LibraryImport(LibraryName)]
+ public static partial int soundio_output_device_count(IntPtr ctx);
+
+ [LibraryImport(LibraryName)]
+ public static partial int soundio_default_output_device_index(IntPtr ctx);
+
+ [LibraryImport(LibraryName)]
+ public static partial IntPtr soundio_get_output_device(IntPtr ctx, int index);
+
+ [LibraryImport(LibraryName)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static partial bool soundio_device_supports_format(IntPtr devCtx, SoundIoFormat format);
+
+ [LibraryImport(LibraryName)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static partial bool soundio_device_supports_layout(IntPtr devCtx, IntPtr layout);
+
+ [LibraryImport(LibraryName)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static partial bool soundio_device_supports_sample_rate(IntPtr devCtx, int sampleRate);
+
+ [LibraryImport(LibraryName)]
+ public static partial IntPtr soundio_outstream_create(IntPtr devCtx);
+
+ [LibraryImport(LibraryName)]
+ public static partial SoundIoError soundio_outstream_open(IntPtr outStreamCtx);
+
+ [LibraryImport(LibraryName)]
+ public static partial SoundIoError soundio_outstream_start(IntPtr outStreamCtx);
+
+ [LibraryImport(LibraryName)]
+ public static partial SoundIoError soundio_outstream_begin_write(IntPtr outStreamCtx, IntPtr areas, IntPtr frameCount);
+
+ [LibraryImport(LibraryName)]
+ public static partial SoundIoError soundio_outstream_end_write(IntPtr outStreamCtx);
+
+ [LibraryImport(LibraryName)]
+ public static partial SoundIoError soundio_outstream_pause(IntPtr devCtx, [MarshalAs(UnmanagedType.Bool)] bool pause);
+
+ [LibraryImport(LibraryName)]
+ public static partial SoundIoError soundio_outstream_set_volume(IntPtr devCtx, double volume);
+
+ [LibraryImport(LibraryName)]
+ public static partial void soundio_outstream_destroy(IntPtr streamCtx);
+
+ [LibraryImport(LibraryName)]
+ public static partial void soundio_destroy(IntPtr ctx);
+
+ [LibraryImport(LibraryName)]
+ public static partial IntPtr soundio_channel_layout_get_default(int channelCount);
+
+ [LibraryImport(LibraryName)]
+ public static partial IntPtr soundio_strerror(SoundIoError err);
+ }
+}
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs
new file mode 100644
index 00000000..92f8ea37
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Audio.Backends.SoundIo.Native
+{
+ public enum SoundIoBackend : int
+ {
+ None = 0,
+ Jack = 1,
+ PulseAudio = 2,
+ Alsa = 3,
+ CoreAudio = 4,
+ Wasapi = 5,
+ Dummy = 6
+ }
+}
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs
new file mode 100644
index 00000000..70346e0b
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs
@@ -0,0 +1,75 @@
+namespace Ryujinx.Audio.Backends.SoundIo.Native
+{
+ public enum SoundIoChannelId
+ {
+ Invalid = 0,
+ FrontLeft = 1,
+ FrontRight = 2,
+ FrontCenter = 3,
+ Lfe = 4,
+ BackLeft = 5,
+ BackRight = 6,
+ FrontLeftCenter = 7,
+ FrontRightCenter = 8,
+ BackCenter = 9,
+ SideLeft = 10,
+ SideRight = 11,
+ TopCenter = 12,
+ TopFrontLeft = 13,
+ TopFrontCenter = 14,
+ TopFrontRight = 15,
+ TopBackLeft = 16,
+ TopBackCenter = 17,
+ TopBackRight = 18,
+ BackLeftCenter = 19,
+ BackRightCenter = 20,
+ FrontLeftWide = 21,
+ FrontRightWide = 22,
+ FrontLeftHigh = 23,
+ FrontCenterHigh = 24,
+ FrontRightHigh = 25,
+ TopFrontLeftCenter = 26,
+ TopFrontRightCenter = 27,
+ TopSideLeft = 28,
+ TopSideRight = 29,
+ LeftLfe = 30,
+ RightLfe = 31,
+ Lfe2 = 32,
+ BottomCenter = 33,
+ BottomLeftCenter = 34,
+ BottomRightCenter = 35,
+ MsMid = 36,
+ MsSide = 37,
+ AmbisonicW = 38,
+ AmbisonicX = 39,
+ AmbisonicY = 40,
+ AmbisonicZ = 41,
+ XyX = 42,
+ XyY = 43,
+ HeadphonesLeft = 44,
+ HeadphonesRight = 45,
+ ClickTrack = 46,
+ ForeignLanguage = 47,
+ HearingImpaired = 48,
+ Narration = 49,
+ Haptic = 50,
+ DialogCentricMix = 51,
+ Aux = 52,
+ Aux0 = 53,
+ Aux1 = 54,
+ Aux2 = 55,
+ Aux3 = 56,
+ Aux4 = 57,
+ Aux5 = 58,
+ Aux6 = 59,
+ Aux7 = 60,
+ Aux8 = 61,
+ Aux9 = 62,
+ Aux10 = 63,
+ Aux11 = 64,
+ Aux12 = 65,
+ Aux13 = 66,
+ Aux14 = 67,
+ Aux15 = 68,
+ }
+}
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs
new file mode 100644
index 00000000..3744c2e6
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Reflection.Metadata;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
+
+namespace Ryujinx.Audio.Backends.SoundIo.Native
+{
+ public class SoundIoContext : IDisposable
+ {
+ private IntPtr _context;
+ private Action<SoundIoError> _onBackendDisconnect;
+ private OnBackendDisconnectedDelegate _onBackendDisconnectNative;
+
+ public IntPtr Context => _context;
+
+ internal SoundIoContext(IntPtr context)
+ {
+ _context = context;
+ _onBackendDisconnect = null;
+ _onBackendDisconnectNative = null;
+ }
+
+ public SoundIoError Connect() => soundio_connect(_context);
+ public void Disconnect() => soundio_disconnect(_context);
+
+ public void FlushEvents() => soundio_flush_events(_context);
+
+ public int OutputDeviceCount => soundio_output_device_count(_context);
+
+ public int DefaultOutputDeviceIndex => soundio_default_output_device_index(_context);
+
+ public Action<SoundIoError> OnBackendDisconnect
+ {
+ get { return _onBackendDisconnect; }
+ set
+ {
+ _onBackendDisconnect = value;
+
+ if (_onBackendDisconnect == null)
+ {
+ _onBackendDisconnectNative = null;
+ }
+ else
+ {
+ _onBackendDisconnectNative = (ctx, err) => _onBackendDisconnect(err);
+ }
+
+ GetContext().OnBackendDisconnected = Marshal.GetFunctionPointerForDelegate(_onBackendDisconnectNative);
+ }
+ }
+
+ private ref SoundIoStruct GetContext()
+ {
+ unsafe
+ {
+ return ref Unsafe.AsRef<SoundIoStruct>((SoundIoStruct*)_context);
+ }
+ }
+
+ public SoundIoDeviceContext GetOutputDevice(int index)
+ {
+ IntPtr deviceContext = soundio_get_output_device(_context, index);
+
+ if (deviceContext == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ return new SoundIoDeviceContext(deviceContext);
+ }
+
+ public static SoundIoContext Create()
+ {
+ IntPtr context = soundio_create();
+
+ if (context == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ return new SoundIoContext(context);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ IntPtr currentContext = Interlocked.Exchange(ref _context, IntPtr.Zero);
+
+ if (currentContext != IntPtr.Zero)
+ {
+ soundio_destroy(currentContext);
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ~SoundIoContext()
+ {
+ Dispose(false);
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs
new file mode 100644
index 00000000..a0689d6d
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Audio.Backends.SoundIo.Native
+{
+ public enum SoundIoDeviceAim
+ {
+ SoundIoDeviceAimInput = 0,
+ SoundIoDeviceAimOutput = 1
+ }
+}
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs
new file mode 100644
index 00000000..42bcc6e3
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
+
+namespace Ryujinx.Audio.Backends.SoundIo.Native
+{
+ public class SoundIoDeviceContext
+ {
+ private readonly IntPtr _context;
+
+ public IntPtr Context => _context;
+
+ internal SoundIoDeviceContext(IntPtr context)
+ {
+ _context = context;
+ }
+
+ private ref SoundIoDevice GetDeviceContext()
+ {
+ unsafe
+ {
+ return ref Unsafe.AsRef<SoundIoDevice>((SoundIoDevice*)_context);
+ }
+ }
+
+ public bool IsRaw => GetDeviceContext().IsRaw;
+
+ public string Id => Marshal.PtrToStringAnsi(GetDeviceContext().Id);
+
+ public bool SupportsSampleRate(int sampleRate) => soundio_device_supports_sample_rate(_context, sampleRate);
+
+ public bool SupportsFormat(SoundIoFormat format) => soundio_device_supports_format(_context, format);
+
+ public bool SupportsChannelCount(int channelCount) => soundio_device_supports_layout(_context, SoundIoChannelLayout.GetDefault(channelCount));
+
+ public SoundIoOutStreamContext CreateOutStream()
+ {
+ IntPtr context = soundio_outstream_create(_context);
+
+ if (context == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ return new SoundIoOutStreamContext(context);
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs
new file mode 100644
index 00000000..9e33fa19
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Audio.Backends.SoundIo.Native
+{
+ public enum SoundIoError
+ {
+ None = 0,
+ NoMem = 1,
+ InitAudioBackend = 2,
+ SystemResources = 3,
+ OpeningDevice = 4,
+ NoSuchDevice = 5,
+ Invalid = 6,
+ BackendUnavailable = 7,
+ Streaming = 8,
+ IncompatibleDevice = 9,
+ NoSuchClient = 10,
+ IncompatibleBackend = 11,
+ BackendDisconnected = 12,
+ Interrupted = 13,
+ Underflow = 14,
+ EncodingString = 15,
+ }
+}
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs
new file mode 100644
index 00000000..a033356e
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Runtime.InteropServices;
+using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
+
+namespace Ryujinx.Audio.Backends.SoundIo.Native
+{
+ internal class SoundIoException : Exception
+ {
+ internal SoundIoException(SoundIoError error) : base(Marshal.PtrToStringAnsi(soundio_strerror(error))) { }
+ }
+}
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs
new file mode 100644
index 00000000..0eee9780
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs
@@ -0,0 +1,25 @@
+namespace Ryujinx.Audio.Backends.SoundIo.Native
+{
+ public enum SoundIoFormat
+ {
+ Invalid = 0,
+ S8 = 1,
+ U8 = 2,
+ S16LE = 3,
+ S16BE = 4,
+ U16LE = 5,
+ U16BE = 6,
+ S24LE = 7,
+ S24BE = 8,
+ U24LE = 9,
+ U24BE = 10,
+ S32LE = 11,
+ S32BE = 12,
+ U32LE = 13,
+ U32BE = 14,
+ Float32LE = 15,
+ Float32BE = 16,
+ Float64LE = 17,
+ Float64BE = 18,
+ }
+}
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs
new file mode 100644
index 00000000..2e432b31
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
+
+namespace Ryujinx.Audio.Backends.SoundIo.Native
+{
+ public class SoundIoOutStreamContext : IDisposable
+ {
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ private unsafe delegate void WriteCallbackDelegate(IntPtr ctx, int frameCountMin, int frameCountMax);
+
+ private IntPtr _context;
+ private IntPtr _nameStored;
+ private Action<int, int> _writeCallback;
+ private WriteCallbackDelegate _writeCallbackNative;
+
+ public IntPtr Context => _context;
+
+ internal SoundIoOutStreamContext(IntPtr context)
+ {
+ _context = context;
+ _nameStored = IntPtr.Zero;
+ _writeCallback = null;
+ _writeCallbackNative = null;
+ }
+
+ private ref SoundIoOutStream GetOutContext()
+ {
+ unsafe
+ {
+ return ref Unsafe.AsRef<SoundIoOutStream>((SoundIoOutStream*)_context);
+ }
+ }
+
+ public string Name
+ {
+ get => Marshal.PtrToStringAnsi(GetOutContext().Name);
+ set
+ {
+ var context = GetOutContext();
+
+ if (_nameStored != IntPtr.Zero && context.Name == _nameStored)
+ {
+ Marshal.FreeHGlobal(_nameStored);
+ }
+
+ _nameStored = Marshal.StringToHGlobalAnsi(value);
+ GetOutContext().Name = _nameStored;
+ }
+ }
+
+ public SoundIoChannelLayout Layout
+ {
+ get => GetOutContext().Layout;
+ set => GetOutContext().Layout = value;
+ }
+
+ public SoundIoFormat Format
+ {
+ get => GetOutContext().Format;
+ set => GetOutContext().Format = value;
+ }
+
+ public int SampleRate
+ {
+ get => GetOutContext().SampleRate;
+ set => GetOutContext().SampleRate = value;
+ }
+
+ public float Volume
+ {
+ get => GetOutContext().Volume;
+ set => GetOutContext().Volume = value;
+ }
+
+ public int BytesPerFrame
+ {
+ get => GetOutContext().BytesPerFrame;
+ set => GetOutContext().BytesPerFrame = value;
+ }
+
+ public int BytesPerSample
+ {
+ get => GetOutContext().BytesPerSample;
+ set => GetOutContext().BytesPerSample = value;
+ }
+
+ public Action<int, int> WriteCallback
+ {
+ get { return _writeCallback; }
+ set
+ {
+ _writeCallback = value;
+
+ if (_writeCallback == null)
+ {
+ _writeCallbackNative = null;
+ }
+ else
+ {
+ _writeCallbackNative = (ctx, frameCountMin, frameCountMax) => _writeCallback(frameCountMin, frameCountMax);
+ }
+
+ GetOutContext().WriteCallback = Marshal.GetFunctionPointerForDelegate(_writeCallbackNative);
+ }
+ }
+
+ private static void CheckError(SoundIoError error)
+ {
+ if (error != SoundIoError.None)
+ {
+ throw new SoundIoException(error);
+ }
+ }
+
+ public void Open() => CheckError(soundio_outstream_open(_context));
+
+ public void Start() => CheckError(soundio_outstream_start(_context));
+
+ public void Pause(bool pause) => CheckError(soundio_outstream_pause(_context, pause));
+
+ public void SetVolume(double volume) => CheckError(soundio_outstream_set_volume(_context, volume));
+
+ public Span<SoundIoChannelArea> BeginWrite(ref int frameCount)
+ {
+ IntPtr arenas = default;
+ int nativeFrameCount = frameCount;
+
+ unsafe
+ {
+ var frameCountPtr = &nativeFrameCount;
+ var arenasPtr = &arenas;
+ CheckError(soundio_outstream_begin_write(_context, (IntPtr)arenasPtr, (IntPtr)frameCountPtr));
+
+ frameCount = *frameCountPtr;
+
+ return new Span<SoundIoChannelArea>((void*)arenas, Layout.ChannelCount);
+ }
+ }
+
+ public void EndWrite() => CheckError(soundio_outstream_end_write(_context));
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_context != IntPtr.Zero)
+ {
+ soundio_outstream_destroy(_context);
+ _context = IntPtr.Zero;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ~SoundIoOutStreamContext()
+ {
+ Dispose(false);
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll
new file mode 100644
index 00000000..48804312
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll
Binary files differ
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylib b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylib
new file mode 100644
index 00000000..10171f4f
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylib
Binary files differ
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so
new file mode 100644
index 00000000..87c8b506
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so
Binary files differ
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj
new file mode 100644
index 00000000..9f242dbe
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj
@@ -0,0 +1,28 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net7.0</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <RuntimeIdentifiers>win10-x64;linux-x64;osx-x64</RuntimeIdentifiers>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ <TargetPath>libsoundio.dll</TargetPath>
+ </ContentWithTargetPath>
+ <ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ <TargetPath>libsoundio.dylib</TargetPath>
+ </ContentWithTargetPath>
+ <ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win10-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ <TargetPath>libsoundio.so</TargetPath>
+ </ContentWithTargetPath>
+ </ItemGroup>
+
+</Project>
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs
new file mode 100644
index 00000000..7f32b953
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.Audio.Backends.SoundIo
+{
+ class SoundIoAudioBuffer
+ {
+ public readonly ulong DriverIdentifier;
+ public readonly ulong SampleCount;
+ public ulong SamplePlayed;
+
+ public SoundIoAudioBuffer(ulong driverIdentifier, ulong sampleCount)
+ {
+ DriverIdentifier = driverIdentifier;
+ SampleCount = sampleCount;
+ SamplePlayed = 0;
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
new file mode 100644
index 00000000..02da2776
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
@@ -0,0 +1,247 @@
+using Ryujinx.Audio.Backends.SoundIo.Native;
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Integration;
+using Ryujinx.Memory;
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
+using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
+
+namespace Ryujinx.Audio.Backends.SoundIo
+{
+ public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver
+ {
+ private readonly SoundIoContext _audioContext;
+ private readonly SoundIoDeviceContext _audioDevice;
+ private readonly ManualResetEvent _updateRequiredEvent;
+ private readonly ManualResetEvent _pauseEvent;
+ private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
+ private int _disposeState;
+
+ public SoundIoHardwareDeviceDriver()
+ {
+ _audioContext = SoundIoContext.Create();
+ _updateRequiredEvent = new ManualResetEvent(false);
+ _pauseEvent = new ManualResetEvent(true);
+ _sessions = new ConcurrentDictionary<SoundIoHardwareDeviceSession, byte>();
+
+ _audioContext.Connect();
+ _audioContext.FlushEvents();
+
+ _audioDevice = FindValidAudioDevice(_audioContext, true);
+ }
+
+ public static bool IsSupported => IsSupportedInternal();
+
+ private static bool IsSupportedInternal()
+ {
+ SoundIoContext context = null;
+ SoundIoDeviceContext device = null;
+ SoundIoOutStreamContext stream = null;
+
+ bool backendDisconnected = false;
+
+ try
+ {
+ context = SoundIoContext.Create();
+ context.OnBackendDisconnect = err =>
+ {
+ backendDisconnected = true;
+ };
+
+ context.Connect();
+ context.FlushEvents();
+
+ if (backendDisconnected)
+ {
+ return false;
+ }
+
+ if (context.OutputDeviceCount == 0)
+ {
+ return false;
+ }
+
+ device = FindValidAudioDevice(context);
+
+ if (device == null || backendDisconnected)
+ {
+ return false;
+ }
+
+ stream = device.CreateOutStream();
+
+ if (stream == null || backendDisconnected)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ finally
+ {
+ stream?.Dispose();
+ context?.Dispose();
+ }
+ }
+
+ private static SoundIoDeviceContext FindValidAudioDevice(SoundIoContext audioContext, bool fallback = false)
+ {
+ SoundIoDeviceContext defaultAudioDevice = audioContext.GetOutputDevice(audioContext.DefaultOutputDeviceIndex);
+
+ if (!defaultAudioDevice.IsRaw)
+ {
+ return defaultAudioDevice;
+ }
+
+ for (int i = 0; i < audioContext.OutputDeviceCount; i++)
+ {
+ SoundIoDeviceContext audioDevice = audioContext.GetOutputDevice(i);
+
+ if (audioDevice.Id == defaultAudioDevice.Id && !audioDevice.IsRaw)
+ {
+ return audioDevice;
+ }
+ }
+
+ return fallback ? defaultAudioDevice : null;
+ }
+
+ public ManualResetEvent GetUpdateRequiredEvent()
+ {
+ return _updateRequiredEvent;
+ }
+
+ public ManualResetEvent GetPauseEvent()
+ {
+ return _pauseEvent;
+ }
+
+ public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
+ {
+ if (channelCount == 0)
+ {
+ channelCount = 2;
+ }
+
+ if (sampleRate == 0)
+ {
+ sampleRate = Constants.TargetSampleRate;
+ }
+
+ volume = Math.Clamp(volume, 0, 1);
+
+ if (direction != Direction.Output)
+ {
+ throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
+ }
+
+ SoundIoHardwareDeviceSession session = new SoundIoHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
+
+ _sessions.TryAdd(session, 0);
+
+ return session;
+ }
+
+ internal bool Unregister(SoundIoHardwareDeviceSession session)
+ {
+ return _sessions.TryRemove(session, out _);
+ }
+
+ public static SoundIoFormat GetSoundIoFormat(SampleFormat format)
+ {
+ return format switch
+ {
+ SampleFormat.PcmInt8 => SoundIoFormat.S8,
+ SampleFormat.PcmInt16 => SoundIoFormat.S16LE,
+ SampleFormat.PcmInt24 => SoundIoFormat.S24LE,
+ SampleFormat.PcmInt32 => SoundIoFormat.S32LE,
+ SampleFormat.PcmFloat => SoundIoFormat.Float32LE,
+ _ => throw new ArgumentException ($"Unsupported sample format {format}"),
+ };
+ }
+
+ internal SoundIoOutStreamContext OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount)
+ {
+ SoundIoFormat driverSampleFormat = GetSoundIoFormat(requestedSampleFormat);
+
+ if (!_audioDevice.SupportsSampleRate((int)requestedSampleRate))
+ {
+ throw new ArgumentException($"This sound device does not support a sample rate of {requestedSampleRate}Hz");
+ }
+
+ if (!_audioDevice.SupportsFormat(driverSampleFormat))
+ {
+ throw new ArgumentException($"This sound device does not support {requestedSampleFormat}");
+ }
+
+ if (!_audioDevice.SupportsChannelCount((int)requestedChannelCount))
+ {
+ throw new ArgumentException($"This sound device does not support channel count {requestedChannelCount}");
+ }
+
+ SoundIoOutStreamContext result = _audioDevice.CreateOutStream();
+
+ result.Name = "Ryujinx";
+ result.Layout = SoundIoChannelLayout.GetDefaultValue((int)requestedChannelCount);
+ result.Format = driverSampleFormat;
+ result.SampleRate = (int)requestedSampleRate;
+
+ return result;
+ }
+
+ internal void FlushContextEvents()
+ {
+ _audioContext.FlushEvents();
+ }
+
+ public void Dispose()
+ {
+ if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
+ {
+ Dispose(true);
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ foreach (SoundIoHardwareDeviceSession session in _sessions.Keys)
+ {
+ session.Dispose();
+ }
+
+ _audioContext.Disconnect();
+ _audioContext.Dispose();
+ _pauseEvent.Dispose();
+ }
+ }
+
+ public bool SupportsSampleRate(uint sampleRate)
+ {
+ return _audioDevice.SupportsSampleRate((int)sampleRate);
+ }
+
+ public bool SupportsSampleFormat(SampleFormat sampleFormat)
+ {
+ return _audioDevice.SupportsFormat(GetSoundIoFormat(sampleFormat));
+ }
+
+ public bool SupportsChannelCount(uint channelCount)
+ {
+ return _audioDevice.SupportsChannelCount((int)channelCount);
+ }
+
+ public bool SupportsDirection(Direction direction)
+ {
+ // TODO: add direction input when supported.
+ return direction == Direction.Output;
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
new file mode 100644
index 00000000..96d9ce97
--- /dev/null
+++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
@@ -0,0 +1,438 @@
+using Ryujinx.Audio.Backends.Common;
+using Ryujinx.Audio.Backends.SoundIo.Native;
+using Ryujinx.Audio.Common;
+using Ryujinx.Memory;
+using System;
+using System.Collections.Concurrent;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
+
+namespace Ryujinx.Audio.Backends.SoundIo
+{
+ class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase
+ {
+ private SoundIoHardwareDeviceDriver _driver;
+ private ConcurrentQueue<SoundIoAudioBuffer> _queuedBuffers;
+ private SoundIoOutStreamContext _outputStream;
+ private DynamicRingBuffer _ringBuffer;
+ private ulong _playedSampleCount;
+ private ManualResetEvent _updateRequiredEvent;
+ private int _disposeState;
+
+ public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
+ {
+ _driver = driver;
+ _updateRequiredEvent = _driver.GetUpdateRequiredEvent();
+ _queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
+ _ringBuffer = new DynamicRingBuffer();
+
+ SetupOutputStream(requestedVolume);
+ }
+
+ private void SetupOutputStream(float requestedVolume)
+ {
+ _outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount);
+ _outputStream.WriteCallback += Update;
+ _outputStream.Volume = requestedVolume;
+ // TODO: Setup other callbacks (errors, ect).
+
+ _outputStream.Open();
+ }
+
+ public override ulong GetPlayedSampleCount()
+ {
+ return Interlocked.Read(ref _playedSampleCount);
+ }
+
+ public override float GetVolume()
+ {
+ return _outputStream.Volume;
+ }
+
+ public override void PrepareToClose() { }
+
+ public override void QueueBuffer(AudioBuffer buffer)
+ {
+ SoundIoAudioBuffer driverBuffer = new SoundIoAudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
+
+ _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
+
+ _queuedBuffers.Enqueue(driverBuffer);
+ }
+
+ public override void SetVolume(float volume)
+ {
+ _outputStream.SetVolume(volume);
+ }
+
+ public override void Start()
+ {
+ _outputStream.Start();
+ _outputStream.Pause(false);
+
+ _driver.FlushContextEvents();
+ }
+
+ public override void Stop()
+ {
+ _outputStream.Pause(true);
+
+ _driver.FlushContextEvents();
+ }
+
+ public override void UnregisterBuffer(AudioBuffer buffer) {}
+
+ public override bool WasBufferFullyConsumed(AudioBuffer buffer)
+ {
+ if (!_queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer))
+ {
+ return true;
+ }
+
+ return driverBuffer.DriverIdentifier != buffer.DataPointer;
+ }
+
+ private unsafe void Update(int minFrameCount, int maxFrameCount)
+ {
+ int bytesPerFrame = _outputStream.BytesPerFrame;
+ uint bytesPerSample = (uint)_outputStream.BytesPerSample;
+
+ int bufferedFrames = _ringBuffer.Length / bytesPerFrame;
+
+ int frameCount = Math.Min(bufferedFrames, maxFrameCount);
+
+ if (frameCount == 0)
+ {
+ return;
+ }
+
+ Span<SoundIoChannelArea> areas = _outputStream.BeginWrite(ref frameCount);
+
+ int channelCount = areas.Length;
+
+ byte[] samples = new byte[frameCount * bytesPerFrame];
+
+ _ringBuffer.Read(samples, 0, samples.Length);
+
+ // This is a huge ugly block of code, but we save
+ // a significant amount of time over the generic
+ // loop that handles other channel counts.
+ // TODO: Is this still right in 2022?
+
+ // Mono
+ if (channelCount == 1)
+ {
+ ref SoundIoChannelArea area = ref areas[0];
+
+ fixed (byte* srcptr = samples)
+ {
+ if (bytesPerSample == 1)
+ {
+ for (int frame = 0; frame < frameCount; frame++)
+ {
+ ((byte*)area.Pointer)[0] = srcptr[frame * bytesPerFrame];
+
+ area.Pointer += area.Step;
+ }
+ }
+ else if (bytesPerSample == 2)
+ {
+ for (int frame = 0; frame < frameCount; frame++)
+ {
+ ((short*)area.Pointer)[0] = ((short*)srcptr)[frame * bytesPerFrame >> 1];
+
+ area.Pointer += area.Step;
+ }
+ }
+ else if (bytesPerSample == 4)
+ {
+ for (int frame = 0; frame < frameCount; frame++)
+ {
+ ((int*)area.Pointer)[0] = ((int*)srcptr)[frame * bytesPerFrame >> 2];
+
+ area.Pointer += area.Step;
+ }
+ }
+ else
+ {
+ for (int frame = 0; frame < frameCount; frame++)
+ {
+ Unsafe.CopyBlockUnaligned((byte*)area.Pointer, srcptr + (frame * bytesPerFrame), bytesPerSample);
+
+ area.Pointer += area.Step;
+ }
+ }
+ }
+ }
+ // Stereo
+ else if (channelCount == 2)
+ {
+ ref SoundIoChannelArea area1 = ref areas[0];
+ ref SoundIoChannelArea area2 = ref areas[1];
+
+ fixed (byte* srcptr = samples)
+ {
+ if (bytesPerSample == 1)
+ {
+ for (int frame = 0; frame < frameCount; frame++)
+ {
+ // Channel 1
+ ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0];
+
+ // Channel 2
+ ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1];
+
+ area1.Pointer += area1.Step;
+ area2.Pointer += area2.Step;
+ }
+ }
+ else if (bytesPerSample == 2)
+ {
+ for (int frame = 0; frame < frameCount; frame++)
+ {
+ // Channel 1
+ ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0];
+
+ // Channel 2
+ ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1];
+
+ area1.Pointer += area1.Step;
+ area2.Pointer += area2.Step;
+ }
+ }
+ else if (bytesPerSample == 4)
+ {
+ for (int frame = 0; frame < frameCount; frame++)
+ {
+ // Channel 1
+ ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0];
+
+ // Channel 2
+ ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1];
+
+ area1.Pointer += area1.Step;
+ area2.Pointer += area2.Step;
+ }
+ }
+ else
+ {
+ for (int frame = 0; frame < frameCount; frame++)
+ {
+ // Channel 1
+ Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample);
+
+ // Channel 2
+ Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample);
+
+ area1.Pointer += area1.Step;
+ area2.Pointer += area2.Step;
+ }
+ }
+ }
+ }
+ // Surround
+ else if (channelCount == 6)
+ {
+ ref SoundIoChannelArea area1 = ref areas[0];
+ ref SoundIoChannelArea area2 = ref areas[1];
+ ref SoundIoChannelArea area3 = ref areas[2];
+ ref SoundIoChannelArea area4 = ref areas[3];
+ ref SoundIoChannelArea area5 = ref areas[4];
+ ref SoundIoChannelArea area6 = ref areas[5];
+
+ fixed (byte* srcptr = samples)
+ {
+ if (bytesPerSample == 1)
+ {
+ for (int frame = 0; frame < frameCount; frame++)
+ {
+ // Channel 1
+ ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0];
+
+ // Channel 2
+ ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1];
+
+ // Channel 3
+ ((byte*)area3.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 2];
+
+ // Channel 4
+ ((byte*)area4.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 3];
+
+ // Channel 5
+ ((byte*)area5.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 4];
+
+ // Channel 6
+ ((byte*)area6.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 5];
+
+ area1.Pointer += area1.Step;
+ area2.Pointer += area2.Step;
+ area3.Pointer += area3.Step;
+ area4.Pointer += area4.Step;
+ area5.Pointer += area5.Step;
+ area6.Pointer += area6.Step;
+ }
+ }
+ else if (bytesPerSample == 2)
+ {
+ for (int frame = 0; frame < frameCount; frame++)
+ {
+ // Channel 1
+ ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0];
+
+ // Channel 2
+ ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1];
+
+ // Channel 3
+ ((short*)area3.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 2];
+
+ // Channel 4
+ ((short*)area4.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 3];
+
+ // Channel 5
+ ((short*)area5.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 4];
+
+ // Channel 6
+ ((short*)area6.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 5];
+
+ area1.Pointer += area1.Step;
+ area2.Pointer += area2.Step;
+ area3.Pointer += area3.Step;
+ area4.Pointer += area4.Step;
+ area5.Pointer += area5.Step;
+ area6.Pointer += area6.Step;
+ }
+ }
+ else if (bytesPerSample == 4)
+ {
+ for (int frame = 0; frame < frameCount; frame++)
+ {
+ // Channel 1
+ ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0];
+
+ // Channel 2
+ ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1];
+
+ // Channel 3
+ ((int*)area3.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 2];
+
+ // Channel 4
+ ((int*)area4.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 3];
+
+ // Channel 5
+ ((int*)area5.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 4];
+
+ // Channel 6
+ ((int*)area6.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 5];
+
+ area1.Pointer += area1.Step;
+ area2.Pointer += area2.Step;
+ area3.Pointer += area3.Step;
+ area4.Pointer += area4.Step;
+ area5.Pointer += area5.Step;
+ area6.Pointer += area6.Step;
+ }
+ }
+ else
+ {
+ for (int frame = 0; frame < frameCount; frame++)
+ {
+ // Channel 1
+ Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample);
+
+ // Channel 2
+ Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample);
+
+ // Channel 3
+ Unsafe.CopyBlockUnaligned((byte*)area3.Pointer, srcptr + (frame * bytesPerFrame) + (2 * bytesPerSample), bytesPerSample);
+
+ // Channel 4
+ Unsafe.CopyBlockUnaligned((byte*)area4.Pointer, srcptr + (frame * bytesPerFrame) + (3 * bytesPerSample), bytesPerSample);
+
+ // Channel 5
+ Unsafe.CopyBlockUnaligned((byte*)area5.Pointer, srcptr + (frame * bytesPerFrame) + (4 * bytesPerSample), bytesPerSample);
+
+ // Channel 6
+ Unsafe.CopyBlockUnaligned((byte*)area6.Pointer, srcptr + (frame * bytesPerFrame) + (5 * bytesPerSample), bytesPerSample);
+
+ area1.Pointer += area1.Step;
+ area2.Pointer += area2.Step;
+ area3.Pointer += area3.Step;
+ area4.Pointer += area4.Step;
+ area5.Pointer += area5.Step;
+ area6.Pointer += area6.Step;
+ }
+ }
+ }
+ }
+ // Every other channel count
+ else
+ {
+ fixed (byte* srcptr = samples)
+ {
+ for (int frame = 0; frame < frameCount; frame++)
+ {
+ for (int channel = 0; channel < areas.Length; channel++)
+ {
+ // Copy channel by channel, frame by frame. This is slow!
+ Unsafe.CopyBlockUnaligned((byte*)areas[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample);
+
+ areas[channel].Pointer += areas[channel].Step;
+ }
+ }
+ }
+ }
+
+ _outputStream.EndWrite();
+
+ ulong sampleCount = (ulong)(samples.Length / bytesPerSample / channelCount);
+
+ ulong availaibleSampleCount = sampleCount;
+
+ bool needUpdate = false;
+
+ while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer))
+ {
+ ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed);
+ ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount);
+
+ Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount);
+ availaibleSampleCount -= playedAudioBufferSampleCount;
+
+ if (Interlocked.Read(ref driverBuffer.SamplePlayed) == driverBuffer.SampleCount)
+ {
+ _queuedBuffers.TryDequeue(out _);
+
+ needUpdate = true;
+ }
+
+ Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount);
+ }
+
+ // Notify the output if needed.
+ if (needUpdate)
+ {
+ _updateRequiredEvent.Set();
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && _driver.Unregister(this))
+ {
+ PrepareToClose();
+ Stop();
+
+ _outputStream.Dispose();
+ }
+ }
+
+ public override void Dispose()
+ {
+ if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
+ {
+ Dispose(true);
+ }
+ }
+ }
+}