From a389dd59bd881cf2cff09a1f67f5c30de61123e6 Mon Sep 17 00:00:00 2001 From: Mary Date: Tue, 18 Aug 2020 03:49:37 +0200 Subject: Amadeus: Final Act (#1481) * Amadeus: Final Act This is my requiem, I present to you Amadeus, a complete reimplementation of the Audio Renderer! This reimplementation is based on my reversing of every version of the audio system module that I carried for the past 10 months. This supports every revision (at the time of writing REV1 to REV8 included) and all features proposed by the Audio Renderer on real hardware. Because this component could be used outside an emulation context, and to avoid possible "inspirations" not crediting the project, I decided to license the Ryujinx.Audio.Renderer project under LGPLv3. - FE3H voices in videos and chapter intro are not present. - Games that use two audio renderer **at the same time** are probably going to have issues right now **until we rewrite the audio output interface** (Crash Team Racing is the only known game to use two renderer at the same time). - Persona 5 Scrambler now goes ingame but audio is garbage. This is caused by the fact that the game engine is syncing audio and video in a really aggressive way. This will disappears the day this game run at full speed. * Make timing more precise when sleeping on Windows Improve precision to a 1ms resolution on Windows NT based OS. This is used to avoid having totally erratic timings and unify all Windows users to the same resolution. NOTE: This is only active when emulation is running. --- .../Common/AuxiliaryBufferAddresses.cs | 30 +++ .../Common/BehaviourParameter.cs | 67 ++++++ Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs | 167 ++++++++++++++ Ryujinx.Audio.Renderer/Common/EffectType.cs | 60 +++++ .../Common/MemoryPoolUserState.cs | 60 +++++ Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs | 45 ++++ Ryujinx.Audio.Renderer/Common/NodeIdType.cs | 50 +++++ Ryujinx.Audio.Renderer/Common/NodeStates.cs | 246 +++++++++++++++++++++ .../Common/PerformanceDetailType.cs | 34 +++ .../Common/PerformanceEntryType.cs | 28 +++ Ryujinx.Audio.Renderer/Common/PlayState.cs | 40 ++++ Ryujinx.Audio.Renderer/Common/ReverbEarlyMode.cs | 50 +++++ Ryujinx.Audio.Renderer/Common/ReverbLateMode.cs | 55 +++++ Ryujinx.Audio.Renderer/Common/SampleFormat.cs | 60 +++++ Ryujinx.Audio.Renderer/Common/SinkType.cs | 40 ++++ Ryujinx.Audio.Renderer/Common/UpdateDataHeader.cs | 50 +++++ Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs | 121 ++++++++++ Ryujinx.Audio.Renderer/Common/WaveBuffer.cs | 99 +++++++++ .../Common/WorkBufferAllocator.cs | 78 +++++++ 19 files changed, 1380 insertions(+) create mode 100644 Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs create mode 100644 Ryujinx.Audio.Renderer/Common/BehaviourParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs create mode 100644 Ryujinx.Audio.Renderer/Common/EffectType.cs create mode 100644 Ryujinx.Audio.Renderer/Common/MemoryPoolUserState.cs create mode 100644 Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs create mode 100644 Ryujinx.Audio.Renderer/Common/NodeIdType.cs create mode 100644 Ryujinx.Audio.Renderer/Common/NodeStates.cs create mode 100644 Ryujinx.Audio.Renderer/Common/PerformanceDetailType.cs create mode 100644 Ryujinx.Audio.Renderer/Common/PerformanceEntryType.cs create mode 100644 Ryujinx.Audio.Renderer/Common/PlayState.cs create mode 100644 Ryujinx.Audio.Renderer/Common/ReverbEarlyMode.cs create mode 100644 Ryujinx.Audio.Renderer/Common/ReverbLateMode.cs create mode 100644 Ryujinx.Audio.Renderer/Common/SampleFormat.cs create mode 100644 Ryujinx.Audio.Renderer/Common/SinkType.cs create mode 100644 Ryujinx.Audio.Renderer/Common/UpdateDataHeader.cs create mode 100644 Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs create mode 100644 Ryujinx.Audio.Renderer/Common/WaveBuffer.cs create mode 100644 Ryujinx.Audio.Renderer/Common/WorkBufferAllocator.cs (limited to 'Ryujinx.Audio.Renderer/Common') diff --git a/Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs b/Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs new file mode 100644 index 00000000..d8e345d8 --- /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 00000000..8881a92c --- /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 00000000..11528174 --- /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 00000000..8349ecb5 --- /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 00000000..f1ea8d9a --- /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 00000000..6bb66058 --- /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 00000000..9ee760e9 --- /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 00000000..6598619a --- /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 00000000..ebf5b469 --- /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 00000000..ba27633f --- /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 00000000..a96b86dd --- /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 00000000..2b77336e --- /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 00000000..dab78c45 --- /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 00000000..24da48bf --- /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 00000000..22cab23c --- /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 00000000..40c29f55 --- /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 00000000..490bb871 --- /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 00000000..c2dd1d6b --- /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 00000000..5e5fdff0 --- /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; + } + } +} -- cgit v1.2.3