From 4d84df94873a070f6f5c199438f957b24d8cf8a9 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Fri, 17 May 2024 16:46:43 -0300 Subject: 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/Dsp/BiquadFilterHelper.cs | 234 ++++++++++++++++++++- .../Dsp/Command/BiquadFilterAndMixCommand.cs | 123 +++++++++++ .../Renderer/Dsp/Command/CommandType.cs | 4 +- .../Dsp/Command/GroupedBiquadFilterCommand.cs | 62 ------ .../Renderer/Dsp/Command/MixRampGroupedCommand.cs | 16 +- .../Command/MultiTapBiquadFilterAndMixCommand.cs | 145 +++++++++++++ .../Dsp/Command/MultiTapBiquadFilterCommand.cs | 62 ++++++ .../Renderer/Dsp/State/BiquadFilterState.cs | 6 +- 8 files changed, 581 insertions(+), 71 deletions(-) create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs delete mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs (limited to 'src/Ryujinx.Audio/Renderer/Dsp') 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/GroupedBiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs deleted file mode 100644 index 7af851bd..00000000 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Ryujinx.Audio.Renderer.Dsp.State; -using Ryujinx.Audio.Renderer.Parameter; -using System; - -namespace Ryujinx.Audio.Renderer.Dsp.Command -{ - public class GroupedBiquadFilterCommand : ICommand - { - public bool Enabled { get; set; } - - public int NodeId { get; } - - public CommandType CommandType => CommandType.GroupedBiquadFilter; - - public uint EstimatedProcessingTime { get; set; } - - private readonly BiquadFilterParameter[] _parameters; - private readonly Memory _biquadFilterStates; - private readonly int _inputBufferIndex; - private readonly int _outputBufferIndex; - private readonly bool[] _isInitialized; - - public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId) - { - _parameters = filters.ToArray(); - _biquadFilterStates = biquadFilterStateMemory; - _inputBufferIndex = baseIndex + inputBufferOffset; - _outputBufferIndex = baseIndex + outputBufferOffset; - _isInitialized = isInitialized.ToArray(); - - Enabled = true; - NodeId = nodeId; - } - - public void Process(CommandList context) - { - Span states = _biquadFilterStates.Span; - - ReadOnlySpan inputBuffer = context.GetBuffer(_inputBufferIndex); - Span outputBuffer = context.GetBuffer(_outputBufferIndex); - - for (int i = 0; i < _parameters.Length; i++) - { - if (!_isInitialized[i]) - { - states[i] = new BiquadFilterState(); - } - } - - // NOTE: Nintendo only implement single and double biquad filters but no generic path when the command definition suggests it could be done. - // As such we currently only implement a generic path for simplicity for double biquad. - if (_parameters.Length == 1) - { - BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount); - } - else - { - BiquadFilterHelper.ProcessBiquadFilter(_parameters, states, outputBuffer, inputBuffer, context.SampleCount); - } - } - } -} 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/MultiTapBiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs new file mode 100644 index 00000000..e159f8ef --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class MultiTapBiquadFilterCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.MultiTapBiquadFilter; + + public uint EstimatedProcessingTime { get; set; } + + private readonly BiquadFilterParameter[] _parameters; + private readonly Memory _biquadFilterStates; + private readonly int _inputBufferIndex; + private readonly int _outputBufferIndex; + private readonly bool[] _isInitialized; + + public MultiTapBiquadFilterCommand(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId) + { + _parameters = filters.ToArray(); + _biquadFilterStates = biquadFilterStateMemory; + _inputBufferIndex = baseIndex + inputBufferOffset; + _outputBufferIndex = baseIndex + outputBufferOffset; + _isInitialized = isInitialized.ToArray(); + + Enabled = true; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + Span states = _biquadFilterStates.Span; + + ReadOnlySpan inputBuffer = context.GetBuffer(_inputBufferIndex); + Span outputBuffer = context.GetBuffer(_outputBufferIndex); + + for (int i = 0; i < _parameters.Length; i++) + { + if (!_isInitialized[i]) + { + states[i] = new BiquadFilterState(); + } + } + + // NOTE: Nintendo only implement single and double biquad filters but no generic path when the command definition suggests it could be done. + // As such we currently only implement a generic path for simplicity for double biquad. + if (_parameters.Length == 1) + { + BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount); + } + else + { + BiquadFilterHelper.ProcessBiquadFilter(_parameters, states, outputBuffer, inputBuffer, context.SampleCount); + } + } + } +} 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; } } -- cgit v1.2.3