mirror of
				https://github.com/ryujinx-mirror/ryujinx.git
				synced 2025-11-04 03:29:01 -06:00 
			
		
		
		
	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:
		@@ -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
 | 
			
		||||
{
 | 
			
		||||
@@ -21,18 +23,20 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
@@ -71,9 +75,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
 | 
			
		||||
            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;
 | 
			
		||||
@@ -92,7 +103,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
                        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;
 | 
			
		||||
 | 
			
		||||
@@ -111,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
 | 
			
		||||
                        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
 | 
			
		||||
                        {
 | 
			
		||||
@@ -126,7 +138,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
 | 
			
		||||
                    if ((unknown4 - z) <= 0.08f)
 | 
			
		||||
                    {
 | 
			
		||||
                        compressionEmaAlpha = Parameter.ReleaseCoefficient;
 | 
			
		||||
                        compressionEmaAlpha = _parameter.ReleaseCoefficient;
 | 
			
		||||
 | 
			
		||||
                        if ((unknown4 - z) >= -0.08f)
 | 
			
		||||
                        {
 | 
			
		||||
@@ -140,18 +152,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
                    }
 | 
			
		||||
                    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;
 | 
			
		||||
@@ -161,7 +186,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                for (int i = 0; i < Parameter.ChannelCount; i++)
 | 
			
		||||
                for (int i = 0; i < _parameter.ChannelCount; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    if (InputBufferIndices[i] != OutputBufferIndices[i])
 | 
			
		||||
                    {
 | 
			
		||||
 
 | 
			
		||||
@@ -38,10 +38,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
            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]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -51,11 +51,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
                }
 | 
			
		||||
@@ -66,56 +66,56 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
@@ -123,16 +123,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
 | 
			
		||||
                        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])
 | 
			
		||||
                    {
 | 
			
		||||
 
 | 
			
		||||
@@ -49,10 +49,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
            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]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -62,11 +62,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
                }
 | 
			
		||||
@@ -77,63 +77,63 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
@@ -141,9 +141,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
 | 
			
		||||
                        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)
 | 
			
		||||
@@ -158,7 +158,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                for (int i = 0; i < Parameter.ChannelCount; i++)
 | 
			
		||||
                for (int i = 0; i < _parameter.ChannelCount; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    if (InputBufferIndices[i] != OutputBufferIndices[i])
 | 
			
		||||
                    {
 | 
			
		||||
 
 | 
			
		||||
@@ -90,9 +90,16 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
 | 
			
		||||
        public bool MakeupGainEnabled;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Reserved/padding.
 | 
			
		||||
        /// Indicate if the compressor effect should output statistics.
 | 
			
		||||
        /// </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>
 | 
			
		||||
        /// Check if the <see cref="ChannelCount"/> is valid.
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -28,6 +28,11 @@ namespace Ryujinx.Audio.Renderer.Parameter
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        bool IsUsed { get; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set to true to force resetting the previous mix volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        bool ResetPrevVolume { get; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Mix buffer volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
 
 | 
			
		||||
@@ -37,10 +37,16 @@ namespace Ryujinx.Audio.Renderer.Parameter
 | 
			
		||||
        [MarshalAs(UnmanagedType.I1)]
 | 
			
		||||
        public bool IsUsed;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set to true to force resetting the previous mix volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [MarshalAs(UnmanagedType.I1)]
 | 
			
		||||
        public bool ResetPrevVolume;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Reserved/padding.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private unsafe fixed byte _reserved[3];
 | 
			
		||||
        private unsafe fixed byte _reserved[2];
 | 
			
		||||
 | 
			
		||||
        [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
 | 
			
		||||
        private struct MixArray { }
 | 
			
		||||
@@ -58,6 +64,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
 | 
			
		||||
        readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => default;
 | 
			
		||||
 | 
			
		||||
        readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
 | 
			
		||||
        readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The expected constant of any input header.
 | 
			
		||||
 
 | 
			
		||||
@@ -42,10 +42,16 @@ namespace Ryujinx.Audio.Renderer.Parameter
 | 
			
		||||
        [MarshalAs(UnmanagedType.I1)]
 | 
			
		||||
        public bool IsUsed;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set to true to force resetting the previous mix volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [MarshalAs(UnmanagedType.I1)]
 | 
			
		||||
        public bool ResetPrevVolume;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Reserved/padding.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private unsafe fixed byte _reserved[11];
 | 
			
		||||
        private unsafe fixed byte _reserved[10];
 | 
			
		||||
 | 
			
		||||
        [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
 | 
			
		||||
        private struct MixArray { }
 | 
			
		||||
@@ -63,6 +69,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
 | 
			
		||||
        readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => BiquadFilters;
 | 
			
		||||
 | 
			
		||||
        readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
 | 
			
		||||
        readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The expected constant of any input header.
 | 
			
		||||
 
 | 
			
		||||
@@ -108,10 +108,18 @@ namespace Ryujinx.Audio.Renderer.Server
 | 
			
		||||
        /// <remarks>This was added in system update 17.0.0</remarks>
 | 
			
		||||
        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>
 | 
			
		||||
        /// Last revision supported by the implementation.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public const int LastRevision = Revision12;
 | 
			
		||||
        public const int LastRevision = Revision13;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Target revision magic supported by the implementation.
 | 
			
		||||
@@ -384,6 +392,15 @@ namespace Ryujinx.Audio.Renderer.Server
 | 
			
		||||
            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>
 | 
			
		||||
        /// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
            {
 | 
			
		||||
                CompressorCommand command = new(bufferOffset, parameter, state, isEnabled, nodeId);
 | 
			
		||||
                CompressorCommand command = new(bufferOffset, parameter, state, effectResultState, isEnabled, nodeId);
 | 
			
		||||
 | 
			
		||||
                command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 | 
			
		||||
            Memory<EffectResultState> dspResultState;
 | 
			
		||||
 | 
			
		||||
            if (effect.Parameter.StatisticsEnabled)
 | 
			
		||||
            {
 | 
			
		||||
                dspResultState = _effectContext.GetDspStateMemory(effectId);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                dspResultState = Memory<EffectResultState>.Empty;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _commandBuffer.GenerateCompressorEffect(
 | 
			
		||||
                bufferOffset,
 | 
			
		||||
                effect.Parameter,
 | 
			
		||||
                effect.State,
 | 
			
		||||
                dspResultState,
 | 
			
		||||
                effect.IsEnabled,
 | 
			
		||||
                nodeId);
 | 
			
		||||
        }
 | 
			
		||||
@@ -795,7 +807,7 @@ namespace Ryujinx.Audio.Renderer.Server
 | 
			
		||||
                    GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
 | 
			
		||||
                    break;
 | 
			
		||||
                case EffectType.Compressor:
 | 
			
		||||
                    GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId);
 | 
			
		||||
                    GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId, effectId);
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    throw new NotImplementedException($"Unsupported effect type {effect.Type}");
 | 
			
		||||
 
 | 
			
		||||
@@ -169,14 +169,28 @@ namespace Ryujinx.Audio.Renderer.Server
 | 
			
		||||
            {
 | 
			
		||||
                if (command.Enabled)
 | 
			
		||||
                {
 | 
			
		||||
                    return command.Parameter.ChannelCount switch
 | 
			
		||||
                    if (command.Parameter.StatisticsEnabled)
 | 
			
		||||
                    {
 | 
			
		||||
                        1 => 34431,
 | 
			
		||||
                        2 => 44253,
 | 
			
		||||
                        4 => 63827,
 | 
			
		||||
                        6 => 83361,
 | 
			
		||||
                        _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
 | 
			
		||||
                    };
 | 
			
		||||
                        return command.Parameter.ChannelCount switch
 | 
			
		||||
                        {
 | 
			
		||||
                            1 => 22100,
 | 
			
		||||
                            2 => 33211,
 | 
			
		||||
                            4 => 41587,
 | 
			
		||||
                            6 => 58819,
 | 
			
		||||
                            _ => 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
 | 
			
		||||
@@ -191,14 +205,28 @@ namespace Ryujinx.Audio.Renderer.Server
 | 
			
		||||
 | 
			
		||||
            if (command.Enabled)
 | 
			
		||||
            {
 | 
			
		||||
                return command.Parameter.ChannelCount switch
 | 
			
		||||
                if (command.Parameter.StatisticsEnabled)
 | 
			
		||||
                {
 | 
			
		||||
                    1 => 51095,
 | 
			
		||||
                    2 => 65693,
 | 
			
		||||
                    4 => 95383,
 | 
			
		||||
                    6 => 124510,
 | 
			
		||||
                    _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
 | 
			
		||||
                };
 | 
			
		||||
                    return command.Parameter.ChannelCount switch
 | 
			
		||||
                    {
 | 
			
		||||
                        1 => 32518,
 | 
			
		||||
                        2 => 49102,
 | 
			
		||||
                        4 => 61685,
 | 
			
		||||
                        6 => 87250,
 | 
			
		||||
                        _ => 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
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
 | 
			
		||||
            UpdateUsageStateForCommandGeneration();
 | 
			
		||||
 | 
			
		||||
            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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -51,6 +51,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        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>
 | 
			
		||||
        /// Initialize <see cref="SplitterContext"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@@ -139,6 +144,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            IsSplitterPrevVolumeResetSupported = behaviourContext.IsSplitterPrevVolumeResetSupported();
 | 
			
		||||
 | 
			
		||||
            SplitterState.InitializeSplitters(splitters.Span);
 | 
			
		||||
 | 
			
		||||
            Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed());
 | 
			
		||||
@@ -277,7 +284,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
                {
 | 
			
		||||
                    SplitterDestination destination = GetDestination(parameter.Id);
 | 
			
		||||
 | 
			
		||||
                    destination.Update(parameter);
 | 
			
		||||
                    destination.Update(parameter, IsSplitterPrevVolumeResetSupported);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return true;
 | 
			
		||||
 
 | 
			
		||||
@@ -184,15 +184,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        /// Update the splitter destination data from user parameter.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <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))
 | 
			
		||||
            {
 | 
			
		||||
                _v1.Update(parameter);
 | 
			
		||||
                _v1.Update(parameter, isPrevVolumeResetSupported);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                _v2.Update(parameter);
 | 
			
		||||
                _v2.Update(parameter, isPrevVolumeResetSupported);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -93,7 +93,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        /// Update the <see cref="SplitterDestinationVersion1"/> from user parameter.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <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);
 | 
			
		||||
 | 
			
		||||
@@ -103,7 +104,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
 | 
			
		||||
                parameter.MixBufferVolume.CopyTo(MixBufferVolume);
 | 
			
		||||
 | 
			
		||||
                if (!IsUsed && parameter.IsUsed)
 | 
			
		||||
                bool resetPrevVolume = isPrevVolumeResetSupported ? parameter.ResetPrevVolume : !IsUsed && parameter.IsUsed;
 | 
			
		||||
                if (resetPrevVolume)
 | 
			
		||||
                {
 | 
			
		||||
                    MixBufferVolume.CopyTo(PreviousMixBufferVolume);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -98,7 +98,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        /// Update the <see cref="SplitterDestinationVersion2"/> from user parameter.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <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);
 | 
			
		||||
 | 
			
		||||
@@ -110,7 +111,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
 | 
			
		||||
                _biquadFilters = parameter.BiquadFilters;
 | 
			
		||||
 | 
			
		||||
                if (!IsUsed && parameter.IsUsed)
 | 
			
		||||
                bool resetPrevVolume = isPrevVolumeResetSupported ? parameter.ResetPrevVolume : !IsUsed && parameter.IsUsed;
 | 
			
		||||
                if (resetPrevVolume)
 | 
			
		||||
                {
 | 
			
		||||
                    MixBufferVolume.CopyTo(PreviousMixBufferVolume);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,5 +8,6 @@ namespace Ryujinx.Horizon.Sdk.Audio
 | 
			
		||||
 | 
			
		||||
        public static Result DeviceNotFound => new(ModuleId, 1);
 | 
			
		||||
        public static Result UnsupportedRevision => new(ModuleId, 2);
 | 
			
		||||
        public static Result NotImplemented => new(ModuleId, 513);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -233,6 +233,48 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail
 | 
			
		||||
            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)
 | 
			
		||||
        {
 | 
			
		||||
            if (disposing)
 | 
			
		||||
 
 | 
			
		||||
@@ -55,6 +55,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
 | 
			
		||||
            Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
 | 
			
		||||
 | 
			
		||||
            Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
 | 
			
		||||
            Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
 | 
			
		||||
@@ -83,6 +84,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
 | 
			
		||||
            Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
 | 
			
		||||
 | 
			
		||||
            Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
 | 
			
		||||
            Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
 | 
			
		||||
@@ -111,6 +113,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
 | 
			
		||||
            Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
 | 
			
		||||
 | 
			
		||||
            Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
 | 
			
		||||
            Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
 | 
			
		||||
@@ -139,6 +142,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
 | 
			
		||||
            Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
 | 
			
		||||
 | 
			
		||||
            Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit());
 | 
			
		||||
            Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
 | 
			
		||||
@@ -167,6 +171,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
 | 
			
		||||
            Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
 | 
			
		||||
 | 
			
		||||
            Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
 | 
			
		||||
            Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
 | 
			
		||||
@@ -195,6 +200,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
 | 
			
		||||
            Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
 | 
			
		||||
 | 
			
		||||
            Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
 | 
			
		||||
            Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
 | 
			
		||||
@@ -223,6 +229,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
 | 
			
		||||
            Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
 | 
			
		||||
 | 
			
		||||
            Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
 | 
			
		||||
            Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
 | 
			
		||||
@@ -251,6 +258,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
 | 
			
		||||
            Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
 | 
			
		||||
 | 
			
		||||
            Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
 | 
			
		||||
            Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
 | 
			
		||||
@@ -279,6 +287,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
 | 
			
		||||
            Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
 | 
			
		||||
 | 
			
		||||
            Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
 | 
			
		||||
            Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
 | 
			
		||||
@@ -307,6 +316,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
 | 
			
		||||
            Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
 | 
			
		||||
 | 
			
		||||
            Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
 | 
			
		||||
            Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
 | 
			
		||||
@@ -335,6 +345,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
 | 
			
		||||
            Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
 | 
			
		||||
            Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
 | 
			
		||||
            Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
 | 
			
		||||
 | 
			
		||||
            Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
 | 
			
		||||
            Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
 | 
			
		||||
@@ -363,6 +374,36 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
 | 
			
		||||
            Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
 | 
			
		||||
            Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
 | 
			
		||||
            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(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user