aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS/Services/Audio
diff options
context:
space:
mode:
authorAc_K <Acoustik666@gmail.com>2019-09-19 02:45:11 +0200
committerjduncanator <1518948+jduncanator@users.noreply.github.com>2019-09-19 10:45:11 +1000
commita0720b5681852f3d786d77bd3793b0359dea321c (patch)
tree9d8f61e540d1d1d827999902dad95e5c0c1e076e /Ryujinx.HLE/HOS/Services/Audio
parent4af3101b22e6957d6aa48a2768566d658699f4ed (diff)
Refactoring HOS folder structure (#771)
* Refactoring HOS folder structure Refactoring HOS folder structure: - Added some subfolders when needed (Following structure decided in private). - Added some `Types` folders when needed. - Little cleanup here and there. - Add services placeholders for every HOS services (close #766 and #753). * Remove Types namespaces
Diffstat (limited to 'Ryujinx.HLE/HOS/Services/Audio')
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs163
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/Types/AudioOutData.cs14
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioDevice.cs236
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioRenderer.cs405
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/MemoryPoolContext.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Resampler.cs191
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioConsts.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererParameter.cs22
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BehaviorIn.cs11
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BiquadFilter.cs16
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolIn.cs14
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolOut.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolState.cs13
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/PlayState.cs9
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/UpdateDataHeader.cs22
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceChannelResourceIn.cs10
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceIn.cs49
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceOut.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/WaveBuffer.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/VoiceContext.cs199
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs81
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs162
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs193
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs65
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs15
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/Types/SampleFormat.cs13
38 files changed, 2063 insertions, 0 deletions
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs
new file mode 100644
index 00000000..5b6983d6
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs
@@ -0,0 +1,163 @@
+using ARMeilleure.Memory;
+using Ryujinx.Audio;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioOutManager
+{
+ class IAudioOut : IpcService, IDisposable
+ {
+ private IAalOutput _audioOut;
+ private KEvent _releaseEvent;
+ private int _track;
+
+ public IAudioOut(IAalOutput audioOut, KEvent releaseEvent, int track)
+ {
+ _audioOut = audioOut;
+ _releaseEvent = releaseEvent;
+ _track = track;
+ }
+
+ [Command(0)]
+ // GetAudioOutState() -> u32 state
+ public ResultCode GetAudioOutState(ServiceCtx context)
+ {
+ context.ResponseData.Write((int)_audioOut.GetState(_track));
+
+ return ResultCode.Success;
+ }
+
+ [Command(1)]
+ // StartAudioOut()
+ public ResultCode StartAudioOut(ServiceCtx context)
+ {
+ _audioOut.Start(_track);
+
+ return ResultCode.Success;
+ }
+
+ [Command(2)]
+ // StopAudioOut()
+ public ResultCode StopAudioOut(ServiceCtx context)
+ {
+ _audioOut.Stop(_track);
+
+ return ResultCode.Success;
+ }
+
+ [Command(3)]
+ // AppendAudioOutBuffer(u64 tag, buffer<nn::audio::AudioOutBuffer, 5>)
+ public ResultCode AppendAudioOutBuffer(ServiceCtx context)
+ {
+ return AppendAudioOutBufferImpl(context, context.Request.SendBuff[0].Position);
+ }
+
+ [Command(4)]
+ // RegisterBufferEvent() -> handle<copy>
+ public ResultCode RegisterBufferEvent(ServiceCtx context)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_releaseEvent.ReadableEvent, out int handle) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ return ResultCode.Success;
+ }
+
+ [Command(5)]
+ // GetReleasedAudioOutBuffer() -> (u32 count, buffer<nn::audio::AudioOutBuffer, 6>)
+ public ResultCode GetReleasedAudioOutBuffer(ServiceCtx context)
+ {
+ long position = context.Request.ReceiveBuff[0].Position;
+ long size = context.Request.ReceiveBuff[0].Size;
+
+ return GetReleasedAudioOutBufferImpl(context, position, size);
+ }
+
+ [Command(6)]
+ // ContainsAudioOutBuffer(u64 tag) -> b8
+ public ResultCode ContainsAudioOutBuffer(ServiceCtx context)
+ {
+ long tag = context.RequestData.ReadInt64();
+
+ context.ResponseData.Write(_audioOut.ContainsBuffer(_track, tag) ? 1 : 0);
+
+ return 0;
+ }
+
+ [Command(7)] // 3.0.0+
+ // AppendAudioOutBufferAuto(u64 tag, buffer<nn::audio::AudioOutBuffer, 0x21>)
+ public ResultCode AppendAudioOutBufferAuto(ServiceCtx context)
+ {
+ (long position, long size) = context.Request.GetBufferType0x21();
+
+ return AppendAudioOutBufferImpl(context, position);
+ }
+
+ public ResultCode AppendAudioOutBufferImpl(ServiceCtx context, long position)
+ {
+ long tag = context.RequestData.ReadInt64();
+
+ AudioOutData data = MemoryHelper.Read<AudioOutData>(
+ context.Memory,
+ position);
+
+ byte[] buffer = context.Memory.ReadBytes(
+ data.SampleBufferPtr,
+ data.SampleBufferSize);
+
+ _audioOut.AppendBuffer(_track, tag, buffer);
+
+ return ResultCode.Success;
+ }
+
+ [Command(8)] // 3.0.0+
+ // GetReleasedAudioOutBufferAuto() -> (u32 count, buffer<nn::audio::AudioOutBuffer, 0x22>)
+ public ResultCode GetReleasedAudioOutBufferAuto(ServiceCtx context)
+ {
+ (long position, long size) = context.Request.GetBufferType0x22();
+
+ return GetReleasedAudioOutBufferImpl(context, position, size);
+ }
+
+ public ResultCode GetReleasedAudioOutBufferImpl(ServiceCtx context, long position, long size)
+ {
+ uint count = (uint)((ulong)size >> 3);
+
+ long[] releasedBuffers = _audioOut.GetReleasedBuffers(_track, (int)count);
+
+ for (uint index = 0; index < count; index++)
+ {
+ long tag = 0;
+
+ if (index < releasedBuffers.Length)
+ {
+ tag = releasedBuffers[index];
+ }
+
+ context.Memory.WriteInt64(position + index * 8, tag);
+ }
+
+ context.ResponseData.Write(releasedBuffers.Length);
+
+ return ResultCode.Success;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _audioOut.CloseTrack(_track);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/Types/AudioOutData.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/Types/AudioOutData.cs
new file mode 100644
index 00000000..2598d0f8
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/Types/AudioOutData.cs
@@ -0,0 +1,14 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioOutManager
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct AudioOutData
+ {
+ public long NextBufferPtr;
+ public long SampleBufferPtr;
+ public long SampleBufferCapacity;
+ public long SampleBufferSize;
+ public long SampleBufferInnerOffset;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioDevice.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioDevice.cs
new file mode 100644
index 00000000..ab4aee76
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioDevice.cs
@@ -0,0 +1,236 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.SystemState;
+using System;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ class IAudioDevice : IpcService
+ {
+ private KEvent _systemEvent;
+
+ public IAudioDevice(Horizon system)
+ {
+ _systemEvent = new KEvent(system);
+
+ // TODO: We shouldn't be signaling this here.
+ _systemEvent.ReadableEvent.Signal();
+ }
+
+ [Command(0)]
+ // ListAudioDeviceName() -> (u32, buffer<bytes, 6>)
+ public ResultCode ListAudioDeviceName(ServiceCtx context)
+ {
+ string[] deviceNames = SystemStateMgr.AudioOutputs;
+
+ context.ResponseData.Write(deviceNames.Length);
+
+ long position = context.Request.ReceiveBuff[0].Position;
+ long size = context.Request.ReceiveBuff[0].Size;
+
+ long basePosition = position;
+
+ foreach (string name in deviceNames)
+ {
+ byte[] buffer = Encoding.ASCII.GetBytes(name + "\0");
+
+ if ((position - basePosition) + buffer.Length > size)
+ {
+ Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+
+ break;
+ }
+
+ context.Memory.WriteBytes(position, buffer);
+
+ position += buffer.Length;
+ }
+
+ return ResultCode.Success;
+ }
+
+ [Command(1)]
+ // SetAudioDeviceOutputVolume(u32, buffer<bytes, 5>)
+ public ResultCode SetAudioDeviceOutputVolume(ServiceCtx context)
+ {
+ float volume = context.RequestData.ReadSingle();
+
+ long position = context.Request.SendBuff[0].Position;
+ long size = context.Request.SendBuff[0].Size;
+
+ byte[] deviceNameBuffer = context.Memory.ReadBytes(position, size);
+
+ string deviceName = Encoding.ASCII.GetString(deviceNameBuffer);
+
+ Logger.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [Command(3)]
+ // GetActiveAudioDeviceName() -> buffer<bytes, 6>
+ public ResultCode GetActiveAudioDeviceName(ServiceCtx context)
+ {
+ string name = context.Device.System.State.ActiveAudioOutput;
+
+ long position = context.Request.ReceiveBuff[0].Position;
+ long size = context.Request.ReceiveBuff[0].Size;
+
+ byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
+
+ if ((ulong)deviceNameBuffer.Length <= (ulong)size)
+ {
+ context.Memory.WriteBytes(position, deviceNameBuffer);
+ }
+ else
+ {
+ Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+ }
+
+ return ResultCode.Success;
+ }
+
+ [Command(4)]
+ // QueryAudioDeviceSystemEvent() -> handle<copy, event>
+ public ResultCode QueryAudioDeviceSystemEvent(ServiceCtx context)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_systemEvent.ReadableEvent, out int handle) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [Command(5)]
+ // GetActiveChannelCount() -> u32
+ public ResultCode GetActiveChannelCount(ServiceCtx context)
+ {
+ context.ResponseData.Write(2);
+
+ Logger.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [Command(6)]
+ // ListAudioDeviceNameAuto() -> (u32, buffer<bytes, 0x22>)
+ public ResultCode ListAudioDeviceNameAuto(ServiceCtx context)
+ {
+ string[] deviceNames = SystemStateMgr.AudioOutputs;
+
+ context.ResponseData.Write(deviceNames.Length);
+
+ (long position, long size) = context.Request.GetBufferType0x22();
+
+ long basePosition = position;
+
+ foreach (string name in deviceNames)
+ {
+ byte[] buffer = Encoding.UTF8.GetBytes(name + '\0');
+
+ if ((position - basePosition) + buffer.Length > size)
+ {
+ Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+
+ break;
+ }
+
+ context.Memory.WriteBytes(position, buffer);
+
+ position += buffer.Length;
+ }
+
+ return ResultCode.Success;
+ }
+
+ [Command(7)]
+ // SetAudioDeviceOutputVolumeAuto(u32, buffer<bytes, 0x21>)
+ public ResultCode SetAudioDeviceOutputVolumeAuto(ServiceCtx context)
+ {
+ float volume = context.RequestData.ReadSingle();
+
+ (long position, long size) = context.Request.GetBufferType0x21();
+
+ byte[] deviceNameBuffer = context.Memory.ReadBytes(position, size);
+
+ string deviceName = Encoding.UTF8.GetString(deviceNameBuffer);
+
+ Logger.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [Command(8)]
+ // GetAudioDeviceOutputVolumeAuto(buffer<bytes, 0x21>) -> u32
+ public ResultCode GetAudioDeviceOutputVolumeAuto(ServiceCtx context)
+ {
+ context.ResponseData.Write(1f);
+
+ Logger.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [Command(10)]
+ // GetActiveAudioDeviceNameAuto() -> buffer<bytes, 0x22>
+ public ResultCode GetActiveAudioDeviceNameAuto(ServiceCtx context)
+ {
+ string name = context.Device.System.State.ActiveAudioOutput;
+
+ (long position, long size) = context.Request.GetBufferType0x22();
+
+ byte[] deviceNameBuffer = Encoding.UTF8.GetBytes(name + '\0');
+
+ if ((ulong)deviceNameBuffer.Length <= (ulong)size)
+ {
+ context.Memory.WriteBytes(position, deviceNameBuffer);
+ }
+ else
+ {
+ Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+ }
+
+ return ResultCode.Success;
+ }
+
+ [Command(11)]
+ // QueryAudioDeviceInputEvent() -> handle<copy, event>
+ public ResultCode QueryAudioDeviceInputEvent(ServiceCtx context)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_systemEvent.ReadableEvent, out int handle) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [Command(12)]
+ // QueryAudioDeviceOutputEvent() -> handle<copy, event>
+ public ResultCode QueryAudioDeviceOutputEvent(ServiceCtx context)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_systemEvent.ReadableEvent, out int handle) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioRenderer.cs
new file mode 100644
index 00000000..975992aa
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioRenderer.cs
@@ -0,0 +1,405 @@
+using ARMeilleure.Memory;
+using Ryujinx.Audio;
+using Ryujinx.Audio.Adpcm;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.Utilities;
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ class IAudioRenderer : IpcService, IDisposable
+ {
+ // This is the amount of samples that are going to be appended
+ // each time that RequestUpdateAudioRenderer is called. Ideally,
+ // this value shouldn't be neither too small (to avoid the player
+ // starving due to running out of samples) or too large (to avoid
+ // high latency).
+ private const int MixBufferSamplesCount = 960;
+
+ private KEvent _updateEvent;
+
+ private IMemoryManager _memory;
+
+ private IAalOutput _audioOut;
+
+ private AudioRendererParameter _params;
+
+ private MemoryPoolContext[] _memoryPools;
+
+ private VoiceContext[] _voices;
+
+ private int _track;
+
+ private PlayState _playState;
+
+ public IAudioRenderer(
+ Horizon system,
+ IMemoryManager memory,
+ IAalOutput audioOut,
+ AudioRendererParameter Params)
+ {
+ _updateEvent = new KEvent(system);
+
+ _memory = memory;
+ _audioOut = audioOut;
+ _params = Params;
+
+ _track = audioOut.OpenTrack(
+ AudioConsts.HostSampleRate,
+ AudioConsts.HostChannelsCount,
+ AudioCallback);
+
+ _memoryPools = CreateArray<MemoryPoolContext>(Params.EffectCount + Params.VoiceCount * 4);
+
+ _voices = CreateArray<VoiceContext>(Params.VoiceCount);
+
+ InitializeAudioOut();
+
+ _playState = PlayState.Stopped;
+ }
+
+ [Command(0)]
+ // GetSampleRate() -> u32
+ public ResultCode GetSampleRate(ServiceCtx context)
+ {
+ context.ResponseData.Write(_params.SampleRate);
+
+ return ResultCode.Success;
+ }
+
+ [Command(1)]
+ // GetSampleCount() -> u32
+ public ResultCode GetSampleCount(ServiceCtx context)
+ {
+ context.ResponseData.Write(_params.SampleCount);
+
+ return ResultCode.Success;
+ }
+
+ [Command(2)]
+ // GetMixBufferCount() -> u32
+ public ResultCode GetMixBufferCount(ServiceCtx context)
+ {
+ context.ResponseData.Write(_params.MixCount);
+
+ return ResultCode.Success;
+ }
+
+ [Command(3)]
+ // GetState() -> u32
+ public ResultCode GetState(ServiceCtx context)
+ {
+ context.ResponseData.Write((int)_playState);
+
+ Logger.PrintStub(LogClass.ServiceAudio, new { State = Enum.GetName(typeof(PlayState), _playState) });
+
+ return ResultCode.Success;
+ }
+
+ private void AudioCallback()
+ {
+ _updateEvent.ReadableEvent.Signal();
+ }
+
+ private static T[] CreateArray<T>(int size) where T : new()
+ {
+ T[] output = new T[size];
+
+ for (int index = 0; index < size; index++)
+ {
+ output[index] = new T();
+ }
+
+ return output;
+ }
+
+ private void InitializeAudioOut()
+ {
+ AppendMixedBuffer(0);
+ AppendMixedBuffer(1);
+ AppendMixedBuffer(2);
+
+ _audioOut.Start(_track);
+ }
+
+ [Command(4)]
+ // RequestUpdateAudioRenderer(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 5>)
+ // -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6>, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6>)
+ public ResultCode RequestUpdateAudioRenderer(ServiceCtx context)
+ {
+ long outputPosition = context.Request.ReceiveBuff[0].Position;
+ long outputSize = context.Request.ReceiveBuff[0].Size;
+
+ MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
+
+ long inputPosition = context.Request.SendBuff[0].Position;
+
+ StructReader reader = new StructReader(context.Memory, inputPosition);
+ StructWriter writer = new StructWriter(context.Memory, outputPosition);
+
+ UpdateDataHeader inputHeader = reader.Read<UpdateDataHeader>();
+
+ reader.Read<BehaviorIn>(inputHeader.BehaviorSize);
+
+ MemoryPoolIn[] memoryPoolsIn = reader.Read<MemoryPoolIn>(inputHeader.MemoryPoolSize);
+
+ for (int index = 0; index < memoryPoolsIn.Length; index++)
+ {
+ MemoryPoolIn memoryPool = memoryPoolsIn[index];
+
+ if (memoryPool.State == MemoryPoolState.RequestAttach)
+ {
+ _memoryPools[index].OutStatus.State = MemoryPoolState.Attached;
+ }
+ else if (memoryPool.State == MemoryPoolState.RequestDetach)
+ {
+ _memoryPools[index].OutStatus.State = MemoryPoolState.Detached;
+ }
+ }
+
+ reader.Read<VoiceChannelResourceIn>(inputHeader.VoiceResourceSize);
+
+ VoiceIn[] voicesIn = reader.Read<VoiceIn>(inputHeader.VoiceSize);
+
+ for (int index = 0; index < voicesIn.Length; index++)
+ {
+ VoiceIn voice = voicesIn[index];
+
+ VoiceContext voiceCtx = _voices[index];
+
+ voiceCtx.SetAcquireState(voice.Acquired != 0);
+
+ if (voice.Acquired == 0)
+ {
+ continue;
+ }
+
+ if (voice.FirstUpdate != 0)
+ {
+ voiceCtx.AdpcmCtx = GetAdpcmDecoderContext(
+ voice.AdpcmCoeffsPosition,
+ voice.AdpcmCoeffsSize);
+
+ voiceCtx.SampleFormat = voice.SampleFormat;
+ voiceCtx.SampleRate = voice.SampleRate;
+ voiceCtx.ChannelsCount = voice.ChannelsCount;
+
+ voiceCtx.SetBufferIndex(voice.BaseWaveBufferIndex);
+ }
+
+ voiceCtx.WaveBuffers[0] = voice.WaveBuffer0;
+ voiceCtx.WaveBuffers[1] = voice.WaveBuffer1;
+ voiceCtx.WaveBuffers[2] = voice.WaveBuffer2;
+ voiceCtx.WaveBuffers[3] = voice.WaveBuffer3;
+ voiceCtx.Volume = voice.Volume;
+ voiceCtx.PlayState = voice.PlayState;
+ }
+
+ UpdateAudio();
+
+ UpdateDataHeader outputHeader = new UpdateDataHeader();
+
+ int updateHeaderSize = Marshal.SizeOf<UpdateDataHeader>();
+
+ outputHeader.Revision = IAudioRendererManager.RevMagic;
+ outputHeader.BehaviorSize = 0xb0;
+ outputHeader.MemoryPoolSize = (_params.EffectCount + _params.VoiceCount * 4) * 0x10;
+ outputHeader.VoiceSize = _params.VoiceCount * 0x10;
+ outputHeader.EffectSize = _params.EffectCount * 0x10;
+ outputHeader.SinkSize = _params.SinkCount * 0x20;
+ outputHeader.PerformanceManagerSize = 0x10;
+ outputHeader.TotalSize = updateHeaderSize +
+ outputHeader.BehaviorSize +
+ outputHeader.MemoryPoolSize +
+ outputHeader.VoiceSize +
+ outputHeader.EffectSize +
+ outputHeader.SinkSize +
+ outputHeader.PerformanceManagerSize;
+
+ writer.Write(outputHeader);
+
+ foreach (MemoryPoolContext memoryPool in _memoryPools)
+ {
+ writer.Write(memoryPool.OutStatus);
+ }
+
+ foreach (VoiceContext voice in _voices)
+ {
+ writer.Write(voice.OutStatus);
+ }
+
+ return ResultCode.Success;
+ }
+
+ [Command(5)]
+ // Start()
+ public ResultCode StartAudioRenderer(ServiceCtx context)
+ {
+ Logger.PrintStub(LogClass.ServiceAudio);
+
+ _playState = PlayState.Playing;
+
+ return ResultCode.Success;
+ }
+
+ [Command(6)]
+ // Stop()
+ public ResultCode StopAudioRenderer(ServiceCtx context)
+ {
+ Logger.PrintStub(LogClass.ServiceAudio);
+
+ _playState = PlayState.Stopped;
+
+ return ResultCode.Success;
+ }
+
+ [Command(7)]
+ // QuerySystemEvent() -> handle<copy, event>
+ public ResultCode QuerySystemEvent(ServiceCtx context)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_updateEvent.ReadableEvent, out int handle) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ return ResultCode.Success;
+ }
+
+ private AdpcmDecoderContext GetAdpcmDecoderContext(long position, long size)
+ {
+ if (size == 0)
+ {
+ return null;
+ }
+
+ AdpcmDecoderContext context = new AdpcmDecoderContext
+ {
+ Coefficients = new short[size >> 1]
+ };
+
+ for (int offset = 0; offset < size; offset += 2)
+ {
+ context.Coefficients[offset >> 1] = _memory.ReadInt16(position + offset);
+ }
+
+ return context;
+ }
+
+ private void UpdateAudio()
+ {
+ long[] released = _audioOut.GetReleasedBuffers(_track, 2);
+
+ for (int index = 0; index < released.Length; index++)
+ {
+ AppendMixedBuffer(released[index]);
+ }
+ }
+
+ private void AppendMixedBuffer(long tag)
+ {
+ int[] mixBuffer = new int[MixBufferSamplesCount * AudioConsts.HostChannelsCount];
+
+ foreach (VoiceContext voice in _voices)
+ {
+ if (!voice.Playing || voice.CurrentWaveBuffer.Size == 0)
+ {
+ continue;
+ }
+
+ int outOffset = 0;
+ int pendingSamples = MixBufferSamplesCount;
+ float volume = voice.Volume;
+
+ while (pendingSamples > 0)
+ {
+ int[] samples = voice.GetBufferData(_memory, pendingSamples, out int returnedSamples);
+
+ if (returnedSamples == 0)
+ {
+ break;
+ }
+
+ pendingSamples -= returnedSamples;
+
+ for (int offset = 0; offset < samples.Length; offset++)
+ {
+ mixBuffer[outOffset++] += (int)(samples[offset] * voice.Volume);
+ }
+ }
+ }
+
+ _audioOut.AppendBuffer(_track, tag, GetFinalBuffer(mixBuffer));
+ }
+
+ private unsafe static short[] GetFinalBuffer(int[] buffer)
+ {
+ short[] output = new short[buffer.Length];
+
+ 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]);
+ }
+
+ return output;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _audioOut.CloseTrack(_track);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/MemoryPoolContext.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/MemoryPoolContext.cs
new file mode 100644
index 00000000..3f48114c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/MemoryPoolContext.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ class MemoryPoolContext
+ {
+ public MemoryPoolOut OutStatus;
+
+ public MemoryPoolContext()
+ {
+ OutStatus.State = MemoryPoolState.Detached;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Resampler.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Resampler.cs
new file mode 100644
index 00000000..936e7f50
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Resampler.cs
@@ -0,0 +1,191 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ static class Resampler
+ {
+#region "LookUp Tables"
+ private static short[] _curveLut0 = new short[]
+ {
+ 6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239, 19412, 7093, 22,
+ 6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377, 7472, 41, 5773, 19361, 7600, 48,
+ 5659, 19342, 7728, 55, 5546, 19321, 7857, 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77,
+ 5213, 19245, 8249, 84, 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109,
+ 4785, 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988, 9183, 147,
+ 4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590, 179, 4083, 18793, 9726, 190,
+ 3987, 18738, 9863, 202, 3893, 18682, 10000, 215, 3800, 18624, 10137, 228, 3709, 18563, 10274, 241,
+ 3618, 18500, 10411, 255, 3529, 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300,
+ 3269, 18230, 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375, 369,
+ 2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428, 2709, 17681, 11925, 449,
+ 2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489, 17418, 12334, 517, 2418, 17327, 12470, 541,
+ 2348, 17234, 12606, 566, 2280, 17140, 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647,
+ 2083, 16846, 13144, 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766,
+ 1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667, 16109, 14062, 901,
+ 1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772, 14445, 1013, 1457, 15657, 14571, 1052,
+ 1407, 15540, 14695, 1093, 1359, 15423, 14819, 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221,
+ 1221, 15064, 15185, 1266, 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407,
+ 1052, 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191, 15998, 1613,
+ 901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327, 1780, 798, 13673, 16434, 1838,
+ 766, 13541, 16539, 1897, 735, 13409, 16643, 1958, 704, 13277, 16745, 2020, 675, 13144, 16846, 2083,
+ 647, 13010, 16946, 2147, 619, 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348,
+ 541, 12470, 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595, 2635,
+ 449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863, 388, 11513, 17927, 2942,
+ 369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334, 11100, 18157, 3186, 317, 10962, 18230, 3269,
+ 300, 10824, 18300, 3355, 285, 10687, 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618,
+ 241, 10274, 18563, 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987,
+ 190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157, 9318, 18942, 4377,
+ 147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914, 19073, 4681, 118, 8780, 19112, 4785,
+ 109, 8646, 19148, 4890, 101, 8513, 19183, 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213,
+ 77, 8118, 19273, 5323, 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659,
+ 48, 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219, 19403, 6121,
+ 22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424, 6479, 3, 6722, 19426, 6600
+ };
+
+ private static short[] _curveLut1 = new short[]
+ {
+ -68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450, 32586, 512, -36,
+ -568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454, 1000, -69, -891, 32393, 1174, -80,
+ -990, 32323, 1352, -92, -1084, 32244, 1536, -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128,
+ -1338, 31956, 2118, -140, -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180,
+ -1617, 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995, 3657, -240,
+ -1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393, -289, -1951, 30272, 4648, -307,
+ -1984, 30072, 4908, -325, -2014, 29866, 5172, -343, -2040, 29652, 5442, -362, -2063, 29431, 5716, -382,
+ -2083, 29203, 5994, -403, -2100, 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468,
+ -2133, 28226, 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066, -563,
+ -2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641, -2121, 26285, 9336, -668,
+ -2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084, 25375, 10326, -753, -2067, 25063, 10662, -783,
+ -2049, 24746, 11000, -813, -2030, 24425, 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907,
+ -1962, 23438, 12382, -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039,
+ -1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, 21027, 14877, -1176,
+ -1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, 15965, -1282, -1633, 19600, 16329, -1317,
+ -1599, 19239, 16694, -1353, -1564, 18878, 17058, -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459,
+ -1459, 17787, 18151, -1495, -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599,
+ -1317, 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, 20673, -1732,
+ -1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, -1825, -1072, 13798, 22077, -1855,
+ -1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, -972, 12733, 23103, -1937, -939, 12382, 23438, -1962,
+ -907, 12033, 23771, -1986, -875, 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049,
+ -783, 10662, 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987, -2111,
+ -668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136, -588, 8378, 27151, -2141,
+ -563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514, 7453, 27966, -2139, -490, 7153, 28226, -2133,
+ -468, 6857, 28480, -2125, -445, 6565, 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083,
+ -382, 5716, 29431, -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984,
+ -307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256, 3897, 30826, -1830,
+ -240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192, 31310, -1676, -194, 2967, 31456, -1617,
+ -180, 2747, 31593, -1554, -167, 2532, 31723, -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338,
+ -128, 1919, 32061, -1258, -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990,
+ -80, 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669, 32551, -568,
+ -36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630, -200, -5, 69, 32639, -68
+ };
+
+ private static short[] _curveLut2 = new short[]
+ {
+ 3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811, 26253, 3751, -42,
+ 2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169, 4199, -54, 2338, 26130, 4354, -58,
+ 2227, 26085, 4512, -63, 2120, 26035, 4673, -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76,
+ 1813, 25852, 5174, -81, 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98,
+ 1442, 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239, 6442, -121,
+ 1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027, -140, 897, 24776, 7227, -146,
+ 829, 24648, 7430, -153, 764, 24516, 7635, -159, 701, 24379, 7842, -166, 641, 24237, 8052, -174,
+ 583, 24091, 8264, -181, 526, 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202,
+ 371, 23462, 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809, -230,
+ 194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250, 81, 22208, 10735, -258,
+ 47, 22015, 10970, -265, 15, 21818, 11206, -271, -16, 21618, 11444, -277, -44, 21415, 11684, -283,
+ -71, 21208, 11924, -290, -97, 20999, 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306,
+ -163, 20354, 12898, -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325,
+ -234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273, 18765, 14625, -337,
+ -284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057, 15369, -341, -310, 17817, 15617, -341,
+ -317, 17577, 15864, -340, -323, 17335, 16111, -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336,
+ -336, 16603, 16848, -332, -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317,
+ -341, 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873, 18531, -284,
+ -337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230, -248, -328, 13882, 19459, -234,
+ -325, 13635, 19686, -218, -321, 13389, 19911, -201, -316, 13143, 20134, -183, -311, 12898, 20354, -163,
+ -306, 12653, 20571, -143, -302, 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71,
+ -283, 11684, 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015, 47,
+ -258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154, -237, 10038, 22769, 194,
+ -230, 9809, 22948, 236, -222, 9583, 23123, 279, -215, 9358, 23295, 324, -209, 9135, 23462, 371,
+ -202, 8914, 23626, 420, -194, 8695, 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583,
+ -174, 8052, 24237, 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829,
+ -146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127, 6635, 25131, 1115,
+ -121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066, 25440, 1357, -103, 5882, 25533, 1442,
+ -98, 5701, 25621, 1531, -92, 5522, 25704, 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813,
+ -76, 5004, 25919, 1912, -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227,
+ -58, 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897, 26230, 2688,
+ -42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281, 3064, -32, 3329, 26287, 3195
+ };
+#endregion
+
+ public static int[] Resample2Ch(
+ int[] buffer,
+ int srcSampleRate,
+ int dstSampleRate,
+ int samplesCount,
+ ref int fracPart)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if (srcSampleRate <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(srcSampleRate));
+ }
+
+ if (dstSampleRate <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(dstSampleRate));
+ }
+
+ double ratio = (double)srcSampleRate / dstSampleRate;
+
+ int newSamplesCount = (int)(samplesCount / ratio);
+
+ int step = (int)(ratio * 0x8000);
+
+ int[] output = new int[newSamplesCount * 2];
+
+ short[] lut;
+
+ if (step > 0xaaaa)
+ {
+ lut = _curveLut0;
+ }
+ else if (step <= 0x8000)
+ {
+ lut = _curveLut1;
+ }
+ else
+ {
+ lut = _curveLut2;
+ }
+
+ int inOffs = 0;
+
+ for (int outOffs = 0; outOffs < output.Length; outOffs += 2)
+ {
+ int lutIndex = (fracPart >> 8) * 4;
+
+ int sample0 = buffer[(inOffs + 0) * 2 + 0] * lut[lutIndex + 0] +
+ buffer[(inOffs + 1) * 2 + 0] * lut[lutIndex + 1] +
+ buffer[(inOffs + 2) * 2 + 0] * lut[lutIndex + 2] +
+ buffer[(inOffs + 3) * 2 + 0] * lut[lutIndex + 3];
+
+ int sample1 = buffer[(inOffs + 0) * 2 + 1] * lut[lutIndex + 0] +
+ buffer[(inOffs + 1) * 2 + 1] * lut[lutIndex + 1] +
+ buffer[(inOffs + 2) * 2 + 1] * lut[lutIndex + 2] +
+ buffer[(inOffs + 3) * 2 + 1] * lut[lutIndex + 3];
+
+ int newOffset = fracPart + step;
+
+ inOffs += newOffset >> 15;
+
+ fracPart = newOffset & 0x7fff;
+
+ output[outOffs + 0] = sample0 >> 15;
+ output[outOffs + 1] = sample1 >> 15;
+ }
+
+ return output;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioConsts.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioConsts.cs
new file mode 100644
index 00000000..f3b6995c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioConsts.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ static class AudioConsts
+ {
+ public const int HostSampleRate = 48000;
+ public const int HostChannelsCount = 2;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererParameter.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererParameter.cs
new file mode 100644
index 00000000..9772f786
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererParameter.cs
@@ -0,0 +1,22 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct AudioRendererParameter
+ {
+ public int SampleRate;
+ public int SampleCount;
+ public int Unknown8;
+ public int MixCount;
+ public int VoiceCount;
+ public int SinkCount;
+ public int EffectCount;
+ public int PerformanceManagerCount;
+ public int VoiceDropEnable;
+ public int SplitterCount;
+ public int SplitterDestinationDataCount;
+ public int Unknown2C;
+ public int Revision;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BehaviorIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BehaviorIn.cs
new file mode 100644
index 00000000..953b4ce3
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BehaviorIn.cs
@@ -0,0 +1,11 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
+ struct BehaviorIn
+ {
+ public long Unknown0;
+ public long Unknown8;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BiquadFilter.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BiquadFilter.cs
new file mode 100644
index 00000000..d0d8ed9b
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BiquadFilter.cs
@@ -0,0 +1,16 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0xc, Pack = 1)]
+ struct BiquadFilter
+ {
+ public byte Enable;
+ public byte Padding;
+ public short B0;
+ public short B1;
+ public short B2;
+ public short A1;
+ public short A2;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolIn.cs
new file mode 100644
index 00000000..8dc53929
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolIn.cs
@@ -0,0 +1,14 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 4)]
+ struct MemoryPoolIn
+ {
+ public long Address;
+ public long Size;
+ public MemoryPoolState State;
+ public int Unknown14;
+ public long Unknown18;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolOut.cs
new file mode 100644
index 00000000..7581e8a7
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolOut.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
+ struct MemoryPoolOut
+ {
+ public MemoryPoolState State;
+ public int Unknown14;
+ public long Unknown18;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolState.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolState.cs
new file mode 100644
index 00000000..a82747b8
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolState.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ enum MemoryPoolState
+ {
+ Invalid = 0,
+ Unknown = 1,
+ RequestDetach = 2,
+ Detached = 3,
+ RequestAttach = 4,
+ Attached = 5,
+ Released = 6
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/PlayState.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/PlayState.cs
new file mode 100644
index 00000000..d63df971
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/PlayState.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ enum PlayState : byte
+ {
+ Playing = 0,
+ Stopped = 1,
+ Paused = 2
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/UpdateDataHeader.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/UpdateDataHeader.cs
new file mode 100644
index 00000000..b1f14984
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/UpdateDataHeader.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ struct UpdateDataHeader
+ {
+ public int Revision;
+ public int BehaviorSize;
+ public int MemoryPoolSize;
+ public int VoiceSize;
+ public int VoiceResourceSize;
+ public int EffectSize;
+ public int MixeSize;
+ public int SinkSize;
+ public int PerformanceManagerSize;
+ public int Unknown24;
+ public int Unknown28;
+ public int Unknown2C;
+ public int Unknown30;
+ public int Unknown34;
+ public int Unknown38;
+ public int TotalSize;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceChannelResourceIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceChannelResourceIn.cs
new file mode 100644
index 00000000..4871713e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceChannelResourceIn.cs
@@ -0,0 +1,10 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x70, Pack = 1)]
+ struct VoiceChannelResourceIn
+ {
+ // ???
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceIn.cs
new file mode 100644
index 00000000..dbcd5558
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceIn.cs
@@ -0,0 +1,49 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x170, Pack = 1)]
+ struct VoiceIn
+ {
+ public int VoiceSlot;
+ public int NodeId;
+
+ public byte FirstUpdate;
+ public byte Acquired;
+
+ public PlayState PlayState;
+
+ public SampleFormat SampleFormat;
+
+ public int SampleRate;
+
+ public int Priority;
+
+ public int Unknown14;
+
+ public int ChannelsCount;
+
+ public float Pitch;
+ public float Volume;
+
+ public BiquadFilter BiquadFilter0;
+ public BiquadFilter BiquadFilter1;
+
+ public int AppendedWaveBuffersCount;
+
+ public int BaseWaveBufferIndex;
+
+ public int Unknown44;
+
+ public long AdpcmCoeffsPosition;
+ public long AdpcmCoeffsSize;
+
+ public int VoiceDestination;
+ public int Padding;
+
+ public WaveBuffer WaveBuffer0;
+ public WaveBuffer WaveBuffer1;
+ public WaveBuffer WaveBuffer2;
+ public WaveBuffer WaveBuffer3;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceOut.cs
new file mode 100644
index 00000000..3a295971
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceOut.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
+ struct VoiceOut
+ {
+ public long PlayedSamplesCount;
+ public int PlayedWaveBuffersCount;
+ public int VoiceDropsCount; //?
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/WaveBuffer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/WaveBuffer.cs
new file mode 100644
index 00000000..1c0d5630
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/WaveBuffer.cs
@@ -0,0 +1,20 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x38, Pack = 1)]
+ struct WaveBuffer
+ {
+ public long Position;
+ public long Size;
+ public int FirstSampleOffset;
+ public int LastSampleOffset;
+ public byte Looping;
+ public byte LastBuffer;
+ public short Unknown1A;
+ public int Unknown1C;
+ public long AdpcmLoopContextPosition;
+ public long AdpcmLoopContextSize;
+ public long Unknown30;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/VoiceContext.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/VoiceContext.cs
new file mode 100644
index 00000000..c9fb8502
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/VoiceContext.cs
@@ -0,0 +1,199 @@
+using ARMeilleure.Memory;
+using Ryujinx.Audio.Adpcm;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
+{
+ class VoiceContext
+ {
+ private bool _acquired;
+ private bool _bufferReload;
+
+ private int _resamplerFracPart;
+
+ private int _bufferIndex;
+ private int _offset;
+
+ public int SampleRate { get; set; }
+ public int ChannelsCount { get; set; }
+
+ public float Volume { get; set; }
+
+ public PlayState PlayState { get; set; }
+
+ public SampleFormat SampleFormat { get; set; }
+
+ public AdpcmDecoderContext AdpcmCtx { get; set; }
+
+ public WaveBuffer[] WaveBuffers { get; }
+
+ public WaveBuffer CurrentWaveBuffer => WaveBuffers[_bufferIndex];
+
+ private VoiceOut _outStatus;
+
+ public VoiceOut OutStatus => _outStatus;
+
+ private int[] _samples;
+
+ public bool Playing => _acquired && PlayState == PlayState.Playing;
+
+ public VoiceContext()
+ {
+ WaveBuffers = new WaveBuffer[4];
+ }
+
+ public void SetAcquireState(bool newState)
+ {
+ if (_acquired && !newState)
+ {
+ // Release.
+ Reset();
+ }
+
+ _acquired = newState;
+ }
+
+ private void Reset()
+ {
+ _bufferReload = true;
+
+ _bufferIndex = 0;
+ _offset = 0;
+
+ _outStatus.PlayedSamplesCount = 0;
+ _outStatus.PlayedWaveBuffersCount = 0;
+ _outStatus.VoiceDropsCount = 0;
+ }
+
+ public int[] GetBufferData(IMemoryManager memory, int maxSamples, out int samplesCount)
+ {
+ if (!Playing)
+ {
+ samplesCount = 0;
+
+ return null;
+ }
+
+ if (_bufferReload)
+ {
+ _bufferReload = false;
+
+ UpdateBuffer(memory);
+ }
+
+ WaveBuffer wb = WaveBuffers[_bufferIndex];
+
+ int maxSize = _samples.Length - _offset;
+
+ int size = maxSamples * AudioConsts.HostChannelsCount;
+
+ if (size > maxSize)
+ {
+ size = maxSize;
+ }
+
+ int[] output = new int[size];
+
+ Array.Copy(_samples, _offset, output, 0, size);
+
+ samplesCount = size / AudioConsts.HostChannelsCount;
+
+ _outStatus.PlayedSamplesCount += samplesCount;
+
+ _offset += size;
+
+ if (_offset == _samples.Length)
+ {
+ _offset = 0;
+
+ if (wb.Looping == 0)
+ {
+ SetBufferIndex((_bufferIndex + 1) & 3);
+ }
+
+ _outStatus.PlayedWaveBuffersCount++;
+
+ if (wb.LastBuffer != 0)
+ {
+ PlayState = PlayState.Paused;
+ }
+ }
+
+ return output;
+ }
+
+ private void UpdateBuffer(IMemoryManager memory)
+ {
+ // TODO: Implement conversion for formats other
+ // than interleaved stereo (2 channels).
+ // As of now, it assumes that HostChannelsCount == 2.
+ WaveBuffer wb = WaveBuffers[_bufferIndex];
+
+ if (wb.Position == 0)
+ {
+ _samples = new int[0];
+
+ return;
+ }
+
+ if (SampleFormat == SampleFormat.PcmInt16)
+ {
+ int samplesCount = (int)(wb.Size / (sizeof(short) * ChannelsCount));
+
+ _samples = new int[samplesCount * AudioConsts.HostChannelsCount];
+
+ if (ChannelsCount == 1)
+ {
+ for (int index = 0; index < samplesCount; index++)
+ {
+ short sample = memory.ReadInt16(wb.Position + index * 2);
+
+ _samples[index * 2 + 0] = sample;
+ _samples[index * 2 + 1] = sample;
+ }
+ }
+ else
+ {
+ for (int index = 0; index < samplesCount * 2; index++)
+ {
+ _samples[index] = memory.ReadInt16(wb.Position + index * 2);
+ }
+ }
+ }
+ else if (SampleFormat == SampleFormat.Adpcm)
+ {
+ byte[] buffer = memory.ReadBytes(wb.Position, wb.Size);
+
+ _samples = AdpcmDecoder.Decode(buffer, AdpcmCtx);
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+
+ if (SampleRate != AudioConsts.HostSampleRate)
+ {
+ // TODO: We should keep the frames being discarded (see the 4 below)
+ // on a buffer and include it on the next samples buffer, to allow
+ // the resampler to do seamless interpolation between wave buffers.
+ int samplesCount = _samples.Length / AudioConsts.HostChannelsCount;
+
+ samplesCount = Math.Max(samplesCount - 4, 0);
+
+ _samples = Resampler.Resample2Ch(
+ _samples,
+ SampleRate,
+ AudioConsts.HostSampleRate,
+ samplesCount,
+ ref _resamplerFracPart);
+ }
+ }
+
+ public void SetBufferIndex(int index)
+ {
+ _bufferIndex = index & 3;
+
+ _bufferReload = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs b/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs
new file mode 100644
index 00000000..e23398df
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs
@@ -0,0 +1,81 @@
+using Concentus.Structs;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
+{
+ class IHardwareOpusDecoder : IpcService
+ {
+ private const int FixedSampleRate = 48000;
+
+ private int _sampleRate;
+ private int _channelsCount;
+
+ private OpusDecoder _decoder;
+
+ public IHardwareOpusDecoder(int sampleRate, int channelsCount)
+ {
+ _sampleRate = sampleRate;
+ _channelsCount = channelsCount;
+
+ _decoder = new OpusDecoder(FixedSampleRate, channelsCount);
+ }
+
+ [Command(0)]
+ // DecodeInterleaved(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
+ public ResultCode DecodeInterleaved(ServiceCtx context)
+ {
+ long inPosition = context.Request.SendBuff[0].Position;
+ long inSize = context.Request.SendBuff[0].Size;
+
+ if (inSize < 8)
+ {
+ return ResultCode.OpusInvalidInput;
+ }
+
+ long outPosition = context.Request.ReceiveBuff[0].Position;
+ long outSize = context.Request.ReceiveBuff[0].Size;
+
+ byte[] opusData = context.Memory.ReadBytes(inPosition, inSize);
+
+ int processed = ((opusData[0] << 24) |
+ (opusData[1] << 16) |
+ (opusData[2] << 8) |
+ (opusData[3] << 0)) + 8;
+
+ if ((uint)processed > (ulong)inSize)
+ {
+ return ResultCode.OpusInvalidInput;
+ }
+
+ short[] pcm = new short[outSize / 2];
+
+ int frameSize = pcm.Length / (_channelsCount * 2);
+
+ int samples = _decoder.Decode(opusData, 0, opusData.Length, pcm, 0, frameSize);
+
+ foreach (short sample in pcm)
+ {
+ context.Memory.WriteInt16(outPosition, sample);
+
+ outPosition += 2;
+ }
+
+ context.ResponseData.Write(processed);
+ context.ResponseData.Write(samples);
+
+ return ResultCode.Success;
+ }
+
+ [Command(4)]
+ // DecodeInterleavedWithPerf(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
+ public ResultCode DecodeInterleavedWithPerf(ServiceCtx context)
+ {
+ ResultCode result = DecodeInterleaved(context);
+
+ // TODO: Figure out what this value is.
+ // According to switchbrew, it is now used.
+ context.ResponseData.Write(0L);
+
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs
new file mode 100644
index 00000000..1bd2e31d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audctl")]
+ class IAudioController : IpcService
+ {
+ public IAudioController(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs
new file mode 100644
index 00000000..d8e1f468
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audin:u")]
+ class IAudioInManager : IpcService
+ {
+ public IAudioInManager(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs
new file mode 100644
index 00000000..37d9a8fe
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audin:a")]
+ class IAudioInManagerForApplet : IpcService
+ {
+ public IAudioInManagerForApplet(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs
new file mode 100644
index 00000000..1a497efb
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audin:d")]
+ class IAudioInManagerForDebugger : IpcService
+ {
+ public IAudioInManagerForDebugger(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs
new file mode 100644
index 00000000..19ee8067
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs
@@ -0,0 +1,162 @@
+using ARMeilleure.Memory;
+using Ryujinx.Audio;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Audio.AudioOutManager;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audout:u")]
+ class IAudioOutManager : IpcService
+ {
+ private const string DefaultAudioOutput = "DeviceOut";
+ private const int DefaultSampleRate = 48000;
+ private const int DefaultChannelsCount = 2;
+
+ public IAudioOutManager(ServiceCtx context) { }
+
+ [Command(0)]
+ // ListAudioOuts() -> (u32 count, buffer<bytes, 6>)
+ public ResultCode ListAudioOuts(ServiceCtx context)
+ {
+ return ListAudioOutsImpl(
+ context,
+ context.Request.ReceiveBuff[0].Position,
+ context.Request.ReceiveBuff[0].Size);
+ }
+
+ [Command(1)]
+ // OpenAudioOut(u32 sample_rate, u16 unused, u16 channel_count, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 5> name_in)
+ // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioOut>, buffer<bytes, 6> name_out)
+ public ResultCode OpenAudioOut(ServiceCtx context)
+ {
+ return OpenAudioOutImpl(
+ context,
+ context.Request.SendBuff[0].Position,
+ context.Request.SendBuff[0].Size,
+ context.Request.ReceiveBuff[0].Position,
+ context.Request.ReceiveBuff[0].Size);
+ }
+
+ [Command(2)] // 3.0.0+
+ // ListAudioOutsAuto() -> (u32 count, buffer<bytes, 0x22>)
+ public ResultCode ListAudioOutsAuto(ServiceCtx context)
+ {
+ (long recvPosition, long recvSize) = context.Request.GetBufferType0x22();
+
+ return ListAudioOutsImpl(context, recvPosition, recvSize);
+ }
+
+ [Command(3)] // 3.0.0+
+ // OpenAudioOutAuto(u32 sample_rate, u16 unused, u16 channel_count, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 0x21>)
+ // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioOut>, buffer<bytes, 0x22> name_out)
+ public ResultCode OpenAudioOutAuto(ServiceCtx context)
+ {
+ (long sendPosition, long sendSize) = context.Request.GetBufferType0x21();
+ (long recvPosition, long recvSize) = context.Request.GetBufferType0x22();
+
+ return OpenAudioOutImpl(
+ context,
+ sendPosition,
+ sendSize,
+ recvPosition,
+ recvSize);
+ }
+
+ private ResultCode ListAudioOutsImpl(ServiceCtx context, long position, long size)
+ {
+ int nameCount = 0;
+
+ byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(DefaultAudioOutput + "\0");
+
+ if ((ulong)deviceNameBuffer.Length <= (ulong)size)
+ {
+ context.Memory.WriteBytes(position, deviceNameBuffer);
+
+ nameCount++;
+ }
+ else
+ {
+ Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+ }
+
+ context.ResponseData.Write(nameCount);
+
+ return ResultCode.Success;
+ }
+
+ private ResultCode OpenAudioOutImpl(ServiceCtx context, long sendPosition, long sendSize, long receivePosition, long receiveSize)
+ {
+ string deviceName = MemoryHelper.ReadAsciiString(
+ context.Memory,
+ sendPosition,
+ sendSize);
+
+ if (deviceName == string.Empty)
+ {
+ deviceName = DefaultAudioOutput;
+ }
+
+ if (deviceName != DefaultAudioOutput)
+ {
+ Logger.PrintWarning(LogClass.Audio, "Invalid device name!");
+
+ return ResultCode.DeviceNotFound;
+ }
+
+ byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(deviceName + "\0");
+
+ if ((ulong)deviceNameBuffer.Length <= (ulong)receiveSize)
+ {
+ context.Memory.WriteBytes(receivePosition, deviceNameBuffer);
+ }
+ else
+ {
+ Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {receiveSize} too small!");
+ }
+
+ int sampleRate = context.RequestData.ReadInt32();
+ int channels = context.RequestData.ReadInt32();
+
+ if (sampleRate == 0)
+ {
+ sampleRate = DefaultSampleRate;
+ }
+
+ if (sampleRate != DefaultSampleRate)
+ {
+ Logger.PrintWarning(LogClass.Audio, "Invalid sample rate!");
+
+ return ResultCode.UnsupportedSampleRate;
+ }
+
+ channels = (ushort)channels;
+
+ if (channels == 0)
+ {
+ channels = DefaultChannelsCount;
+ }
+
+ KEvent releaseEvent = new KEvent(context.Device.System);
+
+ ReleaseCallback callback = () =>
+ {
+ releaseEvent.ReadableEvent.Signal();
+ };
+
+ IAalOutput audioOut = context.Device.AudioOut;
+
+ int track = audioOut.OpenTrack(sampleRate, channels, callback);
+
+ MakeObject(context, new IAudioOut(audioOut, releaseEvent, track));
+
+ context.ResponseData.Write(sampleRate);
+ context.ResponseData.Write(channels);
+ context.ResponseData.Write((int)SampleFormat.PcmInt16);
+ context.ResponseData.Write((int)PlaybackState.Stopped);
+
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs
new file mode 100644
index 00000000..4b41b0cf
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audout:a")]
+ class IAudioOutManagerForApplet : IpcService
+ {
+ public IAudioOutManagerForApplet(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs
new file mode 100644
index 00000000..41cde972
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audout:d")]
+ class IAudioOutManagerForDebugger : IpcService
+ {
+ public IAudioOutManagerForDebugger(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs
new file mode 100644
index 00000000..de1c35b5
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs
@@ -0,0 +1,193 @@
+using Ryujinx.Audio;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager;
+using Ryujinx.HLE.Utilities;
+
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audren:u")]
+ class IAudioRendererManager : IpcService
+ {
+ private const int Rev0Magic = ('R' << 0) |
+ ('E' << 8) |
+ ('V' << 16) |
+ ('0' << 24);
+
+ private const int Rev = 5;
+
+ public const int RevMagic = Rev0Magic + (Rev << 24);
+
+ public IAudioRendererManager(ServiceCtx context) { }
+
+ [Command(0)]
+ // OpenAudioRenderer(nn::audio::detail::AudioRendererParameterInternal, u64, nn::applet::AppletResourceUserId, pid, handle<copy>, handle<copy>)
+ // -> object<nn::audio::detail::IAudioRenderer>
+ public ResultCode OpenAudioRenderer(ServiceCtx context)
+ {
+ IAalOutput audioOut = context.Device.AudioOut;
+
+ AudioRendererParameter Params = GetAudioRendererParameter(context);
+
+ MakeObject(context, new IAudioRenderer(
+ context.Device.System,
+ context.Memory,
+ audioOut,
+ Params));
+
+ return ResultCode.Success;
+ }
+
+ [Command(1)]
+ // GetWorkBufferSize(nn::audio::detail::AudioRendererParameterInternal) -> u64
+ public ResultCode GetAudioRendererWorkBufferSize(ServiceCtx context)
+ {
+ AudioRendererParameter Params = GetAudioRendererParameter(context);
+
+ int revision = (Params.Revision - Rev0Magic) >> 24;
+
+ if (revision <= Rev)
+ {
+ bool isSplitterSupported = revision >= 3;
+ bool isVariadicCommandBufferSizeSupported = revision >= 5;
+
+ long size;
+
+ size = IntUtils.AlignUp(Params.Unknown8 * 4, 64);
+ size += Params.MixCount * 0x400;
+ size += (Params.MixCount + 1) * 0x940;
+ size += Params.VoiceCount * 0x3F0;
+ size += IntUtils.AlignUp((Params.MixCount + 1) * 8, 16);
+ size += IntUtils.AlignUp(Params.VoiceCount * 8, 16);
+ size += IntUtils.AlignUp(
+ ((Params.SinkCount + Params.MixCount) * 0x3C0 + Params.SampleCount * 4) *
+ (Params.Unknown8 + 6), 64);
+ size += (Params.SinkCount + Params.MixCount) * 0x2C0;
+ size += (Params.EffectCount + Params.VoiceCount * 4) * 0x30 + 0x50;
+
+ if (isSplitterSupported)
+ {
+ size += IntUtils.AlignUp((
+ NodeStatesGetWorkBufferSize(Params.MixCount + 1) +
+ EdgeMatrixGetWorkBufferSize(Params.MixCount + 1)), 16);
+
+ size += Params.SplitterDestinationDataCount * 0xE0;
+ size += Params.SplitterCount * 0x20;
+ size += IntUtils.AlignUp(Params.SplitterDestinationDataCount * 4, 16);
+ }
+
+ size = Params.EffectCount * 0x4C0 +
+ Params.SinkCount * 0x170 +
+ Params.VoiceCount * 0x100 +
+ IntUtils.AlignUp(size, 64) + 0x40;
+
+ if (Params.PerformanceManagerCount >= 1)
+ {
+ size += (((Params.EffectCount +
+ Params.SinkCount +
+ Params.VoiceCount +
+ Params.MixCount + 1) * 16 + 0x658) *
+ (Params.PerformanceManagerCount + 1) + 0x13F) & ~0x3FL;
+ }
+
+ if (isVariadicCommandBufferSizeSupported)
+ {
+ size += Params.EffectCount * 0x840 +
+ Params.MixCount * 0x5A38 +
+ Params.SinkCount * 0x148 +
+ Params.SplitterDestinationDataCount * 0x540 +
+ Params.VoiceCount * (Params.SplitterCount * 0x68 + 0x2E0) +
+ ((Params.VoiceCount + Params.MixCount + Params.EffectCount + Params.SinkCount + 0x65) << 6) + 0x3F8 + 0x7E;
+ }
+ else
+ {
+ size += 0x1807E;
+ }
+
+ size = size & ~0xFFFL;
+
+ context.ResponseData.Write(size);
+
+ Logger.PrintDebug(LogClass.ServiceAudio, $"WorkBufferSize is 0x{size:x16}.");
+
+ return ResultCode.Success;
+ }
+ else
+ {
+ context.ResponseData.Write(0L);
+
+ Logger.PrintWarning(LogClass.ServiceAudio, $"Library Revision 0x{Params.Revision:x8} is not supported!");
+
+ return ResultCode.UnsupportedRevision;
+ }
+ }
+
+ private AudioRendererParameter GetAudioRendererParameter(ServiceCtx context)
+ {
+ AudioRendererParameter Params = new AudioRendererParameter
+ {
+ SampleRate = context.RequestData.ReadInt32(),
+ SampleCount = context.RequestData.ReadInt32(),
+ Unknown8 = context.RequestData.ReadInt32(),
+ MixCount = context.RequestData.ReadInt32(),
+ VoiceCount = context.RequestData.ReadInt32(),
+ SinkCount = context.RequestData.ReadInt32(),
+ EffectCount = context.RequestData.ReadInt32(),
+ PerformanceManagerCount = context.RequestData.ReadInt32(),
+ VoiceDropEnable = context.RequestData.ReadInt32(),
+ SplitterCount = context.RequestData.ReadInt32(),
+ SplitterDestinationDataCount = context.RequestData.ReadInt32(),
+ Unknown2C = context.RequestData.ReadInt32(),
+ Revision = context.RequestData.ReadInt32()
+ };
+
+ return Params;
+ }
+
+ private static int NodeStatesGetWorkBufferSize(int value)
+ {
+ int result = IntUtils.AlignUp(value, 64);
+
+ if (result < 0)
+ {
+ result |= 7;
+ }
+
+ return 4 * (value * value) + 0x12 * value + 2 * (result / 8);
+ }
+
+ private static int EdgeMatrixGetWorkBufferSize(int value)
+ {
+ int result = IntUtils.AlignUp(value * value, 64);
+
+ if (result < 0)
+ {
+ result |= 7;
+ }
+
+ return result / 8;
+ }
+
+ [Command(2)]
+ // GetAudioDeviceService(nn::applet::AppletResourceUserId) -> object<nn::audio::detail::IAudioDevice>
+ public ResultCode GetAudioDeviceService(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ MakeObject(context, new IAudioDevice(context.Device.System));
+
+ return ResultCode.Success;
+ }
+
+ [Command(4)] // 4.0.0+
+ // GetAudioDeviceServiceWithRevisionInfo(nn::applet::AppletResourceUserId, u32) -> object<nn::audio::detail::IAudioDevice>
+ public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+ int revisionInfo = context.RequestData.ReadInt32();
+
+ Logger.PrintStub(LogClass.ServiceAudio, new { appletResourceUserId, revisionInfo });
+
+ return GetAudioDeviceService(context);
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs
new file mode 100644
index 00000000..ca5768cc
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audren:a")]
+ class IAudioRendererManagerForApplet : IpcService
+ {
+ public IAudioRendererManagerForApplet(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs
new file mode 100644
index 00000000..a970ae45
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audren:d")]
+ class IAudioRendererManagerForDebugger : IpcService
+ {
+ public IAudioRendererManagerForDebugger(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs
new file mode 100644
index 00000000..59e3ad09
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("auddev")] // 6.0.0+
+ class IAudioSnoopManager : IpcService
+ {
+ public IAudioSnoopManager(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs
new file mode 100644
index 00000000..01435008
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audrec:u")]
+ class IFinalOutputRecorderManager : IpcService
+ {
+ public IFinalOutputRecorderManager(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs b/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs
new file mode 100644
index 00000000..d8fd270d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audrec:a")]
+ class IFinalOutputRecorderManagerForApplet : IpcService
+ {
+ public IFinalOutputRecorderManagerForApplet(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs b/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs
new file mode 100644
index 00000000..a8ec51ee
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audrec:d")]
+ class IFinalOutputRecorderManagerForDebugger : IpcService
+ {
+ public IFinalOutputRecorderManagerForDebugger(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs
new file mode 100644
index 00000000..ed40cdad
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs
@@ -0,0 +1,65 @@
+using Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager;
+
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("hwopus")]
+ class IHardwareOpusDecoderManager : IpcService
+ {
+ public IHardwareOpusDecoderManager(ServiceCtx context) { }
+
+ [Command(0)]
+ // Initialize(bytes<8, 4>, u32, handle<copy>) -> object<nn::codec::detail::IHardwareOpusDecoder>
+ public ResultCode Initialize(ServiceCtx context)
+ {
+ int sampleRate = context.RequestData.ReadInt32();
+ int channelsCount = context.RequestData.ReadInt32();
+
+ MakeObject(context, new IHardwareOpusDecoder(sampleRate, channelsCount));
+
+ return ResultCode.Success;
+ }
+
+ [Command(1)]
+ // GetWorkBufferSize(bytes<8, 4>) -> u32
+ public ResultCode GetWorkBufferSize(ServiceCtx context)
+ {
+ // Note: The sample rate is ignored because it is fixed to 48KHz.
+ int sampleRate = context.RequestData.ReadInt32();
+ int channelsCount = context.RequestData.ReadInt32();
+
+ context.ResponseData.Write(GetOpusDecoderSize(channelsCount));
+
+ return ResultCode.Success;
+ }
+
+ private static int GetOpusDecoderSize(int channelsCount)
+ {
+ const int silkDecoderSize = 0x2198;
+
+ if (channelsCount < 1 || channelsCount > 2)
+ {
+ return 0;
+ }
+
+ int celtDecoderSize = GetCeltDecoderSize(channelsCount);
+
+ int opusDecoderSize = (channelsCount * 0x800 + 0x4807) & -0x800 | 0x50;
+
+ return opusDecoderSize + silkDecoderSize + celtDecoderSize;
+ }
+
+ private static int GetCeltDecoderSize(int channelsCount)
+ {
+ const int decodeBufferSize = 0x2030;
+ const int celtDecoderSize = 0x58;
+ const int celtSigSize = 0x4;
+ const int overlap = 120;
+ const int eBandsCount = 21;
+
+ return (decodeBufferSize + overlap * 4) * channelsCount +
+ eBandsCount * 16 +
+ celtDecoderSize +
+ celtSigSize;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs
new file mode 100644
index 00000000..5bba3582
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ enum ResultCode
+ {
+ ModuleId = 153,
+ ErrorCodeShift = 9,
+
+ Success = 0,
+
+ DeviceNotFound = (1 << ErrorCodeShift) | ModuleId,
+ UnsupportedRevision = (2 << ErrorCodeShift) | ModuleId,
+ UnsupportedSampleRate = (3 << ErrorCodeShift) | ModuleId,
+ OpusInvalidInput = (6 << ErrorCodeShift) | ModuleId
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/Types/SampleFormat.cs b/Ryujinx.HLE/HOS/Services/Audio/Types/SampleFormat.cs
new file mode 100644
index 00000000..654436e4
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/Types/SampleFormat.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ enum SampleFormat : byte
+ {
+ Invalid = 0,
+ PcmInt8 = 1,
+ PcmInt16 = 2,
+ PcmInt24 = 3,
+ PcmInt32 = 4,
+ PcmFloat = 5,
+ Adpcm = 6
+ }
+} \ No newline at end of file