diff options
5 files changed, 201 insertions, 52 deletions
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs index 1617a6421..0870d59ce 100644 --- a/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs | |||
@@ -40,6 +40,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command | |||
40 | info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]); | 40 | info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]); |
41 | } | 41 | } |
42 | 42 | ||
43 | if (info.BufferStates?.Length != (int)inputCount) | ||
44 | { | ||
45 | // Keep state if possible. | ||
46 | info.BufferStates = new UpsamplerBufferState[(int)inputCount]; | ||
47 | } | ||
48 | |||
43 | UpsamplerInfo = info; | 49 | UpsamplerInfo = info; |
44 | } | 50 | } |
45 | 51 | ||
@@ -50,8 +56,6 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command | |||
50 | 56 | ||
51 | public void Process(CommandList context) | 57 | public void Process(CommandList context) |
52 | { | 58 | { |
53 | float ratio = (float)InputSampleRate / Constants.TargetSampleRate; | ||
54 | |||
55 | uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount); | 59 | uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount); |
56 | 60 | ||
57 | for (int i = 0; i < bufferCount; i++) | 61 | for (int i = 0; i < bufferCount; i++) |
@@ -59,9 +63,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command | |||
59 | Span<float> inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]); | 63 | Span<float> inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]); |
60 | Span<float> outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount); | 64 | Span<float> outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount); |
61 | 65 | ||
62 | float fraction = 0.0f; | 66 | UpsamplerHelper.Upsample(outputBuffer, inputBuffer, (int)UpsamplerInfo.SampleCount, (int)InputSampleCount, ref UpsamplerInfo.BufferStates[i]); |
63 | |||
64 | ResamplerHelper.ResampleForUpsampler(outputBuffer, inputBuffer, ratio, ref fraction, (int)(InputSampleCount / ratio)); | ||
65 | } | 67 | } |
66 | } | 68 | } |
67 | } | 69 | } |
diff --git a/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs b/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs index 4de2e078a..b46a33fe0 100644 --- a/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs +++ b/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs | |||
@@ -579,52 +579,5 @@ namespace Ryujinx.Audio.Renderer.Dsp | |||
579 | fraction -= (int)fraction; | 579 | fraction -= (int)fraction; |
580 | } | 580 | } |
581 | } | 581 | } |
582 | |||
583 | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
584 | public static void ResampleForUpsampler(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float ratio, ref float fraction, int sampleCount) | ||
585 | { | ||
586 | // Currently a simple cubic interpolation, assuming duplicated values at edges. | ||
587 | // TODO: Discover and use algorithm that the switch uses. | ||
588 | |||
589 | int inputBufferIndex = 0; | ||
590 | int maxIndex = inputBuffer.Length - 1; | ||
591 | int cubicEnd = inputBuffer.Length - 3; | ||
592 | |||
593 | for (int i = 0; i < sampleCount; i++) | ||
594 | { | ||
595 | float s0, s1, s2, s3; | ||
596 | |||
597 | s1 = inputBuffer[inputBufferIndex]; | ||
598 | |||
599 | if (inputBufferIndex == 0 || inputBufferIndex > cubicEnd) | ||
600 | { | ||
601 | // Clamp interplation values at the ends of the input buffer. | ||
602 | s0 = inputBuffer[Math.Max(0, inputBufferIndex - 1)]; | ||
603 | s2 = inputBuffer[Math.Min(maxIndex, inputBufferIndex + 1)]; | ||
604 | s3 = inputBuffer[Math.Min(maxIndex, inputBufferIndex + 2)]; | ||
605 | } | ||
606 | else | ||
607 | { | ||
608 | s0 = inputBuffer[inputBufferIndex - 1]; | ||
609 | s2 = inputBuffer[inputBufferIndex + 1]; | ||
610 | s3 = inputBuffer[inputBufferIndex + 2]; | ||
611 | } | ||
612 | |||
613 | float a = s3 - s2 - s0 + s1; | ||
614 | float b = s0 - s1 - a; | ||
615 | float c = s2 - s0; | ||
616 | float d = s1; | ||
617 | |||
618 | float f2 = fraction * fraction; | ||
619 | float f3 = f2 * fraction; | ||
620 | |||
621 | outputBuffer[i] = a * f3 + b * f2 + c * fraction + d; | ||
622 | |||
623 | fraction += ratio; | ||
624 | inputBufferIndex += (int)MathF.Truncate(fraction); | ||
625 | |||
626 | fraction -= (int)fraction; | ||
627 | } | ||
628 | } | ||
629 | } | 582 | } |
630 | } \ No newline at end of file | 583 | } \ No newline at end of file |
diff --git a/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs b/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs new file mode 100644 index 000000000..847acec2e --- /dev/null +++ b/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs | |||
@@ -0,0 +1,175 @@ | |||
1 | using Ryujinx.Audio.Renderer.Server.Upsampler; | ||
2 | using Ryujinx.Common.Memory; | ||
3 | using System; | ||
4 | using System.Diagnostics; | ||
5 | using System.Runtime.CompilerServices; | ||
6 | |||
7 | namespace Ryujinx.Audio.Renderer.Dsp | ||
8 | { | ||
9 | public class UpsamplerHelper | ||
10 | { | ||
11 | private const int HistoryLength = UpsamplerBufferState.HistoryLength; | ||
12 | private const int FilterBankLength = 20; | ||
13 | // Bank0 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; | ||
14 | private const int Bank0CenterIndex = 9; | ||
15 | private static readonly Array20<float> Bank1 = PrecomputeFilterBank(1.0f / 6.0f); | ||
16 | private static readonly Array20<float> Bank2 = PrecomputeFilterBank(2.0f / 6.0f); | ||
17 | private static readonly Array20<float> Bank3 = PrecomputeFilterBank(3.0f / 6.0f); | ||
18 | private static readonly Array20<float> Bank4 = PrecomputeFilterBank(4.0f / 6.0f); | ||
19 | private static readonly Array20<float> Bank5 = PrecomputeFilterBank(5.0f / 6.0f); | ||
20 | |||
21 | private static Array20<float> PrecomputeFilterBank(float offset) | ||
22 | { | ||
23 | float Sinc(float x) | ||
24 | { | ||
25 | if (x == 0) | ||
26 | { | ||
27 | return 1.0f; | ||
28 | } | ||
29 | return (MathF.Sin(MathF.PI * x) / (MathF.PI * x)); | ||
30 | } | ||
31 | |||
32 | float BlackmanWindow(float x) | ||
33 | { | ||
34 | const float a = 0.18f; | ||
35 | const float a0 = 0.5f - 0.5f * a; | ||
36 | const float a1 = -0.5f; | ||
37 | const float a2 = 0.5f * a; | ||
38 | return a0 + a1 * MathF.Cos(2 * MathF.PI * x) + a2 * MathF.Cos(4 * MathF.PI * x); | ||
39 | } | ||
40 | |||
41 | Array20<float> result = new Array20<float>(); | ||
42 | |||
43 | for (int i = 0; i < FilterBankLength; i++) | ||
44 | { | ||
45 | float x = (Bank0CenterIndex - i) + offset; | ||
46 | result[i] = Sinc(x) * BlackmanWindow(x / FilterBankLength + 0.5f); | ||
47 | } | ||
48 | |||
49 | return result; | ||
50 | } | ||
51 | |||
52 | // Polyphase upsampling algorithm | ||
53 | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
54 | public static void Upsample(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, int outputSampleCount, int inputSampleCount, ref UpsamplerBufferState state) | ||
55 | { | ||
56 | if (!state.Initialized) | ||
57 | { | ||
58 | state.Scale = inputSampleCount switch | ||
59 | { | ||
60 | 40 => 6.0f, | ||
61 | 80 => 3.0f, | ||
62 | 160 => 1.5f, | ||
63 | _ => throw new ArgumentOutOfRangeException() | ||
64 | }; | ||
65 | state.Initialized = true; | ||
66 | } | ||
67 | |||
68 | if (outputSampleCount == 0) | ||
69 | { | ||
70 | return; | ||
71 | } | ||
72 | |||
73 | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
74 | float DoFilterBank(ref UpsamplerBufferState state, in Array20<float> bank) | ||
75 | { | ||
76 | float result = 0.0f; | ||
77 | |||
78 | Debug.Assert(state.History.Length == HistoryLength); | ||
79 | Debug.Assert(bank.Length == FilterBankLength); | ||
80 | for (int j = 0; j < FilterBankLength; j++) | ||
81 | { | ||
82 | result += bank[j] * state.History[j]; | ||
83 | } | ||
84 | |||
85 | return result; | ||
86 | } | ||
87 | |||
88 | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
89 | void NextInput(ref UpsamplerBufferState state, float input) | ||
90 | { | ||
91 | state.History.AsSpan().Slice(1).CopyTo(state.History.AsSpan()); | ||
92 | state.History[HistoryLength - 1] = input; | ||
93 | } | ||
94 | |||
95 | int inputBufferIndex = 0; | ||
96 | |||
97 | switch (state.Scale) | ||
98 | { | ||
99 | case 6.0f: | ||
100 | for (int i = 0; i < outputSampleCount; i++) | ||
101 | { | ||
102 | switch (state.Phase) | ||
103 | { | ||
104 | case 0: | ||
105 | NextInput(ref state, inputBuffer[inputBufferIndex++]); | ||
106 | outputBuffer[i] = state.History[Bank0CenterIndex]; | ||
107 | break; | ||
108 | case 1: | ||
109 | outputBuffer[i] = DoFilterBank(ref state, Bank1); | ||
110 | break; | ||
111 | case 2: | ||
112 | outputBuffer[i] = DoFilterBank(ref state, Bank2); | ||
113 | break; | ||
114 | case 3: | ||
115 | outputBuffer[i] = DoFilterBank(ref state, Bank3); | ||
116 | break; | ||
117 | case 4: | ||
118 | outputBuffer[i] = DoFilterBank(ref state, Bank4); | ||
119 | break; | ||
120 | case 5: | ||
121 | outputBuffer[i] = DoFilterBank(ref state, Bank5); | ||
122 | break; | ||
123 | } | ||
124 | |||
125 | state.Phase = (state.Phase + 1) % 6; | ||
126 | } | ||
127 | break; | ||
128 | case 3.0f: | ||
129 | for (int i = 0; i < outputSampleCount; i++) | ||
130 | { | ||
131 | switch (state.Phase) | ||
132 | { | ||
133 | case 0: | ||
134 | NextInput(ref state, inputBuffer[inputBufferIndex++]); | ||
135 | outputBuffer[i] = state.History[Bank0CenterIndex]; | ||
136 | break; | ||
137 | case 1: | ||
138 | outputBuffer[i] = DoFilterBank(ref state, Bank2); | ||
139 | break; | ||
140 | case 2: | ||
141 | outputBuffer[i] = DoFilterBank(ref state, Bank4); | ||
142 | break; | ||
143 | } | ||
144 | |||
145 | state.Phase = (state.Phase + 1) % 3; | ||
146 | } | ||
147 | break; | ||
148 | case 1.5f: | ||
149 | // Upsample by 3 then decimate by 2. | ||
150 | for (int i = 0; i < outputSampleCount; i++) | ||
151 | { | ||
152 | switch (state.Phase) | ||
153 | { | ||
154 | case 0: | ||
155 | NextInput(ref state, inputBuffer[inputBufferIndex++]); | ||
156 | outputBuffer[i] = state.History[Bank0CenterIndex]; | ||
157 | break; | ||
158 | case 1: | ||
159 | outputBuffer[i] = DoFilterBank(ref state, Bank4); | ||
160 | break; | ||
161 | case 2: | ||
162 | NextInput(ref state, inputBuffer[inputBufferIndex++]); | ||
163 | outputBuffer[i] = DoFilterBank(ref state, Bank2); | ||
164 | break; | ||
165 | } | ||
166 | |||
167 | state.Phase = (state.Phase + 1) % 3; | ||
168 | } | ||
169 | break; | ||
170 | default: | ||
171 | throw new ArgumentOutOfRangeException(); | ||
172 | } | ||
173 | } | ||
174 | } | ||
175 | } \ No newline at end of file | ||
diff --git a/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs b/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs new file mode 100644 index 000000000..a45fa8e5b --- /dev/null +++ b/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs | |||
@@ -0,0 +1,14 @@ | |||
1 | using Ryujinx.Common.Memory; | ||
2 | |||
3 | namespace Ryujinx.Audio.Renderer.Server.Upsampler | ||
4 | { | ||
5 | public struct UpsamplerBufferState | ||
6 | { | ||
7 | public const int HistoryLength = 20; | ||
8 | |||
9 | public float Scale; | ||
10 | public Array20<float> History; | ||
11 | public bool Initialized; | ||
12 | public int Phase; | ||
13 | } | ||
14 | } \ No newline at end of file | ||
diff --git a/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs b/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs index 065e4838c..e508f35b4 100644 --- a/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs +++ b/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs | |||
@@ -38,6 +38,11 @@ namespace Ryujinx.Audio.Renderer.Server.Upsampler | |||
38 | public ushort[] InputBufferIndices; | 38 | public ushort[] InputBufferIndices; |
39 | 39 | ||
40 | /// <summary> | 40 | /// <summary> |
41 | /// State of each input buffer index kept across invocations of the upsampler. | ||
42 | /// </summary> | ||
43 | public UpsamplerBufferState[] BufferStates; | ||
44 | |||
45 | /// <summary> | ||
41 | /// Create a new <see cref="UpsamplerState"/>. | 46 | /// Create a new <see cref="UpsamplerState"/>. |
42 | /// </summary> | 47 | /// </summary> |
43 | /// <param name="manager">The upsampler manager.</param> | 48 | /// <param name="manager">The upsampler manager.</param> |