diff --git a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
index 744a4bc56..01286992f 100644
--- a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
+++ b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
@@ -20,6 +20,25 @@ namespace Ryujinx.Audio.Backends.OpenAL
         private bool _stillRunning;
         private readonly Thread _updaterThread;
 
+        private float _volume;
+
+        public float Volume
+        {
+            get
+            {
+                return _volume;
+            }
+            set
+            {
+                _volume = value;
+
+                foreach (OpenALHardwareDeviceSession session in _sessions.Keys)
+                {
+                    session.UpdateMasterVolume(value);
+                }
+            }
+        }
+
         public OpenALHardwareDeviceDriver()
         {
             _device = ALC.OpenDevice("");
@@ -34,6 +53,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
                 Name = "HardwareDeviceDriver.OpenAL",
             };
 
+            _volume = 1f;
+
             _updaterThread.Start();
         }
 
@@ -52,7 +73,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
             }
         }
 
-        public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
+        public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
         {
             if (channelCount == 0)
             {
@@ -73,7 +94,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
                 throw new ArgumentException($"{channelCount}");
             }
 
-            OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
+            OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
 
             _sessions.TryAdd(session, 0);
 
diff --git a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs
index a52821616..3b9129130 100644
--- a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs
+++ b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs
@@ -16,10 +16,11 @@ namespace Ryujinx.Audio.Backends.OpenAL
         private bool _isActive;
         private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
         private ulong _playedSampleCount;
+        private float _volume;
 
         private readonly object _lock = new();
 
-        public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
+        public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
         {
             _driver = driver;
             _queuedBuffers = new Queue<OpenALAudioBuffer>();
@@ -27,7 +28,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
             _targetFormat = GetALFormat();
             _isActive = false;
             _playedSampleCount = 0;
-            SetVolume(requestedVolume);
+            SetVolume(1f);
         }
 
         private ALFormat GetALFormat()
@@ -85,17 +86,22 @@ namespace Ryujinx.Audio.Backends.OpenAL
 
         public override void SetVolume(float volume)
         {
-            lock (_lock)
-            {
-                AL.Source(_sourceId, ALSourcef.Gain, volume);
-            }
+            _volume = volume;
+
+            UpdateMasterVolume(_driver.Volume);
         }
 
         public override float GetVolume()
         {
-            AL.GetSource(_sourceId, ALSourcef.Gain, out float volume);
+            return _volume;
+        }
 
-            return volume;
+        public void UpdateMasterVolume(float newVolume)
+        {
+            lock (_lock)
+            {
+                AL.Source(_sourceId, ALSourcef.Gain, newVolume * _volume);
+            }
         }
 
         public override void Start()
diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
index b83e63dbc..e39bfe549 100644
--- a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
+++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
@@ -20,6 +20,8 @@ namespace Ryujinx.Audio.Backends.SDL2
 
         private readonly bool _supportSurroundConfiguration;
 
+        public float Volume { get; set; }
+
         // TODO: Add this to SDL2-CS
         // NOTE: We use a DllImport here because of marshaling issue for spec.
 #pragma warning disable SYSLIB1054
@@ -48,6 +50,8 @@ namespace Ryujinx.Audio.Backends.SDL2
             {
                 _supportSurroundConfiguration = spec.channels >= 6;
             }
+
+            Volume = 1f;
         }
 
         public static bool IsSupported => IsSupportedInternal();
@@ -74,7 +78,7 @@ namespace Ryujinx.Audio.Backends.SDL2
             return _pauseEvent;
         }
 
-        public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
+        public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
         {
             if (channelCount == 0)
             {
@@ -91,7 +95,7 @@ namespace Ryujinx.Audio.Backends.SDL2
                 throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
             }
 
-            SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
+            SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
 
             _sessions.TryAdd(session, 0);
 
diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
index 7a683f4ed..00188ba58 100644
--- a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
+++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
@@ -26,7 +26,7 @@ namespace Ryujinx.Audio.Backends.SDL2
         private float _volume;
         private readonly ushort _nativeSampleFormat;
 
-        public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
+        public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
         {
             _driver = driver;
             _updateRequiredEvent = _driver.GetUpdateRequiredEvent();
@@ -37,7 +37,7 @@ namespace Ryujinx.Audio.Backends.SDL2
             _nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
             _sampleCount = uint.MaxValue;
             _started = false;
-            _volume = requestedVolume;
+            _volume = 1f;
         }
 
         private void EnsureAudioStreamSetup(AudioBuffer buffer)
@@ -99,7 +99,7 @@ namespace Ryujinx.Audio.Backends.SDL2
                 streamSpan.Clear();
 
                 // Apply volume to written data
-                SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME));
+                SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME));
             }
 
             ulong sampleCount = GetSampleCount(samples.Length);
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
index ff0392882..e3e5d2913 100644
--- a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
+++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
@@ -19,6 +19,25 @@ namespace Ryujinx.Audio.Backends.SoundIo
         private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
         private int _disposeState;
 
+        private float _volume = 1f;
+
+        public float Volume
+        {
+            get
+            {
+                return _volume;
+            }
+            set
+            {
+                _volume = value;
+
+                foreach (SoundIoHardwareDeviceSession session in _sessions.Keys)
+                {
+                    session.UpdateMasterVolume(value);
+                }
+            }
+        }
+
         public SoundIoHardwareDeviceDriver()
         {
             _audioContext = SoundIoContext.Create();
@@ -122,7 +141,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
             return _pauseEvent;
         }
 
-        public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
+        public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
         {
             if (channelCount == 0)
             {
@@ -134,14 +153,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
                 sampleRate = Constants.TargetSampleRate;
             }
 
-            volume = Math.Clamp(volume, 0, 1);
-
             if (direction != Direction.Output)
             {
                 throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
             }
 
-            SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
+            SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
 
             _sessions.TryAdd(session, 0);
 
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
index 123cfd27a..f60982e30 100644
--- a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
+++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
@@ -18,16 +18,18 @@ namespace Ryujinx.Audio.Backends.SoundIo
         private readonly DynamicRingBuffer _ringBuffer;
         private ulong _playedSampleCount;
         private readonly ManualResetEvent _updateRequiredEvent;
+        private float _volume;
         private int _disposeState;
 
-        public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
+        public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
         {
             _driver = driver;
             _updateRequiredEvent = _driver.GetUpdateRequiredEvent();
             _queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
             _ringBuffer = new DynamicRingBuffer();
+            _volume = 1f;
 
-            SetupOutputStream(requestedVolume);
+            SetupOutputStream(driver.Volume);
         }
 
         private void SetupOutputStream(float requestedVolume)
@@ -47,7 +49,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
 
         public override float GetVolume()
         {
-            return _outputStream.Volume;
+            return _volume;
         }
 
         public override void PrepareToClose() { }
@@ -63,7 +65,14 @@ namespace Ryujinx.Audio.Backends.SoundIo
 
         public override void SetVolume(float volume)
         {
-            _outputStream.SetVolume(volume);
+            _volume = volume;
+
+            _outputStream.SetVolume(_driver.Volume * volume);
+        }
+
+        public void UpdateMasterVolume(float newVolume)
+        {
+            _outputStream.SetVolume(newVolume * _volume);
         }
 
         public override void Start()
diff --git a/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs
index 3f3806c3e..a2c2cdcd0 100644
--- a/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs
+++ b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs
@@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Backends.CompatLayer
 
         public static bool IsSupported => true;
 
+        public float Volume
+        {
+            get => _realDriver.Volume;
+            set => _realDriver.Volume = value;
+        }
+
         public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
         {
             _realDriver = realDevice;
@@ -90,7 +96,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
             throw new ArgumentException("No valid sample format configuration found!");
         }
 
-        public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
+        public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
         {
             if (channelCount == 0)
             {
@@ -102,8 +108,6 @@ namespace Ryujinx.Audio.Backends.CompatLayer
                 sampleRate = Constants.TargetSampleRate;
             }
 
-            volume = Math.Clamp(volume, 0, 1);
-
             if (!_realDriver.SupportsDirection(direction))
             {
                 if (direction == Direction.Input)
@@ -119,7 +123,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
             SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
             uint hardwareChannelCount = SelectHardwareChannelCount(channelCount);
 
-            IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount, volume);
+            IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount);
 
             if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat)
             {
diff --git a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs
index bac21c448..3a3c1d1b1 100644
--- a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs
+++ b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs
@@ -14,13 +14,17 @@ namespace Ryujinx.Audio.Backends.Dummy
 
         public static bool IsSupported => true;
 
+        public float Volume { get; set; }
+
         public DummyHardwareDeviceDriver()
         {
             _updateRequiredEvent = new ManualResetEvent(false);
             _pauseEvent = new ManualResetEvent(true);
+
+            Volume = 1f;
         }
 
-        public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
+        public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
         {
             if (sampleRate == 0)
             {
@@ -34,7 +38,7 @@ namespace Ryujinx.Audio.Backends.Dummy
 
             if (direction == Direction.Output)
             {
-                return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
+                return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount);
             }
 
             return new DummyHardwareDeviceSessionInput(this, memoryManager);
diff --git a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs
index 1c248faaa..34cf653c2 100644
--- a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs
+++ b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs
@@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Backends.Dummy
 
         private ulong _playedSampleCount;
 
-        public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
+        public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
         {
-            _volume = requestedVolume;
+            _volume = 1f;
             _manager = manager;
         }
 
diff --git a/src/Ryujinx.Audio/Input/AudioInputManager.cs b/src/Ryujinx.Audio/Input/AudioInputManager.cs
index 4d1796c96..d56997e9c 100644
--- a/src/Ryujinx.Audio/Input/AudioInputManager.cs
+++ b/src/Ryujinx.Audio/Input/AudioInputManager.cs
@@ -166,7 +166,6 @@ namespace Ryujinx.Audio.Input
         /// </summary>
         /// <param name="filtered">If true, filter disconnected devices</param>
         /// <returns>The list of all audio inputs name</returns>
-#pragma warning disable CA1822 // Mark member as static
         public string[] ListAudioIns(bool filtered)
         {
             if (filtered)
@@ -176,7 +175,6 @@ namespace Ryujinx.Audio.Input
 
             return new[] { Constants.DefaultDeviceInputName };
         }
-#pragma warning restore CA1822
 
         /// <summary>
         /// Open a new <see cref="AudioInputSystem"/>.
@@ -188,8 +186,6 @@ namespace Ryujinx.Audio.Input
         /// <param name="inputDeviceName">The input device name wanted by the user</param>
         /// <param name="sampleFormat">The sample format to use</param>
         /// <param name="parameter">The user configuration</param>
-        /// <param name="appletResourceUserId">The applet resource user id of the application</param>
-        /// <param name="processHandle">The process handle of the application</param>
         /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
         public ResultCode OpenAudioIn(out string outputDeviceName,
                                       out AudioOutputConfiguration outputConfiguration,
@@ -197,9 +193,7 @@ namespace Ryujinx.Audio.Input
                                       IVirtualMemoryManager memoryManager,
                                       string inputDeviceName,
                                       SampleFormat sampleFormat,
-                                      ref AudioInputConfiguration parameter,
-                                      ulong appletResourceUserId,
-                                      uint processHandle)
+                                      ref AudioInputConfiguration parameter)
         {
             int sessionId = AcquireSessionId();
 
diff --git a/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs b/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs
index 576954b96..1369f953a 100644
--- a/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs
+++ b/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs
@@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Integration
 
         private readonly byte[] _buffer;
 
-        public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate, float volume)
+        public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate)
         {
-            _session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount, volume);
+            _session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount);
             _channelCount = channelCount;
             _sampleRate = sampleRate;
             _currentBufferTag = 0;
diff --git a/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs
index 9c812fb9a..95b0e4e5e 100644
--- a/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs
+++ b/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs
@@ -16,7 +16,9 @@ namespace Ryujinx.Audio.Integration
             Output,
         }
 
-        IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume = 1f);
+        float Volume { get; set; }
+
+        IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount);
 
         ManualResetEvent GetUpdateRequiredEvent();
         ManualResetEvent GetPauseEvent();
diff --git a/src/Ryujinx.Audio/Output/AudioOutputManager.cs b/src/Ryujinx.Audio/Output/AudioOutputManager.cs
index 5232357bb..308cd1564 100644
--- a/src/Ryujinx.Audio/Output/AudioOutputManager.cs
+++ b/src/Ryujinx.Audio/Output/AudioOutputManager.cs
@@ -165,12 +165,10 @@ namespace Ryujinx.Audio.Output
         /// Get the list of all audio outputs name.
         /// </summary>
         /// <returns>The list of all audio outputs name</returns>
-#pragma warning disable CA1822 // Mark member as static
         public string[] ListAudioOuts()
         {
             return new[] { Constants.DefaultDeviceOutputName };
         }
-#pragma warning restore CA1822
 
         /// <summary>
         /// Open a new <see cref="AudioOutputSystem"/>.
@@ -182,9 +180,6 @@ namespace Ryujinx.Audio.Output
         /// <param name="inputDeviceName">The input device name wanted by the user</param>
         /// <param name="sampleFormat">The sample format to use</param>
         /// <param name="parameter">The user configuration</param>
-        /// <param name="appletResourceUserId">The applet resource user id of the application</param>
-        /// <param name="processHandle">The process handle of the application</param>
-        /// <param name="volume">The volume level to request</param>
         /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
         public ResultCode OpenAudioOut(out string outputDeviceName,
                                        out AudioOutputConfiguration outputConfiguration,
@@ -192,16 +187,13 @@ namespace Ryujinx.Audio.Output
                                        IVirtualMemoryManager memoryManager,
                                        string inputDeviceName,
                                        SampleFormat sampleFormat,
-                                       ref AudioInputConfiguration parameter,
-                                       ulong appletResourceUserId,
-                                       uint processHandle,
-                                       float volume)
+                                       ref AudioInputConfiguration parameter)
         {
             int sessionId = AcquireSessionId();
 
             _sessionsBufferEvents[sessionId].Clear();
 
-            IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount, volume);
+            IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount);
 
             AudioOutputSystem audioOut = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]);
 
@@ -234,41 +226,6 @@ namespace Ryujinx.Audio.Output
             return result;
         }
 
-        /// <summary>
-        /// Sets the volume for all output devices.
-        /// </summary>
-        /// <param name="volume">The volume to set.</param>
-        public void SetVolume(float volume)
-        {
-            if (_sessions != null)
-            {
-                foreach (AudioOutputSystem session in _sessions)
-                {
-                    session?.SetVolume(volume);
-                }
-            }
-        }
-
-        /// <summary>
-        /// Gets the volume for all output devices.
-        /// </summary>
-        /// <returns>A float indicating the volume level.</returns>
-        public float GetVolume()
-        {
-            if (_sessions != null)
-            {
-                foreach (AudioOutputSystem session in _sessions)
-                {
-                    if (session != null)
-                    {
-                        return session.GetVolume();
-                    }
-                }
-            }
-
-            return 0.0f;
-        }
-
         public void Dispose()
         {
             GC.SuppressFinalize(this);
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs b/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs
index 9c885b2cf..3e11df056 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs
@@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
             _event = new ManualResetEvent(false);
         }
 
-#pragma warning disable IDE0051 // Remove unused private member
         private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
         {
             // Get the real device driver (In case the compat layer is on top of it).
@@ -59,9 +58,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
             // NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible.
             return 2;
         }
-#pragma warning restore IDE0051
 
-        public void Start(IHardwareDeviceDriver deviceDriver, float volume)
+        public void Start(IHardwareDeviceDriver deviceDriver)
         {
             OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
 
@@ -70,7 +68,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
             for (int i = 0; i < OutputDevices.Length; i++)
             {
                 // TODO: Don't hardcode sample rate.
-                OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate, volume);
+                OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate);
             }
 
             _mailbox = new Mailbox<MailboxMessage>();
@@ -231,33 +229,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
             _mailbox.SendResponse(MailboxMessage.Stop);
         }
 
-        public float GetVolume()
-        {
-            if (OutputDevices != null)
-            {
-                foreach (IHardwareDevice outputDevice in OutputDevices)
-                {
-                    if (outputDevice != null)
-                    {
-                        return outputDevice.GetVolume();
-                    }
-                }
-            }
-
-            return 0f;
-        }
-
-        public void SetVolume(float volume)
-        {
-            if (OutputDevices != null)
-            {
-                foreach (IHardwareDevice outputDevice in OutputDevices)
-                {
-                    outputDevice?.SetVolume(volume);
-                }
-            }
-        }
-
         public void Dispose()
         {
             GC.SuppressFinalize(this);
@@ -269,6 +240,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
             if (disposing)
             {
                 _event.Dispose();
+                _mailbox?.Dispose();
             }
         }
     }
diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
index 0dbbd26c8..e334a89f6 100644
--- a/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
@@ -177,12 +177,12 @@ namespace Ryujinx.Audio.Renderer.Server
         /// <summary>
         /// Start the <see cref="AudioProcessor"/> and worker thread.
         /// </summary>
-        private void StartLocked(float volume)
+        private void StartLocked()
         {
             _isRunning = true;
 
             // TODO: virtual device mapping (IAudioDevice)
-            Processor.Start(_deviceDriver, volume);
+            Processor.Start(_deviceDriver);
 
             _workerThread = new Thread(SendCommands)
             {
@@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server
         /// Register a new <see cref="AudioRenderSystem"/>.
         /// </summary>
         /// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
-        private void Register(AudioRenderSystem renderer, float volume)
+        private void Register(AudioRenderSystem renderer)
         {
             lock (_sessionLock)
             {
@@ -265,7 +265,7 @@ namespace Ryujinx.Audio.Renderer.Server
             {
                 if (!_isRunning)
                 {
-                    StartLocked(volume);
+                    StartLocked();
                 }
             }
         }
@@ -312,8 +312,7 @@ namespace Ryujinx.Audio.Renderer.Server
             ulong appletResourceUserId,
             ulong workBufferAddress,
             ulong workBufferSize,
-            uint processHandle,
-            float volume)
+            uint processHandle)
         {
             int sessionId = AcquireSessionId();
 
@@ -338,7 +337,7 @@ namespace Ryujinx.Audio.Renderer.Server
             {
                 renderer = audioRenderer;
 
-                Register(renderer, volume);
+                Register(renderer);
             }
             else
             {
@@ -350,21 +349,6 @@ namespace Ryujinx.Audio.Renderer.Server
             return result;
         }
 
-        public float GetVolume()
-        {
-            if (Processor != null)
-            {
-                return Processor.GetVolume();
-            }
-
-            return 0f;
-        }
-
-        public void SetVolume(float volume)
-        {
-            Processor?.SetVolume(volume);
-        }
-
         public void Dispose()
         {
             GC.SuppressFinalize(this);
diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs
index cd3365ce2..64b08e309 100644
--- a/src/Ryujinx.HLE/HOS/Horizon.cs
+++ b/src/Ryujinx.HLE/HOS/Horizon.cs
@@ -4,12 +4,6 @@ using LibHac.Fs;
 using LibHac.Fs.Shim;
 using LibHac.FsSystem;
 using LibHac.Tools.FsSystem;
-using Ryujinx.Audio;
-using Ryujinx.Audio.Input;
-using Ryujinx.Audio.Integration;
-using Ryujinx.Audio.Output;
-using Ryujinx.Audio.Renderer.Device;
-using Ryujinx.Audio.Renderer.Server;
 using Ryujinx.Cpu;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS.Kernel;
@@ -20,7 +14,6 @@ using Ryujinx.HLE.HOS.Services;
 using Ryujinx.HLE.HOS.Services.Account.Acc;
 using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
 using Ryujinx.HLE.HOS.Services.Apm;
-using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
 using Ryujinx.HLE.HOS.Services.Caps;
 using Ryujinx.HLE.HOS.Services.Mii;
 using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
@@ -61,11 +54,6 @@ namespace Ryujinx.HLE.HOS
         internal ITickSource TickSource { get; }
 
         internal SurfaceFlinger SurfaceFlinger { get; private set; }
-        internal AudioManager AudioManager { get; private set; }
-        internal AudioOutputManager AudioOutputManager { get; private set; }
-        internal AudioInputManager AudioInputManager { get; private set; }
-        internal AudioRendererManager AudioRendererManager { get; private set; }
-        internal VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; private set; }
 
         public SystemStateMgr State { get; private set; }
 
@@ -79,8 +67,6 @@ namespace Ryujinx.HLE.HOS
 
         internal ServerBase SmServer { get; private set; }
         internal ServerBase BsdServer { get; private set; }
-        internal ServerBase AudRenServer { get; private set; }
-        internal ServerBase AudOutServer { get; private set; }
         internal ServerBase FsServer { get; private set; }
         internal ServerBase HidServer { get; private set; }
         internal ServerBase NvDrvServer { get; private set; }
@@ -248,56 +234,6 @@ namespace Ryujinx.HLE.HOS
             HostSyncpoint = new NvHostSyncpt(device);
 
             SurfaceFlinger = new SurfaceFlinger(device);
-
-            InitializeAudioRenderer(TickSource);
-        }
-
-        private void InitializeAudioRenderer(ITickSource tickSource)
-        {
-            AudioManager = new AudioManager();
-            AudioOutputManager = new AudioOutputManager();
-            AudioInputManager = new AudioInputManager();
-            AudioRendererManager = new AudioRendererManager(tickSource);
-            AudioRendererManager.SetVolume(Device.Configuration.AudioVolume);
-            AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(Device.AudioDeviceDriver);
-
-            IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax];
-
-            for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++)
-            {
-                KEvent registerBufferEvent = new(KernelContext);
-
-                audioOutputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent);
-            }
-
-            AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents);
-            AudioOutputManager.SetVolume(Device.Configuration.AudioVolume);
-
-            IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax];
-
-            for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++)
-            {
-                KEvent registerBufferEvent = new(KernelContext);
-
-                audioInputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent);
-            }
-
-            AudioInputManager.Initialize(Device.AudioDeviceDriver, audioInputRegisterBufferEvents);
-
-            IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax];
-
-            for (int i = 0; i < systemEvents.Length; i++)
-            {
-                KEvent systemEvent = new(KernelContext);
-
-                systemEvents[i] = new AudioKernelEvent(systemEvent);
-            }
-
-            AudioManager.Initialize(Device.AudioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update);
-
-            AudioRendererManager.Initialize(systemEvents, Device.AudioDeviceDriver);
-
-            AudioManager.Start();
         }
 
         public void InitializeServices()
@@ -310,8 +246,6 @@ namespace Ryujinx.HLE.HOS
             SmServer.InitDone.WaitOne();
 
             BsdServer = new ServerBase(KernelContext, "BsdServer");
-            AudRenServer = new ServerBase(KernelContext, "AudioRendererServer");
-            AudOutServer = new ServerBase(KernelContext, "AudioOutServer");
             FsServer = new ServerBase(KernelContext, "FsServer");
             HidServer = new ServerBase(KernelContext, "HidServer");
             NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
@@ -329,7 +263,13 @@ namespace Ryujinx.HLE.HOS
             HorizonFsClient fsClient = new(this);
 
             ServiceTable = new ServiceTable();
-            var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient, AccountManager));
+            var services = ServiceTable.GetServices(new HorizonOptions
+                (Device.Configuration.IgnoreMissingServices,
+                LibHacHorizonManager.BcatClient,
+                fsClient,
+                AccountManager,
+                Device.AudioDeviceDriver,
+                TickSource));
 
             foreach (var service in services)
             {
@@ -384,17 +324,6 @@ namespace Ryujinx.HLE.HOS
             }
         }
 
-        public void SetVolume(float volume)
-        {
-            AudioOutputManager.SetVolume(volume);
-            AudioRendererManager.SetVolume(volume);
-        }
-
-        public float GetVolume()
-        {
-            return AudioOutputManager.GetVolume() == 0 ? AudioRendererManager.GetVolume() : AudioOutputManager.GetVolume();
-        }
-
         public void ReturnFocus()
         {
             AppletState.SetFocus(true);
@@ -458,11 +387,7 @@ namespace Ryujinx.HLE.HOS
                 // "Soft" stops AudioRenderer and AudioManager to avoid some sound between resume and stop.
                 if (IsPaused)
                 {
-                    AudioManager.StopUpdates();
-
                     TogglePauseEmulation(false);
-
-                    AudioRendererManager.StopSendingCommands();
                 }
 
                 KProcess terminationProcess = new(KernelContext);
@@ -513,12 +438,6 @@ namespace Ryujinx.HLE.HOS
                 // This is safe as KThread that are likely to call ioctls are going to be terminated by the post handler hook on the SVC facade.
                 INvDrvServices.Destroy();
 
-                AudioManager.Dispose();
-                AudioOutputManager.Dispose();
-                AudioInputManager.Dispose();
-
-                AudioRendererManager.Dispose();
-
                 if (LibHacHorizonManager.ApplicationClient != null)
                 {
                     LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs
new file mode 100644
index 000000000..738d6b64a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs
@@ -0,0 +1,25 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
+{
+    readonly struct ExternalEvent : IExternalEvent
+    {
+        private readonly KWritableEvent _writableEvent;
+
+        public ExternalEvent(KWritableEvent writableEvent)
+        {
+            _writableEvent = writableEvent;
+        }
+
+        public readonly void Signal()
+        {
+            _writableEvent.Signal();
+        }
+
+        public readonly void Clear()
+        {
+            _writableEvent.Clear();
+        }
+    }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
index b07f5194e..6595ecef2 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
@@ -8,6 +8,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.HOS.Kernel.Threading;
 using Ryujinx.Horizon.Common;
+using Ryujinx.Memory;
 using System;
 using System.Buffers;
 using System.Threading;
@@ -3142,6 +3143,37 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
         }
 #pragma warning restore CA1822
 
+        // Not actual syscalls, used by HLE services and such.
+
+        public IExternalEvent GetExternalEvent(int handle)
+        {
+            KWritableEvent writableEvent = KernelStatic.GetCurrentProcess().HandleTable.GetObject<KWritableEvent>(handle);
+
+            if (writableEvent == null)
+            {
+                return null;
+            }
+
+            return new ExternalEvent(writableEvent);
+        }
+
+        public IVirtualMemoryManager GetMemoryManagerByProcessHandle(int handle)
+        {
+            return KernelStatic.GetCurrentProcess().HandleTable.GetKProcess(handle).CpuMemory;
+        }
+
+        public ulong GetTransferMemoryAddress(int handle)
+        {
+            KTransferMemory transferMemory = KernelStatic.GetCurrentProcess().HandleTable.GetObject<KTransferMemory>(handle);
+
+            if (transferMemory == null)
+            {
+                return 0;
+            }
+
+            return transferMemory.Address;
+        }
+
         private static bool IsPointingInsideKernel(ulong address)
         {
             return (address + 0x1000000000) < 0xffffff000;
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs
deleted file mode 100644
index acf83f488..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs
+++ /dev/null
@@ -1,108 +0,0 @@
-using Ryujinx.Audio.Common;
-using Ryujinx.Audio.Input;
-using Ryujinx.Audio.Integration;
-using Ryujinx.HLE.HOS.Kernel;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
-using System;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
-{
-    class AudioIn : IAudioIn
-    {
-        private readonly AudioInputSystem _system;
-        private readonly uint _processHandle;
-        private readonly KernelContext _kernelContext;
-
-        public AudioIn(AudioInputSystem system, KernelContext kernelContext, uint processHandle)
-        {
-            _system = system;
-            _kernelContext = kernelContext;
-            _processHandle = processHandle;
-        }
-
-        public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer)
-        {
-            return (ResultCode)_system.AppendBuffer(bufferTag, ref buffer);
-        }
-
-        public ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer buffer, uint handle)
-        {
-            return (ResultCode)_system.AppendUacBuffer(bufferTag, ref buffer, handle);
-        }
-
-        public bool ContainsBuffer(ulong bufferTag)
-        {
-            return _system.ContainsBuffer(bufferTag);
-        }
-
-        public void Dispose()
-        {
-            Dispose(true);
-        }
-
-        protected virtual void Dispose(bool disposing)
-        {
-            if (disposing)
-            {
-                _system.Dispose();
-
-                _kernelContext.Syscall.CloseHandle((int)_processHandle);
-            }
-        }
-
-        public bool FlushBuffers()
-        {
-            return _system.FlushBuffers();
-        }
-
-        public uint GetBufferCount()
-        {
-            return _system.GetBufferCount();
-        }
-
-        public ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount)
-        {
-            return (ResultCode)_system.GetReleasedBuffers(releasedBuffers, out releasedCount);
-        }
-
-        public AudioDeviceState GetState()
-        {
-            return _system.GetState();
-        }
-
-        public float GetVolume()
-        {
-            return _system.GetVolume();
-        }
-
-        public KEvent RegisterBufferEvent()
-        {
-            IWritableEvent outEvent = _system.RegisterBufferEvent();
-
-            if (outEvent is AudioKernelEvent kernelEvent)
-            {
-                return kernelEvent.Event;
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
-        }
-
-        public void SetVolume(float volume)
-        {
-            _system.SetVolume(volume);
-        }
-
-        public ResultCode Start()
-        {
-            return (ResultCode)_system.Start();
-        }
-
-        public ResultCode Stop()
-        {
-            return (ResultCode)_system.Stop();
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs
deleted file mode 100644
index 3f138021c..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs
+++ /dev/null
@@ -1,200 +0,0 @@
-using Ryujinx.Audio.Common;
-using Ryujinx.Cpu;
-using Ryujinx.HLE.HOS.Ipc;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.Horizon.Common;
-using Ryujinx.Memory;
-using System;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
-{
-    class AudioInServer : DisposableIpcService
-    {
-        private readonly IAudioIn _impl;
-
-        public AudioInServer(IAudioIn impl)
-        {
-            _impl = impl;
-        }
-
-        [CommandCmif(0)]
-        // GetAudioInState() -> u32 state
-        public ResultCode GetAudioInState(ServiceCtx context)
-        {
-            context.ResponseData.Write((uint)_impl.GetState());
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(1)]
-        // Start()
-        public ResultCode Start(ServiceCtx context)
-        {
-            return _impl.Start();
-        }
-
-        [CommandCmif(2)]
-        // Stop()
-        public ResultCode StopAudioIn(ServiceCtx context)
-        {
-            return _impl.Stop();
-        }
-
-        [CommandCmif(3)]
-        // AppendAudioInBuffer(u64 tag, buffer<nn::audio::AudioInBuffer, 5>)
-        public ResultCode AppendAudioInBuffer(ServiceCtx context)
-        {
-            ulong position = context.Request.SendBuff[0].Position;
-
-            ulong bufferTag = context.RequestData.ReadUInt64();
-
-            AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
-
-            return _impl.AppendBuffer(bufferTag, ref data);
-        }
-
-        [CommandCmif(4)]
-        // RegisterBufferEvent() -> handle<copy>
-        public ResultCode RegisterBufferEvent(ServiceCtx context)
-        {
-            KEvent bufferEvent = _impl.RegisterBufferEvent();
-
-            if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != Result.Success)
-            {
-                throw new InvalidOperationException("Out of handles!");
-            }
-
-            context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(5)]
-        // GetReleasedAudioInBuffers() -> (u32 count, buffer<u64, 6> tags)
-        public ResultCode GetReleasedAudioInBuffers(ServiceCtx context)
-        {
-            ulong position = context.Request.ReceiveBuff[0].Position;
-            ulong size = context.Request.ReceiveBuff[0].Size;
-
-            using WritableRegion outputRegion = context.Memory.GetWritableRegion((ulong)position, (int)size);
-            ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
-
-            context.ResponseData.Write(releasedCount);
-
-            return result;
-        }
-
-        [CommandCmif(6)]
-        // ContainsAudioInBuffer(u64 tag) -> b8
-        public ResultCode ContainsAudioInBuffer(ServiceCtx context)
-        {
-            ulong bufferTag = context.RequestData.ReadUInt64();
-
-            context.ResponseData.Write(_impl.ContainsBuffer(bufferTag));
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(7)] // 3.0.0+
-        // AppendUacInBuffer(u64 tag, handle<copy, unknown>, buffer<nn::audio::AudioInBuffer, 5>)
-        public ResultCode AppendUacInBuffer(ServiceCtx context)
-        {
-            ulong position = context.Request.SendBuff[0].Position;
-
-            ulong bufferTag = context.RequestData.ReadUInt64();
-            uint handle = (uint)context.Request.HandleDesc.ToCopy[0];
-
-            AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
-
-            return _impl.AppendUacBuffer(bufferTag, ref data, handle);
-        }
-
-        [CommandCmif(8)] // 3.0.0+
-        // AppendAudioInBufferAuto(u64 tag, buffer<nn::audio::AudioInBuffer, 0x21>)
-        public ResultCode AppendAudioInBufferAuto(ServiceCtx context)
-        {
-            (ulong position, _) = context.Request.GetBufferType0x21();
-
-            ulong bufferTag = context.RequestData.ReadUInt64();
-
-            AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
-
-            return _impl.AppendBuffer(bufferTag, ref data);
-        }
-
-        [CommandCmif(9)] // 3.0.0+
-        // GetReleasedAudioInBuffersAuto() -> (u32 count, buffer<u64, 0x22> tags)
-        public ResultCode GetReleasedAudioInBuffersAuto(ServiceCtx context)
-        {
-            (ulong position, ulong size) = context.Request.GetBufferType0x22();
-
-            using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size);
-            ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
-
-            context.ResponseData.Write(releasedCount);
-
-            return result;
-        }
-
-        [CommandCmif(10)] // 3.0.0+
-        // AppendUacInBufferAuto(u64 tag, handle<copy, event>, buffer<nn::audio::AudioInBuffer, 0x21>)
-        public ResultCode AppendUacInBufferAuto(ServiceCtx context)
-        {
-            (ulong position, _) = context.Request.GetBufferType0x21();
-
-            ulong bufferTag = context.RequestData.ReadUInt64();
-            uint handle = (uint)context.Request.HandleDesc.ToCopy[0];
-
-            AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
-
-            return _impl.AppendUacBuffer(bufferTag, ref data, handle);
-        }
-
-        [CommandCmif(11)] // 4.0.0+
-        // GetAudioInBufferCount() -> u32
-        public ResultCode GetAudioInBufferCount(ServiceCtx context)
-        {
-            context.ResponseData.Write(_impl.GetBufferCount());
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(12)] // 4.0.0+
-        // SetAudioInVolume(s32)
-        public ResultCode SetAudioInVolume(ServiceCtx context)
-        {
-            float volume = context.RequestData.ReadSingle();
-
-            _impl.SetVolume(volume);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(13)] // 4.0.0+
-        // GetAudioInVolume() -> s32
-        public ResultCode GetAudioInVolume(ServiceCtx context)
-        {
-            context.ResponseData.Write(_impl.GetVolume());
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(14)] // 6.0.0+
-        // FlushAudioInBuffers() -> b8
-        public ResultCode FlushAudioInBuffers(ServiceCtx context)
-        {
-            context.ResponseData.Write(_impl.FlushBuffers());
-
-            return ResultCode.Success;
-        }
-
-        protected override void Dispose(bool isDisposing)
-        {
-            if (isDisposing)
-            {
-                _impl.Dispose();
-            }
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs
deleted file mode 100644
index 4e67303df..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Ryujinx.Audio.Common;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using System;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
-{
-    interface IAudioIn : IDisposable
-    {
-        AudioDeviceState GetState();
-
-        ResultCode Start();
-
-        ResultCode Stop();
-
-        ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer);
-
-        // NOTE: This is broken by design... not quite sure what it's used for (if anything in production).
-        ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer buffer, uint handle);
-
-        KEvent RegisterBufferEvent();
-
-        ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount);
-
-        bool ContainsBuffer(ulong bufferTag);
-
-        uint GetBufferCount();
-
-        bool FlushBuffers();
-
-        void SetVolume(float volume);
-
-        float GetVolume();
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs
deleted file mode 100644
index 1e759e0ca..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using Ryujinx.Audio.Common;
-using Ryujinx.Audio.Input;
-using Ryujinx.HLE.HOS.Services.Audio.AudioIn;
-using AudioInManagerImpl = Ryujinx.Audio.Input.AudioInputManager;
-
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    class AudioInManager : IAudioInManager
-    {
-        private readonly AudioInManagerImpl _impl;
-
-        public AudioInManager(AudioInManagerImpl impl)
-        {
-            _impl = impl;
-        }
-
-        public string[] ListAudioIns(bool filtered)
-        {
-            return _impl.ListAudioIns(filtered);
-        }
-
-        public ResultCode OpenAudioIn(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle)
-        {
-            var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory;
-
-            ResultCode result = (ResultCode)_impl.OpenAudioIn(out outputDeviceName, out outputConfiguration, out AudioInputSystem inSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle);
-
-            if (result == ResultCode.Success)
-            {
-                obj = new AudioIn.AudioIn(inSystem, context.Device.System.KernelContext, processHandle);
-            }
-            else
-            {
-                obj = null;
-            }
-
-            return result;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs
deleted file mode 100644
index 1b35a62d8..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs
+++ /dev/null
@@ -1,243 +0,0 @@
-using Ryujinx.Audio.Common;
-using Ryujinx.Common;
-using Ryujinx.Common.Logging;
-using Ryujinx.Cpu;
-using Ryujinx.HLE.HOS.Services.Audio.AudioIn;
-using System.Text;
-
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("audin:u")]
-    class AudioInManagerServer : IpcService
-    {
-        private const int AudioInNameSize = 0x100;
-
-        private readonly IAudioInManager _impl;
-
-        public AudioInManagerServer(ServiceCtx context) : this(context, new AudioInManager(context.Device.System.AudioInputManager)) { }
-
-        public AudioInManagerServer(ServiceCtx context, IAudioInManager impl) : base(context.Device.System.AudOutServer)
-        {
-            _impl = impl;
-        }
-
-        [CommandCmif(0)]
-        // ListAudioIns() -> (u32, buffer<bytes, 6>)
-        public ResultCode ListAudioIns(ServiceCtx context)
-        {
-            string[] deviceNames = _impl.ListAudioIns(false);
-
-            ulong position = context.Request.ReceiveBuff[0].Position;
-            ulong size = context.Request.ReceiveBuff[0].Size;
-
-            ulong basePosition = position;
-
-            int count = 0;
-
-            foreach (string name in deviceNames)
-            {
-                byte[] buffer = Encoding.ASCII.GetBytes(name);
-
-                if ((position - basePosition) + (ulong)buffer.Length > size)
-                {
-                    Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
-
-                    break;
-                }
-
-                context.Memory.Write(position, buffer);
-                MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
-
-                position += AudioInNameSize;
-                count++;
-            }
-
-            context.ResponseData.Write(count);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(1)]
-        // OpenAudioIn(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 5> name)
-        // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 6> name)
-        public ResultCode OpenAudioIn(ServiceCtx context)
-        {
-            AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
-            ulong appletResourceUserId = context.RequestData.ReadUInt64();
-
-            ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
-            ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
-
-            ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
-#pragma warning disable IDE0059 // Remove unnecessary value assignment
-            ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
-#pragma warning restore IDE0059
-
-            uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
-
-            string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
-
-            ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle);
-
-            if (resultCode == ResultCode.Success)
-            {
-                context.ResponseData.WriteStruct(outputConfiguration);
-
-                byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
-
-                context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
-                MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
-
-                MakeObject(context, new AudioInServer(obj));
-            }
-
-            return resultCode;
-        }
-
-        [CommandCmif(2)] // 3.0.0+
-        // ListAudioInsAuto() -> (u32, buffer<bytes, 0x22>)
-        public ResultCode ListAudioInsAuto(ServiceCtx context)
-        {
-            string[] deviceNames = _impl.ListAudioIns(false);
-
-            (ulong position, ulong size) = context.Request.GetBufferType0x22();
-
-            ulong basePosition = position;
-
-            int count = 0;
-
-            foreach (string name in deviceNames)
-            {
-                byte[] buffer = Encoding.ASCII.GetBytes(name);
-
-                if ((position - basePosition) + (ulong)buffer.Length > size)
-                {
-                    Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
-
-                    break;
-                }
-
-                context.Memory.Write(position, buffer);
-                MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
-
-                position += AudioInNameSize;
-                count++;
-            }
-
-            context.ResponseData.Write(count);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(3)] // 3.0.0+
-        // OpenAudioInAuto(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 0x21>)
-        // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 0x22> name)
-        public ResultCode OpenAudioInAuto(ServiceCtx context)
-        {
-            AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
-            ulong appletResourceUserId = context.RequestData.ReadUInt64();
-
-            (ulong deviceNameInputPosition, ulong deviceNameInputSize) = context.Request.GetBufferType0x21();
-#pragma warning disable IDE0059 // Remove unnecessary value assignment
-            (ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22();
-#pragma warning restore IDE0059
-
-            uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
-
-            string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
-
-            ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle);
-
-            if (resultCode == ResultCode.Success)
-            {
-                context.ResponseData.WriteStruct(outputConfiguration);
-
-                byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
-
-                context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
-                MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
-
-                MakeObject(context, new AudioInServer(obj));
-            }
-
-            return resultCode;
-        }
-
-        [CommandCmif(4)] // 3.0.0+
-        // ListAudioInsAutoFiltered() -> (u32, buffer<bytes, 0x22>)
-        public ResultCode ListAudioInsAutoFiltered(ServiceCtx context)
-        {
-            string[] deviceNames = _impl.ListAudioIns(true);
-
-            (ulong position, ulong size) = context.Request.GetBufferType0x22();
-
-            ulong basePosition = position;
-
-            int count = 0;
-
-            foreach (string name in deviceNames)
-            {
-                byte[] buffer = Encoding.ASCII.GetBytes(name);
-
-                if ((position - basePosition) + (ulong)buffer.Length > size)
-                {
-                    Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
-
-                    break;
-                }
-
-                context.Memory.Write(position, buffer);
-                MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
-
-                position += AudioInNameSize;
-                count++;
-            }
-
-            context.ResponseData.Write(count);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(5)] // 5.0.0+
-        // OpenAudioInProtocolSpecified(b64 protocol_specified_related, AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 5> name)
-        // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 6> name)
-        public ResultCode OpenAudioInProtocolSpecified(ServiceCtx context)
-        {
-            // NOTE: We always assume that only the default device will be plugged (we never report any USB Audio Class type devices).
-#pragma warning disable IDE0059 // Remove unnecessary value assignment
-            bool protocolSpecifiedRelated = context.RequestData.ReadUInt64() == 1;
-#pragma warning restore IDE0059
-
-            AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
-            ulong appletResourceUserId = context.RequestData.ReadUInt64();
-
-            ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
-            ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
-
-            ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
-#pragma warning disable IDE0051, IDE0059 // Remove unused private member
-            ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
-#pragma warning restore IDE0051, IDE0059
-
-            uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
-
-            string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
-
-            ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle);
-
-            if (resultCode == ResultCode.Success)
-            {
-                context.ResponseData.WriteStruct(outputConfiguration);
-
-                byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
-
-                context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
-                MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
-
-                MakeObject(context, new AudioInServer(obj));
-            }
-
-            return resultCode;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs
deleted file mode 100644
index 2ccf0657f..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs
+++ /dev/null
@@ -1,108 +0,0 @@
-using Ryujinx.Audio.Common;
-using Ryujinx.Audio.Integration;
-using Ryujinx.Audio.Output;
-using Ryujinx.HLE.HOS.Kernel;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
-using System;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
-{
-    class AudioOut : IAudioOut
-    {
-        private readonly AudioOutputSystem _system;
-        private readonly uint _processHandle;
-        private readonly KernelContext _kernelContext;
-
-        public AudioOut(AudioOutputSystem system, KernelContext kernelContext, uint processHandle)
-        {
-            _system = system;
-            _kernelContext = kernelContext;
-            _processHandle = processHandle;
-        }
-
-        public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer)
-        {
-            return (ResultCode)_system.AppendBuffer(bufferTag, ref buffer);
-        }
-
-        public bool ContainsBuffer(ulong bufferTag)
-        {
-            return _system.ContainsBuffer(bufferTag);
-        }
-
-        public void Dispose()
-        {
-            Dispose(true);
-        }
-
-        protected virtual void Dispose(bool disposing)
-        {
-            if (disposing)
-            {
-                _system.Dispose();
-
-                _kernelContext.Syscall.CloseHandle((int)_processHandle);
-            }
-        }
-
-        public bool FlushBuffers()
-        {
-            return _system.FlushBuffers();
-        }
-
-        public uint GetBufferCount()
-        {
-            return _system.GetBufferCount();
-        }
-
-        public ulong GetPlayedSampleCount()
-        {
-            return _system.GetPlayedSampleCount();
-        }
-
-        public ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount)
-        {
-            return (ResultCode)_system.GetReleasedBuffer(releasedBuffers, out releasedCount);
-        }
-
-        public AudioDeviceState GetState()
-        {
-            return _system.GetState();
-        }
-
-        public float GetVolume()
-        {
-            return _system.GetVolume();
-        }
-
-        public KEvent RegisterBufferEvent()
-        {
-            IWritableEvent outEvent = _system.RegisterBufferEvent();
-
-            if (outEvent is AudioKernelEvent kernelEvent)
-            {
-                return kernelEvent.Event;
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
-        }
-
-        public void SetVolume(float volume)
-        {
-            _system.SetVolume(volume);
-        }
-
-        public ResultCode Start()
-        {
-            return (ResultCode)_system.Start();
-        }
-
-        public ResultCode Stop()
-        {
-            return (ResultCode)_system.Stop();
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs
deleted file mode 100644
index e1b252631..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs
+++ /dev/null
@@ -1,181 +0,0 @@
-using Ryujinx.Audio.Common;
-using Ryujinx.Cpu;
-using Ryujinx.HLE.HOS.Ipc;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.Horizon.Common;
-using Ryujinx.Memory;
-using System;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
-{
-    class AudioOutServer : DisposableIpcService
-    {
-        private readonly IAudioOut _impl;
-
-        public AudioOutServer(IAudioOut impl)
-        {
-            _impl = impl;
-        }
-
-        [CommandCmif(0)]
-        // GetAudioOutState() -> u32 state
-        public ResultCode GetAudioOutState(ServiceCtx context)
-        {
-            context.ResponseData.Write((uint)_impl.GetState());
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(1)]
-        // Start()
-        public ResultCode Start(ServiceCtx context)
-        {
-            return _impl.Start();
-        }
-
-        [CommandCmif(2)]
-        // Stop()
-        public ResultCode Stop(ServiceCtx context)
-        {
-            return _impl.Stop();
-        }
-
-        [CommandCmif(3)]
-        // AppendAudioOutBuffer(u64 bufferTag, buffer<nn::audio::AudioOutBuffer, 5> buffer)
-        public ResultCode AppendAudioOutBuffer(ServiceCtx context)
-        {
-            ulong position = context.Request.SendBuff[0].Position;
-
-            ulong bufferTag = context.RequestData.ReadUInt64();
-
-            AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
-
-            return _impl.AppendBuffer(bufferTag, ref data);
-        }
-
-        [CommandCmif(4)]
-        // RegisterBufferEvent() -> handle<copy>
-        public ResultCode RegisterBufferEvent(ServiceCtx context)
-        {
-            KEvent bufferEvent = _impl.RegisterBufferEvent();
-
-            if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != Result.Success)
-            {
-                throw new InvalidOperationException("Out of handles!");
-            }
-
-            context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(5)]
-        // GetReleasedAudioOutBuffers() -> (u32 count, buffer<u64, 6> tags)
-        public ResultCode GetReleasedAudioOutBuffers(ServiceCtx context)
-        {
-            ulong position = context.Request.ReceiveBuff[0].Position;
-            ulong size = context.Request.ReceiveBuff[0].Size;
-
-            using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size);
-            ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
-
-            context.ResponseData.Write(releasedCount);
-
-            return result;
-        }
-
-        [CommandCmif(6)]
-        // ContainsAudioOutBuffer(u64 tag) -> b8
-        public ResultCode ContainsAudioOutBuffer(ServiceCtx context)
-        {
-            ulong bufferTag = context.RequestData.ReadUInt64();
-
-            context.ResponseData.Write(_impl.ContainsBuffer(bufferTag));
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(7)] // 3.0.0+
-        // AppendAudioOutBufferAuto(u64 tag, buffer<nn::audio::AudioOutBuffer, 0x21>)
-        public ResultCode AppendAudioOutBufferAuto(ServiceCtx context)
-        {
-            (ulong position, _) = context.Request.GetBufferType0x21();
-
-            ulong bufferTag = context.RequestData.ReadUInt64();
-
-            AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
-
-            return _impl.AppendBuffer(bufferTag, ref data);
-        }
-
-        [CommandCmif(8)] // 3.0.0+
-        // GetReleasedAudioOutBuffersAuto() -> (u32 count, buffer<u64, 0x22> tags)
-        public ResultCode GetReleasedAudioOutBuffersAuto(ServiceCtx context)
-        {
-            (ulong position, ulong size) = context.Request.GetBufferType0x22();
-
-            using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size);
-            ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
-
-            context.ResponseData.Write(releasedCount);
-
-            return result;
-        }
-
-        [CommandCmif(9)] // 4.0.0+
-        // GetAudioOutBufferCount() -> u32
-        public ResultCode GetAudioOutBufferCount(ServiceCtx context)
-        {
-            context.ResponseData.Write(_impl.GetBufferCount());
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10)] // 4.0.0+
-        // GetAudioOutPlayedSampleCount() -> u64
-        public ResultCode GetAudioOutPlayedSampleCount(ServiceCtx context)
-        {
-            context.ResponseData.Write(_impl.GetPlayedSampleCount());
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(11)] // 4.0.0+
-        // FlushAudioOutBuffers() -> b8
-        public ResultCode FlushAudioOutBuffers(ServiceCtx context)
-        {
-            context.ResponseData.Write(_impl.FlushBuffers());
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(12)] // 6.0.0+
-        // SetAudioOutVolume(s32)
-        public ResultCode SetAudioOutVolume(ServiceCtx context)
-        {
-            float volume = context.RequestData.ReadSingle();
-
-            _impl.SetVolume(volume);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(13)] // 6.0.0+
-        // GetAudioOutVolume() -> s32
-        public ResultCode GetAudioOutVolume(ServiceCtx context)
-        {
-            context.ResponseData.Write(_impl.GetVolume());
-
-            return ResultCode.Success;
-        }
-
-        protected override void Dispose(bool isDisposing)
-        {
-            if (isDisposing)
-            {
-                _impl.Dispose();
-            }
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs
deleted file mode 100644
index 8c8c68629..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Ryujinx.Audio.Common;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using System;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
-{
-    interface IAudioOut : IDisposable
-    {
-        AudioDeviceState GetState();
-
-        ResultCode Start();
-
-        ResultCode Stop();
-
-        ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer);
-
-        KEvent RegisterBufferEvent();
-
-        ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount);
-
-        bool ContainsBuffer(ulong bufferTag);
-
-        uint GetBufferCount();
-
-        ulong GetPlayedSampleCount();
-
-        bool FlushBuffers();
-
-        void SetVolume(float volume);
-
-        float GetVolume();
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs
deleted file mode 100644
index c45a485bc..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using Ryujinx.Audio.Common;
-using Ryujinx.Audio.Output;
-using Ryujinx.HLE.HOS.Services.Audio.AudioOut;
-using AudioOutManagerImpl = Ryujinx.Audio.Output.AudioOutputManager;
-
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    class AudioOutManager : IAudioOutManager
-    {
-        private readonly AudioOutManagerImpl _impl;
-
-        public AudioOutManager(AudioOutManagerImpl impl)
-        {
-            _impl = impl;
-        }
-
-        public string[] ListAudioOuts()
-        {
-            return _impl.ListAudioOuts();
-        }
-
-        public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume)
-        {
-            var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory;
-
-            ResultCode result = (ResultCode)_impl.OpenAudioOut(out outputDeviceName, out outputConfiguration, out AudioOutputSystem outSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle, volume);
-
-            if (result == ResultCode.Success)
-            {
-                obj = new AudioOut.AudioOut(outSystem, context.Device.System.KernelContext, processHandle);
-            }
-            else
-            {
-                obj = null;
-            }
-
-            return result;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs
deleted file mode 100644
index 79ae6a141..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs
+++ /dev/null
@@ -1,166 +0,0 @@
-using Ryujinx.Audio.Common;
-using Ryujinx.Common;
-using Ryujinx.Common.Logging;
-using Ryujinx.Cpu;
-using Ryujinx.HLE.HOS.Services.Audio.AudioOut;
-using System.Text;
-
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("audout:u")]
-    class AudioOutManagerServer : IpcService
-    {
-        private const int AudioOutNameSize = 0x100;
-
-        private readonly IAudioOutManager _impl;
-
-        public AudioOutManagerServer(ServiceCtx context) : this(context, new AudioOutManager(context.Device.System.AudioOutputManager)) { }
-
-        public AudioOutManagerServer(ServiceCtx context, IAudioOutManager impl) : base(context.Device.System.AudOutServer)
-        {
-            _impl = impl;
-        }
-
-        [CommandCmif(0)]
-        // ListAudioOuts() -> (u32, buffer<bytes, 6>)
-        public ResultCode ListAudioOuts(ServiceCtx context)
-        {
-            string[] deviceNames = _impl.ListAudioOuts();
-
-            ulong position = context.Request.ReceiveBuff[0].Position;
-            ulong size = context.Request.ReceiveBuff[0].Size;
-
-            ulong basePosition = position;
-
-            int count = 0;
-
-            foreach (string name in deviceNames)
-            {
-                byte[] buffer = Encoding.ASCII.GetBytes(name);
-
-                if ((position - basePosition) + (ulong)buffer.Length > size)
-                {
-                    Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
-
-                    break;
-                }
-
-                context.Memory.Write(position, buffer);
-                MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioOutNameSize - buffer.Length);
-
-                position += AudioOutNameSize;
-                count++;
-            }
-
-            context.ResponseData.Write(count);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(1)]
-        // OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process> process_handle, buffer<bytes, 5> name_in)
-        // -> (AudioOutInputConfiguration output_config, object<nn::audio::detail::IAudioOut>, buffer<bytes, 6> name_out)
-        public ResultCode OpenAudioOut(ServiceCtx context)
-        {
-            AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
-            ulong appletResourceUserId = context.RequestData.ReadUInt64();
-
-            ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
-            ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
-
-            ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
-#pragma warning disable IDE0059 // Remove unnecessary value assignment
-            ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
-#pragma warning restore IDE0059
-
-            uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
-
-            string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
-
-            ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume);
-
-            if (resultCode == ResultCode.Success)
-            {
-                context.ResponseData.WriteStruct(outputConfiguration);
-
-                byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
-
-                context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
-                MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length);
-
-                MakeObject(context, new AudioOutServer(obj));
-            }
-
-            return resultCode;
-        }
-
-        [CommandCmif(2)] // 3.0.0+
-        // ListAudioOutsAuto() -> (u32, buffer<bytes, 0x22>)
-        public ResultCode ListAudioOutsAuto(ServiceCtx context)
-        {
-            string[] deviceNames = _impl.ListAudioOuts();
-
-            (ulong position, ulong size) = context.Request.GetBufferType0x22();
-
-            ulong basePosition = position;
-
-            int count = 0;
-
-            foreach (string name in deviceNames)
-            {
-                byte[] buffer = Encoding.ASCII.GetBytes(name);
-
-                if ((position - basePosition) + (ulong)buffer.Length > size)
-                {
-                    Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
-
-                    break;
-                }
-
-                context.Memory.Write(position, buffer);
-                MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioOutNameSize - buffer.Length);
-
-                position += AudioOutNameSize;
-                count++;
-            }
-
-            context.ResponseData.Write(count);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(3)] // 3.0.0+
-        // OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process> process_handle, buffer<bytes, 0x21> name_in)
-        // -> (AudioOutInputConfiguration output_config, object<nn::audio::detail::IAudioOut>, buffer<bytes, 0x22> name_out)
-        public ResultCode OpenAudioOutAuto(ServiceCtx context)
-        {
-            AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
-            ulong appletResourceUserId = context.RequestData.ReadUInt64();
-
-            (ulong deviceNameInputPosition, ulong deviceNameInputSize) = context.Request.GetBufferType0x21();
-#pragma warning disable IDE0059 // Remove unnecessary value assignment
-            (ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22();
-#pragma warning restore IDE0059
-
-            uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
-
-            string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
-
-            ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume);
-
-            if (resultCode == ResultCode.Success)
-            {
-                context.ResponseData.WriteStruct(outputConfiguration);
-
-                byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
-
-                context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
-                MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length);
-
-                MakeObject(context, new AudioOutServer(obj));
-            }
-
-            return resultCode;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs
deleted file mode 100644
index 6497a3b84..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs
+++ /dev/null
@@ -1,174 +0,0 @@
-using Ryujinx.Audio.Renderer.Device;
-using Ryujinx.Audio.Renderer.Server;
-using Ryujinx.HLE.HOS.Kernel;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
-{
-    class AudioDevice : IAudioDevice
-    {
-        private readonly VirtualDeviceSession[] _sessions;
-#pragma warning disable IDE0052 // Remove unread private member
-        private readonly ulong _appletResourceId;
-        private readonly int _revision;
-#pragma warning restore IDE0052
-        private readonly bool _isUsbDeviceSupported;
-
-        private readonly VirtualDeviceSessionRegistry _registry;
-        private readonly KEvent _systemEvent;
-
-        public AudioDevice(VirtualDeviceSessionRegistry registry, KernelContext context, ulong appletResourceId, int revision)
-        {
-            _registry = registry;
-            _appletResourceId = appletResourceId;
-            _revision = revision;
-
-            BehaviourContext behaviourContext = new();
-            behaviourContext.SetUserRevision(revision);
-
-            _isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported();
-            _sessions = _registry.GetSessionByAppletResourceId(appletResourceId);
-
-            // TODO: support the 3 different events correctly when we will have hot plugable audio devices.
-            _systemEvent = new KEvent(context);
-            _systemEvent.ReadableEvent.Signal();
-        }
-
-        private bool TryGetDeviceByName(out VirtualDeviceSession result, string name, bool ignoreRevLimitation = false)
-        {
-            result = null;
-
-            foreach (VirtualDeviceSession session in _sessions)
-            {
-                if (session.Device.Name.Equals(name))
-                {
-                    if (!ignoreRevLimitation && !_isUsbDeviceSupported && session.Device.IsUsbDevice())
-                    {
-                        return false;
-                    }
-
-                    result = session;
-
-                    return true;
-                }
-            }
-
-            return false;
-        }
-
-        public string GetActiveAudioDeviceName()
-        {
-            VirtualDevice device = _registry.ActiveDevice;
-
-            if (!_isUsbDeviceSupported && device.IsUsbDevice())
-            {
-                device = _registry.DefaultDevice;
-            }
-
-            return device.Name;
-        }
-
-        public uint GetActiveChannelCount()
-        {
-            VirtualDevice device = _registry.ActiveDevice;
-
-            if (!_isUsbDeviceSupported && device.IsUsbDevice())
-            {
-                device = _registry.DefaultDevice;
-            }
-
-            return device.ChannelCount;
-        }
-
-        public ResultCode GetAudioDeviceOutputVolume(string name, out float volume)
-        {
-            if (TryGetDeviceByName(out VirtualDeviceSession result, name))
-            {
-                volume = result.Volume;
-            }
-            else
-            {
-                volume = 0.0f;
-            }
-
-            return ResultCode.Success;
-        }
-
-        public ResultCode SetAudioDeviceOutputVolume(string name, float volume)
-        {
-            if (TryGetDeviceByName(out VirtualDeviceSession result, name, true))
-            {
-                if (!_isUsbDeviceSupported && result.Device.IsUsbDevice())
-                {
-                    result = _sessions[0];
-                }
-
-                result.Volume = volume;
-            }
-
-            return ResultCode.Success;
-        }
-
-        public string GetActiveAudioOutputDeviceName()
-        {
-            return _registry.ActiveDevice.GetOutputDeviceName();
-        }
-
-        public string[] ListAudioDeviceName()
-        {
-            int deviceCount = _sessions.Length;
-
-            if (!_isUsbDeviceSupported)
-            {
-                deviceCount--;
-            }
-
-            string[] result = new string[deviceCount];
-
-            int i = 0;
-
-            foreach (VirtualDeviceSession session in _sessions)
-            {
-                if (!_isUsbDeviceSupported && session.Device.IsUsbDevice())
-                {
-                    continue;
-                }
-
-                result[i] = session.Device.Name;
-
-                i++;
-            }
-
-            return result;
-        }
-
-        public string[] ListAudioOutputDeviceName()
-        {
-            int deviceCount = _sessions.Length;
-
-            string[] result = new string[deviceCount];
-
-            for (int i = 0; i < deviceCount; i++)
-            {
-                result[i] = _sessions[i].Device.GetOutputDeviceName();
-            }
-
-            return result;
-        }
-
-        public KEvent QueryAudioDeviceInputEvent()
-        {
-            return _systemEvent;
-        }
-
-        public KEvent QueryAudioDeviceOutputEvent()
-        {
-            return _systemEvent;
-        }
-
-        public KEvent QueryAudioDeviceSystemEvent()
-        {
-            return _systemEvent;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs
deleted file mode 100644
index 6206215d5..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs
+++ /dev/null
@@ -1,320 +0,0 @@
-using Ryujinx.Common.Logging;
-using Ryujinx.Cpu;
-using Ryujinx.HLE.HOS.Ipc;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.Horizon.Common;
-using System;
-using System.Text;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
-{
-    class AudioDeviceServer : IpcService
-    {
-        private const int AudioDeviceNameSize = 0x100;
-
-        private readonly IAudioDevice _impl;
-
-        public AudioDeviceServer(IAudioDevice impl)
-        {
-            _impl = impl;
-        }
-
-        [CommandCmif(0)]
-        // ListAudioDeviceName() -> (u32, buffer<bytes, 6>)
-        public ResultCode ListAudioDeviceName(ServiceCtx context)
-        {
-            string[] deviceNames = _impl.ListAudioDeviceName();
-
-            ulong position = context.Request.ReceiveBuff[0].Position;
-            ulong size = context.Request.ReceiveBuff[0].Size;
-
-            ulong basePosition = position;
-
-            int count = 0;
-
-            foreach (string name in deviceNames)
-            {
-                byte[] buffer = Encoding.ASCII.GetBytes(name);
-
-                if ((position - basePosition) + (ulong)buffer.Length > size)
-                {
-                    break;
-                }
-
-                context.Memory.Write(position, buffer);
-                MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
-
-                position += AudioDeviceNameSize;
-                count++;
-            }
-
-            context.ResponseData.Write(count);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(1)]
-        // SetAudioDeviceOutputVolume(f32 volume, buffer<bytes, 5> name)
-        public ResultCode SetAudioDeviceOutputVolume(ServiceCtx context)
-        {
-            float volume = context.RequestData.ReadSingle();
-
-            ulong position = context.Request.SendBuff[0].Position;
-            ulong size = context.Request.SendBuff[0].Size;
-
-            string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
-
-            return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
-        }
-
-        [CommandCmif(2)]
-        // GetAudioDeviceOutputVolume(buffer<bytes, 5> name) -> f32 volume
-        public ResultCode GetAudioDeviceOutputVolume(ServiceCtx context)
-        {
-            ulong position = context.Request.SendBuff[0].Position;
-            ulong size = context.Request.SendBuff[0].Size;
-
-            string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
-
-            ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
-
-            if (result == ResultCode.Success)
-            {
-                context.ResponseData.Write(volume);
-            }
-
-            return result;
-        }
-
-        [CommandCmif(3)]
-        // GetActiveAudioDeviceName() -> buffer<bytes, 6>
-        public ResultCode GetActiveAudioDeviceName(ServiceCtx context)
-        {
-            string name = _impl.GetActiveAudioDeviceName();
-
-            ulong position = context.Request.ReceiveBuff[0].Position;
-            ulong size = context.Request.ReceiveBuff[0].Size;
-
-            byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
-
-            if ((ulong)deviceNameBuffer.Length <= size)
-            {
-                context.Memory.Write(position, deviceNameBuffer);
-            }
-            else
-            {
-                Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
-            }
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(4)]
-        // QueryAudioDeviceSystemEvent() -> handle<copy, event>
-        public ResultCode QueryAudioDeviceSystemEvent(ServiceCtx context)
-        {
-            KEvent deviceSystemEvent = _impl.QueryAudioDeviceSystemEvent();
-
-            if (context.Process.HandleTable.GenerateHandle(deviceSystemEvent.ReadableEvent, out int handle) != Result.Success)
-            {
-                throw new InvalidOperationException("Out of handles!");
-            }
-
-            context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
-
-            Logger.Stub?.PrintStub(LogClass.ServiceAudio);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(5)]
-        // GetActiveChannelCount() -> u32
-        public ResultCode GetActiveChannelCount(ServiceCtx context)
-        {
-            context.ResponseData.Write(_impl.GetActiveChannelCount());
-
-            Logger.Stub?.PrintStub(LogClass.ServiceAudio);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(6)] // 3.0.0+
-        // ListAudioDeviceNameAuto() -> (u32, buffer<bytes, 0x22>)
-        public ResultCode ListAudioDeviceNameAuto(ServiceCtx context)
-        {
-            string[] deviceNames = _impl.ListAudioDeviceName();
-
-            (ulong position, ulong size) = context.Request.GetBufferType0x22();
-
-            ulong basePosition = position;
-
-            int count = 0;
-
-            foreach (string name in deviceNames)
-            {
-                byte[] buffer = Encoding.ASCII.GetBytes(name);
-
-                if ((position - basePosition) + (ulong)buffer.Length > size)
-                {
-                    break;
-                }
-
-                context.Memory.Write(position, buffer);
-                MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
-
-                position += AudioDeviceNameSize;
-                count++;
-            }
-
-            context.ResponseData.Write(count);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(7)] // 3.0.0+
-        // SetAudioDeviceOutputVolumeAuto(f32 volume, buffer<bytes, 0x21> name)
-        public ResultCode SetAudioDeviceOutputVolumeAuto(ServiceCtx context)
-        {
-            float volume = context.RequestData.ReadSingle();
-
-            (ulong position, ulong size) = context.Request.GetBufferType0x21();
-
-            string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
-
-            return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
-        }
-
-        [CommandCmif(8)] // 3.0.0+
-        // GetAudioDeviceOutputVolumeAuto(buffer<bytes, 0x21> name) -> f32
-        public ResultCode GetAudioDeviceOutputVolumeAuto(ServiceCtx context)
-        {
-            (ulong position, ulong size) = context.Request.GetBufferType0x21();
-
-            string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
-
-            ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
-
-            if (result == ResultCode.Success)
-            {
-                context.ResponseData.Write(volume);
-            }
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10)] // 3.0.0+
-        // GetActiveAudioDeviceNameAuto() -> buffer<bytes, 0x22>
-        public ResultCode GetActiveAudioDeviceNameAuto(ServiceCtx context)
-        {
-            string name = _impl.GetActiveAudioDeviceName();
-
-            (ulong position, ulong size) = context.Request.GetBufferType0x22();
-
-            byte[] deviceNameBuffer = Encoding.UTF8.GetBytes(name + '\0');
-
-            if ((ulong)deviceNameBuffer.Length <= size)
-            {
-                context.Memory.Write(position, deviceNameBuffer);
-            }
-            else
-            {
-                Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
-            }
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(11)] // 3.0.0+
-        // QueryAudioDeviceInputEvent() -> handle<copy, event>
-        public ResultCode QueryAudioDeviceInputEvent(ServiceCtx context)
-        {
-            KEvent deviceInputEvent = _impl.QueryAudioDeviceInputEvent();
-
-            if (context.Process.HandleTable.GenerateHandle(deviceInputEvent.ReadableEvent, out int handle) != Result.Success)
-            {
-                throw new InvalidOperationException("Out of handles!");
-            }
-
-            context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
-
-            Logger.Stub?.PrintStub(LogClass.ServiceAudio);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(12)] // 3.0.0+
-        // QueryAudioDeviceOutputEvent() -> handle<copy, event>
-        public ResultCode QueryAudioDeviceOutputEvent(ServiceCtx context)
-        {
-            KEvent deviceOutputEvent = _impl.QueryAudioDeviceOutputEvent();
-
-            if (context.Process.HandleTable.GenerateHandle(deviceOutputEvent.ReadableEvent, out int handle) != Result.Success)
-            {
-                throw new InvalidOperationException("Out of handles!");
-            }
-
-            context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
-
-            Logger.Stub?.PrintStub(LogClass.ServiceAudio);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(13)] // 13.0.0+
-        // GetActiveAudioOutputDeviceName() -> buffer<bytes, 6>
-        public ResultCode GetActiveAudioOutputDeviceName(ServiceCtx context)
-        {
-            string name = _impl.GetActiveAudioOutputDeviceName();
-
-            ulong position = context.Request.ReceiveBuff[0].Position;
-            ulong size = context.Request.ReceiveBuff[0].Size;
-
-            byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
-
-            if ((ulong)deviceNameBuffer.Length <= size)
-            {
-                context.Memory.Write(position, deviceNameBuffer);
-            }
-            else
-            {
-                Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
-            }
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(14)] // 13.0.0+
-        // ListAudioOutputDeviceName() -> (u32, buffer<bytes, 6>)
-        public ResultCode ListAudioOutputDeviceName(ServiceCtx context)
-        {
-            string[] deviceNames = _impl.ListAudioOutputDeviceName();
-
-            ulong position = context.Request.ReceiveBuff[0].Position;
-            ulong size = context.Request.ReceiveBuff[0].Size;
-
-            ulong basePosition = position;
-
-            int count = 0;
-
-            foreach (string name in deviceNames)
-            {
-                byte[] buffer = Encoding.ASCII.GetBytes(name);
-
-                if ((position - basePosition) + (ulong)buffer.Length > size)
-                {
-                    break;
-                }
-
-                context.Memory.Write(position, buffer);
-                MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
-
-                position += AudioDeviceNameSize;
-                count++;
-            }
-
-            context.ResponseData.Write(count);
-
-            return ResultCode.Success;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs
deleted file mode 100644
index 414c70a43..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Ryujinx.Audio.Integration;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
-{
-    class AudioKernelEvent : IWritableEvent
-    {
-        public KEvent Event { get; }
-
-        public AudioKernelEvent(KEvent evnt)
-        {
-            Event = evnt;
-        }
-
-        public void Clear()
-        {
-            Event.WritableEvent.Clear();
-        }
-
-        public void Signal()
-        {
-            Event.WritableEvent.Signal();
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs
deleted file mode 100644
index 88456be3e..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs
+++ /dev/null
@@ -1,122 +0,0 @@
-using Ryujinx.Audio.Integration;
-using Ryujinx.Audio.Renderer.Server;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using System;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
-{
-    class AudioRenderer : IAudioRenderer
-    {
-        private readonly AudioRenderSystem _impl;
-
-        public AudioRenderer(AudioRenderSystem impl)
-        {
-            _impl = impl;
-        }
-
-        public ResultCode ExecuteAudioRendererRendering()
-        {
-            return (ResultCode)_impl.ExecuteAudioRendererRendering();
-        }
-
-        public uint GetMixBufferCount()
-        {
-            return _impl.GetMixBufferCount();
-        }
-
-        public uint GetRenderingTimeLimit()
-        {
-            return _impl.GetRenderingTimeLimit();
-        }
-
-        public uint GetSampleCount()
-        {
-            return _impl.GetSampleCount();
-        }
-
-        public uint GetSampleRate()
-        {
-            return _impl.GetSampleRate();
-        }
-
-        public int GetState()
-        {
-            if (_impl.IsActive())
-            {
-                return 0;
-            }
-
-            return 1;
-        }
-
-        public ResultCode QuerySystemEvent(out KEvent systemEvent)
-        {
-            ResultCode resultCode = (ResultCode)_impl.QuerySystemEvent(out IWritableEvent outEvent);
-
-            if (resultCode == ResultCode.Success)
-            {
-                if (outEvent is AudioKernelEvent kernelEvent)
-                {
-                    systemEvent = kernelEvent.Event;
-                }
-                else
-                {
-                    throw new NotImplementedException();
-                }
-            }
-            else
-            {
-                systemEvent = null;
-            }
-
-            return resultCode;
-        }
-
-        public ResultCode RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input)
-        {
-            return (ResultCode)_impl.Update(output, performanceOutput, input);
-        }
-
-        public void SetRenderingTimeLimit(uint percent)
-        {
-            _impl.SetRenderingTimeLimitPercent(percent);
-        }
-
-        public ResultCode Start()
-        {
-            _impl.Start();
-
-            return ResultCode.Success;
-        }
-
-        public ResultCode Stop()
-        {
-            _impl.Stop();
-
-            return ResultCode.Success;
-        }
-
-        public void Dispose()
-        {
-            Dispose(true);
-        }
-
-        protected virtual void Dispose(bool disposing)
-        {
-            if (disposing)
-            {
-                _impl.Dispose();
-            }
-        }
-
-        public void SetVoiceDropParameter(float voiceDropParameter)
-        {
-            _impl.SetVoiceDropParameter(voiceDropParameter);
-        }
-
-        public float GetVoiceDropParameter()
-        {
-            return _impl.GetVoiceDropParameter();
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs
deleted file mode 100644
index baea01072..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs
+++ /dev/null
@@ -1,215 +0,0 @@
-using Ryujinx.Common.Logging;
-using Ryujinx.Common.Memory;
-using Ryujinx.HLE.HOS.Ipc;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.Horizon.Common;
-using System;
-using System.Buffers;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
-{
-    class AudioRendererServer : DisposableIpcService
-    {
-        private readonly IAudioRenderer _impl;
-
-        public AudioRendererServer(IAudioRenderer impl)
-        {
-            _impl = impl;
-        }
-
-        [CommandCmif(0)]
-        // GetSampleRate() -> u32
-        public ResultCode GetSampleRate(ServiceCtx context)
-        {
-            context.ResponseData.Write(_impl.GetSampleRate());
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(1)]
-        // GetSampleCount() -> u32
-        public ResultCode GetSampleCount(ServiceCtx context)
-        {
-            context.ResponseData.Write(_impl.GetSampleCount());
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(2)]
-        // GetMixBufferCount() -> u32
-        public ResultCode GetMixBufferCount(ServiceCtx context)
-        {
-            context.ResponseData.Write(_impl.GetMixBufferCount());
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(3)]
-        // GetState() -> u32
-        public ResultCode GetState(ServiceCtx context)
-        {
-            context.ResponseData.Write(_impl.GetState());
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(4)]
-        // RequestUpdate(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 5> input)
-        // -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> performanceOutput)
-        public ResultCode RequestUpdate(ServiceCtx context)
-        {
-            ulong inputPosition = context.Request.SendBuff[0].Position;
-            ulong inputSize = context.Request.SendBuff[0].Size;
-
-            ulong outputPosition = context.Request.ReceiveBuff[0].Position;
-            ulong outputSize = context.Request.ReceiveBuff[0].Size;
-
-            ulong performanceOutputPosition = context.Request.ReceiveBuff[1].Position;
-            ulong performanceOutputSize = context.Request.ReceiveBuff[1].Size;
-
-            ReadOnlyMemory<byte> input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray();
-
-            using IMemoryOwner<byte> outputOwner = ByteMemoryPool.RentCleared(outputSize);
-            using IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.RentCleared(performanceOutputSize);
-            Memory<byte> output = outputOwner.Memory;
-            Memory<byte> performanceOutput = performanceOutputOwner.Memory;
-
-            using MemoryHandle outputHandle = output.Pin();
-            using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
-
-            ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
-
-            if (result == ResultCode.Success)
-            {
-                context.Memory.Write(outputPosition, output.Span);
-                context.Memory.Write(performanceOutputPosition, performanceOutput.Span);
-            }
-            else
-            {
-                Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{(int)result:X}");
-            }
-
-            return result;
-        }
-
-        [CommandCmif(5)]
-        // Start()
-        public ResultCode Start(ServiceCtx context)
-        {
-            return _impl.Start();
-        }
-
-        [CommandCmif(6)]
-        // Stop()
-        public ResultCode Stop(ServiceCtx context)
-        {
-            return _impl.Stop();
-        }
-
-        [CommandCmif(7)]
-        // QuerySystemEvent() -> handle<copy, event>
-        public ResultCode QuerySystemEvent(ServiceCtx context)
-        {
-            ResultCode result = _impl.QuerySystemEvent(out KEvent systemEvent);
-
-            if (result == ResultCode.Success)
-            {
-                if (context.Process.HandleTable.GenerateHandle(systemEvent.ReadableEvent, out int handle) != Result.Success)
-                {
-                    throw new InvalidOperationException("Out of handles!");
-                }
-
-                context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
-            }
-
-            return result;
-        }
-
-        [CommandCmif(8)]
-        // SetAudioRendererRenderingTimeLimit(u32 limit)
-        public ResultCode SetAudioRendererRenderingTimeLimit(ServiceCtx context)
-        {
-            uint limit = context.RequestData.ReadUInt32();
-
-            _impl.SetRenderingTimeLimit(limit);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(9)]
-        // GetAudioRendererRenderingTimeLimit() -> u32 limit
-        public ResultCode GetAudioRendererRenderingTimeLimit(ServiceCtx context)
-        {
-            uint limit = _impl.GetRenderingTimeLimit();
-
-            context.ResponseData.Write(limit);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10)] // 3.0.0+
-        //  RequestUpdateAuto(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x21> input)
-        // -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> performanceOutput)
-        public ResultCode RequestUpdateAuto(ServiceCtx context)
-        {
-            (ulong inputPosition, ulong inputSize) = context.Request.GetBufferType0x21();
-            (ulong outputPosition, ulong outputSize) = context.Request.GetBufferType0x22(0);
-            (ulong performanceOutputPosition, ulong performanceOutputSize) = context.Request.GetBufferType0x22(1);
-
-            ReadOnlyMemory<byte> input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray();
-
-            Memory<byte> output = new byte[outputSize];
-            Memory<byte> performanceOutput = new byte[performanceOutputSize];
-
-            using MemoryHandle outputHandle = output.Pin();
-            using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
-
-            ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
-
-            if (result == ResultCode.Success)
-            {
-                context.Memory.Write(outputPosition, output.Span);
-                context.Memory.Write(performanceOutputPosition, performanceOutput.Span);
-            }
-
-            return result;
-        }
-
-        [CommandCmif(11)] // 3.0.0+
-        // ExecuteAudioRendererRendering()
-        public ResultCode ExecuteAudioRendererRendering(ServiceCtx context)
-        {
-            return _impl.ExecuteAudioRendererRendering();
-        }
-
-        [CommandCmif(12)] // 15.0.0+
-        // SetVoiceDropParameter(f32 voiceDropParameter)
-        public ResultCode SetVoiceDropParameter(ServiceCtx context)
-        {
-            float voiceDropParameter = context.RequestData.ReadSingle();
-
-            _impl.SetVoiceDropParameter(voiceDropParameter);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(13)] // 15.0.0+
-        // GetVoiceDropParameter() -> f32 voiceDropParameter
-        public ResultCode GetVoiceDropParameter(ServiceCtx context)
-        {
-            float voiceDropParameter = _impl.GetVoiceDropParameter();
-
-            context.ResponseData.Write(voiceDropParameter);
-
-            return ResultCode.Success;
-        }
-
-        protected override void Dispose(bool isDisposing)
-        {
-            if (isDisposing)
-            {
-                _impl.Dispose();
-            }
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs
deleted file mode 100644
index 0f181a477..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Ryujinx.HLE.HOS.Kernel.Threading;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
-{
-    interface IAudioDevice
-    {
-        string[] ListAudioDeviceName();
-        ResultCode SetAudioDeviceOutputVolume(string name, float volume);
-        ResultCode GetAudioDeviceOutputVolume(string name, out float volume);
-        string GetActiveAudioDeviceName();
-        KEvent QueryAudioDeviceSystemEvent();
-        uint GetActiveChannelCount();
-        KEvent QueryAudioDeviceInputEvent();
-        KEvent QueryAudioDeviceOutputEvent();
-        string GetActiveAudioOutputDeviceName();
-        string[] ListAudioOutputDeviceName();
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs
deleted file mode 100644
index 6bb4a5dec..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using System;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
-{
-    interface IAudioRenderer : IDisposable
-    {
-        uint GetSampleRate();
-        uint GetSampleCount();
-        uint GetMixBufferCount();
-        int GetState();
-        ResultCode RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input);
-        ResultCode Start();
-        ResultCode Stop();
-        ResultCode QuerySystemEvent(out KEvent systemEvent);
-        void SetRenderingTimeLimit(uint percent);
-        uint GetRenderingTimeLimit();
-        ResultCode ExecuteAudioRendererRendering();
-        void SetVoiceDropParameter(float voiceDropParameter);
-        float GetVoiceDropParameter();
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs
deleted file mode 100644
index 87d0001e3..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using Ryujinx.Audio.Renderer.Device;
-using Ryujinx.Audio.Renderer.Parameter;
-using Ryujinx.Audio.Renderer.Server;
-using Ryujinx.HLE.HOS.Kernel.Memory;
-using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
-
-using AudioRendererManagerImpl = Ryujinx.Audio.Renderer.Server.AudioRendererManager;
-
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    class AudioRendererManager : IAudioRendererManager
-    {
-        private readonly AudioRendererManagerImpl _impl;
-        private readonly VirtualDeviceSessionRegistry _registry;
-
-        public AudioRendererManager(AudioRendererManagerImpl impl, VirtualDeviceSessionRegistry registry)
-        {
-            _impl = impl;
-            _registry = registry;
-        }
-
-        public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId)
-        {
-            outObject = new AudioDevice(_registry, context.Device.System.KernelContext, appletResourceUserId, revision);
-
-            return ResultCode.Success;
-        }
-
-        public ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter)
-        {
-            return AudioRendererManagerImpl.GetWorkBufferSize(ref parameter);
-        }
-
-        public ResultCode OpenAudioRenderer(
-            ServiceCtx context,
-            out IAudioRenderer obj,
-            ref AudioRendererConfiguration parameter,
-            ulong workBufferSize,
-            ulong appletResourceUserId,
-            KTransferMemory workBufferTransferMemory,
-            uint processHandle)
-        {
-            var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory;
-
-            ResultCode result = (ResultCode)_impl.OpenAudioRenderer(
-                out AudioRenderSystem renderer,
-                memoryManager,
-                ref parameter,
-                appletResourceUserId,
-                workBufferTransferMemory.Address,
-                workBufferTransferMemory.Size,
-                processHandle,
-                context.Device.Configuration.AudioVolume);
-
-            if (result == ResultCode.Success)
-            {
-                obj = new AudioRenderer.AudioRenderer(renderer);
-            }
-            else
-            {
-                obj = null;
-            }
-
-            return result;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs
deleted file mode 100644
index 38a841d82..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-using Ryujinx.Audio.Renderer.Parameter;
-using Ryujinx.Audio.Renderer.Server;
-using Ryujinx.Common;
-using Ryujinx.Common.Logging;
-using Ryujinx.HLE.HOS.Kernel.Memory;
-using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
-
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("audren:u")]
-    class AudioRendererManagerServer : IpcService
-    {
-        private const int InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24);
-
-        private readonly IAudioRendererManager _impl;
-
-        public AudioRendererManagerServer(ServiceCtx context) : this(context, new AudioRendererManager(context.Device.System.AudioRendererManager, context.Device.System.AudioDeviceSessionRegistry)) { }
-
-        public AudioRendererManagerServer(ServiceCtx context, IAudioRendererManager impl) : base(context.Device.System.AudRenServer)
-        {
-            _impl = impl;
-        }
-
-        [CommandCmif(0)]
-        // OpenAudioRenderer(nn::audio::detail::AudioRendererParameterInternal parameter, u64 workBufferSize, nn::applet::AppletResourceUserId appletResourceId, pid, handle<copy> workBuffer, handle<copy> processHandle)
-        // -> object<nn::audio::detail::IAudioRenderer>
-        public ResultCode OpenAudioRenderer(ServiceCtx context)
-        {
-            AudioRendererConfiguration parameter = context.RequestData.ReadStruct<AudioRendererConfiguration>();
-            ulong workBufferSize = context.RequestData.ReadUInt64();
-            ulong appletResourceUserId = context.RequestData.ReadUInt64();
-
-            int transferMemoryHandle = context.Request.HandleDesc.ToCopy[0];
-            KTransferMemory workBufferTransferMemory = context.Process.HandleTable.GetObject<KTransferMemory>(transferMemoryHandle);
-            uint processHandle = (uint)context.Request.HandleDesc.ToCopy[1];
-
-            ResultCode result = _impl.OpenAudioRenderer(
-                context,
-                out IAudioRenderer renderer,
-                ref parameter,
-                workBufferSize,
-                appletResourceUserId,
-                workBufferTransferMemory,
-                processHandle);
-
-            if (result == ResultCode.Success)
-            {
-                MakeObject(context, new AudioRendererServer(renderer));
-            }
-
-            context.Device.System.KernelContext.Syscall.CloseHandle(transferMemoryHandle);
-            context.Device.System.KernelContext.Syscall.CloseHandle((int)processHandle);
-
-            return result;
-        }
-
-        [CommandCmif(1)]
-        // GetWorkBufferSize(nn::audio::detail::AudioRendererParameterInternal parameter) -> u64 workBufferSize
-        public ResultCode GetAudioRendererWorkBufferSize(ServiceCtx context)
-        {
-            AudioRendererConfiguration parameter = context.RequestData.ReadStruct<AudioRendererConfiguration>();
-
-            if (BehaviourContext.CheckValidRevision(parameter.Revision))
-            {
-                ulong size = _impl.GetWorkBufferSize(ref parameter);
-
-                context.ResponseData.Write(size);
-
-                Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{size:x16}.");
-
-                return ResultCode.Success;
-            }
-            else
-            {
-                context.ResponseData.Write(0L);
-
-                Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Revision)} is not supported!");
-
-                return ResultCode.UnsupportedRevision;
-            }
-        }
-
-        [CommandCmif(2)]
-        // GetAudioDeviceService(nn::applet::AppletResourceUserId) -> object<nn::audio::detail::IAudioDevice>
-        public ResultCode GetAudioDeviceService(ServiceCtx context)
-        {
-            ulong appletResourceUserId = context.RequestData.ReadUInt64();
-
-            ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, InitialRevision, appletResourceUserId);
-
-            if (result == ResultCode.Success)
-            {
-                MakeObject(context, new AudioDeviceServer(device));
-            }
-
-            return result;
-        }
-
-        [CommandCmif(4)] // 4.0.0+
-        // GetAudioDeviceServiceWithRevisionInfo(s32 revision, nn::applet::AppletResourceUserId appletResourceId) -> object<nn::audio::detail::IAudioDevice>
-        public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context)
-        {
-            int revision = context.RequestData.ReadInt32();
-            ulong appletResourceUserId = context.RequestData.ReadUInt64();
-
-            ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, revision, appletResourceUserId);
-
-            if (result == ResultCode.Success)
-            {
-                MakeObject(context, new AudioDeviceServer(device));
-            }
-
-            return result;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs
deleted file mode 100644
index c5dd2f00d..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Concentus.Structs;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
-{
-    class Decoder : IDecoder
-    {
-        private readonly OpusDecoder _decoder;
-
-        public int SampleRate => _decoder.SampleRate;
-        public int ChannelsCount => _decoder.NumChannels;
-
-        public Decoder(int sampleRate, int channelsCount)
-        {
-            _decoder = new OpusDecoder(sampleRate, channelsCount);
-        }
-
-        public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
-        {
-            return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize);
-        }
-
-        public void ResetState()
-        {
-            _decoder.ResetState();
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs
deleted file mode 100644
index 9ff511a50..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-using Concentus;
-using Concentus.Enums;
-using Concentus.Structs;
-using Ryujinx.HLE.HOS.Services.Audio.Types;
-using System;
-using System.Runtime.CompilerServices;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
-{
-    static class DecoderCommon
-    {
-        private static ResultCode GetPacketNumSamples(this IDecoder decoder, out int numSamples, byte[] packet)
-        {
-            int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate);
-
-            numSamples = result;
-
-            if (result == OpusError.OPUS_INVALID_PACKET)
-            {
-                return ResultCode.OpusInvalidInput;
-            }
-            else if (result == OpusError.OPUS_BAD_ARG)
-            {
-                return ResultCode.OpusInvalidInput;
-            }
-
-            return ResultCode.Success;
-        }
-
-        public static ResultCode DecodeInterleaved(
-            this IDecoder decoder,
-            bool reset,
-            ReadOnlySpan<byte> input,
-            out short[] outPcmData,
-            ulong outputSize,
-            out uint outConsumed,
-            out int outSamples)
-        {
-            outPcmData = null;
-            outConsumed = 0;
-            outSamples = 0;
-
-            int streamSize = input.Length;
-
-            if (streamSize < Unsafe.SizeOf<OpusPacketHeader>())
-            {
-                return ResultCode.OpusInvalidInput;
-            }
-
-            OpusPacketHeader header = OpusPacketHeader.FromSpan(input);
-            int headerSize = Unsafe.SizeOf<OpusPacketHeader>();
-            uint totalSize = header.length + (uint)headerSize;
-
-            if (totalSize > streamSize)
-            {
-                return ResultCode.OpusInvalidInput;
-            }
-
-            byte[] opusData = input.Slice(headerSize, (int)header.length).ToArray();
-
-            ResultCode result = decoder.GetPacketNumSamples(out int numSamples, opusData);
-
-            if (result == ResultCode.Success)
-            {
-                if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize)
-                {
-                    return ResultCode.OpusInvalidInput;
-                }
-
-                outPcmData = new short[numSamples * decoder.ChannelsCount];
-
-                if (reset)
-                {
-                    decoder.ResetState();
-                }
-
-                try
-                {
-                    outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount);
-                    outConsumed = totalSize;
-                }
-                catch (OpusException)
-                {
-                    // TODO: as OpusException doesn't provide us the exact error code, this is kind of inaccurate in some cases...
-                    return ResultCode.OpusInvalidInput;
-                }
-            }
-
-            return ResultCode.Success;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs
deleted file mode 100644
index cc83be5e3..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
-{
-    interface IDecoder
-    {
-        int SampleRate { get; }
-        int ChannelsCount { get; }
-
-        int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize);
-        void ResetState();
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs
deleted file mode 100644
index 3d5d2839f..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-using Ryujinx.HLE.HOS.Services.Audio.Types;
-using System;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
-{
-    class IHardwareOpusDecoder : IpcService
-    {
-        private readonly IDecoder _decoder;
-        private readonly OpusDecoderFlags _flags;
-
-        public IHardwareOpusDecoder(int sampleRate, int channelsCount, OpusDecoderFlags flags)
-        {
-            _decoder = new Decoder(sampleRate, channelsCount);
-            _flags = flags;
-        }
-
-        public IHardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, OpusDecoderFlags flags, byte[] mapping)
-        {
-            _decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
-            _flags = flags;
-        }
-
-        [CommandCmif(0)]
-        // DecodeInterleavedOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
-        public ResultCode DecodeInterleavedOld(ServiceCtx context)
-        {
-            return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false);
-        }
-
-        [CommandCmif(2)]
-        // DecodeInterleavedForMultiStreamOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
-        public ResultCode DecodeInterleavedForMultiStreamOld(ServiceCtx context)
-        {
-            return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false);
-        }
-
-        [CommandCmif(4)] // 6.0.0+
-        // DecodeInterleavedWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
-        public ResultCode DecodeInterleavedWithPerfOld(ServiceCtx context)
-        {
-            return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true);
-        }
-
-        [CommandCmif(5)] // 6.0.0+
-        // DecodeInterleavedForMultiStreamWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
-        public ResultCode DecodeInterleavedForMultiStreamWithPerfOld(ServiceCtx context)
-        {
-            return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true);
-        }
-
-        [CommandCmif(6)] // 6.0.0+
-        // DecodeInterleavedWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
-        public ResultCode DecodeInterleavedWithPerfAndResetOld(ServiceCtx context)
-        {
-            bool reset = context.RequestData.ReadBoolean();
-
-            return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true);
-        }
-
-        [CommandCmif(7)] // 6.0.0+
-        // DecodeInterleavedForMultiStreamWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
-        public ResultCode DecodeInterleavedForMultiStreamWithPerfAndResetOld(ServiceCtx context)
-        {
-            bool reset = context.RequestData.ReadBoolean();
-
-            return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true);
-        }
-
-        [CommandCmif(8)] // 7.0.0+
-        // DecodeInterleaved(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>)
-        public ResultCode DecodeInterleaved(ServiceCtx context)
-        {
-            bool reset = context.RequestData.ReadBoolean();
-
-            return DecodeInterleavedInternal(context, _flags, reset, withPerf: true);
-        }
-
-        [CommandCmif(9)] // 7.0.0+
-        // DecodeInterleavedForMultiStream(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>)
-        public ResultCode DecodeInterleavedForMultiStream(ServiceCtx context)
-        {
-            bool reset = context.RequestData.ReadBoolean();
-
-            return DecodeInterleavedInternal(context, _flags, reset, withPerf: true);
-        }
-
-        private ResultCode DecodeInterleavedInternal(ServiceCtx context, OpusDecoderFlags flags, bool reset, bool withPerf)
-        {
-            ulong inPosition = context.Request.SendBuff[0].Position;
-            ulong inSize = context.Request.SendBuff[0].Size;
-            ulong outputPosition = context.Request.ReceiveBuff[0].Position;
-            ulong outputSize = context.Request.ReceiveBuff[0].Size;
-
-            ReadOnlySpan<byte> input = context.Memory.GetSpan(inPosition, (int)inSize);
-
-            ResultCode result = _decoder.DecodeInterleaved(reset, input, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples);
-
-            if (result == ResultCode.Success)
-            {
-                context.Memory.Write(outputPosition, MemoryMarshal.Cast<short, byte>(outPcmData.AsSpan()));
-
-                context.ResponseData.Write(outConsumed);
-                context.ResponseData.Write(outSamples);
-
-                if (withPerf)
-                {
-                    // This is the time the DSP took to process the request, TODO: fill this.
-                    context.ResponseData.Write(0UL);
-                }
-            }
-
-            return result;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs
deleted file mode 100644
index 910bb23ee..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using Concentus.Structs;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
-{
-    class MultiSampleDecoder : IDecoder
-    {
-        private readonly OpusMSDecoder _decoder;
-
-        public int SampleRate => _decoder.SampleRate;
-        public int ChannelsCount { get; }
-
-        public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
-        {
-            ChannelsCount = channelsCount;
-            _decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
-        }
-
-        public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
-        {
-            return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0);
-        }
-
-        public void ResetState()
-        {
-            _decoder.ResetState();
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs
deleted file mode 100644
index a250ec799..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("audctl")]
-    class IAudioController : IpcService
-    {
-        public IAudioController(ServiceCtx context) { }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs
deleted file mode 100644
index 861e9f2dc..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Ryujinx.Audio.Common;
-using Ryujinx.HLE.HOS.Services.Audio.AudioIn;
-
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    interface IAudioInManager
-    {
-        public string[] ListAudioIns(bool filtered);
-
-        public ResultCode OpenAudioIn(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle);
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs
deleted file mode 100644
index d0c385b56..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("audin:a")]
-    class IAudioInManagerForApplet : IpcService
-    {
-        public IAudioInManagerForApplet(ServiceCtx context) { }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs
deleted file mode 100644
index 120136158..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("audin:d")]
-    class IAudioInManagerForDebugger : IpcService
-    {
-        public IAudioInManagerForDebugger(ServiceCtx context) { }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs
deleted file mode 100644
index cd7cbe41c..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Ryujinx.Audio.Common;
-using Ryujinx.HLE.HOS.Services.Audio.AudioOut;
-
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    interface IAudioOutManager
-    {
-        public string[] ListAudioOuts();
-
-        public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume);
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs
deleted file mode 100644
index 9925777e2..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("audout:a")]
-    class IAudioOutManagerForApplet : IpcService
-    {
-        public IAudioOutManagerForApplet(ServiceCtx context) { }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs
deleted file mode 100644
index c41767a01..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("audout:d")]
-    class IAudioOutManagerForDebugger : IpcService
-    {
-        public IAudioOutManagerForDebugger(ServiceCtx context) { }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs
deleted file mode 100644
index 112e246c0..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Ryujinx.Audio.Renderer.Parameter;
-using Ryujinx.HLE.HOS.Kernel.Memory;
-using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
-
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    interface IAudioRendererManager
-    {
-        // TODO: Remove ServiceCtx argument
-        // BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend.
-        ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId);
-
-        // TODO: Remove ServiceCtx argument
-        // BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend.
-        ResultCode OpenAudioRenderer(ServiceCtx context, out IAudioRenderer obj, ref AudioRendererConfiguration parameter, ulong workBufferSize, ulong appletResourceUserId, KTransferMemory workBufferTransferMemory, uint processHandle);
-
-        ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter);
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs
deleted file mode 100644
index dd767993d..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("audren:a")]
-    class IAudioRendererManagerForApplet : IpcService
-    {
-        public IAudioRendererManagerForApplet(ServiceCtx context) { }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs
deleted file mode 100644
index cd2af09b2..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("audren:d")]
-    class IAudioRendererManagerForDebugger : IpcService
-    {
-        public IAudioRendererManagerForDebugger(ServiceCtx context) { }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs
deleted file mode 100644
index aa9789ac5..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("auddev")] // 6.0.0+
-    class IAudioSnoopManager : IpcService
-    {
-        public IAudioSnoopManager(ServiceCtx context) { }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs
deleted file mode 100644
index 9b58213e9..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("audrec:u")]
-    class IFinalOutputRecorderManager : IpcService
-    {
-        public IFinalOutputRecorderManager(ServiceCtx context) { }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs
deleted file mode 100644
index e2d62eee3..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("audrec:a")]
-    class IFinalOutputRecorderManagerForApplet : IpcService
-    {
-        public IFinalOutputRecorderManagerForApplet(ServiceCtx context) { }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs
deleted file mode 100644
index 7ded79435..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("audrec:d")]
-    class IFinalOutputRecorderManagerForDebugger : IpcService
-    {
-        public IFinalOutputRecorderManagerForDebugger(ServiceCtx context) { }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs
deleted file mode 100644
index 514b51a51..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs
+++ /dev/null
@@ -1,227 +0,0 @@
-using Ryujinx.Common;
-using Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager;
-using Ryujinx.HLE.HOS.Services.Audio.Types;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Audio
-{
-    [Service("hwopus")]
-    class IHardwareOpusDecoderManager : IpcService
-    {
-        public IHardwareOpusDecoderManager(ServiceCtx context) { }
-
-        [CommandCmif(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, OpusDecoderFlags.None));
-
-            // Close transfer memory immediately as we don't use it.
-            context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(1)]
-        // GetWorkBufferSize(bytes<8, 4>) -> u32
-        public ResultCode GetWorkBufferSize(ServiceCtx context)
-        {
-            int sampleRate = context.RequestData.ReadInt32();
-            int channelsCount = context.RequestData.ReadInt32();
-
-            int opusDecoderSize = GetOpusDecoderSize(channelsCount);
-
-            int frameSize = BitUtils.AlignUp(channelsCount * 1920 / (48000 / sampleRate), 64);
-            int totalSize = opusDecoderSize + 1536 + frameSize;
-
-            context.ResponseData.Write(totalSize);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(2)] // 3.0.0+
-        // InitializeForMultiStream(u32, handle<copy>, buffer<unknown<0x110>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder>
-        public ResultCode InitializeForMultiStream(ServiceCtx context)
-        {
-            ulong parametersAddress = context.Request.PtrBuff[0].Position;
-
-            OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress);
-
-            MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, OpusDecoderFlags.None));
-
-            // Close transfer memory immediately as we don't use it.
-            context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(3)] // 3.0.0+
-        // GetWorkBufferSizeForMultiStream(buffer<unknown<0x110>, 0x19>) -> u32
-        public ResultCode GetWorkBufferSizeForMultiStream(ServiceCtx context)
-        {
-            ulong parametersAddress = context.Request.PtrBuff[0].Position;
-
-            OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress);
-
-            int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams);
-
-            int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64);
-            int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * 1920 / (48000 / parameters.SampleRate), 64);
-            int totalSize = opusDecoderSize + streamSize + frameSize;
-
-            context.ResponseData.Write(totalSize);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(4)] // 12.0.0+
-        // InitializeEx(OpusParametersEx, u32, handle<copy>) -> object<nn::codec::detail::IHardwareOpusDecoder>
-        public ResultCode InitializeEx(ServiceCtx context)
-        {
-            OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>();
-
-            // UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result.
-            MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, parameters.Flags));
-
-            // Close transfer memory immediately as we don't use it.
-            context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(5)] // 12.0.0+
-        // GetWorkBufferSizeEx(OpusParametersEx) -> u32
-        public ResultCode GetWorkBufferSizeEx(ServiceCtx context)
-        {
-            OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>();
-
-            int opusDecoderSize = GetOpusDecoderSize(parameters.ChannelsCount);
-
-            int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
-            int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64);
-            int totalSize = opusDecoderSize + 1536 + frameSize;
-
-            context.ResponseData.Write(totalSize);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(6)] // 12.0.0+
-        // InitializeForMultiStreamEx(u32, handle<copy>, buffer<unknown<0x118>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder>
-        public ResultCode InitializeForMultiStreamEx(ServiceCtx context)
-        {
-            ulong parametersAddress = context.Request.PtrBuff[0].Position;
-
-            OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress);
-
-            byte[] mappings = MemoryMarshal.Cast<uint, byte>(parameters.ChannelMappings.AsSpan()).ToArray();
-
-            // UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result.
-            MakeObject(context, new IHardwareOpusDecoder(
-                parameters.SampleRate,
-                parameters.ChannelsCount,
-                parameters.NumberOfStreams,
-                parameters.NumberOfStereoStreams,
-                parameters.Flags,
-                mappings));
-
-            // Close transfer memory immediately as we don't use it.
-            context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(7)] // 12.0.0+
-        // GetWorkBufferSizeForMultiStreamEx(buffer<unknown<0x118>, 0x19>) -> u32
-        public ResultCode GetWorkBufferSizeForMultiStreamEx(ServiceCtx context)
-        {
-            ulong parametersAddress = context.Request.PtrBuff[0].Position;
-
-            OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress);
-
-            int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams);
-
-            int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
-            int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64);
-            int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64);
-            int totalSize = opusDecoderSize + streamSize + frameSize;
-
-            context.ResponseData.Write(totalSize);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(8)] // 16.0.0+
-        // GetWorkBufferSizeExEx(OpusParametersEx) -> u32
-        public ResultCode GetWorkBufferSizeExEx(ServiceCtx context)
-        {
-            // NOTE: GetWorkBufferSizeEx use hardcoded values to compute the returned size.
-            //       GetWorkBufferSizeExEx fixes that by using dynamic values.
-            //       Since we're already doing that, it's fine to call it directly.
-
-            return GetWorkBufferSizeEx(context);
-        }
-
-        [CommandCmif(9)] // 16.0.0+
-        // GetWorkBufferSizeForMultiStreamExEx(buffer<unknown<0x118>, 0x19>) -> u32
-        public ResultCode GetWorkBufferSizeForMultiStreamExEx(ServiceCtx context)
-        {
-            // NOTE: GetWorkBufferSizeForMultiStreamEx use hardcoded values to compute the returned size.
-            //       GetWorkBufferSizeForMultiStreamExEx fixes that by using dynamic values.
-            //       Since we're already doing that, it's fine to call it directly.
-
-            return GetWorkBufferSizeForMultiStreamEx(context);
-        }
-
-        private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams)
-        {
-            if (streams < 1 || coupledStreams > streams || coupledStreams < 0)
-            {
-                return 0;
-            }
-
-            int coupledSize = GetOpusDecoderSize(2);
-            int monoSize = GetOpusDecoderSize(1);
-
-            return Align4(monoSize - GetOpusDecoderAllocSize(1)) * (streams - coupledStreams) +
-                Align4(coupledSize - GetOpusDecoderAllocSize(2)) * coupledStreams + 0xb90c;
-        }
-
-        private static int Align4(int value)
-        {
-            return BitUtils.AlignUp(value, 4);
-        }
-
-        private static int GetOpusDecoderSize(int channelsCount)
-        {
-            const int SilkDecoderSize = 0x2160;
-
-            if (channelsCount < 1 || channelsCount > 2)
-            {
-                return 0;
-            }
-
-            int celtDecoderSize = GetCeltDecoderSize(channelsCount);
-            int opusDecoderSize = GetOpusDecoderAllocSize(channelsCount) | 0x4c;
-
-            return opusDecoderSize + SilkDecoderSize + celtDecoderSize;
-        }
-
-        private static int GetOpusDecoderAllocSize(int channelsCount)
-        {
-            return (channelsCount * 0x800 + 0x4803) & -0x800;
-        }
-
-        private static int GetCeltDecoderSize(int channelsCount)
-        {
-            const int DecodeBufferSize = 0x2030;
-            const int Overlap = 120;
-            const int EBandsCount = 21;
-
-            return (DecodeBufferSize + Overlap * 4) * channelsCount + EBandsCount * 16 + 0x50;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs
deleted file mode 100644
index c1d49109c..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-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,
-        BufferSizeTooSmall = (4 << ErrorCodeShift) | ModuleId,
-        OpusInvalidInput = (6 << ErrorCodeShift) | ModuleId,
-        TooManyBuffersInUse = (8 << ErrorCodeShift) | ModuleId,
-        InvalidChannelCount = (10 << ErrorCodeShift) | ModuleId,
-        InvalidOperation = (513 << ErrorCodeShift) | ModuleId,
-        InvalidHandle = (1536 << ErrorCodeShift) | ModuleId,
-        OutputAlreadyStarted = (1540 << ErrorCodeShift) | ModuleId,
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs
deleted file mode 100644
index 099769b3a..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using System.Buffers.Binary;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.Types
-{
-    [StructLayout(LayoutKind.Sequential)]
-    struct OpusPacketHeader
-    {
-        public uint length;
-        public uint finalRange;
-
-        public static OpusPacketHeader FromSpan(ReadOnlySpan<byte> data)
-        {
-            OpusPacketHeader header = MemoryMarshal.Cast<byte, OpusPacketHeader>(data)[0];
-
-            header.length = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.length) : header.length;
-            header.finalRange = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.finalRange) : header.finalRange;
-
-            return header;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs
deleted file mode 100644
index 4d1e0c824..000000000
--- a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Ryujinx.Common.Memory;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Audio.Types
-{
-    [StructLayout(LayoutKind.Sequential, Size = 0x10)]
-    struct OpusParametersEx
-    {
-        public int SampleRate;
-        public int ChannelsCount;
-        public OpusDecoderFlags Flags;
-
-        Array4<byte> Padding1;
-    }
-}
diff --git a/src/Ryujinx.HLE/Ryujinx.HLE.csproj b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
index dbcb82212..0fcf9e4b5 100644
--- a/src/Ryujinx.HLE/Ryujinx.HLE.csproj
+++ b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
@@ -21,7 +21,6 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Concentus" />
     <PackageReference Include="LibHac" />
     <PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
     <PackageReference Include="MsgPack.Cli" />
@@ -30,11 +29,6 @@
     <PackageReference Include="NetCoreServer" />
   </ItemGroup>
 
-  <!-- Due to Concentus. -->
-  <PropertyGroup>
-    <NoWarn>NU1605</NoWarn>
-  </PropertyGroup>
-
   <ItemGroup>
     <None Remove="Homebrew.npdm" />
     <None Remove="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs
index 912a39b04..81c3ab473 100644
--- a/src/Ryujinx.HLE/Switch.cs
+++ b/src/Ryujinx.HLE/Switch.cs
@@ -117,12 +117,12 @@ namespace Ryujinx.HLE
 
         public void SetVolume(float volume)
         {
-            System.SetVolume(Math.Clamp(volume, 0, 1));
+            AudioDeviceDriver.Volume = Math.Clamp(volume, 0f, 1f);
         }
 
         public float GetVolume()
         {
-            return System.GetVolume();
+            return AudioDeviceDriver.Volume;
         }
 
         public void EnableCheats()
@@ -132,7 +132,7 @@ namespace Ryujinx.HLE
 
         public bool IsAudioMuted()
         {
-            return System.GetVolume() == 0;
+            return AudioDeviceDriver.Volume == 0;
         }
 
         public void DisposeGpu()
diff --git a/src/Ryujinx.Horizon.Common/IExternalEvent.cs b/src/Ryujinx.Horizon.Common/IExternalEvent.cs
new file mode 100644
index 000000000..dedf4c72a
--- /dev/null
+++ b/src/Ryujinx.Horizon.Common/IExternalEvent.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Ryujinx.Horizon.Common
+{
+    public interface IExternalEvent
+    {
+        void Signal();
+        void Clear();
+    }
+}
diff --git a/src/Ryujinx.Horizon.Common/ISyscallApi.cs b/src/Ryujinx.Horizon.Common/ISyscallApi.cs
index 20277f344..3d6da0416 100644
--- a/src/Ryujinx.Horizon.Common/ISyscallApi.cs
+++ b/src/Ryujinx.Horizon.Common/ISyscallApi.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Memory;
 using System;
 
 namespace Ryujinx.Horizon.Common
@@ -29,5 +30,9 @@ namespace Ryujinx.Horizon.Common
         Result CreatePort(out int serverPortHandle, out int clientPortHandle, int maxSessions, bool isLight, string name);
         Result ManageNamedPort(out int handle, string name, int maxSessions);
         Result ConnectToPort(out int clientSessionHandle, int clientPortHandle);
+
+        IExternalEvent GetExternalEvent(int handle);
+        IVirtualMemoryManager GetMemoryManagerByProcessHandle(int handle);
+        ulong GetTransferMemoryAddress(int handle);
     }
 }
diff --git a/src/Ryujinx.Horizon.Common/Result.cs b/src/Ryujinx.Horizon.Common/Result.cs
index d313554e3..4b120b847 100644
--- a/src/Ryujinx.Horizon.Common/Result.cs
+++ b/src/Ryujinx.Horizon.Common/Result.cs
@@ -36,6 +36,11 @@ namespace Ryujinx.Horizon.Common
             ErrorCode = module | (description << ModuleBits);
         }
 
+        public Result(int errorCode)
+        {
+            ErrorCode = errorCode;
+        }
+
         public readonly override bool Equals(object obj)
         {
             return obj is Result result && result.Equals(this);
diff --git a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs
index a65ec3abd..19667290f 100644
--- a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs
+++ b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs
@@ -286,13 +286,13 @@ namespace Ryujinx.Horizon.Generators.Hipc
                 {
                     if (IsNonSpanOutBuffer(compilation, parameter))
                     {
-                        generator.AppendLine($"using var {argName} = CommandSerialization.GetWritableRegion(processor.GetBufferRange({outArgIndex++}));");
+                        generator.AppendLine($"using var {argName} = CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}));");
 
                         argName = $"out {GenerateSpanCastElement0(canonicalTypeName, $"{argName}.Memory.Span")}";
                     }
                     else
                     {
-                        outParameters.Add(new OutParameter(argName, canonicalTypeName, index, argType));
+                        outParameters.Add(new OutParameter(argName, canonicalTypeName, outArgIndex++, argType));
 
                         argName = $"out {canonicalTypeName} {argName}";
                     }
diff --git a/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs b/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs
index 6080b4750..a6017b8a6 100644
--- a/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs
+++ b/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs
@@ -56,6 +56,7 @@ namespace Ryujinx.Horizon.Arp
         {
             _applicationInstanceManager.Dispose();
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/Audio/AudioMain.cs b/src/Ryujinx.Horizon/Audio/AudioMain.cs
new file mode 100644
index 000000000..92c9e804f
--- /dev/null
+++ b/src/Ryujinx.Horizon/Audio/AudioMain.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Horizon.Audio
+{
+    class AudioMain : IService
+    {
+        public static void Main(ServiceTable serviceTable)
+        {
+            AudioUserIpcServer ipcServer = new();
+
+            ipcServer.Initialize();
+
+            serviceTable.SignalServiceReady();
+
+            ipcServer.ServiceRequests();
+            ipcServer.Shutdown();
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Audio/AudioManagers.cs b/src/Ryujinx.Horizon/Audio/AudioManagers.cs
new file mode 100644
index 000000000..493a6f9b5
--- /dev/null
+++ b/src/Ryujinx.Horizon/Audio/AudioManagers.cs
@@ -0,0 +1,78 @@
+using Ryujinx.Audio;
+using Ryujinx.Audio.Input;
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Output;
+using Ryujinx.Audio.Renderer.Device;
+using Ryujinx.Audio.Renderer.Server;
+using Ryujinx.Cpu;
+using Ryujinx.Horizon.Sdk.Audio;
+using System;
+
+namespace Ryujinx.Horizon.Audio
+{
+    class AudioManagers : IDisposable
+    {
+        public AudioManager AudioManager { get; }
+        public AudioOutputManager AudioOutputManager { get; }
+        public AudioInputManager AudioInputManager { get; }
+        public AudioRendererManager AudioRendererManager { get; }
+        public VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; }
+
+        public AudioManagers(IHardwareDeviceDriver audioDeviceDriver, ITickSource tickSource)
+        {
+            AudioManager = new AudioManager();
+            AudioOutputManager = new AudioOutputManager();
+            AudioInputManager = new AudioInputManager();
+            AudioRendererManager = new AudioRendererManager(tickSource);
+            AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(audioDeviceDriver);
+
+            IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax];
+
+            for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++)
+            {
+                audioOutputRegisterBufferEvents[i] = new AudioEvent();
+            }
+
+            AudioOutputManager.Initialize(audioDeviceDriver, audioOutputRegisterBufferEvents);
+
+            IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax];
+
+            for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++)
+            {
+                audioInputRegisterBufferEvents[i] = new AudioEvent();
+            }
+
+            AudioInputManager.Initialize(audioDeviceDriver, audioInputRegisterBufferEvents);
+
+            IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax];
+
+            for (int i = 0; i < systemEvents.Length; i++)
+            {
+                systemEvents[i] = new AudioEvent();
+            }
+
+            AudioManager.Initialize(audioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update);
+
+            AudioRendererManager.Initialize(systemEvents, audioDeviceDriver);
+
+            AudioManager.Start();
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                AudioManager.Dispose();
+                AudioOutputManager.Dispose();
+                AudioInputManager.Dispose();
+                AudioRendererManager.Dispose();
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(disposing: true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs b/src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs
new file mode 100644
index 000000000..20c824e1e
--- /dev/null
+++ b/src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs
@@ -0,0 +1,55 @@
+using Ryujinx.Horizon.Sdk.Audio.Detail;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using Ryujinx.Horizon.Sdk.Sm;
+
+namespace Ryujinx.Horizon.Audio
+{
+    class AudioUserIpcServer
+    {
+        private const int MaxSessionsCount = 30;
+
+        private const int PointerBufferSize = 0xB40;
+        private const int MaxDomains = 0;
+        private const int MaxDomainObjects = 0;
+        private const int MaxPortsCount = 1;
+
+        private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
+
+        private SmApi _sm;
+        private ServerManager _serverManager;
+        private AudioManagers _managers;
+
+        public void Initialize()
+        {
+            HeapAllocator allocator = new();
+
+            _sm = new SmApi();
+            _sm.Initialize().AbortOnFailure();
+
+            _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, MaxSessionsCount);
+            _managers = new AudioManagers(HorizonStatic.Options.AudioDeviceDriver, HorizonStatic.Options.TickSource);
+
+            AudioRendererManager audioRendererManager = new(_managers.AudioRendererManager, _managers.AudioDeviceSessionRegistry);
+            AudioOutManager audioOutManager = new(_managers.AudioOutputManager);
+            AudioInManager audioInManager = new(_managers.AudioInputManager);
+            FinalOutputRecorderManager finalOutputRecorderManager = new();
+
+            _serverManager.RegisterObjectForServer(audioRendererManager, ServiceName.Encode("audren:u"), MaxSessionsCount);
+            _serverManager.RegisterObjectForServer(audioOutManager, ServiceName.Encode("audout:u"), MaxSessionsCount);
+            _serverManager.RegisterObjectForServer(audioInManager, ServiceName.Encode("audin:u"), MaxSessionsCount);
+            _serverManager.RegisterObjectForServer(finalOutputRecorderManager, ServiceName.Encode("audrec:u"), MaxSessionsCount);
+        }
+
+        public void ServiceRequests()
+        {
+            _serverManager.ServiceRequests();
+        }
+
+        public void Shutdown()
+        {
+            _serverManager.Dispose();
+            _managers.Dispose();
+            _sm.Dispose();
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs b/src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs
new file mode 100644
index 000000000..e60e033cc
--- /dev/null
+++ b/src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs
@@ -0,0 +1,46 @@
+using Ryujinx.Horizon.Sdk.Codec.Detail;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using Ryujinx.Horizon.Sdk.Sm;
+
+namespace Ryujinx.Horizon.Audio
+{
+    class HwopusIpcServer
+    {
+        private const int MaxSessionsCount = 24;
+
+        private const int PointerBufferSize = 0x1000;
+        private const int MaxDomains = 8;
+        private const int MaxDomainObjects = 256;
+        private const int MaxPortsCount = 1;
+
+        private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
+
+        private SmApi _sm;
+        private ServerManager _serverManager;
+
+        public void Initialize()
+        {
+            HeapAllocator allocator = new();
+
+            _sm = new SmApi();
+            _sm.Initialize().AbortOnFailure();
+
+            _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, MaxSessionsCount);
+
+            HardwareOpusDecoderManager hardwareOpusDecoderManager = new();
+
+            _serverManager.RegisterObjectForServer(hardwareOpusDecoderManager, ServiceName.Encode("hwopus"), MaxSessionsCount);
+        }
+
+        public void ServiceRequests()
+        {
+            _serverManager.ServiceRequests();
+        }
+
+        public void Shutdown()
+        {
+            _serverManager.Dispose();
+            _sm.Dispose();
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Audio/HwopusMain.cs b/src/Ryujinx.Horizon/Audio/HwopusMain.cs
new file mode 100644
index 000000000..04eee3fad
--- /dev/null
+++ b/src/Ryujinx.Horizon/Audio/HwopusMain.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Horizon.Audio
+{
+    class HwopusMain : IService
+    {
+        public static void Main(ServiceTable serviceTable)
+        {
+            HwopusIpcServer ipcServer = new();
+
+            ipcServer.Initialize();
+
+            serviceTable.SignalServiceReady();
+
+            ipcServer.ServiceRequests();
+            ipcServer.Shutdown();
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs b/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs
index dd4e5b53a..8da3971cf 100644
--- a/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs
+++ b/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs
@@ -44,6 +44,7 @@ namespace Ryujinx.Horizon.Bcat
         public void Shutdown()
         {
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs b/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs
index 523c617a8..a12c0cae8 100644
--- a/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs
+++ b/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs
@@ -44,6 +44,7 @@ namespace Ryujinx.Horizon.Friends
         public void Shutdown()
         {
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/HorizonOptions.cs b/src/Ryujinx.Horizon/HorizonOptions.cs
index 794620569..a24ce7f61 100644
--- a/src/Ryujinx.Horizon/HorizonOptions.cs
+++ b/src/Ryujinx.Horizon/HorizonOptions.cs
@@ -1,4 +1,6 @@
 using LibHac;
+using Ryujinx.Audio.Integration;
+using Ryujinx.Cpu;
 using Ryujinx.Horizon.Sdk.Account;
 using Ryujinx.Horizon.Sdk.Fs;
 
@@ -12,14 +14,24 @@ namespace Ryujinx.Horizon
         public HorizonClient BcatClient { get; }
         public IFsClient FsClient { get; }
         public IEmulatorAccountManager AccountManager { get; }
+        public IHardwareDeviceDriver AudioDeviceDriver { get; }
+        public ITickSource TickSource { get; }
 
-        public HorizonOptions(bool ignoreMissingServices, HorizonClient bcatClient, IFsClient fsClient, IEmulatorAccountManager accountManager)
+        public HorizonOptions(
+            bool ignoreMissingServices,
+            HorizonClient bcatClient,
+            IFsClient fsClient,
+            IEmulatorAccountManager accountManager,
+            IHardwareDeviceDriver audioDeviceDriver,
+            ITickSource tickSource)
         {
             IgnoreMissingServices = ignoreMissingServices;
             ThrowOnInvalidCommandIds = true;
             BcatClient = bcatClient;
             FsClient = fsClient;
             AccountManager = accountManager;
+            AudioDeviceDriver = audioDeviceDriver;
+            TickSource = tickSource;
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs b/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs
index d7d89e24b..b1cc7259d 100644
--- a/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs
+++ b/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs
@@ -42,6 +42,7 @@ namespace Ryujinx.Horizon.Hshl
         public void Shutdown()
         {
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/Ins/InsIpcServer.cs b/src/Ryujinx.Horizon/Ins/InsIpcServer.cs
index bb2749d5f..4e06dcadd 100644
--- a/src/Ryujinx.Horizon/Ins/InsIpcServer.cs
+++ b/src/Ryujinx.Horizon/Ins/InsIpcServer.cs
@@ -42,6 +42,7 @@ namespace Ryujinx.Horizon.Ins
         public void Shutdown()
         {
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs b/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs
index 6b5421653..f25fc54b1 100644
--- a/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs
+++ b/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs
@@ -38,6 +38,7 @@ namespace Ryujinx.Horizon.Lbl
         public void Shutdown()
         {
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs b/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs
index d023ff927..6bb4e11c7 100644
--- a/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs
+++ b/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs
@@ -38,6 +38,7 @@ namespace Ryujinx.Horizon.LogManager
         public void Shutdown()
         {
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs b/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs
index c52a294f5..b3ce81182 100644
--- a/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs
+++ b/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs
@@ -38,6 +38,7 @@ namespace Ryujinx.Horizon.MmNv
         public void Shutdown()
         {
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs b/src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs
index b2a74fb22..ec73f96ae 100644
--- a/src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs
+++ b/src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs
@@ -3,7 +3,6 @@ using Ryujinx.Horizon.Sdk.Fs;
 using Ryujinx.Horizon.Sdk.Ngc.Detail;
 using Ryujinx.Horizon.Sdk.Sf.Hipc;
 using Ryujinx.Horizon.Sdk.Sm;
-using System;
 
 namespace Ryujinx.Horizon.Ngc
 {
@@ -46,6 +45,7 @@ namespace Ryujinx.Horizon.Ngc
         {
             _serverManager.Dispose();
             _profanityFilter.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs b/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs
index c4580a861..d4257be8d 100644
--- a/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs
+++ b/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs
@@ -43,6 +43,7 @@ namespace Ryujinx.Horizon.Ovln
         public void Shutdown()
         {
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs b/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs
index 1902cde23..669a64594 100644
--- a/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs
+++ b/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs
@@ -51,6 +51,7 @@ namespace Ryujinx.Horizon.Prepo
         {
             _arp.Dispose();
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/Psc/PscIpcServer.cs b/src/Ryujinx.Horizon/Psc/PscIpcServer.cs
index d6ac65685..8e574ddda 100644
--- a/src/Ryujinx.Horizon/Psc/PscIpcServer.cs
+++ b/src/Ryujinx.Horizon/Psc/PscIpcServer.cs
@@ -45,6 +45,7 @@ namespace Ryujinx.Horizon.Psc
         public void Shutdown()
         {
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
index ae40f7b5e..d1f572d5c 100644
--- a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
+++ b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
@@ -5,6 +5,7 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
     <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
     <ProjectReference Include="..\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj" />
     <ProjectReference Include="..\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
@@ -12,7 +13,13 @@
   </ItemGroup>
 
   <ItemGroup>
+    <PackageReference Include="Concentus" />
     <PackageReference Include="LibHac" />
   </ItemGroup>
 
+  <!-- Due to Concentus. -->
+  <PropertyGroup>
+    <NoWarn>NU1605</NoWarn>
+  </PropertyGroup>
+
 </Project>
diff --git a/src/Ryujinx.Horizon/Sdk/Account/Uid.cs b/src/Ryujinx.Horizon/Sdk/Account/Uid.cs
index ada2c02ba..d612f4792 100644
--- a/src/Ryujinx.Horizon/Sdk/Account/Uid.cs
+++ b/src/Ryujinx.Horizon/Sdk/Account/Uid.cs
@@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
 
 namespace Ryujinx.Horizon.Sdk.Account
 {
-    [StructLayout(LayoutKind.Sequential)]
+    [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)]
     public readonly record struct Uid
     {
         public readonly ulong High;
diff --git a/src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs b/src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs
new file mode 100644
index 000000000..2b81fbf6f
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs
@@ -0,0 +1,71 @@
+namespace Ryujinx.Horizon.Sdk.Applet
+{
+    enum AppletId : uint
+    {
+        None = 0x00,
+        Application = 0x01,
+        OverlayApplet = 0x02,
+        SystemAppletMenu = 0x03,
+        SystemApplication = 0x04,
+        LibraryAppletAuth = 0x0A,
+        LibraryAppletCabinet = 0x0B,
+        LibraryAppletController = 0x0C,
+        LibraryAppletDataErase = 0x0D,
+        LibraryAppletError = 0x0E,
+        LibraryAppletNetConnect = 0x0F,
+        LibraryAppletPlayerSelect = 0x10,
+        LibraryAppletSwkbd = 0x11,
+        LibraryAppletMiiEdit = 0x12,
+        LibraryAppletWeb = 0x13,
+        LibraryAppletShop = 0x14,
+        LibraryAppletPhotoViewer = 0x15,
+        LibraryAppletSet = 0x16,
+        LibraryAppletOfflineWeb = 0x17,
+        LibraryAppletLoginShare = 0x18,
+        LibraryAppletWifiWebAuth = 0x19,
+        LibraryAppletMyPage = 0x1A,
+        LibraryAppletGift = 0x1B,
+        LibraryAppletUserMigration = 0x1C,
+        LibraryAppletPreomiaSys = 0x1D,
+        LibraryAppletStory = 0x1E,
+        LibraryAppletPreomiaUsr = 0x1F,
+        LibraryAppletPreomiaUsrDummy = 0x20,
+        LibraryAppletSample = 0x21,
+        LibraryAppletPromoteQualification = 0x22,
+        LibraryAppletOfflineWebFw17 = 0x32,
+        LibraryAppletOfflineWeb2Fw17 = 0x33,
+        LibraryAppletLoginShareFw17 = 0x35,
+        LibraryAppletLoginShare2Fw17 = 0x36,
+        LibraryAppletLoginShare3Fw17 = 0x37,
+        Unknown38 = 0x38,
+        DevlopmentTool = 0x3E8,
+        CombinationLA = 0x3F1,
+        AeSystemApplet = 0x3F2,
+        AeOverlayApplet = 0x3F3,
+        AeStarter = 0x3F4,
+        AeLibraryAppletAlone = 0x3F5,
+        AeLibraryApplet1 = 0x3F6,
+        AeLibraryApplet2 = 0x3F7,
+        AeLibraryApplet3 = 0x3F8,
+        AeLibraryApplet4 = 0x3F9,
+        AppletISA = 0x3FA,
+        AppletIOA = 0x3FB,
+        AppletISTA = 0x3FC,
+        AppletILA1 = 0x3FD,
+        AppletILA2 = 0x3FE,
+        CombinationLAFw17 = 0x700000DC,
+        AeSystemAppletFw17 = 0x700000E6,
+        AeOverlayAppletFw17 = 0x700000E7,
+        AeStarterFw17 = 0x700000E8,
+        AeLibraryAppletAloneFw17 = 0x700000E9,
+        AeLibraryApplet1Fw17 = 0x700000EA,
+        AeLibraryApplet2Fw17 = 0x700000EB,
+        AeLibraryApplet3Fw17 = 0x700000EC,
+        AeLibraryApplet4Fw17 = 0x700000ED,
+        AppletISAFw17 = 0x700000F0,
+        AppletIOAFw17 = 0x700000F1,
+        AppletISTAFw17 = 0x700000F2,
+        AppletILA1Fw17 = 0x700000F3,
+        AppletILA2Fw17 = 0x700000F4,
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs b/src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs
new file mode 100644
index 000000000..00e2ad368
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs
@@ -0,0 +1,15 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Applet
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)]
+    readonly struct AppletResourceUserId
+    {
+        public readonly ulong Id;
+
+        public AppletResourceUserId(ulong id)
+        {
+            Id = id;
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs b/src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs
new file mode 100644
index 000000000..efa8d5bc1
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs
@@ -0,0 +1,50 @@
+using Ryujinx.Audio.Integration;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.OsTypes;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio
+{
+    class AudioEvent : IWritableEvent, IDisposable
+    {
+        private SystemEventType _systemEvent;
+        private readonly IExternalEvent _externalEvent;
+
+        public AudioEvent()
+        {
+            Os.CreateSystemEvent(out _systemEvent, EventClearMode.ManualClear, interProcess: true);
+
+            // We need to do this because the event will be signalled from a different thread.
+            _externalEvent = HorizonStatic.Syscall.GetExternalEvent(Os.GetWritableHandleOfSystemEvent(ref _systemEvent));
+        }
+
+        public void Signal()
+        {
+            _externalEvent.Signal();
+        }
+
+        public void Clear()
+        {
+            _externalEvent.Clear();
+        }
+
+        public int GetReadableHandle()
+        {
+            return Os.GetReadableHandleOfSystemEvent(ref _systemEvent);
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                Os.DestroySystemEvent(ref _systemEvent);
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(disposing: true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs b/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs
new file mode 100644
index 000000000..c18bfee9f
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs
@@ -0,0 +1,12 @@
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.Horizon.Sdk.Audio
+{
+    static class AudioResult
+    {
+        private const int ModuleId = 153;
+
+        public static Result DeviceNotFound => new(ModuleId, 1);
+        public static Result UnsupportedRevision => new(ModuleId, 2);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs
new file mode 100644
index 000000000..f67ea7298
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs
@@ -0,0 +1,252 @@
+using Ryujinx.Audio.Renderer.Device;
+using Ryujinx.Audio.Renderer.Server;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Applet;
+using Ryujinx.Horizon.Sdk.OsTypes;
+using Ryujinx.Horizon.Sdk.Sf;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    partial class AudioDevice : IAudioDevice, IDisposable
+    {
+        private readonly VirtualDeviceSessionRegistry _registry;
+        private readonly VirtualDeviceSession[] _sessions;
+        private readonly bool _isUsbDeviceSupported;
+
+        private SystemEventType _audioEvent;
+        private SystemEventType _audioInputEvent;
+        private SystemEventType _audioOutputEvent;
+
+        public AudioDevice(VirtualDeviceSessionRegistry registry, AppletResourceUserId appletResourceId, uint revision)
+        {
+            _registry = registry;
+
+            BehaviourContext behaviourContext = new();
+            behaviourContext.SetUserRevision((int)revision);
+
+            _isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported();
+            _sessions = registry.GetSessionByAppletResourceId(appletResourceId.Id);
+
+            Os.CreateSystemEvent(out _audioEvent, EventClearMode.AutoClear, interProcess: true);
+            Os.CreateSystemEvent(out _audioInputEvent, EventClearMode.AutoClear, interProcess: true);
+            Os.CreateSystemEvent(out _audioOutputEvent, EventClearMode.AutoClear, interProcess: true);
+        }
+
+        private bool TryGetDeviceByName(out VirtualDeviceSession result, string name, bool ignoreRevLimitation = false)
+        {
+            result = null;
+
+            foreach (VirtualDeviceSession session in _sessions)
+            {
+                if (session.Device.Name.Equals(name))
+                {
+                    if (!ignoreRevLimitation && !_isUsbDeviceSupported && session.Device.IsUsbDevice())
+                    {
+                        return false;
+                    }
+
+                    result = session;
+
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        [CmifCommand(0)]
+        public Result ListAudioDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names, out int nameCount)
+        {
+            int count = 0;
+
+            foreach (VirtualDeviceSession session in _sessions)
+            {
+                if (!_isUsbDeviceSupported && session.Device.IsUsbDevice())
+                {
+                    continue;
+                }
+
+                if (count >= names.Length)
+                {
+                    break;
+                }
+
+                names[count] = new DeviceName(session.Device.Name);
+
+                count++;
+            }
+
+            nameCount = count;
+
+            return Result.Success;
+        }
+
+        [CmifCommand(1)]
+        public Result SetAudioDeviceOutputVolume([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name, float volume)
+        {
+            if (name.Length > 0 && TryGetDeviceByName(out VirtualDeviceSession result, name[0].ToString(), ignoreRevLimitation: true))
+            {
+                if (!_isUsbDeviceSupported && result.Device.IsUsbDevice())
+                {
+                    result = _sessions[0];
+                }
+
+                result.Volume = volume;
+            }
+
+            return Result.Success;
+        }
+
+        [CmifCommand(2)]
+        public Result GetAudioDeviceOutputVolume([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name, out float volume)
+        {
+            if (name.Length > 0 && TryGetDeviceByName(out VirtualDeviceSession result, name[0].ToString()))
+            {
+                volume = result.Volume;
+            }
+            else
+            {
+                volume = 0f;
+            }
+
+            return Result.Success;
+        }
+
+        [CmifCommand(3)]
+        public Result GetActiveAudioDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> name)
+        {
+            VirtualDevice device = _registry.ActiveDevice;
+
+            if (!_isUsbDeviceSupported && device.IsUsbDevice())
+            {
+                device = _registry.DefaultDevice;
+            }
+
+            if (name.Length > 0)
+            {
+                name[0] = new DeviceName(device.Name);
+            }
+
+            return Result.Success;
+        }
+
+        [CmifCommand(4)]
+        public Result QueryAudioDeviceSystemEvent([CopyHandle] out int eventHandle)
+        {
+            eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioEvent);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(5)]
+        public Result GetActiveChannelCount(out int channelCount)
+        {
+            VirtualDevice device = _registry.ActiveDevice;
+
+            if (!_isUsbDeviceSupported && device.IsUsbDevice())
+            {
+                device = _registry.DefaultDevice;
+            }
+
+            channelCount = (int)device.ChannelCount;
+
+            return Result.Success;
+        }
+
+        [CmifCommand(6)] // 3.0.0+
+        public Result ListAudioDeviceNameAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names, out int nameCount)
+        {
+            return ListAudioDeviceName(names, out nameCount);
+        }
+
+        [CmifCommand(7)] // 3.0.0+
+        public Result SetAudioDeviceOutputVolumeAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name, float volume)
+        {
+            return SetAudioDeviceOutputVolume(name, volume);
+        }
+
+        [CmifCommand(8)] // 3.0.0+
+        public Result GetAudioDeviceOutputVolumeAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name, out float volume)
+        {
+            return GetAudioDeviceOutputVolume(name, out volume);
+        }
+
+        [CmifCommand(10)] // 3.0.0+
+        public Result GetActiveAudioDeviceNameAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> name)
+        {
+            return GetActiveAudioDeviceName(name);
+        }
+
+        [CmifCommand(11)] // 3.0.0+
+        public Result QueryAudioDeviceInputEvent([CopyHandle] out int eventHandle)
+        {
+            eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioInputEvent);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(12)] // 3.0.0+
+        public Result QueryAudioDeviceOutputEvent([CopyHandle] out int eventHandle)
+        {
+            eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioOutputEvent);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(13)] // 13.0.0+
+        public Result GetActiveAudioOutputDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> name)
+        {
+            if (name.Length > 0)
+            {
+                name[0] = new DeviceName(_registry.ActiveDevice.GetOutputDeviceName());
+            }
+
+            return Result.Success;
+        }
+
+        [CmifCommand(14)] // 13.0.0+
+        public Result ListAudioOutputDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names, out int nameCount)
+        {
+            int count = 0;
+
+            foreach (VirtualDeviceSession session in _sessions)
+            {
+                if (!_isUsbDeviceSupported && session.Device.IsUsbDevice())
+                {
+                    continue;
+                }
+
+                if (count >= names.Length)
+                {
+                    break;
+                }
+
+                names[count] = new DeviceName(session.Device.GetOutputDeviceName());
+
+                count++;
+            }
+
+            nameCount = count;
+
+            return Result.Success;
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                Os.DestroySystemEvent(ref _audioEvent);
+                Os.DestroySystemEvent(ref _audioInputEvent);
+                Os.DestroySystemEvent(ref _audioOutputEvent);
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(disposing: true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs
new file mode 100644
index 000000000..464ede581
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs
@@ -0,0 +1,171 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Input;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    partial class AudioIn : IAudioIn, IDisposable
+    {
+        private readonly AudioInputSystem _impl;
+        private int _processHandle;
+
+        public AudioIn(AudioInputSystem impl, int processHandle)
+        {
+            _impl = impl;
+            _processHandle = processHandle;
+        }
+
+        [CmifCommand(0)]
+        public Result GetAudioInState(out AudioDeviceState state)
+        {
+            state = _impl.GetState();
+
+            return Result.Success;
+        }
+
+        [CmifCommand(1)]
+        public Result Start()
+        {
+            return new Result((int)_impl.Start());
+        }
+
+        [CmifCommand(2)]
+        public Result Stop()
+        {
+            return new Result((int)_impl.Stop());
+        }
+
+        [CmifCommand(3)]
+        public Result AppendAudioInBuffer(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<AudioUserBuffer> buffer)
+        {
+            AudioUserBuffer userBuffer = default;
+
+            if (buffer.Length > 0)
+            {
+                userBuffer = buffer[0];
+            }
+
+            return new Result((int)_impl.AppendBuffer(bufferTag, ref userBuffer));
+        }
+
+        [CmifCommand(4)]
+        public Result RegisterBufferEvent([CopyHandle] out int eventHandle)
+        {
+            eventHandle = 0;
+
+            if (_impl.RegisterBufferEvent() is AudioEvent audioEvent)
+            {
+                eventHandle = audioEvent.GetReadableHandle();
+            }
+
+            return Result.Success;
+        }
+
+        [CmifCommand(5)]
+        public Result GetReleasedAudioInBuffers(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<ulong> bufferTags)
+        {
+            return new Result((int)_impl.GetReleasedBuffers(bufferTags, out count));
+        }
+
+        [CmifCommand(6)]
+        public Result ContainsAudioInBuffer(out bool contains, ulong bufferTag)
+        {
+            contains = _impl.ContainsBuffer(bufferTag);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(7)] // 3.0.0+
+        public Result AppendUacInBuffer(
+            ulong bufferTag,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<AudioUserBuffer> buffer,
+            [CopyHandle] int eventHandle)
+        {
+            AudioUserBuffer userBuffer = default;
+
+            if (buffer.Length > 0)
+            {
+                userBuffer = buffer[0];
+            }
+
+            return new Result((int)_impl.AppendUacBuffer(bufferTag, ref userBuffer, (uint)eventHandle));
+        }
+
+        [CmifCommand(8)] // 3.0.0+
+        public Result AppendAudioInBufferAuto(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<AudioUserBuffer> buffer)
+        {
+            return AppendAudioInBuffer(bufferTag, buffer);
+        }
+
+        [CmifCommand(9)] // 3.0.0+
+        public Result GetReleasedAudioInBuffersAuto(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<ulong> bufferTags)
+        {
+            return GetReleasedAudioInBuffers(out count, bufferTags);
+        }
+
+        [CmifCommand(10)] // 3.0.0+
+        public Result AppendUacInBufferAuto(
+            ulong bufferTag,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<AudioUserBuffer> buffer,
+            [CopyHandle] int eventHandle)
+        {
+            return AppendUacInBuffer(bufferTag, buffer, eventHandle);
+        }
+
+        [CmifCommand(11)] // 4.0.0+
+        public Result GetAudioInBufferCount(out uint bufferCount)
+        {
+            bufferCount = _impl.GetBufferCount();
+
+            return Result.Success;
+        }
+
+        [CmifCommand(12)] // 4.0.0+
+        public Result SetDeviceGain(float gain)
+        {
+            _impl.SetVolume(gain);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(13)] // 4.0.0+
+        public Result GetDeviceGain(out float gain)
+        {
+            gain = _impl.GetVolume();
+
+            return Result.Success;
+        }
+
+        [CmifCommand(14)] // 6.0.0+
+        public Result FlushAudioInBuffers(out bool pending)
+        {
+            pending = _impl.FlushBuffers();
+
+            return Result.Success;
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                _impl.Dispose();
+
+                if (_processHandle != 0)
+                {
+                    HorizonStatic.Syscall.CloseHandle(_processHandle);
+
+                    _processHandle = 0;
+                }
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(disposing: true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs
new file mode 100644
index 000000000..d5d047201
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs
@@ -0,0 +1,130 @@
+using Ryujinx.Audio;
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Input;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Applet;
+using Ryujinx.Horizon.Sdk.Sf;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    partial class AudioInManager : IAudioInManager
+    {
+        private readonly AudioInputManager _impl;
+
+        public AudioInManager(AudioInputManager impl)
+        {
+            _impl = impl;
+        }
+
+        [CmifCommand(0)]
+        public Result ListAudioIns(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names)
+        {
+            string[] deviceNames = _impl.ListAudioIns(filtered: false);
+
+            count = 0;
+
+            foreach (string deviceName in deviceNames)
+            {
+                if (count >= names.Length)
+                {
+                    break;
+                }
+
+                names[count++] = new DeviceName(deviceName);
+            }
+
+            return Result.Success;
+        }
+
+        [CmifCommand(1)]
+        public Result OpenAudioIn(
+            out AudioOutputConfiguration outputConfiguration,
+            out IAudioIn audioIn,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> outName,
+            AudioInputConfiguration parameter,
+            AppletResourceUserId appletResourceId,
+            [CopyHandle] int processHandle,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name,
+            [ClientProcessId] ulong pid)
+        {
+            var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
+
+            ResultCode rc = _impl.OpenAudioIn(
+                out string outputDeviceName,
+                out outputConfiguration,
+                out AudioInputSystem inSystem,
+                clientMemoryManager,
+                name.Length > 0 ? name[0].ToString() : string.Empty,
+                SampleFormat.PcmInt16,
+                ref parameter);
+
+            if (rc == ResultCode.Success && outName.Length > 0)
+            {
+                outName[0] = new DeviceName(outputDeviceName);
+            }
+
+            audioIn = new AudioIn(inSystem, processHandle);
+
+            return new Result((int)rc);
+        }
+
+        [CmifCommand(2)] // 3.0.0+
+        public Result ListAudioInsAuto(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names)
+        {
+            return ListAudioIns(out count, names);
+        }
+
+        [CmifCommand(3)] // 3.0.0+
+        public Result OpenAudioInAuto(
+            out AudioOutputConfiguration outputConfig,
+            out IAudioIn audioIn,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> outName,
+            AudioInputConfiguration parameter,
+            AppletResourceUserId appletResourceId,
+            [CopyHandle] int processHandle,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name,
+            [ClientProcessId] ulong pid)
+        {
+            return OpenAudioIn(out outputConfig, out audioIn, outName, parameter, appletResourceId, processHandle, name, pid);
+        }
+
+        [CmifCommand(4)] // 3.0.0+
+        public Result ListAudioInsAutoFiltered(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names)
+        {
+            string[] deviceNames = _impl.ListAudioIns(filtered: true);
+
+            count = 0;
+
+            foreach (string deviceName in deviceNames)
+            {
+                if (count >= names.Length)
+                {
+                    break;
+                }
+
+                names[count++] = new DeviceName(deviceName);
+            }
+
+            return Result.Success;
+        }
+
+        [CmifCommand(5)] // 5.0.0+
+        public Result OpenAudioInProtocolSpecified(
+            out AudioOutputConfiguration outputConfig,
+            out IAudioIn audioIn,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> outName,
+            AudioInProtocol protocol,
+            AudioInputConfiguration parameter,
+            AppletResourceUserId appletResourceId,
+            [CopyHandle] int processHandle,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name,
+            [ClientProcessId] ulong pid)
+        {
+            // NOTE: We always assume that only the default device will be plugged (we never report any USB Audio Class type devices).
+
+            return OpenAudioIn(out outputConfig, out audioIn, outName, parameter, appletResourceId, processHandle, name, pid);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs
new file mode 100644
index 000000000..48785f1c0
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs
@@ -0,0 +1,23 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)]
+    struct AudioInProtocol
+    {
+        public AudioInProtocolName Name;
+        public Array7<byte> Padding;
+
+        public AudioInProtocol(AudioInProtocolName name)
+        {
+            Name = name;
+            Padding = new();
+        }
+
+        public override readonly string ToString()
+        {
+            return Name.ToString();
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs
new file mode 100644
index 000000000..68d283cc5
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    enum AudioInProtocolName : byte
+    {
+        DeviceIn = 0,
+        UacIn = 1,
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs
new file mode 100644
index 000000000..7607e2643
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs
@@ -0,0 +1,154 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Output;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    partial class AudioOut : IAudioOut, IDisposable
+    {
+        private readonly AudioOutputSystem _impl;
+        private int _processHandle;
+
+        public AudioOut(AudioOutputSystem impl, int processHandle)
+        {
+            _impl = impl;
+            _processHandle = processHandle;
+        }
+
+        [CmifCommand(0)]
+        public Result GetAudioOutState(out AudioDeviceState state)
+        {
+            state = _impl.GetState();
+
+            return Result.Success;
+        }
+
+        [CmifCommand(1)]
+        public Result Start()
+        {
+            return new Result((int)_impl.Start());
+        }
+
+        [CmifCommand(2)]
+        public Result Stop()
+        {
+            return new Result((int)_impl.Stop());
+        }
+
+        [CmifCommand(3)]
+        public Result AppendAudioOutBuffer(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<AudioUserBuffer> buffer)
+        {
+            AudioUserBuffer userBuffer = default;
+
+            if (buffer.Length > 0)
+            {
+                userBuffer = buffer[0];
+            }
+
+            return new Result((int)_impl.AppendBuffer(bufferTag, ref userBuffer));
+        }
+
+        [CmifCommand(4)]
+        public Result RegisterBufferEvent([CopyHandle] out int eventHandle)
+        {
+            eventHandle = 0;
+
+            if (_impl.RegisterBufferEvent() is AudioEvent audioEvent)
+            {
+                eventHandle = audioEvent.GetReadableHandle();
+            }
+
+            return Result.Success;
+        }
+
+        [CmifCommand(5)]
+        public Result GetReleasedAudioOutBuffers(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<ulong> bufferTags)
+        {
+            return new Result((int)_impl.GetReleasedBuffer(bufferTags, out count));
+        }
+
+        [CmifCommand(6)]
+        public Result ContainsAudioOutBuffer(out bool contains, ulong bufferTag)
+        {
+            contains = _impl.ContainsBuffer(bufferTag);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(7)] // 3.0.0+
+        public Result AppendAudioOutBufferAuto(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<AudioUserBuffer> buffer)
+        {
+            return AppendAudioOutBuffer(bufferTag, buffer);
+        }
+
+        [CmifCommand(8)] // 3.0.0+
+        public Result GetReleasedAudioOutBuffersAuto(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<ulong> bufferTags)
+        {
+            return GetReleasedAudioOutBuffers(out count, bufferTags);
+        }
+
+        [CmifCommand(9)] // 4.0.0+
+        public Result GetAudioOutBufferCount(out uint bufferCount)
+        {
+            bufferCount = _impl.GetBufferCount();
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10)] // 4.0.0+
+        public Result GetAudioOutPlayedSampleCount(out ulong sampleCount)
+        {
+            sampleCount = _impl.GetPlayedSampleCount();
+
+            return Result.Success;
+        }
+
+        [CmifCommand(11)] // 4.0.0+
+        public Result FlushAudioOutBuffers(out bool pending)
+        {
+            pending = _impl.FlushBuffers();
+
+            return Result.Success;
+        }
+
+        [CmifCommand(12)] // 6.0.0+
+        public Result SetAudioOutVolume(float volume)
+        {
+            _impl.SetVolume(volume);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(13)] // 6.0.0+
+        public Result GetAudioOutVolume(out float volume)
+        {
+            volume = _impl.GetVolume();
+
+            return Result.Success;
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                _impl.Dispose();
+
+                if (_processHandle != 0)
+                {
+                    HorizonStatic.Syscall.CloseHandle(_processHandle);
+
+                    _processHandle = 0;
+                }
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(disposing: true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs
new file mode 100644
index 000000000..3d129470c
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs
@@ -0,0 +1,93 @@
+using Ryujinx.Audio;
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Output;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Applet;
+using Ryujinx.Horizon.Sdk.Sf;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    partial class AudioOutManager : IAudioOutManager
+    {
+        private readonly AudioOutputManager _impl;
+
+        public AudioOutManager(AudioOutputManager impl)
+        {
+            _impl = impl;
+        }
+
+        [CmifCommand(0)]
+        public Result ListAudioOuts(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names)
+        {
+            string[] deviceNames = _impl.ListAudioOuts();
+
+            count = 0;
+
+            foreach (string deviceName in deviceNames)
+            {
+                if (count >= names.Length)
+                {
+                    break;
+                }
+
+                names[count++] = new DeviceName(deviceName);
+            }
+
+            return Result.Success;
+        }
+
+        [CmifCommand(1)]
+        public Result OpenAudioOut(
+            out AudioOutputConfiguration outputConfig,
+            out IAudioOut audioOut,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> outName,
+            AudioInputConfiguration parameter,
+            AppletResourceUserId appletResourceId,
+            [CopyHandle] int processHandle,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name,
+            [ClientProcessId] ulong pid)
+        {
+            var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
+
+            ResultCode rc = _impl.OpenAudioOut(
+                out string outputDeviceName,
+                out outputConfig,
+                out AudioOutputSystem outSystem,
+                clientMemoryManager,
+                name.Length > 0 ? name[0].ToString() : string.Empty,
+                SampleFormat.PcmInt16,
+                ref parameter);
+
+            if (rc == ResultCode.Success && outName.Length > 0)
+            {
+                outName[0] = new DeviceName(outputDeviceName);
+            }
+
+            audioOut = new AudioOut(outSystem, processHandle);
+
+            return new Result((int)rc);
+        }
+
+        [CmifCommand(2)] // 3.0.0+
+        public Result ListAudioOutsAuto(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names)
+        {
+            return ListAudioOuts(out count, names);
+        }
+
+        [CmifCommand(3)] // 3.0.0+
+        public Result OpenAudioOutAuto(
+            out AudioOutputConfiguration outputConfig,
+            out IAudioOut audioOut,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> outName,
+            AudioInputConfiguration parameter,
+            AppletResourceUserId appletResourceId,
+            [CopyHandle] int processHandle,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name,
+            [ClientProcessId] ulong pid)
+        {
+            return OpenAudioOut(out outputConfig, out audioOut, outName, parameter, appletResourceId, processHandle, name, pid);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs
new file mode 100644
index 000000000..776df641a
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs
@@ -0,0 +1,187 @@
+using Ryujinx.Audio;
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Renderer.Server;
+using Ryujinx.Common.Memory;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+using System.Buffers;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    partial class AudioRenderer : IAudioRenderer, IDisposable
+    {
+        private readonly AudioRenderSystem _renderSystem;
+        private int _workBufferHandle;
+        private int _processHandle;
+
+        public AudioRenderer(AudioRenderSystem renderSystem, int workBufferHandle, int processHandle)
+        {
+            _renderSystem = renderSystem;
+            _workBufferHandle = workBufferHandle;
+            _processHandle = processHandle;
+        }
+
+        [CmifCommand(0)]
+        public Result GetSampleRate(out int sampleRate)
+        {
+            sampleRate = (int)_renderSystem.GetSampleRate();
+
+            return Result.Success;
+        }
+
+        [CmifCommand(1)]
+        public Result GetSampleCount(out int sampleCount)
+        {
+            sampleCount = (int)_renderSystem.GetSampleCount();
+
+            return Result.Success;
+        }
+
+        [CmifCommand(2)]
+        public Result GetMixBufferCount(out int mixBufferCount)
+        {
+            mixBufferCount = (int)_renderSystem.GetMixBufferCount();
+
+            return Result.Success;
+        }
+
+        [CmifCommand(3)]
+        public Result GetState(out int state)
+        {
+            state = _renderSystem.IsActive() ? 0 : 1;
+
+            return Result.Success;
+        }
+
+        [CmifCommand(4)]
+        public Result RequestUpdate(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> performanceOutput,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
+        {
+            using IMemoryOwner<byte> outputOwner = ByteMemoryPool.Rent(output.Length);
+            using IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.Rent(performanceOutput.Length);
+
+            Memory<byte> outputMemory = outputOwner.Memory;
+            Memory<byte> performanceOutputMemory = performanceOutputOwner.Memory;
+
+            using MemoryHandle outputHandle = outputMemory.Pin();
+            using MemoryHandle performanceOutputHandle = performanceOutputMemory.Pin();
+
+            Result result = new Result((int)_renderSystem.Update(outputMemory, performanceOutputMemory, input.ToArray()));
+
+            outputMemory.Span.CopyTo(output);
+            performanceOutputMemory.Span.CopyTo(performanceOutput);
+
+            return result;
+        }
+
+        [CmifCommand(5)]
+        public Result Start()
+        {
+            _renderSystem.Start();
+
+            return Result.Success;
+        }
+
+        [CmifCommand(6)]
+        public Result Stop()
+        {
+            _renderSystem.Stop();
+
+            return Result.Success;
+        }
+
+        [CmifCommand(7)]
+        public Result QuerySystemEvent([CopyHandle] out int eventHandle)
+        {
+            ResultCode rc = _renderSystem.QuerySystemEvent(out IWritableEvent systemEvent);
+
+            eventHandle = 0;
+
+            if (rc == ResultCode.Success && systemEvent is AudioEvent audioEvent)
+            {
+                eventHandle = audioEvent.GetReadableHandle();
+            }
+
+            return new Result((int)rc);
+        }
+
+        [CmifCommand(8)]
+        public Result SetRenderingTimeLimit(int percent)
+        {
+            _renderSystem.SetRenderingTimeLimitPercent((uint)percent);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(9)]
+        public Result GetRenderingTimeLimit(out int percent)
+        {
+            percent = (int)_renderSystem.GetRenderingTimeLimit();
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10)] // 3.0.0+
+        public Result RequestUpdateAuto(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> output,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> performanceOutput,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<byte> input)
+        {
+            return RequestUpdate(output, performanceOutput, input);
+        }
+
+        [CmifCommand(11)] // 3.0.0+
+        public Result ExecuteAudioRendererRendering()
+        {
+            return new Result((int)_renderSystem.ExecuteAudioRendererRendering());
+        }
+
+        [CmifCommand(12)] // 15.0.0+
+        public Result SetVoiceDropParameter(float voiceDropParameter)
+        {
+            _renderSystem.SetVoiceDropParameter(voiceDropParameter);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(13)] // 15.0.0+
+        public Result GetVoiceDropParameter(out float voiceDropParameter)
+        {
+            voiceDropParameter = _renderSystem.GetVoiceDropParameter();
+
+            return Result.Success;
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                _renderSystem.Dispose();
+
+                if (_workBufferHandle != 0)
+                {
+                    HorizonStatic.Syscall.CloseHandle(_workBufferHandle);
+
+                    _workBufferHandle = 0;
+                }
+
+                if (_processHandle != 0)
+                {
+                    HorizonStatic.Syscall.CloseHandle(_processHandle);
+
+                    _processHandle = 0;
+                }
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(disposing: true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs
new file mode 100644
index 000000000..7138d27ce
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs
@@ -0,0 +1,132 @@
+using Ryujinx.Audio.Renderer.Device;
+using Ryujinx.Audio.Renderer.Server;
+using Ryujinx.Common.Logging;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Applet;
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    partial class AudioRendererManager : IAudioRendererManager
+    {
+        private const uint InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24);
+
+        private readonly Ryujinx.Audio.Renderer.Server.AudioRendererManager _impl;
+        private readonly VirtualDeviceSessionRegistry _registry;
+
+        public AudioRendererManager(Ryujinx.Audio.Renderer.Server.AudioRendererManager impl, VirtualDeviceSessionRegistry registry)
+        {
+            _impl = impl;
+            _registry = registry;
+        }
+
+        [CmifCommand(0)]
+        public Result OpenAudioRenderer(
+            out IAudioRenderer renderer,
+            AudioRendererParameterInternal parameter,
+            [CopyHandle] int workBufferHandle,
+            [CopyHandle] int processHandle,
+            ulong workBufferSize,
+            AppletResourceUserId appletResourceId,
+            [ClientProcessId] ulong pid)
+        {
+            var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
+            ulong workBufferAddress = HorizonStatic.Syscall.GetTransferMemoryAddress(workBufferHandle);
+
+            Result result = new Result((int)_impl.OpenAudioRenderer(
+                out var renderSystem,
+                clientMemoryManager,
+                ref parameter.Configuration,
+                appletResourceId.Id,
+                workBufferAddress,
+                workBufferSize,
+                (uint)processHandle));
+
+            if (result.IsSuccess)
+            {
+                renderer = new AudioRenderer(renderSystem, workBufferHandle, processHandle);
+            }
+            else
+            {
+                renderer = null;
+
+                HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+                HorizonStatic.Syscall.CloseHandle(processHandle);
+            }
+
+            return result;
+        }
+
+        [CmifCommand(1)]
+        public Result GetWorkBufferSize(out long workBufferSize, AudioRendererParameterInternal parameter)
+        {
+            if (BehaviourContext.CheckValidRevision(parameter.Configuration.Revision))
+            {
+                workBufferSize = (long)Ryujinx.Audio.Renderer.Server.AudioRendererManager.GetWorkBufferSize(ref parameter.Configuration);
+
+                Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{workBufferSize:x16}.");
+
+                return Result.Success;
+            }
+            else
+            {
+                workBufferSize = 0;
+
+                Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Configuration.Revision)} is not supported!");
+
+                return AudioResult.UnsupportedRevision;
+            }
+        }
+
+        [CmifCommand(2)]
+        public Result GetAudioDeviceService(out IAudioDevice audioDevice, AppletResourceUserId appletResourceId)
+        {
+            audioDevice = new AudioDevice(_registry, appletResourceId, InitialRevision);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(3)] // 3.0.0+
+        public Result OpenAudioRendererForManualExecution(
+            out IAudioRenderer renderer,
+            AudioRendererParameterInternal parameter,
+            ulong workBufferAddress,
+            [CopyHandle] int processHandle,
+            ulong workBufferSize,
+            AppletResourceUserId appletResourceId,
+            [ClientProcessId] ulong pid)
+        {
+            var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
+
+            Result result = new Result((int)_impl.OpenAudioRenderer(
+                out var renderSystem,
+                clientMemoryManager,
+                ref parameter.Configuration,
+                appletResourceId.Id,
+                workBufferAddress,
+                workBufferSize,
+                (uint)processHandle));
+
+            if (result.IsSuccess)
+            {
+                renderer = new AudioRenderer(renderSystem, 0, processHandle);
+            }
+            else
+            {
+                renderer = null;
+
+                HorizonStatic.Syscall.CloseHandle(processHandle);
+            }
+
+            return result;
+        }
+
+        [CmifCommand(4)] // 4.0.0+
+        public Result GetAudioDeviceServiceWithRevisionInfo(out IAudioDevice audioDevice, AppletResourceUserId appletResourceId, uint revision)
+        {
+            audioDevice = new AudioDevice(_registry, appletResourceId, revision);
+
+            return Result.Success;
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs
new file mode 100644
index 000000000..e5fcf7b3b
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Audio.Renderer.Parameter;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    struct AudioRendererParameterInternal
+    {
+        public AudioRendererConfiguration Configuration;
+
+        public AudioRendererParameterInternal(AudioRendererConfiguration configuration)
+        {
+            Configuration = configuration;
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs
new file mode 100644
index 000000000..cf1fe3d1d
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs
@@ -0,0 +1,30 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    partial class AudioSnoopManager : IAudioSnoopManager
+    {
+        // Note: The interface changed completely on firmware 17.0.0, this implementation is for older firmware.
+
+        [CmifCommand(0)]
+        public Result EnableDspUsageMeasurement()
+        {
+            return Result.Success;
+        }
+
+        [CmifCommand(1)]
+        public Result DisableDspUsageMeasurement()
+        {
+            return Result.Success;
+        }
+
+        [CmifCommand(6)]
+        public Result GetDspUsage(out uint usage)
+        {
+            usage = 0;
+
+            return Result.Success;
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs
new file mode 100644
index 000000000..b77e2f402
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs
@@ -0,0 +1,30 @@
+using Ryujinx.Common.Memory;
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x100, Pack = 1)]
+    struct DeviceName
+    {
+        public Array256<byte> Name;
+
+        public DeviceName(string name)
+        {
+            Name = new();
+            Encoding.ASCII.GetBytes(name, Name.AsSpan());
+        }
+
+        public override string ToString()
+        {
+            int length = Name.AsSpan().IndexOf((byte)0);
+            if (length < 0)
+            {
+                length = 0x100;
+            }
+
+            return Encoding.ASCII.GetString(Name.AsSpan()[..length]);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs
new file mode 100644
index 000000000..393914371
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs
@@ -0,0 +1,147 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.OsTypes;
+using Ryujinx.Horizon.Sdk.Sf;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    partial class FinalOutputRecorder : IFinalOutputRecorder, IDisposable
+    {
+        private int _processHandle;
+        private SystemEventType _event;
+
+        public FinalOutputRecorder(int processHandle)
+        {
+            _processHandle = processHandle;
+            Os.CreateSystemEvent(out _event, EventClearMode.ManualClear, interProcess: true);
+        }
+
+        [CmifCommand(0)]
+        public Result GetFinalOutputRecorderState(out uint state)
+        {
+            state = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(1)]
+        public Result Start()
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(2)]
+        public Result Stop()
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(3)]
+        public Result AppendFinalOutputRecorderBuffer([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> buffer, ulong bufferClientPtr)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferClientPtr });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(4)]
+        public Result RegisterBufferEvent([CopyHandle] out int eventHandle)
+        {
+            eventHandle = Os.GetReadableHandleOfSystemEvent(ref _event);
+
+            Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(5)]
+        public Result GetReleasedFinalOutputRecorderBuffers([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> buffer, out uint count, out ulong released)
+        {
+            count = 0;
+            released = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(6)]
+        public Result ContainsFinalOutputRecorderBuffer(ulong bufferPointer, out bool contains)
+        {
+            contains = false;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferPointer });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(7)]
+        public Result GetFinalOutputRecorderBufferEndTime(ulong bufferPointer, out ulong released)
+        {
+            released = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferPointer });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(8)] // 3.0.0+
+        public Result AppendFinalOutputRecorderBufferAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<byte> buffer, ulong bufferClientPtr)
+        {
+            return AppendFinalOutputRecorderBuffer(buffer, bufferClientPtr);
+        }
+
+        [CmifCommand(9)] // 3.0.0+
+        public Result GetReleasedFinalOutputRecorderBuffersAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> buffer, out uint count, out ulong released)
+        {
+            return GetReleasedFinalOutputRecorderBuffers(buffer, out count, out released);
+        }
+
+        [CmifCommand(10)] // 6.0.0+
+        public Result FlushFinalOutputRecorderBuffers(out bool pending)
+        {
+            pending = false;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(11)] // 9.0.0+
+        public Result AttachWorkBuffer(FinalOutputRecorderParameterInternal parameter)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { parameter });
+
+            return Result.Success;
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                Os.DestroySystemEvent(ref _event);
+
+                if (_processHandle != 0)
+                {
+                    HorizonStatic.Syscall.CloseHandle(_processHandle);
+
+                    _processHandle = 0;
+                }
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(disposing: true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs
new file mode 100644
index 000000000..76491bb79
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs
@@ -0,0 +1,23 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Applet;
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    partial class FinalOutputRecorderManager : IFinalOutputRecorderManager
+    {
+        [CmifCommand(0)]
+        public Result OpenFinalOutputRecorder(
+            out IFinalOutputRecorder recorder,
+            FinalOutputRecorderParameter parameter,
+            [CopyHandle] int processHandle,
+            out FinalOutputRecorderParameterInternal outParameter,
+            AppletResourceUserId appletResourceId)
+        {
+            recorder = new FinalOutputRecorder(processHandle);
+            outParameter = new(parameter.SampleRate, 2, 0);
+
+            return Result.Success;
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs
new file mode 100644
index 000000000..afa060fca
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs
@@ -0,0 +1,17 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)]
+    readonly struct FinalOutputRecorderParameter
+    {
+        public readonly uint SampleRate;
+        public readonly uint Padding;
+
+        public FinalOutputRecorderParameter(uint sampleRate)
+        {
+            SampleRate = sampleRate;
+            Padding = 0;
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs
new file mode 100644
index 000000000..e88398eba
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs
@@ -0,0 +1,21 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)]
+    readonly struct FinalOutputRecorderParameterInternal
+    {
+        public readonly uint SampleRate;
+        public readonly uint ChannelCount;
+        public readonly uint UseLargeFrameSize;
+        public readonly uint Padding;
+
+        public FinalOutputRecorderParameterInternal(uint sampleRate, uint channelCount, uint useLargeFrameSize)
+        {
+            SampleRate = sampleRate;
+            ChannelCount = channelCount;
+            UseLargeFrameSize = useLargeFrameSize;
+            Padding = 0;
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs
new file mode 100644
index 000000000..3df1fe227
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs
@@ -0,0 +1,24 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    interface IAudioDevice : IServiceObject
+    {
+        Result ListAudioDeviceName(Span<DeviceName> names, out int nameCount);
+        Result SetAudioDeviceOutputVolume(ReadOnlySpan<DeviceName> name, float volume);
+        Result GetAudioDeviceOutputVolume(ReadOnlySpan<DeviceName> name, out float volume);
+        Result GetActiveAudioDeviceName(Span<DeviceName> name);
+        Result QueryAudioDeviceSystemEvent(out int eventHandle);
+        Result GetActiveChannelCount(out int channelCount);
+        Result ListAudioDeviceNameAuto(Span<DeviceName> names, out int nameCount);
+        Result SetAudioDeviceOutputVolumeAuto(ReadOnlySpan<DeviceName> name, float volume);
+        Result GetAudioDeviceOutputVolumeAuto(ReadOnlySpan<DeviceName> name, out float volume);
+        Result GetActiveAudioDeviceNameAuto(Span<DeviceName> name);
+        Result QueryAudioDeviceInputEvent(out int eventHandle);
+        Result QueryAudioDeviceOutputEvent(out int eventHandle);
+        Result GetActiveAudioOutputDeviceName(Span<DeviceName> name);
+        Result ListAudioOutputDeviceName(Span<DeviceName> names, out int nameCount);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs
new file mode 100644
index 000000000..bdc3bcf62
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs
@@ -0,0 +1,26 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    interface IAudioIn : IServiceObject
+    {
+        Result GetAudioInState(out AudioDeviceState state);
+        Result Start();
+        Result Stop();
+        Result AppendAudioInBuffer(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer);
+        Result RegisterBufferEvent(out int eventHandle);
+        Result GetReleasedAudioInBuffers(out uint count, Span<ulong> bufferTags);
+        Result ContainsAudioInBuffer(out bool contains, ulong bufferTag);
+        Result AppendUacInBuffer(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer, int eventHandle);
+        Result AppendAudioInBufferAuto(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer);
+        Result GetReleasedAudioInBuffersAuto(out uint count, Span<ulong> bufferTags);
+        Result AppendUacInBufferAuto(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer, int eventHandle);
+        Result GetAudioInBufferCount(out uint bufferCount);
+        Result SetDeviceGain(float gain);
+        Result GetDeviceGain(out float gain);
+        Result FlushAudioInBuffers(out bool pending);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs
new file mode 100644
index 000000000..e7f32fbd2
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs
@@ -0,0 +1,43 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Applet;
+using Ryujinx.Horizon.Sdk.Sf;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    interface IAudioInManager : IServiceObject
+    {
+        Result ListAudioIns(out int count, Span<DeviceName> names);
+        Result OpenAudioIn(
+            out AudioOutputConfiguration outputConfig,
+            out IAudioIn audioIn,
+            Span<DeviceName> outName,
+            AudioInputConfiguration parameter,
+            AppletResourceUserId appletResourceId,
+            int processHandle,
+            ReadOnlySpan<DeviceName> name,
+            ulong pid);
+        Result ListAudioInsAuto(out int count, Span<DeviceName> names);
+        Result OpenAudioInAuto(
+            out AudioOutputConfiguration outputConfig,
+            out IAudioIn audioIn,
+            Span<DeviceName> outName,
+            AudioInputConfiguration parameter,
+            AppletResourceUserId appletResourceId,
+            int processHandle,
+            ReadOnlySpan<DeviceName> name,
+            ulong pid);
+        Result ListAudioInsAutoFiltered(out int count, Span<DeviceName> names);
+        Result OpenAudioInProtocolSpecified(
+            out AudioOutputConfiguration outputConfig,
+            out IAudioIn audioIn,
+            Span<DeviceName> outName,
+            AudioInProtocol protocol,
+            AudioInputConfiguration parameter,
+            AppletResourceUserId appletResourceId,
+            int processHandle,
+            ReadOnlySpan<DeviceName> name,
+            ulong pid);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs
new file mode 100644
index 000000000..1b2009260
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs
@@ -0,0 +1,25 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    interface IAudioOut : IServiceObject
+    {
+        Result GetAudioOutState(out AudioDeviceState state);
+        Result Start();
+        Result Stop();
+        Result AppendAudioOutBuffer(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer);
+        Result RegisterBufferEvent(out int eventHandle);
+        Result GetReleasedAudioOutBuffers(out uint count, Span<ulong> bufferTags);
+        Result ContainsAudioOutBuffer(out bool contains, ulong bufferTag);
+        Result AppendAudioOutBufferAuto(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer);
+        Result GetReleasedAudioOutBuffersAuto(out uint count, Span<ulong> bufferTags);
+        Result GetAudioOutBufferCount(out uint bufferCount);
+        Result GetAudioOutPlayedSampleCount(out ulong sampleCount);
+        Result FlushAudioOutBuffers(out bool pending);
+        Result SetAudioOutVolume(float volume);
+        Result GetAudioOutVolume(out float volume);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs
new file mode 100644
index 000000000..40d62836b
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs
@@ -0,0 +1,32 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Applet;
+using Ryujinx.Horizon.Sdk.Sf;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    interface IAudioOutManager : IServiceObject
+    {
+        Result ListAudioOuts(out int count, Span<DeviceName> names);
+        Result OpenAudioOut(
+            out AudioOutputConfiguration outputConfig,
+            out IAudioOut audioOut,
+            Span<DeviceName> outName,
+            AudioInputConfiguration inputConfig,
+            AppletResourceUserId appletResourceId,
+            int processHandle,
+            ReadOnlySpan<DeviceName> name,
+            ulong pid);
+        Result ListAudioOutsAuto(out int count, Span<DeviceName> names);
+        Result OpenAudioOutAuto(
+            out AudioOutputConfiguration outputConfig,
+            out IAudioOut audioOut,
+            Span<DeviceName> outName,
+            AudioInputConfiguration inputConfig,
+            AppletResourceUserId appletResourceId,
+            int processHandle,
+            ReadOnlySpan<DeviceName> name,
+            ulong pid);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs
new file mode 100644
index 000000000..e4ca2e8ec
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs
@@ -0,0 +1,24 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    interface IAudioRenderer : IServiceObject
+    {
+        Result GetSampleRate(out int sampleRate);
+        Result GetSampleCount(out int sampleCount);
+        Result GetMixBufferCount(out int mixBufferCount);
+        Result GetState(out int state);
+        Result RequestUpdate(Span<byte> output, Span<byte> performanceOutput, ReadOnlySpan<byte> input);
+        Result Start();
+        Result Stop();
+        Result QuerySystemEvent(out int eventHandle);
+        Result SetRenderingTimeLimit(int percent);
+        Result GetRenderingTimeLimit(out int percent);
+        Result RequestUpdateAuto(Span<byte> output, Span<byte> performanceOutput, ReadOnlySpan<byte> input);
+        Result ExecuteAudioRendererRendering();
+        Result SetVoiceDropParameter(float voiceDropParameter);
+        Result GetVoiceDropParameter(out float voiceDropParameter);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs
new file mode 100644
index 000000000..fe95a2084
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs
@@ -0,0 +1,29 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Applet;
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    interface IAudioRendererManager : IServiceObject
+    {
+        Result OpenAudioRenderer(
+            out IAudioRenderer renderer,
+            AudioRendererParameterInternal parameter,
+            int processHandle,
+            int workBufferHandle,
+            ulong workBufferSize,
+            AppletResourceUserId appletUserId,
+            ulong pid);
+        Result GetWorkBufferSize(out long workBufferSize, AudioRendererParameterInternal parameter);
+        Result GetAudioDeviceService(out IAudioDevice audioDevice, AppletResourceUserId appletUserId);
+        Result OpenAudioRendererForManualExecution(
+            out IAudioRenderer renderer,
+            AudioRendererParameterInternal parameter,
+            ulong workBufferAddress,
+            int processHandle,
+            ulong workBufferSize,
+            AppletResourceUserId appletUserId,
+            ulong pid);
+        Result GetAudioDeviceServiceWithRevisionInfo(out IAudioDevice audioDevice, AppletResourceUserId appletUserId, uint revision);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs
new file mode 100644
index 000000000..72853886a
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs
@@ -0,0 +1,12 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    interface IAudioSnoopManager : IServiceObject
+    {
+        Result EnableDspUsageMeasurement();
+        Result DisableDspUsageMeasurement();
+        Result GetDspUsage(out uint usage);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs
new file mode 100644
index 000000000..be21c38b7
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs
@@ -0,0 +1,22 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    interface IFinalOutputRecorder : IServiceObject
+    {
+        Result GetFinalOutputRecorderState(out uint state);
+        Result Start();
+        Result Stop();
+        Result AppendFinalOutputRecorderBuffer(ReadOnlySpan<byte> buffer, ulong bufferClientPtr);
+        Result RegisterBufferEvent(out int eventHandle);
+        Result GetReleasedFinalOutputRecorderBuffers(Span<byte> buffer, out uint count, out ulong released);
+        Result ContainsFinalOutputRecorderBuffer(ulong bufferPointer, out bool contains);
+        Result GetFinalOutputRecorderBufferEndTime(ulong bufferPointer, out ulong released);
+        Result AppendFinalOutputRecorderBufferAuto(ReadOnlySpan<byte> buffer, ulong bufferClientPtr);
+        Result GetReleasedFinalOutputRecorderBuffersAuto(Span<byte> buffer, out uint count, out ulong released);
+        Result FlushFinalOutputRecorderBuffers(out bool pending);
+        Result AttachWorkBuffer(FinalOutputRecorderParameterInternal parameter);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs
new file mode 100644
index 000000000..bac41ca91
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Applet;
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Sdk.Audio.Detail
+{
+    interface IFinalOutputRecorderManager : IServiceObject
+    {
+        Result OpenFinalOutputRecorder(
+            out IFinalOutputRecorder recorder,
+            FinalOutputRecorderParameter parameter,
+            int processHandle,
+            out FinalOutputRecorderParameterInternal outParameter,
+            AppletResourceUserId appletResourceId);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs b/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs
new file mode 100644
index 000000000..21508b7f1
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.Horizon.Sdk.Codec
+{
+    static class CodecResult
+    {
+        private const int ModuleId = 111;
+
+        public static Result InvalidLength => new(ModuleId, 3);
+        public static Result OpusBadArg => new(ModuleId, 130);
+        public static Result OpusInvalidPacket => new(ModuleId, 133);
+        public static Result InvalidNumberOfStreams => new(ModuleId, 1000);
+        public static Result InvalidSampleRate => new(ModuleId, 1001);
+        public static Result InvalidChannelCount => new(ModuleId, 1002);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs
new file mode 100644
index 000000000..5d2798582
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs
@@ -0,0 +1,336 @@
+using Concentus;
+using Concentus.Enums;
+using Concentus.Structs;
+using Ryujinx.Common.Logging;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+    partial class HardwareOpusDecoder : IHardwareOpusDecoder, IDisposable
+    {
+        [StructLayout(LayoutKind.Sequential)]
+        private struct OpusPacketHeader
+        {
+            public uint Length;
+            public uint FinalRange;
+
+            public static OpusPacketHeader FromSpan(ReadOnlySpan<byte> data)
+            {
+                return new()
+                {
+                    Length = BinaryPrimitives.ReadUInt32BigEndian(data),
+                    FinalRange = BinaryPrimitives.ReadUInt32BigEndian(data[sizeof(uint)..]),
+                };
+            }
+        }
+
+        private interface IDecoder
+        {
+            int SampleRate { get; }
+            int ChannelsCount { get; }
+
+            int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize);
+            void ResetState();
+        }
+
+        private class Decoder : IDecoder
+        {
+            private readonly OpusDecoder _decoder;
+
+            public int SampleRate => _decoder.SampleRate;
+            public int ChannelsCount => _decoder.NumChannels;
+
+            public Decoder(int sampleRate, int channelsCount)
+            {
+                _decoder = new OpusDecoder(sampleRate, channelsCount);
+            }
+
+            public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
+            {
+                return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize);
+            }
+
+            public void ResetState()
+            {
+                _decoder.ResetState();
+            }
+        }
+
+        private class MultiSampleDecoder : IDecoder
+        {
+            private readonly OpusMSDecoder _decoder;
+
+            public int SampleRate => _decoder.SampleRate;
+            public int ChannelsCount { get; }
+
+            public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
+            {
+                ChannelsCount = channelsCount;
+                _decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
+            }
+
+            public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
+            {
+                return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0);
+            }
+
+            public void ResetState()
+            {
+                _decoder.ResetState();
+            }
+        }
+
+        private readonly IDecoder _decoder;
+        private int _workBufferHandle;
+
+        private HardwareOpusDecoder(int workBufferHandle)
+        {
+            _workBufferHandle = workBufferHandle;
+        }
+
+        public HardwareOpusDecoder(int sampleRate, int channelsCount, int workBufferHandle) : this(workBufferHandle)
+        {
+            _decoder = new Decoder(sampleRate, channelsCount);
+        }
+
+        public HardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping, int workBufferHandle) : this(workBufferHandle)
+        {
+            _decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
+        }
+
+        [CmifCommand(0)]
+        public Result DecodeInterleavedOld(
+            out int outConsumed,
+            out int outSamples,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
+        {
+            return DecodeInterleavedInternal(out outConsumed, out outSamples, out _, output, input, reset: false, withPerf: false);
+        }
+
+        [CmifCommand(1)]
+        public Result SetContext(ReadOnlySpan<byte> context)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(2)] // 3.0.0+
+        public Result DecodeInterleavedForMultiStreamOld(
+            out int outConsumed,
+            out int outSamples,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
+        {
+            return DecodeInterleavedInternal(out outConsumed, out outSamples, out _, output, input, reset: false, withPerf: false);
+        }
+
+        [CmifCommand(3)] // 3.0.0+
+        public Result SetContextForMultiStream(ReadOnlySpan<byte> arg0)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(4)] // 4.0.0+
+        public Result DecodeInterleavedWithPerfOld(
+            out int outConsumed,
+            out long timeTaken,
+            out int outSamples,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
+        {
+            return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset: false, withPerf: true);
+        }
+
+        [CmifCommand(5)] // 4.0.0+
+        public Result DecodeInterleavedForMultiStreamWithPerfOld(
+            out int outConsumed,
+            out long timeTaken,
+            out int outSamples,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
+        {
+            return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset: false, withPerf: true);
+        }
+
+        [CmifCommand(6)] // 6.0.0+
+        public Result DecodeInterleavedWithPerfAndResetOld(
+            out int outConsumed,
+            out long timeTaken,
+            out int outSamples,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input,
+            bool reset)
+        {
+            return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true);
+        }
+
+        [CmifCommand(7)] // 6.0.0+
+        public Result DecodeInterleavedForMultiStreamWithPerfAndResetOld(
+            out int outConsumed,
+            out long timeTaken,
+            out int outSamples,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input,
+            bool reset)
+        {
+            return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true);
+        }
+
+        [CmifCommand(8)] // 7.0.0+
+        public Result DecodeInterleaved(
+            out int outConsumed,
+            out long timeTaken,
+            out int outSamples,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] ReadOnlySpan<byte> input,
+            bool reset)
+        {
+            return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true);
+        }
+
+        [CmifCommand(9)] // 7.0.0+
+        public Result DecodeInterleavedForMultiStream(
+            out int outConsumed,
+            out long timeTaken,
+            out int outSamples,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] ReadOnlySpan<byte> input,
+            bool reset)
+        {
+            return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true);
+        }
+
+        private Result DecodeInterleavedInternal(
+            out int outConsumed,
+            out int outSamples,
+            out long timeTaken,
+            Span<byte> output,
+            ReadOnlySpan<byte> input,
+            bool reset,
+            bool withPerf)
+        {
+            timeTaken = 0;
+
+            Result result = DecodeInterleaved(_decoder, reset, input, out short[] outPcmData, output.Length, out outConsumed, out outSamples);
+
+            if (withPerf)
+            {
+                // This is the time the DSP took to process the request, TODO: fill this.
+                timeTaken = 0;
+            }
+
+            MemoryMarshal.Cast<short, byte>(outPcmData).CopyTo(output[..(outPcmData.Length * sizeof(short))]);
+
+            return result;
+        }
+
+        private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, byte[] packet)
+        {
+            int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate);
+
+            numSamples = result;
+
+            if (result == OpusError.OPUS_INVALID_PACKET)
+            {
+                return CodecResult.OpusInvalidPacket;
+            }
+            else if (result == OpusError.OPUS_BAD_ARG)
+            {
+                return CodecResult.OpusBadArg;
+            }
+
+            return Result.Success;
+        }
+
+        private static Result DecodeInterleaved(
+            IDecoder decoder,
+            bool reset,
+            ReadOnlySpan<byte> input,
+            out short[] outPcmData,
+            int outputSize,
+            out int outConsumed,
+            out int outSamples)
+        {
+            outPcmData = null;
+            outConsumed = 0;
+            outSamples = 0;
+
+            int streamSize = input.Length;
+
+            if (streamSize < Unsafe.SizeOf<OpusPacketHeader>())
+            {
+                return CodecResult.InvalidLength;
+            }
+
+            OpusPacketHeader header = OpusPacketHeader.FromSpan(input);
+            int headerSize = Unsafe.SizeOf<OpusPacketHeader>();
+            uint totalSize = header.Length + (uint)headerSize;
+
+            if (totalSize > streamSize)
+            {
+                return CodecResult.InvalidLength;
+            }
+
+            byte[] opusData = input.Slice(headerSize, (int)header.Length).ToArray();
+
+            Result result = GetPacketNumSamples(decoder, out int numSamples, opusData);
+
+            if (result.IsSuccess)
+            {
+                if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize)
+                {
+                    return CodecResult.InvalidLength;
+                }
+
+                outPcmData = new short[numSamples * decoder.ChannelsCount];
+
+                if (reset)
+                {
+                    decoder.ResetState();
+                }
+
+                try
+                {
+                    outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount);
+                    outConsumed = (int)totalSize;
+                }
+                catch (OpusException)
+                {
+                    // TODO: As OpusException doesn't return the exact error code, this is inaccurate in some cases...
+                    return CodecResult.InvalidLength;
+                }
+            }
+
+            return Result.Success;
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                if (_workBufferHandle != 0)
+                {
+                    HorizonStatic.Syscall.CloseHandle(_workBufferHandle);
+
+                    _workBufferHandle = 0;
+                }
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(disposing: true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs
new file mode 100644
index 000000000..acec66e82
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs
@@ -0,0 +1,386 @@
+using Ryujinx.Common;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+    partial class HardwareOpusDecoderManager : IHardwareOpusDecoderManager
+    {
+        [CmifCommand(0)]
+        public Result OpenHardwareOpusDecoder(
+            out IHardwareOpusDecoder decoder,
+            HardwareOpusDecoderParameterInternal parameter,
+            [CopyHandle] int workBufferHandle,
+            int workBufferSize)
+        {
+            decoder = null;
+
+            if (!IsValidSampleRate(parameter.SampleRate))
+            {
+                HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+                return CodecResult.InvalidSampleRate;
+            }
+
+            if (!IsValidChannelCount(parameter.ChannelsCount))
+            {
+                HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+                return CodecResult.InvalidChannelCount;
+            }
+
+            decoder = new HardwareOpusDecoder(parameter.SampleRate, parameter.ChannelsCount, workBufferHandle);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(1)]
+        public Result GetWorkBufferSize(out int size, HardwareOpusDecoderParameterInternal parameter)
+        {
+            size = 0;
+
+            if (!IsValidChannelCount(parameter.ChannelsCount))
+            {
+                return CodecResult.InvalidChannelCount;
+            }
+
+            if (!IsValidSampleRate(parameter.SampleRate))
+            {
+                return CodecResult.InvalidSampleRate;
+            }
+
+            int opusDecoderSize = GetOpusDecoderSize(parameter.ChannelsCount);
+
+            int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0;
+            int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * 1920 / sampleRateRatio : 0, 64);
+            size = opusDecoderSize + 1536 + frameSize;
+
+            return Result.Success;
+        }
+
+        [CmifCommand(2)] // 3.0.0+
+        public Result OpenHardwareOpusDecoderForMultiStream(
+            out IHardwareOpusDecoder decoder,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x110)] in HardwareOpusMultiStreamDecoderParameterInternal parameter,
+            [CopyHandle] int workBufferHandle,
+            int workBufferSize)
+        {
+            decoder = null;
+
+            if (!IsValidSampleRate(parameter.SampleRate))
+            {
+                HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+                return CodecResult.InvalidSampleRate;
+            }
+
+            if (!IsValidMultiChannelCount(parameter.ChannelsCount))
+            {
+                HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+                return CodecResult.InvalidChannelCount;
+            }
+
+            if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount))
+            {
+                HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+                return CodecResult.InvalidNumberOfStreams;
+            }
+
+            decoder = new HardwareOpusDecoder(
+                parameter.SampleRate,
+                parameter.ChannelsCount,
+                parameter.NumberOfStreams,
+                parameter.NumberOfStereoStreams,
+                parameter.ChannelMappings.AsSpan().ToArray(),
+                workBufferHandle);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(3)] // 3.0.0+
+        public Result GetWorkBufferSizeForMultiStream(
+            out int size,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x110)] in HardwareOpusMultiStreamDecoderParameterInternal parameter)
+        {
+            size = 0;
+
+            if (!IsValidMultiChannelCount(parameter.ChannelsCount))
+            {
+                return CodecResult.InvalidChannelCount;
+            }
+
+            if (!IsValidSampleRate(parameter.SampleRate))
+            {
+                return CodecResult.InvalidSampleRate;
+            }
+
+            if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount))
+            {
+                return CodecResult.InvalidSampleRate;
+            }
+
+            int opusDecoderSize = GetOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams);
+
+            int streamSize = BitUtils.AlignUp(parameter.NumberOfStreams * 1500, 64);
+            int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0;
+            int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * 1920 / sampleRateRatio : 0, 64);
+            size = opusDecoderSize + streamSize + frameSize;
+
+            return Result.Success;
+        }
+
+        [CmifCommand(4)] // 12.0.0+
+        public Result OpenHardwareOpusDecoderEx(
+            out IHardwareOpusDecoder decoder,
+            HardwareOpusDecoderParameterInternalEx parameter,
+            [CopyHandle] int workBufferHandle,
+            int workBufferSize)
+        {
+            decoder = null;
+
+            if (!IsValidChannelCount(parameter.ChannelsCount))
+            {
+                HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+                return CodecResult.InvalidChannelCount;
+            }
+
+            if (!IsValidSampleRate(parameter.SampleRate))
+            {
+                HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+                return CodecResult.InvalidSampleRate;
+            }
+
+            decoder = new HardwareOpusDecoder(parameter.SampleRate, parameter.ChannelsCount, workBufferHandle);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(5)] // 12.0.0+
+        public Result GetWorkBufferSizeEx(out int size, HardwareOpusDecoderParameterInternalEx parameter)
+        {
+            return GetWorkBufferSizeExImpl(out size, in parameter, fromDsp: false);
+        }
+
+        [CmifCommand(6)] // 12.0.0+
+        public Result OpenHardwareOpusDecoderForMultiStreamEx(
+            out IHardwareOpusDecoder decoder,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter,
+            [CopyHandle] int workBufferHandle,
+            int workBufferSize)
+        {
+            decoder = null;
+
+            if (!IsValidSampleRate(parameter.SampleRate))
+            {
+                HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+                return CodecResult.InvalidSampleRate;
+            }
+
+            if (!IsValidMultiChannelCount(parameter.ChannelsCount))
+            {
+                HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+                return CodecResult.InvalidChannelCount;
+            }
+
+            if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount))
+            {
+                HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+                return CodecResult.InvalidNumberOfStreams;
+            }
+
+            decoder = new HardwareOpusDecoder(
+                parameter.SampleRate,
+                parameter.ChannelsCount,
+                parameter.NumberOfStreams,
+                parameter.NumberOfStereoStreams,
+                parameter.ChannelMappings.AsSpan().ToArray(),
+                workBufferHandle);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(7)] // 12.0.0+
+        public Result GetWorkBufferSizeForMultiStreamEx(
+            out int size,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter)
+        {
+            return GetWorkBufferSizeForMultiStreamExImpl(out size, in parameter, fromDsp: false);
+        }
+
+        [CmifCommand(8)] // 16.0.0+
+        public Result GetWorkBufferSizeExEx(out int size, HardwareOpusDecoderParameterInternalEx parameter)
+        {
+            return GetWorkBufferSizeExImpl(out size, in parameter, fromDsp: true);
+        }
+
+        [CmifCommand(9)] // 16.0.0+
+        public Result GetWorkBufferSizeForMultiStreamExEx(
+            out int size,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter)
+        {
+            return GetWorkBufferSizeForMultiStreamExImpl(out size, in parameter, fromDsp: true);
+        }
+
+        private Result GetWorkBufferSizeExImpl(out int size, in HardwareOpusDecoderParameterInternalEx parameter, bool fromDsp)
+        {
+            size = 0;
+
+            if (!IsValidChannelCount(parameter.ChannelsCount))
+            {
+                return CodecResult.InvalidChannelCount;
+            }
+
+            if (!IsValidSampleRate(parameter.SampleRate))
+            {
+                return CodecResult.InvalidSampleRate;
+            }
+
+            int opusDecoderSize = fromDsp ? GetDspOpusDecoderSize(parameter.ChannelsCount) : GetOpusDecoderSize(parameter.ChannelsCount);
+
+            int frameSizeMono48KHz = parameter.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
+            int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0;
+            int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * frameSizeMono48KHz / sampleRateRatio : 0, 64);
+            size = opusDecoderSize + 1536 + frameSize;
+
+            return Result.Success;
+        }
+
+        private Result GetWorkBufferSizeForMultiStreamExImpl(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, bool fromDsp)
+        {
+            size = 0;
+
+            if (!IsValidMultiChannelCount(parameter.ChannelsCount))
+            {
+                return CodecResult.InvalidChannelCount;
+            }
+
+            if (!IsValidSampleRate(parameter.SampleRate))
+            {
+                return CodecResult.InvalidSampleRate;
+            }
+
+            if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount))
+            {
+                return CodecResult.InvalidSampleRate;
+            }
+
+            int opusDecoderSize = fromDsp
+                ? GetDspOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams)
+                : GetOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams);
+
+            int frameSizeMono48KHz = parameter.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
+            int streamSize = BitUtils.AlignUp(parameter.NumberOfStreams * 1500, 64);
+            int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0;
+            int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * frameSizeMono48KHz / sampleRateRatio : 0, 64);
+            size = opusDecoderSize + streamSize + frameSize;
+
+            return Result.Success;
+        }
+
+        private static int GetDspOpusDecoderSize(int channelsCount)
+        {
+            // TODO: Figure out the size returned here.
+            // Not really important because we don't use the work buffer, and the size being lower is fine.
+
+            return 0;
+        }
+
+        private static int GetDspOpusMultistreamDecoderSize(int streams, int coupledStreams)
+        {
+            // TODO: Figure out the size returned here.
+            // Not really important because we don't use the work buffer, and the size being lower is fine.
+
+            return 0;
+        }
+
+        private static int GetOpusDecoderSize(int channelsCount)
+        {
+            const int SilkDecoderSize = 0x2160;
+
+            if (channelsCount < 1 || channelsCount > 2)
+            {
+                return 0;
+            }
+
+            int celtDecoderSize = GetCeltDecoderSize(channelsCount);
+            int opusDecoderSize = GetOpusDecoderAllocSize(channelsCount) | 0x50;
+
+            return opusDecoderSize + SilkDecoderSize + celtDecoderSize;
+        }
+
+        private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams)
+        {
+            if (streams < 1 || coupledStreams > streams || coupledStreams < 0)
+            {
+                return 0;
+            }
+
+            int coupledSize = GetOpusDecoderSize(2);
+            int monoSize = GetOpusDecoderSize(1);
+
+            return Align4(monoSize - GetOpusDecoderAllocSize(1)) * (streams - coupledStreams) +
+                Align4(coupledSize - GetOpusDecoderAllocSize(2)) * coupledStreams + 0xb920;
+        }
+
+        private static int Align4(int value)
+        {
+            return BitUtils.AlignUp(value, 4);
+        }
+
+        private static int GetOpusDecoderAllocSize(int channelsCount)
+        {
+            return channelsCount * 0x800 + 0x4800;
+        }
+
+        private static int GetCeltDecoderSize(int channelsCount)
+        {
+            const int DecodeBufferSize = 0x2030;
+            const int Overlap = 120;
+            const int EBandsCount = 21;
+
+            return (DecodeBufferSize + Overlap * 4) * channelsCount + EBandsCount * 16 + 0x54;
+        }
+
+        private static bool IsValidChannelCount(int channelsCount)
+        {
+            return channelsCount > 0 && channelsCount <= 2;
+        }
+
+        private static bool IsValidMultiChannelCount(int channelsCount)
+        {
+            return channelsCount > 0 && channelsCount <= 255;
+        }
+
+        private static bool IsValidSampleRate(int sampleRate)
+        {
+            switch (sampleRate)
+            {
+                case 8000:
+                case 12000:
+                case 16000:
+                case 24000:
+                case 48000:
+                    return true;
+            }
+
+            return false;
+        }
+
+        private static bool IsValidNumberOfStreams(int numberOfStreams, int numberOfStereoStreams, int channelsCount)
+        {
+            return numberOfStreams > 0 &&
+                numberOfStreams + numberOfStereoStreams <= channelsCount &&
+                numberOfStereoStreams >= 0 &&
+                numberOfStereoStreams <= numberOfStreams;
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs
new file mode 100644
index 000000000..271a592c1
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs
@@ -0,0 +1,11 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)]
+    struct HardwareOpusDecoderParameterInternal
+    {
+        public int SampleRate;
+        public int ChannelsCount;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs
new file mode 100644
index 000000000..e2b81c771
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs
@@ -0,0 +1,13 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)]
+    struct HardwareOpusDecoderParameterInternalEx
+    {
+        public int SampleRate;
+        public int ChannelsCount;
+        public OpusDecoderFlags Flags;
+        public uint Reserved;
+    }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs
similarity index 65%
rename from src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs
rename to src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs
index fd63a4f79..98536a4f8 100644
--- a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs
@@ -1,15 +1,15 @@
 using Ryujinx.Common.Memory;
 using System.Runtime.InteropServices;
 
-namespace Ryujinx.HLE.HOS.Services.Audio.Types
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
 {
     [StructLayout(LayoutKind.Sequential, Size = 0x110)]
-    struct OpusMultiStreamParameters
+    struct HardwareOpusMultiStreamDecoderParameterInternal
     {
         public int SampleRate;
         public int ChannelsCount;
         public int NumberOfStreams;
         public int NumberOfStereoStreams;
-        public Array64<uint> ChannelMappings;
+        public Array256<byte> ChannelMappings;
     }
 }
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs
similarity index 64%
rename from src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs
rename to src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs
index 1315c734e..8f8615dff 100644
--- a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs
@@ -1,19 +1,17 @@
 using Ryujinx.Common.Memory;
 using System.Runtime.InteropServices;
 
-namespace Ryujinx.HLE.HOS.Services.Audio.Types
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
 {
     [StructLayout(LayoutKind.Sequential, Size = 0x118)]
-    struct OpusMultiStreamParametersEx
+    struct HardwareOpusMultiStreamDecoderParameterInternalEx
     {
         public int SampleRate;
         public int ChannelsCount;
         public int NumberOfStreams;
         public int NumberOfStereoStreams;
         public OpusDecoderFlags Flags;
-
-        Array4<byte> Padding1;
-
-        public Array64<uint> ChannelMappings;
+        public uint Reserved;
+        public Array256<byte> ChannelMappings;
     }
 }
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs
new file mode 100644
index 000000000..ae09ad15a
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs
@@ -0,0 +1,20 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+    interface IHardwareOpusDecoder : IServiceObject
+    {
+        Result DecodeInterleavedOld(out int outConsumed, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input);
+        Result SetContext(ReadOnlySpan<byte> context);
+        Result DecodeInterleavedForMultiStreamOld(out int outConsumed, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input);
+        Result SetContextForMultiStream(ReadOnlySpan<byte> context);
+        Result DecodeInterleavedWithPerfOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input);
+        Result DecodeInterleavedForMultiStreamWithPerfOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input);
+        Result DecodeInterleavedWithPerfAndResetOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset);
+        Result DecodeInterleavedForMultiStreamWithPerfAndResetOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset);
+        Result DecodeInterleaved(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset);
+        Result DecodeInterleavedForMultiStream(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs
new file mode 100644
index 000000000..fb6c787b6
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+    interface IHardwareOpusDecoderManager : IServiceObject
+    {
+        Result OpenHardwareOpusDecoder(out IHardwareOpusDecoder decoder, HardwareOpusDecoderParameterInternal parameter, int workBufferHandle, int workBufferSize);
+        Result GetWorkBufferSize(out int size, HardwareOpusDecoderParameterInternal parameter);
+        Result OpenHardwareOpusDecoderForMultiStream(out IHardwareOpusDecoder decoder, in HardwareOpusMultiStreamDecoderParameterInternal parameter, int workBufferHandle, int workBufferSize);
+        Result GetWorkBufferSizeForMultiStream(out int size, in HardwareOpusMultiStreamDecoderParameterInternal parameter);
+        Result OpenHardwareOpusDecoderEx(out IHardwareOpusDecoder decoder, HardwareOpusDecoderParameterInternalEx parameter, int workBufferHandle, int workBufferSize);
+        Result GetWorkBufferSizeEx(out int size, HardwareOpusDecoderParameterInternalEx parameter);
+        Result OpenHardwareOpusDecoderForMultiStreamEx(out IHardwareOpusDecoder decoder, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, int workBufferHandle, int workBufferSize);
+        Result GetWorkBufferSizeForMultiStreamEx(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter);
+        Result GetWorkBufferSizeExEx(out int size, HardwareOpusDecoderParameterInternalEx parameter);
+        Result GetWorkBufferSizeForMultiStreamExEx(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter);
+    }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs
similarity index 72%
rename from src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs
rename to src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs
index 572535a92..d630b10f4 100644
--- a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs
@@ -1,6 +1,6 @@
 using System;
 
-namespace Ryujinx.HLE.HOS.Services.Audio.Types
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
 {
     [Flags]
     enum OpusDecoderFlags : uint
diff --git a/src/Ryujinx.Horizon/ServiceTable.cs b/src/Ryujinx.Horizon/ServiceTable.cs
index f3fe51940..b81e62a47 100644
--- a/src/Ryujinx.Horizon/ServiceTable.cs
+++ b/src/Ryujinx.Horizon/ServiceTable.cs
@@ -1,4 +1,5 @@
 using Ryujinx.Horizon.Arp;
+using Ryujinx.Horizon.Audio;
 using Ryujinx.Horizon.Bcat;
 using Ryujinx.Horizon.Friends;
 using Ryujinx.Horizon.Hshl;
@@ -39,9 +40,11 @@ namespace Ryujinx.Horizon
             }
 
             RegisterService<ArpMain>();
+            RegisterService<AudioMain>();
             RegisterService<BcatMain>();
             RegisterService<FriendsMain>();
             RegisterService<HshlMain>();
+            RegisterService<HwopusMain>(); // TODO: Merge with audio once we can start multiple threads.
             RegisterService<InsMain>();
             RegisterService<LblMain>();
             RegisterService<LmMain>();
diff --git a/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs b/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs
index 2060782cc..44d008224 100644
--- a/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs
+++ b/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs
@@ -41,6 +41,7 @@ namespace Ryujinx.Horizon.Srepo
         public void Shutdown()
         {
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs b/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs
index 38eeed496..a04b81f97 100644
--- a/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs
+++ b/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs
@@ -66,6 +66,7 @@ namespace Ryujinx.Horizon.Usb
         public void Shutdown()
         {
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs b/src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs
index c7b336231..776b9a7cb 100644
--- a/src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs
+++ b/src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs
@@ -54,6 +54,7 @@ namespace Ryujinx.Horizon.Wlan
         public void Shutdown()
         {
             _serverManager.Dispose();
+            _sm.Dispose();
         }
     }
 }