0
0
Fork 0
mirror of https://github.com/ryujinx-mirror/ryujinx.git synced 2024-12-22 22:25:44 +00:00

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:
Mary 2020-08-18 03:49:37 +02:00 committed by GitHub
parent 2a314f3c28
commit a389dd59bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
250 changed files with 23691 additions and 1512 deletions

View file

@ -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; }

View file

@ -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.

View 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;
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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
}
}

View 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
}
}

View 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;
}
}
}

View 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 &amp; circular buffer sink)
/// </summary>
Sink = 3,
/// <summary>
/// Performance monitoring related node id (performance commands)
/// </summary>
Performance = 15
}
}

View 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;
}
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View 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>();
}
}
}

View 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;
}
}
}
}

View 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;
}
}

View 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;
}
}
}

View 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");
}
}
}

View 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;
}
}
}

View file

@ -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;
}
}
}

View 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;
}
}
}

View 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();
}
}
}
}

View 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 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);
}
}
}

View 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);
}
}
}
}
}

View 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);
}
}
}

View file

@ -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;
}
}
}
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}
}
}

View 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;
}
}
}
}
}

View 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();
}
}
}
}

View 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 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);
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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;
}
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View 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();
}
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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));
}
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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));
}
}
}

View 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;
}
}
}

View 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;
}
}
}
}

View 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;
}
}

View 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];
}
}

View 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;
}
}

View 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;
}
}
}

View 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;
}
}
}
}

View 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));
}
}
}
}

View 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;
}
}
}

View 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();
}
}

View 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.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View file

@ -0,0 +1,116 @@
//
// 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.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Audio Renderer user configuration.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct AudioRendererConfiguration
{
/// <summary>
/// The target sample rate of the user.
/// </summary>
/// <remarks>Only 32000Hz and 48000Hz are considered valid, other sample rates will cause undefined behaviour.</remarks>
public uint SampleRate;
/// <summary>
/// The target sample count per <see cref="Dsp.AudioProcessor"/> updates.
/// </summary>
public uint SampleCount;
/// <summary>
/// The maximum mix buffer count.
/// </summary>
public uint MixBufferCount;
/// <summary>
/// The maximum amount of sub mixes that could be used by the user.
/// </summary>
public uint SubMixBufferCount;
/// <summary>
/// The maximum amount of voices that could be used by the user.
/// </summary>
public uint VoiceCount;
/// <summary>
/// The maximum amount of sinks that could be used by the user.
/// </summary>
public uint SinkCount;
/// <summary>
/// The maximum amount of effects that could be used by the user.
/// </summary>
public uint EffectCount;
/// <summary>
/// The maximum amount of performance metric frames that could be used by the user.
/// </summary>
public uint PerformanceMetricFramesCount;
/// <summary>
/// Set to true if the user allows the <see cref="Server.AudioRenderSystem"/> to drop voices.
/// </summary>
/// <seealso cref="Server.AudioRenderSystem.ComputeVoiceDrop(Server.CommandBuffer, long, long)"/>
[MarshalAs(UnmanagedType.I1)]
public bool VoiceDropEnabled;
/// <summary>
/// Reserved/unused
/// </summary>
private byte _reserved;
/// <summary>
/// The target rendering device.
/// </summary>
/// <remarks>Must be <see cref="AudioRendererRenderingDevice.Dsp"/></remarks>
public AudioRendererRenderingDevice RenderingDevice;
/// <summary>
/// The target execution mode.
/// </summary>
/// <remarks>Must be <see cref="AudioRendererExecutionMode.Auto"/></remarks>
public AudioRendererExecutionMode ExecutionMode;
/// <summary>
/// The maximum amount of splitters that could be used by the user.
/// </summary>
public uint SplitterCount;
/// <summary>
/// The maximum amount of splitters destinations that could be used by the user.
/// </summary>
public uint SplitterDestinationCount;
/// <summary>
/// The size of the external context.
/// </summary>
/// <remarks>This is a leftover of the old "codec" interface system that was present between 1.0.0 and 3.0.0. This was entirely removed from the server side with REV8.</remarks>
public uint ExternalContextSize;
/// <summary>
/// The user audio revision
/// </summary>
/// <seealso cref="Server.BehaviourContext"/>
public int Revision;
}
}

View file

@ -0,0 +1,47 @@
//
// 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.Common.Memory;
using System;
using System.Runtime.InteropServices;
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Output information for behaviour.
/// </summary>
/// <remarks>This is used to report errors to the user during <see cref="Server.AudioRenderSystem.Update(Memory{byte}, Memory{byte}, ReadOnlyMemory{byte})"/> processing.</remarks>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BehaviourErrorInfoOutStatus
{
/// <summary>
/// The reported errors.
/// </summary>
public Array10<ErrorInfo> ErrorInfos;
/// <summary>
/// The amount of error that got reported.
/// </summary>
public uint ErrorInfosCount;
/// <summary>
/// Reserved/unused.
/// </summary>
private unsafe fixed uint _reserved[3];
}
}

View file

@ -0,0 +1,51 @@
//
// 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.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Biquad filter parameters.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 1)]
public struct BiquadFilterParameter
{
/// <summary>
/// Set to true if the biquad filter is active.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool Enable;
/// <summary>
/// Reserved/padding.
/// </summary>
private byte _reserved;
/// <summary>
/// Biquad filter numerator (b0, b1, b2).
/// </summary>
public Array3<short> Numerator;
/// <summary>
/// Biquad filter denominator (a1, a2).
/// </summary>
/// <remarks>a0 = 1</remarks>
public Array2<short> Denominator;
}
}

View file

@ -0,0 +1,100 @@
//
// 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.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.AuxiliaryBuffer"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct AuxiliaryBufferParameter
{
/// <summary>
/// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/> to write data to <see cref="SendBufferInfoAddress"/>.
/// </summary>
public Array24<byte> Input;
/// <summary>
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/> to read data from <see cref="ReturnBufferInfoAddress"/>.
/// </summary>
public Array24<byte> Output;
/// <summary>
/// The total channel count used.
/// </summary>
public uint ChannelCount;
/// <summary>
/// The target sample rate.
/// </summary>
public uint SampleRate;
/// <summary>
/// The buffer storage total size.
/// </summary>
public uint BufferStorageSize;
/// <summary>
/// The maximum number of channels supported.
/// </summary>
/// <remarks>This is unused.</remarks>
public uint ChannelCountMax;
/// <summary>
/// The address of the start of the region containing two <see cref="Dsp.State.AuxiliaryBufferHeader"/> followed by the data that will be written by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public ulong SendBufferInfoAddress;
/// <summary>
/// The address of the start of the region containling data that will be written by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
/// <remarks>This is unused.</remarks>
public ulong SendBufferStorageAddress;
/// <summary>
/// The address of the start of the region containing two <see cref="Dsp.State.AuxiliaryBufferHeader"/> followed by the data that will be read by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public ulong ReturnBufferInfoAddress;
/// <summary>
/// The address of the start of the region containling data that will be read by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
/// <remarks>This is unused.</remarks>
public ulong ReturnBufferStorageAddress;
/// <summary>
/// Size of a sample of the mix buffer.
/// </summary>
/// <remarks>This is unused.</remarks>
public uint MixBufferSampleSize;
/// <summary>
/// The total count of sample that can be stored.
/// </summary>
/// <remarks>This is unused.</remarks>
public uint TotalSampleCount;
/// <summary>
/// The count of sample of the mix buffer.
/// </summary>
/// <remarks>This is unused.</remarks>
public uint MixBufferSampleCount;
}
}

View file

@ -0,0 +1,61 @@
//
// 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.Effect;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.BiquadFilter"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BiquadFilterEffectParameter
{
/// <summary>
/// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Input;
/// <summary>
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Output;
/// <summary>
/// Biquad filter numerator (b0, b1, b2).
/// </summary>
public Array3<short> Numerator;
/// <summary>
/// Biquad filter denominator (a1, a2).
/// </summary>
/// <remarks>a0 = 1</remarks>
public Array2<short> Denominator;
/// <summary>
/// The total channel count used.
/// </summary>
public byte ChannelCount;
/// <summary>
/// The current usage status of the effect on the client side.
/// </summary>
public UsageState Status;
}
}

View 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/>.
//
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.BufferMix"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BufferMixParameter
{
/// <summary>
/// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array24<byte> Input;
/// <summary>
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array24<byte> Output;
/// <summary>
/// The output volumes of the mixes.
/// </summary>
public Array24<float> Volumes;
/// <summary>
/// The total count of mixes used.
/// </summary>
public uint MixesCount;
}
}

View file

@ -0,0 +1,118 @@
//
// 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.Effect;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Delay"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DelayParameter
{
/// <summary>
/// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Input;
/// <summary>
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Output;
/// <summary>
/// The maximum number of channels supported.
/// </summary>
public ushort ChannelCountMax;
/// <summary>
/// The total channel count used.
/// </summary>
public ushort ChannelCount;
/// <summary>
/// The maximum delay time in milliseconds.
/// </summary>
public uint DelayTimeMax;
/// <summary>
/// The delay time in milliseconds.
/// </summary>
public uint DelayTime;
/// <summary>
/// The target sample rate. (Q15)
/// </summary>
public uint SampleRate;
/// <summary>
/// The input gain. (Q15)
/// </summary>
public uint InGain;
/// <summary>
/// The feedback gain. (Q15)
/// </summary>
public uint FeedbackGain;
/// <summary>
/// The output gain. (Q15)
/// </summary>
public uint OutGain;
/// <summary>
/// The dry gain. (Q15)
/// </summary>
public uint DryGain;
/// <summary>
/// The channel spread of the <see cref="FeedbackGain"/>. (Q15)
/// </summary>
public uint ChannelSpread;
/// <summary>
/// The low pass amount. (Q15)
/// </summary>
public uint LowPassAmount;
/// <summary>
/// The current usage status of the effect on the client side.
/// </summary>
public UsageState Status;
/// <summary>
/// Check if the <see cref="ChannelCount"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
public bool IsChannelCountValid()
{
return EffectInParameter.IsChannelCountValid(ChannelCount);
}
/// <summary>
/// Check if the <see cref="ChannelCountMax"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
public bool IsChannelCountMaxValid()
{
return EffectInParameter.IsChannelCountValid(ChannelCountMax);
}
}
}

View file

@ -0,0 +1,144 @@
//
// 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.Effect;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Reverb3d"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Reverb3dParameter
{
/// <summary>
/// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Input;
/// <summary>
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Output;
/// <summary>
/// The maximum number of channels supported.
/// </summary>
public ushort ChannelCountMax;
/// <summary>
/// The total channel count used.
/// </summary>
public ushort ChannelCount;
/// <summary>
/// Reserved/unused.
/// </summary>
private uint _reserved;
/// <summary>
/// The target sample rate.
/// </summary>
/// <remarks>This is in kHz.</remarks>
public uint SampleRate;
/// <summary>
/// Gain of the room high-frequency effect.
/// </summary>
public float RoomHf;
/// <summary>
/// Reference high frequency.
/// </summary>
public float HfReference;
/// <summary>
/// Reverberation decay time at low frequencies.
/// </summary>
public float DecayTime;
/// <summary>
/// Ratio of the decay time at high frequencies to the decay time at low frequencies.
/// </summary>
public float HfDecayRatio;
/// <summary>
/// Gain of the room effect.
/// </summary>
public float RoomGain;
/// <summary>
/// Gain of the early reflections relative to <see cref="RoomGain"/>.
/// </summary>
public float ReflectionsGain;
/// <summary>
/// Gain of the late reverberation relative to <see cref="RoomGain"/>.
/// </summary>
public float ReverbGain;
/// <summary>
/// Echo density in the late reverberation decay.
/// </summary>
public float Diffusion;
/// <summary>
/// Modal density in the late reverberation decay.
/// </summary>
public float ReflectionDelay;
/// <summary>
/// Time limit between the early reflections and the late reverberation relative to the time of the first reflection.
/// </summary>
public float ReverbDelayTime;
/// <summary>
/// Modal density in the late reverberation decay.
/// </summary>
public float Density;
/// <summary>
/// The dry gain.
/// </summary>
public float DryGain;
/// <summary>
/// The current usage status of the effect on the client side.
/// </summary>
public UsageState ParameterStatus;
/// <summary>
/// Check if the <see cref="ChannelCount"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
public bool IsChannelCountValid()
{
return EffectInParameter.IsChannelCountValid(ChannelCount);
}
/// <summary>
/// Check if the <see cref="ChannelCountMax"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
public bool IsChannelCountMaxValid()
{
return EffectInParameter.IsChannelCountValid(ChannelCountMax);
}
}
}

View 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.Common;
using Ryujinx.Audio.Renderer.Server.Effect;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Reverb"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ReverbParameter
{
/// <summary>
/// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Input;
/// <summary>
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Output;
/// <summary>
/// The maximum number of channels supported.
/// </summary>
public ushort ChannelCountMax;
/// <summary>
/// The total channel count used.
/// </summary>
public ushort ChannelCount;
/// <summary>
/// The target sample rate. (Q15)
/// </summary>
/// <remarks>This is in kHz.</remarks>
public int SampleRate;
/// <summary>
/// The early mode to use.
/// </summary>
public ReverbEarlyMode EarlyMode;
/// <summary>
/// The gain to apply to the result of the early reflection. (Q15)
/// </summary>
public int EarlyGain;
/// <summary>
/// The pre-delay time in milliseconds. (Q15)
/// </summary>
public int PreDelayTime;
/// <summary>
/// The late mode to use.
/// </summary>
public ReverbLateMode LateMode;
/// <summary>
/// The gain to apply to the result of the late reflection. (Q15)
/// </summary>
public int LateGain;
/// <summary>
/// The decay time. (Q15)
/// </summary>
public int DecayTime;
/// <summary>
/// The high frequency decay ratio. (Q15)
/// </summary>
/// <remarks>If <see cref="HighFrequencyDecayRatio"/> >= 0.995f, it is considered disabled.</remarks>
public int HighFrequencyDecayRatio;
/// <summary>
/// The coloration of the decay. (Q15)
/// </summary>
public int Coloration;
/// <summary>
/// The reverb gain. (Q15)
/// </summary>
public int ReverbGain;
/// <summary>
/// The output gain. (Q15)
/// </summary>
public int OutGain;
/// <summary>
/// The dry gain. (Q15)
/// </summary>
public int DryGain;
/// <summary>
/// The current usage status of the effect on the client side.
/// </summary>
public UsageState Status;
/// <summary>
/// Check if the <see cref="ChannelCount"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
public bool IsChannelCountValid()
{
return EffectInParameter.IsChannelCountValid(ChannelCount);
}
/// <summary>
/// Check if the <see cref="ChannelCountMax"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
public bool IsChannelCountMaxValid()
{
return EffectInParameter.IsChannelCountValid(ChannelCountMax);
}
}
}

View 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 Ryujinx.Audio.Renderer.Common;
using Ryujinx.Common.Utilities;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Input information for an effect.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EffectInParameter
{
/// <summary>
/// Type of the effect.
/// </summary>
public EffectType Type;
/// <summary>
/// Set to true if the effect is new.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsNew;
/// <summary>
/// Set to true if the effect must be active.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsEnabled;
/// <summary>
/// Reserved/padding.
/// </summary>
private byte _reserved1;
/// <summary>
/// The target mix id of the effect.
/// </summary>
public int MixId;
/// <summary>
/// Address of the processing workbuffer.
/// </summary>
/// <remarks>This is additional data that could be required by the effect processing.</remarks>
public ulong BufferBase;
/// <summary>
/// Size of the processing workbuffer.
/// </summary>
/// <remarks>This is additional data that could be required by the effect processing.</remarks>
public ulong BufferSize;
/// <summary>
/// Position of the effect while processing effects.
/// </summary>
public uint ProcessingOrder;
/// <summary>
/// Reserved/padding.
/// </summary>
private uint _reserved2;
/// <summary>
/// Specific data storage.
/// </summary>
private SpecificDataStruct _specificDataStart;
[StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)]
private struct SpecificDataStruct { }
/// <summary>
/// Specific data changing depending of the <see cref="Type"/>. See also the <see cref="Effect"/> namespace.
/// </summary>
public Span<byte> SpecificData => SpanHelpers.AsSpan<SpecificDataStruct, byte>(ref _specificDataStart);
/// <summary>
/// Check if the given channel count is valid.
/// </summary>
/// <param name="channelCount">The channel count to check</param>
/// <returns>Returns true if the channel count is valid.</returns>
public static bool IsChannelCountValid(int channelCount)
{
return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6;
}
}
}

View 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/>.
//
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Output information for an effect.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EffectOutStatus
{
/// <summary>
/// The state of an effect.
/// </summary>
public enum EffectState : byte
{
/// <summary>
/// The effect is enabled.
/// </summary>
Enabled = 3,
/// <summary>
/// The effect is disabled.
/// </summary>
Disabled = 4
}
/// <summary>
/// Current effect state.
/// </summary>
// TODO: enum?
public EffectState State;
/// <summary>
/// Unused/Reserved.
/// </summary>
private unsafe fixed byte _reserved[15];
}
}

View 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 Ryujinx.Audio.Renderer.Common;
using System.Runtime.InteropServices;
using CpuAddress = System.UInt64;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Input information for a memory pool.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct MemoryPoolInParameter
{
/// <summary>
/// The CPU address used by the memory pool.
/// </summary>
public CpuAddress CpuAddress;
/// <summary>
/// The size used by the memory pool.
/// </summary>
public ulong Size;
/// <summary>
/// The target state the user wants.
/// </summary>
public MemoryPoolUserState State;
/// <summary>
/// Reserved/unused.
/// </summary>
private unsafe fixed uint _reserved[3];
}
}

View file

@ -0,0 +1,39 @@
//
// 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.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Output information for a memory pool.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct MemoryPoolOutStatus
{
/// <summary>
/// The current server memory pool state.
/// </summary>
public MemoryPoolUserState State;
/// <summary>
/// Reserved/unused.
/// </summary>
private unsafe fixed uint _reserved[3];
}
}

View 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/>.
//
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Input information header for mix updates on REV7 and later
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct MixInParameterDirtyOnlyUpdate
{
/// <summary>
/// Magic of the header
/// </summary>
/// <remarks>Never checked on hardware.</remarks>
public uint Magic;
/// <summary>
/// The count of <see cref="MixParameter"/> following this header.
/// </summary>
public uint MixCount;
/// <summary>
/// Reserved/unused.
/// </summary>
private unsafe fixed byte _reserved[24];
}
}

View file

@ -0,0 +1,112 @@
//
// 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.Common.Utilities;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Input information for a mix.
/// </summary>
/// <remarks>Also used on the client side for mix tracking.</remarks>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct MixParameter
{
/// <summary>
/// Base volume of the mix.
/// </summary>
public float Volume;
/// <summary>
/// Target sample rate of the mix.
/// </summary>
public uint SampleRate;
/// <summary>
/// Target buffer count.
/// </summary>
public uint BufferCount;
/// <summary>
/// Set to true if in use.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsUsed;
/// <summary>
/// Set to true if it was changed.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsDirty;
/// <summary>
/// Reserved/padding.
/// </summary>
private ushort _reserved1;
/// <summary>
/// The id of the mix.
/// </summary>
public int MixId;
/// <summary>
/// The effect count. (client side)
/// </summary>
public uint EffectCount;
/// <summary>
/// The mix node id.
/// </summary>
public int NodeId;
/// <summary>
/// Reserved/padding.
/// </summary>
private ulong _reserved2;
/// <summary>
/// Mix buffer volumes storage.
/// </summary>
private MixVolumeArray _mixBufferVolumeArray;
/// <summary>
/// The mix to output the result of this mix.
/// </summary>
public int DestinationMixId;
/// <summary>
/// The splitter to output the result of this mix.
/// </summary>
public uint DestinationSplitterId;
/// <summary>
/// Reserved/padding.
/// </summary>
private uint _reserved3;
[StructLayout(LayoutKind.Sequential, Size = 4 * RendererConstants.MixBufferCountMax * RendererConstants.MixBufferCountMax, Pack = 1)]
private struct MixVolumeArray { }
/// <summary>
/// Mix buffer volumes.
/// </summary>
/// <remarks>Used when no splitter id is specified.</remarks>
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixVolumeArray, float>(ref _mixBufferVolumeArray);
}
}

View file

@ -0,0 +1,38 @@
//
// 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.Parameter.Performance
{
/// <summary>
/// Input information for performance monitoring.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PerformanceInParameter
{
/// <summary>
/// The target node id to monitor performance on.
/// </summary>
public int TargetNodeId;
/// <summary>
/// Reserved/unused.
/// </summary>
private unsafe fixed uint _reserved[3];
}
}

View file

@ -0,0 +1,38 @@
//
// 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.Parameter.Performance
{
/// <summary>
/// Output information for performance monitoring.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PerformanceOutStatus
{
/// <summary>
/// Indicates the total size output to the performance buffer.
/// </summary>
public uint HistorySize;
/// <summary>
/// Reserved/unused.
/// </summary>
private unsafe fixed uint _reserved[3];
}
}

View file

@ -0,0 +1,38 @@
//
// 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.Parameter
{
/// <summary>
/// Renderer output information on REV5 and later.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RendererInfoOutStatus
{
/// <summary>
/// The count of updates sent to the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public ulong ElapsedFrameCount;
/// <summary>
/// Reserved/Unused.
/// </summary>
private ulong _reserved;
}
}

View 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.Common;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
using CpuAddress = System.UInt64;
namespace Ryujinx.Audio.Renderer.Parameter.Sink
{
/// <summary>
/// <see cref="SinkInParameter.SpecificData"/> for <see cref="SinkType.CircularBuffer"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CircularBufferParameter
{
/// <summary>
/// The CPU address of the user circular buffer.
/// </summary>
public CpuAddress BufferAddress;
/// <summary>
/// The size of the user circular buffer.
/// </summary>
public uint BufferSize;
/// <summary>
/// The total count of channels to output to the circular buffer.
/// </summary>
public uint InputCount;
/// <summary>
/// The target sample count to output per update in the circular buffer.
/// </summary>
public uint SampleCount;
/// <summary>
/// Last read offset on the CPU side.
/// </summary>
public uint LastReadOffset;
/// <summary>
/// The target <see cref="SampleFormat"/>.
/// </summary>
/// <remarks>Only <see cref="SampleFormat.PcmInt16"/> is supported.</remarks>
public SampleFormat SampleFormat;
/// <summary>
/// Reserved/padding.
/// </summary>
private unsafe fixed byte _reserved1[3];
/// <summary>
/// The input channels index that will be used.
/// </summary>
public Array6<byte> Input;
/// <summary>
/// Reserved/padding.
/// </summary>
private ushort _reserved2;
}
}

View file

@ -0,0 +1,75 @@
//
// 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.Common.Memory;
using Ryujinx.Common.Utilities;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Sink
{
/// <summary>
/// <see cref="SinkInParameter.SpecificData"/> for <see cref="Common.SinkType.Device"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DeviceParameter
{
/// <summary>
/// Device name storage.
/// </summary>
private DeviceNameStruct _deviceName;
/// <summary>
/// Reserved/padding.
/// </summary>
private byte _padding;
/// <summary>
/// The total count of channels to output to the device.
/// </summary>
public uint InputCount;
/// <summary>
/// The input channels index that will be used.
/// </summary>
public Array6<byte> Input;
/// <summary>
/// Reserved/padding.
/// </summary>
private byte _reserved;
/// <summary>
/// Set to true if the user controls Surround to Stereo downmixing coefficients.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool DownMixParameterEnabled;
/// <summary>
/// The user Surround to Stereo downmixing coefficients.
/// </summary>
public Array4<float> DownMixParameter;
[StructLayout(LayoutKind.Sequential, Size = 0xFF, Pack = 1)]
private struct DeviceNameStruct { }
/// <summary>
/// The output device name.
/// </summary>
public Span<byte> DeviceName => SpanHelpers.AsSpan<DeviceNameStruct, byte>(ref _deviceName);
}
}

View file

@ -0,0 +1,70 @@
//
// 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.Common.Utilities;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Input information for a sink.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SinkInParameter
{
/// <summary>
/// Type of the sink.
/// </summary>
public SinkType Type;
/// <summary>
/// Set to true if the sink is used.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsUsed;
/// <summary>
/// Reserved/padding.
/// </summary>
private ushort _reserved1;
/// <summary>
/// The node id of the sink.
/// </summary>
public int NodeId;
/// <summary>
/// Reserved/padding.
/// </summary>
private unsafe fixed ulong _reserved2[3];
/// <summary>
/// Specific data storage.
/// </summary>
private SpecificDataStruct _specificDataStart;
[StructLayout(LayoutKind.Sequential, Size = 0x120, Pack = 1)]
private struct SpecificDataStruct { }
/// <summary>
/// Specific data changing depending of the <see cref="Type"/>. See also the <see cref="Sink"/> namespace.
/// </summary>
public Span<byte> SpecificData => SpanHelpers.AsSpan<SpecificDataStruct, byte>(ref _specificDataStart);
}
}

View file

@ -0,0 +1,43 @@
//
// 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.Parameter
{
/// <summary>
/// Output information for a sink.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SinkOutStatus
{
/// <summary>
/// Last written offset if the sink type is <see cref="Common.SinkType.CircularBuffer"/>.
/// </summary>
public uint LastWrittenOffset;
/// <summary>
/// Reserved/padding.
/// </summary>
private uint _padding;
/// <summary>
/// Reserved/padding.
/// </summary>
private unsafe fixed ulong _reserved[3];
}
}

View 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 Ryujinx.Common.Utilities;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Input header for a splitter destination update.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SplitterDestinationInParameter
{
/// <summary>
/// Magic of the input header.
/// </summary>
public uint Magic;
/// <summary>
/// Target splitter destination data id.
/// </summary>
public int Id;
/// <summary>
/// Mix buffer volumes storage.
/// </summary>
private MixArray _mixBufferVolume;
/// <summary>
/// The mix to output the result of the splitter.
/// </summary>
public int DestinationId;
/// <summary>
/// Set to true if in use.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsUsed;
/// <summary>
/// Reserved/padding.
/// </summary>
private unsafe fixed byte _reserved[3];
[StructLayout(LayoutKind.Sequential, Size = 4 * RendererConstants.MixBufferCountMax, Pack = 1)]
private struct MixArray { }
/// <summary>
/// Mix buffer volumes.
/// </summary>
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume);
/// <summary>
/// The expected constant of any input header.
/// </summary>
private const uint ValidMagic = 0x44444E53;
/// <summary>
/// Check if the magic is valid.
/// </summary>
/// <returns>Returns true if the magic is valid.</returns>
public bool IsMagicValid()
{
return Magic == ValidMagic;
}
}
}

View file

@ -0,0 +1,63 @@
//
// 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.Parameter
{
/// <summary>
/// Input header for a splitter state update.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SplitterInParameter
{
/// <summary>
/// Magic of the input header.
/// </summary>
public uint Magic;
/// <summary>
/// Target splitter id.
/// </summary>
public int Id;
/// <summary>
/// Target sample rate to use on the splitter.
/// </summary>
public uint SampleRate;
/// <summary>
/// Count of splitter destinations.
/// </summary>
/// <remarks>Splitter destination ids are defined right after this header.</remarks>
public int DestinationCount;
/// <summary>
/// The expected constant of any input header.
/// </summary>
private const uint ValidMagic = 0x49444E53;
/// <summary>
/// Check if the magic is valid.
/// </summary>
/// <returns>Returns true if the magic is valid.</returns>
public bool IsMagicValid()
{
return Magic == ValidMagic;
}
}
}

View file

@ -0,0 +1,62 @@
//
// 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.Parameter
{
/// <summary>
/// Input header for splitter update.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SplitterInParameterHeader
{
/// <summary>
/// Magic of the input header.
/// </summary>
public uint Magic;
/// <summary>
/// The count of <see cref="SplitterInParameter"/> after the header.
/// </summary>
public uint SplitterCount;
/// <summary>
/// The count of splitter destinations after the header and splitter info.
/// </summary>
public uint SplitterDestinationCount;
/// <summary>
/// Reserved/unused.
/// </summary>
private unsafe fixed uint _reserved[5];
/// <summary>
/// The expected constant of any input splitter header.
/// </summary>
private const uint ValidMagic = 0x48444E53;
/// <summary>
/// Check if the magic is valid.
/// </summary>
/// <returns>Returns true if the magic is valid.</returns>
public bool IsMagicValid()
{
return Magic == ValidMagic;
}
}
}

View 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/>.
//
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Input information for a voice channel resources.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 0x70, Pack = 1)]
public struct VoiceChannelResourceInParameter
{
/// <summary>
/// The id of the voice channel resource.
/// </summary>
public uint Id;
/// <summary>
/// Mix volumes for the voice channel resource.
/// </summary>
public Array24<float> Mix;
/// <summary>
/// Indicate if the voice channel resource is used.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsUsed;
}
}

View file

@ -0,0 +1,360 @@
//
// 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;
using Ryujinx.Common.Memory;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Input information for a voice.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 0x170, Pack = 1)]
public struct VoiceInParameter
{
/// <summary>
/// Id of the voice.
/// </summary>
public int Id;
/// <summary>
/// Node id of the voice.
/// </summary>
public int NodeId;
/// <summary>
/// Set to true if the voice is new.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsNew;
/// <summary>
/// Set to true if the voice is used.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool InUse;
/// <summary>
/// The voice <see cref="PlayState"/> wanted by the user.
/// </summary>
public PlayState PlayState;
/// <summary>
/// The <see cref="SampleFormat"/> of the voice.
/// </summary>
public SampleFormat SampleFormat;
/// <summary>
/// The sample rate of the voice.
/// </summary>
public uint SampleRate;
/// <summary>
/// The priority of the voice.
/// </summary>
public uint Priority;
/// <summary>
/// Target sorting position of the voice. (Used to sort voices with the same <see cref="Priority"/>)
/// </summary>
public uint SortingOrder;
/// <summary>
/// The total channel count used.
/// </summary>
public uint ChannelCount;
/// <summary>
/// The pitch used on the voice.
/// </summary>
public float Pitch;
/// <summary>
/// The output volume of the voice.
/// </summary>
public float Volume;
/// <summary>
/// Biquad filters to apply to the output of the voice.
/// </summary>
public Array2<BiquadFilterParameter> BiquadFilters;
/// <summary>
/// Total count of <see cref="WaveBufferInternal"/> of the voice.
/// </summary>
public uint WaveBuffersCount;
/// <summary>
/// Current playing <see cref="WaveBufferInternal"/> of the voice.
/// </summary>
public uint WaveBuffersIndex;
/// <summary>
/// Reserved/unused.
/// </summary>
private uint _reserved1;
/// <summary>
/// User state address required by the data source.
/// </summary>
/// <remarks>Only used for <see cref="SampleFormat.Adpcm"/> as the address of the GC-ADPCM coefficients.</remarks>
public ulong DataSourceStateAddress;
/// <summary>
/// User state size required by the data source.
/// </summary>
/// <remarks>Only used for <see cref="SampleFormat.Adpcm"/> as the size of the GC-ADPCM coefficients.</remarks>
public ulong DataSourceStateSize;
/// <summary>
/// The target mix id of the voice.
/// </summary>
public int MixId;
/// <summary>
/// The target splitter id of the voice.
/// </summary>
public uint SplitterId;
/// <summary>
/// The wavebuffer parameters of this voice.
/// </summary>
public Array4<WaveBufferInternal> WaveBuffers;
/// <summary>
/// The channel resource ids associated to the voice.
/// </summary>
public Array6<int> ChannelResourceIds;
/// <summary>
/// Reset the voice drop flag during voice server update.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool ResetVoiceDropFlag;
/// <summary>
/// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played.
/// </summary>
/// <remarks>This was added on REV5.</remarks>
public byte FlushWaveBufferCount;
/// <summary>
/// Reserved/unused.
/// </summary>
private ushort _reserved2;
/// <summary>
/// Change the behaviour of the voice.
/// </summary>
/// <remarks>This was added on REV5.</remarks>
public DecodingBehaviour DecodingBehaviourFlags;
/// <summary>
/// Change the Sample Rate Conversion (SRC) quality of the voice.
/// </summary>
/// <remarks>This was added on REV8.</remarks>
public SampleRateConversionQuality SrcQuality;
/// <summary>
/// This was previously used for opus codec support on the Audio Renderer and was removed on REV3.
/// </summary>
public uint ExternalContext;
/// <summary>
/// This was previously used for opus codec support on the Audio Renderer and was removed on REV3.
/// </summary>
public uint ExternalContextSize;
/// <summary>
/// Reserved/unused.
/// </summary>
private unsafe fixed uint _reserved3[2];
/// <summary>
/// Input information for a voice wavebuffer.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 0x38, Pack = 1)]
public struct WaveBufferInternal
{
/// <summary>
/// Address of the wavebuffer data.
/// </summary>
public ulong Address;
/// <summary>
/// Size of the wavebuffer data.
/// </summary>
public ulong Size;
/// <summary>
/// Offset of the first sample to play.
/// </summary>
public uint StartSampleOffset;
/// <summary>
/// Offset of the last sample to play.
/// </summary>
public uint EndSampleOffset;
/// <summary>
/// If set to true, the wavebuffer will loop when reaching <see cref="EndSampleOffset"/>.
/// </summary>
/// <remarks>
/// Starting with REV8, you can specify how many times to loop the wavebuffer (<see cref="LoopCount"/>) and where it should start and end when looping (<see cref="LoopFirstSampleOffset"/> and <see cref="LoopLastSampleOffset"/>)
/// </remarks>
[MarshalAs(UnmanagedType.I1)]
public bool ShouldLoop;
/// <summary>
/// Indicates that this is the last wavebuffer to play of the voice.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsEndOfStream;
/// <summary>
/// Indicates if the server should update its internal state.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool SentToServer;
/// <summary>
/// Reserved/unused.
/// </summary>
private byte _reserved;
/// <summary>
/// If set to anything other than 0, specifies how many times to loop the wavebuffer.
/// </summary>
/// <remarks>This was added in REV8.</remarks>
public int LoopCount;
/// <summary>
/// Address of the context used by the sample decoder.
/// </summary>
/// <remarks>This is only currently used by <see cref="SampleFormat.Adpcm"/>.</remarks>
public ulong ContextAddress;
/// <summary>
/// Size of the context used by the sample decoder.
/// </summary>
/// <remarks>This is only currently used by <see cref="SampleFormat.Adpcm"/>.</remarks>
public ulong ContextSize;
/// <summary>
/// If set to anything other than 0, specifies the offset of the first sample to play when looping.
/// </summary>
/// <remarks>This was added in REV8.</remarks>
public uint LoopFirstSampleOffset;
/// <summary>
/// If set to anything other than 0, specifies the offset of the last sample to play when looping.
/// </summary>
/// <remarks>This was added in REV8.</remarks>
public uint LoopLastSampleOffset;
/// <summary>
/// Check if the sample offsets are in a valid range for generic PCM.
/// </summary>
/// <typeparam name="T">The PCM sample type</typeparam>
/// <returns>Returns true if the sample offset are in range of the size.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsSampleOffsetInRangeForPcm<T>() where T : unmanaged
{
uint dataTypeSize = (uint)Unsafe.SizeOf<T>();
return StartSampleOffset * dataTypeSize <= Size &&
EndSampleOffset * dataTypeSize <= Size;
}
/// <summary>
/// Check if the sample offsets are in a valid range for the given <see cref="SampleFormat"/>.
/// </summary>
/// <param name="format">The target <see cref="SampleFormat"/></param>
/// <returns>Returns true if the sample offset are in range of the size.</returns>
public bool IsSampleOffsetValid(SampleFormat format)
{
bool result;
switch (format)
{
case SampleFormat.PcmInt16:
result = IsSampleOffsetInRangeForPcm<ushort>();
break;
case SampleFormat.PcmFloat:
result = IsSampleOffsetInRangeForPcm<float>();
break;
case SampleFormat.Adpcm:
result = AdpcmHelper.GetAdpcmDataSize((int)StartSampleOffset) <= Size &&
AdpcmHelper.GetAdpcmDataSize((int)EndSampleOffset) <= Size;
break;
default:
throw new NotImplementedException($"{format} not implemented!");
}
return result;
}
}
/// <summary>
/// Flag altering the behaviour of wavebuffer decoding.
/// </summary>
[Flags]
public enum DecodingBehaviour : ushort
{
/// <summary>
/// Default decoding behaviour.
/// </summary>
Default = 0,
/// <summary>
/// Reset the played samples accumulator when looping.
/// </summary>
PlayedSampleCountResetWhenLooping = 1,
/// <summary>
/// Skip pitch and Sample Rate Conversion (SRC).
/// </summary>
SkipPitchAndSampleRateConversion = 2
}
/// <summary>
/// Specify the quality to use during Sample Rate Conversion (SRC) and pitch handling.
/// </summary>
/// <remarks>This was added in REV8.</remarks>
public enum SampleRateConversionQuality : byte
{
/// <summary>
/// Resample interpolating 4 samples per output sample.
/// </summary>
Default,
/// <summary>
/// Resample interpolating 8 samples per output sample.
/// </summary>
High,
/// <summary>
/// Resample interpolating 1 samples per output sample.
/// </summary>
Low
}
}
}

View 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.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Output information about a voice.
/// </summary>
/// <remarks>See <seealso cref="Server.StateUpdater.UpdateVoices(Server.Voice.VoiceContext, System.Memory{Server.MemoryPool.MemoryPoolState})"/></remarks>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct VoiceOutStatus
{
/// <summary>
/// The total amount of samples that was played.
/// </summary>
/// <remarks>This is reset to 0 when a <see cref="Common.WaveBuffer"/> finishes playing and <see cref="Common.WaveBuffer.IsEndOfStream"/> is set.</remarks>
/// <remarks>This is reset to 0 when looping while <see cref="Parameter.VoiceInParameter.DecodingBehaviour.PlayedSampleCountResetWhenLooping"/> is set.</remarks>
public ulong PlayedSampleCount;
/// <summary>
/// The total amount of <see cref="WaveBuffer"/> consumed.
/// </summary>
public uint PlayedWaveBuffersCount;
/// <summary>
/// If set to true, the voice was dropped.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool VoiceDropFlag;
/// <summary>
/// Reserved/unused.
/// </summary>
private unsafe fixed byte _reserved[3];
}
}

View 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/>.
//
namespace Ryujinx.Audio.Renderer
{
/// <summary>
/// Define constants used by the Audio Renderer.
/// </summary>
public static class RendererConstants
{
/// <summary>
/// The maximum number of channels supported. (6 channels for 5.1 surround)
/// </summary>
public const int ChannelCountMax = 6;
/// <summary>
/// The maximum number of channels supported per voice.
/// </summary>
public const int VoiceChannelCountMax = ChannelCountMax;
/// <summary>
/// The max count of mix buffer supported per operations (volumes, mix effect, ...)
/// </summary>
public const int MixBufferCountMax = 24;
/// <summary>
/// The max count of wavebuffer per voice.
/// </summary>
public const int VoiceWaveBufferCount = 4;
/// <summary>
/// The max count of biquad filter per voice.
/// </summary>
public const int VoiceBiquadFilterCount = 2;
/// <summary>
/// The lowest priority that a voice can have.
/// </summary>
public const int VoiceLowestPriority = 0xFF;
/// <summary>
/// The highest priority that a voice can have.
/// </summary>
/// <remarks>Voices with the highest priority will not be dropped if a voice drop needs to occur.</remarks>
public const int VoiceHighestPriority = 0;
/// <summary>
/// Max <see cref="Common.BehaviourParameter.ErrorInfo"/> that can be returned by <see cref="Parameter.BehaviourErrorInfoOutStatus"/>.
/// </summary>
public const int MaxErrorInfos = 10;
/// <summary>
/// Default alignment for buffers.
/// </summary>
public const int BufferAlignment = 0x40;
/// <summary>
/// Alignment required for the work buffer.
/// </summary>
public const int WorkBufferAlignment = 0x1000;
/// <summary>
/// Alignment required for every performance metrics frame.
/// </summary>
public const int PerformanceMetricsPerFramesSizeAlignment = 0x100;
/// <summary>
/// The id of the final mix.
/// </summary>
public const int FinalMixId = 0;
/// <summary>
/// The id defining an unused mix id.
/// </summary>
public const int UnusedMixId = int.MaxValue;
/// <summary>
/// The id defining an unused splitter id as a signed integer.
/// </summary>
public const int UnusedSplitterIdInt = -1;
/// <summary>
/// The id defining an unused splitter id.
/// </summary>
public const uint UnusedSplitterId = uint.MaxValue;
/// <summary>
/// The id of invalid/unused node id.
/// </summary>
public const int InvalidNodeId = -268435456;
/// <summary>
/// The indice considered invalid for processing order.
/// </summary>
public const int InvalidProcessingOrder = -1;
/// <summary>
/// The max number of audio renderer sessions allowed to be created system wide.
/// </summary>
public const int AudioRendererSessionCountMax = 2;
/// <summary>
/// The target sample rate of the audio renderer. (48kHz)
/// </summary>
public const uint TargetSampleRate = 48000;
/// <summary>
/// The target sample size of the audio renderer. (PCM16)
/// </summary>
public const int TargetSampleSize = sizeof(ushort);
/// <summary>
/// The target sample count per audio renderer update.
/// </summary>
public const int TargetSampleCount = 240;
/// <summary>
/// The size of an upsampler entry to process upsampling to <see cref="TargetSampleRate"/>.
/// </summary>
public const int UpSampleEntrySize = TargetSampleCount * VoiceChannelCountMax;
/// <summary>
/// The target audio latency computed from <see cref="TargetSampleRate"/> and <see cref="TargetSampleCount"/>.
/// </summary>
public const int AudioProcessorMaxUpdateTimeTarget = 1000000000 / ((int)TargetSampleRate / TargetSampleCount); // 5.00 ms
/// <summary>
/// The max update time of the DSP on original hardware.
/// </summary>
public const int AudioProcessorMaxUpdateTime = 5760000; // 5.76 ms
/// <summary>
/// The max update time per audio renderer session.
/// </summary>
public const int AudioProcessorMaxUpdateTimePerSessions = AudioProcessorMaxUpdateTime / AudioRendererSessionCountMax;
/// <summary>
/// Guest timer frequency used for system ticks.
/// </summary>
public const int TargetTimerFrequency = 19200000;
/// <summary>
/// The default coefficients used for standard 5.1 surround to stereo downmixing.
/// </summary>
public static float[] DefaultSurroundToStereoCoefficients = new float[4]
{
1.0f,
0.707f,
0.251f,
0.707f,
};
}
}

View 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
{
public enum ResultCode
{
ModuleId = 153,
ErrorCodeShift = 9,
Success = 0,
OperationFailed = (2 << ErrorCodeShift) | ModuleId,
WorkBufferTooSmall = (4 << ErrorCodeShift) | ModuleId,
InvalidUpdateInfo = (41 << ErrorCodeShift) | ModuleId,
InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId,
InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId,
UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId,
}
}

Some files were not shown because too many files have changed in this diff Show more