using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Utils;
using Ryujinx.Common;
using Ryujinx.Common.Extensions;
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Server.Splitter
{
///
/// Splitter context.
///
public class SplitterContext
{
///
/// Amount of biquad filter states per splitter destination.
///
public const int BqfStatesPerDestination = 4;
///
/// Storage for .
///
private Memory _splitters;
///
/// Storage for .
///
private Memory _splitterDestinationsV1;
///
/// Storage for .
///
private Memory _splitterDestinationsV2;
///
/// Splitter biquad filtering states.
///
private Memory _splitterBqfStates;
///
/// Version of the splitter context that is being used, currently can be 1 or 2.
///
public int Version { get; private set; }
///
/// If set to true, trust the user destination count in .
///
public bool IsBugFixed { get; private set; }
///
/// Initialize .
///
/// The behaviour context.
/// The audio renderer configuration.
/// The .
/// Memory to store the biquad filtering state for splitters during processing.
/// Return true if the initialization was successful.
public bool Initialize(
ref BehaviourContext behaviourContext,
ref AudioRendererConfiguration parameter,
WorkBufferAllocator workBufferAllocator,
Memory splitterBqfStates)
{
if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0)
{
Setup(Memory.Empty, Memory.Empty, Memory.Empty, false);
return true;
}
Memory splitters = workBufferAllocator.Allocate(parameter.SplitterCount, SplitterState.Alignment);
if (splitters.IsEmpty)
{
return false;
}
int splitterId = 0;
foreach (ref SplitterState splitter in splitters.Span)
{
splitter = new SplitterState(splitterId++);
}
Memory splitterDestinationsV1 = Memory.Empty;
Memory splitterDestinationsV2 = Memory.Empty;
if (!behaviourContext.IsBiquadFilterParameterForSplitterEnabled())
{
Version = 1;
splitterDestinationsV1 = workBufferAllocator.Allocate(parameter.SplitterDestinationCount,
SplitterDestinationVersion1.Alignment);
if (splitterDestinationsV1.IsEmpty)
{
return false;
}
int splitterDestinationId = 0;
foreach (ref SplitterDestinationVersion1 data in splitterDestinationsV1.Span)
{
data = new SplitterDestinationVersion1(splitterDestinationId++);
}
}
else
{
Version = 2;
splitterDestinationsV2 = workBufferAllocator.Allocate(parameter.SplitterDestinationCount,
SplitterDestinationVersion2.Alignment);
if (splitterDestinationsV2.IsEmpty)
{
return false;
}
int splitterDestinationId = 0;
foreach (ref SplitterDestinationVersion2 data in splitterDestinationsV2.Span)
{
data = new SplitterDestinationVersion2(splitterDestinationId++);
}
if (parameter.SplitterDestinationCount > 0)
{
// Official code stores it in the SplitterDestinationVersion2 struct,
// but we don't to avoid using unsafe code.
splitterBqfStates.Span.Clear();
_splitterBqfStates = splitterBqfStates;
}
else
{
_splitterBqfStates = Memory.Empty;
}
}
SplitterState.InitializeSplitters(splitters.Span);
Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed());
return true;
}
///
/// Get the work buffer size while adding the size needed for splitter to operate.
///
/// The current size.
/// The behaviour context.
/// The renderer configuration.
/// Return the new size taking splitter into account.
public static ulong GetWorkBufferSize(ulong size, ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter)
{
if (behaviourContext.IsSplitterSupported())
{
size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterCount, SplitterState.Alignment);
if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled())
{
size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestinationVersion2.Alignment);
}
else
{
size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestinationVersion1.Alignment);
}
if (behaviourContext.IsSplitterBugFixed())
{
size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, 0x10);
}
return size;
}
return size;
}
///
/// Setup the instance.
///
/// The storage.
/// The storage.
/// The storage.
/// If set to true, trust the user destination count in .
private void Setup(
Memory splitters,
Memory splitterDestinationsV1,
Memory splitterDestinationsV2,
bool isBugFixed)
{
_splitters = splitters;
_splitterDestinationsV1 = splitterDestinationsV1;
_splitterDestinationsV2 = splitterDestinationsV2;
IsBugFixed = isBugFixed;
}
///
/// Clear the new connection flag.
///
private void ClearAllNewConnectionFlag()
{
foreach (ref SplitterState splitter in _splitters.Span)
{
splitter.ClearNewConnectionFlag();
}
}
///
/// Get the destination count using the count of splitter.
///
/// The destination count using the count of splitter.
public int GetDestinationCountPerStateForCompatibility()
{
if (_splitters.IsEmpty)
{
return 0;
}
int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length;
return length / _splitters.Length;
}
///
/// Update one or multiple from user parameters.
///
/// The splitter header.
/// The raw data after the splitter header.
private void UpdateState(in SplitterInParameterHeader inputHeader, ref SequenceReader input)
{
for (int i = 0; i < inputHeader.SplitterCount; i++)
{
ref readonly SplitterInParameter parameter = ref input.GetRefOrRefToCopy(out _);
Debug.Assert(parameter.IsMagicValid());
if (parameter.IsMagicValid())
{
if (parameter.Id >= 0 && parameter.Id < _splitters.Length)
{
ref SplitterState splitter = ref GetState(parameter.Id);
splitter.Update(this, in parameter, ref input);
}
// NOTE: there are 12 bytes of unused/unknown data after the destination IDs array.
input.Advance(0xC);
}
else
{
input.Rewind(Unsafe.SizeOf());
break;
}
}
}
///
/// Update one splitter destination data from user parameters.
///
/// The raw data after the splitter header.
/// True if the update was successful, false otherwise
private bool UpdateData(ref SequenceReader input) where T : unmanaged, ISplitterDestinationInParameter
{
ref readonly T parameter = ref input.GetRefOrRefToCopy(out _);
Debug.Assert(parameter.IsMagicValid());
if (parameter.IsMagicValid())
{
int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length;
if (parameter.Id >= 0 && parameter.Id < length)
{
SplitterDestination destination = GetDestination(parameter.Id);
destination.Update(parameter);
}
return true;
}
else
{
input.Rewind(Unsafe.SizeOf());
return false;
}
}
///
/// Update one or multiple splitter destination data from user parameters.
///
/// The splitter header.
/// The raw data after the splitter header.
private void UpdateData(in SplitterInParameterHeader inputHeader, ref SequenceReader input)
{
for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
{
if (Version == 1)
{
if (!UpdateData(ref input))
{
break;
}
}
else if (Version == 2)
{
if (!UpdateData(ref input))
{
break;
}
}
else
{
Debug.Fail($"Invalid splitter context version {Version}.");
}
}
}
///
/// Update splitter from user parameters.
///
/// The input raw user data.
/// Return true if the update was successful.
public bool Update(ref SequenceReader input)
{
if (!UsingSplitter())
{
return true;
}
ref readonly SplitterInParameterHeader header = ref input.GetRefOrRefToCopy(out _);
if (header.IsMagicValid())
{
ClearAllNewConnectionFlag();
UpdateState(in header, ref input);
UpdateData(in header, ref input);
input.SetConsumed(BitUtils.AlignUp(input.Consumed, 0x10));
return true;
}
else
{
input.Rewind(Unsafe.SizeOf());
return false;
}
}
///
/// Get a reference to a at the given .
///
/// The index to use.
/// A reference to a at the given .
public ref SplitterState GetState(int id)
{
return ref SpanIOHelper.GetFromMemory(_splitters, id, (uint)_splitters.Length);
}
///
/// Get a reference to the splitter destination data at the given .
///
/// The index to use.
/// A reference to the splitter destination data at the given .
public SplitterDestination GetDestination(int id)
{
if (_splitterDestinationsV2.IsEmpty)
{
return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV1, id, (uint)_splitterDestinationsV1.Length));
}
else
{
return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV2, id, (uint)_splitterDestinationsV2.Length));
}
}
///
/// Get a in the at and pass to .
///
/// The index to use to get the .
/// The index of the .
/// A .
public SplitterDestination GetDestination(int id, int destinationId)
{
ref SplitterState splitter = ref GetState(id);
return splitter.GetData(destinationId);
}
///
/// Gets the biquad filter state for a given splitter destination.
///
/// The splitter destination.
/// Biquad filter state for the specified destination.
public Memory GetBiquadFilterState(SplitterDestination destination)
{
return _splitterBqfStates.Slice(destination.Id * BqfStatesPerDestination, BqfStatesPerDestination);
}
///
/// Return true if the audio renderer has any splitters.
///
/// True if the audio renderer has any splitters.
public bool UsingSplitter()
{
return !_splitters.IsEmpty && (!_splitterDestinationsV1.IsEmpty || !_splitterDestinationsV2.IsEmpty);
}
///
/// Update the internal state of all splitters.
///
public void UpdateInternalState()
{
foreach (ref SplitterState splitter in _splitters.Span)
{
splitter.UpdateInternalState();
}
}
}
}