aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Audio/Renderer
diff options
context:
space:
mode:
authorMary <me@thog.eu>2021-02-26 01:11:56 +0100
committerGitHub <noreply@github.com>2021-02-26 01:11:56 +0100
commitf556c80d0230056335632b60c71f1567e177239e (patch)
tree748aa6be62b93a8e941e25dbd83f39e1dbb37035 /Ryujinx.Audio/Renderer
parent1c49089ff00fc87dc4872f135dc6a0d36169a970 (diff)
Haydn: Part 1 (#2007)
* Haydn: Part 1 Based on my reverse of audio 11.0.0. As always, core implementation under LGPLv3 for the same reasons as for Amadeus. This place the bases of a more flexible audio system while making audout & audin accurate. This have the following improvements: - Complete reimplementation of audout and audin. - Audin currently only have a dummy backend. - Dramatically reduce CPU usage by up to 50% in common cases (SoundIO and OpenAL). - Audio Renderer now can output to 5.1 devices when supported. - Audio Renderer init its backend on demand instead of keeping two up all the time. - All backends implementation are now in their own project. - Ryujinx.Audio.Renderer was renamed Ryujinx.Audio and was refactored because of this. As a note, games having issues with OpenAL haven't improved and will not because of OpenAL design (stopping when buffers finish playing causing possible audio "pops" when buffers are very small). * Update for latest hexkyz's edits on Switchbrew * audren: Rollback channel configuration changes * Address gdkchan's comments * Fix typo in OpenAL backend driver * Address last comments * Fix a nit * Address gdkchan's comments
Diffstat (limited to 'Ryujinx.Audio/Renderer')
-rw-r--r--Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs30
-rw-r--r--Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs67
-rw-r--r--Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs167
-rw-r--r--Ryujinx.Audio/Renderer/Common/EffectType.cs60
-rw-r--r--Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs60
-rw-r--r--Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs45
-rw-r--r--Ryujinx.Audio/Renderer/Common/NodeIdType.cs50
-rw-r--r--Ryujinx.Audio/Renderer/Common/NodeStates.cs246
-rw-r--r--Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs34
-rw-r--r--Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs28
-rw-r--r--Ryujinx.Audio/Renderer/Common/PlayState.cs40
-rw-r--r--Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs50
-rw-r--r--Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs55
-rw-r--r--Ryujinx.Audio/Renderer/Common/SinkType.cs40
-rw-r--r--Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs50
-rw-r--r--Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs121
-rw-r--r--Ryujinx.Audio/Renderer/Common/WaveBuffer.cs99
-rw-r--r--Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs78
-rw-r--r--Ryujinx.Audio/Renderer/Device/VirtualDevice.cs84
-rw-r--r--Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs44
-rw-r--r--Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs79
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs219
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs255
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs94
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs205
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs89
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs91
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs41
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs124
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs49
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs52
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs127
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs272
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs103
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs72
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs108
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs89
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs37
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs125
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs83
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs106
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs93
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs93
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs64
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs263
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs284
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs87
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs125
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs71
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs409
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs69
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs95
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs93
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs53
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs50
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs84
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs95
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs624
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs29
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs60
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs28
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs73
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs136
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs221
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs116
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs47
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs51
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs100
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs61
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs49
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs118
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs144
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs136
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/EffectInParameter.cs103
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/EffectOutStatus.cs54
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs50
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs39
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs44
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/MixParameter.cs112
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs38
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs38
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs38
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs79
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs75
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs70
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs43
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs84
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs63
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs62
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs45
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs361
-rw-r--r--Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs52
-rw-r--r--Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs838
-rw-r--r--Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs334
-rw-r--r--Ryujinx.Audio/Renderer/Server/BehaviourContext.cs405
-rw-r--r--Ryujinx.Audio/Renderer/Server/CommandBuffer.cs484
-rw-r--r--Ryujinx.Audio/Renderer/Server/CommandGenerator.cs940
-rw-r--r--Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs180
-rw-r--r--Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs544
-rw-r--r--Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs636
-rw-r--r--Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs92
-rw-r--r--Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs257
-rw-r--r--Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs74
-rw-r--r--Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs56
-rw-r--r--Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs100
-rw-r--r--Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs82
-rw-r--r--Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs99
-rw-r--r--Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs102
-rw-r--r--Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs45
-rw-r--r--Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs52
-rw-r--r--Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs151
-rw-r--r--Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs148
-rw-r--r--Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs383
-rw-r--r--Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs276
-rw-r--r--Ryujinx.Audio/Renderer/Server/Mix/MixState.cs330
-rw-r--r--Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs69
-rw-r--r--Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs63
-rw-r--r--Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs97
-rw-r--r--Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs89
-rw-r--r--Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs89
-rw-r--r--Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs73
-rw-r--r--Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs79
-rw-r--r--Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs79
-rw-r--r--Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs118
-rw-r--r--Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs134
-rw-r--r--Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs124
-rw-r--r--Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs311
-rw-r--r--Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs65
-rw-r--r--Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs119
-rw-r--r--Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs126
-rw-r--r--Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs92
-rw-r--r--Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs73
-rw-r--r--Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs320
-rw-r--r--Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs210
-rw-r--r--Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs237
-rw-r--r--Ryujinx.Audio/Renderer/Server/StateUpdater.cs575
-rw-r--r--Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs36
-rw-r--r--Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs41
-rw-r--r--Ryujinx.Audio/Renderer/Server/Types/PlayState.cs56
-rw-r--r--Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs101
-rw-r--r--Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs80
-rw-r--r--Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs57
-rw-r--r--Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs166
-rw-r--r--Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs716
-rw-r--r--Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs121
-rw-r--r--Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs75
-rw-r--r--Ryujinx.Audio/Renderer/Utils/BitArray.cs120
-rw-r--r--Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs105
-rw-r--r--Ryujinx.Audio/Renderer/Utils/Mailbox.cs72
-rw-r--r--Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs188
-rw-r--r--Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs60
-rw-r--r--Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs64
152 files changed, 21147 insertions, 0 deletions
diff --git a/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs b/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs
new file mode 100644
index 00000000..5d826569
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs
@@ -0,0 +1,30 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct AuxiliaryBufferAddresses
+ {
+ public ulong SendBufferInfo;
+ public ulong SendBufferInfoBase;
+ public ulong ReturnBufferInfo;
+ public ulong ReturnBufferInfoBase;
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs b/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs
new file mode 100644
index 00000000..0de3d571
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs
@@ -0,0 +1,67 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ /// <summary>
+ /// Represents the input parameter for <see cref="Server.BehaviourContext"/>.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct BehaviourParameter
+ {
+ /// <summary>
+ /// The current audio renderer revision in use.
+ /// </summary>
+ public int UserRevision;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private uint _padding;
+
+ /// <summary>
+ /// The flags given controlling behaviour of the audio renderer
+ /// </summary>
+ /// <remarks>See <see cref="Server.BehaviourContext.UpdateFlags(ulong)"/> and <see cref="Server.BehaviourContext.IsMemoryPoolForceMappingEnabled"/>.</remarks>
+ public ulong Flags;
+
+ /// <summary>
+ /// Represents an error during <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct ErrorInfo
+ {
+ /// <summary>
+ /// The error code to report.
+ /// </summary>
+ public ResultCode ErrorCode;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private uint _padding;
+
+ /// <summary>
+ /// Extra information given with the <see cref="ResultCode"/>
+ /// </summary>
+ /// <remarks>This is usually used to report a faulting cpu address when a <see cref="Server.MemoryPool.MemoryPoolState"/> mapping fail.</remarks>
+ public ulong ExtraErrorInfo;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs b/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs
new file mode 100644
index 00000000..7423eda4
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs
@@ -0,0 +1,167 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Utils;
+using Ryujinx.Common;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ /// <summary>
+ /// Represents a adjacent matrix.
+ /// </summary>
+ /// <remarks>This is used for splitter routing.</remarks>
+ public class EdgeMatrix
+ {
+ /// <summary>
+ /// Backing <see cref="BitArray"/> used for node connections.
+ /// </summary>
+ private BitArray _storage;
+
+ /// <summary>
+ /// The count of nodes of the current instance.
+ /// </summary>
+ private int _nodeCount;
+
+ /// <summary>
+ /// Get the required work buffer size memory needed for the <see cref="EdgeMatrix"/>.
+ /// </summary>
+ /// <param name="nodeCount">The count of nodes.</param>
+ /// <returns>The size required for the given <paramref name="nodeCount"/>.</returns>
+ public static int GetWorkBufferSize(int nodeCount)
+ {
+ int size = BitUtils.AlignUp(nodeCount * nodeCount, Constants.BufferAlignment);
+
+ return size / Unsafe.SizeOf<byte>();
+ }
+
+ /// <summary>
+ /// Initializes the <see cref="EdgeMatrix"/> instance with backing memory.
+ /// </summary>
+ /// <param name="edgeMatrixWorkBuffer">The backing memory.</param>
+ /// <param name="nodeCount">The count of nodes.</param>
+ public void Initialize(Memory<byte> edgeMatrixWorkBuffer, int nodeCount)
+ {
+ Debug.Assert(edgeMatrixWorkBuffer.Length >= GetWorkBufferSize(nodeCount));
+
+ _storage = new BitArray(edgeMatrixWorkBuffer);
+
+ _nodeCount = nodeCount;
+
+ _storage.Reset();
+ }
+
+ /// <summary>
+ /// Test if the bit at the given index is set.
+ /// </summary>
+ /// <param name="index">A bit index.</param>
+ /// <returns>Returns true if the bit at the given index is set</returns>
+ public bool Test(int index)
+ {
+ return _storage.Test(index);
+ }
+
+ /// <summary>
+ /// Reset all bits in the storage.
+ /// </summary>
+ public void Reset()
+ {
+ _storage.Reset();
+ }
+
+ /// <summary>
+ /// Reset the bit at the given index.
+ /// </summary>
+ /// <param name="index">A bit index.</param>
+ public void Reset(int index)
+ {
+ _storage.Reset(index);
+ }
+
+ /// <summary>
+ /// Set the bit at the given index.
+ /// </summary>
+ /// <param name="index">A bit index.</param>
+ public void Set(int index)
+ {
+ _storage.Set(index);
+ }
+
+ /// <summary>
+ /// Connect a given source to a given destination.
+ /// </summary>
+ /// <param name="source">The source index.</param>
+ /// <param name="destination">The destination index.</param>
+ public void Connect(int source, int destination)
+ {
+ Debug.Assert(source < _nodeCount);
+ Debug.Assert(destination < _nodeCount);
+
+ _storage.Set(_nodeCount * source + destination);
+ }
+
+ /// <summary>
+ /// Check if the given source is connected to the given destination.
+ /// </summary>
+ /// <param name="source">The source index.</param>
+ /// <param name="destination">The destination index.</param>
+ /// <returns>Returns true if the given source is connected to the given destination.</returns>
+ public bool Connected(int source, int destination)
+ {
+ Debug.Assert(source < _nodeCount);
+ Debug.Assert(destination < _nodeCount);
+
+ return _storage.Test(_nodeCount * source + destination);
+ }
+
+ /// <summary>
+ /// Disconnect a given source from a given destination.
+ /// </summary>
+ /// <param name="source">The source index.</param>
+ /// <param name="destination">The destination index.</param>
+ public void Disconnect(int source, int destination)
+ {
+ Debug.Assert(source < _nodeCount);
+ Debug.Assert(destination < _nodeCount);
+
+ _storage.Reset(_nodeCount * source + destination);
+ }
+
+ /// <summary>
+ /// Remove all edges from a given source.
+ /// </summary>
+ /// <param name="source">The source index.</param>
+ public void RemoveEdges(int source)
+ {
+ for (int i = 0; i < _nodeCount; i++)
+ {
+ Disconnect(source, i);
+ }
+ }
+
+ /// <summary>
+ /// Get the total node count.
+ /// </summary>
+ /// <returns>The total node count.</returns>
+ public int GetNodeCount()
+ {
+ return _nodeCount;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/EffectType.cs b/Ryujinx.Audio/Renderer/Common/EffectType.cs
new file mode 100644
index 00000000..082f94f8
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/EffectType.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ /// <summary>
+ /// The type of an effect.
+ /// </summary>
+ public enum EffectType : byte
+ {
+ /// <summary>
+ /// Invalid effect.
+ /// </summary>
+ Invalid,
+
+ /// <summary>
+ /// Effect applying additional mixing capability.
+ /// </summary>
+ BufferMix,
+
+ /// <summary>
+ /// Effect applying custom user effect (via auxiliary buffers).
+ /// </summary>
+ AuxiliaryBuffer,
+
+ /// <summary>
+ /// Effect applying a delay.
+ /// </summary>
+ Delay,
+
+ /// <summary>
+ /// Effect applying a reverberation effect via a given preset.
+ /// </summary>
+ Reverb,
+
+ /// <summary>
+ /// Effect applying a 3D reverberation effect via a given preset.
+ /// </summary>
+ Reverb3d,
+
+ /// <summary>
+ /// Effect applying a biquad filter.
+ /// </summary>
+ BiquadFilter
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs b/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs
new file mode 100644
index 00000000..7f9765d9
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ /// <summary>
+ /// Represents the state of a memory pool.
+ /// </summary>
+ public enum MemoryPoolUserState : uint
+ {
+ /// <summary>
+ /// Invalid state.
+ /// </summary>
+ Invalid = 0,
+
+ /// <summary>
+ /// The memory pool is new. (client side only)
+ /// </summary>
+ New = 1,
+
+ /// <summary>
+ /// The user asked to detach the memory pool from the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ RequestDetach = 2,
+
+ /// <summary>
+ /// The memory pool is detached from the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ Detached = 3,
+
+ /// <summary>
+ /// The user asked to attach the memory pool to the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ RequestAttach = 4,
+
+ /// <summary>
+ /// The memory pool is attached to the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ Attached = 5,
+
+ /// <summary>
+ /// The memory pool is released. (client side only)
+ /// </summary>
+ Released = 6
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs b/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs
new file mode 100644
index 00000000..9bf1f2d0
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs
@@ -0,0 +1,45 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ /// <summary>
+ /// Helper for manipulating node ids.
+ /// </summary>
+ public static class NodeIdHelper
+ {
+ /// <summary>
+ /// Get the type of a node from a given node id.
+ /// </summary>
+ /// <param name="nodeId">Id of the node.</param>
+ /// <returns>The type of the node.</returns>
+ public static NodeIdType GetType(int nodeId)
+ {
+ return (NodeIdType)(nodeId >> 28);
+ }
+
+ /// <summary>
+ /// Get the base of a node from a given node id.
+ /// </summary>
+ /// <param name="nodeId">Id of the node.</param>
+ /// <returns>The base of the node.</returns>
+ public static int GetBase(int nodeId)
+ {
+ return (nodeId >> 16) & 0xFFF;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/NodeIdType.cs b/Ryujinx.Audio/Renderer/Common/NodeIdType.cs
new file mode 100644
index 00000000..4a4796a9
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/NodeIdType.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ /// <summary>
+ /// The type of a node.
+ /// </summary>
+ public enum NodeIdType : byte
+ {
+ /// <summary>
+ /// Invalid node id.
+ /// </summary>
+ Invalid = 0,
+
+ /// <summary>
+ /// Voice related node id. (data source, biquad filter, ...)
+ /// </summary>
+ Voice = 1,
+
+ /// <summary>
+ /// Mix related node id. (mix, effects, splitters, ...)
+ /// </summary>
+ Mix = 2,
+
+ /// <summary>
+ /// Sink related node id. (device &amp; circular buffer sink)
+ /// </summary>
+ Sink = 3,
+
+ /// <summary>
+ /// Performance monitoring related node id (performance commands)
+ /// </summary>
+ Performance = 15
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/NodeStates.cs b/Ryujinx.Audio/Renderer/Common/NodeStates.cs
new file mode 100644
index 00000000..5efd7767
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/NodeStates.cs
@@ -0,0 +1,246 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Utils;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ public class NodeStates
+ {
+ private class Stack
+ {
+ private Memory<int> _storage;
+ private int _index;
+
+ private int _nodeCount;
+
+ public void Reset(Memory<int> storage, int nodeCount)
+ {
+ Debug.Assert(storage.Length * sizeof(int) >= CalcBufferSize(nodeCount));
+
+ _storage = storage;
+ _index = 0;
+ _nodeCount = nodeCount;
+ }
+
+ public int GetCurrentCount()
+ {
+ return _index;
+ }
+
+ public void Push(int data)
+ {
+ Debug.Assert(_index + 1 <= _nodeCount);
+
+ _storage.Span[_index++] = data;
+ }
+
+ public int Pop()
+ {
+ Debug.Assert(_index > 0);
+
+ return _storage.Span[--_index];
+ }
+
+ public int Top()
+ {
+ return _storage.Span[_index - 1];
+ }
+
+ public static int CalcBufferSize(int nodeCount)
+ {
+ return nodeCount * sizeof(int);
+ }
+ }
+
+ private int _nodeCount;
+ private EdgeMatrix _discovered;
+ private EdgeMatrix _finished;
+ private Memory<int> _resultArray;
+ private Stack _stack;
+ private int _tsortResultIndex;
+
+ private enum NodeState : byte
+ {
+ Unknown,
+ Discovered,
+ Finished
+ }
+
+ public NodeStates()
+ {
+ _stack = new Stack();
+ _discovered = new EdgeMatrix();
+ _finished = new EdgeMatrix();
+ }
+
+ public static int GetWorkBufferSize(int nodeCount)
+ {
+ return Stack.CalcBufferSize(nodeCount * nodeCount) + 0xC * nodeCount + 2 * EdgeMatrix.GetWorkBufferSize(nodeCount);
+ }
+
+ public void Initialize(Memory<byte> nodeStatesWorkBuffer, int nodeCount)
+ {
+ int workBufferSize = GetWorkBufferSize(nodeCount);
+
+ Debug.Assert(nodeStatesWorkBuffer.Length >= workBufferSize);
+
+ _nodeCount = nodeCount;
+
+ int edgeMatrixWorkBufferSize = EdgeMatrix.GetWorkBufferSize(nodeCount);
+
+ _discovered.Initialize(nodeStatesWorkBuffer.Slice(0, edgeMatrixWorkBufferSize), nodeCount);
+ _finished.Initialize(nodeStatesWorkBuffer.Slice(edgeMatrixWorkBufferSize, edgeMatrixWorkBufferSize), nodeCount);
+
+ nodeStatesWorkBuffer = nodeStatesWorkBuffer.Slice(edgeMatrixWorkBufferSize * 2);
+
+ _resultArray = SpanMemoryManager<int>.Cast(nodeStatesWorkBuffer.Slice(0, sizeof(int) * nodeCount));
+
+ nodeStatesWorkBuffer = nodeStatesWorkBuffer.Slice(sizeof(int) * nodeCount);
+
+ Memory<int> stackWorkBuffer = SpanMemoryManager<int>.Cast(nodeStatesWorkBuffer.Slice(0, Stack.CalcBufferSize(nodeCount * nodeCount)));
+
+ _stack.Reset(stackWorkBuffer, nodeCount * nodeCount);
+ }
+
+ private void Reset()
+ {
+ _discovered.Reset();
+ _finished.Reset();
+ _tsortResultIndex = 0;
+ _resultArray.Span.Fill(-1);
+ }
+
+ private NodeState GetState(int index)
+ {
+ Debug.Assert(index < _nodeCount);
+
+ if (_discovered.Test(index))
+ {
+ Debug.Assert(!_finished.Test(index));
+
+ return NodeState.Discovered;
+ }
+ else if (_finished.Test(index))
+ {
+ Debug.Assert(!_discovered.Test(index));
+
+ return NodeState.Finished;
+ }
+
+ return NodeState.Unknown;
+ }
+
+ private void SetState(int index, NodeState state)
+ {
+ switch (state)
+ {
+ case NodeState.Unknown:
+ _discovered.Reset(index);
+ _finished.Reset(index);
+ break;
+ case NodeState.Discovered:
+ _discovered.Set(index);
+ _finished.Reset(index);
+ break;
+ case NodeState.Finished:
+ _finished.Set(index);
+ _discovered.Reset(index);
+ break;
+ }
+ }
+
+ private void PushTsortResult(int index)
+ {
+ Debug.Assert(index < _nodeCount);
+
+ _resultArray.Span[_tsortResultIndex++] = index;
+ }
+
+ public ReadOnlySpan<int> GetTsortResult()
+ {
+ return _resultArray.Span.Slice(0, _tsortResultIndex);
+ }
+
+ public bool Sort(EdgeMatrix edgeMatrix)
+ {
+ Reset();
+
+ if (_nodeCount <= 0)
+ {
+ return true;
+ }
+
+ for (int i = 0; i < _nodeCount; i++)
+ {
+ if (GetState(i) == NodeState.Unknown)
+ {
+ _stack.Push(i);
+ }
+
+ while (_stack.GetCurrentCount() > 0)
+ {
+ int topIndex = _stack.Top();
+
+ NodeState topState = GetState(topIndex);
+
+ if (topState == NodeState.Discovered)
+ {
+ SetState(topIndex, NodeState.Finished);
+ PushTsortResult(topIndex);
+ _stack.Pop();
+ }
+ else if (topState == NodeState.Finished)
+ {
+ _stack.Pop();
+ }
+ else
+ {
+ if (topState == NodeState.Unknown)
+ {
+ SetState(topIndex, NodeState.Discovered);
+ }
+
+ for (int j = 0; j < edgeMatrix.GetNodeCount(); j++)
+ {
+ if (edgeMatrix.Connected(topIndex, j))
+ {
+ NodeState jState = GetState(j);
+
+ if (jState == NodeState.Unknown)
+ {
+ _stack.Push(j);
+ }
+ // Found a loop, reset and propagate rejection.
+ else if (jState == NodeState.Discovered)
+ {
+ Reset();
+
+ return false;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs b/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
new file mode 100644
index 00000000..a92bd0cc
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
@@ -0,0 +1,34 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ public enum PerformanceDetailType : byte
+ {
+ Unknown,
+ PcmInt16,
+ Adpcm,
+ VolumeRamp,
+ BiquadFilter,
+ Mix,
+ Delay,
+ Aux,
+ Reverb,
+ Reverb3d,
+ PcmFloat
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs b/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs
new file mode 100644
index 00000000..0d9a547d
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ public enum PerformanceEntryType : byte
+ {
+ Invalid,
+ Voice,
+ SubMix,
+ FinalMix,
+ Sink
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/PlayState.cs b/Ryujinx.Audio/Renderer/Common/PlayState.cs
new file mode 100644
index 00000000..7771c154
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/PlayState.cs
@@ -0,0 +1,40 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ /// <summary>
+ /// Common play state.
+ /// </summary>
+ public enum PlayState : byte
+ {
+ /// <summary>
+ /// The user request the voice to be started.
+ /// </summary>
+ Start,
+
+ /// <summary>
+ /// The user request the voice to be stopped.
+ /// </summary>
+ Stop,
+
+ /// <summary>
+ /// The user request the voice to be paused.
+ /// </summary>
+ Pause
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs b/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs
new file mode 100644
index 00000000..a0f65e5e
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ /// <summary>
+ /// Early reverb reflection.
+ /// </summary>
+ public enum ReverbEarlyMode : uint
+ {
+ /// <summary>
+ /// Room early reflection. (small acoustic space, fast reflection)
+ /// </summary>
+ Room,
+
+ /// <summary>
+ /// Chamber early reflection. (bigger than <see cref="Room"/>'s acoustic space, short reflection)
+ /// </summary>
+ Chamber,
+
+ /// <summary>
+ /// Hall early reflection. (large acoustic space, warm reflection)
+ /// </summary>
+ Hall,
+
+ /// <summary>
+ /// Cathedral early reflection. (very large acoustic space, pronounced bright reflection)
+ /// </summary>
+ Cathedral,
+
+ /// <summary>
+ /// No early reflection.
+ /// </summary>
+ Disabled
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs b/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs
new file mode 100644
index 00000000..57760821
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs
@@ -0,0 +1,55 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ /// <summary>
+ /// Late reverb reflection.
+ /// </summary>
+ public enum ReverbLateMode : uint
+ {
+ /// <summary>
+ /// Room late reflection. (small acoustic space, fast reflection)
+ /// </summary>
+ Room,
+
+ /// <summary>
+ /// Hall late reflection. (large acoustic space, warm reflection)
+ /// </summary>
+ Hall,
+
+ /// <summary>
+ /// Classic plate late reflection. (clean distinctive reverb)
+ /// </summary>
+ Plate,
+
+ /// <summary>
+ /// Cathedral late reflection. (very large acoustic space, pronounced bright reflection)
+ /// </summary>
+ Cathedral,
+
+ /// <summary>
+ /// Do not apply any delay. (max delay)
+ /// </summary>
+ NoDelay,
+
+ /// <summary>
+ /// Max delay. (used for delay line limits)
+ /// </summary>
+ Limit = NoDelay
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/SinkType.cs b/Ryujinx.Audio/Renderer/Common/SinkType.cs
new file mode 100644
index 00000000..e1a35508
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/SinkType.cs
@@ -0,0 +1,40 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ /// <summary>
+ /// The type of a sink.
+ /// </summary>
+ public enum SinkType : byte
+ {
+ /// <summary>
+ /// The sink is in an invalid state.
+ /// </summary>
+ Invalid,
+
+ /// <summary>
+ /// The sink is a device.
+ /// </summary>
+ Device,
+
+ /// <summary>
+ /// The sink is a circular buffer.
+ /// </summary>
+ CircularBuffer
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs b/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs
new file mode 100644
index 00000000..ae516662
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ /// <summary>
+ /// Update data header used for input and output of <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>.
+ /// </summary>
+ public struct UpdateDataHeader
+ {
+ public int Revision;
+ public uint BehaviourSize;
+ public uint MemoryPoolsSize;
+ public uint VoicesSize;
+ public uint VoiceResourcesSize;
+ public uint EffectsSize;
+ public uint MixesSize;
+ public uint SinksSize;
+ public uint PerformanceBufferSize;
+ public uint Unknown24;
+ public uint RenderInfoSize;
+
+ private unsafe fixed int _reserved[4];
+
+ public uint TotalSize;
+
+ public void Initialize(int revision)
+ {
+ Revision = revision;
+
+ TotalSize = (uint)Unsafe.SizeOf<UpdateDataHeader>();
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs b/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
new file mode 100644
index 00000000..6cebad5e
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
@@ -0,0 +1,121 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Common.Memory;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ /// <summary>
+ /// Represent the update state of a voice.
+ /// </summary>
+ /// <remarks>This is shared between the server and audio processor.</remarks>
+ [StructLayout(LayoutKind.Sequential, Pack = Align)]
+ public struct VoiceUpdateState
+ {
+ public const int Align = 0x10;
+ public const int BiquadStateOffset = 0x0;
+ public const int BiquadStateSize = 0x10;
+
+ /// <summary>
+ /// The state of the biquad filters of this voice.
+ /// </summary>
+ public Array2<BiquadFilterState> BiquadFilterState;
+
+ /// <summary>
+ /// The total amount of samples that was played.
+ /// </summary>
+ /// <remarks>This is reset to 0 when a <see cref="WaveBuffer"/> finishes playing and <see cref="WaveBuffer.IsEndOfStream"/> is set.</remarks>
+ /// <remarks>This is reset to 0 when looping while <see cref="Parameter.VoiceInParameter.DecodingBehaviour.PlayedSampleCountResetWhenLooping"/> is set.</remarks>
+ public ulong PlayedSampleCount;
+
+ /// <summary>
+ /// The current sample offset in the <see cref="WaveBuffer"/> pointed by <see cref="WaveBufferIndex"/>.
+ /// </summary>
+ public int Offset;
+
+ /// <summary>
+ /// The current index of the <see cref="WaveBuffer"/> in use.
+ /// </summary>
+ public uint WaveBufferIndex;
+
+ private WaveBufferValidArray _isWaveBufferValid;
+
+ /// <summary>
+ /// The total amount of <see cref="WaveBuffer"/> consumed.
+ /// </summary>
+ public uint WaveBufferConsumed;
+
+ /// <summary>
+ /// Pitch used for Sample Rate Conversion.
+ /// </summary>
+ public Array8<short> Pitch;
+
+ public float Fraction;
+
+ /// <summary>
+ /// The ADPCM loop context when <see cref="SampleFormat.Adpcm"/> is in use.
+ /// </summary>
+ public AdpcmLoopContext LoopContext;
+
+ /// <summary>
+ /// The last samples after a mix ramp.
+ /// </summary>
+ /// <remarks>This is used for depop (to perform voice drop).</remarks>
+ public Array24<float> LastSamples;
+
+ /// <summary>
+ /// The current count of loop performed.
+ /// </summary>
+ public int LoopCount;
+
+ [StructLayout(LayoutKind.Sequential, Size = 1 * Constants.VoiceWaveBufferCount, Pack = 1)]
+ private struct WaveBufferValidArray { }
+
+ /// <summary>
+ /// Contains information of <see cref="WaveBuffer"/> validity.
+ /// </summary>
+ public Span<bool> IsWaveBufferValid => SpanHelpers.AsSpan<WaveBufferValidArray, bool>(ref _isWaveBufferValid);
+
+ /// <summary>
+ /// Mark the current <see cref="WaveBuffer"/> as played and switch to the next one.
+ /// </summary>
+ /// <param name="waveBuffer">The current <see cref="WaveBuffer"/></param>
+ /// <param name="waveBufferIndex">The wavebuffer index.</param>
+ /// <param name="waveBufferConsumed">The amount of wavebuffers consumed.</param>
+ /// <param name="playedSampleCount">The total count of sample played.</param>
+ public void MarkEndOfBufferWaveBufferProcessing(ref WaveBuffer waveBuffer, ref int waveBufferIndex, ref uint waveBufferConsumed, ref ulong playedSampleCount)
+ {
+ IsWaveBufferValid[waveBufferIndex++] = false;
+ LoopCount = 0;
+ waveBufferConsumed++;
+
+ if (waveBufferIndex >= Constants.VoiceWaveBufferCount)
+ {
+ waveBufferIndex = 0;
+ }
+
+ if (waveBuffer.IsEndOfStream)
+ {
+ playedSampleCount = 0;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs b/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs
new file mode 100644
index 00000000..53906c0c
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs
@@ -0,0 +1,99 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+using DspAddr = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ /// <summary>
+ /// A wavebuffer used for data source commands.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct WaveBuffer
+ {
+ /// <summary>
+ /// The DSP address of the sample data of the wavebuffer.
+ /// </summary>
+ public DspAddr Buffer;
+
+ /// <summary>
+ /// The DSP address of the context of the wavebuffer.
+ /// </summary>
+ /// <remarks>Only used by <see cref="SampleFormat.Adpcm"/>.</remarks>
+ public DspAddr Context;
+
+ /// <summary>
+ /// The size of the sample buffer data.
+ /// </summary>
+ public uint BufferSize;
+
+ /// <summary>
+ /// The size of the context buffer.
+ /// </summary>
+ public uint ContextSize;
+
+ /// <summary>
+ /// First sample to play on the wavebuffer.
+ /// </summary>
+ public uint StartSampleOffset;
+
+ /// <summary>
+ /// Last sample to play on the wavebuffer.
+ /// </summary>
+ public uint EndSampleOffset;
+
+ /// <summary>
+ /// First sample to play when looping the wavebuffer.
+ /// </summary>
+ /// <remarks>
+ /// If <see cref="LoopStartSampleOffset"/> or <see cref="LoopEndSampleOffset"/> is equal to zero,, it will default to <see cref="StartSampleOffset"/> and <see cref="EndSampleOffset"/>.
+ /// </remarks>
+ public uint LoopStartSampleOffset;
+
+ /// <summary>
+ /// Last sample to play when looping the wavebuffer.
+ /// </summary>
+ /// <remarks>
+ /// If <see cref="LoopStartSampleOffset"/> or <see cref="LoopEndSampleOffset"/> is equal to zero, it will default to <see cref="StartSampleOffset"/> and <see cref="EndSampleOffset"/>.
+ /// </remarks>
+ public uint LoopEndSampleOffset;
+
+ /// <summary>
+ /// The max loop count.
+ /// </summary>
+ public int LoopCount;
+
+ /// <summary>
+ /// Set to true if the wavebuffer is looping.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool Looping;
+
+ /// <summary>
+ /// Set to true if the wavebuffer is the end of stream.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsEndOfStream;
+
+ /// <summary>
+ /// Padding/Reserved.
+ /// </summary>
+ private ushort _padding;
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs b/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs
new file mode 100644
index 00000000..2aff4eb0
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs
@@ -0,0 +1,78 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Utils;
+using Ryujinx.Common;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ public class WorkBufferAllocator
+ {
+ public Memory<byte> BackingMemory { get; }
+
+ public ulong Offset { get; private set; }
+
+ public WorkBufferAllocator(Memory<byte> backingMemory)
+ {
+ BackingMemory = backingMemory;
+ }
+
+ public Memory<byte> Allocate(ulong size, int align)
+ {
+ Debug.Assert(align != 0);
+
+ if (size != 0)
+ {
+ ulong alignedOffset = BitUtils.AlignUp(Offset, align);
+
+ if (alignedOffset + size <= (ulong)BackingMemory.Length)
+ {
+ Memory<byte> result = BackingMemory.Slice((int)alignedOffset, (int)size);
+
+ Offset = alignedOffset + size;
+
+ // Clear the memory to be sure that is does not contain any garbage.
+ result.Span.Fill(0);
+
+ return result;
+ }
+ }
+
+ return Memory<byte>.Empty;
+ }
+
+ public Memory<T> Allocate<T>(ulong count, int align) where T: unmanaged
+ {
+ Memory<byte> allocatedMemory = Allocate((ulong)Unsafe.SizeOf<T>() * count, align);
+
+ if (allocatedMemory.IsEmpty)
+ {
+ return Memory<T>.Empty;
+ }
+
+ return SpanMemoryManager<T>.Cast(allocatedMemory);
+ }
+
+ public static ulong GetTargetSize<T>(ulong currentSize, ulong count, int align) where T: unmanaged
+ {
+ return BitUtils.AlignUp(currentSize, align) + (ulong)Unsafe.SizeOf<T>() * count;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs b/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs
new file mode 100644
index 00000000..29395e5c
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs
@@ -0,0 +1,84 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Device
+{
+ /// <summary>
+ /// Represents a virtual device used by IAudioDevice.
+ /// </summary>
+ public class VirtualDevice
+ {
+ /// <summary>
+ /// All the defined virtual devices.
+ /// </summary>
+ public static readonly VirtualDevice[] Devices = new VirtualDevice[4]
+ {
+ new VirtualDevice("AudioStereoJackOutput", 2),
+ new VirtualDevice("AudioBuiltInSpeakerOutput", 2),
+ new VirtualDevice("AudioTvOutput", 6),
+ new VirtualDevice("AudioUsbDeviceOutput", 2),
+ };
+
+ /// <summary>
+ /// The name of the <see cref="VirtualDevice"/>.
+ /// </summary>
+ public string Name { get; }
+
+ /// <summary>
+ /// The count of channels supported by the <see cref="VirtualDevice"/>.
+ /// </summary>
+ public uint ChannelCount { get; }
+
+ /// <summary>
+ /// The system master volume of the <see cref="VirtualDevice"/>.
+ /// </summary>
+ public float MasterVolume { get; private set; }
+
+ /// <summary>
+ /// Create a new <see cref="VirtualDevice"/> instance.
+ /// </summary>
+ /// <param name="name">The name of the <see cref="VirtualDevice"/>.</param>
+ /// <param name="channelCount">The count of channels supported by the <see cref="VirtualDevice"/>.</param>
+ private VirtualDevice(string name, uint channelCount)
+ {
+ Name = name;
+ ChannelCount = channelCount;
+ }
+
+ /// <summary>
+ /// Update the master volume of the <see cref="VirtualDevice"/>.
+ /// </summary>
+ /// <param name="volume">The new master volume.</param>
+ public void UpdateMasterVolume(float volume)
+ {
+ Debug.Assert(volume >= 0.0f && volume <= 1.0f);
+
+ MasterVolume = volume;
+ }
+
+ /// <summary>
+ /// Check if the <see cref="VirtualDevice"/> is a usb device.
+ /// </summary>
+ /// <returns>Returns true if the <see cref="VirtualDevice"/> is a usb device.</returns>
+ public bool IsUsbDevice()
+ {
+ return Name.Equals("AudioUsbDeviceOutput");
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs b/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs
new file mode 100644
index 00000000..b2669418
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs
@@ -0,0 +1,44 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Device
+{
+ /// <summary>
+ /// Represents a virtual device session used by IAudioDevice.
+ /// </summary>
+ public class VirtualDeviceSession
+ {
+ /// <summary>
+ /// The <see cref="VirtualDevice"/> associated to this session.
+ /// </summary>
+ public VirtualDevice Device { get; }
+
+ /// <summary>
+ /// The user volume of this session.
+ /// </summary>
+ public float Volume { get; set; }
+
+ /// <summary>
+ /// Create a new <see cref="VirtualDeviceSession"/> instance.
+ /// </summary>
+ /// <param name="virtualDevice">The <see cref="VirtualDevice"/> associated to this session.</param>
+ public VirtualDeviceSession(VirtualDevice virtualDevice)
+ {
+ Device = virtualDevice;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs b/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs
new file mode 100644
index 00000000..ed6806b2
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs
@@ -0,0 +1,79 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Collections.Generic;
+
+namespace Ryujinx.Audio.Renderer.Device
+{
+ /// <summary>
+ /// Represent an instance containing a registry of <see cref="VirtualDeviceSession"/>.
+ /// </summary>
+ public class VirtualDeviceSessionRegistry
+ {
+ /// <summary>
+ /// The session registry, used to store the sessions of a given AppletResourceId.
+ /// </summary>
+ private Dictionary<ulong, VirtualDeviceSession[]> _sessionsRegistry = new Dictionary<ulong, VirtualDeviceSession[]>();
+
+ /// <summary>
+ /// The default <see cref="VirtualDevice"/>.
+ /// </summary>
+ /// <remarks>This is used when the USB device is the default one on older revision.</remarks>
+ public VirtualDevice DefaultDevice => VirtualDevice.Devices[0];
+
+ /// <summary>
+ /// The current active <see cref="VirtualDevice"/>.
+ /// </summary>
+ // TODO: make this configurable
+ public VirtualDevice ActiveDevice = VirtualDevice.Devices[1];
+
+ /// <summary>
+ /// Get the associated <see cref="T:VirtualDeviceSession[]"/> from an AppletResourceId.
+ /// </summary>
+ /// <param name="resourceAppletId">The AppletResourceId used.</param>
+ /// <returns>The associated <see cref="T:VirtualDeviceSession[]"/> from an AppletResourceId.</returns>
+ public VirtualDeviceSession[] GetSessionByAppletResourceId(ulong resourceAppletId)
+ {
+ if (_sessionsRegistry.TryGetValue(resourceAppletId, out VirtualDeviceSession[] result))
+ {
+ return result;
+ }
+
+ result = CreateSessionsFromBehaviourContext();
+
+ _sessionsRegistry.Add(resourceAppletId, result);
+
+ return result;
+ }
+
+ /// <summary>
+ /// Create a new array of sessions for each <see cref="VirtualDevice"/>.
+ /// </summary>
+ /// <returns>A new array of sessions for each <see cref="VirtualDevice"/>.</returns>
+ private static VirtualDeviceSession[] CreateSessionsFromBehaviourContext()
+ {
+ VirtualDeviceSession[] virtualDeviceSession = new VirtualDeviceSession[VirtualDevice.Devices.Length];
+
+ for (int i = 0; i < virtualDeviceSession.Length; i++)
+ {
+ virtualDeviceSession[i] = new VirtualDeviceSession(VirtualDevice.Devices[i]);
+ }
+
+ return virtualDeviceSession;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs b/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs
new file mode 100644
index 00000000..f9f0811d
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs
@@ -0,0 +1,219 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.State;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp
+{
+ public static class AdpcmHelper
+ {
+ private const int FixedPointPrecision = 11;
+ private const int SamplesPerFrame = 14;
+ private const int NibblesPerFrame = SamplesPerFrame + 2;
+ private const int BytesPerFrame = 8;
+ private const int BitsPerFrame = BytesPerFrame * 8;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint GetAdpcmDataSize(int sampleCount)
+ {
+ Debug.Assert(sampleCount >= 0);
+
+ int frames = sampleCount / SamplesPerFrame;
+ int extraSize = 0;
+
+ if ((sampleCount % SamplesPerFrame) != 0)
+ {
+ extraSize = (sampleCount % SamplesPerFrame) / 2 + 1 + (sampleCount % 2);
+ }
+
+ return (uint)(BytesPerFrame * frames + extraSize);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetAdpcmOffsetFromSampleOffset(int sampleOffset)
+ {
+ Debug.Assert(sampleOffset >= 0);
+
+ return GetNibblesFromSampleCount(sampleOffset) / 2;
+ }
+
+ public static int NibbleToSample(int nibble)
+ {
+ int frames = nibble / NibblesPerFrame;
+ int extraNibbles = nibble % NibblesPerFrame;
+ int samples = SamplesPerFrame * frames;
+
+ return samples + extraNibbles - 2;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetNibblesFromSampleCount(int sampleCount)
+ {
+ byte headerSize = 0;
+
+ if ((sampleCount % SamplesPerFrame) != 0)
+ {
+ headerSize = 2;
+ }
+
+ return sampleCount % SamplesPerFrame + NibblesPerFrame * (sampleCount / SamplesPerFrame) + headerSize;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static short Saturate(int value)
+ {
+ if (value > short.MaxValue)
+ value = short.MaxValue;
+
+ if (value < short.MinValue)
+ value = short.MinValue;
+
+ return (short)value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Decode(Span<short> output, ReadOnlySpan<byte> input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan<short> coefficients, ref AdpcmLoopContext loopContext)
+ {
+ if (input.IsEmpty || endSampleOffset < startSampleOffset)
+ {
+ return 0;
+ }
+
+ byte predScale = (byte)loopContext.PredScale;
+ byte scale = (byte)(predScale & 0xF);
+ byte coefficientIndex = (byte)((predScale >> 4) & 0xF);
+ short history0 = loopContext.History0;
+ short history1 = loopContext.History1;
+ short coefficient0 = coefficients[coefficientIndex * 2 + 0];
+ short coefficient1 = coefficients[coefficientIndex * 2 + 1];
+
+ int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset);
+ int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset);
+ int remaining = decodedCount;
+ int outputBufferIndex = 0;
+ int inputIndex = 0;
+
+ ReadOnlySpan<byte> targetInput;
+
+ targetInput = input.Slice(nibbles / 2);
+
+ while (remaining > 0)
+ {
+ int samplesCount;
+
+ if (((uint)nibbles % NibblesPerFrame) == 0)
+ {
+ predScale = targetInput[inputIndex++];
+
+ scale = (byte)(predScale & 0xF);
+
+ coefficientIndex = (byte)((predScale >> 4) & 0xF);
+
+ coefficient0 = coefficients[coefficientIndex * 2 + 0];
+ coefficient1 = coefficients[coefficientIndex * 2 + 1];
+
+ nibbles += 2;
+
+ samplesCount = Math.Min(remaining, SamplesPerFrame);
+ }
+ else
+ {
+ samplesCount = 1;
+ }
+
+ int scaleFixedPoint = FixedPointHelper.ToFixed(1.0f, FixedPointPrecision) << scale;
+
+ if (samplesCount < SamplesPerFrame)
+ {
+ for (int i = 0; i < samplesCount; i++)
+ {
+ int value = targetInput[inputIndex];
+
+ int sample;
+
+ if ((nibbles & 1) != 0)
+ {
+ sample = (value << 28) >> 28;
+
+ inputIndex++;
+ }
+ else
+ {
+ sample = (value << 24) >> 28;
+ }
+
+ nibbles++;
+
+ int prediction = coefficient0 * history0 + coefficient1 * history1;
+
+ sample = FixedPointHelper.RoundUpAndToInt(sample * scaleFixedPoint + prediction, FixedPointPrecision);
+
+ short saturatedSample = Saturate(sample);
+
+ history1 = history0;
+ history0 = saturatedSample;
+
+ output[outputBufferIndex++] = saturatedSample;
+
+ remaining--;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < SamplesPerFrame / 2; i++)
+ {
+ int value = targetInput[inputIndex];
+
+ int sample0;
+ int sample1;
+
+ sample0 = (value << 24) >> 28;
+ sample1 = (value << 28) >> 28;
+
+ inputIndex++;
+
+ int prediction0 = coefficient0 * history0 + coefficient1 * history1;
+ sample0 = FixedPointHelper.RoundUpAndToInt(sample0 * scaleFixedPoint + prediction0, FixedPointPrecision);
+ short saturatedSample0 = Saturate(sample0);
+
+ int prediction1 = coefficient0 * saturatedSample0 + coefficient1 * history0;
+ sample1 = FixedPointHelper.RoundUpAndToInt(sample1 * scaleFixedPoint + prediction1, FixedPointPrecision);
+ short saturatedSample1 = Saturate(sample1);
+
+ history1 = saturatedSample0;
+ history0 = saturatedSample1;
+
+ output[outputBufferIndex++] = saturatedSample0;
+ output[outputBufferIndex++] = saturatedSample1;
+ }
+
+ nibbles += SamplesPerFrame;
+ remaining -= SamplesPerFrame;
+ }
+ }
+
+ loopContext.PredScale = predScale;
+ loopContext.History0 = history0;
+ loopContext.History1 = history1;
+
+ return decodedCount;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs b/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs
new file mode 100644
index 00000000..2c97a0c1
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs
@@ -0,0 +1,255 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Renderer.Dsp.Command;
+using Ryujinx.Audio.Renderer.Utils;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using System;
+using System.Threading;
+
+namespace Ryujinx.Audio.Renderer.Dsp
+{
+ public class AudioProcessor : IDisposable
+ {
+ private const int MaxBufferedFrames = 5;
+ private const int TargetBufferedFrames = 3;
+
+ private enum MailboxMessage : uint
+ {
+ Start,
+ Stop,
+ RenderStart,
+ RenderEnd
+ }
+
+ private class RendererSession
+ {
+ public CommandList CommandList;
+ public int RenderingLimit;
+ public ulong AppletResourceId;
+ }
+
+ private Mailbox<MailboxMessage> _mailbox;
+ private RendererSession[] _sessionCommandList;
+ private Thread _workerThread;
+
+ public IHardwareDevice[] OutputDevices { get; private set; }
+
+ private long _lastTime;
+ private long _playbackEnds;
+ private ManualResetEvent _event;
+
+ public AudioProcessor()
+ {
+ _event = new ManualResetEvent(false);
+ }
+
+ private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
+ {
+ // Get the real device driver (In case the compat layer is on top of it).
+ deviceDriver = deviceDriver.GetRealDeviceDriver();
+
+ if (deviceDriver.SupportsChannelCount(6))
+ {
+ return 6;
+ }
+ else
+ {
+ // NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible.
+ return 2;
+ }
+ }
+
+ public void Start(IHardwareDeviceDriver deviceDriver)
+ {
+ OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
+
+ // TODO: Before enabling this, we need up-mixing from stereo to 5.1.
+ // uint channelCount = GetHardwareChannelCount(deviceDriver);
+ uint channelCount = 2;
+
+ for (int i = 0; i < OutputDevices.Length; i++)
+ {
+ // TODO: Don't hardcode sample rate.
+ OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate);
+ }
+
+ _mailbox = new Mailbox<MailboxMessage>();
+ _sessionCommandList = new RendererSession[Constants.AudioRendererSessionCountMax];
+ _event.Reset();
+ _lastTime = PerformanceCounter.ElapsedNanoseconds;
+
+ StartThread();
+
+ _mailbox.SendMessage(MailboxMessage.Start);
+
+ if (_mailbox.ReceiveResponse() != MailboxMessage.Start)
+ {
+ throw new InvalidOperationException("Audio Processor Start response was invalid!");
+ }
+ }
+
+ public void Stop()
+ {
+ _mailbox.SendMessage(MailboxMessage.Stop);
+
+ if (_mailbox.ReceiveResponse() != MailboxMessage.Stop)
+ {
+ throw new InvalidOperationException("Audio Processor Stop response was invalid!");
+ }
+
+ foreach (IHardwareDevice device in OutputDevices)
+ {
+ device.Dispose();
+ }
+ }
+
+ public void Send(int sessionId, CommandList commands, int renderingLimit, ulong appletResourceId)
+ {
+ _sessionCommandList[sessionId] = new RendererSession
+ {
+ CommandList = commands,
+ RenderingLimit = renderingLimit,
+ AppletResourceId = appletResourceId
+ };
+ }
+
+ public void Signal()
+ {
+ _mailbox.SendMessage(MailboxMessage.RenderStart);
+ }
+
+ public void Wait()
+ {
+ if (_mailbox.ReceiveResponse() != MailboxMessage.RenderEnd)
+ {
+ throw new InvalidOperationException("Audio Processor Wait response was invalid!");
+ }
+
+ long increment = Constants.AudioProcessorMaxUpdateTimeTarget;
+
+ long timeNow = PerformanceCounter.ElapsedNanoseconds;
+
+ if (timeNow > _playbackEnds)
+ {
+ // Playback has restarted.
+ _playbackEnds = timeNow;
+ }
+
+ _playbackEnds += increment;
+
+ // The number of frames we are behind where the timer says we should be.
+ long framesBehind = (timeNow - _lastTime) / increment;
+
+ // The number of frames yet to play on the backend.
+ long bufferedFrames = (_playbackEnds - timeNow) / increment + framesBehind;
+
+ // If we've entered a situation where a lot of buffers will be queued on the backend,
+ // Skip some audio frames so that playback can catch up.
+ if (bufferedFrames > MaxBufferedFrames)
+ {
+ // Skip a few frames so that we're not too far behind. (the target number of frames)
+ _lastTime += increment * (bufferedFrames - TargetBufferedFrames);
+ }
+
+ while (timeNow < _lastTime + increment)
+ {
+ _event.WaitOne(1);
+
+ timeNow = PerformanceCounter.ElapsedNanoseconds;
+ }
+
+ _lastTime += increment;
+ }
+
+ private void StartThread()
+ {
+ _workerThread = new Thread(Work)
+ {
+ Name = "AudioProcessor.Worker"
+ };
+
+ _workerThread.Start();
+ }
+
+ private void Work()
+ {
+ if (_mailbox.ReceiveMessage() != MailboxMessage.Start)
+ {
+ throw new InvalidOperationException("Audio Processor Start message was invalid!");
+ }
+
+ _mailbox.SendResponse(MailboxMessage.Start);
+ _mailbox.SendResponse(MailboxMessage.RenderEnd);
+
+ Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio processor");
+
+ while (true)
+ {
+ MailboxMessage message = _mailbox.ReceiveMessage();
+
+ if (message == MailboxMessage.Stop)
+ {
+ break;
+ }
+
+ if (message == MailboxMessage.RenderStart)
+ {
+ long startTicks = PerformanceCounter.ElapsedNanoseconds;
+
+ for (int i = 0; i < _sessionCommandList.Length; i++)
+ {
+ if (_sessionCommandList[i] != null)
+ {
+ _sessionCommandList[i].CommandList.Process(OutputDevices[i]);
+ _sessionCommandList[i] = null;
+ }
+ }
+
+ long endTicks = PerformanceCounter.ElapsedNanoseconds;
+
+ long elapsedTime = endTicks - startTicks;
+
+ if (Constants.AudioProcessorMaxUpdateTime < elapsedTime)
+ {
+ Logger.Debug?.Print(LogClass.AudioRenderer, $"DSP too slow (exceeded by {elapsedTime - Constants.AudioProcessorMaxUpdateTime}ns)");
+ }
+
+ _mailbox.SendResponse(MailboxMessage.RenderEnd);
+ }
+ }
+
+ Logger.Info?.Print(LogClass.AudioRenderer, "Stopping audio processor");
+ _mailbox.SendResponse(MailboxMessage.Stop);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _event.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs
new file mode 100644
index 00000000..bea6215d
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs
@@ -0,0 +1,94 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using System;
+using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class AdpcmDataSourceCommandVersion1 : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.AdpcmDataSourceVersion1;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ushort OutputBufferIndex { get; }
+ public uint SampleRate { get; }
+
+ public float Pitch { get; }
+
+ public WaveBuffer[] WaveBuffers { get; }
+
+ public Memory<VoiceUpdateState> State { get; }
+
+ public ulong AdpcmParameter { get; }
+ public ulong AdpcmParameterSize { get; }
+
+ public DecodingBehaviour DecodingBehaviour { get; }
+
+ public AdpcmDataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ OutputBufferIndex = outputBufferIndex;
+ SampleRate = serverState.SampleRate;
+ Pitch = serverState.Pitch;
+
+ WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
+
+ for (int i = 0; i < WaveBuffers.Length; i++)
+ {
+ ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i];
+
+ WaveBuffers[i] = voiceWaveBuffer.ToCommon(1);
+ }
+
+ AdpcmParameter = serverState.DataSourceStateAddressInfo.GetReference(true);
+ AdpcmParameterSize = serverState.DataSourceStateAddressInfo.Size;
+ State = state;
+ DecodingBehaviour = serverState.DecodingBehaviour;
+ }
+
+ public void Process(CommandList context)
+ {
+ Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation()
+ {
+ State = State,
+ SourceSampleRate = SampleRate,
+ SampleFormat = SampleFormat.Adpcm,
+ Pitch = Pitch,
+ DecodingBehaviour = DecodingBehaviour,
+ WaveBuffers = WaveBuffers,
+ ExtraParameter = AdpcmParameter,
+ ExtraParameterSize = AdpcmParameterSize,
+ ChannelIndex = 0,
+ ChannelCount = 1,
+ };
+
+ DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
new file mode 100644
index 00000000..5ff3a6b4
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
@@ -0,0 +1,205 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Memory;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader;
+using CpuAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class AuxiliaryBufferCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.AuxiliaryBuffer;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public uint InputBufferIndex { get; }
+ public uint OutputBufferIndex { get; }
+
+ public AuxiliaryBufferAddresses BufferInfo { get; }
+
+ public CpuAddress InputBuffer { get; }
+ public CpuAddress OutputBuffer { get; }
+ public uint CountMax { get; }
+ public uint UpdateCount { get; }
+ public uint WriteOffset { get; }
+
+ public bool IsEffectEnabled { get; }
+
+ public AuxiliaryBufferCommand(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset,
+ ref AuxiliaryBufferAddresses sendBufferInfo, bool isEnabled, uint countMax,
+ CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ InputBufferIndex = bufferOffset + inputBufferOffset;
+ OutputBufferIndex = bufferOffset + outputBufferOffset;
+ BufferInfo = sendBufferInfo;
+ InputBuffer = inputBuffer;
+ OutputBuffer = outputBuffer;
+ CountMax = countMax;
+ UpdateCount = updateCount;
+ WriteOffset = writeOffset;
+ IsEffectEnabled = isEnabled;
+ }
+
+ private uint Read(IVirtualMemoryManager memoryManager, ulong bufferAddress, uint countMax, Span<int> outBuffer, uint count, uint readOffset, uint updateCount)
+ {
+ if (countMax == 0 || bufferAddress == 0)
+ {
+ return 0;
+ }
+
+ uint targetReadOffset = readOffset + AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo);
+
+ if (targetReadOffset > countMax)
+ {
+ return 0;
+ }
+
+ uint remaining = count;
+
+ uint outBufferOffset = 0;
+
+ while (remaining != 0)
+ {
+ uint countToWrite = Math.Min(countMax - targetReadOffset, remaining);
+
+ memoryManager.Read(bufferAddress + targetReadOffset * sizeof(int), MemoryMarshal.Cast<int, byte>(outBuffer.Slice((int)outBufferOffset, (int)countToWrite)));
+
+ targetReadOffset = (targetReadOffset + countToWrite) % countMax;
+ remaining -= countToWrite;
+ outBufferOffset += countToWrite;
+ }
+
+ if (updateCount != 0)
+ {
+ uint newReadOffset = (AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo) + updateCount) % countMax;
+
+ AuxiliaryBufferInfo.SetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo, newReadOffset);
+ }
+
+ return count;
+ }
+
+ private uint Write(IVirtualMemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan<int> buffer, uint count, uint writeOffset, uint updateCount)
+ {
+ if (countMax == 0 || outBufferAddress == 0)
+ {
+ return 0;
+ }
+
+ uint targetWriteOffset = writeOffset + AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo);
+
+ if (targetWriteOffset > countMax)
+ {
+ return 0;
+ }
+
+ uint remaining = count;
+
+ uint inBufferOffset = 0;
+
+ while (remaining != 0)
+ {
+ uint countToWrite = Math.Min(countMax - targetWriteOffset, remaining);
+
+ memoryManager.Write(outBufferAddress + targetWriteOffset * sizeof(int), MemoryMarshal.Cast<int, byte>(buffer.Slice((int)inBufferOffset, (int)countToWrite)));
+
+ targetWriteOffset = (targetWriteOffset + countToWrite) % countMax;
+ remaining -= countToWrite;
+ inBufferOffset += countToWrite;
+ }
+
+ if (updateCount != 0)
+ {
+ uint newWriteOffset = (AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo) + updateCount) % countMax;
+
+ AuxiliaryBufferInfo.SetWriteOffset(memoryManager, BufferInfo.SendBufferInfo, newWriteOffset);
+ }
+
+ return count;
+ }
+
+ public void Process(CommandList context)
+ {
+ Span<float> inputBuffer = context.GetBuffer((int)InputBufferIndex);
+ Span<float> outputBuffer = context.GetBuffer((int)OutputBufferIndex);
+
+ if (IsEffectEnabled)
+ {
+ Span<int> inputBufferInt = MemoryMarshal.Cast<float, int>(inputBuffer);
+ Span<int> outputBufferInt = MemoryMarshal.Cast<float, int>(outputBuffer);
+
+ // Convert input data to the target format for user (int)
+ DataSourceHelper.ToInt(inputBufferInt, inputBuffer, outputBuffer.Length);
+
+ // Send the input to the user
+ Write(context.MemoryManager, OutputBuffer, CountMax, inputBufferInt, context.SampleCount, WriteOffset, UpdateCount);
+
+ // Convert back to float just in case it's reused
+ DataSourceHelper.ToFloat(inputBuffer, inputBufferInt, inputBuffer.Length);
+
+ // Retrieve the input from user
+ uint readResult = Read(context.MemoryManager, InputBuffer, CountMax, outputBufferInt, context.SampleCount, WriteOffset, UpdateCount);
+
+ // Convert the outputBuffer back to the target format of the renderer (float)
+ DataSourceHelper.ToFloat(outputBuffer, outputBufferInt, outputBuffer.Length);
+
+ if (readResult != context.SampleCount)
+ {
+ outputBuffer.Slice((int)readResult, (int)context.SampleCount - (int)readResult).Fill(0);
+ }
+ }
+ else
+ {
+ ZeroFill(context.MemoryManager, BufferInfo.SendBufferInfo, Unsafe.SizeOf<AuxiliaryBufferInfo>());
+ ZeroFill(context.MemoryManager, BufferInfo.ReturnBufferInfo, Unsafe.SizeOf<AuxiliaryBufferInfo>());
+
+ if (InputBufferIndex != OutputBufferIndex)
+ {
+ inputBuffer.CopyTo(outputBuffer);
+ }
+ }
+ }
+
+ private static void ZeroFill(IVirtualMemoryManager memoryManager, ulong address, int size)
+ {
+ ulong endAddress = address + (ulong)size;
+
+ while (address + 7UL < endAddress)
+ {
+ memoryManager.Write(address, 0UL);
+ address += 8;
+ }
+
+ while (address < endAddress)
+ {
+ memoryManager.Write(address, (byte)0);
+ address++;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs
new file mode 100644
index 00000000..580eb732
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs
@@ -0,0 +1,89 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class BiquadFilterCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.BiquadFilter;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public BiquadFilterParameter Parameter { get; }
+ public Memory<BiquadFilterState> BiquadFilterState { get; }
+ public int InputBufferIndex { get; }
+ public int OutputBufferIndex { get; }
+ public bool NeedInitialization { get; }
+
+ public BiquadFilterCommand(int baseIndex, ref BiquadFilterParameter filter, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId)
+ {
+ Parameter = filter;
+ BiquadFilterState = biquadFilterStateMemory;
+ InputBufferIndex = baseIndex + inputBufferOffset;
+ OutputBufferIndex = baseIndex + outputBufferOffset;
+ NeedInitialization = needInitialization;
+
+ Enabled = true;
+ NodeId = nodeId;
+ }
+
+ private void ProcessBiquadFilter(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
+ {
+ const int fixedPointPrecisionForParameter = 14;
+
+ 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);
+
+ ref BiquadFilterState state = ref BiquadFilterState.Span[0];
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i];
+ float output = input * a0 + state.Z1;
+
+ state.Z1 = input * a1 + output * b1 + state.Z2;
+ state.Z2 = input * a2 + output * b2;
+
+ outputBuffer[i] = output;
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ Span<float> outputBuffer = context.GetBuffer(InputBufferIndex);
+
+ if (NeedInitialization)
+ {
+ BiquadFilterState.Span[0] = new BiquadFilterState();
+ }
+
+ ProcessBiquadFilter(outputBuffer, outputBuffer, context.SampleCount);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs
new file mode 100644
index 00000000..e4c635d5
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs
@@ -0,0 +1,91 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Parameter.Sink;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class CircularBufferSinkCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.CircularBufferSink;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ushort[] Input { get; }
+ public uint InputCount { get; }
+
+ public ulong CircularBuffer { get; }
+ public ulong CircularBufferSize { get; }
+ public ulong CurrentOffset { get; }
+
+ public CircularBufferSinkCommand(uint bufferOffset, ref CircularBufferParameter parameter, ref AddressInfo circularBufferAddressInfo, uint currentOffset, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ Input = new ushort[Constants.ChannelCountMax];
+ InputCount = parameter.InputCount;
+
+ for (int i = 0; i < InputCount; i++)
+ {
+ Input[i] = (ushort)(bufferOffset + parameter.Input[i]);
+ }
+
+ CircularBuffer = circularBufferAddressInfo.GetReference(true);
+ CircularBufferSize = parameter.BufferSize;
+ CurrentOffset = currentOffset;
+
+ Debug.Assert(CircularBuffer != 0);
+ }
+
+ public void Process(CommandList context)
+ {
+ const int targetChannelCount = 2;
+
+ ulong currentOffset = CurrentOffset;
+
+ if (CircularBufferSize > 0)
+ {
+ for (int i = 0; i < InputCount; i++)
+ {
+ ReadOnlySpan<float> inputBuffer = context.GetBuffer(Input[i]);
+
+ ulong targetOffset = CircularBuffer + currentOffset;
+
+ for (int y = 0; y < context.SampleCount; y++)
+ {
+ context.MemoryManager.Write(targetOffset + (ulong)y * targetChannelCount, PcmHelper.Saturate(inputBuffer[y]));
+ }
+
+ currentOffset += context.SampleCount * targetChannelCount;
+
+ if (currentOffset >= CircularBufferSize)
+ {
+ currentOffset = 0;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs
new file mode 100644
index 00000000..ba9a0357
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs
@@ -0,0 +1,41 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class ClearMixBufferCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.ClearMixBuffer;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ClearMixBufferCommand(int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ }
+
+ public void Process(CommandList context)
+ {
+ context.Buffers.Span.Fill(0);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs
new file mode 100644
index 00000000..fee90192
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs
@@ -0,0 +1,124 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Renderer.Server;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Memory;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class CommandList
+ {
+ public ulong StartTime { get; private set; }
+ public ulong EndTime { get; private set; }
+ public uint SampleCount { get; }
+ public uint SampleRate { get; }
+
+ public Memory<float> Buffers { get; }
+ public uint BufferCount { get; }
+
+ public List<ICommand> Commands { get; }
+
+ public IVirtualMemoryManager MemoryManager { get; }
+
+ public IHardwareDevice OutputDevice { get; private set; }
+
+ public CommandList(AudioRenderSystem renderSystem) : this(renderSystem.MemoryManager,
+ renderSystem.GetMixBuffer(),
+ renderSystem.GetSampleCount(),
+ renderSystem.GetSampleRate(),
+ renderSystem.GetMixBufferCount(),
+ renderSystem.GetVoiceChannelCountMax())
+ {
+ }
+
+ public CommandList(IVirtualMemoryManager memoryManager, Memory<float> mixBuffer, uint sampleCount, uint sampleRate, uint mixBufferCount, uint voiceChannelCountMax)
+ {
+ SampleCount = sampleCount;
+ SampleRate = sampleRate;
+ BufferCount = mixBufferCount + voiceChannelCountMax;
+ Buffers = mixBuffer;
+ Commands = new List<ICommand>();
+ MemoryManager = memoryManager;
+ }
+
+ public void AddCommand(ICommand command)
+ {
+ Commands.Add(command);
+ }
+
+ public void AddCommand<T>(T command) where T : unmanaged, ICommand
+ {
+ throw new NotImplementedException();
+ }
+
+ public Memory<float> GetBufferMemory(int index)
+ {
+ return Buffers.Slice(index * (int)SampleCount, (int)SampleCount);
+ }
+
+ public Span<float> GetBuffer(int index)
+ {
+ return Buffers.Span.Slice(index * (int)SampleCount, (int)SampleCount);
+ }
+
+ public ulong GetTimeElapsedSinceDspStartedProcessing()
+ {
+ return (ulong)PerformanceCounter.ElapsedNanoseconds - StartTime;
+ }
+
+ public void Process(IHardwareDevice outputDevice)
+ {
+ OutputDevice = outputDevice;
+
+ StartTime = (ulong)PerformanceCounter.ElapsedNanoseconds;
+
+ foreach (ICommand command in Commands)
+ {
+ if (command.Enabled)
+ {
+ bool shouldMeter = command.ShouldMeter();
+
+ long startTime = 0;
+
+ if (shouldMeter)
+ {
+ startTime = PerformanceCounter.ElapsedNanoseconds;
+ }
+
+ command.Process(this);
+
+ if (shouldMeter)
+ {
+ ulong effectiveElapsedTime = (ulong)(PerformanceCounter.ElapsedNanoseconds - startTime);
+
+ if (effectiveElapsedTime > command.EstimatedProcessingTime)
+ {
+ Logger.Warning?.Print(LogClass.AudioRenderer, $"Command {command.GetType().Name} took {effectiveElapsedTime}ns (expected {command.EstimatedProcessingTime}ns)");
+ }
+ }
+ }
+ }
+
+ EndTime = (ulong)PerformanceCounter.ElapsedNanoseconds;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
new file mode 100644
index 00000000..8ff1c581
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
@@ -0,0 +1,49 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public enum CommandType : byte
+ {
+ Invalid,
+ PcmInt16DataSourceVersion1,
+ PcmInt16DataSourceVersion2,
+ PcmFloatDataSourceVersion1,
+ PcmFloatDataSourceVersion2,
+ AdpcmDataSourceVersion1,
+ AdpcmDataSourceVersion2,
+ Volume,
+ VolumeRamp,
+ BiquadFilter,
+ Mix,
+ MixRamp,
+ MixRampGrouped,
+ DepopPrepare,
+ DepopForMixBuffers,
+ Delay,
+ Upsample,
+ DownMixSurroundToStereo,
+ AuxiliaryBuffer,
+ DeviceSink,
+ CircularBufferSink,
+ Reverb,
+ Reverb3d,
+ Performance,
+ ClearMixBuffer,
+ CopyMixBuffer
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs
new file mode 100644
index 00000000..2dc6f04b
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class CopyMixBufferCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.CopyMixBuffer;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ public CopyMixBufferCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = (ushort)inputBufferIndex;
+ OutputBufferIndex = (ushort)outputBufferIndex;
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ inputBuffer.CopyTo(outputBuffer);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs
new file mode 100644
index 00000000..cbc48be7
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs
@@ -0,0 +1,127 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using System;
+using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class DataSourceVersion2Command : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType { get; }
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ushort OutputBufferIndex { get; }
+ public uint SampleRate { get; }
+
+ public float Pitch { get; }
+
+ public WaveBuffer[] WaveBuffers { get; }
+
+ public Memory<VoiceUpdateState> State { get; }
+
+ public ulong ExtraParameter { get; }
+ public ulong ExtraParameterSize { get; }
+
+ public uint ChannelIndex { get; }
+
+ public uint ChannelCount { get; }
+
+ public DecodingBehaviour DecodingBehaviour { get; }
+
+ public SampleFormat SampleFormat { get; }
+
+ public SampleRateConversionQuality SrcQuality { get; }
+
+ public DataSourceVersion2Command(ref Server.Voice.VoiceState serverState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ ChannelIndex = channelIndex;
+ ChannelCount = serverState.ChannelsCount;
+ SampleFormat = serverState.SampleFormat;
+ SrcQuality = serverState.SrcQuality;
+ CommandType = GetCommandTypeBySampleFormat(SampleFormat);
+
+ OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex);
+ SampleRate = serverState.SampleRate;
+ Pitch = serverState.Pitch;
+
+ WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
+
+ for (int i = 0; i < WaveBuffers.Length; i++)
+ {
+ ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i];
+
+ WaveBuffers[i] = voiceWaveBuffer.ToCommon(2);
+ }
+
+ if (SampleFormat == SampleFormat.Adpcm)
+ {
+ ExtraParameter = serverState.DataSourceStateAddressInfo.GetReference(true);
+ ExtraParameterSize = serverState.DataSourceStateAddressInfo.Size;
+ }
+
+ State = state;
+ DecodingBehaviour = serverState.DecodingBehaviour;
+ }
+
+ private static CommandType GetCommandTypeBySampleFormat(SampleFormat sampleFormat)
+ {
+ switch (sampleFormat)
+ {
+ case SampleFormat.Adpcm:
+ return CommandType.AdpcmDataSourceVersion2;
+ case SampleFormat.PcmInt16:
+ return CommandType.PcmInt16DataSourceVersion2;
+ case SampleFormat.PcmFloat:
+ return CommandType.PcmFloatDataSourceVersion2;
+ default:
+ throw new NotImplementedException($"{sampleFormat}");
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation()
+ {
+ State = State,
+ SourceSampleRate = SampleRate,
+ SampleFormat = SampleFormat,
+ Pitch = Pitch,
+ DecodingBehaviour = DecodingBehaviour,
+ WaveBuffers = WaveBuffers,
+ ExtraParameter = ExtraParameter,
+ ExtraParameterSize = ExtraParameterSize,
+ ChannelIndex = (int)ChannelIndex,
+ ChannelCount = (int)ChannelCount,
+ SrcQuality = SrcQuality
+ };
+
+ DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs
new file mode 100644
index 00000000..4ceebcf6
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs
@@ -0,0 +1,272 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Server.Effect;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class DelayCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Delay;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public DelayParameter Parameter => _parameter;
+ public Memory<DelayState> State { get; }
+ public ulong WorkBuffer { get; }
+ public ushort[] OutputBufferIndices { get; }
+ public ushort[] InputBufferIndices { get; }
+ public bool IsEffectEnabled { get; }
+
+ private DelayParameter _parameter;
+
+ private const int FixedPointPrecision = 14;
+
+ public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, ulong workBuffer, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ _parameter = parameter;
+ State = state;
+ WorkBuffer = workBuffer;
+
+ IsEffectEnabled = isEnabled;
+
+ InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
+ OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+ }
+ }
+
+ private void ProcessDelayMono(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
+ {
+ ref DelayState state = ref State.Span[0];
+
+ float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
+ float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
+ float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
+ float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i] * 64;
+ float delayLineValue = state.DelayLines[0].Read();
+
+ float lowPassResult = input * inGain + delayLineValue * feedbackGain * state.LowPassBaseGain + state.LowPassZ[0] * state.LowPassFeedbackGain;
+
+ state.LowPassZ[0] = lowPassResult;
+
+ state.DelayLines[0].Update(lowPassResult);
+
+ outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
+ }
+ }
+
+ private void ProcessDelayStereo(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
+ {
+ ref DelayState state = ref State.Span[0];
+
+ float[] channelInput = new float[Parameter.ChannelCount];
+ float[] delayLineValues = new float[Parameter.ChannelCount];
+ float[] temp = new float[Parameter.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);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ for (int j = 0; j < Parameter.ChannelCount; j++)
+ {
+ channelInput[j] = inputBuffers[j].Span[i] * 64;
+ delayLineValues[j] = state.DelayLines[j].Read();
+ }
+
+ 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 < Parameter.ChannelCount; j++)
+ {
+ float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
+
+ state.LowPassZ[j] = lowPassResult;
+ state.DelayLines[j].Update(lowPassResult);
+
+ outputBuffers[j].Span[i] = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
+ }
+ }
+ }
+
+ private void ProcessDelayQuadraphonic(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
+ {
+ ref DelayState state = ref State.Span[0];
+
+ float[] channelInput = new float[Parameter.ChannelCount];
+ float[] delayLineValues = new float[Parameter.ChannelCount];
+ float[] temp = new float[Parameter.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);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ for (int j = 0; j < Parameter.ChannelCount; j++)
+ {
+ channelInput[j] = inputBuffers[j].Span[i] * 64;
+ delayLineValues[j] = state.DelayLines[j].Read();
+ }
+
+ 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 < Parameter.ChannelCount; j++)
+ {
+ float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
+
+ state.LowPassZ[j] = lowPassResult;
+ state.DelayLines[j].Update(lowPassResult);
+
+ outputBuffers[j].Span[i] = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
+ }
+ }
+ }
+
+ private void ProcessDelaySurround(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
+ {
+ ref DelayState state = ref State.Span[0];
+
+ float[] channelInput = new float[Parameter.ChannelCount];
+ float[] delayLineValues = new float[Parameter.ChannelCount];
+ float[] temp = new float[Parameter.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);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ for (int j = 0; j < Parameter.ChannelCount; j++)
+ {
+ channelInput[j] = inputBuffers[j].Span[i] * 64;
+ delayLineValues[j] = state.DelayLines[j].Read();
+ }
+
+ 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 < Parameter.ChannelCount; j++)
+ {
+ float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
+
+ state.LowPassZ[j] = lowPassResult;
+ state.DelayLines[j].Update(lowPassResult);
+
+ outputBuffers[j].Span[i] = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
+ }
+ }
+ }
+
+ private void ProcessDelay(CommandList context)
+ {
+ Debug.Assert(Parameter.IsChannelCountValid());
+
+ if (IsEffectEnabled && Parameter.IsChannelCountValid())
+ {
+ ReadOnlyMemory<float>[] inputBuffers = new ReadOnlyMemory<float>[Parameter.ChannelCount];
+ Memory<float>[] outputBuffers = new Memory<float>[Parameter.ChannelCount];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]);
+ outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]);
+ }
+
+ switch (Parameter.ChannelCount)
+ {
+ case 1:
+ ProcessDelayMono(outputBuffers[0].Span, inputBuffers[0].Span, context.SampleCount);
+ break;
+ case 2:
+ ProcessDelayStereo(outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 4:
+ ProcessDelayQuadraphonic(outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 6:
+ ProcessDelaySurround(outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ default:
+ throw new NotImplementedException($"{Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ if (InputBufferIndices[i] != OutputBufferIndices[i])
+ {
+ context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
+ }
+ }
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ref DelayState state = ref State.Span[0];
+
+ if (IsEffectEnabled)
+ {
+ if (Parameter.Status == UsageState.Invalid)
+ {
+ state = new DelayState(ref _parameter, WorkBuffer);
+ }
+ else if (Parameter.Status == UsageState.New)
+ {
+ state.UpdateParameter(ref _parameter);
+ }
+ }
+
+ ProcessDelay(context);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs
new file mode 100644
index 00000000..c6feacfb
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs
@@ -0,0 +1,103 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class DepopForMixBuffersCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.DepopForMixBuffers;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public uint MixBufferOffset { get; }
+
+ public uint MixBufferCount { get; }
+
+ public float Decay { get; }
+
+ public Memory<float> DepopBuffer { get; }
+
+ private const int FixedPointPrecisionForDecay = 15;
+
+ public DepopForMixBuffersCommand(Memory<float> depopBuffer, uint bufferOffset, uint mixBufferCount, int nodeId, uint sampleRate)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ MixBufferOffset = bufferOffset;
+ MixBufferCount = mixBufferCount;
+ DepopBuffer = depopBuffer;
+
+ if (sampleRate == 48000)
+ {
+ Decay = 0.962189f;
+ }
+ else // if (sampleRate == 32000)
+ {
+ Decay = 0.943695f;
+ }
+ }
+
+ private float ProcessDepopMix(Span<float> buffer, float depopValue, uint sampleCount)
+ {
+ if (depopValue <= 0)
+ {
+ for (int i = 0; i < sampleCount; i++)
+ {
+ depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue);
+
+ buffer[i] -= depopValue;
+ }
+
+ return -depopValue;
+ }
+ else
+ {
+ for (int i = 0; i < sampleCount; i++)
+ {
+ depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue);
+
+ buffer[i] += depopValue;
+ }
+
+ return depopValue;
+ }
+
+ }
+
+ public void Process(CommandList context)
+ {
+ uint bufferCount = Math.Min(MixBufferOffset + MixBufferCount, context.BufferCount);
+
+ for (int i = (int)MixBufferOffset; i < bufferCount; i++)
+ {
+ float depopValue = DepopBuffer.Span[i];
+ if (depopValue != 0)
+ {
+ Span<float> buffer = context.GetBuffer(i);
+
+ DepopBuffer.Span[i] = ProcessDepopMix(buffer, depopValue, context.SampleCount);
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs
new file mode 100644
index 00000000..35275229
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs
@@ -0,0 +1,72 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class DepopPrepareCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.DepopPrepare;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public uint MixBufferCount { get; }
+
+ public ushort[] OutputBufferIndices { get; }
+
+ public Memory<VoiceUpdateState> State { get; }
+ public Memory<float> DepopBuffer { get; }
+
+ public DepopPrepareCommand(Memory<VoiceUpdateState> state, Memory<float> depopBuffer, uint mixBufferCount, uint bufferOffset, int nodeId, bool enabled)
+ {
+ Enabled = enabled;
+ NodeId = nodeId;
+ MixBufferCount = mixBufferCount;
+
+ OutputBufferIndices = new ushort[Constants.MixBufferCountMax];
+
+ for (int i = 0; i < Constants.MixBufferCountMax; i++)
+ {
+ OutputBufferIndices[i] = (ushort)(bufferOffset + i);
+ }
+
+ State = state;
+ DepopBuffer = depopBuffer;
+ }
+
+ public void Process(CommandList context)
+ {
+ ref VoiceUpdateState state = ref State.Span[0];
+
+ for (int i = 0; i < MixBufferCount; i++)
+ {
+ if (state.LastSamples[i] != 0)
+ {
+ DepopBuffer.Span[OutputBufferIndices[i]] += state.LastSamples[i];
+
+ state.LastSamples[i] = 0;
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs
new file mode 100644
index 00000000..28b4a5f1
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs
@@ -0,0 +1,108 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Renderer.Server.Sink;
+using System;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class DeviceSinkCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.DeviceSink;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public string DeviceName { get; }
+
+ public int SessionId { get; }
+
+ public uint InputCount { get; }
+ public ushort[] InputBufferIndices { get; }
+
+ public Memory<float> Buffers { get; }
+
+ public DeviceSinkCommand(uint bufferOffset, DeviceSink sink, int sessionId, Memory<float> buffers, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ DeviceName = Encoding.ASCII.GetString(sink.Parameter.DeviceName).TrimEnd('\0');
+ SessionId = sessionId;
+ InputCount = sink.Parameter.InputCount;
+ InputBufferIndices = new ushort[InputCount];
+
+ for (int i = 0; i < InputCount; i++)
+ {
+ InputBufferIndices[i] = (ushort)(bufferOffset + sink.Parameter.Input[i]);
+ }
+
+ if (sink.UpsamplerState != null)
+ {
+ Buffers = sink.UpsamplerState.OutputBuffer;
+ }
+ else
+ {
+ Buffers = buffers;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private Span<float> GetBuffer(int index, int sampleCount)
+ {
+ return Buffers.Span.Slice(index * sampleCount, sampleCount);
+ }
+
+ public void Process(CommandList context)
+ {
+ IHardwareDevice device = context.OutputDevice;
+
+ if (device.GetSampleRate() == Constants.TargetSampleRate)
+ {
+ int channelCount = (int)device.GetChannelCount();
+ uint bufferCount = Math.Min(device.GetChannelCount(), InputCount);
+
+ const int sampleCount = Constants.TargetSampleCount;
+
+ short[] outputBuffer = new short[bufferCount * sampleCount];
+
+ for (int i = 0; i < bufferCount; i++)
+ {
+ ReadOnlySpan<float> inputBuffer = GetBuffer(InputBufferIndices[i], sampleCount);
+
+ for (int j = 0; j < sampleCount; j++)
+ {
+ outputBuffer[i + j * channelCount] = PcmHelper.Saturate(inputBuffer[j]);
+ }
+ }
+
+ device.AppendBuffer(outputBuffer, InputCount);
+ }
+ else
+ {
+ // TODO: support resampling for device only supporting something different
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs
new file mode 100644
index 00000000..f81a2849
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs
@@ -0,0 +1,89 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class DownMixSurroundToStereoCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.DownMixSurroundToStereo;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ushort[] InputBufferIndices { get; }
+ public ushort[] OutputBufferIndices { get; }
+
+ public float[] Coefficients { get; }
+
+ public DownMixSurroundToStereoCommand(uint bufferOffset, Span<byte> inputBufferOffset, Span<byte> outputBufferOffset, ReadOnlySpan<float> downMixParameter, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+ for (int i = 0; i < Constants.VoiceChannelCountMax; i++)
+ {
+ InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]);
+ OutputBufferIndices[i] = (ushort)(bufferOffset + outputBufferOffset[i]);
+ }
+
+ Coefficients = downMixParameter.ToArray();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float DownMixSurroundToStereo(ReadOnlySpan<float> coefficients, float back, float lfe, float center, float front)
+ {
+ return FloatingPointHelper.RoundUp(coefficients[3] * back + coefficients[2] * lfe + coefficients[1] * center + coefficients[0] * front);
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan<float> frontLeft = context.GetBuffer(InputBufferIndices[0]);
+ ReadOnlySpan<float> frontRight = context.GetBuffer(InputBufferIndices[1]);
+ ReadOnlySpan<float> frontCenter = context.GetBuffer(InputBufferIndices[2]);
+ ReadOnlySpan<float> lowFrequency = context.GetBuffer(InputBufferIndices[3]);
+ ReadOnlySpan<float> backLeft = context.GetBuffer(InputBufferIndices[4]);
+ ReadOnlySpan<float> backRight = context.GetBuffer(InputBufferIndices[5]);
+
+ Span<float> stereoLeft = context.GetBuffer(OutputBufferIndices[0]);
+ Span<float> stereoRight = context.GetBuffer(OutputBufferIndices[1]);
+ Span<float> unused2 = context.GetBuffer(OutputBufferIndices[2]);
+ Span<float> unused3 = context.GetBuffer(OutputBufferIndices[3]);
+ Span<float> unused4 = context.GetBuffer(OutputBufferIndices[4]);
+ Span<float> unused5 = context.GetBuffer(OutputBufferIndices[5]);
+
+ for (int i = 0; i < context.SampleCount; i++)
+ {
+ stereoLeft[i] = DownMixSurroundToStereo(Coefficients, backLeft[i], lowFrequency[i], frontCenter[i], frontLeft[i]);
+ stereoRight[i] = DownMixSurroundToStereo(Coefficients, backRight[i], lowFrequency[i], frontCenter[i], frontRight[i]);
+ }
+
+ unused2.Fill(0);
+ unused3.Fill(0);
+ unused4.Fill(0);
+ unused5.Fill(0);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs
new file mode 100644
index 00000000..e2f00ef6
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs
@@ -0,0 +1,37 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public interface ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType { get; }
+
+ public ulong EstimatedProcessingTime { get; }
+
+ public void Process(CommandList context);
+
+ public bool ShouldMeter()
+ {
+ return false;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs
new file mode 100644
index 00000000..566fea92
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs
@@ -0,0 +1,125 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class MixCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Mix;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ public float Volume { get; }
+
+ public MixCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = (ushort)inputBufferIndex;
+ OutputBufferIndex = (ushort)outputBufferIndex;
+
+ Volume = volume;
+ }
+
+ private void ProcessMixAvx(Span<float> outputMix, ReadOnlySpan<float> inputMix)
+ {
+ Vector256<float> volumeVec = Vector256.Create(Volume);
+
+ ReadOnlySpan<Vector256<float>> inputVec = MemoryMarshal.Cast<float, Vector256<float>>(inputMix);
+ Span<Vector256<float>> outputVec = MemoryMarshal.Cast<float, Vector256<float>>(outputMix);
+
+ int sisdStart = inputVec.Length * 8;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = Avx.Add(outputVec[i], Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec)));
+ }
+
+ for (int i = sisdStart; i < inputMix.Length; i++)
+ {
+ outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
+ }
+ }
+
+ private void ProcessMixSse41(Span<float> outputMix, ReadOnlySpan<float> inputMix)
+ {
+ Vector128<float> volumeVec = Vector128.Create(Volume);
+
+ ReadOnlySpan<Vector128<float>> inputVec = MemoryMarshal.Cast<float, Vector128<float>>(inputMix);
+ Span<Vector128<float>> outputVec = MemoryMarshal.Cast<float, Vector128<float>>(outputMix);
+
+ int sisdStart = inputVec.Length * 4;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = Sse.Add(outputVec[i], Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec)));
+ }
+
+ for (int i = sisdStart; i < inputMix.Length; i++)
+ {
+ outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessMixSlowPath(Span<float> outputMix, ReadOnlySpan<float> inputMix)
+ {
+ for (int i = 0; i < inputMix.Length; i++)
+ {
+ outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
+ }
+ }
+
+ private void ProcessMix(Span<float> outputMix, ReadOnlySpan<float> inputMix)
+ {
+ if (Avx.IsSupported)
+ {
+ ProcessMixAvx(outputMix, inputMix);
+ }
+ else if (Sse41.IsSupported)
+ {
+ ProcessMixSse41(outputMix, inputMix);
+ }
+ else
+ {
+ ProcessMixSlowPath(outputMix, inputMix);
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ ProcessMix(outputBuffer, inputBuffer);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs
new file mode 100644
index 00000000..f34e50e4
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs
@@ -0,0 +1,83 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class MixRampCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.MixRamp;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ public float Volume0 { get; }
+ public float Volume1 { get; }
+
+ public Memory<VoiceUpdateState> State { get; }
+
+ public int LastSampleIndex { get; }
+
+ public MixRampCommand(float volume0, float volume1, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory<VoiceUpdateState> state, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = (ushort)inputBufferIndex;
+ OutputBufferIndex = (ushort)outputBufferIndex;
+
+ Volume0 = volume0;
+ Volume1 = volume1;
+
+ State = state;
+ LastSampleIndex = lastSampleIndex;
+ }
+
+ private float ProcessMixRamp(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, int sampleCount)
+ {
+ float ramp = (Volume1 - Volume0) / sampleCount;
+ float volume = Volume0;
+ float state = 0;
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume);
+
+ outputBuffer[i] += state;
+ volume += ramp;
+ }
+
+ return state;
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ State.Span[0].LastSamples[LastSampleIndex] = ProcessMixRamp(outputBuffer, inputBuffer, (int)context.SampleCount);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
new file mode 100644
index 00000000..cbd2503b
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
@@ -0,0 +1,106 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class MixRampGroupedCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.MixRampGrouped;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public uint MixBufferCount { get; }
+
+ public ushort[] InputBufferIndices { get; }
+ public ushort[] OutputBufferIndices { get; }
+
+ public float[] Volume0 { get; }
+ public float[] Volume1 { get; }
+
+ public Memory<VoiceUpdateState> State { get; }
+
+ public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> volume0, Span<float> volume1, Memory<VoiceUpdateState> state, int nodeId)
+ {
+ Enabled = true;
+ MixBufferCount = mixBufferCount;
+ NodeId = nodeId;
+
+ InputBufferIndices = new ushort[Constants.MixBufferCountMax];
+ OutputBufferIndices = new ushort[Constants.MixBufferCountMax];
+ Volume0 = new float[Constants.MixBufferCountMax];
+ Volume1 = new float[Constants.MixBufferCountMax];
+
+ for (int i = 0; i < mixBufferCount; i++)
+ {
+ InputBufferIndices[i] = (ushort)inputBufferIndex;
+ OutputBufferIndices[i] = (ushort)(outputBufferIndex + i);
+
+ Volume0[i] = volume0[i];
+ Volume1[i] = volume1[i];
+ }
+
+ State = state;
+ }
+
+ private float ProcessMixRampGrouped(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float volume0, float volume1, int sampleCount)
+ {
+ float ramp = (volume1 - volume0) / sampleCount;
+ float volume = volume0;
+ float state = 0;
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume);
+
+ outputBuffer[i] += state;
+ volume += ramp;
+ }
+
+ return state;
+ }
+
+ public void Process(CommandList context)
+ {
+ for (int i = 0; i < MixBufferCount; i++)
+ {
+ ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndices[i]);
+ Span<float> outputBuffer = context.GetBuffer(OutputBufferIndices[i]);
+
+ float volume0 = Volume0[i];
+ float volume1 = Volume1[i];
+
+ ref VoiceUpdateState state = ref State.Span[0];
+
+ if (volume0 != 0 || volume1 != 0)
+ {
+ state.LastSamples[i] = ProcessMixRampGrouped(outputBuffer, inputBuffer, volume0, volume1, (int)context.SampleCount);
+ }
+ else
+ {
+ state.LastSamples[i] = 0;
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs
new file mode 100644
index 00000000..c4b9b0ff
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs
@@ -0,0 +1,93 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using System;
+using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class PcmFloatDataSourceCommandVersion1 : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ushort OutputBufferIndex { get; }
+ public uint SampleRate { get; }
+ public uint ChannelIndex { get; }
+
+ public uint ChannelCount { get; }
+
+ public float Pitch { get; }
+
+ public WaveBuffer[] WaveBuffers { get; }
+
+ public Memory<VoiceUpdateState> State { get; }
+ public DecodingBehaviour DecodingBehaviour { get; }
+
+ public PcmFloatDataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex);
+ SampleRate = serverState.SampleRate;
+ ChannelIndex = channelIndex;
+ ChannelCount = serverState.ChannelsCount;
+ Pitch = serverState.Pitch;
+
+ WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
+
+ for (int i = 0; i < WaveBuffers.Length; i++)
+ {
+ ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i];
+
+ WaveBuffers[i] = voiceWaveBuffer.ToCommon(1);
+ }
+
+ State = state;
+ DecodingBehaviour = serverState.DecodingBehaviour;
+ }
+
+ public void Process(CommandList context)
+ {
+ Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation()
+ {
+ State = State,
+ SourceSampleRate = SampleRate,
+ SampleFormat = SampleFormat.PcmInt16,
+ Pitch = Pitch,
+ DecodingBehaviour = DecodingBehaviour,
+ WaveBuffers = WaveBuffers,
+ ExtraParameter = 0,
+ ExtraParameterSize = 0,
+ ChannelIndex = (int)ChannelIndex,
+ ChannelCount = (int)ChannelCount,
+ };
+
+ DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs
new file mode 100644
index 00000000..3758dbe0
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs
@@ -0,0 +1,93 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using System;
+using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class PcmInt16DataSourceCommandVersion1 : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ushort OutputBufferIndex { get; }
+ public uint SampleRate { get; }
+ public uint ChannelIndex { get; }
+
+ public uint ChannelCount { get; }
+
+ public float Pitch { get; }
+
+ public WaveBuffer[] WaveBuffers { get; }
+
+ public Memory<VoiceUpdateState> State { get; }
+ public DecodingBehaviour DecodingBehaviour { get; }
+
+ public PcmInt16DataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex);
+ SampleRate = serverState.SampleRate;
+ ChannelIndex = channelIndex;
+ ChannelCount = serverState.ChannelsCount;
+ Pitch = serverState.Pitch;
+
+ WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
+
+ for (int i = 0; i < WaveBuffers.Length; i++)
+ {
+ ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i];
+
+ WaveBuffers[i] = voiceWaveBuffer.ToCommon(1);
+ }
+
+ State = state;
+ DecodingBehaviour = serverState.DecodingBehaviour;
+ }
+
+ public void Process(CommandList context)
+ {
+ Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation()
+ {
+ State = State,
+ SourceSampleRate = SampleRate,
+ SampleFormat = SampleFormat.PcmInt16,
+ Pitch = Pitch,
+ DecodingBehaviour = DecodingBehaviour,
+ WaveBuffers = WaveBuffers,
+ ExtraParameter = 0,
+ ExtraParameterSize = 0,
+ ChannelIndex = (int)ChannelIndex,
+ ChannelCount = (int)ChannelCount,
+ };
+
+ DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs
new file mode 100644
index 00000000..0cc35712
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs
@@ -0,0 +1,64 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Server.Performance;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class PerformanceCommand : ICommand
+ {
+ public enum Type
+ {
+ Invalid,
+ Start,
+ End
+ }
+
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Performance;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public PerformanceEntryAddresses PerformanceEntryAddresses { get; }
+
+ public Type PerformanceType { get; set; }
+
+ public PerformanceCommand(ref PerformanceEntryAddresses performanceEntryAddresses, Type performanceType, int nodeId)
+ {
+ Enabled = true;
+ PerformanceEntryAddresses = performanceEntryAddresses;
+ PerformanceType = performanceType;
+ NodeId = nodeId;
+ }
+
+ public void Process(CommandList context)
+ {
+ if (PerformanceType == Type.Start)
+ {
+ PerformanceEntryAddresses.SetStartTime(context.GetTimeElapsedSinceDspStartedProcessing());
+ }
+ else if (PerformanceType == Type.End)
+ {
+ PerformanceEntryAddresses.SetProcessingTime(context.GetTimeElapsedSinceDspStartedProcessing());
+ PerformanceEntryAddresses.IncrementEntryCount();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs
new file mode 100644
index 00000000..c501bc1c
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs
@@ -0,0 +1,263 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Server.Effect;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class Reverb3dCommand : ICommand
+ {
+ private static readonly int[] OutputEarlyIndicesTableMono = new int[20] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableMono = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableMono = new int[1] { 0 };
+
+ private static readonly int[] OutputEarlyIndicesTableStereo = new int[20] { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableStereo = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableStereo = new int[2] { 0, 1 };
+
+ private static readonly int[] OutputEarlyIndicesTableQuadraphonic = new int[20] { 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableQuadraphonic = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
+
+ private static readonly int[] OutputEarlyIndicesTableSurround = new int[40] { 4, 5, 0, 5, 0, 5, 1, 5, 1, 5, 1, 5, 1, 5, 2, 5, 2, 5, 2, 5, 1, 5, 1, 5, 1, 5, 0, 5, 0, 5, 0, 5, 0, 5, 3, 5, 3, 5, 3, 5 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableSurround = new int[40] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[6] { 0, 1, 2, 3, -1, 3 };
+
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Reverb3d;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ public Reverb3dParameter Parameter => _parameter;
+ public Memory<Reverb3dState> State { get; }
+ public ulong WorkBuffer { get; }
+ public ushort[] OutputBufferIndices { get; }
+ public ushort[] InputBufferIndices { get; }
+
+ public bool IsEffectEnabled { get; }
+
+ private Reverb3dParameter _parameter;
+
+ public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, ulong workBuffer, int nodeId)
+ {
+ Enabled = true;
+ IsEffectEnabled = isEnabled;
+ NodeId = nodeId;
+ _parameter = parameter;
+ State = state;
+ WorkBuffer = workBuffer;
+
+ InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
+ OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+ }
+ }
+
+ private void ProcessReverb3dMono(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
+ {
+ ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableMono, TargetEarlyDelayLineIndicesTableMono, TargetOutputFeedbackIndicesTableMono);
+ }
+
+ private void ProcessReverb3dStereo(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
+ {
+ ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableStereo, TargetEarlyDelayLineIndicesTableStereo, TargetOutputFeedbackIndicesTableStereo);
+ }
+
+ private void ProcessReverb3dQuadraphonic(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
+ {
+ ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableQuadraphonic, TargetEarlyDelayLineIndicesTableQuadraphonic, TargetOutputFeedbackIndicesTableQuadraphonic);
+ }
+
+ private void ProcessReverb3dSurround(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
+ {
+ ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableSurround, TargetEarlyDelayLineIndicesTableSurround, TargetOutputFeedbackIndicesTableSurround);
+ }
+
+ private void ProcessReverb3dGeneric(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount, ReadOnlySpan<int> outputEarlyIndicesTable, ReadOnlySpan<int> targetEarlyDelayLineIndicesTable, ReadOnlySpan<int> targetOutputFeedbackIndicesTable)
+ {
+ const int delayLineSampleIndexOffset = 1;
+
+ ref Reverb3dState state = ref State.Span[0];
+
+ bool isMono = Parameter.ChannelCount == 1;
+ bool isSurround = Parameter.ChannelCount == 6;
+
+ float[] outputValues = new float[Constants.ChannelCountMax];
+ float[] channelInput = new float[Parameter.ChannelCount];
+ float[] feedbackValues = new float[4];
+ float[] feedbackOutputValues = new float[4];
+ float[] values = new float[4];
+
+ for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
+ {
+ outputValues.AsSpan().Fill(0);
+
+ float tapOut = state.PreDelayLine.TapUnsafe(state.ReflectionDelayTime, delayLineSampleIndexOffset);
+
+ for (int i = 0; i < targetEarlyDelayLineIndicesTable.Length; i++)
+ {
+ int earlyDelayIndex = targetEarlyDelayLineIndicesTable[i];
+ int outputIndex = outputEarlyIndicesTable[i];
+
+ float tempTapOut = state.PreDelayLine.TapUnsafe(state.EarlyDelayTime[earlyDelayIndex], delayLineSampleIndexOffset);
+
+ outputValues[outputIndex] += tempTapOut * state.EarlyGain[earlyDelayIndex];
+ }
+
+ float targetPreDelayValue = 0;
+
+ for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+ {
+ channelInput[channelIndex] = inputBuffers[channelIndex].Span[sampleIndex];
+ targetPreDelayValue += channelInput[channelIndex];
+ }
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ outputValues[i] *= state.EarlyReflectionsGain;
+ }
+
+ state.PreviousPreDelayValue = (targetPreDelayValue * state.TargetPreDelayGain) + (state.PreviousPreDelayValue * state.PreviousPreDelayGain);
+
+ state.PreDelayLine.Update(state.PreviousPreDelayValue);
+
+ for (int i = 0; i < state.FdnDelayLines.Length; i++)
+ {
+ float fdnValue = state.FdnDelayLines[i].Read();
+
+ float feedbackOutputValue = fdnValue * state.DecayDirectFdnGain[i] + state.PreviousFeedbackOutputDecayed[i];
+
+ state.PreviousFeedbackOutputDecayed[i] = (fdnValue * state.DecayCurrentFdnGain[i]) + (feedbackOutputValue * state.DecayCurrentOutputGain[i]);
+
+ feedbackOutputValues[i] = feedbackOutputValue;
+ }
+
+ feedbackValues[0] = feedbackOutputValues[2] + feedbackOutputValues[1];
+ feedbackValues[1] = -feedbackOutputValues[0] - feedbackOutputValues[3];
+ feedbackValues[2] = feedbackOutputValues[0] - feedbackOutputValues[3];
+ feedbackValues[3] = feedbackOutputValues[1] - feedbackOutputValues[2];
+
+ for (int i = 0; i < state.DecayDelays1.Length; i++)
+ {
+ float temp = state.DecayDelays1[i].Update(tapOut * state.LateReverbGain + feedbackValues[i]);
+
+ values[i] = state.DecayDelays2[i].Update(temp);
+
+ state.FdnDelayLines[i].Update(values[i]);
+ }
+
+ for (int channelIndex = 0; channelIndex < targetOutputFeedbackIndicesTable.Length; channelIndex++)
+ {
+ int targetOutputFeedbackIndex = targetOutputFeedbackIndicesTable[channelIndex];
+
+ if (targetOutputFeedbackIndex >= 0)
+ {
+ outputBuffers[channelIndex].Span[sampleIndex] = (outputValues[channelIndex] + values[targetOutputFeedbackIndex] + channelInput[channelIndex] * state.DryGain);
+ }
+ }
+
+ if (isMono)
+ {
+ outputBuffers[0].Span[sampleIndex] += values[1];
+ }
+
+ if (isSurround)
+ {
+ outputBuffers[4].Span[sampleIndex] += (outputValues[4] + state.BackLeftDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain);
+ }
+ }
+ }
+
+ public void ProcessReverb3d(CommandList context)
+ {
+ Debug.Assert(Parameter.IsChannelCountValid());
+
+ if (IsEffectEnabled && Parameter.IsChannelCountValid())
+ {
+ ReadOnlyMemory<float>[] inputBuffers = new ReadOnlyMemory<float>[Parameter.ChannelCount];
+ Memory<float>[] outputBuffers = new Memory<float>[Parameter.ChannelCount];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]);
+ outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]);
+ }
+
+ switch (Parameter.ChannelCount)
+ {
+ case 1:
+ ProcessReverb3dMono(outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 2:
+ ProcessReverb3dStereo(outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 4:
+ ProcessReverb3dQuadraphonic(outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 6:
+ ProcessReverb3dSurround(outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ default:
+ throw new NotImplementedException($"{Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ if (InputBufferIndices[i] != OutputBufferIndices[i])
+ {
+ context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
+ }
+ }
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ref Reverb3dState state = ref State.Span[0];
+
+ if (IsEffectEnabled)
+ {
+ if (Parameter.ParameterStatus == UsageState.Invalid)
+ {
+ state = new Reverb3dState(ref _parameter, WorkBuffer);
+ }
+ else if (Parameter.ParameterStatus == UsageState.New)
+ {
+ state.UpdateParameter(ref _parameter);
+ }
+ }
+
+ ProcessReverb3d(context);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs
new file mode 100644
index 00000000..0ed955de
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs
@@ -0,0 +1,284 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class ReverbCommand : ICommand
+ {
+ private static readonly int[] OutputEarlyIndicesTableMono = new int[10] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableMono = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ private static readonly int[] OutputIndicesTableMono = new int[4] { 0, 0, 0, 0 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableMono = new int[4] { 0, 1, 2, 3 };
+
+ private static readonly int[] OutputEarlyIndicesTableStereo = new int[10] { 0, 0, 1, 1, 0, 1, 0, 0, 1, 1 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableStereo = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ private static readonly int[] OutputIndicesTableStereo = new int[4] { 0, 0, 1, 1 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableStereo = new int[4] { 2, 0, 3, 1 };
+
+ private static readonly int[] OutputEarlyIndicesTableQuadraphonic = new int[10] { 0, 0, 1, 1, 0, 1, 2, 2, 3, 3 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableQuadraphonic = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ private static readonly int[] OutputIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
+
+ private static readonly int[] OutputEarlyIndicesTableSurround = new int[20] { 0, 5, 0, 5, 1, 5, 1, 5, 4, 5, 4, 5, 2, 5, 2, 5, 3, 5, 3, 5 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableSurround = new int[20] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9 };
+ private static readonly int[] OutputIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, 4, 5 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, -1, 3 };
+
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Reverb;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ReverbParameter Parameter => _parameter;
+ public Memory<ReverbState> State { get; }
+ public ulong WorkBuffer { get; }
+ public ushort[] OutputBufferIndices { get; }
+ public ushort[] InputBufferIndices { get; }
+ public bool IsLongSizePreDelaySupported { get; }
+
+ public bool IsEffectEnabled { get; }
+
+ private ReverbParameter _parameter;
+
+ private const int FixedPointPrecision = 14;
+
+ public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported)
+ {
+ Enabled = true;
+ IsEffectEnabled = isEnabled;
+ NodeId = nodeId;
+ _parameter = parameter;
+ State = state;
+ WorkBuffer = workBuffer;
+
+ InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
+ OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+ }
+
+ IsLongSizePreDelaySupported = isLongSizePreDelaySupported;
+ }
+
+ private void ProcessReverbMono(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
+ {
+ ProcessReverbGeneric(outputBuffers,
+ inputBuffers,
+ sampleCount,
+ OutputEarlyIndicesTableMono,
+ TargetEarlyDelayLineIndicesTableMono,
+ TargetOutputFeedbackIndicesTableMono,
+ OutputIndicesTableMono);
+ }
+
+ private void ProcessReverbStereo(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
+ {
+ ProcessReverbGeneric(outputBuffers,
+ inputBuffers,
+ sampleCount,
+ OutputEarlyIndicesTableStereo,
+ TargetEarlyDelayLineIndicesTableStereo,
+ TargetOutputFeedbackIndicesTableStereo,
+ OutputIndicesTableStereo);
+ }
+
+ private void ProcessReverbQuadraphonic(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
+ {
+ ProcessReverbGeneric(outputBuffers,
+ inputBuffers,
+ sampleCount,
+ OutputEarlyIndicesTableQuadraphonic,
+ TargetEarlyDelayLineIndicesTableQuadraphonic,
+ TargetOutputFeedbackIndicesTableQuadraphonic,
+ OutputIndicesTableQuadraphonic);
+ }
+
+ private void ProcessReverbSurround(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
+ {
+ ProcessReverbGeneric(outputBuffers,
+ inputBuffers,
+ sampleCount,
+ OutputEarlyIndicesTableSurround,
+ TargetEarlyDelayLineIndicesTableSurround,
+ TargetOutputFeedbackIndicesTableSurround,
+ OutputIndicesTableSurround);
+ }
+
+ private void ProcessReverbGeneric(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount, ReadOnlySpan<int> outputEarlyIndicesTable, ReadOnlySpan<int> targetEarlyDelayLineIndicesTable, ReadOnlySpan<int> targetOutputFeedbackIndicesTable, ReadOnlySpan<int> outputIndicesTable)
+ {
+ ref ReverbState state = ref State.Span[0];
+
+ bool isSurround = Parameter.ChannelCount == 6;
+
+ float reverbGain = FixedPointHelper.ToFloat(Parameter.ReverbGain, FixedPointPrecision);
+ float lateGain = FixedPointHelper.ToFloat(Parameter.LateGain, FixedPointPrecision);
+ float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
+ float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
+
+ float[] outputValues = new float[Constants.ChannelCountMax];
+ float[] feedbackValues = new float[4];
+ float[] feedbackOutputValues = new float[4];
+ float[] channelInput = new float[Parameter.ChannelCount];
+
+ for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
+ {
+ outputValues.AsSpan().Fill(0);
+
+ for (int i = 0; i < targetEarlyDelayLineIndicesTable.Length; i++)
+ {
+ int earlyDelayIndex = targetEarlyDelayLineIndicesTable[i];
+ int outputIndex = outputEarlyIndicesTable[i];
+
+ float tapOutput = state.PreDelayLine.TapUnsafe(state.EarlyDelayTime[earlyDelayIndex], 0);
+
+ outputValues[outputIndex] += tapOutput * state.EarlyGain[earlyDelayIndex];
+ }
+
+ if (isSurround)
+ {
+ outputValues[5] *= 0.2f;
+ }
+
+ float targetPreDelayValue = 0;
+
+ for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+ {
+ channelInput[channelIndex] = inputBuffers[channelIndex].Span[sampleIndex] * 64;
+ targetPreDelayValue += channelInput[channelIndex] * reverbGain;
+ }
+
+ state.PreDelayLine.Update(targetPreDelayValue);
+
+ float lateValue = state.PreDelayLine.Tap(state.PreDelayLineDelayTime) * lateGain;
+
+ for (int i = 0; i < state.FdnDelayLines.Length; i++)
+ {
+ feedbackOutputValues[i] = state.FdnDelayLines[i].Read() * state.HighFrequencyDecayDirectGain[i] + state.PreviousFeedbackOutput[i] * state.HighFrequencyDecayPreviousGain[i];
+ state.PreviousFeedbackOutput[i] = feedbackOutputValues[i];
+ }
+
+ feedbackValues[0] = feedbackOutputValues[2] + feedbackOutputValues[1];
+ feedbackValues[1] = -feedbackOutputValues[0] - feedbackOutputValues[3];
+ feedbackValues[2] = feedbackOutputValues[0] - feedbackOutputValues[3];
+ feedbackValues[3] = feedbackOutputValues[1] - feedbackOutputValues[2];
+
+ for (int i = 0; i < state.FdnDelayLines.Length; i++)
+ {
+ feedbackOutputValues[i] = state.DecayDelays[i].Update(feedbackValues[i] + lateValue);
+ state.FdnDelayLines[i].Update(feedbackOutputValues[i]);
+ }
+
+ for (int i = 0; i < targetOutputFeedbackIndicesTable.Length; i++)
+ {
+ int targetOutputFeedbackIndex = targetOutputFeedbackIndicesTable[i];
+ int outputIndex = outputIndicesTable[i];
+
+ if (targetOutputFeedbackIndex >= 0)
+ {
+ outputValues[outputIndex] += feedbackOutputValues[targetOutputFeedbackIndex];
+ }
+ }
+
+ if (isSurround)
+ {
+ outputValues[4] += state.BackLeftDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f);
+ }
+
+ for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+ {
+ outputBuffers[channelIndex].Span[sampleIndex] = (outputValues[channelIndex] * outGain + channelInput[channelIndex] * dryGain) / 64;
+ }
+ }
+ }
+
+ private void ProcessReverb(CommandList context)
+ {
+ Debug.Assert(Parameter.IsChannelCountValid());
+
+ if (IsEffectEnabled && Parameter.IsChannelCountValid())
+ {
+ ReadOnlyMemory<float>[] inputBuffers = new ReadOnlyMemory<float>[Parameter.ChannelCount];
+ Memory<float>[] outputBuffers = new Memory<float>[Parameter.ChannelCount];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]);
+ outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]);
+ }
+
+ switch (Parameter.ChannelCount)
+ {
+ case 1:
+ ProcessReverbMono(outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 2:
+ ProcessReverbStereo(outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 4:
+ ProcessReverbQuadraphonic(outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 6:
+ ProcessReverbSurround(outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ default:
+ throw new NotImplementedException($"{Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ if (InputBufferIndices[i] != OutputBufferIndices[i])
+ {
+ context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
+ }
+ }
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ref ReverbState state = ref State.Span[0];
+
+ if (IsEffectEnabled)
+ {
+ if (Parameter.Status == Server.Effect.UsageState.Invalid)
+ {
+ state = new ReverbState(ref _parameter, WorkBuffer, IsLongSizePreDelaySupported);
+ }
+ else if (Parameter.Status == Server.Effect.UsageState.New)
+ {
+ state.UpdateParameter(ref _parameter);
+ }
+ }
+
+ ProcessReverb(context);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs
new file mode 100644
index 00000000..6c446f05
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs
@@ -0,0 +1,87 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Server.Upsampler;
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class UpsampleCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Upsample;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public uint BufferCount { get; }
+ public uint InputBufferIndex { get; }
+ public uint InputSampleCount { get; }
+ public uint InputSampleRate { get; }
+
+ public UpsamplerState UpsamplerInfo { get; }
+
+ public Memory<float> OutBuffer { get; }
+
+ public UpsampleCommand(uint bufferOffset, UpsamplerState info, uint inputCount, Span<byte> inputBufferOffset, uint bufferCount, uint sampleCount, uint sampleRate, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = 0;
+ OutBuffer = info.OutputBuffer;
+ BufferCount = bufferCount;
+ InputSampleCount = sampleCount;
+ InputSampleRate = sampleRate;
+ info.SourceSampleCount = inputCount;
+ info.InputBufferIndices = new ushort[inputCount];
+
+ for (int i = 0; i < inputCount; i++)
+ {
+ info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]);
+ }
+
+ UpsamplerInfo = info;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private Span<float> GetBuffer(int index, int sampleCount)
+ {
+ return UpsamplerInfo.OutputBuffer.Span.Slice(index * sampleCount, sampleCount);
+ }
+
+ public void Process(CommandList context)
+ {
+ float ratio = (float)InputSampleRate / Constants.TargetSampleRate;
+
+ uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount);
+
+ for (int i = 0; i < bufferCount; i++)
+ {
+ Span<float> inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]);
+ Span<float> outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount);
+
+ float fraction = 0.0f;
+
+ ResamplerHelper.ResampleForUpsampler(outputBuffer, inputBuffer, ratio, ref fraction, (int)(InputSampleCount / ratio));
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs
new file mode 100644
index 00000000..b58ae1f8
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs
@@ -0,0 +1,125 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class VolumeCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Volume;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ public float Volume { get; }
+
+ public VolumeCommand(float volume, uint bufferIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = (ushort)bufferIndex;
+ OutputBufferIndex = (ushort)bufferIndex;
+
+ Volume = volume;
+ }
+
+ private void ProcessVolumeAvx(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer)
+ {
+ Vector256<float> volumeVec = Vector256.Create(Volume);
+
+ ReadOnlySpan<Vector256<float>> inputVec = MemoryMarshal.Cast<float, Vector256<float>>(inputBuffer);
+ Span<Vector256<float>> outputVec = MemoryMarshal.Cast<float, Vector256<float>>(outputBuffer);
+
+ int sisdStart = inputVec.Length * 8;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec));
+ }
+
+ for (int i = sisdStart; i < inputBuffer.Length; i++)
+ {
+ outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume);
+ }
+ }
+
+ private void ProcessVolumeSse41(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer)
+ {
+ Vector128<float> volumeVec = Vector128.Create(Volume);
+
+ ReadOnlySpan<Vector128<float>> inputVec = MemoryMarshal.Cast<float, Vector128<float>>(inputBuffer);
+ Span<Vector128<float>> outputVec = MemoryMarshal.Cast<float, Vector128<float>>(outputBuffer);
+
+ int sisdStart = inputVec.Length * 4;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec));
+ }
+
+ for (int i = sisdStart; i < inputBuffer.Length; i++)
+ {
+ outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume);
+ }
+ }
+
+ private void ProcessVolume(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer)
+ {
+ if (Avx.IsSupported)
+ {
+ ProcessVolumeAvx(outputBuffer, inputBuffer);
+ }
+ else if (Sse41.IsSupported)
+ {
+ ProcessVolumeSse41(outputBuffer, inputBuffer);
+ }
+ else
+ {
+ ProcessVolumeSlowPath(outputBuffer, inputBuffer);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessVolumeSlowPath(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer)
+ {
+ for (int i = 0; i < outputBuffer.Length; i++)
+ {
+ outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume);
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ ProcessVolume(outputBuffer, inputBuffer);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs
new file mode 100644
index 00000000..d07926c8
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs
@@ -0,0 +1,71 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class VolumeRampCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.VolumeRamp;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ public float Volume0 { get; }
+ public float Volume1 { get; }
+
+ public VolumeRampCommand(float volume0, float volume1, uint bufferIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = (ushort)bufferIndex;
+ OutputBufferIndex = (ushort)bufferIndex;
+
+ Volume0 = volume0;
+ Volume1 = volume1;
+ }
+
+ private void ProcessVolumeRamp(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, int sampleCount)
+ {
+ float ramp = (Volume1 - Volume0) / sampleCount;
+
+ float volume = Volume0;
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume);
+ volume += ramp;
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ ProcessVolumeRamp(outputBuffer, inputBuffer, (int)context.SampleCount);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs b/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs
new file mode 100644
index 00000000..c9514529
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs
@@ -0,0 +1,409 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+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.Buffers;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+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 class WaveBufferInformation
+ {
+ public Memory<VoiceUpdateState> State;
+ public uint SourceSampleRate;
+ public SampleFormat SampleFormat;
+ public float Pitch;
+ public DecodingBehaviour DecodingBehaviour;
+ public WaveBuffer[] WaveBuffers;
+ public ulong ExtraParameter;
+ public ulong ExtraParameterSize;
+ public int ChannelIndex;
+ public int ChannelCount;
+ public SampleRateConversionQuality SrcQuality;
+ }
+
+ private static int GetPitchLimitBySrcQuality(SampleRateConversionQuality quality)
+ {
+ switch (quality)
+ {
+ case SampleRateConversionQuality.Default:
+ case SampleRateConversionQuality.Low:
+ return 4;
+ case SampleRateConversionQuality.High:
+ return 8;
+ default:
+ throw new ArgumentException($"{quality}");
+ }
+ }
+
+ public static void ProcessWaveBuffers(IVirtualMemoryManager memoryManager, Span<float> outputBuffer, WaveBufferInformation info, uint targetSampleRate, int sampleCount)
+ {
+ const int tempBufferSize = 0x3F00;
+
+ ref VoiceUpdateState state = ref info.State.Span[0];
+
+ short[] tempBuffer = ArrayPool<short>.Shared.Rent(tempBufferSize);
+
+ float sampleRateRatio = ((float)info.SourceSampleRate / targetSampleRate * info.Pitch);
+
+ float fraction = state.Fraction;
+ int waveBufferIndex = (int)state.WaveBufferIndex;
+ ulong playedSampleCount = state.PlayedSampleCount;
+ int offset = state.Offset;
+ uint waveBufferConsumed = state.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))
+ {
+ state.Pitch.ToSpan().Slice(0, pitchMaxLength).CopyTo(tempBuffer.AsSpan());
+ 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)
+ {
+ Logger.Error?.Print(LogClass.AudioRenderer, $"Invalid WaveBuffer index {waveBufferIndex}");
+
+ waveBufferIndex = 0;
+ playedSampleCount = 0;
+ }
+
+ if (!state.IsWaveBufferValid[waveBufferIndex])
+ {
+ isStarving = true;
+ break;
+ }
+
+ ref WaveBuffer waveBuffer = ref info.WaveBuffers[waveBufferIndex];
+
+ if (offset == 0 && info.SampleFormat == SampleFormat.Adpcm && waveBuffer.Context != 0)
+ {
+ state.LoopContext = memoryManager.Read<AdpcmLoopContext>(waveBuffer.Context);
+ }
+
+ Span<short> tempSpan = tempBuffer.AsSpan().Slice(tempBufferIndex + y);
+
+ int decodedSampleCount = -1;
+
+ int targetSampleStartOffset;
+ int targetSampleEndOffset;
+
+ if (state.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 state.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.Warning?.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");
+
+ state.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)
+ {
+ state.LoopCount++;
+
+ if (waveBuffer.LoopCount >= 0)
+ {
+ if (decodedSampleCount == 0 || state.LoopCount > waveBuffer.LoopCount)
+ {
+ state.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount);
+ }
+ }
+
+ if (decodedSampleCount == 0)
+ {
+ isStarving = true;
+ break;
+ }
+
+ if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.PlayedSampleCountResetWhenLooping))
+ {
+ playedSampleCount = 0;
+ }
+ }
+ else
+ {
+ state.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount);
+ }
+ }
+ }
+
+ Span<float> outputSpan = outputBuffer.Slice(i);
+ Span<int> outputSpanInt = MemoryMarshal.Cast<float, int>(outputSpan);
+
+ if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion))
+ {
+ for (int j = 0; j < y; j++)
+ {
+ outputBuffer[j] = tempBuffer[j];
+ }
+ }
+ else
+ {
+ Span<short> tempSpan = tempBuffer.AsSpan().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.AsSpan().Slice(sampleCountToDecode, pitchMaxLength).CopyTo(state.Pitch.ToSpan());
+ }
+
+ i += sampleCountToProcess;
+ }
+
+ Debug.Assert(sourceSampleCountToProcess == i || !isStarving);
+
+ state.WaveBufferConsumed = waveBufferConsumed;
+ state.Offset = offset;
+ state.PlayedSampleCount = playedSampleCount;
+ state.WaveBufferIndex = (uint)waveBufferIndex;
+ state.Fraction = fraction;
+ }
+
+ ArrayPool<short>.Shared.Return(tempBuffer);
+ }
+
+ 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];
+ }
+ }
+
+ 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)]
+ public static void ToFloatSlow(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
+ {
+ for (int i = 0; i < sampleCount; i++)
+ {
+ output[i] = input[i];
+ }
+ }
+
+ 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
+ {
+ ToFloatSlow(output, input, sampleCount);
+ }
+ }
+
+ 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];
+ }
+ }
+
+ 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] = Avx.ConvertToVector128Int32(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];
+ }
+ }
+
+ 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
+ {
+ ToIntSlow(output, input, sampleCount);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs b/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs
new file mode 100644
index 00000000..8a815c56
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs
@@ -0,0 +1,69 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Dsp.Effect
+{
+ public class DecayDelay : IDelayLine
+ {
+ private readonly IDelayLine _delayLine;
+
+ public uint CurrentSampleCount => _delayLine.CurrentSampleCount;
+
+ public uint SampleCountMax => _delayLine.SampleCountMax;
+
+ private float _decayRate;
+
+ public DecayDelay(IDelayLine delayLine)
+ {
+ _decayRate = 0.0f;
+ _delayLine = delayLine;
+ }
+
+ public void SetDecayRate(float decayRate)
+ {
+ _decayRate = decayRate;
+ }
+
+ public float Update(float value)
+ {
+ float delayLineValue = _delayLine.Read();
+ float processedValue = value - (_decayRate * delayLineValue);
+
+ return _delayLine.Update(processedValue) + processedValue * _decayRate;
+ }
+
+ public void SetDelay(float delayTime)
+ {
+ _delayLine.SetDelay(delayTime);
+ }
+
+ public float Read()
+ {
+ return _delayLine.Read();
+ }
+
+ public float TapUnsafe(uint sampleIndex, int offset)
+ {
+ return _delayLine.TapUnsafe(sampleIndex, offset);
+ }
+
+ public float Tap(uint sampleIndex)
+ {
+ return _delayLine.Tap(sampleIndex);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs b/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs
new file mode 100644
index 00000000..d178e699
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs
@@ -0,0 +1,95 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Effect
+{
+ public class DelayLine : IDelayLine
+ {
+ private float[] _workBuffer;
+ private uint _sampleRate;
+ private uint _currentSampleIndex;
+ private uint _lastSampleIndex;
+
+ public uint CurrentSampleCount { get; private set; }
+ public uint SampleCountMax { get; private set; }
+
+ public DelayLine(uint sampleRate, float delayTimeMax)
+ {
+ _sampleRate = sampleRate;
+ SampleCountMax = IDelayLine.GetSampleCount(_sampleRate, delayTimeMax);
+ _workBuffer = new float[SampleCountMax];
+
+ SetDelay(delayTimeMax);
+ }
+
+ private void ConfigureDelay(uint targetSampleCount)
+ {
+ CurrentSampleCount = Math.Min(SampleCountMax, targetSampleCount);
+ _currentSampleIndex = 0;
+
+ if (CurrentSampleCount == 0)
+ {
+ _lastSampleIndex = 0;
+ }
+ else
+ {
+ _lastSampleIndex = CurrentSampleCount - 1;
+ }
+ }
+
+ public void SetDelay(float delayTime)
+ {
+ ConfigureDelay(IDelayLine.GetSampleCount(_sampleRate, delayTime));
+ }
+
+ public float Read()
+ {
+ return _workBuffer[_currentSampleIndex];
+ }
+
+ public float Update(float value)
+ {
+ float output = Read();
+
+ _workBuffer[_currentSampleIndex++] = value;
+
+ if (_currentSampleIndex >= _lastSampleIndex)
+ {
+ _currentSampleIndex = 0;
+ }
+
+ return output;
+ }
+
+ public float TapUnsafe(uint sampleIndex, int offset)
+ {
+ return IDelayLine.Tap(_workBuffer, (int)_currentSampleIndex, (int)sampleIndex + offset, (int)CurrentSampleCount);
+ }
+
+ public float Tap(uint sampleIndex)
+ {
+ if (sampleIndex >= CurrentSampleCount)
+ {
+ sampleIndex = CurrentSampleCount - 1;
+ }
+
+ return TapUnsafe(sampleIndex, -1);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs b/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs
new file mode 100644
index 00000000..a30dcc63
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs
@@ -0,0 +1,93 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Effect
+{
+ public class DelayLine3d : IDelayLine
+ {
+ private float[] _workBuffer;
+ private uint _sampleRate;
+ private uint _currentSampleIndex;
+ private uint _lastSampleIndex;
+
+ public uint CurrentSampleCount { get; private set; }
+ public uint SampleCountMax { get; private set; }
+
+ public DelayLine3d(uint sampleRate, float delayTimeMax)
+ {
+ _sampleRate = sampleRate;
+ SampleCountMax = IDelayLine.GetSampleCount(_sampleRate, delayTimeMax);
+ _workBuffer = new float[SampleCountMax + 1];
+
+ SetDelay(delayTimeMax);
+ }
+
+ private void ConfigureDelay(uint targetSampleCount)
+ {
+ if (SampleCountMax >= targetSampleCount)
+ {
+ CurrentSampleCount = targetSampleCount;
+ _lastSampleIndex = (_currentSampleIndex + targetSampleCount) % (SampleCountMax + 1);
+ }
+ }
+
+ public void SetDelay(float delayTime)
+ {
+ ConfigureDelay(IDelayLine.GetSampleCount(_sampleRate, delayTime));
+ }
+
+ public float Read()
+ {
+ return _workBuffer[_currentSampleIndex];
+ }
+
+ public float Update(float value)
+ {
+ Debug.Assert(!float.IsNaN(value) && !float.IsInfinity(value));
+
+ _workBuffer[_lastSampleIndex++] = value;
+
+ float output = Read();
+
+ _currentSampleIndex++;
+
+ if (_currentSampleIndex >= SampleCountMax)
+ {
+ _currentSampleIndex = 0;
+ }
+
+ if (_lastSampleIndex >= SampleCountMax)
+ {
+ _lastSampleIndex = 0;
+ }
+
+ return output;
+ }
+
+ public float TapUnsafe(uint sampleIndex, int offset)
+ {
+ return IDelayLine.Tap(_workBuffer, (int)_lastSampleIndex, (int)sampleIndex + offset, (int)SampleCountMax + 1);
+ }
+
+ public float Tap(uint sampleIndex)
+ {
+ return TapUnsafe(sampleIndex, -1);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs b/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs
new file mode 100644
index 00000000..ac59445e
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Effect
+{
+ public interface IDelayLine
+ {
+ uint CurrentSampleCount { get; }
+ uint SampleCountMax { get; }
+
+ void SetDelay(float delayTime);
+ float Read();
+ float Update(float value);
+
+ float TapUnsafe(uint sampleIndex, int offset);
+ float Tap(uint sampleIndex);
+
+ public static float Tap(Span<float> workBuffer, int baseIndex, int sampleIndex, int delaySampleCount)
+ {
+ int targetIndex = baseIndex - sampleIndex;
+
+ if (targetIndex < 0)
+ {
+ targetIndex += delaySampleCount;
+ }
+
+ return workBuffer[targetIndex];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint GetSampleCount(uint sampleRate, float delayTime)
+ {
+ return (uint)MathF.Round(sampleRate * delayTime);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs b/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs
new file mode 100644
index 00000000..0afd8926
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp
+{
+ public static class FixedPointHelper
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ToInt(long value, int qBits)
+ {
+ return (int)(value >> qBits);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ToFloat(long value, int qBits)
+ {
+ return (float)value / (1 << qBits);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ToFixed(float value, int qBits)
+ {
+ return (int)(value * (1 << qBits));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int RoundUpAndToInt(long value, int qBits)
+ {
+ int half = 1 << (qBits - 1);
+
+ return ToInt(value + half, qBits);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs b/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs
new file mode 100644
index 00000000..b143d8f9
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs
@@ -0,0 +1,84 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp
+{
+ public static class FloatingPointHelper
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float MultiplyRoundDown(float a, float b)
+ {
+ return RoundDown(a * b);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float RoundDown(float a)
+ {
+ return MathF.Round(a, 0);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float RoundUp(float a)
+ {
+ return MathF.Round(a);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float MultiplyRoundUp(float a, float b)
+ {
+ return RoundUp(a * b);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Pow10(float x)
+ {
+ // NOTE: Nintendo implementation uses Q15 and a LUT for this, we don't.
+ // As such, we support the same ranges as Nintendo to avoid unexpected behaviours.
+ if (x >= 0.0f)
+ {
+ return 1.0f;
+ }
+ else if (x <= -5.3f)
+ {
+ return 0.0f;
+ }
+
+ return MathF.Pow(10, x);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float DegreesToRadians(float degrees)
+ {
+ return degrees * MathF.PI / 180.0f;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Cos(float value)
+ {
+ return MathF.Cos(DegreesToRadians(value));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Sin(float value)
+ {
+ return MathF.Sin(DegreesToRadians(value));
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs b/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs
new file mode 100644
index 00000000..12c1d475
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs
@@ -0,0 +1,95 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp
+{
+ public static class PcmHelper
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetCountToDecode(int startSampleOffset, int endSampleOffset, int offset, int count)
+ {
+ return Math.Min(count, endSampleOffset - startSampleOffset - offset);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ulong GetBufferOffset<T>(int startSampleOffset, int offset, int channelCount) where T : unmanaged
+ {
+ return (ulong)(Unsafe.SizeOf<T>() * channelCount * (startSampleOffset + offset));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetBufferSize<T>(int startSampleOffset, int endSampleOffset, int offset, int count) where T : unmanaged
+ {
+ return GetCountToDecode(startSampleOffset, endSampleOffset, offset, count) * Unsafe.SizeOf<T>();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Decode(Span<short> output, ReadOnlySpan<short> input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount)
+ {
+ if (input.IsEmpty || endSampleOffset < startSampleOffset)
+ {
+ return 0;
+ }
+
+ int decodedCount = input.Length / channelCount;
+
+ for (int i = 0; i < decodedCount; i++)
+ {
+ output[i] = input[i * channelCount + channelIndex];
+ }
+
+ return decodedCount;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Decode(Span<short> output, ReadOnlySpan<float> input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount)
+ {
+ if (input.IsEmpty || endSampleOffset < startSampleOffset)
+ {
+ return 0;
+ }
+
+ int decodedCount = input.Length / channelCount;
+
+ for (int i = 0; i < decodedCount; i++)
+ {
+ output[i] = (short)(input[i * channelCount + channelIndex] * short.MaxValue);
+ }
+
+ return decodedCount;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static short Saturate(float value)
+ {
+ if (value > short.MaxValue)
+ {
+ return short.MaxValue;
+ }
+
+ if (value < short.MinValue)
+ {
+ return short.MinValue;
+ }
+
+ return (short)value;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs b/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs
new file mode 100644
index 00000000..d40da412
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs
@@ -0,0 +1,624 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
+
+namespace Ryujinx.Audio.Renderer.Dsp
+{
+ public static class ResamplerHelper
+ {
+ #region "Default Quality Lookup Tables"
+ private static short[] _normalCurveLut0 = new short[]
+ {
+ 6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239, 19412, 7093, 22,
+ 6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377, 7472, 41, 5773, 19361, 7600, 48,
+ 5659, 19342, 7728, 55, 5546, 19321, 7857, 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77,
+ 5213, 19245, 8249, 84, 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109,
+ 4785, 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988, 9183, 147,
+ 4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590, 179, 4083, 18793, 9726, 190,
+ 3987, 18738, 9863, 202, 3893, 18682, 10000, 215, 3800, 18624, 10137, 228, 3709, 18563, 10274, 241,
+ 3618, 18500, 10411, 255, 3529, 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300,
+ 3269, 18230, 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375, 369,
+ 2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428, 2709, 17681, 11925, 449,
+ 2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489, 17418, 12334, 517, 2418, 17327, 12470, 541,
+ 2348, 17234, 12606, 566, 2280, 17140, 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647,
+ 2083, 16846, 13144, 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766,
+ 1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667, 16109, 14062, 901,
+ 1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772, 14445, 1013, 1457, 15657, 14571, 1052,
+ 1407, 15540, 14695, 1093, 1359, 15423, 14819, 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221,
+ 1221, 15064, 15185, 1266, 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407,
+ 1052, 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191, 15998, 1613,
+ 901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327, 1780, 798, 13673, 16434, 1838,
+ 766, 13541, 16539, 1897, 735, 13409, 16643, 1958, 704, 13277, 16745, 2020, 675, 13144, 16846, 2083,
+ 647, 13010, 16946, 2147, 619, 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348,
+ 541, 12470, 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595, 2635,
+ 449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863, 388, 11513, 17927, 2942,
+ 369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334, 11100, 18157, 3186, 317, 10962, 18230, 3269,
+ 300, 10824, 18300, 3355, 285, 10687, 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618,
+ 241, 10274, 18563, 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987,
+ 190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157, 9318, 18942, 4377,
+ 147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914, 19073, 4681, 118, 8780, 19112, 4785,
+ 109, 8646, 19148, 4890, 101, 8513, 19183, 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213,
+ 77, 8118, 19273, 5323, 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659,
+ 48, 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219, 19403, 6121,
+ 22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424, 6479, 3, 6722, 19426, 6600
+ };
+
+ private static short[] _normalCurveLut1 = new short[]
+ {
+ -68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450, 32586, 512, -36,
+ -568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454, 1000, -69, -891, 32393, 1174, -80,
+ -990, 32323, 1352, -92, -1084, 32244, 1536, -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128,
+ -1338, 31956, 2118, -140, -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180,
+ -1617, 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995, 3657, -240,
+ -1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393, -289, -1951, 30272, 4648, -307,
+ -1984, 30072, 4908, -325, -2014, 29866, 5172, -343, -2040, 29652, 5442, -362, -2063, 29431, 5716, -382,
+ -2083, 29203, 5994, -403, -2100, 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468,
+ -2133, 28226, 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066, -563,
+ -2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641, -2121, 26285, 9336, -668,
+ -2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084, 25375, 10326, -753, -2067, 25063, 10662, -783,
+ -2049, 24746, 11000, -813, -2030, 24425, 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907,
+ -1962, 23438, 12382, -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039,
+ -1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, 21027, 14877, -1176,
+ -1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, 15965, -1282, -1633, 19600, 16329, -1317,
+ -1599, 19239, 16694, -1353, -1564, 18878, 17058, -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459,
+ -1459, 17787, 18151, -1495, -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599,
+ -1317, 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, 20673, -1732,
+ -1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, -1825, -1072, 13798, 22077, -1855,
+ -1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, -972, 12733, 23103, -1937, -939, 12382, 23438, -1962,
+ -907, 12033, 23771, -1986, -875, 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049,
+ -783, 10662, 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987, -2111,
+ -668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136, -588, 8378, 27151, -2141,
+ -563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514, 7453, 27966, -2139, -490, 7153, 28226, -2133,
+ -468, 6857, 28480, -2125, -445, 6565, 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083,
+ -382, 5716, 29431, -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984,
+ -307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256, 3897, 30826, -1830,
+ -240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192, 31310, -1676, -194, 2967, 31456, -1617,
+ -180, 2747, 31593, -1554, -167, 2532, 31723, -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338,
+ -128, 1919, 32061, -1258, -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990,
+ -80, 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669, 32551, -568,
+ -36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630, -200, -5, 69, 32639, -68
+ };
+
+ private static short[] _normalCurveLut2 = new short[]
+ {
+ 3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811, 26253, 3751, -42,
+ 2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169, 4199, -54, 2338, 26130, 4354, -58,
+ 2227, 26085, 4512, -63, 2120, 26035, 4673, -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76,
+ 1813, 25852, 5174, -81, 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98,
+ 1442, 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239, 6442, -121,
+ 1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027, -140, 897, 24776, 7227, -146,
+ 829, 24648, 7430, -153, 764, 24516, 7635, -159, 701, 24379, 7842, -166, 641, 24237, 8052, -174,
+ 583, 24091, 8264, -181, 526, 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202,
+ 371, 23462, 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809, -230,
+ 194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250, 81, 22208, 10735, -258,
+ 47, 22015, 10970, -265, 15, 21818, 11206, -271, -16, 21618, 11444, -277, -44, 21415, 11684, -283,
+ -71, 21208, 11924, -290, -97, 20999, 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306,
+ -163, 20354, 12898, -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325,
+ -234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273, 18765, 14625, -337,
+ -284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057, 15369, -341, -310, 17817, 15617, -341,
+ -317, 17577, 15864, -340, -323, 17335, 16111, -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336,
+ -336, 16603, 16848, -332, -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317,
+ -341, 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873, 18531, -284,
+ -337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230, -248, -328, 13882, 19459, -234,
+ -325, 13635, 19686, -218, -321, 13389, 19911, -201, -316, 13143, 20134, -183, -311, 12898, 20354, -163,
+ -306, 12653, 20571, -143, -302, 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71,
+ -283, 11684, 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015, 47,
+ -258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154, -237, 10038, 22769, 194,
+ -230, 9809, 22948, 236, -222, 9583, 23123, 279, -215, 9358, 23295, 324, -209, 9135, 23462, 371,
+ -202, 8914, 23626, 420, -194, 8695, 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583,
+ -174, 8052, 24237, 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829,
+ -146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127, 6635, 25131, 1115,
+ -121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066, 25440, 1357, -103, 5882, 25533, 1442,
+ -98, 5701, 25621, 1531, -92, 5522, 25704, 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813,
+ -76, 5004, 25919, 1912, -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227,
+ -58, 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897, 26230, 2688,
+ -42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281, 3064, -32, 3329, 26287, 3195
+ };
+ #endregion
+
+ #region "High Quality Lookup Tables"
+ private static short[] _highCurveLut0 = new short[]
+ {
+ -582, -23, 8740, 16386, 8833, 8, -590, 0, -573, -54, 8647, 16385, 8925, 40, -598, -1,
+ -565, -84, 8555, 16383, 9018, 72, -606, -1, -557, -113, 8462, 16379, 9110, 105, -614, -2,
+ -549, -142, 8370, 16375, 9203, 139, -622, -2, -541, -170, 8277, 16369, 9295, 173, -630, -3,
+ -533, -198, 8185, 16362, 9387, 208, -638, -4, -525, -225, 8093, 16354, 9480, 244, -646, -5,
+ -516, -251, 8000, 16344, 9572, 280, -654, -5, -508, -277, 7908, 16334, 9664, 317, -662, -6,
+ -500, -302, 7816, 16322, 9756, 355, -670, -7, -492, -327, 7724, 16310, 9847, 393, -678, -8,
+ -484, -351, 7632, 16296, 9939, 432, -686, -9, -476, -374, 7540, 16281, 10030, 471, -694, -10,
+ -468, -397, 7449, 16265, 10121, 511, -702, -11, -460, -419, 7357, 16247, 10212, 552, -709, -13,
+ -452, -441, 7266, 16229, 10303, 593, -717, -14, -445, -462, 7175, 16209, 10394, 635, -724, -15,
+ -437, -483, 7084, 16189, 10484, 678, -732, -16, -429, -503, 6994, 16167, 10574, 722, -739, -18,
+ -421, -523, 6903, 16144, 10664, 766, -747, -19, -414, -542, 6813, 16120, 10754, 810, -754, -21,
+ -406, -560, 6723, 16095, 10843, 856, -761, -22, -398, -578, 6633, 16068, 10932, 902, -768, -24,
+ -391, -596, 6544, 16041, 11021, 949, -775, -26, -383, -612, 6454, 16012, 11109, 996, -782, -27,
+ -376, -629, 6366, 15983, 11197, 1044, -789, -29, -368, -645, 6277, 15952, 11285, 1093, -796, -31,
+ -361, -660, 6189, 15920, 11372, 1142, -802, -33, -354, -675, 6100, 15887, 11459, 1192, -809, -35,
+ -347, -689, 6013, 15853, 11546, 1243, -815, -37, -339, -703, 5925, 15818, 11632, 1294, -821, -39,
+ -332, -717, 5838, 15782, 11718, 1346, -827, -41, -325, -730, 5751, 15745, 11803, 1399, -833, -43,
+ -318, -742, 5665, 15707, 11888, 1452, -839, -46, -312, -754, 5579, 15668, 11973, 1506, -845, -48,
+ -305, -766, 5493, 15627, 12057, 1561, -850, -50, -298, -777, 5408, 15586, 12140, 1616, -855, -53,
+ -291, -787, 5323, 15544, 12224, 1672, -861, -56, -285, -798, 5239, 15500, 12306, 1729, -866, -58,
+ -278, -807, 5155, 15456, 12388, 1786, -871, -61, -272, -817, 5071, 15410, 12470, 1844, -875, -64,
+ -265, -826, 4988, 15364, 12551, 1902, -880, -67, -259, -834, 4905, 15317, 12631, 1962, -884, -70,
+ -253, -842, 4823, 15268, 12711, 2022, -888, -73, -247, -850, 4741, 15219, 12790, 2082, -892, -76,
+ -241, -857, 4659, 15168, 12869, 2143, -896, -79, -235, -864, 4578, 15117, 12947, 2205, -899, -82,
+ -229, -870, 4498, 15065, 13025, 2267, -903, -85, -223, -876, 4417, 15012, 13102, 2331, -906, -89,
+ -217, -882, 4338, 14958, 13178, 2394, -909, -92, -211, -887, 4259, 14903, 13254, 2459, -911, -96,
+ -206, -892, 4180, 14847, 13329, 2523, -914, -100, -200, -896, 4102, 14790, 13403, 2589, -916, -103,
+ -195, -900, 4024, 14732, 13477, 2655, -918, -107, -190, -904, 3947, 14673, 13550, 2722, -919, -111,
+ -184, -908, 3871, 14614, 13622, 2789, -921, -115, -179, -911, 3795, 14553, 13693, 2857, -922, -119,
+ -174, -913, 3719, 14492, 13764, 2926, -923, -123, -169, -916, 3644, 14430, 13834, 2995, -923, -127,
+ -164, -918, 3570, 14367, 13904, 3065, -924, -132, -159, -920, 3496, 14303, 13972, 3136, -924, -136,
+ -154, -921, 3423, 14239, 14040, 3207, -924, -140, -150, -922, 3350, 14173, 14107, 3278, -923, -145,
+ -145, -923, 3278, 14107, 14173, 3350, -922, -150, -140, -924, 3207, 14040, 14239, 3423, -921, -154,
+ -136, -924, 3136, 13972, 14303, 3496, -920, -159, -132, -924, 3065, 13904, 14367, 3570, -918, -164,
+ -127, -923, 2995, 13834, 14430, 3644, -916, -169, -123, -923, 2926, 13764, 14492, 3719, -913, -174,
+ -119, -922, 2857, 13693, 14553, 3795, -911, -179, -115, -921, 2789, 13622, 14614, 3871, -908, -184,
+ -111, -919, 2722, 13550, 14673, 3947, -904, -190, -107, -918, 2655, 13477, 14732, 4024, -900, -195,
+ -103, -916, 2589, 13403, 14790, 4102, -896, -200, -100, -914, 2523, 13329, 14847, 4180, -892, -206,
+ -96, -911, 2459, 13254, 14903, 4259, -887, -211, -92, -909, 2394, 13178, 14958, 4338, -882, -217,
+ -89, -906, 2331, 13102, 15012, 4417, -876, -223, -85, -903, 2267, 13025, 15065, 4498, -870, -229,
+ -82, -899, 2205, 12947, 15117, 4578, -864, -235, -79, -896, 2143, 12869, 15168, 4659, -857, -241,
+ -76, -892, 2082, 12790, 15219, 4741, -850, -247, -73, -888, 2022, 12711, 15268, 4823, -842, -253,
+ -70, -884, 1962, 12631, 15317, 4905, -834, -259, -67, -880, 1902, 12551, 15364, 4988, -826, -265,
+ -64, -875, 1844, 12470, 15410, 5071, -817, -272, -61, -871, 1786, 12388, 15456, 5155, -807, -278,
+ -58, -866, 1729, 12306, 15500, 5239, -798, -285, -56, -861, 1672, 12224, 15544, 5323, -787, -291,
+ -53, -855, 1616, 12140, 15586, 5408, -777, -298, -50, -850, 1561, 12057, 15627, 5493, -766, -305,
+ -48, -845, 1506, 11973, 15668, 5579, -754, -312, -46, -839, 1452, 11888, 15707, 5665, -742, -318,
+ -43, -833, 1399, 11803, 15745, 5751, -730, -325, -41, -827, 1346, 11718, 15782, 5838, -717, -332,
+ -39, -821, 1294, 11632, 15818, 5925, -703, -339, -37, -815, 1243, 11546, 15853, 6013, -689, -347,
+ -35, -809, 1192, 11459, 15887, 6100, -675, -354, -33, -802, 1142, 11372, 15920, 6189, -660, -361,
+ -31, -796, 1093, 11285, 15952, 6277, -645, -368, -29, -789, 1044, 11197, 15983, 6366, -629, -376,
+ -27, -782, 996, 11109, 16012, 6454, -612, -383, -26, -775, 949, 11021, 16041, 6544, -596, -391,
+ -24, -768, 902, 10932, 16068, 6633, -578, -398, -22, -761, 856, 10843, 16095, 6723, -560, -406,
+ -21, -754, 810, 10754, 16120, 6813, -542, -414, -19, -747, 766, 10664, 16144, 6903, -523, -421,
+ -18, -739, 722, 10574, 16167, 6994, -503, -429, -16, -732, 678, 10484, 16189, 7084, -483, -437,
+ -15, -724, 635, 10394, 16209, 7175, -462, -445, -14, -717, 593, 10303, 16229, 7266, -441, -452,
+ -13, -709, 552, 10212, 16247, 7357, -419, -460, -11, -702, 511, 10121, 16265, 7449, -397, -468,
+ -10, -694, 471, 10030, 16281, 7540, -374, -476, -9, -686, 432, 9939, 16296, 7632, -351, -484,
+ -8, -678, 393, 9847, 16310, 7724, -327, -492, -7, -670, 355, 9756, 16322, 7816, -302, -500,
+ -6, -662, 317, 9664, 16334, 7908, -277, -508, -5, -654, 280, 9572, 16344, 8000, -251, -516,
+ -5, -646, 244, 9480, 16354, 8093, -225, -525, -4, -638, 208, 9387, 16362, 8185, -198, -533,
+ -3, -630, 173, 9295, 16369, 8277, -170, -541, -2, -622, 139, 9203, 16375, 8370, -142, -549,
+ -2, -614, 105, 9110, 16379, 8462, -113, -557, -1, -606, 72, 9018, 16383, 8555, -84, -565,
+ -1, -598, 40, 8925, 16385, 8647, -54, -573, 0, -590, 8, 8833, 16386, 8740, -23, -582,
+ };
+
+ private static short[] _highCurveLut1 = new short[]
+ {
+ -12, 47, -134, 32767, 81, -16, 2, 0, -26, 108, -345, 32760, 301, -79, 17, -1,
+ -40, 168, -552, 32745, 526, -144, 32, -2, -53, 226, -753, 32723, 755, -210, 47, -3,
+ -66, 284, -950, 32694, 989, -277, 63, -5, -78, 340, -1143, 32658, 1226, -346, 79, -6,
+ -90, 394, -1331, 32615, 1469, -415, 96, -8, -101, 447, -1514, 32564, 1715, -486, 113, -9,
+ -112, 499, -1692, 32506, 1966, -557, 130, -11, -123, 550, -1865, 32441, 2221, -630, 148, -13,
+ -133, 599, -2034, 32369, 2480, -703, 166, -14, -143, 646, -2198, 32290, 2743, -778, 185, -16,
+ -152, 693, -2357, 32204, 3010, -853, 204, -18, -162, 738, -2512, 32110, 3281, -929, 223, -20,
+ -170, 781, -2662, 32010, 3555, -1007, 242, -23, -178, 823, -2807, 31903, 3834, -1084, 262, -25,
+ -186, 864, -2947, 31789, 4116, -1163, 282, -27, -194, 903, -3082, 31668, 4403, -1242, 303, -30,
+ -201, 940, -3213, 31540, 4692, -1322, 323, -32, -207, 977, -3339, 31406, 4985, -1403, 344, -35,
+ -214, 1011, -3460, 31265, 5282, -1484, 365, -37, -220, 1045, -3577, 31117, 5582, -1566, 387, -40,
+ -225, 1077, -3688, 30963, 5885, -1648, 409, -43, -230, 1107, -3796, 30802, 6191, -1730, 431, -46,
+ -235, 1136, -3898, 30635, 6501, -1813, 453, -49, -240, 1164, -3996, 30462, 6813, -1896, 475, -52,
+ -244, 1190, -4089, 30282, 7128, -1980, 498, -55, -247, 1215, -4178, 30097, 7446, -2064, 520, -58,
+ -251, 1239, -4262, 29905, 7767, -2148, 543, -62, -254, 1261, -4342, 29707, 8091, -2231, 566, -65,
+ -257, 1281, -4417, 29503, 8416, -2315, 589, -69, -259, 1301, -4488, 29293, 8745, -2399, 613, -72,
+ -261, 1319, -4555, 29078, 9075, -2483, 636, -76, -263, 1336, -4617, 28857, 9408, -2567, 659, -80,
+ -265, 1351, -4674, 28631, 9743, -2651, 683, -83, -266, 1365, -4728, 28399, 10080, -2734, 706, -87,
+ -267, 1378, -4777, 28161, 10418, -2817, 730, -91, -267, 1389, -4822, 27919, 10759, -2899, 753, -95,
+ -268, 1400, -4863, 27671, 11100, -2981, 777, -99, -268, 1409, -4900, 27418, 11444, -3063, 800, -103,
+ -268, 1416, -4933, 27161, 11789, -3144, 824, -107, -267, 1423, -4962, 26898, 12135, -3224, 847, -112,
+ -267, 1428, -4987, 26631, 12482, -3303, 870, -116, -266, 1433, -5008, 26359, 12830, -3382, 893, -120,
+ -265, 1436, -5026, 26083, 13179, -3460, 916, -125, -264, 1438, -5039, 25802, 13529, -3537, 939, -129,
+ -262, 1438, -5049, 25517, 13880, -3613, 962, -133, -260, 1438, -5055, 25228, 14231, -3687, 984, -138,
+ -258, 1437, -5058, 24935, 14582, -3761, 1006, -142, -256, 1435, -5058, 24639, 14934, -3833, 1028, -147,
+ -254, 1431, -5053, 24338, 15286, -3904, 1049, -151, -252, 1427, -5046, 24034, 15638, -3974, 1071, -155,
+ -249, 1422, -5035, 23726, 15989, -4042, 1091, -160, -246, 1416, -5021, 23415, 16341, -4109, 1112, -164,
+ -243, 1408, -5004, 23101, 16691, -4174, 1132, -169, -240, 1400, -4984, 22783, 17042, -4237, 1152, -173,
+ -237, 1392, -4960, 22463, 17392, -4299, 1171, -178, -234, 1382, -4934, 22140, 17740, -4358, 1190, -182,
+ -230, 1371, -4905, 21814, 18088, -4416, 1209, -186, -227, 1360, -4873, 21485, 18435, -4472, 1226, -191,
+ -223, 1348, -4839, 21154, 18781, -4526, 1244, -195, -219, 1335, -4801, 20821, 19125, -4578, 1260, -199,
+ -215, 1321, -4761, 20486, 19468, -4627, 1277, -203, -211, 1307, -4719, 20148, 19809, -4674, 1292, -207,
+ -207, 1292, -4674, 19809, 20148, -4719, 1307, -211, -203, 1277, -4627, 19468, 20486, -4761, 1321, -215,
+ -199, 1260, -4578, 19125, 20821, -4801, 1335, -219, -195, 1244, -4526, 18781, 21154, -4839, 1348, -223,
+ -191, 1226, -4472, 18435, 21485, -4873, 1360, -227, -186, 1209, -4416, 18088, 21814, -4905, 1371, -230,
+ -182, 1190, -4358, 17740, 22140, -4934, 1382, -234, -178, 1171, -4299, 17392, 22463, -4960, 1392, -237,
+ -173, 1152, -4237, 17042, 22783, -4984, 1400, -240, -169, 1132, -4174, 16691, 23101, -5004, 1408, -243,
+ -164, 1112, -4109, 16341, 23415, -5021, 1416, -246, -160, 1091, -4042, 15989, 23726, -5035, 1422, -249,
+ -155, 1071, -3974, 15638, 24034, -5046, 1427, -252, -151, 1049, -3904, 15286, 24338, -5053, 1431, -254,
+ -147, 1028, -3833, 14934, 24639, -5058, 1435, -256, -142, 1006, -3761, 14582, 24935, -5058, 1437, -258,
+ -138, 984, -3687, 14231, 25228, -5055, 1438, -260, -133, 962, -3613, 13880, 25517, -5049, 1438, -262,
+ -129, 939, -3537, 13529, 25802, -5039, 1438, -264, -125, 916, -3460, 13179, 26083, -5026, 1436, -265,
+ -120, 893, -3382, 12830, 26359, -5008, 1433, -266, -116, 870, -3303, 12482, 26631, -4987, 1428, -267,
+ -112, 847, -3224, 12135, 26898, -4962, 1423, -267, -107, 824, -3144, 11789, 27161, -4933, 1416, -268,
+ -103, 800, -3063, 11444, 27418, -4900, 1409, -268, -99, 777, -2981, 11100, 27671, -4863, 1400, -268,
+ -95, 753, -2899, 10759, 27919, -4822, 1389, -267, -91, 730, -2817, 10418, 28161, -4777, 1378, -267,
+ -87, 706, -2734, 10080, 28399, -4728, 1365, -266, -83, 683, -2651, 9743, 28631, -4674, 1351, -265,
+ -80, 659, -2567, 9408, 28857, -4617, 1336, -263, -76, 636, -2483, 9075, 29078, -4555, 1319, -261,
+ -72, 613, -2399, 8745, 29293, -4488, 1301, -259, -69, 589, -2315, 8416, 29503, -4417, 1281, -257,
+ -65, 566, -2231, 8091, 29707, -4342, 1261, -254, -62, 543, -2148, 7767, 29905, -4262, 1239, -251,
+ -58, 520, -2064, 7446, 30097, -4178, 1215, -247, -55, 498, -1980, 7128, 30282, -4089, 1190, -244,
+ -52, 475, -1896, 6813, 30462, -3996, 1164, -240, -49, 453, -1813, 6501, 30635, -3898, 1136, -235,
+ -46, 431, -1730, 6191, 30802, -3796, 1107, -230, -43, 409, -1648, 5885, 30963, -3688, 1077, -225,
+ -40, 387, -1566, 5582, 31117, -3577, 1045, -220, -37, 365, -1484, 5282, 31265, -3460, 1011, -214,
+ -35, 344, -1403, 4985, 31406, -3339, 977, -207, -32, 323, -1322, 4692, 31540, -3213, 940, -201,
+ -30, 303, -1242, 4403, 31668, -3082, 903, -194, -27, 282, -1163, 4116, 31789, -2947, 864, -186,
+ -25, 262, -1084, 3834, 31903, -2807, 823, -178, -23, 242, -1007, 3555, 32010, -2662, 781, -170,
+ -20, 223, -929, 3281, 32110, -2512, 738, -162, -18, 204, -853, 3010, 32204, -2357, 693, -152,
+ -16, 185, -778, 2743, 32290, -2198, 646, -143, -14, 166, -703, 2480, 32369, -2034, 599, -133,
+ -13, 148, -630, 2221, 32441, -1865, 550, -123, -11, 130, -557, 1966, 32506, -1692, 499, -112,
+ -9, 113, -486, 1715, 32564, -1514, 447, -101, -8, 96, -415, 1469, 32615, -1331, 394, -90,
+ -6, 79, -346, 1226, 32658, -1143, 340, -78, -5, 63, -277, 989, 32694, -950, 284, -66,
+ -3, 47, -210, 755, 32723, -753, 226, -53, -2, 32, -144, 526, 32745, -552, 168, -40,
+ -1, 17, -79, 301, 32760, -345, 108, -26, 0, 2, -16, 81, 32767, -134, 47, -12,
+ };
+
+ private static short[] _highCurveLut2 = new short[]
+ {
+ 418, -2538, 6118, 24615, 6298, -2563, 417, 0, 420, -2512, 5939, 24611, 6479, -2588, 415, 1,
+ 421, -2485, 5761, 24605, 6662, -2612, 412, 2, 422, -2458, 5585, 24595, 6846, -2635, 409, 3,
+ 423, -2430, 5410, 24582, 7030, -2658, 406, 4, 423, -2402, 5236, 24565, 7216, -2680, 403, 5,
+ 423, -2373, 5064, 24546, 7403, -2701, 399, 6, 423, -2343, 4893, 24523, 7591, -2721, 395, 7,
+ 423, -2313, 4724, 24496, 7780, -2741, 391, 8, 422, -2283, 4556, 24467, 7970, -2759, 386, 9,
+ 421, -2252, 4390, 24434, 8161, -2777, 381, 11, 420, -2221, 4225, 24398, 8353, -2794, 376, 12,
+ 419, -2190, 4062, 24359, 8545, -2810, 370, 14, 418, -2158, 3900, 24316, 8739, -2825, 364, 15,
+ 416, -2126, 3740, 24271, 8933, -2839, 358, 17, 414, -2093, 3582, 24222, 9127, -2851, 351, 19,
+ 412, -2060, 3425, 24170, 9323, -2863, 344, 21, 410, -2027, 3270, 24115, 9519, -2874, 336, 22,
+ 407, -1993, 3117, 24056, 9715, -2884, 328, 24, 404, -1960, 2966, 23995, 9912, -2893, 319, 26,
+ 402, -1926, 2816, 23930, 10110, -2900, 311, 29, 398, -1892, 2668, 23863, 10308, -2907, 301, 31,
+ 395, -1858, 2522, 23792, 10506, -2912, 292, 33, 392, -1823, 2378, 23718, 10705, -2916, 282, 35,
+ 389, -1789, 2235, 23641, 10904, -2919, 271, 38, 385, -1754, 2095, 23561, 11103, -2920, 261, 40,
+ 381, -1719, 1956, 23478, 11303, -2921, 249, 43, 377, -1684, 1819, 23393, 11502, -2920, 238, 45,
+ 373, -1649, 1684, 23304, 11702, -2917, 225, 48, 369, -1615, 1551, 23212, 11902, -2914, 213, 51,
+ 365, -1580, 1420, 23118, 12102, -2909, 200, 54, 361, -1545, 1291, 23020, 12302, -2902, 186, 57,
+ 356, -1510, 1163, 22920, 12502, -2895, 173, 60, 352, -1475, 1038, 22817, 12702, -2885, 158, 63,
+ 347, -1440, 915, 22711, 12901, -2875, 143, 66, 342, -1405, 793, 22602, 13101, -2863, 128, 69,
+ 338, -1370, 674, 22491, 13300, -2849, 113, 73, 333, -1335, 557, 22377, 13499, -2834, 97, 76,
+ 328, -1301, 441, 22260, 13698, -2817, 80, 80, 323, -1266, 328, 22141, 13896, -2799, 63, 83,
+ 318, -1232, 217, 22019, 14094, -2779, 46, 87, 313, -1197, 107, 21894, 14291, -2758, 28, 91,
+ 307, -1163, 0, 21767, 14488, -2735, 9, 95, 302, -1129, -105, 21637, 14684, -2710, -9, 98,
+ 297, -1096, -208, 21506, 14879, -2684, -29, 102, 292, -1062, -310, 21371, 15074, -2656, -48, 106,
+ 286, -1029, -409, 21234, 15268, -2626, -69, 111, 281, -996, -506, 21095, 15461, -2595, -89, 115,
+ 276, -963, -601, 20954, 15654, -2562, -110, 119, 270, -930, -694, 20810, 15846, -2527, -132, 123,
+ 265, -898, -785, 20664, 16036, -2490, -154, 128, 260, -866, -874, 20516, 16226, -2452, -176, 132,
+ 254, -834, -961, 20366, 16415, -2411, -199, 137, 249, -803, -1046, 20213, 16602, -2369, -222, 141,
+ 243, -771, -1129, 20059, 16789, -2326, -246, 146, 238, -740, -1209, 19902, 16974, -2280, -270, 151,
+ 233, -710, -1288, 19744, 17158, -2232, -294, 156, 227, -680, -1365, 19583, 17341, -2183, -319, 160,
+ 222, -650, -1440, 19421, 17523, -2132, -345, 165, 217, -620, -1513, 19257, 17703, -2079, -370, 170,
+ 211, -591, -1583, 19091, 17882, -2023, -396, 175, 206, -562, -1652, 18923, 18059, -1966, -423, 180,
+ 201, -533, -1719, 18754, 18235, -1907, -450, 185, 196, -505, -1784, 18582, 18410, -1847, -477, 191,
+ 191, -477, -1847, 18410, 18582, -1784, -505, 196, 185, -450, -1907, 18235, 18754, -1719, -533, 201,
+ 180, -423, -1966, 18059, 18923, -1652, -562, 206, 175, -396, -2023, 17882, 19091, -1583, -591, 211,
+ 170, -370, -2079, 17703, 19257, -1513, -620, 217, 165, -345, -2132, 17523, 19421, -1440, -650, 222,
+ 160, -319, -2183, 17341, 19583, -1365, -680, 227, 156, -294, -2232, 17158, 19744, -1288, -710, 233,
+ 151, -270, -2280, 16974, 19902, -1209, -740, 238, 146, -246, -2326, 16789, 20059, -1129, -771, 243,
+ 141, -222, -2369, 16602, 20213, -1046, -803, 249, 137, -199, -2411, 16415, 20366, -961, -834, 254,
+ 132, -176, -2452, 16226, 20516, -874, -866, 260, 128, -154, -2490, 16036, 20664, -785, -898, 265,
+ 123, -132, -2527, 15846, 20810, -694, -930, 270, 119, -110, -2562, 15654, 20954, -601, -963, 276,
+ 115, -89, -2595, 15461, 21095, -506, -996, 281, 111, -69, -2626, 15268, 21234, -409, -1029, 286,
+ 106, -48, -2656, 15074, 21371, -310, -1062, 292, 102, -29, -2684, 14879, 21506, -208, -1096, 297,
+ 98, -9, -2710, 14684, 21637, -105, -1129, 302, 95, 9, -2735, 14488, 21767, 0, -1163, 307,
+ 91, 28, -2758, 14291, 21894, 107, -1197, 313, 87, 46, -2779, 14094, 22019, 217, -1232, 318,
+ 83, 63, -2799, 13896, 22141, 328, -1266, 323, 80, 80, -2817, 13698, 22260, 441, -1301, 328,
+ 76, 97, -2834, 13499, 22377, 557, -1335, 333, 73, 113, -2849, 13300, 22491, 674, -1370, 338,
+ 69, 128, -2863, 13101, 22602, 793, -1405, 342, 66, 143, -2875, 12901, 22711, 915, -1440, 347,
+ 63, 158, -2885, 12702, 22817, 1038, -1475, 352, 60, 173, -2895, 12502, 22920, 1163, -1510, 356,
+ 57, 186, -2902, 12302, 23020, 1291, -1545, 361, 54, 200, -2909, 12102, 23118, 1420, -1580, 365,
+ 51, 213, -2914, 11902, 23212, 1551, -1615, 369, 48, 225, -2917, 11702, 23304, 1684, -1649, 373,
+ 45, 238, -2920, 11502, 23393, 1819, -1684, 377, 43, 249, -2921, 11303, 23478, 1956, -1719, 381,
+ 40, 261, -2920, 11103, 23561, 2095, -1754, 385, 38, 271, -2919, 10904, 23641, 2235, -1789, 389,
+ 35, 282, -2916, 10705, 23718, 2378, -1823, 392, 33, 292, -2912, 10506, 23792, 2522, -1858, 395,
+ 31, 301, -2907, 10308, 23863, 2668, -1892, 398, 29, 311, -2900, 10110, 23930, 2816, -1926, 402,
+ 26, 319, -2893, 9912, 23995, 2966, -1960, 404, 24, 328, -2884, 9715, 24056, 3117, -1993, 407,
+ 22, 336, -2874, 9519, 24115, 3270, -2027, 410, 21, 344, -2863, 9323, 24170, 3425, -2060, 412,
+ 19, 351, -2851, 9127, 24222, 3582, -2093, 414, 17, 358, -2839, 8933, 24271, 3740, -2126, 416,
+ 15, 364, -2825, 8739, 24316, 3900, -2158, 418, 14, 370, -2810, 8545, 24359, 4062, -2190, 419,
+ 12, 376, -2794, 8353, 24398, 4225, -2221, 420, 11, 381, -2777, 8161, 24434, 4390, -2252, 421,
+ 9, 386, -2759, 7970, 24467, 4556, -2283, 422, 8, 391, -2741, 7780, 24496, 4724, -2313, 423,
+ 7, 395, -2721, 7591, 24523, 4893, -2343, 423, 6, 399, -2701, 7403, 24546, 5064, -2373, 423,
+ 5, 403, -2680, 7216, 24565, 5236, -2402, 423, 4, 406, -2658, 7030, 24582, 5410, -2430, 423,
+ 3, 409, -2635, 6846, 24595, 5585, -2458, 422, 2, 412, -2612, 6662, 24605, 5761, -2485, 421,
+ 1, 415, -2588, 6479, 24611, 5939, -2512, 420, 0, 417, -2563, 6298, 24615, 6118, -2538, 418,
+ };
+ #endregion
+
+ private static float[] _normalCurveLut0F;
+ private static float[] _normalCurveLut1F;
+ private static float[] _normalCurveLut2F;
+
+ private static float[] _highCurveLut0F;
+ private static float[] _highCurveLut1F;
+ private static float[] _highCurveLut2F;
+
+ static ResamplerHelper()
+ {
+ _normalCurveLut0F = _normalCurveLut0.Select(x => x / 32768f).ToArray();
+ _normalCurveLut1F = _normalCurveLut1.Select(x => x / 32768f).ToArray();
+ _normalCurveLut2F = _normalCurveLut2.Select(x => x / 32768f).ToArray();
+
+ _highCurveLut0F = _highCurveLut0.Select(x => x / 32768f).ToArray();
+ _highCurveLut1F = _highCurveLut1.Select(x => x / 32768f).ToArray();
+ _highCurveLut2F = _highCurveLut2.Select(x => x / 32768f).ToArray();
+ }
+
+ private const int FixedPointPrecision = 15;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Resample(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount, SampleRateConversionQuality srcQuality, bool needPitch)
+ {
+ switch (srcQuality)
+ {
+ case SampleRateConversionQuality.Default:
+ ResampleDefaultQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount, needPitch);
+ break;
+ case SampleRateConversionQuality.Low:
+ ResampleLowQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount);
+ break;
+ case SampleRateConversionQuality.High:
+ ResampleHighQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount);
+ break;
+ default:
+ throw new NotImplementedException($"{srcQuality}");
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ReadOnlySpan<float> GetDefaultParameter(float ratio)
+ {
+ if (ratio <= 1.0f)
+ {
+ return _normalCurveLut1F;
+ }
+ else if (ratio > 1.333313f)
+ {
+ return _normalCurveLut0F;
+ }
+
+ return _normalCurveLut2F;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe static void ResampleDefaultQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount, bool needPitch)
+ {
+ ReadOnlySpan<float> parameters = GetDefaultParameter(ratio);
+
+ int inputBufferIndex = 0, i = 0;
+
+ // TODO: REV8 fast path (when needPitch == false the input index progression is constant + we need SIMD)
+
+ if (Sse41.IsSupported)
+ {
+ if (ratio == 1f)
+ {
+ fixed (short* pInput = inputBuffer)
+ {
+ fixed (float* pOutput = outputBuffer, pParameters = parameters)
+ {
+ Vector128<float> parameter = Sse.LoadVector128(pParameters);
+
+ for (; i < (sampleCount & ~3); i += 4)
+ {
+ Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + (uint)i);
+ Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 1);
+ Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 2);
+ Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 3);
+
+ Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
+ Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
+ Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
+ Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
+
+ Vector128<float> mix0 = Sse.Multiply(input0, parameter);
+ Vector128<float> mix1 = Sse.Multiply(input1, parameter);
+ Vector128<float> mix2 = Sse.Multiply(input2, parameter);
+ Vector128<float> mix3 = Sse.Multiply(input3, parameter);
+
+ Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
+ Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
+
+ Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
+
+ Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
+ }
+ }
+ }
+
+ inputBufferIndex = i;
+ }
+ else
+ {
+ fixed (short* pInput = inputBuffer)
+ {
+ fixed (float* pOutput = outputBuffer, pParameters = parameters)
+ {
+ for (; i < (sampleCount & ~3); i += 4)
+ {
+ uint baseIndex0 = (uint)(fraction * 128) * 4;
+ uint inputIndex0 = (uint)inputBufferIndex;
+
+ fraction += ratio;
+
+ uint baseIndex1 = ((uint)(fraction * 128) & 127) * 4;
+ uint inputIndex1 = (uint)inputBufferIndex + (uint)fraction;
+
+ fraction += ratio;
+
+ uint baseIndex2 = ((uint)(fraction * 128) & 127) * 4;
+ uint inputIndex2 = (uint)inputBufferIndex + (uint)fraction;
+
+ fraction += ratio;
+
+ uint baseIndex3 = ((uint)(fraction * 128) & 127) * 4;
+ uint inputIndex3 = (uint)inputBufferIndex + (uint)fraction;
+
+ fraction += ratio;
+ inputBufferIndex += (int)fraction;
+
+ // Only keep lower part (safe as fraction isn't supposed to be negative)
+ fraction -= (int)fraction;
+
+ Vector128<float> parameter0 = Sse.LoadVector128(pParameters + baseIndex0);
+ Vector128<float> parameter1 = Sse.LoadVector128(pParameters + baseIndex1);
+ Vector128<float> parameter2 = Sse.LoadVector128(pParameters + baseIndex2);
+ Vector128<float> parameter3 = Sse.LoadVector128(pParameters + baseIndex3);
+
+ Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + inputIndex0);
+ Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + inputIndex1);
+ Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + inputIndex2);
+ Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + inputIndex3);
+
+ Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
+ Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
+ Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
+ Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
+
+ Vector128<float> mix0 = Sse.Multiply(input0, parameter0);
+ Vector128<float> mix1 = Sse.Multiply(input1, parameter1);
+ Vector128<float> mix2 = Sse.Multiply(input2, parameter2);
+ Vector128<float> mix3 = Sse.Multiply(input3, parameter3);
+
+ Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
+ Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
+
+ Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
+
+ Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
+ }
+ }
+ }
+ }
+ }
+
+ for (; i < sampleCount; i++)
+ {
+ int baseIndex = (int)(fraction * 128) * 4;
+ ReadOnlySpan<float> parameter = parameters.Slice(baseIndex, 4);
+ ReadOnlySpan<short> currentInput = inputBuffer.Slice(inputBufferIndex, 4);
+
+ outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] +
+ currentInput[1] * parameter[1] +
+ currentInput[2] * parameter[2] +
+ currentInput[3] * parameter[3]);
+
+ fraction += ratio;
+ inputBufferIndex += (int)fraction;
+
+ // Only keep lower part (safe as fraction isn't supposed to be negative)
+ fraction -= (int)fraction;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ReadOnlySpan<float> GetHighParameter(float ratio)
+ {
+ if (ratio <= 1.0f)
+ {
+ return _highCurveLut1F;
+ }
+ else if (ratio > 1.333313f)
+ {
+ return _highCurveLut0F;
+ }
+
+ return _highCurveLut2F;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void ResampleHighQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount)
+ {
+ ReadOnlySpan<float> parameters = GetHighParameter(ratio);
+
+ int inputBufferIndex = 0;
+
+ // TODO: fast path
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ int baseIndex = (int)(fraction * 128) * 8;
+ ReadOnlySpan<float> parameter = parameters.Slice(baseIndex, 8);
+ ReadOnlySpan<short> currentInput = inputBuffer.Slice(inputBufferIndex, 8);
+
+ outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] +
+ currentInput[1] * parameter[1] +
+ currentInput[2] * parameter[2] +
+ currentInput[3] * parameter[3] +
+ currentInput[4] * parameter[4] +
+ currentInput[5] * parameter[5] +
+ currentInput[6] * parameter[6] +
+ currentInput[7] * parameter[7]);
+
+ fraction += ratio;
+ inputBufferIndex += (int)MathF.Truncate(fraction);
+
+ fraction -= (int)fraction;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ResampleLowQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount)
+ {
+ int inputBufferIndex = 0;
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ int outputData = inputBuffer[inputBufferIndex];
+
+ if (fraction > 1.0f)
+ {
+ outputData = inputBuffer[inputBufferIndex + 1];
+ }
+
+ outputBuffer[i] = outputData;
+
+ fraction += ratio;
+ inputBufferIndex += (int)MathF.Truncate(fraction);
+
+ fraction -= (int)fraction;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ResampleForUpsampler(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float ratio, ref float fraction, int sampleCount)
+ {
+ // TODO: use a bandwidth filter to have better resampling.
+ int inputBufferIndex = 0;
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float outputData = inputBuffer[inputBufferIndex];
+
+ if (fraction > 1.0f)
+ {
+ outputData = inputBuffer[inputBufferIndex + 1];
+ }
+
+ outputBuffer[i] = outputData;
+
+ fraction += ratio;
+ inputBufferIndex += (int)MathF.Truncate(fraction);
+
+ fraction -= (int)fraction;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs b/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs
new file mode 100644
index 00000000..695b32d2
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.State
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 6)]
+ public struct AdpcmLoopContext
+ {
+ public short PredScale;
+ public short History0;
+ public short History1;
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs b/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs
new file mode 100644
index 00000000..3cf24302
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.State
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x40)]
+ public struct AuxiliaryBufferHeader
+ {
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0xC)]
+ public struct AuxiliaryBufferInfo
+ {
+ private const uint ReadOffsetPosition = 0x0;
+ private const uint WriteOffsetPosition = 0x4;
+
+ public uint ReadOffset;
+ public uint WriteOffset;
+ private uint _reserved;
+
+ public static uint GetReadOffset(IVirtualMemoryManager manager, ulong bufferAddress)
+ {
+ return manager.Read<uint>(bufferAddress + ReadOffsetPosition);
+ }
+
+ public static uint GetWriteOffset(IVirtualMemoryManager manager, ulong bufferAddress)
+ {
+ return manager.Read<uint>(bufferAddress + WriteOffsetPosition);
+ }
+
+ public static void SetReadOffset(IVirtualMemoryManager manager, ulong bufferAddress, uint value)
+ {
+ manager.Write(bufferAddress + ReadOffsetPosition, value);
+ }
+
+ public static void SetWriteOffset(IVirtualMemoryManager manager, ulong bufferAddress, uint value)
+ {
+ manager.Write(bufferAddress + WriteOffsetPosition, value);
+ }
+ }
+
+ public AuxiliaryBufferInfo BufferInfo;
+ public unsafe fixed uint Unknown[13];
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs b/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
new file mode 100644
index 00000000..9677333d
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.State
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
+ public struct BiquadFilterState
+ {
+ public float Z1;
+ public float Z2;
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs b/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs
new file mode 100644
index 00000000..7b694fb0
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs
@@ -0,0 +1,73 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.Effect;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+
+namespace Ryujinx.Audio.Renderer.Dsp.State
+{
+ public class DelayState
+ {
+ public DelayLine[] DelayLines { get; }
+ public float[] LowPassZ { get; set; }
+ public float FeedbackGain { get; private set; }
+ public float DelayFeedbackBaseGain { get; private set; }
+ public float DelayFeedbackCrossGain { get; private set; }
+ public float LowPassFeedbackGain { get; private set; }
+ public float LowPassBaseGain { get; private set; }
+
+ private const int FixedPointPrecision = 14;
+
+ public DelayState(ref DelayParameter parameter, ulong workBuffer)
+ {
+ DelayLines = new DelayLine[parameter.ChannelCount];
+ LowPassZ = new float[parameter.ChannelCount];
+
+ uint sampleRate = (uint)FixedPointHelper.ToInt(parameter.SampleRate, FixedPointPrecision) / 1000;
+
+ for (int i = 0; i < DelayLines.Length; i++)
+ {
+ DelayLines[i] = new DelayLine(sampleRate, parameter.DelayTimeMax);
+ DelayLines[i].SetDelay(parameter.DelayTime);
+ LowPassZ[0] = 0;
+ }
+
+ UpdateParameter(ref parameter);
+ }
+
+ public void UpdateParameter(ref DelayParameter parameter)
+ {
+ FeedbackGain = FixedPointHelper.ToFloat(parameter.FeedbackGain, FixedPointPrecision) * 0.98f;
+
+ float channelSpread = FixedPointHelper.ToFloat(parameter.ChannelSpread, FixedPointPrecision);
+
+ DelayFeedbackBaseGain = (1.0f - channelSpread) * FeedbackGain;
+
+ if (parameter.ChannelCount == 4 || parameter.ChannelCount == 6)
+ {
+ DelayFeedbackCrossGain = channelSpread * 0.5f * FeedbackGain;
+ }
+ else
+ {
+ DelayFeedbackCrossGain = channelSpread * FeedbackGain;
+ }
+
+ LowPassFeedbackGain = 0.95f * FixedPointHelper.ToFloat(parameter.LowPassAmount, FixedPointPrecision);
+ LowPassBaseGain = 1.0f - LowPassFeedbackGain;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs b/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs
new file mode 100644
index 00000000..be0f9734
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs
@@ -0,0 +1,136 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.Effect;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.State
+{
+ public class Reverb3dState
+ {
+ private readonly float[] FdnDelayMinTimes = new float[4] { 5.0f, 6.0f, 13.0f, 14.0f };
+ private readonly float[] FdnDelayMaxTimes = new float[4] { 45.704f, 82.782f, 149.94f, 271.58f };
+ private readonly float[] DecayDelayMaxTimes1 = new float[4] { 17.0f, 13.0f, 9.0f, 7.0f };
+ private readonly float[] DecayDelayMaxTimes2 = new float[4] { 19.0f, 11.0f, 10.0f, 6.0f };
+ private readonly float[] EarlyDelayTimes = new float[20] { 0.017136f, 0.059154f, 0.16173f, 0.39019f, 0.42526f, 0.45541f, 0.68974f, 0.74591f, 0.83384f, 0.8595f, 0.0f, 0.075024f, 0.16879f, 0.2999f, 0.33744f, 0.3719f, 0.59901f, 0.71674f, 0.81786f, 0.85166f };
+ public readonly float[] EarlyGain = new float[20] { 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f, 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f };
+
+ public IDelayLine[] FdnDelayLines { get; }
+ public DecayDelay[] DecayDelays1 { get; }
+ public DecayDelay[] DecayDelays2 { get; }
+ public IDelayLine PreDelayLine { get; }
+ public IDelayLine BackLeftDelayLine { get; }
+ public float DryGain { get; private set; }
+ public uint[] EarlyDelayTime { get; private set; }
+ public float PreviousPreDelayValue { get; set; }
+ public float PreviousPreDelayGain { get; private set; }
+ public float TargetPreDelayGain { get; private set; }
+ public float EarlyReflectionsGain { get; private set; }
+ public float LateReverbGain { get; private set; }
+ public uint ReflectionDelayTime { get; private set; }
+ public float EchoLateReverbDecay { get; private set; }
+ public float[] DecayDirectFdnGain { get; private set; }
+ public float[] DecayCurrentFdnGain { get; private set; }
+ public float[] DecayCurrentOutputGain { get; private set; }
+ public float[] PreviousFeedbackOutputDecayed { get; private set; }
+
+ public Reverb3dState(ref Reverb3dParameter parameter, ulong workBuffer)
+ {
+ FdnDelayLines = new IDelayLine[4];
+ DecayDelays1 = new DecayDelay[4];
+ DecayDelays2 = new DecayDelay[4];
+ DecayDirectFdnGain = new float[4];
+ DecayCurrentFdnGain = new float[4];
+ DecayCurrentOutputGain = new float[4];
+ PreviousFeedbackOutputDecayed = new float[4];
+
+ uint sampleRate = parameter.SampleRate / 1000;
+
+ for (int i = 0; i < 4; i++)
+ {
+ FdnDelayLines[i] = new DelayLine3d(sampleRate, FdnDelayMaxTimes[i]);
+ DecayDelays1[i] = new DecayDelay(new DelayLine3d(sampleRate, DecayDelayMaxTimes1[i]));
+ DecayDelays2[i] = new DecayDelay(new DelayLine3d(sampleRate, DecayDelayMaxTimes2[i]));
+ }
+
+ PreDelayLine = new DelayLine3d(sampleRate, 400);
+ BackLeftDelayLine = new DelayLine3d(sampleRate, 5);
+
+ UpdateParameter(ref parameter);
+ }
+
+ public void UpdateParameter(ref Reverb3dParameter parameter)
+ {
+ uint sampleRate = parameter.SampleRate / 1000;
+
+ EarlyDelayTime = new uint[20];
+ DryGain = parameter.DryGain;
+ PreviousFeedbackOutputDecayed.AsSpan().Fill(0);
+ PreviousPreDelayValue = 0;
+
+ EarlyReflectionsGain = FloatingPointHelper.Pow10(Math.Min(parameter.RoomGain + parameter.ReflectionsGain, 5000.0f) / 2000.0f);
+ LateReverbGain = FloatingPointHelper.Pow10(Math.Min(parameter.RoomGain + parameter.ReverbGain, 5000.0f) / 2000.0f);
+
+ float highFrequencyRoomGain = FloatingPointHelper.Pow10(parameter.RoomHf / 2000.0f);
+
+ if (highFrequencyRoomGain < 1.0f)
+ {
+ float tempA = 1.0f - highFrequencyRoomGain;
+ float tempB = 2.0f - ((2.0f * highFrequencyRoomGain) * FloatingPointHelper.Cos(256.0f * parameter.HfReference / parameter.SampleRate));
+ float tempC = MathF.Sqrt(MathF.Pow(tempB, 2) - (4.0f * (1.0f - highFrequencyRoomGain) * (1.0f - highFrequencyRoomGain)));
+
+ PreviousPreDelayGain = (tempB - tempC) / (2.0f * tempA);
+ TargetPreDelayGain = 1.0f - PreviousPreDelayGain;
+ }
+ else
+ {
+ PreviousPreDelayGain = 0.0f;
+ TargetPreDelayGain = 1.0f;
+ }
+
+ ReflectionDelayTime = IDelayLine.GetSampleCount(sampleRate, 1000.0f * (parameter.ReflectionDelay + parameter.ReverbDelayTime));
+ EchoLateReverbDecay = 0.6f * parameter.Diffusion * 0.01f;
+
+ for (int i = 0; i < FdnDelayLines.Length; i++)
+ {
+ FdnDelayLines[i].SetDelay(FdnDelayMinTimes[i] + (parameter.Density / 100 * (FdnDelayMaxTimes[i] - FdnDelayMinTimes[i])));
+
+ uint tempSampleCount = FdnDelayLines[i].CurrentSampleCount + DecayDelays1[i].CurrentSampleCount + DecayDelays2[i].CurrentSampleCount;
+
+ float tempA = (-60.0f * tempSampleCount) / (parameter.DecayTime * parameter.SampleRate);
+ float tempB = tempA / parameter.HfDecayRatio;
+ float tempC = FloatingPointHelper.Cos(128.0f * 0.5f * parameter.HfReference / parameter.SampleRate) / FloatingPointHelper.Sin(128.0f * 0.5f * parameter.HfReference / parameter.SampleRate);
+ float tempD = FloatingPointHelper.Pow10((tempB - tempA) / 40.0f);
+ float tempE = FloatingPointHelper.Pow10((tempB + tempA) / 40.0f) * 0.7071f;
+
+ DecayDirectFdnGain[i] = tempE * ((tempD * tempC) + 1.0f) / (tempC + tempD);
+ DecayCurrentFdnGain[i] = tempE * (1.0f - (tempD * tempC)) / (tempC + tempD);
+ DecayCurrentOutputGain[i] = (tempC - tempD) / (tempC + tempD);
+
+ DecayDelays1[i].SetDecayRate(EchoLateReverbDecay);
+ DecayDelays2[i].SetDecayRate(EchoLateReverbDecay * -0.9f);
+ }
+
+ for (int i = 0; i < EarlyDelayTime.Length; i++)
+ {
+ uint sampleCount = Math.Min(IDelayLine.GetSampleCount(sampleRate, (parameter.ReflectionDelay * 1000.0f) + (EarlyDelayTimes[i] * 1000.0f * ((parameter.ReverbDelayTime * 0.9998f) + 0.02f))), PreDelayLine.SampleCountMax);
+ EarlyDelayTime[i] = sampleCount;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs b/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs
new file mode 100644
index 00000000..60d8e284
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs
@@ -0,0 +1,221 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.Effect;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.State
+{
+ public class ReverbState
+ {
+ private static readonly float[] FdnDelayTimes = new float[20]
+ {
+ // Room
+ 53.953247f, 79.192566f, 116.238770f, 130.615295f,
+ // Hall
+ 53.953247f, 79.192566f, 116.238770f, 170.615295f,
+ // Plate
+ 5f, 10f, 5f, 10f,
+ // Cathedral
+ 47.03f, 71f, 103f, 170f,
+ // Max delay (Hall is the one with the highest values so identical to Hall)
+ 53.953247f, 79.192566f, 116.238770f, 170.615295f,
+ };
+
+ private static readonly float[] DecayDelayTimes = new float[20]
+ {
+ // Room
+ 7f, 9f, 13f, 17f,
+ // Hall
+ 7f, 9f, 13f, 17f,
+ // Plate (no decay)
+ 1f, 1f, 1f, 1f,
+ // Cathedral
+ 7f, 7f, 13f, 9f,
+ // Max delay (Hall is the one with the highest values so identical to Hall)
+ 7f, 9f, 13f, 17f,
+ };
+
+ private static readonly float[] EarlyDelayTimes = new float[50]
+ {
+ // Room
+ 0.0f, 3.5f, 2.8f, 3.9f, 2.7f, 13.4f, 7.9f, 8.4f, 9.9f, 12.0f,
+ // Chamber
+ 0.0f, 11.8f, 5.5f, 11.2f, 10.4f, 38.1f, 22.2f, 29.6f, 21.2f, 24.8f,
+ // Hall
+ 0.0f, 41.5f, 20.5f, 41.3f, 0.0f, 29.5f, 33.8f, 45.2f, 46.8f, 0.0f,
+ // Cathedral
+ 33.1f, 43.3f, 22.8f, 37.9f, 14.9f, 35.3f, 17.9f, 34.2f, 0.0f, 43.3f,
+ // Disabled
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f
+ };
+
+ private static readonly float[] EarlyGainBase = new float[50]
+ {
+ // Room
+ 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f,
+ // Chamber
+ 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, 0.68f, 0.68f,
+ // Hall
+ 0.50f, 0.70f, 0.70f, 0.68f, 0.50f, 0.68f, 0.68f, 0.70f, 0.68f, 0.00f,
+ // Cathedral
+ 0.93f, 0.92f, 0.87f, 0.86f, 0.94f, 0.81f, 0.80f, 0.77f, 0.76f, 0.65f,
+ // Disabled
+ 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f
+ };
+
+ private static readonly float[] PreDelayTimes = new float[5]
+ {
+ // Room
+ 12.5f,
+ // Chamber
+ 40.0f,
+ // Hall
+ 50.0f,
+ // Cathedral
+ 50.0f,
+ // Disabled
+ 0.0f
+ };
+
+ public IDelayLine[] FdnDelayLines { get; }
+ public DecayDelay[] DecayDelays { get; }
+ public IDelayLine PreDelayLine { get; }
+ public IDelayLine BackLeftDelayLine { get; }
+ public uint[] EarlyDelayTime { get; }
+ public float[] EarlyGain { get; }
+ public uint PreDelayLineDelayTime { get; private set; }
+
+ public float[] HighFrequencyDecayDirectGain { get; }
+ public float[] HighFrequencyDecayPreviousGain { get; }
+ public float[] PreviousFeedbackOutput { get; }
+
+ public const int EarlyModeCount = 10;
+
+ private const int FixedPointPrecision = 14;
+
+ private ReadOnlySpan<float> GetFdnDelayTimesByLateMode(ReverbLateMode lateMode)
+ {
+ return FdnDelayTimes.AsSpan().Slice((int)lateMode * 4, 4);
+ }
+
+ private ReadOnlySpan<float> GetDecayDelayTimesByLateMode(ReverbLateMode lateMode)
+ {
+ return DecayDelayTimes.AsSpan().Slice((int)lateMode * 4, 4);
+ }
+
+ public ReverbState(ref ReverbParameter parameter, ulong workBuffer, bool isLongSizePreDelaySupported)
+ {
+ FdnDelayLines = new IDelayLine[4];
+ DecayDelays = new DecayDelay[4];
+ EarlyDelayTime = new uint[EarlyModeCount];
+ EarlyGain = new float[EarlyModeCount];
+ HighFrequencyDecayDirectGain = new float[4];
+ HighFrequencyDecayPreviousGain = new float[4];
+ PreviousFeedbackOutput = new float[4];
+
+ ReadOnlySpan<float> fdnDelayTimes = GetFdnDelayTimesByLateMode(ReverbLateMode.Limit);
+ ReadOnlySpan<float> decayDelayTimes = GetDecayDelayTimesByLateMode(ReverbLateMode.Limit);
+
+ uint sampleRate = (uint)FixedPointHelper.ToFloat((uint)parameter.SampleRate, FixedPointPrecision);
+
+ for (int i = 0; i < 4; i++)
+ {
+ FdnDelayLines[i] = new DelayLine(sampleRate, fdnDelayTimes[i]);
+ DecayDelays[i] = new DecayDelay(new DelayLine(sampleRate, decayDelayTimes[i]));
+ }
+
+ float preDelayTimeMax = 150.0f;
+
+ if (isLongSizePreDelaySupported)
+ {
+ preDelayTimeMax = 350.0f;
+ }
+
+ PreDelayLine = new DelayLine(sampleRate, preDelayTimeMax);
+ BackLeftDelayLine = new DelayLine(sampleRate, 5.0f);
+
+ UpdateParameter(ref parameter);
+ }
+
+ public void UpdateParameter(ref ReverbParameter parameter)
+ {
+ uint sampleRate = (uint)FixedPointHelper.ToFloat((uint)parameter.SampleRate, FixedPointPrecision);
+
+ float preDelayTimeInMilliseconds = FixedPointHelper.ToFloat(parameter.PreDelayTime, FixedPointPrecision);
+ float earlyGain = FixedPointHelper.ToFloat(parameter.EarlyGain, FixedPointPrecision);
+ float coloration = FixedPointHelper.ToFloat(parameter.Coloration, FixedPointPrecision);
+ float decayTime = FixedPointHelper.ToFloat(parameter.DecayTime, FixedPointPrecision);
+
+ for (int i = 0; i < 10; i++)
+ {
+ EarlyDelayTime[i] = Math.Min(IDelayLine.GetSampleCount(sampleRate, EarlyDelayTimes[i] + preDelayTimeInMilliseconds), PreDelayLine.SampleCountMax) + 1;
+ EarlyGain[i] = EarlyGainBase[i] * earlyGain;
+ }
+
+ if (parameter.ChannelCount == 2)
+ {
+ EarlyGain[4] = EarlyGain[4] * 0.5f;
+ EarlyGain[5] = EarlyGain[5] * 0.5f;
+ }
+
+ PreDelayLineDelayTime = Math.Min(IDelayLine.GetSampleCount(sampleRate, PreDelayTimes[(int)parameter.EarlyMode] + preDelayTimeInMilliseconds), PreDelayLine.SampleCountMax);
+
+ ReadOnlySpan<float> fdnDelayTimes = GetFdnDelayTimesByLateMode(parameter.LateMode);
+ ReadOnlySpan<float> decayDelayTimes = GetDecayDelayTimesByLateMode(parameter.LateMode);
+
+ float highFrequencyDecayRatio = FixedPointHelper.ToFloat(parameter.HighFrequencyDecayRatio, FixedPointPrecision);
+ float highFrequencyUnknownValue = FloatingPointHelper.Cos(1280.0f / sampleRate);
+
+ for (int i = 0; i < 4; i++)
+ {
+ FdnDelayLines[i].SetDelay(fdnDelayTimes[i]);
+ DecayDelays[i].SetDelay(decayDelayTimes[i]);
+
+ float tempA = -3 * (DecayDelays[i].CurrentSampleCount + FdnDelayLines[i].CurrentSampleCount);
+ float tempB = tempA / (decayTime * sampleRate);
+ float tempC;
+ float tempD;
+
+ if (highFrequencyDecayRatio < 0.995f)
+ {
+ float tempE = FloatingPointHelper.Pow10((((1.0f / highFrequencyDecayRatio) - 1.0f) * 2) / 100 * (tempB / 10));
+ float tempF = 1.0f - tempE;
+ float tempG = 2.0f - (tempE * 2 * highFrequencyUnknownValue);
+ float tempH = MathF.Sqrt((tempG * tempG) - (tempF * tempF * 4));
+
+ tempC = (tempG - tempH) / (tempF * 2);
+ tempD = 1.0f - tempC;
+ }
+ else
+ {
+ // no high frequency decay ratio
+ tempC = 0.0f;
+ tempD = 1.0f;
+ }
+
+ HighFrequencyDecayDirectGain[i] = FloatingPointHelper.Pow10(tempB / 1000) * tempD * 0.7071f;
+ HighFrequencyDecayPreviousGain[i] = tempC;
+ PreviousFeedbackOutput[i] = 0.0f;
+
+ DecayDelays[i].SetDecayRate(0.6f * (1.0f - coloration));
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs b/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs
new file mode 100644
index 00000000..047aa279
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs
@@ -0,0 +1,116 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Server.Types;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Audio Renderer user configuration.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct AudioRendererConfiguration
+ {
+ /// <summary>
+ /// The target sample rate of the user.
+ /// </summary>
+ /// <remarks>Only 32000Hz and 48000Hz are considered valid, other sample rates will cause undefined behaviour.</remarks>
+ public uint SampleRate;
+
+ /// <summary>
+ /// The target sample count per <see cref="Dsp.AudioProcessor"/> updates.
+ /// </summary>
+ public uint SampleCount;
+
+ /// <summary>
+ /// The maximum mix buffer count.
+ /// </summary>
+ public uint MixBufferCount;
+
+ /// <summary>
+ /// The maximum amount of sub mixes that could be used by the user.
+ /// </summary>
+ public uint SubMixBufferCount;
+
+ /// <summary>
+ /// The maximum amount of voices that could be used by the user.
+ /// </summary>
+ public uint VoiceCount;
+
+ /// <summary>
+ /// The maximum amount of sinks that could be used by the user.
+ /// </summary>
+ public uint SinkCount;
+
+ /// <summary>
+ /// The maximum amount of effects that could be used by the user.
+ /// </summary>
+ public uint EffectCount;
+
+ /// <summary>
+ /// The maximum amount of performance metric frames that could be used by the user.
+ /// </summary>
+ public uint PerformanceMetricFramesCount;
+
+ /// <summary>
+ /// Set to true if the user allows the <see cref="Server.AudioRenderSystem"/> to drop voices.
+ /// </summary>
+ /// <seealso cref="Server.AudioRenderSystem.ComputeVoiceDrop(Server.CommandBuffer, long, long)"/>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool VoiceDropEnabled;
+
+ /// <summary>
+ /// Reserved/unused
+ /// </summary>
+ private byte _reserved;
+
+ /// <summary>
+ /// The target rendering device.
+ /// </summary>
+ /// <remarks>Must be <see cref="AudioRendererRenderingDevice.Dsp"/></remarks>
+ public AudioRendererRenderingDevice RenderingDevice;
+
+ /// <summary>
+ /// The target execution mode.
+ /// </summary>
+ /// <remarks>Must be <see cref="AudioRendererExecutionMode.Auto"/></remarks>
+ public AudioRendererExecutionMode ExecutionMode;
+
+ /// <summary>
+ /// The maximum amount of splitters that could be used by the user.
+ /// </summary>
+ public uint SplitterCount;
+
+ /// <summary>
+ /// The maximum amount of splitters destinations that could be used by the user.
+ /// </summary>
+ public uint SplitterDestinationCount;
+
+ /// <summary>
+ /// The size of the external context.
+ /// </summary>
+ /// <remarks>This is a leftover of the old "codec" interface system that was present between 1.0.0 and 3.0.0. This was entirely removed from the server side with REV8.</remarks>
+ public uint ExternalContextSize;
+
+ /// <summary>
+ /// The user audio revision
+ /// </summary>
+ /// <seealso cref="Server.BehaviourContext"/>
+ public int Revision;
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs
new file mode 100644
index 00000000..e5312f04
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs
@@ -0,0 +1,47 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Common.Memory;
+using System;
+using System.Runtime.InteropServices;
+using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Output information for behaviour.
+ /// </summary>
+ /// <remarks>This is used to report errors to the user during <see cref="Server.AudioRenderSystem.Update(Memory{byte}, Memory{byte}, ReadOnlyMemory{byte})"/> processing.</remarks>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct BehaviourErrorInfoOutStatus
+ {
+ /// <summary>
+ /// The reported errors.
+ /// </summary>
+ public Array10<ErrorInfo> ErrorInfos;
+
+ /// <summary>
+ /// The amount of error that got reported.
+ /// </summary>
+ public uint ErrorInfosCount;
+
+ /// <summary>
+ /// Reserved/unused.
+ /// </summary>
+ private unsafe fixed uint _reserved[3];
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs b/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs
new file mode 100644
index 00000000..de6b9577
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs
@@ -0,0 +1,51 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Biquad filter parameters.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 1)]
+ public struct BiquadFilterParameter
+ {
+ /// <summary>
+ /// Set to true if the biquad filter is active.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool Enable;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private byte _reserved;
+
+ /// <summary>
+ /// Biquad filter numerator (b0, b1, b2).
+ /// </summary>
+ public Array3<short> Numerator;
+
+ /// <summary>
+ /// Biquad filter denominator (a1, a2).
+ /// </summary>
+ /// <remarks>a0 = 1</remarks>
+ public Array2<short> Denominator;
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs
new file mode 100644
index 00000000..5a747922
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs
@@ -0,0 +1,100 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Effect
+{
+ /// <summary>
+ /// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.AuxiliaryBuffer"/>.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct AuxiliaryBufferParameter
+ {
+ /// <summary>
+ /// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/> to write data to <see cref="SendBufferInfoAddress"/>.
+ /// </summary>
+ public Array24<byte> Input;
+
+ /// <summary>
+ /// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/> to read data from <see cref="ReturnBufferInfoAddress"/>.
+ /// </summary>
+ public Array24<byte> Output;
+
+ /// <summary>
+ /// The total channel count used.
+ /// </summary>
+ public uint ChannelCount;
+
+ /// <summary>
+ /// The target sample rate.
+ /// </summary>
+ public uint SampleRate;
+
+ /// <summary>
+ /// The buffer storage total size.
+ /// </summary>
+ public uint BufferStorageSize;
+
+ /// <summary>
+ /// The maximum number of channels supported.
+ /// </summary>
+ /// <remarks>This is unused.</remarks>
+ public uint ChannelCountMax;
+
+ /// <summary>
+ /// The address of the start of the region containing two <see cref="Dsp.State.AuxiliaryBufferHeader"/> followed by the data that will be written by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ public ulong SendBufferInfoAddress;
+
+ /// <summary>
+ /// The address of the start of the region containling data that will be written by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ /// <remarks>This is unused.</remarks>
+ public ulong SendBufferStorageAddress;
+
+ /// <summary>
+ /// The address of the start of the region containing two <see cref="Dsp.State.AuxiliaryBufferHeader"/> followed by the data that will be read by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ public ulong ReturnBufferInfoAddress;
+
+ /// <summary>
+ /// The address of the start of the region containling data that will be read by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ /// <remarks>This is unused.</remarks>
+ public ulong ReturnBufferStorageAddress;
+
+ /// <summary>
+ /// Size of a sample of the mix buffer.
+ /// </summary>
+ /// <remarks>This is unused.</remarks>
+ public uint MixBufferSampleSize;
+
+ /// <summary>
+ /// The total count of sample that can be stored.
+ /// </summary>
+ /// <remarks>This is unused.</remarks>
+ public uint TotalSampleCount;
+
+ /// <summary>
+ /// The count of sample of the mix buffer.
+ /// </summary>
+ /// <remarks>This is unused.</remarks>
+ public uint MixBufferSampleCount;
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs
new file mode 100644
index 00000000..7d58d4be
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs
@@ -0,0 +1,61 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Server.Effect;
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Effect
+{
+ /// <summary>
+ /// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.BiquadFilter"/>.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct BiquadFilterEffectParameter
+ {
+ /// <summary>
+ /// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ public Array6<byte> Input;
+
+ /// <summary>
+ /// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ public Array6<byte> Output;
+
+ /// <summary>
+ /// Biquad filter numerator (b0, b1, b2).
+ /// </summary>
+ public Array3<short> Numerator;
+
+ /// <summary>
+ /// Biquad filter denominator (a1, a2).
+ /// </summary>
+ /// <remarks>a0 = 1</remarks>
+ public Array2<short> Denominator;
+
+ /// <summary>
+ /// The total channel count used.
+ /// </summary>
+ public byte ChannelCount;
+
+ /// <summary>
+ /// The current usage status of the effect on the client side.
+ /// </summary>
+ public UsageState Status;
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs
new file mode 100644
index 00000000..b0c0c337
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs
@@ -0,0 +1,49 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Effect
+{
+ /// <summary>
+ /// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.BufferMix"/>.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct BufferMixParameter
+ {
+ /// <summary>
+ /// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ public Array24<byte> Input;
+
+ /// <summary>
+ /// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ public Array24<byte> Output;
+
+ /// <summary>
+ /// The output volumes of the mixes.
+ /// </summary>
+ public Array24<float> Volumes;
+
+ /// <summary>
+ /// The total count of mixes used.
+ /// </summary>
+ public uint MixesCount;
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs
new file mode 100644
index 00000000..e0dbeb7c
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs
@@ -0,0 +1,118 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Server.Effect;
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Effect
+{
+ /// <summary>
+ /// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Delay"/>.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct DelayParameter
+ {
+ /// <summary>
+ /// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ public Array6<byte> Input;
+
+ /// <summary>
+ /// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ public Array6<byte> Output;
+
+ /// <summary>
+ /// The maximum number of channels supported.
+ /// </summary>
+ public ushort ChannelCountMax;
+
+ /// <summary>
+ /// The total channel count used.
+ /// </summary>
+ public ushort ChannelCount;
+
+ /// <summary>
+ /// The maximum delay time in milliseconds.
+ /// </summary>
+ public uint DelayTimeMax;
+
+ /// <summary>
+ /// The delay time in milliseconds.
+ /// </summary>
+ public uint DelayTime;
+
+ /// <summary>
+ /// The target sample rate. (Q15)
+ /// </summary>
+ public uint SampleRate;
+
+ /// <summary>
+ /// The input gain. (Q15)
+ /// </summary>
+ public uint InGain;
+
+ /// <summary>
+ /// The feedback gain. (Q15)
+ /// </summary>
+ public uint FeedbackGain;
+
+ /// <summary>
+ /// The output gain. (Q15)
+ /// </summary>
+ public uint OutGain;
+
+ /// <summary>
+ /// The dry gain. (Q15)
+ /// </summary>
+ public uint DryGain;
+
+ /// <summary>
+ /// The channel spread of the <see cref="FeedbackGain"/>. (Q15)
+ /// </summary>
+ public uint ChannelSpread;
+
+ /// <summary>
+ /// The low pass amount. (Q15)
+ /// </summary>
+ public uint LowPassAmount;
+
+ /// <summary>
+ /// The current usage status of the effect on the client side.
+ /// </summary>
+ public UsageState Status;
+
+ /// <summary>
+ /// Check if the <see cref="ChannelCount"/> is valid.
+ /// </summary>
+ /// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
+ public bool IsChannelCountValid()
+ {
+ return EffectInParameter.IsChannelCountValid(ChannelCount);
+ }
+
+ /// <summary>
+ /// Check if the <see cref="ChannelCountMax"/> is valid.
+ /// </summary>
+ /// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
+ public bool IsChannelCountMaxValid()
+ {
+ return EffectInParameter.IsChannelCountValid(ChannelCountMax);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs
new file mode 100644
index 00000000..0dbecfb8
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs
@@ -0,0 +1,144 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Server.Effect;
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Effect
+{
+ /// <summary>
+ /// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Reverb3d"/>.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct Reverb3dParameter
+ {
+ /// <summary>
+ /// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ public Array6<byte> Input;
+
+ /// <summary>
+ /// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ public Array6<byte> Output;
+
+ /// <summary>
+ /// The maximum number of channels supported.
+ /// </summary>
+ public ushort ChannelCountMax;
+
+ /// <summary>
+ /// The total channel count used.
+ /// </summary>
+ public ushort ChannelCount;
+
+ /// <summary>
+ /// Reserved/unused.
+ /// </summary>
+ private uint _reserved;
+
+ /// <summary>
+ /// The target sample rate.
+ /// </summary>
+ /// <remarks>This is in kHz.</remarks>
+ public uint SampleRate;
+
+ /// <summary>
+ /// Gain of the room high-frequency effect.
+ /// </summary>
+ public float RoomHf;
+
+ /// <summary>
+ /// Reference high frequency.
+ /// </summary>
+ public float HfReference;
+
+ /// <summary>
+ /// Reverberation decay time at low frequencies.
+ /// </summary>
+ public float DecayTime;
+
+ /// <summary>
+ /// Ratio of the decay time at high frequencies to the decay time at low frequencies.
+ /// </summary>
+ public float HfDecayRatio;
+
+ /// <summary>
+ /// Gain of the room effect.
+ /// </summary>
+ public float RoomGain;
+
+ /// <summary>
+ /// Gain of the early reflections relative to <see cref="RoomGain"/>.
+ /// </summary>
+ public float ReflectionsGain;
+
+ /// <summary>
+ /// Gain of the late reverberation relative to <see cref="RoomGain"/>.
+ /// </summary>
+ public float ReverbGain;
+
+ /// <summary>
+ /// Echo density in the late reverberation decay.
+ /// </summary>
+ public float Diffusion;
+
+ /// <summary>
+ /// Modal density in the late reverberation decay.
+ /// </summary>
+ public float ReflectionDelay;
+
+ /// <summary>
+ /// Time limit between the early reflections and the late reverberation relative to the time of the first reflection.
+ /// </summary>
+ public float ReverbDelayTime;
+
+ /// <summary>
+ /// Modal density in the late reverberation decay.
+ /// </summary>
+ public float Density;
+
+ /// <summary>
+ /// The dry gain.
+ /// </summary>
+ public float DryGain;
+
+ /// <summary>
+ /// The current usage status of the effect on the client side.
+ /// </summary>
+ public UsageState ParameterStatus;
+
+ /// <summary>
+ /// Check if the <see cref="ChannelCount"/> is valid.
+ /// </summary>
+ /// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
+ public bool IsChannelCountValid()
+ {
+ return EffectInParameter.IsChannelCountValid(ChannelCount);
+ }
+
+ /// <summary>
+ /// Check if the <see cref="ChannelCountMax"/> is valid.
+ /// </summary>
+ /// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
+ public bool IsChannelCountMaxValid()
+ {
+ return EffectInParameter.IsChannelCountValid(ChannelCountMax);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs
new file mode 100644
index 00000000..daa81c51
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs
@@ -0,0 +1,136 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Server.Effect;
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Effect
+{
+ /// <summary>
+ /// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Reverb"/>.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct ReverbParameter
+ {
+ /// <summary>
+ /// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ public Array6<byte> Input;
+
+ /// <summary>
+ /// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ public Array6<byte> Output;
+
+ /// <summary>
+ /// The maximum number of channels supported.
+ /// </summary>
+ public ushort ChannelCountMax;
+
+ /// <summary>
+ /// The total channel count used.
+ /// </summary>
+ public ushort ChannelCount;
+
+ /// <summary>
+ /// The target sample rate. (Q15)
+ /// </summary>
+ /// <remarks>This is in kHz.</remarks>
+ public int SampleRate;
+
+ /// <summary>
+ /// The early mode to use.
+ /// </summary>
+ public ReverbEarlyMode EarlyMode;
+
+ /// <summary>
+ /// The gain to apply to the result of the early reflection. (Q15)
+ /// </summary>
+ public int EarlyGain;
+
+ /// <summary>
+ /// The pre-delay time in milliseconds. (Q15)
+ /// </summary>
+ public int PreDelayTime;
+
+ /// <summary>
+ /// The late mode to use.
+ /// </summary>
+ public ReverbLateMode LateMode;
+
+ /// <summary>
+ /// The gain to apply to the result of the late reflection. (Q15)
+ /// </summary>
+ public int LateGain;
+
+ /// <summary>
+ /// The decay time. (Q15)
+ /// </summary>
+ public int DecayTime;
+
+ /// <summary>
+ /// The high frequency decay ratio. (Q15)
+ /// </summary>
+ /// <remarks>If <see cref="HighFrequencyDecayRatio"/> >= 0.995f, it is considered disabled.</remarks>
+ public int HighFrequencyDecayRatio;
+
+ /// <summary>
+ /// The coloration of the decay. (Q15)
+ /// </summary>
+ public int Coloration;
+
+ /// <summary>
+ /// The reverb gain. (Q15)
+ /// </summary>
+ public int ReverbGain;
+
+ /// <summary>
+ /// The output gain. (Q15)
+ /// </summary>
+ public int OutGain;
+
+ /// <summary>
+ /// The dry gain. (Q15)
+ /// </summary>
+ public int DryGain;
+
+ /// <summary>
+ /// The current usage status of the effect on the client side.
+ /// </summary>
+ public UsageState Status;
+
+ /// <summary>
+ /// Check if the <see cref="ChannelCount"/> is valid.
+ /// </summary>
+ /// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
+ public bool IsChannelCountValid()
+ {
+ return EffectInParameter.IsChannelCountValid(ChannelCount);
+ }
+
+ /// <summary>
+ /// Check if the <see cref="ChannelCountMax"/> is valid.
+ /// </summary>
+ /// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
+ public bool IsChannelCountMaxValid()
+ {
+ return EffectInParameter.IsChannelCountValid(ChannelCountMax);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/EffectInParameter.cs
new file mode 100644
index 00000000..af8edeff
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/EffectInParameter.cs
@@ -0,0 +1,103 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Input information for an effect.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct EffectInParameter
+ {
+ /// <summary>
+ /// Type of the effect.
+ /// </summary>
+ public EffectType Type;
+
+ /// <summary>
+ /// Set to true if the effect is new.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsNew;
+
+ /// <summary>
+ /// Set to true if the effect must be active.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsEnabled;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private byte _reserved1;
+
+ /// <summary>
+ /// The target mix id of the effect.
+ /// </summary>
+ public int MixId;
+
+ /// <summary>
+ /// Address of the processing workbuffer.
+ /// </summary>
+ /// <remarks>This is additional data that could be required by the effect processing.</remarks>
+ public ulong BufferBase;
+
+ /// <summary>
+ /// Size of the processing workbuffer.
+ /// </summary>
+ /// <remarks>This is additional data that could be required by the effect processing.</remarks>
+ public ulong BufferSize;
+
+ /// <summary>
+ /// Position of the effect while processing effects.
+ /// </summary>
+ public uint ProcessingOrder;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private uint _reserved2;
+
+ /// <summary>
+ /// Specific data storage.
+ /// </summary>
+ private SpecificDataStruct _specificDataStart;
+
+ [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)]
+ private struct SpecificDataStruct { }
+
+ /// <summary>
+ /// Specific data changing depending of the <see cref="Type"/>. See also the <see cref="Effect"/> namespace.
+ /// </summary>
+ public Span<byte> SpecificData => SpanHelpers.AsSpan<SpecificDataStruct, byte>(ref _specificDataStart);
+
+ /// <summary>
+ /// Check if the given channel count is valid.
+ /// </summary>
+ /// <param name="channelCount">The channel count to check</param>
+ /// <returns>Returns true if the channel count is valid.</returns>
+ public static bool IsChannelCountValid(int channelCount)
+ {
+ return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/EffectOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/EffectOutStatus.cs
new file mode 100644
index 00000000..c6178165
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/EffectOutStatus.cs
@@ -0,0 +1,54 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Output information for an effect.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct EffectOutStatus
+ {
+ /// <summary>
+ /// The state of an effect.
+ /// </summary>
+ public enum EffectState : byte
+ {
+ /// <summary>
+ /// The effect is enabled.
+ /// </summary>
+ Enabled = 3,
+
+ /// <summary>
+ /// The effect is disabled.
+ /// </summary>
+ Disabled = 4
+ }
+
+ /// <summary>
+ /// Current effect state.
+ /// </summary>
+ public EffectState State;
+
+ /// <summary>
+ /// Unused/Reserved.
+ /// </summary>
+ private unsafe fixed byte _reserved[15];
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs
new file mode 100644
index 00000000..15498cca
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using System.Runtime.InteropServices;
+using CpuAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Input information for a memory pool.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct MemoryPoolInParameter
+ {
+ /// <summary>
+ /// The CPU address used by the memory pool.
+ /// </summary>
+ public CpuAddress CpuAddress;
+
+ /// <summary>
+ /// The size used by the memory pool.
+ /// </summary>
+ public ulong Size;
+
+ /// <summary>
+ /// The target state the user wants.
+ /// </summary>
+ public MemoryPoolUserState State;
+
+ /// <summary>
+ /// Reserved/unused.
+ /// </summary>
+ private unsafe fixed uint _reserved[3];
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs
new file mode 100644
index 00000000..c125cb8b
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs
@@ -0,0 +1,39 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Output information for a memory pool.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct MemoryPoolOutStatus
+ {
+ /// <summary>
+ /// The current server memory pool state.
+ /// </summary>
+ public MemoryPoolUserState State;
+
+ /// <summary>
+ /// Reserved/unused.
+ /// </summary>
+ private unsafe fixed uint _reserved[3];
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs b/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs
new file mode 100644
index 00000000..cdbd5871
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs
@@ -0,0 +1,44 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Input information header for mix updates on REV7 and later
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct MixInParameterDirtyOnlyUpdate
+ {
+ /// <summary>
+ /// Magic of the header
+ /// </summary>
+ /// <remarks>Never checked on hardware.</remarks>
+ public uint Magic;
+
+ /// <summary>
+ /// The count of <see cref="MixParameter"/> following this header.
+ /// </summary>
+ public uint MixCount;
+
+ /// <summary>
+ /// Reserved/unused.
+ /// </summary>
+ private unsafe fixed byte _reserved[24];
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs b/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs
new file mode 100644
index 00000000..1d7f27f4
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs
@@ -0,0 +1,112 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Input information for a mix.
+ /// </summary>
+ /// <remarks>Also used on the client side for mix tracking.</remarks>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct MixParameter
+ {
+ /// <summary>
+ /// Base volume of the mix.
+ /// </summary>
+ public float Volume;
+
+ /// <summary>
+ /// Target sample rate of the mix.
+ /// </summary>
+ public uint SampleRate;
+
+ /// <summary>
+ /// Target buffer count.
+ /// </summary>
+ public uint BufferCount;
+
+ /// <summary>
+ /// Set to true if in use.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ /// <summary>
+ /// Set to true if it was changed.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsDirty;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private ushort _reserved1;
+
+ /// <summary>
+ /// The id of the mix.
+ /// </summary>
+ public int MixId;
+
+ /// <summary>
+ /// The effect count. (client side)
+ /// </summary>
+ public uint EffectCount;
+
+ /// <summary>
+ /// The mix node id.
+ /// </summary>
+ public int NodeId;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private ulong _reserved2;
+
+ /// <summary>
+ /// Mix buffer volumes storage.
+ /// </summary>
+ private MixVolumeArray _mixBufferVolumeArray;
+
+ /// <summary>
+ /// The mix to output the result of this mix.
+ /// </summary>
+ public int DestinationMixId;
+
+ /// <summary>
+ /// The splitter to output the result of this mix.
+ /// </summary>
+ public uint DestinationSplitterId;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private uint _reserved3;
+
+ [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax * Constants.MixBufferCountMax, Pack = 1)]
+ private struct MixVolumeArray { }
+
+ /// <summary>
+ /// Mix buffer volumes.
+ /// </summary>
+ /// <remarks>Used when no splitter id is specified.</remarks>
+ public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixVolumeArray, float>(ref _mixBufferVolumeArray);
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs
new file mode 100644
index 00000000..929f006b
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Performance
+{
+ /// <summary>
+ /// Input information for performance monitoring.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct PerformanceInParameter
+ {
+ /// <summary>
+ /// The target node id to monitor performance on.
+ /// </summary>
+ public int TargetNodeId;
+
+ /// <summary>
+ /// Reserved/unused.
+ /// </summary>
+ private unsafe fixed uint _reserved[3];
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs
new file mode 100644
index 00000000..9263362c
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Performance
+{
+ /// <summary>
+ /// Output information for performance monitoring.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct PerformanceOutStatus
+ {
+ /// <summary>
+ /// Indicates the total size output to the performance buffer.
+ /// </summary>
+ public uint HistorySize;
+
+ /// <summary>
+ /// Reserved/unused.
+ /// </summary>
+ private unsafe fixed uint _reserved[3];
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs
new file mode 100644
index 00000000..315f0968
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Renderer output information on REV5 and later.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct RendererInfoOutStatus
+ {
+ /// <summary>
+ /// The count of updates sent to the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ public ulong ElapsedFrameCount;
+
+ /// <summary>
+ /// Reserved/Unused.
+ /// </summary>
+ private ulong _reserved;
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs
new file mode 100644
index 00000000..ae06481f
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs
@@ -0,0 +1,79 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+using CpuAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Sink
+{
+ /// <summary>
+ /// <see cref="SinkInParameter.SpecificData"/> for <see cref="SinkType.CircularBuffer"/>.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct CircularBufferParameter
+ {
+ /// <summary>
+ /// The CPU address of the user circular buffer.
+ /// </summary>
+ public CpuAddress BufferAddress;
+
+ /// <summary>
+ /// The size of the user circular buffer.
+ /// </summary>
+ public uint BufferSize;
+
+ /// <summary>
+ /// The total count of channels to output to the circular buffer.
+ /// </summary>
+ public uint InputCount;
+
+ /// <summary>
+ /// The target sample count to output per update in the circular buffer.
+ /// </summary>
+ public uint SampleCount;
+
+ /// <summary>
+ /// Last read offset on the CPU side.
+ /// </summary>
+ public uint LastReadOffset;
+
+ /// <summary>
+ /// The target <see cref="SampleFormat"/>.
+ /// </summary>
+ /// <remarks>Only <see cref="SampleFormat.PcmInt16"/> is supported.</remarks>
+ public SampleFormat SampleFormat;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private unsafe fixed byte _reserved1[3];
+
+ /// <summary>
+ /// The input channels index that will be used.
+ /// </summary>
+ public Array6<byte> Input;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private ushort _reserved2;
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs
new file mode 100644
index 00000000..591b4945
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs
@@ -0,0 +1,75 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Common.Memory;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Sink
+{
+ /// <summary>
+ /// <see cref="SinkInParameter.SpecificData"/> for <see cref="Common.SinkType.Device"/>.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct DeviceParameter
+ {
+ /// <summary>
+ /// Device name storage.
+ /// </summary>
+ private DeviceNameStruct _deviceName;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private byte _padding;
+
+ /// <summary>
+ /// The total count of channels to output to the device.
+ /// </summary>
+ public uint InputCount;
+
+ /// <summary>
+ /// The input channels index that will be used.
+ /// </summary>
+ public Array6<byte> Input;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private byte _reserved;
+
+ /// <summary>
+ /// Set to true if the user controls Surround to Stereo downmixing coefficients.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool DownMixParameterEnabled;
+
+ /// <summary>
+ /// The user Surround to Stereo downmixing coefficients.
+ /// </summary>
+ public Array4<float> DownMixParameter;
+
+ [StructLayout(LayoutKind.Sequential, Size = 0xFF, Pack = 1)]
+ private struct DeviceNameStruct { }
+
+ /// <summary>
+ /// The output device name.
+ /// </summary>
+ public Span<byte> DeviceName => SpanHelpers.AsSpan<DeviceNameStruct, byte>(ref _deviceName);
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs
new file mode 100644
index 00000000..0859ef31
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs
@@ -0,0 +1,70 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Input information for a sink.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct SinkInParameter
+ {
+ /// <summary>
+ /// Type of the sink.
+ /// </summary>
+ public SinkType Type;
+
+ /// <summary>
+ /// Set to true if the sink is used.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private ushort _reserved1;
+
+ /// <summary>
+ /// The node id of the sink.
+ /// </summary>
+ public int NodeId;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private unsafe fixed ulong _reserved2[3];
+
+ /// <summary>
+ /// Specific data storage.
+ /// </summary>
+ private SpecificDataStruct _specificDataStart;
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x120, Pack = 1)]
+ private struct SpecificDataStruct { }
+
+ /// <summary>
+ /// Specific data changing depending of the <see cref="Type"/>. See also the <see cref="Sink"/> namespace.
+ /// </summary>
+ public Span<byte> SpecificData => SpanHelpers.AsSpan<SpecificDataStruct, byte>(ref _specificDataStart);
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs
new file mode 100644
index 00000000..e0cb4942
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs
@@ -0,0 +1,43 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Output information for a sink.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct SinkOutStatus
+ {
+ /// <summary>
+ /// Last written offset if the sink type is <see cref="Common.SinkType.CircularBuffer"/>.
+ /// </summary>
+ public uint LastWrittenOffset;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private uint _padding;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private unsafe fixed ulong _reserved[3];
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs
new file mode 100644
index 00000000..77d5ddc2
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs
@@ -0,0 +1,84 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Input header for a splitter destination update.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct SplitterDestinationInParameter
+ {
+ /// <summary>
+ /// Magic of the input header.
+ /// </summary>
+ public uint Magic;
+
+ /// <summary>
+ /// Target splitter destination data id.
+ /// </summary>
+ public int Id;
+
+ /// <summary>
+ /// Mix buffer volumes storage.
+ /// </summary>
+ private MixArray _mixBufferVolume;
+
+ /// <summary>
+ /// The mix to output the result of the splitter.
+ /// </summary>
+ public int DestinationId;
+
+ /// <summary>
+ /// Set to true if in use.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ /// <summary>
+ /// Reserved/padding.
+ /// </summary>
+ private unsafe fixed byte _reserved[3];
+
+ [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
+ private struct MixArray { }
+
+ /// <summary>
+ /// Mix buffer volumes.
+ /// </summary>
+ /// <remarks>Used when a splitter id is specified in the mix.</remarks>
+ public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume);
+
+ /// <summary>
+ /// The expected constant of any input header.
+ /// </summary>
+ private const uint ValidMagic = 0x44444E53;
+
+ /// <summary>
+ /// Check if the magic is valid.
+ /// </summary>
+ /// <returns>Returns true if the magic is valid.</returns>
+ public bool IsMagicValid()
+ {
+ return Magic == ValidMagic;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs
new file mode 100644
index 00000000..a829abf9
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs
@@ -0,0 +1,63 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Input header for a splitter state update.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct SplitterInParameter
+ {
+ /// <summary>
+ /// Magic of the input header.
+ /// </summary>
+ public uint Magic;
+
+ /// <summary>
+ /// Target splitter id.
+ /// </summary>
+ public int Id;
+
+ /// <summary>
+ /// Target sample rate to use on the splitter.
+ /// </summary>
+ public uint SampleRate;
+
+ /// <summary>
+ /// Count of splitter destinations.
+ /// </summary>
+ /// <remarks>Splitter destination ids are defined right after this header.</remarks>
+ public int DestinationCount;
+
+ /// <summary>
+ /// The expected constant of any input header.
+ /// </summary>
+ private const uint ValidMagic = 0x49444E53;
+
+ /// <summary>
+ /// Check if the magic is valid.
+ /// </summary>
+ /// <returns>Returns true if the magic is valid.</returns>
+ public bool IsMagicValid()
+ {
+ return Magic == ValidMagic;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs b/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs
new file mode 100644
index 00000000..746d59b7
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs
@@ -0,0 +1,62 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Input header for splitter update.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct SplitterInParameterHeader
+ {
+ /// <summary>
+ /// Magic of the input header.
+ /// </summary>
+ public uint Magic;
+
+ /// <summary>
+ /// The count of <see cref="SplitterInParameter"/> after the header.
+ /// </summary>
+ public uint SplitterCount;
+
+ /// <summary>
+ /// The count of splitter destinations after the header and splitter info.
+ /// </summary>
+ public uint SplitterDestinationCount;
+
+ /// <summary>
+ /// Reserved/unused.
+ /// </summary>
+ private unsafe fixed uint _reserved[5];
+
+ /// <summary>
+ /// The expected constant of any input splitter header.
+ /// </summary>
+ private const uint ValidMagic = 0x48444E53;
+
+ /// <summary>
+ /// Check if the magic is valid.
+ /// </summary>
+ /// <returns>Returns true if the magic is valid.</returns>
+ public bool IsMagicValid()
+ {
+ return Magic == ValidMagic;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs
new file mode 100644
index 00000000..73ad2bfd
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs
@@ -0,0 +1,45 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Input information for a voice channel resources.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 0x70, Pack = 1)]
+ public struct VoiceChannelResourceInParameter
+ {
+ /// <summary>
+ /// The id of the voice channel resource.
+ /// </summary>
+ public uint Id;
+
+ /// <summary>
+ /// Mix volumes for the voice channel resource.
+ /// </summary>
+ public Array24<float> Mix;
+
+ /// <summary>
+ /// Indicate if the voice channel resource is used.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs
new file mode 100644
index 00000000..bced1538
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs
@@ -0,0 +1,361 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp;
+using Ryujinx.Common.Memory;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Input information for a voice.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 0x170, Pack = 1)]
+ public struct VoiceInParameter
+ {
+ /// <summary>
+ /// Id of the voice.
+ /// </summary>
+ public int Id;
+
+ /// <summary>
+ /// Node id of the voice.
+ /// </summary>
+ public int NodeId;
+
+ /// <summary>
+ /// Set to true if the voice is new.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsNew;
+
+ /// <summary>
+ /// Set to true if the voice is used.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool InUse;
+
+ /// <summary>
+ /// The voice <see cref="PlayState"/> wanted by the user.
+ /// </summary>
+ public PlayState PlayState;
+
+ /// <summary>
+ /// The <see cref="SampleFormat"/> of the voice.
+ /// </summary>
+ public SampleFormat SampleFormat;
+
+ /// <summary>
+ /// The sample rate of the voice.
+ /// </summary>
+ public uint SampleRate;
+
+ /// <summary>
+ /// The priority of the voice.
+ /// </summary>
+ public uint Priority;
+
+ /// <summary>
+ /// Target sorting position of the voice. (Used to sort voices with the same <see cref="Priority"/>)
+ /// </summary>
+ public uint SortingOrder;
+
+ /// <summary>
+ /// The total channel count used.
+ /// </summary>
+ public uint ChannelCount;
+
+ /// <summary>
+ /// The pitch used on the voice.
+ /// </summary>
+ public float Pitch;
+
+ /// <summary>
+ /// The output volume of the voice.
+ /// </summary>
+ public float Volume;
+
+ /// <summary>
+ /// Biquad filters to apply to the output of the voice.
+ /// </summary>
+ public Array2<BiquadFilterParameter> BiquadFilters;
+
+ /// <summary>
+ /// Total count of <see cref="WaveBufferInternal"/> of the voice.
+ /// </summary>
+ public uint WaveBuffersCount;
+
+ /// <summary>
+ /// Current playing <see cref="WaveBufferInternal"/> of the voice.
+ /// </summary>
+ public uint WaveBuffersIndex;
+
+ /// <summary>
+ /// Reserved/unused.
+ /// </summary>
+ private uint _reserved1;
+
+ /// <summary>
+ /// User state address required by the data source.
+ /// </summary>
+ /// <remarks>Only used for <see cref="SampleFormat.Adpcm"/> as the address of the GC-ADPCM coefficients.</remarks>
+ public ulong DataSourceStateAddress;
+
+ /// <summary>
+ /// User state size required by the data source.
+ /// </summary>
+ /// <remarks>Only used for <see cref="SampleFormat.Adpcm"/> as the size of the GC-ADPCM coefficients.</remarks>
+ public ulong DataSourceStateSize;
+
+ /// <summary>
+ /// The target mix id of the voice.
+ /// </summary>
+ public int MixId;
+
+ /// <summary>
+ /// The target splitter id of the voice.
+ /// </summary>
+ public uint SplitterId;
+
+ /// <summary>
+ /// The wavebuffer parameters of this voice.
+ /// </summary>
+ public Array4<WaveBufferInternal> WaveBuffers;
+
+ /// <summary>
+ /// The channel resource ids associated to the voice.
+ /// </summary>
+ public Array6<int> ChannelResourceIds;
+
+ /// <summary>
+ /// Reset the voice drop flag during voice server update.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool ResetVoiceDropFlag;
+
+ /// <summary>
+ /// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played.
+ /// </summary>
+ /// <remarks>This was added on REV5.</remarks>
+ public byte FlushWaveBufferCount;
+
+ /// <summary>
+ /// Reserved/unused.
+ /// </summary>
+ private ushort _reserved2;
+
+ /// <summary>
+ /// Change the behaviour of the voice.
+ /// </summary>
+ /// <remarks>This was added on REV5.</remarks>
+ public DecodingBehaviour DecodingBehaviourFlags;
+
+ /// <summary>
+ /// Change the Sample Rate Conversion (SRC) quality of the voice.
+ /// </summary>
+ /// <remarks>This was added on REV8.</remarks>
+ public SampleRateConversionQuality SrcQuality;
+
+ /// <summary>
+ /// This was previously used for opus codec support on the Audio Renderer and was removed on REV3.
+ /// </summary>
+ public uint ExternalContext;
+
+ /// <summary>
+ /// This was previously used for opus codec support on the Audio Renderer and was removed on REV3.
+ /// </summary>
+ public uint ExternalContextSize;
+
+ /// <summary>
+ /// Reserved/unused.
+ /// </summary>
+ private unsafe fixed uint _reserved3[2];
+
+ /// <summary>
+ /// Input information for a voice wavebuffer.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 0x38, Pack = 1)]
+ public struct WaveBufferInternal
+ {
+ /// <summary>
+ /// Address of the wavebuffer data.
+ /// </summary>
+ public ulong Address;
+
+ /// <summary>
+ /// Size of the wavebuffer data.
+ /// </summary>
+ public ulong Size;
+
+ /// <summary>
+ /// Offset of the first sample to play.
+ /// </summary>
+ public uint StartSampleOffset;
+
+ /// <summary>
+ /// Offset of the last sample to play.
+ /// </summary>
+ public uint EndSampleOffset;
+
+ /// <summary>
+ /// If set to true, the wavebuffer will loop when reaching <see cref="EndSampleOffset"/>.
+ /// </summary>
+ /// <remarks>
+ /// Starting with REV8, you can specify how many times to loop the wavebuffer (<see cref="LoopCount"/>) and where it should start and end when looping (<see cref="LoopFirstSampleOffset"/> and <see cref="LoopLastSampleOffset"/>)
+ /// </remarks>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool ShouldLoop;
+
+ /// <summary>
+ /// Indicates that this is the last wavebuffer to play of the voice.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsEndOfStream;
+
+ /// <summary>
+ /// Indicates if the server should update its internal state.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool SentToServer;
+
+ /// <summary>
+ /// Reserved/unused.
+ /// </summary>
+ private byte _reserved;
+
+ /// <summary>
+ /// If set to anything other than 0, specifies how many times to loop the wavebuffer.
+ /// </summary>
+ /// <remarks>This was added in REV8.</remarks>
+ public int LoopCount;
+
+ /// <summary>
+ /// Address of the context used by the sample decoder.
+ /// </summary>
+ /// <remarks>This is only currently used by <see cref="SampleFormat.Adpcm"/>.</remarks>
+ public ulong ContextAddress;
+
+ /// <summary>
+ /// Size of the context used by the sample decoder.
+ /// </summary>
+ /// <remarks>This is only currently used by <see cref="SampleFormat.Adpcm"/>.</remarks>
+ public ulong ContextSize;
+
+ /// <summary>
+ /// If set to anything other than 0, specifies the offset of the first sample to play when looping.
+ /// </summary>
+ /// <remarks>This was added in REV8.</remarks>
+ public uint LoopFirstSampleOffset;
+
+ /// <summary>
+ /// If set to anything other than 0, specifies the offset of the last sample to play when looping.
+ /// </summary>
+ /// <remarks>This was added in REV8.</remarks>
+ public uint LoopLastSampleOffset;
+
+ /// <summary>
+ /// Check if the sample offsets are in a valid range for generic PCM.
+ /// </summary>
+ /// <typeparam name="T">The PCM sample type</typeparam>
+ /// <returns>Returns true if the sample offset are in range of the size.</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool IsSampleOffsetInRangeForPcm<T>() where T : unmanaged
+ {
+ uint dataTypeSize = (uint)Unsafe.SizeOf<T>();
+
+ return StartSampleOffset * dataTypeSize <= Size &&
+ EndSampleOffset * dataTypeSize <= Size;
+ }
+
+ /// <summary>
+ /// Check if the sample offsets are in a valid range for the given <see cref="SampleFormat"/>.
+ /// </summary>
+ /// <param name="format">The target <see cref="SampleFormat"/></param>
+ /// <returns>Returns true if the sample offset are in range of the size.</returns>
+ public bool IsSampleOffsetValid(SampleFormat format)
+ {
+ bool result;
+
+ switch (format)
+ {
+ case SampleFormat.PcmInt16:
+ result = IsSampleOffsetInRangeForPcm<ushort>();
+ break;
+ case SampleFormat.PcmFloat:
+ result = IsSampleOffsetInRangeForPcm<float>();
+ break;
+ case SampleFormat.Adpcm:
+ result = AdpcmHelper.GetAdpcmDataSize((int)StartSampleOffset) <= Size &&
+ AdpcmHelper.GetAdpcmDataSize((int)EndSampleOffset) <= Size;
+ break;
+ default:
+ throw new NotImplementedException($"{format} not implemented!");
+ }
+
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Flag altering the behaviour of wavebuffer decoding.
+ /// </summary>
+ [Flags]
+ public enum DecodingBehaviour : ushort
+ {
+ /// <summary>
+ /// Default decoding behaviour.
+ /// </summary>
+ Default = 0,
+
+ /// <summary>
+ /// Reset the played samples accumulator when looping.
+ /// </summary>
+ PlayedSampleCountResetWhenLooping = 1,
+
+ /// <summary>
+ /// Skip pitch and Sample Rate Conversion (SRC).
+ /// </summary>
+ SkipPitchAndSampleRateConversion = 2
+ }
+
+ /// <summary>
+ /// Specify the quality to use during Sample Rate Conversion (SRC) and pitch handling.
+ /// </summary>
+ /// <remarks>This was added in REV8.</remarks>
+ public enum SampleRateConversionQuality : byte
+ {
+ /// <summary>
+ /// Resample interpolating 4 samples per output sample.
+ /// </summary>
+ Default,
+
+ /// <summary>
+ /// Resample interpolating 8 samples per output sample.
+ /// </summary>
+ High,
+
+ /// <summary>
+ /// Resample interpolating 1 samples per output sample.
+ /// </summary>
+ Low
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs
new file mode 100644
index 00000000..62d17a5a
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ /// <summary>
+ /// Output information about a voice.
+ /// </summary>
+ /// <remarks>See <seealso cref="Server.StateUpdater.UpdateVoices(Server.Voice.VoiceContext, System.Memory{Server.MemoryPool.MemoryPoolState})"/></remarks>
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct VoiceOutStatus
+ {
+ /// <summary>
+ /// The total amount of samples that was played.
+ /// </summary>
+ /// <remarks>This is reset to 0 when a <see cref="Common.WaveBuffer"/> finishes playing and <see cref="Common.WaveBuffer.IsEndOfStream"/> is set.</remarks>
+ /// <remarks>This is reset to 0 when looping while <see cref="Parameter.VoiceInParameter.DecodingBehaviour.PlayedSampleCountResetWhenLooping"/> is set.</remarks>
+ public ulong PlayedSampleCount;
+
+ /// <summary>
+ /// The total amount of <see cref="WaveBuffer"/> consumed.
+ /// </summary>
+ public uint PlayedWaveBuffersCount;
+
+ /// <summary>
+ /// If set to true, the voice was dropped.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool VoiceDropFlag;
+
+ /// <summary>
+ /// Reserved/unused.
+ /// </summary>
+ private unsafe fixed byte _reserved[3];
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
new file mode 100644
index 00000000..787b8f9f
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
@@ -0,0 +1,838 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.Command;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Server.Effect;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using Ryujinx.Audio.Renderer.Server.Mix;
+using Ryujinx.Audio.Renderer.Server.Performance;
+using Ryujinx.Audio.Renderer.Server.Sink;
+using Ryujinx.Audio.Renderer.Server.Splitter;
+using Ryujinx.Audio.Renderer.Server.Types;
+using Ryujinx.Audio.Renderer.Server.Upsampler;
+using Ryujinx.Audio.Renderer.Server.Voice;
+using Ryujinx.Audio.Renderer.Utils;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Memory;
+using System;
+using System.Buffers;
+using System.Diagnostics;
+using System.Threading;
+
+using CpuAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Server
+{
+ public class AudioRenderSystem : IDisposable
+ {
+ private object _lock = new object();
+
+ private AudioRendererExecutionMode _executionMode;
+ private IWritableEvent _systemEvent;
+ private ManualResetEvent _terminationEvent;
+ private MemoryPoolState _dspMemoryPoolState;
+ private VoiceContext _voiceContext;
+ private MixContext _mixContext;
+ private SinkContext _sinkContext;
+ private SplitterContext _splitterContext;
+ private EffectContext _effectContext;
+ private PerformanceManager _performanceManager;
+ private UpsamplerManager _upsamplerManager;
+ private bool _isActive;
+ private BehaviourContext _behaviourContext;
+ private ulong _totalElapsedTicksUpdating;
+ private ulong _totalElapsedTicks;
+ private int _sessionId;
+ private Memory<MemoryPoolState> _memoryPools;
+
+ private uint _sampleRate;
+ private uint _sampleCount;
+ private uint _mixBufferCount;
+ private uint _voiceChannelCountMax;
+ private uint _upsamplerCount;
+ private uint _memoryPoolCount;
+ private uint _processHandle;
+ private ulong _appletResourceId;
+
+ private WritableRegion _workBufferRegion;
+ private MemoryHandle _workBufferMemoryPin;
+
+ private Memory<float> _mixBuffer;
+ private Memory<float> _depopBuffer;
+
+ private uint _renderingTimeLimitPercent;
+ private bool _voiceDropEnabled;
+ private uint _voiceDropCount;
+ private bool _isDspRunningBehind;
+
+ private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator;
+
+ private Memory<byte> _performanceBuffer;
+
+ public IVirtualMemoryManager MemoryManager { get; private set; }
+
+ private ulong _elapsedFrameCount;
+ private ulong _renderingStartTick;
+
+ private AudioRendererManager _manager;
+
+ public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent)
+ {
+ _manager = manager;
+ _terminationEvent = new ManualResetEvent(false);
+ _dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp);
+ _voiceContext = new VoiceContext();
+ _mixContext = new MixContext();
+ _sinkContext = new SinkContext();
+ _splitterContext = new SplitterContext();
+ _effectContext = new EffectContext();
+
+ _commandProcessingTimeEstimator = null;
+ _systemEvent = systemEvent;
+ _behaviourContext = new BehaviourContext();
+
+ _totalElapsedTicksUpdating = 0;
+ _sessionId = 0;
+ }
+
+ public ResultCode Initialize(ref AudioRendererConfiguration parameter, uint processHandle, CpuAddress workBuffer, ulong workBufferSize, int sessionId, ulong appletResourceId, IVirtualMemoryManager memoryManager)
+ {
+ if (!BehaviourContext.CheckValidRevision(parameter.Revision))
+ {
+ return ResultCode.OperationFailed;
+ }
+
+ if (GetWorkBufferSize(ref parameter) > workBufferSize)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ Debug.Assert(parameter.RenderingDevice == AudioRendererRenderingDevice.Dsp && parameter.ExecutionMode == AudioRendererExecutionMode.Auto);
+
+ Logger.Info?.Print(LogClass.AudioRenderer, $"Initializing with REV{BehaviourContext.GetRevisionNumber(parameter.Revision)}");
+
+ _behaviourContext.SetUserRevision(parameter.Revision);
+
+ _sampleRate = parameter.SampleRate;
+ _sampleCount = parameter.SampleCount;
+ _mixBufferCount = parameter.MixBufferCount;
+ _voiceChannelCountMax = Constants.VoiceChannelCountMax;
+ _upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount;
+ _appletResourceId = appletResourceId;
+ _memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount;
+ _executionMode = parameter.ExecutionMode;
+ _sessionId = sessionId;
+ MemoryManager = memoryManager;
+
+ WorkBufferAllocator workBufferAllocator;
+
+ _workBufferRegion = MemoryManager.GetWritableRegion(workBuffer, (int)workBufferSize);
+ _workBufferRegion.Memory.Span.Fill(0);
+ _workBufferMemoryPin = _workBufferRegion.Memory.Pin();
+
+ workBufferAllocator = new WorkBufferAllocator(_workBufferRegion.Memory);
+
+ PoolMapper poolMapper = new PoolMapper(processHandle, false);
+ poolMapper.InitializeSystemPool(ref _dspMemoryPoolState, workBuffer, workBufferSize);
+
+ _mixBuffer = workBufferAllocator.Allocate<float>(_sampleCount * (_voiceChannelCountMax + _mixBufferCount), 0x10);
+
+ if (_mixBuffer.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ Memory<float> upSamplerWorkBuffer = workBufferAllocator.Allocate<float>(Constants.TargetSampleCount * (_voiceChannelCountMax + _mixBufferCount) * _upsamplerCount, 0x10);
+
+ if (upSamplerWorkBuffer.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ _depopBuffer = workBufferAllocator.Allocate<float>((ulong)BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment);
+
+ if (_depopBuffer.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ // Invalidate DSP cache on what was currently allocated with workBuffer.
+ AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset);
+
+ Debug.Assert((workBufferAllocator.Offset % Constants.BufferAlignment) == 0);
+
+ Memory<VoiceState> voices = workBufferAllocator.Allocate<VoiceState>(parameter.VoiceCount, VoiceState.Alignment);
+
+ if (voices.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ foreach (ref VoiceState voice in voices.Span)
+ {
+ voice.Initialize();
+ }
+
+ // A pain to handle as we can't have VoiceState*, use indices to be a bit more safe
+ Memory<int> sortedVoices = workBufferAllocator.Allocate<int>(parameter.VoiceCount, 0x10);
+
+ if (sortedVoices.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ // Clear memory (use -1 as it's an invalid index)
+ sortedVoices.Span.Fill(-1);
+
+ Memory<VoiceChannelResource> voiceChannelResources = workBufferAllocator.Allocate<VoiceChannelResource>(parameter.VoiceCount, VoiceChannelResource.Alignment);
+
+ if (voiceChannelResources.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ for (uint id = 0; id < voiceChannelResources.Length; id++)
+ {
+ ref VoiceChannelResource voiceChannelResource = ref voiceChannelResources.Span[(int)id];
+
+ voiceChannelResource.Id = id;
+ voiceChannelResource.IsUsed = false;
+ }
+
+ Memory<VoiceUpdateState> voiceUpdateStates = workBufferAllocator.Allocate<VoiceUpdateState>(parameter.VoiceCount, VoiceUpdateState.Align);
+
+ if (voiceUpdateStates.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ uint mixesCount = parameter.SubMixBufferCount + 1;
+
+ Memory<MixState> mixes = workBufferAllocator.Allocate<MixState>(mixesCount, MixState.Alignment);
+
+ if (mixes.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ if (parameter.EffectCount == 0)
+ {
+ foreach (ref MixState mix in mixes.Span)
+ {
+ mix = new MixState(Memory<int>.Empty, ref _behaviourContext);
+ }
+ }
+ else
+ {
+ Memory<int> effectProcessingOrderArray = workBufferAllocator.Allocate<int>(parameter.EffectCount * mixesCount, 0x10);
+
+ foreach (ref MixState mix in mixes.Span)
+ {
+ mix = new MixState(effectProcessingOrderArray.Slice(0, (int)parameter.EffectCount), ref _behaviourContext);
+
+ effectProcessingOrderArray = effectProcessingOrderArray.Slice((int)parameter.EffectCount);
+ }
+ }
+
+ // Initialize the final mix id
+ mixes.Span[0].MixId = Constants.FinalMixId;
+
+ Memory<int> sortedMixesState = workBufferAllocator.Allocate<int>(mixesCount, 0x10);
+
+ if (sortedMixesState.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ // Clear memory (use -1 as it's an invalid index)
+ sortedMixesState.Span.Fill(-1);
+
+ Memory<byte> nodeStatesWorkBuffer = Memory<byte>.Empty;
+ Memory<byte> edgeMatrixWorkBuffer = Memory<byte>.Empty;
+
+ if (_behaviourContext.IsSplitterSupported())
+ {
+ nodeStatesWorkBuffer = workBufferAllocator.Allocate((uint)NodeStates.GetWorkBufferSize((int)mixesCount), 1);
+ edgeMatrixWorkBuffer = workBufferAllocator.Allocate((uint)EdgeMatrix.GetWorkBufferSize((int)mixesCount), 1);
+
+ if (nodeStatesWorkBuffer.IsEmpty || edgeMatrixWorkBuffer.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+ }
+
+ _mixContext.Initialize(sortedMixesState, mixes, nodeStatesWorkBuffer, edgeMatrixWorkBuffer);
+
+ _memoryPools = workBufferAllocator.Allocate<MemoryPoolState>(_memoryPoolCount, MemoryPoolState.Alignment);
+
+ if (_memoryPools.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ foreach (ref MemoryPoolState state in _memoryPools.Span)
+ {
+ state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu);
+ }
+
+ if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator))
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ _processHandle = processHandle;
+
+ _upsamplerManager = new UpsamplerManager(upSamplerWorkBuffer, _upsamplerCount);
+
+ _effectContext.Initialize(parameter.EffectCount);
+ _sinkContext.Initialize(parameter.SinkCount);
+
+ Memory<VoiceUpdateState> voiceUpdateStatesDsp = workBufferAllocator.Allocate<VoiceUpdateState>(parameter.VoiceCount, VoiceUpdateState.Align);
+
+ if (voiceUpdateStatesDsp.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ _voiceContext.Initialize(sortedVoices, voices, voiceChannelResources, voiceUpdateStates, voiceUpdateStatesDsp, parameter.VoiceCount);
+
+ if (parameter.PerformanceMetricFramesCount > 0)
+ {
+ ulong performanceBufferSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref _behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC;
+
+ _performanceBuffer = workBufferAllocator.Allocate(performanceBufferSize, Constants.BufferAlignment);
+
+ if (_performanceBuffer.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ _performanceManager = PerformanceManager.Create(_performanceBuffer, ref parameter, _behaviourContext);
+ }
+ else
+ {
+ _performanceManager = null;
+ }
+
+ _totalElapsedTicksUpdating = 0;
+ _totalElapsedTicks = 0;
+ _renderingTimeLimitPercent = 100;
+ _voiceDropEnabled = parameter.VoiceDropEnabled && _executionMode == AudioRendererExecutionMode.Auto;
+
+ AudioProcessorMemoryManager.InvalidateDataCache(workBuffer, workBufferSize);
+
+ _processHandle = processHandle;
+ _elapsedFrameCount = 0;
+
+ switch (_behaviourContext.GetCommandProcessingTimeEstimatorVersion())
+ {
+ case 1:
+ _commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion1(_sampleCount, _mixBufferCount);
+ break;
+ case 2:
+ _commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion2(_sampleCount, _mixBufferCount);
+ break;
+ case 3:
+ _commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion3(_sampleCount, _mixBufferCount);
+ break;
+ default:
+ throw new NotImplementedException($"Unsupported processing time estimator version {_behaviourContext.GetCommandProcessingTimeEstimatorVersion()}.");
+ }
+
+ return ResultCode.Success;
+ }
+
+ public void Start()
+ {
+ Logger.Info?.Print(LogClass.AudioRenderer, $"Starting renderer id {_sessionId}");
+
+ lock (_lock)
+ {
+ _elapsedFrameCount = 0;
+ _isActive = true;
+ }
+ }
+
+ public void Stop()
+ {
+ Logger.Info?.Print(LogClass.AudioRenderer, $"Stopping renderer id {_sessionId}");
+
+ lock (_lock)
+ {
+ _isActive = false;
+ }
+
+ if (_executionMode == AudioRendererExecutionMode.Auto)
+ {
+ _terminationEvent.WaitOne();
+ }
+
+ Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}");
+ }
+
+ public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input)
+ {
+ lock (_lock)
+ {
+ ulong updateStartTicks = GetSystemTicks();
+
+ output.Span.Fill(0);
+
+ StateUpdater stateUpdater = new StateUpdater(input, output, _processHandle, _behaviourContext);
+
+ ResultCode result;
+
+ result = stateUpdater.UpdateBehaviourContext();
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+
+ result = stateUpdater.UpdateMemoryPools(_memoryPools.Span);
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+
+ result = stateUpdater.UpdateVoiceChannelResources(_voiceContext);
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+
+ result = stateUpdater.UpdateVoices(_voiceContext, _memoryPools);
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+
+ result = stateUpdater.UpdateEffects(_effectContext, _isActive, _memoryPools);
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+
+ if (_behaviourContext.IsSplitterSupported())
+ {
+ result = stateUpdater.UpdateSplitter(_splitterContext);
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+ }
+
+ result = stateUpdater.UpdateMixes(_mixContext, GetMixBufferCount(), _effectContext, _splitterContext);
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+
+ result = stateUpdater.UpdateSinks(_sinkContext, _memoryPools);
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+
+ result = stateUpdater.UpdatePerformanceBuffer(_performanceManager, performanceOutput.Span);
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+
+ result = stateUpdater.UpdateErrorInfo();
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+
+ if (_behaviourContext.IsElapsedFrameCountSupported())
+ {
+ result = stateUpdater.UpdateRendererInfo(_elapsedFrameCount);
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+ }
+
+ result = stateUpdater.CheckConsumedSize();
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+
+ _systemEvent.Clear();
+
+ ulong updateEndTicks = GetSystemTicks();
+
+ _totalElapsedTicksUpdating += (updateEndTicks - updateStartTicks);
+
+ return result;
+ }
+ }
+
+ private ulong GetSystemTicks()
+ {
+ double ticks = ARMeilleure.State.ExecutionContext.ElapsedTicks * ARMeilleure.State.ExecutionContext.TickFrequency;
+
+ return (ulong)(ticks * Constants.TargetTimerFrequency);
+ }
+
+ private uint ComputeVoiceDrop(CommandBuffer commandBuffer, long voicesEstimatedTime, long deltaTimeDsp)
+ {
+ int i;
+
+ for (i = 0; i < commandBuffer.CommandList.Commands.Count; i++)
+ {
+ ICommand command = commandBuffer.CommandList.Commands[i];
+
+ CommandType commandType = command.CommandType;
+
+ if (commandType == CommandType.AdpcmDataSourceVersion1 ||
+ commandType == CommandType.AdpcmDataSourceVersion2 ||
+ commandType == CommandType.PcmInt16DataSourceVersion1 ||
+ commandType == CommandType.PcmInt16DataSourceVersion2 ||
+ commandType == CommandType.PcmFloatDataSourceVersion1 ||
+ commandType == CommandType.PcmFloatDataSourceVersion2 ||
+ commandType == CommandType.Performance)
+ {
+ break;
+ }
+ }
+
+ uint voiceDropped = 0;
+
+ for (; i < commandBuffer.CommandList.Commands.Count; i++)
+ {
+ ICommand targetCommand = commandBuffer.CommandList.Commands[i];
+
+ int targetNodeId = targetCommand.NodeId;
+
+ if (voicesEstimatedTime <= deltaTimeDsp || NodeIdHelper.GetType(targetNodeId) != NodeIdType.Voice)
+ {
+ break;
+ }
+
+ ref VoiceState voice = ref _voiceContext.GetState(NodeIdHelper.GetBase(targetNodeId));
+
+ if (voice.Priority == Constants.VoiceHighestPriority)
+ {
+ break;
+ }
+
+ // We can safely drop this voice, disable all associated commands while activating depop preparation commands.
+ voiceDropped++;
+ voice.VoiceDropFlag = true;
+
+ Logger.Warning?.Print(LogClass.AudioRenderer, $"Dropping voice {voice.NodeId}");
+
+ for (; i < commandBuffer.CommandList.Commands.Count; i++)
+ {
+ ICommand command = commandBuffer.CommandList.Commands[i];
+
+ if (command.NodeId != targetNodeId)
+ {
+ break;
+ }
+
+ if (command.CommandType == CommandType.DepopPrepare)
+ {
+ command.Enabled = true;
+ }
+ else if (command.CommandType == CommandType.Performance || !command.Enabled)
+ {
+ continue;
+ }
+ else
+ {
+ command.Enabled = false;
+
+ voicesEstimatedTime -= (long)command.EstimatedProcessingTime;
+ }
+ }
+ }
+
+ return voiceDropped;
+ }
+
+ private void GenerateCommandList(out CommandList commandList)
+ {
+ Debug.Assert(_executionMode == AudioRendererExecutionMode.Auto);
+
+ PoolMapper.ClearUsageState(_memoryPools);
+
+ ulong startTicks = GetSystemTicks();
+
+ commandList = new CommandList(this);
+
+ if (_performanceManager != null)
+ {
+ _performanceManager.TapFrame(_isDspRunningBehind, _voiceDropCount, _renderingStartTick);
+
+ _isDspRunningBehind = false;
+ _voiceDropCount = 0;
+ _renderingStartTick = 0;
+ }
+
+ CommandBuffer commandBuffer = new CommandBuffer(commandList, _commandProcessingTimeEstimator);
+
+ CommandGenerator commandGenerator = new CommandGenerator(commandBuffer, GetContext(), _voiceContext, _mixContext, _effectContext, _sinkContext, _splitterContext, _performanceManager);
+
+ _voiceContext.Sort();
+ commandGenerator.GenerateVoices();
+
+ long voicesEstimatedTime = (long)commandBuffer.EstimatedProcessingTime;
+
+ commandGenerator.GenerateSubMixes();
+ commandGenerator.GenerateFinalMixes();
+ commandGenerator.GenerateSinks();
+
+ long totalEstimatedTime = (long)commandBuffer.EstimatedProcessingTime;
+
+ if (_voiceDropEnabled)
+ {
+ long maxDspTime = GetMaxAllocatedTimeForDsp();
+
+ long restEstimateTime = totalEstimatedTime - voicesEstimatedTime;
+
+ long deltaTimeDsp = Math.Max(maxDspTime - restEstimateTime, 0);
+
+ _voiceDropCount = ComputeVoiceDrop(commandBuffer, voicesEstimatedTime, deltaTimeDsp);
+ }
+
+ _voiceContext.UpdateForCommandGeneration();
+
+ ulong endTicks = GetSystemTicks();
+
+ _totalElapsedTicks = endTicks - startTicks;
+
+ _renderingStartTick = GetSystemTicks();
+ _elapsedFrameCount++;
+ }
+
+ private int GetMaxAllocatedTimeForDsp()
+ {
+ return (int)(Constants.AudioProcessorMaxUpdateTimePerSessions * _behaviourContext.GetAudioRendererProcessingTimeLimit() * (GetRenderingTimeLimit() / 100.0f));
+ }
+
+ public void SendCommands()
+ {
+ lock (_lock)
+ {
+ if (_isActive)
+ {
+ _terminationEvent.Reset();
+
+ GenerateCommandList(out CommandList commands);
+
+ _manager.Processor.Send(_sessionId,
+ commands,
+ GetMaxAllocatedTimeForDsp(),
+ _appletResourceId);
+
+ _systemEvent.Signal();
+ }
+ else
+ {
+ _terminationEvent.Set();
+ }
+ }
+ }
+
+ public uint GetMixBufferCount()
+ {
+ return _mixBufferCount;
+ }
+
+ public void SetRenderingTimeLimitPercent(uint percent)
+ {
+ Debug.Assert(percent <= 100);
+
+ _renderingTimeLimitPercent = percent;
+ }
+
+ public uint GetRenderingTimeLimit()
+ {
+ return _renderingTimeLimitPercent;
+ }
+
+ public Memory<float> GetMixBuffer()
+ {
+ return _mixBuffer;
+ }
+
+ public uint GetSampleCount()
+ {
+ return _sampleCount;
+ }
+
+ public uint GetSampleRate()
+ {
+ return _sampleRate;
+ }
+
+ public uint GetVoiceChannelCountMax()
+ {
+ return _voiceChannelCountMax;
+ }
+
+ public bool IsActive()
+ {
+ return _isActive;
+ }
+
+ private RendererSystemContext GetContext()
+ {
+ return new RendererSystemContext
+ {
+ ChannelCount = _manager.Processor.OutputDevices[_sessionId].GetChannelCount(),
+ BehaviourContext = _behaviourContext,
+ DepopBuffer = _depopBuffer,
+ MixBufferCount = GetMixBufferCount(),
+ SessionId = _sessionId,
+ UpsamplerManager = _upsamplerManager
+ };
+ }
+
+ public int GetSessionId()
+ {
+ return _sessionId;
+ }
+
+ public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter)
+ {
+ BehaviourContext behaviourContext = new BehaviourContext();
+
+ behaviourContext.SetUserRevision(parameter.Revision);
+
+ uint mixesCount = parameter.SubMixBufferCount + 1;
+
+ uint memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount;
+
+ ulong size = 0;
+
+ // Mix Buffers
+ size = WorkBufferAllocator.GetTargetSize<float>(size, parameter.SampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount), 0x10);
+
+ // Upsampler workbuffer
+ size = WorkBufferAllocator.GetTargetSize<float>(size, Constants.TargetSampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount) * (parameter.SinkCount + parameter.SubMixBufferCount), 0x10);
+
+ // Depop buffer
+ size = WorkBufferAllocator.GetTargetSize<float>(size, (ulong)BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment);
+
+ // Voice
+ size = WorkBufferAllocator.GetTargetSize<VoiceState>(size, parameter.VoiceCount, VoiceState.Alignment);
+ size = WorkBufferAllocator.GetTargetSize<int>(size, parameter.VoiceCount, 0x10);
+ size = WorkBufferAllocator.GetTargetSize<VoiceChannelResource>(size, parameter.VoiceCount, VoiceChannelResource.Alignment);
+ size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align);
+
+ // Mix
+ size = WorkBufferAllocator.GetTargetSize<MixState>(size, mixesCount, MixState.Alignment);
+ size = WorkBufferAllocator.GetTargetSize<int>(size, parameter.EffectCount * mixesCount, 0x10);
+ size = WorkBufferAllocator.GetTargetSize<int>(size, mixesCount, 0x10);
+
+ if (behaviourContext.IsSplitterSupported())
+ {
+ size += (ulong)BitUtils.AlignUp(NodeStates.GetWorkBufferSize((int)mixesCount) + EdgeMatrix.GetWorkBufferSize((int)mixesCount), 0x10);
+ }
+
+ // Memory Pool
+ size = WorkBufferAllocator.GetTargetSize<MemoryPoolState>(size, memoryPoolCount, MemoryPoolState.Alignment);
+
+ // Splitter
+ size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter);
+
+ // DSP Voice
+ size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align);
+
+ // Performance
+ if (parameter.PerformanceMetricFramesCount > 0)
+ {
+ ulong performanceMetricsPerFramesSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC;
+
+ size += BitUtils.AlignUp(performanceMetricsPerFramesSize, Constants.PerformanceMetricsPerFramesSizeAlignment);
+ }
+
+ return BitUtils.AlignUp(size, Constants.WorkBufferAlignment);
+ }
+
+ public ResultCode QuerySystemEvent(out IWritableEvent systemEvent)
+ {
+ systemEvent = default;
+
+ if (_executionMode == AudioRendererExecutionMode.Manual)
+ {
+ return ResultCode.UnsupportedOperation;
+ }
+
+ systemEvent = _systemEvent;
+
+ return ResultCode.Success;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (_isActive)
+ {
+ Stop();
+ }
+
+ PoolMapper mapper = new PoolMapper(_processHandle, false);
+ mapper.Unmap(ref _dspMemoryPoolState);
+
+ PoolMapper.ClearUsageState(_memoryPools);
+
+ for (int i = 0; i < _memoryPoolCount; i++)
+ {
+ ref MemoryPoolState memoryPool = ref _memoryPools.Span[i];
+
+ if (memoryPool.IsMapped())
+ {
+ mapper.Unmap(ref memoryPool);
+ }
+ }
+
+ _manager.Unregister(this);
+ _terminationEvent.Dispose();
+ _workBufferMemoryPin.Dispose();
+ _workBufferRegion.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs b/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
new file mode 100644
index 00000000..ec847948
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
@@ -0,0 +1,334 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Renderer.Dsp;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Common.Logging;
+using Ryujinx.Memory;
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+namespace Ryujinx.Audio.Renderer.Server
+{
+ /// <summary>
+ /// The audio renderer manager.
+ /// </summary>
+ public class AudioRendererManager : IDisposable
+ {
+ /// <summary>
+ /// Lock used for session allocation.
+ /// </summary>
+ private object _sessionLock = new object();
+
+ /// <summary>
+ /// Lock used to control the <see cref="AudioProcessor"/> running state.
+ /// </summary>
+ private object _audioProcessorLock = new object();
+
+ /// <summary>
+ /// The session ids allocation table.
+ /// </summary>
+ private int[] _sessionIds;
+
+ /// <summary>
+ /// The events linked to each session.
+ /// </summary>
+ private IWritableEvent[] _sessionsSystemEvent;
+
+ /// <summary>
+ /// The <see cref="AudioRenderSystem"/> sessions instances.
+ /// </summary>
+ private AudioRenderSystem[] _sessions;
+
+ /// <summary>
+ /// The count of active sessions.
+ /// </summary>
+ private int _activeSessionCount;
+
+ /// <summary>
+ /// The worker thread used to run <see cref="SendCommands"/>.
+ /// </summary>
+ private Thread _workerThread;
+
+ /// <summary>
+ /// Indicate if the worker thread and <see cref="AudioProcessor"/> are running.
+ /// </summary>
+ private bool _isRunning;
+
+ /// <summary>
+ /// The audio device driver to create audio outputs.
+ /// </summary>
+ private IHardwareDeviceDriver _deviceDriver;
+
+ /// <summary>
+ /// The <see cref="AudioProcessor"/> instance associated to this manager.
+ /// </summary>
+ public AudioProcessor Processor { get; }
+
+ /// <summary>
+ /// Create a new <see cref="AudioRendererManager"/>.
+ /// </summary>
+ public AudioRendererManager()
+ {
+ Processor = new AudioProcessor();
+ _sessionIds = new int[Constants.AudioRendererSessionCountMax];
+ _sessions = new AudioRenderSystem[Constants.AudioRendererSessionCountMax];
+ _activeSessionCount = 0;
+
+ for (int i = 0; i < _sessionIds.Length; i++)
+ {
+ _sessionIds[i] = i;
+ }
+ }
+
+ /// <summary>
+ /// Initialize the <see cref="AudioRendererManager"/>.
+ /// </summary>
+ /// <param name="sessionSystemEvents">The events associated to each session.</param>
+ /// <param name="deviceDriver">The device driver to use to create audio outputs.</param>
+ public void Initialize(IWritableEvent[] sessionSystemEvents, IHardwareDeviceDriver deviceDriver)
+ {
+ _sessionsSystemEvent = sessionSystemEvents;
+ _deviceDriver = deviceDriver;
+ }
+
+ /// <summary>
+ /// Get the work buffer size required by a session.
+ /// </summary>
+ /// <param name="parameter">The user configuration</param>
+ /// <returns>The work buffer size required by a session.</returns>
+ public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter)
+ {
+ return AudioRenderSystem.GetWorkBufferSize(ref parameter);
+ }
+
+ /// <summary>
+ /// Acquire a new session id.
+ /// </summary>
+ /// <returns>A new session id.</returns>
+ private int AcquireSessionId()
+ {
+ lock (_sessionLock)
+ {
+ int index = _activeSessionCount;
+
+ Debug.Assert(index < _sessionIds.Length);
+
+ int sessionId = _sessionIds[index];
+
+ _sessionIds[index] = -1;
+
+ _activeSessionCount++;
+
+ Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new renderer ({sessionId})");
+
+ return sessionId;
+ }
+ }
+
+ /// <summary>
+ /// Release a given <paramref name="sessionId"/>.
+ /// </summary>
+ /// <param name="sessionId">The session id to release.</param>
+ private void ReleaseSessionId(int sessionId)
+ {
+ lock (_sessionLock)
+ {
+ Debug.Assert(_activeSessionCount > 0);
+
+ int newIndex = --_activeSessionCount;
+
+ _sessionIds[newIndex] = sessionId;
+ }
+
+ Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered renderer ({sessionId})");
+ }
+
+ /// <summary>
+ /// Check if there is any audio renderer active.
+ /// </summary>
+ /// <returns>Returns true if there is any audio renderer active.</returns>
+ private bool HasAnyActiveRendererLocked()
+ {
+ foreach (AudioRenderSystem renderer in _sessions)
+ {
+ if (renderer != null)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Start the <see cref="AudioProcessor"/> and worker thread.
+ /// </summary>
+ private void StartLocked()
+ {
+ _isRunning = true;
+
+ // TODO: virtual device mapping (IAudioDevice)
+ Processor.Start(_deviceDriver);
+
+ _workerThread = new Thread(SendCommands)
+ {
+ Name = "AudioRendererManager.Worker"
+ };
+
+ _workerThread.Start();
+ }
+
+ /// <summary>
+ /// Stop the <see cref="AudioProcessor"/> and worker thread.
+ /// </summary>
+ private void StopLocked()
+ {
+ _isRunning = false;
+
+ _workerThread.Join();
+ Processor.Stop();
+
+ Logger.Info?.Print(LogClass.AudioRenderer, "Stopped audio renderer");
+ }
+
+ /// <summary>
+ /// Worker main function. This is used to dispatch audio renderer commands to the <see cref="AudioProcessor"/>.
+ /// </summary>
+ private void SendCommands()
+ {
+ Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio renderer");
+ Processor.Wait();
+
+ while (_isRunning)
+ {
+ lock (_sessionLock)
+ {
+ foreach(AudioRenderSystem renderer in _sessions)
+ {
+ renderer?.SendCommands();
+ }
+ }
+
+ Processor.Signal();
+ Processor.Wait();
+ }
+ }
+
+ /// <summary>
+ /// Register a new <see cref="AudioRenderSystem"/>.
+ /// </summary>
+ /// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
+ private void Register(AudioRenderSystem renderer)
+ {
+ lock (_sessionLock)
+ {
+ _sessions[renderer.GetSessionId()] = renderer;
+ }
+
+ lock (_audioProcessorLock)
+ {
+ if (!_isRunning)
+ {
+ StartLocked();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Unregister a new <see cref="AudioRenderSystem"/>.
+ /// </summary>
+ /// <param name="renderer">The <see cref="AudioRenderSystem"/> to unregister.</param>
+ internal void Unregister(AudioRenderSystem renderer)
+ {
+ lock (_sessionLock)
+ {
+ int sessionId = renderer.GetSessionId();
+
+ _sessions[renderer.GetSessionId()] = null;
+
+ ReleaseSessionId(sessionId);
+ }
+
+ lock (_audioProcessorLock)
+ {
+ if (_isRunning && !HasAnyActiveRendererLocked())
+ {
+ StopLocked();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Open a new <see cref="AudioRenderSystem"/>
+ /// </summary>
+ /// <param name="renderer">The new <see cref="AudioRenderSystem"/></param>
+ /// <param name="memoryManager">The memory manager that will be used for all guest memory operations.</param>
+ /// <param name="parameter">The user configuration</param>
+ /// <param name="appletResourceUserId">The applet resource user id of the application.</param>
+ /// <param name="workBufferAddress">The guest work buffer address.</param>
+ /// <param name="workBufferSize">The guest work buffer size.</param>
+ /// <param name="processHandle">The process handle of the application.</param>
+ /// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns>
+ public ResultCode OpenAudioRenderer(out AudioRenderSystem renderer, IVirtualMemoryManager memoryManager, ref AudioRendererConfiguration parameter, ulong appletResourceUserId, ulong workBufferAddress, ulong workBufferSize, uint processHandle)
+ {
+ int sessionId = AcquireSessionId();
+
+ AudioRenderSystem audioRenderer = new AudioRenderSystem(this, _sessionsSystemEvent[sessionId]);
+
+ ResultCode result = audioRenderer.Initialize(ref parameter, processHandle, workBufferAddress, workBufferSize, sessionId, appletResourceUserId, memoryManager);
+
+ if (result == ResultCode.Success)
+ {
+ renderer = audioRenderer;
+
+ Register(renderer);
+ }
+ else
+ {
+ ReleaseSessionId(sessionId);
+
+ renderer = null;
+ }
+
+ return result;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ lock (_audioProcessorLock)
+ {
+ if (_isRunning)
+ {
+ StopLocked();
+ }
+ }
+
+ Processor.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
new file mode 100644
index 00000000..b31f9e9f
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
@@ -0,0 +1,405 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Diagnostics;
+using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
+
+namespace Ryujinx.Audio.Renderer.Server
+{
+ /// <summary>
+ /// Behaviour context.
+ /// </summary>
+ /// <remarks>This handles features based on the audio renderer revision provided by the user.</remarks>
+ public class BehaviourContext
+ {
+ /// <summary>
+ /// The base magic of the Audio Renderer revision.
+ /// </summary>
+ public const int BaseRevisionMagic = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('0' << 24);
+
+ /// <summary>
+ /// REV1: first revision.
+ /// </summary>
+ public const int Revision1 = 1 << 24;
+
+ /// <summary>
+ /// REV2: Added support for splitter and fix GC-ADPCM context not being provided to the DSP.
+ /// </summary>
+ /// <remarks>This was added in system update 2.0.0</remarks>
+ public const int Revision2 = 2 << 24;
+
+ /// <summary>
+ /// REV3: Incremented the max pre-delay from 150 to 350 for the reverb command and removed the (unused) codec system.
+ /// </summary>
+ /// <remarks>This was added in system update 3.0.0</remarks>
+ public const int Revision3 = 3 << 24;
+
+ /// <summary>
+ /// REV4: Added USB audio device support and incremented the rendering limit percent to 75%.
+ /// </summary>
+ /// <remarks>This was added in system update 4.0.0</remarks>
+ public const int Revision4 = 4 << 24;
+
+ /// <summary>
+ /// REV5: <see cref="Parameter.VoiceInParameter.DecodingBehaviour"/>, <see cref="Parameter.VoiceInParameter.FlushWaveBufferCount"/> were added to voice.
+ /// A new performance frame format (version 2) was added with support for more information about DSP timing.
+ /// <see cref="Parameter.RendererInfoOutStatus"/> was added to supply the count of update done sent to the DSP.
+ /// A new version of the command estimator was added to address timing changes caused by the voice changes.
+ /// Additionally, the rendering limit percent was incremented to 80%.
+ ///
+ /// </summary>
+ /// <remarks>This was added in system update 6.0.0</remarks>
+ public const int Revision5 = 5 << 24;
+
+ /// <summary>
+ /// REV6: This fixed a bug in the biquad filter command not clearing up <see cref="Dsp.State.BiquadFilterState"/> with <see cref="Effect.UsageState.New"/> usage state.
+ /// </summary>
+ /// <remarks>This was added in system update 6.1.0</remarks>
+ public const int Revision6 = 6 << 24;
+
+ /// <summary>
+ /// REV7: Client side (finally) doesn't send all the mix client state to the server and can do partial updates.
+ /// </summary>
+ /// <remarks>This was added in system update 8.0.0</remarks>
+ public const int Revision7 = 7 << 24;
+
+ /// <summary>
+ /// REV8:
+ /// Wavebuffer was changed to support more control over loop (you can now specify where to start and end a loop, and how many times to loop).
+ /// <see cref="Parameter.VoiceInParameter.SrcQuality"/> was added (see <see cref="Parameter.VoiceInParameter.SampleRateConversionQuality"/> for more info).
+ /// Final leftovers of the codec system were removed.
+ /// <see cref="Common.SampleFormat.PcmFloat"/> support was added.
+ /// A new version of the command estimator was added to address timing changes caused by the voice and command changes.
+ /// </summary>
+ /// <remarks>This was added in system update 9.0.0</remarks>
+ public const int Revision8 = 8 << 24;
+
+ /// <summary>
+ /// Last revision supported by the implementation.
+ /// </summary>
+ public const int LastRevision = Revision8;
+
+ /// <summary>
+ /// Target revision magic supported by the implementation.
+ /// </summary>
+ public const int ProcessRevision = BaseRevisionMagic + LastRevision;
+
+ /// <summary>
+ /// Get the revision number from the revision magic.
+ /// </summary>
+ /// <param name="revision">The revision magic.</param>
+ /// <returns>The revision number.</returns>
+ public static int GetRevisionNumber(int revision) => (revision - BaseRevisionMagic) >> 24;
+
+ /// <summary>
+ /// Current active revision.
+ /// </summary>
+ public int UserRevision { get; private set; }
+
+ /// <summary>
+ /// Error storage.
+ /// </summary>
+ private ErrorInfo[] _errorInfos;
+
+ /// <summary>
+ /// Current position in the <see cref="_errorInfos"/> array.
+ /// </summary>
+ private uint _errorIndex;
+
+ /// <summary>
+ /// Current flags of the <see cref="BehaviourContext"/>.
+ /// </summary>
+ private ulong _flags;
+
+ /// <summary>
+ /// Create a new instance of <see cref="BehaviourContext"/>.
+ /// </summary>
+ public BehaviourContext()
+ {
+ UserRevision = 0;
+ _errorInfos = new ErrorInfo[Constants.MaxErrorInfos];
+ _errorIndex = 0;
+ }
+
+ /// <summary>
+ /// Set the active revision.
+ /// </summary>
+ /// <param name="userRevision">The active revision.</param>
+ public void SetUserRevision(int userRevision)
+ {
+ UserRevision = userRevision;
+ }
+
+ /// <summary>
+ /// Update flags of the <see cref="BehaviourContext"/>.
+ /// </summary>
+ /// <param name="flags">The new flags.</param>
+ public void UpdateFlags(ulong flags)
+ {
+ _flags = flags;
+ }
+
+ /// <summary>
+ /// Check if a given revision is valid/supported.
+ /// </summary>
+ /// <param name="revision">The revision magic to check.</param>
+ /// <returns>Returns true if the given revision is valid/supported</returns>
+ public static bool CheckValidRevision(int revision)
+ {
+ return GetRevisionNumber(revision) <= GetRevisionNumber(ProcessRevision);
+ }
+
+ /// <summary>
+ /// Check if the given revision is greater than or equal the supported revision.
+ /// </summary>
+ /// <param name="revision">The revision magic to check.</param>
+ /// <param name="supportedRevision">The revision magic of the supported revision.</param>
+ /// <returns>Returns true if the given revision is greater than or equal the supported revision.</returns>
+ public static bool CheckFeatureSupported(int revision, int supportedRevision)
+ {
+ int revA = GetRevisionNumber(revision);
+ int revB = GetRevisionNumber(supportedRevision);
+
+ if (revA > LastRevision)
+ {
+ revA = 1;
+ }
+
+ if (revB > LastRevision)
+ {
+ revB = 1;
+ }
+
+ return revA >= revB;
+ }
+
+ /// <summary>
+ /// Check if the memory pool mapping bypass flag is active.
+ /// </summary>
+ /// <returns>True if the memory pool mapping bypass flag is active.</returns>
+ public bool IsMemoryPoolForceMappingEnabled()
+ {
+ return (_flags & 1) != 0;
+ }
+
+ /// <summary>
+ /// Check if the audio renderer should fix the GC-ADPCM context not being provided to the DSP.
+ /// </summary>
+ /// <returns>True if if the audio renderer should fix it.</returns>
+ public bool IsAdpcmLoopContextBugFixed()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2);
+ }
+
+ /// <summary>
+ /// Check if the audio renderer should accept splitters.
+ /// </summary>
+ /// <returns>True if the audio renderer should accept splitters.</returns>
+ public bool IsSplitterSupported()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2);
+ }
+
+ /// <summary>
+ /// Check if the audio renderer should use a max pre-delay of 350 instead of 150.
+ /// </summary>
+ /// <returns>True if the max pre-delay must be 350.</returns>
+ public bool IsLongSizePreDelaySupported()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision3);
+ }
+
+ /// <summary>
+ /// Check if the audio renderer should expose USB audio device.
+ /// </summary>
+ /// <returns>True if the audio renderer should expose USB audio device.</returns>
+ public bool IsAudioUsbDeviceOutputSupported()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision4);
+ }
+
+ /// <summary>
+ /// Get the percentage allocated to the audio renderer on the DSP for processing.
+ /// </summary>
+ /// <returns>The percentage allocated to the audio renderer on the DSP for processing.</returns>
+ public float GetAudioRendererProcessingTimeLimit()
+ {
+ if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5))
+ {
+ return 0.80f;
+ }
+ else if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision4))
+ {
+ return 0.75f;
+ }
+
+ return 0.70f;
+ }
+
+ /// <summary>
+ /// Check if the audio render should support voice flushing.
+ /// </summary>
+ /// <returns>True if the audio render should support voice flushing.</returns>
+ public bool IsFlushVoiceWaveBuffersSupported()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5);
+ }
+
+ /// <summary>
+ /// Check if the audio renderer should trust the user destination count in <see cref="Splitter.SplitterState.Update(Splitter.SplitterContext, ref Parameter.SplitterInParameter, ReadOnlySpan{byte})"/>.
+ /// </summary>
+ /// <returns>True if the audio renderer should trust the user destination count.</returns>
+ public bool IsSplitterBugFixed()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5);
+ }
+
+ /// <summary>
+ /// Check if the audio renderer should supply the elapsed frame count to the user when updating.
+ /// </summary>
+ /// <returns>True if the audio renderer should supply the elapsed frame count to the user when updating.</returns>
+ public bool IsElapsedFrameCountSupported()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5);
+ }
+
+ /// <summary>
+ /// Get the performance metric data format version.
+ /// </summary>
+ /// <returns>The performance metric data format version.</returns>
+ public uint GetPerformanceMetricsDataFormat()
+ {
+ if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5))
+ {
+ return 2;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+ /// <summary>
+ /// Check if the audio renderer should support <see cref="Parameter.VoiceInParameter.DecodingBehaviour"/>.
+ /// </summary>
+ /// <returns>True if the audio renderer should support <see cref="Parameter.VoiceInParameter.DecodingBehaviour"/>.</returns>
+ public bool IsDecodingBehaviourFlagSupported()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5);
+ }
+
+ /// <summary>
+ /// Check if the audio renderer should fix the biquad filter command not clearing up <see cref="Dsp.State.BiquadFilterState"/> with <see cref="Effect.UsageState.New"/> usage state.
+ /// </summary>
+ /// <returns>True if the biquad filter state should be cleared.</returns>
+ public bool IsBiquadFilterEffectStateClearBugFixed()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision6);
+ }
+
+ /// <summary>
+ /// Check if the audio renderer should accept partial mix updates.
+ /// </summary>
+ /// <returns>True if the audio renderer should accept partial mix updates.</returns>
+ public bool IsMixInParameterDirtyOnlyUpdateSupported()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision7);
+ }
+
+ /// <summary>
+ /// Check if the audio renderer should use the new wavebuffer format.
+ /// </summary>
+ /// <returns>True if the audio renderer should use the new wavebuffer format.</returns>
+ public bool IsWaveBufferVersion2Supported()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8);
+ }
+
+ /// <summary>
+ /// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
+ /// </summary>
+ /// <returns>The version of the <see cref="ICommandProcessingTimeEstimator"/>.</returns>
+ public int GetCommandProcessingTimeEstimatorVersion()
+ {
+ if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8))
+ {
+ return 3;
+ }
+
+ if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5))
+ {
+ return 2;
+ }
+
+ return 1;
+ }
+
+ /// <summary>
+ /// Append a new <see cref="ErrorInfo"/> to the error array.
+ /// </summary>
+ /// <param name="errorInfo">The new <see cref="ErrorInfo"/> to add.</param>
+ public void AppendError(ref ErrorInfo errorInfo)
+ {
+ Debug.Assert(errorInfo.ErrorCode == ResultCode.Success);
+
+ if (_errorIndex <= Constants.MaxErrorInfos - 1)
+ {
+ _errorInfos[_errorIndex++] = errorInfo;
+ }
+ }
+
+ /// <summary>
+ /// Copy the internal <see cref="ErrorInfo"/> array to the given <see cref="Span{ErrorInfo}"/> and output the count copied.
+ /// </summary>
+ /// <param name="errorInfos">The output <see cref="Span{ErrorInfo}"/>.</param>
+ /// <param name="errorCount">The output error count containing the count of <see cref="ErrorInfo"/> copied.</param>
+ public void CopyErrorInfo(Span<ErrorInfo> errorInfos, out uint errorCount)
+ {
+ if (errorInfos.Length != Constants.MaxErrorInfos)
+ {
+ throw new ArgumentException("Invalid size of errorInfos span!");
+ }
+
+ errorCount = Math.Min(_errorIndex, Constants.MaxErrorInfos);
+
+ for (int i = 0; i < Constants.MaxErrorInfos; i++)
+ {
+ if (i < errorCount)
+ {
+ errorInfos[i] = _errorInfos[i];
+ }
+ else
+ {
+ errorInfos[i] = new ErrorInfo
+ {
+ ErrorCode = 0,
+ ExtraErrorInfo = 0
+ };
+ }
+ }
+ }
+
+ /// <summary>
+ /// Clear the <see cref="ErrorInfo"/> array.
+ /// </summary>
+ public void ClearError()
+ {
+ _errorIndex = 0;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
new file mode 100644
index 00000000..ca37e090
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
@@ -0,0 +1,484 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.Command;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Server.Performance;
+using Ryujinx.Audio.Renderer.Server.Sink;
+using Ryujinx.Audio.Renderer.Server.Upsampler;
+using Ryujinx.Audio.Renderer.Server.Voice;
+using System;
+using CpuAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Server
+{
+ /// <summary>
+ /// An API to generate commands and aggregate them into a <see cref="CommandList"/>.
+ /// </summary>
+ public class CommandBuffer
+ {
+ /// <summary>
+ /// The command processing time estimator in use.
+ /// </summary>
+ private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator;
+
+ /// <summary>
+ /// The estimated total processing time.
+ /// </summary>
+ public ulong EstimatedProcessingTime { get; set; }
+
+ /// <summary>
+ /// The command list that is populated by the <see cref="CommandBuffer"/>.
+ /// </summary>
+ public CommandList CommandList { get; }
+
+ /// <summary>
+ /// Create a new <see cref="CommandBuffer"/>.
+ /// </summary>
+ /// <param name="commandList">The command list that will store the generated commands.</param>
+ /// <param name="commandProcessingTimeEstimator">The command processing time estimator to use.</param>
+ public CommandBuffer(CommandList commandList, ICommandProcessingTimeEstimator commandProcessingTimeEstimator)
+ {
+ CommandList = commandList;
+ EstimatedProcessingTime = 0;
+ _commandProcessingTimeEstimator = commandProcessingTimeEstimator;
+ }
+
+ /// <summary>
+ /// Add a new generated command to the <see cref="CommandList"/>.
+ /// </summary>
+ /// <param name="command">The command to add.</param>
+ private void AddCommand(ICommand command)
+ {
+ EstimatedProcessingTime += command.EstimatedProcessingTime;
+
+ CommandList.AddCommand(command);
+ }
+
+ /// <summary>
+ /// Generate a new <see cref="ClearMixBufferCommand"/>.
+ /// </summary>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateClearMixBuffer(int nodeId)
+ {
+ ClearMixBufferCommand command = new ClearMixBufferCommand(nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Generate a new <see cref="DepopPrepareCommand"/>.
+ /// </summary>
+ /// <param name="state">The voice state associated.</param>
+ /// <param name="depopBuffer">The depop buffer.</param>
+ /// <param name="bufferCount">The buffer count.</param>
+ /// <param name="bufferOffset">The target buffer offset.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ /// <param name="wasPlaying">Set to true if the voice was playing previously.</param>
+ public void GenerateDepopPrepare(Memory<VoiceUpdateState> state, Memory<float> depopBuffer, uint bufferCount, uint bufferOffset, int nodeId, bool wasPlaying)
+ {
+ DepopPrepareCommand command = new DepopPrepareCommand(state, depopBuffer, bufferCount, bufferOffset, nodeId, wasPlaying);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Generate a new <see cref="PerformanceCommand"/>.
+ /// </summary>
+ /// <param name="performanceEntryAddresses">The <see cref="PerformanceEntryAddresses"/>.</param>
+ /// <param name="type">The performance operation to perform.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId)
+ {
+ PerformanceCommand command = new PerformanceCommand(ref performanceEntryAddresses, type, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Create a new <see cref="VolumeRampCommand"/>.
+ /// </summary>
+ /// <param name="previousVolume">The previous volume.</param>
+ /// <param name="volume">The new volume.</param>
+ /// <param name="bufferIndex">The index of the mix buffer to use.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateVolumeRamp(float previousVolume, float volume, uint bufferIndex, int nodeId)
+ {
+ VolumeRampCommand command = new VolumeRampCommand(previousVolume, volume, bufferIndex, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Create a new <see cref="DataSourceVersion2Command"/>.
+ /// </summary>
+ /// <param name="voiceState">The <see cref="VoiceState"/> to generate the command from.</param>
+ /// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
+ /// <param name="outputBufferIndex">The output buffer index to use.</param>
+ /// <param name="channelIndex">The target channel index.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateDataSourceVersion2(ref VoiceState voiceState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
+ {
+ DataSourceVersion2Command command = new DataSourceVersion2Command(ref voiceState, state, outputBufferIndex, channelIndex, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Create a new <see cref="PcmInt16DataSourceCommandVersion1"/>.
+ /// </summary>
+ /// <param name="voiceState">The <see cref="VoiceState"/> to generate the command from.</param>
+ /// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
+ /// <param name="outputBufferIndex">The output buffer index to use.</param>
+ /// <param name="channelIndex">The target channel index.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GeneratePcmInt16DataSourceVersion1(ref VoiceState voiceState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
+ {
+ PcmInt16DataSourceCommandVersion1 command = new PcmInt16DataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, channelIndex, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Create a new <see cref="PcmFloatDataSourceCommandVersion1"/>.
+ /// </summary>
+ /// <param name="voiceState">The <see cref="VoiceState"/> to generate the command from.</param>
+ /// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
+ /// <param name="outputBufferIndex">The output buffer index to use.</param>
+ /// <param name="channelIndex">The target channel index.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GeneratePcmFloatDataSourceVersion1(ref VoiceState voiceState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
+ {
+ PcmFloatDataSourceCommandVersion1 command = new PcmFloatDataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, channelIndex, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Create a new <see cref="AdpcmDataSourceCommandVersion1"/>.
+ /// </summary>
+ /// <param name="voiceState">The <see cref="VoiceState"/> to generate the command from.</param>
+ /// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
+ /// <param name="outputBufferIndex">The output buffer index to use.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateAdpcmDataSourceVersion1(ref VoiceState voiceState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, int nodeId)
+ {
+ AdpcmDataSourceCommandVersion1 command = new AdpcmDataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Create a new <see cref="BiquadFilterCommand"/>.
+ /// </summary>
+ /// <param name="baseIndex">The base index of the input and output buffer.</param>
+ /// <param name="filter">The biquad filter parameter.</param>
+ /// <param name="biquadFilterStateMemory">The biquad state.</param>
+ /// <param name="inputBufferOffset">The input buffer offset.</param>
+ /// <param name="outputBufferOffset">The output buffer offset.</param>
+ /// <param name="needInitialization">Set to true if the biquad filter state needs to be initialized.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateBiquadFilter(int baseIndex, ref BiquadFilterParameter filter, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId)
+ {
+ BiquadFilterCommand command = new BiquadFilterCommand(baseIndex, ref filter, biquadFilterStateMemory, inputBufferOffset, outputBufferOffset, needInitialization, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Generate a new <see cref="MixRampGroupedCommand"/>.
+ /// </summary>
+ /// <param name="mixBufferCount">The mix buffer count.</param>
+ /// <param name="inputBufferIndex">The base input index.</param>
+ /// <param name="outputBufferIndex">The base output index.</param>
+ /// <param name="previousVolume">The previous volume.</param>
+ /// <param name="volume">The new volume.</param>
+ /// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> previousVolume, Span<float> volume, Memory<VoiceUpdateState> state, int nodeId)
+ {
+ MixRampGroupedCommand command = new MixRampGroupedCommand(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Generate a new <see cref="MixRampCommand"/>.
+ /// </summary>
+ /// <param name="previousVolume">The previous volume.</param>
+ /// <param name="volume">The new volume.</param>
+ /// <param name="inputBufferIndex">The input buffer index.</param>
+ /// <param name="outputBufferIndex">The output buffer index.</param>
+ /// <param name="lastSampleIndex">The index in the <see cref="VoiceUpdateState.LastSamples"/> array to store the ramped sample.</param>
+ /// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateMixRamp(float previousVolume, float volume, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory<VoiceUpdateState> state, int nodeId)
+ {
+ MixRampCommand command = new MixRampCommand(previousVolume, volume, inputBufferIndex, outputBufferIndex, lastSampleIndex, state, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Generate a new <see cref="DepopForMixBuffersCommand"/>.
+ /// </summary>
+ /// <param name="depopBuffer">The depop buffer.</param>
+ /// <param name="bufferOffset">The target buffer offset.</param>
+ /// <param name="bufferCount">The buffer count.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ /// <param name="sampleRate">The target sample rate in use.</param>
+ public void GenerateDepopForMixBuffersCommand(Memory<float> depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
+ {
+ DepopForMixBuffersCommand command = new DepopForMixBuffersCommand(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Generate a new <see cref="CopyMixBufferCommand"/>.
+ /// </summary>
+ /// <param name="inputBufferIndex">The input buffer index.</param>
+ /// <param name="outputBufferIndex">The output buffer index.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateCopyMixBuffer(uint inputBufferIndex, uint outputBufferIndex, int nodeId)
+ {
+ CopyMixBufferCommand command = new CopyMixBufferCommand(inputBufferIndex, outputBufferIndex, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Generate a new <see cref="MixCommand"/>.
+ /// </summary>
+ /// <param name="inputBufferIndex">The input buffer index.</param>
+ /// <param name="outputBufferIndex">The output buffer index.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ /// <param name="volume">The mix volume.</param>
+ public void GenerateMix(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume)
+ {
+ MixCommand command = new MixCommand(inputBufferIndex, outputBufferIndex, nodeId, volume);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Generate a new <see cref="ReverbCommand"/>.
+ /// </summary>
+ /// <param name="bufferOffset">The target buffer offset.</param>
+ /// <param name="parameter">The reverb parameter.</param>
+ /// <param name="state">The reverb state.</param>
+ /// <param name="isEnabled">Set to true if the effect should be active.</param>
+ /// <param name="workBuffer">The work buffer to use for processing.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ /// <param name="isLongSizePreDelaySupported">If set to true, the long size pre-delay is supported.</param>
+ public void GenerateReverbEffect(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool isLongSizePreDelaySupported)
+ {
+ if (parameter.IsChannelCountValid())
+ {
+ ReverbCommand command = new ReverbCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, isLongSizePreDelaySupported);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+ }
+
+ /// <summary>
+ /// Generate a new <see cref="Reverb3dCommand"/>.
+ /// </summary>
+ /// <param name="bufferOffset">The target buffer offset.</param>
+ /// <param name="parameter">The reverb 3d parameter.</param>
+ /// <param name="state">The reverb 3d state.</param>
+ /// <param name="isEnabled">Set to true if the effect should be active.</param>
+ /// <param name="workBuffer">The work buffer to use for processing.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateReverb3dEffect(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, CpuAddress workBuffer, int nodeId)
+ {
+ if (parameter.IsChannelCountValid())
+ {
+ Reverb3dCommand command = new Reverb3dCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+ }
+
+
+ /// <summary>
+ /// Generate a new <see cref="DelayCommand"/>.
+ /// </summary>
+ /// <param name="bufferOffset">The target buffer offset.</param>
+ /// <param name="parameter">The delay parameter.</param>
+ /// <param name="state">The delay state.</param>
+ /// <param name="isEnabled">Set to true if the effect should be active.</param>
+ /// <param name="workBuffer">The work buffer to use for processing.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateDelayEffect(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, CpuAddress workBuffer, int nodeId)
+ {
+ if (parameter.IsChannelCountValid())
+ {
+ DelayCommand command = new DelayCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+ }
+
+ /// <summary>
+ /// Generate a new <see cref="AuxiliaryBufferCommand"/>.
+ /// </summary>
+ /// <param name="bufferOffset">The target buffer offset.</param>
+ /// <param name="inputBufferOffset">The input buffer offset.</param>
+ /// <param name="outputBufferOffset">The output buffer offset.</param>
+ /// <param name="state">The aux state.</param>
+ /// <param name="isEnabled">Set to true if the effect should be active.</param>
+ /// <param name="countMax">The limit of the circular buffer.</param>
+ /// <param name="outputBuffer">The guest address of the output buffer.</param>
+ /// <param name="inputBuffer">The guest address of the input buffer.</param>
+ /// <param name="updateCount">The count to add on the offset after write/read operations.</param>
+ /// <param name="writeOffset">The write offset.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateAuxEffect(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset, ref AuxiliaryBufferAddresses state, bool isEnabled, uint countMax, CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId)
+ {
+ if (state.SendBufferInfoBase != 0 && state.ReturnBufferInfoBase != 0)
+ {
+ AuxiliaryBufferCommand command = new AuxiliaryBufferCommand(bufferOffset, inputBufferOffset, outputBufferOffset, ref state, isEnabled, countMax, outputBuffer, inputBuffer, updateCount, writeOffset, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+ }
+
+ /// <summary>
+ /// Generate a new <see cref="VolumeCommand"/>.
+ /// </summary>
+ /// <param name="volume">The target volume to apply.</param>
+ /// <param name="bufferOffset">The offset of the mix buffer.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateVolume(float volume, uint bufferOffset, int nodeId)
+ {
+ VolumeCommand command = new VolumeCommand(volume, bufferOffset, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Create a new <see cref="CircularBufferSinkCommand"/>.
+ /// </summary>
+ /// <param name="bufferOffset">The offset of the mix buffer.</param>
+ /// <param name="sink">The <see cref="BaseSink"/> of the circular buffer.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateCircularBuffer(uint bufferOffset, CircularBufferSink sink, int nodeId)
+ {
+ CircularBufferSinkCommand command = new CircularBufferSinkCommand(bufferOffset, ref sink.Parameter, ref sink.CircularBufferAddressInfo, sink.CurrentWriteOffset, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Create a new <see cref="DownMixSurroundToStereoCommand"/>.
+ /// </summary>
+ /// <param name="bufferOffset">The offset of the mix buffer.</param>
+ /// <param name="inputBufferOffset">The input buffer offset.</param>
+ /// <param name="outputBufferOffset">The output buffer offset.</param>
+ /// <param name="downMixParameter">The downmixer parameters to use.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateDownMixSurroundToStereo(uint bufferOffset, Span<byte> inputBufferOffset, Span<byte> outputBufferOffset, ReadOnlySpan<float> downMixParameter, int nodeId)
+ {
+ DownMixSurroundToStereoCommand command = new DownMixSurroundToStereoCommand(bufferOffset, inputBufferOffset, outputBufferOffset, downMixParameter, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Create a new <see cref="UpsampleCommand"/>.
+ /// </summary>
+ /// <param name="bufferOffset">The offset of the mix buffer.</param>
+ /// <param name="upsampler">The <see cref="UpsamplerState"/> associated.</param>
+ /// <param name="inputCount">The total input count.</param>
+ /// <param name="inputBufferOffset">The input buffer mix offset.</param>
+ /// <param name="bufferCountPerSample">The buffer count per sample.</param>
+ /// <param name="sampleCount">The source sample count.</param>
+ /// <param name="sampleRate">The source sample rate.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateUpsample(uint bufferOffset, UpsamplerState upsampler, uint inputCount, Span<byte> inputBufferOffset, uint bufferCountPerSample, uint sampleCount, uint sampleRate, int nodeId)
+ {
+ UpsampleCommand command = new UpsampleCommand(bufferOffset, upsampler, inputCount, inputBufferOffset, bufferCountPerSample, sampleCount, sampleRate, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ /// <summary>
+ /// Create a new <see cref="DeviceSinkCommand"/>.
+ /// </summary>
+ /// <param name="bufferOffset">The offset of the mix buffer.</param>
+ /// <param name="sink">The <see cref="BaseSink"/> of the device sink.</param>
+ /// <param name="sessionId">The current audio renderer session id.</param>
+ /// <param name="buffer">The mix buffer in use.</param>
+ /// <param name="nodeId">The node id associated to this command.</param>
+ public void GenerateDeviceSink(uint bufferOffset, DeviceSink sink, int sessionId, Memory<float> buffer, int nodeId)
+ {
+ DeviceSinkCommand command = new DeviceSinkCommand(bufferOffset, sink, sessionId, buffer, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
new file mode 100644
index 00000000..d2499c1d
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
@@ -0,0 +1,940 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.Command;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Server.Effect;
+using Ryujinx.Audio.Renderer.Server.Mix;
+using Ryujinx.Audio.Renderer.Server.Performance;
+using Ryujinx.Audio.Renderer.Server.Sink;
+using Ryujinx.Audio.Renderer.Server.Splitter;
+using Ryujinx.Audio.Renderer.Server.Voice;
+using Ryujinx.Audio.Renderer.Utils;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Server
+{
+ public class CommandGenerator
+ {
+ private CommandBuffer _commandBuffer;
+ private RendererSystemContext _rendererContext;
+ private VoiceContext _voiceContext;
+ private MixContext _mixContext;
+ private EffectContext _effectContext;
+ private SinkContext _sinkContext;
+ private SplitterContext _splitterContext;
+ private PerformanceManager _performanceManager;
+
+ public CommandGenerator(CommandBuffer commandBuffer, RendererSystemContext rendererContext, VoiceContext voiceContext, MixContext mixContext, EffectContext effectContext, SinkContext sinkContext, SplitterContext splitterContext, PerformanceManager performanceManager)
+ {
+ _commandBuffer = commandBuffer;
+ _rendererContext = rendererContext;
+ _voiceContext = voiceContext;
+ _mixContext = mixContext;
+ _effectContext = effectContext;
+ _sinkContext = sinkContext;
+ _splitterContext = splitterContext;
+ _performanceManager = performanceManager;
+
+ _commandBuffer.GenerateClearMixBuffer(Constants.InvalidNodeId);
+ }
+
+ private void GenerateDataSource(ref VoiceState voiceState, Memory<VoiceUpdateState> dspState, int channelIndex)
+ {
+ if (voiceState.MixId != Constants.UnusedMixId)
+ {
+ ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
+
+ _commandBuffer.GenerateDepopPrepare(dspState,
+ _rendererContext.DepopBuffer,
+ mix.BufferCount,
+ mix.BufferOffset,
+ voiceState.NodeId,
+ voiceState.WasPlaying);
+ }
+ else if (voiceState.SplitterId != Constants.UnusedSplitterId)
+ {
+ int destinationId = 0;
+
+ while (true)
+ {
+ Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
+
+ if (destinationSpan.IsEmpty)
+ {
+ break;
+ }
+
+ ref SplitterDestination destination = ref destinationSpan[0];
+
+ if (destination.IsConfigured())
+ {
+ int mixId = destination.DestinationId;
+
+ if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt)
+ {
+ ref MixState mix = ref _mixContext.GetState(mixId);
+
+ _commandBuffer.GenerateDepopPrepare(dspState,
+ _rendererContext.DepopBuffer,
+ mix.BufferCount,
+ mix.BufferOffset,
+ voiceState.NodeId,
+ voiceState.WasPlaying);
+
+ destination.MarkAsNeedToUpdateInternalState();
+ }
+ }
+ }
+ }
+
+ if (!voiceState.WasPlaying)
+ {
+ Debug.Assert(voiceState.SampleFormat != SampleFormat.Adpcm || channelIndex == 0);
+
+ if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported())
+ {
+ _commandBuffer.GenerateDataSourceVersion2(ref voiceState,
+ dspState,
+ (ushort)_rendererContext.MixBufferCount,
+ (ushort)channelIndex,
+ voiceState.NodeId);
+ }
+ else
+ {
+ switch (voiceState.SampleFormat)
+ {
+ case SampleFormat.PcmInt16:
+ _commandBuffer.GeneratePcmInt16DataSourceVersion1(ref voiceState,
+ dspState,
+ (ushort)_rendererContext.MixBufferCount,
+ (ushort)channelIndex,
+ voiceState.NodeId);
+ break;
+ case SampleFormat.PcmFloat:
+ _commandBuffer.GeneratePcmFloatDataSourceVersion1(ref voiceState,
+ dspState,
+ (ushort)_rendererContext.MixBufferCount,
+ (ushort)channelIndex,
+ voiceState.NodeId);
+ break;
+ case SampleFormat.Adpcm:
+ _commandBuffer.GenerateAdpcmDataSourceVersion1(ref voiceState,
+ dspState,
+ (ushort)_rendererContext.MixBufferCount,
+ voiceState.NodeId);
+ break;
+ default:
+ throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}");
+ }
+ }
+ }
+ }
+
+ private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory<VoiceUpdateState> state, int baseIndex, int bufferOffset, int nodeId)
+ {
+ for (int i = 0; i < voiceState.BiquadFilters.Length; i++)
+ {
+ ref BiquadFilterParameter filter = ref voiceState.BiquadFilters[i];
+
+ if (filter.Enable)
+ {
+ Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount);
+
+ Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
+
+ _commandBuffer.GenerateBiquadFilter(baseIndex,
+ ref filter,
+ stateMemory.Slice(i, 1),
+ bufferOffset,
+ bufferOffset,
+ !voiceState.BiquadFilterNeedInitialization[i],
+ nodeId);
+ }
+ }
+ }
+
+ private void GenerateVoiceMix(Span<float> mixVolumes, Span<float> previousMixVolumes, Memory<VoiceUpdateState> state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId)
+ {
+ if (bufferCount > Constants.VoiceChannelCountMax)
+ {
+ _commandBuffer.GenerateMixRampGrouped(bufferCount,
+ bufferIndex,
+ bufferOffset,
+ previousMixVolumes,
+ mixVolumes,
+ state,
+ nodeId);
+ }
+ else
+ {
+ for (int i = 0; i < bufferCount; i++)
+ {
+ float previousMixVolume = previousMixVolumes[i];
+ float mixVolume = mixVolumes[i];
+
+ if (mixVolume != 0.0f || previousMixVolume != 0.0f)
+ {
+ _commandBuffer.GenerateMixRamp(previousMixVolume,
+ mixVolume,
+ bufferIndex,
+ bufferOffset + (uint)i,
+ i,
+ state,
+ nodeId);
+ }
+ }
+ }
+ }
+
+ private void GenerateVoice(ref VoiceState voiceState)
+ {
+ int nodeId = voiceState.NodeId;
+ uint channelsCount = voiceState.ChannelsCount;
+
+ for (int channelIndex = 0; channelIndex < channelsCount; channelIndex++)
+ {
+ Memory<VoiceUpdateState> dspStateMemory = _voiceContext.GetUpdateStateForDsp(voiceState.ChannelResourceIds[channelIndex]);
+
+ ref VoiceChannelResource channelResource = ref _voiceContext.GetChannelResource(voiceState.ChannelResourceIds[channelIndex]);
+
+ PerformanceDetailType dataSourceDetailType = PerformanceDetailType.Adpcm;
+
+ if (voiceState.SampleFormat == SampleFormat.PcmInt16)
+ {
+ dataSourceDetailType = PerformanceDetailType.PcmInt16;
+ }
+ else if (voiceState.SampleFormat == SampleFormat.PcmFloat)
+ {
+ dataSourceDetailType = PerformanceDetailType.PcmFloat;
+ }
+
+ bool performanceInitialized = false;
+
+ PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
+
+ if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, dataSourceDetailType, PerformanceEntryType.Voice, nodeId))
+ {
+ performanceInitialized = true;
+
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
+ }
+
+ GenerateDataSource(ref voiceState, dspStateMemory, channelIndex);
+
+ if (performanceInitialized)
+ {
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
+ }
+
+ if (voiceState.WasPlaying)
+ {
+ voiceState.PreviousVolume = 0.0f;
+ }
+ else if (voiceState.HasAnyDestination())
+ {
+ performanceInitialized = false;
+
+ if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.BiquadFilter, PerformanceEntryType.Voice, nodeId))
+ {
+ performanceInitialized = true;
+
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
+ }
+
+ GenerateBiquadFilterForVoice(ref voiceState, dspStateMemory, (int)_rendererContext.MixBufferCount, channelIndex, nodeId);
+
+ if (performanceInitialized)
+ {
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
+ }
+
+ performanceInitialized = false;
+
+ if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.Voice, nodeId))
+ {
+ performanceInitialized = true;
+
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
+ }
+
+ _commandBuffer.GenerateVolumeRamp(voiceState.PreviousVolume,
+ voiceState.Volume,
+ _rendererContext.MixBufferCount + (uint)channelIndex,
+ nodeId);
+
+ if (performanceInitialized)
+ {
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
+ }
+
+ voiceState.PreviousVolume = voiceState.Volume;
+
+ if (voiceState.MixId == Constants.UnusedMixId)
+ {
+ if (voiceState.SplitterId != Constants.UnusedSplitterId)
+ {
+ int destinationId = channelIndex;
+
+ while (true)
+ {
+ Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
+
+ if (destinationSpan.IsEmpty)
+ {
+ break;
+ }
+
+ ref SplitterDestination destination = ref destinationSpan[0];
+
+ destinationId += (int)channelsCount;
+
+ if (destination.IsConfigured())
+ {
+ int mixId = destination.DestinationId;
+
+ if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt)
+ {
+ ref MixState mix = ref _mixContext.GetState(mixId);
+
+ GenerateVoiceMix(destination.MixBufferVolume,
+ destination.PreviousMixBufferVolume,
+ dspStateMemory,
+ mix.BufferOffset,
+ mix.BufferCount,
+ _rendererContext.MixBufferCount + (uint)channelIndex,
+ nodeId);
+
+ destination.MarkAsNeedToUpdateInternalState();
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
+
+ performanceInitialized = false;
+
+ if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.Voice, nodeId))
+ {
+ performanceInitialized = true;
+
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
+ }
+
+ GenerateVoiceMix(channelResource.Mix.ToSpan(),
+ channelResource.PreviousMix.ToSpan(),
+ dspStateMemory,
+ mix.BufferOffset,
+ mix.BufferCount,
+ _rendererContext.MixBufferCount + (uint)channelIndex,
+ nodeId);
+
+ if (performanceInitialized)
+ {
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
+ }
+
+ channelResource.UpdateState();
+ }
+
+ for (int i = 0; i < voiceState.BiquadFilterNeedInitialization.Length; i++)
+ {
+ voiceState.BiquadFilterNeedInitialization[i] = voiceState.BiquadFilters[i].Enable;
+ }
+ }
+ }
+ }
+
+ public void GenerateVoices()
+ {
+ for (int i = 0; i < _voiceContext.GetCount(); i++)
+ {
+ ref VoiceState sortedState = ref _voiceContext.GetSortedState(i);
+
+ if (!sortedState.ShouldSkip() && sortedState.UpdateForCommandGeneration(_voiceContext))
+ {
+ int nodeId = sortedState.NodeId;
+
+ PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
+
+ bool performanceInitialized = false;
+
+ if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Voice, nodeId))
+ {
+ performanceInitialized = true;
+
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
+ }
+
+ GenerateVoice(ref sortedState);
+
+ if (performanceInitialized)
+ {
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
+ }
+ }
+ }
+
+ _splitterContext.UpdateInternalState();
+ }
+
+ public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId)
+ {
+ _commandBuffer.GeneratePerformance(ref performanceEntryAddresses, type, nodeId);
+ }
+
+ private void GenerateBufferMixerEffect(int bufferOffset, BufferMixEffect effect, int nodeId)
+ {
+ Debug.Assert(effect.Type == EffectType.BufferMix);
+
+ if (effect.IsEnabled)
+ {
+ for (int i = 0; i < effect.Parameter.MixesCount; i++)
+ {
+ if (effect.Parameter.Volumes[i] != 0.0f)
+ {
+ _commandBuffer.GenerateMix((uint)bufferOffset + effect.Parameter.Input[i],
+ (uint)bufferOffset + effect.Parameter.Output[i],
+ nodeId,
+ effect.Parameter.Volumes[i]);
+ }
+ }
+ }
+ }
+
+ private void GenerateAuxEffect(uint bufferOffset, AuxiliaryBufferEffect effect, int nodeId)
+ {
+ Debug.Assert(effect.Type == EffectType.AuxiliaryBuffer);
+
+ if (effect.IsEnabled)
+ {
+ effect.GetWorkBuffer(0);
+ effect.GetWorkBuffer(1);
+ }
+
+ if (effect.State.SendBufferInfoBase != 0 && effect.State.ReturnBufferInfoBase != 0)
+ {
+ int i = 0;
+ uint writeOffset = 0;
+ for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--)
+ {
+ uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount;
+
+ uint updateCount;
+
+ if ((channelIndex - 1) != 0)
+ {
+ updateCount = 0;
+ }
+ else
+ {
+ updateCount = newUpdateCount;
+ }
+
+ _commandBuffer.GenerateAuxEffect(bufferOffset,
+ effect.Parameter.Input[i],
+ effect.Parameter.Output[i],
+ ref effect.State,
+ effect.IsEnabled,
+ effect.Parameter.BufferStorageSize,
+ effect.State.SendBufferInfoBase,
+ effect.State.ReturnBufferInfoBase,
+ updateCount,
+ writeOffset,
+ nodeId);
+
+ writeOffset = newUpdateCount;
+
+ i++;
+ }
+ }
+ }
+
+ private void GenerateDelayEffect(uint bufferOffset, DelayEffect effect, int nodeId)
+ {
+ Debug.Assert(effect.Type == EffectType.Delay);
+
+ ulong workBuffer = effect.GetWorkBuffer(-1);
+
+ _commandBuffer.GenerateDelayEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId);
+ }
+
+ private void GenerateReverbEffect(uint bufferOffset, ReverbEffect effect, int nodeId, bool isLongSizePreDelaySupported)
+ {
+ Debug.Assert(effect.Type == EffectType.Reverb);
+
+ ulong workBuffer = effect.GetWorkBuffer(-1);
+
+ _commandBuffer.GenerateReverbEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, isLongSizePreDelaySupported);
+ }
+
+ private void GenerateReverb3dEffect(uint bufferOffset, Reverb3dEffect effect, int nodeId)
+ {
+ Debug.Assert(effect.Type == EffectType.Reverb3d);
+
+ ulong workBuffer = effect.GetWorkBuffer(-1);
+
+ _commandBuffer.GenerateReverb3dEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId);
+ }
+
+ private void GenerateBiquadFilterEffect(uint bufferOffset, BiquadFilterEffect effect, int nodeId)
+ {
+ Debug.Assert(effect.Type == EffectType.BiquadFilter);
+
+ if (effect.IsEnabled)
+ {
+ bool needInitialization = effect.Parameter.Status == UsageState.Invalid ||
+ (effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed());
+
+ BiquadFilterParameter parameter = new BiquadFilterParameter();
+
+ parameter.Enable = true;
+ effect.Parameter.Denominator.ToSpan().CopyTo(parameter.Denominator.ToSpan());
+ effect.Parameter.Numerator.ToSpan().CopyTo(parameter.Numerator.ToSpan());
+
+ for (int i = 0; i < effect.Parameter.ChannelCount; i++)
+ {
+ _commandBuffer.GenerateBiquadFilter((int)bufferOffset, ref parameter, effect.State.Slice(i, 1),
+ effect.Parameter.Input[i],
+ effect.Parameter.Output[i],
+ needInitialization,
+ nodeId);
+ }
+ }
+ else
+ {
+ for (int i = 0; i < effect.Parameter.ChannelCount; i++)
+ {
+ uint inputBufferIndex = bufferOffset + effect.Parameter.Input[i];
+ uint outputBufferIndex = bufferOffset + effect.Parameter.Output[i];
+
+ // If the input and output isn't the same, generate a command.
+ if (inputBufferIndex != outputBufferIndex)
+ {
+ _commandBuffer.GenerateCopyMixBuffer(inputBufferIndex, outputBufferIndex, nodeId);
+ }
+ }
+ }
+ }
+
+ private void GenerateEffect(ref MixState mix, BaseEffect effect)
+ {
+ int nodeId = mix.NodeId;
+
+ bool isFinalMix = mix.MixId == Constants.FinalMixId;
+
+ PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
+
+ bool performanceInitialized = false;
+
+ if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, effect.GetPerformanceDetailType(),
+ isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, nodeId))
+ {
+ performanceInitialized = true;
+
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
+ }
+
+ switch (effect.Type)
+ {
+ case EffectType.BufferMix:
+ GenerateBufferMixerEffect((int)mix.BufferOffset, (BufferMixEffect)effect, nodeId);
+ break;
+ case EffectType.AuxiliaryBuffer:
+ GenerateAuxEffect(mix.BufferOffset, (AuxiliaryBufferEffect)effect, nodeId);
+ break;
+ case EffectType.Delay:
+ GenerateDelayEffect(mix.BufferOffset, (DelayEffect)effect, nodeId);
+ break;
+ case EffectType.Reverb:
+ GenerateReverbEffect(mix.BufferOffset, (ReverbEffect)effect, nodeId, mix.IsLongSizePreDelaySupported);
+ break;
+ case EffectType.Reverb3d:
+ GenerateReverb3dEffect(mix.BufferOffset, (Reverb3dEffect)effect, nodeId);
+ break;
+ case EffectType.BiquadFilter:
+ GenerateBiquadFilterEffect(mix.BufferOffset, (BiquadFilterEffect)effect, nodeId);
+ break;
+ default:
+ throw new NotImplementedException($"Unsupported effect type {effect.Type}");
+ }
+
+ if (performanceInitialized)
+ {
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
+ }
+
+ effect.UpdateForCommandGeneration();
+ }
+
+ private void GenerateEffects(ref MixState mix)
+ {
+ ReadOnlySpan<int> effectProcessingOrderArray = mix.EffectProcessingOrderArray;
+
+ Debug.Assert(_effectContext.GetCount() == 0 || !effectProcessingOrderArray.IsEmpty);
+
+ for (int i = 0; i < _effectContext.GetCount(); i++)
+ {
+ int effectOrder = effectProcessingOrderArray[i];
+
+ if (effectOrder == Constants.InvalidProcessingOrder)
+ {
+ break;
+ }
+
+ // BaseEffect is a class, we don't need to pass it by ref
+ BaseEffect effect = _effectContext.GetEffect(effectOrder);
+
+ Debug.Assert(effect.Type != EffectType.Invalid);
+ Debug.Assert(effect.MixId == mix.MixId);
+
+ if (!effect.ShouldSkip())
+ {
+ GenerateEffect(ref mix, effect);
+ }
+ }
+ }
+
+ private void GenerateMix(ref MixState mix)
+ {
+ if (mix.HasAnyDestination())
+ {
+ Debug.Assert(mix.DestinationMixId != Constants.UnusedMixId || mix.DestinationSplitterId != Constants.UnusedSplitterId);
+
+ if (mix.DestinationMixId == Constants.UnusedMixId)
+ {
+ if (mix.DestinationSplitterId != Constants.UnusedSplitterId)
+ {
+ int destinationId = 0;
+
+ while (true)
+ {
+ int destinationIndex = destinationId++;
+
+ Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
+
+ if (destinationSpan.IsEmpty)
+ {
+ break;
+ }
+
+ ref SplitterDestination destination = ref destinationSpan[0];
+
+ if (destination.IsConfigured())
+ {
+ int mixId = destination.DestinationId;
+
+ if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt)
+ {
+ ref MixState destinationMix = ref _mixContext.GetState(mixId);
+
+ uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount);
+
+ for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
+ {
+ float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex);
+
+ if (volume != 0.0f)
+ {
+ _commandBuffer.GenerateMix(inputBufferIndex,
+ destinationMix.BufferOffset + bufferDestinationIndex,
+ mix.NodeId,
+ volume);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ ref MixState destinationMix = ref _mixContext.GetState(mix.DestinationMixId);
+
+ for (uint bufferIndex = 0; bufferIndex < mix.BufferCount; bufferIndex++)
+ {
+ for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
+ {
+ float volume = mix.Volume * mix.GetMixBufferVolume((int)bufferIndex, (int)bufferDestinationIndex);
+
+ if (volume != 0.0f)
+ {
+ _commandBuffer.GenerateMix(mix.BufferOffset + bufferIndex,
+ destinationMix.BufferOffset + bufferDestinationIndex,
+ mix.NodeId,
+ volume);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void GenerateSubMix(ref MixState subMix)
+ {
+ _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
+ subMix.BufferOffset,
+ subMix.BufferCount,
+ subMix.NodeId,
+ subMix.SampleRate);
+
+ GenerateEffects(ref subMix);
+
+ PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
+
+ int nodeId = subMix.NodeId;
+
+ bool performanceInitialized = false;
+
+ if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.SubMix, nodeId))
+ {
+ performanceInitialized = true;
+
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
+ }
+
+ GenerateMix(ref subMix);
+
+ if (performanceInitialized)
+ {
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
+ }
+ }
+
+ public void GenerateSubMixes()
+ {
+ for (int id = 0; id < _mixContext.GetCount(); id++)
+ {
+ ref MixState sortedState = ref _mixContext.GetSortedState(id);
+
+ if (sortedState.IsUsed && sortedState.MixId != Constants.FinalMixId)
+ {
+ int nodeId = sortedState.NodeId;
+
+ PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
+
+ bool performanceInitialized = false;
+
+ if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.SubMix, nodeId))
+ {
+ performanceInitialized = true;
+
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
+ }
+
+ GenerateSubMix(ref sortedState);
+
+ if (performanceInitialized)
+ {
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
+ }
+ }
+ }
+ }
+
+ private void GenerateFinalMix()
+ {
+ ref MixState finalMix = ref _mixContext.GetFinalState();
+
+ _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
+ finalMix.BufferOffset,
+ finalMix.BufferCount,
+ finalMix.NodeId,
+ finalMix.SampleRate);
+
+ GenerateEffects(ref finalMix);
+
+ PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
+
+ int nodeId = finalMix.NodeId;
+
+ bool performanceInitialized = false;
+
+ if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.FinalMix, nodeId))
+ {
+ performanceInitialized = true;
+
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
+ }
+
+ // Only generate volume command if the volume isn't 100%.
+ if (finalMix.Volume != 1.0f)
+ {
+ for (uint bufferIndex = 0; bufferIndex < finalMix.BufferCount; bufferIndex++)
+ {
+ bool performanceSubInitialized = false;
+
+ if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.FinalMix, nodeId))
+ {
+ performanceSubInitialized = true;
+
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
+ }
+
+ _commandBuffer.GenerateVolume(finalMix.Volume,
+ finalMix.BufferOffset + bufferIndex,
+ nodeId);
+
+ if (performanceSubInitialized)
+ {
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
+ }
+ }
+ }
+
+ if (performanceInitialized)
+ {
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
+ }
+ }
+
+ public void GenerateFinalMixes()
+ {
+ int nodeId = _mixContext.GetFinalState().NodeId;
+
+ PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
+
+ bool performanceInitialized = false;
+
+ if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.FinalMix, nodeId))
+ {
+ performanceInitialized = true;
+
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
+ }
+
+ GenerateFinalMix();
+
+ if (performanceInitialized)
+ {
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
+ }
+ }
+
+ private void GenerateCircularBuffer(CircularBufferSink sink, ref MixState finalMix)
+ {
+ _commandBuffer.GenerateCircularBuffer(finalMix.BufferOffset, sink, Constants.InvalidNodeId);
+ }
+
+ private void GenerateDevice(DeviceSink sink, ref MixState finalMix)
+ {
+ if (_commandBuffer.CommandList.SampleRate != 48000 && sink.UpsamplerState == null)
+ {
+ sink.UpsamplerState = _rendererContext.UpsamplerManager.Allocate();
+ }
+
+ bool useCustomDownMixingCommand = _rendererContext.ChannelCount == 2 && sink.Parameter.DownMixParameterEnabled;
+
+ if (useCustomDownMixingCommand)
+ {
+ _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
+ sink.Parameter.Input.ToSpan(),
+ sink.Parameter.Input.ToSpan(),
+ sink.DownMixCoefficients,
+ Constants.InvalidNodeId);
+ }
+ // NOTE: We do the downmixing at the DSP level as it's easier that way.
+ else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6)
+ {
+ _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
+ sink.Parameter.Input.ToSpan(),
+ sink.Parameter.Input.ToSpan(),
+ Constants.DefaultSurroundToStereoCoefficients,
+ Constants.InvalidNodeId);
+ }
+
+ CommandList commandList = _commandBuffer.CommandList;
+
+ if (sink.UpsamplerState != null)
+ {
+ _commandBuffer.GenerateUpsample(finalMix.BufferOffset,
+ sink.UpsamplerState,
+ sink.Parameter.InputCount,
+ sink.Parameter.Input.ToSpan(),
+ commandList.BufferCount,
+ commandList.SampleCount,
+ commandList.SampleRate,
+ Constants.InvalidNodeId);
+ }
+
+ _commandBuffer.GenerateDeviceSink(finalMix.BufferOffset,
+ sink,
+ _rendererContext.SessionId,
+ commandList.Buffers,
+ Constants.InvalidNodeId);
+ }
+
+ private void GenerateSink(BaseSink sink, ref MixState finalMix)
+ {
+ bool performanceInitialized = false;
+
+ PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses();
+
+ if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Sink, sink.NodeId))
+ {
+ performanceInitialized = true;
+
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, sink.NodeId);
+ }
+
+ if (!sink.ShouldSkip)
+ {
+ switch (sink.Type)
+ {
+ case SinkType.CircularBuffer:
+ GenerateCircularBuffer((CircularBufferSink)sink, ref finalMix);
+ break;
+ case SinkType.Device:
+ GenerateDevice((DeviceSink)sink, ref finalMix);
+ break;
+ default:
+ throw new NotImplementedException($"Unsupported sink type {sink.Type}");
+ }
+
+ sink.UpdateForCommandGeneration();
+ }
+
+ if (performanceInitialized)
+ {
+ GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, sink.NodeId);
+ }
+ }
+
+ public void GenerateSinks()
+ {
+ ref MixState finalMix = ref _mixContext.GetFinalState();
+
+ for (int i = 0; i < _sinkContext.GetCount(); i++)
+ {
+ // BaseSink is a class, we don't need to pass it by ref
+ BaseSink sink = _sinkContext.GetSink(i);
+
+ if (sink.IsUsed)
+ {
+ GenerateSink(sink, ref finalMix);
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
new file mode 100644
index 00000000..81d3b57b
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
@@ -0,0 +1,180 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.Command;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Server
+{
+ /// <summary>
+ /// <see cref="ICommandProcessingTimeEstimator"/> version 1.
+ /// </summary>
+ public class CommandProcessingTimeEstimatorVersion1 : ICommandProcessingTimeEstimator
+ {
+ private uint _sampleCount;
+ private uint _bufferCount;
+
+ public CommandProcessingTimeEstimatorVersion1(uint sampleCount, uint bufferCount)
+ {
+ _sampleCount = sampleCount;
+ _bufferCount = bufferCount;
+ }
+
+ public uint Estimate(PerformanceCommand command)
+ {
+ return 1454;
+ }
+
+ public uint Estimate(ClearMixBufferCommand command)
+ {
+ return (uint)(_sampleCount * 0.83f * _bufferCount * 1.2f);
+ }
+
+ public uint Estimate(BiquadFilterCommand command)
+ {
+ return (uint)(_sampleCount * 58.0f * 1.2f);
+ }
+
+ public uint Estimate(MixRampGroupedCommand command)
+ {
+ int volumeCount = 0;
+
+ for (int i = 0; i < command.MixBufferCount; i++)
+ {
+ if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f)
+ {
+ volumeCount++;
+ }
+ }
+
+ return (uint)(_sampleCount * 14.4f * 1.2f * volumeCount);
+ }
+
+ public uint Estimate(MixRampCommand command)
+ {
+ return (uint)(_sampleCount * 14.4f * 1.2f);
+ }
+
+ public uint Estimate(DepopPrepareCommand command)
+ {
+ return 1080;
+ }
+
+ public uint Estimate(VolumeRampCommand command)
+ {
+ return (uint)(_sampleCount * 9.8f * 1.2f);
+ }
+
+ public uint Estimate(PcmInt16DataSourceCommandVersion1 command)
+ {
+ return (uint)(command.Pitch * 0.25f * 1.2f);
+ }
+
+ public uint Estimate(AdpcmDataSourceCommandVersion1 command)
+ {
+ return (uint)(command.Pitch * 0.46f * 1.2f);
+ }
+
+ public uint Estimate(DepopForMixBuffersCommand command)
+ {
+ return (uint)(_sampleCount * 8.9f * command.MixBufferCount);
+ }
+
+ public uint Estimate(CopyMixBufferCommand command)
+ {
+ // NOTE: Nintendo returns 0 here for some reasons even if it will generate a command like that on version 1.. maybe a mistake?
+ return 0;
+ }
+
+ public uint Estimate(MixCommand command)
+ {
+ return (uint)(_sampleCount * 10.0f * 1.2f);
+ }
+
+ public uint Estimate(DelayCommand command)
+ {
+ return (uint)(_sampleCount * command.Parameter.ChannelCount * 202.5f);
+ }
+
+ public uint Estimate(ReverbCommand command)
+ {
+ Debug.Assert(command.Parameter.IsChannelCountValid());
+
+ if (command.Enabled)
+ {
+ return (uint)(750 * _sampleCount * command.Parameter.ChannelCount * 1.2f);
+ }
+
+ return 0;
+ }
+
+ public uint Estimate(Reverb3dCommand command)
+ {
+ if (command.Enabled)
+ {
+ return (uint)(530 * _sampleCount * command.Parameter.ChannelCount * 1.2f);
+ }
+
+ return 0;
+ }
+
+ public uint Estimate(AuxiliaryBufferCommand command)
+ {
+ if (command.Enabled)
+ {
+ return 15956;
+ }
+
+ return 3765;
+ }
+
+ public uint Estimate(VolumeCommand command)
+ {
+ return (uint)(_sampleCount * 8.8f * 1.2f);
+ }
+
+ public uint Estimate(CircularBufferSinkCommand command)
+ {
+ return 55;
+ }
+
+ public uint Estimate(DownMixSurroundToStereoCommand command)
+ {
+ return 16108;
+ }
+
+ public uint Estimate(UpsampleCommand command)
+ {
+ return 357915;
+ }
+
+ public uint Estimate(DeviceSinkCommand command)
+ {
+ return 10042;
+ }
+
+ public uint Estimate(PcmFloatDataSourceCommandVersion1 command)
+ {
+ return 0;
+ }
+
+ public uint Estimate(DataSourceVersion2Command command)
+ {
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
new file mode 100644
index 00000000..0f4fe3c2
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
@@ -0,0 +1,544 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.Command;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Server
+{
+ /// <summary>
+ /// <see cref="ICommandProcessingTimeEstimator"/> version 2. (added with REV5)
+ /// </summary>
+ public class CommandProcessingTimeEstimatorVersion2 : ICommandProcessingTimeEstimator
+ {
+ private uint _sampleCount;
+ private uint _bufferCount;
+
+ public CommandProcessingTimeEstimatorVersion2(uint sampleCount, uint bufferCount)
+ {
+ _sampleCount = sampleCount;
+ _bufferCount = bufferCount;
+ }
+
+ public uint Estimate(PerformanceCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)489.35f;
+ }
+
+ return (uint)491.18f;
+ }
+
+ public uint Estimate(ClearMixBufferCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ float costPerBuffer = 668.8f;
+ float baseCost = 193.2f;
+
+ if (_sampleCount == 160)
+ {
+ costPerBuffer = 260.4f;
+ baseCost = 139.65f;
+ }
+
+ return (uint)(baseCost + costPerBuffer * _bufferCount);
+ }
+
+ public uint Estimate(BiquadFilterCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)4813.2f;
+ }
+
+ return (uint)6915.4f;
+ }
+
+ public uint Estimate(MixRampGroupedCommand command)
+ {
+ const float costPerSample = 7.245f;
+
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ int volumeCount = 0;
+
+ for (int i = 0; i < command.MixBufferCount; i++)
+ {
+ if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f)
+ {
+ volumeCount++;
+ }
+ }
+
+ return (uint)(_sampleCount * costPerSample * volumeCount);
+ }
+
+ public uint Estimate(MixRampCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)1859.0f;
+ }
+
+ return (uint)2286.1f;
+ }
+
+ public uint Estimate(DepopPrepareCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)306.62f;
+ }
+
+ return (uint)293.22f;
+ }
+
+ public uint Estimate(VolumeRampCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)1403.9f;
+ }
+
+ return (uint)1884.3f;
+ }
+
+ public uint Estimate(PcmInt16DataSourceCommandVersion1 command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ float costPerSample = 1195.5f;
+ float baseCost = 7797.0f;
+
+ if (_sampleCount == 160)
+ {
+ costPerSample = 749.27f;
+ baseCost = 6138.9f;
+ }
+
+ return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f))));
+ }
+
+ public uint Estimate(AdpcmDataSourceCommandVersion1 command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ float costPerSample = 3564.1f;
+ float baseCost = 6225.5f;
+
+ if (_sampleCount == 160)
+ {
+ costPerSample = 2125.6f;
+ baseCost = 9039.5f;
+ }
+
+ return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f))));
+ }
+
+ public uint Estimate(DepopForMixBuffersCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)762.96f;
+ }
+
+ return (uint)726.96f;
+ }
+
+ public uint Estimate(CopyMixBufferCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)836.32f;
+ }
+
+ return (uint)1000.9f;
+ }
+
+ public uint Estimate(MixCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)1342.2f;
+ }
+
+ return (uint)1833.2f;
+ }
+
+ public uint Estimate(DelayCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)41636.0f;
+ case 2:
+ return (uint)97861.0f;
+ case 4:
+ return (uint)192520.0f;
+ case 6:
+ return (uint)301760.0f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)578.53f;
+ case 2:
+ return (uint)663.06f;
+ case 4:
+ return (uint)703.98f;
+ case 6:
+ return (uint)760.03f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+
+ }
+
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)8770.3f;
+ case 2:
+ return (uint)25741.0f;
+ case 4:
+ return (uint)47551.0f;
+ case 6:
+ return (uint)81629.0f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)521.28f;
+ case 2:
+ return (uint)585.4f;
+ case 4:
+ return (uint)629.88f;
+ case 6:
+ return (uint)713.57f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ }
+
+ public uint Estimate(ReverbCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)97192.0f;
+ case 2:
+ return (uint)103280.0f;
+ case 4:
+ return (uint)109580.0f;
+ case 6:
+ return (uint)115070.0f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)492.01f;
+ case 2:
+ return (uint)554.46f;
+ case 4:
+ return (uint)595.86f;
+ case 6:
+ return (uint)656.62f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+
+ }
+
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)136460.0f;
+ case 2:
+ return (uint)145750.0f;
+ case 4:
+ return (uint)154800.0f;
+ case 6:
+ return (uint)161970.0f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)495.79f;
+ case 2:
+ return (uint)527.16f;
+ case 4:
+ return (uint)598.75f;
+ case 6:
+ return (uint)666.03f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ }
+
+ public uint Estimate(Reverb3dCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)138840.0f;
+ case 2:
+ return (uint)135430.0f;
+ case 4:
+ return (uint)199180.0f;
+ case 6:
+ return (uint)247350.0f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)718.7f;
+ case 2:
+ return (uint)751.3f;
+ case 4:
+ return (uint)797.46f;
+ case 6:
+ return (uint)867.43f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ }
+
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)199950.0f;
+ case 2:
+ return (uint)195200.0f;
+ case 4:
+ return (uint)290580.0f;
+ case 6:
+ return (uint)363490.0f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)534.24f;
+ case 2:
+ return (uint)570.87f;
+ case 4:
+ return (uint)660.93f;
+ case 6:
+ return (uint)694.6f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ }
+
+ public uint Estimate(AuxiliaryBufferCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ if (command.Enabled)
+ {
+ return (uint)7177.9f;
+ }
+
+ return (uint)489.16f;
+ }
+
+ if (command.Enabled)
+ {
+ return (uint)9499.8f;
+ }
+
+ return (uint)485.56f;
+ }
+
+ public uint Estimate(VolumeCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)1280.3f;
+ }
+
+ return (uint)1737.8f;
+ }
+
+ public uint Estimate(CircularBufferSinkCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ float costPerBuffer = 1726.0f;
+ float baseCost = 1369.7f;
+
+ if (_sampleCount == 160)
+ {
+ costPerBuffer = 853.63f;
+ baseCost = 1284.5f;
+ }
+
+ return (uint)(baseCost + costPerBuffer * command.InputCount);
+ }
+
+ public uint Estimate(DownMixSurroundToStereoCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)10009.0f;
+ }
+
+ return (uint)14577.0f;
+ }
+
+ public uint Estimate(UpsampleCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)292000.0f;
+ }
+
+ return (uint)0.0f;
+ }
+
+ public uint Estimate(DeviceSinkCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+ Debug.Assert(command.InputCount == 2 || command.InputCount == 6);
+
+ if (command.InputCount == 2)
+ {
+ if (_sampleCount == 160)
+ {
+ return (uint)9261.5f;
+ }
+
+ return (uint)9336.1f;
+ }
+
+ if (_sampleCount == 160)
+ {
+ return (uint)9111.8f;
+ }
+
+ return (uint)9566.7f;
+ }
+
+ public uint Estimate(PcmFloatDataSourceCommandVersion1 command)
+ {
+ // NOTE: This was added between REV7 and REV8 and for some reasons the estimator v2 was changed...
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ float costPerSample = 3490.9f;
+ float baseCost = 10091.0f;
+
+ if (_sampleCount == 160)
+ {
+ costPerSample = 2310.4f;
+ baseCost = 7845.3f;
+ }
+
+ return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f))));
+ }
+
+ public uint Estimate(DataSourceVersion2Command command)
+ {
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
new file mode 100644
index 00000000..b48ff8b5
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
@@ -0,0 +1,636 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.Command;
+using System;
+using System.Diagnostics;
+using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
+
+namespace Ryujinx.Audio.Renderer.Server
+{
+ /// <summary>
+ /// <see cref="ICommandProcessingTimeEstimator"/> version 3. (added with REV8)
+ /// </summary>
+ public class CommandProcessingTimeEstimatorVersion3 : ICommandProcessingTimeEstimator
+ {
+ private uint _sampleCount;
+ private uint _bufferCount;
+
+ public CommandProcessingTimeEstimatorVersion3(uint sampleCount, uint bufferCount)
+ {
+ _sampleCount = sampleCount;
+ _bufferCount = bufferCount;
+ }
+
+ public uint Estimate(PerformanceCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)498.17f;
+ }
+
+ return (uint)489.42f;
+ }
+
+ public uint Estimate(ClearMixBufferCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ float costPerBuffer = 440.68f;
+ float baseCost = 0;
+
+ if (_sampleCount == 160)
+ {
+ costPerBuffer = 266.65f;
+ }
+
+ return (uint)(baseCost + costPerBuffer * _bufferCount);
+ }
+
+ public uint Estimate(BiquadFilterCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)4173.2f;
+ }
+
+ return (uint)5585.1f;
+ }
+
+ public uint Estimate(MixRampGroupedCommand command)
+ {
+ float costPerSample = 6.4434f;
+
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ costPerSample = 6.708f;
+ }
+
+ int volumeCount = 0;
+
+ for (int i = 0; i < command.MixBufferCount; i++)
+ {
+ if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f)
+ {
+ volumeCount++;
+ }
+ }
+
+ return (uint)(_sampleCount * costPerSample * volumeCount);
+ }
+
+ public uint Estimate(MixRampCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)1968.7f;
+ }
+
+ return (uint)2459.4f;
+ }
+
+ public uint Estimate(DepopPrepareCommand command)
+ {
+ return 0;
+ }
+
+ public uint Estimate(VolumeRampCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)1425.3f;
+ }
+
+ return (uint)1700.0f;
+ }
+
+ public uint Estimate(PcmInt16DataSourceCommandVersion1 command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ float costPerSample = 710.143f;
+ float baseCost = 7853.286f;
+
+ if (_sampleCount == 160)
+ {
+ costPerSample = 427.52f;
+ baseCost = 6329.442f;
+ }
+
+ return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f))));
+ }
+
+ public uint Estimate(AdpcmDataSourceCommandVersion1 command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ float costPerSample = 3564.1f;
+ float baseCost = 9736.702f;
+
+ if (_sampleCount == 160)
+ {
+ costPerSample = 2125.6f;
+ baseCost = 7913.808f;
+ }
+
+ return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f))));
+ }
+
+ public uint Estimate(DepopForMixBuffersCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)739.64f;
+ }
+
+ return (uint)910.97f;
+ }
+
+ public uint Estimate(CopyMixBufferCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)842.59f;
+ }
+
+ return (uint)986.72f;
+ }
+
+ public uint Estimate(MixCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)1402.8f;
+ }
+
+ return (uint)1853.2f;
+ }
+
+ public uint Estimate(DelayCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)8929.04f;
+ case 2:
+ return (uint)25500.75f;
+ case 4:
+ return (uint)47759.62f;
+ case 6:
+ return (uint)82203.07f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)1295.20f;
+ case 2:
+ return (uint)1213.60f;
+ case 4:
+ return (uint)942.03f;
+ case 6:
+ return (uint)1001.55f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ }
+
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)11941.05f;
+ case 2:
+ return (uint)37197.37f;
+ case 4:
+ return (uint)69749.84f;
+ case 6:
+ return (uint)120042.40f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)997.67f;
+ case 2:
+ return (uint)977.63f;
+ case 4:
+ return (uint)792.30f;
+ case 6:
+ return (uint)875.43f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ }
+
+ public uint Estimate(ReverbCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)81475.05f;
+ case 2:
+ return (uint)84975.0f;
+ case 4:
+ return (uint)91625.15f;
+ case 6:
+ return (uint)95332.27f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)536.30f;
+ case 2:
+ return (uint)588.70f;
+ case 4:
+ return (uint)643.70f;
+ case 6:
+ return (uint)706.0f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ }
+
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)120174.47f;
+ case 2:
+ return (uint)25262.22f;
+ case 4:
+ return (uint)135751.23f;
+ case 6:
+ return (uint)141129.23f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)617.64f;
+ case 2:
+ return (uint)659.54f;
+ case 4:
+ return (uint)711.43f;
+ case 6:
+ return (uint)778.07f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ }
+
+ public uint Estimate(Reverb3dCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)116754.0f;
+ case 2:
+ return (uint)125912.05f;
+ case 4:
+ return (uint)146336.03f;
+ case 6:
+ return (uint)165812.66f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)734.0f;
+ case 2:
+ return (uint)766.62f;
+ case 4:
+ return (uint)797.46f;
+ case 6:
+ return (uint)867.43f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ }
+
+ if (command.Enabled)
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)170292.34f;
+ case 2:
+ return (uint)183875.63f;
+ case 4:
+ return (uint)214696.19f;
+ case 6:
+ return (uint)243846.77f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ else
+ {
+ switch (command.Parameter.ChannelCount)
+ {
+ case 1:
+ return (uint)508.47f;
+ case 2:
+ return (uint)582.45f;
+ case 4:
+ return (uint)626.42f;
+ case 6:
+ return (uint)682.47f;
+ default:
+ throw new NotImplementedException($"{command.Parameter.ChannelCount}");
+ }
+ }
+ }
+
+ public uint Estimate(AuxiliaryBufferCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ if (command.Enabled)
+ {
+ return (uint)7182.14f;
+ }
+
+ return (uint)472.11f;
+ }
+
+ if (command.Enabled)
+ {
+ return (uint)9435.96f;
+ }
+
+ return (uint)462.62f;
+ }
+
+ public uint Estimate(VolumeCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)1311.1f;
+ }
+
+ return (uint)1713.6f;
+ }
+
+ public uint Estimate(CircularBufferSinkCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ float costPerBuffer = 770.26f;
+ float baseCost = 0f;
+
+ if (_sampleCount == 160)
+ {
+ costPerBuffer = 531.07f;
+ }
+
+ return (uint)(baseCost + costPerBuffer * command.InputCount);
+ }
+
+ public uint Estimate(DownMixSurroundToStereoCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)9949.7f;
+ }
+
+ return (uint)14679.0f;
+ }
+
+ public uint Estimate(UpsampleCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)312990.0f;
+ }
+
+ return (uint)0.0f;
+ }
+
+ public uint Estimate(DeviceSinkCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+ Debug.Assert(command.InputCount == 2 || command.InputCount == 6);
+
+ if (command.InputCount == 2)
+ {
+ if (_sampleCount == 160)
+ {
+ return (uint)8980.0f;
+ }
+
+ return (uint)9221.9f;
+ }
+
+ if (_sampleCount == 160)
+ {
+ return (uint)9177.9f;
+ }
+
+ return (uint)9725.9f;
+ }
+
+ public uint Estimate(PcmFloatDataSourceCommandVersion1 command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ float costPerSample = 3490.9f;
+ float baseCost = 10090.9f;
+
+ if (_sampleCount == 160)
+ {
+ costPerSample = 2310.4f;
+ baseCost = 7845.25f;
+ }
+
+ return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f))));
+ }
+
+ public uint Estimate(DataSourceVersion2Command command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ (float baseCost, float costPerSample) = GetCostByFormat(_sampleCount, command.SampleFormat, command.SrcQuality);
+
+ return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f) - 1.0f)));
+ }
+
+ private static (float, float) GetCostByFormat(uint sampleCount, SampleFormat format, SampleRateConversionQuality quality)
+ {
+ Debug.Assert(sampleCount == 160 || sampleCount == 240);
+
+ switch (format)
+ {
+ case SampleFormat.PcmInt16:
+ switch (quality)
+ {
+ case SampleRateConversionQuality.Default:
+ if (sampleCount == 160)
+ {
+ return (6329.44f, 427.52f);
+ }
+
+ return (7853.28f, 710.14f);
+ case SampleRateConversionQuality.High:
+ if (sampleCount == 160)
+ {
+ return (8049.42f, 371.88f);
+ }
+
+ return (10138.84f, 610.49f);
+ case SampleRateConversionQuality.Low:
+ if (sampleCount == 160)
+ {
+ return (5062.66f, 423.43f);
+ }
+
+ return (5810.96f, 676.72f);
+ default:
+ throw new NotImplementedException($"{format} {quality}");
+ }
+ case SampleFormat.PcmFloat:
+ switch (quality)
+ {
+ case SampleRateConversionQuality.Default:
+ if (sampleCount == 160)
+ {
+ return (7845.25f, 2310.4f);
+ }
+
+ return (10090.9f, 3490.9f);
+ case SampleRateConversionQuality.High:
+ if (sampleCount == 160)
+ {
+ return (9446.36f, 2308.91f);
+ }
+
+ return (12520.85f, 3480.61f);
+ case SampleRateConversionQuality.Low:
+ if (sampleCount == 160)
+ {
+ return (9446.36f, 2308.91f);
+ }
+
+ return (12520.85f, 3480.61f);
+ default:
+ throw new NotImplementedException($"{format} {quality}");
+ }
+ case SampleFormat.Adpcm:
+ switch (quality)
+ {
+ case SampleRateConversionQuality.Default:
+ if (sampleCount == 160)
+ {
+ return (7913.81f, 1827.66f);
+ }
+
+ return (9736.70f, 2756.37f);
+ case SampleRateConversionQuality.High:
+ if (sampleCount == 160)
+ {
+ return (9607.81f, 1829.29f);
+ }
+
+ return (12154.38f, 2731.31f);
+ case SampleRateConversionQuality.Low:
+ if (sampleCount == 160)
+ {
+ return (6517.48f, 1824.61f);
+ }
+
+ return (7929.44f, 2732.15f);
+ default:
+ throw new NotImplementedException($"{format} {quality}");
+ }
+ default:
+ throw new NotImplementedException($"{format}");
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs
new file mode 100644
index 00000000..df82945b
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs
@@ -0,0 +1,92 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using DspAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Server.Effect
+{
+ /// <summary>
+ /// Server state for an auxiliary buffer effect.
+ /// </summary>
+ public class AuxiliaryBufferEffect : BaseEffect
+ {
+ /// <summary>
+ /// The auxiliary buffer parameter.
+ /// </summary>
+ public AuxiliaryBufferParameter Parameter;
+
+ /// <summary>
+ /// Auxiliary buffer state.
+ /// </summary>
+ public AuxiliaryBufferAddresses State;
+
+ public override EffectType TargetEffectType => EffectType.AuxiliaryBuffer;
+
+ public override DspAddress GetWorkBuffer(int index)
+ {
+ return WorkBuffers[index].GetReference(true);
+ }
+
+ public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+ {
+ Debug.Assert(IsTypeValid(ref parameter));
+
+ UpdateParameterBase(ref parameter);
+
+ Parameter = MemoryMarshal.Cast<byte, AuxiliaryBufferParameter>(parameter.SpecificData)[0];
+ IsEnabled = parameter.IsEnabled;
+
+ updateErrorInfo = new BehaviourParameter.ErrorInfo();
+
+ if (BufferUnmapped || parameter.IsNew)
+ {
+ ulong bufferSize = (ulong)Unsafe.SizeOf<int>() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf<AuxiliaryBufferHeader>() * 2;
+
+ bool sendBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], Parameter.SendBufferInfoAddress, bufferSize);
+ bool returnBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[1], Parameter.ReturnBufferInfoAddress, bufferSize);
+
+ BufferUnmapped = sendBufferUnmapped && returnBufferUnmapped;
+
+ if (!BufferUnmapped)
+ {
+ DspAddress sendDspAddress = WorkBuffers[0].GetReference(false);
+ DspAddress returnDspAddress = WorkBuffers[1].GetReference(false);
+
+ State.SendBufferInfo = sendDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>();
+ State.SendBufferInfoBase = sendDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>() * 2;
+
+ State.ReturnBufferInfo = returnDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>();
+ State.ReturnBufferInfoBase = returnDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>() * 2;
+ }
+ }
+ }
+
+ public override void UpdateForCommandGeneration()
+ {
+ UpdateUsageStateForCommandGeneration();
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
new file mode 100644
index 00000000..7529f894
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
@@ -0,0 +1,257 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using Ryujinx.Audio.Renderer.Utils;
+using System;
+using System.Diagnostics;
+using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
+
+using DspAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Server.Effect
+{
+ /// <summary>
+ /// Base class used as a server state for an effect.
+ /// </summary>
+ public class BaseEffect
+ {
+ /// <summary>
+ /// The <see cref="EffectType"/> of the effect.
+ /// </summary>
+ public EffectType Type;
+
+ /// <summary>
+ /// Set to true if the effect must be active.
+ /// </summary>
+ public bool IsEnabled;
+
+ /// <summary>
+ /// Set to true if the internal effect work buffers used wasn't mapped.
+ /// </summary>
+ public bool BufferUnmapped;
+
+ /// <summary>
+ /// The current state of the effect.
+ /// </summary>
+ public UsageState UsageState;
+
+ /// <summary>
+ /// The target mix id of the effect.
+ /// </summary>
+ public int MixId;
+
+ /// <summary>
+ /// Position of the effect while processing effects.
+ /// </summary>
+ public uint ProcessingOrder;
+
+ /// <summary>
+ /// Array of all the work buffer used by the effect.
+ /// </summary>
+ protected AddressInfo[] WorkBuffers;
+
+ /// <summary>
+ /// Create a new <see cref="BaseEffect"/>.
+ /// </summary>
+ public BaseEffect()
+ {
+ Type = TargetEffectType;
+ UsageState = UsageState.Invalid;
+
+ IsEnabled = false;
+ BufferUnmapped = false;
+ MixId = Constants.UnusedMixId;
+ ProcessingOrder = uint.MaxValue;
+
+ WorkBuffers = new AddressInfo[2];
+
+ foreach (ref AddressInfo info in WorkBuffers.AsSpan())
+ {
+ info = AddressInfo.Create();
+ }
+ }
+
+ /// <summary>
+ /// The target <see cref="EffectType"/> handled by this <see cref="BaseEffect"/>.
+ /// </summary>
+ public virtual EffectType TargetEffectType => EffectType.Invalid;
+
+ /// <summary>
+ /// Check if the <see cref="EffectType"/> sent by the user match the internal <see cref="EffectType"/>.
+ /// </summary>
+ /// <param name="parameter">The user parameter.</param>
+ /// <returns>Returns true if the <see cref="EffectType"/> sent by the user matches the internal <see cref="EffectType"/>.</returns>
+ public bool IsTypeValid(ref EffectInParameter parameter)
+ {
+ return parameter.Type == TargetEffectType;
+ }
+
+ /// <summary>
+ /// Update the usage state during command generation.
+ /// </summary>
+ protected void UpdateUsageStateForCommandGeneration()
+ {
+ UsageState = IsEnabled ? UsageState.Enabled : UsageState.Disabled;
+ }
+
+ /// <summary>
+ /// Update the internal common parameters from a user parameter.
+ /// </summary>
+ /// <param name="parameter">The user parameter.</param>
+ protected void UpdateParameterBase(ref EffectInParameter parameter)
+ {
+ MixId = parameter.MixId;
+ ProcessingOrder = parameter.ProcessingOrder;
+ }
+
+ /// <summary>
+ /// Force unmap all the work buffers.
+ /// </summary>
+ /// <param name="mapper">The mapper to use.</param>
+ public void ForceUnmapBuffers(PoolMapper mapper)
+ {
+ foreach (ref AddressInfo info in WorkBuffers.AsSpan())
+ {
+ if (info.GetReference(false) != 0)
+ {
+ mapper.ForceUnmap(ref info);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Check if the effect needs to be skipped.
+ /// </summary>
+ /// <returns>Returns true if the effect needs to be skipped.</returns>
+ public bool ShouldSkip()
+ {
+ return BufferUnmapped;
+ }
+
+ /// <summary>
+ /// Update the <see cref="BaseEffect"/> state during command generation.
+ /// </summary>
+ public virtual void UpdateForCommandGeneration()
+ {
+ Debug.Assert(Type == TargetEffectType);
+ }
+
+ /// <summary>
+ /// Update the internal state from a user parameter.
+ /// </summary>
+ /// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
+ /// <param name="parameter">The user parameter.</param>
+ /// <param name="mapper">The mapper to use.</param>
+ public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+ {
+ Debug.Assert(IsTypeValid(ref parameter));
+
+ updateErrorInfo = new ErrorInfo();
+ }
+
+ /// <summary>
+ /// Get the work buffer DSP address at the given index.
+ /// </summary>
+ /// <param name="index">The index of the work buffer</param>
+ /// <returns>The work buffer DSP address at the given index.</returns>
+ public virtual DspAddress GetWorkBuffer(int index)
+ {
+ throw new InvalidOperationException();
+ }
+
+ /// <summary>
+ /// Get the first work buffer DSP address.
+ /// </summary>
+ /// <returns>The first work buffer DSP address.</returns>
+ protected DspAddress GetSingleBuffer()
+ {
+ if (IsEnabled)
+ {
+ return WorkBuffers[0].GetReference(true);
+ }
+
+ if (UsageState != UsageState.Disabled)
+ {
+ DspAddress address = WorkBuffers[0].GetReference(false);
+ ulong size = WorkBuffers[0].Size;
+
+ if (address != 0 && size != 0)
+ {
+ AudioProcessorMemoryManager.InvalidateDataCache(address, size);
+ }
+ }
+
+ return 0;
+ }
+
+ /// <summary>
+ /// Store the output status to the given user output.
+ /// </summary>
+ /// <param name="outStatus">The given user output.</param>
+ /// <param name="isAudioRendererActive">If set to true, the <see cref="AudioRenderSystem"/> is active.</param>
+ public void StoreStatus(ref EffectOutStatus outStatus, bool isAudioRendererActive)
+ {
+ if (isAudioRendererActive)
+ {
+ if (UsageState == UsageState.Disabled)
+ {
+ outStatus.State = EffectOutStatus.EffectState.Disabled;
+ }
+ else
+ {
+ outStatus.State = EffectOutStatus.EffectState.Enabled;
+ }
+ }
+ else if (UsageState == UsageState.New)
+ {
+ outStatus.State = EffectOutStatus.EffectState.Enabled;
+ }
+ else
+ {
+ outStatus.State = EffectOutStatus.EffectState.Disabled;
+ }
+ }
+
+ /// <summary>
+ /// Get the <see cref="PerformanceDetailType"/> associated to the <see cref="Type"/> of this effect.
+ /// </summary>
+ /// <returns>The <see cref="PerformanceDetailType"/> associated to the <see cref="Type"/> of this effect.</returns>
+ public PerformanceDetailType GetPerformanceDetailType()
+ {
+ switch (Type)
+ {
+ case EffectType.BiquadFilter:
+ return PerformanceDetailType.BiquadFilter;
+ case EffectType.AuxiliaryBuffer:
+ return PerformanceDetailType.Aux;
+ case EffectType.Delay:
+ return PerformanceDetailType.Delay;
+ case EffectType.Reverb:
+ return PerformanceDetailType.Reverb;
+ case EffectType.Reverb3d:
+ return PerformanceDetailType.Reverb3d;
+ case EffectType.BufferMix:
+ return PerformanceDetailType.Mix;
+ default:
+ throw new NotImplementedException($"{Type}");
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs
new file mode 100644
index 00000000..35ba8a0d
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs
@@ -0,0 +1,74 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Effect
+{
+ /// <summary>
+ /// Server state for a biquad filter effect.
+ /// </summary>
+ public class BiquadFilterEffect : BaseEffect
+ {
+ /// <summary>
+ /// The biquad filter parameter.
+ /// </summary>
+ public BiquadFilterEffectParameter Parameter;
+
+ /// <summary>
+ /// The biquad filter state.
+ /// </summary>
+ public Memory<BiquadFilterState> State { get; }
+
+ /// <summary>
+ /// Create a new <see cref="BiquadFilterEffect"/>.
+ /// </summary>
+ public BiquadFilterEffect()
+ {
+ Parameter = new BiquadFilterEffectParameter();
+ State = new BiquadFilterState[Constants.ChannelCountMax];
+ }
+
+ public override EffectType TargetEffectType => EffectType.BiquadFilter;
+
+ public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+ {
+ Debug.Assert(IsTypeValid(ref parameter));
+
+ UpdateParameterBase(ref parameter);
+
+ Parameter = MemoryMarshal.Cast<byte, BiquadFilterEffectParameter>(parameter.SpecificData)[0];
+ IsEnabled = parameter.IsEnabled;
+
+ updateErrorInfo = new BehaviourParameter.ErrorInfo();
+ }
+
+ public override void UpdateForCommandGeneration()
+ {
+ UpdateUsageStateForCommandGeneration();
+
+ Parameter.Status = UsageState.Enabled;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs
new file mode 100644
index 00000000..14ed3950
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs
@@ -0,0 +1,56 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Effect
+{
+ /// <summary>
+ /// Server state for a buffer mix effect.
+ /// </summary>
+ public class BufferMixEffect : BaseEffect
+ {
+ /// <summary>
+ /// The buffer mix parameter.
+ /// </summary>
+ public BufferMixParameter Parameter;
+
+ public override EffectType TargetEffectType => EffectType.BufferMix;
+
+ public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+ {
+ Debug.Assert(IsTypeValid(ref parameter));
+
+ UpdateParameterBase(ref parameter);
+
+ Parameter = MemoryMarshal.Cast<byte, BufferMixParameter>(parameter.SpecificData)[0];
+ IsEnabled = parameter.IsEnabled;
+
+ updateErrorInfo = new BehaviourParameter.ErrorInfo();
+ }
+
+ public override void UpdateForCommandGeneration()
+ {
+ UpdateUsageStateForCommandGeneration();
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs
new file mode 100644
index 00000000..df3e5ee7
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs
@@ -0,0 +1,100 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using DspAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Server.Effect
+{
+ /// <summary>
+ /// Server state for a delay effect.
+ /// </summary>
+ public class DelayEffect : BaseEffect
+ {
+ /// <summary>
+ /// The delay parameter.
+ /// </summary>
+ public DelayParameter Parameter;
+
+ /// <summary>
+ /// The delay state.
+ /// </summary>
+ public Memory<DelayState> State { get; }
+
+ public DelayEffect()
+ {
+ State = new DelayState[1];
+ }
+
+ public override EffectType TargetEffectType => EffectType.Delay;
+
+ public override DspAddress GetWorkBuffer(int index)
+ {
+ return GetSingleBuffer();
+ }
+
+ public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+ {
+ Debug.Assert(IsTypeValid(ref parameter));
+
+ ref DelayParameter delayParameter = ref MemoryMarshal.Cast<byte, DelayParameter>(parameter.SpecificData)[0];
+
+ updateErrorInfo = new BehaviourParameter.ErrorInfo();
+
+ if (delayParameter.IsChannelCountMaxValid())
+ {
+ UpdateParameterBase(ref parameter);
+
+ UsageState oldParameterStatus = Parameter.Status;
+
+ Parameter = delayParameter;
+
+ if (delayParameter.IsChannelCountValid())
+ {
+ IsEnabled = parameter.IsEnabled;
+
+ if (oldParameterStatus != UsageState.Enabled)
+ {
+ Parameter.Status = oldParameterStatus;
+ }
+
+ if (BufferUnmapped || parameter.IsNew)
+ {
+ UsageState = UsageState.New;
+ Parameter.Status = UsageState.Invalid;
+
+ BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize);
+ }
+ }
+ }
+ }
+
+ public override void UpdateForCommandGeneration()
+ {
+ UpdateUsageStateForCommandGeneration();
+
+ Parameter.Status = UsageState.Enabled;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs b/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs
new file mode 100644
index 00000000..ff6051ae
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs
@@ -0,0 +1,82 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Server.Effect
+{
+ /// <summary>
+ /// Effect context.
+ /// </summary>
+ public class EffectContext
+ {
+ /// <summary>
+ /// Storage for <see cref="BaseEffect"/>.
+ /// </summary>
+ private BaseEffect[] _effects;
+
+ /// <summary>
+ /// The total effect count.
+ /// </summary>
+ private uint _effectCount;
+
+ /// <summary>
+ /// Create a new <see cref="EffectContext"/>.
+ /// </summary>
+ public EffectContext()
+ {
+ _effects = null;
+ _effectCount = 0;
+ }
+
+ /// <summary>
+ /// Initialize the <see cref="EffectContext"/>.
+ /// </summary>
+ /// <param name="effectCount">The total effect count.</param>
+ public void Initialize(uint effectCount)
+ {
+ _effectCount = effectCount;
+ _effects = new BaseEffect[effectCount];
+
+ for (int i = 0; i < _effectCount; i++)
+ {
+ _effects[i] = new BaseEffect();
+ }
+ }
+
+ /// <summary>
+ /// Get the total effect count.
+ /// </summary>
+ /// <returns>The total effect count.</returns>
+ public uint GetCount()
+ {
+ return _effectCount;
+ }
+
+ /// <summary>
+ /// Get a reference to a <see cref="BaseEffect"/> at the given <paramref name="index"/>.
+ /// </summary>
+ /// <param name="index">The index to use.</param>
+ /// <returns>A reference to a <see cref="BaseEffect"/> at the given <paramref name="index"/>.</returns>
+ public ref BaseEffect GetEffect(int index)
+ {
+ Debug.Assert(index >= 0 && index < _effectCount);
+
+ return ref _effects[index];
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs
new file mode 100644
index 00000000..d9f23799
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs
@@ -0,0 +1,99 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Effect
+{
+ /// <summary>
+ /// Server state for a 3D reverberation effect.
+ /// </summary>
+ public class Reverb3dEffect : BaseEffect
+ {
+ /// <summary>
+ /// The 3D reverberation parameter.
+ /// </summary>
+ public Reverb3dParameter Parameter;
+
+ /// <summary>
+ /// The 3D reverberation state.
+ /// </summary>
+ public Memory<Reverb3dState> State { get; }
+
+ public Reverb3dEffect()
+ {
+ State = new Reverb3dState[1];
+ }
+
+ public override EffectType TargetEffectType => EffectType.Reverb3d;
+
+ public override ulong GetWorkBuffer(int index)
+ {
+ return GetSingleBuffer();
+ }
+
+ public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+ {
+ Debug.Assert(IsTypeValid(ref parameter));
+
+ ref Reverb3dParameter reverbParameter = ref MemoryMarshal.Cast<byte, Reverb3dParameter>(parameter.SpecificData)[0];
+
+ updateErrorInfo = new BehaviourParameter.ErrorInfo();
+
+ if (reverbParameter.IsChannelCountMaxValid())
+ {
+ UpdateParameterBase(ref parameter);
+
+ UsageState oldParameterStatus = Parameter.ParameterStatus;
+
+ Parameter = reverbParameter;
+
+ if (reverbParameter.IsChannelCountValid())
+ {
+ IsEnabled = parameter.IsEnabled;
+
+ if (oldParameterStatus != UsageState.Enabled)
+ {
+ Parameter.ParameterStatus = oldParameterStatus;
+ }
+
+ if (BufferUnmapped || parameter.IsNew)
+ {
+ UsageState = UsageState.New;
+ Parameter.ParameterStatus = UsageState.Invalid;
+
+ BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize);
+ }
+ }
+ }
+ }
+
+ public override void UpdateForCommandGeneration()
+ {
+ UpdateUsageStateForCommandGeneration();
+
+ Parameter.ParameterStatus = UsageState.Enabled;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs
new file mode 100644
index 00000000..4c81f729
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs
@@ -0,0 +1,102 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Effect
+{
+ /// <summary>
+ /// Server state for a reverberation effect.
+ /// </summary>
+ public class ReverbEffect : BaseEffect
+ {
+ /// <summary>
+ /// The reverberation parameter.
+ /// </summary>
+ public ReverbParameter Parameter;
+
+ /// <summary>
+ /// The reverberation state.
+ /// </summary>
+ public Memory<ReverbState> State { get; }
+
+ /// <summary>
+ /// Create a new <see cref="ReverbEffect"/>.
+ /// </summary>
+ public ReverbEffect()
+ {
+ State = new ReverbState[1];
+ }
+
+ public override EffectType TargetEffectType => EffectType.Reverb;
+
+ public override ulong GetWorkBuffer(int index)
+ {
+ return GetSingleBuffer();
+ }
+
+ public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
+ {
+ Debug.Assert(IsTypeValid(ref parameter));
+
+ ref ReverbParameter reverbParameter = ref MemoryMarshal.Cast<byte, ReverbParameter>(parameter.SpecificData)[0];
+
+ updateErrorInfo = new BehaviourParameter.ErrorInfo();
+
+ if (reverbParameter.IsChannelCountMaxValid())
+ {
+ UpdateParameterBase(ref parameter);
+
+ UsageState oldParameterStatus = Parameter.Status;
+
+ Parameter = reverbParameter;
+
+ if (reverbParameter.IsChannelCountValid())
+ {
+ IsEnabled = parameter.IsEnabled;
+
+ if (oldParameterStatus != UsageState.Enabled)
+ {
+ Parameter.Status = oldParameterStatus;
+ }
+
+ if (BufferUnmapped || parameter.IsNew)
+ {
+ UsageState = UsageState.New;
+ Parameter.Status = UsageState.Invalid;
+
+ BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize);
+ }
+ }
+ }
+ }
+
+ public override void UpdateForCommandGeneration()
+ {
+ UpdateUsageStateForCommandGeneration();
+
+ Parameter.Status = UsageState.Enabled;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs b/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs
new file mode 100644
index 00000000..a519acd4
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs
@@ -0,0 +1,45 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Server.Effect
+{
+ /// <summary>
+ /// The usage state of an effect.
+ /// </summary>
+ public enum UsageState : byte
+ {
+ /// <summary>
+ /// The effect is in an invalid state.
+ /// </summary>
+ Invalid,
+
+ /// <summary>
+ /// The effect is new.
+ /// </summary>
+ New,
+
+ /// <summary>
+ /// The effect is enabled.
+ /// </summary>
+ Enabled,
+
+ /// <summary>
+ /// The effect is disabled.
+ /// </summary>
+ Disabled
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs b/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
new file mode 100644
index 00000000..119ec907
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.Command;
+
+namespace Ryujinx.Audio.Renderer.Server
+{
+ /// <summary>
+ /// Estimate the time that a <see cref="ICommand"/> should take.
+ /// </summary>
+ /// <remarks>This is used for voice dropping.</remarks>
+ public interface ICommandProcessingTimeEstimator
+ {
+ uint Estimate(AuxiliaryBufferCommand command);
+ uint Estimate(BiquadFilterCommand command);
+ uint Estimate(ClearMixBufferCommand command);
+ uint Estimate(DelayCommand command);
+ uint Estimate(Reverb3dCommand command);
+ uint Estimate(ReverbCommand command);
+ uint Estimate(DepopPrepareCommand command);
+ uint Estimate(DepopForMixBuffersCommand command);
+ uint Estimate(MixCommand command);
+ uint Estimate(MixRampCommand command);
+ uint Estimate(MixRampGroupedCommand command);
+ uint Estimate(CopyMixBufferCommand command);
+ uint Estimate(PerformanceCommand command);
+ uint Estimate(VolumeCommand command);
+ uint Estimate(VolumeRampCommand command);
+ uint Estimate(PcmInt16DataSourceCommandVersion1 command);
+ uint Estimate(PcmFloatDataSourceCommandVersion1 command);
+ uint Estimate(AdpcmDataSourceCommandVersion1 command);
+ uint Estimate(DataSourceVersion2Command command);
+ uint Estimate(CircularBufferSinkCommand command);
+ uint Estimate(DeviceSinkCommand command);
+ uint Estimate(DownMixSurroundToStereoCommand command);
+ uint Estimate(UpsampleCommand command);
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs b/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs
new file mode 100644
index 00000000..2b625d06
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs
@@ -0,0 +1,151 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Runtime.InteropServices;
+
+using DspAddress = System.UInt64;
+using CpuAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Server.MemoryPool
+{
+ /// <summary>
+ /// Represents the information of a region shared between the CPU and DSP.
+ /// </summary>
+ public struct AddressInfo
+ {
+ /// <summary>
+ /// The target CPU address of the region.
+ /// </summary>
+ public CpuAddress CpuAddress;
+
+ /// <summary>
+ /// The size of the region.
+ /// </summary>
+ public ulong Size;
+
+ private unsafe MemoryPoolState* _memoryPools;
+
+ /// <summary>
+ /// The forced DSP address of the region.
+ /// </summary>
+ public DspAddress ForceMappedDspAddress;
+
+ private unsafe ref MemoryPoolState MemoryPoolState => ref *_memoryPools;
+
+ public unsafe bool HasMemoryPoolState => (IntPtr)_memoryPools != IntPtr.Zero;
+
+ /// <summary>
+ /// Create an new empty <see cref="AddressInfo"/>.
+ /// </summary>
+ /// <returns>A new empty <see cref="AddressInfo"/>.</returns>
+ public static AddressInfo Create()
+ {
+ return Create(0, 0);
+ }
+
+ /// <summary>
+ /// Create a new <see cref="AddressInfo"/>.
+ /// </summary>
+ /// <param name="cpuAddress">The target <see cref="CpuAddress"/> of the region.</param>
+ /// <param name="size">The target size of the region.</param>
+ /// <returns>A new <see cref="AddressInfo"/>.</returns>
+ public static AddressInfo Create(CpuAddress cpuAddress, ulong size)
+ {
+ unsafe
+ {
+ return new AddressInfo
+ {
+ CpuAddress = cpuAddress,
+ _memoryPools = MemoryPoolState.Null,
+ Size = size,
+ ForceMappedDspAddress = 0
+ };
+ }
+ }
+
+ /// <summary>
+ /// Setup the CPU address and size of the <see cref="AddressInfo"/>.
+ /// </summary>
+ /// <param name="cpuAddress">The target <see cref="CpuAddress"/> of the region.</param>
+ /// <param name="size">The size of the region.</param>
+ public void Setup(CpuAddress cpuAddress, ulong size)
+ {
+ CpuAddress = cpuAddress;
+ Size = size;
+ ForceMappedDspAddress = 0;
+
+ unsafe
+ {
+ _memoryPools = MemoryPoolState.Null;
+ }
+ }
+
+ /// <summary>
+ /// Set the <see cref="MemoryPoolState"/> associated.
+ /// </summary>
+ /// <param name="memoryPoolState">The <see cref="MemoryPoolState"/> associated.</param>
+ public void SetupMemoryPool(Span<MemoryPoolState> memoryPoolState)
+ {
+ unsafe
+ {
+ fixed (MemoryPoolState* ptr = &MemoryMarshal.GetReference(memoryPoolState))
+ {
+ SetupMemoryPool(ptr);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Set the <see cref="MemoryPoolState"/> associated.
+ /// </summary>
+ /// <param name="memoryPoolState">The <see cref="MemoryPoolState"/> associated.</param>
+ public unsafe void SetupMemoryPool(MemoryPoolState* memoryPoolState)
+ {
+ _memoryPools = memoryPoolState;
+ }
+
+ /// <summary>
+ /// Check if the <see cref="MemoryPoolState"/> is mapped.
+ /// </summary>
+ /// <returns>Returns true if the <see cref="MemoryPoolState"/> is mapped.</returns>
+ public bool HasMappedMemoryPool()
+ {
+ return HasMemoryPoolState && MemoryPoolState.IsMapped();
+ }
+
+ /// <summary>
+ /// Get the DSP address associated to the <see cref="AddressInfo"/>.
+ /// </summary>
+ /// <param name="markUsed">If true, mark the <see cref="MemoryPoolState"/> as used.</param>
+ /// <returns>Returns the DSP address associated to the <see cref="AddressInfo"/>.</returns>
+ public DspAddress GetReference(bool markUsed)
+ {
+ if (!HasMappedMemoryPool())
+ {
+ return ForceMappedDspAddress;
+ }
+
+ if (markUsed)
+ {
+ MemoryPoolState.IsUsed = true;
+ }
+
+ return MemoryPoolState.Translate(CpuAddress, Size);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs b/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs
new file mode 100644
index 00000000..ebde1403
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs
@@ -0,0 +1,148 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Runtime.InteropServices;
+
+using DspAddress = System.UInt64;
+using CpuAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Server.MemoryPool
+{
+ /// <summary>
+ /// Server state for a memory pool.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = Alignment)]
+ public struct MemoryPoolState
+ {
+ public const int Alignment = 0x10;
+
+ /// <summary>
+ /// The location of the <see cref="MemoryPoolState"/>.
+ /// </summary>
+ public enum LocationType : uint
+ {
+ /// <summary>
+ /// <see cref="MemoryPoolState"/> located on the CPU side for user use.
+ /// </summary>
+ Cpu,
+
+ /// <summary>
+ /// <see cref="MemoryPoolState"/> located on the DSP side for system use.
+ /// </summary>
+ Dsp
+ }
+
+ /// <summary>
+ /// The CPU address associated to the <see cref="MemoryPoolState"/>.
+ /// </summary>
+ public CpuAddress CpuAddress;
+
+ /// <summary>
+ /// The DSP address associated to the <see cref="MemoryPoolState"/>.
+ /// </summary>
+ public DspAddress DspAddress;
+
+ /// <summary>
+ /// The size associated to the <see cref="MemoryPoolState"/>.
+ /// </summary>
+ public ulong Size;
+
+ /// <summary>
+ /// The <see cref="LocationType"/> associated to the <see cref="MemoryPoolState"/>.
+ /// </summary>
+ public LocationType Location;
+
+ /// <summary>
+ /// Set to true if the <see cref="MemoryPoolState"/> is used.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ public static unsafe MemoryPoolState* Null => (MemoryPoolState*)IntPtr.Zero.ToPointer();
+
+ /// <summary>
+ /// Create a new <see cref="MemoryPoolState"/> with the given <see cref="LocationType"/>.
+ /// </summary>
+ /// <param name="location">The location type to use.</param>
+ /// <returns>A new <see cref="MemoryPoolState"/> with the given <see cref="LocationType"/>.</returns>
+ public static MemoryPoolState Create(LocationType location)
+ {
+ return new MemoryPoolState
+ {
+ CpuAddress = 0,
+ DspAddress = 0,
+ Size = 0,
+ Location = location
+ };
+ }
+
+ /// <summary>
+ /// Set the <see cref="CpuAddress"/> and size of the <see cref="MemoryPoolState"/>.
+ /// </summary>
+ /// <param name="cpuAddress">The <see cref="CpuAddress"/>.</param>
+ /// <param name="size">The size.</param>
+ public void SetCpuAddress(CpuAddress cpuAddress, ulong size)
+ {
+ CpuAddress = cpuAddress;
+ Size = size;
+ }
+
+ /// <summary>
+ /// Check if the given <see cref="CpuAddress"/> and size is contains in the <see cref="MemoryPoolState"/>.
+ /// </summary>
+ /// <param name="targetCpuAddress">The <see cref="CpuAddress"/>.</param>
+ /// <param name="size">The size.</param>
+ /// <returns>True if the <see cref="CpuAddress"/> is contained inside the <see cref="MemoryPoolState"/>.</returns>
+ public bool Contains(CpuAddress targetCpuAddress, ulong size)
+ {
+ if (CpuAddress <= targetCpuAddress && size + targetCpuAddress <= Size + CpuAddress)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Translate the given CPU address to a DSP address.
+ /// </summary>
+ /// <param name="targetCpuAddress">The <see cref="CpuAddress"/>.</param>
+ /// <param name="size">The size.</param>
+ /// <returns>the target DSP address.</returns>
+ public DspAddress Translate(CpuAddress targetCpuAddress, ulong size)
+ {
+ if (Contains(targetCpuAddress, size) && IsMapped())
+ {
+ ulong offset = targetCpuAddress - CpuAddress;
+
+ return DspAddress + offset;
+ }
+
+ return 0;
+ }
+
+ /// <summary>
+ /// Is the <see cref="MemoryPoolState"/> mapped on the DSP?
+ /// </summary>
+ /// <returns>Returns true if the <see cref="MemoryPoolState"/> is mapped on the DSP.</returns>
+ public bool IsMapped()
+ {
+ return DspAddress != 0;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs b/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs
new file mode 100644
index 00000000..b255960c
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs
@@ -0,0 +1,383 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Utils;
+using Ryujinx.Common.Logging;
+using System;
+using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
+
+using CpuAddress = System.UInt64;
+using DspAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Server.MemoryPool
+{
+ /// <summary>
+ /// Memory pool mapping helper.
+ /// </summary>
+ public class PoolMapper
+ {
+ const uint CurrentProcessPseudoHandle = 0xFFFF8001;
+
+ /// <summary>
+ /// The result of <see cref="Update(ref MemoryPoolState, ref MemoryPoolInParameter, ref MemoryPoolOutStatus)"/>.
+ /// </summary>
+ public enum UpdateResult : uint
+ {
+ /// <summary>
+ /// No error reported.
+ /// </summary>
+ Success = 0,
+
+ /// <summary>
+ /// The user parameters were invalid.
+ /// </summary>
+ InvalidParameter = 1,
+
+ /// <summary>
+ /// <see cref="Dsp.AudioProcessor"/> mapping failed.
+ /// </summary>
+ MapError = 2,
+
+ /// <summary>
+ /// <see cref="Dsp.AudioProcessor"/> unmapping failed.
+ /// </summary>
+ UnmapError = 3
+ }
+
+ /// <summary>
+ /// The handle of the process owning the CPU memory manipulated.
+ /// </summary>
+ private uint _processHandle;
+
+ /// <summary>
+ /// The <see cref="Memory{MemoryPoolState}"/> that will be manipulated.
+ /// </summary>
+ private Memory<MemoryPoolState> _memoryPools;
+
+ /// <summary>
+ /// If set to true, this will try to force map memory pool even if their state are considered invalid.
+ /// </summary>
+ private bool _isForceMapEnabled;
+
+ /// <summary>
+ /// Create a new <see cref="PoolMapper"/> used for system mapping.
+ /// </summary>
+ /// <param name="processHandle">The handle of the process owning the CPU memory manipulated.</param>
+ /// <param name="isForceMapEnabled">If set to true, this will try to force map memory pool even if their state are considered invalid.</param>
+ public PoolMapper(uint processHandle, bool isForceMapEnabled)
+ {
+ _processHandle = processHandle;
+ _isForceMapEnabled = isForceMapEnabled;
+ _memoryPools = Memory<MemoryPoolState>.Empty;
+ }
+
+ /// <summary>
+ /// Create a new <see cref="PoolMapper"/> used for user mapping.
+ /// </summary>
+ /// <param name="processHandle">The handle of the process owning the CPU memory manipulated.</param>
+ /// <param name="memoryPool">The user memory pools.</param>
+ /// <param name="isForceMapEnabled">If set to true, this will try to force map memory pool even if their state are considered invalid.</param>
+ public PoolMapper(uint processHandle, Memory<MemoryPoolState> memoryPool, bool isForceMapEnabled)
+ {
+ _processHandle = processHandle;
+ _memoryPools = memoryPool;
+ _isForceMapEnabled = isForceMapEnabled;
+ }
+
+ /// <summary>
+ /// Initialize the <see cref="MemoryPoolState"/> for system use.
+ /// </summary>
+ /// <param name="memoryPool">The <see cref="MemoryPoolState"/> for system use.</param>
+ /// <param name="cpuAddress">The <see cref="CpuAddress"/> to assign.</param>
+ /// <param name="size">The size to assign.</param>
+ /// <returns>Returns true if mapping on the <see cref="Dsp.AudioProcessor"/> succeeded.</returns>
+ public bool InitializeSystemPool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size)
+ {
+ if (memoryPool.Location != MemoryPoolState.LocationType.Dsp)
+ {
+ return false;
+ }
+
+ return InitializePool(ref memoryPool, cpuAddress, size);
+ }
+
+ /// <summary>
+ /// Initialize the <see cref="MemoryPoolState"/>.
+ /// </summary>
+ /// <param name="memoryPool">The <see cref="MemoryPoolState"/>.</param>
+ /// <param name="cpuAddress">The <see cref="CpuAddress"/> to assign.</param>
+ /// <param name="size">The size to assign.</param>
+ /// <returns>Returns true if mapping on the <see cref="Dsp.AudioProcessor"/> succeeded.</returns>
+ public bool InitializePool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size)
+ {
+ memoryPool.SetCpuAddress(cpuAddress, size);
+
+ return Map(ref memoryPool) != 0;
+ }
+
+ /// <summary>
+ /// Get the process handle associated to the <see cref="MemoryPoolState"/>.
+ /// </summary>
+ /// <param name="memoryPool">The <see cref="MemoryPoolState"/>.</param>
+ /// <returns>Returns the process handle associated to the <see cref="MemoryPoolState"/>.</returns>
+ public uint GetProcessHandle(ref MemoryPoolState memoryPool)
+ {
+ if (memoryPool.Location == MemoryPoolState.LocationType.Cpu)
+ {
+ return CurrentProcessPseudoHandle;
+ }
+ else if (memoryPool.Location == MemoryPoolState.LocationType.Dsp)
+ {
+ return _processHandle;
+ }
+
+ return 0;
+ }
+
+ /// <summary>
+ /// Map the <see cref="MemoryPoolState"/> on the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ /// <param name="memoryPool">The <see cref="MemoryPoolState"/> to map.</param>
+ /// <returns>Returns the DSP address mapped.</returns>
+ public DspAddress Map(ref MemoryPoolState memoryPool)
+ {
+ DspAddress result = AudioProcessorMemoryManager.Map(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size);
+
+ if (result != 0)
+ {
+ memoryPool.DspAddress = result;
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Unmap the <see cref="MemoryPoolState"/> from the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ /// <param name="memoryPool">The <see cref="MemoryPoolState"/> to unmap.</param>
+ /// <returns>Returns true if unmapped.</returns>
+ public bool Unmap(ref MemoryPoolState memoryPool)
+ {
+ if (memoryPool.IsUsed)
+ {
+ return false;
+ }
+
+ AudioProcessorMemoryManager.Unmap(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size);
+
+ memoryPool.SetCpuAddress(0, 0);
+ memoryPool.DspAddress = 0;
+
+ return true;
+ }
+
+ /// <summary>
+ /// Find a <see cref="MemoryPoolState"/> associated to the region given.
+ /// </summary>
+ /// <param name="cpuAddress">The region <see cref="CpuAddress"/>.</param>
+ /// <param name="size">The region size.</param>
+ /// <returns>Returns the <see cref="MemoryPoolState"/> found or <see cref="Memory{MemoryPoolState}.Empty"/> if not found.</returns>
+ private Span<MemoryPoolState> FindMemoryPool(CpuAddress cpuAddress, ulong size)
+ {
+ if (!_memoryPools.IsEmpty && _memoryPools.Length > 0)
+ {
+ for (int i = 0; i < _memoryPools.Length; i++)
+ {
+ if (_memoryPools.Span[i].Contains(cpuAddress, size))
+ {
+ return _memoryPools.Span.Slice(i, 1);
+ }
+ }
+ }
+
+ return Span<MemoryPoolState>.Empty;
+ }
+
+ /// <summary>
+ /// Force unmap the given <see cref="AddressInfo"/>.
+ /// </summary>
+ /// <param name="addressInfo">The <see cref="AddressInfo"/> to force unmap</param>
+ public void ForceUnmap(ref AddressInfo addressInfo)
+ {
+ if (_isForceMapEnabled)
+ {
+ Span<MemoryPoolState> memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size);
+
+ if (!memoryPool.IsEmpty)
+ {
+ AudioProcessorMemoryManager.Unmap(_processHandle, memoryPool[0].CpuAddress, memoryPool[0].Size);
+
+ return;
+ }
+
+ AudioProcessorMemoryManager.Unmap(_processHandle, addressInfo.CpuAddress, 0);
+ }
+ }
+
+ /// <summary>
+ /// Try to attach the given region to the <see cref="AddressInfo"/>.
+ /// </summary>
+ /// <param name="errorInfo">The error information if an error was generated.</param>
+ /// <param name="addressInfo">The <see cref="AddressInfo"/> to attach the region to.</param>
+ /// <param name="cpuAddress">The region <see cref="CpuAddress"/>.</param>
+ /// <param name="size">The region size.</param>
+ /// <returns>Returns true if mapping was performed.</returns>
+ public bool TryAttachBuffer(out ErrorInfo errorInfo, ref AddressInfo addressInfo, CpuAddress cpuAddress, ulong size)
+ {
+ errorInfo = new ErrorInfo();
+
+ addressInfo.Setup(cpuAddress, size);
+
+ if (AssignDspAddress(ref addressInfo))
+ {
+ errorInfo.ErrorCode = 0x0;
+ errorInfo.ExtraErrorInfo = 0x0;
+
+ return true;
+ }
+ else
+ {
+ errorInfo.ErrorCode = ResultCode.InvalidAddressInfo;
+ errorInfo.ExtraErrorInfo = addressInfo.CpuAddress;
+
+ return _isForceMapEnabled;
+ }
+ }
+
+ /// <summary>
+ /// Update a <see cref="MemoryPoolState"/> using user parameters.
+ /// </summary>
+ /// <param name="memoryPool">The <see cref="MemoryPoolState"/> to update.</param>
+ /// <param name="inParameter">Input user parameter.</param>
+ /// <param name="outStatus">Output user parameter.</param>
+ /// <returns>Returns the <see cref="UpdateResult"/> of the operations performed.</returns>
+ public UpdateResult Update(ref MemoryPoolState memoryPool, ref MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus)
+ {
+ MemoryPoolUserState inputState = inParameter.State;
+
+ MemoryPoolUserState outputState;
+
+ const uint pageSize = 0x1000;
+
+ if (inputState != MemoryPoolUserState.RequestAttach && inputState != MemoryPoolUserState.RequestDetach)
+ {
+ return UpdateResult.Success;
+ }
+
+ if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress & (pageSize - 1)) != 0)
+ {
+ return UpdateResult.InvalidParameter;
+ }
+
+ if (inParameter.Size == 0 || (inParameter.Size & (pageSize - 1)) != 0)
+ {
+ return UpdateResult.InvalidParameter;
+ }
+
+ if (inputState == MemoryPoolUserState.RequestAttach)
+ {
+ bool initializeSuccess = InitializePool(ref memoryPool, inParameter.CpuAddress, inParameter.Size);
+
+ if (!initializeSuccess)
+ {
+ memoryPool.SetCpuAddress(0, 0);
+
+ Logger.Error?.Print(LogClass.AudioRenderer, $"Map of memory pool (address: 0x{inParameter.CpuAddress:x}, size 0x{inParameter.Size:x}) failed!");
+ return UpdateResult.MapError;
+ }
+
+ outputState = MemoryPoolUserState.Attached;
+ }
+ else
+ {
+ if (memoryPool.CpuAddress != inParameter.CpuAddress || memoryPool.Size != inParameter.Size)
+ {
+ return UpdateResult.InvalidParameter;
+ }
+
+ if (!Unmap(ref memoryPool))
+ {
+ Logger.Error?.Print(LogClass.AudioRenderer, $"Unmap of memory pool (address: 0x{memoryPool.CpuAddress:x}, size 0x{memoryPool.Size:x}) failed!");
+ return UpdateResult.UnmapError;
+ }
+
+ outputState = MemoryPoolUserState.Detached;
+ }
+
+ outStatus.State = outputState;
+
+ return UpdateResult.Success;
+ }
+
+ /// <summary>
+ /// Map the <see cref="AddressInfo"/> to the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ /// <param name="addressInfo">The <see cref="AddressInfo"/> to map.</param>
+ /// <returns>Returns true if mapping was performed.</returns>
+ private bool AssignDspAddress(ref AddressInfo addressInfo)
+ {
+ if (addressInfo.CpuAddress == 0)
+ {
+ return false;
+ }
+
+ if (_memoryPools.Length > 0)
+ {
+ Span<MemoryPoolState> memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size);
+
+ if (!memoryPool.IsEmpty)
+ {
+ addressInfo.SetupMemoryPool(memoryPool);
+
+ return true;
+ }
+ }
+
+ if (_isForceMapEnabled)
+ {
+ DspAddress dspAddress = AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size);
+
+ addressInfo.ForceMappedDspAddress = dspAddress;
+
+ AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size);
+ }
+ else
+ {
+ unsafe
+ {
+ addressInfo.SetupMemoryPool(MemoryPoolState.Null);
+ }
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Remove the usage flag from all the <see cref="MemoryPoolState"/>.
+ /// </summary>
+ /// <param name="memoryPool">The <see cref="Memory{MemoryPoolState}"/> to reset.</param>
+ public static void ClearUsageState(Memory<MemoryPoolState> memoryPool)
+ {
+ foreach (ref MemoryPoolState info in memoryPool.Span)
+ {
+ info.IsUsed = false;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs b/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs
new file mode 100644
index 00000000..0e094665
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs
@@ -0,0 +1,276 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Server.Splitter;
+using Ryujinx.Audio.Renderer.Utils;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Server.Mix
+{
+ /// <summary>
+ /// Mix context.
+ /// </summary>
+ public class MixContext
+ {
+ /// <summary>
+ /// The total mix count.
+ /// </summary>
+ private uint _mixesCount;
+
+ /// <summary>
+ /// Storage for <see cref="MixState"/>.
+ /// </summary>
+ private Memory<MixState> _mixes;
+
+ /// <summary>
+ /// Storage of the sorted indices to <see cref="MixState"/>.
+ /// </summary>
+ private Memory<int> _sortedMixes;
+
+ /// <summary>
+ /// Graph state.
+ /// </summary>
+ public NodeStates NodeStates { get; }
+
+ /// <summary>
+ /// The instance of the adjacent matrix.
+ /// </summary>
+ public EdgeMatrix EdgeMatrix { get; }
+
+ /// <summary>
+ /// Create a new instance of <see cref="MixContext"/>.
+ /// </summary>
+ public MixContext()
+ {
+ NodeStates = new NodeStates();
+ EdgeMatrix = new EdgeMatrix();
+ }
+
+ /// <summary>
+ /// Initialize the <see cref="MixContext"/>.
+ /// </summary>
+ /// <param name="sortedMixes">The storage for sorted indices.</param>
+ /// <param name="mixes">The storage of <see cref="MixState"/>.</param>
+ /// <param name="nodeStatesWorkBuffer">The storage used for the <see cref="NodeStates"/>.</param>
+ /// <param name="edgeMatrixWorkBuffer">The storage used for the <see cref="EdgeMatrix"/>.</param>
+ public void Initialize(Memory<int> sortedMixes, Memory<MixState> mixes, Memory<byte> nodeStatesWorkBuffer, Memory<byte> edgeMatrixWorkBuffer)
+ {
+ _mixesCount = (uint)mixes.Length;
+ _mixes = mixes;
+ _sortedMixes = sortedMixes;
+
+ if (!nodeStatesWorkBuffer.IsEmpty && !edgeMatrixWorkBuffer.IsEmpty)
+ {
+ NodeStates.Initialize(nodeStatesWorkBuffer, mixes.Length);
+ EdgeMatrix.Initialize(edgeMatrixWorkBuffer, mixes.Length);
+ }
+
+ int sortedId = 0;
+ for (int i = 0; i < _mixes.Length; i++)
+ {
+ SetSortedState(sortedId++, i);
+ }
+ }
+
+ /// <summary>
+ /// Associate the given <paramref name="targetIndex"/> to a given <paramref cref="id"/>.
+ /// </summary>
+ /// <param name="id">The sorted id.</param>
+ /// <param name="targetIndex">The index to associate.</param>
+ private void SetSortedState(int id, int targetIndex)
+ {
+ _sortedMixes.Span[id] = targetIndex;
+ }
+
+ /// <summary>
+ /// Get a reference to the final <see cref="MixState"/>.
+ /// </summary>
+ /// <returns>A reference to the final <see cref="MixState"/>.</returns>
+ public ref MixState GetFinalState()
+ {
+ return ref GetState(Constants.FinalMixId);
+ }
+
+ /// <summary>
+ /// Get a reference to a <see cref="MixState"/> at the given <paramref name="id"/>.
+ /// </summary>
+ /// <param name="id">The index to use.</param>
+ /// <returns>A reference to a <see cref="MixState"/> at the given <paramref name="id"/>.</returns>
+ public ref MixState GetState(int id)
+ {
+ return ref SpanIOHelper.GetFromMemory(_mixes, id, _mixesCount);
+ }
+
+ /// <summary>
+ /// Get a reference to a <see cref="MixState"/> at the given <paramref name="id"/> of the sorted mix info.
+ /// </summary>
+ /// <param name="id">The index to use.</param>
+ /// <returns>A reference to a <see cref="MixState"/> at the given <paramref name="id"/>.</returns>
+ public ref MixState GetSortedState(int id)
+ {
+ Debug.Assert(id >= 0 && id < _mixesCount);
+
+ return ref GetState(_sortedMixes.Span[id]);
+ }
+
+ /// <summary>
+ /// Get the total mix count.
+ /// </summary>
+ /// <returns>The total mix count.</returns>
+ public uint GetCount()
+ {
+ return _mixesCount;
+ }
+
+ /// <summary>
+ /// Update the internal distance from the final mix value of every <see cref="MixState"/>.
+ /// </summary>
+ private void UpdateDistancesFromFinalMix()
+ {
+ foreach (ref MixState mix in _mixes.Span)
+ {
+ mix.ClearDistanceFromFinalMix();
+ }
+
+ for (int i = 0; i < GetCount(); i++)
+ {
+ ref MixState mix = ref GetState(i);
+
+ SetSortedState(i, i);
+
+ if (mix.IsUsed)
+ {
+ uint distance;
+
+ if (mix.MixId != Constants.FinalMixId)
+ {
+ int mixId = mix.MixId;
+
+ for (distance = 0; distance < GetCount(); distance++)
+ {
+ if (mixId == Constants.UnusedMixId)
+ {
+ distance = MixState.InvalidDistanceFromFinalMix;
+ break;
+ }
+
+ ref MixState distanceMix = ref GetState(mixId);
+
+ if (distanceMix.DistanceFromFinalMix != MixState.InvalidDistanceFromFinalMix)
+ {
+ distance = distanceMix.DistanceFromFinalMix + 1;
+ break;
+ }
+
+ mixId = distanceMix.DestinationMixId;
+
+ if (mixId == Constants.FinalMixId)
+ {
+ break;
+ }
+ }
+
+ if (distance > GetCount())
+ {
+ distance = MixState.InvalidDistanceFromFinalMix;
+ }
+ }
+ else
+ {
+ distance = MixState.InvalidDistanceFromFinalMix;
+ }
+
+ mix.DistanceFromFinalMix = distance;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Update the internal mix buffer offset of all <see cref="MixState"/>.
+ /// </summary>
+ private void UpdateMixBufferOffset()
+ {
+ uint offset = 0;
+
+ foreach (ref MixState mix in _mixes.Span)
+ {
+ mix.BufferOffset = offset;
+
+ offset += mix.BufferCount;
+ }
+ }
+
+ /// <summary>
+ /// Sort the mixes using distance from the final mix.
+ /// </summary>
+ public void Sort()
+ {
+ UpdateDistancesFromFinalMix();
+
+ int[] sortedMixesTemp = _sortedMixes.Slice(0, (int)GetCount()).ToArray();
+
+ Array.Sort(sortedMixesTemp, (a, b) =>
+ {
+ ref MixState stateA = ref GetState(a);
+ ref MixState stateB = ref GetState(b);
+
+ return stateB.DistanceFromFinalMix.CompareTo(stateA.DistanceFromFinalMix);
+ });
+
+ sortedMixesTemp.AsSpan().CopyTo(_sortedMixes.Span);
+
+ UpdateMixBufferOffset();
+ }
+
+ /// <summary>
+ /// Sort the mixes and splitters using an adjacency matrix.
+ /// </summary>
+ /// <param name="splitterContext">The <see cref="SplitterContext"/> used.</param>
+ /// <returns>Return true, if no errors in the graph were detected.</returns>
+ public bool Sort(SplitterContext splitterContext)
+ {
+ if (splitterContext.UsingSplitter())
+ {
+ bool isValid = NodeStates.Sort(EdgeMatrix);
+
+ if (isValid)
+ {
+ ReadOnlySpan<int> sortedMixesIndex = NodeStates.GetTsortResult();
+
+ int id = 0;
+
+ for (int i = sortedMixesIndex.Length - 1; i >= 0; i--)
+ {
+ SetSortedState(id++, sortedMixesIndex[i]);
+ }
+
+ UpdateMixBufferOffset();
+ }
+
+ return isValid;
+ }
+ else
+ {
+ UpdateMixBufferOffset();
+
+ return true;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs b/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
new file mode 100644
index 00000000..028d0885
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
@@ -0,0 +1,330 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Server.Effect;
+using Ryujinx.Audio.Renderer.Server.Splitter;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using static Ryujinx.Audio.Constants;
+
+namespace Ryujinx.Audio.Renderer.Server.Mix
+{
+ /// <summary>
+ /// Server state for a mix.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 0x940, Pack = Alignment)]
+ public struct MixState
+ {
+ public const uint InvalidDistanceFromFinalMix = 0x80000000;
+
+ public const int Alignment = 0x10;
+
+ /// <summary>
+ /// Base volume of the mix.
+ /// </summary>
+ public float Volume;
+
+ /// <summary>
+ /// Target sample rate of the mix.
+ /// </summary>
+ public uint SampleRate;
+
+ /// <summary>
+ /// Target buffer count.
+ /// </summary>
+ public uint BufferCount;
+
+ /// <summary>
+ /// Set to true if in use.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ /// <summary>
+ /// The id of the mix.
+ /// </summary>
+ public int MixId;
+
+ /// <summary>
+ /// The mix node id.
+ /// </summary>
+ public int NodeId;
+
+ /// <summary>
+ /// the buffer offset to use for command generation.
+ /// </summary>
+ public uint BufferOffset;
+
+ /// <summary>
+ /// The distance of the mix from the final mix.
+ /// </summary>
+ public uint DistanceFromFinalMix;
+
+ /// <summary>
+ /// The effect processing order storage.
+ /// </summary>
+ private IntPtr _effectProcessingOrderArrayPointer;
+
+ /// <summary>
+ /// The max element count that can be found in the effect processing order storage.
+ /// </summary>
+ public uint EffectProcessingOrderArrayMaxCount;
+
+ /// <summary>
+ /// The mix to output the result of this mix.
+ /// </summary>
+ public int DestinationMixId;
+
+ /// <summary>
+ /// Mix buffer volumes storage.
+ /// </summary>
+ private MixVolumeArray _mixVolumeArray;
+
+ /// <summary>
+ /// The splitter to output the result of this mix.
+ /// </summary>
+ public uint DestinationSplitterId;
+
+ /// <summary>
+ /// If set to true, the long size pre-delay is supported on the reverb command.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsLongSizePreDelaySupported;
+
+ [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
+ private struct MixVolumeArray
+ {
+ private const int Size = 4 * MixBufferCountMax * MixBufferCountMax;
+ }
+
+ /// <summary>
+ /// Mix buffer volumes.
+ /// </summary>
+ /// <remarks>Used when no splitter id is specified.</remarks>
+ public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixVolumeArray, float>(ref _mixVolumeArray);
+
+ /// <summary>
+ /// Get the volume for a given connection destination.
+ /// </summary>
+ /// <param name="sourceIndex">The source node index.</param>
+ /// <param name="destinationIndex">The destination node index</param>
+ /// <returns>The volume for the given connection destination.</returns>
+ public float GetMixBufferVolume(int sourceIndex, int destinationIndex)
+ {
+ return MixBufferVolume[sourceIndex * MixBufferCountMax + destinationIndex];
+ }
+
+ /// <summary>
+ /// The array used to order effects associated to this mix.
+ /// </summary>
+ public Span<int> EffectProcessingOrderArray
+ {
+ get
+ {
+ if (_effectProcessingOrderArrayPointer == IntPtr.Zero)
+ {
+ return Span<int>.Empty;
+ }
+
+ unsafe
+ {
+ return new Span<int>((void*)_effectProcessingOrderArrayPointer, (int)EffectProcessingOrderArrayMaxCount);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Create a new <see cref="MixState"/>
+ /// </summary>
+ /// <param name="effectProcessingOrderArray"></param>
+ /// <param name="behaviourContext"></param>
+ public MixState(Memory<int> effectProcessingOrderArray, ref BehaviourContext behaviourContext) : this()
+ {
+ MixId = UnusedMixId;
+
+ DistanceFromFinalMix = InvalidDistanceFromFinalMix;
+
+ DestinationMixId = UnusedMixId;
+
+ DestinationSplitterId = UnusedSplitterId;
+
+ unsafe
+ {
+ // SAFETY: safe as effectProcessingOrderArray comes from the work buffer memory that is pinned.
+ _effectProcessingOrderArrayPointer = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetReference(effectProcessingOrderArray.Span));
+ }
+
+ EffectProcessingOrderArrayMaxCount = (uint)effectProcessingOrderArray.Length;
+
+ IsLongSizePreDelaySupported = behaviourContext.IsLongSizePreDelaySupported();
+
+ ClearEffectProcessingOrder();
+ }
+
+ /// <summary>
+ /// Clear the <see cref="DistanceFromFinalMix"/> value to its default state.
+ /// </summary>
+ public void ClearDistanceFromFinalMix()
+ {
+ DistanceFromFinalMix = InvalidDistanceFromFinalMix;
+ }
+
+ /// <summary>
+ /// Clear the <see cref="EffectProcessingOrderArray"/> to its default state.
+ /// </summary>
+ public void ClearEffectProcessingOrder()
+ {
+ EffectProcessingOrderArray.Fill(-1);
+ }
+
+ /// <summary>
+ /// Return true if the mix has any destinations.
+ /// </summary>
+ /// <returns>True if the mix has any destinations.</returns>
+ public bool HasAnyDestination()
+ {
+ return DestinationMixId != UnusedMixId || DestinationSplitterId != UnusedSplitterId;
+ }
+
+ /// <summary>
+ /// Update the mix connection on the adjacency matrix.
+ /// </summary>
+ /// <param name="edgeMatrix">The adjacency matrix.</param>
+ /// <param name="parameter">The input parameter of the mix.</param>
+ /// <param name="splitterContext">The splitter context.</param>
+ /// <returns>Return true, new connections were done on the adjacency matrix.</returns>
+ private bool UpdateConnection(EdgeMatrix edgeMatrix, ref MixParameter parameter, ref SplitterContext splitterContext)
+ {
+ bool hasNewConnections;
+
+ if (DestinationSplitterId == UnusedSplitterId)
+ {
+ hasNewConnections = false;
+ }
+ else
+ {
+ ref SplitterState splitter = ref splitterContext.GetState((int)DestinationSplitterId);
+
+ hasNewConnections = splitter.HasNewConnection;
+ }
+
+ if (DestinationMixId == parameter.DestinationMixId && DestinationSplitterId == parameter.DestinationSplitterId && !hasNewConnections)
+ {
+ return false;
+ }
+
+ edgeMatrix.RemoveEdges(MixId);
+
+ if (parameter.DestinationMixId == UnusedMixId)
+ {
+ if (parameter.DestinationSplitterId != UnusedSplitterId)
+ {
+ ref SplitterState splitter = ref splitterContext.GetState((int)parameter.DestinationSplitterId);
+
+ for (int i = 0; i < splitter.DestinationCount; i++)
+ {
+ Span<SplitterDestination> destination = splitter.GetData(i);
+
+ if (!destination.IsEmpty)
+ {
+ int destinationMixId = destination[0].DestinationId;
+
+ if (destinationMixId != UnusedMixId)
+ {
+ edgeMatrix.Connect(MixId, destinationMixId);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ edgeMatrix.Connect(MixId, parameter.DestinationMixId);
+ }
+
+ DestinationMixId = parameter.DestinationMixId;
+ DestinationSplitterId = parameter.DestinationSplitterId;
+
+ return true;
+ }
+
+ /// <summary>
+ /// Update the mix from user information.
+ /// </summary>
+ /// <param name="edgeMatrix">The adjacency matrix.</param>
+ /// <param name="parameter">The input parameter of the mix.</param>
+ /// <param name="effectContext">The effect context.</param>
+ /// <param name="splitterContext">The splitter context.</param>
+ /// <param name="behaviourContext">The behaviour context.</param>
+ /// <returns>Return true if the mix was changed.</returns>
+ public bool Update(EdgeMatrix edgeMatrix, ref MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext)
+ {
+ bool isDirty;
+
+ Volume = parameter.Volume;
+ SampleRate = parameter.SampleRate;
+ BufferCount = parameter.BufferCount;
+ IsUsed = parameter.IsUsed;
+ MixId = parameter.MixId;
+ NodeId = parameter.NodeId;
+ parameter.MixBufferVolume.CopyTo(MixBufferVolume);
+
+ if (behaviourContext.IsSplitterSupported())
+ {
+ isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext);
+ }
+ else
+ {
+ isDirty = DestinationMixId != parameter.DestinationMixId;
+
+ if (DestinationMixId != parameter.DestinationMixId)
+ {
+ DestinationMixId = parameter.DestinationMixId;
+ }
+
+ DestinationSplitterId = UnusedSplitterId;
+ }
+
+ ClearEffectProcessingOrder();
+
+ for (int i = 0; i < effectContext.GetCount(); i++)
+ {
+ ref BaseEffect effect = ref effectContext.GetEffect(i);
+
+ if (effect.MixId == MixId)
+ {
+ Debug.Assert(effect.ProcessingOrder <= EffectProcessingOrderArrayMaxCount);
+
+ if (effect.ProcessingOrder > EffectProcessingOrderArrayMaxCount)
+ {
+ return isDirty;
+ }
+
+ EffectProcessingOrderArray[(int)effect.ProcessingOrder] = i;
+ }
+ }
+
+ return isDirty;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs b/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs
new file mode 100644
index 00000000..893cc008
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs
@@ -0,0 +1,69 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+
+namespace Ryujinx.Audio.Renderer.Server.Performance
+{
+ /// <summary>
+ /// Represents a detailed entry in a performance frame.
+ /// </summary>
+ public interface IPerformanceDetailEntry
+ {
+ /// <summary>
+ /// Get the start time of this entry event (in microseconds).
+ /// </summary>
+ /// <returns>The start time of this entry event (in microseconds).</returns>
+ int GetStartTime();
+
+ /// <summary>
+ /// Get the start time offset in this structure.
+ /// </summary>
+ /// <returns>The start time offset in this structure.</returns>
+ int GetStartTimeOffset();
+
+ /// <summary>
+ /// Get the processing time of this entry event (in microseconds).
+ /// </summary>
+ /// <returns>The processing time of this entry event (in microseconds).</returns>
+ int GetProcessingTime();
+
+ /// <summary>
+ /// Get the processing time offset in this structure.
+ /// </summary>
+ /// <returns>The processing time offset in this structure.</returns>
+ int GetProcessingTimeOffset();
+
+ /// <summary>
+ /// Set the <paramref name="nodeId"/> of this entry.
+ /// </summary>
+ /// <param name="nodeId">The node id of this entry.</param>
+ void SetNodeId(int nodeId);
+
+ /// <summary>
+ /// Set the <see cref="PerformanceEntryType"/> of this entry.
+ /// </summary>
+ /// <param name="type">The type to use.</param>
+ void SetEntryType(PerformanceEntryType type);
+
+ /// <summary>
+ /// Set the <see cref="PerformanceDetailType"/> of this entry.
+ /// </summary>
+ /// <param name="detailType">The type to use.</param>
+ void SetDetailType(PerformanceDetailType detailType);
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs b/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs
new file mode 100644
index 00000000..2a2fa9cc
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs
@@ -0,0 +1,63 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+
+namespace Ryujinx.Audio.Renderer.Server.Performance
+{
+ /// <summary>
+ /// Represents an entry in a performance frame.
+ /// </summary>
+ public interface IPerformanceEntry
+ {
+ /// <summary>
+ /// Get the start time of this entry event (in microseconds).
+ /// </summary>
+ /// <returns>The start time of this entry event (in microseconds).</returns>
+ int GetStartTime();
+
+ /// <summary>
+ /// Get the start time offset in this structure.
+ /// </summary>
+ /// <returns>The start time offset in this structure.</returns>
+ int GetStartTimeOffset();
+
+ /// <summary>
+ /// Get the processing time of this entry event (in microseconds).
+ /// </summary>
+ /// <returns>The processing time of this entry event (in microseconds).</returns>
+ int GetProcessingTime();
+
+ /// <summary>
+ /// Get the processing time offset in this structure.
+ /// </summary>
+ /// <returns>The processing time offset in this structure.</returns>
+ int GetProcessingTimeOffset();
+
+ /// <summary>
+ /// Set the <paramref name="nodeId"/> of this entry.
+ /// </summary>
+ /// <param name="nodeId">The node id of this entry.</param>
+ void SetNodeId(int nodeId);
+
+ /// <summary>
+ /// Set the <see cref="PerformanceEntryType"/> of this entry.
+ /// </summary>
+ /// <param name="type">The type to use.</param>
+ void SetEntryType(PerformanceEntryType type);
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs b/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs
new file mode 100644
index 00000000..5a26754d
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs
@@ -0,0 +1,97 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Server.Performance
+{
+ /// <summary>
+ /// The header of a performance frame.
+ /// </summary>
+ public interface IPerformanceHeader
+ {
+ /// <summary>
+ /// Get the entry count offset in this structure.
+ /// </summary>
+ /// <returns>The entry count offset in this structure.</returns>
+ int GetEntryCountOffset();
+
+ /// <summary>
+ /// Set the DSP running behind flag.
+ /// </summary>
+ /// <param name="isRunningBehind">The flag.</param>
+ void SetDspRunningBehind(bool isRunningBehind);
+
+ /// <summary>
+ /// Set the count of voices that were dropped.
+ /// </summary>
+ /// <param name="voiceCount">The count of voices that were dropped.</param>
+ void SetVoiceDropCount(uint voiceCount);
+
+ /// <summary>
+ /// Set the start ticks of the <see cref="Dsp.AudioProcessor"/>. (before sending commands)
+ /// </summary>
+ /// <param name="startTicks">The start ticks of the <see cref="Dsp.AudioProcessor"/>. (before sending commands)</param>
+ void SetStartRenderingTicks(ulong startTicks);
+
+ /// <summary>
+ /// Set the header magic.
+ /// </summary>
+ /// <param name="magic">The header magic.</param>
+ void SetMagic(uint magic);
+
+ /// <summary>
+ /// Set the offset of the next performance header.
+ /// </summary>
+ /// <param name="nextOffset">The offset of the next performance header.</param>
+ void SetNextOffset(int nextOffset);
+
+ /// <summary>
+ /// Set the total time taken by all the commands profiled.
+ /// </summary>
+ /// <param name="totalProcessingTime">The total time taken by all the commands profiled.</param>
+ void SetTotalProcessingTime(int totalProcessingTime);
+
+ /// <summary>
+ /// Set the index of this performance frame.
+ /// </summary>
+ /// <param name="index">The index of this performance frame.</param>
+ void SetIndex(uint index);
+
+ /// <summary>
+ /// Get the total count of entries in this frame.
+ /// </summary>
+ /// <returns>The total count of entries in this frame.</returns>
+ int GetEntryCount();
+
+ /// <summary>
+ /// Get the total count of detailed entries in this frame.
+ /// </summary>
+ /// <returns>The total count of detailed entries in this frame.</returns>
+ int GetEntryDetailCount();
+
+ /// <summary>
+ /// Set the total count of entries in this frame.
+ /// </summary>
+ /// <param name="entryCount">The total count of entries in this frame.</param>
+ void SetEntryCount(int entryCount);
+
+ /// <summary>
+ /// Set the total count of detailed entries in this frame.
+ /// </summary>
+ /// <param name="entryDetailCount">The total count of detailed entries in this frame.</param>
+ void SetEntryDetailCount(int entryDetailCount);
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs
new file mode 100644
index 00000000..b7df7f28
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs
@@ -0,0 +1,89 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Performance
+{
+ /// <summary>
+ /// Implementation of <see cref="IPerformanceDetailEntry"/> for performance metrics version 1.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
+ public struct PerformanceDetailVersion1 : IPerformanceDetailEntry
+ {
+ /// <summary>
+ /// The node id associated to this detailed entry.
+ /// </summary>
+ public int NodeId;
+
+ /// <summary>
+ /// The start time (in microseconds) associated to this detailed entry.
+ /// </summary>
+ public int StartTime;
+
+ /// <summary>
+ /// The processing time (in microseconds) associated to this detailed entry.
+ /// </summary>
+ public int ProcessingTime;
+
+ /// <summary>
+ /// The detailed entry type associated to this detailed entry.
+ /// </summary>
+ public PerformanceDetailType DetailType;
+
+ /// <summary>
+ /// The entry type associated to this detailed entry.
+ /// </summary>
+ public PerformanceEntryType EntryType;
+
+ public int GetProcessingTime()
+ {
+ return ProcessingTime;
+ }
+
+ public int GetProcessingTimeOffset()
+ {
+ return 8;
+ }
+
+ public int GetStartTime()
+ {
+ return StartTime;
+ }
+
+ public int GetStartTimeOffset()
+ {
+ return 4;
+ }
+
+ public void SetDetailType(PerformanceDetailType detailType)
+ {
+ DetailType = detailType;
+ }
+
+ public void SetEntryType(PerformanceEntryType type)
+ {
+ EntryType = type;
+ }
+
+ public void SetNodeId(int nodeId)
+ {
+ NodeId = nodeId;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs
new file mode 100644
index 00000000..da898b29
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs
@@ -0,0 +1,89 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Performance
+{
+ /// <summary>
+ /// Implementation of <see cref="IPerformanceDetailEntry"/> for performance metrics version 2.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)]
+ public struct PerformanceDetailVersion2 : IPerformanceDetailEntry
+ {
+ /// <summary>
+ /// The node id associated to this detailed entry.
+ /// </summary>
+ public int NodeId;
+
+ /// <summary>
+ /// The start time (in microseconds) associated to this detailed entry.
+ /// </summary>
+ public int StartTime;
+
+ /// <summary>
+ /// The processing time (in microseconds) associated to this detailed entry.
+ /// </summary>
+ public int ProcessingTime;
+
+ /// <summary>
+ /// The detailed entry type associated to this detailed entry.
+ /// </summary>
+ public PerformanceDetailType DetailType;
+
+ /// <summary>
+ /// The entry type associated to this detailed entry.
+ /// </summary>
+ public PerformanceEntryType EntryType;
+
+ public int GetProcessingTime()
+ {
+ return ProcessingTime;
+ }
+
+ public int GetProcessingTimeOffset()
+ {
+ return 8;
+ }
+
+ public int GetStartTime()
+ {
+ return StartTime;
+ }
+
+ public int GetStartTimeOffset()
+ {
+ return 4;
+ }
+
+ public void SetDetailType(PerformanceDetailType detailType)
+ {
+ DetailType = detailType;
+ }
+
+ public void SetEntryType(PerformanceEntryType type)
+ {
+ EntryType = type;
+ }
+
+ public void SetNodeId(int nodeId)
+ {
+ NodeId = nodeId;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs
new file mode 100644
index 00000000..ad99867f
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs
@@ -0,0 +1,73 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+
+namespace Ryujinx.Audio.Renderer.Server.Performance
+{
+ /// <summary>
+ /// Information used by the performance command to store informations in the performance entry.
+ /// </summary>
+ public class PerformanceEntryAddresses
+ {
+ /// <summary>
+ /// The memory storing the performance entry.
+ /// </summary>
+ public Memory<int> BaseMemory;
+
+ /// <summary>
+ /// The offset to the start time field.
+ /// </summary>
+ public uint StartTimeOffset;
+
+ /// <summary>
+ /// The offset to the entry count field.
+ /// </summary>
+ public uint EntryCountOffset;
+
+ /// <summary>
+ /// The offset to the processing time field.
+ /// </summary>
+ public uint ProcessingTimeOffset;
+
+ /// <summary>
+ /// Increment the entry count.
+ /// </summary>
+ public void IncrementEntryCount()
+ {
+ BaseMemory.Span[(int)EntryCountOffset / 4]++;
+ }
+
+ /// <summary>
+ /// Set the start time in the entry.
+ /// </summary>
+ /// <param name="startTimeNano">The start time in nanoseconds.</param>
+ public void SetStartTime(ulong startTimeNano)
+ {
+ BaseMemory.Span[(int)StartTimeOffset / 4] = (int)(startTimeNano / 1000);
+ }
+
+ /// <summary>
+ /// Set the processing time in the entry.
+ /// </summary>
+ /// <param name="endTimeNano">The end time in nanoseconds.</param>
+ public void SetProcessingTime(ulong endTimeNano)
+ {
+ BaseMemory.Span[(int)ProcessingTimeOffset / 4] = (int)(endTimeNano / 1000) - BaseMemory.Span[(int)StartTimeOffset / 4];
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs
new file mode 100644
index 00000000..ac91ca19
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs
@@ -0,0 +1,79 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Performance
+{
+ /// <summary>
+ /// Implementation of <see cref="IPerformanceEntry"/> for performance metrics version 1.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
+ public struct PerformanceEntryVersion1 : IPerformanceEntry
+ {
+ /// <summary>
+ /// The node id associated to this entry.
+ /// </summary>
+ public int NodeId;
+
+ /// <summary>
+ /// The start time (in microseconds) associated to this entry.
+ /// </summary>
+ public int StartTime;
+
+ /// <summary>
+ /// The processing time (in microseconds) associated to this entry.
+ /// </summary>
+ public int ProcessingTime;
+
+ /// <summary>
+ /// The entry type associated to this entry.
+ /// </summary>
+ public PerformanceEntryType EntryType;
+
+ public int GetProcessingTime()
+ {
+ return ProcessingTime;
+ }
+
+ public int GetProcessingTimeOffset()
+ {
+ return 8;
+ }
+
+ public int GetStartTime()
+ {
+ return StartTime;
+ }
+
+ public int GetStartTimeOffset()
+ {
+ return 4;
+ }
+
+ public void SetEntryType(PerformanceEntryType type)
+ {
+ EntryType = type;
+ }
+
+ public void SetNodeId(int nodeId)
+ {
+ NodeId = nodeId;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs
new file mode 100644
index 00000000..84ee5bc9
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs
@@ -0,0 +1,79 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Performance
+{
+ /// <summary>
+ /// Implementation of <see cref="IPerformanceEntry"/> for performance metrics version 2.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)]
+ public struct PerformanceEntryVersion2 : IPerformanceEntry
+ {
+ /// <summary>
+ /// The node id associated to this entry.
+ /// </summary>
+ public int NodeId;
+
+ /// <summary>
+ /// The start time (in microseconds) associated to this entry.
+ /// </summary>
+ public int StartTime;
+
+ /// <summary>
+ /// The processing time (in microseconds) associated to this entry.
+ /// </summary>
+ public int ProcessingTime;
+
+ /// <summary>
+ /// The entry type associated to this entry.
+ /// </summary>
+ public PerformanceEntryType EntryType;
+
+ public int GetProcessingTime()
+ {
+ return ProcessingTime;
+ }
+
+ public int GetProcessingTimeOffset()
+ {
+ return 8;
+ }
+
+ public int GetStartTime()
+ {
+ return StartTime;
+ }
+
+ public int GetStartTimeOffset()
+ {
+ return 4;
+ }
+
+ public void SetEntryType(PerformanceEntryType type)
+ {
+ EntryType = type;
+ }
+
+ public void SetNodeId(int nodeId)
+ {
+ NodeId = nodeId;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs
new file mode 100644
index 00000000..b0757b37
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs
@@ -0,0 +1,118 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Performance
+{
+ /// <summary>
+ /// Implementation of <see cref="IPerformanceHeader"/> for performance metrics version 1.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)]
+ public struct PerformanceFrameHeaderVersion1 : IPerformanceHeader
+ {
+ /// <summary>
+ /// The magic of the performance header.
+ /// </summary>
+ public uint Magic;
+
+ /// <summary>
+ /// The total count of entries in this frame.
+ /// </summary>
+ public int EntryCount;
+
+ /// <summary>
+ /// The total count of detailed entries in this frame.
+ /// </summary>
+ public int EntryDetailCount;
+
+ /// <summary>
+ /// The offset of the next performance header.
+ /// </summary>
+ public int NextOffset;
+
+ /// <summary>
+ /// The total time taken by all the commands profiled.
+ /// </summary>
+ public int TotalProcessingTime;
+
+ /// <summary>
+ /// The count of voices that were dropped.
+ /// </summary>
+ public uint VoiceDropCount;
+
+ public int GetEntryCount()
+ {
+ return EntryCount;
+ }
+
+ public int GetEntryCountOffset()
+ {
+ return 4;
+ }
+
+ public int GetEntryDetailCount()
+ {
+ return EntryDetailCount;
+ }
+
+ public void SetDspRunningBehind(bool isRunningBehind)
+ {
+ // NOTE: Not present in version 1
+ }
+
+ public void SetEntryCount(int entryCount)
+ {
+ EntryCount = entryCount;
+ }
+
+ public void SetEntryDetailCount(int entryDetailCount)
+ {
+ EntryDetailCount = entryDetailCount;
+ }
+
+ public void SetIndex(uint index)
+ {
+ // NOTE: Not present in version 1
+ }
+
+ public void SetMagic(uint magic)
+ {
+ Magic = magic;
+ }
+
+ public void SetNextOffset(int nextOffset)
+ {
+ NextOffset = nextOffset;
+ }
+
+ public void SetStartRenderingTicks(ulong startTicks)
+ {
+ // NOTE: not present in version 1
+ }
+
+ public void SetTotalProcessingTime(int totalProcessingTime)
+ {
+ TotalProcessingTime = totalProcessingTime;
+ }
+
+ public void SetVoiceDropCount(uint voiceCount)
+ {
+ VoiceDropCount = voiceCount;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs
new file mode 100644
index 00000000..ba873239
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs
@@ -0,0 +1,134 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Performance
+{
+ /// <summary>
+ /// Implementation of <see cref="IPerformanceHeader"/> for performance metrics version 2.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x30)]
+ public struct PerformanceFrameHeaderVersion2 : IPerformanceHeader
+ {
+ /// <summary>
+ /// The magic of the performance header.
+ /// </summary>
+ public uint Magic;
+
+ /// <summary>
+ /// The total count of entries in this frame.
+ /// </summary>
+ public int EntryCount;
+
+ /// <summary>
+ /// The total count of detailed entries in this frame.
+ /// </summary>
+ public int EntryDetailCount;
+
+ /// <summary>
+ /// The offset of the next performance header.
+ /// </summary>
+ public int NextOffset;
+
+ /// <summary>
+ /// The total time taken by all the commands profiled.
+ /// </summary>
+ public int TotalProcessingTime;
+
+ /// <summary>
+ /// The count of voices that were dropped.
+ /// </summary>
+ public uint VoiceDropCount;
+
+ /// <summary>
+ /// The start ticks of the <see cref="Dsp.AudioProcessor"/>. (before sending commands)
+ /// </summary>
+ public ulong StartRenderingTicks;
+
+ /// <summary>
+ /// The index of this performance frame.
+ /// </summary>
+ public uint Index;
+
+ /// <summary>
+ /// If set to true, the DSP is running behind.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsDspRunningBehind;
+
+ public int GetEntryCount()
+ {
+ return EntryCount;
+ }
+
+ public int GetEntryCountOffset()
+ {
+ return 4;
+ }
+
+ public int GetEntryDetailCount()
+ {
+ return EntryDetailCount;
+ }
+
+ public void SetDspRunningBehind(bool isRunningBehind)
+ {
+ IsDspRunningBehind = isRunningBehind;
+ }
+
+ public void SetEntryCount(int entryCount)
+ {
+ EntryCount = entryCount;
+ }
+
+ public void SetEntryDetailCount(int entryDetailCount)
+ {
+ EntryDetailCount = entryDetailCount;
+ }
+
+ public void SetIndex(uint index)
+ {
+ Index = index;
+ }
+
+ public void SetMagic(uint magic)
+ {
+ Magic = magic;
+ }
+
+ public void SetNextOffset(int nextOffset)
+ {
+ NextOffset = nextOffset;
+ }
+
+ public void SetStartRenderingTicks(ulong startTicks)
+ {
+ StartRenderingTicks = startTicks;
+ }
+
+ public void SetTotalProcessingTime(int totalProcessingTime)
+ {
+ TotalProcessingTime = totalProcessingTime;
+ }
+
+ public void SetVoiceDropCount(uint voiceCount)
+ {
+ VoiceDropCount = voiceCount;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs
new file mode 100644
index 00000000..3a336af3
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs
@@ -0,0 +1,124 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Parameter;
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Performance
+{
+ public abstract class PerformanceManager
+ {
+ /// <summary>
+ /// Get the required size for a single performance frame.
+ /// </summary>
+ /// <param name="parameter">The audio renderer configuration.</param>
+ /// <param name="behaviourContext">The behaviour context.</param>
+ /// <returns>The required size for a single performance frame.</returns>
+ public static ulong GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref AudioRendererConfiguration parameter, ref BehaviourContext behaviourContext)
+ {
+ uint version = behaviourContext.GetPerformanceMetricsDataFormat();
+
+ if (version == 2)
+ {
+ return (ulong)PerformanceManagerGeneric<PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2,
+ PerformanceDetailVersion2>.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
+ }
+ else if (version == 1)
+ {
+ return (ulong)PerformanceManagerGeneric<PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1,
+ PerformanceDetailVersion1>.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
+ }
+
+ throw new NotImplementedException($"Unknown Performance metrics data format version {version}");
+ }
+
+ /// <summary>
+ /// Copy the performance frame history to the supplied user buffer and returns the size copied.
+ /// </summary>
+ /// <param name="performanceOutput">The supplied user buffer to store the performance frame into.</param>
+ /// <returns>The size copied to the supplied buffer.</returns>
+ public abstract uint CopyHistories(Span<byte> performanceOutput);
+
+ /// <summary>
+ /// Set the target node id to profile.
+ /// </summary>
+ /// <param name="target">The target node id to profile.</param>
+ public abstract void SetTargetNodeId(int target);
+
+ /// <summary>
+ /// Check if the given target node id is profiled.
+ /// </summary>
+ /// <param name="target">The target node id to check.</param>
+ /// <returns>Return true, if the given target node id is profiled.</returns>
+ public abstract bool IsTargetNodeId(int target);
+
+ /// <summary>
+ /// Get the next buffer to store a performance entry.
+ /// </summary>
+ /// <param name="performanceEntry">The output <see cref="PerformanceEntryAddresses"/>.</param>
+ /// <param name="entryType">The <see cref="PerformanceEntryType"/> info.</param>
+ /// <param name="nodeId">The node id of the entry.</param>
+ /// <returns>Return true, if a valid <see cref="PerformanceEntryAddresses"/> was returned.</returns>
+ public abstract bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceEntryType entryType, int nodeId);
+
+ /// <summary>
+ /// Get the next buffer to store a performance detailed entry.
+ /// </summary>
+ /// <param name="performanceEntry">The output <see cref="PerformanceEntryAddresses"/>.</param>
+ /// <param name="detailType">The <see cref="PerformanceDetailType"/> info.</param>
+ /// <param name="entryType">The <see cref="PerformanceEntryType"/> info.</param>
+ /// <param name="nodeId">The node id of the entry.</param>
+ /// <returns>Return true, if a valid <see cref="PerformanceEntryAddresses"/> was returned.</returns>
+ public abstract bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceDetailType detailType, PerformanceEntryType entryType, int nodeId);
+
+ /// <summary>
+ /// Finalize the current performance frame.
+ /// </summary>
+ /// <param name="dspRunningBehind">Indicate if the DSP is running behind.</param>
+ /// <param name="voiceDropCount">The count of voices that were dropped.</param>
+ /// <param name="startRenderingTicks">The start ticks of the audio rendering.</param>
+ public abstract void TapFrame(bool dspRunningBehind, uint voiceDropCount, ulong startRenderingTicks);
+
+ /// <summary>
+ /// Create a new <see cref="PerformanceManager"/>.
+ /// </summary>
+ /// <param name="performanceBuffer">The backing memory available for use by the manager.</param>
+ /// <param name="parameter">The audio renderer configuration.</param>
+ /// <param name="behaviourContext">The behaviour context;</param>
+ /// <returns>A new <see cref="PerformanceManager"/>.</returns>
+ public static PerformanceManager Create(Memory<byte> performanceBuffer, ref AudioRendererConfiguration parameter, BehaviourContext behaviourContext)
+ {
+ uint version = behaviourContext.GetPerformanceMetricsDataFormat();
+
+ switch (version)
+ {
+ case 1:
+ return new PerformanceManagerGeneric<PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, PerformanceDetailVersion1>(performanceBuffer,
+ ref parameter);
+ case 2:
+ return new PerformanceManagerGeneric<PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, PerformanceDetailVersion2>(performanceBuffer,
+ ref parameter);
+ default:
+ throw new NotImplementedException($"Unknown Performance metrics data format version {version}");
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs
new file mode 100644
index 00000000..b17e6f3f
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs
@@ -0,0 +1,311 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Utils;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Performance
+{
+ /// <summary>
+ /// A Generic implementation of <see cref="PerformanceManager"/>.
+ /// </summary>
+ /// <typeparam name="THeader">The header implementation of the performance frame.</typeparam>
+ /// <typeparam name="TEntry">The entry implementation of the performance frame.</typeparam>
+ /// <typeparam name="TEntryDetail">A detailed implementation of the performance frame.</typeparam>
+ public class PerformanceManagerGeneric<THeader, TEntry, TEntryDetail> : PerformanceManager where THeader: unmanaged, IPerformanceHeader where TEntry : unmanaged, IPerformanceEntry where TEntryDetail: unmanaged, IPerformanceDetailEntry
+ {
+ /// <summary>
+ /// The magic used for the <see cref="THeader"/>.
+ /// </summary>
+ private const uint MagicPerformanceBuffer = 0x46524550;
+
+ /// <summary>
+ /// The fixed amount of <see cref="TEntryDetail"/> that can be stored in a frame.
+ /// </summary>
+ private const int MaxFrameDetailCount = 100;
+
+ private Memory<byte> _buffer;
+ private Memory<byte> _historyBuffer;
+
+ private Memory<byte> CurrentBuffer => _buffer.Slice(0, _frameSize);
+ private Memory<byte> CurrentBufferData => CurrentBuffer.Slice(Unsafe.SizeOf<THeader>());
+
+ private ref THeader CurrentHeader => ref MemoryMarshal.Cast<byte, THeader>(CurrentBuffer.Span)[0];
+
+ private Span<TEntry> Entries => MemoryMarshal.Cast<byte, TEntry>(CurrentBufferData.Span.Slice(0, GetEntriesSize()));
+ private Span<TEntryDetail> EntriesDetail => MemoryMarshal.Cast<byte, TEntryDetail>(CurrentBufferData.Span.Slice(GetEntriesSize(), GetEntriesDetailSize()));
+
+ private int _frameSize;
+ private int _availableFrameCount;
+ private int _entryCountPerFrame;
+ private int _detailTarget;
+ private int _entryIndex;
+ private int _entryDetailIndex;
+ private int _indexHistoryWrite;
+ private int _indexHistoryRead;
+ private uint _historyFrameIndex;
+
+ public PerformanceManagerGeneric(Memory<byte> buffer, ref AudioRendererConfiguration parameter)
+ {
+ _buffer = buffer;
+ _frameSize = GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
+
+ _entryCountPerFrame = (int)GetEntryCount(ref parameter);
+ _availableFrameCount = buffer.Length / _frameSize - 1;
+
+ _historyFrameIndex = 0;
+
+ _historyBuffer = _buffer.Slice(_frameSize);
+
+ SetupNewHeader();
+ }
+
+ private Span<byte> GetBufferFromIndex(Span<byte> data, int index)
+ {
+ return data.Slice(index * _frameSize, _frameSize);
+ }
+
+ private ref THeader GetHeaderFromBuffer(Span<byte> data, int index)
+ {
+ return ref MemoryMarshal.Cast<byte, THeader>(GetBufferFromIndex(data, index))[0];
+ }
+
+ private Span<TEntry> GetEntriesFromBuffer(Span<byte> data, int index)
+ {
+ return MemoryMarshal.Cast<byte, TEntry>(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf<THeader>(), GetEntriesSize()));
+ }
+
+ private Span<TEntryDetail> GetEntriesDetailFromBuffer(Span<byte> data, int index)
+ {
+ return MemoryMarshal.Cast<byte, TEntryDetail>(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf<THeader>() + GetEntriesSize(), GetEntriesDetailSize()));
+ }
+
+ private void SetupNewHeader()
+ {
+ _entryIndex = 0;
+ _entryDetailIndex = 0;
+
+ CurrentHeader.SetEntryCount(0);
+ CurrentHeader.SetEntryDetailCount(0);
+ }
+
+ public static uint GetEntryCount(ref AudioRendererConfiguration parameter)
+ {
+ return parameter.VoiceCount + parameter.EffectCount + parameter.SubMixBufferCount + parameter.SinkCount + 1;
+ }
+
+ public int GetEntriesSize()
+ {
+ return Unsafe.SizeOf<TEntry>() * _entryCountPerFrame;
+ }
+
+ public static int GetEntriesDetailSize()
+ {
+ return Unsafe.SizeOf<TEntryDetail>() * MaxFrameDetailCount;
+ }
+
+ public static int GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref AudioRendererConfiguration parameter)
+ {
+ return Unsafe.SizeOf<TEntry>() * (int)GetEntryCount(ref parameter) + GetEntriesDetailSize() + Unsafe.SizeOf<THeader>();
+ }
+
+ public override uint CopyHistories(Span<byte> performanceOutput)
+ {
+ if (performanceOutput.IsEmpty)
+ {
+ return 0;
+ }
+
+ int nextOffset = 0;
+
+ while (_indexHistoryRead != _indexHistoryWrite)
+ {
+ if (nextOffset >= performanceOutput.Length)
+ {
+ break;
+ }
+
+ ref THeader inputHeader = ref GetHeaderFromBuffer(_historyBuffer.Span, _indexHistoryRead);
+ Span<TEntry> inputEntries = GetEntriesFromBuffer(_historyBuffer.Span, _indexHistoryRead);
+ Span<TEntryDetail> inputEntriesDetail = GetEntriesDetailFromBuffer(_historyBuffer.Span, _indexHistoryRead);
+
+ Span<byte> targetSpan = performanceOutput.Slice(nextOffset);
+
+ ref THeader outputHeader = ref MemoryMarshal.Cast<byte, THeader>(targetSpan)[0];
+
+ nextOffset += Unsafe.SizeOf<THeader>();
+
+ Span<TEntry> outputEntries = MemoryMarshal.Cast<byte, TEntry>(targetSpan.Slice(nextOffset));
+
+ int totalProcessingTime = 0;
+
+ int effectiveEntryCount = 0;
+
+ for (int entryIndex = 0; entryIndex < inputHeader.GetEntryCount(); entryIndex++)
+ {
+ ref TEntry input = ref inputEntries[entryIndex];
+
+ if (input.GetProcessingTime() != 0 || input.GetStartTime() != 0)
+ {
+ ref TEntry output = ref outputEntries[effectiveEntryCount++];
+
+ output = input;
+
+ nextOffset += Unsafe.SizeOf<TEntry>();
+
+ totalProcessingTime += input.GetProcessingTime();
+ }
+ }
+
+ Span<TEntryDetail> outputEntriesDetail = MemoryMarshal.Cast<byte, TEntryDetail>(targetSpan.Slice(nextOffset));
+
+ int effectiveEntryDetailCount = 0;
+
+ for (int entryDetailIndex = 0; entryDetailIndex < inputHeader.GetEntryDetailCount(); entryDetailIndex++)
+ {
+ ref TEntryDetail input = ref inputEntriesDetail[entryDetailIndex];
+
+ if (input.GetProcessingTime() != 0 || input.GetStartTime() != 0)
+ {
+ ref TEntryDetail output = ref outputEntriesDetail[effectiveEntryDetailCount++];
+
+ output = input;
+
+ nextOffset += Unsafe.SizeOf<TEntryDetail>();
+ }
+ }
+
+ outputHeader = inputHeader;
+ outputHeader.SetMagic(MagicPerformanceBuffer);
+ outputHeader.SetTotalProcessingTime(totalProcessingTime);
+ outputHeader.SetNextOffset(nextOffset);
+ outputHeader.SetEntryCount(effectiveEntryCount);
+ outputHeader.SetEntryDetailCount(effectiveEntryDetailCount);
+
+ _indexHistoryRead = (_indexHistoryRead + 1) % _availableFrameCount;
+ }
+
+ if (nextOffset < performanceOutput.Length && (performanceOutput.Length - nextOffset) >= Unsafe.SizeOf<THeader>())
+ {
+ ref THeader outputHeader = ref MemoryMarshal.Cast<byte, THeader>(performanceOutput.Slice(nextOffset))[0];
+
+ outputHeader = default;
+ }
+
+ return (uint)nextOffset;
+ }
+
+ public override bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceEntryType entryType, int nodeId)
+ {
+ performanceEntry = new PerformanceEntryAddresses();
+ performanceEntry.BaseMemory = SpanMemoryManager<int>.Cast(CurrentBuffer);
+ performanceEntry.EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset();
+
+ uint baseEntryOffset = (uint)(Unsafe.SizeOf<THeader>() + Unsafe.SizeOf<TEntry>() * _entryIndex);
+
+ ref TEntry entry = ref Entries[_entryIndex];
+
+ performanceEntry.StartTimeOffset = baseEntryOffset + (uint)entry.GetStartTimeOffset();
+ performanceEntry.ProcessingTimeOffset = baseEntryOffset + (uint)entry.GetProcessingTimeOffset();
+
+ entry = default;
+ entry.SetEntryType(entryType);
+ entry.SetNodeId(nodeId);
+
+ _entryIndex++;
+
+ return true;
+ }
+
+ public override bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceDetailType detailType, PerformanceEntryType entryType, int nodeId)
+ {
+ performanceEntry = null;
+
+ if (_entryDetailIndex > MaxFrameDetailCount)
+ {
+ return false;
+ }
+
+ performanceEntry = new PerformanceEntryAddresses();
+ performanceEntry.BaseMemory = SpanMemoryManager<int>.Cast(CurrentBuffer);
+ performanceEntry.EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset();
+
+ uint baseEntryOffset = (uint)(Unsafe.SizeOf<THeader>() + GetEntriesSize() + Unsafe.SizeOf<IPerformanceDetailEntry>() * _entryDetailIndex);
+
+ ref TEntryDetail entryDetail = ref EntriesDetail[_entryDetailIndex];
+
+ performanceEntry.StartTimeOffset = baseEntryOffset + (uint)entryDetail.GetStartTimeOffset();
+ performanceEntry.ProcessingTimeOffset = baseEntryOffset + (uint)entryDetail.GetProcessingTimeOffset();
+
+ entryDetail = default;
+ entryDetail.SetDetailType(detailType);
+ entryDetail.SetEntryType(entryType);
+ entryDetail.SetNodeId(nodeId);
+
+ _entryDetailIndex++;
+
+ return true;
+ }
+
+ public override bool IsTargetNodeId(int target)
+ {
+ return _detailTarget == target;
+ }
+
+ public override void SetTargetNodeId(int target)
+ {
+ _detailTarget = target;
+ }
+
+ public override void TapFrame(bool dspRunningBehind, uint voiceDropCount, ulong startRenderingTicks)
+ {
+ if (_availableFrameCount > 0)
+ {
+ int targetIndexForHistory = _indexHistoryWrite;
+
+ _indexHistoryWrite = (_indexHistoryWrite + 1) % _availableFrameCount;
+
+ ref THeader targetHeader = ref GetHeaderFromBuffer(_historyBuffer.Span, targetIndexForHistory);
+
+ CurrentBuffer.Span.CopyTo(GetBufferFromIndex(_historyBuffer.Span, targetIndexForHistory));
+
+ uint targetHistoryFrameIndex = _historyFrameIndex;
+
+ if (_historyFrameIndex == uint.MaxValue)
+ {
+ _historyFrameIndex = 0;
+ }
+ else
+ {
+ _historyFrameIndex++;
+ }
+
+ targetHeader.SetDspRunningBehind(dspRunningBehind);
+ targetHeader.SetVoiceDropCount(voiceDropCount);
+ targetHeader.SetStartRenderingTicks(startRenderingTicks);
+ targetHeader.SetIndex(targetHistoryFrameIndex);
+
+ // Finally setup the new header
+ SetupNewHeader();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs b/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs
new file mode 100644
index 00000000..a89a8a60
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs
@@ -0,0 +1,65 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Server.Upsampler;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Server
+{
+ /// <summary>
+ /// Represents a lite version of <see cref="AudioRenderSystem"/> used by the <see cref="Dsp.AudioProcessor"/>
+ /// </summary>
+ /// <remarks>
+ /// This also allows to reduce dependencies on the <see cref="AudioRenderSystem"/> for unit testing.
+ /// </remarks>
+ public sealed class RendererSystemContext
+ {
+ /// <summary>
+ /// The session id of the current renderer.
+ /// </summary>
+ public int SessionId;
+
+ /// <summary>
+ /// The target channel count for sink.
+ /// </summary>
+ /// <remarks>See <see cref="CommandGenerator.GenerateDevice(Sink.DeviceSink, ref Mix.MixState)"/> for usage.</remarks>
+ public uint ChannelCount;
+
+ /// <summary>
+ /// The total count of mix buffer.
+ /// </summary>
+ public uint MixBufferCount;
+
+ /// <summary>
+ /// Instance of the <see cref="BehaviourContext"/> used to derive bug fixes and features of the current audio renderer revision.
+ /// </summary>
+ public BehaviourContext BehaviourContext;
+
+ /// <summary>
+ /// Instance of the <see cref="UpsamplerManager"/> used for upsampling (see <see cref="UpsamplerState"/>)
+ /// </summary>
+ public UpsamplerManager UpsamplerManager;
+
+ /// <summary>
+ /// The memory to use for depop processing.
+ /// </summary>
+ /// <remarks>
+ /// See <see cref="Dsp.Command.DepopForMixBuffersCommand"/> and <see cref="Dsp.Command.DepopPrepareCommand"/>
+ /// </remarks>
+ public Memory<float> DepopBuffer;
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs b/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs
new file mode 100644
index 00000000..949e3971
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs
@@ -0,0 +1,119 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using System.Diagnostics;
+using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
+
+namespace Ryujinx.Audio.Renderer.Server.Sink
+{
+ /// <summary>
+ /// Base class used for server information of a sink.
+ /// </summary>
+ public class BaseSink
+ {
+ /// <summary>
+ /// The type of this <see cref="BaseSink"/>.
+ /// </summary>
+ public SinkType Type;
+
+ /// <summary>
+ /// Set to true if the sink is used.
+ /// </summary>
+ public bool IsUsed;
+
+ /// <summary>
+ /// Set to true if the sink need to be skipped because of invalid state.
+ /// </summary>
+ public bool ShouldSkip;
+
+ /// <summary>
+ /// The node id of the sink.
+ /// </summary>
+ public int NodeId;
+
+ /// <summary>
+ /// Create a new <see cref="BaseSink"/>.
+ /// </summary>
+ public BaseSink()
+ {
+ CleanUp();
+ }
+
+ /// <summary>
+ /// Clean up the internal state of the <see cref="BaseSink"/>.
+ /// </summary>
+ public virtual void CleanUp()
+ {
+ Type = TargetSinkType;
+ IsUsed = false;
+ ShouldSkip = false;
+ }
+
+ /// <summary>
+ /// The target <see cref="SinkType"/> handled by this <see cref="BaseSink"/>.
+ /// </summary>
+ public virtual SinkType TargetSinkType => SinkType.Invalid;
+
+ /// <summary>
+ /// Check if the <see cref="SinkType"/> sent by the user match the internal <see cref="SinkType"/>.
+ /// </summary>
+ /// <param name="parameter">The user parameter.</param>
+ /// <returns>Return true, if the <see cref="SinkType"/> sent by the user match the internal <see cref="SinkType"/>.</returns>
+ public bool IsTypeValid(ref SinkInParameter parameter)
+ {
+ return parameter.Type == TargetSinkType;
+ }
+
+ /// <summary>
+ /// Update the <see cref="BaseSink"/> state during command generation.
+ /// </summary>
+ public virtual void UpdateForCommandGeneration()
+ {
+ Debug.Assert(Type == TargetSinkType);
+ }
+
+ /// <summary>
+ /// Update the internal common parameters from user parameter.
+ /// </summary>
+ /// <param name="parameter">The user parameter.</param>
+ protected void UpdateStandardParameter(ref SinkInParameter parameter)
+ {
+ if (IsUsed != parameter.IsUsed)
+ {
+ IsUsed = parameter.IsUsed;
+ NodeId = parameter.NodeId;
+ }
+ }
+
+ /// <summary>
+ /// Update the internal state from user parameter.
+ /// </summary>
+ /// <param name="errorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
+ /// <param name="parameter">The user parameter.</param>
+ /// <param name="outStatus">The user output status.</param>
+ /// <param name="mapper">The mapper to use.</param>
+ public virtual void Update(out ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
+ {
+ Debug.Assert(IsTypeValid(ref parameter));
+
+ errorInfo = new ErrorInfo();
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs b/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs
new file mode 100644
index 00000000..ceb955e5
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs
@@ -0,0 +1,126 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Sink;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Sink
+{
+ /// <summary>
+ /// Server information for a circular buffer sink.
+ /// </summary>
+ public class CircularBufferSink : BaseSink
+ {
+ /// <summary>
+ /// The circular buffer parameter.
+ /// </summary>
+ public CircularBufferParameter Parameter;
+
+ /// <summary>
+ /// The last written data offset on the circular buffer.
+ /// </summary>
+ private uint _lastWrittenOffset;
+
+ /// <summary>
+ /// THe previous written offset of the circular buffer.
+ /// </summary>
+ private uint _oldWrittenOffset;
+
+ /// <summary>
+ /// The current offset to write data on the circular buffer.
+ /// </summary>
+ public uint CurrentWriteOffset { get; private set; }
+
+ /// <summary>
+ /// The <see cref="AddressInfo"/> of the circular buffer.
+ /// </summary>
+ public AddressInfo CircularBufferAddressInfo;
+
+ public CircularBufferSink()
+ {
+ CircularBufferAddressInfo = AddressInfo.Create();
+ }
+
+ public override SinkType TargetSinkType => SinkType.CircularBuffer;
+
+ public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
+ {
+ errorInfo = new BehaviourParameter.ErrorInfo();
+ outStatus = new SinkOutStatus();
+
+ Debug.Assert(IsTypeValid(ref parameter));
+
+ ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, CircularBufferParameter>(parameter.SpecificData)[0];
+
+ if (parameter.IsUsed != IsUsed || ShouldSkip)
+ {
+ UpdateStandardParameter(ref parameter);
+
+ if (parameter.IsUsed)
+ {
+ Debug.Assert(CircularBufferAddressInfo.CpuAddress == 0);
+ Debug.Assert(CircularBufferAddressInfo.GetReference(false) == 0);
+
+ ShouldSkip = !mapper.TryAttachBuffer(out errorInfo, ref CircularBufferAddressInfo, inputDeviceParameter.BufferAddress, inputDeviceParameter.BufferSize);
+ }
+ else
+ {
+ Debug.Assert(CircularBufferAddressInfo.CpuAddress != 0);
+ Debug.Assert(CircularBufferAddressInfo.GetReference(false) != 0);
+ }
+
+ Parameter = inputDeviceParameter;
+ }
+
+ outStatus.LastWrittenOffset = _lastWrittenOffset;
+ }
+
+ public override void UpdateForCommandGeneration()
+ {
+ Debug.Assert(Type == TargetSinkType);
+
+ if (IsUsed)
+ {
+ uint frameSize = Constants.TargetSampleSize * Parameter.SampleCount * Parameter.InputCount;
+
+ _lastWrittenOffset = _oldWrittenOffset;
+
+ _oldWrittenOffset = CurrentWriteOffset;
+
+ CurrentWriteOffset += frameSize;
+
+ if (Parameter.BufferSize > 0)
+ {
+ CurrentWriteOffset %= Parameter.BufferSize;
+ }
+ }
+ }
+
+ public override void CleanUp()
+ {
+ CircularBufferAddressInfo = AddressInfo.Create();
+ _lastWrittenOffset = 0;
+ _oldWrittenOffset = 0;
+ CurrentWriteOffset = 0;
+ base.CleanUp();
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs b/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs
new file mode 100644
index 00000000..79af05f7
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs
@@ -0,0 +1,92 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Sink;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using Ryujinx.Audio.Renderer.Server.Upsampler;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Sink
+{
+ /// <summary>
+ /// Server information for a device sink.
+ /// </summary>
+ public class DeviceSink : BaseSink
+ {
+ /// <summary>
+ /// The downmix coefficients.
+ /// </summary>
+ public float[] DownMixCoefficients;
+
+ /// <summary>
+ /// The device parameters.
+ /// </summary>
+ public DeviceParameter Parameter;
+
+ /// <summary>
+ /// The upsampler instance used by this sink.
+ /// </summary>
+ /// <remarks>Null if no upsampling is needed.</remarks>
+ public UpsamplerState UpsamplerState;
+
+ /// <summary>
+ /// Create a new <see cref="DeviceSink"/>.
+ /// </summary>
+ public DeviceSink()
+ {
+ DownMixCoefficients = new float[4];
+ }
+
+ public override void CleanUp()
+ {
+ UpsamplerState?.Release();
+
+ UpsamplerState = null;
+
+ base.CleanUp();
+ }
+
+ public override SinkType TargetSinkType => SinkType.Device;
+
+ public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper)
+ {
+ Debug.Assert(IsTypeValid(ref parameter));
+
+ ref DeviceParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, DeviceParameter>(parameter.SpecificData)[0];
+
+ if (parameter.IsUsed != IsUsed)
+ {
+ UpdateStandardParameter(ref parameter);
+ Parameter = inputDeviceParameter;
+ }
+ else
+ {
+ Parameter.DownMixParameterEnabled = inputDeviceParameter.DownMixParameterEnabled;
+ inputDeviceParameter.DownMixParameter.ToSpan().CopyTo(Parameter.DownMixParameter.ToSpan());
+ }
+
+ Parameter.DownMixParameter.ToSpan().CopyTo(DownMixCoefficients.AsSpan());
+
+ errorInfo = new BehaviourParameter.ErrorInfo();
+ outStatus = new SinkOutStatus();
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs b/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs
new file mode 100644
index 00000000..b57dbde9
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs
@@ -0,0 +1,73 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Server.Sink
+{
+ /// <summary>
+ /// Sink context.
+ /// </summary>
+ public class SinkContext
+ {
+ /// <summary>
+ /// Storage for <see cref="BaseSink"/>.
+ /// </summary>
+ private BaseSink[] _sinks;
+
+ /// <summary>
+ /// The total sink count.
+ /// </summary>
+ private uint _sinkCount;
+
+ /// <summary>
+ /// Initialize the <see cref="SinkContext"/>.
+ /// </summary>
+ /// <param name="sinksCount">The total sink count.</param>
+ public void Initialize(uint sinksCount)
+ {
+ _sinkCount = sinksCount;
+ _sinks = new BaseSink[_sinkCount];
+
+ for (int i = 0; i < _sinkCount; i++)
+ {
+ _sinks[i] = new BaseSink();
+ }
+ }
+
+ /// <summary>
+ /// Get the total sink count.
+ /// </summary>
+ /// <returns>The total sink count.</returns>
+ public uint GetCount()
+ {
+ return _sinkCount;
+ }
+
+ /// <summary>
+ /// Get a reference to a <see cref="BaseSink"/> at the given <paramref name="id"/>.
+ /// </summary>
+ /// <param name="id">The index to use.</param>
+ /// <returns>A reference to a <see cref="BaseSink"/> at the given <paramref name="id"/>.</returns>
+ public ref BaseSink GetSink(int id)
+ {
+ Debug.Assert(id >= 0 && id < _sinkCount);
+
+ return ref _sinks[id];
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
new file mode 100644
index 00000000..0e107a53
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
@@ -0,0 +1,320 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Utils;
+using Ryujinx.Common;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Splitter
+{
+ /// <summary>
+ /// Splitter context.
+ /// </summary>
+ public class SplitterContext
+ {
+ /// <summary>
+ /// Storage for <see cref="SplitterState"/>.
+ /// </summary>
+ private Memory<SplitterState> _splitters;
+
+ /// <summary>
+ /// Storage for <see cref="SplitterDestination"/>.
+ /// </summary>
+ private Memory<SplitterDestination> _splitterDestinations;
+
+ /// <summary>
+ /// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, ref SplitterInParameter, ReadOnlySpan{byte})"/>.
+ /// </summary>
+ public bool IsBugFixed { get; private set; }
+
+ /// <summary>
+ /// Initialize <see cref="SplitterContext"/>.
+ /// </summary>
+ /// <param name="behaviourContext">The behaviour context.</param>
+ /// <param name="parameter">The audio renderer configuration.</param>
+ /// <param name="workBufferAllocator">The <see cref="WorkBufferAllocator"/>.</param>
+ /// <returns>Return true if the initialization was successful.</returns>
+ public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator)
+ {
+ if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0)
+ {
+ Setup(Memory<SplitterState>.Empty, Memory<SplitterDestination>.Empty, false);
+
+ return true;
+ }
+
+ Memory<SplitterState> splitters = workBufferAllocator.Allocate<SplitterState>(parameter.SplitterCount, SplitterState.Alignment);
+
+ if (splitters.IsEmpty)
+ {
+ return false;
+ }
+
+ int splitterId = 0;
+
+ foreach (ref SplitterState splitter in splitters.Span)
+ {
+ splitter = new SplitterState(splitterId++);
+ }
+
+ Memory<SplitterDestination> splitterDestinations = workBufferAllocator.Allocate<SplitterDestination>(parameter.SplitterDestinationCount,
+ SplitterDestination.Alignment);
+
+ if (splitterDestinations.IsEmpty)
+ {
+ return false;
+ }
+
+ int splitterDestinationId = 0;
+ foreach (ref SplitterDestination data in splitterDestinations.Span)
+ {
+ data = new SplitterDestination(splitterDestinationId++);
+ }
+
+ SplitterState.InitializeSplitters(splitters.Span);
+
+ Setup(splitters, splitterDestinations, behaviourContext.IsSplitterBugFixed());
+
+ return true;
+ }
+
+ /// <summary>
+ /// Get the work buffer size while adding the size needed for splitter to operate.
+ /// </summary>
+ /// <param name="size">The current size.</param>
+ /// <param name="behaviourContext">The behaviour context.</param>
+ /// <param name="parameter">The renderer configuration.</param>
+ /// <returns>Return the new size taking splitter into account.</returns>
+ public static ulong GetWorkBufferSize(ulong size, ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter)
+ {
+ if (behaviourContext.IsSplitterSupported())
+ {
+ size = WorkBufferAllocator.GetTargetSize<SplitterState>(size, parameter.SplitterCount, SplitterState.Alignment);
+ size = WorkBufferAllocator.GetTargetSize<SplitterDestination>(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment);
+
+ if (behaviourContext.IsSplitterBugFixed())
+ {
+ size = WorkBufferAllocator.GetTargetSize<int>(size, parameter.SplitterDestinationCount, 0x10);
+ }
+
+ return size;
+ }
+ else
+ {
+ return size;
+ }
+ }
+
+ /// <summary>
+ /// Setup the <see cref="SplitterContext"/> instance.
+ /// </summary>
+ /// <param name="splitters">The <see cref="SplitterState"/> storage.</param>
+ /// <param name="splitterDestinations">The <see cref="SplitterDestination"/> storage.</param>
+ /// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, ref SplitterInParameter, ReadOnlySpan{byte})"/>.</param>
+ private void Setup(Memory<SplitterState> splitters, Memory<SplitterDestination> splitterDestinations, bool isBugFixed)
+ {
+ _splitters = splitters;
+ _splitterDestinations = splitterDestinations;
+ IsBugFixed = isBugFixed;
+ }
+
+ /// <summary>
+ /// Clear the new connection flag.
+ /// </summary>
+ private void ClearAllNewConnectionFlag()
+ {
+ foreach (ref SplitterState splitter in _splitters.Span)
+ {
+ splitter.ClearNewConnectionFlag();
+ }
+ }
+
+ /// <summary>
+ /// Get the destination count using the count of splitter.
+ /// </summary>
+ /// <returns>The destination count using the count of splitter.</returns>
+ public int GetDestinationCountPerStateForCompatibility()
+ {
+ if (_splitters.IsEmpty)
+ {
+ return 0;
+ }
+
+ return _splitterDestinations.Length / _splitters.Length;
+ }
+
+ /// <summary>
+ /// Update one or multiple <see cref="SplitterState"/> from user parameters.
+ /// </summary>
+ /// <param name="inputHeader">The splitter header.</param>
+ /// <param name="input">The raw data after the splitter header.</param>
+ private void UpdateState(ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan<byte> input)
+ {
+ for (int i = 0; i < inputHeader.SplitterCount; i++)
+ {
+ SplitterInParameter parameter = MemoryMarshal.Read<SplitterInParameter>(input);
+
+ Debug.Assert(parameter.IsMagicValid());
+
+ if (parameter.IsMagicValid())
+ {
+ if (parameter.Id >= 0 && parameter.Id < _splitters.Length)
+ {
+ ref SplitterState splitter = ref GetState(parameter.Id);
+
+ splitter.Update(this, ref parameter, input.Slice(Unsafe.SizeOf<SplitterInParameter>()));
+ }
+
+ input = input.Slice(0x1C + (int)parameter.DestinationCount * 4);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Update one or multiple <see cref="SplitterDestination"/> from user parameters.
+ /// </summary>
+ /// <param name="inputHeader">The splitter header.</param>
+ /// <param name="input">The raw data after the splitter header.</param>
+ private void UpdateData(ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan<byte> input)
+ {
+ for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
+ {
+ SplitterDestinationInParameter parameter = MemoryMarshal.Read<SplitterDestinationInParameter>(input);
+
+ Debug.Assert(parameter.IsMagicValid());
+
+ if (parameter.IsMagicValid())
+ {
+ if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length)
+ {
+ ref SplitterDestination destination = ref GetDestination(parameter.Id);
+
+ destination.Update(parameter);
+ }
+
+ input = input.Slice(Unsafe.SizeOf<SplitterDestinationInParameter>());
+ }
+ }
+ }
+
+ /// <summary>
+ /// Update splitter from user parameters.
+ /// </summary>
+ /// <param name="input">The input raw user data.</param>
+ /// <param name="consumedSize">The total consumed size.</param>
+ /// <returns>Return true if the update was successful.</returns>
+ public bool Update(ReadOnlySpan<byte> input, out int consumedSize)
+ {
+ if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
+ {
+ consumedSize = 0;
+
+ return true;
+ }
+
+ int originalSize = input.Length;
+
+ SplitterInParameterHeader header = SpanIOHelper.Read<SplitterInParameterHeader>(ref input);
+
+ if (header.IsMagicValid())
+ {
+ ClearAllNewConnectionFlag();
+
+ UpdateState(ref header, ref input);
+ UpdateData(ref header, ref input);
+
+ consumedSize = BitUtils.AlignUp(originalSize - input.Length, 0x10);
+
+ return true;
+ }
+ else
+ {
+ consumedSize = 0;
+
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Get a reference to a <see cref="SplitterState"/> at the given <paramref name="id"/>.
+ /// </summary>
+ /// <param name="id">The index to use.</param>
+ /// <returns>A reference to a <see cref="SplitterState"/> at the given <paramref name="id"/>.</returns>
+ public ref SplitterState GetState(int id)
+ {
+ return ref SpanIOHelper.GetFromMemory(_splitters, id, (uint)_splitters.Length);
+ }
+
+ /// <summary>
+ /// Get a reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>.
+ /// </summary>
+ /// <param name="id">The index to use.</param>
+ /// <returns>A reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>.</returns>
+ public ref SplitterDestination GetDestination(int id)
+ {
+ return ref SpanIOHelper.GetFromMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
+ }
+
+ /// <summary>
+ /// Get a <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.
+ /// </summary>
+ /// <param name="id">The index to use.</param>
+ /// <returns>A <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.</returns>
+ public Memory<SplitterDestination> GetDestinationMemory(int id)
+ {
+ return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
+ }
+
+ /// <summary>
+ /// Get a <see cref="Span{SplitterDestination}"/> in the <see cref="SplitterState"/> at <paramref name="id"/> and pass <paramref name="destinationId"/> to <see cref="SplitterState.GetData(int)"/>.
+ /// </summary>
+ /// <param name="id">The index to use to get the <see cref="SplitterState"/>.</param>
+ /// <param name="destinationId">The index of the <see cref="SplitterDestination"/>.</param>
+ /// <returns>A <see cref="Span{SplitterDestination}"/>.</returns>
+ public Span<SplitterDestination> GetDestination(int id, int destinationId)
+ {
+ ref SplitterState splitter = ref GetState(id);
+
+ return splitter.GetData(destinationId);
+ }
+
+ /// <summary>
+ /// Return true if the audio renderer has any splitters.
+ /// </summary>
+ /// <returns>True if the audio renderer has any splitters.</returns>
+ public bool UsingSplitter()
+ {
+ return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty;
+ }
+
+ /// <summary>
+ /// Update the internal state of all splitters.
+ /// </summary>
+ public void UpdateInternalState()
+ {
+ foreach (ref SplitterState splitter in _splitters.Span)
+ {
+ splitter.UpdateInternalState();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs b/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
new file mode 100644
index 00000000..077bf8ae
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
@@ -0,0 +1,210 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Splitter
+{
+ /// <summary>
+ /// Server state for a splitter destination.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)]
+ public struct SplitterDestination
+ {
+ public const int Alignment = 0x10;
+
+ /// <summary>
+ /// The unique id of this <see cref="SplitterDestination"/>.
+ /// </summary>
+ public int Id;
+
+ /// <summary>
+ /// The mix to output the result of the splitter.
+ /// </summary>
+ public int DestinationId;
+
+ /// <summary>
+ /// Mix buffer volumes storage.
+ /// </summary>
+ private MixArray _mix;
+ private MixArray _previousMix;
+
+ /// <summary>
+ /// Pointer to the next linked element.
+ /// </summary>
+ private unsafe SplitterDestination* _next;
+
+ /// <summary>
+ /// Set to true if in use.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ /// <summary>
+ /// Set to true if the internal state need to be updated.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool NeedToUpdateInternalState;
+
+ [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
+ private struct MixArray { }
+
+ /// <summary>
+ /// Mix buffer volumes.
+ /// </summary>
+ /// <remarks>Used when a splitter id is specified in the mix.</remarks>
+ public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix);
+
+ /// <summary>
+ /// Previous mix buffer volumes.
+ /// </summary>
+ /// <remarks>Used when a splitter id is specified in the mix.</remarks>
+ public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
+
+ /// <summary>
+ /// Get the <see cref="Span{SplitterDestination}"/> of the next element or <see cref="Span{SplitterDestination}.Empty"/> if not present.
+ /// </summary>
+ public Span<SplitterDestination> Next
+ {
+ get
+ {
+ unsafe
+ {
+ return _next != null ? new Span<SplitterDestination>(_next, 1) : Span<SplitterDestination>.Empty;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Create a new <see cref="SplitterDestination"/>.
+ /// </summary>
+ /// <param name="id">The unique id of this <see cref="SplitterDestination"/>.</param>
+ public SplitterDestination(int id) : this()
+ {
+ Id = id;
+ DestinationId = Constants.UnusedMixId;
+
+ ClearVolumes();
+ }
+
+ /// <summary>
+ /// Update the <see cref="SplitterDestination"/> from user parameter.
+ /// </summary>
+ /// <param name="parameter">The user parameter.</param>
+ public void Update(SplitterDestinationInParameter parameter)
+ {
+ Debug.Assert(Id == parameter.Id);
+
+ if (parameter.IsMagicValid() && Id == parameter.Id)
+ {
+ DestinationId = parameter.DestinationId;
+
+ parameter.MixBufferVolume.CopyTo(MixBufferVolume);
+
+ if (!IsUsed && parameter.IsUsed)
+ {
+ MixBufferVolume.CopyTo(PreviousMixBufferVolume);
+
+ NeedToUpdateInternalState = false;
+ }
+
+ IsUsed = parameter.IsUsed;
+ }
+ }
+
+ /// <summary>
+ /// Update the internal state of the instance.
+ /// </summary>
+ public void UpdateInternalState()
+ {
+ if (IsUsed && NeedToUpdateInternalState)
+ {
+ MixBufferVolume.CopyTo(PreviousMixBufferVolume);
+ }
+
+ NeedToUpdateInternalState = false;
+ }
+
+ /// <summary>
+ /// Set the update internal state marker.
+ /// </summary>
+ public void MarkAsNeedToUpdateInternalState()
+ {
+ NeedToUpdateInternalState = true;
+ }
+
+ /// <summary>
+ /// Return true if the <see cref="SplitterDestination"/> is used and has a destination.
+ /// </summary>
+ /// <returns>True if the <see cref="SplitterDestination"/> is used and has a destination.</returns>
+ public bool IsConfigured()
+ {
+ return IsUsed && DestinationId != Constants.UnusedMixId;
+ }
+
+ /// <summary>
+ /// Get the volume for a given destination.
+ /// </summary>
+ /// <param name="destinationIndex">The destination index to use.</param>
+ /// <returns>The volume for the given destination.</returns>
+ public float GetMixVolume(int destinationIndex)
+ {
+ Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
+
+ return MixBufferVolume[destinationIndex];
+ }
+
+ /// <summary>
+ /// Clear the volumes.
+ /// </summary>
+ public void ClearVolumes()
+ {
+ MixBufferVolume.Fill(0);
+ PreviousMixBufferVolume.Fill(0);
+ }
+
+ /// <summary>
+ /// Link the next element to the given <see cref="SplitterDestination"/>.
+ /// </summary>
+ /// <param name="next">The given <see cref="SplitterDestination"/> to link.</param>
+ public void Link(ref SplitterDestination next)
+ {
+ unsafe
+ {
+ fixed (SplitterDestination *nextPtr = &next)
+ {
+ _next = nextPtr;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Remove the link to the next element.
+ /// </summary>
+ public void Unlink()
+ {
+ unsafe
+ {
+ _next = null;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs b/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
new file mode 100644
index 00000000..c928ec53
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
@@ -0,0 +1,237 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+using Ryujinx.Audio.Renderer.Parameter;
+using System;
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Splitter
+{
+ /// <summary>
+ /// Server state for a splitter.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = Alignment)]
+ public struct SplitterState
+ {
+ public const int Alignment = 0x10;
+
+ /// <summary>
+ /// The unique id of this <see cref="SplitterState"/>.
+ /// </summary>
+ public int Id;
+
+ /// <summary>
+ /// Target sample rate to use on the splitter.
+ /// </summary>
+ public uint SampleRate;
+
+ /// <summary>
+ /// Count of splitter destinations (<see cref="SplitterDestination"/>).
+ /// </summary>
+ public int DestinationCount;
+
+ /// <summary>
+ /// Set to true if the splitter has a new connection.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool HasNewConnection;
+
+ /// <summary>
+ /// Linked list of <see cref="SplitterDestination"/>.
+ /// </summary>
+ private unsafe SplitterDestination* _destinationsData;
+
+ /// <summary>
+ /// Span to the first element of the linked list of <see cref="SplitterDestination"/>.
+ /// </summary>
+ public Span<SplitterDestination> Destinations
+ {
+ get
+ {
+ unsafe
+ {
+ return (IntPtr)_destinationsData != IntPtr.Zero ? new Span<SplitterDestination>(_destinationsData, 1) : Span<SplitterDestination>.Empty;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Create a new <see cref="SplitterState"/>.
+ /// </summary>
+ /// <param name="id">The unique id of this <see cref="SplitterState"/>.</param>
+ public SplitterState(int id) : this()
+ {
+ Id = id;
+ }
+
+ public Span<SplitterDestination> GetData(int index)
+ {
+ int i = 0;
+
+ Span<SplitterDestination> result = Destinations;
+
+ while (i < index)
+ {
+ if (result.IsEmpty)
+ {
+ break;
+ }
+
+ result = result[0].Next;
+ i++;
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Clear the new connection flag.
+ /// </summary>
+ public void ClearNewConnectionFlag()
+ {
+ HasNewConnection = false;
+ }
+
+ /// <summary>
+ /// Utility function to apply a given <see cref="SpanAction{T, TArg}"/> to all <see cref="Destinations"/>.
+ /// </summary>
+ /// <param name="action">The action to execute on each elements.</param>
+ private void ForEachDestination(SpanAction<SplitterDestination, int> action)
+ {
+ Span<SplitterDestination> temp = Destinations;
+
+ int i = 0;
+
+ while (true)
+ {
+ if (temp.IsEmpty)
+ {
+ break;
+ }
+
+ Span<SplitterDestination> next = temp[0].Next;
+
+ action.Invoke(temp, i++);
+
+ temp = next;
+ }
+ }
+
+ /// <summary>
+ /// Update the <see cref="SplitterState"/> from user parameter.
+ /// </summary>
+ /// <param name="context">The splitter context.</param>
+ /// <param name="parameter">The user parameter.</param>
+ /// <param name="input">The raw input data after the <paramref name="parameter"/>.</param>
+ public void Update(SplitterContext context, ref SplitterInParameter parameter, ReadOnlySpan<byte> input)
+ {
+ ClearLinks();
+
+ int destinationCount;
+
+ if (context.IsBugFixed)
+ {
+ destinationCount = parameter.DestinationCount;
+ }
+ else
+ {
+ destinationCount = Math.Min(context.GetDestinationCountPerStateForCompatibility(), parameter.DestinationCount);
+ }
+
+ if (destinationCount > 0)
+ {
+ ReadOnlySpan<int> destinationIds = MemoryMarshal.Cast<byte, int>(input);
+
+ Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationIds[0]);
+
+ SetDestination(ref destination.Span[0]);
+
+ DestinationCount = destinationCount;
+
+ for (int i = 1; i < destinationCount; i++)
+ {
+ Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationIds[i]);
+
+ destination.Span[0].Link(ref nextDestination.Span[0]);
+ destination = nextDestination;
+ }
+ }
+
+ Debug.Assert(parameter.Id == Id);
+
+ if (parameter.Id == Id)
+ {
+ SampleRate = parameter.SampleRate;
+ HasNewConnection = true;
+ }
+ }
+
+ /// <summary>
+ /// Set the head of the linked list of <see cref="Destinations"/>.
+ /// </summary>
+ /// <param name="newValue">A reference to a <see cref="SplitterDestination"/>.</param>
+ public void SetDestination(ref SplitterDestination newValue)
+ {
+ unsafe
+ {
+ fixed (SplitterDestination* newValuePtr = &newValue)
+ {
+ _destinationsData = newValuePtr;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Update the internal state of this instance.
+ /// </summary>
+ public void UpdateInternalState()
+ {
+ ForEachDestination((destination, _) => destination[0].UpdateInternalState());
+ }
+
+ /// <summary>
+ /// Clear all links from the <see cref="Destinations"/>.
+ /// </summary>
+ public void ClearLinks()
+ {
+ ForEachDestination((destination, _) => destination[0].Unlink());
+
+ unsafe
+ {
+ _destinationsData = (SplitterDestination*)IntPtr.Zero;
+ }
+ }
+
+ /// <summary>
+ /// Initialize a given <see cref="Span{SplitterState}"/>.
+ /// </summary>
+ /// <param name="splitters">All the <see cref="SplitterState"/> to initialize.</param>
+ public static void InitializeSplitters(Span<SplitterState> splitters)
+ {
+ foreach (ref SplitterState splitter in splitters)
+ {
+ unsafe
+ {
+ splitter._destinationsData = (SplitterDestination*)IntPtr.Zero;
+ }
+
+ splitter.DestinationCount = 0;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/StateUpdater.cs b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs
new file mode 100644
index 00000000..77935b75
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs
@@ -0,0 +1,575 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Performance;
+using Ryujinx.Audio.Renderer.Server.Effect;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using Ryujinx.Audio.Renderer.Server.Mix;
+using Ryujinx.Audio.Renderer.Server.Performance;
+using Ryujinx.Audio.Renderer.Server.Sink;
+using Ryujinx.Audio.Renderer.Server.Splitter;
+using Ryujinx.Audio.Renderer.Server.Voice;
+using Ryujinx.Audio.Renderer.Utils;
+using Ryujinx.Common.Logging;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
+
+namespace Ryujinx.Audio.Renderer.Server
+{
+ public class StateUpdater
+ {
+ private readonly ReadOnlyMemory<byte> _inputOrigin;
+ private ReadOnlyMemory <byte> _outputOrigin;
+ private ReadOnlyMemory<byte> _input;
+
+ private Memory<byte> _output;
+ private uint _processHandle;
+ private BehaviourContext _behaviourContext;
+
+ private UpdateDataHeader _inputHeader;
+ private Memory<UpdateDataHeader> _outputHeader;
+
+ private ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0];
+
+ public StateUpdater(ReadOnlyMemory<byte> input, Memory<byte> output, uint processHandle, BehaviourContext behaviourContext)
+ {
+ _input = input;
+ _inputOrigin = _input;
+ _output = output;
+ _outputOrigin = _output;
+ _processHandle = processHandle;
+ _behaviourContext = behaviourContext;
+
+ _inputHeader = SpanIOHelper.Read<UpdateDataHeader>(ref _input);
+
+ _outputHeader = SpanMemoryManager<UpdateDataHeader>.Cast(_output.Slice(0, Unsafe.SizeOf<UpdateDataHeader>()));
+ OutputHeader.Initialize(_behaviourContext.UserRevision);
+ _output = _output.Slice(Unsafe.SizeOf<UpdateDataHeader>());
+ }
+
+ public ResultCode UpdateBehaviourContext()
+ {
+ BehaviourParameter parameter = SpanIOHelper.Read<BehaviourParameter>(ref _input);
+
+ if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision)
+ {
+ return ResultCode.InvalidUpdateInfo;
+ }
+
+ _behaviourContext.ClearError();
+ _behaviourContext.UpdateFlags(parameter.Flags);
+
+ if (_inputHeader.BehaviourSize != Unsafe.SizeOf<BehaviourParameter>())
+ {
+ return ResultCode.InvalidUpdateInfo;
+ }
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode UpdateMemoryPools(Span<MemoryPoolState> memoryPools)
+ {
+ PoolMapper mapper = new PoolMapper(_processHandle, _behaviourContext.IsMemoryPoolForceMappingEnabled());
+
+ if (memoryPools.Length * Unsafe.SizeOf<MemoryPoolInParameter>() != _inputHeader.MemoryPoolsSize)
+ {
+ return ResultCode.InvalidUpdateInfo;
+ }
+
+ foreach (ref MemoryPoolState memoryPool in memoryPools)
+ {
+ MemoryPoolInParameter parameter = SpanIOHelper.Read<MemoryPoolInParameter>(ref _input);
+
+ ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef<MemoryPoolOutStatus>(ref _output)[0];
+
+ PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, ref parameter, ref outStatus);
+
+ if (updateResult != PoolMapper.UpdateResult.Success &&
+ updateResult != PoolMapper.UpdateResult.MapError &&
+ updateResult != PoolMapper.UpdateResult.UnmapError)
+ {
+ if (updateResult != PoolMapper.UpdateResult.InvalidParameter)
+ {
+ throw new InvalidOperationException($"{updateResult}");
+ }
+
+ return ResultCode.InvalidUpdateInfo;
+ }
+ }
+
+ OutputHeader.MemoryPoolsSize = (uint)(Unsafe.SizeOf<MemoryPoolOutStatus>() * memoryPools.Length);
+ OutputHeader.TotalSize += OutputHeader.MemoryPoolsSize;
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode UpdateVoiceChannelResources(VoiceContext context)
+ {
+ if (context.GetCount() * Unsafe.SizeOf<VoiceChannelResourceInParameter>() != _inputHeader.VoiceResourcesSize)
+ {
+ return ResultCode.InvalidUpdateInfo;
+ }
+
+ for (int i = 0; i < context.GetCount(); i++)
+ {
+ VoiceChannelResourceInParameter parameter = SpanIOHelper.Read<VoiceChannelResourceInParameter>(ref _input);
+
+ ref VoiceChannelResource resource = ref context.GetChannelResource(i);
+
+ resource.Id = parameter.Id;
+ parameter.Mix.ToSpan().CopyTo(resource.Mix.ToSpan());
+ resource.IsUsed = parameter.IsUsed;
+ }
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode UpdateVoices(VoiceContext context, Memory<MemoryPoolState> memoryPools)
+ {
+ if (context.GetCount() * Unsafe.SizeOf<VoiceInParameter>() != _inputHeader.VoicesSize)
+ {
+ return ResultCode.InvalidUpdateInfo;
+ }
+
+ int initialOutputSize = _output.Length;
+
+ ReadOnlySpan<VoiceInParameter> parameters = MemoryMarshal.Cast<byte, VoiceInParameter>(_input.Slice(0, (int)_inputHeader.VoicesSize).Span);
+
+ _input = _input.Slice((int)_inputHeader.VoicesSize);
+
+ PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
+
+ // First make everything not in use.
+ for (int i = 0; i < context.GetCount(); i++)
+ {
+ ref VoiceState state = ref context.GetState(i);
+
+ state.InUse = false;
+ }
+
+ // Start processing
+ for (int i = 0; i < context.GetCount(); i++)
+ {
+ VoiceInParameter parameter = parameters[i];
+
+ Memory<VoiceUpdateState>[] voiceUpdateStates = new Memory<VoiceUpdateState>[Constants.VoiceChannelCountMax];
+
+ ref VoiceOutStatus outStatus = ref SpanIOHelper.GetWriteRef<VoiceOutStatus>(ref _output)[0];
+
+ if (parameter.InUse)
+ {
+ ref VoiceState currentVoiceState = ref context.GetState(i);
+
+ for (int channelResourceIndex = 0; channelResourceIndex < parameter.ChannelCount; channelResourceIndex++)
+ {
+ int channelId = parameter.ChannelResourceIds[channelResourceIndex];
+
+ Debug.Assert(channelId >= 0 && channelId < context.GetCount());
+
+ voiceUpdateStates[channelResourceIndex] = context.GetUpdateStateForCpu(channelId);
+ }
+
+ if (parameter.IsNew)
+ {
+ currentVoiceState.Initialize();
+ }
+
+ currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, ref parameter, ref mapper, ref _behaviourContext);
+
+ if (updateParameterError.ErrorCode != ResultCode.Success)
+ {
+ _behaviourContext.AppendError(ref updateParameterError);
+ }
+
+ currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, ref parameter, voiceUpdateStates, ref mapper, ref _behaviourContext);
+
+ foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan())
+ {
+ if (errorInfo.ErrorCode != ResultCode.Success)
+ {
+ _behaviourContext.AppendError(ref errorInfo);
+ }
+ }
+
+ currentVoiceState.WriteOutStatus(ref outStatus, ref parameter, voiceUpdateStates);
+ }
+ }
+
+ int currentOutputSize = _output.Length;
+
+ OutputHeader.VoicesSize = (uint)(Unsafe.SizeOf<VoiceOutStatus>() * context.GetCount());
+ OutputHeader.TotalSize += OutputHeader.VoicesSize;
+
+ Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize);
+
+ return ResultCode.Success;
+ }
+
+ private static void ResetEffect(ref BaseEffect effect, ref EffectInParameter parameter, PoolMapper mapper)
+ {
+ effect.ForceUnmapBuffers(mapper);
+
+ switch (parameter.Type)
+ {
+ case EffectType.Invalid:
+ effect = new BaseEffect();
+ break;
+ case EffectType.BufferMix:
+ effect = new BufferMixEffect();
+ break;
+ case EffectType.AuxiliaryBuffer:
+ effect = new AuxiliaryBufferEffect();
+ break;
+ case EffectType.Delay:
+ effect = new DelayEffect();
+ break;
+ case EffectType.Reverb:
+ effect = new ReverbEffect();
+ break;
+ case EffectType.Reverb3d:
+ effect = new Reverb3dEffect();
+ break;
+ case EffectType.BiquadFilter:
+ effect = new BiquadFilterEffect();
+ break;
+ default:
+ throw new NotImplementedException($"EffectType {parameter.Type} not implemented!");
+ }
+ }
+
+ public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
+ {
+ if (context.GetCount() * Unsafe.SizeOf<EffectInParameter>() != _inputHeader.EffectsSize)
+ {
+ return ResultCode.InvalidUpdateInfo;
+ }
+
+ int initialOutputSize = _output.Length;
+
+ ReadOnlySpan<EffectInParameter> parameters = MemoryMarshal.Cast<byte, EffectInParameter>(_input.Slice(0, (int)_inputHeader.EffectsSize).Span);
+
+ _input = _input.Slice((int)_inputHeader.EffectsSize);
+
+ PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
+
+ for (int i = 0; i < context.GetCount(); i++)
+ {
+ EffectInParameter parameter = parameters[i];
+
+ ref EffectOutStatus outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatus>(ref _output)[0];
+
+ ref BaseEffect effect = ref context.GetEffect(i);
+
+ if (!effect.IsTypeValid(ref parameter))
+ {
+ ResetEffect(ref effect, ref parameter, mapper);
+ }
+
+ effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper);
+
+ if (updateErrorInfo.ErrorCode != ResultCode.Success)
+ {
+ _behaviourContext.AppendError(ref updateErrorInfo);
+ }
+
+ effect.StoreStatus(ref outStatus, isAudioRendererActive);
+ }
+
+ int currentOutputSize = _output.Length;
+
+ OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf<EffectOutStatus>() * context.GetCount());
+ OutputHeader.TotalSize += OutputHeader.EffectsSize;
+
+ Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode UpdateSplitter(SplitterContext context)
+ {
+ if (context.Update(_input.Span, out int consumedSize))
+ {
+ _input = _input.Slice(consumedSize);
+
+ return ResultCode.Success;
+ }
+ else
+ {
+ return ResultCode.InvalidUpdateInfo;
+ }
+ }
+
+ private bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, ReadOnlySpan<MixParameter> parameters)
+ {
+ uint maxMixStateCount = mixContext.GetCount();
+ uint totalRequiredMixBufferCount = 0;
+
+ for (int i = 0; i < inputMixCount; i++)
+ {
+ if (parameters[i].IsUsed)
+ {
+ if (parameters[i].DestinationMixId != Constants.UnusedMixId &&
+ parameters[i].DestinationMixId > maxMixStateCount &&
+ parameters[i].MixId != Constants.FinalMixId)
+ {
+ return true;
+ }
+
+ totalRequiredMixBufferCount += parameters[i].BufferCount;
+ }
+ }
+
+ return totalRequiredMixBufferCount > mixBufferCount;
+ }
+
+ public ResultCode UpdateMixes(MixContext mixContext, uint mixBufferCount, EffectContext effectContext, SplitterContext splitterContext)
+ {
+ uint mixCount;
+ uint inputMixSize;
+ uint inputSize = 0;
+
+ if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
+ {
+ MixInParameterDirtyOnlyUpdate parameter = MemoryMarshal.Cast<byte, MixInParameterDirtyOnlyUpdate>(_input.Span)[0];
+
+ mixCount = parameter.MixCount;
+
+ inputSize += (uint)Unsafe.SizeOf<MixInParameterDirtyOnlyUpdate>();
+ }
+ else
+ {
+ mixCount = mixContext.GetCount();
+ }
+
+ inputMixSize = mixCount * (uint)Unsafe.SizeOf<MixParameter>();
+
+ inputSize += inputMixSize;
+
+ if (inputSize != _inputHeader.MixesSize)
+ {
+ return ResultCode.InvalidUpdateInfo;
+ }
+
+ if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
+ {
+ _input = _input.Slice(Unsafe.SizeOf<MixInParameterDirtyOnlyUpdate>());
+ }
+
+ ReadOnlySpan<MixParameter> parameters = MemoryMarshal.Cast<byte, MixParameter>(_input.Span.Slice(0, (int)inputMixSize));
+
+ _input = _input.Slice((int)inputMixSize);
+
+ if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, parameters))
+ {
+ return ResultCode.InvalidUpdateInfo;
+ }
+
+ bool isMixContextDirty = false;
+
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ MixParameter parameter = parameters[i];
+
+ int mixId = i;
+
+ if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
+ {
+ mixId = parameter.MixId;
+ }
+
+ ref MixState mix = ref mixContext.GetState(mixId);
+
+ if (parameter.IsUsed != mix.IsUsed)
+ {
+ mix.IsUsed = parameter.IsUsed;
+
+ if (parameter.IsUsed)
+ {
+ mix.ClearEffectProcessingOrder();
+ }
+
+ isMixContextDirty = true;
+ }
+
+ if (mix.IsUsed)
+ {
+ isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, ref parameter, effectContext, splitterContext, _behaviourContext);
+ }
+ }
+
+ if (isMixContextDirty)
+ {
+ if (_behaviourContext.IsSplitterSupported() && splitterContext.UsingSplitter())
+ {
+ if (!mixContext.Sort(splitterContext))
+ {
+ return ResultCode.InvalidMixSorting;
+ }
+ }
+ else
+ {
+ mixContext.Sort();
+ }
+ }
+
+ return ResultCode.Success;
+ }
+
+ private static void ResetSink(ref BaseSink sink, ref SinkInParameter parameter)
+ {
+ sink.CleanUp();
+
+ switch (parameter.Type)
+ {
+ case SinkType.Invalid:
+ sink = new BaseSink();
+ break;
+ case SinkType.CircularBuffer:
+ sink = new CircularBufferSink();
+ break;
+ case SinkType.Device:
+ sink = new DeviceSink();
+ break;
+ default:
+ throw new NotImplementedException($"SinkType {parameter.Type} not implemented!");
+ }
+ }
+
+ public ResultCode UpdateSinks(SinkContext context, Memory<MemoryPoolState> memoryPools)
+ {
+ PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
+
+ if (context.GetCount() * Unsafe.SizeOf<SinkInParameter>() != _inputHeader.SinksSize)
+ {
+ return ResultCode.InvalidUpdateInfo;
+ }
+
+ int initialOutputSize = _output.Length;
+
+ ReadOnlySpan<SinkInParameter> parameters = MemoryMarshal.Cast<byte, SinkInParameter>(_input.Slice(0, (int)_inputHeader.SinksSize).Span);
+
+ _input = _input.Slice((int)_inputHeader.SinksSize);
+
+ for (int i = 0; i < context.GetCount(); i++)
+ {
+ SinkInParameter parameter = parameters[i];
+ ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef<SinkOutStatus>(ref _output)[0];
+ ref BaseSink sink = ref context.GetSink(i);
+
+ if (!sink.IsTypeValid(ref parameter))
+ {
+ ResetSink(ref sink, ref parameter);
+ }
+
+ sink.Update(out ErrorInfo updateErrorInfo, ref parameter, ref outStatus, mapper);
+
+ if (updateErrorInfo.ErrorCode != ResultCode.Success)
+ {
+ _behaviourContext.AppendError(ref updateErrorInfo);
+ }
+ }
+
+ int currentOutputSize = _output.Length;
+
+ OutputHeader.SinksSize = (uint)(Unsafe.SizeOf<SinkOutStatus>() * context.GetCount());
+ OutputHeader.TotalSize += OutputHeader.SinksSize;
+
+ Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize);
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode UpdatePerformanceBuffer(PerformanceManager manager, Span<byte> performanceOutput)
+ {
+ if (Unsafe.SizeOf<PerformanceInParameter>() != _inputHeader.PerformanceBufferSize)
+ {
+ return ResultCode.InvalidUpdateInfo;
+ }
+
+ PerformanceInParameter parameter = SpanIOHelper.Read<PerformanceInParameter>(ref _input);
+
+ ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef<PerformanceOutStatus>(ref _output)[0];
+
+ if (manager != null)
+ {
+ outStatus.HistorySize = manager.CopyHistories(performanceOutput);
+
+ manager.SetTargetNodeId(parameter.TargetNodeId);
+ }
+ else
+ {
+ outStatus.HistorySize = 0;
+ }
+
+ OutputHeader.PerformanceBufferSize = (uint)Unsafe.SizeOf<PerformanceOutStatus>();
+ OutputHeader.TotalSize += OutputHeader.PerformanceBufferSize;
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode UpdateErrorInfo()
+ {
+ ref BehaviourErrorInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef<BehaviourErrorInfoOutStatus>(ref _output)[0];
+
+ _behaviourContext.CopyErrorInfo(outStatus.ErrorInfos.ToSpan(), out outStatus.ErrorInfosCount);
+
+ OutputHeader.BehaviourSize = (uint)Unsafe.SizeOf<BehaviourErrorInfoOutStatus>();
+ OutputHeader.TotalSize += OutputHeader.BehaviourSize;
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode UpdateRendererInfo(ulong elapsedFrameCount)
+ {
+ ref RendererInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef<RendererInfoOutStatus>(ref _output)[0];
+
+ outStatus.ElapsedFrameCount = elapsedFrameCount;
+
+ OutputHeader.RenderInfoSize = (uint)Unsafe.SizeOf<RendererInfoOutStatus>();
+ OutputHeader.TotalSize += OutputHeader.RenderInfoSize;
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode CheckConsumedSize()
+ {
+ int consumedInputSize = _inputOrigin.Length - _input.Length;
+ int consumedOutputSize = _outputOrigin.Length - _output.Length;
+
+ if (consumedInputSize != _inputHeader.TotalSize)
+ {
+ Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed input size mismatch (got {consumedInputSize} expected {_inputHeader.TotalSize})");
+
+ return ResultCode.InvalidUpdateInfo;
+ }
+
+ if (consumedOutputSize != OutputHeader.TotalSize)
+ {
+ Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed output size mismatch (got {consumedOutputSize} expected {OutputHeader.TotalSize})");
+
+ return ResultCode.InvalidUpdateInfo;
+ }
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs b/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs
new file mode 100644
index 00000000..7bb0507c
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs
@@ -0,0 +1,36 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Server.Types
+{
+ /// <summary>
+ /// The execution mode of an <see cref="AudioRenderSystem"/>.
+ /// </summary>
+ public enum AudioRendererExecutionMode : byte
+ {
+ /// <summary>
+ /// Automatically send commands to the DSP at a fixed rate (see <see cref="AudioRenderSystem.SendCommands"/>
+ /// </summary>
+ Auto,
+
+ /// <summary>
+ /// Audio renderer operation needs to be done manually via ExecuteAudioRenderer.
+ /// </summary>
+ /// <remarks>This is not supported on the DSP and is as such stubbed.</remarks>
+ Manual
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs b/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs
new file mode 100644
index 00000000..f89086f6
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs
@@ -0,0 +1,41 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Server.Types
+{
+ /// <summary>
+ /// The rendering device of an <see cref="AudioRenderSystem"/>.
+ /// </summary>
+ public enum AudioRendererRenderingDevice : byte
+ {
+ /// <summary>
+ /// Rendering is performed on the DSP.
+ /// </summary>
+ /// <remarks>
+ /// Only supports <see cref="AudioRendererExecutionMode.Auto"/>.
+ /// </remarks>
+ Dsp,
+
+ /// <summary>
+ /// Rendering is performed on the CPU.
+ /// </summary>
+ /// <remarks>
+ /// Only supports <see cref="AudioRendererExecutionMode.Manual"/>.
+ /// </remarks>
+ Cpu
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs b/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs
new file mode 100644
index 00000000..7f8895c9
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs
@@ -0,0 +1,56 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+namespace Ryujinx.Audio.Renderer.Server.Types
+{
+ /// <summary>
+ /// The internal play state of a <see cref="Voice.VoiceState"/>
+ /// </summary>
+ public enum PlayState
+ {
+ /// <summary>
+ /// The voice has been started and is playing.
+ /// </summary>
+ Started,
+
+ /// <summary>
+ /// The voice has been stopped.
+ /// </summary>
+ /// <remarks>
+ /// This cannot be directly set by user.
+ /// See <see cref="Stopping"/> for correct usage.
+ /// </remarks>
+ Stopped,
+
+ /// <summary>
+ /// The user asked the voice to be stopped.
+ /// </summary>
+ /// <remarks>
+ /// This is changed to the <see cref="Stopped"/> state after command generation.
+ /// <seealso cref="Voice.VoiceState.UpdateForCommandGeneration(Voice.VoiceContext)"/>
+ /// </remarks>
+ Stopping,
+
+ /// <summary>
+ /// The voice has been paused by user request.
+ /// </summary>
+ /// <remarks>
+ /// The user can resume to the <see cref="Started"/> state.
+ /// </remarks>
+ Paused
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs b/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs
new file mode 100644
index 00000000..6878123b
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs
@@ -0,0 +1,101 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Server.Upsampler
+{
+ /// <summary>
+ /// Upsampler manager.
+ /// </summary>
+ public class UpsamplerManager
+ {
+ /// <summary>
+ /// Work buffer for upsampler.
+ /// </summary>
+ private Memory<float> _upSamplerWorkBuffer;
+
+ /// <summary>
+ /// Global lock of the object.
+ /// </summary>
+ private object Lock = new object();
+
+ /// <summary>
+ /// The upsamplers instances.
+ /// </summary>
+ private UpsamplerState[] _upsamplers;
+
+ /// <summary>
+ /// The count of upsamplers.
+ /// </summary>
+ private uint _count;
+
+ /// <summary>
+ /// Create a new <see cref="UpsamplerManager"/>.
+ /// </summary>
+ /// <param name="upSamplerWorkBuffer">Work buffer for upsampler.</param>
+ /// <param name="count">The count of upsamplers.</param>
+ public UpsamplerManager(Memory<float> upSamplerWorkBuffer, uint count)
+ {
+ _upSamplerWorkBuffer = upSamplerWorkBuffer;
+ _count = count;
+
+ _upsamplers = new UpsamplerState[_count];
+ }
+
+ /// <summary>
+ /// Allocate a new <see cref="UpsamplerState"/>.
+ /// </summary>
+ /// <returns>A new <see cref="UpsamplerState"/> or null if out of memory.</returns>
+ public UpsamplerState Allocate()
+ {
+ int workBufferOffset = 0;
+
+ lock (Lock)
+ {
+ for (int i = 0; i < _count; i++)
+ {
+ if (_upsamplers[i] == null)
+ {
+ _upsamplers[i] = new UpsamplerState(this, i, _upSamplerWorkBuffer.Slice(workBufferOffset, Constants.UpSampleEntrySize), Constants.TargetSampleCount);
+
+ return _upsamplers[i];
+ }
+
+ workBufferOffset += Constants.UpSampleEntrySize;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Free a <see cref="UpsamplerState"/> at the given index.
+ /// </summary>
+ /// <param name="index">The index of the <see cref="UpsamplerState"/> to free.</param>
+ public void Free(int index)
+ {
+ lock (Lock)
+ {
+ Debug.Assert(_upsamplers[index] != null);
+
+ _upsamplers[index] = null;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs b/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs
new file mode 100644
index 00000000..8ea55384
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs
@@ -0,0 +1,80 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+
+namespace Ryujinx.Audio.Renderer.Server.Upsampler
+{
+ /// <summary>
+ /// Server state for a upsampling.
+ /// </summary>
+ public class UpsamplerState
+ {
+ /// <summary>
+ /// The output buffer containing the target samples.
+ /// </summary>
+ public Memory<float> OutputBuffer { get; }
+
+ /// <summary>
+ /// The target sample count.
+ /// </summary>
+ public uint SampleCount { get; }
+
+ /// <summary>
+ /// The index of the <see cref="UpsamplerState"/>. (used to free it)
+ /// </summary>
+ private int _index;
+
+ /// <summary>
+ /// The <see cref="UpsamplerManager"/>.
+ /// </summary>
+ private UpsamplerManager _manager;
+
+ /// <summary>
+ /// The source sample count.
+ /// </summary>
+ public uint SourceSampleCount;
+
+ /// <summary>
+ /// The input buffer indices of the buffers holding the samples that need upsampling.
+ /// </summary>
+ public ushort[] InputBufferIndices;
+
+ /// <summary>
+ /// Create a new <see cref="UpsamplerState"/>.
+ /// </summary>
+ /// <param name="manager">The upsampler manager.</param>
+ /// <param name="index">The index of the <see cref="UpsamplerState"/>. (used to free it)</param>
+ /// <param name="outputBuffer">The output buffer used to contain the target samples.</param>
+ /// <param name="sampleCount">The target sample count.</param>
+ public UpsamplerState(UpsamplerManager manager, int index, Memory<float> outputBuffer, uint sampleCount)
+ {
+ _manager = manager;
+ _index = index;
+ OutputBuffer = outputBuffer;
+ SampleCount = sampleCount;
+ }
+
+ /// <summary>
+ /// Release the <see cref="UpsamplerState"/>.
+ /// </summary>
+ public void Release()
+ {
+ _manager.Free(_index);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs b/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs
new file mode 100644
index 00000000..00520cfc
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs
@@ -0,0 +1,57 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Voice
+{
+ /// <summary>
+ /// Server state for a voice channel resource.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 0xD0, Pack = Alignment)]
+ public struct VoiceChannelResource
+ {
+ public const int Alignment = 0x10;
+
+ /// <summary>
+ /// Mix volumes for the resource.
+ /// </summary>
+ public Array24<float> Mix;
+
+ /// <summary>
+ /// Previous mix volumes for resource.
+ /// </summary>
+ public Array24<float> PreviousMix;
+
+ /// <summary>
+ /// The id of the resource.
+ /// </summary>
+ public uint Id;
+
+ /// <summary>
+ /// Indicate if the resource is used.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ public void UpdateState()
+ {
+ Mix.ToSpan().CopyTo(PreviousMix.ToSpan());
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs b/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs
new file mode 100644
index 00000000..0dfaa4d5
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs
@@ -0,0 +1,166 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Utils;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Server.Voice
+{
+ /// <summary>
+ /// Voice context.
+ /// </summary>
+ public class VoiceContext
+ {
+ /// <summary>
+ /// Storage of the sorted indices to <see cref="VoiceState"/>.
+ /// </summary>
+ private Memory<int> _sortedVoices;
+
+ /// <summary>
+ /// Storage for <see cref="VoiceState"/>.
+ /// </summary>
+ private Memory<VoiceState> _voices;
+
+ /// <summary>
+ /// Storage for <see cref="VoiceChannelResource"/>.
+ /// </summary>
+ private Memory<VoiceChannelResource> _voiceChannelResources;
+
+ /// <summary>
+ /// Storage for <see cref="VoiceUpdateState"/> that are used during audio renderer server updates.
+ /// </summary>
+ private Memory<VoiceUpdateState> _voiceUpdateStatesCpu;
+
+ /// <summary>
+ /// Storage for <see cref="VoiceUpdateState"/> for the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ private Memory<VoiceUpdateState> _voiceUpdateStatesDsp;
+
+ /// <summary>
+ /// The total voice count.
+ /// </summary>
+ private uint _voiceCount;
+
+ public void Initialize(Memory<int> sortedVoices, Memory<VoiceState> voices, Memory<VoiceChannelResource> voiceChannelResources, Memory<VoiceUpdateState> voiceUpdateStatesCpu, Memory<VoiceUpdateState> voiceUpdateStatesDsp, uint voiceCount)
+ {
+ _sortedVoices = sortedVoices;
+ _voices = voices;
+ _voiceChannelResources = voiceChannelResources;
+ _voiceUpdateStatesCpu = voiceUpdateStatesCpu;
+ _voiceUpdateStatesDsp = voiceUpdateStatesDsp;
+ _voiceCount = voiceCount;
+ }
+
+ /// <summary>
+ /// Get the total voice count.
+ /// </summary>
+ /// <returns>The total voice count.</returns>
+ public uint GetCount()
+ {
+ return _voiceCount;
+ }
+
+ /// <summary>
+ /// Get a reference to a <see cref="VoiceChannelResource"/> at the given <paramref name="id"/>.
+ /// </summary>
+ /// <param name="id">The index to use.</param>
+ /// <returns>A reference to a <see cref="VoiceChannelResource"/> at the given <paramref name="id"/>.</returns>
+ public ref VoiceChannelResource GetChannelResource(int id)
+ {
+ return ref SpanIOHelper.GetFromMemory(_voiceChannelResources, id, _voiceCount);
+ }
+
+ /// <summary>
+ /// Get a <see cref="Memory{VoiceUpdateState}"/> at the given <paramref name="id"/>.
+ /// </summary>
+ /// <param name="id">The index to use.</param>
+ /// <returns>A <see cref="Memory{VoiceUpdateState}"/> at the given <paramref name="id"/>.</returns>
+ /// <remarks>The returned <see cref="Memory{VoiceUpdateState}"/> should only be used when updating the server state.</remarks>
+ public Memory<VoiceUpdateState> GetUpdateStateForCpu(int id)
+ {
+ return SpanIOHelper.GetMemory(_voiceUpdateStatesCpu, id, _voiceCount);
+ }
+
+ /// <summary>
+ /// Get a <see cref="Memory{VoiceUpdateState}"/> at the given <paramref name="id"/>.
+ /// </summary>
+ /// <param name="id">The index to use.</param>
+ /// <returns>A <see cref="Memory{VoiceUpdateState}"/> at the given <paramref name="id"/>.</returns>
+ /// <remarks>The returned <see cref="Memory{VoiceUpdateState}"/> should only be used in the context of processing on the <see cref="Dsp.AudioProcessor"/>.</remarks>
+ public Memory<VoiceUpdateState> GetUpdateStateForDsp(int id)
+ {
+ return SpanIOHelper.GetMemory(_voiceUpdateStatesDsp, id, _voiceCount);
+ }
+
+ /// <summary>
+ /// Get a reference to a <see cref="VoiceState"/> at the given <paramref name="id"/>.
+ /// </summary>
+ /// <param name="id">The index to use.</param>
+ /// <returns>A reference to a <see cref="VoiceState"/> at the given <paramref name="id"/>.</returns>
+ public ref VoiceState GetState(int id)
+ {
+ return ref SpanIOHelper.GetFromMemory(_voices, id, _voiceCount);
+ }
+
+ public ref VoiceState GetSortedState(int id)
+ {
+ Debug.Assert(id >= 0 && id < _voiceCount);
+
+ return ref GetState(_sortedVoices.Span[id]);
+ }
+
+ /// <summary>
+ /// Update internal state during command generation.
+ /// </summary>
+ public void UpdateForCommandGeneration()
+ {
+ _voiceUpdateStatesDsp.CopyTo(_voiceUpdateStatesCpu);
+ }
+
+ /// <summary>
+ /// Sort the internal voices by priority and sorting order (if the priorities match).
+ /// </summary>
+ public void Sort()
+ {
+ for (int i = 0; i < _voiceCount; i++)
+ {
+ _sortedVoices.Span[i] = i;
+ }
+
+ int[] sortedVoicesTemp = _sortedVoices.Slice(0, (int)GetCount()).ToArray();
+
+ Array.Sort(sortedVoicesTemp, (a, b) =>
+ {
+ ref VoiceState aState = ref GetState(a);
+ ref VoiceState bState = ref GetState(b);
+
+ int result = aState.Priority.CompareTo(bState.Priority);
+
+ if (result == 0)
+ {
+ return aState.SortingOrder.CompareTo(bState.SortingOrder);
+ }
+
+ return result;
+ });
+
+ sortedVoicesTemp.AsSpan().CopyTo(_sortedVoices.Span);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs b/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs
new file mode 100644
index 00000000..89abe9aa
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs
@@ -0,0 +1,716 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using Ryujinx.Common.Memory;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
+using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
+
+namespace Ryujinx.Audio.Renderer.Server.Voice
+{
+ [StructLayout(LayoutKind.Sequential, Pack = Alignment)]
+ public struct VoiceState
+ {
+ public const int Alignment = 0x10;
+
+ /// <summary>
+ /// Set to true if the voice is used.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool InUse;
+
+ /// <summary>
+ /// Set to true if the voice is new.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsNew;
+
+ [MarshalAs(UnmanagedType.I1)]
+ public bool WasPlaying;
+
+ /// <summary>
+ /// The <see cref="SampleFormat"/> of the voice.
+ /// </summary>
+ public SampleFormat SampleFormat;
+
+ /// <summary>
+ /// The sample rate of the voice.
+ /// </summary>
+ public uint SampleRate;
+
+ /// <summary>
+ /// The total channel count used.
+ /// </summary>
+ public uint ChannelsCount;
+
+ /// <summary>
+ /// Id of the voice.
+ /// </summary>
+ public int Id;
+
+ /// <summary>
+ /// Node id of the voice.
+ /// </summary>
+ public int NodeId;
+
+ /// <summary>
+ /// The target mix id of the voice.
+ /// </summary>
+ public int MixId;
+
+ /// <summary>
+ /// The current voice <see cref="Types.PlayState"/>.
+ /// </summary>
+ public Types.PlayState PlayState;
+
+ /// <summary>
+ /// The previous voice <see cref="Types.PlayState"/>.
+ /// </summary>
+ public Types.PlayState PreviousPlayState;
+
+ /// <summary>
+ /// The priority of the voice.
+ /// </summary>
+ public uint Priority;
+
+ /// <summary>
+ /// Target sorting position of the voice. (used to sort voice with the same <see cref="Priority"/>)
+ /// </summary>
+ public uint SortingOrder;
+
+ /// <summary>
+ /// The pitch used on the voice.
+ /// </summary>
+ public float Pitch;
+
+ /// <summary>
+ /// The output volume of the voice.
+ /// </summary>
+ public float Volume;
+
+ /// <summary>
+ /// The previous output volume of the voice.
+ /// </summary>
+ public float PreviousVolume;
+
+ /// <summary>
+ /// Biquad filters to apply to the output of the voice.
+ /// </summary>
+ public Array2<BiquadFilterParameter> BiquadFilters;
+
+ /// <summary>
+ /// Total count of <see cref="WaveBufferInternal"/> of the voice.
+ /// </summary>
+ public uint WaveBuffersCount;
+
+ /// <summary>
+ /// Current playing <see cref="WaveBufferInternal"/> of the voice.
+ /// </summary>
+ public uint WaveBuffersIndex;
+
+ /// <summary>
+ /// Change the behaviour of the voice.
+ /// </summary>
+ /// <remarks>This was added on REV5.</remarks>
+ public DecodingBehaviour DecodingBehaviour;
+
+ /// <summary>
+ /// User state <see cref="AddressInfo"/> required by the data source.
+ /// </summary>
+ /// <remarks>Only used for <see cref="SampleFormat.Adpcm"/> as the GC-ADPCM coefficients.</remarks>
+ public AddressInfo DataSourceStateAddressInfo;
+
+ /// <summary>
+ /// The wavebuffers of this voice.
+ /// </summary>
+ public Array4<WaveBuffer> WaveBuffers;
+
+ /// <summary>
+ /// The channel resource ids associated to the voice.
+ /// </summary>
+ public Array6<int> ChannelResourceIds;
+
+ /// <summary>
+ /// The target splitter id of the voice.
+ /// </summary>
+ public uint SplitterId;
+
+ /// <summary>
+ /// Change the Sample Rate Conversion (SRC) quality of the voice.
+ /// </summary>
+ /// <remarks>This was added on REV8.</remarks>
+ public SampleRateConversionQuality SrcQuality;
+
+ /// <summary>
+ /// If set to true, the voice was dropped.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool VoiceDropFlag;
+
+ /// <summary>
+ /// Set to true if the data source state work buffer wasn't mapped.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool DataSourceStateUnmapped;
+
+ /// <summary>
+ /// Set to true if any of the <see cref="WaveBuffer.BufferAddressInfo"/> work buffer wasn't mapped.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool BufferInfoUnmapped;
+
+ /// <summary>
+ /// The biquad filter initialization state storage.
+ /// </summary>
+ private BiquadFilterNeedInitializationArrayStruct _biquadFilterNeedInitialization;
+
+ /// <summary>
+ /// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played.
+ /// </summary>
+ /// <remarks>This was added on REV5.</remarks>
+ public byte FlushWaveBufferCount;
+
+ [StructLayout(LayoutKind.Sequential, Size = Constants.VoiceBiquadFilterCount)]
+ private struct BiquadFilterNeedInitializationArrayStruct { }
+
+ /// <summary>
+ /// The biquad filter initialization state array.
+ /// </summary>
+ public Span<bool> BiquadFilterNeedInitialization => SpanHelpers.AsSpan<BiquadFilterNeedInitializationArrayStruct, bool>(ref _biquadFilterNeedInitialization);
+
+ /// <summary>
+ /// Initialize the <see cref="VoiceState"/>.
+ /// </summary>
+ public void Initialize()
+ {
+ IsNew = false;
+ VoiceDropFlag = false;
+ DataSourceStateUnmapped = false;
+ BufferInfoUnmapped = false;
+ FlushWaveBufferCount = 0;
+ PlayState = Types.PlayState.Stopped;
+ Priority = Constants.VoiceLowestPriority;
+ Id = 0;
+ NodeId = 0;
+ SampleRate = 0;
+ SampleFormat = SampleFormat.Invalid;
+ ChannelsCount = 0;
+ Pitch = 0.0f;
+ Volume= 0.0f;
+ PreviousVolume = 0.0f;
+ BiquadFilters.ToSpan().Fill(new BiquadFilterParameter());
+ WaveBuffersCount = 0;
+ WaveBuffersIndex = 0;
+ MixId = Constants.UnusedMixId;
+ SplitterId = Constants.UnusedSplitterId;
+ DataSourceStateAddressInfo.Setup(0, 0);
+
+ InitializeWaveBuffers();
+ }
+
+ /// <summary>
+ /// Initialize the <see cref="WaveBuffer"/> in this <see cref="VoiceState"/>.
+ /// </summary>
+ private void InitializeWaveBuffers()
+ {
+ for (int i = 0; i < WaveBuffers.Length; i++)
+ {
+ WaveBuffers[i].StartSampleOffset = 0;
+ WaveBuffers[i].EndSampleOffset = 0;
+ WaveBuffers[i].ShouldLoop = false;
+ WaveBuffers[i].IsEndOfStream = false;
+ WaveBuffers[i].BufferAddressInfo.Setup(0, 0);
+ WaveBuffers[i].ContextAddressInfo.Setup(0, 0);
+ WaveBuffers[i].IsSendToAudioProcessor = true;
+ }
+ }
+
+ /// <summary>
+ /// Check if the voice needs to be skipped.
+ /// </summary>
+ /// <returns>Returns true if the voice needs to be skipped.</returns>
+ public bool ShouldSkip()
+ {
+ return !InUse || WaveBuffersCount == 0 || DataSourceStateUnmapped || BufferInfoUnmapped || VoiceDropFlag;
+ }
+
+ /// <summary>
+ /// Return true if the mix has any destinations.
+ /// </summary>
+ /// <returns>True if the mix has any destinations.</returns>
+ public bool HasAnyDestination()
+ {
+ return MixId != Constants.UnusedMixId || SplitterId != Constants.UnusedSplitterId;
+ }
+
+ /// <summary>
+ /// Indicate if the server voice information needs to be updated.
+ /// </summary>
+ /// <param name="parameter">The user parameter.</param>
+ /// <returns>Return true, if the server voice information needs to be updated.</returns>
+ private bool ShouldUpdateParameters(ref VoiceInParameter parameter)
+ {
+ if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress)
+ {
+ return DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize;
+ }
+
+ return DataSourceStateAddressInfo.CpuAddress != parameter.DataSourceStateAddress ||
+ DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize ||
+ DataSourceStateUnmapped;
+ }
+
+ /// <summary>
+ /// Update the internal state from a user parameter.
+ /// </summary>
+ /// <param name="outErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
+ /// <param name="parameter">The user parameter.</param>
+ /// <param name="poolMapper">The mapper to use.</param>
+ /// <param name="behaviourContext">The behaviour context.</param>
+ public void UpdateParameters(out ErrorInfo outErrorInfo, ref VoiceInParameter parameter, ref PoolMapper poolMapper, ref BehaviourContext behaviourContext)
+ {
+ InUse = parameter.InUse;
+ Id = parameter.Id;
+ NodeId = parameter.NodeId;
+
+ UpdatePlayState(parameter.PlayState);
+
+ SrcQuality = parameter.SrcQuality;
+
+ Priority = parameter.Priority;
+ SortingOrder = parameter.SortingOrder;
+ SampleRate = parameter.SampleRate;
+ SampleFormat = parameter.SampleFormat;
+ ChannelsCount = parameter.ChannelCount;
+ Pitch = parameter.Pitch;
+ Volume = parameter.Volume;
+ parameter.BiquadFilters.ToSpan().CopyTo(BiquadFilters.ToSpan());
+ WaveBuffersCount = parameter.WaveBuffersCount;
+ WaveBuffersIndex = parameter.WaveBuffersIndex;
+
+ if (behaviourContext.IsFlushVoiceWaveBuffersSupported())
+ {
+ FlushWaveBufferCount += parameter.FlushWaveBufferCount;
+ }
+
+ MixId = parameter.MixId;
+
+ if (behaviourContext.IsSplitterSupported())
+ {
+ SplitterId = parameter.SplitterId;
+ }
+ else
+ {
+ SplitterId = Constants.UnusedSplitterId;
+ }
+
+ parameter.ChannelResourceIds.ToSpan().CopyTo(ChannelResourceIds.ToSpan());
+
+ DecodingBehaviour behaviour = DecodingBehaviour.Default;
+
+ if (behaviourContext.IsDecodingBehaviourFlagSupported())
+ {
+ behaviour = parameter.DecodingBehaviourFlags;
+ }
+
+ DecodingBehaviour = behaviour;
+
+ if (parameter.ResetVoiceDropFlag)
+ {
+ VoiceDropFlag = false;
+ }
+
+ if (ShouldUpdateParameters(ref parameter))
+ {
+ DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize);
+ }
+ else
+ {
+ outErrorInfo = new ErrorInfo();
+ }
+ }
+
+ /// <summary>
+ /// Update the internal play state from user play state.
+ /// </summary>
+ /// <param name="userPlayState">The target user play state.</param>
+ public void UpdatePlayState(PlayState userPlayState)
+ {
+ Types.PlayState oldServerPlayState = PlayState;
+
+ PreviousPlayState = oldServerPlayState;
+
+ Types.PlayState newServerPlayState;
+
+ switch (userPlayState)
+ {
+ case Common.PlayState.Start:
+ newServerPlayState = Types.PlayState.Started;
+ break;
+
+ case Common.PlayState.Stop:
+ if (oldServerPlayState == Types.PlayState.Stopped)
+ {
+ return;
+ }
+
+ newServerPlayState = Types.PlayState.Stopping;
+ break;
+
+ case Common.PlayState.Pause:
+ newServerPlayState = Types.PlayState.Paused;
+ break;
+
+ default:
+ throw new NotImplementedException($"Unhandled PlayState.{userPlayState}");
+ }
+
+ PlayState = newServerPlayState;
+ }
+
+ /// <summary>
+ /// Write the status of the voice to the given user output.
+ /// </summary>
+ /// <param name="outStatus">The given user output.</param>
+ /// <param name="parameter">The user parameter.</param>
+ /// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
+ public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, Memory<VoiceUpdateState>[] voiceUpdateStates)
+ {
+#if DEBUG
+ // Sanity check in debug mode of the internal state
+ if (!parameter.IsNew && !IsNew)
+ {
+ for (int i = 1; i < ChannelsCount; i++)
+ {
+ ref VoiceUpdateState stateA = ref voiceUpdateStates[i - 1].Span[0];
+ ref VoiceUpdateState stateB = ref voiceUpdateStates[i].Span[0];
+
+ Debug.Assert(stateA.WaveBufferConsumed == stateB.WaveBufferConsumed);
+ Debug.Assert(stateA.PlayedSampleCount == stateB.PlayedSampleCount);
+ Debug.Assert(stateA.Offset == stateB.Offset);
+ Debug.Assert(stateA.WaveBufferIndex == stateB.WaveBufferIndex);
+ Debug.Assert(stateA.Fraction == stateB.Fraction);
+ Debug.Assert(stateA.IsWaveBufferValid.SequenceEqual(stateB.IsWaveBufferValid));
+ }
+ }
+#endif
+ if (parameter.IsNew || IsNew)
+ {
+ IsNew = true;
+
+ outStatus.VoiceDropFlag = false;
+ outStatus.PlayedWaveBuffersCount = 0;
+ outStatus.PlayedSampleCount = 0;
+ }
+ else
+ {
+ ref VoiceUpdateState state = ref voiceUpdateStates[0].Span[0];
+
+ outStatus.VoiceDropFlag = VoiceDropFlag;
+ outStatus.PlayedWaveBuffersCount = state.WaveBufferConsumed;
+ outStatus.PlayedSampleCount = state.PlayedSampleCount;
+ }
+ }
+
+ /// <summary>
+ /// Update the internal state of all the <see cref="WaveBuffer"/> of the <see cref="VoiceState"/>.
+ /// </summary>
+ /// <param name="errorInfos">An array of <see cref="ErrorInfo"/> used to report errors when mapping any of the <see cref="WaveBuffer"/>.</param>
+ /// <param name="parameter">The user parameter.</param>
+ /// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
+ /// <param name="mapper">The mapper to use.</param>
+ /// <param name="behaviourContext">The behaviour context.</param>
+ public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, Memory<VoiceUpdateState>[] voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext)
+ {
+ errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2];
+
+ if (parameter.IsNew)
+ {
+ InitializeWaveBuffers();
+
+ for (int i = 0; i < parameter.ChannelCount; i++)
+ {
+ voiceUpdateStates[i].Span[0].IsWaveBufferValid.Fill(false);
+ }
+ }
+
+ ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[0].Span[0];
+
+ for (int i = 0; i < Constants.VoiceWaveBufferCount; i++)
+ {
+ UpdateWaveBuffer(errorInfos.AsSpan().Slice(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext);
+ }
+ }
+
+ /// <summary>
+ /// Update the internal state of one of the <see cref="WaveBuffer"/> of the <see cref="VoiceState"/>.
+ /// </summary>
+ /// <param name="errorInfos">A <see cref="Span{ErrorInfo}"/> used to report errors when mapping the <see cref="WaveBuffer"/>.</param>
+ /// <param name="waveBuffer">The <see cref="WaveBuffer"/> to update.</param>
+ /// <param name="inputWaveBuffer">The <see cref="WaveBufferInternal"/> from the user input.</param>
+ /// <param name="sampleFormat">The <see cref="SampleFormat"/> from the user input.</param>
+ /// <param name="isValid">If set to true, the server side wavebuffer is considered valid.</param>
+ /// <param name="mapper">The mapper to use.</param>
+ /// <param name="behaviourContext">The behaviour context.</param>
+ private void UpdateWaveBuffer(Span<ErrorInfo> errorInfos, ref WaveBuffer waveBuffer, ref WaveBufferInternal inputWaveBuffer, SampleFormat sampleFormat, bool isValid, ref PoolMapper mapper, ref BehaviourContext behaviourContext)
+ {
+ if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0)
+ {
+ mapper.ForceUnmap(ref waveBuffer.BufferAddressInfo);
+ waveBuffer.BufferAddressInfo.Setup(0, 0);
+ }
+
+ if (!inputWaveBuffer.SentToServer || BufferInfoUnmapped)
+ {
+ if (inputWaveBuffer.IsSampleOffsetValid(sampleFormat))
+ {
+ Debug.Assert(waveBuffer.IsSendToAudioProcessor);
+
+ waveBuffer.IsSendToAudioProcessor = false;
+ waveBuffer.StartSampleOffset = inputWaveBuffer.StartSampleOffset;
+ waveBuffer.EndSampleOffset = inputWaveBuffer.EndSampleOffset;
+ waveBuffer.ShouldLoop = inputWaveBuffer.ShouldLoop;
+ waveBuffer.IsEndOfStream = inputWaveBuffer.IsEndOfStream;
+ waveBuffer.LoopStartSampleOffset = inputWaveBuffer.LoopFirstSampleOffset;
+ waveBuffer.LoopEndSampleOffset = inputWaveBuffer.LoopLastSampleOffset;
+ waveBuffer.LoopCount = inputWaveBuffer.LoopCount;
+
+ BufferInfoUnmapped = !mapper.TryAttachBuffer(out ErrorInfo bufferInfoError, ref waveBuffer.BufferAddressInfo, inputWaveBuffer.Address, inputWaveBuffer.Size);
+
+ errorInfos[0] = bufferInfoError;
+
+ if (sampleFormat == SampleFormat.Adpcm && behaviourContext.IsAdpcmLoopContextBugFixed() && inputWaveBuffer.ContextAddress != 0)
+ {
+ bool adpcmLoopContextMapped = mapper.TryAttachBuffer(out ErrorInfo adpcmLoopContextInfoError,
+ ref waveBuffer.ContextAddressInfo,
+ inputWaveBuffer.ContextAddress,
+ inputWaveBuffer.ContextSize);
+
+ errorInfos[1] = adpcmLoopContextInfoError;
+
+ if (adpcmLoopContextMapped)
+ {
+ BufferInfoUnmapped = DataSourceStateUnmapped;
+ }
+ else
+ {
+ BufferInfoUnmapped = true;
+ }
+ }
+ else
+ {
+ waveBuffer.ContextAddressInfo.Setup(0, 0);
+ }
+ }
+ else
+ {
+ errorInfos[0].ErrorCode = ResultCode.InvalidAddressInfo;
+ errorInfos[0].ExtraErrorInfo = inputWaveBuffer.Address;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Reset the resources associated to this <see cref="VoiceState"/>.
+ /// </summary>
+ /// <param name="context">The voice context.</param>
+ private void ResetResources(VoiceContext context)
+ {
+ for (int i = 0; i < ChannelsCount; i++)
+ {
+ int channelResourceId = ChannelResourceIds[i];
+
+ ref VoiceChannelResource voiceChannelResource = ref context.GetChannelResource(channelResourceId);
+
+ Debug.Assert(voiceChannelResource.IsUsed);
+
+ Memory<VoiceUpdateState> dspSharedState = context.GetUpdateStateForDsp(channelResourceId);
+
+ MemoryMarshal.Cast<VoiceUpdateState, byte>(dspSharedState.Span).Fill(0);
+
+ voiceChannelResource.UpdateState();
+ }
+ }
+
+ /// <summary>
+ /// Flush a certain amount of <see cref="WaveBuffer"/>.
+ /// </summary>
+ /// <param name="waveBufferCount">The amount of wavebuffer to flush.</param>
+ /// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
+ /// <param name="channelCount">The channel count from user input.</param>
+ private void FlushWaveBuffers(uint waveBufferCount, Memory<VoiceUpdateState>[] voiceUpdateStates, uint channelCount)
+ {
+ uint waveBufferIndex = WaveBuffersIndex;
+
+ for (int i = 0; i < waveBufferCount; i++)
+ {
+ WaveBuffers[(int)waveBufferIndex].IsSendToAudioProcessor = true;
+
+ for (int j = 0; j < channelCount; j++)
+ {
+ ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0];
+
+ voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount;
+ voiceUpdateState.WaveBufferConsumed++;
+ voiceUpdateState.IsWaveBufferValid[(int)waveBufferIndex] = false;
+ }
+
+ waveBufferIndex = (waveBufferIndex + 1) % Constants.VoiceWaveBufferCount;
+ }
+ }
+
+ /// <summary>
+ /// Update the internal parameters for command generation.
+ /// </summary>
+ /// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
+ /// <returns>Return true if this voice should be played.</returns>
+ public bool UpdateParametersForCommandGeneration(Memory<VoiceUpdateState>[] voiceUpdateStates)
+ {
+ if (FlushWaveBufferCount != 0)
+ {
+ FlushWaveBuffers(FlushWaveBufferCount, voiceUpdateStates, ChannelsCount);
+
+ FlushWaveBufferCount = 0;
+ }
+
+ switch (PlayState)
+ {
+ case Types.PlayState.Started:
+ for (int i = 0; i < WaveBuffers.Length; i++)
+ {
+ ref WaveBuffer wavebuffer = ref WaveBuffers[i];
+
+ if (!wavebuffer.IsSendToAudioProcessor)
+ {
+ for (int y = 0; y < ChannelsCount; y++)
+ {
+ Debug.Assert(!voiceUpdateStates[y].Span[0].IsWaveBufferValid[i]);
+
+ voiceUpdateStates[y].Span[0].IsWaveBufferValid[i] = true;
+ }
+
+ wavebuffer.IsSendToAudioProcessor = true;
+ }
+ }
+
+ WasPlaying = false;
+
+ ref VoiceUpdateState primaryVoiceUpdateState = ref voiceUpdateStates[0].Span[0];
+
+ for (int i = 0; i < primaryVoiceUpdateState.IsWaveBufferValid.Length; i++)
+ {
+ if (primaryVoiceUpdateState.IsWaveBufferValid[i])
+ {
+ return true;
+ }
+ }
+
+ return false;
+
+ case Types.PlayState.Stopping:
+ for (int i = 0; i < WaveBuffers.Length; i++)
+ {
+ ref WaveBuffer wavebuffer = ref WaveBuffers[i];
+
+ wavebuffer.IsSendToAudioProcessor = true;
+
+ for (int j = 0; j < ChannelsCount; j++)
+ {
+ ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0];
+
+ if (voiceUpdateState.IsWaveBufferValid[i])
+ {
+ voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount;
+ voiceUpdateState.WaveBufferConsumed++;
+ }
+
+ voiceUpdateState.IsWaveBufferValid[i] = false;
+ }
+ }
+
+ for (int i = 0; i < ChannelsCount; i++)
+ {
+ ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[i].Span[0];
+
+ voiceUpdateState.Offset = 0;
+ voiceUpdateState.PlayedSampleCount = 0;
+ voiceUpdateState.Pitch.ToSpan().Fill(0);
+ voiceUpdateState.Fraction = 0;
+ voiceUpdateState.LoopContext = new Dsp.State.AdpcmLoopContext();
+ }
+
+ PlayState = Types.PlayState.Stopped;
+ WasPlaying = PreviousPlayState == Types.PlayState.Started;
+
+ return WasPlaying;
+
+ case Types.PlayState.Stopped:
+ case Types.PlayState.Paused:
+ foreach (ref WaveBuffer wavebuffer in WaveBuffers.ToSpan())
+ {
+ wavebuffer.BufferAddressInfo.GetReference(true);
+ wavebuffer.ContextAddressInfo.GetReference(true);
+ }
+
+ if (SampleFormat == SampleFormat.Adpcm)
+ {
+ if (DataSourceStateAddressInfo.CpuAddress != 0)
+ {
+ DataSourceStateAddressInfo.GetReference(true);
+ }
+ }
+
+ WasPlaying = PreviousPlayState == Types.PlayState.Started;
+
+ return WasPlaying;
+ default:
+ throw new NotImplementedException($"{PlayState}");
+ }
+ }
+
+ /// <summary>
+ /// Update the internal state for command generation.
+ /// </summary>
+ /// <param name="context">The voice context.</param>
+ /// <returns>Return true if this voice should be played.</returns>
+ public bool UpdateForCommandGeneration(VoiceContext context)
+ {
+ if (IsNew)
+ {
+ ResetResources(context);
+ PreviousVolume = Volume;
+ IsNew = false;
+ }
+
+ Memory<VoiceUpdateState>[] voiceUpdateStates = new Memory<VoiceUpdateState>[Constants.VoiceChannelCountMax];
+
+ for (int i = 0; i < ChannelsCount; i++)
+ {
+ voiceUpdateStates[i] = context.GetUpdateStateForDsp(ChannelResourceIds[i]);
+ }
+
+ return UpdateParametersForCommandGeneration(voiceUpdateStates);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs b/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs
new file mode 100644
index 00000000..a03aa757
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs
@@ -0,0 +1,121 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Voice
+{
+ /// <summary>
+ /// A wavebuffer used for server update.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 1)]
+ public struct WaveBuffer
+ {
+ /// <summary>
+ /// The <see cref="AddressInfo"/> of the sample data of the wavebuffer.
+ /// </summary>
+ public AddressInfo BufferAddressInfo;
+
+ /// <summary>
+ /// The <see cref="AddressInfo"/> of the context of the wavebuffer.
+ /// </summary>
+ /// <remarks>Only used by <see cref="Common.SampleFormat.Adpcm"/>.</remarks>
+ public AddressInfo ContextAddressInfo;
+
+
+ /// <summary>
+ /// First sample to play of the wavebuffer.
+ /// </summary>
+ public uint StartSampleOffset;
+
+ /// <summary>
+ /// Last sample to play of the wavebuffer.
+ /// </summary>
+ public uint EndSampleOffset;
+
+ /// <summary>
+ /// Set to true if the wavebuffer is looping.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool ShouldLoop;
+
+ /// <summary>
+ /// Set to true if the wavebuffer is the end of stream.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsEndOfStream;
+
+ /// <summary>
+ /// Set to true if the wavebuffer wasn't sent to the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsSendToAudioProcessor;
+
+ /// <summary>
+ /// First sample to play when looping the wavebuffer.
+ /// </summary>
+ public uint LoopStartSampleOffset;
+
+ /// <summary>
+ /// Last sample to play when looping the wavebuffer.
+ /// </summary>
+ public uint LoopEndSampleOffset;
+
+ /// <summary>
+ /// The max loop count.
+ /// </summary>
+ public int LoopCount;
+
+ /// <summary>
+ /// Create a new <see cref="Common.WaveBuffer"/> for use by the <see cref="Dsp.AudioProcessor"/>.
+ /// </summary>
+ /// <param name="version">The target version of the wavebuffer.</param>
+ /// <returns>A new <see cref="Common.WaveBuffer"/> for use by the <see cref="Dsp.AudioProcessor"/>.</returns>
+ public Common.WaveBuffer ToCommon(int version)
+ {
+ Common.WaveBuffer waveBuffer = new Common.WaveBuffer();
+
+ waveBuffer.Buffer = BufferAddressInfo.GetReference(true);
+ waveBuffer.BufferSize = (uint)BufferAddressInfo.Size;
+
+ if (ContextAddressInfo.CpuAddress != 0)
+ {
+ waveBuffer.Context = ContextAddressInfo.GetReference(true);
+ waveBuffer.ContextSize = (uint)ContextAddressInfo.Size;
+ }
+
+ waveBuffer.StartSampleOffset = StartSampleOffset;
+ waveBuffer.EndSampleOffset = EndSampleOffset;
+ waveBuffer.Looping = ShouldLoop;
+ waveBuffer.IsEndOfStream = IsEndOfStream;
+
+ if (version == 2)
+ {
+ waveBuffer.LoopCount = LoopCount;
+ waveBuffer.LoopStartSampleOffset = LoopStartSampleOffset;
+ waveBuffer.LoopEndSampleOffset = LoopEndSampleOffset;
+ }
+ else
+ {
+ waveBuffer.LoopCount = -1;
+ }
+
+ return waveBuffer;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs b/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs
new file mode 100644
index 00000000..99cbfcdc
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs
@@ -0,0 +1,75 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System.Runtime.CompilerServices;
+
+using DspAddress = System.UInt64;
+using CpuAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Utils
+{
+ /// <summary>
+ /// The <see cref="Dsp.AudioProcessor"/> memory management
+ /// </summary>
+ /// <remarks>This is stub for the most part but is kept to permit LLE if wanted.</remarks>
+ static class AudioProcessorMemoryManager
+ {
+ /// <summary>
+ /// Map the given <see cref="CpuAddress"/> to the <see cref="Dsp.AudioProcessor"/> address space.
+ /// </summary>
+ /// <param name="processHandle">The process owning the CPU memory.</param>
+ /// <param name="cpuAddress">The <see cref="CpuAddress"/> to map.</param>
+ /// <param name="size">The size of the CPU memory region to map.</param>
+ /// <returns>The address on the <see cref="Dsp.AudioProcessor"/> address space.</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static DspAddress Map(uint processHandle, CpuAddress cpuAddress, ulong size)
+ {
+ return cpuAddress;
+ }
+
+ /// <summary>
+ /// Unmap the given <see cref="CpuAddress"/> from the <see cref="Dsp.AudioProcessor"/> address space.
+ /// </summary>
+ /// <param name="processHandle">The process owning the CPU memory.</param>
+ /// <param name="cpuAddress">The <see cref="CpuAddress"/> to unmap.</param>
+ /// <param name="size">The size of the CPU memory region to unmap.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Unmap(uint processHandle, CpuAddress cpuAddress, ulong size)
+ {
+ }
+
+ /// <summary>
+ /// Invalidate the <see cref="Dsp.AudioProcessor"/> data cache at the given address.
+ /// </summary>
+ /// <param name="address">The base DSP address to invalidate</param>
+ /// <param name="size">The size of the DSP memory region to invalidate.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void InvalidateDspCache(DspAddress address, ulong size)
+ {
+ }
+
+ /// <summary>
+ /// Invalidate the CPU data cache at the given address.
+ /// </summary>
+ /// <param name="address">The base <see cref="CpuAddress"/> to invalidate</param>
+ /// <param name="size">The size of the CPU memory region to invalidate.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void InvalidateDataCache(CpuAddress address, ulong size)
+ {
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Utils/BitArray.cs b/Ryujinx.Audio/Renderer/Utils/BitArray.cs
new file mode 100644
index 00000000..6109dd01
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Utils/BitArray.cs
@@ -0,0 +1,120 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Utils
+{
+ /// <summary>
+ /// A simple bit array implementation backed by a <see cref="Memory{T}"/>.
+ /// </summary>
+ public class BitArray
+ {
+ /// <summary>
+ /// The backing storage of the <see cref="BitArray"/>.
+ /// </summary>
+ private Memory<byte> _storage;
+
+ /// <summary>
+ /// Create a new <see cref="BitArray"/> from <see cref="Memory{T}"/>.
+ /// </summary>
+ /// <param name="storage">The backing storage of the <see cref="BitArray"/>.</param>
+ public BitArray(Memory<byte> storage)
+ {
+ _storage = storage;
+ }
+
+ /// <summary>
+ /// Get the byte position of a given bit index.
+ /// </summary>
+ /// <param name="index">A bit index.</param>
+ /// <returns>The byte position of a given bit index.</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int ToPosition(int index) => index / 8;
+
+ /// <summary>
+ /// Get the bit position of a given bit index inside a byte.
+ /// </summary>
+ /// <param name="index">A bit index.</param>
+ /// <returns>The bit position of a given bit index inside a byte.</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int ToBitPosition(int index) => index % 8;
+
+ /// <summary>
+ /// Test if the bit at the given index is set.
+ /// </summary>
+ /// <param name="index">A bit index.</param>
+ /// <returns>Return true if the bit at the given index is set</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Test(int index)
+ {
+ ulong mask = 1ul << ToBitPosition(index);
+
+ return (_storage.Span[ToPosition(index)] & mask) == mask;
+ }
+
+ /// <summary>
+ /// Set the bit at the given index.
+ /// </summary>
+ /// <param name="index">A bit index.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Set(int index)
+ {
+ Set(index, true);
+ }
+
+ /// <summary>
+ /// Reset the bit at the given index.
+ /// </summary>
+ /// <param name="index">A bit index.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Reset(int index)
+ {
+ Set(index, false);
+ }
+
+ /// <summary>
+ /// Set a bit value at the given index.
+ /// </summary>
+ /// <param name="index">A bit index.</param>
+ /// <param name="value">The new bit value.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void Set(int index, bool value)
+ {
+ byte mask = (byte)(1 << ToBitPosition(index));
+
+ if (value)
+ {
+ _storage.Span[ToPosition(index)] |= mask;
+ }
+ else
+ {
+ _storage.Span[ToPosition(index)] &= (byte)~mask;
+ }
+ }
+
+ /// <summary>
+ /// Reset all bits in the storage.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Reset()
+ {
+ _storage.Span.Fill(0);
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs b/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs
new file mode 100644
index 00000000..2008bafc
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs
@@ -0,0 +1,105 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Integration;
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.Audio.Renderer.Utils
+{
+ /// <summary>
+ /// A <see cref="IHardwareDevice"/> that outputs to a wav file.
+ /// </summary>
+ public class FileHardwareDevice : IHardwareDevice
+ {
+ private FileStream _stream;
+ private uint _channelCount;
+ private uint _sampleRate;
+
+ private const int HeaderSize = 44;
+
+ public FileHardwareDevice(string path, uint channelCount, uint sampleRate)
+ {
+ _stream = File.OpenWrite(path);
+ _channelCount = channelCount;
+ _sampleRate = sampleRate;
+
+ _stream.Seek(HeaderSize, SeekOrigin.Begin);
+ }
+
+ private void UpdateHeader()
+ {
+ var writer = new BinaryWriter(_stream);
+
+ long currentPos = writer.Seek(0, SeekOrigin.Current);
+
+ writer.Seek(0, SeekOrigin.Begin);
+
+ writer.Write(Encoding.ASCII.GetBytes("RIFF"));
+ writer.Write((int)(writer.BaseStream.Length - 8));
+ writer.Write(Encoding.ASCII.GetBytes("WAVE"));
+ writer.Write(Encoding.ASCII.GetBytes("fmt "));
+ writer.Write(16);
+ writer.Write((short)1);
+ writer.Write((short)GetChannelCount());
+ writer.Write(GetSampleRate());
+ writer.Write(GetSampleRate() * GetChannelCount() * sizeof(short));
+ writer.Write((short)(GetChannelCount() * sizeof(short)));
+ writer.Write((short)(sizeof(short) * 8));
+ writer.Write(Encoding.ASCII.GetBytes("data"));
+ writer.Write((int)(writer.BaseStream.Length - HeaderSize));
+
+ writer.Seek((int)currentPos, SeekOrigin.Begin);
+ }
+
+ public void AppendBuffer(ReadOnlySpan<short> data, uint channelCount)
+ {
+ _stream.Write(MemoryMarshal.Cast<short, byte>(data));
+
+ UpdateHeader();
+ _stream.Flush();
+ }
+
+ public uint GetChannelCount()
+ {
+ return _channelCount;
+ }
+
+ public uint GetSampleRate()
+ {
+ return _sampleRate;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _stream?.Flush();
+ _stream?.Dispose();
+
+ _stream = null;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Utils/Mailbox.cs b/Ryujinx.Audio/Renderer/Utils/Mailbox.cs
new file mode 100644
index 00000000..a32de870
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Utils/Mailbox.cs
@@ -0,0 +1,72 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.Audio.Renderer.Utils
+{
+ /// <summary>
+ /// A simple generic message queue for unmanaged types.
+ /// </summary>
+ /// <typeparam name="T">The target unmanaged type used</typeparam>
+ public class Mailbox<T> : IDisposable where T : unmanaged
+ {
+ private BlockingCollection<T> _messageQueue;
+ private BlockingCollection<T> _responseQueue;
+
+ public Mailbox()
+ {
+ _messageQueue = new BlockingCollection<T>(1);
+ _responseQueue = new BlockingCollection<T>(1);
+ }
+
+ public void SendMessage(T data)
+ {
+ _messageQueue.Add(data);
+ }
+
+ public void SendResponse(T data)
+ {
+ _responseQueue.Add(data);
+ }
+
+ public T ReceiveMessage()
+ {
+ return _messageQueue.Take();
+ }
+
+ public T ReceiveResponse()
+ {
+ return _responseQueue.Take();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _messageQueue.Dispose();
+ _responseQueue.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs b/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs
new file mode 100644
index 00000000..70a2fce6
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs
@@ -0,0 +1,188 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Utils
+{
+ /// <summary>
+ /// Helper for IO operations on <see cref="Span{T}"/> and <see cref="Memory{T}"/>.
+ /// </summary>
+ public static class SpanIOHelper
+ {
+ /// <summary>
+ /// Write the given data to the given backing <see cref="Memory{T}"/> and move cursor after the written data.
+ /// </summary>
+ /// <typeparam name="T">The data type.</typeparam>
+ /// <param name="backingMemory">The backing <see cref="Memory{T}"/> to store the data.</param>
+ /// <param name="data">The data to write to the backing <see cref="Memory{T}"/>.</param>
+ public static void Write<T>(ref Memory<byte> backingMemory, ref T data) where T : unmanaged
+ {
+ int size = Unsafe.SizeOf<T>();
+
+ if (size > backingMemory.Length)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ MemoryMarshal.Write<T>(backingMemory.Span.Slice(0, size), ref data);
+
+ backingMemory = backingMemory.Slice(size);
+ }
+
+ /// <summary>
+ /// Write the given data to the given backing <see cref="Span{T}"/> and move cursor after the written data.
+ /// </summary>
+ /// <typeparam name="T">The data type.</typeparam>
+ /// <param name="backingMemory">The backing <see cref="Span{T}"/> to store the data.</param>
+ /// <param name="data">The data to write to the backing <see cref="Span{T}"/>.</param>
+ public static void Write<T>(ref Span<byte> backingMemory, ref T data) where T : unmanaged
+ {
+ int size = Unsafe.SizeOf<T>();
+
+ if (size > backingMemory.Length)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ MemoryMarshal.Write<T>(backingMemory.Slice(0, size), ref data);
+
+ backingMemory = backingMemory.Slice(size);
+ }
+
+ /// <summary>
+ /// Get a <see cref="Span{T}"/> out of a <paramref name="backingMemory"/> and move cursor after T size.
+ /// </summary>
+ /// <typeparam name="T">The data type.</typeparam>
+ /// <param name="backingMemory">The backing <see cref="Memory{T}"/> to get a <see cref="Span{T}"/> from.</param>
+ /// <returns>A <see cref="Span{T}"/> from backing <see cref="Memory{T}"/>.</returns>
+ public static Span<T> GetWriteRef<T>(ref Memory<byte> backingMemory) where T : unmanaged
+ {
+ int size = Unsafe.SizeOf<T>();
+
+ if (size > backingMemory.Length)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ Span<T> result = MemoryMarshal.Cast<byte, T>(backingMemory.Span.Slice(0, size));
+
+ backingMemory = backingMemory.Slice(size);
+
+ return result;
+ }
+
+ /// <summary>
+ /// Get a <see cref="Span{T}"/> out of a backingMemory and move cursor after T size.
+ /// </summary>
+ /// <typeparam name="T">The data type.</typeparam>
+ /// <param name="backingMemory">The backing <see cref="Span{T}"/> to get a <see cref="Span{T}"/> from.</param>
+ /// <returns>A <see cref="Span{T}"/> from backing <see cref="Span{T}"/>.</returns>
+ public static Span<T> GetWriteRef<T>(ref Span<byte> backingMemory) where T : unmanaged
+ {
+ int size = Unsafe.SizeOf<T>();
+
+ if (size > backingMemory.Length)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ Span<T> result = MemoryMarshal.Cast<byte, T>(backingMemory.Slice(0, size));
+
+ backingMemory = backingMemory.Slice(size);
+
+ return result;
+ }
+
+ /// <summary>
+ /// Read data from the given backing <see cref="ReadOnlyMemory{T}"/> and move cursor after the read data.
+ /// </summary>
+ /// <typeparam name="T">The data type.</typeparam>
+ /// <param name="backingMemory">The backing <see cref="ReadOnlyMemory{T}"/> to read data from.</param>
+ /// <returns>Return the read data.</returns>
+ public static T Read<T>(ref ReadOnlyMemory<byte> backingMemory) where T : unmanaged
+ {
+ int size = Unsafe.SizeOf<T>();
+
+ if (size > backingMemory.Length)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ T result = MemoryMarshal.Read<T>(backingMemory.Span.Slice(0, size));
+
+ backingMemory = backingMemory.Slice(size);
+
+ return result;
+ }
+
+ /// <summary>
+ /// Read data from the given backing <see cref="ReadOnlySpan{T}"/> and move cursor after the read data.
+ /// </summary>
+ /// <typeparam name="T">The data type.</typeparam>
+ /// <param name="backingMemory">The backing <see cref="ReadOnlySpan{T}"/> to read data from.</param>
+ /// <returns>Return the read data.</returns>
+ public static T Read<T>(ref ReadOnlySpan<byte> backingMemory) where T : unmanaged
+ {
+ int size = Unsafe.SizeOf<T>();
+
+ if (size > backingMemory.Length)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ T result = MemoryMarshal.Read<T>(backingMemory.Slice(0, size));
+
+ backingMemory = backingMemory.Slice(size);
+
+ return result;
+ }
+
+ /// <summary>
+ /// Extract a <see cref="Memory{T}"/> at the given index.
+ /// </summary>
+ /// <typeparam name="T">The data type.</typeparam>
+ /// <param name="memory">The <see cref="Memory{T}"/> to extract the data from.</param>
+ /// <param name="id">The id in the provided memory.</param>
+ /// <param name="count">The max allowed count. (for bound checking of the id in debug mode)</param>
+ /// <returns>a <see cref="Memory{T}"/> at the given id.</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Memory<T> GetMemory<T>(Memory<T> memory, int id, uint count) where T : unmanaged
+ {
+ Debug.Assert(id >= 0 && id < count);
+
+ return memory.Slice(id, 1);
+ }
+
+ /// <summary>
+ /// Extract a ref T at the given index.
+ /// </summary>
+ /// <typeparam name="T">The data type.</typeparam>
+ /// <param name="memory">The <see cref="Memory{T}"/> to extract the data from.</param>
+ /// <param name="id">The id in the provided memory.</param>
+ /// <param name="count">The max allowed count. (for bound checking of the id in debug mode)</param>
+ /// <returns>a ref T at the given id.</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T GetFromMemory<T>(Memory<T> memory, int id, uint count) where T : unmanaged
+ {
+ return ref GetMemory(memory, id, count).Span[0];
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs b/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs
new file mode 100644
index 00000000..102a9f95
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using System;
+using System.Buffers;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Utils
+{
+ public sealed unsafe class SpanMemoryManager<T> : MemoryManager<T>
+ where T : unmanaged
+ {
+ private readonly T* _pointer;
+ private readonly int _length;
+
+ public SpanMemoryManager(Span<T> span)
+ {
+ fixed (T* ptr = &MemoryMarshal.GetReference(span))
+ {
+ _pointer = ptr;
+ _length = span.Length;
+ }
+ }
+
+ public override Span<T> GetSpan() => new Span<T>(_pointer, _length);
+
+ public override MemoryHandle Pin(int elementIndex = 0)
+ {
+ if (elementIndex < 0 || elementIndex >= _length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(elementIndex));
+ }
+
+ return new MemoryHandle(_pointer + elementIndex);
+ }
+
+ public override void Unpin() { }
+
+ protected override void Dispose(bool disposing) { }
+
+ public static Memory<T> Cast<TFrom>(Memory<TFrom> memory) where TFrom : unmanaged
+ {
+ return new SpanMemoryManager<T>(MemoryMarshal.Cast<TFrom, T>(memory.Span)).Memory;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs b/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs
new file mode 100644
index 00000000..c5411ac0
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs
@@ -0,0 +1,64 @@
+//
+// Copyright (c) 2019-2021 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Integration;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Utils
+{
+ public class SplitterHardwareDevice : IHardwareDevice
+ {
+ private IHardwareDevice _baseDevice;
+ private IHardwareDevice _secondaryDevice;
+
+ public SplitterHardwareDevice(IHardwareDevice baseDevice, IHardwareDevice secondaryDevice)
+ {
+ _baseDevice = baseDevice;
+ _secondaryDevice = secondaryDevice;
+ }
+
+ public void AppendBuffer(ReadOnlySpan<short> data, uint channelCount)
+ {
+ _baseDevice.AppendBuffer(data, channelCount);
+ _secondaryDevice?.AppendBuffer(data, channelCount);
+ }
+
+ public uint GetChannelCount()
+ {
+ return _baseDevice.GetChannelCount();
+ }
+
+ public uint GetSampleRate()
+ {
+ return _baseDevice.GetSampleRate();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _baseDevice.Dispose();
+ _secondaryDevice?.Dispose();
+ }
+ }
+ }
+}