diff --git a/Ryujinx.Audio/Renderer/Common/EffectType.cs b/Ryujinx.Audio/Renderer/Common/EffectType.cs
index 082f94f84d..daa2203606 100644
--- a/Ryujinx.Audio/Renderer/Common/EffectType.cs
+++ b/Ryujinx.Audio/Renderer/Common/EffectType.cs
@@ -55,6 +55,11 @@ namespace Ryujinx.Audio.Renderer.Common
///
/// Effect applying a biquad filter.
///
- BiquadFilter
+ BiquadFilter,
+
+ ///
+ /// Effect applying a limiter (DRC).
+ ///
+ Limiter,
}
}
diff --git a/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs b/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
index a92bd0cc4a..e9e946ce4b 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 8ff1c5817d..997a080e85 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 0000000000..975e61f950
--- /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 .
+//
+
+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 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 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[] inputBuffers = new ReadOnlyMemory[Parameter.ChannelCount];
+ Memory[] outputBuffers = new Memory[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 0000000000..0a4b14b746
--- /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 .
+//
+
+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 State { get; }
+ public Memory 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 state, Memory 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(ResultState.Span[0].SpecificData)[0];
+
+ statistics.Reset();
+ }
+
+ ReadOnlyMemory[] inputBuffers = new ReadOnlyMemory[Parameter.ChannelCount];
+ Memory[] outputBuffers = new Memory[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(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 0000000000..53913bad0f
--- /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 .
+//
+
+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 5a7479223d..c30c4013f0 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
{
///
- /// for .
+ /// for .
///
[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 7d58d4be8c..39d75b6915 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
{
///
- /// for .
+ /// for .
///
[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 b0c0c33742..ef298e3dc5 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
{
///
- /// for .
+ /// for .
///
[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 e0dbeb7ca6..80e5df25b6 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
{
///
- /// for .
+ /// for .
///
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DelayParameter
@@ -103,7 +103,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// Returns true if the is valid.
public bool IsChannelCountValid()
{
- return EffectInParameter.IsChannelCountValid(ChannelCount);
+ return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
}
///
@@ -112,7 +112,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// Returns true if the is valid.
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 0000000000..2caf23f9d3
--- /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 .
+//
+
+using Ryujinx.Audio.Renderer.Server.Effect;
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Effect
+{
+ ///
+ /// for .
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct LimiterParameter
+ {
+ ///
+ /// The input channel indices that will be used by the .
+ ///
+ public Array6 Input;
+
+ ///
+ /// The output channel indices that will be used by the .
+ ///
+ public Array6 Output;
+
+ ///
+ /// The maximum number of channels supported.
+ ///
+ public ushort ChannelCountMax;
+
+ ///
+ /// The total channel count used.
+ ///
+ public ushort ChannelCount;
+
+ ///
+ /// The target sample rate.
+ ///
+ /// This is in kHz.
+ public int SampleRate;
+
+ ///
+ /// The look ahead max time.
+ /// This is in microseconds.
+ ///
+ public int LookAheadTimeMax;
+
+ ///
+ /// The attack time.
+ /// This is in microseconds.
+ ///
+ public int AttackTime;
+
+ ///
+ /// The release time.
+ /// This is in microseconds.
+ ///
+ public int ReleaseTime;
+
+ ///
+ /// The look ahead time.
+ /// This is in microseconds.
+ ///
+ public int LookAheadTime;
+
+ ///
+ /// The attack coefficient.
+ ///
+ public float AttackCoefficient;
+
+ ///
+ /// The release coefficient.
+ ///
+ public float ReleaseCoefficient;
+
+ ///
+ /// The threshold.
+ ///
+ public float Threshold;
+
+ ///
+ /// The input gain.
+ ///
+ public float InputGain;
+
+ ///
+ /// The output gain.
+ ///
+ public float OutputGain;
+
+ ///
+ /// The minimum samples stored in the delay buffer.
+ ///
+ public int DelayBufferSampleCountMin;
+
+ ///
+ /// The maximum samples stored in the delay buffer.
+ ///
+ public int DelayBufferSampleCountMax;
+
+ ///
+ /// The current usage status of the effect on the client side.
+ ///
+ public UsageState Status;
+
+ ///
+ /// Indicate if the limiter effect should output statistics.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool StatisticsEnabled;
+
+ ///
+ /// Indicate to the DSP that the user did a statistics reset.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool StatisticsReset;
+
+ ///
+ /// Reserved/padding.
+ ///
+ private byte _reserved;
+
+ ///
+ /// Check if the is valid.
+ ///
+ /// Returns true if the is valid.
+ public bool IsChannelCountValid()
+ {
+ return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
+ }
+
+ ///
+ /// Check if the is valid.
+ ///
+ /// Returns true if the is valid.
+ 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 0000000000..faf12f2aaa
--- /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 .
+//
+
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Effect
+{
+ ///
+ /// Effect result state for .
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct LimiterStatistics
+ {
+ ///
+ /// The max input sample value recorded by the limiter.
+ ///
+ public Array6 InputMax;
+
+ ///
+ /// Compression gain min value.
+ ///
+ public Array6 CompressionGainMin;
+
+ ///
+ /// Reset the statistics.
+ ///
+ 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 0dbecfb885..9b775fff3b 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
{
///
- /// for .
+ /// for .
///
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Reverb3dParameter
@@ -129,7 +129,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// Returns true if the is valid.
public bool IsChannelCountValid()
{
- return EffectInParameter.IsChannelCountValid(ChannelCount);
+ return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
}
///
@@ -138,7 +138,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// Returns true if the is valid.
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 daa81c5117..dea54407d8 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
{
///
- /// for .
+ /// for .
///
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ReverbParameter
@@ -121,7 +121,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// Returns true if the is valid.
public bool IsChannelCountValid()
{
- return EffectInParameter.IsChannelCountValid(ChannelCount);
+ return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
}
///
@@ -130,7 +130,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// Returns true if the is valid.
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 af8edeff45..ec2d801e2d 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
{
///
- /// Input information for an effect.
+ /// Input information for an effect version 1.
///
[StructLayout(LayoutKind.Sequential, Pack = 1)]
- public struct EffectInParameter
+ public struct EffectInParameterVersion1 : IEffectInParameter
{
///
/// Type of the effect.
@@ -85,11 +85,22 @@ namespace Ryujinx.Audio.Renderer.Parameter
[StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)]
private struct SpecificDataStruct { }
- ///
- /// Specific data changing depending of the . See also the namespace.
- ///
public Span SpecificData => SpanHelpers.AsSpan(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;
+
///
/// Check if the given channel count is valid.
///
diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs b/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs
new file mode 100644
index 0000000000..ea74f1e646
--- /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 .
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ ///
+ /// Input information for an effect version 2. (added with REV9)
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct EffectInParameterVersion2 : IEffectInParameter
+ {
+ ///
+ /// Type of the effect.
+ ///
+ public EffectType Type;
+
+ ///
+ /// Set to true if the effect is new.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsNew;
+
+ ///
+ /// Set to true if the effect must be active.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsEnabled;
+
+ ///
+ /// Reserved/padding.
+ ///
+ private byte _reserved1;
+
+ ///
+ /// The target mix id of the effect.
+ ///
+ public int MixId;
+
+ ///
+ /// Address of the processing workbuffer.
+ ///
+ /// This is additional data that could be required by the effect processing.
+ public ulong BufferBase;
+
+ ///
+ /// Size of the processing workbuffer.
+ ///
+ /// This is additional data that could be required by the effect processing.
+ public ulong BufferSize;
+
+ ///
+ /// Position of the effect while processing effects.
+ ///
+ public uint ProcessingOrder;
+
+ ///
+ /// Reserved/padding.
+ ///
+ private uint _reserved2;
+
+ ///
+ /// Specific data storage.
+ ///
+ private SpecificDataStruct _specificDataStart;
+
+ [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)]
+ private struct SpecificDataStruct { }
+
+ public Span SpecificData => SpanHelpers.AsSpan(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;
+
+ ///
+ /// Check if the given channel count is valid.
+ ///
+ /// The channel count to check
+ /// Returns true if the channel count is valid.
+ 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 0000000000..0c26d2b91d
--- /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 .
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ ///
+ /// Output information for an effect version 1.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct EffectOutStatusVersion1 : IEffectOutStatus
+ {
+ ///
+ /// Current effect state.
+ ///
+ public EffectState State;
+
+ ///
+ /// Unused/Reserved.
+ ///
+ 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 c617816529..59eeae9b35 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
{
///
- /// Output information for an effect.
+ /// Output information for an effect version 2. (added with REV9)
///
[StructLayout(LayoutKind.Sequential, Pack = 1)]
- public struct EffectOutStatus
+ public struct EffectOutStatusVersion2 : IEffectOutStatus
{
- ///
- /// The state of an effect.
- ///
- public enum EffectState : byte
- {
- ///
- /// The effect is enabled.
- ///
- Enabled = 3,
-
- ///
- /// The effect is disabled.
- ///
- Disabled = 4
- }
-
///
/// Current effect state.
///
@@ -50,5 +34,12 @@ namespace Ryujinx.Audio.Renderer.Parameter
/// Unused/Reserved.
///
private unsafe fixed byte _reserved[15];
+
+ ///
+ /// Current result state.
+ ///
+ 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 0000000000..64a6f50ac9
--- /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 .
+//
+
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ ///
+ /// Effect result state (added in REV9).
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct EffectResultState
+ {
+ ///
+ /// Specific data storage.
+ ///
+ private SpecificDataStruct _specificDataStart;
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x80, Pack = 1)]
+ private struct SpecificDataStruct { }
+
+ ///
+ /// Specific data changing depending of the type of effect. See also the namespace.
+ ///
+ public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart);
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectState.cs b/Ryujinx.Audio/Renderer/Parameter/EffectState.cs
new file mode 100644
index 0000000000..685d0cdb4f
--- /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 .
+//
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ ///
+ /// The state of an effect.
+ ///
+ public enum EffectState : byte
+ {
+ ///
+ /// The effect is enabled.
+ ///
+ Enabled = 3,
+
+ ///
+ /// The effect is disabled.
+ ///
+ Disabled = 4
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs
new file mode 100644
index 0000000000..779d829804
--- /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 .
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ ///
+ /// Generic interface to represent input information for an effect.
+ ///
+ public interface IEffectInParameter
+ {
+ ///
+ /// Type of the effect.
+ ///
+ EffectType Type { get; }
+
+ ///
+ /// Set to true if the effect is new.
+ ///
+ bool IsNew { get; }
+
+ ///
+ /// Set to true if the effect must be active.
+ ///
+ bool IsEnabled { get; }
+
+ ///
+ /// The target mix id of the effect.
+ ///
+ int MixId { get; }
+
+ ///
+ /// Address of the processing workbuffer.
+ ///
+ /// This is additional data that could be required by the effect processing.
+ ulong BufferBase { get; }
+
+ ///
+ /// Size of the processing workbuffer.
+ ///
+ /// This is additional data that could be required by the effect processing.
+ ulong BufferSize { get; }
+
+ ///
+ /// Position of the effect while processing effects.
+ ///
+ uint ProcessingOrder { get; }
+
+ ///
+ /// Specific data changing depending of the . See also the namespace.
+ ///
+ Span SpecificData { get; }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs
new file mode 100644
index 0000000000..29220a9d4e
--- /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 .
+//
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ ///
+ /// Generic interface to represent output information for an effect.
+ ///
+ public interface IEffectOutStatus
+ {
+ ///
+ /// Current effect state.
+ ///
+ EffectState State { get; set; }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
index 112b0e442a..943a2d7804 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 voiceUpdateStatesDsp = workBufferAllocator.Allocate(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 b31f9e9f9a..ed1f402ebe 100644
--- a/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
+++ b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
@@ -89,10 +89,18 @@ namespace Ryujinx.Audio.Renderer.Server
/// This was added in system update 9.0.0
public const int Revision8 = 8 << 24;
+ ///
+ /// 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 .
+ ///
+ /// This was added in system update 12.0.0
+ public const int Revision9 = 9 << 24;
+
///
/// Last revision supported by the implementation.
///
- public const int LastRevision = Revision8;
+ public const int LastRevision = Revision9;
///
/// Target revision magic supported by the implementation.
@@ -330,6 +338,15 @@ namespace Ryujinx.Audio.Renderer.Server
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8);
}
+ ///
+ /// Check if the audio renderer should use the new effect info format.
+ ///
+ /// True if the audio renderer should use the new effect info format.
+ public bool IsEffectInfoVersion2Supported()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision9);
+ }
+
///
/// Get the version of the .
///
diff --git a/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
index ca37e09038..24cc93af0a 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
@@ -371,6 +371,49 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
+ ///
+ /// Generate a new .
+ ///
+ /// The target buffer offset.
+ /// The limiter parameter.
+ /// The limiter state.
+ /// Set to true if the effect should be active.
+ /// The work buffer to use for processing.
+ /// The node id associated to this command.
+ public void GenerateLimiterEffectVersion1(uint bufferOffset, LimiterParameter parameter, Memory 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);
+ }
+ }
+
+ ///
+ /// Generate a new .
+ ///
+ /// The target buffer offset.
+ /// The limiter parameter.
+ /// The limiter state.
+ /// The DSP effect result state.
+ /// Set to true if the effect should be active.
+ /// The work buffer to use for processing.
+ /// The node id associated to this command.
+ public void GenerateLimiterEffectVersion2(uint bufferOffset, LimiterParameter parameter, Memory state, Memory 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);
+ }
+ }
+
///
/// Generate a new .
///
diff --git a/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
index d2499c1da0..01e7c92763 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 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 81d3b57bbb..feb3706fd2 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 0f4fe3c2ca..227f3c8157 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 b48ff8b5e0..e00fcf7b1f 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 df82945bca..eac1708ed5 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(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 7529f894c4..c7b06e7aae 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
///
/// The user parameter.
/// Returns true if the sent by the user matches the internal .
- public bool IsTypeValid(ref EffectInParameter parameter)
+ public bool IsTypeValid(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.
///
/// The user parameter.
- protected void UpdateParameterBase(ref EffectInParameter parameter)
+ protected void UpdateParameterBase(ref T parameter) where T : unmanaged, IEffectInParameter
{
MixId = parameter.MixId;
ProcessingOrder = parameter.ProcessingOrder;
@@ -154,12 +154,38 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
}
///
- /// Update the internal state from a user parameter.
+ /// Initialize the given result state.
+ ///
+ /// The state to initalize
+ public virtual void InitializeResultState(ref EffectResultState state) {}
+
+ ///
+ /// Update the result state with .
+ ///
+ /// The destination result state
+ /// The source result state
+ public virtual void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState) {}
+
+ ///
+ /// Update the internal state from a user version 1 parameter.
///
/// The possible that was generated.
/// The user parameter.
/// The mapper to use.
- 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();
+ }
+
+ ///
+ /// Update the internal state from a user version 2 parameter.
+ ///
+ /// The possible that was generated.
+ /// The user parameter.
+ /// The mapper to use.
+ 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
///
/// The given user output.
/// If set to true, the is active.
- public void StoreStatus(ref EffectOutStatus outStatus, bool isAudioRendererActive)
+ public void StoreStatus(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 35ba8a0d92..6c91b6070e 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(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 14ed3950b3..05cccad919 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(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 df3e5ee757..a5b3dbc5bb 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(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 ff6051aecc..074f4375a2 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 .
//
+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
///
private uint _effectCount;
+ private EffectResultState[] _resultStatesCpu;
+ private EffectResultState[] _resultStatesDsp;
+
///
/// Create a new .
///
@@ -47,7 +53,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
/// Initialize the .
///
/// The total effect count.
- public void Initialize(uint effectCount)
+ /// The total result state count.
+ 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];
}
///
@@ -78,5 +88,53 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return ref _effects[index];
}
+
+ ///
+ /// Get a reference to a at the given .
+ ///
+ /// The index to use.
+ /// A reference to a at the given .
+ /// The returned should only be used when updating the server state.
+ public ref EffectResultState GetState(int index)
+ {
+ Debug.Assert(index >= 0 && index < _resultStatesCpu.Length);
+
+ return ref _resultStatesCpu[index];
+ }
+
+ ///
+ /// Get a reference to a at the given .
+ ///
+ /// The index to use.
+ /// A reference to a at the given .
+ /// The returned should only be used in the context of processing on the .
+ public ref EffectResultState GetDspState(int index)
+ {
+ Debug.Assert(index >= 0 && index < _resultStatesDsp.Length);
+
+ return ref _resultStatesDsp[index];
+ }
+
+ ///
+ /// Get a memory instance to a at the given .
+ ///
+ /// The index to use.
+ /// A memory instance to a at the given .
+ /// The returned should only be used in the context of processing on the .
+ public Memory GetDspStateMemory(int index)
+ {
+ return SpanIOHelper.GetMemory(_resultStatesDsp.AsMemory(), index, (uint)_resultStatesDsp.Length);
+ }
+
+ ///
+ /// Update internal state during command generation.
+ ///
+ 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 0000000000..fa04c02736
--- /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 .
+//
+
+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
+{
+ ///
+ /// Server state for a limiter effect.
+ ///
+ public class LimiterEffect : BaseEffect
+ {
+ ///
+ /// The limiter parameter.
+ ///
+ public LimiterParameter Parameter;
+
+ ///
+ /// The limiter state.
+ ///
+ public Memory State { get; }
+
+ ///
+ /// Create a new .
+ ///
+ 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(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
+ {
+ Debug.Assert(IsTypeValid(ref parameter));
+
+ ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast(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(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 d9f2379922..7b8fd6c412 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(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 4c81f7299b..309083960f 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(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 119ec90783..eae48be65d 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 3a336af35b..fd7c93b197 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 77935b754b..e7a982c4a9 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(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 memoryPools)
{
- if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize)
+ if (_behaviourContext.IsEffectInfoVersion2Supported())
+ {
+ return UpdateEffectsVersion2(context, isAudioRendererActive, memoryPools);
+ }
+ else
+ {
+ return UpdateEffectsVersion1(context, isAudioRendererActive, memoryPools);
+ }
+ }
+
+ public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, Memory memoryPools)
+ {
+ if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize)
{
return ResultCode.InvalidUpdateInfo;
}
int initialOutputSize = _output.Length;
- ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.EffectsSize).Span);
+ ReadOnlySpan parameters = MemoryMarshal.Cast(_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(ref _output)[0];
+ ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef(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() * context.GetCount());
+ OutputHeader.TotalSize += OutputHeader.EffectsSize;
+
+ Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, Memory memoryPools)
+ {
+ if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize)
+ {
+ return ResultCode.InvalidUpdateInfo;
+ }
+
+ int initialOutputSize = _output.Length;
+
+ ReadOnlySpan parameters = MemoryMarshal.Cast(_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(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() * context.GetCount());
+ OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf() * 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 2b482cdb0c..c4ac82f07b 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());
+ Assert.AreEqual(0xC0, Unsafe.SizeOf());
+ Assert.AreEqual(0xC0, Unsafe.SizeOf());
}
}
}
diff --git a/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs b/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs
index 199bcf8a70..8cb57da37e 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());
+ Assert.AreEqual(0x10, Unsafe.SizeOf());
+ Assert.AreEqual(0x90, Unsafe.SizeOf());
}
}
}
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 0000000000..8512ebd47c
--- /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());
+ }
+ }
+}
+
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 0000000000..43645ae427
--- /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());
+ }
+ }
+}
+