amadeus: Update to REV9 (#2309)

* amadeus: Update to REV9

This implements all the changes made with REV9 on 12.0.0.

* Address Ac_k's comments
This commit is contained in:
Mary 2021-05-25 19:01:09 +02:00 committed by GitHub
parent 54ea2285f0
commit f3b0b4831c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1591 additions and 68 deletions

View File

@ -55,6 +55,11 @@ namespace Ryujinx.Audio.Renderer.Common
/// <summary>
/// Effect applying a biquad filter.
/// </summary>
BiquadFilter
BiquadFilter,
/// <summary>
/// Effect applying a limiter (DRC).
/// </summary>
Limiter,
}
}

View File

@ -29,6 +29,7 @@ namespace Ryujinx.Audio.Renderer.Common
Aux,
Reverb,
Reverb3d,
PcmFloat
PcmFloat,
Limiter
}
}

View File

@ -44,6 +44,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
Reverb3d,
Performance,
ClearMixBuffer,
CopyMixBuffer
CopyMixBuffer,
LimiterVersion1,
LimiterVersion2
}
}

View File

@ -0,0 +1,160 @@
//
// Copyright (c) 2019-2021 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System;
using System.Diagnostics;
namespace Ryujinx.Audio.Renderer.Dsp.Command
{
public class LimiterCommandVersion1 : ICommand
{
public bool Enabled { get; set; }
public int NodeId { get; }
public CommandType CommandType => CommandType.LimiterVersion1;
public ulong EstimatedProcessingTime { get; set; }
public LimiterParameter Parameter => _parameter;
public Memory<LimiterState> State { get; }
public ulong WorkBuffer { get; }
public ushort[] OutputBufferIndices { get; }
public ushort[] InputBufferIndices { get; }
public bool IsEffectEnabled { get; }
private LimiterParameter _parameter;
public LimiterCommandVersion1(uint bufferOffset, LimiterParameter parameter, Memory<LimiterState> state, bool isEnabled, ulong workBuffer, int nodeId)
{
Enabled = true;
NodeId = nodeId;
_parameter = parameter;
State = state;
WorkBuffer = workBuffer;
IsEffectEnabled = isEnabled;
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
for (int i = 0; i < Parameter.ChannelCount; i++)
{
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
}
}
public void Process(CommandList context)
{
ref LimiterState state = ref State.Span[0];
if (IsEffectEnabled)
{
if (Parameter.Status == Server.Effect.UsageState.Invalid)
{
state = new LimiterState(ref _parameter, WorkBuffer);
}
else if (Parameter.Status == Server.Effect.UsageState.New)
{
state.UpdateParameter(ref _parameter);
}
}
ProcessLimiter(context);
}
private void ProcessLimiter(CommandList context)
{
Debug.Assert(Parameter.IsChannelCountValid());
if (IsEffectEnabled && Parameter.IsChannelCountValid())
{
ref LimiterState state = ref State.Span[0];
ReadOnlyMemory<float>[] inputBuffers = new ReadOnlyMemory<float>[Parameter.ChannelCount];
Memory<float>[] outputBuffers = new Memory<float>[Parameter.ChannelCount];
for (int i = 0; i < Parameter.ChannelCount; i++)
{
inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]);
outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]);
}
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
{
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{
float inputSample = inputBuffers[channelIndex].Span[sampleIndex];
float sampleInputMax = Math.Abs(inputSample * Parameter.InputGain);
float inputCoefficient = Parameter.ReleaseCoefficient;
if (sampleInputMax > state.DectectorAverage[channelIndex])
{
inputCoefficient = Parameter.AttackCoefficient;
}
state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
float attenuation = 1.0f;
if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
{
attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
}
float outputCoefficient = Parameter.ReleaseCoefficient;
if (state.CompressionGain[channelIndex] > attenuation)
{
outputCoefficient = Parameter.AttackCoefficient;
}
state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
outputBuffers[channelIndex].Span[sampleIndex] = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
delayedSample = inputSample;
state.DelayedSampleBufferPosition[channelIndex]++;
while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
{
state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
}
}
}
}
else
{
for (int i = 0; i < Parameter.ChannelCount; i++)
{
if (InputBufferIndices[i] != OutputBufferIndices[i])
{
context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
}
}
}
}
}
}

View File

@ -0,0 +1,179 @@
//
// Copyright (c) 2019-2021 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Dsp.Command
{
public class LimiterCommandVersion2 : ICommand
{
public bool Enabled { get; set; }
public int NodeId { get; }
public CommandType CommandType => CommandType.LimiterVersion2;
public ulong EstimatedProcessingTime { get; set; }
public LimiterParameter Parameter => _parameter;
public Memory<LimiterState> State { get; }
public Memory<EffectResultState> ResultState { get; }
public ulong WorkBuffer { get; }
public ushort[] OutputBufferIndices { get; }
public ushort[] InputBufferIndices { get; }
public bool IsEffectEnabled { get; }
private LimiterParameter _parameter;
public LimiterCommandVersion2(uint bufferOffset, LimiterParameter parameter, Memory<LimiterState> state, Memory<EffectResultState> resultState, bool isEnabled, ulong workBuffer, int nodeId)
{
Enabled = true;
NodeId = nodeId;
_parameter = parameter;
State = state;
ResultState = resultState;
WorkBuffer = workBuffer;
IsEffectEnabled = isEnabled;
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
for (int i = 0; i < Parameter.ChannelCount; i++)
{
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
}
}
public void Process(CommandList context)
{
ref LimiterState state = ref State.Span[0];
if (IsEffectEnabled)
{
if (Parameter.Status == Server.Effect.UsageState.Invalid)
{
state = new LimiterState(ref _parameter, WorkBuffer);
}
else if (Parameter.Status == Server.Effect.UsageState.New)
{
state.UpdateParameter(ref _parameter);
}
}
ProcessLimiter(context);
}
private void ProcessLimiter(CommandList context)
{
Debug.Assert(Parameter.IsChannelCountValid());
if (IsEffectEnabled && Parameter.IsChannelCountValid())
{
ref LimiterState state = ref State.Span[0];
if (!ResultState.IsEmpty && Parameter.StatisticsReset)
{
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
statistics.Reset();
}
ReadOnlyMemory<float>[] inputBuffers = new ReadOnlyMemory<float>[Parameter.ChannelCount];
Memory<float>[] outputBuffers = new Memory<float>[Parameter.ChannelCount];
for (int i = 0; i < Parameter.ChannelCount; i++)
{
inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]);
outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]);
}
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
{
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{
float inputSample = inputBuffers[channelIndex].Span[sampleIndex];
float sampleInputMax = Math.Abs(inputSample * Parameter.InputGain);
float inputCoefficient = Parameter.ReleaseCoefficient;
if (sampleInputMax > state.DectectorAverage[channelIndex])
{
inputCoefficient = Parameter.AttackCoefficient;
}
state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
float attenuation = 1.0f;
if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
{
attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
}
float outputCoefficient = Parameter.ReleaseCoefficient;
if (state.CompressionGain[channelIndex] > attenuation)
{
outputCoefficient = Parameter.AttackCoefficient;
}
state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
outputBuffers[channelIndex].Span[sampleIndex] = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
delayedSample = inputSample;
state.DelayedSampleBufferPosition[channelIndex]++;
while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
{
state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
}
if (!ResultState.IsEmpty)
{
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
statistics.InputMax[channelIndex] = Math.Max(statistics.InputMax[channelIndex], sampleInputMax);
statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], state.CompressionGain[channelIndex]);
}
}
}
}
else
{
for (int i = 0; i < Parameter.ChannelCount; i++)
{
if (InputBufferIndices[i] != OutputBufferIndices[i])
{
context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
}
}
}
}
}
}

View File

@ -0,0 +1,46 @@
//
// Copyright (c) 2019-2021 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class LimiterState
{
public float[] DectectorAverage;
public float[] CompressionGain;
public float[] DelayedSampleBuffer;
public int[] DelayedSampleBufferPosition;
public LimiterState(ref LimiterParameter parameter, ulong workBuffer)
{
DectectorAverage = new float[parameter.ChannelCount];
CompressionGain = new float[parameter.ChannelCount];
DelayedSampleBuffer = new float[parameter.ChannelCount * parameter.DelayBufferSampleCountMax];
DelayedSampleBufferPosition = new int[parameter.ChannelCount];
DectectorAverage.AsSpan().Fill(0.0f);
CompressionGain.AsSpan().Fill(1.0f);
DelayedSampleBufferPosition.AsSpan().Fill(0);
UpdateParameter(ref parameter);
}
public void UpdateParameter(ref LimiterParameter parameter) {}
}
}

View File

@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.AuxiliaryBuffer"/>.
/// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.AuxiliaryBuffer"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct AuxiliaryBufferParameter

View File

@ -22,7 +22,7 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.BiquadFilter"/>.
/// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.BiquadFilter"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BiquadFilterEffectParameter

View File

@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.BufferMix"/>.
/// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.BufferMix"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BufferMixParameter

View File

@ -22,7 +22,7 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Delay"/>.
/// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Delay"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DelayParameter
@ -103,7 +103,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
public bool IsChannelCountValid()
{
return EffectInParameter.IsChannelCountValid(ChannelCount);
return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
}
/// <summary>
@ -112,7 +112,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
public bool IsChannelCountMaxValid()
{
return EffectInParameter.IsChannelCountValid(ChannelCountMax);
return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax);
}
}
}

View File

@ -0,0 +1,155 @@
//
// Copyright (c) 2019-2021 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
using Ryujinx.Audio.Renderer.Server.Effect;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Limiter"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct LimiterParameter
{
/// <summary>
/// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Input;
/// <summary>
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Output;
/// <summary>
/// The maximum number of channels supported.
/// </summary>
public ushort ChannelCountMax;
/// <summary>
/// The total channel count used.
/// </summary>
public ushort ChannelCount;
/// <summary>
/// The target sample rate.
/// </summary>
/// <remarks>This is in kHz.</remarks>
public int SampleRate;
/// <summary>
/// The look ahead max time.
/// <remarks>This is in microseconds.</remarks>
/// </summary>
public int LookAheadTimeMax;
/// <summary>
/// The attack time.
/// <remarks>This is in microseconds.</remarks>
/// </summary>
public int AttackTime;
/// <summary>
/// The release time.
/// <remarks>This is in microseconds.</remarks>
/// </summary>
public int ReleaseTime;
/// <summary>
/// The look ahead time.
/// <remarks>This is in microseconds.</remarks>
/// </summary>
public int LookAheadTime;
/// <summary>
/// The attack coefficient.
/// </summary>
public float AttackCoefficient;
/// <summary>
/// The release coefficient.
/// </summary>
public float ReleaseCoefficient;
/// <summary>
/// The threshold.
/// </summary>
public float Threshold;
/// <summary>
/// The input gain.
/// </summary>
public float InputGain;
/// <summary>
/// The output gain.
/// </summary>
public float OutputGain;
/// <summary>
/// The minimum samples stored in the delay buffer.
/// </summary>
public int DelayBufferSampleCountMin;
/// <summary>
/// The maximum samples stored in the delay buffer.
/// </summary>
public int DelayBufferSampleCountMax;
/// <summary>
/// The current usage status of the effect on the client side.
/// </summary>
public UsageState Status;
/// <summary>
/// Indicate if the limiter effect should output statistics.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool StatisticsEnabled;
/// <summary>
/// Indicate to the DSP that the user did a statistics reset.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool StatisticsReset;
/// <summary>
/// Reserved/padding.
/// </summary>
private byte _reserved;
/// <summary>
/// Check if the <see cref="ChannelCount"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
public bool IsChannelCountValid()
{
return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
}
/// <summary>
/// Check if the <see cref="ChannelCountMax"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
public bool IsChannelCountMaxValid()
{
return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax);
}
}
}

View File

@ -0,0 +1,48 @@
//
// Copyright (c) 2019-2021 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// Effect result state for <seealso cref="Common.EffectType.Limiter"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct LimiterStatistics
{
/// <summary>
/// The max input sample value recorded by the limiter.
/// </summary>
public Array6<float> InputMax;
/// <summary>
/// Compression gain min value.
/// </summary>
public Array6<float> CompressionGainMin;
/// <summary>
/// Reset the statistics.
/// </summary>
public void Reset()
{
InputMax.ToSpan().Fill(0.0f);
CompressionGainMin.ToSpan().Fill(1.0f);
}
}
}

View File

@ -22,7 +22,7 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Reverb3d"/>.
/// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Reverb3d"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Reverb3dParameter
@ -129,7 +129,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
public bool IsChannelCountValid()
{
return EffectInParameter.IsChannelCountValid(ChannelCount);
return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
}
/// <summary>
@ -138,7 +138,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
public bool IsChannelCountMaxValid()
{
return EffectInParameter.IsChannelCountValid(ChannelCountMax);
return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax);
}
}
}

View File

@ -23,7 +23,7 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="EffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Reverb"/>.
/// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Reverb"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ReverbParameter
@ -121,7 +121,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
public bool IsChannelCountValid()
{
return EffectInParameter.IsChannelCountValid(ChannelCount);
return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
}
/// <summary>
@ -130,7 +130,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
public bool IsChannelCountMaxValid()
{
return EffectInParameter.IsChannelCountValid(ChannelCountMax);
return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax);
}
}
}

View File

@ -23,10 +23,10 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Input information for an effect.
/// Input information for an effect version 1.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EffectInParameter
public struct EffectInParameterVersion1 : IEffectInParameter
{
/// <summary>
/// Type of the effect.
@ -85,11 +85,22 @@ namespace Ryujinx.Audio.Renderer.Parameter
[StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)]
private struct SpecificDataStruct { }
/// <summary>
/// Specific data changing depending of the <see cref="Type"/>. See also the <see cref="Effect"/> namespace.
/// </summary>
public Span<byte> SpecificData => SpanHelpers.AsSpan<SpecificDataStruct, byte>(ref _specificDataStart);
EffectType IEffectInParameter.Type => Type;
bool IEffectInParameter.IsNew => IsNew;
bool IEffectInParameter.IsEnabled => IsEnabled;
int IEffectInParameter.MixId => MixId;
ulong IEffectInParameter.BufferBase => BufferBase;
ulong IEffectInParameter.BufferSize => BufferSize;
uint IEffectInParameter.ProcessingOrder => ProcessingOrder;
/// <summary>
/// Check if the given channel count is valid.
/// </summary>

View File

@ -0,0 +1,114 @@
//
// Copyright (c) 2019-2021 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Common.Utilities;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Input information for an effect version 2. (added with REV9)
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EffectInParameterVersion2 : IEffectInParameter
{
/// <summary>
/// Type of the effect.
/// </summary>
public EffectType Type;
/// <summary>
/// Set to true if the effect is new.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsNew;
/// <summary>
/// Set to true if the effect must be active.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsEnabled;
/// <summary>
/// Reserved/padding.
/// </summary>
private byte _reserved1;
/// <summary>
/// The target mix id of the effect.
/// </summary>
public int MixId;
/// <summary>
/// Address of the processing workbuffer.
/// </summary>
/// <remarks>This is additional data that could be required by the effect processing.</remarks>
public ulong BufferBase;
/// <summary>
/// Size of the processing workbuffer.
/// </summary>
/// <remarks>This is additional data that could be required by the effect processing.</remarks>
public ulong BufferSize;
/// <summary>
/// Position of the effect while processing effects.
/// </summary>
public uint ProcessingOrder;
/// <summary>
/// Reserved/padding.
/// </summary>
private uint _reserved2;
/// <summary>
/// Specific data storage.
/// </summary>
private SpecificDataStruct _specificDataStart;
[StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)]
private struct SpecificDataStruct { }
public Span<byte> SpecificData => SpanHelpers.AsSpan<SpecificDataStruct, byte>(ref _specificDataStart);
EffectType IEffectInParameter.Type => Type;
bool IEffectInParameter.IsNew => IsNew;
bool IEffectInParameter.IsEnabled => IsEnabled;
int IEffectInParameter.MixId => MixId;
ulong IEffectInParameter.BufferBase => BufferBase;
ulong IEffectInParameter.BufferSize => BufferSize;
uint IEffectInParameter.ProcessingOrder => ProcessingOrder;
/// <summary>
/// Check if the given channel count is valid.
/// </summary>
/// <param name="channelCount">The channel count to check</param>
/// <returns>Returns true if the channel count is valid.</returns>
public static bool IsChannelCountValid(int channelCount)
{
return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6;
}
}
}

View File

@ -0,0 +1,40 @@
//
// Copyright (c) 2019-2021 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Output information for an effect version 1.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EffectOutStatusVersion1 : IEffectOutStatus
{
/// <summary>
/// Current effect state.
/// </summary>
public EffectState State;
/// <summary>
/// Unused/Reserved.
/// </summary>
private unsafe fixed byte _reserved[15];
EffectState IEffectOutStatus.State { get => State; set => State = value; }
}
}

View File

@ -1,4 +1,4 @@
//
//
// Copyright (c) 2019-2021 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
@ -20,27 +20,11 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Output information for an effect.
/// Output information for an effect version 2. (added with REV9)
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EffectOutStatus
public struct EffectOutStatusVersion2 : IEffectOutStatus
{
/// <summary>
/// The state of an effect.
/// </summary>
public enum EffectState : byte
{
/// <summary>
/// The effect is enabled.
/// </summary>
Enabled = 3,
/// <summary>
/// The effect is disabled.
/// </summary>
Disabled = 4
}
/// <summary>
/// Current effect state.
/// </summary>
@ -50,5 +34,12 @@ namespace Ryujinx.Audio.Renderer.Parameter
/// Unused/Reserved.
/// </summary>
private unsafe fixed byte _reserved[15];
/// <summary>
/// Current result state.
/// </summary>
public EffectResultState ResultState;
EffectState IEffectOutStatus.State { get => State; set => State = value; }
}
}

View File

@ -0,0 +1,43 @@
//
// Copyright (c) 2019-2021 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
using Ryujinx.Common.Utilities;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Effect result state (added in REV9).
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EffectResultState
{
/// <summary>
/// Specific data storage.
/// </summary>
private SpecificDataStruct _specificDataStart;
[StructLayout(LayoutKind.Sequential, Size = 0x80, Pack = 1)]
private struct SpecificDataStruct { }
/// <summary>
/// Specific data changing depending of the type of effect. See also the <see cref="Effect"/> namespace.
/// </summary>
public Span<byte> SpecificData => SpanHelpers.AsSpan<SpecificDataStruct, byte>(ref _specificDataStart);
}
}

View File

@ -0,0 +1,35 @@
//
// Copyright (c) 2019-2021 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// The state of an effect.
/// </summary>
public enum EffectState : byte
{
/// <summary>
/// The effect is enabled.
/// </summary>
Enabled = 3,
/// <summary>
/// The effect is disabled.
/// </summary>
Disabled = 4
}
}

View File

@ -0,0 +1,70 @@
//
// Copyright (c) 2019-2021 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
using Ryujinx.Audio.Renderer.Common;
using System;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Generic interface to represent input information for an effect.
/// </summary>
public interface IEffectInParameter
{
/// <summary>
/// Type of the effect.
/// </summary>
EffectType Type { get; }
/// <summary>
/// Set to true if the effect is new.
/// </summary>
bool IsNew { get; }
/// <summary>
/// Set to true if the effect must be active.
/// </summary>
bool IsEnabled { get; }
/// <summary>
/// The target mix id of the effect.
/// </summary>
int MixId { get; }
/// <summary>
/// Address of the processing workbuffer.
/// </summary>
/// <remarks>This is additional data that could be required by the effect processing.</remarks>
ulong BufferBase { get; }
/// <summary>
/// Size of the processing workbuffer.
/// </summary>
/// <remarks>This is additional data that could be required by the effect processing.</remarks>
ulong BufferSize { get; }
/// <summary>
/// Position of the effect while processing effects.
/// </summary>
uint ProcessingOrder { get; }
/// <summary>
/// Specific data changing depending of the <see cref="Type"/>. See also the <see cref="Effect"/> namespace.
/// </summary>
Span<byte> SpecificData { get; }
}
}

View File

@ -0,0 +1,30 @@
//
// Copyright (c) 2019-2021 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Generic interface to represent output information for an effect.
/// </summary>
public interface IEffectOutStatus
{
/// <summary>
/// Current effect state.
/// </summary>
EffectState State { get; set; }
}
}

View File

@ -307,7 +307,7 @@ namespace Ryujinx.Audio.Renderer.Server
_upsamplerManager = new UpsamplerManager(upSamplerWorkBuffer, _upsamplerCount);
_effectContext.Initialize(parameter.EffectCount);
_effectContext.Initialize(parameter.EffectCount, _behaviourContext.IsEffectInfoVersion2Supported() ? parameter.EffectCount : 0);
_sinkContext.Initialize(parameter.SinkCount);
Memory<VoiceUpdateState> voiceUpdateStatesDsp = workBufferAllocator.Allocate<VoiceUpdateState>(parameter.VoiceCount, VoiceUpdateState.Align);
@ -636,6 +636,11 @@ namespace Ryujinx.Audio.Renderer.Server
_voiceContext.UpdateForCommandGeneration();
if (_behaviourContext.IsEffectInfoVersion2Supported())
{
_effectContext.UpdateResultStateForCommandGeneration();
}
ulong endTicks = GetSystemTicks();
_totalElapsedTicks = endTicks - startTicks;

View File

@ -89,10 +89,18 @@ namespace Ryujinx.Audio.Renderer.Server
/// <remarks>This was added in system update 9.0.0</remarks>
public const int Revision8 = 8 << 24;
/// <summary>
/// REV9:
/// EffectInfo parameters were revisited with a new revision (version 2) allowing more data control between the client and server.
/// A new effect was added: Limiter. This effect is effectively implemented with a DRC while providing statistics on the processing on <see cref="Parameter.EffectOutStatusVersion2"/>.
/// </summary>
/// <remarks>This was added in system update 12.0.0</remarks>
public const int Revision9 = 9 << 24;
/// <summary>
/// Last revision supported by the implementation.
/// </summary>
public const int LastRevision = Revision8;
public const int LastRevision = Revision9;
/// <summary>
/// Target revision magic supported by the implementation.
@ -330,6 +338,15 @@ namespace Ryujinx.Audio.Renderer.Server
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8);
}
/// <summary>
/// Check if the audio renderer should use the new effect info format.
/// </summary>
/// <returns>True if the audio renderer should use the new effect info format.</returns>
public bool IsEffectInfoVersion2Supported()
{
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision9);
}
/// <summary>
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
/// </summary>

View File

@ -371,6 +371,49 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
/// <summary>
/// Generate a new <see cref="LimiterCommandVersion1"/>.
/// </summary>
/// <param name="bufferOffset">The target buffer offset.</param>
/// <param name="parameter">The limiter parameter.</param>
/// <param name="state">The limiter state.</param>
/// <param name="isEnabled">Set to true if the effect should be active.</param>
/// <param name="workBuffer">The work buffer to use for processing.</param>
/// <param name="nodeId">The node id associated to this command.</param>
public void GenerateLimiterEffectVersion1(uint bufferOffset, LimiterParameter parameter, Memory<LimiterState> state, bool isEnabled, ulong workBuffer, int nodeId)
{
if (parameter.IsChannelCountValid())
{
LimiterCommandVersion1 command = new LimiterCommandVersion1(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
AddCommand(command);
}
}
/// <summary>
/// Generate a new <see cref="LimiterCommandVersion2"/>.
/// </summary>
/// <param name="bufferOffset">The target buffer offset.</param>
/// <param name="parameter">The limiter parameter.</param>
/// <param name="state">The limiter state.</param>
/// <param name="effectResultState">The DSP effect result state.</param>
/// <param name="isEnabled">Set to true if the effect should be active.</param>
/// <param name="workBuffer">The work buffer to use for processing.</param>
/// <param name="nodeId">The node id associated to this command.</param>
public void GenerateLimiterEffectVersion2(uint bufferOffset, LimiterParameter parameter, Memory<LimiterState> state, Memory<EffectResultState> effectResultState, bool isEnabled, ulong workBuffer, int nodeId)
{
if (parameter.IsChannelCountValid())
{
LimiterCommandVersion2 command = new LimiterCommandVersion2(bufferOffset, parameter, state, effectResultState, isEnabled, workBuffer, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
AddCommand(command);
}
}
/// <summary>
/// Generate a new <see cref="AuxiliaryBufferCommand"/>.
/// </summary>

View File

@ -538,7 +538,25 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
private void GenerateEffect(ref MixState mix, BaseEffect effect)
private void GenerateLimiterEffect(uint bufferOffset, LimiterEffect effect, int nodeId, int effectId)
{
Debug.Assert(effect.Type == EffectType.Limiter);
ulong workBuffer = effect.GetWorkBuffer(-1);
if (_rendererContext.BehaviourContext.IsEffectInfoVersion2Supported())
{
Memory<EffectResultState> dspResultState = _effectContext.GetDspStateMemory(effectId);
_commandBuffer.GenerateLimiterEffectVersion2(bufferOffset, effect.Parameter, effect.State, dspResultState, effect.IsEnabled, workBuffer, nodeId);
}
else
{
_commandBuffer.GenerateLimiterEffectVersion1(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId);
}
}
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
{
int nodeId = mix.NodeId;
@ -576,6 +594,9 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.BiquadFilter:
GenerateBiquadFilterEffect(mix.BufferOffset, (BiquadFilterEffect)effect, nodeId);
break;
case EffectType.Limiter:
GenerateLimiterEffect(mix.BufferOffset, (LimiterEffect)effect, nodeId, effectId);
break;
default:
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
}
@ -611,7 +632,7 @@ namespace Ryujinx.Audio.Renderer.Server
if (!effect.ShouldSkip())
{
GenerateEffect(ref mix, effect);
GenerateEffect(ref mix, effectOrder, effect);
}
}
}

View File

@ -176,5 +176,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
public uint Estimate(LimiterCommandVersion1 command)
{
return 0;
}
public uint Estimate(LimiterCommandVersion2 command)
{
return 0;
}
}
}

View File

@ -540,5 +540,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
public uint Estimate(LimiterCommandVersion1 command)
{
return 0;
}
public uint Estimate(LimiterCommandVersion2 command)
{
return 0;
}
}
}

View File

@ -18,6 +18,7 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.Command;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System;
using System.Diagnostics;
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
@ -632,5 +633,127 @@ namespace Ryujinx.Audio.Renderer.Server
throw new NotImplementedException($"{format}");
}
}
private uint EstimateLimiterCommandCommon(LimiterParameter parameter, bool enabled)
{
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
if (_sampleCount == 160)
{
if (enabled)
{
switch (parameter.ChannelCount)
{
case 1:
return (uint)21392.0f;
case 2:
return (uint)26829.0f;
case 4:
return (uint)32405.0f;
case 6:
return (uint)52219.0f;
default:
throw new NotImplementedException($"{parameter.ChannelCount}");
}
}
else
{
switch (parameter.ChannelCount)
{
case 1:
return (uint)897.0f;
case 2:
return (uint)931.55f;
case 4:
return (uint)975.39f;
case 6:
return (uint)1016.8f;
default:
throw new NotImplementedException($"{parameter.ChannelCount}");
}
}
}
if (enabled)
{
switch (parameter.ChannelCount)
{
case 1:
return (uint)30556.0f;
case 2:
return (uint)39011.0f;
case 4:
return (uint)48270.0f;
case 6:
return (uint)76712.0f;
default:
throw new NotImplementedException($"{parameter.ChannelCount}");
}
}
else
{
switch (parameter.ChannelCount)
{
case 1:
return (uint)874.43f;
case 2:
return (uint)921.55f;
case 4:
return (uint)945.26f;
case 6:
return (uint)992.26f;
default:
throw new NotImplementedException($"{parameter.ChannelCount}");
}
}
}
public uint Estimate(LimiterCommandVersion1 command)
{
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled);
}
public uint Estimate(LimiterCommandVersion2 command)
{
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
if (!command.Parameter.StatisticsEnabled || !command.IsEffectEnabled)
{
return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled);
}
if (_sampleCount == 160)
{
switch (command.Parameter.ChannelCount)
{
case 1:
return (uint)23309.0f;
case 2:
return (uint)29954.0f;
case 4:
return (uint)35807.0f;
case 6:
return (uint)58340.0f;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
switch (command.Parameter.ChannelCount)
{
case 1:
return (uint)33526.0f;
case 2:
return (uint)43549.0f;
case 4:
return (uint)52190.0f;
case 6:
return (uint)85527.0f;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
}
}

View File

@ -50,7 +50,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return WorkBuffers[index].GetReference(true);
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{
Debug.Assert(IsTypeValid(ref parameter));

View File

@ -98,7 +98,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
/// </summary>
/// <param name="parameter">The user parameter.</param>
/// <returns>Returns true if the <see cref="EffectType"/> sent by the user matches the internal <see cref="EffectType"/>.</returns>
public bool IsTypeValid(ref EffectInParameter parameter)
public bool IsTypeValid<T>(ref T parameter) where T: unmanaged, IEffectInParameter
{
return parameter.Type == TargetEffectType;
}
@ -115,7 +115,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
/// Update the internal common parameters from a user parameter.
/// </summary>
/// <param name="parameter">The user parameter.</param>
protected void UpdateParameterBase(ref EffectInParameter parameter)
protected void UpdateParameterBase<T>(ref T parameter) where T : unmanaged, IEffectInParameter
{
MixId = parameter.MixId;
ProcessingOrder = parameter.ProcessingOrder;
@ -154,12 +154,38 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
}
/// <summary>
/// Update the internal state from a user parameter.
/// Initialize the given <paramref name="state"/> result state.
/// </summary>
/// <param name="state">The state to initalize</param>
public virtual void InitializeResultState(ref EffectResultState state) {}
/// <summary>
/// Update the <paramref name="destState"/> result state with <paramref name="srcState"/>.
/// </summary>
/// <param name="destState">The destination result state</param>
/// <param name="srcState">The source result state</param>
public virtual void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState) {}
/// <summary>
/// Update the internal state from a user version 1 parameter.
/// </summary>
/// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
/// <param name="parameter">The user parameter.</param>
/// <param name="mapper">The mapper to use.</param>
public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
{
Debug.Assert(IsTypeValid(ref parameter));
updateErrorInfo = new ErrorInfo();
}
/// <summary>
/// Update the internal state from a user version 2 parameter.
/// </summary>
/// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
/// <param name="parameter">The user parameter.</param>
/// <param name="mapper">The mapper to use.</param>
public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
{
Debug.Assert(IsTypeValid(ref parameter));
@ -206,26 +232,26 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
/// </summary>
/// <param name="outStatus">The given user output.</param>
/// <param name="isAudioRendererActive">If set to true, the <see cref="AudioRenderSystem"/> is active.</param>
public void StoreStatus(ref EffectOutStatus outStatus, bool isAudioRendererActive)
public void StoreStatus<T>(ref T outStatus, bool isAudioRendererActive) where T: unmanaged, IEffectOutStatus
{
if (isAudioRendererActive)
{
if (UsageState == UsageState.Disabled)
{
outStatus.State = EffectOutStatus.EffectState.Disabled;
outStatus.State = EffectState.Disabled;
}
else
{
outStatus.State = EffectOutStatus.EffectState.Enabled;
outStatus.State = EffectState.Enabled;
}
}
else if (UsageState == UsageState.New)
{
outStatus.State = EffectOutStatus.EffectState.Enabled;
outStatus.State = EffectState.Enabled;
}
else
{
outStatus.State = EffectOutStatus.EffectState.Disabled;
outStatus.State = EffectState.Disabled;
}
}
@ -249,6 +275,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return PerformanceDetailType.Reverb3d;
case EffectType.BufferMix:
return PerformanceDetailType.Mix;
case EffectType.Limiter:
return PerformanceDetailType.Limiter;
default:
throw new NotImplementedException($"{Type}");
}

View File

@ -52,7 +52,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
public override EffectType TargetEffectType => EffectType.BiquadFilter;
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{
Debug.Assert(IsTypeValid(ref parameter));

View File

@ -36,7 +36,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
public override EffectType TargetEffectType => EffectType.BufferMix;
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{
Debug.Assert(IsTypeValid(ref parameter));

View File

@ -54,7 +54,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return GetSingleBuffer();
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{
Debug.Assert(IsTypeValid(ref parameter));

View File

@ -15,6 +15,9 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Utils;
using System;
using System.Diagnostics;
namespace Ryujinx.Audio.Renderer.Server.Effect
@ -34,6 +37,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
/// </summary>
private uint _effectCount;
private EffectResultState[] _resultStatesCpu;
private EffectResultState[] _resultStatesDsp;
/// <summary>
/// Create a new <see cref="EffectContext"/>.
/// </summary>
@ -47,7 +53,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
/// Initialize the <see cref="EffectContext"/>.
/// </summary>
/// <param name="effectCount">The total effect count.</param>
public void Initialize(uint effectCount)
/// <param name="resultStateCount">The total result state count.</param>
public void Initialize(uint effectCount, uint resultStateCount)
{
_effectCount = effectCount;
_effects = new BaseEffect[effectCount];
@ -56,6 +63,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
{
_effects[i] = new BaseEffect();
}
_resultStatesCpu = new EffectResultState[resultStateCount];
_resultStatesDsp = new EffectResultState[resultStateCount];
}
/// <summary>
@ -78,5 +88,53 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return ref _effects[index];
}
/// <summary>
/// Get a reference to a <see cref="EffectResultState"/> at the given <paramref name="index"/>.
/// </summary>
/// <param name="index">The index to use.</param>
/// <returns>A reference to a <see cref="EffectResultState"/> at the given <paramref name="index"/>.</returns>
/// <remarks>The returned <see cref="EffectResultState"/> should only be used when updating the server state.</remarks>
public ref EffectResultState GetState(int index)
{
Debug.Assert(index >= 0 && index < _resultStatesCpu.Length);
return ref _resultStatesCpu[index];
}
/// <summary>
/// Get a reference to a <see cref="EffectResultState"/> at the given <paramref name="index"/>.
/// </summary>
/// <param name="index">The index to use.</param>
/// <returns>A reference to a <see cref="EffectResultState"/> at the given <paramref name="index"/>.</returns>
/// <remarks>The returned <see cref="EffectResultState"/> should only be used in the context of processing on the <see cref="Dsp.AudioProcessor"/>.</remarks>
public ref EffectResultState GetDspState(int index)
{
Debug.Assert(index >= 0 && index < _resultStatesDsp.Length);
return ref _resultStatesDsp[index];
}
/// <summary>
/// Get a memory instance to a <see cref="EffectResultState"/> at the given <paramref name="index"/>.
/// </summary>
/// <param name="index">The index to use.</param>
/// <returns>A memory instance to a <see cref="EffectResultState"/> at the given <paramref name="index"/>.</returns>
/// <remarks>The returned <see cref="Memory{EffectResultState}"/> should only be used in the context of processing on the <see cref="Dsp.AudioProcessor"/>.</remarks>
public Memory<EffectResultState> GetDspStateMemory(int index)
{
return SpanIOHelper.GetMemory(_resultStatesDsp.AsMemory(), index, (uint)_resultStatesDsp.Length);
}
/// <summary>
/// Update internal state during command generation.
/// </summary>
public void UpdateResultStateForCommandGeneration()
{
for (int index = 0; index < _resultStatesCpu.Length; index++)
{
_effects[index].UpdateResultState(ref _resultStatesCpu[index], ref _resultStatesDsp[index]);
}
}
}
}

View File

@ -0,0 +1,112 @@
//
// Copyright (c) 2019-2021 Ryujinx
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using Ryujinx.Audio.Renderer.Server.MemoryPool;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Server.Effect
{
/// <summary>
/// Server state for a limiter effect.
/// </summary>
public class LimiterEffect : BaseEffect
{
/// <summary>
/// The limiter parameter.
/// </summary>
public LimiterParameter Parameter;
/// <summary>
/// The limiter state.
/// </summary>
public Memory<LimiterState> State { get; }
/// <summary>
/// Create a new <see cref="LimiterEffect"/>.
/// </summary>
public LimiterEffect()
{
State = new LimiterState[1];
}
public override EffectType TargetEffectType => EffectType.Limiter;
public override ulong GetWorkBuffer(int index)
{
return GetSingleBuffer();
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{
Debug.Assert(IsTypeValid(ref parameter));
ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast<byte, LimiterParameter>(parameter.SpecificData)[0];
updateErrorInfo = new BehaviourParameter.ErrorInfo();
UpdateParameterBase(ref parameter);
Parameter = limiterParameter;
IsEnabled = parameter.IsEnabled;
if (BufferUnmapped || parameter.IsNew)
{
UsageState = UsageState.New;
Parameter.Status = UsageState.Invalid;
BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize);
}
}
public override void UpdateForCommandGeneration()
{
UpdateUsageStateForCommandGeneration();
Parameter.Status = UsageState.Enabled;
Parameter.StatisticsReset = false;
}
public override void InitializeResultState(ref EffectResultState state)
{
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(state.SpecificData)[0];
statistics.Reset();
}
public override void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState)
{
destState = srcState;
}
}
}

View File

@ -53,7 +53,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return GetSingleBuffer();
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T: unmanaged, IEffectInParameter
{
Debug.Assert(IsTypeValid(ref parameter));

View File

@ -56,7 +56,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return GetSingleBuffer();
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper)
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
{
Update(out updateErrorInfo, ref parameter, mapper);
}
public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{
Debug.Assert(IsTypeValid(ref parameter));

View File

@ -48,5 +48,7 @@ namespace Ryujinx.Audio.Renderer.Server
uint Estimate(DeviceSinkCommand command);
uint Estimate(DownMixSurroundToStereoCommand command);
uint Estimate(UpsampleCommand command);
uint Estimate(LimiterCommandVersion1 command);
uint Estimate(LimiterCommandVersion2 command);
}
}

View File

@ -18,7 +18,6 @@
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Parameter;
using System;
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Server.Performance
{

View File

@ -224,7 +224,7 @@ namespace Ryujinx.Audio.Renderer.Server
return ResultCode.Success;
}
private static void ResetEffect(ref BaseEffect effect, ref EffectInParameter parameter, PoolMapper mapper)
private static void ResetEffect<T>(ref BaseEffect effect, ref T parameter, PoolMapper mapper) where T: unmanaged, IEffectInParameter
{
effect.ForceUnmapBuffers(mapper);
@ -251,6 +251,9 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.BiquadFilter:
effect = new BiquadFilterEffect();
break;
case EffectType.Limiter:
effect = new LimiterEffect();
break;
default:
throw new NotImplementedException($"EffectType {parameter.Type} not implemented!");
}
@ -258,14 +261,26 @@ namespace Ryujinx.Audio.Renderer.Server
public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
{
if (context.GetCount() * Unsafe.SizeOf<EffectInParameter>() != _inputHeader.EffectsSize)
if (_behaviourContext.IsEffectInfoVersion2Supported())
{
return UpdateEffectsVersion2(context, isAudioRendererActive, memoryPools);
}
else
{
return UpdateEffectsVersion1(context, isAudioRendererActive, memoryPools);
}
}
public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
{
if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion2>() != _inputHeader.EffectsSize)
{
return ResultCode.InvalidUpdateInfo;
}
int initialOutputSize = _output.Length;
ReadOnlySpan<EffectInParameter> parameters = MemoryMarshal.Cast<byte, EffectInParameter>(_input.Slice(0, (int)_inputHeader.EffectsSize).Span);
ReadOnlySpan<EffectInParameterVersion2> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion2>(_input.Slice(0, (int)_inputHeader.EffectsSize).Span);
_input = _input.Slice((int)_inputHeader.EffectsSize);
@ -273,9 +288,65 @@ namespace Ryujinx.Audio.Renderer.Server
for (int i = 0; i < context.GetCount(); i++)
{
EffectInParameter parameter = parameters[i];
EffectInParameterVersion2 parameter = parameters[i];
ref EffectOutStatus outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatus>(ref _output)[0];
ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion2>(ref _output)[0];
ref BaseEffect effect = ref context.GetEffect(i);
if (!effect.IsTypeValid(ref parameter))
{
ResetEffect(ref effect, ref parameter, mapper);
}
effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper);
if (updateErrorInfo.ErrorCode != ResultCode.Success)
{
_behaviourContext.AppendError(ref updateErrorInfo);
}
effect.StoreStatus(ref outStatus, isAudioRendererActive);
if (parameter.IsNew)
{
effect.InitializeResultState(ref context.GetDspState(i));
effect.InitializeResultState(ref context.GetState(i));
}
effect.UpdateResultState(ref outStatus.ResultState, ref context.GetState(i));
}
int currentOutputSize = _output.Length;
OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf<EffectOutStatusVersion2>() * context.GetCount());
OutputHeader.TotalSize += OutputHeader.EffectsSize;
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
return ResultCode.Success;
}
public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
{
if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion1>() != _inputHeader.EffectsSize)
{
return ResultCode.InvalidUpdateInfo;
}
int initialOutputSize = _output.Length;
ReadOnlySpan<EffectInParameterVersion1> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion1>(_input.Slice(0, (int)_inputHeader.EffectsSize).Span);
_input = _input.Slice((int)_inputHeader.EffectsSize);
PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
for (int i = 0; i < context.GetCount(); i++)
{
EffectInParameterVersion1 parameter = parameters[i];
ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion1>(ref _output)[0];
ref BaseEffect effect = ref context.GetEffect(i);
@ -296,7 +367,7 @@ namespace Ryujinx.Audio.Renderer.Server
int currentOutputSize = _output.Length;
OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf<EffectOutStatus>() * context.GetCount());
OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf<EffectOutStatusVersion1>() * context.GetCount());
OutputHeader.TotalSize += OutputHeader.EffectsSize;
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);

View File

@ -9,7 +9,8 @@ namespace Ryujinx.Tests.Audio.Renderer
[Test]
public void EnsureTypeSize()
{
Assert.AreEqual(0xC0, Unsafe.SizeOf<EffectInParameter>());
Assert.AreEqual(0xC0, Unsafe.SizeOf<EffectInParameterVersion1>());
Assert.AreEqual(0xC0, Unsafe.SizeOf<EffectInParameterVersion2>());
}
}
}

View File

@ -9,7 +9,8 @@ namespace Ryujinx.Tests.Audio.Renderer
[Test]
public void EnsureTypeSize()
{
Assert.AreEqual(0x10, Unsafe.SizeOf<EffectOutStatus>());
Assert.AreEqual(0x10, Unsafe.SizeOf<EffectOutStatusVersion1>());
Assert.AreEqual(0x90, Unsafe.SizeOf<EffectOutStatusVersion2>());
}
}
}

View File

@ -0,0 +1,16 @@
using NUnit.Framework;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System.Runtime.CompilerServices;
namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect
{
class LimiterParameterTests
{
[Test]
public void EnsureTypeSize()
{
Assert.AreEqual(0x44, Unsafe.SizeOf<LimiterParameter>());
}
}
}

View File

@ -0,0 +1,16 @@
using NUnit.Framework;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System.Runtime.CompilerServices;
namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect
{
class LimiterStatisticsTests
{
[Test]
public void EnsureTypeSize()
{
Assert.AreEqual(0x30, Unsafe.SizeOf<LimiterStatistics>());
}
}
}