aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs')
-rw-r--r--src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs466
1 files changed, 466 insertions, 0 deletions
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs
new file mode 100644
index 00000000..5ca1ddba
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs
@@ -0,0 +1,466 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Common.Logging;
+using Ryujinx.Memory;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.Arm;
+using System.Runtime.Intrinsics.X86;
+using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
+
+namespace Ryujinx.Audio.Renderer.Dsp
+{
+ public static class DataSourceHelper
+ {
+ private const int FixedPointPrecision = 15;
+
+ public struct WaveBufferInformation
+ {
+ public uint SourceSampleRate;
+ public float Pitch;
+ public ulong ExtraParameter;
+ public ulong ExtraParameterSize;
+ public int ChannelIndex;
+ public int ChannelCount;
+ public DecodingBehaviour DecodingBehaviour;
+ public SampleRateConversionQuality SrcQuality;
+ public SampleFormat SampleFormat;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int GetPitchLimitBySrcQuality(SampleRateConversionQuality quality)
+ {
+ return quality switch
+ {
+ SampleRateConversionQuality.Default or SampleRateConversionQuality.Low => 4,
+ SampleRateConversionQuality.High => 8,
+ _ => throw new ArgumentException(quality.ToString()),
+ };
+ }
+
+ public static void ProcessWaveBuffers(IVirtualMemoryManager memoryManager, Span<float> outputBuffer, ref WaveBufferInformation info, Span<WaveBuffer> wavebuffers, ref VoiceUpdateState voiceState, uint targetSampleRate, int sampleCount)
+ {
+ const int tempBufferSize = 0x3F00;
+
+ Span<short> tempBuffer = stackalloc short[tempBufferSize];
+
+ float sampleRateRatio = (float)info.SourceSampleRate / targetSampleRate * info.Pitch;
+
+ float fraction = voiceState.Fraction;
+ int waveBufferIndex = (int)voiceState.WaveBufferIndex;
+ ulong playedSampleCount = voiceState.PlayedSampleCount;
+ int offset = voiceState.Offset;
+ uint waveBufferConsumed = voiceState.WaveBufferConsumed;
+
+ int pitchMaxLength = GetPitchLimitBySrcQuality(info.SrcQuality);
+
+ int totalNeededSize = (int)MathF.Truncate(fraction + sampleRateRatio * sampleCount);
+
+ if (totalNeededSize + pitchMaxLength <= tempBufferSize && totalNeededSize >= 0)
+ {
+ int sourceSampleCountToProcess = sampleCount;
+
+ int maxSampleCountPerIteration = Math.Min((int)MathF.Truncate((tempBufferSize - fraction) / sampleRateRatio), sampleCount);
+
+ bool isStarving = false;
+
+ int i = 0;
+
+ while (i < sourceSampleCountToProcess)
+ {
+ int tempBufferIndex = 0;
+
+ if (!info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion))
+ {
+ voiceState.Pitch.AsSpan().Slice(0, pitchMaxLength).CopyTo(tempBuffer);
+ tempBufferIndex += pitchMaxLength;
+ }
+
+ int sampleCountToProcess = Math.Min(sourceSampleCountToProcess, maxSampleCountPerIteration);
+
+ int y = 0;
+
+ int sampleCountToDecode = (int)MathF.Truncate(fraction + sampleRateRatio * sampleCountToProcess);
+
+ while (y < sampleCountToDecode)
+ {
+ if (waveBufferIndex >= Constants.VoiceWaveBufferCount)
+ {
+ waveBufferIndex = 0;
+ playedSampleCount = 0;
+ }
+
+ if (!voiceState.IsWaveBufferValid[waveBufferIndex])
+ {
+ isStarving = true;
+ break;
+ }
+
+ ref WaveBuffer waveBuffer = ref wavebuffers[waveBufferIndex];
+
+ if (offset == 0 && info.SampleFormat == SampleFormat.Adpcm && waveBuffer.Context != 0)
+ {
+ voiceState.LoopContext = memoryManager.Read<AdpcmLoopContext>(waveBuffer.Context);
+ }
+
+ Span<short> tempSpan = tempBuffer.Slice(tempBufferIndex + y);
+
+ int decodedSampleCount = -1;
+
+ int targetSampleStartOffset;
+ int targetSampleEndOffset;
+
+ if (voiceState.LoopCount > 0 && waveBuffer.LoopStartSampleOffset != 0 && waveBuffer.LoopEndSampleOffset != 0 && waveBuffer.LoopStartSampleOffset <= waveBuffer.LoopEndSampleOffset)
+ {
+ targetSampleStartOffset = (int)waveBuffer.LoopStartSampleOffset;
+ targetSampleEndOffset = (int)waveBuffer.LoopEndSampleOffset;
+ }
+ else
+ {
+ targetSampleStartOffset = (int)waveBuffer.StartSampleOffset;
+ targetSampleEndOffset = (int)waveBuffer.EndSampleOffset;
+ }
+
+ int targetWaveBufferSampleCount = targetSampleEndOffset - targetSampleStartOffset;
+
+ switch (info.SampleFormat)
+ {
+ case SampleFormat.Adpcm:
+ ReadOnlySpan<byte> waveBufferAdpcm = ReadOnlySpan<byte>.Empty;
+
+ if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0)
+ {
+ // TODO: we are possibly copying a lot of unneeded data here, we should only take what we need.
+ waveBufferAdpcm = memoryManager.GetSpan(waveBuffer.Buffer, (int)waveBuffer.BufferSize);
+ }
+
+ ReadOnlySpan<short> coefficients = MemoryMarshal.Cast<byte, short>(memoryManager.GetSpan(info.ExtraParameter, (int)info.ExtraParameterSize));
+ decodedSampleCount = AdpcmHelper.Decode(tempSpan, waveBufferAdpcm, targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y, coefficients, ref voiceState.LoopContext);
+ break;
+ case SampleFormat.PcmInt16:
+ ReadOnlySpan<short> waveBufferPcm16 = ReadOnlySpan<short>.Empty;
+
+ if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0)
+ {
+ ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset<short>(targetSampleStartOffset, offset, info.ChannelCount);
+ int bufferSize = PcmHelper.GetBufferSize<short>(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount;
+
+ waveBufferPcm16 = MemoryMarshal.Cast<byte, short>(memoryManager.GetSpan(bufferOffset, bufferSize));
+ }
+
+ decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcm16, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount);
+ break;
+ case SampleFormat.PcmFloat:
+ ReadOnlySpan<float> waveBufferPcmFloat = ReadOnlySpan<float>.Empty;
+
+ if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0)
+ {
+ ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset<float>(targetSampleStartOffset, offset, info.ChannelCount);
+ int bufferSize = PcmHelper.GetBufferSize<float>(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount;
+
+ waveBufferPcmFloat = MemoryMarshal.Cast<byte, float>(memoryManager.GetSpan(bufferOffset, bufferSize));
+ }
+
+ decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcmFloat, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount);
+ break;
+ default:
+ Logger.Error?.Print(LogClass.AudioRenderer, $"Unsupported sample format " + info.SampleFormat);
+ break;
+ }
+
+ Debug.Assert(decodedSampleCount <= sampleCountToDecode);
+
+ if (decodedSampleCount < 0)
+ {
+ Logger.Warning?.Print(LogClass.AudioRenderer, "Decoding failed, skipping WaveBuffer");
+
+ voiceState.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount);
+ decodedSampleCount = 0;
+ }
+
+ y += decodedSampleCount;
+ offset += decodedSampleCount;
+ playedSampleCount += (uint)decodedSampleCount;
+
+ if (offset >= targetWaveBufferSampleCount || decodedSampleCount == 0)
+ {
+ offset = 0;
+
+ if (waveBuffer.Looping)
+ {
+ voiceState.LoopCount++;
+
+ if (waveBuffer.LoopCount >= 0)
+ {
+ if (decodedSampleCount == 0 || voiceState.LoopCount > waveBuffer.LoopCount)
+ {
+ voiceState.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount);
+ }
+ }
+
+ if (decodedSampleCount == 0)
+ {
+ isStarving = true;
+ break;
+ }
+
+ if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.PlayedSampleCountResetWhenLooping))
+ {
+ playedSampleCount = 0;
+ }
+ }
+ else
+ {
+ voiceState.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount);
+ }
+ }
+ }
+
+ Span<int> outputSpanInt = MemoryMarshal.Cast<float, int>(outputBuffer.Slice(i));
+
+ if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion))
+ {
+ for (int j = 0; j < y; j++)
+ {
+ outputBuffer[j] = tempBuffer[j];
+ }
+ }
+ else
+ {
+ Span<short> tempSpan = tempBuffer.Slice(tempBufferIndex + y);
+
+ tempSpan.Slice(0, sampleCountToDecode - y).Fill(0);
+
+ ToFloat(outputBuffer, outputSpanInt, sampleCountToProcess);
+
+ ResamplerHelper.Resample(outputBuffer, tempBuffer, sampleRateRatio, ref fraction, sampleCountToProcess, info.SrcQuality, y != sourceSampleCountToProcess || info.Pitch != 1.0f);
+
+ tempBuffer.Slice(sampleCountToDecode, pitchMaxLength).CopyTo(voiceState.Pitch.AsSpan());
+ }
+
+ i += sampleCountToProcess;
+ }
+
+ Debug.Assert(sourceSampleCountToProcess == i || !isStarving);
+
+ voiceState.WaveBufferConsumed = waveBufferConsumed;
+ voiceState.Offset = offset;
+ voiceState.PlayedSampleCount = playedSampleCount;
+ voiceState.WaveBufferIndex = (uint)waveBufferIndex;
+ voiceState.Fraction = fraction;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void ToFloatAvx(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
+ {
+ ReadOnlySpan<Vector256<int>> inputVec = MemoryMarshal.Cast<int, Vector256<int>>(input);
+ Span<Vector256<float>> outputVec = MemoryMarshal.Cast<float, Vector256<float>>(output);
+
+ int sisdStart = inputVec.Length * 8;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = Avx.ConvertToVector256Single(inputVec[i]);
+ }
+
+ for (int i = sisdStart; i < sampleCount; i++)
+ {
+ output[i] = input[i];
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void ToFloatSse2(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
+ {
+ ReadOnlySpan<Vector128<int>> inputVec = MemoryMarshal.Cast<int, Vector128<int>>(input);
+ Span<Vector128<float>> outputVec = MemoryMarshal.Cast<float, Vector128<float>>(output);
+
+ int sisdStart = inputVec.Length * 4;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = Sse2.ConvertToVector128Single(inputVec[i]);
+ }
+
+ for (int i = sisdStart; i < sampleCount; i++)
+ {
+ output[i] = input[i];
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void ToFloatAdvSimd(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
+ {
+ ReadOnlySpan<Vector128<int>> inputVec = MemoryMarshal.Cast<int, Vector128<int>>(input);
+ Span<Vector128<float>> outputVec = MemoryMarshal.Cast<float, Vector128<float>>(output);
+
+ int sisdStart = inputVec.Length * 4;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = AdvSimd.ConvertToSingle(inputVec[i]);
+ }
+
+ for (int i = sisdStart; i < sampleCount; i++)
+ {
+ output[i] = input[i];
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ToFloatSlow(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
+ {
+ for (int i = 0; i < sampleCount; i++)
+ {
+ output[i] = input[i];
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ToFloat(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
+ {
+ if (Avx.IsSupported)
+ {
+ ToFloatAvx(output, input, sampleCount);
+ }
+ else if (Sse2.IsSupported)
+ {
+ ToFloatSse2(output, input, sampleCount);
+ }
+ else if (AdvSimd.IsSupported)
+ {
+ ToFloatAdvSimd(output, input, sampleCount);
+ }
+ else
+ {
+ ToFloatSlow(output, input, sampleCount);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ToIntAvx(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
+ {
+ ReadOnlySpan<Vector256<float>> inputVec = MemoryMarshal.Cast<float, Vector256<float>>(input);
+ Span<Vector256<int>> outputVec = MemoryMarshal.Cast<int, Vector256<int>>(output);
+
+ int sisdStart = inputVec.Length * 8;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = Avx.ConvertToVector256Int32(inputVec[i]);
+ }
+
+ for (int i = sisdStart; i < sampleCount; i++)
+ {
+ output[i] = (int)input[i];
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ToIntSse2(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
+ {
+ ReadOnlySpan<Vector128<float>> inputVec = MemoryMarshal.Cast<float, Vector128<float>>(input);
+ Span<Vector128<int>> outputVec = MemoryMarshal.Cast<int, Vector128<int>>(output);
+
+ int sisdStart = inputVec.Length * 4;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = Sse2.ConvertToVector128Int32(inputVec[i]);
+ }
+
+ for (int i = sisdStart; i < sampleCount; i++)
+ {
+ output[i] = (int)input[i];
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ToIntAdvSimd(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
+ {
+ ReadOnlySpan<Vector128<float>> inputVec = MemoryMarshal.Cast<float, Vector128<float>>(input);
+ Span<Vector128<int>> outputVec = MemoryMarshal.Cast<int, Vector128<int>>(output);
+
+ int sisdStart = inputVec.Length * 4;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = AdvSimd.ConvertToInt32RoundToZero(inputVec[i]);
+ }
+
+ for (int i = sisdStart; i < sampleCount; i++)
+ {
+ output[i] = (int)input[i];
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ToIntSlow(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
+ {
+ for (int i = 0; i < sampleCount; i++)
+ {
+ output[i] = (int)input[i];
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ToInt(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
+ {
+ if (Avx.IsSupported)
+ {
+ ToIntAvx(output, input, sampleCount);
+ }
+ else if (Sse2.IsSupported)
+ {
+ ToIntSse2(output, input, sampleCount);
+ }
+ else if (AdvSimd.IsSupported)
+ {
+ ToIntAdvSimd(output, input, sampleCount);
+ }
+ else
+ {
+ ToIntSlow(output, input, sampleCount);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RemapLegacyChannelEffectMappingToChannelResourceMapping(bool isSupported, Span<ushort> bufferIndices)
+ {
+ if (!isSupported && bufferIndices.Length == 6)
+ {
+ ushort backLeft = bufferIndices[2];
+ ushort backRight = bufferIndices[3];
+ ushort frontCenter = bufferIndices[4];
+ ushort lowFrequency = bufferIndices[5];
+
+ bufferIndices[2] = frontCenter;
+ bufferIndices[3] = lowFrequency;
+ bufferIndices[4] = backLeft;
+ bufferIndices[5] = backRight;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RemapChannelResourceMappingToLegacy(bool isSupported, Span<ushort> bufferIndices)
+ {
+ if (isSupported && bufferIndices.Length == 6)
+ {
+ ushort frontCenter = bufferIndices[2];
+ ushort lowFrequency = bufferIndices[3];
+ ushort backLeft = bufferIndices[4];
+ ushort backRight = bufferIndices[5];
+
+ bufferIndices[2] = backLeft;
+ bufferIndices[3] = backRight;
+ bufferIndices[4] = frontCenter;
+ bufferIndices[5] = lowFrequency;
+ }
+ }
+ }
+} \ No newline at end of file