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.
This commit is contained in:
parent
2a314f3c28
commit
a389dd59bd
@ -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; }
|
||||
|
@ -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.
|
||||
|
30
Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs
Normal file
30
Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct AuxiliaryBufferAddresses
|
||||
{
|
||||
public ulong SendBufferInfo;
|
||||
public ulong SendBufferInfoBase;
|
||||
public ulong ReturnBufferInfo;
|
||||
public ulong ReturnBufferInfoBase;
|
||||
}
|
||||
}
|
67
Ryujinx.Audio.Renderer/Common/BehaviourParameter.cs
Normal file
67
Ryujinx.Audio.Renderer/Common/BehaviourParameter.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the input parameter for <see cref="Server.BehaviourContext"/>.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct BehaviourParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// The current audio renderer revision in use.
|
||||
/// </summary>
|
||||
public int UserRevision;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved/padding.
|
||||
/// </summary>
|
||||
private uint _padding;
|
||||
|
||||
/// <summary>
|
||||
/// The flags given controlling behaviour of the audio renderer
|
||||
/// </summary>
|
||||
/// <remarks>See <see cref="Server.BehaviourContext.UpdateFlags(ulong)"/> and <see cref="Server.BehaviourContext.IsMemoryPoolForceMappingEnabled"/>.</remarks>
|
||||
public ulong Flags;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an error during <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct ErrorInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The error code to report.
|
||||
/// </summary>
|
||||
public ResultCode ErrorCode;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved/padding.
|
||||
/// </summary>
|
||||
private uint _padding;
|
||||
|
||||
/// <summary>
|
||||
/// Extra information given with the <see cref="ResultCode"/>
|
||||
/// </summary>
|
||||
/// <remarks>This is usually used to report a faulting cpu address when a <see cref="Server.MemoryPool.MemoryPoolState"/> mapping fail.</remarks>
|
||||
public ulong ExtraErrorInfo;
|
||||
}
|
||||
}
|
||||
}
|
167
Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs
Normal file
167
Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a adjacent matrix.
|
||||
/// </summary>
|
||||
/// <remarks>This is used for splitter routing.</remarks>
|
||||
public class EdgeMatrix
|
||||
{
|
||||
/// <summary>
|
||||
/// Backing <see cref="BitArray"/> used for node connections.
|
||||
/// </summary>
|
||||
private BitArray _storage;
|
||||
|
||||
/// <summary>
|
||||
/// The count of nodes of the current instance.
|
||||
/// </summary>
|
||||
private int _nodeCount;
|
||||
|
||||
/// <summary>
|
||||
/// Get the required work buffer size memory needed for the <see cref="EdgeMatrix"/>.
|
||||
/// </summary>
|
||||
/// <param name="nodeCount">The count of nodes.</param>
|
||||
/// <returns>The size required for the given <paramref name="nodeCount"/>.</returns>
|
||||
public static int GetWorkBufferSize(int nodeCount)
|
||||
{
|
||||
int size = BitUtils.AlignUp(nodeCount * nodeCount, RendererConstants.BufferAlignment);
|
||||
|
||||
return size / Unsafe.SizeOf<byte>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="EdgeMatrix"/> instance with backing memory.
|
||||
/// </summary>
|
||||
/// <param name="edgeMatrixWorkBuffer">The backing memory.</param>
|
||||
/// <param name="nodeCount">The count of nodes.</param>
|
||||
public void Initialize(Memory<byte> edgeMatrixWorkBuffer, int nodeCount)
|
||||
{
|
||||
Debug.Assert(edgeMatrixWorkBuffer.Length >= GetWorkBufferSize(nodeCount));
|
||||
|
||||
_storage = new BitArray(edgeMatrixWorkBuffer);
|
||||
|
||||
_nodeCount = nodeCount;
|
||||
|
||||
_storage.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if the bit at the given index is set.
|
||||
/// </summary>
|
||||
/// <param name="index">A bit index.</param>
|
||||
/// <returns>Returns true if the bit at the given index is set</returns>
|
||||
public bool Test(int index)
|
||||
{
|
||||
return _storage.Test(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset all bits in the storage.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_storage.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the bit at the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">A bit index.</param>
|
||||
public void Reset(int index)
|
||||
{
|
||||
_storage.Reset(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the bit at the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">A bit index.</param>
|
||||
public void Set(int index)
|
||||
{
|
||||
_storage.Set(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect a given source to a given destination.
|
||||
/// </summary>
|
||||
/// <param name="source">The source index.</param>
|
||||
/// <param name="destination">The destination index.</param>
|
||||
public void Connect(int source, int destination)
|
||||
{
|
||||
Debug.Assert(source < _nodeCount);
|
||||
Debug.Assert(destination < _nodeCount);
|
||||
|
||||
_storage.Set(_nodeCount * source + destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given source is connected to the given destination.
|
||||
/// </summary>
|
||||
/// <param name="source">The source index.</param>
|
||||
/// <param name="destination">The destination index.</param>
|
||||
/// <returns>Returns true if the given source is connected to the given destination.</returns>
|
||||
public bool Connected(int source, int destination)
|
||||
{
|
||||
Debug.Assert(source < _nodeCount);
|
||||
Debug.Assert(destination < _nodeCount);
|
||||
|
||||
return _storage.Test(_nodeCount * source + destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect a given source from a given destination.
|
||||
/// </summary>
|
||||
/// <param name="source">The source index.</param>
|
||||
/// <param name="destination">The destination index.</param>
|
||||
public void Disconnect(int source, int destination)
|
||||
{
|
||||
Debug.Assert(source < _nodeCount);
|
||||
Debug.Assert(destination < _nodeCount);
|
||||
|
||||
_storage.Reset(_nodeCount * source + destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all edges from a given source.
|
||||
/// </summary>
|
||||
/// <param name="source">The source index.</param>
|
||||
public void RemoveEdges(int source)
|
||||
{
|
||||
for (int i = 0; i < _nodeCount; i++)
|
||||
{
|
||||
Disconnect(source, i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the total node count.
|
||||
/// </summary>
|
||||
/// <returns>The total node count.</returns>
|
||||
public int GetNodeCount()
|
||||
{
|
||||
return _nodeCount;
|
||||
}
|
||||
}
|
||||
}
|
60
Ryujinx.Audio.Renderer/Common/EffectType.cs
Normal file
60
Ryujinx.Audio.Renderer/Common/EffectType.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of an effect.
|
||||
/// </summary>
|
||||
public enum EffectType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid effect.
|
||||
/// </summary>
|
||||
Invalid,
|
||||
|
||||
/// <summary>
|
||||
/// Effect applying additional mixing capability.
|
||||
/// </summary>
|
||||
BufferMix,
|
||||
|
||||
/// <summary>
|
||||
/// Effect applying custom user effect (via auxiliary buffers).
|
||||
/// </summary>
|
||||
AuxiliaryBuffer,
|
||||
|
||||
/// <summary>
|
||||
/// Effect applying a delay.
|
||||
/// </summary>
|
||||
Delay,
|
||||
|
||||
/// <summary>
|
||||
/// Effect applying a reverberation effect via a given preset.
|
||||
/// </summary>
|
||||
Reverb,
|
||||
|
||||
/// <summary>
|
||||
/// Effect applying a 3D reverberation effect via a given preset.
|
||||
/// </summary>
|
||||
Reverb3d,
|
||||
|
||||
/// <summary>
|
||||
/// Effect applying a biquad filter.
|
||||
/// </summary>
|
||||
BiquadFilter
|
||||
}
|
||||
}
|
60
Ryujinx.Audio.Renderer/Common/MemoryPoolUserState.cs
Normal file
60
Ryujinx.Audio.Renderer/Common/MemoryPoolUserState.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the state of a memory pool.
|
||||
/// </summary>
|
||||
public enum MemoryPoolUserState : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid state.
|
||||
/// </summary>
|
||||
Invalid = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The memory pool is new. (client side only)
|
||||
/// </summary>
|
||||
New = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The user asked to detach the memory pool from the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
RequestDetach = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The memory pool is detached from the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
Detached = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The user asked to attach the memory pool to the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
RequestAttach = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The memory pool is attached to the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
Attached = 5,
|
||||
|
||||
/// <summary>
|
||||
/// The memory pool is released. (client side only)
|
||||
/// </summary>
|
||||
Released = 6
|
||||
}
|
||||
}
|
45
Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs
Normal file
45
Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper for manipulating node ids.
|
||||
/// </summary>
|
||||
public static class NodeIdHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the type of a node from a given node id.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">Id of the node.</param>
|
||||
/// <returns>The type of the node.</returns>
|
||||
public static NodeIdType GetType(int nodeId)
|
||||
{
|
||||
return (NodeIdType)(nodeId >> 28);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the base of a node from a given node id.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">Id of the node.</param>
|
||||
/// <returns>The base of the node.</returns>
|
||||
public static int GetBase(int nodeId)
|
||||
{
|
||||
return (nodeId >> 16) & 0xFFF;
|
||||
}
|
||||
}
|
||||
}
|
50
Ryujinx.Audio.Renderer/Common/NodeIdType.cs
Normal file
50
Ryujinx.Audio.Renderer/Common/NodeIdType.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of a node.
|
||||
/// </summary>
|
||||
public enum NodeIdType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid node id.
|
||||
/// </summary>
|
||||
Invalid = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Voice related node id. (data source, biquad filter, ...)
|
||||
/// </summary>
|
||||
Voice = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Mix related node id. (mix, effects, splitters, ...)
|
||||
/// </summary>
|
||||
Mix = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Sink related node id. (device & circular buffer sink)
|
||||
/// </summary>
|
||||
Sink = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Performance monitoring related node id (performance commands)
|
||||
/// </summary>
|
||||
Performance = 15
|
||||
}
|
||||
}
|
246
Ryujinx.Audio.Renderer/Common/NodeStates.cs
Normal file
246
Ryujinx.Audio.Renderer/Common/NodeStates.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
public class NodeStates
|
||||
{
|
||||
private class Stack
|
||||
{
|
||||
private Memory<int> _storage;
|
||||
private int _index;
|
||||
|
||||
private int _nodeCount;
|
||||
|
||||
public void Reset(Memory<int> storage, int nodeCount)
|
||||
{
|
||||
Debug.Assert(storage.Length * sizeof(int) >= CalcBufferSize(nodeCount));
|
||||
|
||||
_storage = storage;
|
||||
_index = 0;
|
||||
_nodeCount = nodeCount;
|
||||
}
|
||||
|
||||
public int GetCurrentCount()
|
||||
{
|
||||
return _index;
|
||||
}
|
||||
|
||||
public void Push(int data)
|
||||
{
|
||||
Debug.Assert(_index + 1 <= _nodeCount);
|
||||
|
||||
_storage.Span[_index++] = data;
|
||||
}
|
||||
|
||||
public int Pop()
|
||||
{
|
||||
Debug.Assert(_index > 0);
|
||||
|
||||
return _storage.Span[--_index];
|
||||
}
|
||||
|
||||
public int Top()
|
||||
{
|
||||
return _storage.Span[_index - 1];
|
||||
}
|
||||
|
||||
public static int CalcBufferSize(int nodeCount)
|
||||
{
|
||||
return nodeCount * sizeof(int);
|
||||
}
|
||||
}
|
||||
|
||||
private int _nodeCount;
|
||||
private EdgeMatrix _discovered;
|
||||
private EdgeMatrix _finished;
|
||||
private Memory<int> _resultArray;
|
||||
private Stack _stack;
|
||||
private int _tsortResultIndex;
|
||||
|
||||
private enum NodeState : byte
|
||||
{
|
||||
Unknown,
|
||||
Discovered,
|
||||
Finished
|
||||
}
|
||||
|
||||
public NodeStates()
|
||||
{
|
||||
_stack = new Stack();
|
||||
_discovered = new EdgeMatrix();
|
||||
_finished = new EdgeMatrix();
|
||||
}
|
||||
|
||||
public static int GetWorkBufferSize(int nodeCount)
|
||||
{
|
||||
return Stack.CalcBufferSize(nodeCount * nodeCount) + 0xC * nodeCount + 2 * EdgeMatrix.GetWorkBufferSize(nodeCount);
|
||||
}
|
||||
|
||||
public void Initialize(Memory<byte> nodeStatesWorkBuffer, int nodeCount)
|
||||
{
|
||||
int workBufferSize = GetWorkBufferSize(nodeCount);
|
||||
|
||||
Debug.Assert(nodeStatesWorkBuffer.Length >= workBufferSize);
|
||||
|
||||
_nodeCount = nodeCount;
|
||||
|
||||
int edgeMatrixWorkBufferSize = EdgeMatrix.GetWorkBufferSize(nodeCount);
|
||||
|
||||
_discovered.Initialize(nodeStatesWorkBuffer.Slice(0, edgeMatrixWorkBufferSize), nodeCount);
|
||||
_finished.Initialize(nodeStatesWorkBuffer.Slice(edgeMatrixWorkBufferSize, edgeMatrixWorkBufferSize), nodeCount);
|
||||
|
||||
nodeStatesWorkBuffer = nodeStatesWorkBuffer.Slice(edgeMatrixWorkBufferSize * 2);
|
||||
|
||||
_resultArray = SpanMemoryManager<int>.Cast(nodeStatesWorkBuffer.Slice(0, sizeof(int) * nodeCount));
|
||||
|
||||
nodeStatesWorkBuffer = nodeStatesWorkBuffer.Slice(sizeof(int) * nodeCount);
|
||||
|
||||
Memory<int> stackWorkBuffer = SpanMemoryManager<int>.Cast(nodeStatesWorkBuffer.Slice(0, Stack.CalcBufferSize(nodeCount * nodeCount)));
|
||||
|
||||
_stack.Reset(stackWorkBuffer, nodeCount * nodeCount);
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_discovered.Reset();
|
||||
_finished.Reset();
|
||||
_tsortResultIndex = 0;
|
||||
_resultArray.Span.Fill(-1);
|
||||
}
|
||||
|
||||
private NodeState GetState(int index)
|
||||
{
|
||||
Debug.Assert(index < _nodeCount);
|
||||
|
||||
if (_discovered.Test(index))
|
||||
{
|
||||
Debug.Assert(!_finished.Test(index));
|
||||
|
||||
return NodeState.Discovered;
|
||||
}
|
||||
else if (_finished.Test(index))
|
||||
{
|
||||
Debug.Assert(!_discovered.Test(index));
|
||||
|
||||
return NodeState.Finished;
|
||||
}
|
||||
|
||||
return NodeState.Unknown;
|
||||
}
|
||||
|
||||
private void SetState(int index, NodeState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case NodeState.Unknown:
|
||||
_discovered.Reset(index);
|
||||
_finished.Reset(index);
|
||||
break;
|
||||
case NodeState.Discovered:
|
||||
_discovered.Set(index);
|
||||
_finished.Reset(index);
|
||||
break;
|
||||
case NodeState.Finished:
|
||||
_finished.Set(index);
|
||||
_discovered.Reset(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PushTsortResult(int index)
|
||||
{
|
||||
Debug.Assert(index < _nodeCount);
|
||||
|
||||
_resultArray.Span[_tsortResultIndex++] = index;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<int> GetTsortResult()
|
||||
{
|
||||
return _resultArray.Span.Slice(0, _tsortResultIndex);
|
||||
}
|
||||
|
||||
public bool Sort(EdgeMatrix edgeMatrix)
|
||||
{
|
||||
Reset();
|
||||
|
||||
if (_nodeCount <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _nodeCount; i++)
|
||||
{
|
||||
if (GetState(i) == NodeState.Unknown)
|
||||
{
|
||||
_stack.Push(i);
|
||||
}
|
||||
|
||||
while (_stack.GetCurrentCount() > 0)
|
||||
{
|
||||
int topIndex = _stack.Top();
|
||||
|
||||
NodeState topState = GetState(topIndex);
|
||||
|
||||
if (topState == NodeState.Discovered)
|
||||
{
|
||||
SetState(topIndex, NodeState.Finished);
|
||||
PushTsortResult(topIndex);
|
||||
_stack.Pop();
|
||||
}
|
||||
else if (topState == NodeState.Finished)
|
||||
{
|
||||
_stack.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (topState == NodeState.Unknown)
|
||||
{
|
||||
SetState(topIndex, NodeState.Discovered);
|
||||
}
|
||||
|
||||
for (int j = 0; j < edgeMatrix.GetNodeCount(); j++)
|
||||
{
|
||||
if (edgeMatrix.Connected(topIndex, j))
|
||||
{
|
||||
NodeState jState = GetState(j);
|
||||
|
||||
if (jState == NodeState.Unknown)
|
||||
{
|
||||
_stack.Push(j);
|
||||
}
|
||||
// Found a loop, reset and propagate rejection.
|
||||
else if (jState == NodeState.Discovered)
|
||||
{
|
||||
Reset();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
34
Ryujinx.Audio.Renderer/Common/PerformanceDetailType.cs
Normal file
34
Ryujinx.Audio.Renderer/Common/PerformanceDetailType.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
public enum PerformanceDetailType : byte
|
||||
{
|
||||
Unknown,
|
||||
PcmInt16,
|
||||
Adpcm,
|
||||
VolumeRamp,
|
||||
BiquadFilter,
|
||||
Mix,
|
||||
Delay,
|
||||
Aux,
|
||||
Reverb,
|
||||
Reverb3d,
|
||||
PcmFloat
|
||||
}
|
||||
}
|
28
Ryujinx.Audio.Renderer/Common/PerformanceEntryType.cs
Normal file
28
Ryujinx.Audio.Renderer/Common/PerformanceEntryType.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
public enum PerformanceEntryType : byte
|
||||
{
|
||||
Invalid,
|
||||
Voice,
|
||||
SubMix,
|
||||
FinalMix,
|
||||
Sink
|
||||
}
|
||||
}
|
40
Ryujinx.Audio.Renderer/Common/PlayState.cs
Normal file
40
Ryujinx.Audio.Renderer/Common/PlayState.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Common play state.
|
||||
/// </summary>
|
||||
public enum PlayState : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The user request the voice to be started.
|
||||
/// </summary>
|
||||
Start,
|
||||
|
||||
/// <summary>
|
||||
/// The user request the voice to be stopped.
|
||||
/// </summary>
|
||||
Stop,
|
||||
|
||||
/// <summary>
|
||||
/// The user request the voice to be paused.
|
||||
/// </summary>
|
||||
Pause
|
||||
}
|
||||
}
|
50
Ryujinx.Audio.Renderer/Common/ReverbEarlyMode.cs
Normal file
50
Ryujinx.Audio.Renderer/Common/ReverbEarlyMode.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Early reverb reflection.
|
||||
/// </summary>
|
||||
public enum ReverbEarlyMode : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Room early reflection. (small acoustic space, fast reflection)
|
||||
/// </summary>
|
||||
Room,
|
||||
|
||||
/// <summary>
|
||||
/// Chamber early reflection. (bigger than <see cref="Room"/>'s acoustic space, short reflection)
|
||||
/// </summary>
|
||||
Chamber,
|
||||
|
||||
/// <summary>
|
||||
/// Hall early reflection. (large acoustic space, warm reflection)
|
||||
/// </summary>
|
||||
Hall,
|
||||
|
||||
/// <summary>
|
||||
/// Cathedral early reflection. (very large acoustic space, pronounced bright reflection)
|
||||
/// </summary>
|
||||
Cathedral,
|
||||
|
||||
/// <summary>
|
||||
/// No early reflection.
|
||||
/// </summary>
|
||||
Disabled
|
||||
}
|
||||
}
|
55
Ryujinx.Audio.Renderer/Common/ReverbLateMode.cs
Normal file
55
Ryujinx.Audio.Renderer/Common/ReverbLateMode.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Late reverb reflection.
|
||||
/// </summary>
|
||||
public enum ReverbLateMode : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Room late reflection. (small acoustic space, fast reflection)
|
||||
/// </summary>
|
||||
Room,
|
||||
|
||||
/// <summary>
|
||||
/// Hall late reflection. (large acoustic space, warm reflection)
|
||||
/// </summary>
|
||||
Hall,
|
||||
|
||||
/// <summary>
|
||||
/// Classic plate late reflection. (clean distinctive reverb)
|
||||
/// </summary>
|
||||
Plate,
|
||||
|
||||
/// <summary>
|
||||
/// Cathedral late reflection. (very large acoustic space, pronounced bright reflection)
|
||||
/// </summary>
|
||||
Cathedral,
|
||||
|
||||
/// <summary>
|
||||
/// Do not apply any delay. (max delay)
|
||||
/// </summary>
|
||||
NoDelay,
|
||||
|
||||
/// <summary>
|
||||
/// Max delay. (used for delay line limits)
|
||||
/// </summary>
|
||||
Limit = NoDelay
|
||||
}
|
||||
}
|
60
Ryujinx.Audio.Renderer/Common/SampleFormat.cs
Normal file
60
Ryujinx.Audio.Renderer/Common/SampleFormat.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample format definition.
|
||||
/// </summary>
|
||||
public enum SampleFormat : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid sample format.
|
||||
/// </summary>
|
||||
Invalid = 0,
|
||||
|
||||
/// <summary>
|
||||
/// PCM8 sample format. (unsupported)
|
||||
/// </summary>
|
||||
PcmInt8 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// PCM16 sample format.
|
||||
/// </summary>
|
||||
PcmInt16 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// PCM24 sample format. (unsupported)
|
||||
/// </summary>
|
||||
PcmInt24 = 3,
|
||||
|
||||
/// <summary>
|
||||
/// PCM32 sample format.
|
||||
/// </summary>
|
||||
PcmInt32 = 4,
|
||||
|
||||
/// <summary>
|
||||
/// PCM Float sample format.
|
||||
/// </summary>
|
||||
PcmFloat = 5,
|
||||
|
||||
/// <summary>
|
||||
/// ADPCM sample format. (Also known as GC-ADPCM)
|
||||
/// </summary>
|
||||
Adpcm = 6
|
||||
}
|
||||
}
|
40
Ryujinx.Audio.Renderer/Common/SinkType.cs
Normal file
40
Ryujinx.Audio.Renderer/Common/SinkType.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of a sink.
|
||||
/// </summary>
|
||||
public enum SinkType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The sink is in an invalid state.
|
||||
/// </summary>
|
||||
Invalid,
|
||||
|
||||
/// <summary>
|
||||
/// The sink is a device.
|
||||
/// </summary>
|
||||
Device,
|
||||
|
||||
/// <summary>
|
||||
/// The sink is a circular buffer.
|
||||
/// </summary>
|
||||
CircularBuffer
|
||||
}
|
||||
}
|
50
Ryujinx.Audio.Renderer/Common/UpdateDataHeader.cs
Normal file
50
Ryujinx.Audio.Renderer/Common/UpdateDataHeader.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Update data header used for input and output of <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>.
|
||||
/// </summary>
|
||||
public struct UpdateDataHeader
|
||||
{
|
||||
public int Revision;
|
||||
public uint BehaviourSize;
|
||||
public uint MemoryPoolsSize;
|
||||
public uint VoicesSize;
|
||||
public uint VoiceResourcesSize;
|
||||
public uint EffectsSize;
|
||||
public uint MixesSize;
|
||||
public uint SinksSize;
|
||||
public uint PerformanceBufferSize;
|
||||
public uint Unknown24;
|
||||
public uint RenderInfoSize;
|
||||
|
||||
private unsafe fixed int _reserved[4];
|
||||
|
||||
public uint TotalSize;
|
||||
|
||||
public void Initialize(int revision)
|
||||
{
|
||||
Revision = revision;
|
||||
|
||||
TotalSize = (uint)Unsafe.SizeOf<UpdateDataHeader>();
|
||||
}
|
||||
}
|
||||
}
|
121
Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs
Normal file
121
Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent the update state of a voice.
|
||||
/// </summary>
|
||||
/// <remarks>This is shared between the server and audio processor.</remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = Align)]
|
||||
public struct VoiceUpdateState
|
||||
{
|
||||
public const int Align = 0x10;
|
||||
public const int BiquadStateOffset = 0x0;
|
||||
public const int BiquadStateSize = 0x10;
|
||||
|
||||
/// <summary>
|
||||
/// The state of the biquad filters of this voice.
|
||||
/// </summary>
|
||||
public Array2<BiquadFilterState> BiquadFilterState;
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of samples that was played.
|
||||
/// </summary>
|
||||
/// <remarks>This is reset to 0 when a <see cref="WaveBuffer"/> finishes playing and <see cref="WaveBuffer.IsEndOfStream"/> is set.</remarks>
|
||||
/// <remarks>This is reset to 0 when looping while <see cref="Parameter.VoiceInParameter.DecodingBehaviour.PlayedSampleCountResetWhenLooping"/> is set.</remarks>
|
||||
public ulong PlayedSampleCount;
|
||||
|
||||
/// <summary>
|
||||
/// The current sample offset in the <see cref="WaveBuffer"/> pointed by <see cref="WaveBufferIndex"/>.
|
||||
/// </summary>
|
||||
public int Offset;
|
||||
|
||||
/// <summary>
|
||||
/// The current index of the <see cref="WaveBuffer"/> in use.
|
||||
/// </summary>
|
||||
public uint WaveBufferIndex;
|
||||
|
||||
private WaveBufferValidArray _isWaveBufferValid;
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of <see cref="WaveBuffer"/> consumed.
|
||||
/// </summary>
|
||||
public uint WaveBufferConsumed;
|
||||
|
||||
/// <summary>
|
||||
/// Pitch used for Sample Rate Conversion.
|
||||
/// </summary>
|
||||
public Array8<short> Pitch;
|
||||
|
||||
public float Fraction;
|
||||
|
||||
/// <summary>
|
||||
/// The ADPCM loop context when <see cref="SampleFormat.Adpcm"/> is in use.
|
||||
/// </summary>
|
||||
public AdpcmLoopContext LoopContext;
|
||||
|
||||
/// <summary>
|
||||
/// The last samples after a mix ramp.
|
||||
/// </summary>
|
||||
/// <remarks>This is used for depop (to perform voice drop).</remarks>
|
||||
public Array24<float> LastSamples;
|
||||
|
||||
/// <summary>
|
||||
/// The current count of loop performed.
|
||||
/// </summary>
|
||||
public int LoopCount;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 1 * RendererConstants.VoiceWaveBufferCount, Pack = 1)]
|
||||
private struct WaveBufferValidArray { }
|
||||
|
||||
/// <summary>
|
||||
/// Contains information of <see cref="WaveBuffer"/> validity.
|
||||
/// </summary>
|
||||
public Span<bool> IsWaveBufferValid => SpanHelpers.AsSpan<WaveBufferValidArray, bool>(ref _isWaveBufferValid);
|
||||
|
||||
/// <summary>
|
||||
/// Mark the current <see cref="WaveBuffer"/> as played and switch to the next one.
|
||||
/// </summary>
|
||||
/// <param name="waveBuffer">The current <see cref="WaveBuffer"/></param>
|
||||
/// <param name="waveBufferIndex">The wavebuffer index.</param>
|
||||
/// <param name="waveBufferConsumed">The amount of wavebuffers consumed.</param>
|
||||
/// <param name="playedSampleCount">The total count of sample played.</param>
|
||||
public void MarkEndOfBufferWaveBufferProcessing(ref WaveBuffer waveBuffer, ref int waveBufferIndex, ref uint waveBufferConsumed, ref ulong playedSampleCount)
|
||||
{
|
||||
IsWaveBufferValid[waveBufferIndex++] = false;
|
||||
LoopCount = 0;
|
||||
waveBufferConsumed++;
|
||||
|
||||
if (waveBufferIndex >= RendererConstants.VoiceWaveBufferCount)
|
||||
{
|
||||
waveBufferIndex = 0;
|
||||
}
|
||||
|
||||
if (waveBuffer.IsEndOfStream)
|
||||
{
|
||||
playedSampleCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
99
Ryujinx.Audio.Renderer/Common/WaveBuffer.cs
Normal file
99
Ryujinx.Audio.Renderer/Common/WaveBuffer.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using DspAddr = System.UInt64;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// A wavebuffer used for data source commands.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct WaveBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// The DSP address of the sample data of the wavebuffer.
|
||||
/// </summary>
|
||||
public DspAddr Buffer;
|
||||
|
||||
/// <summary>
|
||||
/// The DSP address of the context of the wavebuffer.
|
||||
/// </summary>
|
||||
/// <remarks>Only used by <see cref="SampleFormat.Adpcm"/>.</remarks>
|
||||
public DspAddr Context;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the sample buffer data.
|
||||
/// </summary>
|
||||
public uint BufferSize;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the context buffer.
|
||||
/// </summary>
|
||||
public uint ContextSize;
|
||||
|
||||
/// <summary>
|
||||
/// First sample to play on the wavebuffer.
|
||||
/// </summary>
|
||||
public uint StartSampleOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Last sample to play on the wavebuffer.
|
||||
/// </summary>
|
||||
public uint EndSampleOffset;
|
||||
|
||||
/// <summary>
|
||||
/// First sample to play when looping the wavebuffer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <see cref="LoopStartSampleOffset"/> or <see cref="LoopEndSampleOffset"/> is equal to zero,, it will default to <see cref="StartSampleOffset"/> and <see cref="EndSampleOffset"/>.
|
||||
/// </remarks>
|
||||
public uint LoopStartSampleOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Last sample to play when looping the wavebuffer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <see cref="LoopStartSampleOffset"/> or <see cref="LoopEndSampleOffset"/> is equal to zero, it will default to <see cref="StartSampleOffset"/> and <see cref="EndSampleOffset"/>.
|
||||
/// </remarks>
|
||||
public uint LoopEndSampleOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The max loop count.
|
||||
/// </summary>
|
||||
public int LoopCount;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the wavebuffer is looping.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool Looping;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the wavebuffer is the end of stream.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsEndOfStream;
|
||||
|
||||
/// <summary>
|
||||
/// Padding/Reserved.
|
||||
/// </summary>
|
||||
private ushort _padding;
|
||||
}
|
||||
}
|
78
Ryujinx.Audio.Renderer/Common/WorkBufferAllocator.cs
Normal file
78
Ryujinx.Audio.Renderer/Common/WorkBufferAllocator.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Common
|
||||
{
|
||||
public class WorkBufferAllocator
|
||||
{
|
||||
public Memory<byte> BackingMemory { get; }
|
||||
|
||||
public ulong Offset { get; private set; }
|
||||
|
||||
public WorkBufferAllocator(Memory<byte> backingMemory)
|
||||
{
|
||||
BackingMemory = backingMemory;
|
||||
}
|
||||
|
||||
public Memory<byte> Allocate(ulong size, int align)
|
||||
{
|
||||
Debug.Assert(align != 0);
|
||||
|
||||
if (size != 0)
|
||||
{
|
||||
ulong alignedOffset = BitUtils.AlignUp(Offset, align);
|
||||
|
||||
if (alignedOffset + size <= (ulong)BackingMemory.Length)
|
||||
{
|
||||
Memory<byte> result = BackingMemory.Slice((int)alignedOffset, (int)size);
|
||||
|
||||
Offset = alignedOffset + size;
|
||||
|
||||
// Clear the memory to be sure that is does not contain any garbage.
|
||||
result.Span.Fill(0);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return Memory<byte>.Empty;
|
||||
}
|
||||
|
||||
public Memory<T> Allocate<T>(ulong count, int align) where T: unmanaged
|
||||
{
|
||||
Memory<byte> allocatedMemory = Allocate((ulong)Unsafe.SizeOf<T>() * count, align);
|
||||
|
||||
if (allocatedMemory.IsEmpty)
|
||||
{
|
||||
return Memory<T>.Empty;
|
||||
}
|
||||
|
||||
return SpanMemoryManager<T>.Cast(allocatedMemory);
|
||||
}
|
||||
|
||||
public static ulong GetTargetSize<T>(ulong currentSize, ulong count, int align) where T: unmanaged
|
||||
{
|
||||
return BitUtils.AlignUp(currentSize, align) + (ulong)Unsafe.SizeOf<T>() * count;
|
||||
}
|
||||
}
|
||||
}
|
84
Ryujinx.Audio.Renderer/Device/VirtualDevice.cs
Normal file
84
Ryujinx.Audio.Renderer/Device/VirtualDevice.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Device
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a virtual device used by IAudioDevice.
|
||||
/// </summary>
|
||||
public class VirtualDevice
|
||||
{
|
||||
/// <summary>
|
||||
/// All the defined virtual devices.
|
||||
/// </summary>
|
||||
public static readonly VirtualDevice[] Devices = new VirtualDevice[4]
|
||||
{
|
||||
new VirtualDevice("AudioStereoJackOutput", 2),
|
||||
new VirtualDevice("AudioBuiltInSpeakerOutput", 2),
|
||||
new VirtualDevice("AudioTvOutput", 6),
|
||||
new VirtualDevice("AudioUsbDeviceOutput", 2),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The count of channels supported by the <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
public uint ChannelCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The system master volume of the <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
public float MasterVolume { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="VirtualDevice"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the <see cref="VirtualDevice"/>.</param>
|
||||
/// <param name="channelCount">The count of channels supported by the <see cref="VirtualDevice"/>.</param>
|
||||
private VirtualDevice(string name, uint channelCount)
|
||||
{
|
||||
Name = name;
|
||||
ChannelCount = channelCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the master volume of the <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
/// <param name="volume">The new master volume.</param>
|
||||
public void UpdateMasterVolume(float volume)
|
||||
{
|
||||
Debug.Assert(volume >= 0.0f && volume <= 1.0f);
|
||||
|
||||
MasterVolume = volume;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the <see cref="VirtualDevice"/> is a usb device.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the <see cref="VirtualDevice"/> is a usb device.</returns>
|
||||
public bool IsUsbDevice()
|
||||
{
|
||||
return Name.Equals("AudioUsbDeviceOutput");
|
||||
}
|
||||
}
|
||||
}
|
44
Ryujinx.Audio.Renderer/Device/VirtualDeviceSession.cs
Normal file
44
Ryujinx.Audio.Renderer/Device/VirtualDeviceSession.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Device
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a virtual device session used by IAudioDevice.
|
||||
/// </summary>
|
||||
public class VirtualDeviceSession
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="VirtualDevice"/> associated to this session.
|
||||
/// </summary>
|
||||
public VirtualDevice Device { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The user volume of this session.
|
||||
/// </summary>
|
||||
public float Volume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="VirtualDeviceSession"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="virtualDevice">The <see cref="VirtualDevice"/> associated to this session.</param>
|
||||
public VirtualDeviceSession(VirtualDevice virtualDevice)
|
||||
{
|
||||
Device = virtualDevice;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Device
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent an instance containing a registry of <see cref="VirtualDeviceSession"/>.
|
||||
/// </summary>
|
||||
public class VirtualDeviceSessionRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// The session registry, used to store the sessions of a given AppletResourceId.
|
||||
/// </summary>
|
||||
private Dictionary<ulong, VirtualDeviceSession[]> _sessionsRegistry = new Dictionary<ulong, VirtualDeviceSession[]>();
|
||||
|
||||
/// <summary>
|
||||
/// The default <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
/// <remarks>This is used when the USB device is the default one on older revision.</remarks>
|
||||
public VirtualDevice DefaultDevice => VirtualDevice.Devices[0];
|
||||
|
||||
/// <summary>
|
||||
/// The current active <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
// TODO: make this configurable
|
||||
public VirtualDevice ActiveDevice = VirtualDevice.Devices[1];
|
||||
|
||||
/// <summary>
|
||||
/// Get the associated <see cref="T:VirtualDeviceSession[]"/> from an AppletResourceId.
|
||||
/// </summary>
|
||||
/// <param name="resourceAppletId">The AppletResourceId used.</param>
|
||||
/// <returns>The associated <see cref="T:VirtualDeviceSession[]"/> from an AppletResourceId.</returns>
|
||||
public VirtualDeviceSession[] GetSessionByAppletResourceId(ulong resourceAppletId)
|
||||
{
|
||||
if (_sessionsRegistry.TryGetValue(resourceAppletId, out VirtualDeviceSession[] result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = CreateSessionsFromBehaviourContext();
|
||||
|
||||
_sessionsRegistry.Add(resourceAppletId, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new array of sessions for each <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
/// <returns>A new array of sessions for each <see cref="VirtualDevice"/>.</returns>
|
||||
private static VirtualDeviceSession[] CreateSessionsFromBehaviourContext()
|
||||
{
|
||||
VirtualDeviceSession[] virtualDeviceSession = new VirtualDeviceSession[VirtualDevice.Devices.Length];
|
||||
|
||||
for (int i = 0; i < virtualDeviceSession.Length; i++)
|
||||
{
|
||||
virtualDeviceSession[i] = new VirtualDeviceSession(VirtualDevice.Devices[i]);
|
||||
}
|
||||
|
||||
return virtualDeviceSession;
|
||||
}
|
||||
}
|
||||
}
|
219
Ryujinx.Audio.Renderer/Dsp/AdpcmHelper.cs
Normal file
219
Ryujinx.Audio.Renderer/Dsp/AdpcmHelper.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
{
|
||||
public static class AdpcmHelper
|
||||
{
|
||||
private const int FixedPointPrecision = 11;
|
||||
private const int SamplesPerFrame = 14;
|
||||
private const int NibblesPerFrame = SamplesPerFrame + 2;
|
||||
private const int BytesPerFrame = 8;
|
||||
private const int BitsPerFrame = BytesPerFrame * 8;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint GetAdpcmDataSize(int sampleCount)
|
||||
{
|
||||
Debug.Assert(sampleCount >= 0);
|
||||
|
||||
int frames = sampleCount / SamplesPerFrame;
|
||||
int extraSize = 0;
|
||||
|
||||
if ((sampleCount % SamplesPerFrame) != 0)
|
||||
{
|
||||
extraSize = (sampleCount % SamplesPerFrame) / 2 + 1 + (sampleCount % 2);
|
||||
}
|
||||
|
||||
return (uint)(BytesPerFrame * frames + extraSize);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetAdpcmOffsetFromSampleOffset(int sampleOffset)
|
||||
{
|
||||
Debug.Assert(sampleOffset >= 0);
|
||||
|
||||
return GetNibblesFromSampleCount(sampleOffset) / 2;
|
||||
}
|
||||
|
||||
public static int NibbleToSample(int nibble)
|
||||
{
|
||||
int frames = nibble / NibblesPerFrame;
|
||||
int extraNibbles = nibble % NibblesPerFrame;
|
||||
int samples = SamplesPerFrame * frames;
|
||||
|
||||
return samples + extraNibbles - 2;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetNibblesFromSampleCount(int sampleCount)
|
||||
{
|
||||
byte headerSize = 0;
|
||||
|
||||
if ((sampleCount % SamplesPerFrame) != 0)
|
||||
{
|
||||
headerSize = 2;
|
||||
}
|
||||
|
||||
return sampleCount % SamplesPerFrame + NibblesPerFrame * (sampleCount / SamplesPerFrame) + headerSize;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static short Saturate(int value)
|
||||
{
|
||||
if (value > short.MaxValue)
|
||||
value = short.MaxValue;
|
||||
|
||||
if (value < short.MinValue)
|
||||
value = short.MinValue;
|
||||
|
||||
return (short)value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Decode(Span<short> output, ReadOnlySpan<byte> input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan<short> coefficients, ref AdpcmLoopContext loopContext)
|
||||
{
|
||||
if (input.IsEmpty || endSampleOffset < startSampleOffset)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte predScale = (byte)loopContext.PredScale;
|
||||
byte scale = (byte)(predScale & 0xF);
|
||||
byte coefficientIndex = (byte)((predScale >> 4) & 0xF);
|
||||
short history0 = loopContext.History0;
|
||||
short history1 = loopContext.History1;
|
||||
short coefficient0 = coefficients[coefficientIndex * 2 + 0];
|
||||
short coefficient1 = coefficients[coefficientIndex * 2 + 1];
|
||||
|
||||
int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset);
|
||||
int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset);
|
||||
int remaining = decodedCount;
|
||||
int outputBufferIndex = 0;
|
||||
int inputIndex = 0;
|
||||
|
||||
ReadOnlySpan<byte> targetInput;
|
||||
|
||||
targetInput = input.Slice(nibbles / 2);
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
int samplesCount;
|
||||
|
||||
if (((uint)nibbles % NibblesPerFrame) == 0)
|
||||
{
|
||||
predScale = targetInput[inputIndex++];
|
||||
|
||||
scale = (byte)(predScale & 0xF);
|
||||
|
||||
coefficientIndex = (byte)((predScale >> 4) & 0xF);
|
||||
|
||||
coefficient0 = coefficients[coefficientIndex * 2 + 0];
|
||||
coefficient1 = coefficients[coefficientIndex * 2 + 1];
|
||||
|
||||
nibbles += 2;
|
||||
|
||||
samplesCount = Math.Min(remaining, SamplesPerFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
samplesCount = 1;
|
||||
}
|
||||
|
||||
int scaleFixedPoint = FixedPointHelper.ToFixed(1.0f, FixedPointPrecision) << scale;
|
||||
|
||||
if (samplesCount < SamplesPerFrame)
|
||||
{
|
||||
for (int i = 0; i < samplesCount; i++)
|
||||
{
|
||||
int value = targetInput[inputIndex];
|
||||
|
||||
int sample;
|
||||
|
||||
if ((nibbles & 1) != 0)
|
||||
{
|
||||
sample = (value << 28) >> 28;
|
||||
|
||||
inputIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = (value << 24) >> 28;
|
||||
}
|
||||
|
||||
nibbles++;
|
||||
|
||||
int prediction = coefficient0 * history0 + coefficient1 * history1;
|
||||
|
||||
sample = FixedPointHelper.RoundUpAndToInt(sample * scaleFixedPoint + prediction, FixedPointPrecision);
|
||||
|
||||
short saturatedSample = Saturate(sample);
|
||||
|
||||
history1 = history0;
|
||||
history0 = saturatedSample;
|
||||
|
||||
output[outputBufferIndex++] = saturatedSample;
|
||||
|
||||
remaining--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < SamplesPerFrame / 2; i++)
|
||||
{
|
||||
int value = targetInput[inputIndex];
|
||||
|
||||
int sample0;
|
||||
int sample1;
|
||||
|
||||
sample0 = (value << 24) >> 28;
|
||||
sample1 = (value << 28) >> 28;
|
||||
|
||||
inputIndex++;
|
||||
|
||||
int prediction0 = coefficient0 * history0 + coefficient1 * history1;
|
||||
sample0 = FixedPointHelper.RoundUpAndToInt(sample0 * scaleFixedPoint + prediction0, FixedPointPrecision);
|
||||
short saturatedSample0 = Saturate(sample0);
|
||||
|
||||
int prediction1 = coefficient0 * saturatedSample0 + coefficient1 * history0;
|
||||
sample1 = FixedPointHelper.RoundUpAndToInt(sample1 * scaleFixedPoint + prediction1, FixedPointPrecision);
|
||||
short saturatedSample1 = Saturate(sample1);
|
||||
|
||||
history1 = saturatedSample0;
|
||||
history0 = saturatedSample1;
|
||||
|
||||
output[outputBufferIndex++] = saturatedSample0;
|
||||
output[outputBufferIndex++] = saturatedSample1;
|
||||
}
|
||||
|
||||
nibbles += SamplesPerFrame;
|
||||
remaining -= SamplesPerFrame;
|
||||
}
|
||||
}
|
||||
|
||||
loopContext.PredScale = predScale;
|
||||
loopContext.History0 = history0;
|
||||
loopContext.History1 = history1;
|
||||
|
||||
return decodedCount;
|
||||
}
|
||||
}
|
||||
}
|
221
Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs
Normal file
221
Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
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<MailboxMessage> _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<MailboxMessage>();
|
||||
_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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using System;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class AdpcmDataSourceCommandVersion1 : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.AdpcmDataSourceVersion1;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort OutputBufferIndex { get; }
|
||||
public uint SampleRate { get; }
|
||||
|
||||
public float Pitch { get; }
|
||||
|
||||
public WaveBuffer[] WaveBuffers { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
|
||||
public ulong AdpcmParameter { get; }
|
||||
public ulong AdpcmParameterSize { get; }
|
||||
|
||||
public DecodingBehaviour DecodingBehaviour { get; }
|
||||
|
||||
public AdpcmDataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
OutputBufferIndex = outputBufferIndex;
|
||||
SampleRate = serverState.SampleRate;
|
||||
Pitch = serverState.Pitch;
|
||||
|
||||
WaveBuffers = new WaveBuffer[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<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation()
|
||||
{
|
||||
State = State,
|
||||
SourceSampleRate = SampleRate,
|
||||
SampleFormat = SampleFormat.Adpcm,
|
||||
Pitch = Pitch,
|
||||
DecodingBehaviour = DecodingBehaviour,
|
||||
WaveBuffers = WaveBuffers,
|
||||
ExtraParameter = AdpcmParameter,
|
||||
ExtraParameterSize = AdpcmParameterSize,
|
||||
ChannelIndex = 0,
|
||||
ChannelCount = 1,
|
||||
};
|
||||
|
||||
DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
188
Ryujinx.Audio.Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
Normal file
188
Ryujinx.Audio.Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
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<int> outBuffer, uint count, uint readOffset, uint updateCount)
|
||||
{
|
||||
if (countMax == 0 || bufferAddress == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint targetReadOffset = readOffset + AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo);
|
||||
|
||||
if (targetReadOffset > countMax)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint remaining = count;
|
||||
|
||||
uint outBufferOffset = 0;
|
||||
|
||||
while (remaining != 0)
|
||||
{
|
||||
uint countToWrite = Math.Min(countMax - targetReadOffset, remaining);
|
||||
|
||||
memoryManager.Read(bufferAddress + targetReadOffset * sizeof(int), MemoryMarshal.Cast<int, byte>(outBuffer.Slice((int)outBufferOffset, (int)countToWrite)));
|
||||
|
||||
targetReadOffset = (targetReadOffset + countToWrite) % countMax;
|
||||
remaining -= countToWrite;
|
||||
outBufferOffset += countToWrite;
|
||||
}
|
||||
|
||||
if (updateCount != 0)
|
||||
{
|
||||
uint newReadOffset = (AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo) + updateCount) % countMax;
|
||||
|
||||
AuxiliaryBufferInfo.SetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo, newReadOffset);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private uint Write(MemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan<int> buffer, uint count, uint writeOffset, uint updateCount)
|
||||
{
|
||||
if (countMax == 0 || outBufferAddress == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint targetWriteOffset = writeOffset + AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo);
|
||||
|
||||
if (targetWriteOffset > countMax)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint remaining = count;
|
||||
|
||||
uint inBufferOffset = 0;
|
||||
|
||||
while (remaining != 0)
|
||||
{
|
||||
uint countToWrite = Math.Min(countMax - targetWriteOffset, remaining);
|
||||
|
||||
memoryManager.Write(outBufferAddress + targetWriteOffset * sizeof(int), MemoryMarshal.Cast<int, byte>(buffer.Slice((int)inBufferOffset, (int)countToWrite)));
|
||||
|
||||
targetWriteOffset = (targetWriteOffset + countToWrite) % countMax;
|
||||
remaining -= countToWrite;
|
||||
inBufferOffset += countToWrite;
|
||||
}
|
||||
|
||||
if (updateCount != 0)
|
||||
{
|
||||
uint newWriteOffset = (AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo) + updateCount) % countMax;
|
||||
|
||||
AuxiliaryBufferInfo.SetWriteOffset(memoryManager, BufferInfo.SendBufferInfo, newWriteOffset);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
Span<float> inputBuffer = context.GetBuffer((int)InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer((int)OutputBufferIndex);
|
||||
|
||||
if (IsEffectEnabled)
|
||||
{
|
||||
Span<int> inputBufferInt = MemoryMarshal.Cast<float, int>(inputBuffer);
|
||||
Span<int> outputBufferInt = MemoryMarshal.Cast<float, int>(outputBuffer);
|
||||
|
||||
// Convert input data to the target format for user (int)
|
||||
DataSourceHelper.ToInt(inputBufferInt, inputBuffer, outputBuffer.Length);
|
||||
|
||||
// Send the input to the user
|
||||
Write(context.MemoryManager, OutputBuffer, CountMax, inputBufferInt, context.SampleCount, WriteOffset, UpdateCount);
|
||||
|
||||
// Convert back to float just in case it's reused
|
||||
DataSourceHelper.ToFloat(inputBuffer, inputBufferInt, inputBuffer.Length);
|
||||
|
||||
// Retrieve the input from user
|
||||
uint readResult = Read(context.MemoryManager, InputBuffer, CountMax, outputBufferInt, context.SampleCount, WriteOffset, UpdateCount);
|
||||
|
||||
// Convert the outputBuffer back to the target format of the renderer (float)
|
||||
DataSourceHelper.ToFloat(outputBuffer, outputBufferInt, outputBuffer.Length);
|
||||
|
||||
if (readResult != context.SampleCount)
|
||||
{
|
||||
outputBuffer.Slice((int)readResult, (int)context.SampleCount - (int)readResult).Fill(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryHelper.FillWithZeros(context.MemoryManager, (long)BufferInfo.SendBufferInfo, Unsafe.SizeOf<AuxiliaryBufferInfo>());
|
||||
MemoryHelper.FillWithZeros(context.MemoryManager, (long)BufferInfo.ReturnBufferInfo, Unsafe.SizeOf<AuxiliaryBufferInfo>());
|
||||
|
||||
if (InputBufferIndex != OutputBufferIndex)
|
||||
{
|
||||
inputBuffer.CopyTo(outputBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
89
Ryujinx.Audio.Renderer/Dsp/Command/BiquadFilterCommand.cs
Normal file
89
Ryujinx.Audio.Renderer/Dsp/Command/BiquadFilterCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class BiquadFilterCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.BiquadFilter;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public BiquadFilterParameter Parameter { get; }
|
||||
public Memory<BiquadFilterState> BiquadFilterState { get; }
|
||||
public int InputBufferIndex { get; }
|
||||
public int OutputBufferIndex { get; }
|
||||
public bool NeedInitialization { get; }
|
||||
|
||||
public BiquadFilterCommand(int baseIndex, ref BiquadFilterParameter filter, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId)
|
||||
{
|
||||
Parameter = filter;
|
||||
BiquadFilterState = biquadFilterStateMemory;
|
||||
InputBufferIndex = baseIndex + inputBufferOffset;
|
||||
OutputBufferIndex = baseIndex + outputBufferOffset;
|
||||
NeedInitialization = needInitialization;
|
||||
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
}
|
||||
|
||||
private void ProcessBiquadFilter(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
|
||||
{
|
||||
const int fixedPointPrecisionForParameter = 14;
|
||||
|
||||
float a0 = FixedPointHelper.ToFloat(Parameter.Numerator[0], fixedPointPrecisionForParameter);
|
||||
float a1 = FixedPointHelper.ToFloat(Parameter.Numerator[1], fixedPointPrecisionForParameter);
|
||||
float a2 = FixedPointHelper.ToFloat(Parameter.Numerator[2], fixedPointPrecisionForParameter);
|
||||
|
||||
float b1 = FixedPointHelper.ToFloat(Parameter.Denominator[0], fixedPointPrecisionForParameter);
|
||||
float b2 = FixedPointHelper.ToFloat(Parameter.Denominator[1], fixedPointPrecisionForParameter);
|
||||
|
||||
ref BiquadFilterState state = ref BiquadFilterState.Span[0];
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
float input = inputBuffer[i];
|
||||
float output = input * a0 + state.Z1;
|
||||
|
||||
state.Z1 = input * a1 + output * b1 + state.Z2;
|
||||
state.Z2 = input * a2 + output * b2;
|
||||
|
||||
outputBuffer[i] = output;
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
Span<float> outputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
|
||||
if (NeedInitialization)
|
||||
{
|
||||
BiquadFilterState.Span[0] = new BiquadFilterState();
|
||||
}
|
||||
|
||||
ProcessBiquadFilter(outputBuffer, outputBuffer, context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Parameter.Sink;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class CircularBufferSinkCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.CircularBufferSink;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort[] Input { get; }
|
||||
public uint InputCount { get; }
|
||||
|
||||
public ulong CircularBuffer { get; }
|
||||
public ulong CircularBufferSize { get; }
|
||||
public ulong CurrentOffset { get; }
|
||||
|
||||
public CircularBufferSinkCommand(uint bufferOffset, ref CircularBufferParameter parameter, ref AddressInfo circularBufferAddressInfo, uint currentOffset, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
Input = new ushort[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<float> inputBuffer = context.GetBuffer(Input[i]);
|
||||
|
||||
ulong targetOffset = CircularBuffer + currentOffset;
|
||||
|
||||
for (int y = 0; y < context.SampleCount; y++)
|
||||
{
|
||||
context.MemoryManager.Write(targetOffset + (ulong)y * targetChannelCount, PcmHelper.Saturate(inputBuffer[y]));
|
||||
}
|
||||
|
||||
currentOffset += context.SampleCount * targetChannelCount;
|
||||
|
||||
if (currentOffset >= CircularBufferSize)
|
||||
{
|
||||
currentOffset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
Ryujinx.Audio.Renderer/Dsp/Command/ClearMixBufferCommand.cs
Normal file
41
Ryujinx.Audio.Renderer/Dsp/Command/ClearMixBufferCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class ClearMixBufferCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.ClearMixBuffer;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ClearMixBufferCommand(int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
context.Buffers.Span.Fill(0);
|
||||
}
|
||||
}
|
||||
}
|
124
Ryujinx.Audio.Renderer/Dsp/Command/CommandList.cs
Normal file
124
Ryujinx.Audio.Renderer/Dsp/Command/CommandList.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
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<float> Buffers { get; }
|
||||
public uint BufferCount { get; }
|
||||
|
||||
public List<ICommand> 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<float> mixBuffer, uint sampleCount, uint sampleRate, uint mixBufferCount, uint voiceChannelCountMax)
|
||||
{
|
||||
SampleCount = sampleCount;
|
||||
SampleRate = sampleRate;
|
||||
BufferCount = mixBufferCount + voiceChannelCountMax;
|
||||
Buffers = mixBuffer;
|
||||
Commands = new List<ICommand>();
|
||||
MemoryManager = memoryManager;
|
||||
}
|
||||
|
||||
public void AddCommand(ICommand command)
|
||||
{
|
||||
Commands.Add(command);
|
||||
}
|
||||
|
||||
public void AddCommand<T>(T command) where T : unmanaged, ICommand
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Memory<float> GetBufferMemory(int index)
|
||||
{
|
||||
return Buffers.Slice(index * (int)SampleCount, (int)SampleCount);
|
||||
}
|
||||
|
||||
public Span<float> GetBuffer(int index)
|
||||
{
|
||||
return Buffers.Span.Slice(index * (int)SampleCount, (int)SampleCount);
|
||||
}
|
||||
|
||||
public ulong GetTimeElapsedSinceDspStartedProcessing()
|
||||
{
|
||||
return (ulong)PerformanceCounter.ElapsedNanoseconds - StartTime;
|
||||
}
|
||||
|
||||
public void Process(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;
|
||||
}
|
||||
}
|
||||
}
|
49
Ryujinx.Audio.Renderer/Dsp/Command/CommandType.cs
Normal file
49
Ryujinx.Audio.Renderer/Dsp/Command/CommandType.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public enum CommandType : byte
|
||||
{
|
||||
Invalid,
|
||||
PcmInt16DataSourceVersion1,
|
||||
PcmInt16DataSourceVersion2,
|
||||
PcmFloatDataSourceVersion1,
|
||||
PcmFloatDataSourceVersion2,
|
||||
AdpcmDataSourceVersion1,
|
||||
AdpcmDataSourceVersion2,
|
||||
Volume,
|
||||
VolumeRamp,
|
||||
BiquadFilter,
|
||||
Mix,
|
||||
MixRamp,
|
||||
MixRampGrouped,
|
||||
DepopPrepare,
|
||||
DepopForMixBuffers,
|
||||
Delay,
|
||||
Upsample,
|
||||
DownMixSurroundToStereo,
|
||||
AuxiliaryBuffer,
|
||||
DeviceSink,
|
||||
CircularBufferSink,
|
||||
Reverb,
|
||||
Reverb3d,
|
||||
Performance,
|
||||
ClearMixBuffer,
|
||||
CopyMixBuffer
|
||||
}
|
||||
}
|
52
Ryujinx.Audio.Renderer/Dsp/Command/CopyMixBufferCommand.cs
Normal file
52
Ryujinx.Audio.Renderer/Dsp/Command/CopyMixBufferCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class CopyMixBufferCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.CopyMixBuffer;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
public CopyMixBufferCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = (ushort)inputBufferIndex;
|
||||
OutputBufferIndex = (ushort)outputBufferIndex;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
inputBuffer.CopyTo(outputBuffer);
|
||||
}
|
||||
}
|
||||
}
|
126
Ryujinx.Audio.Renderer/Dsp/Command/DataSourceVersion2Command.cs
Normal file
126
Ryujinx.Audio.Renderer/Dsp/Command/DataSourceVersion2Command.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using System;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class DataSourceVersion2Command : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType { get; }
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort OutputBufferIndex { get; }
|
||||
public uint SampleRate { get; }
|
||||
|
||||
public float Pitch { get; }
|
||||
|
||||
public WaveBuffer[] WaveBuffers { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
|
||||
public ulong ExtraParameter { get; }
|
||||
public ulong ExtraParameterSize { get; }
|
||||
|
||||
public uint ChannelIndex { get; }
|
||||
|
||||
public uint ChannelCount { get; }
|
||||
|
||||
public DecodingBehaviour DecodingBehaviour { get; }
|
||||
|
||||
public SampleFormat SampleFormat { get; }
|
||||
|
||||
public SampleRateConversionQuality SrcQuality { get; }
|
||||
|
||||
public DataSourceVersion2Command(ref Server.Voice.VoiceState serverState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
ChannelIndex = channelIndex;
|
||||
ChannelCount = serverState.ChannelsCount;
|
||||
SampleFormat = serverState.SampleFormat;
|
||||
SrcQuality = serverState.SrcQuality;
|
||||
CommandType = GetCommandTypeBySampleFormat(SampleFormat);
|
||||
|
||||
OutputBufferIndex = 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<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation()
|
||||
{
|
||||
State = State,
|
||||
SourceSampleRate = SampleRate,
|
||||
SampleFormat = SampleFormat,
|
||||
Pitch = Pitch,
|
||||
DecodingBehaviour = DecodingBehaviour,
|
||||
WaveBuffers = WaveBuffers,
|
||||
ExtraParameter = ExtraParameter,
|
||||
ExtraParameterSize = ExtraParameterSize,
|
||||
ChannelIndex = (int)ChannelIndex,
|
||||
ChannelCount = (int)ChannelCount,
|
||||
SrcQuality = SrcQuality
|
||||
};
|
||||
|
||||
DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
272
Ryujinx.Audio.Renderer/Dsp/Command/DelayCommand.cs
Normal file
272
Ryujinx.Audio.Renderer/Dsp/Command/DelayCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class DelayCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Delay;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public DelayParameter Parameter => _parameter;
|
||||
public Memory<DelayState> State { get; }
|
||||
public ulong WorkBuffer { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
public bool IsEffectEnabled { get; }
|
||||
|
||||
private DelayParameter _parameter;
|
||||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, ulong workBuffer, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
_parameter = parameter;
|
||||
State = state;
|
||||
WorkBuffer = workBuffer;
|
||||
|
||||
IsEffectEnabled = isEnabled;
|
||||
|
||||
InputBufferIndices = new ushort[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<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
|
||||
{
|
||||
ref DelayState state = ref State.Span[0];
|
||||
|
||||
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
float input = inputBuffer[i] * 64;
|
||||
float delayLineValue = state.DelayLines[0].Read();
|
||||
|
||||
float lowPassResult = input * inGain + delayLineValue * feedbackGain * state.LowPassBaseGain + state.LowPassZ[0] * state.LowPassFeedbackGain;
|
||||
|
||||
state.LowPassZ[0] = lowPassResult;
|
||||
|
||||
state.DelayLines[0].Update(lowPassResult);
|
||||
|
||||
outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDelayStereo(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ref DelayState state = ref State.Span[0];
|
||||
|
||||
float[] channelInput = new float[Parameter.ChannelCount];
|
||||
float[] delayLineValues = new float[Parameter.ChannelCount];
|
||||
float[] temp = new float[Parameter.ChannelCount];
|
||||
|
||||
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
|
||||
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
for (int j = 0; j < Parameter.ChannelCount; j++)
|
||||
{
|
||||
channelInput[j] = inputBuffers[j].Span[i] * 64;
|
||||
delayLineValues[j] = state.DelayLines[j].Read();
|
||||
}
|
||||
|
||||
temp[0] = channelInput[0] * inGain + delayLineValues[1] * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
|
||||
temp[1] = channelInput[1] * inGain + delayLineValues[0] * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
|
||||
|
||||
for (int j = 0; j < Parameter.ChannelCount; j++)
|
||||
{
|
||||
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
|
||||
|
||||
state.LowPassZ[j] = lowPassResult;
|
||||
state.DelayLines[j].Update(lowPassResult);
|
||||
|
||||
outputBuffers[j].Span[i] = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDelayQuadraphonic(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ref DelayState state = ref State.Span[0];
|
||||
|
||||
float[] channelInput = new float[Parameter.ChannelCount];
|
||||
float[] delayLineValues = new float[Parameter.ChannelCount];
|
||||
float[] temp = new float[Parameter.ChannelCount];
|
||||
|
||||
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
|
||||
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
for (int j = 0; j < Parameter.ChannelCount; j++)
|
||||
{
|
||||
channelInput[j] = inputBuffers[j].Span[i] * 64;
|
||||
delayLineValues[j] = state.DelayLines[j].Read();
|
||||
}
|
||||
|
||||
temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
|
||||
temp[1] = channelInput[1] * inGain + (delayLineValues[0] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
|
||||
temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain;
|
||||
temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain;
|
||||
|
||||
for (int j = 0; j < Parameter.ChannelCount; j++)
|
||||
{
|
||||
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
|
||||
|
||||
state.LowPassZ[j] = lowPassResult;
|
||||
state.DelayLines[j].Update(lowPassResult);
|
||||
|
||||
outputBuffers[j].Span[i] = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDelaySurround(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ref DelayState state = ref State.Span[0];
|
||||
|
||||
float[] channelInput = new float[Parameter.ChannelCount];
|
||||
float[] delayLineValues = new float[Parameter.ChannelCount];
|
||||
float[] temp = new float[Parameter.ChannelCount];
|
||||
|
||||
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
|
||||
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
for (int j = 0; j < Parameter.ChannelCount; j++)
|
||||
{
|
||||
channelInput[j] = inputBuffers[j].Span[i] * 64;
|
||||
delayLineValues[j] = state.DelayLines[j].Read();
|
||||
}
|
||||
|
||||
temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[4]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
|
||||
temp[1] = channelInput[1] * inGain + (delayLineValues[4] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
|
||||
temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain;
|
||||
temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain;
|
||||
temp[4] = channelInput[4] * inGain + (delayLineValues[0] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[4] * delayFeedbackBaseGain;
|
||||
temp[5] = channelInput[5] * inGain + delayLineValues[5] * delayFeedbackBaseGain;
|
||||
|
||||
for (int j = 0; j < Parameter.ChannelCount; j++)
|
||||
{
|
||||
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
|
||||
|
||||
state.LowPassZ[j] = lowPassResult;
|
||||
state.DelayLines[j].Update(lowPassResult);
|
||||
|
||||
outputBuffers[j].Span[i] = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDelay(CommandList context)
|
||||
{
|
||||
Debug.Assert(Parameter.IsChannelCountValid());
|
||||
|
||||
if (IsEffectEnabled && Parameter.IsChannelCountValid())
|
||||
{
|
||||
ReadOnlyMemory<float>[] inputBuffers = new ReadOnlyMemory<float>[Parameter.ChannelCount];
|
||||
Memory<float>[] outputBuffers = new Memory<float>[Parameter.ChannelCount];
|
||||
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]);
|
||||
outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]);
|
||||
}
|
||||
|
||||
switch (Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
ProcessDelayMono(outputBuffers[0].Span, inputBuffers[0].Span, context.SampleCount);
|
||||
break;
|
||||
case 2:
|
||||
ProcessDelayStereo(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 4:
|
||||
ProcessDelayQuadraphonic(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 6:
|
||||
ProcessDelaySurround(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"{Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||
{
|
||||
context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ref DelayState state = ref State.Span[0];
|
||||
|
||||
if (IsEffectEnabled)
|
||||
{
|
||||
if (Parameter.Status == UsageState.Invalid)
|
||||
{
|
||||
state = new DelayState(ref _parameter, WorkBuffer);
|
||||
}
|
||||
else if (Parameter.Status == UsageState.New)
|
||||
{
|
||||
state.UpdateParameter(ref _parameter);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessDelay(context);
|
||||
}
|
||||
}
|
||||
}
|
103
Ryujinx.Audio.Renderer/Dsp/Command/DepopForMixBuffersCommand.cs
Normal file
103
Ryujinx.Audio.Renderer/Dsp/Command/DepopForMixBuffersCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class DepopForMixBuffersCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.DepopForMixBuffers;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint MixBufferOffset { get; }
|
||||
|
||||
public uint MixBufferCount { get; }
|
||||
|
||||
public float Decay { get; }
|
||||
|
||||
public Memory<float> DepopBuffer { get; }
|
||||
|
||||
private const int FixedPointPrecisionForDecay = 15;
|
||||
|
||||
public DepopForMixBuffersCommand(Memory<float> depopBuffer, uint bufferOffset, uint mixBufferCount, int nodeId, uint sampleRate)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
MixBufferOffset = bufferOffset;
|
||||
MixBufferCount = mixBufferCount;
|
||||
DepopBuffer = depopBuffer;
|
||||
|
||||
if (sampleRate == 48000)
|
||||
{
|
||||
Decay = 0.962189f;
|
||||
}
|
||||
else // if (sampleRate == 32000)
|
||||
{
|
||||
Decay = 0.943695f;
|
||||
}
|
||||
}
|
||||
|
||||
private float ProcessDepopMix(Span<float> buffer, float depopValue, uint sampleCount)
|
||||
{
|
||||
if (depopValue <= 0)
|
||||
{
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue);
|
||||
|
||||
buffer[i] -= depopValue;
|
||||
}
|
||||
|
||||
return -depopValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue);
|
||||
|
||||
buffer[i] += depopValue;
|
||||
}
|
||||
|
||||
return depopValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
uint bufferCount = Math.Min(MixBufferOffset + MixBufferCount, context.BufferCount);
|
||||
|
||||
for (int i = (int)MixBufferOffset; i < bufferCount; i++)
|
||||
{
|
||||
float depopValue = DepopBuffer.Span[i];
|
||||
if (depopValue != 0)
|
||||
{
|
||||
Span<float> buffer = context.GetBuffer(i);
|
||||
|
||||
DepopBuffer.Span[i] = ProcessDepopMix(buffer, depopValue, context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
Ryujinx.Audio.Renderer/Dsp/Command/DepopPrepareCommand.cs
Normal file
72
Ryujinx.Audio.Renderer/Dsp/Command/DepopPrepareCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class DepopPrepareCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.DepopPrepare;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint MixBufferCount { get; }
|
||||
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
public Memory<float> DepopBuffer { get; }
|
||||
|
||||
public DepopPrepareCommand(Memory<VoiceUpdateState> state, Memory<float> depopBuffer, uint mixBufferCount, uint bufferOffset, int nodeId, bool enabled)
|
||||
{
|
||||
Enabled = enabled;
|
||||
NodeId = nodeId;
|
||||
MixBufferCount = mixBufferCount;
|
||||
|
||||
OutputBufferIndices = new ushort[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
108
Ryujinx.Audio.Renderer/Dsp/Command/DeviceSinkCommand.cs
Normal file
108
Ryujinx.Audio.Renderer/Dsp/Command/DeviceSinkCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
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<float> Buffers { get; }
|
||||
|
||||
public DeviceSinkCommand(uint bufferOffset, DeviceSink sink, int sessionId, Memory<float> buffers, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
DeviceName = Encoding.ASCII.GetString(sink.Parameter.DeviceName).TrimEnd('\0');
|
||||
SessionId = sessionId;
|
||||
InputCount = sink.Parameter.InputCount;
|
||||
InputBufferIndices = new ushort[InputCount];
|
||||
|
||||
for (int i = 0; i < InputCount; i++)
|
||||
{
|
||||
InputBufferIndices[i] = (ushort)(bufferOffset + sink.Parameter.Input[i]);
|
||||
}
|
||||
|
||||
if (sink.UpsamplerState != null)
|
||||
{
|
||||
Buffers = sink.UpsamplerState.OutputBuffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffers = buffers;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Span<float> GetBuffer(int index, int sampleCount)
|
||||
{
|
||||
return Buffers.Span.Slice(index * sampleCount, sampleCount);
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
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<float> inputBuffer = GetBuffer(InputBufferIndices[i], sampleCount);
|
||||
|
||||
for (int j = 0; j < sampleCount; j++)
|
||||
{
|
||||
outputBuffer[i + j * channelCount] = PcmHelper.Saturate(inputBuffer[j]);
|
||||
}
|
||||
}
|
||||
|
||||
device.AppendBuffer(outputBuffer, InputCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: support resampling for device only supporting something different
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class DownMixSurroundToStereoCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.DownMixSurroundToStereo;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
|
||||
public float[] Coefficients { get; }
|
||||
|
||||
public DownMixSurroundToStereoCommand(uint bufferOffset, Span<byte> inputBufferOffset, Span<byte> outputBufferOffset, ReadOnlySpan<float> downMixParameter, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndices = new ushort[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<float> coefficients, float back, float lfe, float center, float front)
|
||||
{
|
||||
return FloatingPointHelper.RoundUp(coefficients[3] * back + coefficients[2] * lfe + coefficients[1] * center + coefficients[0] * front);
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> frontLeft = context.GetBuffer(InputBufferIndices[0]);
|
||||
ReadOnlySpan<float> frontRight = context.GetBuffer(InputBufferIndices[1]);
|
||||
ReadOnlySpan<float> lowFrequency = context.GetBuffer(InputBufferIndices[2]);
|
||||
ReadOnlySpan<float> frontCenter = context.GetBuffer(InputBufferIndices[3]);
|
||||
ReadOnlySpan<float> backLeft = context.GetBuffer(InputBufferIndices[4]);
|
||||
ReadOnlySpan<float> backRight = context.GetBuffer(InputBufferIndices[5]);
|
||||
|
||||
Span<float> stereoLeft = context.GetBuffer(OutputBufferIndices[0]);
|
||||
Span<float> stereoRight = context.GetBuffer(OutputBufferIndices[1]);
|
||||
Span<float> unused2 = context.GetBuffer(OutputBufferIndices[2]);
|
||||
Span<float> unused3 = context.GetBuffer(OutputBufferIndices[3]);
|
||||
Span<float> unused4 = context.GetBuffer(OutputBufferIndices[4]);
|
||||
Span<float> unused5 = context.GetBuffer(OutputBufferIndices[5]);
|
||||
|
||||
for (int i = 0; i < context.SampleCount; i++)
|
||||
{
|
||||
stereoLeft[i] = DownMixSurroundToStereo(Coefficients, backLeft[i], lowFrequency[i], frontCenter[i], frontLeft[i]);
|
||||
stereoRight[i] = DownMixSurroundToStereo(Coefficients, backRight[i], lowFrequency[i], frontCenter[i], frontRight[i]);
|
||||
}
|
||||
|
||||
unused2.Fill(0);
|
||||
unused3.Fill(0);
|
||||
unused4.Fill(0);
|
||||
unused5.Fill(0);
|
||||
}
|
||||
}
|
||||
}
|
37
Ryujinx.Audio.Renderer/Dsp/Command/ICommand.cs
Normal file
37
Ryujinx.Audio.Renderer/Dsp/Command/ICommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public interface ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType { get; }
|
||||
|
||||
public ulong EstimatedProcessingTime { get; }
|
||||
|
||||
public void Process(CommandList context);
|
||||
|
||||
public bool ShouldMeter()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
125
Ryujinx.Audio.Renderer/Dsp/Command/MixCommand.cs
Normal file
125
Ryujinx.Audio.Renderer/Dsp/Command/MixCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class MixCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Mix;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
public float Volume { get; }
|
||||
|
||||
public MixCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = (ushort)inputBufferIndex;
|
||||
OutputBufferIndex = (ushort)outputBufferIndex;
|
||||
|
||||
Volume = volume;
|
||||
}
|
||||
|
||||
private void ProcessMixAvx(Span<float> outputMix, ReadOnlySpan<float> inputMix)
|
||||
{
|
||||
Vector256<float> volumeVec = Vector256.Create(Volume);
|
||||
|
||||
ReadOnlySpan<Vector256<float>> inputVec = MemoryMarshal.Cast<float, Vector256<float>>(inputMix);
|
||||
Span<Vector256<float>> outputVec = MemoryMarshal.Cast<float, Vector256<float>>(outputMix);
|
||||
|
||||
int sisdStart = inputVec.Length * 8;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Avx.Add(outputVec[i], Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec)));
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < inputMix.Length; i++)
|
||||
{
|
||||
outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessMixSse41(Span<float> outputMix, ReadOnlySpan<float> inputMix)
|
||||
{
|
||||
Vector128<float> volumeVec = Vector128.Create(Volume);
|
||||
|
||||
ReadOnlySpan<Vector128<float>> inputVec = MemoryMarshal.Cast<float, Vector128<float>>(inputMix);
|
||||
Span<Vector128<float>> outputVec = MemoryMarshal.Cast<float, Vector128<float>>(outputMix);
|
||||
|
||||
int sisdStart = inputVec.Length * 4;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Sse.Add(outputVec[i], Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec)));
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < inputMix.Length; i++)
|
||||
{
|
||||
outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ProcessMixSlowPath(Span<float> outputMix, ReadOnlySpan<float> inputMix)
|
||||
{
|
||||
for (int i = 0; i < inputMix.Length; i++)
|
||||
{
|
||||
outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessMix(Span<float> outputMix, ReadOnlySpan<float> inputMix)
|
||||
{
|
||||
if (Avx.IsSupported)
|
||||
{
|
||||
ProcessMixAvx(outputMix, inputMix);
|
||||
}
|
||||
else if (Sse41.IsSupported)
|
||||
{
|
||||
ProcessMixSse41(outputMix, inputMix);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessMixSlowPath(outputMix, inputMix);
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
ProcessMix(outputBuffer, inputBuffer);
|
||||
}
|
||||
}
|
||||
}
|
83
Ryujinx.Audio.Renderer/Dsp/Command/MixRampCommand.cs
Normal file
83
Ryujinx.Audio.Renderer/Dsp/Command/MixRampCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class MixRampCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.MixRamp;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
public float Volume0 { get; }
|
||||
public float Volume1 { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
|
||||
public int LastSampleIndex { get; }
|
||||
|
||||
public MixRampCommand(float volume0, float volume1, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory<VoiceUpdateState> state, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = (ushort)inputBufferIndex;
|
||||
OutputBufferIndex = (ushort)outputBufferIndex;
|
||||
|
||||
Volume0 = volume0;
|
||||
Volume1 = volume1;
|
||||
|
||||
State = state;
|
||||
LastSampleIndex = lastSampleIndex;
|
||||
}
|
||||
|
||||
private float ProcessMixRamp(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, int sampleCount)
|
||||
{
|
||||
float ramp = (Volume1 - Volume0) / sampleCount;
|
||||
float volume = Volume0;
|
||||
float state = 0;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume);
|
||||
|
||||
outputBuffer[i] += state;
|
||||
volume += ramp;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
State.Span[0].LastSamples[LastSampleIndex] = ProcessMixRamp(outputBuffer, inputBuffer, (int)context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
106
Ryujinx.Audio.Renderer/Dsp/Command/MixRampGroupedCommand.cs
Normal file
106
Ryujinx.Audio.Renderer/Dsp/Command/MixRampGroupedCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class MixRampGroupedCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.MixRampGrouped;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint MixBufferCount { get; }
|
||||
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
|
||||
public float[] Volume0 { get; }
|
||||
public float[] Volume1 { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
|
||||
public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> volume0, Span<float> volume1, Memory<VoiceUpdateState> state, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
MixBufferCount = mixBufferCount;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndices = new ushort[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<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float volume0, float volume1, int sampleCount)
|
||||
{
|
||||
float ramp = (volume1 - volume0) / sampleCount;
|
||||
float volume = volume0;
|
||||
float state = 0;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume);
|
||||
|
||||
outputBuffer[i] += state;
|
||||
volume += ramp;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
for (int i = 0; i < MixBufferCount; i++)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndices[i]);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndices[i]);
|
||||
|
||||
float volume0 = Volume0[i];
|
||||
float volume1 = Volume1[i];
|
||||
|
||||
ref VoiceUpdateState state = ref State.Span[0];
|
||||
|
||||
if (volume0 != 0 || volume1 != 0)
|
||||
{
|
||||
state.LastSamples[i] = ProcessMixRampGrouped(outputBuffer, inputBuffer, volume0, volume1, (int)context.SampleCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.LastSamples[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using System;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class PcmFloatDataSourceCommandVersion1 : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort OutputBufferIndex { get; }
|
||||
public uint SampleRate { get; }
|
||||
public uint ChannelIndex { get; }
|
||||
|
||||
public uint ChannelCount { get; }
|
||||
|
||||
public float Pitch { get; }
|
||||
|
||||
public WaveBuffer[] WaveBuffers { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
public DecodingBehaviour DecodingBehaviour { get; }
|
||||
|
||||
public PcmFloatDataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex);
|
||||
SampleRate = serverState.SampleRate;
|
||||
ChannelIndex = channelIndex;
|
||||
ChannelCount = serverState.ChannelsCount;
|
||||
Pitch = serverState.Pitch;
|
||||
|
||||
WaveBuffers = new WaveBuffer[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<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation()
|
||||
{
|
||||
State = State,
|
||||
SourceSampleRate = SampleRate,
|
||||
SampleFormat = SampleFormat.PcmInt16,
|
||||
Pitch = Pitch,
|
||||
DecodingBehaviour = DecodingBehaviour,
|
||||
WaveBuffers = WaveBuffers,
|
||||
ExtraParameter = 0,
|
||||
ExtraParameterSize = 0,
|
||||
ChannelIndex = (int)ChannelIndex,
|
||||
ChannelCount = (int)ChannelCount,
|
||||
};
|
||||
|
||||
DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using System;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class PcmInt16DataSourceCommandVersion1 : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort OutputBufferIndex { get; }
|
||||
public uint SampleRate { get; }
|
||||
public uint ChannelIndex { get; }
|
||||
|
||||
public uint ChannelCount { get; }
|
||||
|
||||
public float Pitch { get; }
|
||||
|
||||
public WaveBuffer[] WaveBuffers { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
public DecodingBehaviour DecodingBehaviour { get; }
|
||||
|
||||
public PcmInt16DataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex);
|
||||
SampleRate = serverState.SampleRate;
|
||||
ChannelIndex = channelIndex;
|
||||
ChannelCount = serverState.ChannelsCount;
|
||||
Pitch = serverState.Pitch;
|
||||
|
||||
WaveBuffers = new WaveBuffer[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<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation()
|
||||
{
|
||||
State = State,
|
||||
SourceSampleRate = SampleRate,
|
||||
SampleFormat = SampleFormat.PcmInt16,
|
||||
Pitch = Pitch,
|
||||
DecodingBehaviour = DecodingBehaviour,
|
||||
WaveBuffers = WaveBuffers,
|
||||
ExtraParameter = 0,
|
||||
ExtraParameterSize = 0,
|
||||
ChannelIndex = (int)ChannelIndex,
|
||||
ChannelCount = (int)ChannelCount,
|
||||
};
|
||||
|
||||
DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
64
Ryujinx.Audio.Renderer/Dsp/Command/PerformanceCommand.cs
Normal file
64
Ryujinx.Audio.Renderer/Dsp/Command/PerformanceCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Server.Performance;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class PerformanceCommand : ICommand
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
Invalid,
|
||||
Start,
|
||||
End
|
||||
}
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Performance;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public PerformanceEntryAddresses PerformanceEntryAddresses { get; }
|
||||
|
||||
public Type PerformanceType { get; set; }
|
||||
|
||||
public PerformanceCommand(ref PerformanceEntryAddresses performanceEntryAddresses, Type performanceType, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
PerformanceEntryAddresses = performanceEntryAddresses;
|
||||
PerformanceType = performanceType;
|
||||
NodeId = nodeId;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
if (PerformanceType == Type.Start)
|
||||
{
|
||||
PerformanceEntryAddresses.SetStartTime(context.GetTimeElapsedSinceDspStartedProcessing());
|
||||
}
|
||||
else if (PerformanceType == Type.End)
|
||||
{
|
||||
PerformanceEntryAddresses.SetProcessingTime(context.GetTimeElapsedSinceDspStartedProcessing());
|
||||
PerformanceEntryAddresses.IncrementEntryCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
269
Ryujinx.Audio.Renderer/Dsp/Command/Reverb3dCommand.cs
Normal file
269
Ryujinx.Audio.Renderer/Dsp/Command/Reverb3dCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class Reverb3dCommand : ICommand
|
||||
{
|
||||
private static readonly int[] OutputEarlyIndicesTableMono = new int[20] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableMono = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableMono = new int[1] { 0 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableStereo = new int[20] { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableStereo = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableStereo = new int[2] { 0, 1 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableQuadraphonic = new int[20] { 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableQuadraphonic = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableSurround = new int[40] { 4, 5, 0, 5, 0, 5, 1, 5, 1, 5, 1, 5, 1, 5, 2, 5, 2, 5, 2, 5, 1, 5, 1, 5, 1, 5, 0, 5, 0, 5, 0, 5, 0, 5, 3, 5, 3, 5, 3, 5 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableSurround = new int[40] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[6] { 0, 1, 2, 3, -1, 3 };
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Reverb3d;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
public Reverb3dParameter Parameter => _parameter;
|
||||
public Memory<Reverb3dState> State { get; }
|
||||
public ulong WorkBuffer { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
|
||||
public bool IsEffectEnabled { get; }
|
||||
|
||||
private Reverb3dParameter _parameter;
|
||||
|
||||
public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, ulong workBuffer, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
IsEffectEnabled = isEnabled;
|
||||
NodeId = nodeId;
|
||||
_parameter = parameter;
|
||||
State = state;
|
||||
WorkBuffer = workBuffer;
|
||||
|
||||
InputBufferIndices = new ushort[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<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
const int delayLineSampleIndexOffset = -1;
|
||||
|
||||
ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableMono, TargetEarlyDelayLineIndicesTableMono, TargetOutputFeedbackIndicesTableMono, delayLineSampleIndexOffset);
|
||||
}
|
||||
|
||||
private void ProcessReverb3dStereo(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
const int delayLineSampleIndexOffset = 1;
|
||||
|
||||
ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableStereo, TargetEarlyDelayLineIndicesTableStereo, TargetOutputFeedbackIndicesTableStereo, delayLineSampleIndexOffset);
|
||||
}
|
||||
|
||||
private void ProcessReverb3dQuadraphonic(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
const int delayLineSampleIndexOffset = 1;
|
||||
|
||||
ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableQuadraphonic, TargetEarlyDelayLineIndicesTableQuadraphonic, TargetOutputFeedbackIndicesTableQuadraphonic, delayLineSampleIndexOffset);
|
||||
}
|
||||
|
||||
private void ProcessReverb3dSurround(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
const int delayLineSampleIndexOffset = 1;
|
||||
|
||||
ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableSurround, TargetEarlyDelayLineIndicesTableSurround, TargetOutputFeedbackIndicesTableSurround, delayLineSampleIndexOffset);
|
||||
}
|
||||
|
||||
private void ProcessReverb3dGeneric(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount, ReadOnlySpan<int> outputEarlyIndicesTable, ReadOnlySpan<int> targetEarlyDelayLineIndicesTable, ReadOnlySpan<int> 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<float>[] inputBuffers = new ReadOnlyMemory<float>[Parameter.ChannelCount];
|
||||
Memory<float>[] outputBuffers = new Memory<float>[Parameter.ChannelCount];
|
||||
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]);
|
||||
outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]);
|
||||
}
|
||||
|
||||
switch (Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
ProcessReverb3dMono(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 2:
|
||||
ProcessReverb3dStereo(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 4:
|
||||
ProcessReverb3dQuadraphonic(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 6:
|
||||
ProcessReverb3dSurround(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"{Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||
{
|
||||
context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ref Reverb3dState state = ref State.Span[0];
|
||||
|
||||
if (IsEffectEnabled)
|
||||
{
|
||||
if (Parameter.ParameterStatus == UsageState.Invalid)
|
||||
{
|
||||
state = new Reverb3dState(ref _parameter, WorkBuffer);
|
||||
}
|
||||
else if (Parameter.ParameterStatus == UsageState.New)
|
||||
{
|
||||
state.UpdateParameter(ref _parameter);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessReverb3d(context);
|
||||
}
|
||||
}
|
||||
}
|
284
Ryujinx.Audio.Renderer/Dsp/Command/ReverbCommand.cs
Normal file
284
Ryujinx.Audio.Renderer/Dsp/Command/ReverbCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class ReverbCommand : ICommand
|
||||
{
|
||||
private static readonly int[] OutputEarlyIndicesTableMono = new int[10] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableMono = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
private static readonly int[] OutputIndicesTableMono = new int[4] { 0, 0, 0, 0 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableMono = new int[4] { 0, 1, 2, 3 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableStereo = new int[10] { 0, 0, 1, 1, 0, 1, 0, 0, 1, 1 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableStereo = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
private static readonly int[] OutputIndicesTableStereo = new int[4] { 0, 0, 1, 1 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableStereo = new int[4] { 2, 0, 3, 1 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableQuadraphonic = new int[10] { 0, 0, 1, 1, 0, 1, 2, 2, 3, 3 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableQuadraphonic = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
private static readonly int[] OutputIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableSurround = new int[20] { 0, 5, 0, 5, 1, 5, 1, 5, 4, 5, 4, 5, 2, 5, 2, 5, 3, 5, 3, 5 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableSurround = new int[20] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9 };
|
||||
private static readonly int[] OutputIndicesTableSurround = new int[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<ReverbState> State { get; }
|
||||
public ulong WorkBuffer { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
public bool IsLongSizePreDelaySupported { get; }
|
||||
|
||||
public bool IsEffectEnabled { get; }
|
||||
|
||||
private ReverbParameter _parameter;
|
||||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported)
|
||||
{
|
||||
Enabled = true;
|
||||
IsEffectEnabled = isEnabled;
|
||||
NodeId = nodeId;
|
||||
_parameter = parameter;
|
||||
State = state;
|
||||
WorkBuffer = workBuffer;
|
||||
|
||||
InputBufferIndices = new ushort[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<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverbGeneric(outputBuffers,
|
||||
inputBuffers,
|
||||
sampleCount,
|
||||
OutputEarlyIndicesTableMono,
|
||||
TargetEarlyDelayLineIndicesTableMono,
|
||||
TargetOutputFeedbackIndicesTableMono,
|
||||
OutputIndicesTableMono);
|
||||
}
|
||||
|
||||
private void ProcessReverbStereo(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverbGeneric(outputBuffers,
|
||||
inputBuffers,
|
||||
sampleCount,
|
||||
OutputEarlyIndicesTableStereo,
|
||||
TargetEarlyDelayLineIndicesTableStereo,
|
||||
TargetOutputFeedbackIndicesTableStereo,
|
||||
OutputIndicesTableStereo);
|
||||
}
|
||||
|
||||
private void ProcessReverbQuadraphonic(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverbGeneric(outputBuffers,
|
||||
inputBuffers,
|
||||
sampleCount,
|
||||
OutputEarlyIndicesTableQuadraphonic,
|
||||
TargetEarlyDelayLineIndicesTableQuadraphonic,
|
||||
TargetOutputFeedbackIndicesTableQuadraphonic,
|
||||
OutputIndicesTableQuadraphonic);
|
||||
}
|
||||
|
||||
private void ProcessReverbSurround(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverbGeneric(outputBuffers,
|
||||
inputBuffers,
|
||||
sampleCount,
|
||||
OutputEarlyIndicesTableSurround,
|
||||
TargetEarlyDelayLineIndicesTableSurround,
|
||||
TargetOutputFeedbackIndicesTableSurround,
|
||||
OutputIndicesTableSurround);
|
||||
}
|
||||
|
||||
private void ProcessReverbGeneric(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount, ReadOnlySpan<int> outputEarlyIndicesTable, ReadOnlySpan<int> targetEarlyDelayLineIndicesTable, ReadOnlySpan<int> targetOutputFeedbackIndicesTable, ReadOnlySpan<int> outputIndicesTable)
|
||||
{
|
||||
ref ReverbState state = ref State.Span[0];
|
||||
|
||||
bool isSurround = Parameter.ChannelCount == 6;
|
||||
|
||||
float reverbGain = FixedPointHelper.ToFloat(Parameter.ReverbGain, FixedPointPrecision);
|
||||
float lateGain = FixedPointHelper.ToFloat(Parameter.LateGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
|
||||
float[] outputValues = new float[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<float>[] inputBuffers = new ReadOnlyMemory<float>[Parameter.ChannelCount];
|
||||
Memory<float>[] outputBuffers = new Memory<float>[Parameter.ChannelCount];
|
||||
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]);
|
||||
outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]);
|
||||
}
|
||||
|
||||
switch (Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
ProcessReverbMono(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 2:
|
||||
ProcessReverbStereo(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 4:
|
||||
ProcessReverbQuadraphonic(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 6:
|
||||
ProcessReverbSurround(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"{Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||
{
|
||||
context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ref ReverbState state = ref State.Span[0];
|
||||
|
||||
if (IsEffectEnabled)
|
||||
{
|
||||
if (Parameter.Status == Server.Effect.UsageState.Invalid)
|
||||
{
|
||||
state = new ReverbState(ref _parameter, WorkBuffer, IsLongSizePreDelaySupported);
|
||||
}
|
||||
else if (Parameter.Status == Server.Effect.UsageState.New)
|
||||
{
|
||||
state.UpdateParameter(ref _parameter);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessReverb(context);
|
||||
}
|
||||
}
|
||||
}
|
87
Ryujinx.Audio.Renderer/Dsp/Command/UpsampleCommand.cs
Normal file
87
Ryujinx.Audio.Renderer/Dsp/Command/UpsampleCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Server.Upsampler;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class UpsampleCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Upsample;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint BufferCount { get; }
|
||||
public uint InputBufferIndex { get; }
|
||||
public uint InputSampleCount { get; }
|
||||
public uint InputSampleRate { get; }
|
||||
|
||||
public UpsamplerState UpsamplerInfo { get; }
|
||||
|
||||
public Memory<float> OutBuffer { get; }
|
||||
|
||||
public UpsampleCommand(uint bufferOffset, UpsamplerState info, uint inputCount, Span<byte> inputBufferOffset, uint bufferCount, uint sampleCount, uint sampleRate, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = 0;
|
||||
OutBuffer = info.OutputBuffer;
|
||||
BufferCount = bufferCount;
|
||||
InputSampleCount = sampleCount;
|
||||
InputSampleRate = sampleRate;
|
||||
info.SourceSampleCount = inputCount;
|
||||
info.InputBufferIndices = new ushort[inputCount];
|
||||
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]);
|
||||
}
|
||||
|
||||
UpsamplerInfo = info;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Span<float> GetBuffer(int index, int sampleCount)
|
||||
{
|
||||
return UpsamplerInfo.OutputBuffer.Span.Slice(index * sampleCount, sampleCount);
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
float ratio = (float)InputSampleRate / RendererConstants.TargetSampleRate;
|
||||
|
||||
uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount);
|
||||
|
||||
for (int i = 0; i < bufferCount; i++)
|
||||
{
|
||||
Span<float> inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]);
|
||||
Span<float> outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount);
|
||||
|
||||
float fraction = 0.0f;
|
||||
|
||||
ResamplerHelper.ResampleForUpsampler(outputBuffer, inputBuffer, ratio, ref fraction, (int)(InputSampleCount / ratio));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
125
Ryujinx.Audio.Renderer/Dsp/Command/VolumeCommand.cs
Normal file
125
Ryujinx.Audio.Renderer/Dsp/Command/VolumeCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class VolumeCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Volume;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
public float Volume { get; }
|
||||
|
||||
public VolumeCommand(float volume, uint bufferIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = (ushort)bufferIndex;
|
||||
OutputBufferIndex = (ushort)bufferIndex;
|
||||
|
||||
Volume = volume;
|
||||
}
|
||||
|
||||
private void ProcessVolumeAvx(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer)
|
||||
{
|
||||
Vector256<float> volumeVec = Vector256.Create(Volume);
|
||||
|
||||
ReadOnlySpan<Vector256<float>> inputVec = MemoryMarshal.Cast<float, Vector256<float>>(inputBuffer);
|
||||
Span<Vector256<float>> outputVec = MemoryMarshal.Cast<float, Vector256<float>>(outputBuffer);
|
||||
|
||||
int sisdStart = inputVec.Length * 8;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec));
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < inputBuffer.Length; i++)
|
||||
{
|
||||
outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessVolumeSse41(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer)
|
||||
{
|
||||
Vector128<float> volumeVec = Vector128.Create(Volume);
|
||||
|
||||
ReadOnlySpan<Vector128<float>> inputVec = MemoryMarshal.Cast<float, Vector128<float>>(inputBuffer);
|
||||
Span<Vector128<float>> outputVec = MemoryMarshal.Cast<float, Vector128<float>>(outputBuffer);
|
||||
|
||||
int sisdStart = inputVec.Length * 4;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec));
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < inputBuffer.Length; i++)
|
||||
{
|
||||
outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessVolume(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer)
|
||||
{
|
||||
if (Avx.IsSupported)
|
||||
{
|
||||
ProcessVolumeAvx(outputBuffer, inputBuffer);
|
||||
}
|
||||
else if (Sse41.IsSupported)
|
||||
{
|
||||
ProcessVolumeSse41(outputBuffer, inputBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessVolumeSlowPath(outputBuffer, inputBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ProcessVolumeSlowPath(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer)
|
||||
{
|
||||
for (int i = 0; i < outputBuffer.Length; i++)
|
||||
{
|
||||
outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume);
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
ProcessVolume(outputBuffer, inputBuffer);
|
||||
}
|
||||
}
|
||||
}
|
71
Ryujinx.Audio.Renderer/Dsp/Command/VolumeRampCommand.cs
Normal file
71
Ryujinx.Audio.Renderer/Dsp/Command/VolumeRampCommand.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class VolumeRampCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.VolumeRamp;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
public float Volume0 { get; }
|
||||
public float Volume1 { get; }
|
||||
|
||||
public VolumeRampCommand(float volume0, float volume1, uint bufferIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = (ushort)bufferIndex;
|
||||
OutputBufferIndex = (ushort)bufferIndex;
|
||||
|
||||
Volume0 = volume0;
|
||||
Volume1 = volume1;
|
||||
}
|
||||
|
||||
private void ProcessVolumeRamp(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, int sampleCount)
|
||||
{
|
||||
float ramp = (Volume1 - Volume0) / sampleCount;
|
||||
|
||||
float volume = Volume0;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume);
|
||||
volume += ramp;
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
ProcessVolumeRamp(outputBuffer, inputBuffer, (int)context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
408
Ryujinx.Audio.Renderer/Dsp/DataSourceHelper.cs
Normal file
408
Ryujinx.Audio.Renderer/Dsp/DataSourceHelper.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
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<VoiceUpdateState> State;
|
||||
public uint SourceSampleRate;
|
||||
public SampleFormat SampleFormat;
|
||||
public float Pitch;
|
||||
public DecodingBehaviour DecodingBehaviour;
|
||||
public WaveBuffer[] WaveBuffers;
|
||||
public ulong ExtraParameter;
|
||||
public ulong ExtraParameterSize;
|
||||
public int ChannelIndex;
|
||||
public int ChannelCount;
|
||||
public SampleRateConversionQuality SrcQuality;
|
||||
}
|
||||
|
||||
private static int GetPitchLimitBySrcQuality(SampleRateConversionQuality quality)
|
||||
{
|
||||
switch (quality)
|
||||
{
|
||||
case SampleRateConversionQuality.Default:
|
||||
case SampleRateConversionQuality.Low:
|
||||
return 4;
|
||||
case SampleRateConversionQuality.High:
|
||||
return 8;
|
||||
default:
|
||||
throw new ArgumentException($"{quality}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void ProcessWaveBuffers(MemoryManager memoryManager, Span<float> outputBuffer, WaveBufferInformation info, uint targetSampleRate, int sampleCount)
|
||||
{
|
||||
const int tempBufferSize = 0x3F00;
|
||||
|
||||
ref VoiceUpdateState state = ref info.State.Span[0];
|
||||
|
||||
short[] tempBuffer = ArrayPool<short>.Shared.Rent(tempBufferSize);
|
||||
|
||||
float sampleRateRatio = ((float)info.SourceSampleRate / targetSampleRate * info.Pitch);
|
||||
|
||||
float fraction = state.Fraction;
|
||||
int waveBufferIndex = (int)state.WaveBufferIndex;
|
||||
ulong playedSampleCount = state.PlayedSampleCount;
|
||||
int offset = state.Offset;
|
||||
uint waveBufferConsumed = state.WaveBufferConsumed;
|
||||
|
||||
int pitchMaxLength = GetPitchLimitBySrcQuality(info.SrcQuality);
|
||||
|
||||
int totalNeededSize = (int)MathF.Truncate(fraction + sampleRateRatio * sampleCount);
|
||||
|
||||
if (totalNeededSize + pitchMaxLength <= tempBufferSize && totalNeededSize >= 0)
|
||||
{
|
||||
int sourceSampleCountToProcess = sampleCount;
|
||||
|
||||
int maxSampleCountPerIteration = Math.Min((int)MathF.Truncate((tempBufferSize - fraction) / sampleRateRatio), sampleCount);
|
||||
|
||||
bool isStarving = false;
|
||||
|
||||
int i = 0;
|
||||
|
||||
while (i < sourceSampleCountToProcess)
|
||||
{
|
||||
int tempBufferIndex = 0;
|
||||
|
||||
if (!info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion))
|
||||
{
|
||||
state.Pitch.ToSpan().Slice(0, pitchMaxLength).CopyTo(tempBuffer.AsSpan());
|
||||
tempBufferIndex += pitchMaxLength;
|
||||
}
|
||||
|
||||
int sampleCountToProcess = Math.Min(sourceSampleCountToProcess, maxSampleCountPerIteration);
|
||||
|
||||
int y = 0;
|
||||
|
||||
int sampleCountToDecode = (int)MathF.Truncate(fraction + sampleRateRatio * sampleCountToProcess);
|
||||
|
||||
while (y < sampleCountToDecode)
|
||||
{
|
||||
if (waveBufferIndex >= 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<AdpcmLoopContext>(waveBuffer.Context);
|
||||
}
|
||||
|
||||
Span<short> tempSpan = tempBuffer.AsSpan().Slice(tempBufferIndex + y);
|
||||
|
||||
int decodedSampleCount = -1;
|
||||
|
||||
int targetSampleStartOffset;
|
||||
int targetSampleEndOffset;
|
||||
|
||||
if (state.LoopCount > 0 && waveBuffer.LoopStartSampleOffset != 0 && waveBuffer.LoopEndSampleOffset != 0 && waveBuffer.LoopStartSampleOffset <= waveBuffer.LoopEndSampleOffset)
|
||||
{
|
||||
targetSampleStartOffset = (int)waveBuffer.LoopStartSampleOffset;
|
||||
targetSampleEndOffset = (int)waveBuffer.LoopEndSampleOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetSampleStartOffset = (int)waveBuffer.StartSampleOffset;
|
||||
targetSampleEndOffset = (int)waveBuffer.EndSampleOffset;
|
||||
}
|
||||
|
||||
int targetWaveBufferSampleCount = targetSampleEndOffset - targetSampleStartOffset;
|
||||
|
||||
switch (info.SampleFormat)
|
||||
{
|
||||
case SampleFormat.Adpcm:
|
||||
ReadOnlySpan<byte> waveBufferAdpcm = ReadOnlySpan<byte>.Empty;
|
||||
|
||||
if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0)
|
||||
{
|
||||
// TODO: we are possibly copying a lot of unneeded data here, we should only take what we need.
|
||||
waveBufferAdpcm = memoryManager.GetSpan(waveBuffer.Buffer, (int)waveBuffer.BufferSize);
|
||||
}
|
||||
|
||||
ReadOnlySpan<short> coefficients = MemoryMarshal.Cast<byte, short>(memoryManager.GetSpan(info.ExtraParameter, (int)info.ExtraParameterSize));
|
||||
decodedSampleCount = AdpcmHelper.Decode(tempSpan, waveBufferAdpcm, targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y, coefficients, ref state.LoopContext);
|
||||
break;
|
||||
case SampleFormat.PcmInt16:
|
||||
ReadOnlySpan<short> waveBufferPcm16 = ReadOnlySpan<short>.Empty;
|
||||
|
||||
if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0)
|
||||
{
|
||||
ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset<short>(targetSampleStartOffset, offset, info.ChannelCount);
|
||||
int bufferSize = PcmHelper.GetBufferSize<short>(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount;
|
||||
|
||||
waveBufferPcm16 = MemoryMarshal.Cast<byte, short>(memoryManager.GetSpan(bufferOffset, bufferSize));
|
||||
}
|
||||
|
||||
decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcm16, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount);
|
||||
break;
|
||||
case SampleFormat.PcmFloat:
|
||||
ReadOnlySpan<float> waveBufferPcmFloat = ReadOnlySpan<float>.Empty;
|
||||
|
||||
if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0)
|
||||
{
|
||||
ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset<float>(targetSampleStartOffset, offset, info.ChannelCount);
|
||||
int bufferSize = PcmHelper.GetBufferSize<float>(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount;
|
||||
|
||||
waveBufferPcmFloat = MemoryMarshal.Cast<byte, float>(memoryManager.GetSpan(bufferOffset, bufferSize));
|
||||
}
|
||||
|
||||
decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcmFloat, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount);
|
||||
break;
|
||||
default:
|
||||
Logger.Warning?.Print(LogClass.AudioRenderer, $"Unsupported sample format {info.SampleFormat}");
|
||||
break;
|
||||
}
|
||||
|
||||
Debug.Assert(decodedSampleCount <= sampleCountToDecode);
|
||||
|
||||
if (decodedSampleCount < 0)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.AudioRenderer, $"Decoding failed, skipping WaveBuffer");
|
||||
|
||||
state.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount);
|
||||
decodedSampleCount = 0;
|
||||
}
|
||||
|
||||
y += decodedSampleCount;
|
||||
offset += decodedSampleCount;
|
||||
playedSampleCount += (uint)decodedSampleCount;
|
||||
|
||||
if (offset >= targetWaveBufferSampleCount || decodedSampleCount == 0)
|
||||
{
|
||||
offset = 0;
|
||||
|
||||
if (waveBuffer.Looping)
|
||||
{
|
||||
state.LoopCount++;
|
||||
|
||||
if (waveBuffer.LoopCount >= 0)
|
||||
{
|
||||
if (decodedSampleCount == 0 || state.LoopCount > waveBuffer.LoopCount)
|
||||
{
|
||||
state.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (decodedSampleCount == 0)
|
||||
{
|
||||
isStarving = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.PlayedSampleCountResetWhenLooping))
|
||||
{
|
||||
playedSampleCount = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Span<float> outputSpan = outputBuffer.Slice(i);
|
||||
Span<int> outputSpanInt = MemoryMarshal.Cast<float, int>(outputSpan);
|
||||
|
||||
if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion))
|
||||
{
|
||||
for (int j = 0; j < y; j++)
|
||||
{
|
||||
outputBuffer[j] = tempBuffer[j];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<short> tempSpan = tempBuffer.AsSpan().Slice(tempBufferIndex + y);
|
||||
|
||||
tempSpan.Slice(0, sampleCountToDecode - y).Fill(0);
|
||||
|
||||
ToFloat(outputBuffer, outputSpanInt, sampleCountToProcess);
|
||||
|
||||
ResamplerHelper.Resample(outputBuffer, tempBuffer, sampleRateRatio, ref fraction, sampleCountToProcess, info.SrcQuality, y != sourceSampleCountToProcess || info.Pitch != 1.0f);
|
||||
|
||||
tempBuffer.AsSpan().Slice(sampleCountToDecode, pitchMaxLength).CopyTo(state.Pitch.ToSpan());
|
||||
}
|
||||
|
||||
i += sampleCountToProcess;
|
||||
}
|
||||
|
||||
Debug.Assert(sourceSampleCountToProcess == i || !isStarving);
|
||||
|
||||
state.WaveBufferConsumed = waveBufferConsumed;
|
||||
state.Offset = offset;
|
||||
state.PlayedSampleCount = playedSampleCount;
|
||||
state.WaveBufferIndex = (uint)waveBufferIndex;
|
||||
state.Fraction = fraction;
|
||||
}
|
||||
|
||||
ArrayPool<short>.Shared.Return(tempBuffer);
|
||||
}
|
||||
|
||||
private static void ToFloatAvx(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<Vector256<int>> inputVec = MemoryMarshal.Cast<int, Vector256<int>>(input);
|
||||
Span<Vector256<float>> outputVec = MemoryMarshal.Cast<float, Vector256<float>>(output);
|
||||
|
||||
int sisdStart = inputVec.Length * 8;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Avx.ConvertToVector256Single(inputVec[i]);
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = input[i];
|
||||
}
|
||||
}
|
||||
|
||||
private static void ToFloatSse2(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<Vector128<int>> inputVec = MemoryMarshal.Cast<int, Vector128<int>>(input);
|
||||
Span<Vector128<float>> outputVec = MemoryMarshal.Cast<float, Vector128<float>>(output);
|
||||
|
||||
int sisdStart = inputVec.Length * 4;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Sse2.ConvertToVector128Single(inputVec[i]);
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = input[i];
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToFloatSlow(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
|
||||
{
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = input[i];
|
||||
}
|
||||
}
|
||||
|
||||
public static void ToFloat(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
|
||||
{
|
||||
if (Avx.IsSupported)
|
||||
{
|
||||
ToFloatAvx(output, input, sampleCount);
|
||||
}
|
||||
else if (Sse2.IsSupported)
|
||||
{
|
||||
ToFloatSse2(output, input, sampleCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToFloatSlow(output, input, sampleCount);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ToIntAvx(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<Vector256<float>> inputVec = MemoryMarshal.Cast<float, Vector256<float>>(input);
|
||||
Span<Vector256<int>> outputVec = MemoryMarshal.Cast<int, Vector256<int>>(output);
|
||||
|
||||
int sisdStart = inputVec.Length * 8;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Avx.ConvertToVector256Int32(inputVec[i]);
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = (int)input[i];
|
||||
}
|
||||
}
|
||||
|
||||
public static void ToIntSse2(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<Vector128<float>> inputVec = MemoryMarshal.Cast<float, Vector128<float>>(input);
|
||||
Span<Vector128<int>> outputVec = MemoryMarshal.Cast<int, Vector128<int>>(output);
|
||||
|
||||
int sisdStart = inputVec.Length * 4;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Avx.ConvertToVector128Int32(inputVec[i]);
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = (int)input[i];
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToIntSlow(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
|
||||
{
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = (int)input[i];
|
||||
}
|
||||
}
|
||||
|
||||
public static void ToInt(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
|
||||
{
|
||||
if (Avx.IsSupported)
|
||||
{
|
||||
ToIntAvx(output, input, sampleCount);
|
||||
}
|
||||
else if (Sse2.IsSupported)
|
||||
{
|
||||
ToIntSse2(output, input, sampleCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToIntSlow(output, input, sampleCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
Ryujinx.Audio.Renderer/Dsp/Effect/DecayDelay.cs
Normal file
69
Ryujinx.Audio.Renderer/Dsp/Effect/DecayDelay.cs
Normal file
@ -0,0 +1,69 @@
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Effect
|
||||
{
|
||||
public class DecayDelay : IDelayLine
|
||||
{
|
||||
private readonly IDelayLine _delayLine;
|
||||
|
||||
public uint CurrentSampleCount => _delayLine.CurrentSampleCount;
|
||||
|
||||
public uint SampleCountMax => _delayLine.SampleCountMax;
|
||||
|
||||
private float _decayRate;
|
||||
|
||||
public DecayDelay(IDelayLine delayLine)
|
||||
{
|
||||
_decayRate = 0.0f;
|
||||
_delayLine = delayLine;
|
||||
}
|
||||
|
||||
public void SetDecayRate(float decayRate)
|
||||
{
|
||||
_decayRate = decayRate;
|
||||
}
|
||||
|
||||
public float Update(float value)
|
||||
{
|
||||
float delayLineValue = _delayLine.Read();
|
||||
float processedValue = value - (_decayRate * delayLineValue);
|
||||
|
||||
return _delayLine.Update(processedValue) + processedValue * _decayRate;
|
||||
}
|
||||
|
||||
public void SetDelay(float delayTime)
|
||||
{
|
||||
_delayLine.SetDelay(delayTime);
|
||||
}
|
||||
|
||||
public float Read()
|
||||
{
|
||||
return _delayLine.Read();
|
||||
}
|
||||
|
||||
public float TapUnsafe(uint sampleIndex, int offset)
|
||||
{
|
||||
return _delayLine.TapUnsafe(sampleIndex, offset);
|
||||
}
|
||||
|
||||
public float Tap(uint sampleIndex)
|
||||
{
|
||||
return _delayLine.Tap(sampleIndex);
|
||||
}
|
||||
}
|
||||
}
|
87
Ryujinx.Audio.Renderer/Dsp/Effect/DelayLine.cs
Normal file
87
Ryujinx.Audio.Renderer/Dsp/Effect/DelayLine.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Effect
|
||||
{
|
||||
public class DelayLine : IDelayLine
|
||||
{
|
||||
private float[] _workBuffer;
|
||||
private uint _sampleRate;
|
||||
private uint _currentSampleIndex;
|
||||
private uint _lastSampleIndex;
|
||||
|
||||
public uint CurrentSampleCount { get; private set; }
|
||||
public uint SampleCountMax { get; private set; }
|
||||
|
||||
public DelayLine(uint sampleRate, float delayTimeMax)
|
||||
{
|
||||
_sampleRate = sampleRate;
|
||||
SampleCountMax = IDelayLine.GetSampleCount(_sampleRate, delayTimeMax);
|
||||
_workBuffer = new float[SampleCountMax];
|
||||
|
||||
SetDelay(delayTimeMax);
|
||||
}
|
||||
|
||||
private void ConfigureDelay(uint targetSampleCount)
|
||||
{
|
||||
CurrentSampleCount = Math.Min(SampleCountMax, targetSampleCount);
|
||||
_currentSampleIndex = 0;
|
||||
_lastSampleIndex = CurrentSampleCount - 1;
|
||||
}
|
||||
|
||||
public void SetDelay(float delayTime)
|
||||
{
|
||||
ConfigureDelay(IDelayLine.GetSampleCount(_sampleRate, delayTime));
|
||||
}
|
||||
|
||||
public float Read()
|
||||
{
|
||||
return _workBuffer[_currentSampleIndex];
|
||||
}
|
||||
|
||||
public float Update(float value)
|
||||
{
|
||||
float output = Read();
|
||||
|
||||
_workBuffer[_currentSampleIndex++] = value;
|
||||
|
||||
if (_currentSampleIndex >= _lastSampleIndex)
|
||||
{
|
||||
_currentSampleIndex = 0;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public float TapUnsafe(uint sampleIndex, int offset)
|
||||
{
|
||||
return IDelayLine.Tap(_workBuffer, (int)_currentSampleIndex, (int)sampleIndex + offset, (int)CurrentSampleCount);
|
||||
}
|
||||
|
||||
public float Tap(uint sampleIndex)
|
||||
{
|
||||
if (sampleIndex >= CurrentSampleCount)
|
||||
{
|
||||
sampleIndex = CurrentSampleCount - 1;
|
||||
}
|
||||
|
||||
return TapUnsafe(sampleIndex, -1);
|
||||
}
|
||||
}
|
||||
}
|
93
Ryujinx.Audio.Renderer/Dsp/Effect/DelayLineReverb3d.cs
Normal file
93
Ryujinx.Audio.Renderer/Dsp/Effect/DelayLineReverb3d.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Effect
|
||||
{
|
||||
public class DelayLine3d : IDelayLine
|
||||
{
|
||||
private float[] _workBuffer;
|
||||
private uint _sampleRate;
|
||||
private uint _currentSampleIndex;
|
||||
private uint _lastSampleIndex;
|
||||
|
||||
public uint CurrentSampleCount { get; private set; }
|
||||
public uint SampleCountMax { get; private set; }
|
||||
|
||||
public DelayLine3d(uint sampleRate, float delayTimeMax)
|
||||
{
|
||||
_sampleRate = sampleRate;
|
||||
SampleCountMax = IDelayLine.GetSampleCount(_sampleRate, delayTimeMax);
|
||||
_workBuffer = new float[SampleCountMax + 1];
|
||||
|
||||
SetDelay(delayTimeMax);
|
||||
}
|
||||
|
||||
private void ConfigureDelay(uint targetSampleCount)
|
||||
{
|
||||
if (SampleCountMax >= targetSampleCount)
|
||||
{
|
||||
CurrentSampleCount = targetSampleCount;
|
||||
_lastSampleIndex = (_currentSampleIndex + targetSampleCount) % (SampleCountMax + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDelay(float delayTime)
|
||||
{
|
||||
ConfigureDelay(IDelayLine.GetSampleCount(_sampleRate, delayTime));
|
||||
}
|
||||
|
||||
public float Read()
|
||||
{
|
||||
return _workBuffer[_currentSampleIndex];
|
||||
}
|
||||
|
||||
public float Update(float value)
|
||||
{
|
||||
Debug.Assert(!float.IsNaN(value) && !float.IsInfinity(value));
|
||||
|
||||
_workBuffer[_lastSampleIndex++] = value;
|
||||
|
||||
float output = Read();
|
||||
|
||||
_currentSampleIndex++;
|
||||
|
||||
if (_currentSampleIndex >= SampleCountMax)
|
||||
{
|
||||
_currentSampleIndex = 0;
|
||||
}
|
||||
|
||||
if (_lastSampleIndex >= SampleCountMax)
|
||||
{
|
||||
_lastSampleIndex = 0;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public float TapUnsafe(uint sampleIndex, int offset)
|
||||
{
|
||||
return IDelayLine.Tap(_workBuffer, (int)_lastSampleIndex, (int)sampleIndex + offset, (int)SampleCountMax + 1);
|
||||
}
|
||||
|
||||
public float Tap(uint sampleIndex)
|
||||
{
|
||||
return TapUnsafe(sampleIndex, -1);
|
||||
}
|
||||
}
|
||||
}
|
53
Ryujinx.Audio.Renderer/Dsp/Effect/IDelayLine.cs
Normal file
53
Ryujinx.Audio.Renderer/Dsp/Effect/IDelayLine.cs
Normal file
@ -0,0 +1,53 @@
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Effect
|
||||
{
|
||||
public interface IDelayLine
|
||||
{
|
||||
uint CurrentSampleCount { get; }
|
||||
uint SampleCountMax { get; }
|
||||
|
||||
void SetDelay(float delayTime);
|
||||
float Read();
|
||||
float Update(float value);
|
||||
|
||||
float TapUnsafe(uint sampleIndex, int offset);
|
||||
float Tap(uint sampleIndex);
|
||||
|
||||
public static float Tap(Span<float> workBuffer, int baseIndex, int sampleIndex, int delaySampleCount)
|
||||
{
|
||||
int targetIndex = baseIndex - sampleIndex;
|
||||
|
||||
if (targetIndex < 0)
|
||||
{
|
||||
targetIndex += delaySampleCount;
|
||||
}
|
||||
|
||||
return workBuffer[targetIndex];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint GetSampleCount(uint sampleRate, float delayTime)
|
||||
{
|
||||
return (uint)MathF.Round(sampleRate * delayTime);
|
||||
}
|
||||
}
|
||||
}
|
50
Ryujinx.Audio.Renderer/Dsp/FixedPointHelper.cs
Normal file
50
Ryujinx.Audio.Renderer/Dsp/FixedPointHelper.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
{
|
||||
public static class FixedPointHelper
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ToInt(long value, int qBits)
|
||||
{
|
||||
return (int)(value >> qBits);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float ToFloat(long value, int qBits)
|
||||
{
|
||||
return (float)value / (1 << qBits);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ToFixed(float value, int qBits)
|
||||
{
|
||||
return (int)(value * (1 << qBits));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int RoundUpAndToInt(long value, int qBits)
|
||||
{
|
||||
int half = 1 << (qBits - 1);
|
||||
|
||||
return ToInt(value + half, qBits);
|
||||
}
|
||||
}
|
||||
}
|
84
Ryujinx.Audio.Renderer/Dsp/FloatingPointHelper.cs
Normal file
84
Ryujinx.Audio.Renderer/Dsp/FloatingPointHelper.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
{
|
||||
public static class FloatingPointHelper
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float MultiplyRoundDown(float a, float b)
|
||||
{
|
||||
return RoundDown(a * b);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float RoundDown(float a)
|
||||
{
|
||||
return MathF.Round(a, 0);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float RoundUp(float a)
|
||||
{
|
||||
return MathF.Round(a);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float MultiplyRoundUp(float a, float b)
|
||||
{
|
||||
return RoundUp(a * b);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float Pow10(float x)
|
||||
{
|
||||
// NOTE: Nintendo implementation uses Q15 and a LUT for this, we don't.
|
||||
// As such, we support the same ranges as Nintendo to avoid unexpected behaviours.
|
||||
if (x >= 0.0f)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
else if (x <= -5.3f)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return MathF.Pow(10, x);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float DegreesToRadians(float degrees)
|
||||
{
|
||||
return degrees * MathF.PI / 180.0f;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float Cos(float value)
|
||||
{
|
||||
return MathF.Cos(DegreesToRadians(value));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float Sin(float value)
|
||||
{
|
||||
return MathF.Sin(DegreesToRadians(value));
|
||||
}
|
||||
}
|
||||
}
|
95
Ryujinx.Audio.Renderer/Dsp/PcmHelper.cs
Normal file
95
Ryujinx.Audio.Renderer/Dsp/PcmHelper.cs
Normal file
@ -0,0 +1,95 @@
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
{
|
||||
public static class PcmHelper
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetCountToDecode(int startSampleOffset, int endSampleOffset, int offset, int count)
|
||||
{
|
||||
return Math.Min(count, endSampleOffset - startSampleOffset - offset);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong GetBufferOffset<T>(int startSampleOffset, int offset, int channelCount) where T : unmanaged
|
||||
{
|
||||
return (ulong)(Unsafe.SizeOf<T>() * channelCount * (startSampleOffset + offset));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetBufferSize<T>(int startSampleOffset, int endSampleOffset, int offset, int count) where T : unmanaged
|
||||
{
|
||||
return GetCountToDecode(startSampleOffset, endSampleOffset, offset, count) * Unsafe.SizeOf<T>();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Decode(Span<short> output, ReadOnlySpan<short> input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount)
|
||||
{
|
||||
if (input.IsEmpty || endSampleOffset < startSampleOffset)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int decodedCount = input.Length / channelCount;
|
||||
|
||||
for (int i = 0; i < decodedCount; i++)
|
||||
{
|
||||
output[i] = input[i * channelCount + channelIndex];
|
||||
}
|
||||
|
||||
return decodedCount;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Decode(Span<short> output, ReadOnlySpan<float> input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount)
|
||||
{
|
||||
if (input.IsEmpty || endSampleOffset < startSampleOffset)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int decodedCount = input.Length / channelCount;
|
||||
|
||||
for (int i = 0; i < decodedCount; i++)
|
||||
{
|
||||
output[i] = (short)(input[i * channelCount + channelIndex] * short.MaxValue);
|
||||
}
|
||||
|
||||
return decodedCount;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static short Saturate(float value)
|
||||
{
|
||||
if (value > short.MaxValue)
|
||||
{
|
||||
return short.MaxValue;
|
||||
}
|
||||
|
||||
if (value < short.MinValue)
|
||||
{
|
||||
return short.MinValue;
|
||||
}
|
||||
|
||||
return (short)value;
|
||||
}
|
||||
}
|
||||
}
|
624
Ryujinx.Audio.Renderer/Dsp/ResamplerHelper.cs
Normal file
624
Ryujinx.Audio.Renderer/Dsp/ResamplerHelper.cs
Normal file
@ -0,0 +1,624 @@
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
{
|
||||
public static class ResamplerHelper
|
||||
{
|
||||
#region "Default Quality Lookup Tables"
|
||||
private static short[] _normalCurveLut0 = new short[]
|
||||
{
|
||||
6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239, 19412, 7093, 22,
|
||||
6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377, 7472, 41, 5773, 19361, 7600, 48,
|
||||
5659, 19342, 7728, 55, 5546, 19321, 7857, 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77,
|
||||
5213, 19245, 8249, 84, 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109,
|
||||
4785, 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988, 9183, 147,
|
||||
4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590, 179, 4083, 18793, 9726, 190,
|
||||
3987, 18738, 9863, 202, 3893, 18682, 10000, 215, 3800, 18624, 10137, 228, 3709, 18563, 10274, 241,
|
||||
3618, 18500, 10411, 255, 3529, 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300,
|
||||
3269, 18230, 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375, 369,
|
||||
2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428, 2709, 17681, 11925, 449,
|
||||
2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489, 17418, 12334, 517, 2418, 17327, 12470, 541,
|
||||
2348, 17234, 12606, 566, 2280, 17140, 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647,
|
||||
2083, 16846, 13144, 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766,
|
||||
1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667, 16109, 14062, 901,
|
||||
1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772, 14445, 1013, 1457, 15657, 14571, 1052,
|
||||
1407, 15540, 14695, 1093, 1359, 15423, 14819, 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221,
|
||||
1221, 15064, 15185, 1266, 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407,
|
||||
1052, 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191, 15998, 1613,
|
||||
901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327, 1780, 798, 13673, 16434, 1838,
|
||||
766, 13541, 16539, 1897, 735, 13409, 16643, 1958, 704, 13277, 16745, 2020, 675, 13144, 16846, 2083,
|
||||
647, 13010, 16946, 2147, 619, 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348,
|
||||
541, 12470, 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595, 2635,
|
||||
449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863, 388, 11513, 17927, 2942,
|
||||
369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334, 11100, 18157, 3186, 317, 10962, 18230, 3269,
|
||||
300, 10824, 18300, 3355, 285, 10687, 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618,
|
||||
241, 10274, 18563, 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987,
|
||||
190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157, 9318, 18942, 4377,
|
||||
147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914, 19073, 4681, 118, 8780, 19112, 4785,
|
||||
109, 8646, 19148, 4890, 101, 8513, 19183, 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213,
|
||||
77, 8118, 19273, 5323, 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659,
|
||||
48, 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219, 19403, 6121,
|
||||
22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424, 6479, 3, 6722, 19426, 6600
|
||||
};
|
||||
|
||||
private static short[] _normalCurveLut1 = new short[]
|
||||
{
|
||||
-68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450, 32586, 512, -36,
|
||||
-568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454, 1000, -69, -891, 32393, 1174, -80,
|
||||
-990, 32323, 1352, -92, -1084, 32244, 1536, -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128,
|
||||
-1338, 31956, 2118, -140, -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180,
|
||||
-1617, 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995, 3657, -240,
|
||||
-1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393, -289, -1951, 30272, 4648, -307,
|
||||
-1984, 30072, 4908, -325, -2014, 29866, 5172, -343, -2040, 29652, 5442, -362, -2063, 29431, 5716, -382,
|
||||
-2083, 29203, 5994, -403, -2100, 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468,
|
||||
-2133, 28226, 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066, -563,
|
||||
-2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641, -2121, 26285, 9336, -668,
|
||||
-2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084, 25375, 10326, -753, -2067, 25063, 10662, -783,
|
||||
-2049, 24746, 11000, -813, -2030, 24425, 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907,
|
||||
-1962, 23438, 12382, -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039,
|
||||
-1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, 21027, 14877, -1176,
|
||||
-1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, 15965, -1282, -1633, 19600, 16329, -1317,
|
||||
-1599, 19239, 16694, -1353, -1564, 18878, 17058, -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459,
|
||||
-1459, 17787, 18151, -1495, -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599,
|
||||
-1317, 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, 20673, -1732,
|
||||
-1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, -1825, -1072, 13798, 22077, -1855,
|
||||
-1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, -972, 12733, 23103, -1937, -939, 12382, 23438, -1962,
|
||||
-907, 12033, 23771, -1986, -875, 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049,
|
||||
-783, 10662, 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987, -2111,
|
||||
-668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136, -588, 8378, 27151, -2141,
|
||||
-563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514, 7453, 27966, -2139, -490, 7153, 28226, -2133,
|
||||
-468, 6857, 28480, -2125, -445, 6565, 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083,
|
||||
-382, 5716, 29431, -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984,
|
||||
-307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256, 3897, 30826, -1830,
|
||||
-240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192, 31310, -1676, -194, 2967, 31456, -1617,
|
||||
-180, 2747, 31593, -1554, -167, 2532, 31723, -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338,
|
||||
-128, 1919, 32061, -1258, -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990,
|
||||
-80, 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669, 32551, -568,
|
||||
-36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630, -200, -5, 69, 32639, -68
|
||||
};
|
||||
|
||||
private static short[] _normalCurveLut2 = new short[]
|
||||
{
|
||||
3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811, 26253, 3751, -42,
|
||||
2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169, 4199, -54, 2338, 26130, 4354, -58,
|
||||
2227, 26085, 4512, -63, 2120, 26035, 4673, -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76,
|
||||
1813, 25852, 5174, -81, 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98,
|
||||
1442, 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239, 6442, -121,
|
||||
1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027, -140, 897, 24776, 7227, -146,
|
||||
829, 24648, 7430, -153, 764, 24516, 7635, -159, 701, 24379, 7842, -166, 641, 24237, 8052, -174,
|
||||
583, 24091, 8264, -181, 526, 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202,
|
||||
371, 23462, 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809, -230,
|
||||
194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250, 81, 22208, 10735, -258,
|
||||
47, 22015, 10970, -265, 15, 21818, 11206, -271, -16, 21618, 11444, -277, -44, 21415, 11684, -283,
|
||||
-71, 21208, 11924, -290, -97, 20999, 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306,
|
||||
-163, 20354, 12898, -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325,
|
||||
-234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273, 18765, 14625, -337,
|
||||
-284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057, 15369, -341, -310, 17817, 15617, -341,
|
||||
-317, 17577, 15864, -340, -323, 17335, 16111, -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336,
|
||||
-336, 16603, 16848, -332, -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317,
|
||||
-341, 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873, 18531, -284,
|
||||
-337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230, -248, -328, 13882, 19459, -234,
|
||||
-325, 13635, 19686, -218, -321, 13389, 19911, -201, -316, 13143, 20134, -183, -311, 12898, 20354, -163,
|
||||
-306, 12653, 20571, -143, -302, 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71,
|
||||
-283, 11684, 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015, 47,
|
||||
-258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154, -237, 10038, 22769, 194,
|
||||
-230, 9809, 22948, 236, -222, 9583, 23123, 279, -215, 9358, 23295, 324, -209, 9135, 23462, 371,
|
||||
-202, 8914, 23626, 420, -194, 8695, 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583,
|
||||
-174, 8052, 24237, 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829,
|
||||
-146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127, 6635, 25131, 1115,
|
||||
-121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066, 25440, 1357, -103, 5882, 25533, 1442,
|
||||
-98, 5701, 25621, 1531, -92, 5522, 25704, 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813,
|
||||
-76, 5004, 25919, 1912, -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227,
|
||||
-58, 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897, 26230, 2688,
|
||||
-42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281, 3064, -32, 3329, 26287, 3195
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region "High Quality Lookup Tables"
|
||||
private static short[] _highCurveLut0 = new short[]
|
||||
{
|
||||
-582, -23, 8740, 16386, 8833, 8, -590, 0, -573, -54, 8647, 16385, 8925, 40, -598, -1,
|
||||
-565, -84, 8555, 16383, 9018, 72, -606, -1, -557, -113, 8462, 16379, 9110, 105, -614, -2,
|
||||
-549, -142, 8370, 16375, 9203, 139, -622, -2, -541, -170, 8277, 16369, 9295, 173, -630, -3,
|
||||
-533, -198, 8185, 16362, 9387, 208, -638, -4, -525, -225, 8093, 16354, 9480, 244, -646, -5,
|
||||
-516, -251, 8000, 16344, 9572, 280, -654, -5, -508, -277, 7908, 16334, 9664, 317, -662, -6,
|
||||
-500, -302, 7816, 16322, 9756, 355, -670, -7, -492, -327, 7724, 16310, 9847, 393, -678, -8,
|
||||
-484, -351, 7632, 16296, 9939, 432, -686, -9, -476, -374, 7540, 16281, 10030, 471, -694, -10,
|
||||
-468, -397, 7449, 16265, 10121, 511, -702, -11, -460, -419, 7357, 16247, 10212, 552, -709, -13,
|
||||
-452, -441, 7266, 16229, 10303, 593, -717, -14, -445, -462, 7175, 16209, 10394, 635, -724, -15,
|
||||
-437, -483, 7084, 16189, 10484, 678, -732, -16, -429, -503, 6994, 16167, 10574, 722, -739, -18,
|
||||
-421, -523, 6903, 16144, 10664, 766, -747, -19, -414, -542, 6813, 16120, 10754, 810, -754, -21,
|
||||
-406, -560, 6723, 16095, 10843, 856, -761, -22, -398, -578, 6633, 16068, 10932, 902, -768, -24,
|
||||
-391, -596, 6544, 16041, 11021, 949, -775, -26, -383, -612, 6454, 16012, 11109, 996, -782, -27,
|
||||
-376, -629, 6366, 15983, 11197, 1044, -789, -29, -368, -645, 6277, 15952, 11285, 1093, -796, -31,
|
||||
-361, -660, 6189, 15920, 11372, 1142, -802, -33, -354, -675, 6100, 15887, 11459, 1192, -809, -35,
|
||||
-347, -689, 6013, 15853, 11546, 1243, -815, -37, -339, -703, 5925, 15818, 11632, 1294, -821, -39,
|
||||
-332, -717, 5838, 15782, 11718, 1346, -827, -41, -325, -730, 5751, 15745, 11803, 1399, -833, -43,
|
||||
-318, -742, 5665, 15707, 11888, 1452, -839, -46, -312, -754, 5579, 15668, 11973, 1506, -845, -48,
|
||||
-305, -766, 5493, 15627, 12057, 1561, -850, -50, -298, -777, 5408, 15586, 12140, 1616, -855, -53,
|
||||
-291, -787, 5323, 15544, 12224, 1672, -861, -56, -285, -798, 5239, 15500, 12306, 1729, -866, -58,
|
||||
-278, -807, 5155, 15456, 12388, 1786, -871, -61, -272, -817, 5071, 15410, 12470, 1844, -875, -64,
|
||||
-265, -826, 4988, 15364, 12551, 1902, -880, -67, -259, -834, 4905, 15317, 12631, 1962, -884, -70,
|
||||
-253, -842, 4823, 15268, 12711, 2022, -888, -73, -247, -850, 4741, 15219, 12790, 2082, -892, -76,
|
||||
-241, -857, 4659, 15168, 12869, 2143, -896, -79, -235, -864, 4578, 15117, 12947, 2205, -899, -82,
|
||||
-229, -870, 4498, 15065, 13025, 2267, -903, -85, -223, -876, 4417, 15012, 13102, 2331, -906, -89,
|
||||
-217, -882, 4338, 14958, 13178, 2394, -909, -92, -211, -887, 4259, 14903, 13254, 2459, -911, -96,
|
||||
-206, -892, 4180, 14847, 13329, 2523, -914, -100, -200, -896, 4102, 14790, 13403, 2589, -916, -103,
|
||||
-195, -900, 4024, 14732, 13477, 2655, -918, -107, -190, -904, 3947, 14673, 13550, 2722, -919, -111,
|
||||
-184, -908, 3871, 14614, 13622, 2789, -921, -115, -179, -911, 3795, 14553, 13693, 2857, -922, -119,
|
||||
-174, -913, 3719, 14492, 13764, 2926, -923, -123, -169, -916, 3644, 14430, 13834, 2995, -923, -127,
|
||||
-164, -918, 3570, 14367, 13904, 3065, -924, -132, -159, -920, 3496, 14303, 13972, 3136, -924, -136,
|
||||
-154, -921, 3423, 14239, 14040, 3207, -924, -140, -150, -922, 3350, 14173, 14107, 3278, -923, -145,
|
||||
-145, -923, 3278, 14107, 14173, 3350, -922, -150, -140, -924, 3207, 14040, 14239, 3423, -921, -154,
|
||||
-136, -924, 3136, 13972, 14303, 3496, -920, -159, -132, -924, 3065, 13904, 14367, 3570, -918, -164,
|
||||
-127, -923, 2995, 13834, 14430, 3644, -916, -169, -123, -923, 2926, 13764, 14492, 3719, -913, -174,
|
||||
-119, -922, 2857, 13693, 14553, 3795, -911, -179, -115, -921, 2789, 13622, 14614, 3871, -908, -184,
|
||||
-111, -919, 2722, 13550, 14673, 3947, -904, -190, -107, -918, 2655, 13477, 14732, 4024, -900, -195,
|
||||
-103, -916, 2589, 13403, 14790, 4102, -896, -200, -100, -914, 2523, 13329, 14847, 4180, -892, -206,
|
||||
-96, -911, 2459, 13254, 14903, 4259, -887, -211, -92, -909, 2394, 13178, 14958, 4338, -882, -217,
|
||||
-89, -906, 2331, 13102, 15012, 4417, -876, -223, -85, -903, 2267, 13025, 15065, 4498, -870, -229,
|
||||
-82, -899, 2205, 12947, 15117, 4578, -864, -235, -79, -896, 2143, 12869, 15168, 4659, -857, -241,
|
||||
-76, -892, 2082, 12790, 15219, 4741, -850, -247, -73, -888, 2022, 12711, 15268, 4823, -842, -253,
|
||||
-70, -884, 1962, 12631, 15317, 4905, -834, -259, -67, -880, 1902, 12551, 15364, 4988, -826, -265,
|
||||
-64, -875, 1844, 12470, 15410, 5071, -817, -272, -61, -871, 1786, 12388, 15456, 5155, -807, -278,
|
||||
-58, -866, 1729, 12306, 15500, 5239, -798, -285, -56, -861, 1672, 12224, 15544, 5323, -787, -291,
|
||||
-53, -855, 1616, 12140, 15586, 5408, -777, -298, -50, -850, 1561, 12057, 15627, 5493, -766, -305,
|
||||
-48, -845, 1506, 11973, 15668, 5579, -754, -312, -46, -839, 1452, 11888, 15707, 5665, -742, -318,
|
||||
-43, -833, 1399, 11803, 15745, 5751, -730, -325, -41, -827, 1346, 11718, 15782, 5838, -717, -332,
|
||||
-39, -821, 1294, 11632, 15818, 5925, -703, -339, -37, -815, 1243, 11546, 15853, 6013, -689, -347,
|
||||
-35, -809, 1192, 11459, 15887, 6100, -675, -354, -33, -802, 1142, 11372, 15920, 6189, -660, -361,
|
||||
-31, -796, 1093, 11285, 15952, 6277, -645, -368, -29, -789, 1044, 11197, 15983, 6366, -629, -376,
|
||||
-27, -782, 996, 11109, 16012, 6454, -612, -383, -26, -775, 949, 11021, 16041, 6544, -596, -391,
|
||||
-24, -768, 902, 10932, 16068, 6633, -578, -398, -22, -761, 856, 10843, 16095, 6723, -560, -406,
|
||||
-21, -754, 810, 10754, 16120, 6813, -542, -414, -19, -747, 766, 10664, 16144, 6903, -523, -421,
|
||||
-18, -739, 722, 10574, 16167, 6994, -503, -429, -16, -732, 678, 10484, 16189, 7084, -483, -437,
|
||||
-15, -724, 635, 10394, 16209, 7175, -462, -445, -14, -717, 593, 10303, 16229, 7266, -441, -452,
|
||||
-13, -709, 552, 10212, 16247, 7357, -419, -460, -11, -702, 511, 10121, 16265, 7449, -397, -468,
|
||||
-10, -694, 471, 10030, 16281, 7540, -374, -476, -9, -686, 432, 9939, 16296, 7632, -351, -484,
|
||||
-8, -678, 393, 9847, 16310, 7724, -327, -492, -7, -670, 355, 9756, 16322, 7816, -302, -500,
|
||||
-6, -662, 317, 9664, 16334, 7908, -277, -508, -5, -654, 280, 9572, 16344, 8000, -251, -516,
|
||||
-5, -646, 244, 9480, 16354, 8093, -225, -525, -4, -638, 208, 9387, 16362, 8185, -198, -533,
|
||||
-3, -630, 173, 9295, 16369, 8277, -170, -541, -2, -622, 139, 9203, 16375, 8370, -142, -549,
|
||||
-2, -614, 105, 9110, 16379, 8462, -113, -557, -1, -606, 72, 9018, 16383, 8555, -84, -565,
|
||||
-1, -598, 40, 8925, 16385, 8647, -54, -573, 0, -590, 8, 8833, 16386, 8740, -23, -582,
|
||||
};
|
||||
|
||||
private static short[] _highCurveLut1 = new short[]
|
||||
{
|
||||
-12, 47, -134, 32767, 81, -16, 2, 0, -26, 108, -345, 32760, 301, -79, 17, -1,
|
||||
-40, 168, -552, 32745, 526, -144, 32, -2, -53, 226, -753, 32723, 755, -210, 47, -3,
|
||||
-66, 284, -950, 32694, 989, -277, 63, -5, -78, 340, -1143, 32658, 1226, -346, 79, -6,
|
||||
-90, 394, -1331, 32615, 1469, -415, 96, -8, -101, 447, -1514, 32564, 1715, -486, 113, -9,
|
||||
-112, 499, -1692, 32506, 1966, -557, 130, -11, -123, 550, -1865, 32441, 2221, -630, 148, -13,
|
||||
-133, 599, -2034, 32369, 2480, -703, 166, -14, -143, 646, -2198, 32290, 2743, -778, 185, -16,
|
||||
-152, 693, -2357, 32204, 3010, -853, 204, -18, -162, 738, -2512, 32110, 3281, -929, 223, -20,
|
||||
-170, 781, -2662, 32010, 3555, -1007, 242, -23, -178, 823, -2807, 31903, 3834, -1084, 262, -25,
|
||||
-186, 864, -2947, 31789, 4116, -1163, 282, -27, -194, 903, -3082, 31668, 4403, -1242, 303, -30,
|
||||
-201, 940, -3213, 31540, 4692, -1322, 323, -32, -207, 977, -3339, 31406, 4985, -1403, 344, -35,
|
||||
-214, 1011, -3460, 31265, 5282, -1484, 365, -37, -220, 1045, -3577, 31117, 5582, -1566, 387, -40,
|
||||
-225, 1077, -3688, 30963, 5885, -1648, 409, -43, -230, 1107, -3796, 30802, 6191, -1730, 431, -46,
|
||||
-235, 1136, -3898, 30635, 6501, -1813, 453, -49, -240, 1164, -3996, 30462, 6813, -1896, 475, -52,
|
||||
-244, 1190, -4089, 30282, 7128, -1980, 498, -55, -247, 1215, -4178, 30097, 7446, -2064, 520, -58,
|
||||
-251, 1239, -4262, 29905, 7767, -2148, 543, -62, -254, 1261, -4342, 29707, 8091, -2231, 566, -65,
|
||||
-257, 1281, -4417, 29503, 8416, -2315, 589, -69, -259, 1301, -4488, 29293, 8745, -2399, 613, -72,
|
||||
-261, 1319, -4555, 29078, 9075, -2483, 636, -76, -263, 1336, -4617, 28857, 9408, -2567, 659, -80,
|
||||
-265, 1351, -4674, 28631, 9743, -2651, 683, -83, -266, 1365, -4728, 28399, 10080, -2734, 706, -87,
|
||||
-267, 1378, -4777, 28161, 10418, -2817, 730, -91, -267, 1389, -4822, 27919, 10759, -2899, 753, -95,
|
||||
-268, 1400, -4863, 27671, 11100, -2981, 777, -99, -268, 1409, -4900, 27418, 11444, -3063, 800, -103,
|
||||
-268, 1416, -4933, 27161, 11789, -3144, 824, -107, -267, 1423, -4962, 26898, 12135, -3224, 847, -112,
|
||||
-267, 1428, -4987, 26631, 12482, -3303, 870, -116, -266, 1433, -5008, 26359, 12830, -3382, 893, -120,
|
||||
-265, 1436, -5026, 26083, 13179, -3460, 916, -125, -264, 1438, -5039, 25802, 13529, -3537, 939, -129,
|
||||
-262, 1438, -5049, 25517, 13880, -3613, 962, -133, -260, 1438, -5055, 25228, 14231, -3687, 984, -138,
|
||||
-258, 1437, -5058, 24935, 14582, -3761, 1006, -142, -256, 1435, -5058, 24639, 14934, -3833, 1028, -147,
|
||||
-254, 1431, -5053, 24338, 15286, -3904, 1049, -151, -252, 1427, -5046, 24034, 15638, -3974, 1071, -155,
|
||||
-249, 1422, -5035, 23726, 15989, -4042, 1091, -160, -246, 1416, -5021, 23415, 16341, -4109, 1112, -164,
|
||||
-243, 1408, -5004, 23101, 16691, -4174, 1132, -169, -240, 1400, -4984, 22783, 17042, -4237, 1152, -173,
|
||||
-237, 1392, -4960, 22463, 17392, -4299, 1171, -178, -234, 1382, -4934, 22140, 17740, -4358, 1190, -182,
|
||||
-230, 1371, -4905, 21814, 18088, -4416, 1209, -186, -227, 1360, -4873, 21485, 18435, -4472, 1226, -191,
|
||||
-223, 1348, -4839, 21154, 18781, -4526, 1244, -195, -219, 1335, -4801, 20821, 19125, -4578, 1260, -199,
|
||||
-215, 1321, -4761, 20486, 19468, -4627, 1277, -203, -211, 1307, -4719, 20148, 19809, -4674, 1292, -207,
|
||||
-207, 1292, -4674, 19809, 20148, -4719, 1307, -211, -203, 1277, -4627, 19468, 20486, -4761, 1321, -215,
|
||||
-199, 1260, -4578, 19125, 20821, -4801, 1335, -219, -195, 1244, -4526, 18781, 21154, -4839, 1348, -223,
|
||||
-191, 1226, -4472, 18435, 21485, -4873, 1360, -227, -186, 1209, -4416, 18088, 21814, -4905, 1371, -230,
|
||||
-182, 1190, -4358, 17740, 22140, -4934, 1382, -234, -178, 1171, -4299, 17392, 22463, -4960, 1392, -237,
|
||||
-173, 1152, -4237, 17042, 22783, -4984, 1400, -240, -169, 1132, -4174, 16691, 23101, -5004, 1408, -243,
|
||||
-164, 1112, -4109, 16341, 23415, -5021, 1416, -246, -160, 1091, -4042, 15989, 23726, -5035, 1422, -249,
|
||||
-155, 1071, -3974, 15638, 24034, -5046, 1427, -252, -151, 1049, -3904, 15286, 24338, -5053, 1431, -254,
|
||||
-147, 1028, -3833, 14934, 24639, -5058, 1435, -256, -142, 1006, -3761, 14582, 24935, -5058, 1437, -258,
|
||||
-138, 984, -3687, 14231, 25228, -5055, 1438, -260, -133, 962, -3613, 13880, 25517, -5049, 1438, -262,
|
||||
-129, 939, -3537, 13529, 25802, -5039, 1438, -264, -125, 916, -3460, 13179, 26083, -5026, 1436, -265,
|
||||
-120, 893, -3382, 12830, 26359, -5008, 1433, -266, -116, 870, -3303, 12482, 26631, -4987, 1428, -267,
|
||||
-112, 847, -3224, 12135, 26898, -4962, 1423, -267, -107, 824, -3144, 11789, 27161, -4933, 1416, -268,
|
||||
-103, 800, -3063, 11444, 27418, -4900, 1409, -268, -99, 777, -2981, 11100, 27671, -4863, 1400, -268,
|
||||
-95, 753, -2899, 10759, 27919, -4822, 1389, -267, -91, 730, -2817, 10418, 28161, -4777, 1378, -267,
|
||||
-87, 706, -2734, 10080, 28399, -4728, 1365, -266, -83, 683, -2651, 9743, 28631, -4674, 1351, -265,
|
||||
-80, 659, -2567, 9408, 28857, -4617, 1336, -263, -76, 636, -2483, 9075, 29078, -4555, 1319, -261,
|
||||
-72, 613, -2399, 8745, 29293, -4488, 1301, -259, -69, 589, -2315, 8416, 29503, -4417, 1281, -257,
|
||||
-65, 566, -2231, 8091, 29707, -4342, 1261, -254, -62, 543, -2148, 7767, 29905, -4262, 1239, -251,
|
||||
-58, 520, -2064, 7446, 30097, -4178, 1215, -247, -55, 498, -1980, 7128, 30282, -4089, 1190, -244,
|
||||
-52, 475, -1896, 6813, 30462, -3996, 1164, -240, -49, 453, -1813, 6501, 30635, -3898, 1136, -235,
|
||||
-46, 431, -1730, 6191, 30802, -3796, 1107, -230, -43, 409, -1648, 5885, 30963, -3688, 1077, -225,
|
||||
-40, 387, -1566, 5582, 31117, -3577, 1045, -220, -37, 365, -1484, 5282, 31265, -3460, 1011, -214,
|
||||
-35, 344, -1403, 4985, 31406, -3339, 977, -207, -32, 323, -1322, 4692, 31540, -3213, 940, -201,
|
||||
-30, 303, -1242, 4403, 31668, -3082, 903, -194, -27, 282, -1163, 4116, 31789, -2947, 864, -186,
|
||||
-25, 262, -1084, 3834, 31903, -2807, 823, -178, -23, 242, -1007, 3555, 32010, -2662, 781, -170,
|
||||
-20, 223, -929, 3281, 32110, -2512, 738, -162, -18, 204, -853, 3010, 32204, -2357, 693, -152,
|
||||
-16, 185, -778, 2743, 32290, -2198, 646, -143, -14, 166, -703, 2480, 32369, -2034, 599, -133,
|
||||
-13, 148, -630, 2221, 32441, -1865, 550, -123, -11, 130, -557, 1966, 32506, -1692, 499, -112,
|
||||
-9, 113, -486, 1715, 32564, -1514, 447, -101, -8, 96, -415, 1469, 32615, -1331, 394, -90,
|
||||
-6, 79, -346, 1226, 32658, -1143, 340, -78, -5, 63, -277, 989, 32694, -950, 284, -66,
|
||||
-3, 47, -210, 755, 32723, -753, 226, -53, -2, 32, -144, 526, 32745, -552, 168, -40,
|
||||
-1, 17, -79, 301, 32760, -345, 108, -26, 0, 2, -16, 81, 32767, -134, 47, -12,
|
||||
};
|
||||
|
||||
private static short[] _highCurveLut2 = new short[]
|
||||
{
|
||||
418, -2538, 6118, 24615, 6298, -2563, 417, 0, 420, -2512, 5939, 24611, 6479, -2588, 415, 1,
|
||||
421, -2485, 5761, 24605, 6662, -2612, 412, 2, 422, -2458, 5585, 24595, 6846, -2635, 409, 3,
|
||||
423, -2430, 5410, 24582, 7030, -2658, 406, 4, 423, -2402, 5236, 24565, 7216, -2680, 403, 5,
|
||||
423, -2373, 5064, 24546, 7403, -2701, 399, 6, 423, -2343, 4893, 24523, 7591, -2721, 395, 7,
|
||||
423, -2313, 4724, 24496, 7780, -2741, 391, 8, 422, -2283, 4556, 24467, 7970, -2759, 386, 9,
|
||||
421, -2252, 4390, 24434, 8161, -2777, 381, 11, 420, -2221, 4225, 24398, 8353, -2794, 376, 12,
|
||||
419, -2190, 4062, 24359, 8545, -2810, 370, 14, 418, -2158, 3900, 24316, 8739, -2825, 364, 15,
|
||||
416, -2126, 3740, 24271, 8933, -2839, 358, 17, 414, -2093, 3582, 24222, 9127, -2851, 351, 19,
|
||||
412, -2060, 3425, 24170, 9323, -2863, 344, 21, 410, -2027, 3270, 24115, 9519, -2874, 336, 22,
|
||||
407, -1993, 3117, 24056, 9715, -2884, 328, 24, 404, -1960, 2966, 23995, 9912, -2893, 319, 26,
|
||||
402, -1926, 2816, 23930, 10110, -2900, 311, 29, 398, -1892, 2668, 23863, 10308, -2907, 301, 31,
|
||||
395, -1858, 2522, 23792, 10506, -2912, 292, 33, 392, -1823, 2378, 23718, 10705, -2916, 282, 35,
|
||||
389, -1789, 2235, 23641, 10904, -2919, 271, 38, 385, -1754, 2095, 23561, 11103, -2920, 261, 40,
|
||||
381, -1719, 1956, 23478, 11303, -2921, 249, 43, 377, -1684, 1819, 23393, 11502, -2920, 238, 45,
|
||||
373, -1649, 1684, 23304, 11702, -2917, 225, 48, 369, -1615, 1551, 23212, 11902, -2914, 213, 51,
|
||||
365, -1580, 1420, 23118, 12102, -2909, 200, 54, 361, -1545, 1291, 23020, 12302, -2902, 186, 57,
|
||||
356, -1510, 1163, 22920, 12502, -2895, 173, 60, 352, -1475, 1038, 22817, 12702, -2885, 158, 63,
|
||||
347, -1440, 915, 22711, 12901, -2875, 143, 66, 342, -1405, 793, 22602, 13101, -2863, 128, 69,
|
||||
338, -1370, 674, 22491, 13300, -2849, 113, 73, 333, -1335, 557, 22377, 13499, -2834, 97, 76,
|
||||
328, -1301, 441, 22260, 13698, -2817, 80, 80, 323, -1266, 328, 22141, 13896, -2799, 63, 83,
|
||||
318, -1232, 217, 22019, 14094, -2779, 46, 87, 313, -1197, 107, 21894, 14291, -2758, 28, 91,
|
||||
307, -1163, 0, 21767, 14488, -2735, 9, 95, 302, -1129, -105, 21637, 14684, -2710, -9, 98,
|
||||
297, -1096, -208, 21506, 14879, -2684, -29, 102, 292, -1062, -310, 21371, 15074, -2656, -48, 106,
|
||||
286, -1029, -409, 21234, 15268, -2626, -69, 111, 281, -996, -506, 21095, 15461, -2595, -89, 115,
|
||||
276, -963, -601, 20954, 15654, -2562, -110, 119, 270, -930, -694, 20810, 15846, -2527, -132, 123,
|
||||
265, -898, -785, 20664, 16036, -2490, -154, 128, 260, -866, -874, 20516, 16226, -2452, -176, 132,
|
||||
254, -834, -961, 20366, 16415, -2411, -199, 137, 249, -803, -1046, 20213, 16602, -2369, -222, 141,
|
||||
243, -771, -1129, 20059, 16789, -2326, -246, 146, 238, -740, -1209, 19902, 16974, -2280, -270, 151,
|
||||
233, -710, -1288, 19744, 17158, -2232, -294, 156, 227, -680, -1365, 19583, 17341, -2183, -319, 160,
|
||||
222, -650, -1440, 19421, 17523, -2132, -345, 165, 217, -620, -1513, 19257, 17703, -2079, -370, 170,
|
||||
211, -591, -1583, 19091, 17882, -2023, -396, 175, 206, -562, -1652, 18923, 18059, -1966, -423, 180,
|
||||
201, -533, -1719, 18754, 18235, -1907, -450, 185, 196, -505, -1784, 18582, 18410, -1847, -477, 191,
|
||||
191, -477, -1847, 18410, 18582, -1784, -505, 196, 185, -450, -1907, 18235, 18754, -1719, -533, 201,
|
||||
180, -423, -1966, 18059, 18923, -1652, -562, 206, 175, -396, -2023, 17882, 19091, -1583, -591, 211,
|
||||
170, -370, -2079, 17703, 19257, -1513, -620, 217, 165, -345, -2132, 17523, 19421, -1440, -650, 222,
|
||||
160, -319, -2183, 17341, 19583, -1365, -680, 227, 156, -294, -2232, 17158, 19744, -1288, -710, 233,
|
||||
151, -270, -2280, 16974, 19902, -1209, -740, 238, 146, -246, -2326, 16789, 20059, -1129, -771, 243,
|
||||
141, -222, -2369, 16602, 20213, -1046, -803, 249, 137, -199, -2411, 16415, 20366, -961, -834, 254,
|
||||
132, -176, -2452, 16226, 20516, -874, -866, 260, 128, -154, -2490, 16036, 20664, -785, -898, 265,
|
||||
123, -132, -2527, 15846, 20810, -694, -930, 270, 119, -110, -2562, 15654, 20954, -601, -963, 276,
|
||||
115, -89, -2595, 15461, 21095, -506, -996, 281, 111, -69, -2626, 15268, 21234, -409, -1029, 286,
|
||||
106, -48, -2656, 15074, 21371, -310, -1062, 292, 102, -29, -2684, 14879, 21506, -208, -1096, 297,
|
||||
98, -9, -2710, 14684, 21637, -105, -1129, 302, 95, 9, -2735, 14488, 21767, 0, -1163, 307,
|
||||
91, 28, -2758, 14291, 21894, 107, -1197, 313, 87, 46, -2779, 14094, 22019, 217, -1232, 318,
|
||||
83, 63, -2799, 13896, 22141, 328, -1266, 323, 80, 80, -2817, 13698, 22260, 441, -1301, 328,
|
||||
76, 97, -2834, 13499, 22377, 557, -1335, 333, 73, 113, -2849, 13300, 22491, 674, -1370, 338,
|
||||
69, 128, -2863, 13101, 22602, 793, -1405, 342, 66, 143, -2875, 12901, 22711, 915, -1440, 347,
|
||||
63, 158, -2885, 12702, 22817, 1038, -1475, 352, 60, 173, -2895, 12502, 22920, 1163, -1510, 356,
|
||||
57, 186, -2902, 12302, 23020, 1291, -1545, 361, 54, 200, -2909, 12102, 23118, 1420, -1580, 365,
|
||||
51, 213, -2914, 11902, 23212, 1551, -1615, 369, 48, 225, -2917, 11702, 23304, 1684, -1649, 373,
|
||||
45, 238, -2920, 11502, 23393, 1819, -1684, 377, 43, 249, -2921, 11303, 23478, 1956, -1719, 381,
|
||||
40, 261, -2920, 11103, 23561, 2095, -1754, 385, 38, 271, -2919, 10904, 23641, 2235, -1789, 389,
|
||||
35, 282, -2916, 10705, 23718, 2378, -1823, 392, 33, 292, -2912, 10506, 23792, 2522, -1858, 395,
|
||||
31, 301, -2907, 10308, 23863, 2668, -1892, 398, 29, 311, -2900, 10110, 23930, 2816, -1926, 402,
|
||||
26, 319, -2893, 9912, 23995, 2966, -1960, 404, 24, 328, -2884, 9715, 24056, 3117, -1993, 407,
|
||||
22, 336, -2874, 9519, 24115, 3270, -2027, 410, 21, 344, -2863, 9323, 24170, 3425, -2060, 412,
|
||||
19, 351, -2851, 9127, 24222, 3582, -2093, 414, 17, 358, -2839, 8933, 24271, 3740, -2126, 416,
|
||||
15, 364, -2825, 8739, 24316, 3900, -2158, 418, 14, 370, -2810, 8545, 24359, 4062, -2190, 419,
|
||||
12, 376, -2794, 8353, 24398, 4225, -2221, 420, 11, 381, -2777, 8161, 24434, 4390, -2252, 421,
|
||||
9, 386, -2759, 7970, 24467, 4556, -2283, 422, 8, 391, -2741, 7780, 24496, 4724, -2313, 423,
|
||||
7, 395, -2721, 7591, 24523, 4893, -2343, 423, 6, 399, -2701, 7403, 24546, 5064, -2373, 423,
|
||||
5, 403, -2680, 7216, 24565, 5236, -2402, 423, 4, 406, -2658, 7030, 24582, 5410, -2430, 423,
|
||||
3, 409, -2635, 6846, 24595, 5585, -2458, 422, 2, 412, -2612, 6662, 24605, 5761, -2485, 421,
|
||||
1, 415, -2588, 6479, 24611, 5939, -2512, 420, 0, 417, -2563, 6298, 24615, 6118, -2538, 418,
|
||||
};
|
||||
#endregion
|
||||
|
||||
private static float[] _normalCurveLut0F;
|
||||
private static float[] _normalCurveLut1F;
|
||||
private static float[] _normalCurveLut2F;
|
||||
|
||||
private static float[] _highCurveLut0F;
|
||||
private static float[] _highCurveLut1F;
|
||||
private static float[] _highCurveLut2F;
|
||||
|
||||
static ResamplerHelper()
|
||||
{
|
||||
_normalCurveLut0F = _normalCurveLut0.Select(x => x / 32768f).ToArray();
|
||||
_normalCurveLut1F = _normalCurveLut1.Select(x => x / 32768f).ToArray();
|
||||
_normalCurveLut2F = _normalCurveLut2.Select(x => x / 32768f).ToArray();
|
||||
|
||||
_highCurveLut0F = _highCurveLut0.Select(x => x / 32768f).ToArray();
|
||||
_highCurveLut1F = _highCurveLut1.Select(x => x / 32768f).ToArray();
|
||||
_highCurveLut2F = _highCurveLut2.Select(x => x / 32768f).ToArray();
|
||||
}
|
||||
|
||||
private const int FixedPointPrecision = 15;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Resample(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount, SampleRateConversionQuality srcQuality, bool needPitch)
|
||||
{
|
||||
switch (srcQuality)
|
||||
{
|
||||
case SampleRateConversionQuality.Default:
|
||||
ResampleDefaultQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount, needPitch);
|
||||
break;
|
||||
case SampleRateConversionQuality.Low:
|
||||
ResampleLowQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount);
|
||||
break;
|
||||
case SampleRateConversionQuality.High:
|
||||
ResampleHighQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"{srcQuality}");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ReadOnlySpan<float> GetDefaultParameter(float ratio)
|
||||
{
|
||||
if (ratio <= 1.0f)
|
||||
{
|
||||
return _normalCurveLut1F;
|
||||
}
|
||||
else if (ratio > 1.333313f)
|
||||
{
|
||||
return _normalCurveLut0F;
|
||||
}
|
||||
|
||||
return _normalCurveLut2F;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe static void ResampleDefaultQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount, bool needPitch)
|
||||
{
|
||||
ReadOnlySpan<float> parameters = GetDefaultParameter(ratio);
|
||||
|
||||
int inputBufferIndex = 0, i = 0;
|
||||
|
||||
// TODO: REV8 fast path (when needPitch == false the input index progression is constant + we need SIMD)
|
||||
|
||||
if (Sse41.IsSupported)
|
||||
{
|
||||
if (ratio == 1f)
|
||||
{
|
||||
fixed (short* pInput = inputBuffer)
|
||||
{
|
||||
fixed (float* pOutput = outputBuffer, pParameters = parameters)
|
||||
{
|
||||
Vector128<float> parameter = Sse.LoadVector128(pParameters);
|
||||
|
||||
for (; i < (sampleCount & ~3); i += 4)
|
||||
{
|
||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + (uint)i);
|
||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 1);
|
||||
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 2);
|
||||
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 3);
|
||||
|
||||
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||
|
||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter);
|
||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter);
|
||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter);
|
||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter);
|
||||
|
||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
||||
|
||||
Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
|
||||
|
||||
Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputBufferIndex = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
fixed (short* pInput = inputBuffer)
|
||||
{
|
||||
fixed (float* pOutput = outputBuffer, pParameters = parameters)
|
||||
{
|
||||
for (; i < (sampleCount & ~3); i += 4)
|
||||
{
|
||||
uint baseIndex0 = (uint)(fraction * 128) * 4;
|
||||
uint inputIndex0 = (uint)inputBufferIndex;
|
||||
|
||||
fraction += ratio;
|
||||
|
||||
uint baseIndex1 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex1 = (uint)inputBufferIndex + (uint)fraction;
|
||||
|
||||
fraction += ratio;
|
||||
|
||||
uint baseIndex2 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex2 = (uint)inputBufferIndex + (uint)fraction;
|
||||
|
||||
fraction += ratio;
|
||||
|
||||
uint baseIndex3 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex3 = (uint)inputBufferIndex + (uint)fraction;
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)fraction;
|
||||
|
||||
// Only keep lower part (safe as fraction isn't supposed to be negative)
|
||||
fraction -= (int)fraction;
|
||||
|
||||
Vector128<float> parameter0 = Sse.LoadVector128(pParameters + baseIndex0);
|
||||
Vector128<float> parameter1 = Sse.LoadVector128(pParameters + baseIndex1);
|
||||
Vector128<float> parameter2 = Sse.LoadVector128(pParameters + baseIndex2);
|
||||
Vector128<float> parameter3 = Sse.LoadVector128(pParameters + baseIndex3);
|
||||
|
||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + inputIndex0);
|
||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + inputIndex1);
|
||||
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + inputIndex2);
|
||||
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + inputIndex3);
|
||||
|
||||
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||
|
||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter0);
|
||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter1);
|
||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter2);
|
||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter3);
|
||||
|
||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
||||
|
||||
Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
|
||||
|
||||
Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < sampleCount; i++)
|
||||
{
|
||||
int baseIndex = (int)(fraction * 128) * 4;
|
||||
ReadOnlySpan<float> parameter = parameters.Slice(baseIndex, 4);
|
||||
ReadOnlySpan<short> currentInput = inputBuffer.Slice(inputBufferIndex, 4);
|
||||
|
||||
outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] +
|
||||
currentInput[1] * parameter[1] +
|
||||
currentInput[2] * parameter[2] +
|
||||
currentInput[3] * parameter[3]);
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)fraction;
|
||||
|
||||
// Only keep lower part (safe as fraction isn't supposed to be negative)
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ReadOnlySpan<float> GetHighParameter(float ratio)
|
||||
{
|
||||
if (ratio <= 1.0f)
|
||||
{
|
||||
return _highCurveLut1F;
|
||||
}
|
||||
else if (ratio > 1.333313f)
|
||||
{
|
||||
return _highCurveLut0F;
|
||||
}
|
||||
|
||||
return _highCurveLut2F;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ResampleHighQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<float> parameters = GetHighParameter(ratio);
|
||||
|
||||
int inputBufferIndex = 0;
|
||||
|
||||
// TODO: fast path
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
int baseIndex = (int)(fraction * 128) * 8;
|
||||
ReadOnlySpan<float> parameter = parameters.Slice(baseIndex, 8);
|
||||
ReadOnlySpan<short> currentInput = inputBuffer.Slice(inputBufferIndex, 8);
|
||||
|
||||
outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] +
|
||||
currentInput[1] * parameter[1] +
|
||||
currentInput[2] * parameter[2] +
|
||||
currentInput[3] * parameter[3] +
|
||||
currentInput[4] * parameter[4] +
|
||||
currentInput[5] * parameter[5] +
|
||||
currentInput[6] * parameter[6] +
|
||||
currentInput[7] * parameter[7]);
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
||||
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ResampleLowQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
||||
{
|
||||
int inputBufferIndex = 0;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
int outputData = inputBuffer[inputBufferIndex];
|
||||
|
||||
if (fraction > 1.0f)
|
||||
{
|
||||
outputData = inputBuffer[inputBufferIndex + 1];
|
||||
}
|
||||
|
||||
outputBuffer[i] = outputData;
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
||||
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ResampleForUpsampler(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
||||
{
|
||||
// TODO: use a bandwidth filter to have better resampling.
|
||||
int inputBufferIndex = 0;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
float outputData = inputBuffer[inputBufferIndex];
|
||||
|
||||
if (fraction > 1.0f)
|
||||
{
|
||||
outputData = inputBuffer[inputBufferIndex + 1];
|
||||
}
|
||||
|
||||
outputBuffer[i] = outputData;
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
||||
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
Ryujinx.Audio.Renderer/Dsp/State/AdpcmLoopContext.cs
Normal file
29
Ryujinx.Audio.Renderer/Dsp/State/AdpcmLoopContext.cs
Normal file
@ -0,0 +1,29 @@
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 6)]
|
||||
public struct AdpcmLoopContext
|
||||
{
|
||||
public short PredScale;
|
||||
public short History0;
|
||||
public short History1;
|
||||
}
|
||||
}
|
60
Ryujinx.Audio.Renderer/Dsp/State/AuxiliaryBufferHeader.cs
Normal file
60
Ryujinx.Audio.Renderer/Dsp/State/AuxiliaryBufferHeader.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Cpu;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x40)]
|
||||
public struct AuxiliaryBufferHeader
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0xC)]
|
||||
public struct AuxiliaryBufferInfo
|
||||
{
|
||||
private const uint ReadOffsetPosition = 0x0;
|
||||
private const uint WriteOffsetPosition = 0x4;
|
||||
|
||||
public uint ReadOffset;
|
||||
public uint WriteOffset;
|
||||
private uint _reserved;
|
||||
|
||||
public static uint GetReadOffset(MemoryManager manager, ulong bufferAddress)
|
||||
{
|
||||
return manager.Read<uint>(bufferAddress + ReadOffsetPosition);
|
||||
}
|
||||
|
||||
public static uint GetWriteOffset(MemoryManager manager, ulong bufferAddress)
|
||||
{
|
||||
return manager.Read<uint>(bufferAddress + WriteOffsetPosition);
|
||||
}
|
||||
|
||||
public static void SetReadOffset(MemoryManager manager, ulong bufferAddress, uint value)
|
||||
{
|
||||
manager.Write(bufferAddress + ReadOffsetPosition, value);
|
||||
}
|
||||
|
||||
public static void SetWriteOffset(MemoryManager manager, ulong bufferAddress, uint value)
|
||||
{
|
||||
manager.Write(bufferAddress + WriteOffsetPosition, value);
|
||||
}
|
||||
}
|
||||
|
||||
public AuxiliaryBufferInfo BufferInfo;
|
||||
public unsafe fixed uint Unknown[13];
|
||||
}
|
||||
}
|
28
Ryujinx.Audio.Renderer/Dsp/State/BiquadFilterState.cs
Normal file
28
Ryujinx.Audio.Renderer/Dsp/State/BiquadFilterState.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
|
||||
public struct BiquadFilterState
|
||||
{
|
||||
public float Z1;
|
||||
public float Z2;
|
||||
}
|
||||
}
|
73
Ryujinx.Audio.Renderer/Dsp/State/DelayState.cs
Normal file
73
Ryujinx.Audio.Renderer/Dsp/State/DelayState.cs
Normal file
@ -0,0 +1,73 @@
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
{
|
||||
public class DelayState
|
||||
{
|
||||
public DelayLine[] DelayLines { get; }
|
||||
public float[] LowPassZ { get; set; }
|
||||
public float FeedbackGain { get; private set; }
|
||||
public float DelayFeedbackBaseGain { get; private set; }
|
||||
public float DelayFeedbackCrossGain { get; private set; }
|
||||
public float LowPassFeedbackGain { get; private set; }
|
||||
public float LowPassBaseGain { get; private set; }
|
||||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
public DelayState(ref DelayParameter parameter, ulong workBuffer)
|
||||
{
|
||||
DelayLines = new DelayLine[parameter.ChannelCount];
|
||||
LowPassZ = new float[parameter.ChannelCount];
|
||||
|
||||
uint sampleRate = (uint)FixedPointHelper.ToInt(parameter.SampleRate, FixedPointPrecision) / 1000;
|
||||
|
||||
for (int i = 0; i < DelayLines.Length; i++)
|
||||
{
|
||||
DelayLines[i] = new DelayLine(sampleRate, parameter.DelayTimeMax);
|
||||
DelayLines[i].SetDelay(parameter.DelayTime);
|
||||
LowPassZ[0] = 0;
|
||||
}
|
||||
|
||||
UpdateParameter(ref parameter);
|
||||
}
|
||||
|
||||
public void UpdateParameter(ref DelayParameter parameter)
|
||||
{
|
||||
FeedbackGain = FixedPointHelper.ToFloat(parameter.FeedbackGain, FixedPointPrecision) * 0.98f;
|
||||
|
||||
float channelSpread = FixedPointHelper.ToFloat(parameter.ChannelSpread, FixedPointPrecision);
|
||||
|
||||
DelayFeedbackBaseGain = (1.0f - channelSpread) * FeedbackGain;
|
||||
|
||||
if (parameter.ChannelCount == 4 || parameter.ChannelCount == 6)
|
||||
{
|
||||
DelayFeedbackCrossGain = channelSpread * 0.5f * FeedbackGain;
|
||||
}
|
||||
else
|
||||
{
|
||||
DelayFeedbackCrossGain = channelSpread * FeedbackGain;
|
||||
}
|
||||
|
||||
LowPassFeedbackGain = 0.95f * FixedPointHelper.ToFloat(parameter.LowPassAmount, FixedPointPrecision);
|
||||
LowPassBaseGain = 1.0f - LowPassFeedbackGain;
|
||||
}
|
||||
}
|
||||
}
|
136
Ryujinx.Audio.Renderer/Dsp/State/Reverb3dState.cs
Normal file
136
Ryujinx.Audio.Renderer/Dsp/State/Reverb3dState.cs
Normal file
@ -0,0 +1,136 @@
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
{
|
||||
public class Reverb3dState
|
||||
{
|
||||
private readonly float[] FdnDelayMinTimes = new float[4] { 5.0f, 6.0f, 13.0f, 14.0f };
|
||||
private readonly float[] FdnDelayMaxTimes = new float[4] { 45.704f, 82.782f, 149.94f, 271.58f };
|
||||
private readonly float[] DecayDelayMaxTimes1 = new float[4] { 17.0f, 13.0f, 9.0f, 7.0f };
|
||||
private readonly float[] DecayDelayMaxTimes2 = new float[4] { 19.0f, 11.0f, 10.0f, 6.0f };
|
||||
private readonly float[] EarlyDelayTimes = new float[20] { 0.017136f, 0.059154f, 0.16173f, 0.39019f, 0.42526f, 0.45541f, 0.68974f, 0.74591f, 0.83384f, 0.8595f, 0.0f, 0.075024f, 0.16879f, 0.2999f, 0.33744f, 0.3719f, 0.59901f, 0.71674f, 0.81786f, 0.85166f };
|
||||
public readonly float[] EarlyGain = new float[20] { 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f, 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f };
|
||||
|
||||
public IDelayLine[] FdnDelayLines { get; }
|
||||
public DecayDelay[] DecayDelays1 { get; }
|
||||
public DecayDelay[] DecayDelays2 { get; }
|
||||
public IDelayLine PreDelayLine { get; }
|
||||
public IDelayLine BackLeftDelayLine { get; }
|
||||
public float DryGain { get; private set; }
|
||||
public uint[] EarlyDelayTime { get; private set; }
|
||||
public float PreviousPreDelayValue { get; set; }
|
||||
public float PreviousPreDelayGain { get; private set; }
|
||||
public float TargetPreDelayGain { get; private set; }
|
||||
public float EarlyReflectionsGain { get; private set; }
|
||||
public float LateReverbGain { get; private set; }
|
||||
public uint ReflectionDelayTime { get; private set; }
|
||||
public float EchoLateReverbDecay { get; private set; }
|
||||
public float[] DecayDirectFdnGain { get; private set; }
|
||||
public float[] DecayCurrentFdnGain { get; private set; }
|
||||
public float[] DecayCurrentOutputGain { get; private set; }
|
||||
public float[] PreviousFeedbackOutputDecayed { get; private set; }
|
||||
|
||||
public Reverb3dState(ref Reverb3dParameter parameter, ulong workBuffer)
|
||||
{
|
||||
FdnDelayLines = new IDelayLine[4];
|
||||
DecayDelays1 = new DecayDelay[4];
|
||||
DecayDelays2 = new DecayDelay[4];
|
||||
DecayDirectFdnGain = new float[4];
|
||||
DecayCurrentFdnGain = new float[4];
|
||||
DecayCurrentOutputGain = new float[4];
|
||||
PreviousFeedbackOutputDecayed = new float[4];
|
||||
|
||||
uint sampleRate = parameter.SampleRate / 1000;
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
FdnDelayLines[i] = new DelayLine3d(sampleRate, FdnDelayMaxTimes[i]);
|
||||
DecayDelays1[i] = new DecayDelay(new DelayLine3d(sampleRate, DecayDelayMaxTimes1[i]));
|
||||
DecayDelays2[i] = new DecayDelay(new DelayLine3d(sampleRate, DecayDelayMaxTimes2[i]));
|
||||
}
|
||||
|
||||
PreDelayLine = new DelayLine3d(sampleRate, 400);
|
||||
BackLeftDelayLine = new DelayLine3d(sampleRate, 5);
|
||||
|
||||
UpdateParameter(ref parameter);
|
||||
}
|
||||
|
||||
public void UpdateParameter(ref Reverb3dParameter parameter)
|
||||
{
|
||||
uint sampleRate = parameter.SampleRate / 1000;
|
||||
|
||||
EarlyDelayTime = new uint[20];
|
||||
DryGain = parameter.DryGain;
|
||||
PreviousFeedbackOutputDecayed.AsSpan().Fill(0);
|
||||
PreviousPreDelayValue = 0;
|
||||
|
||||
EarlyReflectionsGain = FloatingPointHelper.Pow10(Math.Min(parameter.RoomGain + parameter.ReflectionsGain, 5000.0f) / 2000.0f);
|
||||
LateReverbGain = FloatingPointHelper.Pow10(Math.Min(parameter.RoomGain + parameter.ReverbGain, 5000.0f) / 2000.0f);
|
||||
|
||||
float highFrequencyRoomGain = FloatingPointHelper.Pow10(parameter.RoomHf / 2000.0f);
|
||||
|
||||
if (highFrequencyRoomGain < 1.0f)
|
||||
{
|
||||
float tempA = 1.0f - highFrequencyRoomGain;
|
||||
float tempB = 2.0f - ((2.0f * highFrequencyRoomGain) * FloatingPointHelper.Cos(256.0f * parameter.HfReference / parameter.SampleRate));
|
||||
float tempC = MathF.Sqrt(MathF.Pow(tempB, 2) - (4.0f * (1.0f - highFrequencyRoomGain) * (1.0f - highFrequencyRoomGain)));
|
||||
|
||||
PreviousPreDelayGain = (tempB - tempC) / (2.0f * tempA);
|
||||
TargetPreDelayGain = 1.0f - PreviousPreDelayGain;
|
||||
}
|
||||
else
|
||||
{
|
||||
PreviousPreDelayGain = 0.0f;
|
||||
TargetPreDelayGain = 1.0f;
|
||||
}
|
||||
|
||||
ReflectionDelayTime = IDelayLine.GetSampleCount(sampleRate, 1000.0f * (parameter.ReflectionDelay + parameter.ReverbDelayTime));
|
||||
EchoLateReverbDecay = 0.6f * parameter.Diffusion * 0.01f;
|
||||
|
||||
for (int i = 0; i < FdnDelayLines.Length; i++)
|
||||
{
|
||||
FdnDelayLines[i].SetDelay(FdnDelayMinTimes[i] + (parameter.Density / 100 * (FdnDelayMaxTimes[i] - FdnDelayMinTimes[i])));
|
||||
|
||||
uint tempSampleCount = FdnDelayLines[i].CurrentSampleCount + DecayDelays1[i].CurrentSampleCount + DecayDelays2[i].CurrentSampleCount;
|
||||
|
||||
float tempA = (-60.0f * tempSampleCount) / (parameter.DecayTime * parameter.SampleRate);
|
||||
float tempB = tempA / parameter.HfDecayRatio;
|
||||
float tempC = FloatingPointHelper.Cos(128.0f * 0.5f * parameter.HfReference / parameter.SampleRate) / FloatingPointHelper.Sin(128.0f * 0.5f * parameter.HfReference / parameter.SampleRate);
|
||||
float tempD = FloatingPointHelper.Pow10((tempB - tempA) / 40.0f);
|
||||
float tempE = FloatingPointHelper.Pow10((tempB + tempA) / 40.0f) * 0.7071f;
|
||||
|
||||
DecayDirectFdnGain[i] = tempE * ((tempD * tempC) + 1.0f) / (tempC + tempD);
|
||||
DecayCurrentFdnGain[i] = tempE * (1.0f - (tempD * tempC)) / (tempC + tempD);
|
||||
DecayCurrentOutputGain[i] = (tempC - tempD) / (tempC + tempD);
|
||||
|
||||
DecayDelays1[i].SetDecayRate(EchoLateReverbDecay);
|
||||
DecayDelays2[i].SetDecayRate(EchoLateReverbDecay * -0.9f);
|
||||
}
|
||||
|
||||
for (int i = 0; i < EarlyDelayTime.Length; i++)
|
||||
{
|
||||
uint sampleCount = Math.Min(IDelayLine.GetSampleCount(sampleRate, (parameter.ReflectionDelay * 1000.0f) + (EarlyDelayTimes[i] * 1000.0f * ((parameter.ReverbDelayTime * 0.9998f) + 0.02f))), PreDelayLine.SampleCountMax);
|
||||
EarlyDelayTime[i] = sampleCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
221
Ryujinx.Audio.Renderer/Dsp/State/ReverbState.cs
Normal file
221
Ryujinx.Audio.Renderer/Dsp/State/ReverbState.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
{
|
||||
public class ReverbState
|
||||
{
|
||||
private static readonly float[] FdnDelayTimes = new float[20]
|
||||
{
|
||||
// Room
|
||||
53.953247f, 79.192566f, 116.238770f, 130.615295f,
|
||||
// Hall
|
||||
53.953247f, 79.192566f, 116.238770f, 170.615295f,
|
||||
// Plate
|
||||
5f, 10f, 5f, 10f,
|
||||
// Cathedral
|
||||
47.03f, 71f, 103f, 170f,
|
||||
// Max delay (Hall is the one with the highest values so identical to Hall)
|
||||
53.953247f, 79.192566f, 116.238770f, 170.615295f,
|
||||
};
|
||||
|
||||
private static readonly float[] DecayDelayTimes = new float[20]
|
||||
{
|
||||
// Room
|
||||
7f, 9f, 13f, 17f,
|
||||
// Hall
|
||||
7f, 9f, 13f, 17f,
|
||||
// Plate (no decay)
|
||||
1f, 1f, 1f, 1f,
|
||||
// Cathedral
|
||||
7f, 7f, 13f, 9f,
|
||||
// Max delay (Hall is the one with the highest values so identical to Hall)
|
||||
7f, 9f, 13f, 17f,
|
||||
};
|
||||
|
||||
private static readonly float[] EarlyDelayTimes = new float[50]
|
||||
{
|
||||
// Room
|
||||
0.0f, 3.5f, 2.8f, 3.9f, 2.7f, 13.4f, 7.9f, 8.4f, 9.9f, 12.0f,
|
||||
// Chamber
|
||||
0.0f, 11.8f, 5.5f, 11.2f, 10.4f, 38.1f, 22.2f, 29.6f, 21.2f, 24.8f,
|
||||
// Hall
|
||||
0.0f, 41.5f, 20.5f, 41.3f, 0.0f, 29.5f, 33.8f, 45.2f, 46.8f, 0.0f,
|
||||
// Cathedral
|
||||
33.1f, 43.3f, 22.8f, 37.9f, 14.9f, 35.3f, 17.9f, 34.2f, 0.0f, 43.3f,
|
||||
// Disabled
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f
|
||||
};
|
||||
|
||||
private static readonly float[] EarlyGainBase = new float[50]
|
||||
{
|
||||
// Room
|
||||
0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f,
|
||||
// Chamber
|
||||
0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, 0.68f, 0.68f,
|
||||
// Hall
|
||||
0.50f, 0.70f, 0.70f, 0.68f, 0.50f, 0.68f, 0.68f, 0.70f, 0.68f, 0.00f,
|
||||
// Cathedral
|
||||
0.93f, 0.92f, 0.87f, 0.86f, 0.94f, 0.81f, 0.80f, 0.77f, 0.76f, 0.65f,
|
||||
// Disabled
|
||||
0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f
|
||||
};
|
||||
|
||||
private static readonly float[] PreDelayTimes = new float[5]
|
||||
{
|
||||
// Room
|
||||
12.5f,
|
||||
// Chamber
|
||||
40.0f,
|
||||
// Hall
|
||||
50.0f,
|
||||
// Cathedral
|
||||
50.0f,
|
||||
// Disabled
|
||||
0.0f
|
||||
};
|
||||
|
||||
public IDelayLine[] FdnDelayLines { get; }
|
||||
public DecayDelay[] DecayDelays { get; }
|
||||
public IDelayLine PreDelayLine { get; }
|
||||
public IDelayLine BackLeftDelayLine { get; }
|
||||
public uint[] EarlyDelayTime { get; }
|
||||
public float[] EarlyGain { get; }
|
||||
public uint PreDelayLineDelayTime { get; private set; }
|
||||
|
||||
public float[] HighFrequencyDecayDirectGain { get; }
|
||||
public float[] HighFrequencyDecayPreviousGain { get; }
|
||||
public float[] PreviousFeedbackOutput { get; }
|
||||
|
||||
public const int EarlyModeCount = 10;
|
||||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
private ReadOnlySpan<float> GetFdnDelayTimesByLateMode(ReverbLateMode lateMode)
|
||||
{
|
||||
return FdnDelayTimes.AsSpan().Slice((int)lateMode * 4, 4);
|
||||
}
|
||||
|
||||
private ReadOnlySpan<float> GetDecayDelayTimesByLateMode(ReverbLateMode lateMode)
|
||||
{
|
||||
return DecayDelayTimes.AsSpan().Slice((int)lateMode * 4, 4);
|
||||
}
|
||||
|
||||
public ReverbState(ref ReverbParameter parameter, ulong workBuffer, bool isLongSizePreDelaySupported)
|
||||
{
|
||||
FdnDelayLines = new IDelayLine[4];
|
||||
DecayDelays = new DecayDelay[4];
|
||||
EarlyDelayTime = new uint[EarlyModeCount];
|
||||
EarlyGain = new float[EarlyModeCount];
|
||||
HighFrequencyDecayDirectGain = new float[4];
|
||||
HighFrequencyDecayPreviousGain = new float[4];
|
||||
PreviousFeedbackOutput = new float[4];
|
||||
|
||||
ReadOnlySpan<float> fdnDelayTimes = GetFdnDelayTimesByLateMode(ReverbLateMode.Limit);
|
||||
ReadOnlySpan<float> decayDelayTimes = GetDecayDelayTimesByLateMode(ReverbLateMode.Limit);
|
||||
|
||||
uint sampleRate = (uint)FixedPointHelper.ToFloat((uint)parameter.SampleRate, FixedPointPrecision);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
FdnDelayLines[i] = new DelayLine(sampleRate, fdnDelayTimes[i]);
|
||||
DecayDelays[i] = new DecayDelay(new DelayLine(sampleRate, decayDelayTimes[i]));
|
||||
}
|
||||
|
||||
float preDelayTimeMax = 150.0f;
|
||||
|
||||
if (isLongSizePreDelaySupported)
|
||||
{
|
||||
preDelayTimeMax = 350.0f;
|
||||
}
|
||||
|
||||
PreDelayLine = new DelayLine(sampleRate, preDelayTimeMax);
|
||||
BackLeftDelayLine = new DelayLine(sampleRate, 5.0f);
|
||||
|
||||
UpdateParameter(ref parameter);
|
||||
}
|
||||
|
||||
public void UpdateParameter(ref ReverbParameter parameter)
|
||||
{
|
||||
uint sampleRate = (uint)FixedPointHelper.ToFloat((uint)parameter.SampleRate, FixedPointPrecision);
|
||||
|
||||
float preDelayTimeInMilliseconds = FixedPointHelper.ToFloat(parameter.PreDelayTime, FixedPointPrecision);
|
||||
float earlyGain = FixedPointHelper.ToFloat(parameter.EarlyGain, FixedPointPrecision);
|
||||
float coloration = FixedPointHelper.ToFloat(parameter.Coloration, FixedPointPrecision);
|
||||
float decayTime = FixedPointHelper.ToFloat(parameter.DecayTime, FixedPointPrecision);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
EarlyDelayTime[i] = Math.Min(IDelayLine.GetSampleCount(sampleRate, EarlyDelayTimes[i] + preDelayTimeInMilliseconds), PreDelayLine.SampleCountMax) + 1;
|
||||
EarlyGain[i] = EarlyGainBase[i] * earlyGain;
|
||||
}
|
||||
|
||||
if (parameter.ChannelCount == 2)
|
||||
{
|
||||
EarlyGain[4] = EarlyGain[4] * 0.5f;
|
||||
EarlyGain[5] = EarlyGain[5] * 0.5f;
|
||||
}
|
||||
|
||||
PreDelayLineDelayTime = Math.Min(IDelayLine.GetSampleCount(sampleRate, PreDelayTimes[(int)parameter.EarlyMode] + preDelayTimeInMilliseconds), PreDelayLine.SampleCountMax);
|
||||
|
||||
ReadOnlySpan<float> fdnDelayTimes = GetFdnDelayTimesByLateMode(parameter.LateMode);
|
||||
ReadOnlySpan<float> decayDelayTimes = GetDecayDelayTimesByLateMode(parameter.LateMode);
|
||||
|
||||
float highFrequencyDecayRatio = FixedPointHelper.ToFloat(parameter.HighFrequencyDecayRatio, FixedPointPrecision);
|
||||
float highFrequencyUnknownValue = FloatingPointHelper.Cos(1280.0f / sampleRate);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
FdnDelayLines[i].SetDelay(fdnDelayTimes[i]);
|
||||
DecayDelays[i].SetDelay(decayDelayTimes[i]);
|
||||
|
||||
float tempA = -3 * (DecayDelays[i].CurrentSampleCount + FdnDelayLines[i].CurrentSampleCount);
|
||||
float tempB = tempA / (decayTime * sampleRate);
|
||||
float tempC;
|
||||
float tempD;
|
||||
|
||||
if (highFrequencyDecayRatio < 0.995f)
|
||||
{
|
||||
float tempE = FloatingPointHelper.Pow10((((1.0f / highFrequencyDecayRatio) - 1.0f) * 2) / 100 * (tempB / 10));
|
||||
float tempF = 1.0f - tempE;
|
||||
float tempG = 2.0f - (tempE * 2 * highFrequencyUnknownValue);
|
||||
float tempH = MathF.Sqrt((tempG * tempG) - (tempF * tempF * 4));
|
||||
|
||||
tempC = (tempG - tempH) / (tempF * 2);
|
||||
tempD = 1.0f - tempC;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no high frequency decay ratio
|
||||
tempC = 0.0f;
|
||||
tempD = 1.0f;
|
||||
}
|
||||
|
||||
HighFrequencyDecayDirectGain[i] = FloatingPointHelper.Pow10(tempB / 1000) * tempD * 0.7071f;
|
||||
HighFrequencyDecayPreviousGain[i] = tempC;
|
||||
PreviousFeedbackOutput[i] = 0.0f;
|
||||
|
||||
DecayDelays[i].SetDecayRate(0.6f * (1.0f - coloration));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
60
Ryujinx.Audio.Renderer/Integration/HardwareDevice.cs
Normal file
60
Ryujinx.Audio.Renderer/Integration/HardwareDevice.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Integration
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent an hardware device used in <see cref="Dsp.Command.DeviceSinkCommand"/>
|
||||
/// </summary>
|
||||
public interface HardwareDevice : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the supported sample rate of this device.
|
||||
/// </summary>
|
||||
/// <returns>The supported sample rate of this device.</returns>
|
||||
uint GetSampleRate();
|
||||
|
||||
/// <summary>
|
||||
/// Get the channel count supported by this device.
|
||||
/// </summary>
|
||||
/// <returns>The channel count supported by this device.</returns>
|
||||
uint GetChannelCount();
|
||||
|
||||
/// <summary>
|
||||
/// Appends new PCM16 samples to the device.
|
||||
/// </summary>
|
||||
/// <param name="data">The new PCM16 samples.</param>
|
||||
/// <param name="channelCount">The number of channels.</param>
|
||||
void AppendBuffer(ReadOnlySpan<short> data, uint channelCount);
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer needs to perform downmixing.
|
||||
/// </summary>
|
||||
/// <returns>True if downmixing is needed.</returns>
|
||||
public bool NeedDownmixing()
|
||||
{
|
||||
uint channelCount = GetChannelCount();
|
||||
|
||||
Debug.Assert(channelCount > 0 && channelCount <= RendererConstants.ChannelCountMax);
|
||||
|
||||
return channelCount != RendererConstants.ChannelCountMax;
|
||||
}
|
||||
}
|
||||
}
|
35
Ryujinx.Audio.Renderer/Integration/IWritableEvent.cs
Normal file
35
Ryujinx.Audio.Renderer/Integration/IWritableEvent.cs
Normal file
@ -0,0 +1,35 @@
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Integration
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent a writable event with manual clear.
|
||||
/// </summary>
|
||||
public interface IWritableEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Signal the event.
|
||||
/// </summary>
|
||||
void Signal();
|
||||
|
||||
/// <summary>
|
||||
/// Clear the signaled state of the event.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
||||
}
|
165
Ryujinx.Audio.Renderer/LICENSE.txt
Normal file
165
Ryujinx.Audio.Renderer/LICENSE.txt
Normal file
@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||