diff --git a/Ryujinx.Audio/Renderer/Common/EffectType.cs b/Ryujinx.Audio/Renderer/Common/EffectType.cs
index 2c50b9eb..7128db4c 100644
--- a/Ryujinx.Audio/Renderer/Common/EffectType.cs
+++ b/Ryujinx.Audio/Renderer/Common/EffectType.cs
@@ -48,6 +48,11 @@ namespace Ryujinx.Audio.Renderer.Common
///
/// Effect to capture mixes (via auxiliary buffers).
///
- CaptureBuffer
+ CaptureBuffer,
+
+ ///
+ /// Effect applying a compressor filter (DRC).
+ ///
+ Compressor,
}
}
\ No newline at end of file
diff --git a/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs b/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
index 8467ed8d..805d5518 100644
--- a/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
+++ b/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
@@ -14,6 +14,7 @@ namespace Ryujinx.Audio.Renderer.Common
Reverb3d,
PcmFloat,
Limiter,
- CaptureBuffer
+ CaptureBuffer,
+ Compressor
}
}
\ No newline at end of file
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
index dfe7f886..9ce181b1 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
@@ -31,6 +31,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
LimiterVersion1,
LimiterVersion2,
GroupedBiquadFilter,
- CaptureBuffer
+ CaptureBuffer,
+ Compressor
}
}
\ No newline at end of file
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs
new file mode 100644
index 00000000..8c344293
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Diagnostics;
+using Ryujinx.Audio.Renderer.Dsp.Effect;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class CompressorCommand : ICommand
+ {
+ private const int FixedPointPrecision = 15;
+
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Compressor;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public CompressorParameter Parameter => _parameter;
+ public Memory State { get; }
+ public ushort[] OutputBufferIndices { get; }
+ public ushort[] InputBufferIndices { get; }
+ public bool IsEffectEnabled { get; }
+
+ private CompressorParameter _parameter;
+
+ public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory state, bool isEnabled, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ _parameter = parameter;
+ State = state;
+
+ 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 CompressorState state = ref State.Span[0];
+
+ if (IsEffectEnabled)
+ {
+ if (_parameter.Status == Server.Effect.UsageState.Invalid)
+ {
+ state = new CompressorState(ref _parameter);
+ }
+ else if (_parameter.Status == Server.Effect.UsageState.New)
+ {
+ state.UpdateParameter(ref _parameter);
+ }
+ }
+
+ ProcessCompressor(context, ref state);
+ }
+
+ private unsafe void ProcessCompressor(CommandList context, ref CompressorState state)
+ {
+ Debug.Assert(_parameter.IsChannelCountValid());
+
+ if (IsEffectEnabled && _parameter.IsChannelCountValid())
+ {
+ Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+ Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+ Span channelInput = stackalloc float[Parameter.ChannelCount];
+ ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
+ float unknown4 = state.Unknown4;
+ ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
+ float previousCompressionEmaAlpha = state.PreviousCompressionEmaAlpha;
+
+ for (int i = 0; i < _parameter.ChannelCount; i++)
+ {
+ inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
+ outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
+ }
+
+ for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
+ {
+ for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
+ {
+ channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
+ }
+
+ float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
+ float y = FloatingPointHelper.Log10(newMean) * 10.0f;
+ float z = 0.0f;
+
+ bool unknown10OutOfRange = false;
+
+ if (newMean < 1.0e-10f)
+ {
+ z = 1.0f;
+
+ unknown10OutOfRange = state.Unknown10 < -100.0f;
+ }
+
+ if (y >= state.Unknown10 || unknown10OutOfRange)
+ {
+ float tmpGain;
+
+ if (y >= state.Unknown14)
+ {
+ tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold);
+ }
+ else
+ {
+ tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction);
+ }
+
+ z = FloatingPointHelper.DecibelToLinearExtended(tmpGain);
+ }
+
+ float unknown4New = z;
+ float compressionEmaAlpha;
+
+ if ((unknown4 - z) <= 0.08f)
+ {
+ compressionEmaAlpha = Parameter.ReleaseCoefficient;
+
+ if ((unknown4 - z) >= -0.08f)
+ {
+ if (MathF.Abs(compressionGainAverage.Read() - z) >= 0.001f)
+ {
+ unknown4New = unknown4;
+ }
+
+ compressionEmaAlpha = previousCompressionEmaAlpha;
+ }
+ }
+ else
+ {
+ compressionEmaAlpha = Parameter.AttackCoefficient;
+ }
+
+ float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);
+
+ for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+ {
+ *((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
+ }
+
+ unknown4 = unknown4New;
+ previousCompressionEmaAlpha = compressionEmaAlpha;
+ }
+
+ state.InputMovingAverage = inputMovingAverage;
+ state.Unknown4 = unknown4;
+ state.CompressionGainAverage = compressionGainAverage;
+ state.PreviousCompressionEmaAlpha = previousCompressionEmaAlpha;
+ }
+ else
+ {
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ if (InputBufferIndices[i] != OutputBufferIndices[i])
+ {
+ context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
index 9cfef736..a464ad70 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
@@ -90,32 +90,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float inputCoefficient = Parameter.ReleaseCoefficient;
- if (sampleInputMax > state.DectectorAverage[channelIndex])
+ if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{
inputCoefficient = Parameter.AttackCoefficient;
}
- state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
-
+ float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f;
- if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
+ if (detectorValue > Parameter.Threshold)
{
- attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
+ attenuation = Parameter.Threshold / detectorValue;
}
float outputCoefficient = Parameter.ReleaseCoefficient;
- if (state.CompressionGain[channelIndex] > attenuation)
+ if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{
outputCoefficient = Parameter.AttackCoefficient;
}
- state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
+ float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
- float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
+ float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
index 46c95e4f..950de97b 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
@@ -101,32 +101,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float inputCoefficient = Parameter.ReleaseCoefficient;
- if (sampleInputMax > state.DectectorAverage[channelIndex])
+ if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{
inputCoefficient = Parameter.AttackCoefficient;
}
- state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
-
+ float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f;
- if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
+ if (detectorValue > Parameter.Threshold)
{
- attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
+ attenuation = Parameter.Threshold / detectorValue;
}
float outputCoefficient = Parameter.ReleaseCoefficient;
- if (state.CompressionGain[channelIndex] > attenuation)
+ if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{
outputCoefficient = Parameter.AttackCoefficient;
}
- state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
+ float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
- float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
+ float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
@@ -144,7 +143,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
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]);
+ statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], compressionGain);
}
}
}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs b/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs
new file mode 100644
index 00000000..78e46bf9
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs
@@ -0,0 +1,26 @@
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Effect
+{
+ public struct ExponentialMovingAverage
+ {
+ private float _mean;
+
+ public ExponentialMovingAverage(float mean)
+ {
+ _mean = mean;
+ }
+
+ public float Read()
+ {
+ return _mean;
+ }
+
+ public float Update(float value, float alpha)
+ {
+ _mean += alpha * (value - _mean);
+
+ return _mean;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs b/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs
index 0d0ff2ae..280e47c0 100644
--- a/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs
@@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Renderer.Dsp
return (float)value / (1 << qBits);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ConvertFloat(float value, int qBits)
+ {
+ return value / (1 << qBits);
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ToFixed(float value, int qBits)
{
diff --git a/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs b/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs
index 226def46..6645e20a 100644
--- a/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs
@@ -1,4 +1,5 @@
using System;
+using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp
@@ -46,6 +47,53 @@ namespace Ryujinx.Audio.Renderer.Dsp
return MathF.Pow(10, x);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Log10(float x)
+ {
+ // NOTE: Nintendo uses an approximation of log10, we don't.
+ // As such, we support the same ranges as Nintendo to avoid unexpected behaviours.
+ return MathF.Pow(10, MathF.Max(x, 1.0e-10f));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float MeanSquare(ReadOnlySpan inputs)
+ {
+ float res = 0.0f;
+
+ foreach (float input in inputs)
+ {
+ res += (input * input);
+ }
+
+ res /= inputs.Length;
+
+ return res;
+ }
+
+ ///
+ /// Map decibel to linear.
+ ///
+ /// The decibel value to convert
+ /// Converted linear value/returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float DecibelToLinear(float db)
+ {
+ return MathF.Pow(10.0f, db / 20.0f);
+ }
+
+ ///
+ /// Map decibel to linear in [0, 2] range.
+ ///
+ /// The decibel value to convert
+ /// Converted linear value in [0, 2] range
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float DecibelToLinearExtended(float db)
+ {
+ float tmp = MathF.Log2(DecibelToLinear(db));
+
+ return MathF.Truncate(tmp) + MathF.Pow(2.0f, tmp - MathF.Truncate(tmp));
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees)
{
diff --git a/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs b/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs
new file mode 100644
index 00000000..76aff807
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs
@@ -0,0 +1,51 @@
+using Ryujinx.Audio.Renderer.Dsp.Effect;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+
+namespace Ryujinx.Audio.Renderer.Dsp.State
+{
+ public class CompressorState
+ {
+ public ExponentialMovingAverage InputMovingAverage;
+ public float Unknown4;
+ public ExponentialMovingAverage CompressionGainAverage;
+ public float CompressorGainReduction;
+ public float Unknown10;
+ public float Unknown14;
+ public float PreviousCompressionEmaAlpha;
+ public float MakeupGain;
+ public float OutputGain;
+
+ public CompressorState(ref CompressorParameter parameter)
+ {
+ InputMovingAverage = new ExponentialMovingAverage(0.0f);
+ Unknown4 = 1.0f;
+ CompressionGainAverage = new ExponentialMovingAverage(1.0f);
+
+ UpdateParameter(ref parameter);
+ }
+
+ public void UpdateParameter(ref CompressorParameter parameter)
+ {
+ float threshold = parameter.Threshold;
+ float ratio = 1.0f / parameter.Ratio;
+ float attackCoefficient = parameter.AttackCoefficient;
+ float makeupGain;
+
+ if (parameter.MakeupGainEnabled)
+ {
+ makeupGain = (threshold * 0.5f * (ratio - 1.0f)) - 3.0f;
+ }
+ else
+ {
+ makeupGain = 0.0f;
+ }
+
+ PreviousCompressionEmaAlpha = attackCoefficient;
+ MakeupGain = makeupGain;
+ CompressorGainReduction = (1.0f - ratio) / Constants.ChannelCountMax;
+ Unknown10 = threshold - 1.5f;
+ Unknown14 = threshold + 1.5f;
+ OutputGain = FloatingPointHelper.DecibelToLinearExtended(parameter.OutputGain + makeupGain);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs b/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs
index 92ed13ff..0560757c 100644
--- a/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System;
@@ -5,20 +6,20 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class LimiterState
{
- public float[] DectectorAverage;
- public float[] CompressionGain;
+ public ExponentialMovingAverage[] DetectorAverage;
+ public ExponentialMovingAverage[] CompressionGainAverage;
public float[] DelayedSampleBuffer;
public int[] DelayedSampleBufferPosition;
public LimiterState(ref LimiterParameter parameter, ulong workBuffer)
{
- DectectorAverage = new float[parameter.ChannelCount];
- CompressionGain = new float[parameter.ChannelCount];
+ DetectorAverage = new ExponentialMovingAverage[parameter.ChannelCount];
+ CompressionGainAverage = new ExponentialMovingAverage[parameter.ChannelCount];
DelayedSampleBuffer = new float[parameter.ChannelCount * parameter.DelayBufferSampleCountMax];
DelayedSampleBufferPosition = new int[parameter.ChannelCount];
- DectectorAverage.AsSpan().Fill(0.0f);
- CompressionGain.AsSpan().Fill(1.0f);
+ DetectorAverage.AsSpan().Fill(new ExponentialMovingAverage(0.0f));
+ CompressionGainAverage.AsSpan().Fill(new ExponentialMovingAverage(1.0f));
DelayedSampleBufferPosition.AsSpan().Fill(0);
DelayedSampleBuffer.AsSpan().Fill(0.0f);
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs
new file mode 100644
index 00000000..0be37608
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs
@@ -0,0 +1,115 @@
+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 CompressorParameter
+ {
+ ///
+ /// 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 threshold.
+ ///
+ public float Threshold;
+
+ ///
+ /// The compressor ratio.
+ ///
+ public float Ratio;
+
+ ///
+ /// The attack time.
+ /// This is in microseconds.
+ ///
+ public int AttackTime;
+
+ ///
+ /// The release time.
+ /// This is in microseconds.
+ ///
+ public int ReleaseTime;
+
+ ///
+ /// The input gain.
+ ///
+ public float InputGain;
+
+ ///
+ /// The attack coefficient.
+ ///
+ public float AttackCoefficient;
+
+ ///
+ /// The release coefficient.
+ ///
+ public float ReleaseCoefficient;
+
+ ///
+ /// The output gain.
+ ///
+ public float OutputGain;
+
+ ///
+ /// The current usage status of the effect on the client side.
+ ///
+ public UsageState Status;
+
+ ///
+ /// Indicate if the makeup gain should be used.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool MakeupGainEnabled;
+
+ ///
+ /// Reserved/padding.
+ ///
+ private Array2 _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/Server/BehaviourContext.cs b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
index adf5294e..821947a9 100644
--- a/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
+++ b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
@@ -44,7 +44,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// was added to supply the count of update done sent to the DSP.
/// A new version of the command estimator was added to address timing changes caused by the voice changes.
/// Additionally, the rendering limit percent was incremented to 80%.
- ///
+ ///
///
/// This was added in system update 6.0.0
public const int Revision5 = 5 << 24;
@@ -93,6 +93,7 @@ namespace Ryujinx.Audio.Renderer.Server
///
/// REV11:
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
+ /// A new effect was added: Compressor. This effect is effectively implemented with a DRC.
/// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
/// A voice drop parameter was added in 15.0.0: This allows an application to amplify or attenuate the estimated time of DSP commands.
///
diff --git a/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
index e0741cc6..905cb205 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
@@ -469,6 +469,18 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
+ public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory state, bool isEnabled, int nodeId)
+ {
+ if (parameter.IsChannelCountValid())
+ {
+ CompressorCommand command = new CompressorCommand(bufferOffset, parameter, state, isEnabled, 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 87e5c77f..afc1e39b 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
@@ -606,6 +606,17 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
+ private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId)
+ {
+ Debug.Assert(effect.Type == EffectType.Compressor);
+
+ _commandBuffer.GenerateCompressorEffect(bufferOffset,
+ effect.Parameter,
+ effect.State,
+ effect.IsEnabled,
+ nodeId);
+ }
+
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
{
int nodeId = mix.NodeId;
@@ -650,6 +661,9 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.CaptureBuffer:
GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
break;
+ case EffectType.Compressor:
+ GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId);
+ break;
default:
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
}
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
index 32c52dc4..63dc9ca9 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
@@ -179,5 +179,10 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public uint Estimate(CompressorCommand command)
+ {
+ return 0;
+ }
}
}
\ No newline at end of file
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
index 15800c99..7ee491cd 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
@@ -543,5 +543,10 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public uint Estimate(CompressorCommand command)
+ {
+ return 0;
+ }
}
}
\ No newline at end of file
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
index 6c6e2828..b79ca136 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
@@ -747,5 +747,10 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public virtual uint Estimate(CompressorCommand command)
+ {
+ return 0;
+ }
}
}
\ No newline at end of file
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
index 961d92aa..2ed7e6a5 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
@@ -232,5 +232,79 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
}
+
+ public override uint Estimate(CompressorCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return 34431;
+ case 2:
+ return 44253;
+ case 4:
+ return 63827;
+ case 6:
+ return 83361;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)630.12f;
+ case 2:
+ return (uint)638.27f;
+ case 4:
+ return (uint)705.86f;
+ case 6:
+ return (uint)782.02f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ }
+
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return 51095;
+ case 2:
+ return 65693;
+ case 4:
+ return 95383;
+ case 6:
+ return 124510;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)840.14f;
+ case 2:
+ return (uint)826.1f;
+ case 4:
+ return (uint)901.88f;
+ case 6:
+ return (uint)965.29f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
index 35314aca..825b3bf7 100644
--- a/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
+++ b/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
@@ -262,6 +262,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return PerformanceDetailType.Limiter;
case EffectType.CaptureBuffer:
return PerformanceDetailType.CaptureBuffer;
+ case EffectType.Compressor:
+ return PerformanceDetailType.Compressor;
default:
throw new NotImplementedException($"{Type}");
}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs
new file mode 100644
index 00000000..f4e5ae82
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs
@@ -0,0 +1,67 @@
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Parameter;
+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 compressor effect.
+ ///
+ public class CompressorEffect : BaseEffect
+ {
+ ///
+ /// The compressor parameter.
+ ///
+ public CompressorParameter Parameter;
+
+ ///
+ /// The compressor state.
+ ///
+ public Memory State { get; }
+
+ ///
+ /// Create a new .
+ ///
+ public CompressorEffect()
+ {
+ State = new CompressorState[1];
+ }
+
+ public override EffectType TargetEffectType => EffectType.Compressor;
+
+ public override ulong GetWorkBuffer(int index)
+ {
+ return GetSingleBuffer();
+ }
+
+ public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
+ {
+ // Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised.
+ updateErrorInfo = new BehaviourParameter.ErrorInfo();
+ }
+
+ public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
+ {
+ Debug.Assert(IsTypeValid(ref parameter));
+
+ UpdateParameterBase(ref parameter);
+
+ Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0];
+ IsEnabled = parameter.IsEnabled;
+
+ updateErrorInfo = new BehaviourParameter.ErrorInfo();
+ }
+
+ public override void UpdateForCommandGeneration()
+ {
+ UpdateUsageStateForCommandGeneration();
+
+ Parameter.Status = UsageState.Enabled;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs b/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
index e365a86c..4872ddb3 100644
--- a/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
+++ b/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
@@ -35,5 +35,6 @@ namespace Ryujinx.Audio.Renderer.Server
uint Estimate(LimiterCommandVersion2 command);
uint Estimate(GroupedBiquadFilterCommand command);
uint Estimate(CaptureBufferCommand command);
+ uint Estimate(CompressorCommand command);
}
}
\ No newline at end of file
diff --git a/Ryujinx.Audio/Renderer/Server/StateUpdater.cs b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs
index 0c2cfa7e..0446cd8c 100644
--- a/Ryujinx.Audio/Renderer/Server/StateUpdater.cs
+++ b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs
@@ -240,6 +240,10 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.CaptureBuffer:
effect = new CaptureBufferEffect();
break;
+ case EffectType.Compressor:
+ effect = new CompressorEffect();
+ break;
+
default:
throw new NotImplementedException($"EffectType {parameter.Type} not implemented!");
}
diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/CompressorParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/CompressorParameterTests.cs
new file mode 100644
index 00000000..24b834fc
--- /dev/null
+++ b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/CompressorParameterTests.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 CompressorParameterTests
+ {
+ [Test]
+ public void EnsureTypeSize()
+ {
+ Assert.AreEqual(0x38, Unsafe.SizeOf());
+ }
+ }
+}
+