Skip to content
This repository has been archived by the owner on Oct 10, 2024. It is now read-only.

Commit

Permalink
Update audio renderer to REV13: Add support for compressor statistics…
Browse files Browse the repository at this point in the history
… and volume reset (#7372)

* Update audio renderer to REV13: Add support for compressor statistics and volume reset

* XML docs

* Disable stats reset

* Wrong comment

* Fix more XML docs

* PR feedback
  • Loading branch information
gdkchan authored Oct 1, 2024
1 parent 7d158ac commit a2c0035
Show file tree
Hide file tree
Showing 20 changed files with 352 additions and 88 deletions.
45 changes: 35 additions & 10 deletions src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using Ryujinx.Audio.Renderer.Server.Effect;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Ryujinx.Audio.Renderer.Dsp.Command
{
Expand All @@ -21,18 +23,20 @@ public class CompressorCommand : ICommand

public CompressorParameter Parameter => _parameter;
public Memory<CompressorState> State { get; }
public Memory<EffectResultState> ResultState { get; }
public ushort[] OutputBufferIndices { get; }
public ushort[] InputBufferIndices { get; }
public bool IsEffectEnabled { get; }

private CompressorParameter _parameter;

public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, Memory<EffectResultState> resultState, bool isEnabled, int nodeId)
{
Enabled = true;
NodeId = nodeId;
_parameter = parameter;
State = state;
ResultState = resultState;

IsEffectEnabled = isEnabled;

Expand Down Expand Up @@ -71,9 +75,16 @@ private unsafe void ProcessCompressor(CommandList context, ref CompressorState s

if (IsEffectEnabled && _parameter.IsChannelCountValid())
{
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
Span<float> channelInput = stackalloc float[Parameter.ChannelCount];
if (!ResultState.IsEmpty && _parameter.StatisticsReset)
{
ref CompressorStatistics statistics = ref MemoryMarshal.Cast<byte, CompressorStatistics>(ResultState.Span[0].SpecificData)[0];

statistics.Reset(_parameter.ChannelCount);
}

Span<IntPtr> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
Span<IntPtr> outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
Span<float> channelInput = stackalloc float[_parameter.ChannelCount];
ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
float unknown4 = state.Unknown4;
ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
Expand All @@ -92,7 +103,8 @@ private unsafe void ProcessCompressor(CommandList context, ref CompressorState s
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
}

float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
float mean = FloatingPointHelper.MeanSquare(channelInput);
float newMean = inputMovingAverage.Update(mean, _parameter.InputGain);
float y = FloatingPointHelper.Log10(newMean) * 10.0f;
float z = 1.0f;

Expand All @@ -111,7 +123,7 @@ private unsafe void ProcessCompressor(CommandList context, ref CompressorState s

if (y >= state.Unknown14)
{
tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold);
tmpGain = ((1.0f / _parameter.Ratio) - 1.0f) * (y - _parameter.Threshold);
}
else
{
Expand All @@ -126,7 +138,7 @@ private unsafe void ProcessCompressor(CommandList context, ref CompressorState s

if ((unknown4 - z) <= 0.08f)
{
compressionEmaAlpha = Parameter.ReleaseCoefficient;
compressionEmaAlpha = _parameter.ReleaseCoefficient;

if ((unknown4 - z) >= -0.08f)
{
Expand All @@ -140,18 +152,31 @@ private unsafe void ProcessCompressor(CommandList context, ref CompressorState s
}
else
{
compressionEmaAlpha = Parameter.AttackCoefficient;
compressionEmaAlpha = _parameter.AttackCoefficient;
}

float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);

for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{
*((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
}

unknown4 = unknown4New;
previousCompressionEmaAlpha = compressionEmaAlpha;

if (!ResultState.IsEmpty)
{
ref CompressorStatistics statistics = ref MemoryMarshal.Cast<byte, CompressorStatistics>(ResultState.Span[0].SpecificData)[0];

statistics.MinimumGain = MathF.Min(statistics.MinimumGain, compressionGain * state.OutputGain);
statistics.MaximumMean = MathF.Max(statistics.MaximumMean, mean);

for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{
statistics.LastSamples[channelIndex] = MathF.Abs(channelInput[channelIndex] * (1f / 32768f));
}
}
}

state.InputMovingAverage = inputMovingAverage;
Expand All @@ -161,7 +186,7 @@ private unsafe void ProcessCompressor(CommandList context, ref CompressorState s
}
else
{
for (int i = 0; i < Parameter.ChannelCount; i++)
for (int i = 0; i < _parameter.ChannelCount; i++)
{
if (InputBufferIndices[i] != OutputBufferIndices[i])
{
Expand Down
46 changes: 23 additions & 23 deletions src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ public LimiterCommandVersion1(uint bufferOffset, LimiterParameter parameter, Mem
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];

for (int i = 0; i < Parameter.ChannelCount; i++)
for (int i = 0; i < _parameter.ChannelCount; i++)
{
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
}
}

Expand All @@ -51,11 +51,11 @@ public void Process(CommandList context)

if (IsEffectEnabled)
{
if (Parameter.Status == UsageState.Invalid)
if (_parameter.Status == UsageState.Invalid)
{
state = new LimiterState(ref _parameter, WorkBuffer);
}
else if (Parameter.Status == UsageState.New)
else if (_parameter.Status == UsageState.New)
{
LimiterState.UpdateParameter(ref _parameter);
}
Expand All @@ -66,73 +66,73 @@ public void Process(CommandList context)

private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
{
Debug.Assert(Parameter.IsChannelCountValid());
Debug.Assert(_parameter.IsChannelCountValid());

if (IsEffectEnabled && Parameter.IsChannelCountValid())
if (IsEffectEnabled && _parameter.IsChannelCountValid())
{
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
Span<IntPtr> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
Span<IntPtr> outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];

for (int i = 0; i < Parameter.ChannelCount; i++)
for (int i = 0; i < _parameter.ChannelCount; i++)
{
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
}

for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);

float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
float inputSample = (rawInputSample / short.MaxValue) * _parameter.InputGain;

float sampleInputMax = Math.Abs(inputSample);

float inputCoefficient = Parameter.ReleaseCoefficient;
float inputCoefficient = _parameter.ReleaseCoefficient;

if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{
inputCoefficient = Parameter.AttackCoefficient;
inputCoefficient = _parameter.AttackCoefficient;
}

float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f;

if (detectorValue > Parameter.Threshold)
if (detectorValue > _parameter.Threshold)
{
attenuation = Parameter.Threshold / detectorValue;
attenuation = _parameter.Threshold / detectorValue;
}

float outputCoefficient = Parameter.ReleaseCoefficient;
float outputCoefficient = _parameter.ReleaseCoefficient;

if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{
outputCoefficient = Parameter.AttackCoefficient;
outputCoefficient = _parameter.AttackCoefficient;
}

float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);

ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * _parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];

float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
float outputSample = delayedSample * compressionGain * _parameter.OutputGain;

*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;

delayedSample = inputSample;

state.DelayedSampleBufferPosition[channelIndex]++;

while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
while (state.DelayedSampleBufferPosition[channelIndex] >= _parameter.DelayBufferSampleCountMin)
{
state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
state.DelayedSampleBufferPosition[channelIndex] -= _parameter.DelayBufferSampleCountMin;
}
}
}
}
else
{
for (int i = 0; i < Parameter.ChannelCount; i++)
for (int i = 0; i < _parameter.ChannelCount; i++)
{
if (InputBufferIndices[i] != OutputBufferIndices[i])
{
Expand Down
48 changes: 24 additions & 24 deletions src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ public LimiterCommandVersion2(
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];

for (int i = 0; i < Parameter.ChannelCount; i++)
for (int i = 0; i < _parameter.ChannelCount; i++)
{
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
}
}

Expand All @@ -62,11 +62,11 @@ public void Process(CommandList context)

if (IsEffectEnabled)
{
if (Parameter.Status == UsageState.Invalid)
if (_parameter.Status == UsageState.Invalid)
{
state = new LimiterState(ref _parameter, WorkBuffer);
}
else if (Parameter.Status == UsageState.New)
else if (_parameter.Status == UsageState.New)
{
LimiterState.UpdateParameter(ref _parameter);
}
Expand All @@ -77,73 +77,73 @@ public void Process(CommandList context)

private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
{
Debug.Assert(Parameter.IsChannelCountValid());
Debug.Assert(_parameter.IsChannelCountValid());

if (IsEffectEnabled && Parameter.IsChannelCountValid())
if (IsEffectEnabled && _parameter.IsChannelCountValid())
{
if (!ResultState.IsEmpty && Parameter.StatisticsReset)
if (!ResultState.IsEmpty && _parameter.StatisticsReset)
{
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];

statistics.Reset();
}

Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
Span<IntPtr> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
Span<IntPtr> outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];

for (int i = 0; i < Parameter.ChannelCount; i++)
for (int i = 0; i < _parameter.ChannelCount; i++)
{
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
}

for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);

float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
float inputSample = (rawInputSample / short.MaxValue) * _parameter.InputGain;

float sampleInputMax = Math.Abs(inputSample);

float inputCoefficient = Parameter.ReleaseCoefficient;
float inputCoefficient = _parameter.ReleaseCoefficient;

if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{
inputCoefficient = Parameter.AttackCoefficient;
inputCoefficient = _parameter.AttackCoefficient;
}

float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f;

if (detectorValue > Parameter.Threshold)
if (detectorValue > _parameter.Threshold)
{
attenuation = Parameter.Threshold / detectorValue;
attenuation = _parameter.Threshold / detectorValue;
}

float outputCoefficient = Parameter.ReleaseCoefficient;
float outputCoefficient = _parameter.ReleaseCoefficient;

if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{
outputCoefficient = Parameter.AttackCoefficient;
outputCoefficient = _parameter.AttackCoefficient;
}

float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);

ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * _parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];

float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
float outputSample = delayedSample * compressionGain * _parameter.OutputGain;

*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;

delayedSample = inputSample;

state.DelayedSampleBufferPosition[channelIndex]++;

while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
while (state.DelayedSampleBufferPosition[channelIndex] >= _parameter.DelayBufferSampleCountMin)
{
state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
state.DelayedSampleBufferPosition[channelIndex] -= _parameter.DelayBufferSampleCountMin;
}

if (!ResultState.IsEmpty)
Expand All @@ -158,7 +158,7 @@ private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
}
else
{
for (int i = 0; i < Parameter.ChannelCount; i++)
for (int i = 0; i < _parameter.ChannelCount; i++)
{
if (InputBufferIndices[i] != OutputBufferIndices[i])
{
Expand Down
Loading

0 comments on commit a2c0035

Please sign in to comment.