aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2024-05-17 16:46:43 -0300
committerGitHub <noreply@github.com>2024-05-17 16:46:43 -0300
commit4d84df94873a070f6f5c199438f957b24d8cf8a9 (patch)
tree3e4b4cc9585526c63f3a9fdb3a150bfd721a5030
parent9ec8b2c01a0b00f3a33d9a23b9f5ff3758520484 (diff)
Update audio renderer to REV12: Add support for splitter biquad filter (#6813)1.1.1314
* 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
-rw-r--r--src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs1
-rw-r--r--src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs234
-rw-r--r--src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs123
-rw-r--r--src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs4
-rw-r--r--src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs16
-rw-r--r--src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs145
-rw-r--r--src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs (renamed from src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs)6
-rw-r--r--src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs6
-rw-r--r--src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs43
-rw-r--r--src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs (renamed from src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs)15
-rw-r--r--src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs81
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs26
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs22
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs124
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs570
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs12
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs12
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs12
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs2
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs48
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs4
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs6
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs218
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs339
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs206
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs250
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs74
-rw-r--r--src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs96
-rw-r--r--src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs3
29 files changed, 2324 insertions, 374 deletions
diff --git a/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
index 608381af..7f881373 100644
--- a/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
+++ b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
@@ -15,7 +15,6 @@ namespace Ryujinx.Audio.Renderer.Common
{
public const int Align = 0x10;
public const int BiquadStateOffset = 0x0;
- public const int BiquadStateSize = 0x10;
/// <summary>
/// The state of the biquad filters of this voice.
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
index 1a51a1fb..31f614d6 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
@@ -16,10 +16,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
/// <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);
@@ -41,16 +46,111 @@ 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>
/// <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 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;
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs
new file mode 100644
index 00000000..106fc035
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs
@@ -0,0 +1,123 @@
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class BiquadFilterAndMixCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.BiquadFilterAndMix;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ private BiquadFilterParameter _parameter;
+
+ public Memory<BiquadFilterState> 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);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
index 098a04a0..de5c0ea2 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
@@ -30,8 +30,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
CopyMixBuffer,
LimiterVersion1,
LimiterVersion2,
- GroupedBiquadFilter,
+ MultiTapBiquadFilter,
CaptureBuffer,
Compressor,
+ BiquadFilterAndMix,
+ MultiTapBiquadFilterAndMix,
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
index 3c7dd63b..41ac84c1 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
@@ -24,7 +24,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public Memory<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;
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs
new file mode 100644
index 00000000..e359371b
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs
@@ -0,0 +1,145 @@
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class MultiTapBiquadFilterAndMixCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.MultiTapBiquadFilterAndMix;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ private BiquadFilterParameter _parameter0;
+ private BiquadFilterParameter _parameter1;
+
+ public Memory<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);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs
index 7af851bd..e159f8ef 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs
@@ -4,13 +4,13 @@ using System;
namespace Ryujinx.Audio.Renderer.Dsp.Command
{
- public class GroupedBiquadFilterCommand : ICommand
+ public class MultiTapBiquadFilterCommand : ICommand
{
public bool Enabled { get; set; }
public int NodeId { get; }
- public CommandType CommandType => CommandType.GroupedBiquadFilter;
+ public CommandType CommandType => CommandType.MultiTapBiquadFilter;
public uint EstimatedProcessingTime { get; set; }
@@ -20,7 +20,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private readonly int _outputBufferIndex;
private readonly bool[] _isInitialized;
- public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan<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;
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
index f9a32b3f..58a2d9cc 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
@@ -2,12 +2,16 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
- [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x20)]
public struct BiquadFilterState
{
public float State0;
public float State1;
public float State2;
public float State3;
+ public float State4;
+ public float State5;
+ public float State6;
+ public float State7;
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs
new file mode 100644
index 00000000..807232f2
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs
@@ -0,0 +1,43 @@
+using Ryujinx.Common.Memory;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <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();
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs
index b74b67be..029c001e 100644
--- a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs
+++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using System;
using System.Runtime.InteropServices;
@@ -5,10 +6,10 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <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>
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs
new file mode 100644
index 00000000..312be8b7
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs
@@ -0,0 +1,81 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <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;
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
index 9b56f5cb..246889c4 100644
--- a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
@@ -1,6 +1,7 @@
using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.Command;
+using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Server.Effect;
using Ryujinx.Audio.Renderer.Server.MemoryPool;
@@ -173,6 +174,22 @@ namespace Ryujinx.Audio.Renderer.Server
return ResultCode.WorkBufferTooSmall;
}
+ Memory<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);
diff --git a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
index fe1dfc4b..32c7de6c 100644
--- a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
@@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Server
/// <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;
@@ -102,9 +101,17 @@ namespace Ryujinx.Audio.Renderer.Server
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);
}
@@ -369,6 +376,15 @@ namespace Ryujinx.Audio.Renderer.Server
}
/// <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>
/// <returns>The version of the <see cref="ICommandProcessingTimeEstimator"/>.</returns>
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
index f4174a91..702f0546 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
@@ -204,7 +204,7 @@ namespace Ryujinx.Audio.Renderer.Server
}
/// <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);
@@ -261,6 +261,120 @@ namespace Ryujinx.Audio.Renderer.Server
}
/// <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>
/// <param name="depopBuffer">The depop buffer.</param>
@@ -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);
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
index ae8f699f..d798230c 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
@@ -12,6 +12,7 @@ using Ryujinx.Audio.Renderer.Server.Voice;
using Ryujinx.Audio.Renderer.Utils;
using System;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Server
{
@@ -46,12 +47,13 @@ namespace Ryujinx.Audio.Renderer.Server
{
ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
- _commandBuffer.GenerateDepopPrepare(dspState,
- _rendererContext.DepopBuffer,
- mix.BufferCount,
- mix.BufferOffset,
- voiceState.NodeId,
- voiceState.WasPlaying);
+ _commandBuffer.GenerateDepopPrepare(
+ dspState,
+ _rendererContext.DepopBuffer,
+ mix.BufferCount,
+ mix.BufferOffset,
+ voiceState.NodeId,
+ voiceState.WasPlaying);
}
else if (voiceState.SplitterId != Constants.UnusedSplitterId)
{
@@ -59,15 +61,13 @@ namespace Ryujinx.Audio.Renderer.Server
while (true)
{
- Span<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 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(Span<float> mixVolumes, Span<float> previousMixVolumes, Memory<VoiceUpdateState> state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId)
+ 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)
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
index d95e9aa7..cff754b8 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
@@ -170,7 +170,7 @@ namespace Ryujinx.Audio.Renderer.Server
return 0;
}
- public uint Estimate(GroupedBiquadFilterCommand command)
+ public uint Estimate(MultiTapBiquadFilterCommand command)
{
return 0;
}
@@ -184,5 +184,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public uint Estimate(BiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
+
+ public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
index 929aaf38..ef132692 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
@@ -462,7 +462,7 @@ namespace Ryujinx.Audio.Renderer.Server
return 0;
}
- public uint Estimate(GroupedBiquadFilterCommand command)
+ public uint Estimate(MultiTapBiquadFilterCommand command)
{
return 0;
}
@@ -476,5 +476,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public uint Estimate(BiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
+
+ public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
index 8ae4bc05..31a5347b 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
@@ -632,7 +632,7 @@ namespace Ryujinx.Audio.Renderer.Server
};
}
- public virtual uint Estimate(GroupedBiquadFilterCommand command)
+ public virtual uint Estimate(MultiTapBiquadFilterCommand command)
{
return 0;
}
@@ -646,5 +646,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public virtual uint Estimate(BiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
+
+ public virtual uint Estimate(MultiTapBiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs
index 25bc67cd..fb357120 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs
@@ -10,7 +10,7 @@ namespace Ryujinx.Audio.Renderer.Server
{
public CommandProcessingTimeEstimatorVersion4(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { }
- public override uint Estimate(GroupedBiquadFilterCommand command)
+ public override uint Estimate(MultiTapBiquadFilterCommand command)
{
Debug.Assert(SampleCount == 160 || SampleCount == 240);
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
index 7135c1c4..06f135a8 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
@@ -210,5 +210,53 @@ namespace Ryujinx.Audio.Renderer.Server
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
};
}
+
+ public override uint Estimate(BiquadFilterAndMixCommand command)
+ {
+ Debug.Assert(SampleCount == 160 || SampleCount == 240);
+
+ if (command.HasVolumeRamp)
+ {
+ if (SampleCount == 160)
+ {
+ return 5204;
+ }
+
+ return 6683;
+ }
+ else
+ {
+ if (SampleCount == 160)
+ {
+ return 3427;
+ }
+
+ return 4752;
+ }
+ }
+
+ public override uint Estimate(MultiTapBiquadFilterAndMixCommand command)
+ {
+ Debug.Assert(SampleCount == 160 || SampleCount == 240);
+
+ if (command.HasVolumeRamp)
+ {
+ if (SampleCount == 160)
+ {
+ return 7939;
+ }
+
+ return 10669;
+ }
+ else
+ {
+ if (SampleCount == 160)
+ {
+ return 6256;
+ }
+
+ return 8683;
+ }
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
index 27b22363..9c4312ad 100644
--- a/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
@@ -33,8 +33,10 @@ namespace Ryujinx.Audio.Renderer.Server
uint Estimate(UpsampleCommand command);
uint Estimate(LimiterCommandVersion1 command);
uint Estimate(LimiterCommandVersion2 command);
- uint Estimate(GroupedBiquadFilterCommand command);
+ uint Estimate(MultiTapBiquadFilterCommand command);
uint Estimate(CaptureBufferCommand command);
uint Estimate(CompressorCommand command);
+ uint Estimate(BiquadFilterAndMixCommand command);
+ uint Estimate(MultiTapBiquadFilterAndMixCommand command);
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
index b90574da..5ba58ea5 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
@@ -225,11 +225,11 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
for (int i = 0; i < splitter.DestinationCount; i++)
{
- Span<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)
{
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
index 3efa783c..a7b82a6b 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
@@ -1,4 +1,5 @@
using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Utils;
using Ryujinx.Common;
@@ -16,14 +17,34 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
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<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>
- private Memory<SplitterDestination> _splitterDestinations;
+ 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 SplitterDestination data in splitterDestinations.Span)
+ int splitterDestinationId = 0;
+ foreach (ref SplitterDestinationVersion1 data in splitterDestinationsV1.Span)
+ {
+ data = new SplitterDestinationVersion1(splitterDestinationId++);
+ }
+ }
+ 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,32 +364,29 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
}
/// <summary>
- /// Get a reference to a <see cref="SplitterDestination"/> 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)
- {
- return ref SpanIOHelper.GetFromMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
- }
-
- /// <summary>
- /// Get a <see cref="Memory{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 <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.</returns>
- public Memory<SplitterDestination> GetDestinationMemory(int id)
+ /// <returns>A reference to the splitter destination data at the given <paramref name="id"/>.</returns>
+ public SplitterDestination GetDestination(int id)
{
- return SpanIOHelper.GetMemory(_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="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);
@@ -284,12 +394,22 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
}
/// <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>
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
index 1faf7921..36dfa5e4 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
@@ -1,115 +1,198 @@
using Ryujinx.Audio.Renderer.Parameter;
-using Ryujinx.Common.Utilities;
using System;
using System.Diagnostics;
-using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Server.Splitter
{
/// <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.
- /// </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.
+ /// The splitter unique id.
/// </summary>
- [MarshalAs(UnmanagedType.I1)]
- public bool IsUsed;
+ public int Id
+ {
+ get
+ {
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ if (Unsafe.IsNullRef(ref _v1))
+ {
+ return 0;
+ }
+ else
+ {
+ return _v1.Id;
+ }
+ }
+ else
+ {
+ return _v2.Id;
+ }
+ }
+ }
/// <summary>
- /// Set to true if the internal state need to be updated.
+ /// The mix to output the result of the splitter.
/// </summary>
- [MarshalAs(UnmanagedType.I1)]
- public bool NeedToUpdateInternalState;
-
- [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
- private struct MixArray { }
+ public int DestinationId
+ {
+ get
+ {
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ if (Unsafe.IsNullRef(ref _v1))
+ {
+ return 0;
+ }
+ else
+ {
+ return _v1.DestinationId;
+ }
+ }
+ else
+ {
+ return _v2.DestinationId;
+ }
+ }
+ }
/// <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);
+ 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 => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
+ 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="Span{SplitterDestination}"/> of the next element or <see cref="Span{SplitterDestination}.Empty"/> if not present.
+ /// Get the <see cref="SplitterDestination"/> of the next element or null if not present.
/// </summary>
- public readonly Span<SplitterDestination> Next
+ public readonly SplitterDestination Next
{
get
{
unsafe
{
- return _next != null ? new Span<SplitterDestination>(_next, 1) : Span<SplitterDestination>.Empty;
+ 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>
- /// Create a new <see cref="SplitterDestination"/>.
+ /// Creates a new splitter destination wrapper for the version 1 splitter destination data.
/// </summary>
- /// <param name="id">The unique id of this <see cref="SplitterDestination"/>.</param>
- public SplitterDestination(int id) : this()
+ /// <param name="v1">Version 1 splitter destination data</param>
+ public SplitterDestination(ref SplitterDestinationVersion1 v1)
{
- Id = id;
- DestinationId = Constants.UnusedMixId;
-
- ClearVolumes();
+ _v1 = ref v1;
+ _v2 = ref Unsafe.NullRef<SplitterDestinationVersion2>();
}
/// <summary>
- /// Update the <see cref="SplitterDestination"/> from user parameter.
+ /// Creates a new splitter destination wrapper for the version 2 splitter destination data.
/// </summary>
- /// <param name="parameter">The user parameter.</param>
- public void Update(SplitterDestinationInParameter parameter)
+ /// <param name="v2">Version 2 splitter destination data</param>
+ public SplitterDestination(ref SplitterDestinationVersion2 v2)
{
- 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);
+ _v1 = ref Unsafe.NullRef<SplitterDestinationVersion1>();
+ _v2 = ref v2;
+ }
- NeedToUpdateInternalState = false;
- }
+ /// <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);
+ }
- IsUsed = parameter.IsUsed;
+ /// <summary>
+ /// Update the splitter destination data from user parameter.
+ /// </summary>
+ /// <param name="parameter">The user parameter.</param>
+ public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter
+ {
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ _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))
+ {
+ _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))
{
- _next = null;
+ _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;
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs
new file mode 100644
index 00000000..5d2b8fb0
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs
@@ -0,0 +1,206 @@
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Splitter
+{
+ /// <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;
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs
new file mode 100644
index 00000000..f9487909
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs
@@ -0,0 +1,250 @@
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Common.Memory;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Splitter
+{
+ /// <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;
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
index 944f092d..3e7dce55 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
@@ -15,6 +15,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{
public const int Alignment = 0x10;
+ private delegate void SplitterDestinationAction(SplitterDestination destination, int index);
+
/// <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 SplitterDestinationVersion1* _destinationDataV1;
+
+ /// <summary>
+ /// Linked list of <see cref="SplitterDestinationVersion2"/>.
/// </summary>
- private unsafe SplitterDestination* _destinationsData;
+ private unsafe SplitterDestinationVersion2* _destinationDataV2;
/// <summary>
- /// Span to the first element of the linked list of <see cref="SplitterDestination"/>.
+ /// First element of the linked list of splitter destinations data.
/// </summary>
- public readonly Span<SplitterDestination> Destinations
+ 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())
+ {
+ _destinationDataV1 = newValuePtr;
+ }
+
+ fixed (SplitterDestinationVersion2* newValuePtr = &newValue.GetV2RefOrNull())
{
- _destinationsData = newValuePtr;
+ _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;
diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs
index 55758188..3e48a5b4 100644
--- a/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs
+++ b/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs
@@ -52,7 +52,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
- Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
+ Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
+ Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -78,7 +80,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
- Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
+ Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
+ Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -104,7 +108,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
- Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
+ Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
+ Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -130,7 +136,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
- Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
+ Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
+ Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -156,7 +164,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
- Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
+ Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
+ Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -182,7 +192,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
- Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
+ Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
+ Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -208,7 +220,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
- Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
+ Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
+ Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -234,7 +248,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
- Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
+ Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
+ Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -260,7 +276,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
- Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
+ Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
+ Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -286,11 +304,69 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
- Assert.IsTrue(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
+ Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
+ Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
}
+
+ [Test]
+ public void TestRevision11()
+ {
+ BehaviourContext behaviourContext = new();
+
+ behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision11);
+
+ Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed());
+ Assert.IsTrue(behaviourContext.IsSplitterSupported());
+ Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported());
+ Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported());
+ Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported());
+ Assert.IsTrue(behaviourContext.IsSplitterBugFixed());
+ Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported());
+ Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported());
+ Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
+ Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
+ Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
+ Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
+ Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
+ Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+
+ Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
+ Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
+ Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
+ }
+
+ [Test]
+ public void TestRevision12()
+ {
+ BehaviourContext behaviourContext = new();
+
+ behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision12);
+
+ Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed());
+ Assert.IsTrue(behaviourContext.IsSplitterSupported());
+ Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported());
+ Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported());
+ Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported());
+ Assert.IsTrue(behaviourContext.IsSplitterBugFixed());
+ Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported());
+ Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported());
+ Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
+ Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
+ Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
+ Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
+ Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
+ Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
+ Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+
+ Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
+ Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
+ Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
+ }
}
}
diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs
index ad974aab..80b80133 100644
--- a/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs
+++ b/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs
@@ -9,7 +9,8 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
[Test]
public void EnsureTypeSize()
{
- Assert.AreEqual(0xE0, Unsafe.SizeOf<SplitterDestination>());
+ Assert.AreEqual(0xE0, Unsafe.SizeOf<SplitterDestinationVersion1>());
+ Assert.AreEqual(0x110, Unsafe.SizeOf<SplitterDestinationVersion2>());
}
}
}