From f3b0b4831c323a20393aa0388f947317354372b7 Mon Sep 17 00:00:00 2001 From: Mary Date: Tue, 25 May 2021 19:01:09 +0200 Subject: [PATCH] amadeus: Update to REV9 (#2309) * amadeus: Update to REV9 This implements all the changes made with REV9 on 12.0.0. * Address Ac_k's comments --- Ryujinx.Audio/Renderer/Common/EffectType.cs | 7 +- .../Renderer/Common/PerformanceDetailType.cs | 3 +- .../Renderer/Dsp/Command/CommandType.cs | 4 +- .../Dsp/Command/LimiterCommandVersion1.cs | 160 ++++++++++++++++ .../Dsp/Command/LimiterCommandVersion2.cs | 179 ++++++++++++++++++ .../Renderer/Dsp/State/LimiterState.cs | 46 +++++ .../Effect/AuxiliaryBufferParameter.cs | 2 +- .../Effect/BiquadFilterEffectParameter.cs | 2 +- .../Parameter/Effect/BufferMixerParameter.cs | 2 +- .../Parameter/Effect/DelayParameter.cs | 6 +- .../Parameter/Effect/LimiterParameter.cs | 155 +++++++++++++++ .../Parameter/Effect/LimiterStatistics.cs | 48 +++++ .../Parameter/Effect/Reverb3dParameter.cs | 6 +- .../Parameter/Effect/ReverbParameter.cs | 6 +- ...ameter.cs => EffectInParameterVersion1.cs} | 21 +- .../Parameter/EffectInParameterVersion2.cs | 114 +++++++++++ .../Parameter/EffectOutStatusVersion1.cs | 40 ++++ ...utStatus.cs => EffectOutStatusVersion2.cs} | 29 +-- .../Renderer/Parameter/EffectResultState.cs | 43 +++++ .../Renderer/Parameter/EffectState.cs | 35 ++++ .../Renderer/Parameter/IEffectInParameter.cs | 70 +++++++ .../Renderer/Parameter/IEffectOutStatus.cs | 30 +++ .../Renderer/Server/AudioRenderSystem.cs | 7 +- .../Renderer/Server/BehaviourContext.cs | 19 +- .../Renderer/Server/CommandBuffer.cs | 43 +++++ .../Renderer/Server/CommandGenerator.cs | 25 ++- .../CommandProcessingTimeEstimatorVersion1.cs | 10 + .../CommandProcessingTimeEstimatorVersion2.cs | 10 + .../CommandProcessingTimeEstimatorVersion3.cs | 123 ++++++++++++ .../Server/Effect/AuxiliaryBufferEffect.cs | 12 +- .../Renderer/Server/Effect/BaseEffect.cs | 46 ++++- .../Server/Effect/BiquadFilterEffect.cs | 12 +- .../Renderer/Server/Effect/BufferMixEffect.cs | 12 +- .../Renderer/Server/Effect/DelayEffect.cs | 12 +- .../Renderer/Server/Effect/EffectContext.cs | 60 +++++- .../Renderer/Server/Effect/LimiterEffect.cs | 112 +++++++++++ .../Renderer/Server/Effect/Reverb3dEffect.cs | 12 +- .../Renderer/Server/Effect/ReverbEffect.cs | 12 +- .../Server/ICommandProcessingTimeEstimator.cs | 2 + .../Server/Performance/PerformanceManager.cs | 1 - Ryujinx.Audio/Renderer/Server/StateUpdater.cs | 83 +++++++- .../Renderer/EffectInfoParameterTests.cs | 3 +- .../Audio/Renderer/EffectOutStatusTests.cs | 3 +- .../Parameter/Effect/LimiterParameterTests.cs | 16 ++ .../Effect/LimiterStatisticsTests.cs | 16 ++ 45 files changed, 1591 insertions(+), 68 deletions(-) create mode 100644 Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs create mode 100644 Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs create mode 100644 Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs create mode 100644 Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs create mode 100644 Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs rename Ryujinx.Audio/Renderer/Parameter/{EffectInParameter.cs => EffectInParameterVersion1.cs} (86%) create mode 100644 Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs create mode 100644 Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs rename Ryujinx.Audio/Renderer/Parameter/{EffectOutStatus.cs => EffectOutStatusVersion2.cs} (74%) create mode 100644 Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs create mode 100644 Ryujinx.Audio/Renderer/Parameter/EffectState.cs create mode 100644 Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs create mode 100644 Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs create mode 100644 Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterStatisticsTests.cs diff --git a/Ryujinx.Audio/Renderer/Common/EffectType.cs b/Ryujinx.Audio/Renderer/Common/EffectType.cs index 082f94f84..daa220360 100644 --- a/Ryujinx.Audio/Renderer/Common/EffectType.cs +++ b/Ryujinx.Audio/Renderer/Common/EffectType.cs @@ -55,6 +55,11 @@ namespace Ryujinx.Audio.Renderer.Common /// /// Effect applying a biquad filter. /// - BiquadFilter + BiquadFilter, + + /// + /// Effect applying a limiter (DRC). + /// + Limiter, } } diff --git a/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs b/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs index a92bd0cc4..e9e946ce4 100644 --- a/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs +++ b/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs @@ -29,6 +29,7 @@ namespace Ryujinx.Audio.Renderer.Common Aux, Reverb, Reverb3d, - PcmFloat + PcmFloat, + Limiter } } diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs index 8ff1c5817..997a080e8 100644 --- a/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs @@ -44,6 +44,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command Reverb3d, Performance, ClearMixBuffer, - CopyMixBuffer + CopyMixBuffer, + LimiterVersion1, + LimiterVersion2 } } diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs new file mode 100644 index 000000000..975e61f95 --- /dev/null +++ b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs @@ -0,0 +1,160 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class LimiterCommandVersion1 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.LimiterVersion1; + + public ulong EstimatedProcessingTime { get; set; } + + public LimiterParameter Parameter => _parameter; + public Memory State { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsEffectEnabled { get; } + + private LimiterParameter _parameter; + + public LimiterCommandVersion1(uint bufferOffset, LimiterParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId) + { + Enabled = true; + NodeId = nodeId; + _parameter = parameter; + State = state; + WorkBuffer = workBuffer; + + IsEffectEnabled = isEnabled; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + } + + public void Process(CommandList context) + { + ref LimiterState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.Status == Server.Effect.UsageState.Invalid) + { + state = new LimiterState(ref _parameter, WorkBuffer); + } + else if (Parameter.Status == Server.Effect.UsageState.New) + { + state.UpdateParameter(ref _parameter); + } + } + + ProcessLimiter(context); + } + + private void ProcessLimiter(CommandList context) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + ref LimiterState state = ref State.Span[0]; + + ReadOnlyMemory[] inputBuffers = new ReadOnlyMemory[Parameter.ChannelCount]; + Memory[] outputBuffers = new Memory[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]); + } + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++) + { + float inputSample = inputBuffers[channelIndex].Span[sampleIndex]; + + float sampleInputMax = Math.Abs(inputSample * Parameter.InputGain); + + float inputCoefficient = Parameter.ReleaseCoefficient; + + if (sampleInputMax > state.DectectorAverage[channelIndex]) + { + inputCoefficient = Parameter.AttackCoefficient; + } + + state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]); + + float attenuation = 1.0f; + + if (state.DectectorAverage[channelIndex] > Parameter.Threshold) + { + attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex]; + } + + float outputCoefficient = Parameter.ReleaseCoefficient; + + if (state.CompressionGain[channelIndex] > attenuation) + { + outputCoefficient = Parameter.AttackCoefficient; + } + + state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]); + + ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]]; + + outputBuffers[channelIndex].Span[sampleIndex] = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain; + + delayedSample = inputSample; + + state.DelayedSampleBufferPosition[channelIndex]++; + + while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin) + { + state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin; + } + } + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i])); + } + } + } + } + } +} diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs new file mode 100644 index 000000000..0a4b14b74 --- /dev/null +++ b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs @@ -0,0 +1,179 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class LimiterCommandVersion2 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.LimiterVersion2; + + public ulong EstimatedProcessingTime { get; set; } + + public LimiterParameter Parameter => _parameter; + public Memory State { get; } + public Memory ResultState { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsEffectEnabled { get; } + + private LimiterParameter _parameter; + + public LimiterCommandVersion2(uint bufferOffset, LimiterParameter parameter, Memory state, Memory resultState, bool isEnabled, ulong workBuffer, int nodeId) + { + Enabled = true; + NodeId = nodeId; + _parameter = parameter; + State = state; + ResultState = resultState; + WorkBuffer = workBuffer; + + IsEffectEnabled = isEnabled; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + } + + public void Process(CommandList context) + { + ref LimiterState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.Status == Server.Effect.UsageState.Invalid) + { + state = new LimiterState(ref _parameter, WorkBuffer); + } + else if (Parameter.Status == Server.Effect.UsageState.New) + { + state.UpdateParameter(ref _parameter); + } + } + + ProcessLimiter(context); + } + + private void ProcessLimiter(CommandList context) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + ref LimiterState state = ref State.Span[0]; + + if (!ResultState.IsEmpty && Parameter.StatisticsReset) + { + ref LimiterStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0]; + + statistics.Reset(); + } + + ReadOnlyMemory[] inputBuffers = new ReadOnlyMemory[Parameter.ChannelCount]; + Memory[] outputBuffers = new Memory[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]); + } + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++) + { + float inputSample = inputBuffers[channelIndex].Span[sampleIndex]; + + float sampleInputMax = Math.Abs(inputSample * Parameter.InputGain); + + float inputCoefficient = Parameter.ReleaseCoefficient; + + if (sampleInputMax > state.DectectorAverage[channelIndex]) + { + inputCoefficient = Parameter.AttackCoefficient; + } + + state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]); + + float attenuation = 1.0f; + + if (state.DectectorAverage[channelIndex] > Parameter.Threshold) + { + attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex]; + } + + float outputCoefficient = Parameter.ReleaseCoefficient; + + if (state.CompressionGain[channelIndex] > attenuation) + { + outputCoefficient = Parameter.AttackCoefficient; + } + + state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]); + + ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]]; + + outputBuffers[channelIndex].Span[sampleIndex] = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain; + + delayedSample = inputSample; + + state.DelayedSampleBufferPosition[channelIndex]++; + + while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin) + { + state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin; + } + + if (!ResultState.IsEmpty) + { + ref LimiterStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0]; + + statistics.InputMax[channelIndex] = Math.Max(statistics.InputMax[channelIndex], sampleInputMax); + statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], state.CompressionGain[channelIndex]); + } + } + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i])); + } + } + } + } + } +} diff --git a/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs b/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs new file mode 100644 index 000000000..53913bad0 --- /dev/null +++ b/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public class LimiterState + { + public float[] DectectorAverage; + public float[] CompressionGain; + public float[] DelayedSampleBuffer; + public int[] DelayedSampleBufferPosition; + + public LimiterState(ref LimiterParameter parameter, ulong workBuffer) + { + DectectorAverage = new float[parameter.ChannelCount]; + CompressionGain = new float[parameter.ChannelCount]; + DelayedSampleBuffer = new float[parameter.ChannelCount * parameter.DelayBufferSampleCountMax]; + DelayedSampleBufferPosition = new int[parameter.ChannelCount]; + + DectectorAverage.AsSpan().Fill(0.0f); + CompressionGain.AsSpan().Fill(1.0f); + DelayedSampleBufferPosition.AsSpan().Fill(0); + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref LimiterParameter parameter) {} + } +} diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs index 5a7479223..c30c4013f 100644 --- a/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs +++ b/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs @@ -21,7 +21,7 @@ using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Parameter.Effect { /// - /// for . + /// for . /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct AuxiliaryBufferParameter diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs index 7d58d4be8..39d75b691 100644 --- a/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs +++ b/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs @@ -22,7 +22,7 @@ using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Parameter.Effect { /// - /// for . + /// for . /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct BiquadFilterEffectParameter diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs index b0c0c3374..ef298e3dc 100644 --- a/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs +++ b/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs @@ -21,7 +21,7 @@ using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Parameter.Effect { /// - /// for . + /// for . /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct BufferMixParameter diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs index e0dbeb7ca..80e5df25b 100644 --- a/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs +++ b/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs @@ -22,7 +22,7 @@ using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Parameter.Effect { /// - /// for . + /// for . /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct DelayParameter @@ -103,7 +103,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect /// Returns true if the is valid. public bool IsChannelCountValid() { - return EffectInParameter.IsChannelCountValid(ChannelCount); + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); } /// @@ -112,7 +112,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect /// Returns true if the is valid. public bool IsChannelCountMaxValid() { - return EffectInParameter.IsChannelCountValid(ChannelCountMax); + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); } } } diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs new file mode 100644 index 000000000..2caf23f9d --- /dev/null +++ b/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs @@ -0,0 +1,155 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LimiterParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The target sample rate. + /// + /// This is in kHz. + public int SampleRate; + + /// + /// The look ahead max time. + /// This is in microseconds. + /// + public int LookAheadTimeMax; + + /// + /// The attack time. + /// This is in microseconds. + /// + public int AttackTime; + + /// + /// The release time. + /// This is in microseconds. + /// + public int ReleaseTime; + + /// + /// The look ahead time. + /// This is in microseconds. + /// + public int LookAheadTime; + + /// + /// The attack coefficient. + /// + public float AttackCoefficient; + + /// + /// The release coefficient. + /// + public float ReleaseCoefficient; + + /// + /// The threshold. + /// + public float Threshold; + + /// + /// The input gain. + /// + public float InputGain; + + /// + /// The output gain. + /// + public float OutputGain; + + /// + /// The minimum samples stored in the delay buffer. + /// + public int DelayBufferSampleCountMin; + + /// + /// The maximum samples stored in the delay buffer. + /// + public int DelayBufferSampleCountMax; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Indicate if the limiter effect should output statistics. + /// + [MarshalAs(UnmanagedType.I1)] + public bool StatisticsEnabled; + + /// + /// Indicate to the DSP that the user did a statistics reset. + /// + [MarshalAs(UnmanagedType.I1)] + public bool StatisticsReset; + + /// + /// Reserved/padding. + /// + private byte _reserved; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs new file mode 100644 index 000000000..faf12f2aa --- /dev/null +++ b/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// Effect result state for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LimiterStatistics + { + /// + /// The max input sample value recorded by the limiter. + /// + public Array6 InputMax; + + /// + /// Compression gain min value. + /// + public Array6 CompressionGainMin; + + /// + /// Reset the statistics. + /// + public void Reset() + { + InputMax.ToSpan().Fill(0.0f); + CompressionGainMin.ToSpan().Fill(1.0f); + } + } +} diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs index 0dbecfb88..9b775fff3 100644 --- a/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs +++ b/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs @@ -22,7 +22,7 @@ using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Parameter.Effect { /// - /// for . + /// for . /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Reverb3dParameter @@ -129,7 +129,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect /// Returns true if the is valid. public bool IsChannelCountValid() { - return EffectInParameter.IsChannelCountValid(ChannelCount); + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); } /// @@ -138,7 +138,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect /// Returns true if the is valid. public bool IsChannelCountMaxValid() { - return EffectInParameter.IsChannelCountValid(ChannelCountMax); + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); } } } diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs index daa81c511..dea54407d 100644 --- a/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs +++ b/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs @@ -23,7 +23,7 @@ using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Parameter.Effect { /// - /// for . + /// for . /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct ReverbParameter @@ -121,7 +121,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect /// Returns true if the is valid. public bool IsChannelCountValid() { - return EffectInParameter.IsChannelCountValid(ChannelCount); + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); } /// @@ -130,7 +130,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect /// Returns true if the is valid. public bool IsChannelCountMaxValid() { - return EffectInParameter.IsChannelCountValid(ChannelCountMax); + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); } } } diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs similarity index 86% rename from Ryujinx.Audio/Renderer/Parameter/EffectInParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs index af8edeff4..ec2d801e2 100644 --- a/Ryujinx.Audio/Renderer/Parameter/EffectInParameter.cs +++ b/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs @@ -23,10 +23,10 @@ using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Parameter { /// - /// Input information for an effect. + /// Input information for an effect version 1. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct EffectInParameter + public struct EffectInParameterVersion1 : IEffectInParameter { /// /// Type of the effect. @@ -85,11 +85,22 @@ namespace Ryujinx.Audio.Renderer.Parameter [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)] private struct SpecificDataStruct { } - /// - /// Specific data changing depending of the . See also the namespace. - /// public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + EffectType IEffectInParameter.Type => Type; + + bool IEffectInParameter.IsNew => IsNew; + + bool IEffectInParameter.IsEnabled => IsEnabled; + + int IEffectInParameter.MixId => MixId; + + ulong IEffectInParameter.BufferBase => BufferBase; + + ulong IEffectInParameter.BufferSize => BufferSize; + + uint IEffectInParameter.ProcessingOrder => ProcessingOrder; + /// /// Check if the given channel count is valid. /// diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs b/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs new file mode 100644 index 000000000..ea74f1e64 --- /dev/null +++ b/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs @@ -0,0 +1,114 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for an effect version 2. (added with REV9) + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectInParameterVersion2 : IEffectInParameter + { + /// + /// Type of the effect. + /// + public EffectType Type; + + /// + /// Set to true if the effect is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + /// + /// Set to true if the effect must be active. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEnabled; + + /// + /// Reserved/padding. + /// + private byte _reserved1; + + /// + /// The target mix id of the effect. + /// + public int MixId; + + /// + /// Address of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferBase; + + /// + /// Size of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferSize; + + /// + /// Position of the effect while processing effects. + /// + public uint ProcessingOrder; + + /// + /// Reserved/padding. + /// + private uint _reserved2; + + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)] + private struct SpecificDataStruct { } + + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + + EffectType IEffectInParameter.Type => Type; + + bool IEffectInParameter.IsNew => IsNew; + + bool IEffectInParameter.IsEnabled => IsEnabled; + + int IEffectInParameter.MixId => MixId; + + ulong IEffectInParameter.BufferBase => BufferBase; + + ulong IEffectInParameter.BufferSize => BufferSize; + + uint IEffectInParameter.ProcessingOrder => ProcessingOrder; + + /// + /// Check if the given channel count is valid. + /// + /// The channel count to check + /// Returns true if the channel count is valid. + public static bool IsChannelCountValid(int channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6; + } + } +} diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs b/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs new file mode 100644 index 000000000..0c26d2b91 --- /dev/null +++ b/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for an effect version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectOutStatusVersion1 : IEffectOutStatus + { + /// + /// Current effect state. + /// + public EffectState State; + + /// + /// Unused/Reserved. + /// + private unsafe fixed byte _reserved[15]; + + EffectState IEffectOutStatus.State { get => State; set => State = value; } + } +} diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs similarity index 74% rename from Ryujinx.Audio/Renderer/Parameter/EffectOutStatus.cs rename to Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs index c61781652..59eeae9b3 100644 --- a/Ryujinx.Audio/Renderer/Parameter/EffectOutStatus.cs +++ b/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) 2019-2021 Ryujinx // // This program is free software: you can redistribute it and/or modify @@ -20,27 +20,11 @@ using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Parameter { /// - /// Output information for an effect. + /// Output information for an effect version 2. (added with REV9) /// [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct EffectOutStatus + public struct EffectOutStatusVersion2 : IEffectOutStatus { - /// - /// The state of an effect. - /// - public enum EffectState : byte - { - /// - /// The effect is enabled. - /// - Enabled = 3, - - /// - /// The effect is disabled. - /// - Disabled = 4 - } - /// /// Current effect state. /// @@ -50,5 +34,12 @@ namespace Ryujinx.Audio.Renderer.Parameter /// Unused/Reserved. /// private unsafe fixed byte _reserved[15]; + + /// + /// Current result state. + /// + public EffectResultState ResultState; + + EffectState IEffectOutStatus.State { get => State; set => State = value; } } } diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs b/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs new file mode 100644 index 000000000..64a6f50ac --- /dev/null +++ b/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Effect result state (added in REV9). + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectResultState + { + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0x80, Pack = 1)] + private struct SpecificDataStruct { } + + /// + /// Specific data changing depending of the type of effect. See also the namespace. + /// + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + } +} diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectState.cs b/Ryujinx.Audio/Renderer/Parameter/EffectState.cs new file mode 100644 index 000000000..685d0cdb4 --- /dev/null +++ b/Ryujinx.Audio/Renderer/Parameter/EffectState.cs @@ -0,0 +1,35 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// The state of an effect. + /// + public enum EffectState : byte + { + /// + /// The effect is enabled. + /// + Enabled = 3, + + /// + /// The effect is disabled. + /// + Disabled = 4 + } +} diff --git a/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs new file mode 100644 index 000000000..779d82980 --- /dev/null +++ b/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs @@ -0,0 +1,70 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using System; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Generic interface to represent input information for an effect. + /// + public interface IEffectInParameter + { + /// + /// Type of the effect. + /// + EffectType Type { get; } + + /// + /// Set to true if the effect is new. + /// + bool IsNew { get; } + + /// + /// Set to true if the effect must be active. + /// + bool IsEnabled { get; } + + /// + /// The target mix id of the effect. + /// + int MixId { get; } + + /// + /// Address of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + ulong BufferBase { get; } + + /// + /// Size of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + ulong BufferSize { get; } + + /// + /// Position of the effect while processing effects. + /// + uint ProcessingOrder { get; } + + /// + /// Specific data changing depending of the . See also the namespace. + /// + Span SpecificData { get; } + } +} diff --git a/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs new file mode 100644 index 000000000..29220a9d4 --- /dev/null +++ b/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Generic interface to represent output information for an effect. + /// + public interface IEffectOutStatus + { + /// + /// Current effect state. + /// + EffectState State { get; set; } + } +} diff --git a/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs index 112b0e442..943a2d780 100644 --- a/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs +++ b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs @@ -307,7 +307,7 @@ namespace Ryujinx.Audio.Renderer.Server _upsamplerManager = new UpsamplerManager(upSamplerWorkBuffer, _upsamplerCount); - _effectContext.Initialize(parameter.EffectCount); + _effectContext.Initialize(parameter.EffectCount, _behaviourContext.IsEffectInfoVersion2Supported() ? parameter.EffectCount : 0); _sinkContext.Initialize(parameter.SinkCount); Memory voiceUpdateStatesDsp = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceUpdateState.Align); @@ -636,6 +636,11 @@ namespace Ryujinx.Audio.Renderer.Server _voiceContext.UpdateForCommandGeneration(); + if (_behaviourContext.IsEffectInfoVersion2Supported()) + { + _effectContext.UpdateResultStateForCommandGeneration(); + } + ulong endTicks = GetSystemTicks(); _totalElapsedTicks = endTicks - startTicks; diff --git a/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs index b31f9e9f9..ed1f402eb 100644 --- a/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs +++ b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs @@ -89,10 +89,18 @@ namespace Ryujinx.Audio.Renderer.Server /// This was added in system update 9.0.0 public const int Revision8 = 8 << 24; + /// + /// REV9: + /// EffectInfo parameters were revisited with a new revision (version 2) allowing more data control between the client and server. + /// A new effect was added: Limiter. This effect is effectively implemented with a DRC while providing statistics on the processing on . + /// + /// This was added in system update 12.0.0 + public const int Revision9 = 9 << 24; + /// /// Last revision supported by the implementation. /// - public const int LastRevision = Revision8; + public const int LastRevision = Revision9; /// /// Target revision magic supported by the implementation. @@ -330,6 +338,15 @@ namespace Ryujinx.Audio.Renderer.Server return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8); } + /// + /// Check if the audio renderer should use the new effect info format. + /// + /// True if the audio renderer should use the new effect info format. + public bool IsEffectInfoVersion2Supported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision9); + } + /// /// Get the version of the . /// diff --git a/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs index ca37e0903..24cc93af0 100644 --- a/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs +++ b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs @@ -371,6 +371,49 @@ namespace Ryujinx.Audio.Renderer.Server } } + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The limiter parameter. + /// The limiter state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + public void GenerateLimiterEffectVersion1(uint bufferOffset, LimiterParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + LimiterCommandVersion1 command = new LimiterCommandVersion1(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The limiter parameter. + /// The limiter state. + /// The DSP effect result state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + public void GenerateLimiterEffectVersion2(uint bufferOffset, LimiterParameter parameter, Memory state, Memory effectResultState, bool isEnabled, ulong workBuffer, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + LimiterCommandVersion2 command = new LimiterCommandVersion2(bufferOffset, parameter, state, effectResultState, isEnabled, workBuffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + /// /// Generate a new . /// diff --git a/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs index d2499c1da..01e7c9276 100644 --- a/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs +++ b/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs @@ -538,7 +538,25 @@ namespace Ryujinx.Audio.Renderer.Server } } - private void GenerateEffect(ref MixState mix, BaseEffect effect) + private void GenerateLimiterEffect(uint bufferOffset, LimiterEffect effect, int nodeId, int effectId) + { + Debug.Assert(effect.Type == EffectType.Limiter); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + if (_rendererContext.BehaviourContext.IsEffectInfoVersion2Supported()) + { + Memory dspResultState = _effectContext.GetDspStateMemory(effectId); + + _commandBuffer.GenerateLimiterEffectVersion2(bufferOffset, effect.Parameter, effect.State, dspResultState, effect.IsEnabled, workBuffer, nodeId); + } + else + { + _commandBuffer.GenerateLimiterEffectVersion1(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId); + } + } + + private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect) { int nodeId = mix.NodeId; @@ -576,6 +594,9 @@ namespace Ryujinx.Audio.Renderer.Server case EffectType.BiquadFilter: GenerateBiquadFilterEffect(mix.BufferOffset, (BiquadFilterEffect)effect, nodeId); break; + case EffectType.Limiter: + GenerateLimiterEffect(mix.BufferOffset, (LimiterEffect)effect, nodeId, effectId); + break; default: throw new NotImplementedException($"Unsupported effect type {effect.Type}"); } @@ -611,7 +632,7 @@ namespace Ryujinx.Audio.Renderer.Server if (!effect.ShouldSkip()) { - GenerateEffect(ref mix, effect); + GenerateEffect(ref mix, effectOrder, effect); } } } diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs index 81d3b57bb..feb3706fd 100644 --- a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs +++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs @@ -176,5 +176,15 @@ namespace Ryujinx.Audio.Renderer.Server { return 0; } + + public uint Estimate(LimiterCommandVersion1 command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion2 command) + { + return 0; + } } } diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs index 0f4fe3c2c..227f3c815 100644 --- a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs +++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs @@ -540,5 +540,15 @@ namespace Ryujinx.Audio.Renderer.Server { return 0; } + + public uint Estimate(LimiterCommandVersion1 command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion2 command) + { + return 0; + } } } diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs index b48ff8b5e..e00fcf7b1 100644 --- a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs +++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs @@ -18,6 +18,7 @@ using Ryujinx.Audio.Common; using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Parameter.Effect; using System; using System.Diagnostics; using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; @@ -632,5 +633,127 @@ namespace Ryujinx.Audio.Renderer.Server throw new NotImplementedException($"{format}"); } } + + private uint EstimateLimiterCommandCommon(LimiterParameter parameter, bool enabled) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (enabled) + { + switch (parameter.ChannelCount) + { + case 1: + return (uint)21392.0f; + case 2: + return (uint)26829.0f; + case 4: + return (uint)32405.0f; + case 6: + return (uint)52219.0f; + default: + throw new NotImplementedException($"{parameter.ChannelCount}"); + } + } + else + { + switch (parameter.ChannelCount) + { + case 1: + return (uint)897.0f; + case 2: + return (uint)931.55f; + case 4: + return (uint)975.39f; + case 6: + return (uint)1016.8f; + default: + throw new NotImplementedException($"{parameter.ChannelCount}"); + } + } + } + + if (enabled) + { + switch (parameter.ChannelCount) + { + case 1: + return (uint)30556.0f; + case 2: + return (uint)39011.0f; + case 4: + return (uint)48270.0f; + case 6: + return (uint)76712.0f; + default: + throw new NotImplementedException($"{parameter.ChannelCount}"); + } + } + else + { + switch (parameter.ChannelCount) + { + case 1: + return (uint)874.43f; + case 2: + return (uint)921.55f; + case 4: + return (uint)945.26f; + case 6: + return (uint)992.26f; + default: + throw new NotImplementedException($"{parameter.ChannelCount}"); + } + } + } + + public uint Estimate(LimiterCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled); + } + + public uint Estimate(LimiterCommandVersion2 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (!command.Parameter.StatisticsEnabled || !command.IsEffectEnabled) + { + return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled); + } + + if (_sampleCount == 160) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)23309.0f; + case 2: + return (uint)29954.0f; + case 4: + return (uint)35807.0f; + case 6: + return (uint)58340.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)33526.0f; + case 2: + return (uint)43549.0f; + case 4: + return (uint)52190.0f; + case 6: + return (uint)85527.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } } } diff --git a/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs index df82945bc..eac1708ed 100644 --- a/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs +++ b/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs @@ -50,7 +50,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return WorkBuffers[index].GetReference(true); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { Debug.Assert(IsTypeValid(ref parameter)); diff --git a/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs index 7529f894c..c7b06e7aa 100644 --- a/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs +++ b/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs @@ -98,7 +98,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// /// The user parameter. /// Returns true if the sent by the user matches the internal . - public bool IsTypeValid(ref EffectInParameter parameter) + public bool IsTypeValid(ref T parameter) where T: unmanaged, IEffectInParameter { return parameter.Type == TargetEffectType; } @@ -115,7 +115,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// Update the internal common parameters from a user parameter. /// /// The user parameter. - protected void UpdateParameterBase(ref EffectInParameter parameter) + protected void UpdateParameterBase(ref T parameter) where T : unmanaged, IEffectInParameter { MixId = parameter.MixId; ProcessingOrder = parameter.ProcessingOrder; @@ -154,12 +154,38 @@ namespace Ryujinx.Audio.Renderer.Server.Effect } /// - /// Update the internal state from a user parameter. + /// Initialize the given result state. + /// + /// The state to initalize + public virtual void InitializeResultState(ref EffectResultState state) {} + + /// + /// Update the result state with . + /// + /// The destination result state + /// The source result state + public virtual void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState) {} + + /// + /// Update the internal state from a user version 1 parameter. /// /// The possible that was generated. /// The user parameter. /// The mapper to use. - public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + updateErrorInfo = new ErrorInfo(); + } + + /// + /// Update the internal state from a user version 2 parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The mapper to use. + public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) { Debug.Assert(IsTypeValid(ref parameter)); @@ -206,26 +232,26 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// /// The given user output. /// If set to true, the is active. - public void StoreStatus(ref EffectOutStatus outStatus, bool isAudioRendererActive) + public void StoreStatus(ref T outStatus, bool isAudioRendererActive) where T: unmanaged, IEffectOutStatus { if (isAudioRendererActive) { if (UsageState == UsageState.Disabled) { - outStatus.State = EffectOutStatus.EffectState.Disabled; + outStatus.State = EffectState.Disabled; } else { - outStatus.State = EffectOutStatus.EffectState.Enabled; + outStatus.State = EffectState.Enabled; } } else if (UsageState == UsageState.New) { - outStatus.State = EffectOutStatus.EffectState.Enabled; + outStatus.State = EffectState.Enabled; } else { - outStatus.State = EffectOutStatus.EffectState.Disabled; + outStatus.State = EffectState.Disabled; } } @@ -249,6 +275,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return PerformanceDetailType.Reverb3d; case EffectType.BufferMix: return PerformanceDetailType.Mix; + case EffectType.Limiter: + return PerformanceDetailType.Limiter; default: throw new NotImplementedException($"{Type}"); } diff --git a/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs index 35ba8a0d9..6c91b6070 100644 --- a/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs +++ b/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs @@ -52,7 +52,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect public override EffectType TargetEffectType => EffectType.BiquadFilter; - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { Debug.Assert(IsTypeValid(ref parameter)); diff --git a/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs index 14ed3950b..05cccad91 100644 --- a/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs +++ b/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs @@ -36,7 +36,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect public override EffectType TargetEffectType => EffectType.BufferMix; - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { Debug.Assert(IsTypeValid(ref parameter)); diff --git a/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs index df3e5ee75..a5b3dbc5b 100644 --- a/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs +++ b/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs @@ -54,7 +54,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { Debug.Assert(IsTypeValid(ref parameter)); diff --git a/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs b/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs index ff6051aec..074f4375a 100644 --- a/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs +++ b/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs @@ -15,6 +15,9 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using System; using System.Diagnostics; namespace Ryujinx.Audio.Renderer.Server.Effect @@ -34,6 +37,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// private uint _effectCount; + private EffectResultState[] _resultStatesCpu; + private EffectResultState[] _resultStatesDsp; + /// /// Create a new . /// @@ -47,7 +53,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// Initialize the . /// /// The total effect count. - public void Initialize(uint effectCount) + /// The total result state count. + public void Initialize(uint effectCount, uint resultStateCount) { _effectCount = effectCount; _effects = new BaseEffect[effectCount]; @@ -56,6 +63,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect { _effects[i] = new BaseEffect(); } + + _resultStatesCpu = new EffectResultState[resultStateCount]; + _resultStatesDsp = new EffectResultState[resultStateCount]; } /// @@ -78,5 +88,53 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return ref _effects[index]; } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + /// The returned should only be used when updating the server state. + public ref EffectResultState GetState(int index) + { + Debug.Assert(index >= 0 && index < _resultStatesCpu.Length); + + return ref _resultStatesCpu[index]; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + /// The returned should only be used in the context of processing on the . + public ref EffectResultState GetDspState(int index) + { + Debug.Assert(index >= 0 && index < _resultStatesDsp.Length); + + return ref _resultStatesDsp[index]; + } + + /// + /// Get a memory instance to a at the given . + /// + /// The index to use. + /// A memory instance to a at the given . + /// The returned should only be used in the context of processing on the . + public Memory GetDspStateMemory(int index) + { + return SpanIOHelper.GetMemory(_resultStatesDsp.AsMemory(), index, (uint)_resultStatesDsp.Length); + } + + /// + /// Update internal state during command generation. + /// + public void UpdateResultStateForCommandGeneration() + { + for (int index = 0; index < _resultStatesCpu.Length; index++) + { + _effects[index].UpdateResultState(ref _resultStatesCpu[index], ref _resultStatesDsp[index]); + } + } } } diff --git a/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs new file mode 100644 index 000000000..fa04c0273 --- /dev/null +++ b/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs @@ -0,0 +1,112 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a limiter effect. + /// + public class LimiterEffect : BaseEffect + { + /// + /// The limiter parameter. + /// + public LimiterParameter Parameter; + + /// + /// The limiter state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public LimiterEffect() + { + State = new LimiterState[1]; + } + + public override EffectType TargetEffectType => EffectType.Limiter; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + UpdateParameterBase(ref parameter); + + Parameter = limiterParameter; + + IsEnabled = parameter.IsEnabled; + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.Status = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + Parameter.StatisticsReset = false; + } + + public override void InitializeResultState(ref EffectResultState state) + { + ref LimiterStatistics statistics = ref MemoryMarshal.Cast(state.SpecificData)[0]; + + statistics.Reset(); + } + + public override void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState) + { + destState = srcState; + } + } +} diff --git a/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs index d9f237992..7b8fd6c41 100644 --- a/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs +++ b/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs @@ -53,7 +53,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T: unmanaged, IEffectInParameter { Debug.Assert(IsTypeValid(ref parameter)); diff --git a/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs index 4c81f7299..309083960 100644 --- a/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs +++ b/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs @@ -56,7 +56,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { Debug.Assert(IsTypeValid(ref parameter)); diff --git a/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs b/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs index 119ec9078..eae48be65 100644 --- a/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs +++ b/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs @@ -48,5 +48,7 @@ namespace Ryujinx.Audio.Renderer.Server uint Estimate(DeviceSinkCommand command); uint Estimate(DownMixSurroundToStereoCommand command); uint Estimate(UpsampleCommand command); + uint Estimate(LimiterCommandVersion1 command); + uint Estimate(LimiterCommandVersion2 command); } } diff --git a/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs index 3a336af35..fd7c93b19 100644 --- a/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs +++ b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs @@ -18,7 +18,6 @@ using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Parameter; using System; -using System.Runtime.CompilerServices; namespace Ryujinx.Audio.Renderer.Server.Performance { diff --git a/Ryujinx.Audio/Renderer/Server/StateUpdater.cs b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs index 77935b754..e7a982c4a 100644 --- a/Ryujinx.Audio/Renderer/Server/StateUpdater.cs +++ b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs @@ -224,7 +224,7 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.Success; } - private static void ResetEffect(ref BaseEffect effect, ref EffectInParameter parameter, PoolMapper mapper) + private static void ResetEffect(ref BaseEffect effect, ref T parameter, PoolMapper mapper) where T: unmanaged, IEffectInParameter { effect.ForceUnmapBuffers(mapper); @@ -251,6 +251,9 @@ namespace Ryujinx.Audio.Renderer.Server case EffectType.BiquadFilter: effect = new BiquadFilterEffect(); break; + case EffectType.Limiter: + effect = new LimiterEffect(); + break; default: throw new NotImplementedException($"EffectType {parameter.Type} not implemented!"); } @@ -258,14 +261,26 @@ namespace Ryujinx.Audio.Renderer.Server public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory memoryPools) { - if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) + if (_behaviourContext.IsEffectInfoVersion2Supported()) + { + return UpdateEffectsVersion2(context, isAudioRendererActive, memoryPools); + } + else + { + return UpdateEffectsVersion1(context, isAudioRendererActive, memoryPools); + } + } + + public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) { return ResultCode.InvalidUpdateInfo; } int initialOutputSize = _output.Length; - ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.EffectsSize).Span); + ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.EffectsSize).Span); _input = _input.Slice((int)_inputHeader.EffectsSize); @@ -273,9 +288,65 @@ namespace Ryujinx.Audio.Renderer.Server for (int i = 0; i < context.GetCount(); i++) { - EffectInParameter parameter = parameters[i]; + EffectInParameterVersion2 parameter = parameters[i]; - ref EffectOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + ref BaseEffect effect = ref context.GetEffect(i); + + if (!effect.IsTypeValid(ref parameter)) + { + ResetEffect(ref effect, ref parameter, mapper); + } + + effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper); + + if (updateErrorInfo.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref updateErrorInfo); + } + + effect.StoreStatus(ref outStatus, isAudioRendererActive); + + if (parameter.IsNew) + { + effect.InitializeResultState(ref context.GetDspState(i)); + effect.InitializeResultState(ref context.GetState(i)); + } + + effect.UpdateResultState(ref outStatus.ResultState, ref context.GetState(i)); + } + + int currentOutputSize = _output.Length; + + OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.EffectsSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); + + return ResultCode.Success; + } + + public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.EffectsSize).Span); + + _input = _input.Slice((int)_inputHeader.EffectsSize); + + PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + for (int i = 0; i < context.GetCount(); i++) + { + EffectInParameterVersion1 parameter = parameters[i]; + + ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; ref BaseEffect effect = ref context.GetEffect(i); @@ -296,7 +367,7 @@ namespace Ryujinx.Audio.Renderer.Server int currentOutputSize = _output.Length; - OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf() * context.GetCount()); OutputHeader.TotalSize += OutputHeader.EffectsSize; Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); diff --git a/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs index 2b482cdb0..c4ac82f07 100644 --- a/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs +++ b/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs @@ -9,7 +9,8 @@ namespace Ryujinx.Tests.Audio.Renderer [Test] public void EnsureTypeSize() { - Assert.AreEqual(0xC0, Unsafe.SizeOf()); + Assert.AreEqual(0xC0, Unsafe.SizeOf()); + Assert.AreEqual(0xC0, Unsafe.SizeOf()); } } } diff --git a/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs b/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs index 199bcf8a7..8cb57da37 100644 --- a/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs +++ b/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs @@ -9,7 +9,8 @@ namespace Ryujinx.Tests.Audio.Renderer [Test] public void EnsureTypeSize() { - Assert.AreEqual(0x10, Unsafe.SizeOf()); + Assert.AreEqual(0x10, Unsafe.SizeOf()); + Assert.AreEqual(0x90, Unsafe.SizeOf()); } } } diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterParameterTests.cs new file mode 100644 index 000000000..8512ebd47 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterParameterTests.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class LimiterParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x44, Unsafe.SizeOf()); + } + } +} + diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterStatisticsTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterStatisticsTests.cs new file mode 100644 index 000000000..43645ae42 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterStatisticsTests.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class LimiterStatisticsTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x30, Unsafe.SizeOf()); + } + } +} +