amadeus: Improve and fix delay effect processing (#3205)

* amadeus: Improve and fix delay effect processing

This rework the delay effect processing by representing calculation with the appropriate matrix and by unrolling some loop in the code.
This allows better optimization by the JIT while making it more readeable.

Also fix a bug in the Surround code path found while looking back at my notes.

* Remove useless GetHashCode

* Address gdkchan's comments
This commit is contained in:
Mary 2022-04-08 10:52:18 +02:00 committed by GitHub
parent 55ee261363
commit d04ba51bb0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 363 additions and 66 deletions

View file

@ -18,8 +18,10 @@
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using Ryujinx.Audio.Renderer.Server.Effect;
using Ryujinx.Audio.Renderer.Utils.Math;
using System;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp.Command
@ -70,7 +72,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount)
{
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
@ -83,133 +85,148 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float input = inputBuffer[i] * 64;
float delayLineValue = state.DelayLines[0].Read();
float lowPassResult = (input * inGain + delayLineValue * feedbackGain) * state.LowPassBaseGain + state.LowPassZ[0] * state.LowPassFeedbackGain;
float temp = input * inGain + delayLineValue * feedbackGain;
state.LowPassZ[0] = lowPassResult;
state.DelayLines[0].Update(lowPassResult);
state.UpdateLowPassFilter(ref temp, 1);
outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private unsafe void ProcessDelayStereo(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
const ushort channelCount = 2;
Span<float> channelInput = stackalloc float[channelCount];
Span<float> delayLineValues = stackalloc float[channelCount];
Span<float> temp = stackalloc float[channelCount];
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix2x2 delayFeedback = new Matrix2x2(delayFeedbackBaseGain , delayFeedbackCrossGain,
delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++)
{
for (int j = 0; j < channelCount; j++)
Vector2 channelInput = new Vector2
{
channelInput[j] = *((float*)inputBuffers[j] + i) * 64;
delayLineValues[j] = state.DelayLines[j].Read();
}
X = *((float*)inputBuffers[0] + i) * 64,
Y = *((float*)inputBuffers[1] + i) * 64,
};
temp[0] = channelInput[0] * inGain + delayLineValues[1] * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
temp[1] = channelInput[1] * inGain + delayLineValues[0] * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
for (int j = 0; j < channelCount; j++)
Vector2 delayLineValues = new Vector2()
{
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
X = state.DelayLines[0].Read(),
Y = state.DelayLines[1].Read(),
};
state.LowPassZ[j] = lowPassResult;
state.DelayLines[j].Update(lowPassResult);
Vector2 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
*((float*)outputBuffers[j] + i) = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
}
state.UpdateLowPassFilter(ref Unsafe.As<Vector2, float>(ref temp), channelCount);
*((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
*((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private unsafe void ProcessDelayQuadraphonic(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
const ushort channelCount = 4;
Span<float> channelInput = stackalloc float[channelCount];
Span<float> delayLineValues = stackalloc float[channelCount];
Span<float> temp = stackalloc float[channelCount];
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix4x4 delayFeedback = new Matrix4x4(delayFeedbackBaseGain , delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f,
delayFeedbackCrossGain, delayFeedbackBaseGain , 0.0f , delayFeedbackCrossGain,
delayFeedbackCrossGain, 0.0f , delayFeedbackBaseGain , delayFeedbackCrossGain,
0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++)
{
for (int j = 0; j < channelCount; j++)
Vector4 channelInput = new Vector4
{
channelInput[j] = *((float*)inputBuffers[j] + i) * 64;
delayLineValues[j] = state.DelayLines[j].Read();
}
X = *((float*)inputBuffers[0] + i) * 64,
Y = *((float*)inputBuffers[1] + i) * 64,
Z = *((float*)inputBuffers[2] + i) * 64,
W = *((float*)inputBuffers[3] + i) * 64
};
temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
temp[1] = channelInput[1] * inGain + (delayLineValues[0] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain;
temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain;
for (int j = 0; j < channelCount; j++)
Vector4 delayLineValues = new Vector4()
{
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
X = state.DelayLines[0].Read(),
Y = state.DelayLines[1].Read(),
Z = state.DelayLines[2].Read(),
W = state.DelayLines[3].Read()
};
state.LowPassZ[j] = lowPassResult;
state.DelayLines[j].Update(lowPassResult);
Vector4 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
state.UpdateLowPassFilter(ref Unsafe.As<Vector4, float>(ref temp), channelCount);
*((float*)outputBuffers[j] + i) = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
}
*((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
*((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
*((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64;
*((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private unsafe void ProcessDelaySurround(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
const ushort channelCount = 6;
Span<float> channelInput = stackalloc float[channelCount];
Span<float> delayLineValues = stackalloc float[channelCount];
Span<float> temp = stackalloc float[channelCount];
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain , 0.0f , 0.0f , 0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain,
0.0f , delayFeedbackBaseGain , 0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f ,
delayFeedbackCrossGain, 0.0f , delayFeedbackBaseGain , delayFeedbackCrossGain, 0.0f , 0.0f ,
0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain , 0.0f , 0.0f ,
delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f , 0.0f , delayFeedbackBaseGain , 0.0f ,
0.0f , 0.0f , 0.0f , 0.0f , 0.0f , feedbackGain);
for (int i = 0; i < sampleCount; i++)
{
for (int j = 0; j < channelCount; j++)
Vector6 channelInput = new Vector6
{
channelInput[j] = *((float*)inputBuffers[j] + i) * 64;
delayLineValues[j] = state.DelayLines[j].Read();
}
X = *((float*)inputBuffers[0] + i) * 64,
Y = *((float*)inputBuffers[1] + i) * 64,
Z = *((float*)inputBuffers[2] + i) * 64,
W = *((float*)inputBuffers[3] + i) * 64,
V = *((float*)inputBuffers[4] + i) * 64,
U = *((float*)inputBuffers[5] + i) * 64
};
temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[4]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
temp[1] = channelInput[1] * inGain + (delayLineValues[4] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain;
temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain;
temp[4] = channelInput[4] * inGain + (delayLineValues[0] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[4] * delayFeedbackBaseGain;
temp[5] = channelInput[5] * inGain + delayLineValues[5] * delayFeedbackBaseGain;
for (int j = 0; j < channelCount; j++)
Vector6 delayLineValues = new Vector6
{
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
X = state.DelayLines[0].Read(),
Y = state.DelayLines[1].Read(),
Z = state.DelayLines[2].Read(),
W = state.DelayLines[3].Read(),
V = state.DelayLines[4].Read(),
U = state.DelayLines[5].Read()
};
state.LowPassZ[j] = lowPassResult;
state.DelayLines[j].Update(lowPassResult);
Vector6 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
*((float*)outputBuffers[j] + i) = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
}
state.UpdateLowPassFilter(ref Unsafe.As<Vector6, float>(ref temp), channelCount);
*((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
*((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
*((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64;
*((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64;
*((float*)outputBuffers[4] + i) = (channelInput.V * dryGain + delayLineValues.V * outGain) / 64;
*((float*)outputBuffers[5] + i) = (channelInput.U * dryGain + delayLineValues.U * outGain) / 64;
}
}

View file

@ -17,6 +17,7 @@
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
@ -43,7 +44,6 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
{
DelayLines[i] = new DelayLine(sampleRate, parameter.DelayTimeMax);
DelayLines[i].SetDelay(parameter.DelayTime);
LowPassZ[0] = 0;
}
UpdateParameter(ref parameter);
@ -69,5 +69,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
LowPassFeedbackGain = 0.95f * FixedPointHelper.ToFloat(parameter.LowPassAmount, FixedPointPrecision);
LowPassBaseGain = 1.0f - LowPassFeedbackGain;
}
public void UpdateLowPassFilter(ref float tempRawRef, uint channelCount)
{
for (int i = 0; i < channelCount; i++)
{
float lowPassResult = LowPassFeedbackGain * LowPassZ[i] + Unsafe.Add(ref tempRawRef, i) * LowPassBaseGain;
LowPassZ[i] = lowPassResult;
DelayLines[i].Update(lowPassResult);
}
}
}
}

View file

@ -0,0 +1,71 @@
namespace Ryujinx.Audio.Renderer.Utils.Math
{
record struct Matrix2x2
{
public float M11;
public float M12;
public float M21;
public float M22;
public Matrix2x2(float m11, float m12,
float m21, float m22)
{
M11 = m11;
M12 = m12;
M21 = m21;
M22 = m22;
}
public static Matrix2x2 operator +(Matrix2x2 value1, Matrix2x2 value2)
{
Matrix2x2 m;
m.M11 = value1.M11 + value2.M11;
m.M12 = value1.M12 + value2.M12;
m.M21 = value1.M21 + value2.M21;
m.M22 = value1.M22 + value2.M22;
return m;
}
public static Matrix2x2 operator -(Matrix2x2 value1, float value2)
{
Matrix2x2 m;
m.M11 = value1.M11 - value2;
m.M12 = value1.M12 - value2;
m.M21 = value1.M21 - value2;
m.M22 = value1.M22 - value2;
return m;
}
public static Matrix2x2 operator *(Matrix2x2 value1, float value2)
{
Matrix2x2 m;
m.M11 = value1.M11 * value2;
m.M12 = value1.M12 * value2;
m.M21 = value1.M21 * value2;
m.M22 = value1.M22 * value2;
return m;
}
public static Matrix2x2 operator *(Matrix2x2 value1, Matrix2x2 value2)
{
Matrix2x2 m;
// First row
m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
// Second row
m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
return m;
}
}
}

View file

@ -0,0 +1,97 @@
namespace Ryujinx.Audio.Renderer.Utils.Math
{
record struct Matrix6x6
{
public float M11;
public float M12;
public float M13;
public float M14;
public float M15;
public float M16;
public float M21;
public float M22;
public float M23;
public float M24;
public float M25;
public float M26;
public float M31;
public float M32;
public float M33;
public float M34;
public float M35;
public float M36;
public float M41;
public float M42;
public float M43;
public float M44;
public float M45;
public float M46;
public float M51;
public float M52;
public float M53;
public float M54;
public float M55;
public float M56;
public float M61;
public float M62;
public float M63;
public float M64;
public float M65;
public float M66;
public Matrix6x6(float m11, float m12, float m13, float m14, float m15, float m16,
float m21, float m22, float m23, float m24, float m25, float m26,
float m31, float m32, float m33, float m34, float m35, float m36,
float m41, float m42, float m43, float m44, float m45, float m46,
float m51, float m52, float m53, float m54, float m55, float m56,
float m61, float m62, float m63, float m64, float m65, float m66)
{
M11 = m11;
M12 = m12;
M13 = m13;
M14 = m14;
M15 = m15;
M16 = m16;
M21 = m21;
M22 = m22;
M23 = m23;
M24 = m24;
M25 = m25;
M26 = m26;
M31 = m31;
M32 = m32;
M33 = m33;
M34 = m34;
M35 = m35;
M36 = m36;
M41 = m41;
M42 = m42;
M43 = m43;
M44 = m44;
M45 = m45;
M46 = m46;
M51 = m51;
M52 = m52;
M53 = m53;
M54 = m54;
M55 = m55;
M56 = m56;
M61 = m61;
M62 = m62;
M63 = m63;
M64 = m64;
M65 = m65;
M66 = m66;
}
}
}

View file

@ -0,0 +1,45 @@
using Ryujinx.Audio.Renderer.Utils.Math;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp
{
static class MatrixHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector6 Transform(ref Vector6 value1, ref Matrix6x6 value2)
{
return new Vector6
{
X = value2.M11 * value1.X + value2.M12 * value1.Y + value2.M13 * value1.Z + value2.M14 * value1.W + value2.M15 * value1.V + value2.M16 * value1.U,
Y = value2.M21 * value1.X + value2.M22 * value1.Y + value2.M23 * value1.Z + value2.M24 * value1.W + value2.M25 * value1.V + value2.M26 * value1.U,
Z = value2.M31 * value1.X + value2.M32 * value1.Y + value2.M33 * value1.Z + value2.M34 * value1.W + value2.M35 * value1.V + value2.M36 * value1.U,
W = value2.M41 * value1.X + value2.M42 * value1.Y + value2.M43 * value1.Z + value2.M44 * value1.W + value2.M45 * value1.V + value2.M46 * value1.U,
V = value2.M51 * value1.X + value2.M52 * value1.Y + value2.M53 * value1.Z + value2.M54 * value1.W + value2.M55 * value1.V + value2.M56 * value1.U,
U = value2.M61 * value1.X + value2.M62 * value1.Y + value2.M63 * value1.Z + value2.M64 * value1.W + value2.M65 * value1.V + value2.M66 * value1.U,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Transform(ref Vector4 value1, ref Matrix4x4 value2)
{
return new Vector4
{
X = value2.M11 * value1.X + value2.M12 * value1.Y + value2.M13 * value1.Z + value2.M14 * value1.W,
Y = value2.M21 * value1.X + value2.M22 * value1.Y + value2.M23 * value1.Z + value2.M24 * value1.W,
Z = value2.M31 * value1.X + value2.M32 * value1.Y + value2.M33 * value1.Z + value2.M34 * value1.W,
W = value2.M41 * value1.X + value2.M42 * value1.Y + value2.M43 * value1.Z + value2.M44 * value1.W
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 Transform(ref Vector2 value1, ref Matrix2x2 value2)
{
return new Vector2
{
X = value2.M11 * value1.X + value2.M12 * value1.Y,
Y = value2.M21 * value1.X + value2.M22 * value1.Y,
};
}
}
}

View file

@ -0,0 +1,56 @@
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Utils.Math
{
record struct Vector6
{
public float X;
public float Y;
public float Z;
public float W;
public float V;
public float U;
public Vector6(float value) : this(value, value, value, value, value, value)
{
}
public Vector6(float x, float y, float z, float w, float v, float u)
{
X = x;
Y = y;
Z = z;
W = w;
V = v;
U = u;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector6 operator +(Vector6 left, Vector6 right)
{
return new Vector6(left.X + right.X,
left.Y + right.Y,
left.Z + right.Z,
left.W + right.W,
left.V + right.V,
left.U + right.U);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector6 operator *(Vector6 left, Vector6 right)
{
return new Vector6(left.X * right.X,
left.Y * right.Y,
left.Z * right.Z,
left.W * right.W,
left.V * right.V,
left.U * right.U);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector6 operator *(Vector6 left, float right)
{
return left * new Vector6(right);
}
}
}