From 4d84df94873a070f6f5c199438f957b24d8cf8a9 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Fri, 17 May 2024 16:46:43 -0300 Subject: [PATCH] Update audio renderer to REV12: Add support for splitter biquad filter (#6813) * Update audio renderer to REV12: Add support for splitter biquad filter * Formatting * Official names * Update BiquadFilterState size + other fixes * Update tests * Update comment for version 2 * Size test for SplitterDestinationVersion2 * Should use Volume1 if no ramp --- .../Renderer/Common/VoiceUpdateState.cs | 1 - .../Renderer/Dsp/BiquadFilterHelper.cs | 234 ++++++- .../Dsp/Command/BiquadFilterAndMixCommand.cs | 123 ++++ .../Renderer/Dsp/Command/CommandType.cs | 4 +- .../Dsp/Command/MixRampGroupedCommand.cs | 16 +- .../MultiTapBiquadFilterAndMixCommand.cs | 145 +++++ ...mand.cs => MultiTapBiquadFilterCommand.cs} | 6 +- .../Renderer/Dsp/State/BiquadFilterState.cs | 6 +- .../ISplitterDestinationInParameter.cs | 43 ++ ...SplitterDestinationInParameterVersion1.cs} | 15 +- .../SplitterDestinationInParameterVersion2.cs | 81 +++ .../Renderer/Server/AudioRenderSystem.cs | 26 +- .../Renderer/Server/BehaviourContext.cs | 22 +- .../Renderer/Server/CommandBuffer.cs | 124 +++- .../Renderer/Server/CommandGenerator.cs | 570 ++++++++++++------ .../CommandProcessingTimeEstimatorVersion1.cs | 12 +- .../CommandProcessingTimeEstimatorVersion2.cs | 12 +- .../CommandProcessingTimeEstimatorVersion3.cs | 12 +- .../CommandProcessingTimeEstimatorVersion4.cs | 2 +- .../CommandProcessingTimeEstimatorVersion5.cs | 48 ++ .../Server/ICommandProcessingTimeEstimator.cs | 4 +- .../Renderer/Server/Mix/MixState.cs | 6 +- .../Server/Splitter/SplitterContext.cs | 218 +++++-- .../Server/Splitter/SplitterDestination.cs | 375 +++++++++--- .../Splitter/SplitterDestinationVersion1.cs | 206 +++++++ .../Splitter/SplitterDestinationVersion2.cs | 250 ++++++++ .../Renderer/Server/Splitter/SplitterState.cs | 74 ++- .../Renderer/Server/BehaviourContextTests.cs | 96 ++- .../Server/SplitterDestinationTests.cs | 3 +- 29 files changed, 2342 insertions(+), 392 deletions(-) create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs rename src/Ryujinx.Audio/Renderer/Dsp/Command/{GroupedBiquadFilterCommand.cs => MultiTapBiquadFilterCommand.cs} (84%) create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs rename src/Ryujinx.Audio/Renderer/Parameter/{SplitterDestinationInParameter.cs => SplitterDestinationInParameterVersion1.cs} (73%) create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs diff --git a/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs index 608381af..7f881373 100644 --- a/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs +++ b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs @@ -15,7 +15,6 @@ namespace Ryujinx.Audio.Renderer.Common { public const int Align = 0x10; public const int BiquadStateOffset = 0x0; - public const int BiquadStateSize = 0x10; /// /// The state of the biquad filters of this voice. diff --git a/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs index 1a51a1fb..31f614d6 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs @@ -16,10 +16,15 @@ namespace Ryujinx.Audio.Renderer.Dsp /// The biquad filter parameter /// The biquad filter state /// The output buffer to write the result - /// The input buffer to write the result + /// The input buffer to read the samples from /// The count of samples to process [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount) + public static void ProcessBiquadFilter( + ref BiquadFilterParameter parameter, + ref BiquadFilterState state, + Span outputBuffer, + ReadOnlySpan inputBuffer, + uint sampleCount) { float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); @@ -40,6 +45,96 @@ namespace Ryujinx.Audio.Renderer.Dsp } } + /// + /// Apply a single biquad filter and mix the result into the output buffer. + /// + /// This is implemented with a direct form 1. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to read the samples from + /// The count of samples to process + /// Mix volume + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ProcessBiquadFilterAndMix( + ref BiquadFilterParameter parameter, + ref BiquadFilterState state, + Span outputBuffer, + ReadOnlySpan inputBuffer, + uint sampleCount, + float volume) + { + float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); + float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); + float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter); + + float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); + float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2; + + state.State1 = state.State0; + state.State0 = input; + state.State3 = state.State2; + state.State2 = output; + + outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume); + } + } + + /// + /// Apply a single biquad filter and mix the result into the output buffer with volume ramp. + /// + /// This is implemented with a direct form 1. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to read the samples from + /// The count of samples to process + /// Initial mix volume + /// Volume increment step + /// Last filtered sample value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ProcessBiquadFilterAndMixRamp( + ref BiquadFilterParameter parameter, + ref BiquadFilterState state, + Span outputBuffer, + ReadOnlySpan inputBuffer, + uint sampleCount, + float volume, + float ramp) + { + float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); + float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); + float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter); + + float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); + float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); + + float mixState = 0f; + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2; + + state.State1 = state.State0; + state.State0 = input; + state.State3 = state.State2; + state.State2 = output; + + mixState = FloatingPointHelper.MultiplyRoundUp(output, volume); + + outputBuffer[i] += mixState; + volume += ramp; + } + + return mixState; + } + /// /// Apply multiple biquad filter. /// @@ -47,10 +142,15 @@ namespace Ryujinx.Audio.Renderer.Dsp /// The biquad filter parameter /// The biquad filter state /// The output buffer to write the result - /// The input buffer to write the result + /// The input buffer to read the samples from /// The count of samples to process [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ProcessBiquadFilter(ReadOnlySpan parameters, Span states, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount) + public static void ProcessBiquadFilter( + ReadOnlySpan parameters, + Span states, + Span outputBuffer, + ReadOnlySpan inputBuffer, + uint sampleCount) { for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++) { @@ -67,7 +167,7 @@ namespace Ryujinx.Audio.Renderer.Dsp for (int i = 0; i < sampleCount; i++) { - float input = inputBuffer[i]; + float input = stageIndex != 0 ? outputBuffer[i] : inputBuffer[i]; float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2; state.State1 = state.State0; @@ -79,5 +179,129 @@ namespace Ryujinx.Audio.Renderer.Dsp } } } + + /// + /// Apply double biquad filter and mix the result into the output buffer. + /// + /// This is implemented with a direct form 1. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to read the samples from + /// The count of samples to process + /// Mix volume + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ProcessDoubleBiquadFilterAndMix( + ref BiquadFilterParameter parameter0, + ref BiquadFilterParameter parameter1, + ref BiquadFilterState state0, + ref BiquadFilterState state1, + Span outputBuffer, + ReadOnlySpan inputBuffer, + uint sampleCount, + float volume) + { + float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter); + float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter); + float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter); + + float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter); + float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter); + + float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter); + float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter); + float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter); + + float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter); + float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter); + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20; + + state0.State1 = state0.State0; + state0.State0 = input; + state0.State3 = state0.State2; + state0.State2 = output; + + input = output; + output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21; + + state1.State1 = state1.State0; + state1.State0 = input; + state1.State3 = state1.State2; + state1.State2 = output; + + outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume); + } + } + + /// + /// Apply double biquad filter and mix the result into the output buffer with volume ramp. + /// + /// This is implemented with a direct form 1. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to read the samples from + /// The count of samples to process + /// Initial mix volume + /// Volume increment step + /// Last filtered sample value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ProcessDoubleBiquadFilterAndMixRamp( + ref BiquadFilterParameter parameter0, + ref BiquadFilterParameter parameter1, + ref BiquadFilterState state0, + ref BiquadFilterState state1, + Span outputBuffer, + ReadOnlySpan inputBuffer, + uint sampleCount, + float volume, + float ramp) + { + float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter); + float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter); + float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter); + + float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter); + float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter); + + float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter); + float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter); + float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter); + + float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter); + float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter); + + float mixState = 0f; + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20; + + state0.State1 = state0.State0; + state0.State0 = input; + state0.State3 = state0.State2; + state0.State2 = output; + + input = output; + output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21; + + state1.State1 = state1.State0; + state1.State0 = input; + state1.State3 = state1.State2; + state1.State2 = output; + + mixState = FloatingPointHelper.MultiplyRoundUp(output, volume); + + outputBuffer[i] += mixState; + volume += ramp; + } + + return mixState; + } } } diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs new file mode 100644 index 00000000..106fc035 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs @@ -0,0 +1,123 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class BiquadFilterAndMixCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.BiquadFilterAndMix; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + private BiquadFilterParameter _parameter; + + public Memory BiquadFilterState { get; } + public Memory PreviousBiquadFilterState { get; } + + public Memory State { get; } + + public int LastSampleIndex { get; } + + public float Volume0 { get; } + public float Volume1 { get; } + + public bool NeedInitialization { get; } + public bool HasVolumeRamp { get; } + public bool IsFirstMixBuffer { get; } + + public BiquadFilterAndMixCommand( + float volume0, + float volume1, + uint inputBufferIndex, + uint outputBufferIndex, + int lastSampleIndex, + Memory state, + ref BiquadFilterParameter filter, + Memory biquadFilterState, + Memory previousBiquadFilterState, + bool needInitialization, + bool hasVolumeRamp, + bool isFirstMixBuffer, + int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)inputBufferIndex; + OutputBufferIndex = (ushort)outputBufferIndex; + + _parameter = filter; + BiquadFilterState = biquadFilterState; + PreviousBiquadFilterState = previousBiquadFilterState; + + State = state; + LastSampleIndex = lastSampleIndex; + + Volume0 = volume0; + Volume1 = volume1; + + NeedInitialization = needInitialization; + HasVolumeRamp = hasVolumeRamp; + IsFirstMixBuffer = isFirstMixBuffer; + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + if (NeedInitialization) + { + // If there is no previous state, initialize to zero. + + BiquadFilterState.Span[0] = new BiquadFilterState(); + } + else if (IsFirstMixBuffer) + { + // This is the first buffer, set previous state to current state. + + PreviousBiquadFilterState.Span[0] = BiquadFilterState.Span[0]; + } + else + { + // Rewind the current state by copying back the previous state. + + BiquadFilterState.Span[0] = PreviousBiquadFilterState.Span[0]; + } + + if (HasVolumeRamp) + { + float volume = Volume0; + float ramp = (Volume1 - Volume0) / (int)context.SampleCount; + + State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessBiquadFilterAndMixRamp( + ref _parameter, + ref BiquadFilterState.Span[0], + outputBuffer, + inputBuffer, + context.SampleCount, + volume, + ramp); + } + else + { + BiquadFilterHelper.ProcessBiquadFilterAndMix( + ref _parameter, + ref BiquadFilterState.Span[0], + outputBuffer, + inputBuffer, + context.SampleCount, + Volume1); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs index 098a04a0..de5c0ea2 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs @@ -30,8 +30,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command CopyMixBuffer, LimiterVersion1, LimiterVersion2, - GroupedBiquadFilter, + MultiTapBiquadFilter, CaptureBuffer, Compressor, + BiquadFilterAndMix, + MultiTapBiquadFilterAndMix, } } diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs index 3c7dd63b..41ac84c1 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs @@ -24,7 +24,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command public Memory State { get; } - public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span volume0, Span volume1, Memory state, int nodeId) + public MixRampGroupedCommand( + uint mixBufferCount, + uint inputBufferIndex, + uint outputBufferIndex, + ReadOnlySpan volume0, + ReadOnlySpan volume1, + Memory state, + int nodeId) { Enabled = true; MixBufferCount = mixBufferCount; @@ -48,7 +55,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float ProcessMixRampGrouped(Span outputBuffer, ReadOnlySpan inputBuffer, float volume0, float volume1, int sampleCount) + private static float ProcessMixRampGrouped( + Span outputBuffer, + ReadOnlySpan inputBuffer, + float volume0, + float volume1, + int sampleCount) { float ramp = (volume1 - volume0) / sampleCount; float volume = volume0; diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs new file mode 100644 index 00000000..e359371b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs @@ -0,0 +1,145 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class MultiTapBiquadFilterAndMixCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.MultiTapBiquadFilterAndMix; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + private BiquadFilterParameter _parameter0; + private BiquadFilterParameter _parameter1; + + public Memory BiquadFilterState0 { get; } + public Memory BiquadFilterState1 { get; } + public Memory PreviousBiquadFilterState0 { get; } + public Memory PreviousBiquadFilterState1 { get; } + + public Memory State { get; } + + public int LastSampleIndex { get; } + + public float Volume0 { get; } + public float Volume1 { get; } + + public bool NeedInitialization0 { get; } + public bool NeedInitialization1 { get; } + public bool HasVolumeRamp { get; } + public bool IsFirstMixBuffer { get; } + + public MultiTapBiquadFilterAndMixCommand( + float volume0, + float volume1, + uint inputBufferIndex, + uint outputBufferIndex, + int lastSampleIndex, + Memory state, + ref BiquadFilterParameter filter0, + ref BiquadFilterParameter filter1, + Memory biquadFilterState0, + Memory biquadFilterState1, + Memory previousBiquadFilterState0, + Memory previousBiquadFilterState1, + bool needInitialization0, + bool needInitialization1, + bool hasVolumeRamp, + bool isFirstMixBuffer, + int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)inputBufferIndex; + OutputBufferIndex = (ushort)outputBufferIndex; + + _parameter0 = filter0; + _parameter1 = filter1; + BiquadFilterState0 = biquadFilterState0; + BiquadFilterState1 = biquadFilterState1; + PreviousBiquadFilterState0 = previousBiquadFilterState0; + PreviousBiquadFilterState1 = previousBiquadFilterState1; + + State = state; + LastSampleIndex = lastSampleIndex; + + Volume0 = volume0; + Volume1 = volume1; + + NeedInitialization0 = needInitialization0; + NeedInitialization1 = needInitialization1; + HasVolumeRamp = hasVolumeRamp; + IsFirstMixBuffer = isFirstMixBuffer; + } + + private void UpdateState(Memory state, Memory previousState, bool needInitialization) + { + if (needInitialization) + { + // If there is no previous state, initialize to zero. + + state.Span[0] = new BiquadFilterState(); + } + else if (IsFirstMixBuffer) + { + // This is the first buffer, set previous state to current state. + + previousState.Span[0] = state.Span[0]; + } + else + { + // Rewind the current state by copying back the previous state. + + state.Span[0] = previousState.Span[0]; + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + UpdateState(BiquadFilterState0, PreviousBiquadFilterState0, NeedInitialization0); + UpdateState(BiquadFilterState1, PreviousBiquadFilterState1, NeedInitialization1); + + if (HasVolumeRamp) + { + float volume = Volume0; + float ramp = (Volume1 - Volume0) / (int)context.SampleCount; + + State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessDoubleBiquadFilterAndMixRamp( + ref _parameter0, + ref _parameter1, + ref BiquadFilterState0.Span[0], + ref BiquadFilterState1.Span[0], + outputBuffer, + inputBuffer, + context.SampleCount, + volume, + ramp); + } + else + { + BiquadFilterHelper.ProcessDoubleBiquadFilterAndMix( + ref _parameter0, + ref _parameter1, + ref BiquadFilterState0.Span[0], + ref BiquadFilterState1.Span[0], + outputBuffer, + inputBuffer, + context.SampleCount, + Volume1); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs similarity index 84% rename from src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs rename to src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs index 7af851bd..e159f8ef 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs @@ -4,13 +4,13 @@ using System; namespace Ryujinx.Audio.Renderer.Dsp.Command { - public class GroupedBiquadFilterCommand : ICommand + public class MultiTapBiquadFilterCommand : ICommand { public bool Enabled { get; set; } public int NodeId { get; } - public CommandType CommandType => CommandType.GroupedBiquadFilter; + public CommandType CommandType => CommandType.MultiTapBiquadFilter; public uint EstimatedProcessingTime { get; set; } @@ -20,7 +20,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command private readonly int _outputBufferIndex; private readonly bool[] _isInitialized; - public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId) + public MultiTapBiquadFilterCommand(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId) { _parameters = filters.ToArray(); _biquadFilterStates = biquadFilterStateMemory; diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs index f9a32b3f..58a2d9cc 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs @@ -2,12 +2,16 @@ using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Dsp.State { - [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x20)] public struct BiquadFilterState { public float State0; public float State1; public float State2; public float State3; + public float State4; + public float State5; + public float State6; + public float State7; } } diff --git a/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs new file mode 100644 index 00000000..807232f2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs @@ -0,0 +1,43 @@ +using Ryujinx.Common.Memory; +using System; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Generic interface for the splitter destination parameters. + /// + public interface ISplitterDestinationInParameter + { + /// + /// Target splitter destination data id. + /// + int Id { get; } + + /// + /// The mix to output the result of the splitter. + /// + int DestinationId { get; } + + /// + /// Biquad filter parameters. + /// + Array2 BiquadFilters { get; } + + /// + /// Set to true if in use. + /// + bool IsUsed { get; } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + Span MixBufferVolume { get; } + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + bool IsMagicValid(); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs similarity index 73% rename from src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs rename to src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs index b74b67be..029c001e 100644 --- a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Memory; using Ryujinx.Common.Utilities; using System; using System.Runtime.InteropServices; @@ -5,10 +6,10 @@ using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Parameter { /// - /// Input header for a splitter destination update. + /// Input header for a splitter destination version 1 update. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct SplitterDestinationInParameter + public struct SplitterDestinationInParameterVersion1 : ISplitterDestinationInParameter { /// /// Magic of the input header. @@ -41,7 +42,7 @@ namespace Ryujinx.Audio.Renderer.Parameter /// private unsafe fixed byte _reserved[3]; - [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)] + [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] private struct MixArray { } /// @@ -50,6 +51,14 @@ namespace Ryujinx.Audio.Renderer.Parameter /// Used when a splitter id is specified in the mix. public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolume); + readonly int ISplitterDestinationInParameter.Id => Id; + + readonly int ISplitterDestinationInParameter.DestinationId => DestinationId; + + readonly Array2 ISplitterDestinationInParameter.BiquadFilters => default; + + readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed; + /// /// The expected constant of any input header. /// diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs new file mode 100644 index 00000000..312be8b7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs @@ -0,0 +1,81 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for a splitter destination version 2 update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterDestinationInParameterVersion2 : ISplitterDestinationInParameter + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// Target splitter destination data id. + /// + public int Id; + + /// + /// Mix buffer volumes storage. + /// + private MixArray _mixBufferVolume; + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId; + + /// + /// Biquad filter parameters. + /// + public Array2 BiquadFilters; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Reserved/padding. + /// + private unsafe fixed byte _reserved[11]; + + [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolume); + + readonly int ISplitterDestinationInParameter.Id => Id; + + readonly int ISplitterDestinationInParameter.DestinationId => DestinationId; + + readonly Array2 ISplitterDestinationInParameter.BiquadFilters => BiquadFilters; + + readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed; + + /// + /// The expected constant of any input header. + /// + private const uint ValidMagic = 0x44444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public readonly bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs index 9b56f5cb..246889c4 100644 --- a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs +++ b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs @@ -1,6 +1,7 @@ using Ryujinx.Audio.Integration; using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Dsp.State; using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Server.Effect; using Ryujinx.Audio.Renderer.Server.MemoryPool; @@ -173,6 +174,22 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.WorkBufferTooSmall; } + Memory splitterBqfStates = Memory.Empty; + + if (_behaviourContext.IsBiquadFilterParameterForSplitterEnabled() && + parameter.SplitterCount > 0 && + parameter.SplitterDestinationCount > 0) + { + splitterBqfStates = workBufferAllocator.Allocate(parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10); + + if (splitterBqfStates.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + splitterBqfStates.Span.Clear(); + } + // Invalidate DSP cache on what was currently allocated with workBuffer. AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset); @@ -292,7 +309,7 @@ namespace Ryujinx.Audio.Renderer.Server state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); } - if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator)) + if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator, splitterBqfStates)) { return ResultCode.WorkBufferTooSmall; } @@ -775,6 +792,13 @@ namespace Ryujinx.Audio.Renderer.Server // Splitter size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter); + if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled() && + parameter.SplitterCount > 0 && + parameter.SplitterDestinationCount > 0) + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10); + } + // DSP Voice size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceUpdateState.Align); diff --git a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs index fe1dfc4b..32c7de6c 100644 --- a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs +++ b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs @@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Server /// was added to supply the count of update done sent to the DSP. /// A new version of the command estimator was added to address timing changes caused by the voice changes. /// Additionally, the rendering limit percent was incremented to 80%. - /// /// /// This was added in system update 6.0.0 public const int Revision5 = 5 << 24; @@ -101,10 +100,18 @@ namespace Ryujinx.Audio.Renderer.Server /// This was added in system update 14.0.0 but some changes were made in 15.0.0 public const int Revision11 = 11 << 24; + /// + /// REV12: + /// Two new commands were added to for biquad filtering and mixing (with optinal volume ramp) on the same command. + /// Splitter destinations can now specify up to two biquad filtering parameters, used for filtering the buffer before mixing. + /// + /// This was added in system update 17.0.0 + public const int Revision12 = 12 << 24; + /// /// Last revision supported by the implementation. /// - public const int LastRevision = Revision11; + public const int LastRevision = Revision12; /// /// Target revision magic supported by the implementation. @@ -354,7 +361,7 @@ namespace Ryujinx.Audio.Renderer.Server /// Check if the audio renderer should use an optimized Biquad Filter (Direct Form 1) in case of two biquad filters are defined on a voice. /// /// True if the audio renderer should use the optimization. - public bool IsBiquadFilterGroupedOptimizationSupported() + public bool UseMultiTapBiquadFilterProcessing() { return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10); } @@ -368,6 +375,15 @@ namespace Ryujinx.Audio.Renderer.Server return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11); } + /// + /// Check if the audio renderer should support biquad filter on splitter. + /// + /// True if the audio renderer support biquad filter on splitter + public bool IsBiquadFilterParameterForSplitterEnabled() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12); + } + /// /// Get the version of the . /// diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs index f4174a91..702f0546 100644 --- a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs +++ b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs @@ -204,7 +204,7 @@ namespace Ryujinx.Audio.Renderer.Server } /// - /// Create a new . + /// Create a new . /// /// The base index of the input and output buffer. /// The biquad filter parameters. @@ -213,9 +213,9 @@ namespace Ryujinx.Audio.Renderer.Server /// The output buffer offset. /// Set to true if the biquad filter state is initialized. /// The node id associated to this command. - public void GenerateGroupedBiquadFilter(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId) + public void GenerateMultiTapBiquadFilter(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId) { - GroupedBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId); + MultiTapBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId); command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); @@ -232,7 +232,7 @@ namespace Ryujinx.Audio.Renderer.Server /// The new volume. /// The to generate the command from. /// The node id associated to this command. - public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span previousVolume, Span volume, Memory state, int nodeId) + public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, ReadOnlySpan previousVolume, ReadOnlySpan volume, Memory state, int nodeId) { MixRampGroupedCommand command = new(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId); @@ -260,6 +260,120 @@ namespace Ryujinx.Audio.Renderer.Server AddCommand(command); } + /// + /// Generate a new . + /// + /// The previous volume. + /// The new volume. + /// The input buffer index. + /// The output buffer index. + /// The index in the array to store the ramped sample. + /// The to generate the command from. + /// The biquad filter parameter. + /// The biquad state. + /// The previous biquad state. + /// Set to true if the biquad filter state needs to be initialized. + /// Set to true if the mix has volume ramp, and should be taken into account. + /// Set to true if the buffer is the first mix buffer. + /// The node id associated to this command. + public void GenerateBiquadFilterAndMix( + float previousVolume, + float volume, + uint inputBufferIndex, + uint outputBufferIndex, + int lastSampleIndex, + Memory state, + ref BiquadFilterParameter filter, + Memory biquadFilterState, + Memory previousBiquadFilterState, + bool needInitialization, + bool hasVolumeRamp, + bool isFirstMixBuffer, + int nodeId) + { + BiquadFilterAndMixCommand command = new( + previousVolume, + volume, + inputBufferIndex, + outputBufferIndex, + lastSampleIndex, + state, + ref filter, + biquadFilterState, + previousBiquadFilterState, + needInitialization, + hasVolumeRamp, + isFirstMixBuffer, + nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The previous volume. + /// The new volume. + /// The input buffer index. + /// The output buffer index. + /// The index in the array to store the ramped sample. + /// The to generate the command from. + /// First biquad filter parameter. + /// Second biquad filter parameter. + /// First biquad state. + /// Second biquad state. + /// First previous biquad state. + /// Second previous biquad state. + /// Set to true if the first biquad filter state needs to be initialized. + /// Set to true if the second biquad filter state needs to be initialized. + /// Set to true if the mix has volume ramp, and should be taken into account. + /// Set to true if the buffer is the first mix buffer. + /// The node id associated to this command. + public void GenerateMultiTapBiquadFilterAndMix( + float previousVolume, + float volume, + uint inputBufferIndex, + uint outputBufferIndex, + int lastSampleIndex, + Memory state, + ref BiquadFilterParameter filter0, + ref BiquadFilterParameter filter1, + Memory biquadFilterState0, + Memory biquadFilterState1, + Memory previousBiquadFilterState0, + Memory previousBiquadFilterState1, + bool needInitialization0, + bool needInitialization1, + bool hasVolumeRamp, + bool isFirstMixBuffer, + int nodeId) + { + MultiTapBiquadFilterAndMixCommand command = new( + previousVolume, + volume, + inputBufferIndex, + outputBufferIndex, + lastSampleIndex, + state, + ref filter0, + ref filter1, + biquadFilterState0, + biquadFilterState1, + previousBiquadFilterState0, + previousBiquadFilterState1, + needInitialization0, + needInitialization1, + hasVolumeRamp, + isFirstMixBuffer, + nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + /// /// Generate a new . /// @@ -268,7 +382,7 @@ namespace Ryujinx.Audio.Renderer.Server /// The buffer count. /// The node id associated to this command. /// The target sample rate in use. - public void GenerateDepopForMixBuffersCommand(Memory depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate) + public void GenerateDepopForMixBuffers(Memory depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate) { DepopForMixBuffersCommand command = new(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate); diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs index ae8f699f..d798230c 100644 --- a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs +++ b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs @@ -12,6 +12,7 @@ using Ryujinx.Audio.Renderer.Server.Voice; using Ryujinx.Audio.Renderer.Utils; using System; using System.Diagnostics; +using System.Runtime.CompilerServices; namespace Ryujinx.Audio.Renderer.Server { @@ -46,12 +47,13 @@ namespace Ryujinx.Audio.Renderer.Server { ref MixState mix = ref _mixContext.GetState(voiceState.MixId); - _commandBuffer.GenerateDepopPrepare(dspState, - _rendererContext.DepopBuffer, - mix.BufferCount, - mix.BufferOffset, - voiceState.NodeId, - voiceState.WasPlaying); + _commandBuffer.GenerateDepopPrepare( + dspState, + _rendererContext.DepopBuffer, + mix.BufferCount, + mix.BufferOffset, + voiceState.NodeId, + voiceState.WasPlaying); } else if (voiceState.SplitterId != Constants.UnusedSplitterId) { @@ -59,15 +61,13 @@ namespace Ryujinx.Audio.Renderer.Server while (true) { - Span destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++); + SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++); - if (destinationSpan.IsEmpty) + if (destination.IsNull) { break; } - ref SplitterDestination destination = ref destinationSpan[0]; - if (destination.IsConfigured()) { int mixId = destination.DestinationId; @@ -76,12 +76,13 @@ namespace Ryujinx.Audio.Renderer.Server { ref MixState mix = ref _mixContext.GetState(mixId); - _commandBuffer.GenerateDepopPrepare(dspState, - _rendererContext.DepopBuffer, - mix.BufferCount, - mix.BufferOffset, - voiceState.NodeId, - voiceState.WasPlaying); + _commandBuffer.GenerateDepopPrepare( + dspState, + _rendererContext.DepopBuffer, + mix.BufferCount, + mix.BufferOffset, + voiceState.NodeId, + voiceState.WasPlaying); destination.MarkAsNeedToUpdateInternalState(); } @@ -95,35 +96,39 @@ namespace Ryujinx.Audio.Renderer.Server if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported()) { - _commandBuffer.GenerateDataSourceVersion2(ref voiceState, - dspState, - (ushort)_rendererContext.MixBufferCount, - (ushort)channelIndex, - voiceState.NodeId); + _commandBuffer.GenerateDataSourceVersion2( + ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); } else { switch (voiceState.SampleFormat) { case SampleFormat.PcmInt16: - _commandBuffer.GeneratePcmInt16DataSourceVersion1(ref voiceState, - dspState, - (ushort)_rendererContext.MixBufferCount, - (ushort)channelIndex, - voiceState.NodeId); + _commandBuffer.GeneratePcmInt16DataSourceVersion1( + ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); break; case SampleFormat.PcmFloat: - _commandBuffer.GeneratePcmFloatDataSourceVersion1(ref voiceState, - dspState, - (ushort)_rendererContext.MixBufferCount, - (ushort)channelIndex, - voiceState.NodeId); + _commandBuffer.GeneratePcmFloatDataSourceVersion1( + ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); break; case SampleFormat.Adpcm: - _commandBuffer.GenerateAdpcmDataSourceVersion1(ref voiceState, - dspState, - (ushort)_rendererContext.MixBufferCount, - voiceState.NodeId); + _commandBuffer.GenerateAdpcmDataSourceVersion1( + ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + voiceState.NodeId); break; default: throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}"); @@ -134,14 +139,14 @@ namespace Ryujinx.Audio.Renderer.Server private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory state, int baseIndex, int bufferOffset, int nodeId) { - bool supportsOptimizedPath = _rendererContext.BehaviourContext.IsBiquadFilterGroupedOptimizationSupported(); + bool supportsOptimizedPath = _rendererContext.BehaviourContext.UseMultiTapBiquadFilterProcessing(); if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable) { - Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)]; + Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(Unsafe.SizeOf() * Constants.VoiceBiquadFilterCount)]; Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory); - _commandBuffer.GenerateGroupedBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId); + _commandBuffer.GenerateMultiTapBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId); } else { @@ -151,33 +156,134 @@ namespace Ryujinx.Audio.Renderer.Server if (filter.Enable) { - Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)]; - + Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(Unsafe.SizeOf() * Constants.VoiceBiquadFilterCount)]; Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory); - _commandBuffer.GenerateBiquadFilter(baseIndex, - ref filter, - stateMemory.Slice(i, 1), - bufferOffset, - bufferOffset, - !voiceState.BiquadFilterNeedInitialization[i], - nodeId); + _commandBuffer.GenerateBiquadFilter( + baseIndex, + ref filter, + stateMemory.Slice(i, 1), + bufferOffset, + bufferOffset, + !voiceState.BiquadFilterNeedInitialization[i], + nodeId); } } } } - private void GenerateVoiceMix(Span mixVolumes, Span previousMixVolumes, Memory state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId) + private void GenerateVoiceMixWithSplitter( + SplitterDestination destination, + Memory state, + uint bufferOffset, + uint bufferCount, + uint bufferIndex, + int nodeId) + { + ReadOnlySpan mixVolumes = destination.MixBufferVolume; + ReadOnlySpan previousMixVolumes = destination.PreviousMixBufferVolume; + + ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0); + ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1); + + Memory bqfState = _splitterContext.GetBiquadFilterState(destination); + + bool isFirstMixBuffer = true; + + for (int i = 0; i < bufferCount; i++) + { + float previousMixVolume = previousMixVolumes[i]; + float mixVolume = mixVolumes[i]; + + if (mixVolume != 0.0f || previousMixVolume != 0.0f) + { + if (bqf0.Enable && bqf1.Enable) + { + _commandBuffer.GenerateMultiTapBiquadFilterAndMix( + previousMixVolume, + mixVolume, + bufferIndex, + bufferOffset + (uint)i, + i, + state, + ref bqf0, + ref bqf1, + bqfState[..1], + bqfState.Slice(1, 1), + bqfState.Slice(2, 1), + bqfState.Slice(3, 1), + !destination.IsBiquadFilterEnabledPrev(), + !destination.IsBiquadFilterEnabledPrev(), + true, + isFirstMixBuffer, + nodeId); + + destination.UpdateBiquadFilterEnabledPrev(0); + destination.UpdateBiquadFilterEnabledPrev(1); + } + else if (bqf0.Enable) + { + _commandBuffer.GenerateBiquadFilterAndMix( + previousMixVolume, + mixVolume, + bufferIndex, + bufferOffset + (uint)i, + i, + state, + ref bqf0, + bqfState[..1], + bqfState.Slice(1, 1), + !destination.IsBiquadFilterEnabledPrev(), + true, + isFirstMixBuffer, + nodeId); + + destination.UpdateBiquadFilterEnabledPrev(0); + } + else if (bqf1.Enable) + { + _commandBuffer.GenerateBiquadFilterAndMix( + previousMixVolume, + mixVolume, + bufferIndex, + bufferOffset + (uint)i, + i, + state, + ref bqf1, + bqfState[..1], + bqfState.Slice(1, 1), + !destination.IsBiquadFilterEnabledPrev(), + true, + isFirstMixBuffer, + nodeId); + + destination.UpdateBiquadFilterEnabledPrev(1); + } + + isFirstMixBuffer = false; + } + } + } + + private void GenerateVoiceMix( + ReadOnlySpan mixVolumes, + ReadOnlySpan previousMixVolumes, + Memory state, + uint bufferOffset, + uint bufferCount, + uint bufferIndex, + int nodeId) { if (bufferCount > Constants.VoiceChannelCountMax) { - _commandBuffer.GenerateMixRampGrouped(bufferCount, - bufferIndex, - bufferOffset, - previousMixVolumes, - mixVolumes, - state, - nodeId); + _commandBuffer.GenerateMixRampGrouped( + bufferCount, + bufferIndex, + bufferOffset, + previousMixVolumes, + mixVolumes, + state, + nodeId); } else { @@ -188,13 +294,14 @@ namespace Ryujinx.Audio.Renderer.Server if (mixVolume != 0.0f || previousMixVolume != 0.0f) { - _commandBuffer.GenerateMixRamp(previousMixVolume, - mixVolume, - bufferIndex, - bufferOffset + (uint)i, - i, - state, - nodeId); + _commandBuffer.GenerateMixRamp( + previousMixVolume, + mixVolume, + bufferIndex, + bufferOffset + (uint)i, + i, + state, + nodeId); } } } @@ -271,10 +378,11 @@ namespace Ryujinx.Audio.Renderer.Server GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); } - _commandBuffer.GenerateVolumeRamp(voiceState.PreviousVolume, - voiceState.Volume, - _rendererContext.MixBufferCount + (uint)channelIndex, - nodeId); + _commandBuffer.GenerateVolumeRamp( + voiceState.PreviousVolume, + voiceState.Volume, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); if (performanceInitialized) { @@ -291,15 +399,13 @@ namespace Ryujinx.Audio.Renderer.Server while (true) { - Span destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId); + SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId); - if (destinationSpan.IsEmpty) + if (destination.IsNull) { break; } - ref SplitterDestination destination = ref destinationSpan[0]; - destinationId += (int)channelsCount; if (destination.IsConfigured()) @@ -310,13 +416,27 @@ namespace Ryujinx.Audio.Renderer.Server { ref MixState mix = ref _mixContext.GetState(mixId); - GenerateVoiceMix(destination.MixBufferVolume, - destination.PreviousMixBufferVolume, - dspStateMemory, - mix.BufferOffset, - mix.BufferCount, - _rendererContext.MixBufferCount + (uint)channelIndex, - nodeId); + if (destination.IsBiquadFilterEnabled()) + { + GenerateVoiceMixWithSplitter( + destination, + dspStateMemory, + mix.BufferOffset, + mix.BufferCount, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + } + else + { + GenerateVoiceMix( + destination.MixBufferVolume, + destination.PreviousMixBufferVolume, + dspStateMemory, + mix.BufferOffset, + mix.BufferCount, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + } destination.MarkAsNeedToUpdateInternalState(); } @@ -337,13 +457,14 @@ namespace Ryujinx.Audio.Renderer.Server GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); } - GenerateVoiceMix(channelResource.Mix.AsSpan(), - channelResource.PreviousMix.AsSpan(), - dspStateMemory, - mix.BufferOffset, - mix.BufferCount, - _rendererContext.MixBufferCount + (uint)channelIndex, - nodeId); + GenerateVoiceMix( + channelResource.Mix.AsSpan(), + channelResource.PreviousMix.AsSpan(), + dspStateMemory, + mix.BufferOffset, + mix.BufferCount, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); if (performanceInitialized) { @@ -409,10 +530,11 @@ namespace Ryujinx.Audio.Renderer.Server { if (effect.Parameter.Volumes[i] != 0.0f) { - _commandBuffer.GenerateMix((uint)bufferOffset + effect.Parameter.Input[i], - (uint)bufferOffset + effect.Parameter.Output[i], - nodeId, - effect.Parameter.Volumes[i]); + _commandBuffer.GenerateMix( + (uint)bufferOffset + effect.Parameter.Input[i], + (uint)bufferOffset + effect.Parameter.Output[i], + nodeId, + effect.Parameter.Volumes[i]); } } } @@ -447,17 +569,18 @@ namespace Ryujinx.Audio.Renderer.Server updateCount = newUpdateCount; } - _commandBuffer.GenerateAuxEffect(bufferOffset, - effect.Parameter.Input[i], - effect.Parameter.Output[i], - ref effect.State, - effect.IsEnabled, - effect.Parameter.BufferStorageSize, - effect.State.SendBufferInfoBase, - effect.State.ReturnBufferInfoBase, - updateCount, - writeOffset, - nodeId); + _commandBuffer.GenerateAuxEffect( + bufferOffset, + effect.Parameter.Input[i], + effect.Parameter.Output[i], + ref effect.State, + effect.IsEnabled, + effect.Parameter.BufferStorageSize, + effect.State.SendBufferInfoBase, + effect.State.ReturnBufferInfoBase, + updateCount, + writeOffset, + nodeId); writeOffset = newUpdateCount; @@ -500,7 +623,7 @@ namespace Ryujinx.Audio.Renderer.Server if (effect.IsEnabled) { bool needInitialization = effect.Parameter.Status == UsageState.Invalid || - (effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + (effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed()); BiquadFilterParameter parameter = new() { @@ -512,11 +635,14 @@ namespace Ryujinx.Audio.Renderer.Server for (int i = 0; i < effect.Parameter.ChannelCount; i++) { - _commandBuffer.GenerateBiquadFilter((int)bufferOffset, ref parameter, effect.State.Slice(i, 1), - effect.Parameter.Input[i], - effect.Parameter.Output[i], - needInitialization, - nodeId); + _commandBuffer.GenerateBiquadFilter( + (int)bufferOffset, + ref parameter, + effect.State.Slice(i, 1), + effect.Parameter.Input[i], + effect.Parameter.Output[i], + needInitialization, + nodeId); } } else @@ -591,15 +717,16 @@ namespace Ryujinx.Audio.Renderer.Server updateCount = newUpdateCount; } - _commandBuffer.GenerateCaptureEffect(bufferOffset, - effect.Parameter.Input[i], - effect.State.SendBufferInfo, - effect.IsEnabled, - effect.Parameter.BufferStorageSize, - effect.State.SendBufferInfoBase, - updateCount, - writeOffset, - nodeId); + _commandBuffer.GenerateCaptureEffect( + bufferOffset, + effect.Parameter.Input[i], + effect.State.SendBufferInfo, + effect.IsEnabled, + effect.Parameter.BufferStorageSize, + effect.State.SendBufferInfoBase, + updateCount, + writeOffset, + nodeId); writeOffset = newUpdateCount; @@ -612,11 +739,12 @@ namespace Ryujinx.Audio.Renderer.Server { Debug.Assert(effect.Type == EffectType.Compressor); - _commandBuffer.GenerateCompressorEffect(bufferOffset, - effect.Parameter, - effect.State, - effect.IsEnabled, - nodeId); + _commandBuffer.GenerateCompressorEffect( + bufferOffset, + effect.Parameter, + effect.State, + effect.IsEnabled, + nodeId); } private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect) @@ -629,8 +757,11 @@ namespace Ryujinx.Audio.Renderer.Server bool performanceInitialized = false; - if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, effect.GetPerformanceDetailType(), - isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, nodeId)) + if (_performanceManager != null && _performanceManager.GetNextEntry( + out performanceEntry, + effect.GetPerformanceDetailType(), + isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, + nodeId)) { performanceInitialized = true; @@ -706,6 +837,85 @@ namespace Ryujinx.Audio.Renderer.Server } } + private void GenerateMixWithSplitter( + uint inputBufferIndex, + uint outputBufferIndex, + float volume, + SplitterDestination destination, + ref bool isFirstMixBuffer, + int nodeId) + { + ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0); + ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1); + + Memory bqfState = _splitterContext.GetBiquadFilterState(destination); + + if (bqf0.Enable && bqf1.Enable) + { + _commandBuffer.GenerateMultiTapBiquadFilterAndMix( + 0f, + volume, + inputBufferIndex, + outputBufferIndex, + 0, + Memory.Empty, + ref bqf0, + ref bqf1, + bqfState[..1], + bqfState.Slice(1, 1), + bqfState.Slice(2, 1), + bqfState.Slice(3, 1), + !destination.IsBiquadFilterEnabledPrev(), + !destination.IsBiquadFilterEnabledPrev(), + false, + isFirstMixBuffer, + nodeId); + + destination.UpdateBiquadFilterEnabledPrev(0); + destination.UpdateBiquadFilterEnabledPrev(1); + } + else if (bqf0.Enable) + { + _commandBuffer.GenerateBiquadFilterAndMix( + 0f, + volume, + inputBufferIndex, + outputBufferIndex, + 0, + Memory.Empty, + ref bqf0, + bqfState[..1], + bqfState.Slice(1, 1), + !destination.IsBiquadFilterEnabledPrev(), + false, + isFirstMixBuffer, + nodeId); + + destination.UpdateBiquadFilterEnabledPrev(0); + } + else if (bqf1.Enable) + { + _commandBuffer.GenerateBiquadFilterAndMix( + 0f, + volume, + inputBufferIndex, + outputBufferIndex, + 0, + Memory.Empty, + ref bqf1, + bqfState[..1], + bqfState.Slice(1, 1), + !destination.IsBiquadFilterEnabledPrev(), + false, + isFirstMixBuffer, + nodeId); + + destination.UpdateBiquadFilterEnabledPrev(1); + } + + isFirstMixBuffer = false; + } + private void GenerateMix(ref MixState mix) { if (mix.HasAnyDestination()) @@ -722,15 +932,13 @@ namespace Ryujinx.Audio.Renderer.Server { int destinationIndex = destinationId++; - Span destinationSpan = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex); + SplitterDestination destination = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex); - if (destinationSpan.IsEmpty) + if (destination.IsNull) { break; } - ref SplitterDestination destination = ref destinationSpan[0]; - if (destination.IsConfigured()) { int mixId = destination.DestinationId; @@ -741,16 +949,32 @@ namespace Ryujinx.Audio.Renderer.Server uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount); + bool isFirstMixBuffer = true; + for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++) { float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex); if (volume != 0.0f) { - _commandBuffer.GenerateMix(inputBufferIndex, - destinationMix.BufferOffset + bufferDestinationIndex, - mix.NodeId, - volume); + if (destination.IsBiquadFilterEnabled()) + { + GenerateMixWithSplitter( + inputBufferIndex, + destinationMix.BufferOffset + bufferDestinationIndex, + volume, + destination, + ref isFirstMixBuffer, + mix.NodeId); + } + else + { + _commandBuffer.GenerateMix( + inputBufferIndex, + destinationMix.BufferOffset + bufferDestinationIndex, + mix.NodeId, + volume); + } } } } @@ -770,10 +994,11 @@ namespace Ryujinx.Audio.Renderer.Server if (volume != 0.0f) { - _commandBuffer.GenerateMix(mix.BufferOffset + bufferIndex, - destinationMix.BufferOffset + bufferDestinationIndex, - mix.NodeId, - volume); + _commandBuffer.GenerateMix( + mix.BufferOffset + bufferIndex, + destinationMix.BufferOffset + bufferDestinationIndex, + mix.NodeId, + volume); } } } @@ -783,11 +1008,12 @@ namespace Ryujinx.Audio.Renderer.Server private void GenerateSubMix(ref MixState subMix) { - _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer, - subMix.BufferOffset, - subMix.BufferCount, - subMix.NodeId, - subMix.SampleRate); + _commandBuffer.GenerateDepopForMixBuffers( + _rendererContext.DepopBuffer, + subMix.BufferOffset, + subMix.BufferCount, + subMix.NodeId, + subMix.SampleRate); GenerateEffects(ref subMix); @@ -847,11 +1073,12 @@ namespace Ryujinx.Audio.Renderer.Server { ref MixState finalMix = ref _mixContext.GetFinalState(); - _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer, - finalMix.BufferOffset, - finalMix.BufferCount, - finalMix.NodeId, - finalMix.SampleRate); + _commandBuffer.GenerateDepopForMixBuffers( + _rendererContext.DepopBuffer, + finalMix.BufferOffset, + finalMix.BufferCount, + finalMix.NodeId, + finalMix.SampleRate); GenerateEffects(ref finalMix); @@ -882,9 +1109,10 @@ namespace Ryujinx.Audio.Renderer.Server GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); } - _commandBuffer.GenerateVolume(finalMix.Volume, - finalMix.BufferOffset + bufferIndex, - nodeId); + _commandBuffer.GenerateVolume( + finalMix.Volume, + finalMix.BufferOffset + bufferIndex, + nodeId); if (performanceSubInitialized) { @@ -938,41 +1166,45 @@ namespace Ryujinx.Audio.Renderer.Server if (useCustomDownMixingCommand) { - _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset, - sink.Parameter.Input.AsSpan(), - sink.Parameter.Input.AsSpan(), - sink.DownMixCoefficients, - Constants.InvalidNodeId); + _commandBuffer.GenerateDownMixSurroundToStereo( + finalMix.BufferOffset, + sink.Parameter.Input.AsSpan(), + sink.Parameter.Input.AsSpan(), + sink.DownMixCoefficients, + Constants.InvalidNodeId); } // NOTE: We do the downmixing at the DSP level as it's easier that way. else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6) { - _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset, - sink.Parameter.Input.AsSpan(), - sink.Parameter.Input.AsSpan(), - Constants.DefaultSurroundToStereoCoefficients, - Constants.InvalidNodeId); + _commandBuffer.GenerateDownMixSurroundToStereo( + finalMix.BufferOffset, + sink.Parameter.Input.AsSpan(), + sink.Parameter.Input.AsSpan(), + Constants.DefaultSurroundToStereoCoefficients, + Constants.InvalidNodeId); } CommandList commandList = _commandBuffer.CommandList; if (sink.UpsamplerState != null) { - _commandBuffer.GenerateUpsample(finalMix.BufferOffset, - sink.UpsamplerState, - sink.Parameter.InputCount, - sink.Parameter.Input.AsSpan(), - commandList.BufferCount, - commandList.SampleCount, - commandList.SampleRate, - Constants.InvalidNodeId); + _commandBuffer.GenerateUpsample( + finalMix.BufferOffset, + sink.UpsamplerState, + sink.Parameter.InputCount, + sink.Parameter.Input.AsSpan(), + commandList.BufferCount, + commandList.SampleCount, + commandList.SampleRate, + Constants.InvalidNodeId); } - _commandBuffer.GenerateDeviceSink(finalMix.BufferOffset, - sink, - _rendererContext.SessionId, - commandList.Buffers, - Constants.InvalidNodeId); + _commandBuffer.GenerateDeviceSink( + finalMix.BufferOffset, + sink, + _rendererContext.SessionId, + commandList.Buffers, + Constants.InvalidNodeId); } private void GenerateSink(BaseSink sink, ref MixState finalMix) diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs index d95e9aa7..cff754b8 100644 --- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs @@ -170,7 +170,7 @@ namespace Ryujinx.Audio.Renderer.Server return 0; } - public uint Estimate(GroupedBiquadFilterCommand command) + public uint Estimate(MultiTapBiquadFilterCommand command) { return 0; } @@ -184,5 +184,15 @@ namespace Ryujinx.Audio.Renderer.Server { return 0; } + + public uint Estimate(BiquadFilterAndMixCommand command) + { + return 0; + } + + public uint Estimate(MultiTapBiquadFilterAndMixCommand command) + { + return 0; + } } } diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs index 929aaf38..ef132692 100644 --- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs @@ -462,7 +462,7 @@ namespace Ryujinx.Audio.Renderer.Server return 0; } - public uint Estimate(GroupedBiquadFilterCommand command) + public uint Estimate(MultiTapBiquadFilterCommand command) { return 0; } @@ -476,5 +476,15 @@ namespace Ryujinx.Audio.Renderer.Server { return 0; } + + public uint Estimate(BiquadFilterAndMixCommand command) + { + return 0; + } + + public uint Estimate(MultiTapBiquadFilterAndMixCommand command) + { + return 0; + } } } diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs index 8ae4bc05..31a5347b 100644 --- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs @@ -632,7 +632,7 @@ namespace Ryujinx.Audio.Renderer.Server }; } - public virtual uint Estimate(GroupedBiquadFilterCommand command) + public virtual uint Estimate(MultiTapBiquadFilterCommand command) { return 0; } @@ -646,5 +646,15 @@ namespace Ryujinx.Audio.Renderer.Server { return 0; } + + public virtual uint Estimate(BiquadFilterAndMixCommand command) + { + return 0; + } + + public virtual uint Estimate(MultiTapBiquadFilterAndMixCommand command) + { + return 0; + } } } diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs index 25bc67cd..fb357120 100644 --- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs @@ -10,7 +10,7 @@ namespace Ryujinx.Audio.Renderer.Server { public CommandProcessingTimeEstimatorVersion4(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { } - public override uint Estimate(GroupedBiquadFilterCommand command) + public override uint Estimate(MultiTapBiquadFilterCommand command) { Debug.Assert(SampleCount == 160 || SampleCount == 240); diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs index 7135c1c4..06f135a8 100644 --- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs @@ -210,5 +210,53 @@ namespace Ryujinx.Audio.Renderer.Server _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), }; } + + public override uint Estimate(BiquadFilterAndMixCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (command.HasVolumeRamp) + { + if (SampleCount == 160) + { + return 5204; + } + + return 6683; + } + else + { + if (SampleCount == 160) + { + return 3427; + } + + return 4752; + } + } + + public override uint Estimate(MultiTapBiquadFilterAndMixCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (command.HasVolumeRamp) + { + if (SampleCount == 160) + { + return 7939; + } + + return 10669; + } + else + { + if (SampleCount == 160) + { + return 6256; + } + + return 8683; + } + } } } diff --git a/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs index 27b22363..9c4312ad 100644 --- a/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs +++ b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs @@ -33,8 +33,10 @@ namespace Ryujinx.Audio.Renderer.Server uint Estimate(UpsampleCommand command); uint Estimate(LimiterCommandVersion1 command); uint Estimate(LimiterCommandVersion2 command); - uint Estimate(GroupedBiquadFilterCommand command); + uint Estimate(MultiTapBiquadFilterCommand command); uint Estimate(CaptureBufferCommand command); uint Estimate(CompressorCommand command); + uint Estimate(BiquadFilterAndMixCommand command); + uint Estimate(MultiTapBiquadFilterAndMixCommand command); } } diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs index b90574da..5ba58ea5 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs @@ -225,11 +225,11 @@ namespace Ryujinx.Audio.Renderer.Server.Mix for (int i = 0; i < splitter.DestinationCount; i++) { - Span destination = splitter.GetData(i); + SplitterDestination destination = splitter.GetData(i); - if (!destination.IsEmpty) + if (!destination.IsNull) { - int destinationMixId = destination[0].DestinationId; + int destinationMixId = destination.DestinationId; if (destinationMixId != UnusedMixId) { diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs index 3efa783c..a7b82a6b 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs @@ -1,4 +1,5 @@ using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Common; @@ -15,15 +16,35 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// public class SplitterContext { + /// + /// Amount of biquad filter states per splitter destination. + /// + public const int BqfStatesPerDestination = 4; + /// /// Storage for . /// private Memory _splitters; /// - /// Storage for . + /// Storage for . /// - private Memory _splitterDestinations; + private Memory _splitterDestinationsV1; + + /// + /// Storage for . + /// + private Memory _splitterDestinationsV2; + + /// + /// Splitter biquad filtering states. + /// + private Memory _splitterBqfStates; + + /// + /// Version of the splitter context that is being used, currently can be 1 or 2. + /// + public int Version { get; private set; } /// /// If set to true, trust the user destination count in . @@ -36,12 +57,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// The behaviour context. /// The audio renderer configuration. /// The . + /// Memory to store the biquad filtering state for splitters during processing. /// Return true if the initialization was successful. - public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator) + public bool Initialize( + ref BehaviourContext behaviourContext, + ref AudioRendererConfiguration parameter, + WorkBufferAllocator workBufferAllocator, + Memory splitterBqfStates) { if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0) { - Setup(Memory.Empty, Memory.Empty, false); + Setup(Memory.Empty, Memory.Empty, Memory.Empty, false); return true; } @@ -60,23 +86,62 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter splitter = new SplitterState(splitterId++); } - Memory splitterDestinations = workBufferAllocator.Allocate(parameter.SplitterDestinationCount, - SplitterDestination.Alignment); + Memory splitterDestinationsV1 = Memory.Empty; + Memory splitterDestinationsV2 = Memory.Empty; - if (splitterDestinations.IsEmpty) + if (!behaviourContext.IsBiquadFilterParameterForSplitterEnabled()) { - return false; + Version = 1; + + splitterDestinationsV1 = workBufferAllocator.Allocate(parameter.SplitterDestinationCount, + SplitterDestinationVersion1.Alignment); + + if (splitterDestinationsV1.IsEmpty) + { + return false; + } + + int splitterDestinationId = 0; + foreach (ref SplitterDestinationVersion1 data in splitterDestinationsV1.Span) + { + data = new SplitterDestinationVersion1(splitterDestinationId++); + } } - - int splitterDestinationId = 0; - foreach (ref SplitterDestination data in splitterDestinations.Span) + else { - data = new SplitterDestination(splitterDestinationId++); + Version = 2; + + splitterDestinationsV2 = workBufferAllocator.Allocate(parameter.SplitterDestinationCount, + SplitterDestinationVersion2.Alignment); + + if (splitterDestinationsV2.IsEmpty) + { + return false; + } + + int splitterDestinationId = 0; + foreach (ref SplitterDestinationVersion2 data in splitterDestinationsV2.Span) + { + data = new SplitterDestinationVersion2(splitterDestinationId++); + } + + if (parameter.SplitterDestinationCount > 0) + { + // Official code stores it in the SplitterDestinationVersion2 struct, + // but we don't to avoid using unsafe code. + + splitterBqfStates.Span.Clear(); + _splitterBqfStates = splitterBqfStates; + } + else + { + _splitterBqfStates = Memory.Empty; + } } SplitterState.InitializeSplitters(splitters.Span); - Setup(splitters, splitterDestinations, behaviourContext.IsSplitterBugFixed()); + Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed()); return true; } @@ -93,7 +158,15 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter if (behaviourContext.IsSplitterSupported()) { size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterCount, SplitterState.Alignment); - size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment); + + if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled()) + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestinationVersion2.Alignment); + } + else + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestinationVersion1.Alignment); + } if (behaviourContext.IsSplitterBugFixed()) { @@ -110,12 +183,18 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// Setup the instance. /// /// The storage. - /// The storage. + /// The storage. + /// The storage. /// If set to true, trust the user destination count in . - private void Setup(Memory splitters, Memory splitterDestinations, bool isBugFixed) + private void Setup( + Memory splitters, + Memory splitterDestinationsV1, + Memory splitterDestinationsV2, + bool isBugFixed) { _splitters = splitters; - _splitterDestinations = splitterDestinations; + _splitterDestinationsV1 = splitterDestinationsV1; + _splitterDestinationsV2 = splitterDestinationsV2; IsBugFixed = isBugFixed; } @@ -141,7 +220,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter return 0; } - return _splitterDestinations.Length / _splitters.Length; + int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length; + + return length / _splitters.Length; } /// @@ -178,7 +259,39 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter } /// - /// Update one or multiple from user parameters. + /// Update one splitter destination data from user parameters. + /// + /// The raw data after the splitter header. + /// True if the update was successful, false otherwise + private bool UpdateData(ref SequenceReader input) where T : unmanaged, ISplitterDestinationInParameter + { + ref readonly T parameter = ref input.GetRefOrRefToCopy(out _); + + Debug.Assert(parameter.IsMagicValid()); + + if (parameter.IsMagicValid()) + { + int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length; + + if (parameter.Id >= 0 && parameter.Id < length) + { + SplitterDestination destination = GetDestination(parameter.Id); + + destination.Update(parameter); + } + + return true; + } + else + { + input.Rewind(Unsafe.SizeOf()); + + return false; + } + } + + /// + /// Update one or multiple splitter destination data from user parameters. /// /// The splitter header. /// The raw data after the splitter header. @@ -186,23 +299,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { for (int i = 0; i < inputHeader.SplitterDestinationCount; i++) { - ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy(out _); - - Debug.Assert(parameter.IsMagicValid()); - - if (parameter.IsMagicValid()) + if (Version == 1) { - if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length) + if (!UpdateData(ref input)) { - ref SplitterDestination destination = ref GetDestination(parameter.Id); - - destination.Update(parameter); + break; + } + } + else if (Version == 2) + { + if (!UpdateData(ref input)) + { + break; } } else { - input.Rewind(Unsafe.SizeOf()); - break; + Debug.Fail($"Invalid splitter context version {Version}."); } } } @@ -214,7 +327,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// Return true if the update was successful. public bool Update(ref SequenceReader input) { - if (_splitterDestinations.IsEmpty || _splitters.IsEmpty) + if (!UsingSplitter()) { return true; } @@ -251,45 +364,52 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter } /// - /// Get a reference to a at the given . + /// Get a reference to the splitter destination data at the given . /// /// The index to use. - /// A reference to a at the given . - public ref SplitterDestination GetDestination(int id) + /// A reference to the splitter destination data at the given . + public SplitterDestination GetDestination(int id) { - return ref SpanIOHelper.GetFromMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length); + if (_splitterDestinationsV2.IsEmpty) + { + return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV1, id, (uint)_splitterDestinationsV1.Length)); + } + else + { + return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV2, id, (uint)_splitterDestinationsV2.Length)); + } } /// - /// Get a at the given . - /// - /// The index to use. - /// A at the given . - public Memory GetDestinationMemory(int id) - { - return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length); - } - - /// - /// Get a in the at and pass to . + /// Get a in the at and pass to . /// /// The index to use to get the . /// The index of the . - /// A . - public Span GetDestination(int id, int destinationId) + /// A . + public SplitterDestination GetDestination(int id, int destinationId) { ref SplitterState splitter = ref GetState(id); return splitter.GetData(destinationId); } + /// + /// Gets the biquad filter state for a given splitter destination. + /// + /// The splitter destination. + /// Biquad filter state for the specified destination. + public Memory GetBiquadFilterState(SplitterDestination destination) + { + return _splitterBqfStates.Slice(destination.Id * BqfStatesPerDestination, BqfStatesPerDestination); + } + /// /// Return true if the audio renderer has any splitters. /// /// True if the audio renderer has any splitters. public bool UsingSplitter() { - return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty; + return !_splitters.IsEmpty && (!_splitterDestinationsV1.IsEmpty || !_splitterDestinationsV2.IsEmpty); } /// diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs index 1faf7921..36dfa5e4 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs @@ -1,115 +1,198 @@ using Ryujinx.Audio.Renderer.Parameter; -using Ryujinx.Common.Utilities; using System; using System.Diagnostics; -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; namespace Ryujinx.Audio.Renderer.Server.Splitter { /// /// Server state for a splitter destination. /// - [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)] - public struct SplitterDestination + public ref struct SplitterDestination { - public const int Alignment = 0x10; + private ref SplitterDestinationVersion1 _v1; + private ref SplitterDestinationVersion2 _v2; /// - /// The unique id of this . + /// Checks if the splitter destination data reference is null. /// - public int Id; + public bool IsNull => Unsafe.IsNullRef(ref _v1) && Unsafe.IsNullRef(ref _v2); /// - /// The mix to output the result of the splitter. + /// The splitter unique id. /// - public int DestinationId; - - /// - /// Mix buffer volumes storage. - /// - private MixArray _mix; - private MixArray _previousMix; - - /// - /// Pointer to the next linked element. - /// - private unsafe SplitterDestination* _next; - - /// - /// Set to true if in use. - /// - [MarshalAs(UnmanagedType.I1)] - public bool IsUsed; - - /// - /// Set to true if the internal state need to be updated. - /// - [MarshalAs(UnmanagedType.I1)] - public bool NeedToUpdateInternalState; - - [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)] - private struct MixArray { } - - /// - /// Mix buffer volumes. - /// - /// Used when a splitter id is specified in the mix. - public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix); - - /// - /// Previous mix buffer volumes. - /// - /// Used when a splitter id is specified in the mix. - public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix); - - /// - /// Get the of the next element or if not present. - /// - public readonly Span Next + public int Id { get { - unsafe + if (Unsafe.IsNullRef(ref _v2)) { - return _next != null ? new Span(_next, 1) : Span.Empty; + if (Unsafe.IsNullRef(ref _v1)) + { + return 0; + } + else + { + return _v1.Id; + } + } + else + { + return _v2.Id; } } } /// - /// Create a new . + /// The mix to output the result of the splitter. /// - /// The unique id of this . - public SplitterDestination(int id) : this() + public int DestinationId { - Id = id; - DestinationId = Constants.UnusedMixId; - - ClearVolumes(); + get + { + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return 0; + } + else + { + return _v1.DestinationId; + } + } + else + { + return _v2.DestinationId; + } + } } /// - /// Update the from user parameter. + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume + { + get + { + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return Span.Empty; + } + else + { + return _v1.MixBufferVolume; + } + } + else + { + return _v2.MixBufferVolume; + } + } + } + + /// + /// Previous mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span PreviousMixBufferVolume + { + get + { + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return Span.Empty; + } + else + { + return _v1.PreviousMixBufferVolume; + } + } + else + { + return _v2.PreviousMixBufferVolume; + } + } + } + + /// + /// Get the of the next element or null if not present. + /// + public readonly SplitterDestination Next + { + get + { + unsafe + { + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return new SplitterDestination(); + } + else + { + return new SplitterDestination(ref _v1.Next); + } + } + else + { + return new SplitterDestination(ref _v2.Next); + } + } + } + } + + /// + /// Creates a new splitter destination wrapper for the version 1 splitter destination data. + /// + /// Version 1 splitter destination data + public SplitterDestination(ref SplitterDestinationVersion1 v1) + { + _v1 = ref v1; + _v2 = ref Unsafe.NullRef(); + } + + /// + /// Creates a new splitter destination wrapper for the version 2 splitter destination data. + /// + /// Version 2 splitter destination data + public SplitterDestination(ref SplitterDestinationVersion2 v2) + { + + _v1 = ref Unsafe.NullRef(); + _v2 = ref v2; + } + + /// + /// Creates a new splitter destination wrapper for the splitter destination data. + /// + /// Version 1 splitter destination data + /// Version 2 splitter destination data + public unsafe SplitterDestination(SplitterDestinationVersion1* v1, SplitterDestinationVersion2* v2) + { + _v1 = ref Unsafe.AsRef(v1); + _v2 = ref Unsafe.AsRef(v2); + } + + /// + /// Update the splitter destination data from user parameter. /// /// The user parameter. - public void Update(SplitterDestinationInParameter parameter) + public void Update(in T parameter) where T : ISplitterDestinationInParameter { - Debug.Assert(Id == parameter.Id); - - if (parameter.IsMagicValid() && Id == parameter.Id) + if (Unsafe.IsNullRef(ref _v2)) { - DestinationId = parameter.DestinationId; - - parameter.MixBufferVolume.CopyTo(MixBufferVolume); - - if (!IsUsed && parameter.IsUsed) - { - MixBufferVolume.CopyTo(PreviousMixBufferVolume); - - NeedToUpdateInternalState = false; - } - - IsUsed = parameter.IsUsed; + _v1.Update(parameter); + } + else + { + _v2.Update(parameter); } } @@ -118,12 +201,14 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// public void UpdateInternalState() { - if (IsUsed && NeedToUpdateInternalState) + if (Unsafe.IsNullRef(ref _v2)) { - MixBufferVolume.CopyTo(PreviousMixBufferVolume); + _v1.UpdateInternalState(); + } + else + { + _v2.UpdateInternalState(); } - - NeedToUpdateInternalState = false; } /// @@ -131,16 +216,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// public void MarkAsNeedToUpdateInternalState() { - NeedToUpdateInternalState = true; + if (Unsafe.IsNullRef(ref _v2)) + { + _v1.MarkAsNeedToUpdateInternalState(); + } + else + { + _v2.MarkAsNeedToUpdateInternalState(); + } } /// - /// Return true if the is used and has a destination. + /// Return true if the splitter destination is used and has a destination. /// - /// True if the is used and has a destination. + /// True if the splitter destination is used and has a destination. public readonly bool IsConfigured() { - return IsUsed && DestinationId != Constants.UnusedMixId; + return Unsafe.IsNullRef(ref _v2) ? _v1.IsConfigured() : _v2.IsConfigured(); } /// @@ -150,9 +242,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// The volume for the given destination. public float GetMixVolume(int destinationIndex) { - Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolume(destinationIndex) : _v2.GetMixVolume(destinationIndex); + } - return MixBufferVolume[destinationIndex]; + /// + /// Get the previous volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolumePrev(int destinationIndex) + { + return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolumePrev(destinationIndex) : _v2.GetMixVolumePrev(destinationIndex); } /// @@ -160,22 +260,33 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// public void ClearVolumes() { - MixBufferVolume.Clear(); - PreviousMixBufferVolume.Clear(); + if (Unsafe.IsNullRef(ref _v2)) + { + _v1.ClearVolumes(); + } + else + { + _v2.ClearVolumes(); + } } /// - /// Link the next element to the given . + /// Link the next element to the given splitter destination. /// - /// The given to link. - public void Link(ref SplitterDestination next) + /// The given splitter destination to link. + public void Link(SplitterDestination next) { - unsafe + if (Unsafe.IsNullRef(ref _v2)) { - fixed (SplitterDestination* nextPtr = &next) - { - _next = nextPtr; - } + Debug.Assert(!Unsafe.IsNullRef(ref next._v1)); + + _v1.Link(ref next._v1); + } + else + { + Debug.Assert(!Unsafe.IsNullRef(ref next._v2)); + + _v2.Link(ref next._v2); } } @@ -184,10 +295,74 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// public void Unlink() { - unsafe + if (Unsafe.IsNullRef(ref _v2)) { - _next = null; + _v1.Unlink(); } + else + { + _v2.Unlink(); + } + } + + /// + /// Checks if any biquad filter is enabled. + /// + /// True if any biquad filter is enabled. + public bool IsBiquadFilterEnabled() + { + return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabled(); + } + + /// + /// Checks if any biquad filter was previously enabled. + /// + /// True if any biquad filter was previously enabled. + public bool IsBiquadFilterEnabledPrev() + { + return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabledPrev(); + } + + /// + /// Gets the biquad filter parameters. + /// + /// Biquad filter index (0 or 1). + /// Biquad filter parameters. + public ref BiquadFilterParameter GetBiquadFilterParameter(int index) + { + Debug.Assert(!Unsafe.IsNullRef(ref _v2)); + + return ref _v2.GetBiquadFilterParameter(index); + } + + /// + /// Checks if any biquad filter was previously enabled. + /// + /// Biquad filter index (0 or 1). + public void UpdateBiquadFilterEnabledPrev(int index) + { + if (!Unsafe.IsNullRef(ref _v2)) + { + _v2.UpdateBiquadFilterEnabledPrev(index); + } + } + + /// + /// Get the reference for the version 1 splitter destination data, or null if version 2 is being used or the destination is null. + /// + /// Reference for the version 1 splitter destination data. + public ref SplitterDestinationVersion1 GetV1RefOrNull() + { + return ref _v1; + } + + /// + /// Get the reference for the version 2 splitter destination data, or null if version 1 is being used or the destination is null. + /// + /// Reference for the version 2 splitter destination data. + public ref SplitterDestinationVersion2 GetV2RefOrNull() + { + return ref _v2; } } } diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs new file mode 100644 index 00000000..5d2b8fb0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs @@ -0,0 +1,206 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Server state for a splitter destination (version 1). + /// + [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)] + public struct SplitterDestinationVersion1 + { + public const int Alignment = 0x10; + + /// + /// The unique id of this . + /// + public int Id; + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId; + + /// + /// Mix buffer volumes storage. + /// + private MixArray _mix; + private MixArray _previousMix; + + /// + /// Pointer to the next linked element. + /// + private unsafe SplitterDestinationVersion1* _next; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Set to true if the internal state need to be updated. + /// + [MarshalAs(UnmanagedType.I1)] + public bool NeedToUpdateInternalState; + + [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix); + + /// + /// Previous mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix); + + /// + /// Get the reference of the next element or null if not present. + /// + public readonly ref SplitterDestinationVersion1 Next + { + get + { + unsafe + { + return ref Unsafe.AsRef(_next); + } + } + } + + /// + /// Create a new . + /// + /// The unique id of this . + public SplitterDestinationVersion1(int id) : this() + { + Id = id; + DestinationId = Constants.UnusedMixId; + + ClearVolumes(); + } + + /// + /// Update the from user parameter. + /// + /// The user parameter. + public void Update(in T parameter) where T : ISplitterDestinationInParameter + { + Debug.Assert(Id == parameter.Id); + + if (parameter.IsMagicValid() && Id == parameter.Id) + { + DestinationId = parameter.DestinationId; + + parameter.MixBufferVolume.CopyTo(MixBufferVolume); + + if (!IsUsed && parameter.IsUsed) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + + NeedToUpdateInternalState = false; + } + + IsUsed = parameter.IsUsed; + } + } + + /// + /// Update the internal state of the instance. + /// + public void UpdateInternalState() + { + if (IsUsed && NeedToUpdateInternalState) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + } + + NeedToUpdateInternalState = false; + } + + /// + /// Set the update internal state marker. + /// + public void MarkAsNeedToUpdateInternalState() + { + NeedToUpdateInternalState = true; + } + + /// + /// Return true if the is used and has a destination. + /// + /// True if the is used and has a destination. + public readonly bool IsConfigured() + { + return IsUsed && DestinationId != Constants.UnusedMixId; + } + + /// + /// Get the volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolume(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return MixBufferVolume[destinationIndex]; + } + + /// + /// Get the previous volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolumePrev(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return PreviousMixBufferVolume[destinationIndex]; + } + + /// + /// Clear the volumes. + /// + public void ClearVolumes() + { + MixBufferVolume.Clear(); + PreviousMixBufferVolume.Clear(); + } + + /// + /// Link the next element to the given . + /// + /// The given to link. + public void Link(ref SplitterDestinationVersion1 next) + { + unsafe + { + fixed (SplitterDestinationVersion1* nextPtr = &next) + { + _next = nextPtr; + } + } + } + + /// + /// Remove the link to the next element. + /// + public void Unlink() + { + unsafe + { + _next = null; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs new file mode 100644 index 00000000..f9487909 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs @@ -0,0 +1,250 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Server state for a splitter destination (version 2). + /// + [StructLayout(LayoutKind.Sequential, Size = 0x110, Pack = Alignment)] + public struct SplitterDestinationVersion2 + { + public const int Alignment = 0x10; + + /// + /// The unique id of this . + /// + public int Id; + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId; + + /// + /// Mix buffer volumes storage. + /// + private MixArray _mix; + private MixArray _previousMix; + + /// + /// Pointer to the next linked element. + /// + private unsafe SplitterDestinationVersion2* _next; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Set to true if the internal state need to be updated. + /// + [MarshalAs(UnmanagedType.I1)] + public bool NeedToUpdateInternalState; + + [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix); + + /// + /// Previous mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix); + + /// + /// Get the reference of the next element or null if not present. + /// + public readonly ref SplitterDestinationVersion2 Next + { + get + { + unsafe + { + return ref Unsafe.AsRef(_next); + } + } + } + + private Array2 _biquadFilters; + + private Array2 _isPreviousBiquadFilterEnabled; + + /// + /// Create a new . + /// + /// The unique id of this . + public SplitterDestinationVersion2(int id) : this() + { + Id = id; + DestinationId = Constants.UnusedMixId; + + ClearVolumes(); + } + + /// + /// Update the from user parameter. + /// + /// The user parameter. + public void Update(in T parameter) where T : ISplitterDestinationInParameter + { + Debug.Assert(Id == parameter.Id); + + if (parameter.IsMagicValid() && Id == parameter.Id) + { + DestinationId = parameter.DestinationId; + + parameter.MixBufferVolume.CopyTo(MixBufferVolume); + + _biquadFilters = parameter.BiquadFilters; + + if (!IsUsed && parameter.IsUsed) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + + NeedToUpdateInternalState = false; + } + + IsUsed = parameter.IsUsed; + } + } + + /// + /// Update the internal state of the instance. + /// + public void UpdateInternalState() + { + if (IsUsed && NeedToUpdateInternalState) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + } + + NeedToUpdateInternalState = false; + } + + /// + /// Set the update internal state marker. + /// + public void MarkAsNeedToUpdateInternalState() + { + NeedToUpdateInternalState = true; + } + + /// + /// Return true if the is used and has a destination. + /// + /// True if the is used and has a destination. + public readonly bool IsConfigured() + { + return IsUsed && DestinationId != Constants.UnusedMixId; + } + + /// + /// Get the volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolume(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return MixBufferVolume[destinationIndex]; + } + + /// + /// Get the previous volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolumePrev(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return PreviousMixBufferVolume[destinationIndex]; + } + + /// + /// Clear the volumes. + /// + public void ClearVolumes() + { + MixBufferVolume.Clear(); + PreviousMixBufferVolume.Clear(); + } + + /// + /// Link the next element to the given . + /// + /// The given to link. + public void Link(ref SplitterDestinationVersion2 next) + { + unsafe + { + fixed (SplitterDestinationVersion2* nextPtr = &next) + { + _next = nextPtr; + } + } + } + + /// + /// Remove the link to the next element. + /// + public void Unlink() + { + unsafe + { + _next = null; + } + } + + /// + /// Checks if any biquad filter is enabled. + /// + /// True if any biquad filter is enabled. + public bool IsBiquadFilterEnabled() + { + return _biquadFilters[0].Enable || _biquadFilters[1].Enable; + } + + /// + /// Checks if any biquad filter was previously enabled. + /// + /// True if any biquad filter was previously enabled. + public bool IsBiquadFilterEnabledPrev() + { + return _isPreviousBiquadFilterEnabled[0]; + } + + /// + /// Gets the biquad filter parameters. + /// + /// Biquad filter index (0 or 1). + /// Biquad filter parameters. + public ref BiquadFilterParameter GetBiquadFilterParameter(int index) + { + return ref _biquadFilters[index]; + } + + /// + /// Checks if any biquad filter was previously enabled. + /// + /// Biquad filter index (0 or 1). + public void UpdateBiquadFilterEnabledPrev(int index) + { + _isPreviousBiquadFilterEnabled[index] = _biquadFilters[index].Enable; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs index 944f092d..3e7dce55 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs @@ -15,6 +15,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { public const int Alignment = 0x10; + private delegate void SplitterDestinationAction(SplitterDestination destination, int index); + /// /// The unique id of this . /// @@ -26,7 +28,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter public uint SampleRate; /// - /// Count of splitter destinations (). + /// Count of splitter destinations. /// public int DestinationCount; @@ -37,20 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter public bool HasNewConnection; /// - /// Linked list of . + /// Linked list of . /// - private unsafe SplitterDestination* _destinationsData; + private unsafe SplitterDestinationVersion1* _destinationDataV1; /// - /// Span to the first element of the linked list of . + /// Linked list of . /// - public readonly Span Destinations + private unsafe SplitterDestinationVersion2* _destinationDataV2; + + /// + /// First element of the linked list of splitter destinations data. + /// + public readonly SplitterDestination Destination { get { unsafe { - return (IntPtr)_destinationsData != IntPtr.Zero ? new Span(_destinationsData, 1) : Span.Empty; + return new SplitterDestination(_destinationDataV1, _destinationDataV2); } } } @@ -64,20 +71,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter Id = id; } - public readonly Span GetData(int index) + public readonly SplitterDestination GetData(int index) { int i = 0; - Span result = Destinations; + SplitterDestination result = Destination; while (i < index) { - if (result.IsEmpty) + if (result.IsNull) { break; } - result = result[0].Next; + result = result.Next; i++; } @@ -93,25 +100,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter } /// - /// Utility function to apply a given to all . + /// Utility function to apply an action to all . /// /// The action to execute on each elements. - private readonly void ForEachDestination(SpanAction action) + private readonly void ForEachDestination(SplitterDestinationAction action) { - Span temp = Destinations; + SplitterDestination temp = Destination; int i = 0; while (true) { - if (temp.IsEmpty) + if (temp.IsNull) { break; } - Span next = temp[0].Next; + SplitterDestination next = temp.Next; - action.Invoke(temp, i++); + action(temp, i++); temp = next; } @@ -142,9 +149,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { input.ReadLittleEndian(out int destinationId); - Memory destination = context.GetDestinationMemory(destinationId); + SplitterDestination destination = context.GetDestination(destinationId); - SetDestination(ref destination.Span[0]); + SetDestination(destination); DestinationCount = destinationCount; @@ -152,9 +159,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { input.ReadLittleEndian(out destinationId); - Memory nextDestination = context.GetDestinationMemory(destinationId); + SplitterDestination nextDestination = context.GetDestination(destinationId); - destination.Span[0].Link(ref nextDestination.Span[0]); + destination.Link(nextDestination); destination = nextDestination; } } @@ -174,16 +181,21 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter } /// - /// Set the head of the linked list of . + /// Set the head of the linked list of . /// - /// A reference to a . - public void SetDestination(ref SplitterDestination newValue) + /// New destination value. + public void SetDestination(SplitterDestination newValue) { unsafe { - fixed (SplitterDestination* newValuePtr = &newValue) + fixed (SplitterDestinationVersion1* newValuePtr = &newValue.GetV1RefOrNull()) { - _destinationsData = newValuePtr; + _destinationDataV1 = newValuePtr; + } + + fixed (SplitterDestinationVersion2* newValuePtr = &newValue.GetV2RefOrNull()) + { + _destinationDataV2 = newValuePtr; } } } @@ -193,19 +205,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// public readonly void UpdateInternalState() { - ForEachDestination((destination, _) => destination[0].UpdateInternalState()); + ForEachDestination((destination, _) => destination.UpdateInternalState()); } /// - /// Clear all links from the . + /// Clear all links from the . /// public void ClearLinks() { - ForEachDestination((destination, _) => destination[0].Unlink()); + ForEachDestination((destination, _) => destination.Unlink()); unsafe { - _destinationsData = (SplitterDestination*)IntPtr.Zero; + _destinationDataV1 = null; + _destinationDataV2 = null; } } @@ -219,7 +232,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { unsafe { - splitter._destinationsData = (SplitterDestination*)IntPtr.Zero; + splitter._destinationDataV1 = null; + splitter._destinationDataV2 = null; } splitter.DestinationCount = 0; diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs index 55758188..3e48a5b4 100644 --- a/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs @@ -52,7 +52,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -78,7 +80,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -104,7 +108,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -130,7 +136,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -156,7 +164,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -182,7 +192,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -208,7 +220,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -234,7 +248,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -260,7 +276,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -286,11 +304,69 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsTrue(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); } + + [Test] + public void TestRevision11() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision11); + + 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.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision12() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision12); + + 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.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } } } diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs index ad974aab..80b80133 100644 --- a/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs @@ -9,7 +9,8 @@ namespace Ryujinx.Tests.Audio.Renderer.Server [Test] public void EnsureTypeSize() { - Assert.AreEqual(0xE0, Unsafe.SizeOf()); + Assert.AreEqual(0xE0, Unsafe.SizeOf()); + Assert.AreEqual(0x110, Unsafe.SizeOf()); } } }