diff options
Diffstat (limited to 'Ryujinx.Audio/Renderer/Dsp')
4 files changed, 388 insertions, 1 deletions
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs index 8ff1c581..997a080e 100644 --- a/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs @@ -44,6 +44,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command Reverb3d, Performance, ClearMixBuffer, - CopyMixBuffer + CopyMixBuffer, + LimiterVersion1, + LimiterVersion2 } } diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs new file mode 100644 index 00000000..975e61f9 --- /dev/null +++ b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs @@ -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])); + } + } + } + } + } +} diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs new file mode 100644 index 00000000..0a4b14b7 --- /dev/null +++ b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs @@ -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])); + } + } + } + } + } +} diff --git a/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs b/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs new file mode 100644 index 00000000..53913bad --- /dev/null +++ b/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs @@ -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) {} + } +} |