mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-11-20 02:16:34 +00:00
Implement libsoundio as an alternative audio backend (#406)
* Audio: Implement libsoundio as an alternative audio backend libsoundio will be preferred over OpenAL if it is available on the machine. If neither are available, it will fallback to a dummy audio renderer that outputs no sound. * Audio: Fix SoundIoRingBuffer documentation * Audio: Unroll and optimize the audio write callback Copying one sample at a time is slow, this unrolls the most common audio channel layouts and manually copies the bytes between source and destination. This is over 2x faster than calling CopyBlockUnaligned every sample. * Audio: Optimize the write callback further This dramatically reduces the audio buffer copy time. When the sample size is one of handled sample sizes the buffer copy operation is almost 10x faster than CopyBlockAligned. This works by copying full samples at a time, rather than the individual bytes that make up the sample. This allows for 2x or 4x faster copy operations depending on sample size. * Audio: Fix typo in Stereo write callback * Audio: Fix Surround (5.1) audio write callback * Audio: Update Documentation * Audio: Use built-in Unsafe.SizeOf<T>() Built-in `SizeOf<T>()` is 10x faster than our `TypeSize<T>` helper. This also helps reduce code surface area. * Audio: Keep fixed buffer style consistent * Audio: Address styling nits * Audio: More style nits * Audio: Add additional documentation * Audio: Move libsoundio bindings internal As per discussion, moving the libsoundio native bindings into Ryujinx.Audio * Audio: Bump Target Framework back up to .NET Core 2.1 * Audio: Remove voice mixing optimizations. Leaves Saturation optimizations in place.
This commit is contained in:
parent
85ffd76016
commit
8275bc3c08
34 changed files with 3399 additions and 21 deletions
|
@ -4,19 +4,19 @@ namespace Ryujinx.Audio
|
|||
{
|
||||
public interface IAalOutput : IDisposable
|
||||
{
|
||||
int OpenTrack(int SampleRate, int Channels, ReleaseCallback Callback);
|
||||
int OpenTrack(int sampleRate, int channels, ReleaseCallback callback);
|
||||
|
||||
void CloseTrack(int Track);
|
||||
void CloseTrack(int trackId);
|
||||
|
||||
bool ContainsBuffer(int Track, long Tag);
|
||||
bool ContainsBuffer(int trackId, long bufferTag);
|
||||
|
||||
long[] GetReleasedBuffers(int Track, int MaxCount);
|
||||
long[] GetReleasedBuffers(int trackId, int maxCount);
|
||||
|
||||
void AppendBuffer<T>(int Track, long Tag, T[] Buffer) where T : struct;
|
||||
void AppendBuffer<T>(int trackId, long bufferTag, T[] buffer) where T : struct;
|
||||
|
||||
void Start(int Track);
|
||||
void Stop(int Track);
|
||||
void Start(int trackId);
|
||||
void Stop(int trackId);
|
||||
|
||||
PlaybackState GetState(int Track);
|
||||
PlaybackState GetState(int trackId);
|
||||
}
|
||||
}
|
23
Ryujinx.Audio/Native/libsoundio/MarshalExtensions.cs
Normal file
23
Ryujinx.Audio/Native/libsoundio/MarshalExtensions.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public static class MarshalEx
|
||||
{
|
||||
public static double ReadDouble (IntPtr handle, int offset = 0)
|
||||
{
|
||||
return BitConverter.Int64BitsToDouble (Marshal.ReadInt64 (handle, offset));
|
||||
}
|
||||
|
||||
public static void WriteDouble (IntPtr handle, double value)
|
||||
{
|
||||
WriteDouble (handle, 0, value);
|
||||
}
|
||||
|
||||
public static void WriteDouble (IntPtr handle, int offset, double value)
|
||||
{
|
||||
Marshal.WriteInt64 (handle, offset, BitConverter.DoubleToInt64Bits (value));
|
||||
}
|
||||
}
|
||||
}
|
311
Ryujinx.Audio/Native/libsoundio/SoundIO.cs
Normal file
311
Ryujinx.Audio/Native/libsoundio/SoundIO.cs
Normal file
|
@ -0,0 +1,311 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public class SoundIO : IDisposable
|
||||
{
|
||||
Pointer<SoundIo> handle;
|
||||
|
||||
public SoundIO ()
|
||||
{
|
||||
handle = Natives.soundio_create ();
|
||||
}
|
||||
|
||||
internal SoundIO (Pointer<SoundIo> handle)
|
||||
{
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
public void Dispose ()
|
||||
{
|
||||
foreach (var h in allocated_hglobals)
|
||||
Marshal.FreeHGlobal (h);
|
||||
Natives.soundio_destroy (handle);
|
||||
}
|
||||
|
||||
// Equality (based on handle)
|
||||
|
||||
public override bool Equals (object other)
|
||||
{
|
||||
var d = other as SoundIO;
|
||||
return d != null && this.handle == d.handle;
|
||||
}
|
||||
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
return (int) (IntPtr) handle;
|
||||
}
|
||||
|
||||
public static bool operator == (SoundIO obj1, SoundIO obj2)
|
||||
{
|
||||
return (object)obj1 == null ? (object)obj2 == null : obj1.Equals (obj2);
|
||||
}
|
||||
|
||||
public static bool operator != (SoundIO obj1, SoundIO obj2)
|
||||
{
|
||||
return (object)obj1 == null ? (object)obj2 != null : !obj1.Equals (obj2);
|
||||
}
|
||||
|
||||
// fields
|
||||
|
||||
// FIXME: this should be taken care in more centralized/decent manner... we don't want to write
|
||||
// this kind of code anywhere we need string marshaling.
|
||||
List<IntPtr> allocated_hglobals = new List<IntPtr> ();
|
||||
|
||||
public string ApplicationName {
|
||||
get { return Marshal.PtrToStringAnsi (Marshal.ReadIntPtr (handle, app_name_offset)); }
|
||||
set {
|
||||
unsafe {
|
||||
var existing = Marshal.ReadIntPtr (handle, app_name_offset);
|
||||
if (allocated_hglobals.Contains (existing)) {
|
||||
allocated_hglobals.Remove (existing);
|
||||
Marshal.FreeHGlobal (existing);
|
||||
}
|
||||
var ptr = Marshal.StringToHGlobalAnsi (value);
|
||||
Marshal.WriteIntPtr (handle, app_name_offset, ptr);
|
||||
allocated_hglobals.Add (ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
static readonly int app_name_offset = (int)Marshal.OffsetOf<SoundIo> ("app_name");
|
||||
|
||||
public SoundIOBackend CurrentBackend {
|
||||
get { return (SoundIOBackend) Marshal.ReadInt32 (handle, current_backend_offset); }
|
||||
}
|
||||
static readonly int current_backend_offset = (int)Marshal.OffsetOf<SoundIo> ("current_backend");
|
||||
|
||||
// emit_rtprio_warning
|
||||
public Action EmitRealtimePriorityWarning {
|
||||
get { return emit_rtprio_warning; }
|
||||
set {
|
||||
emit_rtprio_warning = value;
|
||||
var ptr = Marshal.GetFunctionPointerForDelegate (on_devices_change);
|
||||
Marshal.WriteIntPtr (handle, emit_rtprio_warning_offset, ptr);
|
||||
}
|
||||
}
|
||||
static readonly int emit_rtprio_warning_offset = (int)Marshal.OffsetOf<SoundIo> ("emit_rtprio_warning");
|
||||
Action emit_rtprio_warning;
|
||||
|
||||
// jack_error_callback
|
||||
public Action<string> JackErrorCallback {
|
||||
get { return jack_error_callback; }
|
||||
set {
|
||||
jack_error_callback = value;
|
||||
if (value == null)
|
||||
jack_error_callback = null;
|
||||
else
|
||||
jack_error_callback_native = msg => jack_error_callback (msg);
|
||||
var ptr = Marshal.GetFunctionPointerForDelegate (jack_error_callback_native);
|
||||
Marshal.WriteIntPtr (handle, jack_error_callback_offset, ptr);
|
||||
}
|
||||
}
|
||||
static readonly int jack_error_callback_offset = (int)Marshal.OffsetOf<SoundIo> ("jack_error_callback");
|
||||
Action<string> jack_error_callback;
|
||||
delegate void jack_error_delegate (string message);
|
||||
jack_error_delegate jack_error_callback_native;
|
||||
|
||||
// jack_info_callback
|
||||
public Action<string> JackInfoCallback {
|
||||
get { return jack_info_callback; }
|
||||
set {
|
||||
jack_info_callback = value;
|
||||
if (value == null)
|
||||
jack_info_callback = null;
|
||||
else
|
||||
jack_info_callback_native = msg => jack_info_callback (msg);
|
||||
var ptr = Marshal.GetFunctionPointerForDelegate (jack_info_callback_native);
|
||||
Marshal.WriteIntPtr (handle, jack_info_callback_offset, ptr);
|
||||
}
|
||||
}
|
||||
static readonly int jack_info_callback_offset = (int)Marshal.OffsetOf<SoundIo> ("jack_info_callback");
|
||||
Action<string> jack_info_callback;
|
||||
delegate void jack_info_delegate (string message);
|
||||
jack_info_delegate jack_info_callback_native;
|
||||
|
||||
// on_backend_disconnect
|
||||
public Action<int> OnBackendDisconnect {
|
||||
get { return on_backend_disconnect; }
|
||||
set {
|
||||
on_backend_disconnect = value;
|
||||
if (value == null)
|
||||
on_backend_disconnect_native = null;
|
||||
else
|
||||
on_backend_disconnect_native = (sio, err) => on_backend_disconnect (err);
|
||||
var ptr = Marshal.GetFunctionPointerForDelegate (on_backend_disconnect_native);
|
||||
Marshal.WriteIntPtr (handle, on_backend_disconnect_offset, ptr);
|
||||
}
|
||||
}
|
||||
static readonly int on_backend_disconnect_offset = (int)Marshal.OffsetOf<SoundIo> ("on_backend_disconnect");
|
||||
Action<int> on_backend_disconnect;
|
||||
delegate void on_backend_disconnect_delegate (IntPtr handle, int errorCode);
|
||||
on_backend_disconnect_delegate on_backend_disconnect_native;
|
||||
|
||||
// on_devices_change
|
||||
public Action OnDevicesChange {
|
||||
get { return on_devices_change; }
|
||||
set {
|
||||
on_devices_change = value;
|
||||
if (value == null)
|
||||
on_devices_change_native = null;
|
||||
else
|
||||
on_devices_change_native = sio => on_devices_change ();
|
||||
var ptr = Marshal.GetFunctionPointerForDelegate (on_devices_change_native);
|
||||
Marshal.WriteIntPtr (handle, on_devices_change_offset, ptr);
|
||||
}
|
||||
}
|
||||
static readonly int on_devices_change_offset = (int)Marshal.OffsetOf<SoundIo> ("on_devices_change");
|
||||
Action on_devices_change;
|
||||
delegate void on_devices_change_delegate (IntPtr handle);
|
||||
on_devices_change_delegate on_devices_change_native;
|
||||
|
||||
// on_events_signal
|
||||
public Action OnEventsSignal {
|
||||
get { return on_events_signal; }
|
||||
set {
|
||||
on_events_signal = value;
|
||||
if (value == null)
|
||||
on_events_signal_native = null;
|
||||
else
|
||||
on_events_signal_native = sio => on_events_signal ();
|
||||
var ptr = Marshal.GetFunctionPointerForDelegate (on_events_signal_native);
|
||||
Marshal.WriteIntPtr (handle, on_events_signal_offset, ptr);
|
||||
}
|
||||
}
|
||||
static readonly int on_events_signal_offset = (int)Marshal.OffsetOf<SoundIo> ("on_events_signal");
|
||||
Action on_events_signal;
|
||||
delegate void on_events_signal_delegate (IntPtr handle);
|
||||
on_events_signal_delegate on_events_signal_native;
|
||||
|
||||
|
||||
// functions
|
||||
|
||||
public int BackendCount {
|
||||
get { return Natives.soundio_backend_count (handle); }
|
||||
}
|
||||
|
||||
public int InputDeviceCount {
|
||||
get { return Natives.soundio_input_device_count (handle); }
|
||||
}
|
||||
|
||||
public int OutputDeviceCount {
|
||||
get { return Natives.soundio_output_device_count (handle); }
|
||||
}
|
||||
|
||||
public int DefaultInputDeviceIndex {
|
||||
get { return Natives.soundio_default_input_device_index (handle); }
|
||||
}
|
||||
|
||||
public int DefaultOutputDeviceIndex {
|
||||
get { return Natives.soundio_default_output_device_index (handle); }
|
||||
}
|
||||
|
||||
public SoundIOBackend GetBackend (int index)
|
||||
{
|
||||
return (SoundIOBackend) Natives.soundio_get_backend (handle, index);
|
||||
}
|
||||
|
||||
public SoundIODevice GetInputDevice (int index)
|
||||
{
|
||||
return new SoundIODevice (Natives.soundio_get_input_device (handle, index));
|
||||
}
|
||||
|
||||
public SoundIODevice GetOutputDevice (int index)
|
||||
{
|
||||
return new SoundIODevice (Natives.soundio_get_output_device (handle, index));
|
||||
}
|
||||
|
||||
public void Connect ()
|
||||
{
|
||||
var ret = (SoundIoError) Natives.soundio_connect (handle);
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
}
|
||||
|
||||
public void ConnectBackend (SoundIOBackend backend)
|
||||
{
|
||||
var ret = (SoundIoError) Natives.soundio_connect_backend (handle, (SoundIoBackend) backend);
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
}
|
||||
|
||||
public void Disconnect ()
|
||||
{
|
||||
Natives.soundio_disconnect (handle);
|
||||
}
|
||||
|
||||
public void FlushEvents ()
|
||||
{
|
||||
Natives.soundio_flush_events (handle);
|
||||
}
|
||||
|
||||
public void WaitEvents ()
|
||||
{
|
||||
Natives.soundio_wait_events (handle);
|
||||
}
|
||||
|
||||
public void Wakeup ()
|
||||
{
|
||||
Natives.soundio_wakeup (handle);
|
||||
}
|
||||
|
||||
public void ForceDeviceScan ()
|
||||
{
|
||||
Natives.soundio_force_device_scan (handle);
|
||||
}
|
||||
|
||||
public SoundIORingBuffer CreateRingBuffer (int capacity)
|
||||
{
|
||||
return new SoundIORingBuffer (Natives.soundio_ring_buffer_create (handle, capacity));
|
||||
}
|
||||
|
||||
// static methods
|
||||
|
||||
public static string VersionString {
|
||||
get { return Marshal.PtrToStringAnsi (Natives.soundio_version_string ()); }
|
||||
}
|
||||
|
||||
public static int VersionMajor {
|
||||
get { return Natives.soundio_version_major (); }
|
||||
}
|
||||
|
||||
public static int VersionMinor {
|
||||
get { return Natives.soundio_version_minor (); }
|
||||
}
|
||||
|
||||
public static int VersionPatch {
|
||||
get { return Natives.soundio_version_patch (); }
|
||||
}
|
||||
|
||||
public static string GetBackendName (SoundIOBackend backend)
|
||||
{
|
||||
return Marshal.PtrToStringAnsi (Natives.soundio_backend_name ((SoundIoBackend) backend));
|
||||
}
|
||||
|
||||
public static bool HaveBackend (SoundIOBackend backend)
|
||||
{
|
||||
return Natives.soundio_have_backend ((SoundIoBackend) backend);
|
||||
}
|
||||
|
||||
public static int GetBytesPerSample (SoundIOFormat format)
|
||||
{
|
||||
return Natives.soundio_get_bytes_per_sample ((SoundIoFormat) format);
|
||||
}
|
||||
|
||||
public static int GetBytesPerFrame (SoundIOFormat format, int channelCount)
|
||||
{
|
||||
return Natives.soundio_get_bytes_per_frame ((SoundIoFormat) format, channelCount);
|
||||
}
|
||||
|
||||
public static int GetBytesPerSecond (SoundIOFormat format, int channelCount, int sampleRate)
|
||||
{
|
||||
return Natives.soundio_get_bytes_per_second ((SoundIoFormat) format, channelCount, sampleRate);
|
||||
}
|
||||
|
||||
public static string GetSoundFormatName (SoundIOFormat format)
|
||||
{
|
||||
return Marshal.PtrToStringAnsi (Natives.soundio_format_string ((SoundIoFormat) format));
|
||||
}
|
||||
}
|
||||
}
|
15
Ryujinx.Audio/Native/libsoundio/SoundIOBackend.cs
Normal file
15
Ryujinx.Audio/Native/libsoundio/SoundIOBackend.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public enum SoundIOBackend
|
||||
{
|
||||
None = 0,
|
||||
Jack = 1,
|
||||
PulseAudio = 2,
|
||||
Alsa = 3,
|
||||
CoreAudio = 4,
|
||||
Wasapi = 5,
|
||||
Dummy = 6,
|
||||
}
|
||||
|
||||
}
|
26
Ryujinx.Audio/Native/libsoundio/SoundIOChannelArea.cs
Normal file
26
Ryujinx.Audio/Native/libsoundio/SoundIOChannelArea.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public struct SoundIOChannelArea
|
||||
{
|
||||
internal SoundIOChannelArea (Pointer<SoundIoChannelArea> handle)
|
||||
{
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
Pointer<SoundIoChannelArea> handle;
|
||||
|
||||
public IntPtr Pointer {
|
||||
get { return Marshal.ReadIntPtr (handle, ptr_offset); }
|
||||
set { Marshal.WriteIntPtr (handle, ptr_offset, value); }
|
||||
}
|
||||
static readonly int ptr_offset = (int) Marshal.OffsetOf<SoundIoChannelArea> ("ptr");
|
||||
|
||||
public int Step {
|
||||
get { return Marshal.ReadInt32 (handle, step_offset); }
|
||||
}
|
||||
static readonly int step_offset = (int)Marshal.OffsetOf<SoundIoChannelArea> ("step");
|
||||
}
|
||||
}
|
33
Ryujinx.Audio/Native/libsoundio/SoundIOChannelAreas.cs
Normal file
33
Ryujinx.Audio/Native/libsoundio/SoundIOChannelAreas.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public struct SoundIOChannelAreas
|
||||
{
|
||||
static readonly int native_size = Marshal.SizeOf<SoundIoChannelArea> ();
|
||||
|
||||
internal SoundIOChannelAreas (IntPtr head, int channelCount, int frameCount)
|
||||
{
|
||||
this.head = head;
|
||||
this.channel_count = channelCount;
|
||||
this.frame_count = frameCount;
|
||||
}
|
||||
|
||||
IntPtr head;
|
||||
int channel_count;
|
||||
int frame_count;
|
||||
|
||||
public bool IsEmpty {
|
||||
get { return head == IntPtr.Zero; }
|
||||
}
|
||||
|
||||
public SoundIOChannelArea GetArea (int channel)
|
||||
{
|
||||
return new SoundIOChannelArea (head + native_size * channel);
|
||||
}
|
||||
|
||||
public int ChannelCount => channel_count;
|
||||
public int FrameCount => frame_count;
|
||||
}
|
||||
}
|
77
Ryujinx.Audio/Native/libsoundio/SoundIOChannelId.cs
Normal file
77
Ryujinx.Audio/Native/libsoundio/SoundIOChannelId.cs
Normal file
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
99
Ryujinx.Audio/Native/libsoundio/SoundIOChannelLayout.cs
Normal file
99
Ryujinx.Audio/Native/libsoundio/SoundIOChannelLayout.cs
Normal file
|
@ -0,0 +1,99 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public struct SoundIOChannelLayout
|
||||
{
|
||||
public static int BuiltInCount {
|
||||
get { return Natives.soundio_channel_layout_builtin_count (); }
|
||||
}
|
||||
|
||||
public static SoundIOChannelLayout GetBuiltIn (int index)
|
||||
{
|
||||
return new SoundIOChannelLayout (Natives.soundio_channel_layout_get_builtin (index));
|
||||
}
|
||||
|
||||
public static SoundIOChannelLayout GetDefault (int channelCount)
|
||||
{
|
||||
var handle = Natives.soundio_channel_layout_get_default (channelCount);
|
||||
return new SoundIOChannelLayout (handle);
|
||||
}
|
||||
|
||||
public static SoundIOChannelId ParseChannelId (string name)
|
||||
{
|
||||
var ptr = Marshal.StringToHGlobalAnsi (name);
|
||||
try {
|
||||
return (SoundIOChannelId)Natives.soundio_parse_channel_id (ptr, name.Length);
|
||||
} finally {
|
||||
Marshal.FreeHGlobal (ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// instance members
|
||||
|
||||
internal SoundIOChannelLayout (Pointer<SoundIoChannelLayout> handle)
|
||||
{
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
readonly Pointer<SoundIoChannelLayout> handle;
|
||||
|
||||
public bool IsNull {
|
||||
get { return handle.Handle == IntPtr.Zero; }
|
||||
}
|
||||
|
||||
internal IntPtr Handle {
|
||||
get { return handle; }
|
||||
}
|
||||
|
||||
public int ChannelCount {
|
||||
get { return IsNull ? 0 : Marshal.ReadInt32 ((IntPtr) handle + channel_count_offset); }
|
||||
}
|
||||
static readonly int channel_count_offset = (int) Marshal.OffsetOf<SoundIoChannelLayout> ("channel_count");
|
||||
|
||||
public string Name {
|
||||
get { return IsNull ? null : Marshal.PtrToStringAnsi (Marshal.ReadIntPtr ((IntPtr) handle + name_offset)); }
|
||||
}
|
||||
static readonly int name_offset = (int)Marshal.OffsetOf<SoundIoChannelLayout> ("name");
|
||||
|
||||
public IEnumerable<SoundIOChannelId> Channels {
|
||||
get {
|
||||
if (IsNull)
|
||||
yield break;
|
||||
for (int i = 0; i < 24; i++)
|
||||
yield return (SoundIOChannelId) Marshal.ReadInt32 ((IntPtr) handle + channels_offset + sizeof (SoundIoChannelId) * i);
|
||||
}
|
||||
}
|
||||
static readonly int channels_offset = (int)Marshal.OffsetOf<SoundIoChannelLayout> ("channels");
|
||||
|
||||
public override bool Equals (object other)
|
||||
{
|
||||
if (!(other is SoundIOChannelLayout))
|
||||
return false;
|
||||
var s = (SoundIOChannelLayout) other;
|
||||
return handle == s.handle || Natives.soundio_channel_layout_equal (handle, s.handle);
|
||||
}
|
||||
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
return handle.GetHashCode ();
|
||||
}
|
||||
|
||||
public string DetectBuiltInName ()
|
||||
{
|
||||
if (IsNull)
|
||||
throw new InvalidOperationException ();
|
||||
return Natives.soundio_channel_layout_detect_builtin (handle) ? Name : null;
|
||||
}
|
||||
|
||||
public int FindChannel (SoundIOChannelId channel)
|
||||
{
|
||||
if (IsNull)
|
||||
throw new InvalidOperationException ();
|
||||
return Natives.soundio_channel_layout_find_channel (handle, (SoundIoChannelId) channel);
|
||||
}
|
||||
}
|
||||
}
|
215
Ryujinx.Audio/Native/libsoundio/SoundIODevice.cs
Normal file
215
Ryujinx.Audio/Native/libsoundio/SoundIODevice.cs
Normal file
|
@ -0,0 +1,215 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public class SoundIODevice
|
||||
{
|
||||
public static SoundIOChannelLayout BestMatchingChannelLayout (SoundIODevice device1, SoundIODevice device2)
|
||||
{
|
||||
var ptr1 = Marshal.ReadIntPtr (device1.handle, layouts_offset);
|
||||
var ptr2 = Marshal.ReadIntPtr (device2.handle, layouts_offset);
|
||||
return new SoundIOChannelLayout (Natives.soundio_best_matching_channel_layout (ptr1, device1.LayoutCount, ptr2, device2.LayoutCount));
|
||||
}
|
||||
|
||||
internal SoundIODevice (Pointer<SoundIoDevice> handle)
|
||||
{
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
readonly Pointer<SoundIoDevice> handle;
|
||||
|
||||
// Equality (based on handle and native func)
|
||||
|
||||
public override bool Equals (object other)
|
||||
{
|
||||
var d = other as SoundIODevice;
|
||||
return d != null && (this.handle == d.handle || Natives.soundio_device_equal (this.handle, d.handle));
|
||||
}
|
||||
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
return (int) (IntPtr) handle;
|
||||
}
|
||||
|
||||
public static bool operator == (SoundIODevice obj1, SoundIODevice obj2)
|
||||
{
|
||||
return (object)obj1 == null ? (object)obj2 == null : obj1.Equals (obj2);
|
||||
}
|
||||
|
||||
public static bool operator != (SoundIODevice obj1, SoundIODevice obj2)
|
||||
{
|
||||
return (object)obj1 == null ? (object) obj2 != null : !obj1.Equals (obj2);
|
||||
}
|
||||
|
||||
// fields
|
||||
|
||||
public SoundIODeviceAim Aim {
|
||||
get { return (SoundIODeviceAim) Marshal.ReadInt32 (handle, aim_offset); }
|
||||
}
|
||||
static readonly int aim_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("aim");
|
||||
|
||||
public SoundIOFormat CurrentFormat {
|
||||
get { return (SoundIOFormat) Marshal.ReadInt32 (handle, current_format_offset); }
|
||||
}
|
||||
static readonly int current_format_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("current_format");
|
||||
|
||||
public SoundIOChannelLayout CurrentLayout {
|
||||
get { return new SoundIOChannelLayout ((IntPtr) handle + current_layout_offset);
|
||||
}
|
||||
}
|
||||
static readonly int current_layout_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("current_layout");
|
||||
|
||||
public int FormatCount {
|
||||
get { return Marshal.ReadInt32 (handle, format_count_offset); }
|
||||
}
|
||||
static readonly int format_count_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("format_count");
|
||||
|
||||
public IEnumerable<SoundIOFormat> Formats {
|
||||
get {
|
||||
var ptr = Marshal.ReadIntPtr (handle, formats_offset);
|
||||
for (int i = 0; i < FormatCount; i++)
|
||||
yield return (SoundIOFormat) Marshal.ReadInt32 (ptr, i);
|
||||
}
|
||||
}
|
||||
static readonly int formats_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("formats");
|
||||
|
||||
public string Id {
|
||||
get { return Marshal.PtrToStringAnsi (Marshal.ReadIntPtr (handle, id_offset)); }
|
||||
}
|
||||
static readonly int id_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("id");
|
||||
|
||||
public bool IsRaw {
|
||||
get { return Marshal.ReadInt32 (handle, is_raw_offset) != 0; }
|
||||
}
|
||||
static readonly int is_raw_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("is_raw");
|
||||
|
||||
public int LayoutCount {
|
||||
get { return Marshal.ReadInt32 (handle, layout_count_offset); }
|
||||
}
|
||||
static readonly int layout_count_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("layout_count");
|
||||
|
||||
public IEnumerable<SoundIOChannelLayout> Layouts {
|
||||
get {
|
||||
var ptr = Marshal.ReadIntPtr (handle, layouts_offset);
|
||||
for (int i = 0; i < LayoutCount; i++)
|
||||
yield return new SoundIOChannelLayout (ptr + i * Marshal.SizeOf<SoundIoChannelLayout> ());
|
||||
}
|
||||
}
|
||||
static readonly int layouts_offset = (int) Marshal.OffsetOf<SoundIoDevice> ("layouts");
|
||||
|
||||
public string Name {
|
||||
get { return Marshal.PtrToStringAnsi (Marshal.ReadIntPtr (handle, name_offset)); }
|
||||
}
|
||||
static readonly int name_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("name");
|
||||
|
||||
public int ProbeError {
|
||||
get { return Marshal.ReadInt32 (handle, probe_error_offset); }
|
||||
}
|
||||
static readonly int probe_error_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("probe_error");
|
||||
|
||||
public int ReferenceCount {
|
||||
get { return Marshal.ReadInt32 (handle, ref_count_offset); }
|
||||
}
|
||||
static readonly int ref_count_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("ref_count");
|
||||
|
||||
public int SampleRateCount {
|
||||
get { return Marshal.ReadInt32 (handle, sample_rate_count_offset); }
|
||||
}
|
||||
static readonly int sample_rate_count_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("sample_rate_count");
|
||||
|
||||
public IEnumerable<SoundIOSampleRateRange> SampleRates {
|
||||
get {
|
||||
var ptr = Marshal.ReadIntPtr (handle, sample_rates_offset);
|
||||
for (int i = 0; i < SampleRateCount; i++)
|
||||
yield return new SoundIOSampleRateRange (
|
||||
Marshal.ReadInt32 (ptr, i * 2),
|
||||
Marshal.ReadInt32 (ptr, i * 2 + 1));
|
||||
}
|
||||
}
|
||||
static readonly int sample_rates_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("sample_rates");
|
||||
|
||||
public double SoftwareLatencyCurrent {
|
||||
get { return MarshalEx.ReadDouble (handle, software_latency_current_offset); }
|
||||
set { MarshalEx.WriteDouble (handle, software_latency_current_offset, value); }
|
||||
}
|
||||
static readonly int software_latency_current_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("software_latency_current");
|
||||
|
||||
public double SoftwareLatencyMin {
|
||||
get { return MarshalEx.ReadDouble (handle, software_latency_min_offset); }
|
||||
set { MarshalEx.WriteDouble (handle, software_latency_min_offset, value); }
|
||||
}
|
||||
static readonly int software_latency_min_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("software_latency_min");
|
||||
|
||||
public double SoftwareLatencyMax {
|
||||
get { return MarshalEx.ReadDouble (handle, software_latency_max_offset); }
|
||||
set { MarshalEx.WriteDouble (handle, software_latency_max_offset, value); }
|
||||
}
|
||||
static readonly int software_latency_max_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("software_latency_max");
|
||||
|
||||
public SoundIO SoundIO {
|
||||
get { return new SoundIO (Marshal.ReadIntPtr (handle, soundio_offset)); }
|
||||
}
|
||||
static readonly int soundio_offset = (int)Marshal.OffsetOf<SoundIoDevice> ("soundio");
|
||||
|
||||
// functions
|
||||
|
||||
public void AddReference ()
|
||||
{
|
||||
Natives.soundio_device_ref (handle);
|
||||
}
|
||||
|
||||
public void RemoveReference ()
|
||||
{
|
||||
Natives.soundio_device_unref (handle);
|
||||
}
|
||||
|
||||
public void SortDeviceChannelLayouts ()
|
||||
{
|
||||
Natives.soundio_device_sort_channel_layouts (handle);
|
||||
}
|
||||
|
||||
public static readonly SoundIOFormat S16NE = BitConverter.IsLittleEndian ? SoundIOFormat.S16LE : SoundIOFormat.S16BE;
|
||||
public static readonly SoundIOFormat U16NE = BitConverter.IsLittleEndian ? SoundIOFormat.U16LE : SoundIOFormat.U16BE;
|
||||
public static readonly SoundIOFormat S24NE = BitConverter.IsLittleEndian ? SoundIOFormat.S24LE : SoundIOFormat.S24BE;
|
||||
public static readonly SoundIOFormat U24NE = BitConverter.IsLittleEndian ? SoundIOFormat.U24LE : SoundIOFormat.U24BE;
|
||||
public static readonly SoundIOFormat S32NE = BitConverter.IsLittleEndian ? SoundIOFormat.S32LE : SoundIOFormat.S32BE;
|
||||
public static readonly SoundIOFormat U32NE = BitConverter.IsLittleEndian ? SoundIOFormat.U32LE : SoundIOFormat.U32BE;
|
||||
public static readonly SoundIOFormat Float32NE = BitConverter.IsLittleEndian ? SoundIOFormat.Float32LE : SoundIOFormat.Float32BE;
|
||||
public static readonly SoundIOFormat Float64NE = BitConverter.IsLittleEndian ? SoundIOFormat.Float64LE : SoundIOFormat.Float64BE;
|
||||
public static readonly SoundIOFormat S16FE = !BitConverter.IsLittleEndian ? SoundIOFormat.S16LE : SoundIOFormat.S16BE;
|
||||
public static readonly SoundIOFormat U16FE = !BitConverter.IsLittleEndian ? SoundIOFormat.U16LE : SoundIOFormat.U16BE;
|
||||
public static readonly SoundIOFormat S24FE = !BitConverter.IsLittleEndian ? SoundIOFormat.S24LE : SoundIOFormat.S24BE;
|
||||
public static readonly SoundIOFormat U24FE = !BitConverter.IsLittleEndian ? SoundIOFormat.U24LE : SoundIOFormat.U24BE;
|
||||
public static readonly SoundIOFormat S32FE = !BitConverter.IsLittleEndian ? SoundIOFormat.S32LE : SoundIOFormat.S32BE;
|
||||
public static readonly SoundIOFormat U32FE = !BitConverter.IsLittleEndian ? SoundIOFormat.U32LE : SoundIOFormat.U32BE;
|
||||
public static readonly SoundIOFormat Float32FE = !BitConverter.IsLittleEndian ? SoundIOFormat.Float32LE : SoundIOFormat.Float32BE;
|
||||
public static readonly SoundIOFormat Float64FE = !BitConverter.IsLittleEndian ? SoundIOFormat.Float64LE : SoundIOFormat.Float64BE;
|
||||
|
||||
public bool SupportsFormat (SoundIOFormat format)
|
||||
{
|
||||
return Natives.soundio_device_supports_format (handle, (SoundIoFormat) format);
|
||||
}
|
||||
|
||||
public bool SupportsSampleRate (int sampleRate)
|
||||
{
|
||||
return Natives.soundio_device_supports_sample_rate (handle, sampleRate);
|
||||
}
|
||||
|
||||
public int GetNearestSampleRate (int sampleRate)
|
||||
{
|
||||
return Natives.soundio_device_nearest_sample_rate (handle, sampleRate);
|
||||
}
|
||||
|
||||
public SoundIOInStream CreateInStream ()
|
||||
{
|
||||
return new SoundIOInStream (Natives.soundio_instream_create (handle));
|
||||
}
|
||||
|
||||
public SoundIOOutStream CreateOutStream ()
|
||||
{
|
||||
return new SoundIOOutStream (Natives.soundio_outstream_create (handle));
|
||||
}
|
||||
}
|
||||
}
|
9
Ryujinx.Audio/Native/libsoundio/SoundIODeviceAim.cs
Normal file
9
Ryujinx.Audio/Native/libsoundio/SoundIODeviceAim.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public enum SoundIODeviceAim // soundio.h (228, 6)
|
||||
{
|
||||
Input = 0,
|
||||
Output = 1,
|
||||
}
|
||||
}
|
13
Ryujinx.Audio/Native/libsoundio/SoundIOException.cs
Normal file
13
Ryujinx.Audio/Native/libsoundio/SoundIOException.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public class SoundIOException : Exception
|
||||
{
|
||||
internal SoundIOException (SoundIoError errorCode)
|
||||
: base (Marshal.PtrToStringAnsi (Natives.soundio_strerror ((int) errorCode)))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
26
Ryujinx.Audio/Native/libsoundio/SoundIOFormat.cs
Normal file
26
Ryujinx.Audio/Native/libsoundio/SoundIOFormat.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
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,
|
||||
}
|
||||
}
|
228
Ryujinx.Audio/Native/libsoundio/SoundIOInStream.cs
Normal file
228
Ryujinx.Audio/Native/libsoundio/SoundIOInStream.cs
Normal file
|
@ -0,0 +1,228 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public class SoundIOInStream : IDisposable
|
||||
{
|
||||
internal SoundIOInStream (Pointer<SoundIoInStream> handle)
|
||||
{
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
Pointer<SoundIoInStream> handle;
|
||||
|
||||
public void Dispose ()
|
||||
{
|
||||
Natives.soundio_instream_destroy (handle);
|
||||
}
|
||||
|
||||
// Equality (based on handle)
|
||||
|
||||
public override bool Equals (object other)
|
||||
{
|
||||
var d = other as SoundIOInStream;
|
||||
return d != null && (this.handle == d.handle);
|
||||
}
|
||||
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
return (int)(IntPtr)handle;
|
||||
}
|
||||
|
||||
public static bool operator == (SoundIOInStream obj1, SoundIOInStream obj2)
|
||||
{
|
||||
return (object)obj1 == null ? (object)obj2 == null : obj1.Equals (obj2);
|
||||
}
|
||||
|
||||
public static bool operator != (SoundIOInStream obj1, SoundIOInStream obj2)
|
||||
{
|
||||
return (object)obj1 == null ? (object)obj2 != null : !obj1.Equals (obj2);
|
||||
}
|
||||
|
||||
// fields
|
||||
|
||||
public SoundIODevice Device {
|
||||
get { return new SoundIODevice (Marshal.ReadIntPtr (handle, device_offset)); }
|
||||
}
|
||||
static readonly int device_offset = (int)Marshal.OffsetOf<SoundIoInStream> ("device");
|
||||
|
||||
public SoundIOFormat Format {
|
||||
get { return (SoundIOFormat) Marshal.ReadInt32 (handle, format_offset); }
|
||||
set { Marshal.WriteInt32 (handle, format_offset, (int) value); }
|
||||
}
|
||||
static readonly int format_offset = (int)Marshal.OffsetOf<SoundIoInStream> ("format");
|
||||
|
||||
public int SampleRate {
|
||||
get { return Marshal.ReadInt32 (handle, sample_rate_offset); }
|
||||
set { Marshal.WriteInt32 (handle, sample_rate_offset, value); }
|
||||
}
|
||||
static readonly int sample_rate_offset = (int)Marshal.OffsetOf<SoundIoInStream> ("sample_rate");
|
||||
|
||||
public SoundIOChannelLayout Layout {
|
||||
get { return new SoundIOChannelLayout ((IntPtr) handle + layout_offset); }
|
||||
set {
|
||||
unsafe {
|
||||
Buffer.MemoryCopy ((void*) ((IntPtr) handle + layout_offset), (void*)value.Handle,
|
||||
Marshal.SizeOf<SoundIoChannelLayout> (), Marshal.SizeOf<SoundIoChannelLayout> ());
|
||||
}
|
||||
}
|
||||
}
|
||||
static readonly int layout_offset = (int)Marshal.OffsetOf<SoundIoInStream> ("layout");
|
||||
|
||||
|
||||
public double SoftwareLatency {
|
||||
get { return MarshalEx.ReadDouble (handle, software_latency_offset); }
|
||||
set { MarshalEx.WriteDouble (handle, software_latency_offset, value); }
|
||||
}
|
||||
static readonly int software_latency_offset = (int)Marshal.OffsetOf<SoundIoInStream> ("software_latency");
|
||||
|
||||
// error_callback
|
||||
public Action ErrorCallback {
|
||||
get { return error_callback; }
|
||||
set {
|
||||
error_callback = value;
|
||||
error_callback_native = _ => error_callback ();
|
||||
var ptr = Marshal.GetFunctionPointerForDelegate (error_callback_native);
|
||||
Marshal.WriteIntPtr (handle, error_callback_offset, ptr);
|
||||
}
|
||||
}
|
||||
static readonly int error_callback_offset = (int)Marshal.OffsetOf<SoundIoInStream> ("error_callback");
|
||||
Action error_callback;
|
||||
delegate void error_callback_delegate (IntPtr handle);
|
||||
error_callback_delegate error_callback_native;
|
||||
|
||||
// read_callback
|
||||
public Action<int,int> ReadCallback {
|
||||
get { return read_callback; }
|
||||
set {
|
||||
read_callback = value;
|
||||
read_callback_native = (_, minFrameCount, maxFrameCount) => read_callback (minFrameCount, maxFrameCount);
|
||||
var ptr = Marshal.GetFunctionPointerForDelegate (read_callback_native);
|
||||
Marshal.WriteIntPtr (handle, read_callback_offset, ptr);
|
||||
}
|
||||
}
|
||||
static readonly int read_callback_offset = (int)Marshal.OffsetOf<SoundIoInStream> ("read_callback");
|
||||
Action<int, int> read_callback;
|
||||
delegate void read_callback_delegate (IntPtr handle, int min, int max);
|
||||
read_callback_delegate read_callback_native;
|
||||
|
||||
// overflow_callback
|
||||
public Action OverflowCallback {
|
||||
get { return overflow_callback; }
|
||||
set {
|
||||
overflow_callback = value;
|
||||
overflow_callback_native = _ => overflow_callback ();
|
||||
var ptr = Marshal.GetFunctionPointerForDelegate (overflow_callback_native);
|
||||
Marshal.WriteIntPtr (handle, overflow_callback_offset, ptr);
|
||||
}
|
||||
}
|
||||
static readonly int overflow_callback_offset = (int)Marshal.OffsetOf<SoundIoInStream> ("overflow_callback");
|
||||
Action overflow_callback;
|
||||
delegate void overflow_callback_delegate (IntPtr handle);
|
||||
overflow_callback_delegate overflow_callback_native;
|
||||
|
||||
// FIXME: this should be taken care in more centralized/decent manner... we don't want to write
|
||||
// this kind of code anywhere we need string marshaling.
|
||||
List<IntPtr> allocated_hglobals = new List<IntPtr> ();
|
||||
|
||||
public string Name {
|
||||
get { return Marshal.PtrToStringAnsi (Marshal.ReadIntPtr (handle, name_offset)); }
|
||||
set {
|
||||
unsafe {
|
||||
var existing = Marshal.ReadIntPtr (handle, name_offset);
|
||||
if (allocated_hglobals.Contains (existing)) {
|
||||
allocated_hglobals.Remove (existing);
|
||||
Marshal.FreeHGlobal (existing);
|
||||
}
|
||||
var ptr = Marshal.StringToHGlobalAnsi (value);
|
||||
Marshal.WriteIntPtr (handle, name_offset, ptr);
|
||||
allocated_hglobals.Add (ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
static readonly int name_offset = (int)Marshal.OffsetOf<SoundIoInStream> ("name");
|
||||
|
||||
public bool NonTerminalHint {
|
||||
get { return Marshal.ReadInt32 (handle, non_terminal_hint_offset) != 0; }
|
||||
}
|
||||
static readonly int non_terminal_hint_offset = (int)Marshal.OffsetOf<SoundIoInStream> ("non_terminal_hint");
|
||||
|
||||
public int BytesPerFrame {
|
||||
get { return Marshal.ReadInt32 (handle, bytes_per_frame_offset); }
|
||||
}
|
||||
static readonly int bytes_per_frame_offset = (int)Marshal.OffsetOf<SoundIoInStream> ("bytes_per_frame");
|
||||
|
||||
public int BytesPerSample {
|
||||
get { return Marshal.ReadInt32 (handle, bytes_per_sample_offset); }
|
||||
}
|
||||
static readonly int bytes_per_sample_offset = (int)Marshal.OffsetOf<SoundIoInStream> ("bytes_per_sample");
|
||||
|
||||
public string LayoutErrorMessage {
|
||||
get {
|
||||
var code = (SoundIoError) Marshal.ReadInt32 (handle, layout_error_offset);
|
||||
return code == SoundIoError.SoundIoErrorNone ? null : Marshal.PtrToStringAnsi (Natives.soundio_strerror ((int) code));
|
||||
}
|
||||
}
|
||||
static readonly int layout_error_offset = (int)Marshal.OffsetOf<SoundIoInStream> ("layout_error");
|
||||
|
||||
// functions
|
||||
|
||||
public void Open ()
|
||||
{
|
||||
var ret = (SoundIoError) Natives.soundio_instream_open (handle);
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
}
|
||||
|
||||
public void Start ()
|
||||
{
|
||||
var ret = (SoundIoError)Natives.soundio_instream_start (handle);
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
}
|
||||
|
||||
public SoundIOChannelAreas BeginRead (ref int frameCount)
|
||||
{
|
||||
IntPtr ptrs = default (IntPtr);
|
||||
int nativeFrameCount = frameCount;
|
||||
unsafe {
|
||||
var frameCountPtr = &nativeFrameCount;
|
||||
var ptrptr = &ptrs;
|
||||
var ret = (SoundIoError) Natives.soundio_instream_begin_read (handle, (IntPtr)ptrptr, (IntPtr)frameCountPtr);
|
||||
frameCount = *frameCountPtr;
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
return new SoundIOChannelAreas (ptrs, Layout.ChannelCount, frameCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void EndRead ()
|
||||
{
|
||||
var ret = (SoundIoError) Natives.soundio_instream_end_read (handle);
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
}
|
||||
|
||||
public void Pause (bool pause)
|
||||
{
|
||||
var ret = (SoundIoError) Natives.soundio_instream_pause (handle, pause);
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
}
|
||||
|
||||
public double GetLatency ()
|
||||
{
|
||||
unsafe {
|
||||
double* dptr = null;
|
||||
IntPtr p = new IntPtr (dptr);
|
||||
var ret = (SoundIoError) Natives.soundio_instream_get_latency (handle, p);
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
dptr = (double*) p;
|
||||
return *dptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
241
Ryujinx.Audio/Native/libsoundio/SoundIOOutStream.cs
Normal file
241
Ryujinx.Audio/Native/libsoundio/SoundIOOutStream.cs
Normal file
|
@ -0,0 +1,241 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public class SoundIOOutStream : IDisposable
|
||||
{
|
||||
internal SoundIOOutStream (Pointer<SoundIoOutStream> handle)
|
||||
{
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
Pointer<SoundIoOutStream> handle;
|
||||
|
||||
public void Dispose ()
|
||||
{
|
||||
Natives.soundio_outstream_destroy (handle);
|
||||
}
|
||||
// Equality (based on handle)
|
||||
|
||||
public override bool Equals (object other)
|
||||
{
|
||||
var d = other as SoundIOOutStream;
|
||||
return d != null && (this.handle == d.handle);
|
||||
}
|
||||
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
return (int)(IntPtr)handle;
|
||||
}
|
||||
|
||||
public static bool operator == (SoundIOOutStream obj1, SoundIOOutStream obj2)
|
||||
{
|
||||
return (object)obj1 == null ? (object)obj2 == null : obj1.Equals (obj2);
|
||||
}
|
||||
|
||||
public static bool operator != (SoundIOOutStream obj1, SoundIOOutStream obj2)
|
||||
{
|
||||
return (object)obj1 == null ? (object)obj2 != null : !obj1.Equals (obj2);
|
||||
}
|
||||
|
||||
// fields
|
||||
|
||||
public SoundIODevice Device {
|
||||
get { return new SoundIODevice (Marshal.ReadIntPtr (handle, device_offset)); }
|
||||
}
|
||||
static readonly int device_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("device");
|
||||
|
||||
public SoundIOFormat Format {
|
||||
get { return (SoundIOFormat) Marshal.ReadInt32 (handle, format_offset); }
|
||||
set { Marshal.WriteInt32 (handle, format_offset, (int) value); }
|
||||
}
|
||||
static readonly int format_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("format");
|
||||
|
||||
public int SampleRate {
|
||||
get { return Marshal.ReadInt32 (handle, sample_rate_offset); }
|
||||
set { Marshal.WriteInt32 (handle, sample_rate_offset, value); }
|
||||
}
|
||||
static readonly int sample_rate_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("sample_rate");
|
||||
|
||||
|
||||
public SoundIOChannelLayout Layout {
|
||||
get { unsafe { return new SoundIOChannelLayout ((IntPtr) ((void*) ((IntPtr) handle + layout_offset))); } }
|
||||
set {
|
||||
unsafe {
|
||||
Buffer.MemoryCopy ((void*)((IntPtr)handle + layout_offset), (void*)value.Handle,
|
||||
Marshal.SizeOf<SoundIoChannelLayout> (), Marshal.SizeOf<SoundIoChannelLayout> ());
|
||||
}
|
||||
}
|
||||
}
|
||||
static readonly int layout_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("layout");
|
||||
|
||||
public double SoftwareLatency {
|
||||
get { return MarshalEx.ReadDouble (handle, software_latency_offset); }
|
||||
set { MarshalEx.WriteDouble (handle, software_latency_offset, value); }
|
||||
}
|
||||
static readonly int software_latency_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("software_latency");
|
||||
|
||||
// error_callback
|
||||
public Action ErrorCallback {
|
||||
get { return error_callback; }
|
||||
set {
|
||||
error_callback = value;
|
||||
if (value == null)
|
||||
error_callback_native = null;
|
||||
else
|
||||
error_callback_native = stream => error_callback ();
|
||||
var ptr = Marshal.GetFunctionPointerForDelegate (error_callback_native);
|
||||
Marshal.WriteIntPtr (handle, error_callback_offset, ptr);
|
||||
}
|
||||
}
|
||||
static readonly int error_callback_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("error_callback");
|
||||
Action error_callback;
|
||||
delegate void error_callback_delegate (IntPtr handle);
|
||||
error_callback_delegate error_callback_native;
|
||||
|
||||
// write_callback
|
||||
public Action<int, int> WriteCallback {
|
||||
get { return write_callback; }
|
||||
set {
|
||||
write_callback = value;
|
||||
if (value == null)
|
||||
write_callback_native = null;
|
||||
else
|
||||
write_callback_native = (h, frame_count_min, frame_count_max) => write_callback (frame_count_min, frame_count_max);
|
||||
var ptr = Marshal.GetFunctionPointerForDelegate (write_callback_native);
|
||||
Marshal.WriteIntPtr (handle, write_callback_offset, ptr);
|
||||
}
|
||||
}
|
||||
static readonly int write_callback_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("write_callback");
|
||||
Action<int, int> write_callback;
|
||||
delegate void write_callback_delegate (IntPtr handle, int min, int max);
|
||||
write_callback_delegate write_callback_native;
|
||||
|
||||
// underflow_callback
|
||||
public Action UnderflowCallback {
|
||||
get { return underflow_callback; }
|
||||
set {
|
||||
underflow_callback = value;
|
||||
if (value == null)
|
||||
underflow_callback_native = null;
|
||||
else
|
||||
underflow_callback_native = h => underflow_callback ();
|
||||
var ptr = Marshal.GetFunctionPointerForDelegate (underflow_callback_native);
|
||||
Marshal.WriteIntPtr (handle, underflow_callback_offset, ptr);
|
||||
}
|
||||
}
|
||||
static readonly int underflow_callback_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("underflow_callback");
|
||||
Action underflow_callback;
|
||||
delegate void underflow_callback_delegate (IntPtr handle);
|
||||
underflow_callback_delegate underflow_callback_native;
|
||||
|
||||
// FIXME: this should be taken care in more centralized/decent manner... we don't want to write
|
||||
// this kind of code anywhere we need string marshaling.
|
||||
List<IntPtr> allocated_hglobals = new List<IntPtr> ();
|
||||
|
||||
public string Name {
|
||||
get { return Marshal.PtrToStringAnsi (Marshal.ReadIntPtr (handle, name_offset)); }
|
||||
set {
|
||||
unsafe {
|
||||
var existing = Marshal.ReadIntPtr (handle, name_offset);
|
||||
if (allocated_hglobals.Contains (existing)) {
|
||||
allocated_hglobals.Remove (existing);
|
||||
Marshal.FreeHGlobal (existing);
|
||||
}
|
||||
var ptr = Marshal.StringToHGlobalAnsi (value);
|
||||
Marshal.WriteIntPtr (handle, name_offset, ptr);
|
||||
allocated_hglobals.Add (ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
static readonly int name_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("name");
|
||||
|
||||
public bool NonTerminalHint {
|
||||
get { return Marshal.ReadInt32 (handle, non_terminal_hint_offset) != 0; }
|
||||
}
|
||||
static readonly int non_terminal_hint_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("non_terminal_hint");
|
||||
|
||||
public int BytesPerFrame {
|
||||
get { return Marshal.ReadInt32 (handle, bytes_per_frame_offset); }
|
||||
}
|
||||
static readonly int bytes_per_frame_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("bytes_per_frame");
|
||||
|
||||
public int BytesPerSample {
|
||||
get { return Marshal.ReadInt32 (handle, bytes_per_sample_offset); }
|
||||
}
|
||||
static readonly int bytes_per_sample_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("bytes_per_sample");
|
||||
|
||||
public string LayoutErrorMessage {
|
||||
get {
|
||||
var code = (SoundIoError) Marshal.ReadInt32 (handle, layout_error_offset);
|
||||
return code == SoundIoError.SoundIoErrorNone ? null : Marshal.PtrToStringAnsi (Natives.soundio_strerror ((int) code));
|
||||
}
|
||||
}
|
||||
static readonly int layout_error_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("layout_error");
|
||||
|
||||
// functions
|
||||
|
||||
public void Open ()
|
||||
{
|
||||
var ret = (SoundIoError) Natives.soundio_outstream_open (handle);
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
}
|
||||
|
||||
public void Start ()
|
||||
{
|
||||
var ret = (SoundIoError)Natives.soundio_outstream_start (handle);
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
}
|
||||
|
||||
public SoundIOChannelAreas BeginWrite (ref int frameCount)
|
||||
{
|
||||
IntPtr ptrs = default (IntPtr);
|
||||
int nativeFrameCount = frameCount;
|
||||
unsafe {
|
||||
var frameCountPtr = &nativeFrameCount;
|
||||
var ptrptr = &ptrs;
|
||||
var ret = (SoundIoError)Natives.soundio_outstream_begin_write (handle, (IntPtr) ptrptr, (IntPtr) frameCountPtr);
|
||||
frameCount = *frameCountPtr;
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
return new SoundIOChannelAreas (ptrs, Layout.ChannelCount, frameCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void EndWrite ()
|
||||
{
|
||||
var ret = (SoundIoError) Natives.soundio_outstream_end_write (handle);
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
}
|
||||
|
||||
public void ClearBuffer ()
|
||||
{
|
||||
Natives.soundio_outstream_clear_buffer (handle);
|
||||
}
|
||||
|
||||
public void Pause (bool pause)
|
||||
{
|
||||
var ret = (SoundIoError) Natives.soundio_outstream_pause (handle, pause);
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
}
|
||||
|
||||
public double GetLatency ()
|
||||
{
|
||||
unsafe {
|
||||
double* dptr = null;
|
||||
IntPtr p = new IntPtr (dptr);
|
||||
var ret = (SoundIoError) Natives.soundio_outstream_get_latency (handle, p);
|
||||
if (ret != SoundIoError.SoundIoErrorNone)
|
||||
throw new SoundIOException (ret);
|
||||
dptr = (double*) p;
|
||||
return *dptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
Ryujinx.Audio/Native/libsoundio/SoundIORingBuffer.cs
Normal file
61
Ryujinx.Audio/Native/libsoundio/SoundIORingBuffer.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public class SoundIORingBuffer : IDisposable
|
||||
{
|
||||
internal SoundIORingBuffer (IntPtr handle)
|
||||
{
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
IntPtr handle;
|
||||
|
||||
public int Capacity {
|
||||
get { return Natives.soundio_ring_buffer_capacity (handle); }
|
||||
}
|
||||
|
||||
public void Clear ()
|
||||
{
|
||||
Natives.soundio_ring_buffer_clear (handle);
|
||||
}
|
||||
|
||||
public void Dispose ()
|
||||
{
|
||||
Natives.soundio_ring_buffer_destroy (handle);
|
||||
}
|
||||
|
||||
public int FillCount {
|
||||
get {
|
||||
return Natives.soundio_ring_buffer_fill_count (handle);
|
||||
}
|
||||
}
|
||||
|
||||
public int FreeCount {
|
||||
get {
|
||||
return Natives.soundio_ring_buffer_free_count (handle);
|
||||
}
|
||||
}
|
||||
|
||||
public IntPtr ReadPointer {
|
||||
get {
|
||||
return Natives.soundio_ring_buffer_read_ptr (handle);
|
||||
}
|
||||
}
|
||||
|
||||
public IntPtr WritePointer {
|
||||
get {
|
||||
return Natives.soundio_ring_buffer_write_ptr (handle);
|
||||
}
|
||||
}
|
||||
|
||||
public void AdvanceReadPointer (int count)
|
||||
{
|
||||
Natives.soundio_ring_buffer_advance_read_ptr (handle, count);
|
||||
}
|
||||
|
||||
public void AdvanceWritePointer (int count)
|
||||
{
|
||||
Natives.soundio_ring_buffer_advance_write_ptr (handle, count);
|
||||
}
|
||||
}
|
||||
}
|
15
Ryujinx.Audio/Native/libsoundio/SoundIOSampleRateRange.cs
Normal file
15
Ryujinx.Audio/Native/libsoundio/SoundIOSampleRateRange.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public struct SoundIOSampleRateRange
|
||||
{
|
||||
internal SoundIOSampleRateRange (int min, int max)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
}
|
||||
|
||||
public readonly int Min;
|
||||
public readonly int Max;
|
||||
}
|
||||
}
|
BIN
Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dll
Normal file
BIN
Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dll
Normal file
Binary file not shown.
BIN
Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dylib
Normal file
BIN
Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dylib
Normal file
Binary file not shown.
BIN
Ryujinx.Audio/Native/libsoundio/libs/libsoundio.so
Normal file
BIN
Ryujinx.Audio/Native/libsoundio/libs/libsoundio.so
Normal file
Binary file not shown.
638
Ryujinx.Audio/Native/libsoundio/libsoundio-interop.cs
Normal file
638
Ryujinx.Audio/Native/libsoundio/libsoundio-interop.cs
Normal file
|
@ -0,0 +1,638 @@
|
|||
// This source file is generated by nclang PInvokeGenerator.
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using delegate0 = SoundIOSharp.Delegates.delegate0;
|
||||
using delegate1 = SoundIOSharp.Delegates.delegate1;
|
||||
using delegate2 = SoundIOSharp.Delegates.delegate2;
|
||||
using delegate3 = SoundIOSharp.Delegates.delegate3;
|
||||
using delegate4 = SoundIOSharp.Delegates.delegate4;
|
||||
using delegate5 = SoundIOSharp.Delegates.delegate5;
|
||||
using delegate6 = SoundIOSharp.Delegates.delegate6;
|
||||
using delegate7 = SoundIOSharp.Delegates.delegate7;
|
||||
using delegate8 = SoundIOSharp.Delegates.delegate8;
|
||||
using delegate9 = SoundIOSharp.Delegates.delegate9;
|
||||
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
enum SoundIoError // soundio.h (72, 6)
|
||||
{
|
||||
SoundIoErrorNone = 0,
|
||||
SoundIoErrorNoMem = 1,
|
||||
SoundIoErrorInitAudioBackend = 2,
|
||||
SoundIoErrorSystemResources = 3,
|
||||
SoundIoErrorOpeningDevice = 4,
|
||||
SoundIoErrorNoSuchDevice = 5,
|
||||
SoundIoErrorInvalid = 6,
|
||||
SoundIoErrorBackendUnavailable = 7,
|
||||
SoundIoErrorStreaming = 8,
|
||||
SoundIoErrorIncompatibleDevice = 9,
|
||||
SoundIoErrorNoSuchClient = 10,
|
||||
SoundIoErrorIncompatibleBackend = 11,
|
||||
SoundIoErrorBackendDisconnected = 12,
|
||||
SoundIoErrorInterrupted = 13,
|
||||
SoundIoErrorUnderflow = 14,
|
||||
SoundIoErrorEncodingString = 15,
|
||||
}
|
||||
|
||||
enum SoundIoChannelId // soundio.h (106, 6)
|
||||
{
|
||||
SoundIoChannelIdInvalid = 0,
|
||||
SoundIoChannelIdFrontLeft = 1,
|
||||
SoundIoChannelIdFrontRight = 2,
|
||||
SoundIoChannelIdFrontCenter = 3,
|
||||
SoundIoChannelIdLfe = 4,
|
||||
SoundIoChannelIdBackLeft = 5,
|
||||
SoundIoChannelIdBackRight = 6,
|
||||
SoundIoChannelIdFrontLeftCenter = 7,
|
||||
SoundIoChannelIdFrontRightCenter = 8,
|
||||
SoundIoChannelIdBackCenter = 9,
|
||||
SoundIoChannelIdSideLeft = 10,
|
||||
SoundIoChannelIdSideRight = 11,
|
||||
SoundIoChannelIdTopCenter = 12,
|
||||
SoundIoChannelIdTopFrontLeft = 13,
|
||||
SoundIoChannelIdTopFrontCenter = 14,
|
||||
SoundIoChannelIdTopFrontRight = 15,
|
||||
SoundIoChannelIdTopBackLeft = 16,
|
||||
SoundIoChannelIdTopBackCenter = 17,
|
||||
SoundIoChannelIdTopBackRight = 18,
|
||||
SoundIoChannelIdBackLeftCenter = 19,
|
||||
SoundIoChannelIdBackRightCenter = 20,
|
||||
SoundIoChannelIdFrontLeftWide = 21,
|
||||
SoundIoChannelIdFrontRightWide = 22,
|
||||
SoundIoChannelIdFrontLeftHigh = 23,
|
||||
SoundIoChannelIdFrontCenterHigh = 24,
|
||||
SoundIoChannelIdFrontRightHigh = 25,
|
||||
SoundIoChannelIdTopFrontLeftCenter = 26,
|
||||
SoundIoChannelIdTopFrontRightCenter = 27,
|
||||
SoundIoChannelIdTopSideLeft = 28,
|
||||
SoundIoChannelIdTopSideRight = 29,
|
||||
SoundIoChannelIdLeftLfe = 30,
|
||||
SoundIoChannelIdRightLfe = 31,
|
||||
SoundIoChannelIdLfe2 = 32,
|
||||
SoundIoChannelIdBottomCenter = 33,
|
||||
SoundIoChannelIdBottomLeftCenter = 34,
|
||||
SoundIoChannelIdBottomRightCenter = 35,
|
||||
SoundIoChannelIdMsMid = 36,
|
||||
SoundIoChannelIdMsSide = 37,
|
||||
SoundIoChannelIdAmbisonicW = 38,
|
||||
SoundIoChannelIdAmbisonicX = 39,
|
||||
SoundIoChannelIdAmbisonicY = 40,
|
||||
SoundIoChannelIdAmbisonicZ = 41,
|
||||
SoundIoChannelIdXyX = 42,
|
||||
SoundIoChannelIdXyY = 43,
|
||||
SoundIoChannelIdHeadphonesLeft = 44,
|
||||
SoundIoChannelIdHeadphonesRight = 45,
|
||||
SoundIoChannelIdClickTrack = 46,
|
||||
SoundIoChannelIdForeignLanguage = 47,
|
||||
SoundIoChannelIdHearingImpaired = 48,
|
||||
SoundIoChannelIdNarration = 49,
|
||||
SoundIoChannelIdHaptic = 50,
|
||||
SoundIoChannelIdDialogCentricMix = 51,
|
||||
SoundIoChannelIdAux = 52,
|
||||
SoundIoChannelIdAux0 = 53,
|
||||
SoundIoChannelIdAux1 = 54,
|
||||
SoundIoChannelIdAux2 = 55,
|
||||
SoundIoChannelIdAux3 = 56,
|
||||
SoundIoChannelIdAux4 = 57,
|
||||
SoundIoChannelIdAux5 = 58,
|
||||
SoundIoChannelIdAux6 = 59,
|
||||
SoundIoChannelIdAux7 = 60,
|
||||
SoundIoChannelIdAux8 = 61,
|
||||
SoundIoChannelIdAux9 = 62,
|
||||
SoundIoChannelIdAux10 = 63,
|
||||
SoundIoChannelIdAux11 = 64,
|
||||
SoundIoChannelIdAux12 = 65,
|
||||
SoundIoChannelIdAux13 = 66,
|
||||
SoundIoChannelIdAux14 = 67,
|
||||
SoundIoChannelIdAux15 = 68,
|
||||
}
|
||||
|
||||
enum SoundIoChannelLayoutId // soundio.h (189, 6)
|
||||
{
|
||||
SoundIoChannelLayoutIdMono = 0,
|
||||
SoundIoChannelLayoutIdStereo = 1,
|
||||
SoundIoChannelLayoutId2Point1 = 2,
|
||||
SoundIoChannelLayoutId3Point0 = 3,
|
||||
SoundIoChannelLayoutId3Point0Back = 4,
|
||||
SoundIoChannelLayoutId3Point1 = 5,
|
||||
SoundIoChannelLayoutId4Point0 = 6,
|
||||
SoundIoChannelLayoutIdQuad = 7,
|
||||
SoundIoChannelLayoutIdQuadSide = 8,
|
||||
SoundIoChannelLayoutId4Point1 = 9,
|
||||
SoundIoChannelLayoutId5Point0Back = 10,
|
||||
SoundIoChannelLayoutId5Point0Side = 11,
|
||||
SoundIoChannelLayoutId5Point1 = 12,
|
||||
SoundIoChannelLayoutId5Point1Back = 13,
|
||||
SoundIoChannelLayoutId6Point0Side = 14,
|
||||
SoundIoChannelLayoutId6Point0Front = 15,
|
||||
SoundIoChannelLayoutIdHexagonal = 16,
|
||||
SoundIoChannelLayoutId6Point1 = 17,
|
||||
SoundIoChannelLayoutId6Point1Back = 18,
|
||||
SoundIoChannelLayoutId6Point1Front = 19,
|
||||
SoundIoChannelLayoutId7Point0 = 20,
|
||||
SoundIoChannelLayoutId7Point0Front = 21,
|
||||
SoundIoChannelLayoutId7Point1 = 22,
|
||||
SoundIoChannelLayoutId7Point1Wide = 23,
|
||||
SoundIoChannelLayoutId7Point1WideBack = 24,
|
||||
SoundIoChannelLayoutIdOctagonal = 25,
|
||||
}
|
||||
|
||||
enum SoundIoBackend // soundio.h (218, 6)
|
||||
{
|
||||
SoundIoBackendNone = 0,
|
||||
SoundIoBackendJack = 1,
|
||||
SoundIoBackendPulseAudio = 2,
|
||||
SoundIoBackendAlsa = 3,
|
||||
SoundIoBackendCoreAudio = 4,
|
||||
SoundIoBackendWasapi = 5,
|
||||
SoundIoBackendDummy = 6,
|
||||
}
|
||||
|
||||
enum SoundIoDeviceAim // soundio.h (228, 6)
|
||||
{
|
||||
SoundIoDeviceAimInput = 0,
|
||||
SoundIoDeviceAimOutput = 1,
|
||||
}
|
||||
|
||||
enum SoundIoFormat // soundio.h (235, 6)
|
||||
{
|
||||
SoundIoFormatInvalid = 0,
|
||||
SoundIoFormatS8 = 1,
|
||||
SoundIoFormatU8 = 2,
|
||||
SoundIoFormatS16LE = 3,
|
||||
SoundIoFormatS16BE = 4,
|
||||
SoundIoFormatU16LE = 5,
|
||||
SoundIoFormatU16BE = 6,
|
||||
SoundIoFormatS24LE = 7,
|
||||
SoundIoFormatS24BE = 8,
|
||||
SoundIoFormatU24LE = 9,
|
||||
SoundIoFormatU24BE = 10,
|
||||
SoundIoFormatS32LE = 11,
|
||||
SoundIoFormatS32BE = 12,
|
||||
SoundIoFormatU32LE = 13,
|
||||
SoundIoFormatU32BE = 14,
|
||||
SoundIoFormatFloat32LE = 15,
|
||||
SoundIoFormatFloat32BE = 16,
|
||||
SoundIoFormatFloat64LE = 17,
|
||||
SoundIoFormatFloat64BE = 18,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct SoundIoChannelLayout // soundio.h (302, 8)
|
||||
{
|
||||
[CTypeDetails("Pointer<byte>")] public System.IntPtr @name;
|
||||
public int @channel_count;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)]
|
||||
[CTypeDetails("ConstArrayOf<SoundIoChannelId>")] public SoundIoChannelId[] @channels;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct SoundIoSampleRateRange // soundio.h (309, 8)
|
||||
{
|
||||
public int @min;
|
||||
public int @max;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct SoundIoChannelArea // soundio.h (315, 8)
|
||||
{
|
||||
[CTypeDetails("Pointer<byte>")] public System.IntPtr @ptr;
|
||||
public int @step;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct SoundIo // soundio.h (324, 8)
|
||||
{
|
||||
[CTypeDetails("Pointer<void>")] public System.IntPtr @userdata;
|
||||
[CTypeDetails("Pointer<void (SoundIo *)>")] public delegate0 @on_devices_change;
|
||||
[CTypeDetails("Pointer<void (SoundIo *, int)>")] public delegate1 @on_backend_disconnect;
|
||||
[CTypeDetails("Pointer<void (SoundIo *)>")] public Delegates.delegate0 @on_events_signal;
|
||||
public SoundIoBackend @current_backend;
|
||||
[CTypeDetails("Pointer<byte>")] public System.IntPtr @app_name;
|
||||
[CTypeDetails("Pointer<void ()>")] public delegate2 @emit_rtprio_warning;
|
||||
[CTypeDetails("Pointer<void (const char *)>")] public delegate3 @jack_info_callback;
|
||||
[CTypeDetails("Pointer<void (const char *)>")] public Delegates.delegate3 @jack_error_callback;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct SoundIoDevice // soundio.h (383, 8)
|
||||
{
|
||||
[CTypeDetails("Pointer<SoundIo>")] public System.IntPtr @soundio;
|
||||
[CTypeDetails("Pointer<byte>")] public System.IntPtr @id;
|
||||
[CTypeDetails("Pointer<byte>")] public System.IntPtr @name;
|
||||
public SoundIoDeviceAim @aim;
|
||||
[CTypeDetails("Pointer<SoundIoChannelLayout>")] public System.IntPtr @layouts;
|
||||
public int @layout_count;
|
||||
public SoundIoChannelLayout @current_layout;
|
||||
[CTypeDetails("Pointer<SoundIoFormat>")] public System.IntPtr @formats;
|
||||
public int @format_count;
|
||||
public SoundIoFormat @current_format;
|
||||
[CTypeDetails("Pointer<SoundIoSampleRateRange>")] public System.IntPtr @sample_rates;
|
||||
public int @sample_rate_count;
|
||||
public int @sample_rate_current;
|
||||
public double @software_latency_min;
|
||||
public double @software_latency_max;
|
||||
public double @software_latency_current;
|
||||
public bool @is_raw;
|
||||
public int @ref_count;
|
||||
public int @probe_error;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct SoundIoOutStream // soundio.h (493, 8)
|
||||
{
|
||||
[CTypeDetails("Pointer<SoundIoDevice>")] public System.IntPtr @device;
|
||||
public SoundIoFormat @format;
|
||||
public int @sample_rate;
|
||||
public SoundIoChannelLayout @layout;
|
||||
public double @software_latency;
|
||||
[CTypeDetails("Pointer<void>")] public System.IntPtr @userdata;
|
||||
[CTypeDetails("Pointer<void (SoundIoOutStream *, int, int)>")] public delegate4 @write_callback;
|
||||
[CTypeDetails("Pointer<void (SoundIoOutStream *)>")] public delegate5 @underflow_callback;
|
||||
[CTypeDetails("Pointer<void (SoundIoOutStream *, int)>")] public delegate6 @error_callback;
|
||||
[CTypeDetails("Pointer<byte>")] public System.IntPtr @name;
|
||||
public bool @non_terminal_hint;
|
||||
public int @bytes_per_frame;
|
||||
public int @bytes_per_sample;
|
||||
public int @layout_error;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct SoundIoInStream // soundio.h (595, 8)
|
||||
{
|
||||
[CTypeDetails("Pointer<SoundIoDevice>")] public System.IntPtr @device;
|
||||
public SoundIoFormat @format;
|
||||
public int @sample_rate;
|
||||
public SoundIoChannelLayout @layout;
|
||||
public double @software_latency;
|
||||
[CTypeDetails("Pointer<void>")] public System.IntPtr @userdata;
|
||||
[CTypeDetails("Pointer<void (SoundIoInStream *, int, int)>")] public delegate7 @read_callback;
|
||||
[CTypeDetails("Pointer<void (SoundIoInStream *)>")] public delegate8 @overflow_callback;
|
||||
[CTypeDetails("Pointer<void (SoundIoInStream *, int)>")] public delegate9 @error_callback;
|
||||
[CTypeDetails("Pointer<byte>")] public System.IntPtr @name;
|
||||
public bool @non_terminal_hint;
|
||||
public int @bytes_per_frame;
|
||||
public int @bytes_per_sample;
|
||||
public int @layout_error;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct SoundIoRingBuffer // soundio.h (1167, 8)
|
||||
{
|
||||
}
|
||||
|
||||
partial class Natives
|
||||
{
|
||||
const string LibraryName = "libsoundio";
|
||||
// function soundio_version_string - soundio.h (677, 28)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_version_string();
|
||||
|
||||
// function soundio_version_major - soundio.h (679, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_version_major();
|
||||
|
||||
// function soundio_version_minor - soundio.h (681, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_version_minor();
|
||||
|
||||
// function soundio_version_patch - soundio.h (683, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_version_patch();
|
||||
|
||||
// function soundio_create - soundio.h (689, 32)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_create();
|
||||
|
||||
// function soundio_destroy - soundio.h (690, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_destroy([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
|
||||
|
||||
// function soundio_connect - soundio.h (700, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_connect([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
|
||||
|
||||
// function soundio_connect_backend - soundio.h (712, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_connect_backend([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio, SoundIoBackend @backend);
|
||||
|
||||
// function soundio_disconnect - soundio.h (713, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_disconnect([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
|
||||
|
||||
// function soundio_strerror - soundio.h (716, 28)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_strerror(int @error);
|
||||
|
||||
// function soundio_backend_name - soundio.h (718, 28)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_backend_name(SoundIoBackend @backend);
|
||||
|
||||
// function soundio_backend_count - soundio.h (721, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_backend_count([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
|
||||
|
||||
// function soundio_get_backend - soundio.h (724, 36)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern SoundIoBackend soundio_get_backend([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio, int @index);
|
||||
|
||||
// function soundio_have_backend - soundio.h (727, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern bool soundio_have_backend(SoundIoBackend @backend);
|
||||
|
||||
// function soundio_flush_events - soundio.h (751, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_flush_events([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
|
||||
|
||||
// function soundio_wait_events - soundio.h (755, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_wait_events([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
|
||||
|
||||
// function soundio_wakeup - soundio.h (758, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_wakeup([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
|
||||
|
||||
// function soundio_force_device_scan - soundio.h (775, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_force_device_scan([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
|
||||
|
||||
// function soundio_channel_layout_equal - soundio.h (782, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern bool soundio_channel_layout_equal([CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @a, [CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @b);
|
||||
|
||||
// function soundio_get_channel_name - soundio.h (786, 28)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_get_channel_name(SoundIoChannelId @id);
|
||||
|
||||
// function soundio_parse_channel_id - soundio.h (790, 38)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern SoundIoChannelId soundio_parse_channel_id([CTypeDetails("Pointer<byte>")]System.IntPtr @str, int @str_len);
|
||||
|
||||
// function soundio_channel_layout_builtin_count - soundio.h (793, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_channel_layout_builtin_count();
|
||||
|
||||
// function soundio_channel_layout_get_builtin - soundio.h (798, 51)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_channel_layout_get_builtin(int @index);
|
||||
|
||||
// function soundio_channel_layout_get_default - soundio.h (801, 51)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_channel_layout_get_default(int @channel_count);
|
||||
|
||||
// function soundio_channel_layout_find_channel - soundio.h (804, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_channel_layout_find_channel([CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @layout, SoundIoChannelId @channel);
|
||||
|
||||
// function soundio_channel_layout_detect_builtin - soundio.h (809, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern bool soundio_channel_layout_detect_builtin([CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @layout);
|
||||
|
||||
// function soundio_best_matching_channel_layout - soundio.h (814, 51)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_best_matching_channel_layout([CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @preferred_layouts, int @preferred_layout_count, [CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @available_layouts, int @available_layout_count);
|
||||
|
||||
// function soundio_sort_channel_layouts - soundio.h (819, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_sort_channel_layouts([CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @layouts, int @layout_count);
|
||||
|
||||
// function soundio_get_bytes_per_sample - soundio.h (825, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_get_bytes_per_sample(SoundIoFormat @format);
|
||||
|
||||
// function soundio_get_bytes_per_frame - soundio.h (828, 19)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_get_bytes_per_frame(SoundIoFormat @format, int @channel_count);
|
||||
|
||||
// function soundio_get_bytes_per_second - soundio.h (833, 19)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_get_bytes_per_second(SoundIoFormat @format, int @channel_count, int @sample_rate);
|
||||
|
||||
// function soundio_format_string - soundio.h (840, 29)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_format_string(SoundIoFormat @format);
|
||||
|
||||
// function soundio_input_device_count - soundio.h (856, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_input_device_count([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
|
||||
|
||||
// function soundio_output_device_count - soundio.h (859, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_output_device_count([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
|
||||
|
||||
// function soundio_get_input_device - soundio.h (865, 38)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_get_input_device([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio, int @index);
|
||||
|
||||
// function soundio_get_output_device - soundio.h (870, 38)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_get_output_device([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio, int @index);
|
||||
|
||||
// function soundio_default_input_device_index - soundio.h (875, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_default_input_device_index([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
|
||||
|
||||
// function soundio_default_output_device_index - soundio.h (880, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_default_output_device_index([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
|
||||
|
||||
// function soundio_device_ref - soundio.h (883, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_device_ref([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device);
|
||||
|
||||
// function soundio_device_unref - soundio.h (886, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_device_unref([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device);
|
||||
|
||||
// function soundio_device_equal - soundio.h (890, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern bool soundio_device_equal([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @a, [CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @b);
|
||||
|
||||
// function soundio_device_sort_channel_layouts - soundio.h (895, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_device_sort_channel_layouts([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device);
|
||||
|
||||
// function soundio_device_supports_format - soundio.h (899, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern bool soundio_device_supports_format([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device, SoundIoFormat @format);
|
||||
|
||||
// function soundio_device_supports_layout - soundio.h (904, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern bool soundio_device_supports_layout([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device, [CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @layout);
|
||||
|
||||
// function soundio_device_supports_sample_rate - soundio.h (909, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern bool soundio_device_supports_sample_rate([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device, int @sample_rate);
|
||||
|
||||
// function soundio_device_nearest_sample_rate - soundio.h (914, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_device_nearest_sample_rate([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device, int @sample_rate);
|
||||
|
||||
// function soundio_outstream_create - soundio.h (924, 41)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_outstream_create([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device);
|
||||
|
||||
// function soundio_outstream_destroy - soundio.h (926, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_outstream_destroy([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream);
|
||||
|
||||
// function soundio_outstream_open - soundio.h (950, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_outstream_open([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream);
|
||||
|
||||
// function soundio_outstream_start - soundio.h (961, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_outstream_start([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream);
|
||||
|
||||
// function soundio_outstream_begin_write - soundio.h (993, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_outstream_begin_write([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream, [CTypeDetails("Pointer<System.IntPtr>")]System.IntPtr @areas, [CTypeDetails("Pointer<int>")]System.IntPtr @frame_count);
|
||||
|
||||
// function soundio_outstream_end_write - soundio.h (1005, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_outstream_end_write([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream);
|
||||
|
||||
// function soundio_outstream_clear_buffer - soundio.h (1020, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_outstream_clear_buffer([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream);
|
||||
|
||||
// function soundio_outstream_pause - soundio.h (1041, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_outstream_pause([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream, bool @pause);
|
||||
|
||||
// function soundio_outstream_get_latency - soundio.h (1054, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_outstream_get_latency([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream, [CTypeDetails("Pointer<double>")]System.IntPtr @out_latency);
|
||||
|
||||
// function soundio_instream_create - soundio.h (1064, 40)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_instream_create([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device);
|
||||
|
||||
// function soundio_instream_destroy - soundio.h (1066, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_instream_destroy([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream);
|
||||
|
||||
// function soundio_instream_open - soundio.h (1086, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_instream_open([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream);
|
||||
|
||||
// function soundio_instream_start - soundio.h (1095, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_instream_start([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream);
|
||||
|
||||
// function soundio_instream_begin_read - soundio.h (1126, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_instream_begin_read([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream, [CTypeDetails("Pointer<System.IntPtr>")]System.IntPtr @areas, [CTypeDetails("Pointer<int>")]System.IntPtr @frame_count);
|
||||
|
||||
// function soundio_instream_end_read - soundio.h (1136, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_instream_end_read([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream);
|
||||
|
||||
// function soundio_instream_pause - soundio.h (1149, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_instream_pause([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream, bool @pause);
|
||||
|
||||
// function soundio_instream_get_latency - soundio.h (1159, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_instream_get_latency([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream, [CTypeDetails("Pointer<double>")]System.IntPtr @out_latency);
|
||||
|
||||
// function soundio_ring_buffer_create - soundio.h (1173, 42)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_ring_buffer_create([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio, int @requested_capacity);
|
||||
|
||||
// function soundio_ring_buffer_destroy - soundio.h (1174, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_ring_buffer_destroy([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
|
||||
|
||||
// function soundio_ring_buffer_capacity - soundio.h (1178, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_ring_buffer_capacity([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
|
||||
|
||||
// function soundio_ring_buffer_write_ptr - soundio.h (1181, 22)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_ring_buffer_write_ptr([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
|
||||
|
||||
// function soundio_ring_buffer_advance_write_ptr - soundio.h (1183, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_ring_buffer_advance_write_ptr([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer, int @count);
|
||||
|
||||
// function soundio_ring_buffer_read_ptr - soundio.h (1186, 22)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern System.IntPtr soundio_ring_buffer_read_ptr([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
|
||||
|
||||
// function soundio_ring_buffer_advance_read_ptr - soundio.h (1188, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_ring_buffer_advance_read_ptr([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer, int @count);
|
||||
|
||||
// function soundio_ring_buffer_fill_count - soundio.h (1191, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_ring_buffer_fill_count([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
|
||||
|
||||
// function soundio_ring_buffer_free_count - soundio.h (1194, 20)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern int soundio_ring_buffer_free_count([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
|
||||
|
||||
// function soundio_ring_buffer_clear - soundio.h (1197, 21)
|
||||
[DllImport(LibraryName)]
|
||||
internal static extern void soundio_ring_buffer_clear([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
|
||||
|
||||
}
|
||||
|
||||
class Delegates
|
||||
{
|
||||
public delegate void delegate0(System.IntPtr p0);
|
||||
public delegate void delegate1(System.IntPtr p0, int p1);
|
||||
public delegate void delegate2();
|
||||
public delegate void delegate3(System.IntPtr p0);
|
||||
public delegate void delegate4(System.IntPtr p0, int p1, int p2);
|
||||
public delegate void delegate5(System.IntPtr p0);
|
||||
public delegate void delegate6(System.IntPtr p0, int p1);
|
||||
public delegate void delegate7(System.IntPtr p0, int p1, int p2);
|
||||
public delegate void delegate8(System.IntPtr p0);
|
||||
public delegate void delegate9(System.IntPtr p0, int p1);
|
||||
}
|
||||
|
||||
public struct Pointer<T>
|
||||
{
|
||||
public IntPtr Handle;
|
||||
public static implicit operator IntPtr(Pointer<T> value) { return value.Handle; }
|
||||
public static implicit operator Pointer<T>(IntPtr value) { return new Pointer<T>(value); }
|
||||
|
||||
public Pointer(IntPtr handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Pointer<T> && this == (Pointer<T>)obj;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (int)Handle;
|
||||
}
|
||||
|
||||
public static bool operator ==(Pointer<T> p1, Pointer<T> p2)
|
||||
{
|
||||
return p1.Handle == p2.Handle;
|
||||
}
|
||||
|
||||
public static bool operator !=(Pointer<T> p1, Pointer<T> p2)
|
||||
{
|
||||
return p1.Handle != p2.Handle;
|
||||
}
|
||||
}
|
||||
public struct ArrayOf<T> { }
|
||||
public struct ConstArrayOf<T> { }
|
||||
public class CTypeDetailsAttribute : Attribute
|
||||
{
|
||||
public CTypeDetailsAttribute(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,17 @@
|
|||
namespace Ryujinx.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// The playback state of a track
|
||||
/// </summary>
|
||||
public enum PlaybackState
|
||||
{
|
||||
/// <summary>
|
||||
/// The track is currently playing
|
||||
/// </summary>
|
||||
Playing = 0,
|
||||
/// <summary>
|
||||
/// The track is currently stopped
|
||||
/// </summary>
|
||||
Stopped = 1
|
||||
}
|
||||
}
|
63
Ryujinx.Audio/Renderers/DummyAudioOut.cs
Normal file
63
Ryujinx.Audio/Renderers/DummyAudioOut.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// A Dummy audio renderer that does not output any audio
|
||||
/// </summary>
|
||||
public class DummyAudioOut : IAalOutput
|
||||
{
|
||||
private ConcurrentQueue<long> m_Buffers;
|
||||
|
||||
public DummyAudioOut()
|
||||
{
|
||||
m_Buffers = new ConcurrentQueue<long>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dummy audio output is always available, Baka!
|
||||
/// </summary>
|
||||
public static bool IsSupported => true;
|
||||
|
||||
public PlaybackState GetState(int trackId) => PlaybackState.Stopped;
|
||||
|
||||
public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback) => 1;
|
||||
|
||||
public void CloseTrack(int trackId) { }
|
||||
|
||||
public void Start(int trackId) { }
|
||||
|
||||
public void Stop(int trackId) { }
|
||||
|
||||
public void AppendBuffer<T>(int trackID, long bufferTag, T[] buffer)
|
||||
where T : struct
|
||||
{
|
||||
m_Buffers.Enqueue(bufferTag);
|
||||
}
|
||||
|
||||
public long[] GetReleasedBuffers(int trackId, int maxCount)
|
||||
{
|
||||
List<long> bufferTags = new List<long>();
|
||||
|
||||
for (int i = 0; i < maxCount; i++)
|
||||
{
|
||||
if (!m_Buffers.TryDequeue(out long tag))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bufferTags.Add(tag);
|
||||
}
|
||||
|
||||
return bufferTags.ToArray();
|
||||
}
|
||||
|
||||
public bool ContainsBuffer(int trackID, long bufferTag) => false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_Buffers.Clear();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,8 +6,11 @@ using System.Collections.Generic;
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Audio.OpenAL
|
||||
namespace Ryujinx.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// An audio renderer that uses OpenAL as the audio backend
|
||||
/// </summary>
|
||||
public class OpenALAudioOut : IAalOutput, IDisposable
|
||||
{
|
||||
private const int MaxTracks = 256;
|
||||
|
@ -176,6 +179,24 @@ namespace Ryujinx.Audio.OpenAL
|
|||
AudioPollerThread.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if OpenAL is supported on the device.
|
||||
/// </summary>
|
||||
public static bool IsSupported
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return AudioContext.AvailableDevices.Count > 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AudioPollerWork()
|
||||
{
|
||||
do
|
189
Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs
Normal file
189
Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs
Normal file
|
@ -0,0 +1,189 @@
|
|||
using Ryujinx.Audio.SoundIo;
|
||||
using SoundIOSharp;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// An audio renderer that uses libsoundio as the audio backend
|
||||
/// </summary>
|
||||
public class SoundIoAudioOut : IAalOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum amount of tracks we can issue simultaneously
|
||||
/// </summary>
|
||||
private const int MaximumTracks = 256;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SoundIO"/> audio context
|
||||
/// </summary>
|
||||
private SoundIO m_AudioContext;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SoundIODevice"/> audio device
|
||||
/// </summary>
|
||||
private SoundIODevice m_AudioDevice;
|
||||
|
||||
/// <summary>
|
||||
/// An object pool containing <see cref="SoundIoAudioTrack"/> objects
|
||||
/// </summary>
|
||||
private SoundIoAudioTrackPool m_TrackPool;
|
||||
|
||||
/// <summary>
|
||||
/// True if SoundIO is supported on the device.
|
||||
/// </summary>
|
||||
public static bool IsSupported => true;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of a <see cref="SoundIoAudioOut"/>
|
||||
/// </summary>
|
||||
public SoundIoAudioOut()
|
||||
{
|
||||
m_AudioContext = new SoundIO();
|
||||
|
||||
m_AudioContext.Connect();
|
||||
m_AudioContext.FlushEvents();
|
||||
|
||||
m_AudioDevice = m_AudioContext.GetOutputDevice(m_AudioContext.DefaultOutputDeviceIndex);
|
||||
m_TrackPool = new SoundIoAudioTrackPool(m_AudioContext, m_AudioDevice, MaximumTracks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current playback state of the specified track
|
||||
/// </summary>
|
||||
/// <param name="trackId">The track to retrieve the playback state for</param>
|
||||
public PlaybackState GetState(int trackId)
|
||||
{
|
||||
if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
|
||||
{
|
||||
return track.State;
|
||||
}
|
||||
|
||||
return PlaybackState.Stopped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new audio track with the specified parameters
|
||||
/// </summary>
|
||||
/// <param name="sampleRate">The requested sample rate</param>
|
||||
/// <param name="channels">The requested channels</param>
|
||||
/// <param name="callback">A <see cref="ReleaseCallback" /> that represents the delegate to invoke when a buffer has been released by the audio track</param>
|
||||
/// <returns>The created track's Track ID</returns>
|
||||
public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback)
|
||||
{
|
||||
if (!m_TrackPool.TryGet(out SoundIoAudioTrack track))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Open the output. We currently only support 16-bit signed LE
|
||||
track.Open(sampleRate, channels, callback, SoundIOFormat.S16LE);
|
||||
|
||||
return track.TrackID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops playback and closes the track specified by <paramref name="trackId"/>
|
||||
/// </summary>
|
||||
/// <param name="trackId">The ID of the track to close</param>
|
||||
public void CloseTrack(int trackId)
|
||||
{
|
||||
if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
|
||||
{
|
||||
// Close and dispose of the track
|
||||
track.Close();
|
||||
|
||||
// Recycle the track back into the pool
|
||||
m_TrackPool.Put(track);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts playback
|
||||
/// </summary>
|
||||
/// <param name="trackId">The ID of the track to start playback on</param>
|
||||
public void Start(int trackId)
|
||||
{
|
||||
if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
|
||||
{
|
||||
track.Start();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops playback
|
||||
/// </summary>
|
||||
/// <param name="trackId">The ID of the track to stop playback on</param>
|
||||
public void Stop(int trackId)
|
||||
{
|
||||
if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
|
||||
{
|
||||
track.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends an audio buffer to the specified track
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The sample type of the buffer</typeparam>
|
||||
/// <param name="trackId">The track to append the buffer to</param>
|
||||
/// <param name="bufferTag">The internal tag of the buffer</param>
|
||||
/// <param name="buffer">The buffer to append to the track</param>
|
||||
public void AppendBuffer<T>(int trackId, long bufferTag, T[] buffer)
|
||||
where T : struct
|
||||
{
|
||||
if(m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
|
||||
{
|
||||
track.AppendBuffer(bufferTag, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the specified buffer is currently reserved by the specified track
|
||||
/// </summary>
|
||||
/// <param name="trackId">The track to check</param>
|
||||
/// <param name="bufferTag">The buffer tag to check</param>
|
||||
public bool ContainsBuffer(int trackId, long bufferTag)
|
||||
{
|
||||
if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
|
||||
{
|
||||
return track.ContainsBuffer(bufferTag);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of buffer tags the specified track is no longer reserving
|
||||
/// </summary>
|
||||
/// <param name="trackId">The track to retrieve buffer tags from</param>
|
||||
/// <param name="maxCount">The maximum amount of buffer tags to retrieve</param>
|
||||
/// <returns>Buffers released by the specified track</returns>
|
||||
public long[] GetReleasedBuffers(int trackId, int maxCount)
|
||||
{
|
||||
if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track))
|
||||
{
|
||||
List<long> bufferTags = new List<long>();
|
||||
|
||||
while(maxCount-- > 0 && track.ReleasedBuffers.TryDequeue(out long tag))
|
||||
{
|
||||
bufferTags.Add(tag);
|
||||
}
|
||||
|
||||
return bufferTags.ToArray();
|
||||
}
|
||||
|
||||
return new long[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the <see cref="SoundIoAudioOut" />
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
m_TrackPool.Dispose();
|
||||
m_AudioContext.Disconnect();
|
||||
m_AudioContext.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
560
Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs
Normal file
560
Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs
Normal file
|
@ -0,0 +1,560 @@
|
|||
using SoundIOSharp;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.SoundIo
|
||||
{
|
||||
internal class SoundIoAudioTrack : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The audio track ring buffer
|
||||
/// </summary>
|
||||
private SoundIoRingBuffer m_Buffer;
|
||||
|
||||
/// <summary>
|
||||
/// A list of buffers currently pending writeback to the audio backend
|
||||
/// </summary>
|
||||
private ConcurrentQueue<SoundIoBuffer> m_ReservedBuffers;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a buffer has been released by the audio backend
|
||||
/// </summary>
|
||||
private event ReleaseCallback BufferReleased;
|
||||
|
||||
/// <summary>
|
||||
/// The track ID of this <see cref="SoundIoAudioTrack"/>
|
||||
/// </summary>
|
||||
public int TrackID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current playback state
|
||||
/// </summary>
|
||||
public PlaybackState State { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SoundIO"/> audio context this track belongs to
|
||||
/// </summary>
|
||||
public SoundIO AudioContext { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SoundIODevice"/> this track belongs to
|
||||
/// </summary>
|
||||
public SoundIODevice AudioDevice { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The audio output stream of this track
|
||||
/// </summary>
|
||||
public SoundIOOutStream AudioStream { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Released buffers the track is no longer holding
|
||||
/// </summary>
|
||||
public ConcurrentQueue<long> ReleasedBuffers { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of a <see cref="SoundIoAudioTrack"/>
|
||||
/// </summary>
|
||||
/// <param name="trackId">The track ID</param>
|
||||
/// <param name="audioContext">The SoundIO audio context</param>
|
||||
/// <param name="audioDevice">The SoundIO audio device</param>
|
||||
public SoundIoAudioTrack(int trackId, SoundIO audioContext, SoundIODevice audioDevice)
|
||||
{
|
||||
TrackID = trackId;
|
||||
AudioContext = audioContext;
|
||||
AudioDevice = audioDevice;
|
||||
State = PlaybackState.Stopped;
|
||||
ReleasedBuffers = new ConcurrentQueue<long>();
|
||||
|
||||
m_Buffer = new SoundIoRingBuffer();
|
||||
m_ReservedBuffers = new ConcurrentQueue<SoundIoBuffer>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the audio track with the specified parameters
|
||||
/// </summary>
|
||||
/// <param name="sampleRate">The requested sample rate of the track</param>
|
||||
/// <param name="channelCount">The requested channel count of the track</param>
|
||||
/// <param name="callback">A <see cref="ReleaseCallback" /> that represents the delegate to invoke when a buffer has been released by the audio track</param>
|
||||
/// <param name="format">The requested sample format of the track</param>
|
||||
public void Open(
|
||||
int sampleRate,
|
||||
int channelCount,
|
||||
ReleaseCallback callback,
|
||||
SoundIOFormat format = SoundIOFormat.S16LE)
|
||||
{
|
||||
// Close any existing audio streams
|
||||
if (AudioStream != null)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
if (!AudioDevice.SupportsSampleRate(sampleRate))
|
||||
{
|
||||
throw new InvalidOperationException($"This sound device does not support a sample rate of {sampleRate}Hz");
|
||||
}
|
||||
|
||||
if (!AudioDevice.SupportsFormat(format))
|
||||
{
|
||||
throw new InvalidOperationException($"This sound device does not support SoundIOFormat.{Enum.GetName(typeof(SoundIOFormat), format)}");
|
||||
}
|
||||
|
||||
AudioStream = AudioDevice.CreateOutStream();
|
||||
|
||||
AudioStream.Name = $"SwitchAudioTrack_{TrackID}";
|
||||
AudioStream.Layout = SoundIOChannelLayout.GetDefault(channelCount);
|
||||
AudioStream.Format = format;
|
||||
AudioStream.SampleRate = sampleRate;
|
||||
|
||||
AudioStream.WriteCallback = WriteCallback;
|
||||
|
||||
BufferReleased += callback;
|
||||
|
||||
AudioStream.Open();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This callback occurs when the sound device is ready to buffer more frames
|
||||
/// </summary>
|
||||
/// <param name="minFrameCount">The minimum amount of frames expected by the audio backend</param>
|
||||
/// <param name="maxFrameCount">The maximum amount of frames that can be written to the audio backend</param>
|
||||
private unsafe void WriteCallback(int minFrameCount, int maxFrameCount)
|
||||
{
|
||||
int bytesPerFrame = AudioStream.BytesPerFrame;
|
||||
uint bytesPerSample = (uint)AudioStream.BytesPerSample;
|
||||
|
||||
int bufferedFrames = m_Buffer.Length / bytesPerFrame;
|
||||
long bufferedSamples = m_Buffer.Length / bytesPerSample;
|
||||
|
||||
int frameCount = Math.Min(bufferedFrames, maxFrameCount);
|
||||
|
||||
if (frameCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SoundIOChannelAreas areas = AudioStream.BeginWrite(ref frameCount);
|
||||
int channelCount = areas.ChannelCount;
|
||||
|
||||
byte[] samples = new byte[frameCount * bytesPerFrame];
|
||||
|
||||
m_Buffer.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.
|
||||
|
||||
// Mono
|
||||
if (channelCount == 1)
|
||||
{
|
||||
SoundIOChannelArea area = areas.GetArea(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)
|
||||
{
|
||||
SoundIOChannelArea area1 = areas.GetArea(0);
|
||||
SoundIOChannelArea area2 = areas.GetArea(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)
|
||||
{
|
||||
SoundIOChannelArea area1 = areas.GetArea(0);
|
||||
SoundIOChannelArea area2 = areas.GetArea(1);
|
||||
SoundIOChannelArea area3 = areas.GetArea(2);
|
||||
SoundIOChannelArea area4 = areas.GetArea(3);
|
||||
SoundIOChannelArea area5 = areas.GetArea(4);
|
||||
SoundIOChannelArea area6 = areas.GetArea(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
|
||||
{
|
||||
SoundIOChannelArea[] channels = new SoundIOChannelArea[channelCount];
|
||||
|
||||
// Obtain the channel area for each channel
|
||||
for (int i = 0; i < channelCount; i++)
|
||||
{
|
||||
channels[i] = areas.GetArea(i);
|
||||
}
|
||||
|
||||
fixed (byte* srcptr = samples)
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
for (int channel = 0; channel < areas.ChannelCount; channel++)
|
||||
{
|
||||
// Copy channel by channel, frame by frame. This is slow!
|
||||
Unsafe.CopyBlockUnaligned((byte*)channels[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample);
|
||||
|
||||
channels[channel].Pointer += channels[channel].Step;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AudioStream.EndWrite();
|
||||
|
||||
UpdateReleasedBuffers(samples.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases any buffers that have been fully written to the output device
|
||||
/// </summary>
|
||||
/// <param name="bytesRead">The amount of bytes written in the last device write</param>
|
||||
private void UpdateReleasedBuffers(int bytesRead)
|
||||
{
|
||||
bool bufferReleased = false;
|
||||
|
||||
while (bytesRead > 0)
|
||||
{
|
||||
if (m_ReservedBuffers.TryPeek(out SoundIoBuffer buffer))
|
||||
{
|
||||
if (buffer.Length > bytesRead)
|
||||
{
|
||||
buffer.Length -= bytesRead;
|
||||
bytesRead = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferReleased = true;
|
||||
bytesRead -= buffer.Length;
|
||||
|
||||
m_ReservedBuffers.TryDequeue(out buffer);
|
||||
ReleasedBuffers.Enqueue(buffer.Tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bufferReleased)
|
||||
{
|
||||
OnBufferReleased();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts audio playback
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
if (AudioStream == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AudioStream.Start();
|
||||
AudioStream.Pause(false);
|
||||
AudioContext.FlushEvents();
|
||||
State = PlaybackState.Playing;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops audio playback
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (AudioStream == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AudioStream.Pause(true);
|
||||
AudioContext.FlushEvents();
|
||||
State = PlaybackState.Stopped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends an audio buffer to the tracks internal ring buffer
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The audio sample type</typeparam>
|
||||
/// <param name="bufferTag">The unqiue tag of the buffer being appended</param>
|
||||
/// <param name="buffer">The buffer to append</param>
|
||||
public void AppendBuffer<T>(long bufferTag, T[] buffer)
|
||||
{
|
||||
if (AudioStream == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the size of the audio samples
|
||||
int size = Unsafe.SizeOf<T>();
|
||||
|
||||
// Calculate the amount of bytes to copy from the buffer
|
||||
int bytesToCopy = size * buffer.Length;
|
||||
|
||||
// Copy the memory to our ring buffer
|
||||
m_Buffer.Write(buffer, 0, bytesToCopy);
|
||||
|
||||
// Keep track of "buffered" buffers
|
||||
m_ReservedBuffers.Enqueue(new SoundIoBuffer(bufferTag, bytesToCopy));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the specified buffer is currently reserved by the track
|
||||
/// </summary>
|
||||
/// <param name="bufferTag">The buffer tag to check</param>
|
||||
public bool ContainsBuffer(long bufferTag)
|
||||
{
|
||||
return m_ReservedBuffers.Any(x => x.Tag == bufferTag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the <see cref="SoundIoAudioTrack"/>
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (AudioStream != null)
|
||||
{
|
||||
AudioStream.Pause(true);
|
||||
AudioStream.Dispose();
|
||||
}
|
||||
|
||||
m_Buffer.Clear();
|
||||
OnBufferReleased();
|
||||
ReleasedBuffers.Clear();
|
||||
|
||||
State = PlaybackState.Stopped;
|
||||
AudioStream = null;
|
||||
BufferReleased = null;
|
||||
}
|
||||
|
||||
private void OnBufferReleased()
|
||||
{
|
||||
BufferReleased?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the <see cref="SoundIoAudioTrack" />
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
~SoundIoAudioTrack()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
193
Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrackPool.cs
Normal file
193
Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrackPool.cs
Normal file
|
@ -0,0 +1,193 @@
|
|||
using SoundIOSharp;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Audio.SoundIo
|
||||
{
|
||||
/// <summary>
|
||||
/// An object pool containing a set of audio tracks
|
||||
/// </summary>
|
||||
internal class SoundIoAudioTrackPool : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The current size of the <see cref="SoundIoAudioTrackPool"/>
|
||||
/// </summary>
|
||||
private int m_Size;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum size of the <see cref="SoundIoAudioTrackPool"/>
|
||||
/// </summary>
|
||||
private int m_MaxSize;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SoundIO"/> audio context this track pool belongs to
|
||||
/// </summary>
|
||||
private SoundIO m_Context;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SoundIODevice"/> audio device this track pool belongs to
|
||||
/// </summary>
|
||||
private SoundIODevice m_Device;
|
||||
|
||||
/// <summary>
|
||||
/// The queue that keeps track of the available <see cref="SoundIoAudioTrack"/> in the pool.
|
||||
/// </summary>
|
||||
private ConcurrentQueue<SoundIoAudioTrack> m_Queue;
|
||||
|
||||
/// <summary>
|
||||
/// The dictionary providing mapping between a TrackID and <see cref="SoundIoAudioTrack"/>
|
||||
/// </summary>
|
||||
private ConcurrentDictionary<int, SoundIoAudioTrack> m_TrackList;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current size of the <see cref="SoundIoAudioTrackPool"/>
|
||||
/// </summary>
|
||||
public int Size { get => m_Size; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum size of the <see cref="SoundIoAudioTrackPool"/>
|
||||
/// </summary>
|
||||
public int MaxSize { get => m_MaxSize; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the <see cref="SoundIoAudioTrackPool"/> is empty
|
||||
/// </summary>
|
||||
public bool IsEmpty { get => m_Queue.IsEmpty; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of a <see cref="SoundIoAudioTrackPool"/> that is empty
|
||||
/// </summary>
|
||||
/// <param name="maxSize">The maximum amount of tracks that can be created</param>
|
||||
public SoundIoAudioTrackPool(SoundIO context, SoundIODevice device, int maxSize)
|
||||
{
|
||||
m_Size = 0;
|
||||
m_Context = context;
|
||||
m_Device = device;
|
||||
m_MaxSize = maxSize;
|
||||
|
||||
m_Queue = new ConcurrentQueue<SoundIoAudioTrack>();
|
||||
m_TrackList = new ConcurrentDictionary<int, SoundIoAudioTrack>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of a <see cref="SoundIoAudioTrackPool"/> that contains
|
||||
/// the specified amount of <see cref="SoundIoAudioTrack"/>
|
||||
/// </summary>
|
||||
/// <param name="maxSize">The maximum amount of tracks that can be created</param>
|
||||
/// <param name="initialCapacity">The initial number of tracks that the pool contains</param>
|
||||
public SoundIoAudioTrackPool(SoundIO context, SoundIODevice device, int maxSize, int initialCapacity)
|
||||
: this(context, device, maxSize)
|
||||
{
|
||||
var trackCollection = Enumerable.Range(0, initialCapacity)
|
||||
.Select(TrackFactory);
|
||||
|
||||
m_Size = initialCapacity;
|
||||
m_Queue = new ConcurrentQueue<SoundIoAudioTrack>(trackCollection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SoundIoAudioTrack"/> with the proper AudioContext and AudioDevice
|
||||
/// and the specified <paramref name="trackId" />
|
||||
/// </summary>
|
||||
/// <param name="trackId">The ID of the track to be created</param>
|
||||
/// <returns>A new AudioTrack with the specified ID</returns>
|
||||
private SoundIoAudioTrack TrackFactory(int trackId)
|
||||
{
|
||||
// Create a new AudioTrack
|
||||
SoundIoAudioTrack track = new SoundIoAudioTrack(trackId, m_Context, m_Device);
|
||||
|
||||
// Keep track of issued tracks
|
||||
m_TrackList[trackId] = track;
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a <see cref="SoundIoAudioTrack"/> from the pool
|
||||
/// </summary>
|
||||
/// <returns>An AudioTrack from the pool</returns>
|
||||
public SoundIoAudioTrack Get()
|
||||
{
|
||||
// If we have a track available, reuse it
|
||||
if (m_Queue.TryDequeue(out SoundIoAudioTrack track))
|
||||
{
|
||||
return track;
|
||||
}
|
||||
|
||||
// Have we reached the maximum size of our pool?
|
||||
if (m_Size >= m_MaxSize)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// We don't have any pooled tracks, so create a new one
|
||||
return TrackFactory(m_Size++);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the <see cref="SoundIoAudioTrack"/> associated with the specified <paramref name="trackId"/> from the pool
|
||||
/// </summary>
|
||||
/// <param name="trackId">The ID of the track to retrieve</param>
|
||||
public SoundIoAudioTrack Get(int trackId)
|
||||
{
|
||||
if (m_TrackList.TryGetValue(trackId, out SoundIoAudioTrack track))
|
||||
{
|
||||
return track;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempers to get a <see cref="SoundIoAudioTrack"/> from the pool
|
||||
/// </summary>
|
||||
/// <param name="track">The track retrieved from the pool</param>
|
||||
/// <returns>True if retrieve was successful</returns>
|
||||
public bool TryGet(out SoundIoAudioTrack track)
|
||||
{
|
||||
track = Get();
|
||||
|
||||
return track != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the <see cref="SoundIoAudioTrack" /> associated with the specified <paramref name="trackId"/> from the pool
|
||||
/// </summary>
|
||||
/// <param name="trackId">The ID of the track to retrieve</param>
|
||||
/// <param name="track">The track retrieved from the pool</param>
|
||||
public bool TryGet(int trackId, out SoundIoAudioTrack track)
|
||||
{
|
||||
return m_TrackList.TryGetValue(trackId, out track);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an <see cref="SoundIoAudioTrack"/> back to the pool for reuse
|
||||
/// </summary>
|
||||
/// <param name="track">The track to be returned to the pool</param>
|
||||
public void Put(SoundIoAudioTrack track)
|
||||
{
|
||||
// Ensure the track is disposed and not playing audio
|
||||
track.Close();
|
||||
|
||||
// Requeue the track for reuse later
|
||||
m_Queue.Enqueue(track);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the <see cref="SoundIoAudioTrackPool" />
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var track in m_TrackList)
|
||||
{
|
||||
track.Value.Close();
|
||||
track.Value.Dispose();
|
||||
}
|
||||
|
||||
m_Size = 0;
|
||||
m_Queue.Clear();
|
||||
m_TrackList.Clear();
|
||||
}
|
||||
}
|
||||
}
|
29
Ryujinx.Audio/Renderers/SoundIo/SoundIoBuffer.cs
Normal file
29
Ryujinx.Audio/Renderers/SoundIo/SoundIoBuffer.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
namespace Ryujinx.Audio.SoundIo
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the remaining bytes left buffered for a specific buffer tag
|
||||
/// </summary>
|
||||
internal class SoundIoBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// The buffer tag this <see cref="SoundIoBuffer"/> represents
|
||||
/// </summary>
|
||||
public long Tag { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The remaining bytes still to be released
|
||||
/// </summary>
|
||||
public int Length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of a <see cref="SoundIoBuffer"/>
|
||||
/// </summary>
|
||||
/// <param name="tag">The buffer tag</param>
|
||||
/// <param name="length">The size of the buffer</param>
|
||||
public SoundIoBuffer(long tag, int length)
|
||||
{
|
||||
Tag = tag;
|
||||
Length = length;
|
||||
}
|
||||
}
|
||||
}
|
204
Ryujinx.Audio/Renderers/SoundIo/SoundIoRingBuffer.cs
Normal file
204
Ryujinx.Audio/Renderers/SoundIo/SoundIoRingBuffer.cs
Normal file
|
@ -0,0 +1,204 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.SoundIo
|
||||
{
|
||||
/// <summary>
|
||||
/// A thread-safe variable-size circular buffer
|
||||
/// </summary>
|
||||
internal class SoundIoRingBuffer
|
||||
{
|
||||
private byte[] m_Buffer;
|
||||
private int m_Size;
|
||||
private int m_HeadOffset;
|
||||
private int m_TailOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available bytes in the ring buffer
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
get { return m_Size; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of a <see cref="SoundIoRingBuffer"/>
|
||||
/// </summary>
|
||||
public SoundIoRingBuffer()
|
||||
{
|
||||
m_Buffer = new byte[2048];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of a <see cref="SoundIoRingBuffer"/> with the specified capacity
|
||||
/// </summary>
|
||||
/// <param name="capacity">The number of entries that the <see cref="SoundIoRingBuffer"/> can initially contain</param>
|
||||
public SoundIoRingBuffer(int capacity)
|
||||
{
|
||||
m_Buffer = new byte[capacity];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the ring buffer
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
m_Size = 0;
|
||||
m_HeadOffset = 0;
|
||||
m_TailOffset = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the specified amount of bytes from the ring buffer
|
||||
/// </summary>
|
||||
/// <param name="size">The amount of bytes to clear from the ring buffer</param>
|
||||
public void Clear(int size)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (size > m_Size)
|
||||
{
|
||||
size = m_Size;
|
||||
}
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_HeadOffset = (m_HeadOffset + size) % m_Buffer.Length;
|
||||
m_Size -= size;
|
||||
|
||||
if (m_Size == 0)
|
||||
{
|
||||
m_HeadOffset = 0;
|
||||
m_TailOffset = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extends the capacity of the ring buffer
|
||||
/// </summary>
|
||||
private void SetCapacity(int capacity)
|
||||
{
|
||||
byte[] buffer = new byte[capacity];
|
||||
|
||||
if (m_Size > 0)
|
||||
{
|
||||
if (m_HeadOffset < m_TailOffset)
|
||||
{
|
||||
Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, 0, m_Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, 0, m_Buffer.Length - m_HeadOffset);
|
||||
Buffer.BlockCopy(m_Buffer, 0, buffer, m_Buffer.Length - m_HeadOffset, m_TailOffset);
|
||||
}
|
||||
}
|
||||
|
||||
m_Buffer = buffer;
|
||||
m_HeadOffset = 0;
|
||||
m_TailOffset = m_Size;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to the ring buffer
|
||||
/// </summary>
|
||||
/// <param name="buffer">A byte array containing the data to write</param>
|
||||
/// <param name="index">The zero-based byte offset in <paramref name="buffer" /> from which to begin copying bytes to the ring buffer</param>
|
||||
/// <param name="count">The number of bytes to write</param>
|
||||
public void Write<T>(T[] buffer, int index, int count)
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
if ((m_Size + count) > m_Buffer.Length)
|
||||
{
|
||||
SetCapacity((m_Size + count + 2047) & ~2047);
|
||||
}
|
||||
|
||||
if (m_HeadOffset < m_TailOffset)
|
||||
{
|
||||
int tailLength = m_Buffer.Length - m_TailOffset;
|
||||
|
||||
if (tailLength >= count)
|
||||
{
|
||||
Buffer.BlockCopy(buffer, index, m_Buffer, m_TailOffset, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer.BlockCopy(buffer, index, m_Buffer, m_TailOffset, tailLength);
|
||||
Buffer.BlockCopy(buffer, index + tailLength, m_Buffer, 0, count - tailLength);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer.BlockCopy(buffer, index, m_Buffer, m_TailOffset, count);
|
||||
}
|
||||
|
||||
m_Size += count;
|
||||
m_TailOffset = (m_TailOffset + count) % m_Buffer.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a sequence of bytes from the ring buffer and advances the position within the ring buffer by the number of bytes read
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to write the data into</param>
|
||||
/// <param name="index">The zero-based byte offset in <paramref name="buffer" /> at which the read bytes will be placed</param>
|
||||
/// <param name="count">The maximum number of bytes to read</param>
|
||||
/// <returns>The total number of bytes read into the buffer. This might be less than the number of bytes requested if that number of bytes are not currently available, or zero if the ring buffer is empty</returns>
|
||||
public int Read<T>(T[] buffer, int index, int count)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (count > m_Size)
|
||||
{
|
||||
count = m_Size;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (m_HeadOffset < m_TailOffset)
|
||||
{
|
||||
Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, index, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
int tailLength = m_Buffer.Length - m_HeadOffset;
|
||||
|
||||
if (tailLength >= count)
|
||||
{
|
||||
Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, index, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, index, tailLength);
|
||||
Buffer.BlockCopy(m_Buffer, 0, buffer, index + tailLength, count - tailLength);
|
||||
}
|
||||
}
|
||||
|
||||
m_Size -= count;
|
||||
m_HeadOffset = (m_HeadOffset + count) % m_Buffer.Length;
|
||||
|
||||
if (m_Size == 0)
|
||||
{
|
||||
m_HeadOffset = 0;
|
||||
m_TailOffset = 0;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,12 +5,36 @@
|
|||
<RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dll</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dylib</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.so</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -8,6 +8,8 @@ using Ryujinx.HLE.Utilities;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
|
||||
{
|
||||
|
@ -303,7 +305,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
|
|||
}
|
||||
}
|
||||
|
||||
private void AppendMixedBuffer(long Tag)
|
||||
private unsafe void AppendMixedBuffer(long Tag)
|
||||
{
|
||||
int[] MixBuffer = new int[MixBufferSamplesCount * AudioConsts.HostChannelsCount];
|
||||
|
||||
|
@ -315,8 +317,8 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
|
|||
}
|
||||
|
||||
int OutOffset = 0;
|
||||
|
||||
int PendingSamples = MixBufferSamplesCount;
|
||||
float Volume = Voice.Volume;
|
||||
|
||||
while (PendingSamples > 0)
|
||||
{
|
||||
|
@ -331,9 +333,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
|
|||
|
||||
for (int Offset = 0; Offset < Samples.Length; Offset++)
|
||||
{
|
||||
int Sample = (int)(Samples[Offset] * Voice.Volume);
|
||||
|
||||
MixBuffer[OutOffset++] += Sample;
|
||||
MixBuffer[OutOffset++] += (int)(Samples[Offset] * Voice.Volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -341,11 +341,49 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
|
|||
AudioOut.AppendBuffer(Track, Tag, GetFinalBuffer(MixBuffer));
|
||||
}
|
||||
|
||||
private static short[] GetFinalBuffer(int[] Buffer)
|
||||
private unsafe static short[] GetFinalBuffer(int[] Buffer)
|
||||
{
|
||||
short[] Output = new short[Buffer.Length];
|
||||
|
||||
for (int Offset = 0; Offset < Buffer.Length; Offset++)
|
||||
int Offset = 0;
|
||||
|
||||
// Perform Saturation using SSE2 if supported
|
||||
if (Sse2.IsSupported)
|
||||
{
|
||||
fixed (int* inptr = Buffer)
|
||||
fixed (short* outptr = Output)
|
||||
{
|
||||
for (; Offset + 32 <= Buffer.Length; Offset += 32)
|
||||
{
|
||||
// Unroll the loop a little to ensure the CPU pipeline
|
||||
// is always full.
|
||||
Vector128<int> block1A = Sse2.LoadVector128(inptr + Offset + 0);
|
||||
Vector128<int> block1B = Sse2.LoadVector128(inptr + Offset + 4);
|
||||
|
||||
Vector128<int> block2A = Sse2.LoadVector128(inptr + Offset + 8);
|
||||
Vector128<int> block2B = Sse2.LoadVector128(inptr + Offset + 12);
|
||||
|
||||
Vector128<int> block3A = Sse2.LoadVector128(inptr + Offset + 16);
|
||||
Vector128<int> block3B = Sse2.LoadVector128(inptr + Offset + 20);
|
||||
|
||||
Vector128<int> block4A = Sse2.LoadVector128(inptr + Offset + 24);
|
||||
Vector128<int> block4B = Sse2.LoadVector128(inptr + Offset + 28);
|
||||
|
||||
Vector128<short> output1 = Sse2.PackSignedSaturate(block1A, block1B);
|
||||
Vector128<short> output2 = Sse2.PackSignedSaturate(block2A, block2B);
|
||||
Vector128<short> output3 = Sse2.PackSignedSaturate(block3A, block3B);
|
||||
Vector128<short> output4 = Sse2.PackSignedSaturate(block4A, block4B);
|
||||
|
||||
Sse2.Store(outptr + Offset + 0, output1);
|
||||
Sse2.Store(outptr + Offset + 8, output2);
|
||||
Sse2.Store(outptr + Offset + 16, output3);
|
||||
Sse2.Store(outptr + Offset + 24, output4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process left overs
|
||||
for (; Offset < Buffer.Length; Offset++)
|
||||
{
|
||||
Output[Offset] = DspUtils.Saturate(Buffer[Offset]);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using Ryujinx.Audio;
|
||||
using Ryujinx.Audio.OpenAL;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gal;
|
||||
using Ryujinx.Graphics.Gal.OpenGL;
|
||||
|
@ -17,7 +16,7 @@ namespace Ryujinx
|
|||
|
||||
IGalRenderer renderer = new OGLRenderer();
|
||||
|
||||
IAalOutput audioOut = new OpenALAudioOut();
|
||||
IAalOutput audioOut = InitializeAudioEngine();
|
||||
|
||||
Switch device = new Switch(renderer, audioOut);
|
||||
|
||||
|
@ -86,5 +85,25 @@ namespace Ryujinx
|
|||
|
||||
audioOut.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Picks an <see cref="IAalOutput"/> audio output renderer supported on this machine
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="IAalOutput"/> supported by this machine</returns>
|
||||
private static IAalOutput InitializeAudioEngine()
|
||||
{
|
||||
if (SoundIoAudioOut.IsSupported)
|
||||
{
|
||||
return new SoundIoAudioOut();
|
||||
}
|
||||
else if (OpenALAudioOut.IsSupported)
|
||||
{
|
||||
return new OpenALAudioOut();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new DummyAudioOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
Loading…
Reference in a new issue