From f3b0b4831c323a20393aa0388f947317354372b7 Mon Sep 17 00:00:00 2001
From: Mary <me@thog.eu>
Date: Tue, 25 May 2021 19:01:09 +0200
Subject: [PATCH] amadeus: Update to REV9 (#2309)

* amadeus: Update to REV9

This implements all the changes made with REV9 on 12.0.0.

* Address Ac_k's comments
---
 Ryujinx.Audio/Renderer/Common/EffectType.cs   |   7 +-
 .../Renderer/Common/PerformanceDetailType.cs  |   3 +-
 .../Renderer/Dsp/Command/CommandType.cs       |   4 +-
 .../Dsp/Command/LimiterCommandVersion1.cs     | 160 ++++++++++++++++
 .../Dsp/Command/LimiterCommandVersion2.cs     | 179 ++++++++++++++++++
 .../Renderer/Dsp/State/LimiterState.cs        |  46 +++++
 .../Effect/AuxiliaryBufferParameter.cs        |   2 +-
 .../Effect/BiquadFilterEffectParameter.cs     |   2 +-
 .../Parameter/Effect/BufferMixerParameter.cs  |   2 +-
 .../Parameter/Effect/DelayParameter.cs        |   6 +-
 .../Parameter/Effect/LimiterParameter.cs      | 155 +++++++++++++++
 .../Parameter/Effect/LimiterStatistics.cs     |  48 +++++
 .../Parameter/Effect/Reverb3dParameter.cs     |   6 +-
 .../Parameter/Effect/ReverbParameter.cs       |   6 +-
 ...ameter.cs => EffectInParameterVersion1.cs} |  21 +-
 .../Parameter/EffectInParameterVersion2.cs    | 114 +++++++++++
 .../Parameter/EffectOutStatusVersion1.cs      |  40 ++++
 ...utStatus.cs => EffectOutStatusVersion2.cs} |  29 +--
 .../Renderer/Parameter/EffectResultState.cs   |  43 +++++
 .../Renderer/Parameter/EffectState.cs         |  35 ++++
 .../Renderer/Parameter/IEffectInParameter.cs  |  70 +++++++
 .../Renderer/Parameter/IEffectOutStatus.cs    |  30 +++
 .../Renderer/Server/AudioRenderSystem.cs      |   7 +-
 .../Renderer/Server/BehaviourContext.cs       |  19 +-
 .../Renderer/Server/CommandBuffer.cs          |  43 +++++
 .../Renderer/Server/CommandGenerator.cs       |  25 ++-
 .../CommandProcessingTimeEstimatorVersion1.cs |  10 +
 .../CommandProcessingTimeEstimatorVersion2.cs |  10 +
 .../CommandProcessingTimeEstimatorVersion3.cs | 123 ++++++++++++
 .../Server/Effect/AuxiliaryBufferEffect.cs    |  12 +-
 .../Renderer/Server/Effect/BaseEffect.cs      |  46 ++++-
 .../Server/Effect/BiquadFilterEffect.cs       |  12 +-
 .../Renderer/Server/Effect/BufferMixEffect.cs |  12 +-
 .../Renderer/Server/Effect/DelayEffect.cs     |  12 +-
 .../Renderer/Server/Effect/EffectContext.cs   |  60 +++++-
 .../Renderer/Server/Effect/LimiterEffect.cs   | 112 +++++++++++
 .../Renderer/Server/Effect/Reverb3dEffect.cs  |  12 +-
 .../Renderer/Server/Effect/ReverbEffect.cs    |  12 +-
 .../Server/ICommandProcessingTimeEstimator.cs |   2 +
 .../Server/Performance/PerformanceManager.cs  |   1 -
 Ryujinx.Audio/Renderer/Server/StateUpdater.cs |  83 +++++++-
 .../Renderer/EffectInfoParameterTests.cs      |   3 +-
 .../Audio/Renderer/EffectOutStatusTests.cs    |   3 +-
 .../Parameter/Effect/LimiterParameterTests.cs |  16 ++
 .../Effect/LimiterStatisticsTests.cs          |  16 ++
 45 files changed, 1591 insertions(+), 68 deletions(-)
 create mode 100644 Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
 create mode 100644 Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
 create mode 100644 Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs
 create mode 100644 Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs
 create mode 100644 Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs
 rename Ryujinx.Audio/Renderer/Parameter/{EffectInParameter.cs => EffectInParameterVersion1.cs} (86%)
 create mode 100644 Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs
 create mode 100644 Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs
 rename Ryujinx.Audio/Renderer/Parameter/{EffectOutStatus.cs => EffectOutStatusVersion2.cs} (74%)
 create mode 100644 Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs
 create mode 100644 Ryujinx.Audio/Renderer/Parameter/EffectState.cs
 create mode 100644 Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs
 create mode 100644 Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs
 create mode 100644 Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs
 create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterParameterTests.cs
 create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterStatisticsTests.cs

diff --git a/Ryujinx.Audio/Renderer/Common/EffectType.cs b/Ryujinx.Audio/Renderer/Common/EffectType.cs
index 082f94f8..daa22036 100644
--- a/Ryujinx.Audio/Renderer/Common/EffectType.cs
+++ b/Ryujinx.Audio/Renderer/Common/EffectType.cs
@@ -55,6 +55,11 @@ namespace Ryujinx.Audio.Renderer.Common
         /// <summary>
         /// Effect applying a biquad filter.
         /// </summary>
-        BiquadFilter
+        BiquadFilter,
+
+        /// <summary>
+        /// Effect applying a limiter (DRC).
+        /// </summary>
+        Limiter,
     }
 }
diff --git a/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs b/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
index a92bd0cc..e9e946ce 100644
--- a/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
+++ b/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
@@ -29,6 +29,7 @@ namespace Ryujinx.Audio.Renderer.Common
         Aux,
         Reverb,
         Reverb3d,
-        PcmFloat
+        PcmFloat,
+        Limiter
     }
 }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
index 8ff1c581..997a080e 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
@@ -44,6 +44,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
         Reverb3d,
         Performance,
         ClearMixBuffer,
-        CopyMixBuffer
+        CopyMixBuffer,
+        LimiterVersion1,
+        LimiterVersion2
     }
 }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
new file mode 100644
index 00000000..975e61f9
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
@@ -0,0 +1,160 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+    public class LimiterCommandVersion1 : ICommand
+    {
+        public bool Enabled { get; set; }
+
+        public int NodeId { get; }
+
+        public CommandType CommandType => CommandType.LimiterVersion1;
+
+        public ulong EstimatedProcessingTime { get; set; }
+
+        public LimiterParameter Parameter => _parameter;
+        public Memory<LimiterState> State { get; }
+        public ulong WorkBuffer { get; }
+        public ushort[] OutputBufferIndices { get; }
+        public ushort[] InputBufferIndices { get; }
+        public bool IsEffectEnabled { get; }
+
+        private LimiterParameter _parameter;
+
+        public LimiterCommandVersion1(uint bufferOffset, LimiterParameter parameter, Memory<LimiterState> state, bool isEnabled, ulong workBuffer, int nodeId)
+        {
+            Enabled = true;
+            NodeId = nodeId;
+            _parameter = parameter;
+            State = state;
+            WorkBuffer = workBuffer;
+
+            IsEffectEnabled = isEnabled;
+
+            InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+            OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+            for (int i = 0; i < Parameter.ChannelCount; i++)
+            {
+                InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
+                OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+            }
+        }
+
+        public void Process(CommandList context)
+        {
+            ref LimiterState state = ref State.Span[0];
+
+            if (IsEffectEnabled)
+            {
+                if (Parameter.Status == Server.Effect.UsageState.Invalid)
+                {
+                    state = new LimiterState(ref _parameter, WorkBuffer);
+                }
+                else if (Parameter.Status == Server.Effect.UsageState.New)
+                {
+                    state.UpdateParameter(ref _parameter);
+                }
+            }
+
+            ProcessLimiter(context);
+        }
+
+        private void ProcessLimiter(CommandList context)
+        {
+            Debug.Assert(Parameter.IsChannelCountValid());
+
+            if (IsEffectEnabled && Parameter.IsChannelCountValid())
+            {
+                ref LimiterState state = ref State.Span[0];
+
+                ReadOnlyMemory<float>[] inputBuffers = new ReadOnlyMemory<float>[Parameter.ChannelCount];
+                Memory<float>[] outputBuffers = new Memory<float>[Parameter.ChannelCount];
+
+                for (int i = 0; i < Parameter.ChannelCount; i++)
+                {
+                    inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]);
+                    outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]);
+                }
+
+                for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+                {
+                    for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
+                    {
+                        float inputSample = inputBuffers[channelIndex].Span[sampleIndex];
+
+                        float sampleInputMax = Math.Abs(inputSample * Parameter.InputGain);
+
+                        float inputCoefficient = Parameter.ReleaseCoefficient;
+
+                        if (sampleInputMax > state.DectectorAverage[channelIndex])
+                        {
+                            inputCoefficient = Parameter.AttackCoefficient;
+                        }
+
+                        state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
+
+                        float attenuation = 1.0f;
+
+                        if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
+                        {
+                            attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
+                        }
+
+                        float outputCoefficient = Parameter.ReleaseCoefficient;
+
+                        if (state.CompressionGain[channelIndex] > attenuation)
+                        {
+                            outputCoefficient = Parameter.AttackCoefficient;
+                        }
+
+                        state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
+
+                        ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
+
+                        outputBuffers[channelIndex].Span[sampleIndex] = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
+
+                        delayedSample = inputSample;
+
+                        state.DelayedSampleBufferPosition[channelIndex]++;
+
+                        while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
+                        {
+                            state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                for (int i = 0; i < Parameter.ChannelCount; i++)
+                {
+                    if (InputBufferIndices[i] != OutputBufferIndices[i])
+                    {
+                        context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
new file mode 100644
index 00000000..0a4b14b7
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
@@ -0,0 +1,179 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+    public class LimiterCommandVersion2 : ICommand
+    {
+        public bool Enabled { get; set; }
+
+        public int NodeId { get; }
+
+        public CommandType CommandType => CommandType.LimiterVersion2;
+
+        public ulong EstimatedProcessingTime { get; set; }
+
+        public LimiterParameter Parameter => _parameter;
+        public Memory<LimiterState> State { get; }
+        public Memory<EffectResultState> ResultState { get; }
+        public ulong WorkBuffer { get; }
+        public ushort[] OutputBufferIndices { get; }
+        public ushort[] InputBufferIndices { get; }
+        public bool IsEffectEnabled { get; }
+
+        private LimiterParameter _parameter;
+
+        public LimiterCommandVersion2(uint bufferOffset, LimiterParameter parameter, Memory<LimiterState> state, Memory<EffectResultState> resultState, bool isEnabled, ulong workBuffer, int nodeId)
+        {
+            Enabled = true;
+            NodeId = nodeId;
+            _parameter = parameter;
+            State = state;
+            ResultState = resultState;
+            WorkBuffer = workBuffer;
+
+            IsEffectEnabled = isEnabled;
+
+            InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+            OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+            for (int i = 0; i < Parameter.ChannelCount; i++)
+            {
+                InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
+                OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+            }
+        }
+
+        public void Process(CommandList context)
+        {
+            ref LimiterState state = ref State.Span[0];
+
+            if (IsEffectEnabled)
+            {
+                if (Parameter.Status == Server.Effect.UsageState.Invalid)
+                {
+                    state = new LimiterState(ref _parameter, WorkBuffer);
+                }
+                else if (Parameter.Status == Server.Effect.UsageState.New)
+                {
+                    state.UpdateParameter(ref _parameter);
+                }
+            }
+
+            ProcessLimiter(context);
+        }
+
+        private void ProcessLimiter(CommandList context)
+        {
+            Debug.Assert(Parameter.IsChannelCountValid());
+
+            if (IsEffectEnabled && Parameter.IsChannelCountValid())
+            {
+                ref LimiterState state = ref State.Span[0];
+
+                if (!ResultState.IsEmpty && Parameter.StatisticsReset)
+                {
+                    ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
+
+                    statistics.Reset();
+                }
+
+                ReadOnlyMemory<float>[] inputBuffers = new ReadOnlyMemory<float>[Parameter.ChannelCount];
+                Memory<float>[] outputBuffers = new Memory<float>[Parameter.ChannelCount];
+
+                for (int i = 0; i < Parameter.ChannelCount; i++)
+                {
+                    inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]);
+                    outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]);
+                }
+
+                for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+                {
+                    for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
+                    {
+                        float inputSample = inputBuffers[channelIndex].Span[sampleIndex];
+
+                        float sampleInputMax = Math.Abs(inputSample * Parameter.InputGain);
+
+                        float inputCoefficient = Parameter.ReleaseCoefficient;
+
+                        if (sampleInputMax > state.DectectorAverage[channelIndex])
+                        {
+                            inputCoefficient = Parameter.AttackCoefficient;
+                        }
+
+                        state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
+
+                        float attenuation = 1.0f;
+
+                        if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
+                        {
+                            attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
+                        }
+
+                        float outputCoefficient = Parameter.ReleaseCoefficient;
+
+                        if (state.CompressionGain[channelIndex] > attenuation)
+                        {
+                            outputCoefficient = Parameter.AttackCoefficient;
+                        }
+
+                        state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
+
+                        ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
+
+                        outputBuffers[channelIndex].Span[sampleIndex] = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
+
+                        delayedSample = inputSample;
+
+                        state.DelayedSampleBufferPosition[channelIndex]++;
+
+                        while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
+                        {
+                            state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
+                        }
+
+                        if (!ResultState.IsEmpty)
+                        {
+                            ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
+
+                            statistics.InputMax[channelIndex] = Math.Max(statistics.InputMax[channelIndex], sampleInputMax);
+                            statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], state.CompressionGain[channelIndex]);
+                        }
+                    }
+                }
+            }
+            else
+            {
+                for (int i = 0; i < Parameter.ChannelCount; i++)
+                {
+                    if (InputBufferIndices[i] != OutputBufferIndices[i])
+                    {
+                        context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs b/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs
new file mode 100644
index 00000000..53913bad
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs
@@ -0,0 +1,46 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.State
+{
+    public class LimiterState
+    {
+        public float[] DectectorAverage;
+        public float[] CompressionGain;
+        public float[] DelayedSampleBuffer;
+        public int[] DelayedSampleBufferPosition;
+
+        public LimiterState(ref LimiterParameter parameter, ulong workBuffer)
+        {
+            DectectorAverage = new float[parameter.ChannelCount];
+            CompressionGain = new float[parameter.ChannelCount];
+            DelayedSampleBuffer = new float[parameter.ChannelCount * parameter.DelayBufferSampleCountMax];
+            DelayedSampleBufferPosition = new int[parameter.ChannelCount];
+
+            DectectorAverage.AsSpan().Fill(0.0f);
+            CompressionGain.AsSpan().Fill(1.0f);
+            DelayedSampleBufferPosition.AsSpan().Fill(0);
+
+            UpdateParameter(ref parameter);
+        }
+
+        public void UpdateParameter(ref LimiterParameter parameter) {}
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs
index 5a747922..c30c4013 100644
--- a/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs
@@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
 namespace Ryujinx.Audio.Renderer.Parameter.Effect
 {
     /// <summary>
-    /// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.AuxiliaryBuffer"/>.
+    /// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.AuxiliaryBuffer"/>.
     /// </summary>
     [StructLayout(LayoutKind.Sequential, Pack = 1)]
     public struct AuxiliaryBufferParameter
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs
index 7d58d4be..39d75b69 100644
--- a/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs
@@ -22,7 +22,7 @@ using System.Runtime.InteropServices;
 namespace Ryujinx.Audio.Renderer.Parameter.Effect
 {
     /// <summary>
-    /// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.BiquadFilter"/>.
+    /// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.BiquadFilter"/>.
     /// </summary>
     [StructLayout(LayoutKind.Sequential, Pack = 1)]
     public struct BiquadFilterEffectParameter
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs
index b0c0c337..ef298e3d 100644
--- a/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs
@@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
 namespace Ryujinx.Audio.Renderer.Parameter.Effect
 {
     /// <summary>
-    /// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.BufferMix"/>.
+    /// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.BufferMix"/>.
     /// </summary>
     [StructLayout(LayoutKind.Sequential, Pack = 1)]
     public struct BufferMixParameter
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs
index e0dbeb7c..80e5df25 100644
--- a/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs
@@ -22,7 +22,7 @@ using System.Runtime.InteropServices;
 namespace Ryujinx.Audio.Renderer.Parameter.Effect
 {
     /// <summary>
-    /// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Delay"/>.
+    /// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Delay"/>.
     /// </summary>
     [StructLayout(LayoutKind.Sequential, Pack = 1)]
     public struct DelayParameter
@@ -103,7 +103,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
         /// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
         public bool IsChannelCountValid()
         {
-            return EffectInParameter.IsChannelCountValid(ChannelCount);
+            return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
         }
 
         /// <summary>
@@ -112,7 +112,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
         /// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
         public bool IsChannelCountMaxValid()
         {
-            return EffectInParameter.IsChannelCountValid(ChannelCountMax);
+            return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax);
         }
     }
 }
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs
new file mode 100644
index 00000000..2caf23f9
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs
@@ -0,0 +1,155 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Server.Effect;
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Effect
+{
+    /// <summary>
+    /// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Limiter"/>.
+    /// </summary>
+    [StructLayout(LayoutKind.Sequential, Pack = 1)]
+    public struct LimiterParameter
+    {
+        /// <summary>
+        /// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
+        /// </summary>
+        public Array6<byte> Input;
+
+        /// <summary>
+        /// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
+        /// </summary>
+        public Array6<byte> Output;
+
+        /// <summary>
+        /// The maximum number of channels supported.
+        /// </summary>
+        public ushort ChannelCountMax;
+
+        /// <summary>
+        /// The total channel count used.
+        /// </summary>
+        public ushort ChannelCount;
+
+        /// <summary>
+        /// The target sample rate.
+        /// </summary>
+        /// <remarks>This is in kHz.</remarks>
+        public int SampleRate;
+
+        /// <summary>
+        /// The look ahead max time.
+        /// <remarks>This is in microseconds.</remarks>
+        /// </summary>
+        public int LookAheadTimeMax;
+
+        /// <summary>
+        /// The attack time.
+        /// <remarks>This is in microseconds.</remarks>
+        /// </summary>
+        public int AttackTime;
+
+        /// <summary>
+        /// The release time.
+        /// <remarks>This is in microseconds.</remarks>
+        /// </summary>
+        public int ReleaseTime;
+
+        /// <summary>
+        /// The look ahead time.
+        /// <remarks>This is in microseconds.</remarks>
+        /// </summary>
+        public int LookAheadTime;
+
+        /// <summary>
+        /// The attack coefficient.
+        /// </summary>
+        public float AttackCoefficient;
+
+        /// <summary>
+        /// The release coefficient.
+        /// </summary>
+        public float ReleaseCoefficient;
+
+        /// <summary>
+        /// The threshold.
+        /// </summary>
+        public float Threshold;
+
+        /// <summary>
+        /// The input gain.
+        /// </summary>
+        public float InputGain;
+
+        /// <summary>
+        /// The output gain.
+        /// </summary>
+        public float OutputGain;
+
+        /// <summary>
+        /// The minimum samples stored in the delay buffer.
+        /// </summary>
+        public int DelayBufferSampleCountMin;
+
+        /// <summary>
+        /// The maximum samples stored in the delay buffer.
+        /// </summary>
+        public int DelayBufferSampleCountMax;
+
+        /// <summary>
+        /// The current usage status of the effect on the client side.
+        /// </summary>
+        public UsageState Status;
+
+        /// <summary>
+        /// Indicate if the limiter effect should output statistics.
+        /// </summary>
+        [MarshalAs(UnmanagedType.I1)]
+        public bool StatisticsEnabled;
+
+        /// <summary>
+        /// Indicate to the DSP that the user did a statistics reset.
+        /// </summary>
+        [MarshalAs(UnmanagedType.I1)]
+        public bool StatisticsReset;
+
+        /// <summary>
+        /// Reserved/padding.
+        /// </summary>
+        private byte _reserved;
+
+        /// <summary>
+        /// Check if the <see cref="ChannelCount"/> is valid.
+        /// </summary>
+        /// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
+        public bool IsChannelCountValid()
+        {
+            return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
+        }
+
+        /// <summary>
+        /// Check if the <see cref="ChannelCountMax"/> is valid.
+        /// </summary>
+        /// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
+        public bool IsChannelCountMaxValid()
+        {
+            return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax);
+        }
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs
new file mode 100644
index 00000000..faf12f2a
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs
@@ -0,0 +1,48 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Effect
+{
+    /// <summary>
+    /// Effect result state for <seealso cref="Common.EffectType.Limiter"/>.
+    /// </summary>
+    [StructLayout(LayoutKind.Sequential, Pack = 1)]
+    public struct LimiterStatistics
+    {
+        /// <summary>
+        /// The max input sample value recorded by the limiter.
+        /// </summary>
+        public Array6<float> InputMax;
+
+        /// <summary>
+        /// Compression gain min value.
+        /// </summary>
+        public Array6<float> CompressionGainMin;
+
+        /// <summary>
+        /// Reset the statistics.
+        /// </summary>
+        public void Reset()
+        {
+            InputMax.ToSpan().Fill(0.0f);
+            CompressionGainMin.ToSpan().Fill(1.0f);
+        }
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs
index 0dbecfb8..9b775fff 100644
--- a/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs
@@ -22,7 +22,7 @@ using System.Runtime.InteropServices;
 namespace Ryujinx.Audio.Renderer.Parameter.Effect
 {
     /// <summary>
-    /// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Reverb3d"/>.
+    /// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Reverb3d"/>.
     /// </summary>
     [StructLayout(LayoutKind.Sequential, Pack = 1)]
     public struct Reverb3dParameter
@@ -129,7 +129,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
         /// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
         public bool IsChannelCountValid()
         {
-            return EffectInParameter.IsChannelCountValid(ChannelCount);
+            return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
         }
 
         /// <summary>
@@ -138,7 +138,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
         /// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
         public bool IsChannelCountMaxValid()
         {
-            return EffectInParameter.IsChannelCountValid(ChannelCountMax);
+            return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax);
         }
     }
 }
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs
index daa81c51..dea54407 100644
--- a/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs
@@ -23,7 +23,7 @@ using System.Runtime.InteropServices;
 namespace Ryujinx.Audio.Renderer.Parameter.Effect
 {
     /// <summary>
-    /// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Reverb"/>.
+    /// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Reverb"/>.
     /// </summary>
     [StructLayout(LayoutKind.Sequential, Pack = 1)]
     public struct ReverbParameter
@@ -121,7 +121,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
         /// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
         public bool IsChannelCountValid()
         {
-            return EffectInParameter.IsChannelCountValid(ChannelCount);
+            return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
         }
 
         /// <summary>
@@ -130,7 +130,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
         /// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
         public bool IsChannelCountMaxValid()
         {
-            return EffectInParameter.IsChannelCountValid(ChannelCountMax);
+            return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax);
         }
     }
 }
diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs
similarity index 86%
rename from Ryujinx.Audio/Renderer/Parameter/EffectInParameter.cs
rename to Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs
index af8edeff..ec2d801e 100644
--- a/Ryujinx.Audio/Renderer/Parameter/EffectInParameter.cs
+++ b/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs
@@ -23,10 +23,10 @@ using System.Runtime.InteropServices;
 namespace Ryujinx.Audio.Renderer.Parameter
 {
     /// <summary>
-    /// Input information for an effect.
+    /// Input information for an effect version 1.
     /// </summary>
     [StructLayout(LayoutKind.Sequential, Pack = 1)]
-    public struct EffectInParameter
+    public struct EffectInParameterVersion1 : IEffectInParameter
     {
         /// <summary>
         /// Type of the effect.
@@ -85,11 +85,22 @@ namespace Ryujinx.Audio.Renderer.Parameter
         [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)]
         private struct SpecificDataStruct { }
 
-        /// <summary>
-        /// Specific data changing depending of the <see cref="Type"/>. See also the <see cref="Effect"/> namespace.
-        /// </summary>
         public Span<byte> SpecificData => SpanHelpers.AsSpan<SpecificDataStruct, byte>(ref _specificDataStart);
 
+        EffectType IEffectInParameter.Type => Type;
+
+        bool IEffectInParameter.IsNew => IsNew;
+
+        bool IEffectInParameter.IsEnabled => IsEnabled;
+
+        int IEffectInParameter.MixId => MixId;
+
+        ulong IEffectInParameter.BufferBase => BufferBase;
+
+        ulong IEffectInParameter.BufferSize => BufferSize;
+
+        uint IEffectInParameter.ProcessingOrder => ProcessingOrder;
+
         /// <summary>
         ///  Check if the given channel count is valid.
         /// </summary>
diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs b/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs
new file mode 100644
index 00000000..ea74f1e6
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs
@@ -0,0 +1,114 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+    /// <summary>
+    /// Input information for an effect version 2. (added with REV9)
+    /// </summary>
+    [StructLayout(LayoutKind.Sequential, Pack = 1)]
+    public struct EffectInParameterVersion2 : IEffectInParameter
+    {
+        /// <summary>
+        /// Type of the effect.
+        /// </summary>
+        public EffectType Type;
+
+        /// <summary>
+        /// Set to true if the effect is new.
+        /// </summary>
+        [MarshalAs(UnmanagedType.I1)]
+        public bool IsNew;
+
+        /// <summary>
+        /// Set to true if the effect must be active.
+        /// </summary>
+        [MarshalAs(UnmanagedType.I1)]
+        public bool IsEnabled;
+
+        /// <summary>
+        /// Reserved/padding.
+        /// </summary>
+        private byte _reserved1;
+
+        /// <summary>
+        /// The target mix id of the effect.
+        /// </summary>
+        public int MixId;
+
+        /// <summary>
+        /// Address of the processing workbuffer.
+        /// </summary>
+        /// <remarks>This is additional data that could be required by the effect processing.</remarks>
+        public ulong BufferBase;
+
+        /// <summary>
+        /// Size of the processing workbuffer.
+        /// </summary>
+        /// <remarks>This is additional data that could be required by the effect processing.</remarks>
+        public ulong BufferSize;
+
+        /// <summary>
+        /// Position of the effect while processing effects.
+        /// </summary>
+        public uint ProcessingOrder;
+
+        /// <summary>
+        /// Reserved/padding.
+        /// </summary>
+        private uint _reserved2;
+
+        /// <summary>
+        /// Specific data storage.
+        /// </summary>
+        private SpecificDataStruct _specificDataStart;
+
+        [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)]
+        private struct SpecificDataStruct { }
+
+        public Span<byte> SpecificData => SpanHelpers.AsSpan<SpecificDataStruct, byte>(ref _specificDataStart);
+
+        EffectType IEffectInParameter.Type => Type;
+
+        bool IEffectInParameter.IsNew => IsNew;
+
+        bool IEffectInParameter.IsEnabled => IsEnabled;
+
+        int IEffectInParameter.MixId => MixId;
+
+        ulong IEffectInParameter.BufferBase => BufferBase;
+
+        ulong IEffectInParameter.BufferSize => BufferSize;
+
+        uint IEffectInParameter.ProcessingOrder => ProcessingOrder;
+
+        /// <summary>
+        ///  Check if the given channel count is valid.
+        /// </summary>
+        /// <param name="channelCount">The channel count to check</param>
+        /// <returns>Returns true if the channel count is valid.</returns>
+        public static bool IsChannelCountValid(int channelCount)
+        {
+            return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6;
+        }
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs b/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs
new file mode 100644
index 00000000..0c26d2b9
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs
@@ -0,0 +1,40 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+    /// <summary>
+    /// Output information for an effect version 1.
+    /// </summary>
+    [StructLayout(LayoutKind.Sequential, Pack = 1)]
+    public struct EffectOutStatusVersion1 : IEffectOutStatus
+    {
+        /// <summary>
+        /// Current effect state.
+        /// </summary>
+        public EffectState State;
+
+        /// <summary>
+        /// Unused/Reserved.
+        /// </summary>
+        private unsafe fixed byte _reserved[15];
+
+        EffectState IEffectOutStatus.State { get => State; set => State = value; }
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs
similarity index 74%
rename from Ryujinx.Audio/Renderer/Parameter/EffectOutStatus.cs
rename to Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs
index c6178165..59eeae9b 100644
--- a/Ryujinx.Audio/Renderer/Parameter/EffectOutStatus.cs
+++ b/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs
@@ -1,4 +1,4 @@
-//
+//
 // Copyright (c) 2019-2021 Ryujinx
 //
 // This program is free software: you can redistribute it and/or modify
@@ -20,27 +20,11 @@ using System.Runtime.InteropServices;
 namespace Ryujinx.Audio.Renderer.Parameter
 {
     /// <summary>
-    /// Output information for an effect.
+    /// Output information for an effect version 2. (added with REV9)
     /// </summary>
     [StructLayout(LayoutKind.Sequential, Pack = 1)]
-    public struct EffectOutStatus
+    public struct EffectOutStatusVersion2 : IEffectOutStatus
     {
-        /// <summary>
-        /// The state of an effect.
-        /// </summary>
-        public enum EffectState : byte
-        {
-            /// <summary>
-            /// The effect is enabled.
-            /// </summary>
-            Enabled = 3,
-
-            /// <summary>
-            /// The effect is disabled.
-            /// </summary>
-            Disabled = 4
-        }
-
         /// <summary>
         /// Current effect state.
         /// </summary>
@@ -50,5 +34,12 @@ namespace Ryujinx.Audio.Renderer.Parameter
         /// Unused/Reserved.
         /// </summary>
         private unsafe fixed byte _reserved[15];
+
+        /// <summary>
+        /// Current result state.
+        /// </summary>
+        public EffectResultState ResultState;
+
+        EffectState IEffectOutStatus.State { get => State; set => State = value; }
     }
 }
diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs b/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs
new file mode 100644
index 00000000..64a6f50a
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs
@@ -0,0 +1,43 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+    /// <summary>
+    /// Effect result state (added in REV9).
+    /// </summary>
+    [StructLayout(LayoutKind.Sequential, Pack = 1)]
+    public struct EffectResultState
+    {
+        /// <summary>
+        /// Specific data storage.
+        /// </summary>
+        private SpecificDataStruct _specificDataStart;
+
+        [StructLayout(LayoutKind.Sequential, Size = 0x80, Pack = 1)]
+        private struct SpecificDataStruct { }
+
+        /// <summary>
+        /// Specific data changing depending of the type of effect. See also the <see cref="Effect"/> namespace.
+        /// </summary>
+        public Span<byte> SpecificData => SpanHelpers.AsSpan<SpecificDataStruct, byte>(ref _specificDataStart);
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectState.cs b/Ryujinx.Audio/Renderer/Parameter/EffectState.cs
new file mode 100644
index 00000000..685d0cdb
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/EffectState.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+    /// <summary>
+    /// The state of an effect.
+    /// </summary>
+    public enum EffectState : byte
+    {
+        /// <summary>
+        /// The effect is enabled.
+        /// </summary>
+        Enabled = 3,
+
+        /// <summary>
+        /// The effect is disabled.
+        /// </summary>
+        Disabled = 4
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs
new file mode 100644
index 00000000..779d8298
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs
@@ -0,0 +1,70 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+    /// <summary>
+    /// Generic interface to represent input information for an effect.
+    /// </summary>
+    public interface IEffectInParameter
+    {
+        /// <summary>
+        /// Type of the effect.
+        /// </summary>
+        EffectType Type { get; }
+
+        /// <summary>
+        /// Set to true if the effect is new.
+        /// </summary>
+        bool IsNew { get; }
+
+        /// <summary>
+        /// Set to true if the effect must be active.
+        /// </summary>
+        bool IsEnabled { get; }
+
+        /// <summary>
+        /// The target mix id of the effect.
+        /// </summary>
+        int MixId { get; }
+
+        /// <summary>
+        /// Address of the processing workbuffer.
+        /// </summary>
+        /// <remarks>This is additional data that could be required by the effect processing.</remarks>
+        ulong BufferBase { get; }
+
+        /// <summary>
+        /// Size of the processing workbuffer.
+        /// </summary>
+        /// <remarks>This is additional data that could be required by the effect processing.</remarks>
+        ulong BufferSize { get; }
+
+        /// <summary>
+        /// Position of the effect while processing effects.
+        /// </summary>
+        uint ProcessingOrder { get; }
+
+        /// <summary>
+        /// Specific data changing depending of the <see cref="Type"/>. See also the <see cref="Effect"/> namespace.
+        /// </summary>
+        Span<byte> SpecificData { get; }
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs
new file mode 100644
index 00000000..29220a9d
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs
@@ -0,0 +1,30 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+    /// <summary>
+    /// Generic interface to represent output information for an effect.
+    /// </summary>
+    public interface IEffectOutStatus
+    {
+        /// <summary>
+        /// Current effect state.
+        /// </summary>
+        EffectState State { get; set; }
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
index 112b0e44..943a2d78 100644
--- a/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
+++ b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
@@ -307,7 +307,7 @@ namespace Ryujinx.Audio.Renderer.Server
 
             _upsamplerManager = new UpsamplerManager(upSamplerWorkBuffer, _upsamplerCount);
 
-            _effectContext.Initialize(parameter.EffectCount);
+            _effectContext.Initialize(parameter.EffectCount, _behaviourContext.IsEffectInfoVersion2Supported() ? parameter.EffectCount : 0);
             _sinkContext.Initialize(parameter.SinkCount);
 
             Memory<VoiceUpdateState> voiceUpdateStatesDsp = workBufferAllocator.Allocate<VoiceUpdateState>(parameter.VoiceCount, VoiceUpdateState.Align);
@@ -636,6 +636,11 @@ namespace Ryujinx.Audio.Renderer.Server
 
             _voiceContext.UpdateForCommandGeneration();
 
+            if (_behaviourContext.IsEffectInfoVersion2Supported())
+            {
+                _effectContext.UpdateResultStateForCommandGeneration();
+            }
+
             ulong endTicks = GetSystemTicks();
 
             _totalElapsedTicks = endTicks - startTicks;
diff --git a/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
index b31f9e9f..ed1f402e 100644
--- a/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
+++ b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
@@ -89,10 +89,18 @@ namespace Ryujinx.Audio.Renderer.Server
         /// <remarks>This was added in system update 9.0.0</remarks>
         public const int Revision8 = 8 << 24;
 
+        /// <summary>
+        /// REV9:
+        /// EffectInfo parameters were revisited with a new revision (version 2) allowing more data control between the client and server.
+        /// A new effect was added: Limiter. This effect is effectively implemented with a DRC while providing statistics on the processing on <see cref="Parameter.EffectOutStatusVersion2"/>.
+        /// </summary>
+        /// <remarks>This was added in system update 12.0.0</remarks>
+        public const int Revision9 = 9 << 24;
+
         /// <summary>
         /// Last revision supported by the implementation.
         /// </summary>
-        public const int LastRevision = Revision8;
+        public const int LastRevision = Revision9;
 
         /// <summary>
         /// Target revision magic supported by the implementation.
@@ -330,6 +338,15 @@ namespace Ryujinx.Audio.Renderer.Server
             return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8);
         }
 
+        /// <summary>
+        /// Check if the audio renderer should use the new effect info format.
+        /// </summary>
+        /// <returns>True if the audio renderer should use the new effect info format.</returns>
+        public bool IsEffectInfoVersion2Supported()
+        {
+            return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision9);
+        }
+
         /// <summary>
         /// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
         /// </summary>
diff --git a/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
index ca37e090..24cc93af 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
@@ -371,6 +371,49 @@ namespace Ryujinx.Audio.Renderer.Server
             }
         }
 
+        /// <summary>
+        /// Generate a new <see cref="LimiterCommandVersion1"/>.
+        /// </summary>
+        /// <param name="bufferOffset">The target buffer offset.</param>
+        /// <param name="parameter">The limiter parameter.</param>
+        /// <param name="state">The limiter state.</param>
+        /// <param name="isEnabled">Set to true if the effect should be active.</param>
+        /// <param name="workBuffer">The work buffer to use for processing.</param>
+        /// <param name="nodeId">The node id associated to this command.</param>
+        public void GenerateLimiterEffectVersion1(uint bufferOffset, LimiterParameter parameter, Memory<LimiterState> state, bool isEnabled, ulong workBuffer, int nodeId)
+        {
+            if (parameter.IsChannelCountValid())
+            {
+                LimiterCommandVersion1 command = new LimiterCommandVersion1(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId);
+
+                command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+                AddCommand(command);
+            }
+        }
+
+        /// <summary>
+        /// Generate a new <see cref="LimiterCommandVersion2"/>.
+        /// </summary>
+        /// <param name="bufferOffset">The target buffer offset.</param>
+        /// <param name="parameter">The limiter parameter.</param>
+        /// <param name="state">The limiter state.</param>
+        /// <param name="effectResultState">The DSP effect result state.</param>
+        /// <param name="isEnabled">Set to true if the effect should be active.</param>
+        /// <param name="workBuffer">The work buffer to use for processing.</param>
+        /// <param name="nodeId">The node id associated to this command.</param>
+        public void GenerateLimiterEffectVersion2(uint bufferOffset, LimiterParameter parameter, Memory<LimiterState> state, Memory<EffectResultState> effectResultState, bool isEnabled, ulong workBuffer, int nodeId)
+        {
+            if (parameter.IsChannelCountValid())
+            {
+                LimiterCommandVersion2 command = new LimiterCommandVersion2(bufferOffset, parameter, state, effectResultState, isEnabled, workBuffer, nodeId);
+
+                command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+                AddCommand(command);
+            }
+        }
+
         /// <summary>
         /// Generate a new <see cref="AuxiliaryBufferCommand"/>.
         /// </summary>
diff --git a/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
index d2499c1d..01e7c927 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
@@ -538,7 +538,25 @@ namespace Ryujinx.Audio.Renderer.Server
             }
         }
 
-        private void GenerateEffect(ref MixState mix, BaseEffect effect)
+        private void GenerateLimiterEffect(uint bufferOffset, LimiterEffect effect, int nodeId, int effectId)
+        {
+            Debug.Assert(effect.Type == EffectType.Limiter);
+
+            ulong workBuffer = effect.GetWorkBuffer(-1);
+
+            if (_rendererContext.BehaviourContext.IsEffectInfoVersion2Supported())
+            {
+                Memory<EffectResultState> dspResultState = _effectContext.GetDspStateMemory(effectId);
+
+                _commandBuffer.GenerateLimiterEffectVersion2(bufferOffset, effect.Parameter, effect.State, dspResultState, effect.IsEnabled, workBuffer, nodeId);
+            }
+            else
+            {
+                _commandBuffer.GenerateLimiterEffectVersion1(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId);
+            }
+        }
+
+        private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
         {
             int nodeId = mix.NodeId;
 
@@ -576,6 +594,9 @@ namespace Ryujinx.Audio.Renderer.Server
                 case EffectType.BiquadFilter:
                     GenerateBiquadFilterEffect(mix.BufferOffset, (BiquadFilterEffect)effect, nodeId);
                     break;
+                case EffectType.Limiter:
+                    GenerateLimiterEffect(mix.BufferOffset, (LimiterEffect)effect, nodeId, effectId);
+                    break;
                 default:
                     throw new NotImplementedException($"Unsupported effect type {effect.Type}");
             }
@@ -611,7 +632,7 @@ namespace Ryujinx.Audio.Renderer.Server
 
                 if (!effect.ShouldSkip())
                 {
-                    GenerateEffect(ref mix, effect);
+                    GenerateEffect(ref mix, effectOrder, effect);
                 }
             }
         }
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
index 81d3b57b..feb3706f 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
@@ -176,5 +176,15 @@ namespace Ryujinx.Audio.Renderer.Server
         {
             return 0;
         }
+
+        public uint Estimate(LimiterCommandVersion1 command)
+        {
+            return 0;
+        }
+
+        public uint Estimate(LimiterCommandVersion2 command)
+        {
+            return 0;
+        }
     }
 }
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
index 0f4fe3c2..227f3c81 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
@@ -540,5 +540,15 @@ namespace Ryujinx.Audio.Renderer.Server
         {
             return 0;
         }
+
+        public uint Estimate(LimiterCommandVersion1 command)
+        {
+            return 0;
+        }
+
+        public uint Estimate(LimiterCommandVersion2 command)
+        {
+            return 0;
+        }
     }
 }
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
index b48ff8b5..e00fcf7b 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
@@ -18,6 +18,7 @@
 using Ryujinx.Audio.Common;
 using Ryujinx.Audio.Renderer.Common;
 using Ryujinx.Audio.Renderer.Dsp.Command;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
 using System;
 using System.Diagnostics;
 using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
@@ -632,5 +633,127 @@ namespace Ryujinx.Audio.Renderer.Server
                     throw new NotImplementedException($"{format}");
             }
         }
+
+        private uint EstimateLimiterCommandCommon(LimiterParameter parameter, bool enabled)
+        {
+            Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+            if (_sampleCount == 160)
+            {
+                if (enabled)
+                {
+                    switch (parameter.ChannelCount)
+                    {
+                        case 1:
+                            return (uint)21392.0f;
+                        case 2:
+                            return (uint)26829.0f;
+                        case 4:
+                            return (uint)32405.0f;
+                        case 6:
+                            return (uint)52219.0f;
+                        default:
+                            throw new NotImplementedException($"{parameter.ChannelCount}");
+                    }
+                }
+                else
+                {
+                    switch (parameter.ChannelCount)
+                    {
+                        case 1:
+                            return (uint)897.0f;
+                        case 2:
+                            return (uint)931.55f;
+                        case 4:
+                            return (uint)975.39f;
+                        case 6:
+                            return (uint)1016.8f;
+                        default:
+                            throw new NotImplementedException($"{parameter.ChannelCount}");
+                    }
+                }
+            }
+
+            if (enabled)
+            {
+                switch (parameter.ChannelCount)
+                {
+                    case 1:
+                        return (uint)30556.0f;
+                    case 2:
+                        return (uint)39011.0f;
+                    case 4:
+                        return (uint)48270.0f;
+                    case 6:
+                        return (uint)76712.0f;
+                    default:
+                        throw new NotImplementedException($"{parameter.ChannelCount}");
+                }
+            }
+            else
+            {
+                switch (parameter.ChannelCount)
+                {
+                    case 1:
+                        return (uint)874.43f;
+                    case 2:
+                        return (uint)921.55f;
+                    case 4:
+                        return (uint)945.26f;
+                    case 6:
+                        return (uint)992.26f;
+                    default:
+                        throw new NotImplementedException($"{parameter.ChannelCount}");
+                }
+            }
+        }
+
+        public uint Estimate(LimiterCommandVersion1 command)
+        {
+            Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+            return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled);
+        }
+
+        public uint Estimate(LimiterCommandVersion2 command)
+        {
+            Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+            if (!command.Parameter.StatisticsEnabled || !command.IsEffectEnabled)
+            {
+                return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled);
+            }
+
+            if (_sampleCount == 160)
+            {
+                switch (command.Parameter.ChannelCount)
+                {
+                    case 1:
+                        return (uint)23309.0f;
+                    case 2:
+                        return (uint)29954.0f;
+                    case 4:
+                        return (uint)35807.0f;
+                    case 6:
+                        return (uint)58340.0f;
+                    default:
+                        throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+                }
+            }
+
+            switch (command.Parameter.ChannelCount)
+            {
+                case 1:
+                    return (uint)33526.0f;
+                case 2:
+                    return (uint)43549.0f;
+                case 4:
+                    return (uint)52190.0f;
+                case 6:
+                    return (uint)85527.0f;
+                default:
+                    throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+            }
+        }
     }
 }
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs
index df82945b..eac1708e 100644
--- a/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs
+++ b/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs
@@ -50,7 +50,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
             return WorkBuffers[index].GetReference(true);
         }
 
-        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
         {
             Debug.Assert(IsTypeValid(ref parameter));
 
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
index 7529f894..c7b06e7a 100644
--- a/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
+++ b/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
@@ -98,7 +98,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
         /// </summary>
         /// <param name="parameter">The user parameter.</param>
         /// <returns>Returns true if the <see cref="EffectType"/> sent by the user matches the internal <see cref="EffectType"/>.</returns>
-        public bool IsTypeValid(ref EffectInParameter parameter)
+        public bool IsTypeValid<T>(ref T parameter) where T: unmanaged, IEffectInParameter
         {
             return parameter.Type == TargetEffectType;
         }
@@ -115,7 +115,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
         /// Update the internal common parameters from a user parameter.
         /// </summary>
         /// <param name="parameter">The user parameter.</param>
-        protected void UpdateParameterBase(ref EffectInParameter parameter)
+        protected void UpdateParameterBase<T>(ref T parameter) where T : unmanaged, IEffectInParameter
         {
             MixId = parameter.MixId;
             ProcessingOrder = parameter.ProcessingOrder;
@@ -154,12 +154,38 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
         }
 
         /// <summary>
-        /// Update the internal state from a user parameter.
+        /// Initialize the given <paramref name="state"/> result state.
+        /// </summary>
+        /// <param name="state">The state to initalize</param>
+        public virtual void InitializeResultState(ref EffectResultState state) {}
+
+        /// <summary>
+        /// Update the <paramref name="destState"/> result state with <paramref name="srcState"/>.
+        /// </summary>
+        /// <param name="destState">The destination result state</param>
+        /// <param name="srcState">The source result state</param>
+        public virtual void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState) {}
+
+        /// <summary>
+        /// Update the internal state from a user version 1 parameter.
         /// </summary>
         /// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
         /// <param name="parameter">The user parameter.</param>
         /// <param name="mapper">The mapper to use.</param>
-        public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+        public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
+        {
+            Debug.Assert(IsTypeValid(ref parameter));
+
+            updateErrorInfo = new ErrorInfo();
+        }
+
+        /// <summary>
+        /// Update the internal state from a user version 2 parameter.
+        /// </summary>
+        /// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
+        /// <param name="parameter">The user parameter.</param>
+        /// <param name="mapper">The mapper to use.</param>
+        public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
         {
             Debug.Assert(IsTypeValid(ref parameter));
 
@@ -206,26 +232,26 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
         /// </summary>
         /// <param name="outStatus">The given user output.</param>
         /// <param name="isAudioRendererActive">If set to true, the <see cref="AudioRenderSystem"/> is active.</param>
-        public void StoreStatus(ref EffectOutStatus outStatus, bool isAudioRendererActive)
+        public void StoreStatus<T>(ref T outStatus, bool isAudioRendererActive) where T: unmanaged, IEffectOutStatus
         {
             if (isAudioRendererActive)
             {
                 if (UsageState == UsageState.Disabled)
                 {
-                    outStatus.State = EffectOutStatus.EffectState.Disabled;
+                    outStatus.State = EffectState.Disabled;
                 }
                 else
                 {
-                    outStatus.State = EffectOutStatus.EffectState.Enabled;
+                    outStatus.State = EffectState.Enabled;
                 }
             }
             else if (UsageState == UsageState.New)
             {
-                outStatus.State = EffectOutStatus.EffectState.Enabled;
+                outStatus.State = EffectState.Enabled;
             }
             else
             {
-                outStatus.State = EffectOutStatus.EffectState.Disabled;
+                outStatus.State = EffectState.Disabled;
             }
         }
 
@@ -249,6 +275,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
                     return PerformanceDetailType.Reverb3d;
                 case EffectType.BufferMix:
                     return PerformanceDetailType.Mix;
+                case EffectType.Limiter:
+                    return PerformanceDetailType.Limiter;
                 default:
                     throw new NotImplementedException($"{Type}");
             }
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs
index 35ba8a0d..6c91b607 100644
--- a/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs
+++ b/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs
@@ -52,7 +52,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
 
         public override EffectType TargetEffectType => EffectType.BiquadFilter;
 
-        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
         {
             Debug.Assert(IsTypeValid(ref parameter));
 
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs
index 14ed3950..05cccad9 100644
--- a/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs
+++ b/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs
@@ -36,7 +36,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
 
         public override EffectType TargetEffectType => EffectType.BufferMix;
 
-        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
         {
             Debug.Assert(IsTypeValid(ref parameter));
 
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs
index df3e5ee7..a5b3dbc5 100644
--- a/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs
+++ b/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs
@@ -54,7 +54,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
             return GetSingleBuffer();
         }
 
-        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
         {
             Debug.Assert(IsTypeValid(ref parameter));
 
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs b/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs
index ff6051ae..074f4375 100644
--- a/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs
+++ b/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs
@@ -15,6 +15,9 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 //
 
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Utils;
+using System;
 using System.Diagnostics;
 
 namespace Ryujinx.Audio.Renderer.Server.Effect
@@ -34,6 +37,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
         /// </summary>
         private uint _effectCount;
 
+        private EffectResultState[] _resultStatesCpu;
+        private EffectResultState[] _resultStatesDsp;
+
         /// <summary>
         /// Create a new <see cref="EffectContext"/>.
         /// </summary>
@@ -47,7 +53,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
         /// Initialize the <see cref="EffectContext"/>.
         /// </summary>
         /// <param name="effectCount">The total effect count.</param>
-        public void Initialize(uint effectCount)
+        /// <param name="resultStateCount">The total result state count.</param>
+        public void Initialize(uint effectCount, uint resultStateCount)
         {
             _effectCount = effectCount;
             _effects = new BaseEffect[effectCount];
@@ -56,6 +63,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
             {
                 _effects[i] = new BaseEffect();
             }
+
+            _resultStatesCpu = new EffectResultState[resultStateCount];
+            _resultStatesDsp = new EffectResultState[resultStateCount];
         }
 
         /// <summary>
@@ -78,5 +88,53 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
 
             return ref _effects[index];
         }
+
+        /// <summary>
+        /// Get a reference to a <see cref="EffectResultState"/> at the given <paramref name="index"/>.
+        /// </summary>
+        /// <param name="index">The index to use.</param>
+        /// <returns>A reference to a <see cref="EffectResultState"/> at the given <paramref name="index"/>.</returns>
+        /// <remarks>The returned <see cref="EffectResultState"/> should only be used when updating the server state.</remarks>
+        public ref EffectResultState GetState(int index)
+        {
+            Debug.Assert(index >= 0 && index < _resultStatesCpu.Length);
+
+            return ref _resultStatesCpu[index];
+        }
+
+        /// <summary>
+        /// Get a reference to a <see cref="EffectResultState"/> at the given <paramref name="index"/>.
+        /// </summary>
+        /// <param name="index">The index to use.</param>
+        /// <returns>A reference to a <see cref="EffectResultState"/> at the given <paramref name="index"/>.</returns>
+        /// <remarks>The returned <see cref="EffectResultState"/> should only be used in the context of processing on the <see cref="Dsp.AudioProcessor"/>.</remarks>
+        public ref EffectResultState GetDspState(int index)
+        {
+            Debug.Assert(index >= 0 && index < _resultStatesDsp.Length);
+
+            return ref _resultStatesDsp[index];
+        }
+
+        /// <summary>
+        /// Get a memory instance to a <see cref="EffectResultState"/> at the given <paramref name="index"/>.
+        /// </summary>
+        /// <param name="index">The index to use.</param>
+        /// <returns>A memory instance to a <see cref="EffectResultState"/> at the given <paramref name="index"/>.</returns>
+        /// <remarks>The returned <see cref="Memory{EffectResultState}"/> should only be used in the context of processing on the <see cref="Dsp.AudioProcessor"/>.</remarks>
+        public Memory<EffectResultState> GetDspStateMemory(int index)
+        {
+            return SpanIOHelper.GetMemory(_resultStatesDsp.AsMemory(), index, (uint)_resultStatesDsp.Length);
+        }
+
+        /// <summary>
+        /// Update internal state during command generation.
+        /// </summary>
+        public void UpdateResultStateForCommandGeneration()
+        {
+            for (int index = 0; index < _resultStatesCpu.Length; index++)
+            {
+                _effects[index].UpdateResultState(ref _resultStatesCpu[index], ref _resultStatesDsp[index]);
+            }
+        }
     }
 }
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs
new file mode 100644
index 00000000..fa04c027
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs
@@ -0,0 +1,112 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Effect
+{
+    /// <summary>
+    /// Server state for a limiter effect.
+    /// </summary>
+    public class LimiterEffect : BaseEffect
+    {
+        /// <summary>
+        /// The limiter parameter.
+        /// </summary>
+        public LimiterParameter Parameter;
+
+        /// <summary>
+        /// The limiter state.
+        /// </summary>
+        public Memory<LimiterState> State { get; }
+
+        /// <summary>
+        /// Create a new <see cref="LimiterEffect"/>.
+        /// </summary>
+        public LimiterEffect()
+        {
+            State = new LimiterState[1];
+        }
+
+        public override EffectType TargetEffectType => EffectType.Limiter;
+
+        public override ulong GetWorkBuffer(int index)
+        {
+            return GetSingleBuffer();
+        }
+
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
+        {
+            Debug.Assert(IsTypeValid(ref parameter));
+
+            ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast<byte, LimiterParameter>(parameter.SpecificData)[0];
+
+            updateErrorInfo = new BehaviourParameter.ErrorInfo();
+
+            UpdateParameterBase(ref parameter);
+
+            Parameter = limiterParameter;
+
+            IsEnabled = parameter.IsEnabled;
+
+            if (BufferUnmapped || parameter.IsNew)
+            {
+                UsageState = UsageState.New;
+                Parameter.Status = UsageState.Invalid;
+
+                BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize);
+            }
+        }
+
+        public override void UpdateForCommandGeneration()
+        {
+            UpdateUsageStateForCommandGeneration();
+
+            Parameter.Status = UsageState.Enabled;
+            Parameter.StatisticsReset = false;
+        }
+
+        public override void InitializeResultState(ref EffectResultState state)
+        {
+            ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(state.SpecificData)[0];
+
+            statistics.Reset();
+        }
+
+        public override void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState)
+        {
+            destState = srcState;
+        }
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs
index d9f23799..7b8fd6c4 100644
--- a/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs
+++ b/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs
@@ -53,7 +53,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
             return GetSingleBuffer();
         }
 
-        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T: unmanaged, IEffectInParameter
         {
             Debug.Assert(IsTypeValid(ref parameter));
 
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs
index 4c81f729..30908396 100644
--- a/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs
+++ b/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs
@@ -56,7 +56,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
             return GetSingleBuffer();
         }
 
-        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
+        {
+            Update(out updateErrorInfo, ref parameter, mapper);
+        }
+
+        public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
         {
             Debug.Assert(IsTypeValid(ref parameter));
 
diff --git a/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs b/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
index 119ec907..eae48be6 100644
--- a/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
+++ b/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
@@ -48,5 +48,7 @@ namespace Ryujinx.Audio.Renderer.Server
         uint Estimate(DeviceSinkCommand command);
         uint Estimate(DownMixSurroundToStereoCommand command);
         uint Estimate(UpsampleCommand command);
+        uint Estimate(LimiterCommandVersion1 command);
+        uint Estimate(LimiterCommandVersion2 command);
     }
 }
diff --git a/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs
index 3a336af3..fd7c93b1 100644
--- a/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs
+++ b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs
@@ -18,7 +18,6 @@
 using Ryujinx.Audio.Renderer.Common;
 using Ryujinx.Audio.Renderer.Parameter;
 using System;
-using System.Runtime.CompilerServices;
 
 namespace Ryujinx.Audio.Renderer.Server.Performance
 {
diff --git a/Ryujinx.Audio/Renderer/Server/StateUpdater.cs b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs
index 77935b75..e7a982c4 100644
--- a/Ryujinx.Audio/Renderer/Server/StateUpdater.cs
+++ b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs
@@ -224,7 +224,7 @@ namespace Ryujinx.Audio.Renderer.Server
             return ResultCode.Success;
         }
 
-        private static void ResetEffect(ref BaseEffect effect, ref EffectInParameter parameter, PoolMapper mapper)
+        private static void ResetEffect<T>(ref BaseEffect effect, ref T parameter, PoolMapper mapper) where T: unmanaged, IEffectInParameter
         {
             effect.ForceUnmapBuffers(mapper);
 
@@ -251,6 +251,9 @@ namespace Ryujinx.Audio.Renderer.Server
                 case EffectType.BiquadFilter:
                     effect = new BiquadFilterEffect();
                     break;
+                case EffectType.Limiter:
+                    effect = new LimiterEffect();
+                    break;
                 default:
                     throw new NotImplementedException($"EffectType {parameter.Type} not implemented!");
             }
@@ -258,14 +261,26 @@ namespace Ryujinx.Audio.Renderer.Server
 
         public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
         {
-            if (context.GetCount() * Unsafe.SizeOf<EffectInParameter>() != _inputHeader.EffectsSize)
+            if (_behaviourContext.IsEffectInfoVersion2Supported())
+            {
+                return UpdateEffectsVersion2(context, isAudioRendererActive, memoryPools);
+            }
+            else
+            {
+                return UpdateEffectsVersion1(context, isAudioRendererActive, memoryPools);
+            }
+        }
+
+        public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
+        {
+            if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion2>() != _inputHeader.EffectsSize)
             {
                 return ResultCode.InvalidUpdateInfo;
             }
 
             int initialOutputSize = _output.Length;
 
-            ReadOnlySpan<EffectInParameter> parameters = MemoryMarshal.Cast<byte, EffectInParameter>(_input.Slice(0, (int)_inputHeader.EffectsSize).Span);
+            ReadOnlySpan<EffectInParameterVersion2> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion2>(_input.Slice(0, (int)_inputHeader.EffectsSize).Span);
 
             _input = _input.Slice((int)_inputHeader.EffectsSize);
 
@@ -273,9 +288,65 @@ namespace Ryujinx.Audio.Renderer.Server
 
             for (int i = 0; i < context.GetCount(); i++)
             {
-                EffectInParameter parameter = parameters[i];
+                EffectInParameterVersion2 parameter = parameters[i];
 
-                ref EffectOutStatus outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatus>(ref _output)[0];
+                ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion2>(ref _output)[0];
+
+                ref BaseEffect effect = ref context.GetEffect(i);
+
+                if (!effect.IsTypeValid(ref parameter))
+                {
+                    ResetEffect(ref effect, ref parameter, mapper);
+                }
+
+                effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper);
+
+                if (updateErrorInfo.ErrorCode != ResultCode.Success)
+                {
+                    _behaviourContext.AppendError(ref updateErrorInfo);
+                }
+
+                effect.StoreStatus(ref outStatus, isAudioRendererActive);
+
+                if (parameter.IsNew)
+                {
+                    effect.InitializeResultState(ref context.GetDspState(i));
+                    effect.InitializeResultState(ref context.GetState(i));
+                }
+
+                effect.UpdateResultState(ref outStatus.ResultState, ref context.GetState(i));
+            }
+
+            int currentOutputSize = _output.Length;
+
+            OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf<EffectOutStatusVersion2>() * context.GetCount());
+            OutputHeader.TotalSize += OutputHeader.EffectsSize;
+
+            Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
+
+            return ResultCode.Success;
+        }
+
+        public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
+        {
+            if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion1>() != _inputHeader.EffectsSize)
+            {
+                return ResultCode.InvalidUpdateInfo;
+            }
+
+            int initialOutputSize = _output.Length;
+
+            ReadOnlySpan<EffectInParameterVersion1> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion1>(_input.Slice(0, (int)_inputHeader.EffectsSize).Span);
+
+            _input = _input.Slice((int)_inputHeader.EffectsSize);
+
+            PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
+
+            for (int i = 0; i < context.GetCount(); i++)
+            {
+                EffectInParameterVersion1 parameter = parameters[i];
+
+                ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion1>(ref _output)[0];
 
                 ref BaseEffect effect = ref context.GetEffect(i);
 
@@ -296,7 +367,7 @@ namespace Ryujinx.Audio.Renderer.Server
 
             int currentOutputSize = _output.Length;
 
-            OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf<EffectOutStatus>() * context.GetCount());
+            OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf<EffectOutStatusVersion1>() * context.GetCount());
             OutputHeader.TotalSize += OutputHeader.EffectsSize;
 
             Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
diff --git a/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs
index 2b482cdb..c4ac82f0 100644
--- a/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs
+++ b/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs
@@ -9,7 +9,8 @@ namespace Ryujinx.Tests.Audio.Renderer
         [Test]
         public void EnsureTypeSize()
         {
-            Assert.AreEqual(0xC0, Unsafe.SizeOf<EffectInParameter>());
+            Assert.AreEqual(0xC0, Unsafe.SizeOf<EffectInParameterVersion1>());
+            Assert.AreEqual(0xC0, Unsafe.SizeOf<EffectInParameterVersion2>());
         }
     }
 }
diff --git a/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs b/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs
index 199bcf8a..8cb57da3 100644
--- a/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs
+++ b/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs
@@ -9,7 +9,8 @@ namespace Ryujinx.Tests.Audio.Renderer
         [Test]
         public void EnsureTypeSize()
         {
-            Assert.AreEqual(0x10, Unsafe.SizeOf<EffectOutStatus>());
+            Assert.AreEqual(0x10, Unsafe.SizeOf<EffectOutStatusVersion1>());
+            Assert.AreEqual(0x90, Unsafe.SizeOf<EffectOutStatusVersion2>());
         }
     }
 }
diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterParameterTests.cs
new file mode 100644
index 00000000..8512ebd4
--- /dev/null
+++ b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterParameterTests.cs
@@ -0,0 +1,16 @@
+using NUnit.Framework;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect
+{
+    class LimiterParameterTests
+    {
+        [Test]
+        public void EnsureTypeSize()
+        {
+            Assert.AreEqual(0x44, Unsafe.SizeOf<LimiterParameter>());
+        }
+    }
+}
+
diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterStatisticsTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterStatisticsTests.cs
new file mode 100644
index 00000000..43645ae4
--- /dev/null
+++ b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterStatisticsTests.cs
@@ -0,0 +1,16 @@
+using NUnit.Framework;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect
+{
+    class LimiterStatisticsTests
+    {
+        [Test]
+        public void EnsureTypeSize()
+        {
+            Assert.AreEqual(0x30, Unsafe.SizeOf<LimiterStatistics>());
+        }
+    }
+}
+