aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs12
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs47
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs175
-rw-r--r--Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs14
-rw-r--r--Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs5
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 @@
1using Ryujinx.Audio.Renderer.Server.Upsampler;
2using Ryujinx.Common.Memory;
3using System;
4using System.Diagnostics;
5using System.Runtime.CompilerServices;
6
7namespace 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 @@
1using Ryujinx.Common.Memory;
2
3namespace 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>