forked from Mirror/Ryujinx
f556c80d02
* Haydn: Part 1 Based on my reverse of audio 11.0.0. As always, core implementation under LGPLv3 for the same reasons as for Amadeus. This place the bases of a more flexible audio system while making audout & audin accurate. This have the following improvements: - Complete reimplementation of audout and audin. - Audin currently only have a dummy backend. - Dramatically reduce CPU usage by up to 50% in common cases (SoundIO and OpenAL). - Audio Renderer now can output to 5.1 devices when supported. - Audio Renderer init its backend on demand instead of keeping two up all the time. - All backends implementation are now in their own project. - Ryujinx.Audio.Renderer was renamed Ryujinx.Audio and was refactored because of this. As a note, games having issues with OpenAL haven't improved and will not because of OpenAL design (stopping when buffers finish playing causing possible audio "pops" when buffers are very small). * Update for latest hexkyz's edits on Switchbrew * audren: Rollback channel configuration changes * Address gdkchan's comments * Fix typo in OpenAL backend driver * Address last comments * Fix a nit * Address gdkchan's comments
276 lines
9 KiB
C#
276 lines
9 KiB
C#
//
|
|
// Copyright (c) 2019-2021 Ryujinx
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
|
|
using Ryujinx.Audio.Renderer.Common;
|
|
using Ryujinx.Audio.Renderer.Server.Splitter;
|
|
using Ryujinx.Audio.Renderer.Utils;
|
|
using System;
|
|
using System.Diagnostics;
|
|
|
|
namespace Ryujinx.Audio.Renderer.Server.Mix
|
|
{
|
|
/// <summary>
|
|
/// Mix context.
|
|
/// </summary>
|
|
public class MixContext
|
|
{
|
|
/// <summary>
|
|
/// The total mix count.
|
|
/// </summary>
|
|
private uint _mixesCount;
|
|
|
|
/// <summary>
|
|
/// Storage for <see cref="MixState"/>.
|
|
/// </summary>
|
|
private Memory<MixState> _mixes;
|
|
|
|
/// <summary>
|
|
/// Storage of the sorted indices to <see cref="MixState"/>.
|
|
/// </summary>
|
|
private Memory<int> _sortedMixes;
|
|
|
|
/// <summary>
|
|
/// Graph state.
|
|
/// </summary>
|
|
public NodeStates NodeStates { get; }
|
|
|
|
/// <summary>
|
|
/// The instance of the adjacent matrix.
|
|
/// </summary>
|
|
public EdgeMatrix EdgeMatrix { get; }
|
|
|
|
/// <summary>
|
|
/// Create a new instance of <see cref="MixContext"/>.
|
|
/// </summary>
|
|
public MixContext()
|
|
{
|
|
NodeStates = new NodeStates();
|
|
EdgeMatrix = new EdgeMatrix();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize the <see cref="MixContext"/>.
|
|
/// </summary>
|
|
/// <param name="sortedMixes">The storage for sorted indices.</param>
|
|
/// <param name="mixes">The storage of <see cref="MixState"/>.</param>
|
|
/// <param name="nodeStatesWorkBuffer">The storage used for the <see cref="NodeStates"/>.</param>
|
|
/// <param name="edgeMatrixWorkBuffer">The storage used for the <see cref="EdgeMatrix"/>.</param>
|
|
public void Initialize(Memory<int> sortedMixes, Memory<MixState> mixes, Memory<byte> nodeStatesWorkBuffer, Memory<byte> edgeMatrixWorkBuffer)
|
|
{
|
|
_mixesCount = (uint)mixes.Length;
|
|
_mixes = mixes;
|
|
_sortedMixes = sortedMixes;
|
|
|
|
if (!nodeStatesWorkBuffer.IsEmpty && !edgeMatrixWorkBuffer.IsEmpty)
|
|
{
|
|
NodeStates.Initialize(nodeStatesWorkBuffer, mixes.Length);
|
|
EdgeMatrix.Initialize(edgeMatrixWorkBuffer, mixes.Length);
|
|
}
|
|
|
|
int sortedId = 0;
|
|
for (int i = 0; i < _mixes.Length; i++)
|
|
{
|
|
SetSortedState(sortedId++, i);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Associate the given <paramref name="targetIndex"/> to a given <paramref cref="id"/>.
|
|
/// </summary>
|
|
/// <param name="id">The sorted id.</param>
|
|
/// <param name="targetIndex">The index to associate.</param>
|
|
private void SetSortedState(int id, int targetIndex)
|
|
{
|
|
_sortedMixes.Span[id] = targetIndex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a reference to the final <see cref="MixState"/>.
|
|
/// </summary>
|
|
/// <returns>A reference to the final <see cref="MixState"/>.</returns>
|
|
public ref MixState GetFinalState()
|
|
{
|
|
return ref GetState(Constants.FinalMixId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a reference to a <see cref="MixState"/> at the given <paramref name="id"/>.
|
|
/// </summary>
|
|
/// <param name="id">The index to use.</param>
|
|
/// <returns>A reference to a <see cref="MixState"/> at the given <paramref name="id"/>.</returns>
|
|
public ref MixState GetState(int id)
|
|
{
|
|
return ref SpanIOHelper.GetFromMemory(_mixes, id, _mixesCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a reference to a <see cref="MixState"/> at the given <paramref name="id"/> of the sorted mix info.
|
|
/// </summary>
|
|
/// <param name="id">The index to use.</param>
|
|
/// <returns>A reference to a <see cref="MixState"/> at the given <paramref name="id"/>.</returns>
|
|
public ref MixState GetSortedState(int id)
|
|
{
|
|
Debug.Assert(id >= 0 && id < _mixesCount);
|
|
|
|
return ref GetState(_sortedMixes.Span[id]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the total mix count.
|
|
/// </summary>
|
|
/// <returns>The total mix count.</returns>
|
|
public uint GetCount()
|
|
{
|
|
return _mixesCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the internal distance from the final mix value of every <see cref="MixState"/>.
|
|
/// </summary>
|
|
private void UpdateDistancesFromFinalMix()
|
|
{
|
|
foreach (ref MixState mix in _mixes.Span)
|
|
{
|
|
mix.ClearDistanceFromFinalMix();
|
|
}
|
|
|
|
for (int i = 0; i < GetCount(); i++)
|
|
{
|
|
ref MixState mix = ref GetState(i);
|
|
|
|
SetSortedState(i, i);
|
|
|
|
if (mix.IsUsed)
|
|
{
|
|
uint distance;
|
|
|
|
if (mix.MixId != Constants.FinalMixId)
|
|
{
|
|
int mixId = mix.MixId;
|
|
|
|
for (distance = 0; distance < GetCount(); distance++)
|
|
{
|
|
if (mixId == Constants.UnusedMixId)
|
|
{
|
|
distance = MixState.InvalidDistanceFromFinalMix;
|
|
break;
|
|
}
|
|
|
|
ref MixState distanceMix = ref GetState(mixId);
|
|
|
|
if (distanceMix.DistanceFromFinalMix != MixState.InvalidDistanceFromFinalMix)
|
|
{
|
|
distance = distanceMix.DistanceFromFinalMix + 1;
|
|
break;
|
|
}
|
|
|
|
mixId = distanceMix.DestinationMixId;
|
|
|
|
if (mixId == Constants.FinalMixId)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (distance > GetCount())
|
|
{
|
|
distance = MixState.InvalidDistanceFromFinalMix;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
distance = MixState.InvalidDistanceFromFinalMix;
|
|
}
|
|
|
|
mix.DistanceFromFinalMix = distance;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the internal mix buffer offset of all <see cref="MixState"/>.
|
|
/// </summary>
|
|
private void UpdateMixBufferOffset()
|
|
{
|
|
uint offset = 0;
|
|
|
|
foreach (ref MixState mix in _mixes.Span)
|
|
{
|
|
mix.BufferOffset = offset;
|
|
|
|
offset += mix.BufferCount;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sort the mixes using distance from the final mix.
|
|
/// </summary>
|
|
public void Sort()
|
|
{
|
|
UpdateDistancesFromFinalMix();
|
|
|
|
int[] sortedMixesTemp = _sortedMixes.Slice(0, (int)GetCount()).ToArray();
|
|
|
|
Array.Sort(sortedMixesTemp, (a, b) =>
|
|
{
|
|
ref MixState stateA = ref GetState(a);
|
|
ref MixState stateB = ref GetState(b);
|
|
|
|
return stateB.DistanceFromFinalMix.CompareTo(stateA.DistanceFromFinalMix);
|
|
});
|
|
|
|
sortedMixesTemp.AsSpan().CopyTo(_sortedMixes.Span);
|
|
|
|
UpdateMixBufferOffset();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sort the mixes and splitters using an adjacency matrix.
|
|
/// </summary>
|
|
/// <param name="splitterContext">The <see cref="SplitterContext"/> used.</param>
|
|
/// <returns>Return true, if no errors in the graph were detected.</returns>
|
|
public bool Sort(SplitterContext splitterContext)
|
|
{
|
|
if (splitterContext.UsingSplitter())
|
|
{
|
|
bool isValid = NodeStates.Sort(EdgeMatrix);
|
|
|
|
if (isValid)
|
|
{
|
|
ReadOnlySpan<int> sortedMixesIndex = NodeStates.GetTsortResult();
|
|
|
|
int id = 0;
|
|
|
|
for (int i = sortedMixesIndex.Length - 1; i >= 0; i--)
|
|
{
|
|
SetSortedState(id++, sortedMixesIndex[i]);
|
|
}
|
|
|
|
UpdateMixBufferOffset();
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
else
|
|
{
|
|
UpdateMixBufferOffset();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|