Update audio renderer to REV12: Add support for splitter biquad filter (#6813)
* Update audio renderer to REV12: Add support for splitter biquad filter * Formatting * Official names * Update BiquadFilterState size + other fixes * Update tests * Update comment for version 2 * Size test for SplitterDestinationVersion2 * Should use Volume1 if no ramp
This commit is contained in:
parent
9ec8b2c01a
commit
4d84df9487
29 changed files with 2342 additions and 392 deletions
|
@ -15,7 +15,6 @@ namespace Ryujinx.Audio.Renderer.Common
|
|||
{
|
||||
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.
|
||||
|
|
|
@ -16,10 +16,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||
/// <param name="parameter">The biquad filter parameter</param>
|
||||
/// <param name="state">The biquad filter state</param>
|
||||
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||
/// <param name="inputBuffer">The input buffer to write the result</param>
|
||||
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||
/// <param name="sampleCount">The count of samples to process</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
|
||||
public static void ProcessBiquadFilter(
|
||||
ref BiquadFilterParameter parameter,
|
||||
ref BiquadFilterState state,
|
||||
Span<float> outputBuffer,
|
||||
ReadOnlySpan<float> inputBuffer,
|
||||
uint sampleCount)
|
||||
{
|
||||
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
|
||||
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
|
||||
|
@ -41,23 +46,24 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply multiple biquad filter.
|
||||
/// Apply a single biquad filter and mix the result into the output buffer.
|
||||
/// </summary>
|
||||
/// <remarks>This is implemented with a direct form 1.</remarks>
|
||||
/// <param name="parameters">The biquad filter parameter</param>
|
||||
/// <param name="states">The biquad filter state</param>
|
||||
/// <param name="parameter">The biquad filter parameter</param>
|
||||
/// <param name="state">The biquad filter state</param>
|
||||
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||
/// <param name="inputBuffer">The input buffer to write the result</param>
|
||||
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||
/// <param name="sampleCount">The count of samples to process</param>
|
||||
/// <param name="volume">Mix volume</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ProcessBiquadFilter(ReadOnlySpan<BiquadFilterParameter> parameters, Span<BiquadFilterState> states, Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
|
||||
public static void ProcessBiquadFilterAndMix(
|
||||
ref BiquadFilterParameter parameter,
|
||||
ref BiquadFilterState state,
|
||||
Span<float> outputBuffer,
|
||||
ReadOnlySpan<float> inputBuffer,
|
||||
uint sampleCount,
|
||||
float volume)
|
||||
{
|
||||
for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++)
|
||||
{
|
||||
BiquadFilterParameter parameter = parameters[stageIndex];
|
||||
|
||||
ref BiquadFilterState state = ref states[stageIndex];
|
||||
|
||||
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
|
||||
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
|
||||
float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
|
||||
|
@ -75,9 +81,227 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||
state.State3 = state.State2;
|
||||
state.State2 = output;
|
||||
|
||||
outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a single biquad filter and mix the result into the output buffer with volume ramp.
|
||||
/// </summary>
|
||||
/// <remarks>This is implemented with a direct form 1.</remarks>
|
||||
/// <param name="parameter">The biquad filter parameter</param>
|
||||
/// <param name="state">The biquad filter state</param>
|
||||
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||
/// <param name="sampleCount">The count of samples to process</param>
|
||||
/// <param name="volume">Initial mix volume</param>
|
||||
/// <param name="ramp">Volume increment step</param>
|
||||
/// <returns>Last filtered sample value</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float ProcessBiquadFilterAndMixRamp(
|
||||
ref BiquadFilterParameter parameter,
|
||||
ref BiquadFilterState state,
|
||||
Span<float> outputBuffer,
|
||||
ReadOnlySpan<float> inputBuffer,
|
||||
uint sampleCount,
|
||||
float volume,
|
||||
float ramp)
|
||||
{
|
||||
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);
|
||||
|
||||
float mixState = 0f;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
float input = inputBuffer[i];
|
||||
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
|
||||
|
||||
state.State1 = state.State0;
|
||||
state.State0 = input;
|
||||
state.State3 = state.State2;
|
||||
state.State2 = output;
|
||||
|
||||
mixState = FloatingPointHelper.MultiplyRoundUp(output, volume);
|
||||
|
||||
outputBuffer[i] += mixState;
|
||||
volume += ramp;
|
||||
}
|
||||
|
||||
return mixState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply multiple biquad filter.
|
||||
/// </summary>
|
||||
/// <remarks>This is implemented with a direct form 1.</remarks>
|
||||
/// <param name="parameters">The biquad filter parameter</param>
|
||||
/// <param name="states">The biquad filter state</param>
|
||||
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||
/// <param name="sampleCount">The count of samples to process</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ProcessBiquadFilter(
|
||||
ReadOnlySpan<BiquadFilterParameter> parameters,
|
||||
Span<BiquadFilterState> states,
|
||||
Span<float> outputBuffer,
|
||||
ReadOnlySpan<float> inputBuffer,
|
||||
uint sampleCount)
|
||||
{
|
||||
for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++)
|
||||
{
|
||||
BiquadFilterParameter parameter = parameters[stageIndex];
|
||||
|
||||
ref BiquadFilterState state = ref states[stageIndex];
|
||||
|
||||
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);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
float input = stageIndex != 0 ? outputBuffer[i] : inputBuffer[i];
|
||||
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
|
||||
|
||||
state.State1 = state.State0;
|
||||
state.State0 = input;
|
||||
state.State3 = state.State2;
|
||||
state.State2 = output;
|
||||
|
||||
outputBuffer[i] = output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply double biquad filter and mix the result into the output buffer.
|
||||
/// </summary>
|
||||
/// <remarks>This is implemented with a direct form 1.</remarks>
|
||||
/// <param name="parameters">The biquad filter parameter</param>
|
||||
/// <param name="states">The biquad filter state</param>
|
||||
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||
/// <param name="sampleCount">The count of samples to process</param>
|
||||
/// <param name="volume">Mix volume</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ProcessDoubleBiquadFilterAndMix(
|
||||
ref BiquadFilterParameter parameter0,
|
||||
ref BiquadFilterParameter parameter1,
|
||||
ref BiquadFilterState state0,
|
||||
ref BiquadFilterState state1,
|
||||
Span<float> outputBuffer,
|
||||
ReadOnlySpan<float> inputBuffer,
|
||||
uint sampleCount,
|
||||
float volume)
|
||||
{
|
||||
float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter);
|
||||
float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter);
|
||||
float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter);
|
||||
|
||||
float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter);
|
||||
float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter);
|
||||
|
||||
float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter);
|
||||
float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter);
|
||||
float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter);
|
||||
|
||||
float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter);
|
||||
float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
float input = inputBuffer[i];
|
||||
float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20;
|
||||
|
||||
state0.State1 = state0.State0;
|
||||
state0.State0 = input;
|
||||
state0.State3 = state0.State2;
|
||||
state0.State2 = output;
|
||||
|
||||
input = output;
|
||||
output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21;
|
||||
|
||||
state1.State1 = state1.State0;
|
||||
state1.State0 = input;
|
||||
state1.State3 = state1.State2;
|
||||
state1.State2 = output;
|
||||
|
||||
outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply double biquad filter and mix the result into the output buffer with volume ramp.
|
||||
/// </summary>
|
||||
/// <remarks>This is implemented with a direct form 1.</remarks>
|
||||
/// <param name="parameters">The biquad filter parameter</param>
|
||||
/// <param name="states">The biquad filter state</param>
|
||||
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||
/// <param name="sampleCount">The count of samples to process</param>
|
||||
/// <param name="volume">Initial mix volume</param>
|
||||
/// <param name="ramp">Volume increment step</param>
|
||||
/// <returns>Last filtered sample value</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float ProcessDoubleBiquadFilterAndMixRamp(
|
||||
ref BiquadFilterParameter parameter0,
|
||||
ref BiquadFilterParameter parameter1,
|
||||
ref BiquadFilterState state0,
|
||||
ref BiquadFilterState state1,
|
||||
Span<float> outputBuffer,
|
||||
ReadOnlySpan<float> inputBuffer,
|
||||
uint sampleCount,
|
||||
float volume,
|
||||
float ramp)
|
||||
{
|
||||
float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter);
|
||||
float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter);
|
||||
float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter);
|
||||
|
||||
float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter);
|
||||
float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter);
|
||||
|
||||
float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter);
|
||||
float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter);
|
||||
float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter);
|
||||
|
||||
float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter);
|
||||
float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter);
|
||||
|
||||
float mixState = 0f;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
float input = inputBuffer[i];
|
||||
float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20;
|
||||
|
||||
state0.State1 = state0.State0;
|
||||
state0.State0 = input;
|
||||
state0.State3 = state0.State2;
|
||||
state0.State2 = output;
|
||||
|
||||
input = output;
|
||||
output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21;
|
||||
|
||||
state1.State1 = state1.State0;
|
||||
state1.State0 = input;
|
||||
state1.State3 = state1.State2;
|
||||
state1.State2 = output;
|
||||
|
||||
mixState = FloatingPointHelper.MultiplyRoundUp(output, volume);
|
||||
|
||||
outputBuffer[i] += mixState;
|
||||
volume += ramp;
|
||||
}
|
||||
|
||||
return mixState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
using Ryujinx.Audio.Renderer.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class BiquadFilterAndMixCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.BiquadFilterAndMix;
|
||||
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
private BiquadFilterParameter _parameter;
|
||||
|
||||
public Memory<BiquadFilterState> BiquadFilterState { get; }
|
||||
public Memory<BiquadFilterState> PreviousBiquadFilterState { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
|
||||
public int LastSampleIndex { get; }
|
||||
|
||||
public float Volume0 { get; }
|
||||
public float Volume1 { get; }
|
||||
|
||||
public bool NeedInitialization { get; }
|
||||
public bool HasVolumeRamp { get; }
|
||||
public bool IsFirstMixBuffer { get; }
|
||||
|
||||
public BiquadFilterAndMixCommand(
|
||||
float volume0,
|
||||
float volume1,
|
||||
uint inputBufferIndex,
|
||||
uint outputBufferIndex,
|
||||
int lastSampleIndex,
|
||||
Memory<VoiceUpdateState> state,
|
||||
ref BiquadFilterParameter filter,
|
||||
Memory<BiquadFilterState> biquadFilterState,
|
||||
Memory<BiquadFilterState> previousBiquadFilterState,
|
||||
bool needInitialization,
|
||||
bool hasVolumeRamp,
|
||||
bool isFirstMixBuffer,
|
||||
int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = (ushort)inputBufferIndex;
|
||||
OutputBufferIndex = (ushort)outputBufferIndex;
|
||||
|
||||
_parameter = filter;
|
||||
BiquadFilterState = biquadFilterState;
|
||||
PreviousBiquadFilterState = previousBiquadFilterState;
|
||||
|
||||
State = state;
|
||||
LastSampleIndex = lastSampleIndex;
|
||||
|
||||
Volume0 = volume0;
|
||||
Volume1 = volume1;
|
||||
|
||||
NeedInitialization = needInitialization;
|
||||
HasVolumeRamp = hasVolumeRamp;
|
||||
IsFirstMixBuffer = isFirstMixBuffer;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
if (NeedInitialization)
|
||||
{
|
||||
// If there is no previous state, initialize to zero.
|
||||
|
||||
BiquadFilterState.Span[0] = new BiquadFilterState();
|
||||
}
|
||||
else if (IsFirstMixBuffer)
|
||||
{
|
||||
// This is the first buffer, set previous state to current state.
|
||||
|
||||
PreviousBiquadFilterState.Span[0] = BiquadFilterState.Span[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rewind the current state by copying back the previous state.
|
||||
|
||||
BiquadFilterState.Span[0] = PreviousBiquadFilterState.Span[0];
|
||||
}
|
||||
|
||||
if (HasVolumeRamp)
|
||||
{
|
||||
float volume = Volume0;
|
||||
float ramp = (Volume1 - Volume0) / (int)context.SampleCount;
|
||||
|
||||
State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessBiquadFilterAndMixRamp(
|
||||
ref _parameter,
|
||||
ref BiquadFilterState.Span[0],
|
||||
outputBuffer,
|
||||
inputBuffer,
|
||||
context.SampleCount,
|
||||
volume,
|
||||
ramp);
|
||||
}
|
||||
else
|
||||
{
|
||||
BiquadFilterHelper.ProcessBiquadFilterAndMix(
|
||||
ref _parameter,
|
||||
ref BiquadFilterState.Span[0],
|
||||
outputBuffer,
|
||||
inputBuffer,
|
||||
context.SampleCount,
|
||||
Volume1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,8 +30,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||
CopyMixBuffer,
|
||||
LimiterVersion1,
|
||||
LimiterVersion2,
|
||||
GroupedBiquadFilter,
|
||||
MultiTapBiquadFilter,
|
||||
CaptureBuffer,
|
||||
Compressor,
|
||||
BiquadFilterAndMix,
|
||||
MultiTapBiquadFilterAndMix,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
|
||||
public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> volume0, Span<float> volume1, Memory<VoiceUpdateState> state, int nodeId)
|
||||
public MixRampGroupedCommand(
|
||||
uint mixBufferCount,
|
||||
uint inputBufferIndex,
|
||||
uint outputBufferIndex,
|
||||
ReadOnlySpan<float> volume0,
|
||||
ReadOnlySpan<float> volume1,
|
||||
Memory<VoiceUpdateState> state,
|
||||
int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
MixBufferCount = mixBufferCount;
|
||||
|
@ -48,7 +55,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static float ProcessMixRampGrouped(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float volume0, float volume1, int sampleCount)
|
||||
private static float ProcessMixRampGrouped(
|
||||
Span<float> outputBuffer,
|
||||
ReadOnlySpan<float> inputBuffer,
|
||||
float volume0,
|
||||
float volume1,
|
||||
int sampleCount)
|
||||
{
|
||||
float ramp = (volume1 - volume0) / sampleCount;
|
||||
float volume = volume0;
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
using Ryujinx.Audio.Renderer.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class MultiTapBiquadFilterAndMixCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.MultiTapBiquadFilterAndMix;
|
||||
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
private BiquadFilterParameter _parameter0;
|
||||
private BiquadFilterParameter _parameter1;
|
||||
|
||||
public Memory<BiquadFilterState> BiquadFilterState0 { get; }
|
||||
public Memory<BiquadFilterState> BiquadFilterState1 { get; }
|
||||
public Memory<BiquadFilterState> PreviousBiquadFilterState0 { get; }
|
||||
public Memory<BiquadFilterState> PreviousBiquadFilterState1 { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
|
||||
public int LastSampleIndex { get; }
|
||||
|
||||
public float Volume0 { get; }
|
||||
public float Volume1 { get; }
|
||||
|
||||
public bool NeedInitialization0 { get; }
|
||||
public bool NeedInitialization1 { get; }
|
||||
public bool HasVolumeRamp { get; }
|
||||
public bool IsFirstMixBuffer { get; }
|
||||
|
||||
public MultiTapBiquadFilterAndMixCommand(
|
||||
float volume0,
|
||||
float volume1,
|
||||
uint inputBufferIndex,
|
||||
uint outputBufferIndex,
|
||||
int lastSampleIndex,
|
||||
Memory<VoiceUpdateState> state,
|
||||
ref BiquadFilterParameter filter0,
|
||||
ref BiquadFilterParameter filter1,
|
||||
Memory<BiquadFilterState> biquadFilterState0,
|
||||
Memory<BiquadFilterState> biquadFilterState1,
|
||||
Memory<BiquadFilterState> previousBiquadFilterState0,
|
||||
Memory<BiquadFilterState> previousBiquadFilterState1,
|
||||
bool needInitialization0,
|
||||
bool needInitialization1,
|
||||
bool hasVolumeRamp,
|
||||
bool isFirstMixBuffer,
|
||||
int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = (ushort)inputBufferIndex;
|
||||
OutputBufferIndex = (ushort)outputBufferIndex;
|
||||
|
||||
_parameter0 = filter0;
|
||||
_parameter1 = filter1;
|
||||
BiquadFilterState0 = biquadFilterState0;
|
||||
BiquadFilterState1 = biquadFilterState1;
|
||||
PreviousBiquadFilterState0 = previousBiquadFilterState0;
|
||||
PreviousBiquadFilterState1 = previousBiquadFilterState1;
|
||||
|
||||
State = state;
|
||||
LastSampleIndex = lastSampleIndex;
|
||||
|
||||
Volume0 = volume0;
|
||||
Volume1 = volume1;
|
||||
|
||||
NeedInitialization0 = needInitialization0;
|
||||
NeedInitialization1 = needInitialization1;
|
||||
HasVolumeRamp = hasVolumeRamp;
|
||||
IsFirstMixBuffer = isFirstMixBuffer;
|
||||
}
|
||||
|
||||
private void UpdateState(Memory<BiquadFilterState> state, Memory<BiquadFilterState> previousState, bool needInitialization)
|
||||
{
|
||||
if (needInitialization)
|
||||
{
|
||||
// If there is no previous state, initialize to zero.
|
||||
|
||||
state.Span[0] = new BiquadFilterState();
|
||||
}
|
||||
else if (IsFirstMixBuffer)
|
||||
{
|
||||
// This is the first buffer, set previous state to current state.
|
||||
|
||||
previousState.Span[0] = state.Span[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rewind the current state by copying back the previous state.
|
||||
|
||||
state.Span[0] = previousState.Span[0];
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
UpdateState(BiquadFilterState0, PreviousBiquadFilterState0, NeedInitialization0);
|
||||
UpdateState(BiquadFilterState1, PreviousBiquadFilterState1, NeedInitialization1);
|
||||
|
||||
if (HasVolumeRamp)
|
||||
{
|
||||
float volume = Volume0;
|
||||
float ramp = (Volume1 - Volume0) / (int)context.SampleCount;
|
||||
|
||||
State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessDoubleBiquadFilterAndMixRamp(
|
||||
ref _parameter0,
|
||||
ref _parameter1,
|
||||
ref BiquadFilterState0.Span[0],
|
||||
ref BiquadFilterState1.Span[0],
|
||||
outputBuffer,
|
||||
inputBuffer,
|
||||
context.SampleCount,
|
||||
volume,
|
||||
ramp);
|
||||
}
|
||||
else
|
||||
{
|
||||
BiquadFilterHelper.ProcessDoubleBiquadFilterAndMix(
|
||||
ref _parameter0,
|
||||
ref _parameter1,
|
||||
ref BiquadFilterState0.Span[0],
|
||||
ref BiquadFilterState1.Span[0],
|
||||
outputBuffer,
|
||||
inputBuffer,
|
||||
context.SampleCount,
|
||||
Volume1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,13 +4,13 @@ using System;
|
|||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class GroupedBiquadFilterCommand : ICommand
|
||||
public class MultiTapBiquadFilterCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.GroupedBiquadFilter;
|
||||
public CommandType CommandType => CommandType.MultiTapBiquadFilter;
|
||||
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
|
@ -20,7 +20,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||
private readonly int _outputBufferIndex;
|
||||
private readonly bool[] _isInitialized;
|
||||
|
||||
public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
|
||||
public MultiTapBiquadFilterCommand(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
|
||||
{
|
||||
_parameters = filters.ToArray();
|
||||
_biquadFilterStates = biquadFilterStateMemory;
|
|
@ -2,12 +2,16 @@ using System.Runtime.InteropServices;
|
|||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x20)]
|
||||
public struct BiquadFilterState
|
||||
{
|
||||
public float State0;
|
||||
public float State1;
|
||||
public float State2;
|
||||
public float State3;
|
||||
public float State4;
|
||||
public float State5;
|
||||
public float State6;
|
||||
public float State7;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Parameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic interface for the splitter destination parameters.
|
||||
/// </summary>
|
||||
public interface ISplitterDestinationInParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Target splitter destination data id.
|
||||
/// </summary>
|
||||
int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The mix to output the result of the splitter.
|
||||
/// </summary>
|
||||
int DestinationId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Biquad filter parameters.
|
||||
/// </summary>
|
||||
Array2<BiquadFilterParameter> BiquadFilters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if in use.
|
||||
/// </summary>
|
||||
bool IsUsed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Mix buffer volumes.
|
||||
/// </summary>
|
||||
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||
Span<float> MixBufferVolume { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Check if the magic is valid.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the magic is valid.</returns>
|
||||
bool IsMagicValid();
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
@ -5,10 +6,10 @@ using System.Runtime.InteropServices;
|
|||
namespace Ryujinx.Audio.Renderer.Parameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Input header for a splitter destination update.
|
||||
/// Input header for a splitter destination version 1 update.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct SplitterDestinationInParameter
|
||||
public struct SplitterDestinationInParameterVersion1 : ISplitterDestinationInParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Magic of the input header.
|
||||
|
@ -41,7 +42,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
|||
/// </summary>
|
||||
private unsafe fixed byte _reserved[3];
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
|
||||
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
|
||||
private struct MixArray { }
|
||||
|
||||
/// <summary>
|
||||
|
@ -50,6 +51,14 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
|||
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume);
|
||||
|
||||
readonly int ISplitterDestinationInParameter.Id => Id;
|
||||
|
||||
readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
|
||||
|
||||
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => default;
|
||||
|
||||
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
|
||||
|
||||
/// <summary>
|
||||
/// The expected constant of any input header.
|
||||
/// </summary>
|
|
@ -0,0 +1,81 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Parameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Input header for a splitter destination version 2 update.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct SplitterDestinationInParameterVersion2 : ISplitterDestinationInParameter
|
||||
{
|
||||
/// <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>
|
||||
/// Biquad filter parameters.
|
||||
/// </summary>
|
||||
public Array2<BiquadFilterParameter> BiquadFilters;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if in use.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsUsed;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved/padding.
|
||||
/// </summary>
|
||||
private unsafe fixed byte _reserved[11];
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.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);
|
||||
|
||||
readonly int ISplitterDestinationInParameter.Id => Id;
|
||||
|
||||
readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
|
||||
|
||||
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => BiquadFilters;
|
||||
|
||||
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
|
||||
|
||||
/// <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 readonly bool IsMagicValid()
|
||||
{
|
||||
return Magic == ValidMagic;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp.Command;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
|
@ -173,6 +174,22 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
Memory<BiquadFilterState> splitterBqfStates = Memory<BiquadFilterState>.Empty;
|
||||
|
||||
if (_behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
|
||||
parameter.SplitterCount > 0 &&
|
||||
parameter.SplitterDestinationCount > 0)
|
||||
{
|
||||
splitterBqfStates = workBufferAllocator.Allocate<BiquadFilterState>(parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
|
||||
|
||||
if (splitterBqfStates.IsEmpty)
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
||||
splitterBqfStates.Span.Clear();
|
||||
}
|
||||
|
||||
// Invalidate DSP cache on what was currently allocated with workBuffer.
|
||||
AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset);
|
||||
|
||||
|
@ -292,7 +309,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu);
|
||||
}
|
||||
|
||||
if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator))
|
||||
if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator, splitterBqfStates))
|
||||
{
|
||||
return ResultCode.WorkBufferTooSmall;
|
||||
}
|
||||
|
@ -775,6 +792,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
// Splitter
|
||||
size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter);
|
||||
|
||||
if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
|
||||
parameter.SplitterCount > 0 &&
|
||||
parameter.SplitterDestinationCount > 0)
|
||||
{
|
||||
size = WorkBufferAllocator.GetTargetSize<BiquadFilterState>(size, parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
|
||||
}
|
||||
|
||||
// DSP Voice
|
||||
size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align);
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// <see cref="Parameter.RendererInfoOutStatus"/> was added to supply the count of update done sent to the DSP.
|
||||
/// A new version of the command estimator was added to address timing changes caused by the voice changes.
|
||||
/// Additionally, the rendering limit percent was incremented to 80%.
|
||||
///
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 6.0.0</remarks>
|
||||
public const int Revision5 = 5 << 24;
|
||||
|
@ -101,10 +100,18 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// <remarks>This was added in system update 14.0.0 but some changes were made in 15.0.0</remarks>
|
||||
public const int Revision11 = 11 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// REV12:
|
||||
/// Two new commands were added to for biquad filtering and mixing (with optinal volume ramp) on the same command.
|
||||
/// Splitter destinations can now specify up to two biquad filtering parameters, used for filtering the buffer before mixing.
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 17.0.0</remarks>
|
||||
public const int Revision12 = 12 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// Last revision supported by the implementation.
|
||||
/// </summary>
|
||||
public const int LastRevision = Revision11;
|
||||
public const int LastRevision = Revision12;
|
||||
|
||||
/// <summary>
|
||||
/// Target revision magic supported by the implementation.
|
||||
|
@ -354,7 +361,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// Check if the audio renderer should use an optimized Biquad Filter (Direct Form 1) in case of two biquad filters are defined on a voice.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer should use the optimization.</returns>
|
||||
public bool IsBiquadFilterGroupedOptimizationSupported()
|
||||
public bool UseMultiTapBiquadFilterProcessing()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
|
||||
}
|
||||
|
@ -368,6 +375,15 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should support biquad filter on splitter.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer support biquad filter on splitter</returns>
|
||||
public bool IsBiquadFilterParameterForSplitterEnabled()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
|
||||
/// </summary>
|
||||
|
|
|
@ -204,7 +204,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="GroupedBiquadFilterCommand"/>.
|
||||
/// Create a new <see cref="MultiTapBiquadFilterCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="baseIndex">The base index of the input and output buffer.</param>
|
||||
/// <param name="filters">The biquad filter parameters.</param>
|
||||
|
@ -213,9 +213,9 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// <param name="outputBufferOffset">The output buffer offset.</param>
|
||||
/// <param name="isInitialized">Set to true if the biquad filter state is initialized.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateGroupedBiquadFilter(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
|
||||
public void GenerateMultiTapBiquadFilter(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
|
||||
{
|
||||
GroupedBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId);
|
||||
MultiTapBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
|
@ -232,7 +232,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// <param name="volume">The new volume.</param>
|
||||
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> previousVolume, Span<float> volume, Memory<VoiceUpdateState> state, int nodeId)
|
||||
public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, ReadOnlySpan<float> previousVolume, ReadOnlySpan<float> volume, Memory<VoiceUpdateState> state, int nodeId)
|
||||
{
|
||||
MixRampGroupedCommand command = new(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId);
|
||||
|
||||
|
@ -260,6 +260,120 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="BiquadFilterAndMixCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="previousVolume">The previous volume.</param>
|
||||
/// <param name="volume">The new volume.</param>
|
||||
/// <param name="inputBufferIndex">The input buffer index.</param>
|
||||
/// <param name="outputBufferIndex">The output buffer index.</param>
|
||||
/// <param name="lastSampleIndex">The index in the <see cref="VoiceUpdateState.LastSamples"/> array to store the ramped sample.</param>
|
||||
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||
/// <param name="filter">The biquad filter parameter.</param>
|
||||
/// <param name="biquadFilterState">The biquad state.</param>
|
||||
/// <param name="previousBiquadFilterState">The previous biquad state.</param>
|
||||
/// <param name="needInitialization">Set to true if the biquad filter state needs to be initialized.</param>
|
||||
/// <param name="hasVolumeRamp">Set to true if the mix has volume ramp, and <paramref name="previousVolume"/> should be taken into account.</param>
|
||||
/// <param name="isFirstMixBuffer">Set to true if the buffer is the first mix buffer.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateBiquadFilterAndMix(
|
||||
float previousVolume,
|
||||
float volume,
|
||||
uint inputBufferIndex,
|
||||
uint outputBufferIndex,
|
||||
int lastSampleIndex,
|
||||
Memory<VoiceUpdateState> state,
|
||||
ref BiquadFilterParameter filter,
|
||||
Memory<BiquadFilterState> biquadFilterState,
|
||||
Memory<BiquadFilterState> previousBiquadFilterState,
|
||||
bool needInitialization,
|
||||
bool hasVolumeRamp,
|
||||
bool isFirstMixBuffer,
|
||||
int nodeId)
|
||||
{
|
||||
BiquadFilterAndMixCommand command = new(
|
||||
previousVolume,
|
||||
volume,
|
||||
inputBufferIndex,
|
||||
outputBufferIndex,
|
||||
lastSampleIndex,
|
||||
state,
|
||||
ref filter,
|
||||
biquadFilterState,
|
||||
previousBiquadFilterState,
|
||||
needInitialization,
|
||||
hasVolumeRamp,
|
||||
isFirstMixBuffer,
|
||||
nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="MultiTapBiquadFilterAndMixCommand"/>.
|
||||
/// </summary>
|
||||
/// <param name="previousVolume">The previous volume.</param>
|
||||
/// <param name="volume">The new volume.</param>
|
||||
/// <param name="inputBufferIndex">The input buffer index.</param>
|
||||
/// <param name="outputBufferIndex">The output buffer index.</param>
|
||||
/// <param name="lastSampleIndex">The index in the <see cref="VoiceUpdateState.LastSamples"/> array to store the ramped sample.</param>
|
||||
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||
/// <param name="filter0">First biquad filter parameter.</param>
|
||||
/// <param name="filter1">Second biquad filter parameter.</param>
|
||||
/// <param name="biquadFilterState0">First biquad state.</param>
|
||||
/// <param name="biquadFilterState1">Second biquad state.</param>
|
||||
/// <param name="previousBiquadFilterState0">First previous biquad state.</param>
|
||||
/// <param name="previousBiquadFilterState1">Second previous biquad state.</param>
|
||||
/// <param name="needInitialization0">Set to true if the first biquad filter state needs to be initialized.</param>
|
||||
/// <param name="needInitialization1">Set to true if the second biquad filter state needs to be initialized.</param>
|
||||
/// <param name="hasVolumeRamp">Set to true if the mix has volume ramp, and <paramref name="previousVolume"/> should be taken into account.</param>
|
||||
/// <param name="isFirstMixBuffer">Set to true if the buffer is the first mix buffer.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateMultiTapBiquadFilterAndMix(
|
||||
float previousVolume,
|
||||
float volume,
|
||||
uint inputBufferIndex,
|
||||
uint outputBufferIndex,
|
||||
int lastSampleIndex,
|
||||
Memory<VoiceUpdateState> state,
|
||||
ref BiquadFilterParameter filter0,
|
||||
ref BiquadFilterParameter filter1,
|
||||
Memory<BiquadFilterState> biquadFilterState0,
|
||||
Memory<BiquadFilterState> biquadFilterState1,
|
||||
Memory<BiquadFilterState> previousBiquadFilterState0,
|
||||
Memory<BiquadFilterState> previousBiquadFilterState1,
|
||||
bool needInitialization0,
|
||||
bool needInitialization1,
|
||||
bool hasVolumeRamp,
|
||||
bool isFirstMixBuffer,
|
||||
int nodeId)
|
||||
{
|
||||
MultiTapBiquadFilterAndMixCommand command = new(
|
||||
previousVolume,
|
||||
volume,
|
||||
inputBufferIndex,
|
||||
outputBufferIndex,
|
||||
lastSampleIndex,
|
||||
state,
|
||||
ref filter0,
|
||||
ref filter1,
|
||||
biquadFilterState0,
|
||||
biquadFilterState1,
|
||||
previousBiquadFilterState0,
|
||||
previousBiquadFilterState1,
|
||||
needInitialization0,
|
||||
needInitialization1,
|
||||
hasVolumeRamp,
|
||||
isFirstMixBuffer,
|
||||
nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="DepopForMixBuffersCommand"/>.
|
||||
/// </summary>
|
||||
|
@ -268,7 +382,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// <param name="bufferCount">The buffer count.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
/// <param name="sampleRate">The target sample rate in use.</param>
|
||||
public void GenerateDepopForMixBuffersCommand(Memory<float> depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
|
||||
public void GenerateDepopForMixBuffers(Memory<float> depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
|
||||
{
|
||||
DepopForMixBuffersCommand command = new(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate);
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ using Ryujinx.Audio.Renderer.Server.Voice;
|
|||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
|
@ -46,7 +47,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
{
|
||||
ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
|
||||
|
||||
_commandBuffer.GenerateDepopPrepare(dspState,
|
||||
_commandBuffer.GenerateDepopPrepare(
|
||||
dspState,
|
||||
_rendererContext.DepopBuffer,
|
||||
mix.BufferCount,
|
||||
mix.BufferOffset,
|
||||
|
@ -59,15 +61,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
while (true)
|
||||
{
|
||||
Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
|
||||
SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
|
||||
|
||||
if (destinationSpan.IsEmpty)
|
||||
if (destination.IsNull)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ref SplitterDestination destination = ref destinationSpan[0];
|
||||
|
||||
if (destination.IsConfigured())
|
||||
{
|
||||
int mixId = destination.DestinationId;
|
||||
|
@ -76,7 +76,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
{
|
||||
ref MixState mix = ref _mixContext.GetState(mixId);
|
||||
|
||||
_commandBuffer.GenerateDepopPrepare(dspState,
|
||||
_commandBuffer.GenerateDepopPrepare(
|
||||
dspState,
|
||||
_rendererContext.DepopBuffer,
|
||||
mix.BufferCount,
|
||||
mix.BufferOffset,
|
||||
|
@ -95,7 +96,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported())
|
||||
{
|
||||
_commandBuffer.GenerateDataSourceVersion2(ref voiceState,
|
||||
_commandBuffer.GenerateDataSourceVersion2(
|
||||
ref voiceState,
|
||||
dspState,
|
||||
(ushort)_rendererContext.MixBufferCount,
|
||||
(ushort)channelIndex,
|
||||
|
@ -106,21 +108,24 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
switch (voiceState.SampleFormat)
|
||||
{
|
||||
case SampleFormat.PcmInt16:
|
||||
_commandBuffer.GeneratePcmInt16DataSourceVersion1(ref voiceState,
|
||||
_commandBuffer.GeneratePcmInt16DataSourceVersion1(
|
||||
ref voiceState,
|
||||
dspState,
|
||||
(ushort)_rendererContext.MixBufferCount,
|
||||
(ushort)channelIndex,
|
||||
voiceState.NodeId);
|
||||
break;
|
||||
case SampleFormat.PcmFloat:
|
||||
_commandBuffer.GeneratePcmFloatDataSourceVersion1(ref voiceState,
|
||||
_commandBuffer.GeneratePcmFloatDataSourceVersion1(
|
||||
ref voiceState,
|
||||
dspState,
|
||||
(ushort)_rendererContext.MixBufferCount,
|
||||
(ushort)channelIndex,
|
||||
voiceState.NodeId);
|
||||
break;
|
||||
case SampleFormat.Adpcm:
|
||||
_commandBuffer.GenerateAdpcmDataSourceVersion1(ref voiceState,
|
||||
_commandBuffer.GenerateAdpcmDataSourceVersion1(
|
||||
ref voiceState,
|
||||
dspState,
|
||||
(ushort)_rendererContext.MixBufferCount,
|
||||
voiceState.NodeId);
|
||||
|
@ -134,14 +139,14 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory<VoiceUpdateState> state, int baseIndex, int bufferOffset, int nodeId)
|
||||
{
|
||||
bool supportsOptimizedPath = _rendererContext.BehaviourContext.IsBiquadFilterGroupedOptimizationSupported();
|
||||
bool supportsOptimizedPath = _rendererContext.BehaviourContext.UseMultiTapBiquadFilterProcessing();
|
||||
|
||||
if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable)
|
||||
{
|
||||
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
|
||||
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)];
|
||||
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
|
||||
|
||||
_commandBuffer.GenerateGroupedBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId);
|
||||
_commandBuffer.GenerateMultiTapBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -151,11 +156,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
if (filter.Enable)
|
||||
{
|
||||
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
|
||||
|
||||
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)];
|
||||
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
|
||||
|
||||
_commandBuffer.GenerateBiquadFilter(baseIndex,
|
||||
_commandBuffer.GenerateBiquadFilter(
|
||||
baseIndex,
|
||||
ref filter,
|
||||
stateMemory.Slice(i, 1),
|
||||
bufferOffset,
|
||||
|
@ -167,11 +172,112 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
}
|
||||
|
||||
private void GenerateVoiceMix(Span<float> mixVolumes, Span<float> previousMixVolumes, Memory<VoiceUpdateState> state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId)
|
||||
private void GenerateVoiceMixWithSplitter(
|
||||
SplitterDestination destination,
|
||||
Memory<VoiceUpdateState> state,
|
||||
uint bufferOffset,
|
||||
uint bufferCount,
|
||||
uint bufferIndex,
|
||||
int nodeId)
|
||||
{
|
||||
ReadOnlySpan<float> mixVolumes = destination.MixBufferVolume;
|
||||
ReadOnlySpan<float> previousMixVolumes = destination.PreviousMixBufferVolume;
|
||||
|
||||
ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
|
||||
ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
|
||||
|
||||
Memory<BiquadFilterState> bqfState = _splitterContext.GetBiquadFilterState(destination);
|
||||
|
||||
bool isFirstMixBuffer = true;
|
||||
|
||||
for (int i = 0; i < bufferCount; i++)
|
||||
{
|
||||
float previousMixVolume = previousMixVolumes[i];
|
||||
float mixVolume = mixVolumes[i];
|
||||
|
||||
if (mixVolume != 0.0f || previousMixVolume != 0.0f)
|
||||
{
|
||||
if (bqf0.Enable && bqf1.Enable)
|
||||
{
|
||||
_commandBuffer.GenerateMultiTapBiquadFilterAndMix(
|
||||
previousMixVolume,
|
||||
mixVolume,
|
||||
bufferIndex,
|
||||
bufferOffset + (uint)i,
|
||||
i,
|
||||
state,
|
||||
ref bqf0,
|
||||
ref bqf1,
|
||||
bqfState[..1],
|
||||
bqfState.Slice(1, 1),
|
||||
bqfState.Slice(2, 1),
|
||||
bqfState.Slice(3, 1),
|
||||
!destination.IsBiquadFilterEnabledPrev(),
|
||||
!destination.IsBiquadFilterEnabledPrev(),
|
||||
true,
|
||||
isFirstMixBuffer,
|
||||
nodeId);
|
||||
|
||||
destination.UpdateBiquadFilterEnabledPrev(0);
|
||||
destination.UpdateBiquadFilterEnabledPrev(1);
|
||||
}
|
||||
else if (bqf0.Enable)
|
||||
{
|
||||
_commandBuffer.GenerateBiquadFilterAndMix(
|
||||
previousMixVolume,
|
||||
mixVolume,
|
||||
bufferIndex,
|
||||
bufferOffset + (uint)i,
|
||||
i,
|
||||
state,
|
||||
ref bqf0,
|
||||
bqfState[..1],
|
||||
bqfState.Slice(1, 1),
|
||||
!destination.IsBiquadFilterEnabledPrev(),
|
||||
true,
|
||||
isFirstMixBuffer,
|
||||
nodeId);
|
||||
|
||||
destination.UpdateBiquadFilterEnabledPrev(0);
|
||||
}
|
||||
else if (bqf1.Enable)
|
||||
{
|
||||
_commandBuffer.GenerateBiquadFilterAndMix(
|
||||
previousMixVolume,
|
||||
mixVolume,
|
||||
bufferIndex,
|
||||
bufferOffset + (uint)i,
|
||||
i,
|
||||
state,
|
||||
ref bqf1,
|
||||
bqfState[..1],
|
||||
bqfState.Slice(1, 1),
|
||||
!destination.IsBiquadFilterEnabledPrev(),
|
||||
true,
|
||||
isFirstMixBuffer,
|
||||
nodeId);
|
||||
|
||||
destination.UpdateBiquadFilterEnabledPrev(1);
|
||||
}
|
||||
|
||||
isFirstMixBuffer = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateVoiceMix(
|
||||
ReadOnlySpan<float> mixVolumes,
|
||||
ReadOnlySpan<float> previousMixVolumes,
|
||||
Memory<VoiceUpdateState> state,
|
||||
uint bufferOffset,
|
||||
uint bufferCount,
|
||||
uint bufferIndex,
|
||||
int nodeId)
|
||||
{
|
||||
if (bufferCount > Constants.VoiceChannelCountMax)
|
||||
{
|
||||
_commandBuffer.GenerateMixRampGrouped(bufferCount,
|
||||
_commandBuffer.GenerateMixRampGrouped(
|
||||
bufferCount,
|
||||
bufferIndex,
|
||||
bufferOffset,
|
||||
previousMixVolumes,
|
||||
|
@ -188,7 +294,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
if (mixVolume != 0.0f || previousMixVolume != 0.0f)
|
||||
{
|
||||
_commandBuffer.GenerateMixRamp(previousMixVolume,
|
||||
_commandBuffer.GenerateMixRamp(
|
||||
previousMixVolume,
|
||||
mixVolume,
|
||||
bufferIndex,
|
||||
bufferOffset + (uint)i,
|
||||
|
@ -271,7 +378,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
_commandBuffer.GenerateVolumeRamp(voiceState.PreviousVolume,
|
||||
_commandBuffer.GenerateVolumeRamp(
|
||||
voiceState.PreviousVolume,
|
||||
voiceState.Volume,
|
||||
_rendererContext.MixBufferCount + (uint)channelIndex,
|
||||
nodeId);
|
||||
|
@ -291,15 +399,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
while (true)
|
||||
{
|
||||
Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
|
||||
SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
|
||||
|
||||
if (destinationSpan.IsEmpty)
|
||||
if (destination.IsNull)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ref SplitterDestination destination = ref destinationSpan[0];
|
||||
|
||||
destinationId += (int)channelsCount;
|
||||
|
||||
if (destination.IsConfigured())
|
||||
|
@ -310,13 +416,27 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
{
|
||||
ref MixState mix = ref _mixContext.GetState(mixId);
|
||||
|
||||
GenerateVoiceMix(destination.MixBufferVolume,
|
||||
if (destination.IsBiquadFilterEnabled())
|
||||
{
|
||||
GenerateVoiceMixWithSplitter(
|
||||
destination,
|
||||
dspStateMemory,
|
||||
mix.BufferOffset,
|
||||
mix.BufferCount,
|
||||
_rendererContext.MixBufferCount + (uint)channelIndex,
|
||||
nodeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
GenerateVoiceMix(
|
||||
destination.MixBufferVolume,
|
||||
destination.PreviousMixBufferVolume,
|
||||
dspStateMemory,
|
||||
mix.BufferOffset,
|
||||
mix.BufferCount,
|
||||
_rendererContext.MixBufferCount + (uint)channelIndex,
|
||||
nodeId);
|
||||
}
|
||||
|
||||
destination.MarkAsNeedToUpdateInternalState();
|
||||
}
|
||||
|
@ -337,7 +457,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
GenerateVoiceMix(channelResource.Mix.AsSpan(),
|
||||
GenerateVoiceMix(
|
||||
channelResource.Mix.AsSpan(),
|
||||
channelResource.PreviousMix.AsSpan(),
|
||||
dspStateMemory,
|
||||
mix.BufferOffset,
|
||||
|
@ -409,7 +530,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
{
|
||||
if (effect.Parameter.Volumes[i] != 0.0f)
|
||||
{
|
||||
_commandBuffer.GenerateMix((uint)bufferOffset + effect.Parameter.Input[i],
|
||||
_commandBuffer.GenerateMix(
|
||||
(uint)bufferOffset + effect.Parameter.Input[i],
|
||||
(uint)bufferOffset + effect.Parameter.Output[i],
|
||||
nodeId,
|
||||
effect.Parameter.Volumes[i]);
|
||||
|
@ -447,7 +569,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
updateCount = newUpdateCount;
|
||||
}
|
||||
|
||||
_commandBuffer.GenerateAuxEffect(bufferOffset,
|
||||
_commandBuffer.GenerateAuxEffect(
|
||||
bufferOffset,
|
||||
effect.Parameter.Input[i],
|
||||
effect.Parameter.Output[i],
|
||||
ref effect.State,
|
||||
|
@ -512,7 +635,10 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
for (int i = 0; i < effect.Parameter.ChannelCount; i++)
|
||||
{
|
||||
_commandBuffer.GenerateBiquadFilter((int)bufferOffset, ref parameter, effect.State.Slice(i, 1),
|
||||
_commandBuffer.GenerateBiquadFilter(
|
||||
(int)bufferOffset,
|
||||
ref parameter,
|
||||
effect.State.Slice(i, 1),
|
||||
effect.Parameter.Input[i],
|
||||
effect.Parameter.Output[i],
|
||||
needInitialization,
|
||||
|
@ -591,7 +717,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
updateCount = newUpdateCount;
|
||||
}
|
||||
|
||||
_commandBuffer.GenerateCaptureEffect(bufferOffset,
|
||||
_commandBuffer.GenerateCaptureEffect(
|
||||
bufferOffset,
|
||||
effect.Parameter.Input[i],
|
||||
effect.State.SendBufferInfo,
|
||||
effect.IsEnabled,
|
||||
|
@ -612,7 +739,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
{
|
||||
Debug.Assert(effect.Type == EffectType.Compressor);
|
||||
|
||||
_commandBuffer.GenerateCompressorEffect(bufferOffset,
|
||||
_commandBuffer.GenerateCompressorEffect(
|
||||
bufferOffset,
|
||||
effect.Parameter,
|
||||
effect.State,
|
||||
effect.IsEnabled,
|
||||
|
@ -629,8 +757,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
bool performanceInitialized = false;
|
||||
|
||||
if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, effect.GetPerformanceDetailType(),
|
||||
isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, nodeId))
|
||||
if (_performanceManager != null && _performanceManager.GetNextEntry(
|
||||
out performanceEntry,
|
||||
effect.GetPerformanceDetailType(),
|
||||
isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix,
|
||||
nodeId))
|
||||
{
|
||||
performanceInitialized = true;
|
||||
|
||||
|
@ -706,6 +837,85 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
}
|
||||
|
||||
private void GenerateMixWithSplitter(
|
||||
uint inputBufferIndex,
|
||||
uint outputBufferIndex,
|
||||
float volume,
|
||||
SplitterDestination destination,
|
||||
ref bool isFirstMixBuffer,
|
||||
int nodeId)
|
||||
{
|
||||
ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
|
||||
ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
|
||||
|
||||
Memory<BiquadFilterState> bqfState = _splitterContext.GetBiquadFilterState(destination);
|
||||
|
||||
if (bqf0.Enable && bqf1.Enable)
|
||||
{
|
||||
_commandBuffer.GenerateMultiTapBiquadFilterAndMix(
|
||||
0f,
|
||||
volume,
|
||||
inputBufferIndex,
|
||||
outputBufferIndex,
|
||||
0,
|
||||
Memory<VoiceUpdateState>.Empty,
|
||||
ref bqf0,
|
||||
ref bqf1,
|
||||
bqfState[..1],
|
||||
bqfState.Slice(1, 1),
|
||||
bqfState.Slice(2, 1),
|
||||
bqfState.Slice(3, 1),
|
||||
!destination.IsBiquadFilterEnabledPrev(),
|
||||
!destination.IsBiquadFilterEnabledPrev(),
|
||||
false,
|
||||
isFirstMixBuffer,
|
||||
nodeId);
|
||||
|
||||
destination.UpdateBiquadFilterEnabledPrev(0);
|
||||
destination.UpdateBiquadFilterEnabledPrev(1);
|
||||
}
|
||||
else if (bqf0.Enable)
|
||||
{
|
||||
_commandBuffer.GenerateBiquadFilterAndMix(
|
||||
0f,
|
||||
volume,
|
||||
inputBufferIndex,
|
||||
outputBufferIndex,
|
||||
0,
|
||||
Memory<VoiceUpdateState>.Empty,
|
||||
ref bqf0,
|
||||
bqfState[..1],
|
||||
bqfState.Slice(1, 1),
|
||||
!destination.IsBiquadFilterEnabledPrev(),
|
||||
false,
|
||||
isFirstMixBuffer,
|
||||
nodeId);
|
||||
|
||||
destination.UpdateBiquadFilterEnabledPrev(0);
|
||||
}
|
||||
else if (bqf1.Enable)
|
||||
{
|
||||
_commandBuffer.GenerateBiquadFilterAndMix(
|
||||
0f,
|
||||
volume,
|
||||
inputBufferIndex,
|
||||
outputBufferIndex,
|
||||
0,
|
||||
Memory<VoiceUpdateState>.Empty,
|
||||
ref bqf1,
|
||||
bqfState[..1],
|
||||
bqfState.Slice(1, 1),
|
||||
!destination.IsBiquadFilterEnabledPrev(),
|
||||
false,
|
||||
isFirstMixBuffer,
|
||||
nodeId);
|
||||
|
||||
destination.UpdateBiquadFilterEnabledPrev(1);
|
||||
}
|
||||
|
||||
isFirstMixBuffer = false;
|
||||
}
|
||||
|
||||
private void GenerateMix(ref MixState mix)
|
||||
{
|
||||
if (mix.HasAnyDestination())
|
||||
|
@ -722,15 +932,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
{
|
||||
int destinationIndex = destinationId++;
|
||||
|
||||
Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
|
||||
SplitterDestination destination = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
|
||||
|
||||
if (destinationSpan.IsEmpty)
|
||||
if (destination.IsNull)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ref SplitterDestination destination = ref destinationSpan[0];
|
||||
|
||||
if (destination.IsConfigured())
|
||||
{
|
||||
int mixId = destination.DestinationId;
|
||||
|
@ -741,13 +949,28 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount);
|
||||
|
||||
bool isFirstMixBuffer = true;
|
||||
|
||||
for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
|
||||
{
|
||||
float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex);
|
||||
|
||||
if (volume != 0.0f)
|
||||
{
|
||||
_commandBuffer.GenerateMix(inputBufferIndex,
|
||||
if (destination.IsBiquadFilterEnabled())
|
||||
{
|
||||
GenerateMixWithSplitter(
|
||||
inputBufferIndex,
|
||||
destinationMix.BufferOffset + bufferDestinationIndex,
|
||||
volume,
|
||||
destination,
|
||||
ref isFirstMixBuffer,
|
||||
mix.NodeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_commandBuffer.GenerateMix(
|
||||
inputBufferIndex,
|
||||
destinationMix.BufferOffset + bufferDestinationIndex,
|
||||
mix.NodeId,
|
||||
volume);
|
||||
|
@ -758,6 +981,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ref MixState destinationMix = ref _mixContext.GetState(mix.DestinationMixId);
|
||||
|
@ -770,7 +994,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
if (volume != 0.0f)
|
||||
{
|
||||
_commandBuffer.GenerateMix(mix.BufferOffset + bufferIndex,
|
||||
_commandBuffer.GenerateMix(
|
||||
mix.BufferOffset + bufferIndex,
|
||||
destinationMix.BufferOffset + bufferDestinationIndex,
|
||||
mix.NodeId,
|
||||
volume);
|
||||
|
@ -783,7 +1008,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
private void GenerateSubMix(ref MixState subMix)
|
||||
{
|
||||
_commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
|
||||
_commandBuffer.GenerateDepopForMixBuffers(
|
||||
_rendererContext.DepopBuffer,
|
||||
subMix.BufferOffset,
|
||||
subMix.BufferCount,
|
||||
subMix.NodeId,
|
||||
|
@ -847,7 +1073,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
{
|
||||
ref MixState finalMix = ref _mixContext.GetFinalState();
|
||||
|
||||
_commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
|
||||
_commandBuffer.GenerateDepopForMixBuffers(
|
||||
_rendererContext.DepopBuffer,
|
||||
finalMix.BufferOffset,
|
||||
finalMix.BufferCount,
|
||||
finalMix.NodeId,
|
||||
|
@ -882,7 +1109,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||
}
|
||||
|
||||
_commandBuffer.GenerateVolume(finalMix.Volume,
|
||||
_commandBuffer.GenerateVolume(
|
||||
finalMix.Volume,
|
||||
finalMix.BufferOffset + bufferIndex,
|
||||
nodeId);
|
||||
|
||||
|
@ -938,7 +1166,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
if (useCustomDownMixingCommand)
|
||||
{
|
||||
_commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
|
||||
_commandBuffer.GenerateDownMixSurroundToStereo(
|
||||
finalMix.BufferOffset,
|
||||
sink.Parameter.Input.AsSpan(),
|
||||
sink.Parameter.Input.AsSpan(),
|
||||
sink.DownMixCoefficients,
|
||||
|
@ -947,7 +1176,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
// NOTE: We do the downmixing at the DSP level as it's easier that way.
|
||||
else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6)
|
||||
{
|
||||
_commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
|
||||
_commandBuffer.GenerateDownMixSurroundToStereo(
|
||||
finalMix.BufferOffset,
|
||||
sink.Parameter.Input.AsSpan(),
|
||||
sink.Parameter.Input.AsSpan(),
|
||||
Constants.DefaultSurroundToStereoCoefficients,
|
||||
|
@ -958,7 +1188,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
|
||||
if (sink.UpsamplerState != null)
|
||||
{
|
||||
_commandBuffer.GenerateUpsample(finalMix.BufferOffset,
|
||||
_commandBuffer.GenerateUpsample(
|
||||
finalMix.BufferOffset,
|
||||
sink.UpsamplerState,
|
||||
sink.Parameter.InputCount,
|
||||
sink.Parameter.Input.AsSpan(),
|
||||
|
@ -968,7 +1199,8 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
Constants.InvalidNodeId);
|
||||
}
|
||||
|
||||
_commandBuffer.GenerateDeviceSink(finalMix.BufferOffset,
|
||||
_commandBuffer.GenerateDeviceSink(
|
||||
finalMix.BufferOffset,
|
||||
sink,
|
||||
_rendererContext.SessionId,
|
||||
commandList.Buffers,
|
||||
|
|
|
@ -170,7 +170,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return 0;
|
||||
}
|
||||
|
||||
public uint Estimate(GroupedBiquadFilterCommand command)
|
||||
public uint Estimate(MultiTapBiquadFilterCommand command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
@ -184,5 +184,15 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint Estimate(BiquadFilterAndMixCommand command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -462,7 +462,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return 0;
|
||||
}
|
||||
|
||||
public uint Estimate(GroupedBiquadFilterCommand command)
|
||||
public uint Estimate(MultiTapBiquadFilterCommand command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
@ -476,5 +476,15 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint Estimate(BiquadFilterAndMixCommand command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -632,7 +632,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
};
|
||||
}
|
||||
|
||||
public virtual uint Estimate(GroupedBiquadFilterCommand command)
|
||||
public virtual uint Estimate(MultiTapBiquadFilterCommand command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
@ -646,5 +646,15 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public virtual uint Estimate(BiquadFilterAndMixCommand command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public virtual uint Estimate(MultiTapBiquadFilterAndMixCommand command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
{
|
||||
public CommandProcessingTimeEstimatorVersion4(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { }
|
||||
|
||||
public override uint Estimate(GroupedBiquadFilterCommand command)
|
||||
public override uint Estimate(MultiTapBiquadFilterCommand command)
|
||||
{
|
||||
Debug.Assert(SampleCount == 160 || SampleCount == 240);
|
||||
|
||||
|
|
|
@ -210,5 +210,53 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
|
||||
};
|
||||
}
|
||||
|
||||
public override uint Estimate(BiquadFilterAndMixCommand command)
|
||||
{
|
||||
Debug.Assert(SampleCount == 160 || SampleCount == 240);
|
||||
|
||||
if (command.HasVolumeRamp)
|
||||
{
|
||||
if (SampleCount == 160)
|
||||
{
|
||||
return 5204;
|
||||
}
|
||||
|
||||
return 6683;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SampleCount == 160)
|
||||
{
|
||||
return 3427;
|
||||
}
|
||||
|
||||
return 4752;
|
||||
}
|
||||
}
|
||||
|
||||
public override uint Estimate(MultiTapBiquadFilterAndMixCommand command)
|
||||
{
|
||||
Debug.Assert(SampleCount == 160 || SampleCount == 240);
|
||||
|
||||
if (command.HasVolumeRamp)
|
||||
{
|
||||
if (SampleCount == 160)
|
||||
{
|
||||
return 7939;
|
||||
}
|
||||
|
||||
return 10669;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SampleCount == 160)
|
||||
{
|
||||
return 6256;
|
||||
}
|
||||
|
||||
return 8683;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,8 +33,10 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
uint Estimate(UpsampleCommand command);
|
||||
uint Estimate(LimiterCommandVersion1 command);
|
||||
uint Estimate(LimiterCommandVersion2 command);
|
||||
uint Estimate(GroupedBiquadFilterCommand command);
|
||||
uint Estimate(MultiTapBiquadFilterCommand command);
|
||||
uint Estimate(CaptureBufferCommand command);
|
||||
uint Estimate(CompressorCommand command);
|
||||
uint Estimate(BiquadFilterAndMixCommand command);
|
||||
uint Estimate(MultiTapBiquadFilterAndMixCommand command);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -225,11 +225,11 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
|
|||
|
||||
for (int i = 0; i < splitter.DestinationCount; i++)
|
||||
{
|
||||
Span<SplitterDestination> destination = splitter.GetData(i);
|
||||
SplitterDestination destination = splitter.GetData(i);
|
||||
|
||||
if (!destination.IsEmpty)
|
||||
if (!destination.IsNull)
|
||||
{
|
||||
int destinationMixId = destination[0].DestinationId;
|
||||
int destinationMixId = destination.DestinationId;
|
||||
|
||||
if (destinationMixId != UnusedMixId)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Audio.Renderer.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using Ryujinx.Common;
|
||||
|
@ -15,15 +16,35 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
public class SplitterContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Amount of biquad filter states per splitter destination.
|
||||
/// </summary>
|
||||
public const int BqfStatesPerDestination = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for <see cref="SplitterState"/>.
|
||||
/// </summary>
|
||||
private Memory<SplitterState> _splitters;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for <see cref="SplitterDestination"/>.
|
||||
/// Storage for <see cref="SplitterDestinationVersion1"/>.
|
||||
/// </summary>
|
||||
private Memory<SplitterDestination> _splitterDestinations;
|
||||
private Memory<SplitterDestinationVersion1> _splitterDestinationsV1;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for <see cref="SplitterDestinationVersion2"/>.
|
||||
/// </summary>
|
||||
private Memory<SplitterDestinationVersion2> _splitterDestinationsV2;
|
||||
|
||||
/// <summary>
|
||||
/// Splitter biquad filtering states.
|
||||
/// </summary>
|
||||
private Memory<BiquadFilterState> _splitterBqfStates;
|
||||
|
||||
/// <summary>
|
||||
/// Version of the splitter context that is being used, currently can be 1 or 2.
|
||||
/// </summary>
|
||||
public int Version { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.
|
||||
|
@ -36,12 +57,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
/// <param name="parameter">The audio renderer configuration.</param>
|
||||
/// <param name="workBufferAllocator">The <see cref="WorkBufferAllocator"/>.</param>
|
||||
/// <param name="splitterBqfStates">Memory to store the biquad filtering state for splitters during processing.</param>
|
||||
/// <returns>Return true if the initialization was successful.</returns>
|
||||
public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator)
|
||||
public bool Initialize(
|
||||
ref BehaviourContext behaviourContext,
|
||||
ref AudioRendererConfiguration parameter,
|
||||
WorkBufferAllocator workBufferAllocator,
|
||||
Memory<BiquadFilterState> splitterBqfStates)
|
||||
{
|
||||
if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0)
|
||||
{
|
||||
Setup(Memory<SplitterState>.Empty, Memory<SplitterDestination>.Empty, false);
|
||||
Setup(Memory<SplitterState>.Empty, Memory<SplitterDestinationVersion1>.Empty, Memory<SplitterDestinationVersion2>.Empty, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -60,23 +86,62 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
splitter = new SplitterState(splitterId++);
|
||||
}
|
||||
|
||||
Memory<SplitterDestination> splitterDestinations = workBufferAllocator.Allocate<SplitterDestination>(parameter.SplitterDestinationCount,
|
||||
SplitterDestination.Alignment);
|
||||
Memory<SplitterDestinationVersion1> splitterDestinationsV1 = Memory<SplitterDestinationVersion1>.Empty;
|
||||
Memory<SplitterDestinationVersion2> splitterDestinationsV2 = Memory<SplitterDestinationVersion2>.Empty;
|
||||
|
||||
if (splitterDestinations.IsEmpty)
|
||||
if (!behaviourContext.IsBiquadFilterParameterForSplitterEnabled())
|
||||
{
|
||||
Version = 1;
|
||||
|
||||
splitterDestinationsV1 = workBufferAllocator.Allocate<SplitterDestinationVersion1>(parameter.SplitterDestinationCount,
|
||||
SplitterDestinationVersion1.Alignment);
|
||||
|
||||
if (splitterDestinationsV1.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int splitterDestinationId = 0;
|
||||
foreach (ref SplitterDestination data in splitterDestinations.Span)
|
||||
foreach (ref SplitterDestinationVersion1 data in splitterDestinationsV1.Span)
|
||||
{
|
||||
data = new SplitterDestination(splitterDestinationId++);
|
||||
data = new SplitterDestinationVersion1(splitterDestinationId++);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Version = 2;
|
||||
|
||||
splitterDestinationsV2 = workBufferAllocator.Allocate<SplitterDestinationVersion2>(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<BiquadFilterState>.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
SplitterState.InitializeSplitters(splitters.Span);
|
||||
|
||||
Setup(splitters, splitterDestinations, behaviourContext.IsSplitterBugFixed());
|
||||
Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -93,7 +158,15 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
if (behaviourContext.IsSplitterSupported())
|
||||
{
|
||||
size = WorkBufferAllocator.GetTargetSize<SplitterState>(size, parameter.SplitterCount, SplitterState.Alignment);
|
||||
size = WorkBufferAllocator.GetTargetSize<SplitterDestination>(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment);
|
||||
|
||||
if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled())
|
||||
{
|
||||
size = WorkBufferAllocator.GetTargetSize<SplitterDestinationVersion2>(size, parameter.SplitterDestinationCount, SplitterDestinationVersion2.Alignment);
|
||||
}
|
||||
else
|
||||
{
|
||||
size = WorkBufferAllocator.GetTargetSize<SplitterDestinationVersion1>(size, parameter.SplitterDestinationCount, SplitterDestinationVersion1.Alignment);
|
||||
}
|
||||
|
||||
if (behaviourContext.IsSplitterBugFixed())
|
||||
{
|
||||
|
@ -110,12 +183,18 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// Setup the <see cref="SplitterContext"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="splitters">The <see cref="SplitterState"/> storage.</param>
|
||||
/// <param name="splitterDestinations">The <see cref="SplitterDestination"/> storage.</param>
|
||||
/// <param name="splitterDestinationsV1">The <see cref="SplitterDestinationVersion1"/> storage.</param>
|
||||
/// <param name="splitterDestinationsV2">The <see cref="SplitterDestinationVersion2"/> storage.</param>
|
||||
/// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.</param>
|
||||
private void Setup(Memory<SplitterState> splitters, Memory<SplitterDestination> splitterDestinations, bool isBugFixed)
|
||||
private void Setup(
|
||||
Memory<SplitterState> splitters,
|
||||
Memory<SplitterDestinationVersion1> splitterDestinationsV1,
|
||||
Memory<SplitterDestinationVersion2> splitterDestinationsV2,
|
||||
bool isBugFixed)
|
||||
{
|
||||
_splitters = splitters;
|
||||
_splitterDestinations = splitterDestinations;
|
||||
_splitterDestinationsV1 = splitterDestinationsV1;
|
||||
_splitterDestinationsV2 = splitterDestinationsV2;
|
||||
IsBugFixed = isBugFixed;
|
||||
}
|
||||
|
||||
|
@ -141,7 +220,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
return 0;
|
||||
}
|
||||
|
||||
return _splitterDestinations.Length / _splitters.Length;
|
||||
int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length;
|
||||
|
||||
return length / _splitters.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -178,7 +259,39 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update one or multiple <see cref="SplitterDestination"/> from user parameters.
|
||||
/// Update one splitter destination data from user parameters.
|
||||
/// </summary>
|
||||
/// <param name="input">The raw data after the splitter header.</param>
|
||||
/// <returns>True if the update was successful, false otherwise</returns>
|
||||
private bool UpdateData<T>(ref SequenceReader<byte> input) where T : unmanaged, ISplitterDestinationInParameter
|
||||
{
|
||||
ref readonly T parameter = ref input.GetRefOrRefToCopy<T>(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<T>());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update one or multiple splitter destination data from user parameters.
|
||||
/// </summary>
|
||||
/// <param name="inputHeader">The splitter header.</param>
|
||||
/// <param name="input">The raw data after the splitter header.</param>
|
||||
|
@ -186,23 +299,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
{
|
||||
for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
|
||||
{
|
||||
ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy<SplitterDestinationInParameter>(out _);
|
||||
|
||||
Debug.Assert(parameter.IsMagicValid());
|
||||
|
||||
if (parameter.IsMagicValid())
|
||||
if (Version == 1)
|
||||
{
|
||||
if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length)
|
||||
if (!UpdateData<SplitterDestinationInParameterVersion1>(ref input))
|
||||
{
|
||||
ref SplitterDestination destination = ref GetDestination(parameter.Id);
|
||||
|
||||
destination.Update(parameter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (Version == 2)
|
||||
{
|
||||
if (!UpdateData<SplitterDestinationInParameterVersion2>(ref input))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
input.Rewind(Unsafe.SizeOf<SplitterDestinationInParameter>());
|
||||
break;
|
||||
Debug.Fail($"Invalid splitter context version {Version}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +327,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// <returns>Return true if the update was successful.</returns>
|
||||
public bool Update(ref SequenceReader<byte> input)
|
||||
{
|
||||
if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
|
||||
if (!UsingSplitter())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -251,45 +364,52 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>.
|
||||
/// Get a reference to the splitter destination data at the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use.</param>
|
||||
/// <returns>A reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>.</returns>
|
||||
public ref SplitterDestination GetDestination(int id)
|
||||
/// <returns>A reference to the splitter destination data at the given <paramref name="id"/>.</returns>
|
||||
public SplitterDestination GetDestination(int id)
|
||||
{
|
||||
return ref SpanIOHelper.GetFromMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use.</param>
|
||||
/// <returns>A <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.</returns>
|
||||
public Memory<SplitterDestination> GetDestinationMemory(int id)
|
||||
{
|
||||
return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="Span{SplitterDestination}"/> in the <see cref="SplitterState"/> at <paramref name="id"/> and pass <paramref name="destinationId"/> to <see cref="SplitterState.GetData(int)"/>.
|
||||
/// Get a <see cref="SplitterDestination"/> in the <see cref="SplitterState"/> at <paramref name="id"/> and pass <paramref name="destinationId"/> to <see cref="SplitterState.GetData(int)"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The index to use to get the <see cref="SplitterState"/>.</param>
|
||||
/// <param name="destinationId">The index of the <see cref="SplitterDestination"/>.</param>
|
||||
/// <returns>A <see cref="Span{SplitterDestination}"/>.</returns>
|
||||
public Span<SplitterDestination> GetDestination(int id, int destinationId)
|
||||
/// <returns>A <see cref="SplitterDestination"/>.</returns>
|
||||
public SplitterDestination GetDestination(int id, int destinationId)
|
||||
{
|
||||
ref SplitterState splitter = ref GetState(id);
|
||||
|
||||
return splitter.GetData(destinationId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the biquad filter state for a given splitter destination.
|
||||
/// </summary>
|
||||
/// <param name="destination">The splitter destination.</param>
|
||||
/// <returns>Biquad filter state for the specified destination.</returns>
|
||||
public Memory<BiquadFilterState> GetBiquadFilterState(SplitterDestination destination)
|
||||
{
|
||||
return _splitterBqfStates.Slice(destination.Id * BqfStatesPerDestination, BqfStatesPerDestination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the audio renderer has any splitters.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer has any splitters.</returns>
|
||||
public bool UsingSplitter()
|
||||
{
|
||||
return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty;
|
||||
return !_splitters.IsEmpty && (!_splitterDestinationsV1.IsEmpty || !_splitterDestinationsV2.IsEmpty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,115 +1,198 @@
|
|||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a splitter destination.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)]
|
||||
public struct SplitterDestination
|
||||
public ref struct SplitterDestination
|
||||
{
|
||||
public const int Alignment = 0x10;
|
||||
private ref SplitterDestinationVersion1 _v1;
|
||||
private ref SplitterDestinationVersion2 _v2;
|
||||
|
||||
/// <summary>
|
||||
/// The unique id of this <see cref="SplitterDestination"/>.
|
||||
/// Checks if the splitter destination data reference is null.
|
||||
/// </summary>
|
||||
public int Id;
|
||||
public bool IsNull => Unsafe.IsNullRef(ref _v1) && Unsafe.IsNullRef(ref _v2);
|
||||
|
||||
/// <summary>
|
||||
/// The splitter unique id.
|
||||
/// </summary>
|
||||
public int Id
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Unsafe.IsNullRef(ref _v2))
|
||||
{
|
||||
if (Unsafe.IsNullRef(ref _v1))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _v1.Id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return _v2.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The mix to output the result of the splitter.
|
||||
/// </summary>
|
||||
public int DestinationId;
|
||||
|
||||
/// <summary>
|
||||
/// Mix buffer volumes storage.
|
||||
/// </summary>
|
||||
private MixArray _mix;
|
||||
private MixArray _previousMix;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the next linked element.
|
||||
/// </summary>
|
||||
private unsafe SplitterDestination* _next;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if in use.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsUsed;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the internal state need to be updated.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool NeedToUpdateInternalState;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
|
||||
private struct MixArray { }
|
||||
public int DestinationId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Unsafe.IsNullRef(ref _v2))
|
||||
{
|
||||
if (Unsafe.IsNullRef(ref _v1))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _v1.DestinationId;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return _v2.DestinationId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 _mix);
|
||||
public Span<float> MixBufferVolume
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Unsafe.IsNullRef(ref _v2))
|
||||
{
|
||||
if (Unsafe.IsNullRef(ref _v1))
|
||||
{
|
||||
return Span<float>.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _v1.MixBufferVolume;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return _v2.MixBufferVolume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Previous mix buffer volumes.
|
||||
/// </summary>
|
||||
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||
public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
|
||||
public Span<float> PreviousMixBufferVolume
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Unsafe.IsNullRef(ref _v2))
|
||||
{
|
||||
if (Unsafe.IsNullRef(ref _v1))
|
||||
{
|
||||
return Span<float>.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _v1.PreviousMixBufferVolume;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return _v2.PreviousMixBufferVolume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Span{SplitterDestination}"/> of the next element or <see cref="Span{SplitterDestination}.Empty"/> if not present.
|
||||
/// Get the <see cref="SplitterDestination"/> of the next element or null if not present.
|
||||
/// </summary>
|
||||
public readonly Span<SplitterDestination> Next
|
||||
public readonly SplitterDestination Next
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return _next != null ? new Span<SplitterDestination>(_next, 1) : Span<SplitterDestination>.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="SplitterDestination"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique id of this <see cref="SplitterDestination"/>.</param>
|
||||
public SplitterDestination(int id) : this()
|
||||
if (Unsafe.IsNullRef(ref _v2))
|
||||
{
|
||||
Id = id;
|
||||
DestinationId = Constants.UnusedMixId;
|
||||
|
||||
ClearVolumes();
|
||||
if (Unsafe.IsNullRef(ref _v1))
|
||||
{
|
||||
return new SplitterDestination();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SplitterDestination(ref _v1.Next);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SplitterDestination(ref _v2.Next);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the <see cref="SplitterDestination"/> from user parameter.
|
||||
/// Creates a new splitter destination wrapper for the version 1 splitter destination data.
|
||||
/// </summary>
|
||||
/// <param name="v1">Version 1 splitter destination data</param>
|
||||
public SplitterDestination(ref SplitterDestinationVersion1 v1)
|
||||
{
|
||||
_v1 = ref v1;
|
||||
_v2 = ref Unsafe.NullRef<SplitterDestinationVersion2>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new splitter destination wrapper for the version 2 splitter destination data.
|
||||
/// </summary>
|
||||
/// <param name="v2">Version 2 splitter destination data</param>
|
||||
public SplitterDestination(ref SplitterDestinationVersion2 v2)
|
||||
{
|
||||
|
||||
_v1 = ref Unsafe.NullRef<SplitterDestinationVersion1>();
|
||||
_v2 = ref v2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new splitter destination wrapper for the splitter destination data.
|
||||
/// </summary>
|
||||
/// <param name="v1">Version 1 splitter destination data</param>
|
||||
/// <param name="v2">Version 2 splitter destination data</param>
|
||||
public unsafe SplitterDestination(SplitterDestinationVersion1* v1, SplitterDestinationVersion2* v2)
|
||||
{
|
||||
_v1 = ref Unsafe.AsRef<SplitterDestinationVersion1>(v1);
|
||||
_v2 = ref Unsafe.AsRef<SplitterDestinationVersion2>(v2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the splitter destination data from user parameter.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
public void Update(SplitterDestinationInParameter parameter)
|
||||
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter
|
||||
{
|
||||
Debug.Assert(Id == parameter.Id);
|
||||
|
||||
if (parameter.IsMagicValid() && Id == parameter.Id)
|
||||
if (Unsafe.IsNullRef(ref _v2))
|
||||
{
|
||||
DestinationId = parameter.DestinationId;
|
||||
|
||||
parameter.MixBufferVolume.CopyTo(MixBufferVolume);
|
||||
|
||||
if (!IsUsed && parameter.IsUsed)
|
||||
{
|
||||
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
||||
|
||||
NeedToUpdateInternalState = false;
|
||||
_v1.Update(parameter);
|
||||
}
|
||||
|
||||
IsUsed = parameter.IsUsed;
|
||||
else
|
||||
{
|
||||
_v2.Update(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,12 +201,14 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
public void UpdateInternalState()
|
||||
{
|
||||
if (IsUsed && NeedToUpdateInternalState)
|
||||
if (Unsafe.IsNullRef(ref _v2))
|
||||
{
|
||||
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
||||
_v1.UpdateInternalState();
|
||||
}
|
||||
else
|
||||
{
|
||||
_v2.UpdateInternalState();
|
||||
}
|
||||
|
||||
NeedToUpdateInternalState = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -131,16 +216,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
public void MarkAsNeedToUpdateInternalState()
|
||||
{
|
||||
NeedToUpdateInternalState = true;
|
||||
if (Unsafe.IsNullRef(ref _v2))
|
||||
{
|
||||
_v1.MarkAsNeedToUpdateInternalState();
|
||||
}
|
||||
else
|
||||
{
|
||||
_v2.MarkAsNeedToUpdateInternalState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the <see cref="SplitterDestination"/> is used and has a destination.
|
||||
/// Return true if the splitter destination is used and has a destination.
|
||||
/// </summary>
|
||||
/// <returns>True if the <see cref="SplitterDestination"/> is used and has a destination.</returns>
|
||||
/// <returns>True if the splitter destination is used and has a destination.</returns>
|
||||
public readonly bool IsConfigured()
|
||||
{
|
||||
return IsUsed && DestinationId != Constants.UnusedMixId;
|
||||
return Unsafe.IsNullRef(ref _v2) ? _v1.IsConfigured() : _v2.IsConfigured();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -150,9 +242,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// <returns>The volume for the given destination.</returns>
|
||||
public float GetMixVolume(int destinationIndex)
|
||||
{
|
||||
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
|
||||
return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolume(destinationIndex) : _v2.GetMixVolume(destinationIndex);
|
||||
}
|
||||
|
||||
return MixBufferVolume[destinationIndex];
|
||||
/// <summary>
|
||||
/// Get the previous volume for a given destination.
|
||||
/// </summary>
|
||||
/// <param name="destinationIndex">The destination index to use.</param>
|
||||
/// <returns>The volume for the given destination.</returns>
|
||||
public float GetMixVolumePrev(int destinationIndex)
|
||||
{
|
||||
return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolumePrev(destinationIndex) : _v2.GetMixVolumePrev(destinationIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -160,22 +260,33 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
public void ClearVolumes()
|
||||
{
|
||||
MixBufferVolume.Clear();
|
||||
PreviousMixBufferVolume.Clear();
|
||||
if (Unsafe.IsNullRef(ref _v2))
|
||||
{
|
||||
_v1.ClearVolumes();
|
||||
}
|
||||
else
|
||||
{
|
||||
_v2.ClearVolumes();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Link the next element to the given <see cref="SplitterDestination"/>.
|
||||
/// Link the next element to the given splitter destination.
|
||||
/// </summary>
|
||||
/// <param name="next">The given <see cref="SplitterDestination"/> to link.</param>
|
||||
public void Link(ref SplitterDestination next)
|
||||
/// <param name="next">The given splitter destination to link.</param>
|
||||
public void Link(SplitterDestination next)
|
||||
{
|
||||
unsafe
|
||||
if (Unsafe.IsNullRef(ref _v2))
|
||||
{
|
||||
fixed (SplitterDestination* nextPtr = &next)
|
||||
{
|
||||
_next = nextPtr;
|
||||
Debug.Assert(!Unsafe.IsNullRef(ref next._v1));
|
||||
|
||||
_v1.Link(ref next._v1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(!Unsafe.IsNullRef(ref next._v2));
|
||||
|
||||
_v2.Link(ref next._v2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,10 +295,74 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
public void Unlink()
|
||||
{
|
||||
unsafe
|
||||
if (Unsafe.IsNullRef(ref _v2))
|
||||
{
|
||||
_next = null;
|
||||
_v1.Unlink();
|
||||
}
|
||||
else
|
||||
{
|
||||
_v2.Unlink();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any biquad filter is enabled.
|
||||
/// </summary>
|
||||
/// <returns>True if any biquad filter is enabled.</returns>
|
||||
public bool IsBiquadFilterEnabled()
|
||||
{
|
||||
return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabled();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any biquad filter was previously enabled.
|
||||
/// </summary>
|
||||
/// <returns>True if any biquad filter was previously enabled.</returns>
|
||||
public bool IsBiquadFilterEnabledPrev()
|
||||
{
|
||||
return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabledPrev();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the biquad filter parameters.
|
||||
/// </summary>
|
||||
/// <param name="index">Biquad filter index (0 or 1).</param>
|
||||
/// <returns>Biquad filter parameters.</returns>
|
||||
public ref BiquadFilterParameter GetBiquadFilterParameter(int index)
|
||||
{
|
||||
Debug.Assert(!Unsafe.IsNullRef(ref _v2));
|
||||
|
||||
return ref _v2.GetBiquadFilterParameter(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any biquad filter was previously enabled.
|
||||
/// </summary>
|
||||
/// <param name="index">Biquad filter index (0 or 1).</param>
|
||||
public void UpdateBiquadFilterEnabledPrev(int index)
|
||||
{
|
||||
if (!Unsafe.IsNullRef(ref _v2))
|
||||
{
|
||||
_v2.UpdateBiquadFilterEnabledPrev(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the reference for the version 1 splitter destination data, or null if version 2 is being used or the destination is null.
|
||||
/// </summary>
|
||||
/// <returns>Reference for the version 1 splitter destination data.</returns>
|
||||
public ref SplitterDestinationVersion1 GetV1RefOrNull()
|
||||
{
|
||||
return ref _v1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the reference for the version 2 splitter destination data, or null if version 1 is being used or the destination is null.
|
||||
/// </summary>
|
||||
/// <returns>Reference for the version 2 splitter destination data.</returns>
|
||||
public ref SplitterDestinationVersion2 GetV2RefOrNull()
|
||||
{
|
||||
return ref _v2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a splitter destination (version 1).
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)]
|
||||
public struct SplitterDestinationVersion1
|
||||
{
|
||||
public const int Alignment = 0x10;
|
||||
|
||||
/// <summary>
|
||||
/// The unique id of this <see cref="SplitterDestinationVersion1"/>.
|
||||
/// </summary>
|
||||
public int Id;
|
||||
|
||||
/// <summary>
|
||||
/// The mix to output the result of the splitter.
|
||||
/// </summary>
|
||||
public int DestinationId;
|
||||
|
||||
/// <summary>
|
||||
/// Mix buffer volumes storage.
|
||||
/// </summary>
|
||||
private MixArray _mix;
|
||||
private MixArray _previousMix;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the next linked element.
|
||||
/// </summary>
|
||||
private unsafe SplitterDestinationVersion1* _next;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if in use.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsUsed;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the internal state need to be updated.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool NeedToUpdateInternalState;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.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 _mix);
|
||||
|
||||
/// <summary>
|
||||
/// Previous mix buffer volumes.
|
||||
/// </summary>
|
||||
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||
public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
|
||||
|
||||
/// <summary>
|
||||
/// Get the reference of the next element or null if not present.
|
||||
/// </summary>
|
||||
public readonly ref SplitterDestinationVersion1 Next
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<SplitterDestinationVersion1>(_next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="SplitterDestinationVersion1"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique id of this <see cref="SplitterDestinationVersion1"/>.</param>
|
||||
public SplitterDestinationVersion1(int id) : this()
|
||||
{
|
||||
Id = id;
|
||||
DestinationId = Constants.UnusedMixId;
|
||||
|
||||
ClearVolumes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the <see cref="SplitterDestinationVersion1"/> from user parameter.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter
|
||||
{
|
||||
Debug.Assert(Id == parameter.Id);
|
||||
|
||||
if (parameter.IsMagicValid() && Id == parameter.Id)
|
||||
{
|
||||
DestinationId = parameter.DestinationId;
|
||||
|
||||
parameter.MixBufferVolume.CopyTo(MixBufferVolume);
|
||||
|
||||
if (!IsUsed && parameter.IsUsed)
|
||||
{
|
||||
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
||||
|
||||
NeedToUpdateInternalState = false;
|
||||
}
|
||||
|
||||
IsUsed = parameter.IsUsed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state of the instance.
|
||||
/// </summary>
|
||||
public void UpdateInternalState()
|
||||
{
|
||||
if (IsUsed && NeedToUpdateInternalState)
|
||||
{
|
||||
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
||||
}
|
||||
|
||||
NeedToUpdateInternalState = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the update internal state marker.
|
||||
/// </summary>
|
||||
public void MarkAsNeedToUpdateInternalState()
|
||||
{
|
||||
NeedToUpdateInternalState = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the <see cref="SplitterDestinationVersion1"/> is used and has a destination.
|
||||
/// </summary>
|
||||
/// <returns>True if the <see cref="SplitterDestinationVersion1"/> is used and has a destination.</returns>
|
||||
public readonly bool IsConfigured()
|
||||
{
|
||||
return IsUsed && DestinationId != Constants.UnusedMixId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the volume for a given destination.
|
||||
/// </summary>
|
||||
/// <param name="destinationIndex">The destination index to use.</param>
|
||||
/// <returns>The volume for the given destination.</returns>
|
||||
public float GetMixVolume(int destinationIndex)
|
||||
{
|
||||
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
|
||||
|
||||
return MixBufferVolume[destinationIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the previous volume for a given destination.
|
||||
/// </summary>
|
||||
/// <param name="destinationIndex">The destination index to use.</param>
|
||||
/// <returns>The volume for the given destination.</returns>
|
||||
public float GetMixVolumePrev(int destinationIndex)
|
||||
{
|
||||
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
|
||||
|
||||
return PreviousMixBufferVolume[destinationIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the volumes.
|
||||
/// </summary>
|
||||
public void ClearVolumes()
|
||||
{
|
||||
MixBufferVolume.Clear();
|
||||
PreviousMixBufferVolume.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Link the next element to the given <see cref="SplitterDestinationVersion1"/>.
|
||||
/// </summary>
|
||||
/// <param name="next">The given <see cref="SplitterDestinationVersion1"/> to link.</param>
|
||||
public void Link(ref SplitterDestinationVersion1 next)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (SplitterDestinationVersion1* nextPtr = &next)
|
||||
{
|
||||
_next = nextPtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the link to the next element.
|
||||
/// </summary>
|
||||
public void Unlink()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
_next = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a splitter destination (version 2).
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x110, Pack = Alignment)]
|
||||
public struct SplitterDestinationVersion2
|
||||
{
|
||||
public const int Alignment = 0x10;
|
||||
|
||||
/// <summary>
|
||||
/// The unique id of this <see cref="SplitterDestinationVersion2"/>.
|
||||
/// </summary>
|
||||
public int Id;
|
||||
|
||||
/// <summary>
|
||||
/// The mix to output the result of the splitter.
|
||||
/// </summary>
|
||||
public int DestinationId;
|
||||
|
||||
/// <summary>
|
||||
/// Mix buffer volumes storage.
|
||||
/// </summary>
|
||||
private MixArray _mix;
|
||||
private MixArray _previousMix;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the next linked element.
|
||||
/// </summary>
|
||||
private unsafe SplitterDestinationVersion2* _next;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if in use.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsUsed;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the internal state need to be updated.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool NeedToUpdateInternalState;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.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 _mix);
|
||||
|
||||
/// <summary>
|
||||
/// Previous mix buffer volumes.
|
||||
/// </summary>
|
||||
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||
public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
|
||||
|
||||
/// <summary>
|
||||
/// Get the reference of the next element or null if not present.
|
||||
/// </summary>
|
||||
public readonly ref SplitterDestinationVersion2 Next
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<SplitterDestinationVersion2>(_next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Array2<BiquadFilterParameter> _biquadFilters;
|
||||
|
||||
private Array2<bool> _isPreviousBiquadFilterEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="SplitterDestinationVersion2"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique id of this <see cref="SplitterDestinationVersion2"/>.</param>
|
||||
public SplitterDestinationVersion2(int id) : this()
|
||||
{
|
||||
Id = id;
|
||||
DestinationId = Constants.UnusedMixId;
|
||||
|
||||
ClearVolumes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the <see cref="SplitterDestinationVersion2"/> from user parameter.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter
|
||||
{
|
||||
Debug.Assert(Id == parameter.Id);
|
||||
|
||||
if (parameter.IsMagicValid() && Id == parameter.Id)
|
||||
{
|
||||
DestinationId = parameter.DestinationId;
|
||||
|
||||
parameter.MixBufferVolume.CopyTo(MixBufferVolume);
|
||||
|
||||
_biquadFilters = parameter.BiquadFilters;
|
||||
|
||||
if (!IsUsed && parameter.IsUsed)
|
||||
{
|
||||
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
||||
|
||||
NeedToUpdateInternalState = false;
|
||||
}
|
||||
|
||||
IsUsed = parameter.IsUsed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state of the instance.
|
||||
/// </summary>
|
||||
public void UpdateInternalState()
|
||||
{
|
||||
if (IsUsed && NeedToUpdateInternalState)
|
||||
{
|
||||
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
||||
}
|
||||
|
||||
NeedToUpdateInternalState = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the update internal state marker.
|
||||
/// </summary>
|
||||
public void MarkAsNeedToUpdateInternalState()
|
||||
{
|
||||
NeedToUpdateInternalState = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the <see cref="SplitterDestinationVersion2"/> is used and has a destination.
|
||||
/// </summary>
|
||||
/// <returns>True if the <see cref="SplitterDestinationVersion2"/> is used and has a destination.</returns>
|
||||
public readonly bool IsConfigured()
|
||||
{
|
||||
return IsUsed && DestinationId != Constants.UnusedMixId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the volume for a given destination.
|
||||
/// </summary>
|
||||
/// <param name="destinationIndex">The destination index to use.</param>
|
||||
/// <returns>The volume for the given destination.</returns>
|
||||
public float GetMixVolume(int destinationIndex)
|
||||
{
|
||||
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
|
||||
|
||||
return MixBufferVolume[destinationIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the previous volume for a given destination.
|
||||
/// </summary>
|
||||
/// <param name="destinationIndex">The destination index to use.</param>
|
||||
/// <returns>The volume for the given destination.</returns>
|
||||
public float GetMixVolumePrev(int destinationIndex)
|
||||
{
|
||||
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
|
||||
|
||||
return PreviousMixBufferVolume[destinationIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the volumes.
|
||||
/// </summary>
|
||||
public void ClearVolumes()
|
||||
{
|
||||
MixBufferVolume.Clear();
|
||||
PreviousMixBufferVolume.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Link the next element to the given <see cref="SplitterDestinationVersion2"/>.
|
||||
/// </summary>
|
||||
/// <param name="next">The given <see cref="SplitterDestinationVersion2"/> to link.</param>
|
||||
public void Link(ref SplitterDestinationVersion2 next)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (SplitterDestinationVersion2* nextPtr = &next)
|
||||
{
|
||||
_next = nextPtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the link to the next element.
|
||||
/// </summary>
|
||||
public void Unlink()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
_next = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any biquad filter is enabled.
|
||||
/// </summary>
|
||||
/// <returns>True if any biquad filter is enabled.</returns>
|
||||
public bool IsBiquadFilterEnabled()
|
||||
{
|
||||
return _biquadFilters[0].Enable || _biquadFilters[1].Enable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any biquad filter was previously enabled.
|
||||
/// </summary>
|
||||
/// <returns>True if any biquad filter was previously enabled.</returns>
|
||||
public bool IsBiquadFilterEnabledPrev()
|
||||
{
|
||||
return _isPreviousBiquadFilterEnabled[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the biquad filter parameters.
|
||||
/// </summary>
|
||||
/// <param name="index">Biquad filter index (0 or 1).</param>
|
||||
/// <returns>Biquad filter parameters.</returns>
|
||||
public ref BiquadFilterParameter GetBiquadFilterParameter(int index)
|
||||
{
|
||||
return ref _biquadFilters[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any biquad filter was previously enabled.
|
||||
/// </summary>
|
||||
/// <param name="index">Biquad filter index (0 or 1).</param>
|
||||
public void UpdateBiquadFilterEnabledPrev(int index)
|
||||
{
|
||||
_isPreviousBiquadFilterEnabled[index] = _biquadFilters[index].Enable;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
{
|
||||
public const int Alignment = 0x10;
|
||||
|
||||
private delegate void SplitterDestinationAction(SplitterDestination destination, int index);
|
||||
|
||||
/// <summary>
|
||||
/// The unique id of this <see cref="SplitterState"/>.
|
||||
/// </summary>
|
||||
|
@ -26,7 +28,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
public uint SampleRate;
|
||||
|
||||
/// <summary>
|
||||
/// Count of splitter destinations (<see cref="SplitterDestination"/>).
|
||||
/// Count of splitter destinations.
|
||||
/// </summary>
|
||||
public int DestinationCount;
|
||||
|
||||
|
@ -37,20 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
public bool HasNewConnection;
|
||||
|
||||
/// <summary>
|
||||
/// Linked list of <see cref="SplitterDestination"/>.
|
||||
/// Linked list of <see cref="SplitterDestinationVersion1"/>.
|
||||
/// </summary>
|
||||
private unsafe SplitterDestination* _destinationsData;
|
||||
private unsafe SplitterDestinationVersion1* _destinationDataV1;
|
||||
|
||||
/// <summary>
|
||||
/// Span to the first element of the linked list of <see cref="SplitterDestination"/>.
|
||||
/// Linked list of <see cref="SplitterDestinationVersion2"/>.
|
||||
/// </summary>
|
||||
public readonly Span<SplitterDestination> Destinations
|
||||
private unsafe SplitterDestinationVersion2* _destinationDataV2;
|
||||
|
||||
/// <summary>
|
||||
/// First element of the linked list of splitter destinations data.
|
||||
/// </summary>
|
||||
public readonly SplitterDestination Destination
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return (IntPtr)_destinationsData != IntPtr.Zero ? new Span<SplitterDestination>(_destinationsData, 1) : Span<SplitterDestination>.Empty;
|
||||
return new SplitterDestination(_destinationDataV1, _destinationDataV2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,20 +71,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
Id = id;
|
||||
}
|
||||
|
||||
public readonly Span<SplitterDestination> GetData(int index)
|
||||
public readonly SplitterDestination GetData(int index)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
Span<SplitterDestination> result = Destinations;
|
||||
SplitterDestination result = Destination;
|
||||
|
||||
while (i < index)
|
||||
{
|
||||
if (result.IsEmpty)
|
||||
if (result.IsNull)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
result = result[0].Next;
|
||||
result = result.Next;
|
||||
i++;
|
||||
}
|
||||
|
||||
|
@ -93,25 +100,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility function to apply a given <see cref="SpanAction{T, TArg}"/> to all <see cref="Destinations"/>.
|
||||
/// Utility function to apply an action to all <see cref="Destination"/>.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to execute on each elements.</param>
|
||||
private readonly void ForEachDestination(SpanAction<SplitterDestination, int> action)
|
||||
private readonly void ForEachDestination(SplitterDestinationAction action)
|
||||
{
|
||||
Span<SplitterDestination> temp = Destinations;
|
||||
SplitterDestination temp = Destination;
|
||||
|
||||
int i = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (temp.IsEmpty)
|
||||
if (temp.IsNull)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Span<SplitterDestination> next = temp[0].Next;
|
||||
SplitterDestination next = temp.Next;
|
||||
|
||||
action.Invoke(temp, i++);
|
||||
action(temp, i++);
|
||||
|
||||
temp = next;
|
||||
}
|
||||
|
@ -142,9 +149,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
{
|
||||
input.ReadLittleEndian(out int destinationId);
|
||||
|
||||
Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationId);
|
||||
SplitterDestination destination = context.GetDestination(destinationId);
|
||||
|
||||
SetDestination(ref destination.Span[0]);
|
||||
SetDestination(destination);
|
||||
|
||||
DestinationCount = destinationCount;
|
||||
|
||||
|
@ -152,9 +159,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
{
|
||||
input.ReadLittleEndian(out destinationId);
|
||||
|
||||
Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationId);
|
||||
SplitterDestination nextDestination = context.GetDestination(destinationId);
|
||||
|
||||
destination.Span[0].Link(ref nextDestination.Span[0]);
|
||||
destination.Link(nextDestination);
|
||||
destination = nextDestination;
|
||||
}
|
||||
}
|
||||
|
@ -174,16 +181,21 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the head of the linked list of <see cref="Destinations"/>.
|
||||
/// Set the head of the linked list of <see cref="Destination"/>.
|
||||
/// </summary>
|
||||
/// <param name="newValue">A reference to a <see cref="SplitterDestination"/>.</param>
|
||||
public void SetDestination(ref SplitterDestination newValue)
|
||||
/// <param name="newValue">New destination value.</param>
|
||||
public void SetDestination(SplitterDestination newValue)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (SplitterDestination* newValuePtr = &newValue)
|
||||
fixed (SplitterDestinationVersion1* newValuePtr = &newValue.GetV1RefOrNull())
|
||||
{
|
||||
_destinationsData = newValuePtr;
|
||||
_destinationDataV1 = newValuePtr;
|
||||
}
|
||||
|
||||
fixed (SplitterDestinationVersion2* newValuePtr = &newValue.GetV2RefOrNull())
|
||||
{
|
||||
_destinationDataV2 = newValuePtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -193,19 +205,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
/// </summary>
|
||||
public readonly void UpdateInternalState()
|
||||
{
|
||||
ForEachDestination((destination, _) => destination[0].UpdateInternalState());
|
||||
ForEachDestination((destination, _) => destination.UpdateInternalState());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all links from the <see cref="Destinations"/>.
|
||||
/// Clear all links from the <see cref="Destination"/>.
|
||||
/// </summary>
|
||||
public void ClearLinks()
|
||||
{
|
||||
ForEachDestination((destination, _) => destination[0].Unlink());
|
||||
ForEachDestination((destination, _) => destination.Unlink());
|
||||
|
||||
unsafe
|
||||
{
|
||||
_destinationsData = (SplitterDestination*)IntPtr.Zero;
|
||||
_destinationDataV1 = null;
|
||||
_destinationDataV2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -219,7 +232,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
|||
{
|
||||
unsafe
|
||||
{
|
||||
splitter._destinationsData = (SplitterDestination*)IntPtr.Zero;
|
||||
splitter._destinationDataV1 = null;
|
||||
splitter._destinationDataV2 = null;
|
||||
}
|
||||
|
||||
splitter.DestinationCount = 0;
|
||||
|
|
|
@ -52,7 +52,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
|||
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
|
||||
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
|
||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||
|
||||
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||
|
@ -78,7 +80,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
|||
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
|
||||
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
|
||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||
|
||||
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||
|
@ -104,7 +108,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
|||
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
|
||||
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
|
||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||
|
||||
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||
|
@ -130,7 +136,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
|||
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
|
||||
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
|
||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||
|
||||
Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||
|
@ -156,7 +164,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
|||
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
|
||||
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
|
||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||
|
||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||
|
@ -182,7 +192,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
|||
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
|
||||
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
|
||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||
|
||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||
|
@ -208,7 +220,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
|||
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
|
||||
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
|
||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||
|
||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||
|
@ -234,7 +248,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
|||
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
|
||||
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
|
||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||
|
||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||
|
@ -260,7 +276,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
|||
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
|
||||
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
|
||||
Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
|
||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||
|
||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||
|
@ -286,11 +304,69 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
|||
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
|
||||
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
|
||||
Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
|
||||
Assert.IsTrue(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
|
||||
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||
|
||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||
Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||
Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRevision11()
|
||||
{
|
||||
BehaviourContext behaviourContext = new();
|
||||
|
||||
behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision11);
|
||||
|
||||
Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed());
|
||||
Assert.IsTrue(behaviourContext.IsSplitterSupported());
|
||||
Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported());
|
||||
Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported());
|
||||
Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported());
|
||||
Assert.IsTrue(behaviourContext.IsSplitterBugFixed());
|
||||
Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported());
|
||||
Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported());
|
||||
Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
|
||||
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
|
||||
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
|
||||
Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
|
||||
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||
|
||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||
Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRevision12()
|
||||
{
|
||||
BehaviourContext behaviourContext = new();
|
||||
|
||||
behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision12);
|
||||
|
||||
Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed());
|
||||
Assert.IsTrue(behaviourContext.IsSplitterSupported());
|
||||
Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported());
|
||||
Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported());
|
||||
Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported());
|
||||
Assert.IsTrue(behaviourContext.IsSplitterBugFixed());
|
||||
Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported());
|
||||
Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported());
|
||||
Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
|
||||
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
|
||||
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
|
||||
Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
|
||||
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||
Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||
|
||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||
Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
|||
[Test]
|
||||
public void EnsureTypeSize()
|
||||
{
|
||||
Assert.AreEqual(0xE0, Unsafe.SizeOf<SplitterDestination>());
|
||||
Assert.AreEqual(0xE0, Unsafe.SizeOf<SplitterDestinationVersion1>());
|
||||
Assert.AreEqual(0x110, Unsafe.SizeOf<SplitterDestinationVersion2>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue