diff --git a/ARMeilleure/State/ExecutionContext.cs b/ARMeilleure/State/ExecutionContext.cs
index 2755122f3..9f89bff45 100644
--- a/ARMeilleure/State/ExecutionContext.cs
+++ b/ARMeilleure/State/ExecutionContext.cs
@@ -37,6 +37,8 @@ namespace ARMeilleure.State
public ulong CntvctEl0 => CntpctEl0;
public static TimeSpan ElapsedTime => _tickCounter.Elapsed;
+ public static long ElapsedTicks => _tickCounter.ElapsedTicks;
+ public static double TickFrequency => _hostTickFreq;
public long TpidrEl0 { get; set; }
public long Tpidr { get; set; }
diff --git a/README.md b/README.md
index e6d4f23d6..b2e3b8e86 100644
--- a/README.md
+++ b/README.md
@@ -120,5 +120,6 @@ If you'd like to donate, please take a look at our [Patreon](https://www.patreon
## License
This software is licensed under the terms of the MIT license.
+The Ryujinx.Audio.Renderer project is licensed under the terms of the LGPLv3 license.
This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3.
See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](Ryujinx/THIRDPARTY.md) for more details.
diff --git a/Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs b/Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs
new file mode 100644
index 000000000..d8e345d84
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs
@@ -0,0 +1,30 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 000000000..8881a92c5
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/BehaviourParameter.cs
@@ -0,0 +1,67 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Represents the input parameter for .
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct BehaviourParameter
+ {
+ ///
+ /// The current audio renderer revision in use.
+ ///
+ public int UserRevision;
+
+ ///
+ /// Reserved/padding.
+ ///
+ private uint _padding;
+
+ ///
+ /// The flags given controlling behaviour of the audio renderer
+ ///
+ /// See and .
+ public ulong Flags;
+
+ ///
+ /// Represents an error during .
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct ErrorInfo
+ {
+ ///
+ /// The error code to report.
+ ///
+ public ResultCode ErrorCode;
+
+ ///
+ /// Reserved/padding.
+ ///
+ private uint _padding;
+
+ ///
+ /// Extra information given with the
+ ///
+ /// This is usually used to report a faulting cpu address when a mapping fail.
+ public ulong ExtraErrorInfo;
+ }
+ }
+}
diff --git a/Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs b/Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs
new file mode 100644
index 000000000..115281740
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs
@@ -0,0 +1,167 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+using Ryujinx.Audio.Renderer.Utils;
+using Ryujinx.Common;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Represents a adjacent matrix.
+ ///
+ /// This is used for splitter routing.
+ public class EdgeMatrix
+ {
+ ///
+ /// Backing used for node connections.
+ ///
+ private BitArray _storage;
+
+ ///
+ /// The count of nodes of the current instance.
+ ///
+ private int _nodeCount;
+
+ ///
+ /// Get the required work buffer size memory needed for the .
+ ///
+ /// The count of nodes.
+ /// The size required for the given .
+ public static int GetWorkBufferSize(int nodeCount)
+ {
+ int size = BitUtils.AlignUp(nodeCount * nodeCount, RendererConstants.BufferAlignment);
+
+ return size / Unsafe.SizeOf();
+ }
+
+ ///
+ /// Initializes the instance with backing memory.
+ ///
+ /// The backing memory.
+ /// The count of nodes.
+ public void Initialize(Memory edgeMatrixWorkBuffer, int nodeCount)
+ {
+ Debug.Assert(edgeMatrixWorkBuffer.Length >= GetWorkBufferSize(nodeCount));
+
+ _storage = new BitArray(edgeMatrixWorkBuffer);
+
+ _nodeCount = nodeCount;
+
+ _storage.Reset();
+ }
+
+ ///
+ /// Test if the bit at the given index is set.
+ ///
+ /// A bit index.
+ /// Returns true if the bit at the given index is set
+ public bool Test(int index)
+ {
+ return _storage.Test(index);
+ }
+
+ ///
+ /// Reset all bits in the storage.
+ ///
+ public void Reset()
+ {
+ _storage.Reset();
+ }
+
+ ///
+ /// Reset the bit at the given index.
+ ///
+ /// A bit index.
+ public void Reset(int index)
+ {
+ _storage.Reset(index);
+ }
+
+ ///
+ /// Set the bit at the given index.
+ ///
+ /// A bit index.
+ public void Set(int index)
+ {
+ _storage.Set(index);
+ }
+
+ ///
+ /// Connect a given source to a given destination.
+ ///
+ /// The source index.
+ /// The destination index.
+ public void Connect(int source, int destination)
+ {
+ Debug.Assert(source < _nodeCount);
+ Debug.Assert(destination < _nodeCount);
+
+ _storage.Set(_nodeCount * source + destination);
+ }
+
+ ///
+ /// Check if the given source is connected to the given destination.
+ ///
+ /// The source index.
+ /// The destination index.
+ /// Returns true if the given source is connected to the given destination.
+ public bool Connected(int source, int destination)
+ {
+ Debug.Assert(source < _nodeCount);
+ Debug.Assert(destination < _nodeCount);
+
+ return _storage.Test(_nodeCount * source + destination);
+ }
+
+ ///
+ /// Disconnect a given source from a given destination.
+ ///
+ /// The source index.
+ /// The destination index.
+ public void Disconnect(int source, int destination)
+ {
+ Debug.Assert(source < _nodeCount);
+ Debug.Assert(destination < _nodeCount);
+
+ _storage.Reset(_nodeCount * source + destination);
+ }
+
+ ///
+ /// Remove all edges from a given source.
+ ///
+ /// The source index.
+ public void RemoveEdges(int source)
+ {
+ for (int i = 0; i < _nodeCount; i++)
+ {
+ Disconnect(source, i);
+ }
+ }
+
+ ///
+ /// Get the total node count.
+ ///
+ /// The total node count.
+ 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 000000000..8349ecb5b
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/EffectType.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// The type of an effect.
+ ///
+ public enum EffectType : byte
+ {
+ ///
+ /// Invalid effect.
+ ///
+ Invalid,
+
+ ///
+ /// Effect applying additional mixing capability.
+ ///
+ BufferMix,
+
+ ///
+ /// Effect applying custom user effect (via auxiliary buffers).
+ ///
+ AuxiliaryBuffer,
+
+ ///
+ /// Effect applying a delay.
+ ///
+ Delay,
+
+ ///
+ /// Effect applying a reverberation effect via a given preset.
+ ///
+ Reverb,
+
+ ///
+ /// Effect applying a 3D reverberation effect via a given preset.
+ ///
+ Reverb3d,
+
+ ///
+ /// Effect applying a biquad filter.
+ ///
+ BiquadFilter
+ }
+}
diff --git a/Ryujinx.Audio.Renderer/Common/MemoryPoolUserState.cs b/Ryujinx.Audio.Renderer/Common/MemoryPoolUserState.cs
new file mode 100644
index 000000000..f1ea8d9a9
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/MemoryPoolUserState.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Represents the state of a memory pool.
+ ///
+ public enum MemoryPoolUserState : uint
+ {
+ ///
+ /// Invalid state.
+ ///
+ Invalid = 0,
+
+ ///
+ /// The memory pool is new. (client side only)
+ ///
+ New = 1,
+
+ ///
+ /// The user asked to detach the memory pool from the .
+ ///
+ RequestDetach = 2,
+
+ ///
+ /// The memory pool is detached from the .
+ ///
+ Detached = 3,
+
+ ///
+ /// The user asked to attach the memory pool to the .
+ ///
+ RequestAttach = 4,
+
+ ///
+ /// The memory pool is attached to the .
+ ///
+ Attached = 5,
+
+ ///
+ /// The memory pool is released. (client side only)
+ ///
+ Released = 6
+ }
+}
diff --git a/Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs b/Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs
new file mode 100644
index 000000000..6bb660581
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs
@@ -0,0 +1,45 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Helper for manipulating node ids.
+ ///
+ public static class NodeIdHelper
+ {
+ ///
+ /// Get the type of a node from a given node id.
+ ///
+ /// Id of the node.
+ /// The type of the node.
+ public static NodeIdType GetType(int nodeId)
+ {
+ return (NodeIdType)(nodeId >> 28);
+ }
+
+ ///
+ /// Get the base of a node from a given node id.
+ ///
+ /// Id of the node.
+ /// The base of the node.
+ 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 000000000..9ee760e96
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/NodeIdType.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// The type of a node.
+ ///
+ public enum NodeIdType : byte
+ {
+ ///
+ /// Invalid node id.
+ ///
+ Invalid = 0,
+
+ ///
+ /// Voice related node id. (data source, biquad filter, ...)
+ ///
+ Voice = 1,
+
+ ///
+ /// Mix related node id. (mix, effects, splitters, ...)
+ ///
+ Mix = 2,
+
+ ///
+ /// Sink related node id. (device & circular buffer sink)
+ ///
+ Sink = 3,
+
+ ///
+ /// Performance monitoring related node id (performance commands)
+ ///
+ Performance = 15
+ }
+}
diff --git a/Ryujinx.Audio.Renderer/Common/NodeStates.cs b/Ryujinx.Audio.Renderer/Common/NodeStates.cs
new file mode 100644
index 000000000..6598619a6
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/NodeStates.cs
@@ -0,0 +1,246 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+using Ryujinx.Audio.Renderer.Utils;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ public class NodeStates
+ {
+ private class Stack
+ {
+ private Memory _storage;
+ private int _index;
+
+ private int _nodeCount;
+
+ public void Reset(Memory 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 _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 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.Cast(nodeStatesWorkBuffer.Slice(0, sizeof(int) * nodeCount));
+
+ nodeStatesWorkBuffer = nodeStatesWorkBuffer.Slice(sizeof(int) * nodeCount);
+
+ Memory stackWorkBuffer = SpanMemoryManager.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 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 000000000..ebf5b469e
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/PerformanceDetailType.cs
@@ -0,0 +1,34 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 000000000..ba27633ff
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/PerformanceEntryType.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 000000000..a96b86dd8
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/PlayState.cs
@@ -0,0 +1,40 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Common play state.
+ ///
+ public enum PlayState : byte
+ {
+ ///
+ /// The user request the voice to be started.
+ ///
+ Start,
+
+ ///
+ /// The user request the voice to be stopped.
+ ///
+ Stop,
+
+ ///
+ /// The user request the voice to be paused.
+ ///
+ Pause
+ }
+}
diff --git a/Ryujinx.Audio.Renderer/Common/ReverbEarlyMode.cs b/Ryujinx.Audio.Renderer/Common/ReverbEarlyMode.cs
new file mode 100644
index 000000000..2b77336ec
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/ReverbEarlyMode.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Early reverb reflection.
+ ///
+ public enum ReverbEarlyMode : uint
+ {
+ ///
+ /// Room early reflection. (small acoustic space, fast reflection)
+ ///
+ Room,
+
+ ///
+ /// Chamber early reflection. (bigger than 's acoustic space, short reflection)
+ ///
+ Chamber,
+
+ ///
+ /// Hall early reflection. (large acoustic space, warm reflection)
+ ///
+ Hall,
+
+ ///
+ /// Cathedral early reflection. (very large acoustic space, pronounced bright reflection)
+ ///
+ Cathedral,
+
+ ///
+ /// No early reflection.
+ ///
+ Disabled
+ }
+}
diff --git a/Ryujinx.Audio.Renderer/Common/ReverbLateMode.cs b/Ryujinx.Audio.Renderer/Common/ReverbLateMode.cs
new file mode 100644
index 000000000..dab78c45b
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/ReverbLateMode.cs
@@ -0,0 +1,55 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Late reverb reflection.
+ ///
+ public enum ReverbLateMode : uint
+ {
+ ///
+ /// Room late reflection. (small acoustic space, fast reflection)
+ ///
+ Room,
+
+ ///
+ /// Hall late reflection. (large acoustic space, warm reflection)
+ ///
+ Hall,
+
+ ///
+ /// Classic plate late reflection. (clean distinctive reverb)
+ ///
+ Plate,
+
+ ///
+ /// Cathedral late reflection. (very large acoustic space, pronounced bright reflection)
+ ///
+ Cathedral,
+
+ ///
+ /// Do not apply any delay. (max delay)
+ ///
+ NoDelay,
+
+ ///
+ /// Max delay. (used for delay line limits)
+ ///
+ Limit = NoDelay
+ }
+}
diff --git a/Ryujinx.Audio.Renderer/Common/SampleFormat.cs b/Ryujinx.Audio.Renderer/Common/SampleFormat.cs
new file mode 100644
index 000000000..24da48bff
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/SampleFormat.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Sample format definition.
+ ///
+ public enum SampleFormat : byte
+ {
+ ///
+ /// Invalid sample format.
+ ///
+ Invalid = 0,
+
+ ///
+ /// PCM8 sample format. (unsupported)
+ ///
+ PcmInt8 = 1,
+
+ ///
+ /// PCM16 sample format.
+ ///
+ PcmInt16 = 2,
+
+ ///
+ /// PCM24 sample format. (unsupported)
+ ///
+ PcmInt24 = 3,
+
+ ///
+ /// PCM32 sample format.
+ ///
+ PcmInt32 = 4,
+
+ ///
+ /// PCM Float sample format.
+ ///
+ PcmFloat = 5,
+
+ ///
+ /// ADPCM sample format. (Also known as GC-ADPCM)
+ ///
+ Adpcm = 6
+ }
+}
diff --git a/Ryujinx.Audio.Renderer/Common/SinkType.cs b/Ryujinx.Audio.Renderer/Common/SinkType.cs
new file mode 100644
index 000000000..22cab23c5
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/SinkType.cs
@@ -0,0 +1,40 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// The type of a sink.
+ ///
+ public enum SinkType : byte
+ {
+ ///
+ /// The sink is in an invalid state.
+ ///
+ Invalid,
+
+ ///
+ /// The sink is a device.
+ ///
+ Device,
+
+ ///
+ /// The sink is a circular buffer.
+ ///
+ CircularBuffer
+ }
+}
diff --git a/Ryujinx.Audio.Renderer/Common/UpdateDataHeader.cs b/Ryujinx.Audio.Renderer/Common/UpdateDataHeader.cs
new file mode 100644
index 000000000..40c29f551
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/UpdateDataHeader.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Update data header used for input and output of .
+ ///
+ 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();
+ }
+ }
+}
diff --git a/Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs b/Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs
new file mode 100644
index 000000000..490bb871d
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs
@@ -0,0 +1,121 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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
+{
+ ///
+ /// Represent the update state of a voice.
+ ///
+ /// This is shared between the server and audio processor.
+ [StructLayout(LayoutKind.Sequential, Pack = Align)]
+ public struct VoiceUpdateState
+ {
+ public const int Align = 0x10;
+ public const int BiquadStateOffset = 0x0;
+ public const int BiquadStateSize = 0x10;
+
+ ///
+ /// The state of the biquad filters of this voice.
+ ///
+ public Array2 BiquadFilterState;
+
+ ///
+ /// The total amount of samples that was played.
+ ///
+ /// This is reset to 0 when a finishes playing and is set.
+ /// This is reset to 0 when looping while is set.
+ public ulong PlayedSampleCount;
+
+ ///
+ /// The current sample offset in the pointed by .
+ ///
+ public int Offset;
+
+ ///
+ /// The current index of the in use.
+ ///
+ public uint WaveBufferIndex;
+
+ private WaveBufferValidArray _isWaveBufferValid;
+
+ ///
+ /// The total amount of consumed.
+ ///
+ public uint WaveBufferConsumed;
+
+ ///
+ /// Pitch used for Sample Rate Conversion.
+ ///
+ public Array8 Pitch;
+
+ public float Fraction;
+
+ ///
+ /// The ADPCM loop context when is in use.
+ ///
+ public AdpcmLoopContext LoopContext;
+
+ ///
+ /// The last samples after a mix ramp.
+ ///
+ /// This is used for depop (to perform voice drop).
+ public Array24 LastSamples;
+
+ ///
+ /// The current count of loop performed.
+ ///
+ public int LoopCount;
+
+ [StructLayout(LayoutKind.Sequential, Size = 1 * RendererConstants.VoiceWaveBufferCount, Pack = 1)]
+ private struct WaveBufferValidArray { }
+
+ ///
+ /// Contains information of validity.
+ ///
+ public Span IsWaveBufferValid => SpanHelpers.AsSpan(ref _isWaveBufferValid);
+
+ ///
+ /// Mark the current as played and switch to the next one.
+ ///
+ /// The current
+ /// The wavebuffer index.
+ /// The amount of wavebuffers consumed.
+ /// The total count of sample played.
+ public void MarkEndOfBufferWaveBufferProcessing(ref WaveBuffer waveBuffer, ref int waveBufferIndex, ref uint waveBufferConsumed, ref ulong playedSampleCount)
+ {
+ IsWaveBufferValid[waveBufferIndex++] = false;
+ LoopCount = 0;
+ waveBufferConsumed++;
+
+ if (waveBufferIndex >= RendererConstants.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 000000000..c2dd1d6b9
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/WaveBuffer.cs
@@ -0,0 +1,99 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+using System.Runtime.InteropServices;
+
+using DspAddr = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// A wavebuffer used for data source commands.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct WaveBuffer
+ {
+ ///
+ /// The DSP address of the sample data of the wavebuffer.
+ ///
+ public DspAddr Buffer;
+
+ ///
+ /// The DSP address of the context of the wavebuffer.
+ ///
+ /// Only used by .
+ public DspAddr Context;
+
+ ///
+ /// The size of the sample buffer data.
+ ///
+ public uint BufferSize;
+
+ ///
+ /// The size of the context buffer.
+ ///
+ public uint ContextSize;
+
+ ///
+ /// First sample to play on the wavebuffer.
+ ///
+ public uint StartSampleOffset;
+
+ ///
+ /// Last sample to play on the wavebuffer.
+ ///
+ public uint EndSampleOffset;
+
+ ///
+ /// First sample to play when looping the wavebuffer.
+ ///
+ ///
+ /// If or is equal to zero,, it will default to and .
+ ///
+ public uint LoopStartSampleOffset;
+
+ ///
+ /// Last sample to play when looping the wavebuffer.
+ ///
+ ///
+ /// If or is equal to zero, it will default to and .
+ ///
+ public uint LoopEndSampleOffset;
+
+ ///
+ /// The max loop count.
+ ///
+ public int LoopCount;
+
+ ///
+ /// Set to true if the wavebuffer is looping.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool Looping;
+
+ ///
+ /// Set to true if the wavebuffer is the end of stream.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsEndOfStream;
+
+ ///
+ /// Padding/Reserved.
+ ///
+ private ushort _padding;
+ }
+}
diff --git a/Ryujinx.Audio.Renderer/Common/WorkBufferAllocator.cs b/Ryujinx.Audio.Renderer/Common/WorkBufferAllocator.cs
new file mode 100644
index 000000000..5e5fdff04
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Common/WorkBufferAllocator.cs
@@ -0,0 +1,78 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 BackingMemory { get; }
+
+ public ulong Offset { get; private set; }
+
+ public WorkBufferAllocator(Memory backingMemory)
+ {
+ BackingMemory = backingMemory;
+ }
+
+ public Memory 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 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.Empty;
+ }
+
+ public Memory Allocate(ulong count, int align) where T: unmanaged
+ {
+ Memory allocatedMemory = Allocate((ulong)Unsafe.SizeOf() * count, align);
+
+ if (allocatedMemory.IsEmpty)
+ {
+ return Memory.Empty;
+ }
+
+ return SpanMemoryManager.Cast(allocatedMemory);
+ }
+
+ public static ulong GetTargetSize(ulong currentSize, ulong count, int align) where T: unmanaged
+ {
+ return BitUtils.AlignUp(currentSize, align) + (ulong)Unsafe.SizeOf() * count;
+ }
+ }
+}
diff --git a/Ryujinx.Audio.Renderer/Device/VirtualDevice.cs b/Ryujinx.Audio.Renderer/Device/VirtualDevice.cs
new file mode 100644
index 000000000..ca9011e87
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Device/VirtualDevice.cs
@@ -0,0 +1,84 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Device
+{
+ ///
+ /// Represents a virtual device used by IAudioDevice.
+ ///
+ public class VirtualDevice
+ {
+ ///
+ /// All the defined virtual devices.
+ ///
+ public static readonly VirtualDevice[] Devices = new VirtualDevice[4]
+ {
+ new VirtualDevice("AudioStereoJackOutput", 2),
+ new VirtualDevice("AudioBuiltInSpeakerOutput", 2),
+ new VirtualDevice("AudioTvOutput", 6),
+ new VirtualDevice("AudioUsbDeviceOutput", 2),
+ };
+
+ ///
+ /// The name of the .
+ ///
+ public string Name { get; }
+
+ ///
+ /// The count of channels supported by the .
+ ///
+ public uint ChannelCount { get; }
+
+ ///
+ /// The system master volume of the .
+ ///
+ public float MasterVolume { get; private set; }
+
+ ///
+ /// Create a new instance.
+ ///
+ /// The name of the .
+ /// The count of channels supported by the .
+ private VirtualDevice(string name, uint channelCount)
+ {
+ Name = name;
+ ChannelCount = channelCount;
+ }
+
+ ///
+ /// Update the master volume of the .
+ ///
+ /// The new master volume.
+ public void UpdateMasterVolume(float volume)
+ {
+ Debug.Assert(volume >= 0.0f && volume <= 1.0f);
+
+ MasterVolume = volume;
+ }
+
+ ///
+ /// Check if the is a usb device.
+ ///
+ /// Returns true if the is a usb device.
+ 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 000000000..091842387
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Device/VirtualDeviceSession.cs
@@ -0,0 +1,44 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+namespace Ryujinx.Audio.Renderer.Device
+{
+ ///
+ /// Represents a virtual device session used by IAudioDevice.
+ ///
+ public class VirtualDeviceSession
+ {
+ ///
+ /// The associated to this session.
+ ///
+ public VirtualDevice Device { get; }
+
+ ///
+ /// The user volume of this session.
+ ///
+ public float Volume { get; set; }
+
+ ///
+ /// Create a new instance.
+ ///
+ /// The associated to this session.
+ 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 000000000..569cb7c44
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Device/VirtualDeviceSessionRegistry.cs
@@ -0,0 +1,79 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+using System.Collections.Generic;
+
+namespace Ryujinx.Audio.Renderer.Device
+{
+ ///
+ /// Represent an instance containing a registry of .
+ ///
+ public class VirtualDeviceSessionRegistry
+ {
+ ///
+ /// The session registry, used to store the sessions of a given AppletResourceId.
+ ///
+ private Dictionary _sessionsRegistry = new Dictionary();
+
+ ///
+ /// The default .
+ ///
+ /// This is used when the USB device is the default one on older revision.
+ public VirtualDevice DefaultDevice => VirtualDevice.Devices[0];
+
+ ///
+ /// The current active .
+ ///
+ // TODO: make this configurable
+ public VirtualDevice ActiveDevice = VirtualDevice.Devices[1];
+
+ ///
+ /// Get the associated from an AppletResourceId.
+ ///
+ /// The AppletResourceId used.
+ /// The associated from an AppletResourceId.
+ public VirtualDeviceSession[] GetSessionByAppletResourceId(ulong resourceAppletId)
+ {
+ if (_sessionsRegistry.TryGetValue(resourceAppletId, out VirtualDeviceSession[] result))
+ {
+ return result;
+ }
+
+ result = CreateSessionsFromBehaviourContext();
+
+ _sessionsRegistry.Add(resourceAppletId, result);
+
+ return result;
+ }
+
+ ///
+ /// Create a new array of sessions for each .
+ ///
+ /// A new array of sessions for each .
+ 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 000000000..e49c74d37
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/AdpcmHelper.cs
@@ -0,0 +1,219 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 output, ReadOnlySpan input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan 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 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 000000000..90f6cd517
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs
@@ -0,0 +1,221 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+using Ryujinx.Audio.Renderer.Dsp.Command;
+using Ryujinx.Audio.Renderer.Integration;
+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 _mailbox;
+ private RendererSession[] _sessionCommandList;
+ private Thread _workerThread;
+ private HardwareDevice[] _outputDevices;
+
+ private long _lastTime;
+ private long _playbackEnds;
+ private ManualResetEvent _event;
+
+ public void SetOutputDevices(HardwareDevice[] outputDevices)
+ {
+ _outputDevices = outputDevices;
+ }
+
+ public void Start()
+ {
+ _mailbox = new Mailbox();
+ _sessionCommandList = new RendererSession[RendererConstants.AudioRendererSessionCountMax];
+ _event = new ManualResetEvent(false);
+ _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!");
+ }
+ }
+
+ 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 = RendererConstants.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 (RendererConstants.AudioProcessorMaxUpdateTime < elapsedTime)
+ {
+ Logger.Debug?.Print(LogClass.AudioRenderer, $"DSP too slow (exceeded by {elapsedTime - RendererConstants.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 000000000..df9ef0865
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs
@@ -0,0 +1,93 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 State { get; }
+
+ public ulong AdpcmParameter { get; }
+ public ulong AdpcmParameterSize { get; }
+
+ public DecodingBehaviour DecodingBehaviour { get; }
+
+ public AdpcmDataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory state, ushort outputBufferIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ OutputBufferIndex = outputBufferIndex;
+ SampleRate = serverState.SampleRate;
+ Pitch = serverState.Pitch;
+
+ WaveBuffers = new WaveBuffer[RendererConstants.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 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 000000000..f63722c12
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
@@ -0,0 +1,188 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Cpu;
+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(MemoryManager memoryManager, ulong bufferAddress, uint countMax, Span 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(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(MemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan 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(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 inputBuffer = context.GetBuffer((int)InputBufferIndex);
+ Span outputBuffer = context.GetBuffer((int)OutputBufferIndex);
+
+ if (IsEffectEnabled)
+ {
+ Span inputBufferInt = MemoryMarshal.Cast(inputBuffer);
+ Span outputBufferInt = MemoryMarshal.Cast(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
+ {
+ MemoryHelper.FillWithZeros(context.MemoryManager, (long)BufferInfo.SendBufferInfo, Unsafe.SizeOf());
+ MemoryHelper.FillWithZeros(context.MemoryManager, (long)BufferInfo.ReturnBufferInfo, Unsafe.SizeOf());
+
+ if (InputBufferIndex != OutputBufferIndex)
+ {
+ inputBuffer.CopyTo(outputBuffer);
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/BiquadFilterCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/BiquadFilterCommand.cs
new file mode 100644
index 000000000..d00d33710
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/BiquadFilterCommand.cs
@@ -0,0 +1,89 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 { get; }
+ public int InputBufferIndex { get; }
+ public int OutputBufferIndex { get; }
+ public bool NeedInitialization { get; }
+
+ public BiquadFilterCommand(int baseIndex, ref BiquadFilterParameter filter, Memory 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 outputBuffer, ReadOnlySpan 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 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 000000000..63719a75f
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/CircularBufferSinkCommand.cs
@@ -0,0 +1,91 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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[RendererConstants.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 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 000000000..3ec725d12
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/ClearMixBufferCommand.cs
@@ -0,0 +1,41 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 000000000..d65f6ceda
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/CommandList.cs
@@ -0,0 +1,124 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+using Ryujinx.Audio.Renderer.Integration;
+using Ryujinx.Audio.Renderer.Server;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
+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 Buffers { get; }
+ public uint BufferCount { get; }
+
+ public List Commands { get; }
+
+ public MemoryManager MemoryManager { get; }
+
+ public HardwareDevice OutputDevice { get; private set; }
+
+ public CommandList(AudioRenderSystem renderSystem) : this(renderSystem.MemoryManager,
+ renderSystem.GetMixBuffer(),
+ renderSystem.GetSampleCount(),
+ renderSystem.GetSampleRate(),
+ renderSystem.GetMixBufferCount(),
+ renderSystem.GetVoiceChannelCountMax())
+ {
+ }
+
+ public CommandList(MemoryManager memoryManager, Memory mixBuffer, uint sampleCount, uint sampleRate, uint mixBufferCount, uint voiceChannelCountMax)
+ {
+ SampleCount = sampleCount;
+ SampleRate = sampleRate;
+ BufferCount = mixBufferCount + voiceChannelCountMax;
+ Buffers = mixBuffer;
+ Commands = new List();
+ MemoryManager = memoryManager;
+ }
+
+ public void AddCommand(ICommand command)
+ {
+ Commands.Add(command);
+ }
+
+ public void AddCommand(T command) where T : unmanaged, ICommand
+ {
+ throw new NotImplementedException();
+ }
+
+ public Memory GetBufferMemory(int index)
+ {
+ return Buffers.Slice(index * (int)SampleCount, (int)SampleCount);
+ }
+
+ public Span GetBuffer(int index)
+ {
+ return Buffers.Span.Slice(index * (int)SampleCount, (int)SampleCount);
+ }
+
+ public ulong GetTimeElapsedSinceDspStartedProcessing()
+ {
+ return (ulong)PerformanceCounter.ElapsedNanoseconds - StartTime;
+ }
+
+ public void Process(HardwareDevice 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 000000000..6532cd153
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/CommandType.cs
@@ -0,0 +1,49 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 000000000..62b952151
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/CopyMixBufferCommand.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span 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 000000000..7b68ff5dc
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/DataSourceVersion2Command.cs
@@ -0,0 +1,126 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 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 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 = outputBufferIndex;
+ SampleRate = serverState.SampleRate;
+ Pitch = serverState.Pitch;
+
+ WaveBuffers = new WaveBuffer[RendererConstants.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 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 000000000..1bf844d8a
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/DelayCommand.cs
@@ -0,0 +1,272 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 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 state, bool isEnabled, ulong workBuffer, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ _parameter = parameter;
+ State = state;
+ WorkBuffer = workBuffer;
+
+ IsEffectEnabled = isEnabled;
+
+ InputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[RendererConstants.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 outputBuffer, ReadOnlySpan 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[] outputBuffers, ReadOnlyMemory[] 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[] outputBuffers, ReadOnlyMemory[] 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[] outputBuffers, ReadOnlyMemory[] 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[] inputBuffers = new ReadOnlyMemory[Parameter.ChannelCount];
+ Memory[] outputBuffers = new Memory[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 000000000..5a9806b66
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/DepopForMixBuffersCommand.cs
@@ -0,0 +1,103 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 DepopBuffer { get; }
+
+ private const int FixedPointPrecisionForDecay = 15;
+
+ public DepopForMixBuffersCommand(Memory 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 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 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 000000000..21ded53af
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/DepopPrepareCommand.cs
@@ -0,0 +1,72 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 State { get; }
+ public Memory DepopBuffer { get; }
+
+ public DepopPrepareCommand(Memory state, Memory depopBuffer, uint mixBufferCount, uint bufferOffset, int nodeId, bool enabled)
+ {
+ Enabled = enabled;
+ NodeId = nodeId;
+ MixBufferCount = mixBufferCount;
+
+ OutputBufferIndices = new ushort[RendererConstants.MixBufferCountMax];
+
+ for (int i = 0; i < RendererConstants.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 000000000..111d2a4c6
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/DeviceSinkCommand.cs
@@ -0,0 +1,108 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+using Ryujinx.Audio.Renderer.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 Buffers { get; }
+
+ public DeviceSinkCommand(uint bufferOffset, DeviceSink sink, int sessionId, Memory 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 GetBuffer(int index, int sampleCount)
+ {
+ return Buffers.Span.Slice(index * sampleCount, sampleCount);
+ }
+
+ public void Process(CommandList context)
+ {
+ HardwareDevice device = context.OutputDevice;
+
+ if (device.GetSampleRate() == RendererConstants.TargetSampleRate)
+ {
+ int channelCount = (int)device.GetChannelCount();
+ uint bufferCount = Math.Min(device.GetChannelCount(), InputCount);
+
+ const int sampleCount = RendererConstants.TargetSampleCount;
+
+ short[] outputBuffer = new short[bufferCount * sampleCount];
+
+ for (int i = 0; i < bufferCount; i++)
+ {
+ ReadOnlySpan 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 000000000..76f5c5767
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs
@@ -0,0 +1,89 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 inputBufferOffset, Span outputBufferOffset, ReadOnlySpan downMixParameter, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax];
+
+ for (int i = 0; i < RendererConstants.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 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 frontLeft = context.GetBuffer(InputBufferIndices[0]);
+ ReadOnlySpan frontRight = context.GetBuffer(InputBufferIndices[1]);
+ ReadOnlySpan lowFrequency = context.GetBuffer(InputBufferIndices[2]);
+ ReadOnlySpan frontCenter = context.GetBuffer(InputBufferIndices[3]);
+ ReadOnlySpan backLeft = context.GetBuffer(InputBufferIndices[4]);
+ ReadOnlySpan backRight = context.GetBuffer(InputBufferIndices[5]);
+
+ Span stereoLeft = context.GetBuffer(OutputBufferIndices[0]);
+ Span stereoRight = context.GetBuffer(OutputBufferIndices[1]);
+ Span unused2 = context.GetBuffer(OutputBufferIndices[2]);
+ Span unused3 = context.GetBuffer(OutputBufferIndices[3]);
+ Span unused4 = context.GetBuffer(OutputBufferIndices[4]);
+ Span 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 000000000..85a011234
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/ICommand.cs
@@ -0,0 +1,37 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 000000000..87b296f63
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/MixCommand.cs
@@ -0,0 +1,125 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 outputMix, ReadOnlySpan inputMix)
+ {
+ Vector256 volumeVec = Vector256.Create(Volume);
+
+ ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix);
+ Span> outputVec = MemoryMarshal.Cast>(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 outputMix, ReadOnlySpan inputMix)
+ {
+ Vector128 volumeVec = Vector128.Create(Volume);
+
+ ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix);
+ Span> outputVec = MemoryMarshal.Cast>(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 outputMix, ReadOnlySpan inputMix)
+ {
+ for (int i = 0; i < inputMix.Length; i++)
+ {
+ outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
+ }
+ }
+
+ private void ProcessMix(Span outputMix, ReadOnlySpan inputMix)
+ {
+ if (Avx.IsSupported)
+ {
+ ProcessMixAvx(outputMix, inputMix);
+ }
+ else if (Sse41.IsSupported)
+ {
+ ProcessMixSse41(outputMix, inputMix);
+ }
+ else
+ {
+ ProcessMixSlowPath(outputMix, inputMix);
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span 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 000000000..0397dee2a
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/MixRampCommand.cs
@@ -0,0 +1,83 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 State { get; }
+
+ public int LastSampleIndex { get; }
+
+ public MixRampCommand(float volume0, float volume1, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory 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 outputBuffer, ReadOnlySpan 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 inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span 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 000000000..700b906c7
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/MixRampGroupedCommand.cs
@@ -0,0 +1,106 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 State { get; }
+
+ public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span volume0, Span volume1, Memory state, int nodeId)
+ {
+ Enabled = true;
+ MixBufferCount = mixBufferCount;
+ NodeId = nodeId;
+
+ InputBufferIndices = new ushort[RendererConstants.MixBufferCountMax];
+ OutputBufferIndices = new ushort[RendererConstants.MixBufferCountMax];
+ Volume0 = new float[RendererConstants.MixBufferCountMax];
+ Volume1 = new float[RendererConstants.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 outputBuffer, ReadOnlySpan 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 inputBuffer = context.GetBuffer(InputBufferIndices[i]);
+ Span 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 000000000..49e6253d7
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs
@@ -0,0 +1,92 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 State { get; }
+ public DecodingBehaviour DecodingBehaviour { get; }
+
+ public PcmFloatDataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory 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[RendererConstants.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 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 000000000..56f85a90d
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs
@@ -0,0 +1,92 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 State { get; }
+ public DecodingBehaviour DecodingBehaviour { get; }
+
+ public PcmInt16DataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory 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[RendererConstants.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 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 000000000..199667e02
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/PerformanceCommand.cs
@@ -0,0 +1,64 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 000000000..eeb7322a6
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/Reverb3dCommand.cs
@@ -0,0 +1,269 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 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 state, bool isEnabled, ulong workBuffer, int nodeId)
+ {
+ Enabled = true;
+ IsEffectEnabled = isEnabled;
+ NodeId = nodeId;
+ _parameter = parameter;
+ State = state;
+ WorkBuffer = workBuffer;
+
+ InputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[RendererConstants.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[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount)
+ {
+ const int delayLineSampleIndexOffset = -1;
+
+ ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableMono, TargetEarlyDelayLineIndicesTableMono, TargetOutputFeedbackIndicesTableMono, delayLineSampleIndexOffset);
+ }
+
+ private void ProcessReverb3dStereo(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount)
+ {
+ const int delayLineSampleIndexOffset = 1;
+
+ ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableStereo, TargetEarlyDelayLineIndicesTableStereo, TargetOutputFeedbackIndicesTableStereo, delayLineSampleIndexOffset);
+ }
+
+ private void ProcessReverb3dQuadraphonic(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount)
+ {
+ const int delayLineSampleIndexOffset = 1;
+
+ ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableQuadraphonic, TargetEarlyDelayLineIndicesTableQuadraphonic, TargetOutputFeedbackIndicesTableQuadraphonic, delayLineSampleIndexOffset);
+ }
+
+ private void ProcessReverb3dSurround(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount)
+ {
+ const int delayLineSampleIndexOffset = 1;
+
+ ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableSurround, TargetEarlyDelayLineIndicesTableSurround, TargetOutputFeedbackIndicesTableSurround, delayLineSampleIndexOffset);
+ }
+
+ private void ProcessReverb3dGeneric(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount, ReadOnlySpan outputEarlyIndicesTable, ReadOnlySpan targetEarlyDelayLineIndicesTable, ReadOnlySpan targetOutputFeedbackIndicesTable, int delayLineSampleIndexOffset)
+ {
+ ref Reverb3dState state = ref State.Span[0];
+
+ bool isMono = Parameter.ChannelCount == 1;
+ bool isSurround = Parameter.ChannelCount == 6;
+
+ float[] outputValues = new float[RendererConstants.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[] inputBuffers = new ReadOnlyMemory[Parameter.ChannelCount];
+ Memory[] outputBuffers = new Memory[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 000000000..3935234ee
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/ReverbCommand.cs
@@ -0,0 +1,284 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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[RendererConstants.ChannelCountMax] { 0, 1, 2, 3, 4, 5 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[RendererConstants.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 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 state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported)
+ {
+ Enabled = true;
+ IsEffectEnabled = isEnabled;
+ NodeId = nodeId;
+ _parameter = parameter;
+ State = state;
+ WorkBuffer = workBuffer;
+
+ InputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[RendererConstants.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[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount)
+ {
+ ProcessReverbGeneric(outputBuffers,
+ inputBuffers,
+ sampleCount,
+ OutputEarlyIndicesTableMono,
+ TargetEarlyDelayLineIndicesTableMono,
+ TargetOutputFeedbackIndicesTableMono,
+ OutputIndicesTableMono);
+ }
+
+ private void ProcessReverbStereo(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount)
+ {
+ ProcessReverbGeneric(outputBuffers,
+ inputBuffers,
+ sampleCount,
+ OutputEarlyIndicesTableStereo,
+ TargetEarlyDelayLineIndicesTableStereo,
+ TargetOutputFeedbackIndicesTableStereo,
+ OutputIndicesTableStereo);
+ }
+
+ private void ProcessReverbQuadraphonic(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount)
+ {
+ ProcessReverbGeneric(outputBuffers,
+ inputBuffers,
+ sampleCount,
+ OutputEarlyIndicesTableQuadraphonic,
+ TargetEarlyDelayLineIndicesTableQuadraphonic,
+ TargetOutputFeedbackIndicesTableQuadraphonic,
+ OutputIndicesTableQuadraphonic);
+ }
+
+ private void ProcessReverbSurround(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount)
+ {
+ ProcessReverbGeneric(outputBuffers,
+ inputBuffers,
+ sampleCount,
+ OutputEarlyIndicesTableSurround,
+ TargetEarlyDelayLineIndicesTableSurround,
+ TargetOutputFeedbackIndicesTableSurround,
+ OutputIndicesTableSurround);
+ }
+
+ private void ProcessReverbGeneric(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount, ReadOnlySpan outputEarlyIndicesTable, ReadOnlySpan targetEarlyDelayLineIndicesTable, ReadOnlySpan targetOutputFeedbackIndicesTable, ReadOnlySpan 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[RendererConstants.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[] inputBuffers = new ReadOnlyMemory[Parameter.ChannelCount];
+ Memory[] outputBuffers = new Memory[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 000000000..0761a8dad
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/UpsampleCommand.cs
@@ -0,0 +1,87 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 OutBuffer { get; }
+
+ public UpsampleCommand(uint bufferOffset, UpsamplerState info, uint inputCount, Span 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 GetBuffer(int index, int sampleCount)
+ {
+ return UpsamplerInfo.OutputBuffer.Span.Slice(index * sampleCount, sampleCount);
+ }
+
+ public void Process(CommandList context)
+ {
+ float ratio = (float)InputSampleRate / RendererConstants.TargetSampleRate;
+
+ uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount);
+
+ for (int i = 0; i < bufferCount; i++)
+ {
+ Span inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]);
+ Span 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 000000000..735a0a685
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/VolumeCommand.cs
@@ -0,0 +1,125 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 outputBuffer, ReadOnlySpan inputBuffer)
+ {
+ Vector256 volumeVec = Vector256.Create(Volume);
+
+ ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputBuffer);
+ Span> outputVec = MemoryMarshal.Cast>(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 outputBuffer, ReadOnlySpan inputBuffer)
+ {
+ Vector128 volumeVec = Vector128.Create(Volume);
+
+ ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputBuffer);
+ Span> outputVec = MemoryMarshal.Cast>(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 outputBuffer, ReadOnlySpan 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 outputBuffer, ReadOnlySpan inputBuffer)
+ {
+ for (int i = 0; i < outputBuffer.Length; i++)
+ {
+ outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume);
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span 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 000000000..98427a2c1
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/Command/VolumeRampCommand.cs
@@ -0,0 +1,71 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+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 outputBuffer, ReadOnlySpan 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 inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span 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 000000000..ccc97b924
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/DataSourceHelper.cs
@@ -0,0 +1,408 @@
+//
+// Copyright (c) 2019-2020 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 .
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
+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 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(MemoryManager memoryManager, Span outputBuffer, WaveBufferInformation info, uint targetSampleRate, int sampleCount)
+ {
+ const int tempBufferSize = 0x3F00;
+
+ ref VoiceUpdateState state = ref info.State.Span[0];
+
+ short[] tempBuffer = ArrayPool.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 >= RendererConstants.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(waveBuffer.Context);
+ }
+
+ Span 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 waveBufferAdpcm = ReadOnlySpan.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 coefficients = MemoryMarshal.Cast(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 waveBufferPcm16 = ReadOnlySpan.Empty;
+
+ if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0)
+ {
+ ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset(targetSampleStartOffset, offset, info.ChannelCount);
+ int bufferSize = PcmHelper.GetBufferSize(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount;
+
+ waveBufferPcm16 = MemoryMarshal.Cast(memoryManager.GetSpan(bufferOffset, bufferSize));
+ }
+
+ decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcm16, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount);
+ break;
+ case SampleFormat.PcmFloat:
+ ReadOnlySpan waveBufferPcmFloat = ReadOnlySpan.Empty;
+
+ if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0)
+ {
+ ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset(targetSampleStartOffset, offset, info.ChannelCount);
+ int bufferSize = PcmHelper.GetBufferSize(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount;
+
+ waveBufferPcmFloat = MemoryMarshal.Cast(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 outputSpan = outputBuffer.Slice(i);
+ Span outputSpanInt = MemoryMarshal.Cast(outputSpan);
+
+ if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion))
+ {
+ for (int j = 0; j < y; j++)
+ {
+ outputBuffer[j] = tempBuffer[j];
+ }
+ }
+ else
+ {
+ Span 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.Shared.Return(tempBuffer);
+ }
+
+ private static void ToFloatAvx(Span output, ReadOnlySpan input, int sampleCount)
+ {
+ ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input);
+ Span> outputVec = MemoryMarshal.Cast>(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 output, ReadOnlySpan input, int sampleCount)
+ {
+ ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input);
+ Span