mirror of
				https://github.com/ryujinx-mirror/ryujinx.git
				synced 2025-11-04 08:18:58 -06:00 
			
		
		
		
	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
This commit is contained in:
		@@ -15,7 +15,6 @@ namespace Ryujinx.Audio.Renderer.Common
 | 
			
		||||
    {
 | 
			
		||||
        public const int Align = 0x10;
 | 
			
		||||
        public const int BiquadStateOffset = 0x0;
 | 
			
		||||
        public const int BiquadStateSize = 0x10;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The state of the biquad filters of this voice.
 | 
			
		||||
 
 | 
			
		||||
@@ -16,10 +16,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
 | 
			
		||||
        /// <param name="parameter">The biquad filter parameter</param>
 | 
			
		||||
        /// <param name="state">The biquad filter state</param>
 | 
			
		||||
        /// <param name="outputBuffer">The output buffer to write the result</param>
 | 
			
		||||
        /// <param name="inputBuffer">The input buffer to write the result</param>
 | 
			
		||||
        /// <param name="inputBuffer">The input buffer to read the samples from</param>
 | 
			
		||||
        /// <param name="sampleCount">The count of samples to process</param>
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
 | 
			
		||||
        public static void ProcessBiquadFilter(
 | 
			
		||||
            ref BiquadFilterParameter parameter,
 | 
			
		||||
            ref BiquadFilterState state,
 | 
			
		||||
            Span<float> outputBuffer,
 | 
			
		||||
            ReadOnlySpan<float> 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
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Apply a single biquad filter and mix the result into the output buffer.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>This is implemented with a direct form 1.</remarks>
 | 
			
		||||
        /// <param name="parameter">The biquad filter parameter</param>
 | 
			
		||||
        /// <param name="state">The biquad filter state</param>
 | 
			
		||||
        /// <param name="outputBuffer">The output buffer to write the result</param>
 | 
			
		||||
        /// <param name="inputBuffer">The input buffer to read the samples from</param>
 | 
			
		||||
        /// <param name="sampleCount">The count of samples to process</param>
 | 
			
		||||
        /// <param name="volume">Mix volume</param>
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        public static void ProcessBiquadFilterAndMix(
 | 
			
		||||
            ref BiquadFilterParameter parameter,
 | 
			
		||||
            ref BiquadFilterState state,
 | 
			
		||||
            Span<float> outputBuffer,
 | 
			
		||||
            ReadOnlySpan<float> 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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Apply a single biquad filter and mix the result into the output buffer with volume ramp.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>This is implemented with a direct form 1.</remarks>
 | 
			
		||||
        /// <param name="parameter">The biquad filter parameter</param>
 | 
			
		||||
        /// <param name="state">The biquad filter state</param>
 | 
			
		||||
        /// <param name="outputBuffer">The output buffer to write the result</param>
 | 
			
		||||
        /// <param name="inputBuffer">The input buffer to read the samples from</param>
 | 
			
		||||
        /// <param name="sampleCount">The count of samples to process</param>
 | 
			
		||||
        /// <param name="volume">Initial mix volume</param>
 | 
			
		||||
        /// <param name="ramp">Volume increment step</param>
 | 
			
		||||
        /// <returns>Last filtered sample value</returns>
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        public static float ProcessBiquadFilterAndMixRamp(
 | 
			
		||||
            ref BiquadFilterParameter parameter,
 | 
			
		||||
            ref BiquadFilterState state,
 | 
			
		||||
            Span<float> outputBuffer,
 | 
			
		||||
            ReadOnlySpan<float> 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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Apply multiple biquad filter.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@@ -47,10 +142,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
 | 
			
		||||
        /// <param name="parameters">The biquad filter parameter</param>
 | 
			
		||||
        /// <param name="states">The biquad filter state</param>
 | 
			
		||||
        /// <param name="outputBuffer">The output buffer to write the result</param>
 | 
			
		||||
        /// <param name="inputBuffer">The input buffer to write the result</param>
 | 
			
		||||
        /// <param name="inputBuffer">The input buffer to read the samples from</param>
 | 
			
		||||
        /// <param name="sampleCount">The count of samples to process</param>
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        public static void ProcessBiquadFilter(ReadOnlySpan<BiquadFilterParameter> parameters, Span<BiquadFilterState> states, Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
 | 
			
		||||
        public static void ProcessBiquadFilter(
 | 
			
		||||
            ReadOnlySpan<BiquadFilterParameter> parameters,
 | 
			
		||||
            Span<BiquadFilterState> states,
 | 
			
		||||
            Span<float> outputBuffer,
 | 
			
		||||
            ReadOnlySpan<float> 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
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Apply double biquad filter and mix the result into the output buffer.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>This is implemented with a direct form 1.</remarks>
 | 
			
		||||
        /// <param name="parameters">The biquad filter parameter</param>
 | 
			
		||||
        /// <param name="states">The biquad filter state</param>
 | 
			
		||||
        /// <param name="outputBuffer">The output buffer to write the result</param>
 | 
			
		||||
        /// <param name="inputBuffer">The input buffer to read the samples from</param>
 | 
			
		||||
        /// <param name="sampleCount">The count of samples to process</param>
 | 
			
		||||
        /// <param name="volume">Mix volume</param>
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        public static void ProcessDoubleBiquadFilterAndMix(
 | 
			
		||||
            ref BiquadFilterParameter parameter0,
 | 
			
		||||
            ref BiquadFilterParameter parameter1,
 | 
			
		||||
            ref BiquadFilterState state0,
 | 
			
		||||
            ref BiquadFilterState state1,
 | 
			
		||||
            Span<float> outputBuffer,
 | 
			
		||||
            ReadOnlySpan<float> 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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Apply double biquad filter and mix the result into the output buffer with volume ramp.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>This is implemented with a direct form 1.</remarks>
 | 
			
		||||
        /// <param name="parameters">The biquad filter parameter</param>
 | 
			
		||||
        /// <param name="states">The biquad filter state</param>
 | 
			
		||||
        /// <param name="outputBuffer">The output buffer to write the result</param>
 | 
			
		||||
        /// <param name="inputBuffer">The input buffer to read the samples from</param>
 | 
			
		||||
        /// <param name="sampleCount">The count of samples to process</param>
 | 
			
		||||
        /// <param name="volume">Initial mix volume</param>
 | 
			
		||||
        /// <param name="ramp">Volume increment step</param>
 | 
			
		||||
        /// <returns>Last filtered sample value</returns>
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        public static float ProcessDoubleBiquadFilterAndMixRamp(
 | 
			
		||||
            ref BiquadFilterParameter parameter0,
 | 
			
		||||
            ref BiquadFilterParameter parameter1,
 | 
			
		||||
            ref BiquadFilterState state0,
 | 
			
		||||
            ref BiquadFilterState state1,
 | 
			
		||||
            Span<float> outputBuffer,
 | 
			
		||||
            ReadOnlySpan<float> 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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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> BiquadFilterState { get; }
 | 
			
		||||
        public Memory<BiquadFilterState> PreviousBiquadFilterState { get; }
 | 
			
		||||
 | 
			
		||||
        public Memory<VoiceUpdateState> 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<VoiceUpdateState> state,
 | 
			
		||||
            ref BiquadFilterParameter filter,
 | 
			
		||||
            Memory<BiquadFilterState> biquadFilterState,
 | 
			
		||||
            Memory<BiquadFilterState> 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<float> inputBuffer = context.GetBuffer(InputBufferIndex);
 | 
			
		||||
            Span<float> 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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -30,8 +30,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
        CopyMixBuffer,
 | 
			
		||||
        LimiterVersion1,
 | 
			
		||||
        LimiterVersion2,
 | 
			
		||||
        GroupedBiquadFilter,
 | 
			
		||||
        MultiTapBiquadFilter,
 | 
			
		||||
        CaptureBuffer,
 | 
			
		||||
        Compressor,
 | 
			
		||||
        BiquadFilterAndMix,
 | 
			
		||||
        MultiTapBiquadFilterAndMix,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 | 
			
		||||
 | 
			
		||||
        public Memory<VoiceUpdateState> State { get; }
 | 
			
		||||
 | 
			
		||||
        public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> volume0, Span<float> volume1, Memory<VoiceUpdateState> state, int nodeId)
 | 
			
		||||
        public MixRampGroupedCommand(
 | 
			
		||||
            uint mixBufferCount,
 | 
			
		||||
            uint inputBufferIndex,
 | 
			
		||||
            uint outputBufferIndex,
 | 
			
		||||
            ReadOnlySpan<float> volume0,
 | 
			
		||||
            ReadOnlySpan<float> volume1,
 | 
			
		||||
            Memory<VoiceUpdateState> 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<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float volume0, float volume1, int sampleCount)
 | 
			
		||||
        private static float ProcessMixRampGrouped(
 | 
			
		||||
            Span<float> outputBuffer,
 | 
			
		||||
            ReadOnlySpan<float> inputBuffer,
 | 
			
		||||
            float volume0,
 | 
			
		||||
            float volume1,
 | 
			
		||||
            int sampleCount)
 | 
			
		||||
        {
 | 
			
		||||
            float ramp = (volume1 - volume0) / sampleCount;
 | 
			
		||||
            float volume = volume0;
 | 
			
		||||
 
 | 
			
		||||
@@ -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<BiquadFilterState> BiquadFilterState0 { get; }
 | 
			
		||||
        public Memory<BiquadFilterState> BiquadFilterState1 { get; }
 | 
			
		||||
        public Memory<BiquadFilterState> PreviousBiquadFilterState0 { get; }
 | 
			
		||||
        public Memory<BiquadFilterState> PreviousBiquadFilterState1 { get; }
 | 
			
		||||
 | 
			
		||||
        public Memory<VoiceUpdateState> 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<VoiceUpdateState> state,
 | 
			
		||||
            ref BiquadFilterParameter filter0,
 | 
			
		||||
            ref BiquadFilterParameter filter1,
 | 
			
		||||
            Memory<BiquadFilterState> biquadFilterState0,
 | 
			
		||||
            Memory<BiquadFilterState> biquadFilterState1,
 | 
			
		||||
            Memory<BiquadFilterState> previousBiquadFilterState0,
 | 
			
		||||
            Memory<BiquadFilterState> 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<BiquadFilterState> state, Memory<BiquadFilterState> 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<float> inputBuffer = context.GetBuffer(InputBufferIndex);
 | 
			
		||||
            Span<float> 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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
 | 
			
		||||
        public MultiTapBiquadFilterCommand(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
 | 
			
		||||
        {
 | 
			
		||||
            _parameters = filters.ToArray();
 | 
			
		||||
            _biquadFilterStates = biquadFilterStateMemory;
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
using Ryujinx.Common.Memory;
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
namespace Ryujinx.Audio.Renderer.Parameter
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Generic interface for the splitter destination parameters.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public interface ISplitterDestinationInParameter
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Target splitter destination data id.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        int Id { get; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The mix to output the result of the splitter.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        int DestinationId { get; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Biquad filter parameters.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        Array2<BiquadFilterParameter> BiquadFilters { get; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set to true if in use.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        bool IsUsed { get; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Mix buffer volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>Used when a splitter id is specified in the mix.</remarks>
 | 
			
		||||
        Span<float> MixBufferVolume { get; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Check if the magic is valid.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>Returns true if the magic is valid.</returns>
 | 
			
		||||
        bool IsMagicValid();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Input header for a splitter destination update.
 | 
			
		||||
    /// Input header for a splitter destination version 1 update.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
 | 
			
		||||
    public struct SplitterDestinationInParameter
 | 
			
		||||
    public struct SplitterDestinationInParameterVersion1 : ISplitterDestinationInParameter
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Magic of the input header.
 | 
			
		||||
@@ -41,7 +42,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        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 { }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@@ -50,6 +51,14 @@ namespace Ryujinx.Audio.Renderer.Parameter
 | 
			
		||||
        /// <remarks>Used when a splitter id is specified in the mix.</remarks>
 | 
			
		||||
        public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume);
 | 
			
		||||
 | 
			
		||||
        readonly int ISplitterDestinationInParameter.Id => Id;
 | 
			
		||||
 | 
			
		||||
        readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
 | 
			
		||||
 | 
			
		||||
        readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => default;
 | 
			
		||||
 | 
			
		||||
        readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The expected constant of any input header.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@@ -0,0 +1,81 @@
 | 
			
		||||
using Ryujinx.Common.Memory;
 | 
			
		||||
using Ryujinx.Common.Utilities;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace Ryujinx.Audio.Renderer.Parameter
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Input header for a splitter destination version 2 update.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
 | 
			
		||||
    public struct SplitterDestinationInParameterVersion2 : ISplitterDestinationInParameter
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Magic of the input header.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public uint Magic;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Target splitter destination data id.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int Id;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Mix buffer volumes storage.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private MixArray _mixBufferVolume;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The mix to output the result of the splitter.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int DestinationId;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Biquad filter parameters.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public Array2<BiquadFilterParameter> BiquadFilters;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set to true if in use.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [MarshalAs(UnmanagedType.I1)]
 | 
			
		||||
        public bool IsUsed;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Reserved/padding.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private unsafe fixed byte _reserved[11];
 | 
			
		||||
 | 
			
		||||
        [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
 | 
			
		||||
        private struct MixArray { }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Mix buffer volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>Used when a splitter id is specified in the mix.</remarks>
 | 
			
		||||
        public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume);
 | 
			
		||||
 | 
			
		||||
        readonly int ISplitterDestinationInParameter.Id => Id;
 | 
			
		||||
 | 
			
		||||
        readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
 | 
			
		||||
 | 
			
		||||
        readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => BiquadFilters;
 | 
			
		||||
 | 
			
		||||
        readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The expected constant of any input header.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private const uint ValidMagic = 0x44444E53;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Check if the magic is valid.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>Returns true if the magic is valid.</returns>
 | 
			
		||||
        public readonly bool IsMagicValid()
 | 
			
		||||
        {
 | 
			
		||||
            return Magic == ValidMagic;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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<BiquadFilterState> splitterBqfStates = Memory<BiquadFilterState>.Empty;
 | 
			
		||||
 | 
			
		||||
            if (_behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
 | 
			
		||||
                parameter.SplitterCount > 0 &&
 | 
			
		||||
                parameter.SplitterDestinationCount > 0)
 | 
			
		||||
            {
 | 
			
		||||
                splitterBqfStates = workBufferAllocator.Allocate<BiquadFilterState>(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<BiquadFilterState>(size, parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // DSP Voice
 | 
			
		||||
            size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Server
 | 
			
		||||
        /// <see cref="Parameter.RendererInfoOutStatus"/> 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%.
 | 
			
		||||
        ///
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>This was added in system update 6.0.0</remarks>
 | 
			
		||||
        public const int Revision5 = 5 << 24;
 | 
			
		||||
@@ -101,10 +100,18 @@ namespace Ryujinx.Audio.Renderer.Server
 | 
			
		||||
        /// <remarks>This was added in system update 14.0.0 but some changes were made in 15.0.0</remarks>
 | 
			
		||||
        public const int Revision11 = 11 << 24;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>This was added in system update 17.0.0</remarks>
 | 
			
		||||
        public const int Revision12 = 12 << 24;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Last revision supported by the implementation.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public const int LastRevision = Revision11;
 | 
			
		||||
        public const int LastRevision = Revision12;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 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.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>True if the audio renderer should use the optimization.</returns>
 | 
			
		||||
        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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Check if the audio renderer should support biquad filter on splitter.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>True if the audio renderer support biquad filter on splitter</returns>
 | 
			
		||||
        public bool IsBiquadFilterParameterForSplitterEnabled()
 | 
			
		||||
        {
 | 
			
		||||
            return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
 
 | 
			
		||||
@@ -204,7 +204,7 @@ namespace Ryujinx.Audio.Renderer.Server
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Create a new <see cref="GroupedBiquadFilterCommand"/>.
 | 
			
		||||
        /// Create a new <see cref="MultiTapBiquadFilterCommand"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="baseIndex">The base index of the input and output buffer.</param>
 | 
			
		||||
        /// <param name="filters">The biquad filter parameters.</param>
 | 
			
		||||
@@ -213,9 +213,9 @@ namespace Ryujinx.Audio.Renderer.Server
 | 
			
		||||
        /// <param name="outputBufferOffset">The output buffer offset.</param>
 | 
			
		||||
        /// <param name="isInitialized">Set to true if the biquad filter state is initialized.</param>
 | 
			
		||||
        /// <param name="nodeId">The node id associated to this command.</param>
 | 
			
		||||
        public void GenerateGroupedBiquadFilter(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
 | 
			
		||||
        public void GenerateMultiTapBiquadFilter(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> 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
 | 
			
		||||
        /// <param name="volume">The new volume.</param>
 | 
			
		||||
        /// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
 | 
			
		||||
        /// <param name="nodeId">The node id associated to this command.</param>
 | 
			
		||||
        public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> previousVolume, Span<float> volume, Memory<VoiceUpdateState> state, int nodeId)
 | 
			
		||||
        public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, ReadOnlySpan<float> previousVolume, ReadOnlySpan<float> volume, Memory<VoiceUpdateState> state, int nodeId)
 | 
			
		||||
        {
 | 
			
		||||
            MixRampGroupedCommand command = new(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId);
 | 
			
		||||
 | 
			
		||||
@@ -260,6 +260,120 @@ namespace Ryujinx.Audio.Renderer.Server
 | 
			
		||||
            AddCommand(command);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Generate a new <see cref="BiquadFilterAndMixCommand"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="previousVolume">The previous volume.</param>
 | 
			
		||||
        /// <param name="volume">The new volume.</param>
 | 
			
		||||
        /// <param name="inputBufferIndex">The input buffer index.</param>
 | 
			
		||||
        /// <param name="outputBufferIndex">The output buffer index.</param>
 | 
			
		||||
        /// <param name="lastSampleIndex">The index in the <see cref="VoiceUpdateState.LastSamples"/> array to store the ramped sample.</param>
 | 
			
		||||
        /// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
 | 
			
		||||
        /// <param name="filter">The biquad filter parameter.</param>
 | 
			
		||||
        /// <param name="biquadFilterState">The biquad state.</param>
 | 
			
		||||
        /// <param name="previousBiquadFilterState">The previous biquad state.</param>
 | 
			
		||||
        /// <param name="needInitialization">Set to true if the biquad filter state needs to be initialized.</param>
 | 
			
		||||
        /// <param name="hasVolumeRamp">Set to true if the mix has volume ramp, and <paramref name="previousVolume"/> should be taken into account.</param>
 | 
			
		||||
        /// <param name="isFirstMixBuffer">Set to true if the buffer is the first mix buffer.</param>
 | 
			
		||||
        /// <param name="nodeId">The node id associated to this command.</param>
 | 
			
		||||
        public void GenerateBiquadFilterAndMix(
 | 
			
		||||
            float previousVolume,
 | 
			
		||||
            float volume,
 | 
			
		||||
            uint inputBufferIndex,
 | 
			
		||||
            uint outputBufferIndex,
 | 
			
		||||
            int lastSampleIndex,
 | 
			
		||||
            Memory<VoiceUpdateState> state,
 | 
			
		||||
            ref BiquadFilterParameter filter,
 | 
			
		||||
            Memory<BiquadFilterState> biquadFilterState,
 | 
			
		||||
            Memory<BiquadFilterState> 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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Generate a new <see cref="MultiTapBiquadFilterAndMixCommand"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="previousVolume">The previous volume.</param>
 | 
			
		||||
        /// <param name="volume">The new volume.</param>
 | 
			
		||||
        /// <param name="inputBufferIndex">The input buffer index.</param>
 | 
			
		||||
        /// <param name="outputBufferIndex">The output buffer index.</param>
 | 
			
		||||
        /// <param name="lastSampleIndex">The index in the <see cref="VoiceUpdateState.LastSamples"/> array to store the ramped sample.</param>
 | 
			
		||||
        /// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
 | 
			
		||||
        /// <param name="filter0">First biquad filter parameter.</param>
 | 
			
		||||
        /// <param name="filter1">Second biquad filter parameter.</param>
 | 
			
		||||
        /// <param name="biquadFilterState0">First biquad state.</param>
 | 
			
		||||
        /// <param name="biquadFilterState1">Second biquad state.</param>
 | 
			
		||||
        /// <param name="previousBiquadFilterState0">First previous biquad state.</param>
 | 
			
		||||
        /// <param name="previousBiquadFilterState1">Second previous biquad state.</param>
 | 
			
		||||
        /// <param name="needInitialization0">Set to true if the first biquad filter state needs to be initialized.</param>
 | 
			
		||||
        /// <param name="needInitialization1">Set to true if the second biquad filter state needs to be initialized.</param>
 | 
			
		||||
        /// <param name="hasVolumeRamp">Set to true if the mix has volume ramp, and <paramref name="previousVolume"/> should be taken into account.</param>
 | 
			
		||||
        /// <param name="isFirstMixBuffer">Set to true if the buffer is the first mix buffer.</param>
 | 
			
		||||
        /// <param name="nodeId">The node id associated to this command.</param>
 | 
			
		||||
        public void GenerateMultiTapBiquadFilterAndMix(
 | 
			
		||||
            float previousVolume,
 | 
			
		||||
            float volume,
 | 
			
		||||
            uint inputBufferIndex,
 | 
			
		||||
            uint outputBufferIndex,
 | 
			
		||||
            int lastSampleIndex,
 | 
			
		||||
            Memory<VoiceUpdateState> state,
 | 
			
		||||
            ref BiquadFilterParameter filter0,
 | 
			
		||||
            ref BiquadFilterParameter filter1,
 | 
			
		||||
            Memory<BiquadFilterState> biquadFilterState0,
 | 
			
		||||
            Memory<BiquadFilterState> biquadFilterState1,
 | 
			
		||||
            Memory<BiquadFilterState> previousBiquadFilterState0,
 | 
			
		||||
            Memory<BiquadFilterState> 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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Generate a new <see cref="DepopForMixBuffersCommand"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@@ -268,7 +382,7 @@ namespace Ryujinx.Audio.Renderer.Server
 | 
			
		||||
        /// <param name="bufferCount">The buffer count.</param>
 | 
			
		||||
        /// <param name="nodeId">The node id associated to this command.</param>
 | 
			
		||||
        /// <param name="sampleRate">The target sample rate in use.</param>
 | 
			
		||||
        public void GenerateDepopForMixBuffersCommand(Memory<float> depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
 | 
			
		||||
        public void GenerateDepopForMixBuffers(Memory<float> depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
 | 
			
		||||
        {
 | 
			
		||||
            DepopForMixBuffersCommand command = new(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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<SplitterDestination> 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<VoiceUpdateState> 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<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
 | 
			
		||||
                Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)];
 | 
			
		||||
                Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.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<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
 | 
			
		||||
 | 
			
		||||
                        Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)];
 | 
			
		||||
                        Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.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<float> mixVolumes, Span<float> previousMixVolumes, Memory<VoiceUpdateState> state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId)
 | 
			
		||||
        private void GenerateVoiceMixWithSplitter(
 | 
			
		||||
            SplitterDestination destination,
 | 
			
		||||
            Memory<VoiceUpdateState> state,
 | 
			
		||||
            uint bufferOffset,
 | 
			
		||||
            uint bufferCount,
 | 
			
		||||
            uint bufferIndex,
 | 
			
		||||
            int nodeId)
 | 
			
		||||
        {
 | 
			
		||||
            ReadOnlySpan<float> mixVolumes = destination.MixBufferVolume;
 | 
			
		||||
            ReadOnlySpan<float> previousMixVolumes = destination.PreviousMixBufferVolume;
 | 
			
		||||
 | 
			
		||||
            ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
 | 
			
		||||
            ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
 | 
			
		||||
 | 
			
		||||
            Memory<BiquadFilterState> 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<float> mixVolumes,
 | 
			
		||||
            ReadOnlySpan<float> previousMixVolumes,
 | 
			
		||||
            Memory<VoiceUpdateState> 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<SplitterDestination> 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<BiquadFilterState> bqfState = _splitterContext.GetBiquadFilterState(destination);
 | 
			
		||||
 | 
			
		||||
            if (bqf0.Enable && bqf1.Enable)
 | 
			
		||||
            {
 | 
			
		||||
                _commandBuffer.GenerateMultiTapBiquadFilterAndMix(
 | 
			
		||||
                    0f,
 | 
			
		||||
                    volume,
 | 
			
		||||
                    inputBufferIndex,
 | 
			
		||||
                    outputBufferIndex,
 | 
			
		||||
                    0,
 | 
			
		||||
                    Memory<VoiceUpdateState>.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<VoiceUpdateState>.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<VoiceUpdateState>.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<SplitterDestination> 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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -225,11 +225,11 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
 | 
			
		||||
 | 
			
		||||
                    for (int i = 0; i < splitter.DestinationCount; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        Span<SplitterDestination> 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)
 | 
			
		||||
                            {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class SplitterContext
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Amount of biquad filter states per splitter destination.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public const int BqfStatesPerDestination = 4;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Storage for <see cref="SplitterState"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private Memory<SplitterState> _splitters;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Storage for <see cref="SplitterDestination"/>.
 | 
			
		||||
        /// Storage for <see cref="SplitterDestinationVersion1"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private Memory<SplitterDestination> _splitterDestinations;
 | 
			
		||||
        private Memory<SplitterDestinationVersion1> _splitterDestinationsV1;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Storage for <see cref="SplitterDestinationVersion2"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private Memory<SplitterDestinationVersion2> _splitterDestinationsV2;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Splitter biquad filtering states.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private Memory<BiquadFilterState> _splitterBqfStates;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Version of the splitter context that is being used, currently can be 1 or 2.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int Version { get; private set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.
 | 
			
		||||
@@ -36,12 +57,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        /// <param name="behaviourContext">The behaviour context.</param>
 | 
			
		||||
        /// <param name="parameter">The audio renderer configuration.</param>
 | 
			
		||||
        /// <param name="workBufferAllocator">The <see cref="WorkBufferAllocator"/>.</param>
 | 
			
		||||
        /// <param name="splitterBqfStates">Memory to store the biquad filtering state for splitters during processing.</param>
 | 
			
		||||
        /// <returns>Return true if the initialization was successful.</returns>
 | 
			
		||||
        public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator)
 | 
			
		||||
        public bool Initialize(
 | 
			
		||||
            ref BehaviourContext behaviourContext,
 | 
			
		||||
            ref AudioRendererConfiguration parameter,
 | 
			
		||||
            WorkBufferAllocator workBufferAllocator,
 | 
			
		||||
            Memory<BiquadFilterState> splitterBqfStates)
 | 
			
		||||
        {
 | 
			
		||||
            if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0)
 | 
			
		||||
            {
 | 
			
		||||
                Setup(Memory<SplitterState>.Empty, Memory<SplitterDestination>.Empty, false);
 | 
			
		||||
                Setup(Memory<SplitterState>.Empty, Memory<SplitterDestinationVersion1>.Empty, Memory<SplitterDestinationVersion2>.Empty, false);
 | 
			
		||||
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
@@ -60,23 +86,62 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
                splitter = new SplitterState(splitterId++);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Memory<SplitterDestination> splitterDestinations = workBufferAllocator.Allocate<SplitterDestination>(parameter.SplitterDestinationCount,
 | 
			
		||||
                SplitterDestination.Alignment);
 | 
			
		||||
            Memory<SplitterDestinationVersion1> splitterDestinationsV1 = Memory<SplitterDestinationVersion1>.Empty;
 | 
			
		||||
            Memory<SplitterDestinationVersion2> splitterDestinationsV2 = Memory<SplitterDestinationVersion2>.Empty;
 | 
			
		||||
 | 
			
		||||
            if (splitterDestinations.IsEmpty)
 | 
			
		||||
            if (!behaviourContext.IsBiquadFilterParameterForSplitterEnabled())
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
                Version = 1;
 | 
			
		||||
 | 
			
		||||
                splitterDestinationsV1 = workBufferAllocator.Allocate<SplitterDestinationVersion1>(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<SplitterDestinationVersion2>(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<BiquadFilterState>.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<SplitterState>(size, parameter.SplitterCount, SplitterState.Alignment);
 | 
			
		||||
                size = WorkBufferAllocator.GetTargetSize<SplitterDestination>(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment);
 | 
			
		||||
 | 
			
		||||
                if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled())
 | 
			
		||||
                {
 | 
			
		||||
                    size = WorkBufferAllocator.GetTargetSize<SplitterDestinationVersion2>(size, parameter.SplitterDestinationCount, SplitterDestinationVersion2.Alignment);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    size = WorkBufferAllocator.GetTargetSize<SplitterDestinationVersion1>(size, parameter.SplitterDestinationCount, SplitterDestinationVersion1.Alignment);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (behaviourContext.IsSplitterBugFixed())
 | 
			
		||||
                {
 | 
			
		||||
@@ -110,12 +183,18 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        /// Setup the <see cref="SplitterContext"/> instance.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="splitters">The <see cref="SplitterState"/> storage.</param>
 | 
			
		||||
        /// <param name="splitterDestinations">The <see cref="SplitterDestination"/> storage.</param>
 | 
			
		||||
        /// <param name="splitterDestinationsV1">The <see cref="SplitterDestinationVersion1"/> storage.</param>
 | 
			
		||||
        /// <param name="splitterDestinationsV2">The <see cref="SplitterDestinationVersion2"/> storage.</param>
 | 
			
		||||
        /// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.</param>
 | 
			
		||||
        private void Setup(Memory<SplitterState> splitters, Memory<SplitterDestination> splitterDestinations, bool isBugFixed)
 | 
			
		||||
        private void Setup(
 | 
			
		||||
            Memory<SplitterState> splitters,
 | 
			
		||||
            Memory<SplitterDestinationVersion1> splitterDestinationsV1,
 | 
			
		||||
            Memory<SplitterDestinationVersion2> 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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@@ -178,7 +259,39 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Update one or multiple <see cref="SplitterDestination"/> from user parameters.
 | 
			
		||||
        /// Update one splitter destination data from user parameters.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="input">The raw data after the splitter header.</param>
 | 
			
		||||
        /// <returns>True if the update was successful, false otherwise</returns>
 | 
			
		||||
        private bool UpdateData<T>(ref SequenceReader<byte> input) where T : unmanaged, ISplitterDestinationInParameter
 | 
			
		||||
        {
 | 
			
		||||
            ref readonly T parameter = ref input.GetRefOrRefToCopy<T>(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<T>());
 | 
			
		||||
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Update one or multiple splitter destination data from user parameters.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="inputHeader">The splitter header.</param>
 | 
			
		||||
        /// <param name="input">The raw data after the splitter header.</param>
 | 
			
		||||
@@ -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<SplitterDestinationInParameter>(out _);
 | 
			
		||||
 | 
			
		||||
                Debug.Assert(parameter.IsMagicValid());
 | 
			
		||||
 | 
			
		||||
                if (parameter.IsMagicValid())
 | 
			
		||||
                if (Version == 1)
 | 
			
		||||
                {
 | 
			
		||||
                    if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length)
 | 
			
		||||
                    if (!UpdateData<SplitterDestinationInParameterVersion1>(ref input))
 | 
			
		||||
                    {
 | 
			
		||||
                        ref SplitterDestination destination = ref GetDestination(parameter.Id);
 | 
			
		||||
 | 
			
		||||
                        destination.Update(parameter);
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else if (Version == 2)
 | 
			
		||||
                {
 | 
			
		||||
                    if (!UpdateData<SplitterDestinationInParameterVersion2>(ref input))
 | 
			
		||||
                    {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    input.Rewind(Unsafe.SizeOf<SplitterDestinationInParameter>());
 | 
			
		||||
                    break;
 | 
			
		||||
                    Debug.Fail($"Invalid splitter context version {Version}.");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -214,7 +327,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        /// <returns>Return true if the update was successful.</returns>
 | 
			
		||||
        public bool Update(ref SequenceReader<byte> input)
 | 
			
		||||
        {
 | 
			
		||||
            if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
 | 
			
		||||
            if (!UsingSplitter())
 | 
			
		||||
            {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
@@ -251,45 +364,52 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get a reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>.
 | 
			
		||||
        /// Get a reference to the splitter destination data at the given <paramref name="id"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The index to use.</param>
 | 
			
		||||
        /// <returns>A reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>.</returns>
 | 
			
		||||
        public ref SplitterDestination GetDestination(int id)
 | 
			
		||||
        /// <returns>A reference to the splitter destination data at the given <paramref name="id"/>.</returns>
 | 
			
		||||
        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));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get a <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The index to use.</param>
 | 
			
		||||
        /// <returns>A <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.</returns>
 | 
			
		||||
        public Memory<SplitterDestination> GetDestinationMemory(int id)
 | 
			
		||||
        {
 | 
			
		||||
            return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get a <see cref="Span{SplitterDestination}"/> in the <see cref="SplitterState"/> at <paramref name="id"/> and pass <paramref name="destinationId"/> to <see cref="SplitterState.GetData(int)"/>.
 | 
			
		||||
        /// Get a <see cref="SplitterDestination"/> in the <see cref="SplitterState"/> at <paramref name="id"/> and pass <paramref name="destinationId"/> to <see cref="SplitterState.GetData(int)"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The index to use to get the <see cref="SplitterState"/>.</param>
 | 
			
		||||
        /// <param name="destinationId">The index of the <see cref="SplitterDestination"/>.</param>
 | 
			
		||||
        /// <returns>A <see cref="Span{SplitterDestination}"/>.</returns>
 | 
			
		||||
        public Span<SplitterDestination> GetDestination(int id, int destinationId)
 | 
			
		||||
        /// <returns>A <see cref="SplitterDestination"/>.</returns>
 | 
			
		||||
        public SplitterDestination GetDestination(int id, int destinationId)
 | 
			
		||||
        {
 | 
			
		||||
            ref SplitterState splitter = ref GetState(id);
 | 
			
		||||
 | 
			
		||||
            return splitter.GetData(destinationId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the biquad filter state for a given splitter destination.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="destination">The splitter destination.</param>
 | 
			
		||||
        /// <returns>Biquad filter state for the specified destination.</returns>
 | 
			
		||||
        public Memory<BiquadFilterState> GetBiquadFilterState(SplitterDestination destination)
 | 
			
		||||
        {
 | 
			
		||||
            return _splitterBqfStates.Slice(destination.Id * BqfStatesPerDestination, BqfStatesPerDestination);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Return true if the audio renderer has any splitters.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>True if the audio renderer has any splitters.</returns>
 | 
			
		||||
        public bool UsingSplitter()
 | 
			
		||||
        {
 | 
			
		||||
            return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty;
 | 
			
		||||
            return !_splitters.IsEmpty && (!_splitterDestinationsV1.IsEmpty || !_splitterDestinationsV2.IsEmpty);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Server state for a splitter destination.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [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;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The unique id of this <see cref="SplitterDestination"/>.
 | 
			
		||||
        /// Checks if the splitter destination data reference is null.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int Id;
 | 
			
		||||
        public bool IsNull => Unsafe.IsNullRef(ref _v1) && Unsafe.IsNullRef(ref _v2);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The mix to output the result of the splitter.
 | 
			
		||||
        /// The splitter unique id.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int DestinationId;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Mix buffer volumes storage.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private MixArray _mix;
 | 
			
		||||
        private MixArray _previousMix;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Pointer to the next linked element.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private unsafe SplitterDestination* _next;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set to true if in use.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [MarshalAs(UnmanagedType.I1)]
 | 
			
		||||
        public bool IsUsed;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set to true if the internal state need to be updated.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [MarshalAs(UnmanagedType.I1)]
 | 
			
		||||
        public bool NeedToUpdateInternalState;
 | 
			
		||||
 | 
			
		||||
        [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
 | 
			
		||||
        private struct MixArray { }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Mix buffer volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>Used when a splitter id is specified in the mix.</remarks>
 | 
			
		||||
        public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Previous mix buffer volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>Used when a splitter id is specified in the mix.</remarks>
 | 
			
		||||
        public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get the  <see cref="Span{SplitterDestination}"/> of the next element or <see cref="Span{SplitterDestination}.Empty"/> if not present.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public readonly Span<SplitterDestination> Next
 | 
			
		||||
        public int Id
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                unsafe
 | 
			
		||||
                if (Unsafe.IsNullRef(ref _v2))
 | 
			
		||||
                {
 | 
			
		||||
                    return _next != null ? new Span<SplitterDestination>(_next, 1) : Span<SplitterDestination>.Empty;
 | 
			
		||||
                    if (Unsafe.IsNullRef(ref _v1))
 | 
			
		||||
                    {
 | 
			
		||||
                        return 0;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        return _v1.Id;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return _v2.Id;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Create a new <see cref="SplitterDestination"/>.
 | 
			
		||||
        /// The mix to output the result of the splitter.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The unique id of this <see cref="SplitterDestination"/>.</param>
 | 
			
		||||
        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;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Update the <see cref="SplitterDestination"/> from user parameter.
 | 
			
		||||
        /// Mix buffer volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>Used when a splitter id is specified in the mix.</remarks>
 | 
			
		||||
        public Span<float> MixBufferVolume
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                if (Unsafe.IsNullRef(ref _v2))
 | 
			
		||||
                {
 | 
			
		||||
                    if (Unsafe.IsNullRef(ref _v1))
 | 
			
		||||
                    {
 | 
			
		||||
                        return Span<float>.Empty;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        return _v1.MixBufferVolume;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return _v2.MixBufferVolume;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Previous mix buffer volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>Used when a splitter id is specified in the mix.</remarks>
 | 
			
		||||
        public Span<float> PreviousMixBufferVolume
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                if (Unsafe.IsNullRef(ref _v2))
 | 
			
		||||
                {
 | 
			
		||||
                    if (Unsafe.IsNullRef(ref _v1))
 | 
			
		||||
                    {
 | 
			
		||||
                        return Span<float>.Empty;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        return _v1.PreviousMixBufferVolume;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return _v2.PreviousMixBufferVolume;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get the <see cref="SplitterDestination"/> of the next element or null if not present.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        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);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a new splitter destination wrapper for the version 1 splitter destination data.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="v1">Version 1 splitter destination data</param>
 | 
			
		||||
        public SplitterDestination(ref SplitterDestinationVersion1 v1)
 | 
			
		||||
        {
 | 
			
		||||
            _v1 = ref v1;
 | 
			
		||||
            _v2 = ref Unsafe.NullRef<SplitterDestinationVersion2>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a new splitter destination wrapper for the version 2 splitter destination data.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="v2">Version 2 splitter destination data</param>
 | 
			
		||||
        public SplitterDestination(ref SplitterDestinationVersion2 v2)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            _v1 = ref Unsafe.NullRef<SplitterDestinationVersion1>();
 | 
			
		||||
            _v2 = ref v2;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a new splitter destination wrapper for the splitter destination data.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="v1">Version 1 splitter destination data</param>
 | 
			
		||||
        /// <param name="v2">Version 2 splitter destination data</param>
 | 
			
		||||
        public unsafe SplitterDestination(SplitterDestinationVersion1* v1, SplitterDestinationVersion2* v2)
 | 
			
		||||
        {
 | 
			
		||||
            _v1 = ref Unsafe.AsRef<SplitterDestinationVersion1>(v1);
 | 
			
		||||
            _v2 = ref Unsafe.AsRef<SplitterDestinationVersion2>(v2);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Update the splitter destination data from user parameter.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="parameter">The user parameter.</param>
 | 
			
		||||
        public void Update(SplitterDestinationInParameter parameter)
 | 
			
		||||
        public void Update<T>(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
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public void UpdateInternalState()
 | 
			
		||||
        {
 | 
			
		||||
            if (IsUsed && NeedToUpdateInternalState)
 | 
			
		||||
            if (Unsafe.IsNullRef(ref _v2))
 | 
			
		||||
            {
 | 
			
		||||
                MixBufferVolume.CopyTo(PreviousMixBufferVolume);
 | 
			
		||||
                _v1.UpdateInternalState();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                _v2.UpdateInternalState();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            NeedToUpdateInternalState = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@@ -131,16 +216,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public void MarkAsNeedToUpdateInternalState()
 | 
			
		||||
        {
 | 
			
		||||
            NeedToUpdateInternalState = true;
 | 
			
		||||
            if (Unsafe.IsNullRef(ref _v2))
 | 
			
		||||
            {
 | 
			
		||||
                _v1.MarkAsNeedToUpdateInternalState();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                _v2.MarkAsNeedToUpdateInternalState();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Return true if the <see cref="SplitterDestination"/> is used and has a destination.
 | 
			
		||||
        /// Return true if the splitter destination is used and has a destination.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>True if the <see cref="SplitterDestination"/> is used and has a destination.</returns>
 | 
			
		||||
        /// <returns>True if the splitter destination is used and has a destination.</returns>
 | 
			
		||||
        public readonly bool IsConfigured()
 | 
			
		||||
        {
 | 
			
		||||
            return IsUsed && DestinationId != Constants.UnusedMixId;
 | 
			
		||||
            return Unsafe.IsNullRef(ref _v2) ? _v1.IsConfigured() : _v2.IsConfigured();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@@ -150,9 +242,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        /// <returns>The volume for the given destination.</returns>
 | 
			
		||||
        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];
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get the previous volume for a given destination.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="destinationIndex">The destination index to use.</param>
 | 
			
		||||
        /// <returns>The volume for the given destination.</returns>
 | 
			
		||||
        public float GetMixVolumePrev(int destinationIndex)
 | 
			
		||||
        {
 | 
			
		||||
            return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolumePrev(destinationIndex) : _v2.GetMixVolumePrev(destinationIndex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@@ -160,22 +260,33 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public void ClearVolumes()
 | 
			
		||||
        {
 | 
			
		||||
            MixBufferVolume.Clear();
 | 
			
		||||
            PreviousMixBufferVolume.Clear();
 | 
			
		||||
            if (Unsafe.IsNullRef(ref _v2))
 | 
			
		||||
            {
 | 
			
		||||
                _v1.ClearVolumes();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                _v2.ClearVolumes();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Link the next element to the given <see cref="SplitterDestination"/>.
 | 
			
		||||
        /// Link the next element to the given splitter destination.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="next">The given <see cref="SplitterDestination"/> to link.</param>
 | 
			
		||||
        public void Link(ref SplitterDestination next)
 | 
			
		||||
        /// <param name="next">The given splitter destination to link.</param>
 | 
			
		||||
        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
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public void Unlink()
 | 
			
		||||
        {
 | 
			
		||||
            unsafe
 | 
			
		||||
            if (Unsafe.IsNullRef(ref _v2))
 | 
			
		||||
            {
 | 
			
		||||
                _next = null;
 | 
			
		||||
                _v1.Unlink();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                _v2.Unlink();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Checks if any biquad filter is enabled.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>True if any biquad filter is enabled.</returns>
 | 
			
		||||
        public bool IsBiquadFilterEnabled()
 | 
			
		||||
        {
 | 
			
		||||
            return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabled();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Checks if any biquad filter was previously enabled.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>True if any biquad filter was previously enabled.</returns>
 | 
			
		||||
        public bool IsBiquadFilterEnabledPrev()
 | 
			
		||||
        {
 | 
			
		||||
            return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabledPrev();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the biquad filter parameters.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="index">Biquad filter index (0 or 1).</param>
 | 
			
		||||
        /// <returns>Biquad filter parameters.</returns>
 | 
			
		||||
        public ref BiquadFilterParameter GetBiquadFilterParameter(int index)
 | 
			
		||||
        {
 | 
			
		||||
            Debug.Assert(!Unsafe.IsNullRef(ref _v2));
 | 
			
		||||
 | 
			
		||||
            return ref _v2.GetBiquadFilterParameter(index);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Checks if any biquad filter was previously enabled.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="index">Biquad filter index (0 or 1).</param>
 | 
			
		||||
        public void UpdateBiquadFilterEnabledPrev(int index)
 | 
			
		||||
        {
 | 
			
		||||
            if (!Unsafe.IsNullRef(ref _v2))
 | 
			
		||||
            {
 | 
			
		||||
                _v2.UpdateBiquadFilterEnabledPrev(index);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get the reference for the version 1 splitter destination data, or null if version 2 is being used or the destination is null.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>Reference for the version 1 splitter destination data.</returns>
 | 
			
		||||
        public ref SplitterDestinationVersion1 GetV1RefOrNull()
 | 
			
		||||
        {
 | 
			
		||||
            return ref _v1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get the reference for the version 2 splitter destination data, or null if version 1 is being used or the destination is null.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>Reference for the version 2 splitter destination data.</returns>
 | 
			
		||||
        public ref SplitterDestinationVersion2 GetV2RefOrNull()
 | 
			
		||||
        {
 | 
			
		||||
            return ref _v2;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Server state for a splitter destination (version 1).
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)]
 | 
			
		||||
    public struct SplitterDestinationVersion1
 | 
			
		||||
    {
 | 
			
		||||
        public const int Alignment = 0x10;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The unique id of this <see cref="SplitterDestinationVersion1"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int Id;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The mix to output the result of the splitter.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int DestinationId;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Mix buffer volumes storage.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private MixArray _mix;
 | 
			
		||||
        private MixArray _previousMix;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Pointer to the next linked element.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private unsafe SplitterDestinationVersion1* _next;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set to true if in use.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [MarshalAs(UnmanagedType.I1)]
 | 
			
		||||
        public bool IsUsed;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set to true if the internal state need to be updated.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [MarshalAs(UnmanagedType.I1)]
 | 
			
		||||
        public bool NeedToUpdateInternalState;
 | 
			
		||||
 | 
			
		||||
        [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
 | 
			
		||||
        private struct MixArray { }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Mix buffer volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>Used when a splitter id is specified in the mix.</remarks>
 | 
			
		||||
        public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Previous mix buffer volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>Used when a splitter id is specified in the mix.</remarks>
 | 
			
		||||
        public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get the reference of the next element or null if not present.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public readonly ref SplitterDestinationVersion1 Next
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                unsafe
 | 
			
		||||
                {
 | 
			
		||||
                    return ref Unsafe.AsRef<SplitterDestinationVersion1>(_next);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Create a new <see cref="SplitterDestinationVersion1"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The unique id of this <see cref="SplitterDestinationVersion1"/>.</param>
 | 
			
		||||
        public SplitterDestinationVersion1(int id) : this()
 | 
			
		||||
        {
 | 
			
		||||
            Id = id;
 | 
			
		||||
            DestinationId = Constants.UnusedMixId;
 | 
			
		||||
 | 
			
		||||
            ClearVolumes();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Update the <see cref="SplitterDestinationVersion1"/> from user parameter.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="parameter">The user parameter.</param>
 | 
			
		||||
        public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter
 | 
			
		||||
        {
 | 
			
		||||
            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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Update the internal state of the instance.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public void UpdateInternalState()
 | 
			
		||||
        {
 | 
			
		||||
            if (IsUsed && NeedToUpdateInternalState)
 | 
			
		||||
            {
 | 
			
		||||
                MixBufferVolume.CopyTo(PreviousMixBufferVolume);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            NeedToUpdateInternalState = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set the update internal state marker.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public void MarkAsNeedToUpdateInternalState()
 | 
			
		||||
        {
 | 
			
		||||
            NeedToUpdateInternalState = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Return true if the <see cref="SplitterDestinationVersion1"/> is used and has a destination.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>True if the <see cref="SplitterDestinationVersion1"/> is used and has a destination.</returns>
 | 
			
		||||
        public readonly bool IsConfigured()
 | 
			
		||||
        {
 | 
			
		||||
            return IsUsed && DestinationId != Constants.UnusedMixId;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get the volume for a given destination.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="destinationIndex">The destination index to use.</param>
 | 
			
		||||
        /// <returns>The volume for the given destination.</returns>
 | 
			
		||||
        public float GetMixVolume(int destinationIndex)
 | 
			
		||||
        {
 | 
			
		||||
            Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
 | 
			
		||||
 | 
			
		||||
            return MixBufferVolume[destinationIndex];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get the previous volume for a given destination.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="destinationIndex">The destination index to use.</param>
 | 
			
		||||
        /// <returns>The volume for the given destination.</returns>
 | 
			
		||||
        public float GetMixVolumePrev(int destinationIndex)
 | 
			
		||||
        {
 | 
			
		||||
            Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
 | 
			
		||||
 | 
			
		||||
            return PreviousMixBufferVolume[destinationIndex];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Clear the volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public void ClearVolumes()
 | 
			
		||||
        {
 | 
			
		||||
            MixBufferVolume.Clear();
 | 
			
		||||
            PreviousMixBufferVolume.Clear();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Link the next element to the given <see cref="SplitterDestinationVersion1"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="next">The given <see cref="SplitterDestinationVersion1"/> to link.</param>
 | 
			
		||||
        public void Link(ref SplitterDestinationVersion1 next)
 | 
			
		||||
        {
 | 
			
		||||
            unsafe
 | 
			
		||||
            {
 | 
			
		||||
                fixed (SplitterDestinationVersion1* nextPtr = &next)
 | 
			
		||||
                {
 | 
			
		||||
                    _next = nextPtr;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Remove the link to the next element.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public void Unlink()
 | 
			
		||||
        {
 | 
			
		||||
            unsafe
 | 
			
		||||
            {
 | 
			
		||||
                _next = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Server state for a splitter destination (version 2).
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [StructLayout(LayoutKind.Sequential, Size = 0x110, Pack = Alignment)]
 | 
			
		||||
    public struct SplitterDestinationVersion2
 | 
			
		||||
    {
 | 
			
		||||
        public const int Alignment = 0x10;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The unique id of this <see cref="SplitterDestinationVersion2"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int Id;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The mix to output the result of the splitter.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int DestinationId;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Mix buffer volumes storage.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private MixArray _mix;
 | 
			
		||||
        private MixArray _previousMix;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Pointer to the next linked element.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private unsafe SplitterDestinationVersion2* _next;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set to true if in use.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [MarshalAs(UnmanagedType.I1)]
 | 
			
		||||
        public bool IsUsed;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set to true if the internal state need to be updated.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [MarshalAs(UnmanagedType.I1)]
 | 
			
		||||
        public bool NeedToUpdateInternalState;
 | 
			
		||||
 | 
			
		||||
        [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
 | 
			
		||||
        private struct MixArray { }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Mix buffer volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>Used when a splitter id is specified in the mix.</remarks>
 | 
			
		||||
        public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Previous mix buffer volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>Used when a splitter id is specified in the mix.</remarks>
 | 
			
		||||
        public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get the reference of the next element or null if not present.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public readonly ref SplitterDestinationVersion2 Next
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                unsafe
 | 
			
		||||
                {
 | 
			
		||||
                    return ref Unsafe.AsRef<SplitterDestinationVersion2>(_next);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Array2<BiquadFilterParameter> _biquadFilters;
 | 
			
		||||
 | 
			
		||||
        private Array2<bool> _isPreviousBiquadFilterEnabled;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Create a new <see cref="SplitterDestinationVersion2"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The unique id of this <see cref="SplitterDestinationVersion2"/>.</param>
 | 
			
		||||
        public SplitterDestinationVersion2(int id) : this()
 | 
			
		||||
        {
 | 
			
		||||
            Id = id;
 | 
			
		||||
            DestinationId = Constants.UnusedMixId;
 | 
			
		||||
 | 
			
		||||
            ClearVolumes();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Update the <see cref="SplitterDestinationVersion2"/> from user parameter.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="parameter">The user parameter.</param>
 | 
			
		||||
        public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter
 | 
			
		||||
        {
 | 
			
		||||
            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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Update the internal state of the instance.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public void UpdateInternalState()
 | 
			
		||||
        {
 | 
			
		||||
            if (IsUsed && NeedToUpdateInternalState)
 | 
			
		||||
            {
 | 
			
		||||
                MixBufferVolume.CopyTo(PreviousMixBufferVolume);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            NeedToUpdateInternalState = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set the update internal state marker.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public void MarkAsNeedToUpdateInternalState()
 | 
			
		||||
        {
 | 
			
		||||
            NeedToUpdateInternalState = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Return true if the <see cref="SplitterDestinationVersion2"/> is used and has a destination.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>True if the <see cref="SplitterDestinationVersion2"/> is used and has a destination.</returns>
 | 
			
		||||
        public readonly bool IsConfigured()
 | 
			
		||||
        {
 | 
			
		||||
            return IsUsed && DestinationId != Constants.UnusedMixId;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get the volume for a given destination.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="destinationIndex">The destination index to use.</param>
 | 
			
		||||
        /// <returns>The volume for the given destination.</returns>
 | 
			
		||||
        public float GetMixVolume(int destinationIndex)
 | 
			
		||||
        {
 | 
			
		||||
            Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
 | 
			
		||||
 | 
			
		||||
            return MixBufferVolume[destinationIndex];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Get the previous volume for a given destination.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="destinationIndex">The destination index to use.</param>
 | 
			
		||||
        /// <returns>The volume for the given destination.</returns>
 | 
			
		||||
        public float GetMixVolumePrev(int destinationIndex)
 | 
			
		||||
        {
 | 
			
		||||
            Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
 | 
			
		||||
 | 
			
		||||
            return PreviousMixBufferVolume[destinationIndex];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Clear the volumes.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public void ClearVolumes()
 | 
			
		||||
        {
 | 
			
		||||
            MixBufferVolume.Clear();
 | 
			
		||||
            PreviousMixBufferVolume.Clear();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Link the next element to the given <see cref="SplitterDestinationVersion2"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="next">The given <see cref="SplitterDestinationVersion2"/> to link.</param>
 | 
			
		||||
        public void Link(ref SplitterDestinationVersion2 next)
 | 
			
		||||
        {
 | 
			
		||||
            unsafe
 | 
			
		||||
            {
 | 
			
		||||
                fixed (SplitterDestinationVersion2* nextPtr = &next)
 | 
			
		||||
                {
 | 
			
		||||
                    _next = nextPtr;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Remove the link to the next element.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public void Unlink()
 | 
			
		||||
        {
 | 
			
		||||
            unsafe
 | 
			
		||||
            {
 | 
			
		||||
                _next = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Checks if any biquad filter is enabled.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>True if any biquad filter is enabled.</returns>
 | 
			
		||||
        public bool IsBiquadFilterEnabled()
 | 
			
		||||
        {
 | 
			
		||||
            return _biquadFilters[0].Enable || _biquadFilters[1].Enable;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Checks if any biquad filter was previously enabled.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>True if any biquad filter was previously enabled.</returns>
 | 
			
		||||
        public bool IsBiquadFilterEnabledPrev()
 | 
			
		||||
        {
 | 
			
		||||
            return _isPreviousBiquadFilterEnabled[0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the biquad filter parameters.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="index">Biquad filter index (0 or 1).</param>
 | 
			
		||||
        /// <returns>Biquad filter parameters.</returns>
 | 
			
		||||
        public ref BiquadFilterParameter GetBiquadFilterParameter(int index)
 | 
			
		||||
        {
 | 
			
		||||
            return ref _biquadFilters[index];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Checks if any biquad filter was previously enabled.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="index">Biquad filter index (0 or 1).</param>
 | 
			
		||||
        public void UpdateBiquadFilterEnabledPrev(int index)
 | 
			
		||||
        {
 | 
			
		||||
            _isPreviousBiquadFilterEnabled[index] = _biquadFilters[index].Enable;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -15,6 +15,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
    {
 | 
			
		||||
        public const int Alignment = 0x10;
 | 
			
		||||
 | 
			
		||||
        private delegate void SplitterDestinationAction(SplitterDestination destination, int index);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The unique id of this <see cref="SplitterState"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@@ -26,7 +28,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        public uint SampleRate;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Count of splitter destinations (<see cref="SplitterDestination"/>).
 | 
			
		||||
        /// Count of splitter destinations.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int DestinationCount;
 | 
			
		||||
 | 
			
		||||
@@ -37,20 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
        public bool HasNewConnection;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Linked list of <see cref="SplitterDestination"/>.
 | 
			
		||||
        /// Linked list of <see cref="SplitterDestinationVersion1"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private unsafe SplitterDestination* _destinationsData;
 | 
			
		||||
        private unsafe SplitterDestinationVersion1* _destinationDataV1;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Span to the first element of the linked list of <see cref="SplitterDestination"/>.
 | 
			
		||||
        /// Linked list of <see cref="SplitterDestinationVersion2"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public readonly Span<SplitterDestination> Destinations
 | 
			
		||||
        private unsafe SplitterDestinationVersion2* _destinationDataV2;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// First element of the linked list of splitter destinations data.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public readonly SplitterDestination Destination
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                unsafe
 | 
			
		||||
                {
 | 
			
		||||
                    return (IntPtr)_destinationsData != IntPtr.Zero ? new Span<SplitterDestination>(_destinationsData, 1) : Span<SplitterDestination>.Empty;
 | 
			
		||||
                    return new SplitterDestination(_destinationDataV1, _destinationDataV2);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -64,20 +71,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
 | 
			
		||||
            Id = id;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public readonly Span<SplitterDestination> GetData(int index)
 | 
			
		||||
        public readonly SplitterDestination GetData(int index)
 | 
			
		||||
        {
 | 
			
		||||
            int i = 0;
 | 
			
		||||
 | 
			
		||||
            Span<SplitterDestination> 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
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Utility function to apply a given <see cref="SpanAction{T, TArg}"/> to all <see cref="Destinations"/>.
 | 
			
		||||
        /// Utility function to apply an action to all <see cref="Destination"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="action">The action to execute on each elements.</param>
 | 
			
		||||
        private readonly void ForEachDestination(SpanAction<SplitterDestination, int> action)
 | 
			
		||||
        private readonly void ForEachDestination(SplitterDestinationAction action)
 | 
			
		||||
        {
 | 
			
		||||
            Span<SplitterDestination> temp = Destinations;
 | 
			
		||||
            SplitterDestination temp = Destination;
 | 
			
		||||
 | 
			
		||||
            int i = 0;
 | 
			
		||||
 | 
			
		||||
            while (true)
 | 
			
		||||
            {
 | 
			
		||||
                if (temp.IsEmpty)
 | 
			
		||||
                if (temp.IsNull)
 | 
			
		||||
                {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Span<SplitterDestination> 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<SplitterDestination> 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<SplitterDestination> 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
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Set the head of the linked list of <see cref="Destinations"/>.
 | 
			
		||||
        /// Set the head of the linked list of <see cref="Destination"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="newValue">A reference to a <see cref="SplitterDestination"/>.</param>
 | 
			
		||||
        public void SetDestination(ref SplitterDestination newValue)
 | 
			
		||||
        /// <param name="newValue">New destination value.</param>
 | 
			
		||||
        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
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public readonly void UpdateInternalState()
 | 
			
		||||
        {
 | 
			
		||||
            ForEachDestination((destination, _) => destination[0].UpdateInternalState());
 | 
			
		||||
            ForEachDestination((destination, _) => destination.UpdateInternalState());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Clear all links from the <see cref="Destinations"/>.
 | 
			
		||||
        /// Clear all links from the <see cref="Destination"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,8 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
 | 
			
		||||
        [Test]
 | 
			
		||||
        public void EnsureTypeSize()
 | 
			
		||||
        {
 | 
			
		||||
            Assert.AreEqual(0xE0, Unsafe.SizeOf<SplitterDestination>());
 | 
			
		||||
            Assert.AreEqual(0xE0, Unsafe.SizeOf<SplitterDestinationVersion1>());
 | 
			
		||||
            Assert.AreEqual(0x110, Unsafe.SizeOf<SplitterDestinationVersion2>());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user