Update audio renderer to REV12: Add support for splitter biquad filter (#6813)

* Update audio renderer to REV12: Add support for splitter biquad filter

* Formatting

* Official names

* Update BiquadFilterState size + other fixes

* Update tests

* Update comment for version 2

* Size test for SplitterDestinationVersion2

* Should use Volume1 if no ramp
This commit is contained in:
gdkchan
2024-05-17 16:46:43 -03:00
committed by GitHub
parent 9ec8b2c01a
commit 4d84df9487
29 changed files with 2342 additions and 392 deletions

View File

@@ -16,10 +16,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
/// <param name="parameter">The biquad filter parameter</param>
/// <param name="state">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to read the samples from</param>
/// <param name="sampleCount">The count of samples to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
public static void ProcessBiquadFilter(
ref BiquadFilterParameter parameter,
ref BiquadFilterState state,
Span<float> outputBuffer,
ReadOnlySpan<float> inputBuffer,
uint sampleCount)
{
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
@@ -40,6 +45,96 @@ namespace Ryujinx.Audio.Renderer.Dsp
}
}
/// <summary>
/// Apply a single biquad filter and mix the result into the output buffer.
/// </summary>
/// <remarks>This is implemented with a direct form 1.</remarks>
/// <param name="parameter">The biquad filter parameter</param>
/// <param name="state">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to read the samples from</param>
/// <param name="sampleCount">The count of samples to process</param>
/// <param name="volume">Mix volume</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ProcessBiquadFilterAndMix(
ref BiquadFilterParameter parameter,
ref BiquadFilterState state,
Span<float> outputBuffer,
ReadOnlySpan<float> inputBuffer,
uint sampleCount,
float volume)
{
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
for (int i = 0; i < sampleCount; i++)
{
float input = inputBuffer[i];
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
state.State1 = state.State0;
state.State0 = input;
state.State3 = state.State2;
state.State2 = output;
outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume);
}
}
/// <summary>
/// Apply a single biquad filter and mix the result into the output buffer with volume ramp.
/// </summary>
/// <remarks>This is implemented with a direct form 1.</remarks>
/// <param name="parameter">The biquad filter parameter</param>
/// <param name="state">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to read the samples from</param>
/// <param name="sampleCount">The count of samples to process</param>
/// <param name="volume">Initial mix volume</param>
/// <param name="ramp">Volume increment step</param>
/// <returns>Last filtered sample value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ProcessBiquadFilterAndMixRamp(
ref BiquadFilterParameter parameter,
ref BiquadFilterState state,
Span<float> outputBuffer,
ReadOnlySpan<float> inputBuffer,
uint sampleCount,
float volume,
float ramp)
{
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
float mixState = 0f;
for (int i = 0; i < sampleCount; i++)
{
float input = inputBuffer[i];
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
state.State1 = state.State0;
state.State0 = input;
state.State3 = state.State2;
state.State2 = output;
mixState = FloatingPointHelper.MultiplyRoundUp(output, volume);
outputBuffer[i] += mixState;
volume += ramp;
}
return mixState;
}
/// <summary>
/// Apply multiple biquad filter.
/// </summary>
@@ -47,10 +142,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
/// <param name="parameters">The biquad filter parameter</param>
/// <param name="states">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to read the samples from</param>
/// <param name="sampleCount">The count of samples to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ProcessBiquadFilter(ReadOnlySpan<BiquadFilterParameter> parameters, Span<BiquadFilterState> states, Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
public static void ProcessBiquadFilter(
ReadOnlySpan<BiquadFilterParameter> parameters,
Span<BiquadFilterState> states,
Span<float> outputBuffer,
ReadOnlySpan<float> inputBuffer,
uint sampleCount)
{
for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++)
{
@@ -67,7 +167,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
for (int i = 0; i < sampleCount; i++)
{
float input = inputBuffer[i];
float input = stageIndex != 0 ? outputBuffer[i] : inputBuffer[i];
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
state.State1 = state.State0;
@@ -79,5 +179,129 @@ namespace Ryujinx.Audio.Renderer.Dsp
}
}
}
/// <summary>
/// Apply double biquad filter and mix the result into the output buffer.
/// </summary>
/// <remarks>This is implemented with a direct form 1.</remarks>
/// <param name="parameters">The biquad filter parameter</param>
/// <param name="states">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to read the samples from</param>
/// <param name="sampleCount">The count of samples to process</param>
/// <param name="volume">Mix volume</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ProcessDoubleBiquadFilterAndMix(
ref BiquadFilterParameter parameter0,
ref BiquadFilterParameter parameter1,
ref BiquadFilterState state0,
ref BiquadFilterState state1,
Span<float> outputBuffer,
ReadOnlySpan<float> inputBuffer,
uint sampleCount,
float volume)
{
float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter);
float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter);
float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter);
float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter);
float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter);
float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter);
float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter);
float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter);
float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter);
float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter);
for (int i = 0; i < sampleCount; i++)
{
float input = inputBuffer[i];
float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20;
state0.State1 = state0.State0;
state0.State0 = input;
state0.State3 = state0.State2;
state0.State2 = output;
input = output;
output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21;
state1.State1 = state1.State0;
state1.State0 = input;
state1.State3 = state1.State2;
state1.State2 = output;
outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume);
}
}
/// <summary>
/// Apply double biquad filter and mix the result into the output buffer with volume ramp.
/// </summary>
/// <remarks>This is implemented with a direct form 1.</remarks>
/// <param name="parameters">The biquad filter parameter</param>
/// <param name="states">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to read the samples from</param>
/// <param name="sampleCount">The count of samples to process</param>
/// <param name="volume">Initial mix volume</param>
/// <param name="ramp">Volume increment step</param>
/// <returns>Last filtered sample value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ProcessDoubleBiquadFilterAndMixRamp(
ref BiquadFilterParameter parameter0,
ref BiquadFilterParameter parameter1,
ref BiquadFilterState state0,
ref BiquadFilterState state1,
Span<float> outputBuffer,
ReadOnlySpan<float> inputBuffer,
uint sampleCount,
float volume,
float ramp)
{
float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter);
float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter);
float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter);
float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter);
float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter);
float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter);
float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter);
float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter);
float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter);
float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter);
float mixState = 0f;
for (int i = 0; i < sampleCount; i++)
{
float input = inputBuffer[i];
float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20;
state0.State1 = state0.State0;
state0.State0 = input;
state0.State3 = state0.State2;
state0.State2 = output;
input = output;
output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21;
state1.State1 = state1.State0;
state1.State0 = input;
state1.State3 = state1.State2;
state1.State2 = output;
mixState = FloatingPointHelper.MultiplyRoundUp(output, volume);
outputBuffer[i] += mixState;
volume += ramp;
}
return mixState;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -30,8 +30,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
CopyMixBuffer,
LimiterVersion1,
LimiterVersion2,
GroupedBiquadFilter,
MultiTapBiquadFilter,
CaptureBuffer,
Compressor,
BiquadFilterAndMix,
MultiTapBiquadFilterAndMix,
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}
}

View File

@@ -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;

View File

@@ -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;
}
}