Update audio renderer to REV13: Add support for compressor statistics 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
This commit is contained in:
gdkchan 2024-10-01 07:30:57 -03:00 committed by GitHub
parent 7d158acc3b
commit a2c0035013
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 352 additions and 88 deletions

View File

@ -1,9 +1,11 @@
using Ryujinx.Audio.Renderer.Dsp.Effect; using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Dsp.State; using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Parameter.Effect; using Ryujinx.Audio.Renderer.Parameter.Effect;
using Ryujinx.Audio.Renderer.Server.Effect; using Ryujinx.Audio.Renderer.Server.Effect;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Dsp.Command namespace Ryujinx.Audio.Renderer.Dsp.Command
{ {
@ -21,18 +23,20 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CompressorParameter Parameter => _parameter; public CompressorParameter Parameter => _parameter;
public Memory<CompressorState> State { get; } public Memory<CompressorState> State { get; }
public Memory<EffectResultState> ResultState { get; }
public ushort[] OutputBufferIndices { get; } public ushort[] OutputBufferIndices { get; }
public ushort[] InputBufferIndices { get; } public ushort[] InputBufferIndices { get; }
public bool IsEffectEnabled { get; } public bool IsEffectEnabled { get; }
private CompressorParameter _parameter; 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; Enabled = true;
NodeId = nodeId; NodeId = nodeId;
_parameter = parameter; _parameter = parameter;
State = state; State = state;
ResultState = resultState;
IsEffectEnabled = isEnabled; IsEffectEnabled = isEnabled;
@ -71,9 +75,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (IsEffectEnabled && _parameter.IsChannelCountValid()) if (IsEffectEnabled && _parameter.IsChannelCountValid())
{ {
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; if (!ResultState.IsEmpty && _parameter.StatisticsReset)
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; {
Span<float> channelInput = stackalloc float[Parameter.ChannelCount]; 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; ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
float unknown4 = state.Unknown4; float unknown4 = state.Unknown4;
ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage; ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
@ -92,7 +103,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex); 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 y = FloatingPointHelper.Log10(newMean) * 10.0f;
float z = 1.0f; float z = 1.0f;
@ -111,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (y >= state.Unknown14) 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 else
{ {
@ -126,7 +138,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if ((unknown4 - z) <= 0.08f) if ((unknown4 - z) <= 0.08f)
{ {
compressionEmaAlpha = Parameter.ReleaseCoefficient; compressionEmaAlpha = _parameter.ReleaseCoefficient;
if ((unknown4 - z) >= -0.08f) if ((unknown4 - z) >= -0.08f)
{ {
@ -140,18 +152,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
} }
else else
{ {
compressionEmaAlpha = Parameter.AttackCoefficient; compressionEmaAlpha = _parameter.AttackCoefficient;
} }
float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha); 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; *((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
} }
unknown4 = unknown4New; unknown4 = unknown4New;
previousCompressionEmaAlpha = compressionEmaAlpha; 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; state.InputMovingAverage = inputMovingAverage;
@ -161,7 +186,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
} }
else else
{ {
for (int i = 0; i < Parameter.ChannelCount; i++) for (int i = 0; i < _parameter.ChannelCount; i++)
{ {
if (InputBufferIndices[i] != OutputBufferIndices[i]) if (InputBufferIndices[i] != OutputBufferIndices[i])
{ {

View File

@ -38,10 +38,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = 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]); InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
} }
} }
@ -51,11 +51,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (IsEffectEnabled) if (IsEffectEnabled)
{ {
if (Parameter.Status == UsageState.Invalid) if (_parameter.Status == UsageState.Invalid)
{ {
state = new LimiterState(ref _parameter, WorkBuffer); state = new LimiterState(ref _parameter, WorkBuffer);
} }
else if (Parameter.Status == UsageState.New) else if (_parameter.Status == UsageState.New)
{ {
LimiterState.UpdateParameter(ref _parameter); LimiterState.UpdateParameter(ref _parameter);
} }
@ -66,56 +66,56 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state) 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> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
Span<IntPtr> outputBuffers = 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]); inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[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++) for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{ {
float rawInputSample = *((float*)inputBuffers[channelIndex] + 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 sampleInputMax = Math.Abs(inputSample);
float inputCoefficient = Parameter.ReleaseCoefficient; float inputCoefficient = _parameter.ReleaseCoefficient;
if (sampleInputMax > state.DetectorAverage[channelIndex].Read()) if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{ {
inputCoefficient = Parameter.AttackCoefficient; inputCoefficient = _parameter.AttackCoefficient;
} }
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient); float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f; 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) if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{ {
outputCoefficient = Parameter.AttackCoefficient; outputCoefficient = _parameter.AttackCoefficient;
} }
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient); 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; *((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
@ -123,16 +123,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
state.DelayedSampleBufferPosition[channelIndex]++; 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 else
{ {
for (int i = 0; i < Parameter.ChannelCount; i++) for (int i = 0; i < _parameter.ChannelCount; i++)
{ {
if (InputBufferIndices[i] != OutputBufferIndices[i]) if (InputBufferIndices[i] != OutputBufferIndices[i])
{ {

View File

@ -49,10 +49,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = 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]); InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
} }
} }
@ -62,11 +62,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (IsEffectEnabled) if (IsEffectEnabled)
{ {
if (Parameter.Status == UsageState.Invalid) if (_parameter.Status == UsageState.Invalid)
{ {
state = new LimiterState(ref _parameter, WorkBuffer); state = new LimiterState(ref _parameter, WorkBuffer);
} }
else if (Parameter.Status == UsageState.New) else if (_parameter.Status == UsageState.New)
{ {
LimiterState.UpdateParameter(ref _parameter); LimiterState.UpdateParameter(ref _parameter);
} }
@ -77,63 +77,63 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state) 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]; ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
statistics.Reset(); statistics.Reset();
} }
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; Span<IntPtr> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
Span<IntPtr> outputBuffers = 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]); inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[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++) for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{ {
float rawInputSample = *((float*)inputBuffers[channelIndex] + 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 sampleInputMax = Math.Abs(inputSample);
float inputCoefficient = Parameter.ReleaseCoefficient; float inputCoefficient = _parameter.ReleaseCoefficient;
if (sampleInputMax > state.DetectorAverage[channelIndex].Read()) if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{ {
inputCoefficient = Parameter.AttackCoefficient; inputCoefficient = _parameter.AttackCoefficient;
} }
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient); float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f; 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) if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{ {
outputCoefficient = Parameter.AttackCoefficient; outputCoefficient = _parameter.AttackCoefficient;
} }
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient); 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; *((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
@ -141,9 +141,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
state.DelayedSampleBufferPosition[channelIndex]++; 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) if (!ResultState.IsEmpty)
@ -158,7 +158,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
} }
else else
{ {
for (int i = 0; i < Parameter.ChannelCount; i++) for (int i = 0; i < _parameter.ChannelCount; i++)
{ {
if (InputBufferIndices[i] != OutputBufferIndices[i]) if (InputBufferIndices[i] != OutputBufferIndices[i])
{ {

View File

@ -90,9 +90,16 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
public bool MakeupGainEnabled; public bool MakeupGainEnabled;
/// <summary> /// <summary>
/// Reserved/padding. /// Indicate if the compressor effect should output statistics.
/// </summary> /// </summary>
private Array2<byte> _reserved; [MarshalAs(UnmanagedType.I1)]
public bool StatisticsEnabled;
/// <summary>
/// Indicate to the DSP that the user did a statistics reset.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool StatisticsReset;
/// <summary> /// <summary>
/// Check if the <see cref="ChannelCount"/> is valid. /// Check if the <see cref="ChannelCount"/> is valid.

View File

@ -0,0 +1,38 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// Effect result state for <seealso cref="Common.EffectType.Compressor"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CompressorStatistics
{
/// <summary>
/// Maximum input mean value since last reset.
/// </summary>
public float MaximumMean;
/// <summary>
/// Minimum output gain since last reset.
/// </summary>
public float MinimumGain;
/// <summary>
/// Last processed input sample, per channel.
/// </summary>
public Array6<float> LastSamples;
/// <summary>
/// Reset the statistics.
/// </summary>
/// <param name="channelCount">Number of channels to reset.</param>
public void Reset(ushort channelCount)
{
MaximumMean = 0.0f;
MinimumGain = 1.0f;
LastSamples.AsSpan()[..channelCount].Clear();
}
}
}

View File

@ -28,6 +28,11 @@ namespace Ryujinx.Audio.Renderer.Parameter
/// </summary> /// </summary>
bool IsUsed { get; } bool IsUsed { get; }
/// <summary>
/// Set to true to force resetting the previous mix volumes.
/// </summary>
bool ResetPrevVolume { get; }
/// <summary> /// <summary>
/// Mix buffer volumes. /// Mix buffer volumes.
/// </summary> /// </summary>

View File

@ -37,10 +37,16 @@ namespace Ryujinx.Audio.Renderer.Parameter
[MarshalAs(UnmanagedType.I1)] [MarshalAs(UnmanagedType.I1)]
public bool IsUsed; public bool IsUsed;
/// <summary>
/// Set to true to force resetting the previous mix volumes.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool ResetPrevVolume;
/// <summary> /// <summary>
/// Reserved/padding. /// Reserved/padding.
/// </summary> /// </summary>
private unsafe fixed byte _reserved[3]; private unsafe fixed byte _reserved[2];
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
private struct MixArray { } private struct MixArray { }
@ -58,6 +64,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => default; readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => default;
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed; readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume;
/// <summary> /// <summary>
/// The expected constant of any input header. /// The expected constant of any input header.

View File

@ -42,10 +42,16 @@ namespace Ryujinx.Audio.Renderer.Parameter
[MarshalAs(UnmanagedType.I1)] [MarshalAs(UnmanagedType.I1)]
public bool IsUsed; public bool IsUsed;
/// <summary>
/// Set to true to force resetting the previous mix volumes.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool ResetPrevVolume;
/// <summary> /// <summary>
/// Reserved/padding. /// Reserved/padding.
/// </summary> /// </summary>
private unsafe fixed byte _reserved[11]; private unsafe fixed byte _reserved[10];
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
private struct MixArray { } private struct MixArray { }
@ -63,6 +69,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => BiquadFilters; readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => BiquadFilters;
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed; readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume;
/// <summary> /// <summary>
/// The expected constant of any input header. /// The expected constant of any input header.

View File

@ -108,10 +108,18 @@ namespace Ryujinx.Audio.Renderer.Server
/// <remarks>This was added in system update 17.0.0</remarks> /// <remarks>This was added in system update 17.0.0</remarks>
public const int Revision12 = 12 << 24; public const int Revision12 = 12 << 24;
/// <summary>
/// REV13:
/// The compressor effect can now output statistics.
/// Splitter destinations now explicitly reset the previous mix volume, instead of doing so on first use.
/// </summary>
/// <remarks>This was added in system update 18.0.0</remarks>
public const int Revision13 = 13 << 24;
/// <summary> /// <summary>
/// Last revision supported by the implementation. /// Last revision supported by the implementation.
/// </summary> /// </summary>
public const int LastRevision = Revision12; public const int LastRevision = Revision13;
/// <summary> /// <summary>
/// Target revision magic supported by the implementation. /// Target revision magic supported by the implementation.
@ -384,6 +392,15 @@ namespace Ryujinx.Audio.Renderer.Server
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12); return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12);
} }
/// <summary>
/// Check if the audio renderer should support explicit previous mix volume reset on splitter.
/// </summary>
/// <returns>True if the audio renderer support explicit previous mix volume reset on splitter</returns>
public bool IsSplitterPrevVolumeResetSupported()
{
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision13);
}
/// <summary> /// <summary>
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>. /// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
/// </summary> /// </summary>

View File

@ -583,11 +583,20 @@ namespace Ryujinx.Audio.Renderer.Server
} }
} }
public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId) /// <summary>
/// Generate a new <see cref="CompressorCommand"/>.
/// </summary>
/// <param name="bufferOffset">The target buffer offset.</param>
/// <param name="parameter">The compressor parameter.</param>
/// <param name="state">The compressor state.</param>
/// <param name="effectResultState">The DSP effect result state.</param>
/// <param name="isEnabled">Set to true if the effect should be active.</param>
/// <param name="nodeId">The node id associated to this command.</param>
public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, Memory<EffectResultState> effectResultState, bool isEnabled, int nodeId)
{ {
if (parameter.IsChannelCountValid()) if (parameter.IsChannelCountValid())
{ {
CompressorCommand command = new(bufferOffset, parameter, state, isEnabled, nodeId); CompressorCommand command = new(bufferOffset, parameter, state, effectResultState, isEnabled, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);

View File

@ -735,14 +735,26 @@ namespace Ryujinx.Audio.Renderer.Server
} }
} }
private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId) private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId, int effectId)
{ {
Debug.Assert(effect.Type == EffectType.Compressor); Debug.Assert(effect.Type == EffectType.Compressor);
Memory<EffectResultState> dspResultState;
if (effect.Parameter.StatisticsEnabled)
{
dspResultState = _effectContext.GetDspStateMemory(effectId);
}
else
{
dspResultState = Memory<EffectResultState>.Empty;
}
_commandBuffer.GenerateCompressorEffect( _commandBuffer.GenerateCompressorEffect(
bufferOffset, bufferOffset,
effect.Parameter, effect.Parameter,
effect.State, effect.State,
dspResultState,
effect.IsEnabled, effect.IsEnabled,
nodeId); nodeId);
} }
@ -795,7 +807,7 @@ namespace Ryujinx.Audio.Renderer.Server
GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId); GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
break; break;
case EffectType.Compressor: case EffectType.Compressor:
GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId); GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId, effectId);
break; break;
default: default:
throw new NotImplementedException($"Unsupported effect type {effect.Type}"); throw new NotImplementedException($"Unsupported effect type {effect.Type}");

View File

@ -168,16 +168,30 @@ namespace Ryujinx.Audio.Renderer.Server
if (SampleCount == 160) if (SampleCount == 160)
{ {
if (command.Enabled) if (command.Enabled)
{
if (command.Parameter.StatisticsEnabled)
{ {
return command.Parameter.ChannelCount switch return command.Parameter.ChannelCount switch
{ {
1 => 34431, 1 => 22100,
2 => 44253, 2 => 33211,
4 => 63827, 4 => 41587,
6 => 83361, 6 => 58819,
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
}; };
} }
else
{
return command.Parameter.ChannelCount switch
{
1 => 19052,
2 => 29852,
4 => 37904,
6 => 55020,
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
};
}
}
return command.Parameter.ChannelCount switch return command.Parameter.ChannelCount switch
{ {
@ -190,16 +204,30 @@ namespace Ryujinx.Audio.Renderer.Server
} }
if (command.Enabled) if (command.Enabled)
{
if (command.Parameter.StatisticsEnabled)
{ {
return command.Parameter.ChannelCount switch return command.Parameter.ChannelCount switch
{ {
1 => 51095, 1 => 32518,
2 => 65693, 2 => 49102,
4 => 95383, 4 => 61685,
6 => 124510, 6 => 87250,
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
}; };
} }
else
{
return command.Parameter.ChannelCount switch
{
1 => 27963,
2 => 44016,
4 => 56183,
6 => 81862,
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
};
}
}
return command.Parameter.ChannelCount switch return command.Parameter.ChannelCount switch
{ {

View File

@ -62,6 +62,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
UpdateUsageStateForCommandGeneration(); UpdateUsageStateForCommandGeneration();
Parameter.Status = UsageState.Enabled; Parameter.Status = UsageState.Enabled;
Parameter.StatisticsReset = false;
}
public override void InitializeResultState(ref EffectResultState state)
{
ref CompressorStatistics statistics = ref MemoryMarshal.Cast<byte, CompressorStatistics>(state.SpecificData)[0];
statistics.Reset(Parameter.ChannelCount);
}
public override void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState)
{
destState = srcState;
} }
} }
} }

View File

@ -51,6 +51,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// </summary> /// </summary>
public bool IsBugFixed { get; private set; } public bool IsBugFixed { get; private set; }
/// <summary>
/// If set to true, the previous mix volume is explicitly resetted using the input parameter, instead of implicitly on first use.
/// </summary>
public bool IsSplitterPrevVolumeResetSupported { get; private set; }
/// <summary> /// <summary>
/// Initialize <see cref="SplitterContext"/>. /// Initialize <see cref="SplitterContext"/>.
/// </summary> /// </summary>
@ -139,6 +144,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
} }
} }
IsSplitterPrevVolumeResetSupported = behaviourContext.IsSplitterPrevVolumeResetSupported();
SplitterState.InitializeSplitters(splitters.Span); SplitterState.InitializeSplitters(splitters.Span);
Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed()); Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed());
@ -277,7 +284,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{ {
SplitterDestination destination = GetDestination(parameter.Id); SplitterDestination destination = GetDestination(parameter.Id);
destination.Update(parameter); destination.Update(parameter, IsSplitterPrevVolumeResetSupported);
} }
return true; return true;

View File

@ -184,15 +184,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Update the splitter destination data from user parameter. /// Update the splitter destination data from user parameter.
/// </summary> /// </summary>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter /// <param name="isPrevVolumeResetSupported">Indicates that the audio renderer revision in use supports explicitly resetting the volume.</param>
public void Update<T>(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter
{ {
if (Unsafe.IsNullRef(ref _v2)) if (Unsafe.IsNullRef(ref _v2))
{ {
_v1.Update(parameter); _v1.Update(parameter, isPrevVolumeResetSupported);
} }
else else
{ {
_v2.Update(parameter); _v2.Update(parameter, isPrevVolumeResetSupported);
} }
} }

View File

@ -93,7 +93,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Update the <see cref="SplitterDestinationVersion1"/> from user parameter. /// Update the <see cref="SplitterDestinationVersion1"/> from user parameter.
/// </summary> /// </summary>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter /// <param name="isPrevVolumeResetSupported">Indicates that the audio renderer revision in use supports explicitly resetting the volume.</param>
public void Update<T>(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter
{ {
Debug.Assert(Id == parameter.Id); Debug.Assert(Id == parameter.Id);
@ -103,7 +104,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
parameter.MixBufferVolume.CopyTo(MixBufferVolume); parameter.MixBufferVolume.CopyTo(MixBufferVolume);
if (!IsUsed && parameter.IsUsed) bool resetPrevVolume = isPrevVolumeResetSupported ? parameter.ResetPrevVolume : !IsUsed && parameter.IsUsed;
if (resetPrevVolume)
{ {
MixBufferVolume.CopyTo(PreviousMixBufferVolume); MixBufferVolume.CopyTo(PreviousMixBufferVolume);

View File

@ -98,7 +98,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Update the <see cref="SplitterDestinationVersion2"/> from user parameter. /// Update the <see cref="SplitterDestinationVersion2"/> from user parameter.
/// </summary> /// </summary>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter /// <param name="isPrevVolumeResetSupported">Indicates that the audio renderer revision in use supports explicitly resetting the volume.</param>
public void Update<T>(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter
{ {
Debug.Assert(Id == parameter.Id); Debug.Assert(Id == parameter.Id);
@ -110,7 +111,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
_biquadFilters = parameter.BiquadFilters; _biquadFilters = parameter.BiquadFilters;
if (!IsUsed && parameter.IsUsed) bool resetPrevVolume = isPrevVolumeResetSupported ? parameter.ResetPrevVolume : !IsUsed && parameter.IsUsed;
if (resetPrevVolume)
{ {
MixBufferVolume.CopyTo(PreviousMixBufferVolume); MixBufferVolume.CopyTo(PreviousMixBufferVolume);

View File

@ -8,5 +8,6 @@ namespace Ryujinx.Horizon.Sdk.Audio
public static Result DeviceNotFound => new(ModuleId, 1); public static Result DeviceNotFound => new(ModuleId, 1);
public static Result UnsupportedRevision => new(ModuleId, 2); public static Result UnsupportedRevision => new(ModuleId, 2);
public static Result NotImplemented => new(ModuleId, 513);
} }
} }

View File

@ -233,6 +233,48 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail
return Result.Success; return Result.Success;
} }
[CmifCommand(15)] // 17.0.0+
public Result AcquireAudioOutputDeviceNotification([CopyHandle] out int eventHandle, ulong deviceId)
{
eventHandle = 0;
return AudioResult.NotImplemented;
}
[CmifCommand(16)] // 17.0.0+
public Result ReleaseAudioOutputDeviceNotification(ulong deviceId)
{
return AudioResult.NotImplemented;
}
[CmifCommand(17)] // 17.0.0+
public Result AcquireAudioInputDeviceNotification([CopyHandle] out int eventHandle, ulong deviceId)
{
eventHandle = 0;
return AudioResult.NotImplemented;
}
[CmifCommand(18)] // 17.0.0+
public Result ReleaseAudioInputDeviceNotification(ulong deviceId)
{
return AudioResult.NotImplemented;
}
[CmifCommand(19)] // 18.0.0+
public Result SetAudioDeviceOutputVolumeAutoTuneEnabled(bool enabled)
{
return AudioResult.NotImplemented;
}
[CmifCommand(20)] // 18.0.0+
public Result IsAudioDeviceOutputVolumeAutoTuneEnabled(out bool enabled)
{
enabled = false;
return AudioResult.NotImplemented;
}
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing) if (disposing)

View File

@ -55,6 +55,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -83,6 +84,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -111,6 +113,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -139,6 +142,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -167,6 +171,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -195,6 +200,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -223,6 +229,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -251,6 +258,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -279,6 +287,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -307,6 +316,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -335,6 +345,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -363,6 +374,36 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
}
[Test]
public void TestRevision13()
{
BehaviourContext behaviourContext = new();
behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision13);
Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed());
Assert.IsTrue(behaviourContext.IsSplitterSupported());
Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported());
Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported());
Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported());
Assert.IsTrue(behaviourContext.IsSplitterBugFixed());
Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported());
Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported());
Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsTrue(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());